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