Chromium Code Reviews| Index: chrome/browser/resources/policy.js |
| diff --git a/chrome/browser/resources/policy.js b/chrome/browser/resources/policy.js |
| index 064a6c5fc774f39a5d6f218ad742fdb4aac46cc4..e3f180e1f927b331deb962966b2f62e2e242890e 100644 |
| --- a/chrome/browser/resources/policy.js |
| +++ b/chrome/browser/resources/policy.js |
| @@ -1,277 +1,422 @@ |
| -// Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| +// 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. |
| -/** |
| - * This variable structure is here to document the structure that the template |
| - * expects to correctly populate the page. |
| - */ |
| -var policyDataFormat = { |
| - // Whether any of the policies in 'policies' have a value. |
| - 'anyPoliciesSet': true, |
| - |
| - 'policies': [ |
| - { |
| - 'level': 'managed', |
| - 'name': 'AllowXYZ', |
| - 'set': true, |
| - 'scope': 'Machine', |
| - 'status': 'ok', |
| - 'value': true |
| - } |
| - ], |
| - 'status': { |
| - 'deviceFetchInterval': '8min', |
| - 'deviceId': 'D2AC39A2-3C8FC-E2C0-E45D2DC3782C', |
| - 'deviceLastFetchTime': '9:50 PM', |
| - 'devicePolicyDomain': 'google.com', |
| - 'deviceStatusMessage': 'OK', |
| - 'displayDeviceStatus': true, |
| - 'displayStatusSection': true, |
| - 'displayUserStatus': true, |
| - 'user': 'simo@google.com', |
| - 'userFetchInterval': '8min', |
| - 'userId': 'D2AC39A2-3C8FC-E2C0-E45D2DC3782C', |
| - 'userLastFetchTime': '9:50 PM', |
| - 'userStatusMessage': 'OK' |
| - } |
| -}; |
| - |
| -cr.define('policies', function() { |
| +cr.define('policy', function() { |
| + /** |
| + * 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; |
| + }); |
| - function Policy() { |
| - } |
| + StatusBox.prototype = { |
| + // Set up the prototype chain. |
| + __proto__: HTMLFieldSetElement.prototype, |
| - cr.addSingletonGetter(Policy); |
| + /** |
| + * Initialization function for the cr.ui framework. |
| + */ |
| + decorate: function() { |
| + }, |
| - Policy.prototype = { |
| /** |
| - * True if none of the received policies are actually set, false otherwise. |
| - * @type {boolean} |
| + * 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. |
| */ |
| - noActivePolicies_: false, |
| + 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, |
| /** |
| - * The current search term for filtering of the policy table. |
| - * @type {string} |
| - * @private |
| + * Initialization function for the cr.ui framework. |
| */ |
| - searchTerm_: '', |
| + decorate: function() { |
| + this.updateToggleExpandedValueText_(); |
| + this.querySelector('.toggle-expanded-value').addEventListener( |
| + 'click', this.toggleExpandedValue_.bind(this)); |
| + }, |
| /** |
| - * Takes the |policyData| argument and populates the page with this data. It |
| - * expects an object structure like the policyDataFormat above. |
| - * @param {Object} policyData Detailed info about policies. |
| + * 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. |
| */ |
| - renderTemplate: function(policyData) { |
| - this.noActivePolicies_ = !policyData.anyPoliciesSet; |
| - |
| - if (this.noActivePolicies_) |
| - $('no-policies').hidden = false; |
| - if (policyData.status.displayStatusSection) |
| - $('status-section').hidden = false; |
| - |
| - // This is the javascript code that processes the template: |
| - var input = new JsEvalContext(policyData); |
| - var output = $('data-template'); |
| - jstProcess(input, output); |
| - |
| - var toggles = document.querySelectorAll('.policy-set * .toggler'); |
| - for (var i = 0; i < toggles.length; i++) { |
| - toggles[i].hidden = true; |
| - toggles[i].onclick = function() { |
| - Policy.getInstance().toggleCellExpand_(this); |
| - }; |
| + 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; |
| } |
| - var containers = document.querySelectorAll('.text-container'); |
| - for (var i = 0; i < containers.length; i++) |
| - this.initTextContainer_(containers[i]); |
| + // 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; |
| }, |
| /** |
| - * Filters the table of policies by name. |
| - * @param {string} term The search string. |
| + * 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. |
| */ |
| - filterTable: function(term) { |
| - this.searchTerm_ = term.toLowerCase(); |
| - var table = $('policy-table'); |
| - var showUnsent = $('toggle-unsent-policies').checked; |
| - for (var r = 1; r < table.rows.length; r++) { |
| - var row = table.rows[r]; |
| - |
| - // Don't change visibility of policies that aren't set if the checkbox |
| - // isn't checked. |
| - if (!showUnsent && row.className == 'policy-unset') |
| - continue; |
| - |
| - var nameCell = row.querySelector('.policy-name'); |
| - var cellContents = nameCell.textContent; |
| - row.hidden = |
| - !(cellContents.toLowerCase().indexOf(this.searchTerm_) >= 0); |
| + 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) { |
|
James Hawkins
2013/01/31 17:08:05
nit: i++
Here and elsewhere.
bartfab (slow)
2013/01/31 17:36:13
Done.
|
| + 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. |
| + this.classList.toggle( |
| + 'has-overflowed-value', |
| + valueContainer.offsetWidth < valueContainer.valueWidth); |
| }, |
| /** |
| - * Updates the visibility of the policies depending on the state of the |
| - * 'toggle-unsent-policies' checkbox. |
| + * 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 |
| */ |
| - updatePolicyVisibility: function() { |
| - if ($('toggle-unsent-policies').checked) |
| - $('policies').style.display = ''; |
| - else if (this.noActivePolicies_) |
| - $('policies').style.display = 'none'; |
| - |
| - var tableRows = document.getElementsByClassName('policy-unset'); |
| - for (var i = 0; i < tableRows.length; i++) |
| - tableRows[i].hidden = !($('toggle-unsent-policies').checked); |
| - |
| - // Filter table again in case a search was active. |
| - this.filterTable(this.searchTerm_); |
| + updateToggleExpandedValueText_: function(event) { |
| + this.querySelector('.toggle-expanded-value').textContent = |
| + loadTimeData.getString( |
| + this.classList.contains('show-overflowed-value') ? |
| + 'hideExpandedValue' : 'showExpandedValue'); |
| }, |
| /** |
| - * Expands or collapses a table cell that has overflowing text. |
| - * @param {Object} toggler The toggler that was clicked on. |
| + * Toggle the visibility of an additional row containing the complete policy |
| + * value. |
| * @private |
| */ |
| - toggleCellExpand_: function(toggler) { |
| - var textContainer = toggler.parentElement; |
| - textContainer.collapsed = !textContainer.collapsed; |
| - |
| - if (textContainer.collapsed) |
| - this.collapseCell_(textContainer); |
| - else |
| - this.expandCell_(textContainer); |
| + 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, |
| /** |
| - * Collapses all expanded table cells and updates the visibility of the |
| - * toggles accordingly. Should be called before the policy information in |
| - * the table is updated. |
| + * Initialization function for the cr.ui framework. |
| */ |
| - collapseExpandedCells: function() { |
| - var textContainers = document.querySelectorAll('.text-expanded'); |
| - for (var i = 0; i < textContainers.length; i++) |
| - this.collapseCell_(textContainers[i]); |
| + decorate: function() { |
| + this.policies_ = {}; |
| + this.filterPattern_ = ''; |
| + window.addEventListener('resize', this.checkOverflow_.bind(this)); |
| }, |
| /** |
| - * Expands a table cell so that all the text it contains is visible. |
| - * @param {Object} textContainer The cell's div element that contains the |
| - * text. |
| - * @private |
| + * Initialize the list of all known policies. |
| + * @param {Object} names Dictionary containing all known policy names. |
| */ |
| - expandCell_: function(textContainer) { |
| - textContainer.classList.remove('text-collapsed'); |
| - textContainer.classList.add('text-expanded'); |
| - textContainer.querySelector('.expand').hidden = true; |
| - textContainer.querySelector('.collapse').hidden = false; |
| + setPolicyNames: function(names) { |
| + this.policies_ = names; |
| + this.setPolicyValues({}); |
| }, |
| /** |
| - * Collapses a table cell so that overflowing text is hidden. |
| - * @param {Object} textContainer The cell's div element that contains the |
| - * text. |
| - * @private |
| + * 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. |
| */ |
| - collapseCell_: function(textContainer) { |
| - textContainer.classList.remove('text-expanded'); |
| - textContainer.classList.add('text-collapsed'); |
| - textContainer.querySelector('.expand').hidden = false; |
| - textContainer.querySelector('.collapse').hidden = true; |
| + 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; |
| + } |
| + this.parentElement.classList.toggle( |
| + 'empty', !this.querySelector('tbody:not([hidden])')); |
| + setTimeout(this.checkOverflow_.bind(this), 0); |
| }, |
| /** |
| - * Initializes a text container, showing the expand toggle if necessary. |
| - * @param {Object} textContainer The text container element. |
| + * Check the table columns for overflow. |
| + * @private |
| */ |
| - initTextContainer_: function(textContainer) { |
| - textContainer.collapsed = true; |
| - var textValue = textContainer.querySelector('.text-value'); |
| - |
| - // If the text is wider than the text container, the expand toggler should |
| - // appear. |
| - if (textContainer.offsetWidth < textValue.offsetWidth || |
| - textContainer.offsetHeight < textValue.offsetHeight) { |
| - this.collapseCell_(textContainer); |
| + 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); |
| + }, |
| }; |
| /** |
| - * Asks the C++ PolicyUIHandler to get details about policies and status |
| - * information. The PolicyUIHandler should reply to returnData() (below). |
| + * A singelton object that handles communication between browser and WebUI. |
| + * @constructor |
| */ |
| - Policy.requestData = function() { |
| - chrome.send('requestData'); |
| - }; |
| + function Page() { |
| + } |
| + |
| + // Make Page a singleton. |
| + cr.addSingletonGetter(Page); |
| /** |
| - * Called by the C++ PolicyUIHandler when it has the requested data. |
| - * @param {Object} policyData The policy information in the format described |
| - * by the policyDataFormat. |
| + * 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. |
| */ |
| - Policy.returnData = function(policyData) { |
| - var policy = Policy.getInstance(); |
| - policy.collapseExpandedCells(); |
| - policy.renderTemplate(policyData); |
| - policy.updatePolicyVisibility(); |
| + Page.setPolicyNames = function(names) { |
| + this.getInstance().policyTable.setPolicyNames(names); |
| }; |
| /** |
| - * Called by the C++ PolicyUIHandler when a requested policy refresh has |
| - * completed. |
| + * 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. |
| */ |
| - Policy.refreshDone = function() { |
| - $('fetch-policies-button').disabled = false; |
| + Page.setPolicyValues = function(values) { |
| + this.getInstance().policyTable.setPolicyValues(values); |
| }; |
| /** |
| - * Asks the C++ PolicyUIHandler to re-fetch policy information. |
| + * 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. |
| */ |
| - Policy.triggerPolicyFetch = function() { |
| - chrome.send('fetchPolicy'); |
| + Page.setStatus = function(status) { |
| + this.getInstance().setStatus(status); |
| }; |
| /** |
| - * Determines whether a policy should be visible or not. |
| - * @param {Object} policy An entry in the 'policies' array given by the above |
| - * PolicyDataFormat. |
| + * 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. |
| */ |
| - Policy.shouldDisplayPolicy = function(policy) { |
| - return $('toggle-unsent-policies').checked || policy.set; |
| + Page.reloadPoliciesDone = function() { |
| + this.getInstance().reloadPoliciesDone(); |
| }; |
| - /** |
| - * Initializes the page and loads the list of policies and the policy |
| - * status data. |
| - */ |
| - Policy.initialize = function() { |
| - Policy.requestData(); |
| - |
| - // Set HTML event handlers. |
| - $('fetch-policies-button').onclick = function(event) { |
| - this.disabled = true; |
| - Policy.triggerPolicyFetch(); |
| - }; |
| - |
| - $('toggle-unsent-policies').onchange = function(event) { |
| - Policy.getInstance().updatePolicyVisibility(); |
| - }; |
| - |
| - $('search-field').onsearch = function(event) { |
| - Policy.getInstance().filterTable(this.value); |
| - }; |
| + Page.prototype = { |
| + /** |
| + * Main initialization function. Called by the browser on page load. |
| + */ |
| + initialize: function() { |
| + uber.onContentFrameLoaded(); |
| + this.policyTable = $('policy-table'); |
| + cr.ui.decorate(this.policyTable, PolicyTable); |
| + |
| + // Place the initial focus on the filter input field. |
| + $('filter').focus(); |
| + |
| + var self = this; |
| + $('filter').onsearch = function(event) { |
| + self.policyTable.setFilterPattern(this.value); |
| + }; |
| + $('reload-policies').onclick = function(event) { |
| + this.disabled = true; |
| + chrome.send('reloadPolicies'); |
| + }; |
| + $('show-unset').onchange = this.policyTable.filter.bind(this.policyTable); |
| + |
| + // 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'); |
| + }, |
| + |
| + /** |
| + * 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.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; |
| + }, |
| }; |
| - // Export |
| return { |
| - Policy: Policy |
| + Page: Page |
| }; |
| }); |
| -var Policy = policies.Policy; |
| - |
| -// Get data and have it displayed upon loading. |
| -document.addEventListener('DOMContentLoaded', policies.Policy.initialize); |
| +// Have the main initialization function be called when the page finishes |
| +// loading. |
| +document.addEventListener( |
| + 'DOMContentLoaded', |
| + policy.Page.getInstance().initialize.bind(policy.Page.getInstance())); |