Elim cr-checkbox
[chromium-blink-merge.git] / chrome / browser / resources / policy.js
blobaaba4ff6e212501a0e9b9db32b1fd6648c9536bc
1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 cr.define('policy', function() {
6   /**
7    * A hack to check if we are displaying the mobile version of this page by
8    * checking if the first column is hidden.
9    * @return {boolean} True if this is the mobile version.
10    */
11   var isMobilePage = function() {
12     return document.defaultView.getComputedStyle(document.querySelector(
13         '.scope-column')).display == 'none';
14   };
16   /**
17    * A box that shows the status of cloud policy for a device or user.
18    * @constructor
19    * @extends {HTMLFieldSetElement}
20    */
21   var StatusBox = cr.ui.define(function() {
22     var node = $('status-box-template').cloneNode(true);
23     node.removeAttribute('id');
24     return node;
25   });
27   StatusBox.prototype = {
28     // Set up the prototype chain.
29     __proto__: HTMLFieldSetElement.prototype,
31     /**
32      * Initialization function for the cr.ui framework.
33      */
34     decorate: function() {
35     },
37     /**
38      * Populate the box with the given cloud policy status.
39      * @param {string} scope The policy scope, either "device" or "user".
40      * @param {Object} status Dictionary with information about the status.
41      */
42     initialize: function(scope, status) {
43       if (scope == 'device') {
44         // For device policy, set the appropriate title and populate the topmost
45         // status item with the domain the device is enrolled into.
46         this.querySelector('.legend').textContent =
47             loadTimeData.getString('statusDevice');
48         var domain = this.querySelector('.domain');
49         domain.textContent = status.domain;
50         domain.parentElement.hidden = false;
52         // Populate the device naming information.
53         // Populate the asset identifier.
54         var assetId = this.querySelector('.asset-id');
55         assetId.textContent = status.assetId ||
56             loadTimeData.getString('notSpecified');
57         assetId.parentElement.hidden = false;
59         // Populate the device location.
60         var location = this.querySelector('.location');
61         location.textContent = status.location ||
62             loadTimeData.getString('notSpecified');
63         location.parentElement.hidden = false;
65         // Populate the directory API ID.
66         var directoryApiId = this.querySelector('.directory-api-id');
67         directoryApiId.textContent = status.directoryApiId ||
68             loadTimeData.getString('notSpecified');
69         directoryApiId.parentElement.hidden = false;
70       } else {
71         // For user policy, set the appropriate title and populate the topmost
72         // status item with the username that policies apply to.
73         this.querySelector('.legend').textContent =
74             loadTimeData.getString('statusUser');
75         // Populate the topmost item with the username.
76         var username = this.querySelector('.username');
77         username.textContent = status.username;
78         username.parentElement.hidden = false;
79       }
80       // Populate all remaining items.
81       this.querySelector('.client-id').textContent = status.clientId || '';
82       this.querySelector('.time-since-last-refresh').textContent =
83           status.timeSinceLastRefresh || '';
84       this.querySelector('.refresh-interval').textContent =
85           status.refreshInterval || '';
86       this.querySelector('.status').textContent = status.status || '';
87     },
88   };
90   /**
91    * A single policy's entry in the policy table.
92    * @constructor
93    * @extends {HTMLTableSectionElement}
94    */
95   var Policy = cr.ui.define(function() {
96     var node = $('policy-template').cloneNode(true);
97     node.removeAttribute('id');
98     return node;
99   });
101   Policy.prototype = {
102     // Set up the prototype chain.
103     __proto__: HTMLTableSectionElement.prototype,
105     /**
106      * Initialization function for the cr.ui framework.
107      */
108     decorate: function() {
109       this.updateToggleExpandedValueText_();
110       this.querySelector('.toggle-expanded-value').addEventListener(
111           'click', this.toggleExpandedValue_.bind(this));
112     },
114     /**
115      * Populate the table columns with information about the policy name, value
116      * and status.
117      * @param {string} name The policy name.
118      * @param {Object} value Dictionary with information about the policy value.
119      * @param {boolean} unknown Whether the policy name is not recognized.
120      */
121     initialize: function(name, value, unknown) {
122       this.name = name;
123       this.unset = !value;
125       // Populate the name column.
126       this.querySelector('.name').textContent = name;
128       // Populate the remaining columns with policy scope, level and value if a
129       // value has been set. Otherwise, leave them blank.
130       if (value) {
131         this.querySelector('.scope').textContent =
132             loadTimeData.getString(value.scope == 'user' ?
133                 'scopeUser' : 'scopeDevice');
134         this.querySelector('.level').textContent =
135             loadTimeData.getString(value.level == 'recommended' ?
136                 'levelRecommended' : 'levelMandatory');
137         this.querySelector('.source').textContent =
138             loadTimeData.getString(value.source);
139         this.querySelector('.value').textContent = value.value;
140         this.querySelector('.expanded-value').textContent = value.value;
141       }
143       // Populate the status column.
144       var status;
145       if (!value) {
146         // If the policy value has not been set, show an error message.
147         status = loadTimeData.getString('unset');
148       } else if (unknown) {
149         // If the policy name is not recognized, show an error message.
150         status = loadTimeData.getString('unknown');
151       } else if (value.error) {
152         // If an error occurred while parsing the policy value, show the error
153         // message.
154         status = value.error;
155       } else {
156         // Otherwise, indicate that the policy value was parsed correctly.
157         status = loadTimeData.getString('ok');
158       }
159       this.querySelector('.status').textContent = status;
161       if (isMobilePage()) {
162         // The number of columns which are hidden by the css file for the mobile
163         // (Android) version of this page.
164         /** @const */ var HIDDEN_COLUMNS_IN_MOBILE_VERSION = 2;
166         var expandedValue = this.querySelector('.expanded-value');
167         expandedValue.setAttribute('colspan',
168             expandedValue.colSpan - HIDDEN_COLUMNS_IN_MOBILE_VERSION);
169       }
170     },
172     /**
173      * Check the table columns for overflow. Most columns are automatically
174      * elided when overflow occurs. The only action required is to add a tooltip
175      * that shows the complete content. The value column is an exception. If
176      * overflow occurs here, the contents is replaced with a link that toggles
177      * the visibility of an additional row containing the complete value.
178      */
179     checkOverflow: function() {
180       // Set a tooltip on all overflowed columns except the value column.
181       var divs = this.querySelectorAll('div.elide');
182       for (var i = 0; i < divs.length; i++) {
183         var div = divs[i];
184         div.title = div.offsetWidth < div.scrollWidth ? div.textContent : '';
185       }
187       // Cache the width of the value column's contents when it is first shown.
188       // This is required to be able to check whether the contents would still
189       // overflow the column once it has been hidden and replaced by a link.
190       var valueContainer = this.querySelector('.value-container');
191       if (valueContainer.valueWidth == undefined) {
192         valueContainer.valueWidth =
193             valueContainer.querySelector('.value').offsetWidth;
194       }
196       // Determine whether the contents of the value column overflows. The
197       // visibility of the contents, replacement link and additional row
198       // containing the complete value that depend on this are handled by CSS.
199       if (valueContainer.offsetWidth < valueContainer.valueWidth)
200         this.classList.add('has-overflowed-value');
201       else
202         this.classList.remove('has-overflowed-value');
203     },
205     /**
206      * Update the text of the link that toggles the visibility of an additional
207      * row containing the complete policy value, depending on the toggle state.
208      * @private
209      */
210     updateToggleExpandedValueText_: function(event) {
211       this.querySelector('.toggle-expanded-value').textContent =
212           loadTimeData.getString(
213               this.classList.contains('show-overflowed-value') ?
214                   'hideExpandedValue' : 'showExpandedValue');
215     },
217     /**
218      * Toggle the visibility of an additional row containing the complete policy
219      * value.
220      * @private
221      */
222     toggleExpandedValue_: function() {
223       this.classList.toggle('show-overflowed-value');
224       this.updateToggleExpandedValueText_();
225     },
226   };
228   /**
229    * A table of policies and their values.
230    * @constructor
231    * @extends {HTMLTableElement}
232    */
233   var PolicyTable = cr.ui.define('tbody');
235   PolicyTable.prototype = {
236     // Set up the prototype chain.
237     __proto__: HTMLTableElement.prototype,
239     /**
240      * Initialization function for the cr.ui framework.
241      */
242     decorate: function() {
243       this.policies_ = {};
244       this.filterPattern_ = '';
245       window.addEventListener('resize', this.checkOverflow_.bind(this));
246     },
248     /**
249      * Initialize the list of all known policies.
250      * @param {Object} names Dictionary containing all known policy names.
251      */
252     setPolicyNames: function(names) {
253       this.policies_ = names;
254       this.setPolicyValues({});
255     },
257     /**
258      * Populate the table with the currently set policy values and any errors
259      * detected while parsing these.
260      * @param {Object} values Dictionary containing the current policy values.
261      */
262     setPolicyValues: function(values) {
263       // Remove all policies from the table.
264       var policies = this.getElementsByTagName('tbody');
265       while (policies.length > 0)
266         this.removeChild(policies.item(0));
268       // First, add known policies whose value is currently set.
269       var unset = [];
270       for (var name in this.policies_) {
271         if (name in values)
272           this.setPolicyValue_(name, values[name], false);
273         else
274           unset.push(name);
275       }
277       // Second, add policies whose value is currently set but whose name is not
278       // recognized.
279       for (var name in values) {
280         if (!(name in this.policies_))
281           this.setPolicyValue_(name, values[name], true);
282       }
284       // Finally, add known policies whose value is not currently set.
285       for (var i = 0; i < unset.length; i++)
286         this.setPolicyValue_(unset[i], undefined, false);
288       // Filter the policies.
289       this.filter();
290     },
292     /**
293      * Set the filter pattern. Only policies whose name contains |pattern| are
294      * shown in the policy table. The filter is case insensitive. It can be
295      * disabled by setting |pattern| to an empty string.
296      * @param {string} pattern The filter pattern.
297      */
298     setFilterPattern: function(pattern) {
299       this.filterPattern_ = pattern.toLowerCase();
300       this.filter();
301     },
303     /**
304      * Filter policies. Only policies whose name contains the filter pattern are
305      * shown in the table. Furthermore, policies whose value is not currently
306      * set are only shown if the corresponding checkbox is checked.
307      */
308     filter: function() {
309       var showUnset = $('show-unset').checked;
310       var policies = this.getElementsByTagName('tbody');
311       for (var i = 0; i < policies.length; i++) {
312         var policy = policies[i];
313         policy.hidden =
314             policy.unset && !showUnset ||
315             policy.name.toLowerCase().indexOf(this.filterPattern_) == -1;
316       }
317       if (this.querySelector('tbody:not([hidden])'))
318         this.parentElement.classList.remove('empty');
319       else
320         this.parentElement.classList.add('empty');
321       setTimeout(this.checkOverflow_.bind(this), 0);
322     },
324     /**
325      * Check the table columns for overflow.
326      * @private
327      */
328     checkOverflow_: function() {
329       var policies = this.getElementsByTagName('tbody');
330       for (var i = 0; i < policies.length; i++) {
331         if (!policies[i].hidden)
332           policies[i].checkOverflow();
333       }
334     },
336     /**
337      * Add a policy with the given |name| and |value| to the table.
338      * @param {string} name The policy name.
339      * @param {Object} value Dictionary with information about the policy value.
340      * @param {boolean} unknown Whether the policy name is not recoginzed.
341      * @private
342      */
343     setPolicyValue_: function(name, value, unknown) {
344       var policy = new Policy;
345       policy.initialize(name, value, unknown);
346       this.appendChild(policy);
347     },
348   };
350   /**
351    * A singelton object that handles communication between browser and WebUI.
352    * @constructor
353    */
354   function Page() {
355   }
357   // Make Page a singleton.
358   cr.addSingletonGetter(Page);
360   /**
361    * Provide a list of all known policies to the UI. Called by the browser on
362    * page load.
363    * @param {Object} names Dictionary containing all known policy names.
364    */
365   Page.setPolicyNames = function(names) {
366     var page = this.getInstance();
368     // Clear all policy tables.
369     page.mainSection.innerHTML = '';
370     page.policyTables = {};
372     // Create tables and set known policy names for Chrome and extensions.
373     if (names.hasOwnProperty('chromePolicyNames')) {
374       var table = page.appendNewTable('chrome', 'Chrome policies', '');
375       table.setPolicyNames(names.chromePolicyNames);
376     }
378     if (names.hasOwnProperty('extensionPolicyNames')) {
379       for (var ext in names.extensionPolicyNames) {
380         var table = page.appendNewTable('extension-' + ext,
381             names.extensionPolicyNames[ext].name, 'ID: ' + ext);
382         table.setPolicyNames(names.extensionPolicyNames[ext].policyNames);
383       }
384     }
385   };
387   /**
388    * Provide a list of the currently set policy values and any errors detected
389    * while parsing these to the UI. Called by the browser on page load and
390    * whenever policy values change.
391    * @param {Object} values Dictionary containing the current policy values.
392    */
393   Page.setPolicyValues = function(values) {
394     var page = this.getInstance();
395     if (values.hasOwnProperty('chromePolicies')) {
396       var table = page.policyTables['chrome'];
397       table.setPolicyValues(values.chromePolicies);
398     }
400     if (values.hasOwnProperty('extensionPolicies')) {
401       for (var extensionId in values.extensionPolicies) {
402         var table = page.policyTables['extension-' + extensionId];
403         if (table)
404           table.setPolicyValues(values.extensionPolicies[extensionId]);
405       }
406     }
407   };
409   /**
410    * Provide the current cloud policy status to the UI. Called by the browser on
411    * page load if cloud policy is present and whenever the status changes.
412    * @param {Object} status Dictionary containing the current policy status.
413    */
414   Page.setStatus = function(status) {
415     this.getInstance().setStatus(status);
416   };
418   /**
419    * Notify the UI that a request to reload policy values has completed. Called
420    * by the browser after a request to reload policy has been sent by the UI.
421    */
422   Page.reloadPoliciesDone = function() {
423     this.getInstance().reloadPoliciesDone();
424   };
426   Page.prototype = {
427     /**
428      * Main initialization function. Called by the browser on page load.
429      */
430     initialize: function() {
431       uber.onContentFrameLoaded();
432       cr.ui.FocusOutlineManager.forDocument(document);
434       this.mainSection = $('main-section');
435       this.policyTables = {};
437       // Place the initial focus on the filter input field.
438       $('filter').focus();
440       var self = this;
441       $('filter').onsearch = function(event) {
442         for (policyTable in self.policyTables) {
443           self.policyTables[policyTable].setFilterPattern(this.value);
444         }
445       };
446       $('reload-policies').onclick = function(event) {
447         this.disabled = true;
448         chrome.send('reloadPolicies');
449       };
451       $('show-unset').onchange = function() {
452         for (policyTable in self.policyTables) {
453           self.policyTables[policyTable].filter();
454         }
455       };
457       // Notify the browser that the page has loaded, causing it to send the
458       // list of all known policies, the current policy values and the cloud
459       // policy status.
460       chrome.send('initialized');
461     },
463    /**
464      * Creates a new policy table section, adds the section to the page,
465      * and returns the new table from that section.
466      * @param {string} id The key for storing the new table in policyTables.
467      * @param {string} label_title Title for this policy table.
468      * @param {string} label_content Description for the policy table.
469      * @return {Element} The newly created table.
470      */
471     appendNewTable: function(id, label_title, label_content) {
472       var newSection = this.createPolicyTableSection(id, label_title,
473           label_content);
474       this.mainSection.appendChild(newSection);
475       return this.policyTables[id];
476     },
478     /**
479      * Creates a new section containing a title, description and table of
480      * policies.
481      * @param {id} id The key for storing the new table in policyTables.
482      * @param {string} label_title Title for this policy table.
483      * @param {string} label_content Description for the policy table.
484      * @return {Element} The newly created section.
485      */
486     createPolicyTableSection: function(id, label_title, label_content) {
487       var section = document.createElement('section');
488       section.setAttribute('class', 'policy-table-section');
490       // Add title and description.
491       var title = window.document.createElement('h3');
492       title.textContent = label_title;
493       section.appendChild(title);
495       if (label_content) {
496         var description = window.document.createElement('div');
497         description.classList.add('table-description');
498         description.textContent = label_content;
499         section.appendChild(description);
500       }
502       // Add 'No Policies Set' element.
503       var noPolicies = window.document.createElement('div');
504       noPolicies.classList.add('no-policies-set');
505       noPolicies.textContent = loadTimeData.getString('noPoliciesSet');
506       section.appendChild(noPolicies);
508       // Add table of policies.
509       var newTable = this.createPolicyTable();
510       this.policyTables[id] = newTable;
511       section.appendChild(newTable);
513       return section;
514     },
516     /**
517      * Creates a new table for displaying policies.
518      * @return {Element} The newly created table.
519      */
520     createPolicyTable: function() {
521       var newTable = window.document.createElement('table');
522       var tableHead = window.document.createElement('thead');
523       var tableRow = window.document.createElement('tr');
524       var tableHeadings = ['Scope', 'Level', 'Source', 'Name', 'Value',
525           'Status'];
526       for (var i = 0; i < tableHeadings.length; i++) {
527         var tableHeader = window.document.createElement('th');
528         tableHeader.classList.add(tableHeadings[i].toLowerCase() + '-column');
529         tableHeader.textContent = loadTimeData.getString('header' +
530                                                          tableHeadings[i]);
531         tableRow.appendChild(tableHeader);
532       }
533       tableHead.appendChild(tableRow);
534       newTable.appendChild(tableHead);
535       cr.ui.decorate(newTable, PolicyTable);
536       return newTable;
537     },
539     /**
540      * Update the status section of the page to show the current cloud policy
541      * status.
542      * @param {Object} status Dictionary containing the current policy status.
543      */
544     setStatus: function(status) {
545       // Remove any existing status boxes.
546       var container = $('status-box-container');
547       while (container.firstChild)
548         container.removeChild(container.firstChild);
549       // Hide the status section.
550       var section = $('status-section');
551       section.hidden = true;
553       // Add a status box for each scope that has a cloud policy status.
554       for (var scope in status) {
555         var box = new StatusBox;
556         box.initialize(scope, status[scope]);
557         container.appendChild(box);
558         // Show the status section.
559         section.hidden = false;
560       }
561     },
563     /**
564      * Re-enable the reload policies button when the previous request to reload
565      * policies values has completed.
566      */
567     reloadPoliciesDone: function() {
568       $('reload-policies').disabled = false;
569     },
570   };
572   return {
573     Page: Page
574   };
577 // Have the main initialization function be called when the page finishes
578 // loading.
579 document.addEventListener(
580     'DOMContentLoaded',
581     policy.Page.getInstance().initialize.bind(policy.Page.getInstance()));