// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('policy', function() {
/**
* A hack to check if we are displaying the mobile version of this page by
* checking if the first column is hidden.
* @return {boolean} True if this is the mobile version.
*/
var isMobilePage = function() {
return document.defaultView.getComputedStyle(document.querySelector(
'.scope-column')).display == 'none';
}
/**
* A box that shows the status of cloud policy for a device or user.
* @constructor
* @extends {HTMLFieldSetElement}
*/
var StatusBox = cr.ui.define(function() {
var node = $('status-box-template').cloneNode(true);
node.removeAttribute('id');
return node;
});
StatusBox.prototype = {
// Set up the prototype chain.
__proto__: HTMLFieldSetElement.prototype,
/**
* Initialization function for the cr.ui framework.
*/
decorate: function() {
},
/**
* Populate the box with the given cloud policy status.
* @param {string} scope The policy scope, either "device" or "user".
* @param {Object} status Dictionary with information about the status.
*/
initialize: function(scope, status) {
if (scope == 'device') {
// For device policy, set the appropriate title and populate the topmost
// status item with the domain the device is enrolled into.
this.querySelector('.legend').textContent =
loadTimeData.getString('statusDevice');
var domain = this.querySelector('.domain');
domain.textContent = status.domain;
domain.parentElement.hidden = false;
} else {
// For user policy, set the appropriate title and populate the topmost
// status item with the username that policies apply to.
this.querySelector('.legend').textContent =
loadTimeData.getString('statusUser');
// Populate the topmost item with the username.
var username = this.querySelector('.username');
username.textContent = status.username;
username.parentElement.hidden = false;
}
// Populate all remaining items.
this.querySelector('.client-id').textContent = status.clientId || '';
this.querySelector('.time-since-last-refresh').textContent =
status.timeSinceLastRefresh || '';
this.querySelector('.refresh-interval').textContent =
status.refreshInterval || '';
this.querySelector('.status').textContent = status.status || '';
},
};
/**
* A single policy's entry in the policy table.
* @constructor
* @extends {HTMLTableSectionElement}
*/
var Policy = cr.ui.define(function() {
var node = $('policy-template').cloneNode(true);
node.removeAttribute('id');
return node;
});
Policy.prototype = {
// Set up the prototype chain.
__proto__: HTMLTableSectionElement.prototype,
/**
* Initialization function for the cr.ui framework.
*/
decorate: function() {
this.updateToggleExpandedValueText_();
this.querySelector('.toggle-expanded-value').addEventListener(
'click', this.toggleExpandedValue_.bind(this));
},
/**
* Populate the table columns with information about the policy name, value
* and status.
* @param {string} name The policy name.
* @param {Object} value Dictionary with information about the policy value.
* @param {boolean} unknown Whether the policy name is not recognized.
*/
initialize: function(name, value, unknown) {
this.name = name;
this.unset = !value;
// Populate the name column.
this.querySelector('.name').textContent = name;
// Populate the remaining columns with policy scope, level and value if a
// value has been set. Otherwise, leave them blank.
if (value) {
this.querySelector('.scope').textContent =
loadTimeData.getString(value.scope == 'user' ?
'scopeUser' : 'scopeDevice');
this.querySelector('.level').textContent =
loadTimeData.getString(value.level == 'recommended' ?
'levelRecommended' : 'levelMandatory');
this.querySelector('.value').textContent = value.value;
this.querySelector('.expanded-value').textContent = value.value;
}
// Populate the status column.
var status;
if (!value) {
// If the policy value has not been set, show an error message.
status = loadTimeData.getString('unset');
} else if (unknown) {
// If the policy name is not recognized, show an error message.
status = loadTimeData.getString('unknown');
} else if (value.error) {
// If an error occurred while parsing the policy value, show the error
// message.
status = value.error;
} else {
// Otherwise, indicate that the policy value was parsed correctly.
status = loadTimeData.getString('ok');
}
this.querySelector('.status').textContent = status;
if (isMobilePage()) {
// The number of columns which are hidden by the css file for the mobile
// (Android) version of this page.
/** @const */ var HIDDEN_COLUMNS_IN_MOBILE_VERSION = 2;
var expandedValue = this.querySelector('.expanded-value');
expandedValue.setAttribute('colspan',
expandedValue.colSpan - HIDDEN_COLUMNS_IN_MOBILE_VERSION);
}
},
/**
* Check the table columns for overflow. Most columns are automatically
* elided when overflow occurs. The only action required is to add a tooltip
* that shows the complete content. The value column is an exception. If
* overflow occurs here, the contents is replaced with a link that toggles
* the visibility of an additional row containing the complete value.
*/
checkOverflow: function() {
// Set a tooltip on all overflowed columns except the value column.
var divs = this.querySelectorAll('div.elide');
for (var i = 0; i < divs.length; i++) {
var div = divs[i];
div.title = div.offsetWidth < div.scrollWidth ? div.textContent : '';
}
// Cache the width of the value column's contents when it is first shown.
// This is required to be able to check whether the contents would still
// overflow the column once it has been hidden and replaced by a link.
var valueContainer = this.querySelector('.value-container');
if (valueContainer.valueWidth == undefined) {
valueContainer.valueWidth =
valueContainer.querySelector('.value').offsetWidth;
}
// Determine whether the contents of the value column overflows. The
// visibility of the contents, replacement link and additional row
// containing the complete value that depend on this are handled by CSS.
if (valueContainer.offsetWidth < valueContainer.valueWidth)
this.classList.add('has-overflowed-value');
else
this.classList.remove('has-overflowed-value');
},
/**
* Update the text of the link that toggles the visibility of an additional
* row containing the complete policy value, depending on the toggle state.
* @private
*/
updateToggleExpandedValueText_: function(event) {
this.querySelector('.toggle-expanded-value').textContent =
loadTimeData.getString(
this.classList.contains('show-overflowed-value') ?
'hideExpandedValue' : 'showExpandedValue');
},
/**
* Toggle the visibility of an additional row containing the complete policy
* value.
* @private
*/
toggleExpandedValue_: function() {
this.classList.toggle('show-overflowed-value');
this.updateToggleExpandedValueText_();
},
};
/**
* A table of policies and their values.
* @constructor
* @extends {HTMLTableSectionElement}
*/
var PolicyTable = cr.ui.define('tbody');
PolicyTable.prototype = {
// Set up the prototype chain.
__proto__: HTMLTableSectionElement.prototype,
/**
* Initialization function for the cr.ui framework.
*/
decorate: function() {
this.policies_ = {};
this.filterPattern_ = '';
window.addEventListener('resize', this.checkOverflow_.bind(this));
},
/**
* Initialize the list of all known policies.
* @param {Object} names Dictionary containing all known policy names.
*/
setPolicyNames: function(names) {
this.policies_ = names;
this.setPolicyValues({});
},
/**
* Populate the table with the currently set policy values and any errors
* detected while parsing these.
* @param {Object} values Dictionary containing the current policy values.
*/
setPolicyValues: function(values) {
// Remove all policies from the table.
var policies = this.getElementsByTagName('tbody');
while (policies.length > 0)
this.removeChild(policies.item(0));
// First, add known policies whose value is currently set.
var unset = [];
for (var name in this.policies_) {
if (name in values)
this.setPolicyValue_(name, values[name], false);
else
unset.push(name);
}
// Second, add policies whose value is currently set but whose name is not
// recognized.
for (var name in values) {
if (!(name in this.policies_))
this.setPolicyValue_(name, values[name], true);
}
// Finally, add known policies whose value is not currently set.
for (var i = 0; i < unset.length; i++)
this.setPolicyValue_(unset[i], undefined, false);
// Filter the policies.
this.filter();
},
/**
* Set the filter pattern. Only policies whose name contains |pattern| are
* shown in the policy table. The filter is case insensitive. It can be
* disabled by setting |pattern| to an empty string.
* @param {string} pattern The filter pattern.
*/
setFilterPattern: function(pattern) {
this.filterPattern_ = pattern.toLowerCase();
this.filter();
},
/**
* Filter policies. Only policies whose name contains the filter pattern are
* shown in the table. Furthermore, policies whose value is not currently
* set are only shown if the corresponding checkbox is checked.
*/
filter: function() {
var showUnset = $('show-unset').checked;
var policies = this.getElementsByTagName('tbody');
for (var i = 0; i < policies.length; i++) {
var policy = policies[i];
policy.hidden =
policy.unset && !showUnset ||
policy.name.toLowerCase().indexOf(this.filterPattern_) == -1;
}
if (this.querySelector('tbody:not([hidden])'))
this.parentElement.classList.remove('empty');
else
this.parentElement.classList.add('empty');
setTimeout(this.checkOverflow_.bind(this), 0);
},
/**
* Check the table columns for overflow.
* @private
*/
checkOverflow_: function() {
var policies = this.getElementsByTagName('tbody');
for (var i = 0; i < policies.length; i++) {
if (!policies[i].hidden)
policies[i].checkOverflow();
}
},
/**
* Add a policy with the given |name| and |value| to the table.
* @param {string} name The policy name.
* @param {Object} value Dictionary with information about the policy value.
* @param {boolean} unknown Whether the policy name is not recoginzed.
* @private
*/
setPolicyValue_: function(name, value, unknown) {
var policy = new Policy;
policy.initialize(name, value, unknown);
this.appendChild(policy);
},
};
/**
* A singelton object that handles communication between browser and WebUI.
* @constructor
*/
function Page() {
}
// Make Page a singleton.
cr.addSingletonGetter(Page);
/**
* Provide a list of all known policies to the UI. Called by the browser on
* page load.
* @param {Object} names Dictionary containing all known policy names.
*/
Page.setPolicyNames = function(names) {
var page = this.getInstance();
// Clear all policy tables.
page.mainSection.innerHTML = '';
page.policyTables = {};
// Create tables and set known policy names for Chrome and extensions.
if (names.hasOwnProperty('chromePolicyNames')) {
var table = page.appendNewTable('chrome', 'Chrome policies', '');
table.setPolicyNames(names.chromePolicyNames);
}
if (names.hasOwnProperty('extensionPolicyNames')) {
for (var ext in names.extensionPolicyNames) {
var table = page.appendNewTable('extension-' + ext,
names.extensionPolicyNames[ext].name, 'ID: ' + ext);
table.setPolicyNames(names.extensionPolicyNames[ext].policyNames);
}
}
};
/**
* Provide a list of the currently set policy values and any errors detected
* while parsing these to the UI. Called by the browser on page load and
* whenever policy values change.
* @param {Object} values Dictionary containing the current policy values.
*/
Page.setPolicyValues = function(values) {
var page = this.getInstance();
if (values.hasOwnProperty('chromePolicies')) {
var table = page.policyTables['chrome'];
table.setPolicyValues(values.chromePolicies);
}
if (values.hasOwnProperty('extensionPolicies')) {
for (var extensionId in values.extensionPolicies) {
var table = page.policyTables['extension-' + extensionId];
if (table)
table.setPolicyValues(values.extensionPolicies[extensionId]);
}
}
};
/**
* Provide the current cloud policy status to the UI. Called by the browser on
* page load if cloud policy is present and whenever the status changes.
* @param {Object} status Dictionary containing the current policy status.
*/
Page.setStatus = function(status) {
this.getInstance().setStatus(status);
};
/**
* Notify the UI that a request to reload policy values has completed. Called
* by the browser after a request to reload policy has been sent by the UI.
*/
Page.reloadPoliciesDone = function() {
this.getInstance().reloadPoliciesDone();
};
Page.prototype = {
/**
* Main initialization function. Called by the browser on page load.
*/
initialize: function() {
uber.onContentFrameLoaded();
cr.ui.FocusOutlineManager.forDocument(document);
this.mainSection = $('main-section');
this.policyTables = {};
// Place the initial focus on the filter input field.
$('filter').focus();
var self = this;
$('filter').onsearch = function(event) {
for (policyTable in self.policyTables) {
self.policyTables[policyTable].setFilterPattern(this.value);
}
};
$('reload-policies').onclick = function(event) {
this.disabled = true;
chrome.send('reloadPolicies');
};
$('show-unset').onchange = function() {
for (policyTable in self.policyTables) {
self.policyTables[policyTable].filter();
}
};
// Notify the browser that the page has loaded, causing it to send the
// list of all known policies, the current policy values and the cloud
// policy status.
chrome.send('initialized');
},
/**
* Creates a new policy table section, adds the section to the page,
* and returns the new table from that section.
* @param {string} id The key for storing the new table in policyTables.
* @param {string} label_title Title for this policy table.
* @param {string} label_content Description for the policy table.
* @return {Element} The newly created table.
*/
appendNewTable: function(id, label_title, label_content) {
var newSection = this.createPolicyTableSection(id, label_title,
label_content);
this.mainSection.appendChild(newSection);
return this.policyTables[id];
},
/**
* Creates a new section containing a title, description and table of
* policies.
* @param {id} id The key for storing the new table in policyTables.
* @param {string} label_title Title for this policy table.
* @param {string} label_content Description for the policy table.
* @return {Element} The newly created section.
*/
createPolicyTableSection: function(id, label_title, label_content) {
var section = document.createElement('section');
section.setAttribute('class', 'policy-table-section');
// Add title and description.
var title = window.document.createElement('h3');
title.textContent = label_title;
section.appendChild(title);
if (label_content) {
var description = window.document.createElement('div');
description.classList.add('table-description');
description.textContent = label_content;
section.appendChild(description);
}
// Add 'No Policies Set' element.
var noPolicies = window.document.createElement('div');
noPolicies.classList.add('no-policies-set');
noPolicies.textContent = loadTimeData.getString('noPoliciesSet');
section.appendChild(noPolicies);
// Add table of policies.
var newTable = this.createPolicyTable();
this.policyTables[id] = newTable;
section.appendChild(newTable);
return section;
},
/**
* Creates a new table for displaying policies.
* @return {Element} The newly created table.
*/
createPolicyTable: function() {
var newTable = window.document.createElement('table');
var tableHead = window.document.createElement('thead');
var tableRow = window.document.createElement('tr');
var tableHeadings = ['Scope', 'Level', 'Name', 'Value', 'Status'];
for (var i = 0; i < tableHeadings.length; i++) {
var tableHeader = window.document.createElement('th');
tableHeader.classList.add(tableHeadings[i].toLowerCase() + '-column');
tableHeader.textContent = loadTimeData.getString('header' +
tableHeadings[i]);
tableRow.appendChild(tableHeader);
}
tableHead.appendChild(tableRow);
newTable.appendChild(tableHead);
cr.ui.decorate(newTable, PolicyTable);
return newTable;
},
/**
* Update the status section of the page to show the current cloud policy
* status.
* @param {Object} status Dictionary containing the current policy status.
*/
setStatus: function(status) {
// Remove any existing status boxes.
var container = $('status-box-container');
while (container.firstChild)
container.removeChild(container.firstChild);
// Hide the status section.
var section = $('status-section');
section.hidden = true;
// Add a status box for each scope that has a cloud policy status.
for (var scope in status) {
var box = new StatusBox;
box.initialize(scope, status[scope]);
container.appendChild(box);
// Show the status section.
section.hidden = false;
}
},
/**
* Re-enable the reload policies button when the previous request to reload
* policies values has completed.
*/
reloadPoliciesDone: function() {
$('reload-policies').disabled = false;
},
};
return {
Page: Page
};
});
// Have the main initialization function be called when the page finishes
// loading.
document.addEventListener(
'DOMContentLoaded',
policy.Page.getInstance().initialize.bind(policy.Page.getInstance()));