Pin Chrome's shortcut to the Win10 Start menu on install and OS upgrade.
[chromium-blink-merge.git] / chrome / browser / resources / policy.js
blobbec30a7caaef25080064288f05d06cc4624f7bb7
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('.value').textContent = value.value;
138         this.querySelector('.expanded-value').textContent = value.value;
139       }
141       // Populate the status column.
142       var status;
143       if (!value) {
144         // If the policy value has not been set, show an error message.
145         status = loadTimeData.getString('unset');
146       } else if (unknown) {
147         // If the policy name is not recognized, show an error message.
148         status = loadTimeData.getString('unknown');
149       } else if (value.error) {
150         // If an error occurred while parsing the policy value, show the error
151         // message.
152         status = value.error;
153       } else {
154         // Otherwise, indicate that the policy value was parsed correctly.
155         status = loadTimeData.getString('ok');
156       }
157       this.querySelector('.status').textContent = status;
159       if (isMobilePage()) {
160         // The number of columns which are hidden by the css file for the mobile
161         // (Android) version of this page.
162         /** @const */ var HIDDEN_COLUMNS_IN_MOBILE_VERSION = 2;
164         var expandedValue = this.querySelector('.expanded-value');
165         expandedValue.setAttribute('colspan',
166             expandedValue.colSpan - HIDDEN_COLUMNS_IN_MOBILE_VERSION);
167       }
168     },
170     /**
171      * Check the table columns for overflow. Most columns are automatically
172      * elided when overflow occurs. The only action required is to add a tooltip
173      * that shows the complete content. The value column is an exception. If
174      * overflow occurs here, the contents is replaced with a link that toggles
175      * the visibility of an additional row containing the complete value.
176      */
177     checkOverflow: function() {
178       // Set a tooltip on all overflowed columns except the value column.
179       var divs = this.querySelectorAll('div.elide');
180       for (var i = 0; i < divs.length; i++) {
181         var div = divs[i];
182         div.title = div.offsetWidth < div.scrollWidth ? div.textContent : '';
183       }
185       // Cache the width of the value column's contents when it is first shown.
186       // This is required to be able to check whether the contents would still
187       // overflow the column once it has been hidden and replaced by a link.
188       var valueContainer = this.querySelector('.value-container');
189       if (valueContainer.valueWidth == undefined) {
190         valueContainer.valueWidth =
191             valueContainer.querySelector('.value').offsetWidth;
192       }
194       // Determine whether the contents of the value column overflows. The
195       // visibility of the contents, replacement link and additional row
196       // containing the complete value that depend on this are handled by CSS.
197       if (valueContainer.offsetWidth < valueContainer.valueWidth)
198         this.classList.add('has-overflowed-value');
199       else
200         this.classList.remove('has-overflowed-value');
201     },
203     /**
204      * Update the text of the link that toggles the visibility of an additional
205      * row containing the complete policy value, depending on the toggle state.
206      * @private
207      */
208     updateToggleExpandedValueText_: function(event) {
209       this.querySelector('.toggle-expanded-value').textContent =
210           loadTimeData.getString(
211               this.classList.contains('show-overflowed-value') ?
212                   'hideExpandedValue' : 'showExpandedValue');
213     },
215     /**
216      * Toggle the visibility of an additional row containing the complete policy
217      * value.
218      * @private
219      */
220     toggleExpandedValue_: function() {
221       this.classList.toggle('show-overflowed-value');
222       this.updateToggleExpandedValueText_();
223     },
224   };
226   /**
227    * A table of policies and their values.
228    * @constructor
229    * @extends {HTMLTableElement}
230    */
231   var PolicyTable = cr.ui.define('tbody');
233   PolicyTable.prototype = {
234     // Set up the prototype chain.
235     __proto__: HTMLTableElement.prototype,
237     /**
238      * Initialization function for the cr.ui framework.
239      */
240     decorate: function() {
241       this.policies_ = {};
242       this.filterPattern_ = '';
243       window.addEventListener('resize', this.checkOverflow_.bind(this));
244     },
246     /**
247      * Initialize the list of all known policies.
248      * @param {Object} names Dictionary containing all known policy names.
249      */
250     setPolicyNames: function(names) {
251       this.policies_ = names;
252       this.setPolicyValues({});
253     },
255     /**
256      * Populate the table with the currently set policy values and any errors
257      * detected while parsing these.
258      * @param {Object} values Dictionary containing the current policy values.
259      */
260     setPolicyValues: function(values) {
261       // Remove all policies from the table.
262       var policies = this.getElementsByTagName('tbody');
263       while (policies.length > 0)
264         this.removeChild(policies.item(0));
266       // First, add known policies whose value is currently set.
267       var unset = [];
268       for (var name in this.policies_) {
269         if (name in values)
270           this.setPolicyValue_(name, values[name], false);
271         else
272           unset.push(name);
273       }
275       // Second, add policies whose value is currently set but whose name is not
276       // recognized.
277       for (var name in values) {
278         if (!(name in this.policies_))
279           this.setPolicyValue_(name, values[name], true);
280       }
282       // Finally, add known policies whose value is not currently set.
283       for (var i = 0; i < unset.length; i++)
284         this.setPolicyValue_(unset[i], undefined, false);
286       // Filter the policies.
287       this.filter();
288     },
290     /**
291      * Set the filter pattern. Only policies whose name contains |pattern| are
292      * shown in the policy table. The filter is case insensitive. It can be
293      * disabled by setting |pattern| to an empty string.
294      * @param {string} pattern The filter pattern.
295      */
296     setFilterPattern: function(pattern) {
297       this.filterPattern_ = pattern.toLowerCase();
298       this.filter();
299     },
301     /**
302      * Filter policies. Only policies whose name contains the filter pattern are
303      * shown in the table. Furthermore, policies whose value is not currently
304      * set are only shown if the corresponding checkbox is checked.
305      */
306     filter: function() {
307       var showUnset = $('show-unset').checked;
308       var policies = this.getElementsByTagName('tbody');
309       for (var i = 0; i < policies.length; i++) {
310         var policy = policies[i];
311         policy.hidden =
312             policy.unset && !showUnset ||
313             policy.name.toLowerCase().indexOf(this.filterPattern_) == -1;
314       }
315       if (this.querySelector('tbody:not([hidden])'))
316         this.parentElement.classList.remove('empty');
317       else
318         this.parentElement.classList.add('empty');
319       setTimeout(this.checkOverflow_.bind(this), 0);
320     },
322     /**
323      * Check the table columns for overflow.
324      * @private
325      */
326     checkOverflow_: function() {
327       var policies = this.getElementsByTagName('tbody');
328       for (var i = 0; i < policies.length; i++) {
329         if (!policies[i].hidden)
330           policies[i].checkOverflow();
331       }
332     },
334     /**
335      * Add a policy with the given |name| and |value| to the table.
336      * @param {string} name The policy name.
337      * @param {Object} value Dictionary with information about the policy value.
338      * @param {boolean} unknown Whether the policy name is not recoginzed.
339      * @private
340      */
341     setPolicyValue_: function(name, value, unknown) {
342       var policy = new Policy;
343       policy.initialize(name, value, unknown);
344       this.appendChild(policy);
345     },
346   };
348   /**
349    * A singelton object that handles communication between browser and WebUI.
350    * @constructor
351    */
352   function Page() {
353   }
355   // Make Page a singleton.
356   cr.addSingletonGetter(Page);
358   /**
359    * Provide a list of all known policies to the UI. Called by the browser on
360    * page load.
361    * @param {Object} names Dictionary containing all known policy names.
362    */
363   Page.setPolicyNames = function(names) {
364     var page = this.getInstance();
366     // Clear all policy tables.
367     page.mainSection.innerHTML = '';
368     page.policyTables = {};
370     // Create tables and set known policy names for Chrome and extensions.
371     if (names.hasOwnProperty('chromePolicyNames')) {
372       var table = page.appendNewTable('chrome', 'Chrome policies', '');
373       table.setPolicyNames(names.chromePolicyNames);
374     }
376     if (names.hasOwnProperty('extensionPolicyNames')) {
377       for (var ext in names.extensionPolicyNames) {
378         var table = page.appendNewTable('extension-' + ext,
379             names.extensionPolicyNames[ext].name, 'ID: ' + ext);
380         table.setPolicyNames(names.extensionPolicyNames[ext].policyNames);
381       }
382     }
383   };
385   /**
386    * Provide a list of the currently set policy values and any errors detected
387    * while parsing these to the UI. Called by the browser on page load and
388    * whenever policy values change.
389    * @param {Object} values Dictionary containing the current policy values.
390    */
391   Page.setPolicyValues = function(values) {
392     var page = this.getInstance();
393     if (values.hasOwnProperty('chromePolicies')) {
394       var table = page.policyTables['chrome'];
395       table.setPolicyValues(values.chromePolicies);
396     }
398     if (values.hasOwnProperty('extensionPolicies')) {
399       for (var extensionId in values.extensionPolicies) {
400         var table = page.policyTables['extension-' + extensionId];
401         if (table)
402           table.setPolicyValues(values.extensionPolicies[extensionId]);
403       }
404     }
405   };
407   /**
408    * Provide the current cloud policy status to the UI. Called by the browser on
409    * page load if cloud policy is present and whenever the status changes.
410    * @param {Object} status Dictionary containing the current policy status.
411    */
412   Page.setStatus = function(status) {
413     this.getInstance().setStatus(status);
414   };
416   /**
417    * Notify the UI that a request to reload policy values has completed. Called
418    * by the browser after a request to reload policy has been sent by the UI.
419    */
420   Page.reloadPoliciesDone = function() {
421     this.getInstance().reloadPoliciesDone();
422   };
424   Page.prototype = {
425     /**
426      * Main initialization function. Called by the browser on page load.
427      */
428     initialize: function() {
429       uber.onContentFrameLoaded();
430       cr.ui.FocusOutlineManager.forDocument(document);
432       this.mainSection = $('main-section');
433       this.policyTables = {};
435       // Place the initial focus on the filter input field.
436       $('filter').focus();
438       var self = this;
439       $('filter').onsearch = function(event) {
440         for (policyTable in self.policyTables) {
441           self.policyTables[policyTable].setFilterPattern(this.value);
442         }
443       };
444       $('reload-policies').onclick = function(event) {
445         this.disabled = true;
446         chrome.send('reloadPolicies');
447       };
449       $('show-unset').onchange = function() {
450         for (policyTable in self.policyTables) {
451           self.policyTables[policyTable].filter();
452         }
453       };
455       // Notify the browser that the page has loaded, causing it to send the
456       // list of all known policies, the current policy values and the cloud
457       // policy status.
458       chrome.send('initialized');
459     },
461    /**
462      * Creates a new policy table section, adds the section to the page,
463      * and returns the new table from that section.
464      * @param {string} id The key for storing the new table in policyTables.
465      * @param {string} label_title Title for this policy table.
466      * @param {string} label_content Description for the policy table.
467      * @return {Element} The newly created table.
468      */
469     appendNewTable: function(id, label_title, label_content) {
470       var newSection = this.createPolicyTableSection(id, label_title,
471           label_content);
472       this.mainSection.appendChild(newSection);
473       return this.policyTables[id];
474     },
476     /**
477      * Creates a new section containing a title, description and table of
478      * policies.
479      * @param {id} id The key for storing the new table in policyTables.
480      * @param {string} label_title Title for this policy table.
481      * @param {string} label_content Description for the policy table.
482      * @return {Element} The newly created section.
483      */
484     createPolicyTableSection: function(id, label_title, label_content) {
485       var section = document.createElement('section');
486       section.setAttribute('class', 'policy-table-section');
488       // Add title and description.
489       var title = window.document.createElement('h3');
490       title.textContent = label_title;
491       section.appendChild(title);
493       if (label_content) {
494         var description = window.document.createElement('div');
495         description.classList.add('table-description');
496         description.textContent = label_content;
497         section.appendChild(description);
498       }
500       // Add 'No Policies Set' element.
501       var noPolicies = window.document.createElement('div');
502       noPolicies.classList.add('no-policies-set');
503       noPolicies.textContent = loadTimeData.getString('noPoliciesSet');
504       section.appendChild(noPolicies);
506       // Add table of policies.
507       var newTable = this.createPolicyTable();
508       this.policyTables[id] = newTable;
509       section.appendChild(newTable);
511       return section;
512     },
514     /**
515      * Creates a new table for displaying policies.
516      * @return {Element} The newly created table.
517      */
518     createPolicyTable: function() {
519       var newTable = window.document.createElement('table');
520       var tableHead = window.document.createElement('thead');
521       var tableRow = window.document.createElement('tr');
522       var tableHeadings = ['Scope', 'Level', 'Name', 'Value', 'Status'];
523       for (var i = 0; i < tableHeadings.length; i++) {
524         var tableHeader = window.document.createElement('th');
525         tableHeader.classList.add(tableHeadings[i].toLowerCase() + '-column');
526         tableHeader.textContent = loadTimeData.getString('header' +
527                                                          tableHeadings[i]);
528         tableRow.appendChild(tableHeader);
529       }
530       tableHead.appendChild(tableRow);
531       newTable.appendChild(tableHead);
532       cr.ui.decorate(newTable, PolicyTable);
533       return newTable;
534     },
536     /**
537      * Update the status section of the page to show the current cloud policy
538      * status.
539      * @param {Object} status Dictionary containing the current policy status.
540      */
541     setStatus: function(status) {
542       // Remove any existing status boxes.
543       var container = $('status-box-container');
544       while (container.firstChild)
545         container.removeChild(container.firstChild);
546       // Hide the status section.
547       var section = $('status-section');
548       section.hidden = true;
550       // Add a status box for each scope that has a cloud policy status.
551       for (var scope in status) {
552         var box = new StatusBox;
553         box.initialize(scope, status[scope]);
554         container.appendChild(box);
555         // Show the status section.
556         section.hidden = false;
557       }
558     },
560     /**
561      * Re-enable the reload policies button when the previous request to reload
562      * policies values has completed.
563      */
564     reloadPoliciesDone: function() {
565       $('reload-policies').disabled = false;
566     },
567   };
569   return {
570     Page: Page
571   };
574 // Have the main initialization function be called when the page finishes
575 // loading.
576 document.addEventListener(
577     'DOMContentLoaded',
578     policy.Page.getInstance().initialize.bind(policy.Page.getInstance()));