Want more features on Pastebin? Sign Up, it's FREE!
Guest

Script

By: a guest on Mar 27th, 2012  |  syntax: None  |  size: 490.37 KB  |  views: 169  |  expires: Never
download  |  raw  |  embed  |  report abuse  |  print
Text below is selected. Please press Ctrl+C to copy to your clipboard. (⌘+C on Mac)
  1. // Copyright (c) 2011 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. //
  5. // This file exists to aggregate all of the javascript used by the
  6. // settings page into a single file which will be flattened and served
  7. // as a single resource.
  8. // Copyright (c) 2011 The Chromium Authors. All rights reserved.
  9. // Use of this source code is governed by a BSD-style license that can be
  10. // found in the LICENSE file.
  11.  
  12. cr.define('options', function() {
  13.  
  14.   /////////////////////////////////////////////////////////////////////////////
  15.   // Preferences class:
  16.  
  17.   /**
  18.    * Preferences class manages access to Chrome profile preferences.
  19.    * @constructor
  20.    */
  21.   function Preferences() {
  22.   }
  23.  
  24.   cr.addSingletonGetter(Preferences);
  25.  
  26.   /**
  27.    * Sets value of a boolean preference.
  28.    * and signals its changed value.
  29.    * @param {string} name Preference name.
  30.    * @param {boolean} value New preference value.
  31.    * @param {string} metric User metrics identifier.
  32.    */
  33.   Preferences.setBooleanPref = function(name, value, metric) {
  34.     var argumentList = [name, Boolean(value)];
  35.     if (metric != undefined) argumentList.push(metric);
  36.     chrome.send('setBooleanPref', argumentList);
  37.   };
  38.  
  39.   /**
  40.    * Sets value of an integer preference.
  41.    * and signals its changed value.
  42.    * @param {string} name Preference name.
  43.    * @param {number} value New preference value.
  44.    * @param {string} metric User metrics identifier.
  45.    */
  46.   Preferences.setIntegerPref = function(name, value, metric) {
  47.     var argumentList = [name, Number(value)];
  48.     if (metric != undefined) argumentList.push(metric);
  49.     chrome.send('setIntegerPref', argumentList);
  50.   };
  51.  
  52.   /**
  53.    * Sets value of a double-valued preference.
  54.    * and signals its changed value.
  55.    * @param {string} name Preference name.
  56.    * @param {number} value New preference value.
  57.    * @param {string} metric User metrics identifier.
  58.    */
  59.   Preferences.setDoublePref = function(name, value, metric) {
  60.     var argumentList = [name, Number(value)];
  61.     if (metric != undefined) argumentList.push(metric);
  62.     chrome.send('setDoublePref', argumentList);
  63.   };
  64.  
  65.   /**
  66.    * Sets value of a string preference.
  67.    * and signals its changed value.
  68.    * @param {string} name Preference name.
  69.    * @param {string} value New preference value.
  70.    * @param {string} metric User metrics identifier.
  71.    */
  72.   Preferences.setStringPref = function(name, value, metric) {
  73.     var argumentList = [name, String(value)];
  74.     if (metric != undefined) argumentList.push(metric);
  75.     chrome.send('setStringPref', argumentList);
  76.   };
  77.  
  78.   /**
  79.    * Sets value of a string preference that represents a URL
  80.    * and signals its changed value. The value will be fixed to be a valid URL.
  81.    * @param {string} name Preference name.
  82.    * @param {string} value New preference value.
  83.    * @param {string} metric User metrics identifier.
  84.    */
  85.   Preferences.setURLPref = function(name, value, metric) {
  86.     var argumentList = [name, String(value)];
  87.     if (metric != undefined) argumentList.push(metric);
  88.     chrome.send('setURLPref', argumentList);
  89.   };
  90.  
  91.   /**
  92.    * Sets value of a JSON list preference.
  93.    * and signals its changed value.
  94.    * @param {string} name Preference name.
  95.    * @param {Array} value New preference value.
  96.    * @param {string} metric User metrics identifier.
  97.    */
  98.   Preferences.setListPref = function(name, value, metric) {
  99.     var argumentList = [name, JSON.stringify(value)];
  100.     if (metric != undefined) argumentList.push(metric);
  101.     chrome.send('setListPref', argumentList);
  102.   };
  103.  
  104.   /**
  105.    * Clears value of a JSON preference.
  106.    * @param {string} name Preference name.
  107.    * @param {string} metric User metrics identifier.
  108.    */
  109.   Preferences.clearPref = function(name, metric) {
  110.     var argumentList = [name];
  111.     if (metric != undefined) argumentList.push(metric);
  112.     chrome.send('clearPref', argumentList);
  113.   };
  114.  
  115.   Preferences.prototype = {
  116.     __proto__: cr.EventTarget.prototype,
  117.  
  118.     // Map of registered preferences.
  119.     registeredPreferences_: {},
  120.  
  121.     /**
  122.      * Adds an event listener to the target.
  123.      * @param {string} type The name of the event.
  124.      * @param {!Function|{handleEvent:Function}} handler The handler for the
  125.      *     event. This is called when the event is dispatched.
  126.      */
  127.     addEventListener: function(type, handler) {
  128.       cr.EventTarget.prototype.addEventListener.call(this, type, handler);
  129.       this.registeredPreferences_[type] = true;
  130.     },
  131.  
  132.     /**
  133.      * Initializes preference reading and change notifications.
  134.      */
  135.     initialize: function() {
  136.       var params1 = ['Preferences.prefsFetchedCallback'];
  137.       var params2 = ['Preferences.prefsChangedCallback'];
  138.       for (var prefName in this.registeredPreferences_) {
  139.         params1.push(prefName);
  140.         params2.push(prefName);
  141.       }
  142.       chrome.send('fetchPrefs', params1);
  143.       chrome.send('observePrefs', params2);
  144.     },
  145.  
  146.     /**
  147.      * Helper function for flattening of dictionary passed via fetchPrefs
  148.      * callback.
  149.      * @param {string} prefix Preference name prefix.
  150.      * @param {object} dict Map with preference values.
  151.      */
  152.     flattenMapAndDispatchEvent_: function(prefix, dict) {
  153.       for (var prefName in dict) {
  154.         if (typeof dict[prefName] == 'object' &&
  155.             !this.registeredPreferences_[prefix + prefName]) {
  156.           this.flattenMapAndDispatchEvent_(prefix + prefName + '.',
  157.               dict[prefName]);
  158.         } else {
  159.           var event = new cr.Event(prefix + prefName);
  160.           event.value = dict[prefName];
  161.           this.dispatchEvent(event);
  162.         }
  163.       }
  164.     }
  165.   };
  166.  
  167.   /**
  168.    * Callback for fetchPrefs method.
  169.    * @param {object} dict Map of fetched property values.
  170.    */
  171.   Preferences.prefsFetchedCallback = function(dict) {
  172.     Preferences.getInstance().flattenMapAndDispatchEvent_('', dict);
  173.   };
  174.  
  175.   /**
  176.    * Callback for observePrefs method.
  177.    * @param {array} notification An array defining changed preference values.
  178.    * notification[0] contains name of the change preference while its new value
  179.    * is stored in notification[1].
  180.    */
  181.   Preferences.prefsChangedCallback = function(notification) {
  182.     var event = new cr.Event(notification[0]);
  183.     event.value = notification[1];
  184.     Preferences.getInstance().dispatchEvent(event);
  185.   };
  186.  
  187.   // Export
  188.   return {
  189.     Preferences: Preferences
  190.   };
  191.  
  192. });
  193.  
  194. // Copyright (c) 2011 The Chromium Authors. All rights reserved.
  195. // Use of this source code is governed by a BSD-style license that can be
  196. // found in the LICENSE file.
  197.  
  198. cr.define('options', function() {
  199.  
  200.   var Preferences = options.Preferences;
  201.  
  202.   /**
  203.    * Allows an element to be disabled for several reasons.
  204.    * The element is disabled if at least one reason is true, and the reasons
  205.    * can be set separately.
  206.    * @private
  207.    * @param {!HTMLElement} el The element to update.
  208.    * @param {string} reason The reason for disabling the element.
  209.    * @param {boolean} disabled Whether the element should be disabled or enabled
  210.    * for the given |reason|.
  211.    */
  212.   function updateDisabledState_(el, reason, disabled) {
  213.     if (!el.disabledReasons)
  214.       el.disabledReasons = {};
  215.     if (el.disabled && (Object.keys(el.disabledReasons).length == 0)) {
  216.       // The element has been previously disabled without a reason, so we add
  217.       // one to keep it disabled.
  218.       el.disabledReasons['other'] = true;
  219.     }
  220.     if (!el.disabled) {
  221.       // If the element is not disabled, there should be no reason, except for
  222.       // 'other'.
  223.       delete el.disabledReasons['other'];
  224.       if (Object.keys(el.disabledReasons).length > 0)
  225.         console.error("Element is not disabled but should be");
  226.     }
  227.     if (disabled) {
  228.       el.disabledReasons[reason] = true;
  229.     } else {
  230.       delete el.disabledReasons[reason];
  231.     }
  232.     el.disabled = Object.keys(el.disabledReasons).length > 0;
  233.   }
  234.  
  235.   /**
  236.    * Helper function to update element's state from pref change event.
  237.    * @private
  238.    * @param {!HTMLElement} el The element to update.
  239.    * @param {!Event} event The pref change event.
  240.    */
  241.   function updateElementState_(el, event) {
  242.     el.controlledBy = null;
  243.  
  244.     if (!event.value)
  245.       return;
  246.  
  247.     updateDisabledState_(el, 'notUserModifiable', event.value.disabled);
  248.  
  249.     el.controlledBy = event.value['controlledBy'];
  250.  
  251.     OptionsPage.updateManagedBannerVisibility();
  252.   }
  253.  
  254.   /////////////////////////////////////////////////////////////////////////////
  255.   // PrefCheckbox class:
  256.   // TODO(jhawkins): Refactor all this copy-pasted code!
  257.  
  258.   // Define a constructor that uses an input element as its underlying element.
  259.   var PrefCheckbox = cr.ui.define('input');
  260.  
  261.   PrefCheckbox.prototype = {
  262.     // Set up the prototype chain
  263.     __proto__: HTMLInputElement.prototype,
  264.  
  265.     /**
  266.      * Initialization function for the cr.ui framework.
  267.      */
  268.     decorate: function() {
  269.       this.type = 'checkbox';
  270.       var self = this;
  271.  
  272.       self.initializeValueType(self.getAttribute('value-type'));
  273.  
  274.       // Listen to pref changes.
  275.       Preferences.getInstance().addEventListener(
  276.           this.pref,
  277.           function(event) {
  278.             var value = event.value && event.value['value'] != undefined ?
  279.                 event.value['value'] : event.value;
  280.  
  281.             // Invert pref value if inverted_pref == true.
  282.             if (self.inverted_pref)
  283.               self.checked = !Boolean(value);
  284.             else
  285.               self.checked = Boolean(value);
  286.  
  287.             updateElementState_(self, event);
  288.           });
  289.  
  290.       // Listen to user events.
  291.       this.addEventListener(
  292.           'change',
  293.           function(e) {
  294.             if (self.customChangeHandler(e))
  295.               return;
  296.             var value = self.inverted_pref ? !self.checked : self.checked;
  297.             switch(self.valueType) {
  298.               case 'number':
  299.                 Preferences.setIntegerPref(self.pref,
  300.                     Number(value), self.metric);
  301.                 break;
  302.               case 'boolean':
  303.                 Preferences.setBooleanPref(self.pref,
  304.                     value, self.metric);
  305.                 break;
  306.             }
  307.           });
  308.     },
  309.  
  310.     /**
  311.      * Sets up options in checkbox element.
  312.      * @param {String} valueType The preference type for this checkbox.
  313.      */
  314.     initializeValueType: function(valueType) {
  315.       this.valueType = valueType || 'boolean';
  316.     },
  317.  
  318.     /**
  319.      * See |updateDisabledState_| above.
  320.      */
  321.     setDisabled: function(reason, disabled) {
  322.       updateDisabledState_(this, reason, disabled);
  323.     },
  324.  
  325.     /**
  326.      * This method is called first while processing an onchange event. If it
  327.      * returns false, regular onchange processing continues (setting the
  328.      * associated pref, etc). If it returns true, the rest of the onchange is
  329.      * not performed. I.e., this works like stopPropagation or cancelBubble.
  330.      * @param {Event} event Change event.
  331.      */
  332.     customChangeHandler: function(event) {
  333.       return false;
  334.     },
  335.   };
  336.  
  337.   /**
  338.    * The preference name.
  339.    * @type {string}
  340.    */
  341.   cr.defineProperty(PrefCheckbox, 'pref', cr.PropertyKind.ATTR);
  342.  
  343.   /**
  344.    * Whether the preference is controlled by something else than the user's
  345.    * settings (either 'policy' or 'extension').
  346.    * @type {string}
  347.    */
  348.   cr.defineProperty(PrefCheckbox, 'controlledBy', cr.PropertyKind.ATTR);
  349.  
  350.   /**
  351.    * The user metric string.
  352.    * @type {string}
  353.    */
  354.   cr.defineProperty(PrefCheckbox, 'metric', cr.PropertyKind.ATTR);
  355.  
  356.   /**
  357.    * Whether to use inverted pref value.
  358.    * @type {boolean}
  359.    */
  360.   cr.defineProperty(PrefCheckbox, 'inverted_pref', cr.PropertyKind.BOOL_ATTR);
  361.  
  362.   /////////////////////////////////////////////////////////////////////////////
  363.   // PrefRadio class:
  364.  
  365.   //Define a constructor that uses an input element as its underlying element.
  366.   var PrefRadio = cr.ui.define('input');
  367.  
  368.   PrefRadio.prototype = {
  369.     // Set up the prototype chain
  370.     __proto__: HTMLInputElement.prototype,
  371.  
  372.     /**
  373.      * Initialization function for the cr.ui framework.
  374.      */
  375.     decorate: function() {
  376.       this.type = 'radio';
  377.       var self = this;
  378.  
  379.       // Listen to pref changes.
  380.       Preferences.getInstance().addEventListener(this.pref,
  381.           function(event) {
  382.             var value = event.value && event.value['value'] != undefined ?
  383.                 event.value['value'] : event.value;
  384.             self.checked = String(value) == self.value;
  385.  
  386.             updateElementState_(self, event);
  387.           });
  388.  
  389.       // Listen to user events.
  390.       this.addEventListener('change',
  391.           function(e) {
  392.             if(self.value == 'true' || self.value == 'false') {
  393.               Preferences.setBooleanPref(self.pref,
  394.                   self.value == 'true', self.metric);
  395.             } else {
  396.               Preferences.setIntegerPref(self.pref,
  397.                   parseInt(self.value, 10), self.metric);
  398.             }
  399.           });
  400.     },
  401.  
  402.     /**
  403.      * See |updateDisabledState_| above.
  404.      */
  405.     setDisabled: function(reason, disabled) {
  406.       updateDisabledState_(this, reason, disabled);
  407.     },
  408.   };
  409.  
  410.   /**
  411.    * The preference name.
  412.    * @type {string}
  413.    */
  414.   cr.defineProperty(PrefRadio, 'pref', cr.PropertyKind.ATTR);
  415.  
  416.   /**
  417.    * Whether the preference is controlled by something else than the user's
  418.    * settings (either 'policy' or 'extension').
  419.    * @type {string}
  420.    */
  421.   cr.defineProperty(PrefRadio, 'controlledBy', cr.PropertyKind.ATTR);
  422.  
  423.   /**
  424.    * The user metric string.
  425.    * @type {string}
  426.    */
  427.   cr.defineProperty(PrefRadio, 'metric', cr.PropertyKind.ATTR);
  428.  
  429.   /////////////////////////////////////////////////////////////////////////////
  430.   // PrefNumeric class:
  431.  
  432.   // Define a constructor that uses an input element as its underlying element.
  433.   var PrefNumeric = function() {};
  434.   PrefNumeric.prototype = {
  435.     // Set up the prototype chain
  436.     __proto__: HTMLInputElement.prototype,
  437.  
  438.     /**
  439.      * Initialization function for the cr.ui framework.
  440.      */
  441.     decorate: function() {
  442.       var self = this;
  443.  
  444.       // Listen to pref changes.
  445.       Preferences.getInstance().addEventListener(this.pref,
  446.           function(event) {
  447.             self.value = event.value && event.value['value'] != undefined ?
  448.                 event.value['value'] : event.value;
  449.  
  450.             updateElementState_(self, event);
  451.           });
  452.  
  453.       // Listen to user events.
  454.       this.addEventListener('change',
  455.           function(e) {
  456.             if (this.validity.valid) {
  457.               Preferences.setIntegerPref(self.pref, self.value, self.metric);
  458.             }
  459.           });
  460.     },
  461.  
  462.     /**
  463.      * See |updateDisabledState_| above.
  464.      */
  465.     setDisabled: function(reason, disabled) {
  466.       updateDisabledState_(this, reason, disabled);
  467.     },
  468.   };
  469.  
  470.   /**
  471.    * The preference name.
  472.    * @type {string}
  473.    */
  474.   cr.defineProperty(PrefNumeric, 'pref', cr.PropertyKind.ATTR);
  475.  
  476.   /**
  477.    * Whether the preference is controlled by something else than the user's
  478.    * settings (either 'policy' or 'extension').
  479.    * @type {string}
  480.    */
  481.   cr.defineProperty(PrefNumeric, 'controlledBy', cr.PropertyKind.ATTR);
  482.  
  483.   /**
  484.    * The user metric string.
  485.    * @type {string}
  486.    */
  487.   cr.defineProperty(PrefNumeric, 'metric', cr.PropertyKind.ATTR);
  488.  
  489.   /////////////////////////////////////////////////////////////////////////////
  490.   // PrefNumber class:
  491.  
  492.   // Define a constructor that uses an input element as its underlying element.
  493.   var PrefNumber = cr.ui.define('input');
  494.  
  495.   PrefNumber.prototype = {
  496.     // Set up the prototype chain
  497.     __proto__: PrefNumeric.prototype,
  498.  
  499.     /**
  500.      * Initialization function for the cr.ui framework.
  501.      */
  502.     decorate: function() {
  503.       this.type = 'number';
  504.       PrefNumeric.prototype.decorate.call(this);
  505.  
  506.       // Listen to user events.
  507.       this.addEventListener('input',
  508.           function(e) {
  509.             if (this.validity.valid) {
  510.               Preferences.setIntegerPref(self.pref, self.value, self.metric);
  511.             }
  512.           });
  513.     },
  514.  
  515.     /**
  516.      * See |updateDisabledState_| above.
  517.      */
  518.     setDisabled: function(reason, disabled) {
  519.       updateDisabledState_(this, reason, disabled);
  520.     },
  521.   };
  522.  
  523.   /////////////////////////////////////////////////////////////////////////////
  524.   // PrefRange class:
  525.  
  526.   // Define a constructor that uses an input element as its underlying element.
  527.   var PrefRange = cr.ui.define('input');
  528.  
  529.   PrefRange.prototype = {
  530.     // Set up the prototype chain
  531.     __proto__: HTMLInputElement.prototype,
  532.  
  533.     /**
  534.      * The map from input range value to the corresponding preference value.
  535.      */
  536.     valueMap: undefined,
  537.  
  538.     /**
  539.      * If true, the associated pref will be modified on each onchange event;
  540.      * otherwise, the pref will only be modified on the onmouseup event after
  541.      * the drag.
  542.      */
  543.     continuous: true,
  544.  
  545.     /**
  546.      * Initialization function for the cr.ui framework.
  547.      */
  548.     decorate: function() {
  549.       this.type = 'range';
  550.  
  551.       // Update the UI when the pref changes.
  552.       Preferences.getInstance().addEventListener(
  553.           this.pref, this.onPrefChange_.bind(this));
  554.  
  555.       // Listen to user events.
  556.       // TODO(jhawkins): Add onmousewheel handling once the associated WK bug is
  557.       // fixed.
  558.       // https://bugs.webkit.org/show_bug.cgi?id=52256
  559.       this.onchange = this.onChange_.bind(this);
  560.       this.onkeyup = this.onmouseup = this.onInputUp_.bind(this);
  561.     },
  562.  
  563.     /**
  564.      * Event listener that updates the UI when the underlying pref changes.
  565.      * @param {Event} event The event that details the pref change.
  566.      * @private
  567.      */
  568.     onPrefChange_: function(event) {
  569.       var value = event.value && event.value['value'] != undefined ?
  570.           event.value['value'] : event.value;
  571.       if (value != undefined)
  572.         this.value = this.valueMap ? this.valueMap.indexOf(value) : value;
  573.     },
  574.  
  575.     /**
  576.      * onchange handler that sets the pref when the user changes the value of
  577.      * the input element.
  578.      * @private
  579.      */
  580.     onChange_: function(event) {
  581.       if (this.continuous)
  582.         this.setRangePref_();
  583.  
  584.       if (this.notifyChange)
  585.         this.notifyChange(this, this.mapValueToRange_(this.value));
  586.     },
  587.  
  588.     /**
  589.      * Sets the integer value of |pref| to the value of this element.
  590.      * @private
  591.      */
  592.     setRangePref_: function() {
  593.       Preferences.setIntegerPref(
  594.           this.pref, this.mapValueToRange_(this.value), this.metric);
  595.  
  596.       if (this.notifyPrefChange)
  597.         this.notifyPrefChange(this, this.mapValueToRange_(this.value));
  598.     },
  599.  
  600.     /**
  601.      * onkeyup/onmouseup handler that modifies the pref if |continuous| is
  602.      * false.
  603.      * @private
  604.      */
  605.     onInputUp_: function(event) {
  606.       if (!this.continuous)
  607.         this.setRangePref_();
  608.     },
  609.  
  610.     /**
  611.      * Maps the value of this element into the range provided by the client,
  612.      * represented by |valueMap|.
  613.      * @param {number} value The value to map.
  614.      * @private
  615.      */
  616.     mapValueToRange_: function(value) {
  617.       return this.valueMap ? this.valueMap[value] : value;
  618.     },
  619.  
  620.     /**
  621.      * Called when the client has specified non-continuous mode and the value of
  622.      * the range control changes.
  623.      * @param {Element} el This element.
  624.      * @param {number} value The value of this element.
  625.      */
  626.     notifyChange: function(el, value) {
  627.     },
  628.  
  629.     /**
  630.      * See |updateDisabledState_| above.
  631.      */
  632.     setDisabled: function(reason, disabled) {
  633.       updateDisabledState_(this, reason, disabled);
  634.     },
  635.   };
  636.  
  637.   /**
  638.    * The preference name.
  639.    * @type {string}
  640.    */
  641.   cr.defineProperty(PrefRange, 'pref', cr.PropertyKind.ATTR);
  642.  
  643.   /**
  644.    * Whether the preference is controlled by something else than the user's
  645.    * settings (either 'policy' or 'extension').
  646.    * @type {string}
  647.    */
  648.   cr.defineProperty(PrefRange, 'controlledBy', cr.PropertyKind.ATTR);
  649.  
  650.   /**
  651.    * The user metric string.
  652.    * @type {string}
  653.    */
  654.   cr.defineProperty(PrefRange, 'metric', cr.PropertyKind.ATTR);
  655.  
  656.   /////////////////////////////////////////////////////////////////////////////
  657.   // PrefSelect class:
  658.  
  659.   // Define a constructor that uses a select element as its underlying element.
  660.   var PrefSelect = cr.ui.define('select');
  661.  
  662.   PrefSelect.prototype = {
  663.     // Set up the prototype chain
  664.     __proto__: HTMLSelectElement.prototype,
  665.  
  666.     /**
  667.     * Initialization function for the cr.ui framework.
  668.     */
  669.     decorate: function() {
  670.       var self = this;
  671.  
  672.       // Listen to pref changes.
  673.       Preferences.getInstance().addEventListener(this.pref,
  674.           function(event) {
  675.             var value = event.value && event.value['value'] != undefined ?
  676.                 event.value['value'] : event.value;
  677.  
  678.             // Make sure |value| is a string, because the value is stored as a
  679.             // string in the HTMLOptionElement.
  680.             value = value.toString();
  681.  
  682.             updateElementState_(self, event);
  683.  
  684.             var found = false;
  685.             for (var i = 0; i < self.options.length; i++) {
  686.               if (self.options[i].value == value) {
  687.                 self.selectedIndex = i;
  688.                 found = true;
  689.               }
  690.             }
  691.  
  692.             // Item not found, select first item.
  693.             if (!found)
  694.               self.selectedIndex = 0;
  695.  
  696.             if (self.onchange != undefined)
  697.               self.onchange(event);
  698.           });
  699.  
  700.       // Listen to user events.
  701.       this.addEventListener('change',
  702.           function(e) {
  703.             if (!self.dataType) {
  704.               console.error('undefined data type for <select> pref');
  705.               return;
  706.             }
  707.  
  708.             switch(self.dataType) {
  709.               case 'number':
  710.                 Preferences.setIntegerPref(self.pref,
  711.                     self.options[self.selectedIndex].value, self.metric);
  712.                 break;
  713.               case 'double':
  714.                 Preferences.setDoublePref(self.pref,
  715.                     self.options[self.selectedIndex].value, self.metric);
  716.                 break;
  717.               case 'boolean':
  718.                 var option = self.options[self.selectedIndex];
  719.                 var value = (option.value == 'true') ? true : false;
  720.                 Preferences.setBooleanPref(self.pref, value, self.metric);
  721.                 break;
  722.               case 'string':
  723.                 Preferences.setStringPref(self.pref,
  724.                     self.options[self.selectedIndex].value, self.metric);
  725.                 break;
  726.               default:
  727.                 console.error('unknown data type for <select> pref: ' +
  728.                               self.dataType);
  729.             }
  730.           });
  731.     },
  732.  
  733.     /**
  734.      * See |updateDisabledState_| above.
  735.      */
  736.     setDisabled: function(reason, disabled) {
  737.       updateDisabledState_(this, reason, disabled);
  738.     },
  739.   };
  740.  
  741.   /**
  742.    * The preference name.
  743.    * @type {string}
  744.    */
  745.   cr.defineProperty(PrefSelect, 'pref', cr.PropertyKind.ATTR);
  746.  
  747.   /**
  748.    * Whether the preference is controlled by something else than the user's
  749.    * settings (either 'policy' or 'extension').
  750.    * @type {string}
  751.    */
  752.   cr.defineProperty(PrefSelect, 'controlledBy', cr.PropertyKind.ATTR);
  753.  
  754.   /**
  755.    * The user metric string.
  756.    * @type {string}
  757.    */
  758.   cr.defineProperty(PrefSelect, 'metric', cr.PropertyKind.ATTR);
  759.  
  760.   /**
  761.    * The data type for the preference options.
  762.    * @type {string}
  763.    */
  764.   cr.defineProperty(PrefSelect, 'dataType', cr.PropertyKind.ATTR);
  765.  
  766.   /////////////////////////////////////////////////////////////////////////////
  767.   // PrefTextField class:
  768.  
  769.   // Define a constructor that uses an input element as its underlying element.
  770.   var PrefTextField = cr.ui.define('input');
  771.  
  772.   PrefTextField.prototype = {
  773.     // Set up the prototype chain
  774.     __proto__: HTMLInputElement.prototype,
  775.  
  776.     /**
  777.      * Initialization function for the cr.ui framework.
  778.      */
  779.     decorate: function() {
  780.       var self = this;
  781.  
  782.       // Listen to pref changes.
  783.       Preferences.getInstance().addEventListener(this.pref,
  784.           function(event) {
  785.             self.value = event.value && event.value['value'] != undefined ?
  786.                 event.value['value'] : event.value;
  787.  
  788.             updateElementState_(self, event);
  789.           });
  790.  
  791.       // Listen to user events.
  792.       this.addEventListener('change',
  793.           function(e) {
  794.             switch(self.dataType) {
  795.               case 'number':
  796.                 Preferences.setIntegerPref(self.pref, self.value, self.metric);
  797.                 break;
  798.               case 'double':
  799.                 Preferences.setDoublePref(self.pref, self.value, self.metric);
  800.                 break;
  801.               case 'url':
  802.                 Preferences.setURLPref(self.pref, self.value, self.metric);
  803.                 break;
  804.               default:
  805.                 Preferences.setStringPref(self.pref, self.value, self.metric);
  806.                 break;
  807.             }
  808.           });
  809.  
  810.       window.addEventListener('unload',
  811.           function() {
  812.             if (document.activeElement == self)
  813.               self.blur();
  814.           });
  815.     },
  816.  
  817.     /**
  818.      * See |updateDisabledState_| above.
  819.      */
  820.     setDisabled: function(reason, disabled) {
  821.       updateDisabledState_(this, reason, disabled);
  822.     },
  823.   };
  824.  
  825.   /**
  826.    * The preference name.
  827.    * @type {string}
  828.    */
  829.   cr.defineProperty(PrefTextField, 'pref', cr.PropertyKind.ATTR);
  830.  
  831.   /**
  832.    * Whether the preference is controlled by something else than the user's
  833.    * settings (either 'policy' or 'extension').
  834.    * @type {string}
  835.    */
  836.   cr.defineProperty(PrefTextField, 'controlledBy', cr.PropertyKind.ATTR);
  837.  
  838.   /**
  839.    * The user metric string.
  840.    * @type {string}
  841.    */
  842.   cr.defineProperty(PrefTextField, 'metric', cr.PropertyKind.ATTR);
  843.  
  844.   /**
  845.    * The data type for the preference options.
  846.    * @type {string}
  847.    */
  848.   cr.defineProperty(PrefTextField, 'dataType', cr.PropertyKind.ATTR);
  849.  
  850.   /////////////////////////////////////////////////////////////////////////////
  851.   // PrefButton class:
  852.  
  853.   // Define a constructor that uses a button element as its underlying element.
  854.   var PrefButton = cr.ui.define('button');
  855.  
  856.   PrefButton.prototype = {
  857.     // Set up the prototype chain
  858.     __proto__: HTMLButtonElement.prototype,
  859.  
  860.     /**
  861.     * Initialization function for the cr.ui framework.
  862.     */
  863.     decorate: function() {
  864.       var self = this;
  865.  
  866.       // Listen to pref changes. This element behaves like a normal button and
  867.       // doesn't affect the underlying preference; it just becomes disabled
  868.       // when the preference is managed, and its value is false.
  869.       // This is useful for buttons that should be disabled when the underlying
  870.       // boolean preference is set to false by a policy or extension.
  871.       Preferences.getInstance().addEventListener(this.pref,
  872.           function(event) {
  873.             var e = {
  874.               value: {
  875.                 'disabled': event.value['disabled'] && !event.value['value'],
  876.                 'controlledBy': event.value['controlledBy']
  877.               }
  878.             };
  879.             updateElementState_(self, e);
  880.           });
  881.     },
  882.  
  883.     /**
  884.      * See |updateDisabledState_| above.
  885.      */
  886.     setDisabled: function(reason, disabled) {
  887.       updateDisabledState_(this, reason, disabled);
  888.     },
  889.   };
  890.  
  891.   /**
  892.    * The preference name.
  893.    * @type {string}
  894.    */
  895.   cr.defineProperty(PrefButton, 'pref', cr.PropertyKind.ATTR);
  896.  
  897.   /**
  898.    * Whether the preference is controlled by something else than the user's
  899.    * settings (either 'policy' or 'extension').
  900.    * @type {string}
  901.    */
  902.   cr.defineProperty(PrefButton, 'controlledBy', cr.PropertyKind.ATTR);
  903.  
  904.   // Export
  905.   return {
  906.     PrefCheckbox: PrefCheckbox,
  907.     PrefNumber: PrefNumber,
  908.     PrefNumeric: PrefNumeric,
  909.     PrefRadio: PrefRadio,
  910.     PrefRange: PrefRange,
  911.     PrefSelect: PrefSelect,
  912.     PrefTextField: PrefTextField,
  913.     PrefButton: PrefButton
  914.   };
  915.  
  916. });
  917.  
  918. // Copyright (c) 2011 The Chromium Authors. All rights reserved.
  919. // Use of this source code is governed by a BSD-style license that can be
  920. // found in the LICENSE file.
  921.  
  922. cr.define('options', function() {
  923.   const List = cr.ui.List;
  924.   const ListItem = cr.ui.ListItem;
  925.  
  926.   /**
  927.    * Creates a deletable list item, which has a button that will trigger a call
  928.    * to deleteItemAtIndex(index) in the list.
  929.    */
  930.   var DeletableItem = cr.ui.define('li');
  931.  
  932.   DeletableItem.prototype = {
  933.     __proto__: ListItem.prototype,
  934.  
  935.     /**
  936.      * The element subclasses should populate with content.
  937.      * @type {HTMLElement}
  938.      * @private
  939.      */
  940.     contentElement_: null,
  941.  
  942.     /**
  943.      * The close button element.
  944.      * @type {HTMLElement}
  945.      * @private
  946.      */
  947.     closeButtonElement_: null,
  948.  
  949.     /**
  950.      * Whether or not this item can be deleted.
  951.      * @type {boolean}
  952.      * @private
  953.      */
  954.     deletable_: true,
  955.  
  956.     /** @inheritDoc */
  957.     decorate: function() {
  958.       ListItem.prototype.decorate.call(this);
  959.  
  960.       this.classList.add('deletable-item');
  961.  
  962.       this.contentElement_ = this.ownerDocument.createElement('div');
  963.       this.appendChild(this.contentElement_);
  964.  
  965.       this.closeButtonElement_ = this.ownerDocument.createElement('button');
  966.       this.closeButtonElement_.className =
  967.           'raw-button close-button custom-appearance';
  968.       this.closeButtonElement_.addEventListener('mousedown',
  969.                                                 this.handleMouseDownUpOnClose_);
  970.       this.closeButtonElement_.addEventListener('mouseup',
  971.                                                 this.handleMouseDownUpOnClose_);
  972.       this.closeButtonElement_.addEventListener('focus',
  973.                                                 this.handleFocus_.bind(this));
  974.       this.appendChild(this.closeButtonElement_);
  975.     },
  976.  
  977.     /**
  978.      * Returns the element subclasses should add content to.
  979.      * @return {HTMLElement} The element subclasses should popuplate.
  980.      */
  981.     get contentElement() {
  982.       return this.contentElement_;
  983.     },
  984.  
  985.     /* Gets/sets the deletable property. An item that is not deletable doesn't
  986.      * show the delete button (although space is still reserved for it).
  987.      */
  988.     get deletable() {
  989.       return this.deletable_;
  990.     },
  991.     set deletable(value) {
  992.       this.deletable_ = value;
  993.       this.closeButtonElement_.disabled = !value;
  994.     },
  995.  
  996.     /**
  997.      * Called when a focusable child element receives focus. Selects this item
  998.      * in the list selection model.
  999.      * @private
  1000.      */
  1001.     handleFocus_: function() {
  1002.       var list = this.parentNode;
  1003.       var index = list.getIndexOfListItem(this);
  1004.       list.selectionModel.selectedIndex = index;
  1005.       list.selectionModel.anchorIndex = index;
  1006.     },
  1007.  
  1008.     /**
  1009.      * Don't let the list have a crack at the event. We don't want clicking the
  1010.      * close button to change the selection of the list.
  1011.      * @param {Event} e The mouse down/up event object.
  1012.      * @private
  1013.      */
  1014.     handleMouseDownUpOnClose_: function(e) {
  1015.       if (!e.target.disabled)
  1016.         e.stopPropagation();
  1017.     },
  1018.   };
  1019.  
  1020.   var DeletableItemList = cr.ui.define('list');
  1021.  
  1022.   DeletableItemList.prototype = {
  1023.     __proto__: List.prototype,
  1024.  
  1025.     /** @inheritDoc */
  1026.     decorate: function() {
  1027.       List.prototype.decorate.call(this);
  1028.       this.addEventListener('click', this.handleClick_);
  1029.       this.addEventListener('keydown', this.handleKeyDown_);
  1030.     },
  1031.  
  1032.     /**
  1033.      * Callback for onclick events.
  1034.      * @param {Event} e The click event object.
  1035.      * @private
  1036.      */
  1037.     handleClick_: function(e) {
  1038.       if (this.disabled)
  1039.         return;
  1040.  
  1041.       var target = e.target;
  1042.       if (target.classList.contains('close-button')) {
  1043.         var listItem = this.getListItemAncestor(target);
  1044.         var selected = this.selectionModel.selectedIndexes;
  1045.  
  1046.         // Check if the list item that contains the close button being clicked
  1047.         // is not in the list of selected items. Only delete this item in that
  1048.         // case.
  1049.         var idx = this.getIndexOfListItem(listItem);
  1050.         if (selected.indexOf(idx) == -1) {
  1051.           this.deleteItemAtIndex(idx);
  1052.         } else {
  1053.           this.deleteSelectedItems_();
  1054.         }
  1055.       }
  1056.     },
  1057.  
  1058.     /**
  1059.      * Callback for keydown events.
  1060.      * @param {Event} e The keydown event object.
  1061.      * @private
  1062.      */
  1063.     handleKeyDown_: function(e) {
  1064.       // Map delete (and backspace on Mac) to item deletion (unless focus is
  1065.       // in an input field, where it's intended for text editing).
  1066.       if ((e.keyCode == 46 || (e.keyCode == 8 && cr.isMac)) &&
  1067.           e.target.tagName != 'INPUT') {
  1068.         this.deleteSelectedItems_();
  1069.         // Prevent the browser from going back.
  1070.         e.preventDefault();
  1071.       }
  1072.     },
  1073.  
  1074.     /**
  1075.      * Deletes all the currently selected items that are deletable.
  1076.      * @private
  1077.      */
  1078.     deleteSelectedItems_: function() {
  1079.       var selected = this.selectionModel.selectedIndexes;
  1080.       // Reverse through the list of selected indexes to maintain the
  1081.       // correct index values after deletion.
  1082.       for (var j = selected.length - 1; j >= 0; j--) {
  1083.         var index = selected[j];
  1084.         if (this.getListItemByIndex(index).deletable)
  1085.           this.deleteItemAtIndex(index);
  1086.       }
  1087.     },
  1088.  
  1089.     /**
  1090.      * Called when an item should be deleted; subclasses are responsible for
  1091.      * implementing.
  1092.      * @param {number} index The index of the item that is being deleted.
  1093.      */
  1094.     deleteItemAtIndex: function(index) {
  1095.     },
  1096.   };
  1097.  
  1098.   return {
  1099.     DeletableItemList: DeletableItemList,
  1100.     DeletableItem: DeletableItem,
  1101.   };
  1102. });
  1103.  
  1104. // Copyright (c) 2011 The Chromium Authors. All rights reserved.
  1105. // Use of this source code is governed by a BSD-style license that can be
  1106. // found in the LICENSE file.
  1107.  
  1108. cr.define('options', function() {
  1109.   const DeletableItem = options.DeletableItem;
  1110.   const DeletableItemList = options.DeletableItemList;
  1111.  
  1112.   /**
  1113.    * Creates a new list item with support for inline editing.
  1114.    * @constructor
  1115.    * @extends {options.DeletableListItem}
  1116.    */
  1117.   function InlineEditableItem() {
  1118.     var el = cr.doc.createElement('div');
  1119.     InlineEditableItem.decorate(el);
  1120.     return el;
  1121.   }
  1122.  
  1123.   /**
  1124.    * Decorates an element as a inline-editable list item. Note that this is
  1125.    * a subclass of DeletableItem.
  1126.    * @param {!HTMLElement} el The element to decorate.
  1127.    */
  1128.   InlineEditableItem.decorate = function(el) {
  1129.     el.__proto__ = InlineEditableItem.prototype;
  1130.     el.decorate();
  1131.   };
  1132.  
  1133.   InlineEditableItem.prototype = {
  1134.     __proto__: DeletableItem.prototype,
  1135.  
  1136.     /**
  1137.      * Whether or not this item can be edited.
  1138.      * @type {boolean}
  1139.      * @private
  1140.      */
  1141.     editable_: true,
  1142.  
  1143.     /**
  1144.      * Whether or not this is a placeholder for adding a new item.
  1145.      * @type {boolean}
  1146.      * @private
  1147.      */
  1148.     isPlaceholder_: false,
  1149.  
  1150.     /**
  1151.      * Fields associated with edit mode.
  1152.      * @type {array}
  1153.      * @private
  1154.      */
  1155.     editFields_: null,
  1156.  
  1157.     /**
  1158.      * Whether or not the current edit should be considered cancelled, rather
  1159.      * than committed, when editing ends.
  1160.      * @type {boolean}
  1161.      * @private
  1162.      */
  1163.     editCancelled_: true,
  1164.  
  1165.     /**
  1166.      * The editable item corresponding to the last click, if any. Used to decide
  1167.      * initial focus when entering edit mode.
  1168.      * @type {HTMLElement}
  1169.      * @private
  1170.      */
  1171.     editClickTarget_: null,
  1172.  
  1173.     /** @inheritDoc */
  1174.     decorate: function() {
  1175.       DeletableItem.prototype.decorate.call(this);
  1176.  
  1177.       this.editFields_ = [];
  1178.       this.addEventListener('mousedown', this.handleMouseDown_);
  1179.       this.addEventListener('keydown', this.handleKeyDown_);
  1180.       this.addEventListener('leadChange', this.handleLeadChange_);
  1181.     },
  1182.  
  1183.     /** @inheritDoc */
  1184.     selectionChanged: function() {
  1185.       this.updateEditState();
  1186.     },
  1187.  
  1188.     /**
  1189.      * Called when this element gains or loses 'lead' status. Updates editing
  1190.      * mode accordingly.
  1191.      * @private
  1192.      */
  1193.     handleLeadChange_: function() {
  1194.       this.updateEditState();
  1195.     },
  1196.  
  1197.     /**
  1198.      * Updates the edit state based on the current selected and lead states.
  1199.      */
  1200.     updateEditState: function() {
  1201.       if (this.editable)
  1202.         this.editing = this.selected && this.lead;
  1203.     },
  1204.  
  1205.     /**
  1206.      * Whether the user is currently editing the list item.
  1207.      * @type {boolean}
  1208.      */
  1209.     get editing() {
  1210.       return this.hasAttribute('editing');
  1211.     },
  1212.     set editing(editing) {
  1213.       if (this.editing == editing)
  1214.         return;
  1215.  
  1216.       if (editing)
  1217.         this.setAttribute('editing', '');
  1218.       else
  1219.         this.removeAttribute('editing');
  1220.  
  1221.       if (editing) {
  1222.         this.editCancelled_ = false;
  1223.  
  1224.         cr.dispatchSimpleEvent(this, 'edit', true);
  1225.  
  1226.         var focusElement = this.editClickTarget_ || this.initialFocusElement;
  1227.         this.editClickTarget_ = null;
  1228.  
  1229.         // When this is called in response to the selectedChange event,
  1230.         // the list grabs focus immediately afterwards. Thus we must delay
  1231.         // our focus grab.
  1232.         var self = this;
  1233.         if (focusElement) {
  1234.           window.setTimeout(function() {
  1235.             // Make sure we are still in edit mode by the time we execute.
  1236.             if (self.editing) {
  1237.               focusElement.focus();
  1238.               focusElement.select();
  1239.             }
  1240.           }, 50);
  1241.         }
  1242.       } else {
  1243.         if (!this.editCancelled_ && this.hasBeenEdited &&
  1244.             this.currentInputIsValid) {
  1245.           if (this.isPlaceholder)
  1246.             this.parentNode.focusPlaceholder = true;
  1247.  
  1248.           this.updateStaticValues_();
  1249.           cr.dispatchSimpleEvent(this, 'commitedit', true);
  1250.         } else {
  1251.           this.resetEditableValues_();
  1252.           cr.dispatchSimpleEvent(this, 'canceledit', true);
  1253.         }
  1254.       }
  1255.     },
  1256.  
  1257.     /**
  1258.      * Whether the item is editable.
  1259.      * @type {boolean}
  1260.      */
  1261.     get editable() {
  1262.       return this.editable_;
  1263.     },
  1264.     set editable(editable) {
  1265.       this.editable_ = editable;
  1266.       if (!editable)
  1267.         this.editing = false;
  1268.     },
  1269.  
  1270.     /**
  1271.      * Whether the item is a new item placeholder.
  1272.      * @type {boolean}
  1273.      */
  1274.     get isPlaceholder() {
  1275.       return this.isPlaceholder_;
  1276.     },
  1277.     set isPlaceholder(isPlaceholder) {
  1278.       this.isPlaceholder_ = isPlaceholder;
  1279.       if (isPlaceholder)
  1280.         this.deletable = false;
  1281.     },
  1282.  
  1283.     /**
  1284.      * The HTML element that should have focus initially when editing starts,
  1285.      * if a specific element wasn't clicked.
  1286.      * Defaults to the first <input> element; can be overriden by subclasses if
  1287.      * a different element should be focused.
  1288.      * @type {HTMLElement}
  1289.      */
  1290.     get initialFocusElement() {
  1291.       return this.contentElement.querySelector('input');
  1292.     },
  1293.  
  1294.     /**
  1295.      * Whether the input in currently valid to submit. If this returns false
  1296.      * when editing would be submitted, either editing will not be ended,
  1297.      * or it will be cancelled, depending on the context.
  1298.      * Can be overrided by subclasses to perform input validation.
  1299.      * @type {boolean}
  1300.      */
  1301.     get currentInputIsValid() {
  1302.       return true;
  1303.     },
  1304.  
  1305.     /**
  1306.      * Returns true if the item has been changed by an edit.
  1307.      * Can be overrided by subclasses to return false when nothing has changed
  1308.      * to avoid unnecessary commits.
  1309.      * @type {boolean}
  1310.      */
  1311.     get hasBeenEdited() {
  1312.       return true;
  1313.     },
  1314.  
  1315.     /**
  1316.      * Returns a div containing an <input>, as well as static text if
  1317.      * isPlaceholder is not true.
  1318.      * @param {string} text The text of the cell.
  1319.      * @return {HTMLElement} The HTML element for the cell.
  1320.      * @private
  1321.      */
  1322.     createEditableTextCell: function(text) {
  1323.       var container = this.ownerDocument.createElement('div');
  1324.  
  1325.       if (!this.isPlaceholder) {
  1326.         var textEl = this.ownerDocument.createElement('div');
  1327.         textEl.className = 'static-text';
  1328.         textEl.textContent = text;
  1329.         textEl.setAttribute('displaymode', 'static');
  1330.         container.appendChild(textEl);
  1331.       }
  1332.  
  1333.       var inputEl = this.ownerDocument.createElement('input');
  1334.       inputEl.type = 'text';
  1335.       inputEl.value = text;
  1336.       if (!this.isPlaceholder) {
  1337.         inputEl.setAttribute('displaymode', 'edit');
  1338.         inputEl.staticVersion = textEl;
  1339.       } else {
  1340.         // At this point |this| is not attached to the parent list yet, so give
  1341.         // a short timeout in order for the attachment to occur.
  1342.         var self = this;
  1343.         window.setTimeout(function() {
  1344.           var list = self.parentNode;
  1345.           if (list && list.focusPlaceholder) {
  1346.             list.focusPlaceholder = false;
  1347.             if (list.shouldFocusPlaceholder())
  1348.               inputEl.focus();
  1349.           }
  1350.         }, 50);
  1351.       }
  1352.  
  1353.       inputEl.addEventListener('focus', this.handleFocus_.bind(this));
  1354.       container.appendChild(inputEl);
  1355.       this.editFields_.push(inputEl);
  1356.  
  1357.       return container;
  1358.     },
  1359.  
  1360.     /**
  1361.      * Resets the editable version of any controls created by createEditable*
  1362.      * to match the static text.
  1363.      * @private
  1364.      */
  1365.     resetEditableValues_: function() {
  1366.       var editFields = this.editFields_;
  1367.       for (var i = 0; i < editFields.length; i++) {
  1368.         var staticLabel = editFields[i].staticVersion;
  1369.         if (!staticLabel && !this.isPlaceholder)
  1370.           continue;
  1371.  
  1372.         if (editFields[i].tagName == 'INPUT') {
  1373.           editFields[i].value =
  1374.             this.isPlaceholder ? '' : staticLabel.textContent;
  1375.         }
  1376.         // Add more tag types here as new createEditable* methods are added.
  1377.  
  1378.         editFields[i].setCustomValidity('');
  1379.       }
  1380.     },
  1381.  
  1382.     /**
  1383.      * Sets the static version of any controls created by createEditable*
  1384.      * to match the current value of the editable version. Called on commit so
  1385.      * that there's no flicker of the old value before the model updates.
  1386.      * @private
  1387.      */
  1388.     updateStaticValues_: function() {
  1389.       var editFields = this.editFields_;
  1390.       for (var i = 0; i < editFields.length; i++) {
  1391.         var staticLabel = editFields[i].staticVersion;
  1392.         if (!staticLabel)
  1393.           continue;
  1394.  
  1395.         if (editFields[i].tagName == 'INPUT')
  1396.           staticLabel.textContent = editFields[i].value;
  1397.         // Add more tag types here as new createEditable* methods are added.
  1398.       }
  1399.     },
  1400.  
  1401.     /**
  1402.      * Called a key is pressed. Handles committing and cancelling edits.
  1403.      * @param {Event} e The key down event.
  1404.      * @private
  1405.      */
  1406.     handleKeyDown_: function(e) {
  1407.       if (!this.editing)
  1408.         return;
  1409.  
  1410.       var endEdit = false;
  1411.       switch (e.keyIdentifier) {
  1412.         case 'U+001B':  // Esc
  1413.           this.editCancelled_ = true;
  1414.           endEdit = true;
  1415.           break;
  1416.         case 'Enter':
  1417.           if (this.currentInputIsValid)
  1418.             endEdit = true;
  1419.           break;
  1420.       }
  1421.  
  1422.       if (endEdit) {
  1423.         // Blurring will trigger the edit to end; see InlineEditableItemList.
  1424.         this.ownerDocument.activeElement.blur();
  1425.         // Make sure that handled keys aren't passed on and double-handled.
  1426.         // (e.g., esc shouldn't both cancel an edit and close a subpage)
  1427.         e.stopPropagation();
  1428.       }
  1429.     },
  1430.  
  1431.     /**
  1432.      * Called when the list item is clicked. If the click target corresponds to
  1433.      * an editable item, stores that item to focus when edit mode is started.
  1434.      * @param {Event} e The mouse down event.
  1435.      * @private
  1436.      */
  1437.     handleMouseDown_: function(e) {
  1438.       if (!this.editable || this.editing)
  1439.         return;
  1440.  
  1441.       var clickTarget = e.target;
  1442.       var editFields = this.editFields_;
  1443.       for (var i = 0; i < editFields.length; i++) {
  1444.         if (editFields[i] == clickTarget ||
  1445.             editFields[i].staticVersion == clickTarget) {
  1446.           this.editClickTarget_ = editFields[i];
  1447.           return;
  1448.         }
  1449.       }
  1450.     },
  1451.   };
  1452.  
  1453.   /**
  1454.    * Takes care of committing changes to inline editable list items when the
  1455.    * window loses focus.
  1456.    */
  1457.   function handleWindowBlurs() {
  1458.     window.addEventListener('blur', function(e) {
  1459.       var itemAncestor = findAncestor(document.activeElement, function(node) {
  1460.         return node instanceof InlineEditableItem;
  1461.       });
  1462.       if (itemAncestor);
  1463.         document.activeElement.blur();
  1464.     });
  1465.   }
  1466.   handleWindowBlurs();
  1467.  
  1468.   var InlineEditableItemList = cr.ui.define('list');
  1469.  
  1470.   InlineEditableItemList.prototype = {
  1471.     __proto__: DeletableItemList.prototype,
  1472.  
  1473.     /**
  1474.      * Focuses the input element of the placeholder if true.
  1475.      * @type {boolean}
  1476.      */
  1477.     focusPlaceholder: false,
  1478.  
  1479.     /** @inheritDoc */
  1480.     decorate: function() {
  1481.       DeletableItemList.prototype.decorate.call(this);
  1482.       this.setAttribute('inlineeditable', '');
  1483.       this.addEventListener('hasElementFocusChange',
  1484.                             this.handleListFocusChange_);
  1485.     },
  1486.  
  1487.     /**
  1488.      * Called when the list hierarchy as a whole loses or gains focus; starts
  1489.      * or ends editing for the lead item if necessary.
  1490.      * @param {Event} e The change event.
  1491.      * @private
  1492.      */
  1493.     handleListFocusChange_: function(e) {
  1494.       var leadItem = this.getListItemByIndex(this.selectionModel.leadIndex);
  1495.       if (leadItem) {
  1496.         if (e.newValue)
  1497.           leadItem.updateEditState();
  1498.         else
  1499.           leadItem.editing = false;
  1500.       }
  1501.     },
  1502.  
  1503.     /**
  1504.      * May be overridden by subclasses to disable focusing the placeholder.
  1505.      * @return true if the placeholder element should be focused on edit commit.
  1506.      */
  1507.     shouldFocusPlaceholder: function() {
  1508.       return true;
  1509.     },
  1510.   };
  1511.  
  1512.   // Export
  1513.   return {
  1514.     InlineEditableItem: InlineEditableItem,
  1515.     InlineEditableItemList: InlineEditableItemList,
  1516.   };
  1517. });
  1518.  
  1519. // Copyright (c) 2011 The Chromium Authors. All rights reserved.
  1520. // Use of this source code is governed by a BSD-style license that can be
  1521. // found in the LICENSE file.
  1522.  
  1523. cr.define('options', function() {
  1524.   var Preferences = options.Preferences;
  1525.  
  1526.   /**
  1527.    * A controlled setting indicator that can be placed on a setting as an
  1528.    * indicator that the value is controlled by some external entity such as
  1529.    * policy or an extension.
  1530.    * @constructor
  1531.    * @extends {HTMLSpanElement}
  1532.    */
  1533.   var ControlledSettingIndicator = cr.ui.define('span');
  1534.  
  1535.   ControlledSettingIndicator.prototype = {
  1536.     __proto__: HTMLSpanElement.prototype,
  1537.  
  1538.     /**
  1539.      * Decorates the base element to show the proper icon.
  1540.      */
  1541.     decorate: function() {
  1542.       var self = this;
  1543.       var doc = self.ownerDocument;
  1544.  
  1545.       // Create the details and summary elements.
  1546.       var detailsContainer = doc.createElement('details');
  1547.       detailsContainer.appendChild(doc.createElement('summary'));
  1548.  
  1549.       // This should really create a div element, but that breaks :hover. See
  1550.       // https://bugs.webkit.org/show_bug.cgi?id=72957
  1551.       var bubbleContainer = doc.createElement('p');
  1552.       bubbleContainer.className = 'controlled-setting-bubble';
  1553.       detailsContainer.appendChild(bubbleContainer);
  1554.  
  1555.       self.appendChild(detailsContainer);
  1556.  
  1557.       // If there is a pref, track its controlledBy property in order to be able
  1558.       // to bring up the correct bubble.
  1559.       if (this.hasAttribute('pref')) {
  1560.         Preferences.getInstance().addEventListener(
  1561.             this.getAttribute('pref'),
  1562.             function(event) {
  1563.               if (event.value) {
  1564.                 var controlledBy = event.value['controlledBy'];
  1565.                 self.controlledBy = controlledBy ? controlledBy : null;
  1566.               }
  1567.             });
  1568.       }
  1569.  
  1570.       self.addEventListener('click', self.show_);
  1571.     },
  1572.  
  1573.  
  1574.     /**
  1575.      * Closes the bubble.
  1576.      */
  1577.     close: function() {
  1578.       this.querySelector('details').removeAttribute('open');
  1579.       this.ownerDocument.removeEventListener('click', this.closeHandler_, true);
  1580.     },
  1581.  
  1582.     /**
  1583.      * Constructs the bubble DOM tree and shows it.
  1584.      * @private
  1585.      */
  1586.     show_: function() {
  1587.       var self = this;
  1588.       var doc = self.ownerDocument;
  1589.  
  1590.       // Clear out the old bubble contents.
  1591.       var bubbleContainer = this.querySelector('.controlled-setting-bubble');
  1592.       if (bubbleContainer) {
  1593.         while (bubbleContainer.hasChildNodes())
  1594.           bubbleContainer.removeChild(bubbleContainer.lastChild);
  1595.       }
  1596.  
  1597.       // Work out the bubble text.
  1598.       defaultStrings = {
  1599.         'policy' : localStrings.getString('controlledSettingPolicy'),
  1600.         'extension' : localStrings.getString('controlledSettingExtension'),
  1601.         'recommended' : localStrings.getString('controlledSettingRecommended'),
  1602.       };
  1603.  
  1604.       // No controller, no bubble.
  1605.       if (!self.controlledBy || !self.controlledBy in defaultStrings)
  1606.         return;
  1607.  
  1608.       var text = defaultStrings[self.controlledBy];
  1609.  
  1610.       // Apply text overrides.
  1611.       if (self.hasAttribute('text' + self.controlledBy))
  1612.         text = self.getAttribute('text' + self.controlledBy);
  1613.  
  1614.       // Create the DOM tree.
  1615.       var bubbleText = doc.createElement('p');
  1616.       bubbleText.className = 'controlled-setting-bubble-text';
  1617.       bubbleText.textContent = text;
  1618.  
  1619.       var pref = self.getAttribute('pref');
  1620.       if (self.controlledBy == 'recommended' && pref) {
  1621.         var container = doc.createElement('div');
  1622.         var action = doc.createElement('button');
  1623.         action.classList.add('link-button');
  1624.         action.classList.add('controlled-setting-bubble-action');
  1625.         action.textContent =
  1626.             localStrings.getString('controlledSettingApplyRecommendation');
  1627.         action.addEventListener(
  1628.             'click',
  1629.             function(e) {
  1630.               Preferences.clearPref(pref);
  1631.             });
  1632.         container.appendChild(action);
  1633.         bubbleText.appendChild(container);
  1634.       }
  1635.  
  1636.       bubbleContainer.appendChild(bubbleText);
  1637.  
  1638.       // One-time bubble-closing event handler.
  1639.       self.closeHandler_ = this.close.bind(this);
  1640.       doc.addEventListener('click', self.closeHandler_, true);
  1641.     }
  1642.   };
  1643.  
  1644.   /**
  1645.    * The controlling entity of the setting. Can take the values "policy",
  1646.    * "extension", "recommended" or be unset.
  1647.    */
  1648.   cr.defineProperty(ControlledSettingIndicator, 'controlledBy',
  1649.                     cr.PropertyKind.ATTR,
  1650.                     ControlledSettingIndicator.prototype.close);
  1651.  
  1652.   // Export.
  1653.   return {
  1654.     ControlledSettingIndicator : ControlledSettingIndicator
  1655.   };
  1656. });
  1657.  
  1658. // Copyright (c) 2011 The Chromium Authors. All rights reserved.
  1659. // Use of this source code is governed by a BSD-style license that can be
  1660. // found in the LICENSE file.
  1661.  
  1662. cr.define('options', function() {
  1663.   /////////////////////////////////////////////////////////////////////////////
  1664.   // OptionsPage class:
  1665.  
  1666.   /**
  1667.    * Base class for options page.
  1668.    * @constructor
  1669.    * @param {string} name Options page name, also defines id of the div element
  1670.    *     containing the options view and the name of options page navigation bar
  1671.    *     item as name+'PageNav'.
  1672.    * @param {string} title Options page title, used for navigation bar
  1673.    * @extends {EventTarget}
  1674.    */
  1675.   function OptionsPage(name, title, pageDivName) {
  1676.     this.name = name;
  1677.     this.title = title;
  1678.     this.pageDivName = pageDivName;
  1679.     this.pageDiv = $(this.pageDivName);
  1680.     this.tab = null;
  1681.   }
  1682.  
  1683.   const SUBPAGE_SHEET_COUNT = 2;
  1684.  
  1685.   /**
  1686.    * Main level option pages. Maps lower-case page names to the respective page
  1687.    * object.
  1688.    * @protected
  1689.    */
  1690.   OptionsPage.registeredPages = {};
  1691.  
  1692.   /**
  1693.    * Pages which are meant to behave like modal dialogs. Maps lower-case overlay
  1694.    * names to the respective overlay object.
  1695.    * @protected
  1696.    */
  1697.   OptionsPage.registeredOverlayPages = {};
  1698.  
  1699.   /**
  1700.    * Whether or not |initialize| has been called.
  1701.    * @private
  1702.    */
  1703.   OptionsPage.initialized_ = false;
  1704.  
  1705.   /**
  1706.    * Gets the default page (to be shown on initial load).
  1707.    */
  1708.   OptionsPage.getDefaultPage = function() {
  1709.     return BrowserOptions.getInstance();
  1710.   };
  1711.  
  1712.   /**
  1713.    * Shows the default page.
  1714.    */
  1715.   OptionsPage.showDefaultPage = function() {
  1716.     this.navigateToPage(this.getDefaultPage().name);
  1717.   };
  1718.  
  1719.   /**
  1720.    * "Navigates" to a page, meaning that the page will be shown and the
  1721.    * appropriate entry is placed in the history.
  1722.    * @param {string} pageName Page name.
  1723.    */
  1724.   OptionsPage.navigateToPage = function(pageName) {
  1725.     this.showPageByName(pageName, true);
  1726.   };
  1727.  
  1728.   /**
  1729.    * Shows a registered page. This handles both top-level pages and sub-pages.
  1730.    * @param {string} pageName Page name.
  1731.    * @param {boolean} updateHistory True if we should update the history after
  1732.    *     showing the page.
  1733.    * @private
  1734.    */
  1735.   OptionsPage.showPageByName = function(pageName, updateHistory) {
  1736.     // Find the currently visible root-level page.
  1737.     var rootPage = null;
  1738.     for (var name in this.registeredPages) {
  1739.       var page = this.registeredPages[name];
  1740.       if (page.visible && !page.parentPage) {
  1741.         rootPage = page;
  1742.         break;
  1743.       }
  1744.     }
  1745.  
  1746.     // Find the target page.
  1747.     var targetPage = this.registeredPages[pageName.toLowerCase()];
  1748.     if (!targetPage || !targetPage.canShowPage()) {
  1749.       // If it's not a page, try it as an overlay.
  1750.       if (!targetPage && this.showOverlay_(pageName, rootPage)) {
  1751.         if (updateHistory)
  1752.           this.updateHistoryState_();
  1753.         return;
  1754.       } else {
  1755.         targetPage = this.getDefaultPage();
  1756.       }
  1757.     }
  1758.  
  1759.     pageName = targetPage.name.toLowerCase();
  1760.     var targetPageWasVisible = targetPage.visible;
  1761.  
  1762.     // Determine if the root page is 'sticky', meaning that it
  1763.     // shouldn't change when showing a sub-page.  This can happen for special
  1764.     // pages like Search.
  1765.     var isRootPageLocked =
  1766.         rootPage && rootPage.sticky && targetPage.parentPage;
  1767.  
  1768.     // Notify pages if they will be hidden.
  1769.     for (var name in this.registeredPages) {
  1770.       var page = this.registeredPages[name];
  1771.       if (!page.parentPage && isRootPageLocked)
  1772.         continue;
  1773.       if (page.willHidePage && name != pageName &&
  1774.           !page.isAncestorOfPage(targetPage))
  1775.         page.willHidePage();
  1776.     }
  1777.  
  1778.     // Update visibilities to show only the hierarchy of the target page.
  1779.     for (var name in this.registeredPages) {
  1780.       var page = this.registeredPages[name];
  1781.       if (!page.parentPage && isRootPageLocked)
  1782.         continue;
  1783.       page.visible = name == pageName ||
  1784.           (!document.documentElement.classList.contains('hide-menu') &&
  1785.            page.isAncestorOfPage(targetPage));
  1786.     }
  1787.  
  1788.     // Update the history and current location.
  1789.     if (updateHistory)
  1790.       this.updateHistoryState_();
  1791.  
  1792.     // Always update the page title.
  1793.     document.title = targetPage.title;
  1794.  
  1795.     // Notify pages if they were shown.
  1796.     for (var name in this.registeredPages) {
  1797.       var page = this.registeredPages[name];
  1798.       if (!page.parentPage && isRootPageLocked)
  1799.         continue;
  1800.       if (!targetPageWasVisible && page.didShowPage && (name == pageName ||
  1801.           page.isAncestorOfPage(targetPage)))
  1802.         page.didShowPage();
  1803.     }
  1804.   };
  1805.  
  1806.   /**
  1807.    * Updates the visibility and stacking order of the subpage backdrop
  1808.    * according to which subpage is topmost and visible.
  1809.    * @private
  1810.    */
  1811.   OptionsPage.updateSubpageBackdrop_ = function () {
  1812.     var topmostPage = this.getTopmostVisibleNonOverlayPage_();
  1813.     var nestingLevel = topmostPage ? topmostPage.nestingLevel : 0;
  1814.  
  1815.     var subpageBackdrop = $('subpage-backdrop');
  1816.     if (nestingLevel > 0) {
  1817.       var container = $('subpage-sheet-container-' + nestingLevel);
  1818.       subpageBackdrop.style.zIndex =
  1819.           parseInt(window.getComputedStyle(container).zIndex) - 1;
  1820.       subpageBackdrop.hidden = false;
  1821.     } else {
  1822.       subpageBackdrop.hidden = true;
  1823.     }
  1824.   };
  1825.  
  1826.   /**
  1827.    * Scrolls the page to the correct position (the top when opening a subpage,
  1828.    * or the old scroll position a previously hidden subpage becomes visible).
  1829.    * @private
  1830.    */
  1831.   OptionsPage.updateScrollPosition_ = function () {
  1832.     var topmostPage = this.getTopmostVisibleNonOverlayPage_();
  1833.     var nestingLevel = topmostPage ? topmostPage.nestingLevel : 0;
  1834.  
  1835.     var container = (nestingLevel > 0) ?
  1836.        $('subpage-sheet-container-' + nestingLevel) : $('page-container');
  1837.  
  1838.     var scrollTop = container.oldScrollTop || 0;
  1839.     container.oldScrollTop = undefined;
  1840.     window.scroll(document.body.scrollLeft, scrollTop);
  1841.   };
  1842.  
  1843.   /**
  1844.    * Pushes the current page onto the history stack, overriding the last page
  1845.    * if it is the generic chrome://settings/.
  1846.    * @private
  1847.    */
  1848.   OptionsPage.updateHistoryState_ = function() {
  1849.     var page = this.getTopmostVisiblePage();
  1850.     var path = location.pathname;
  1851.     if (path)
  1852.       path = path.slice(1).replace(/\/$/, '');  // Remove trailing slash.
  1853.     // The page is already in history (the user may have clicked the same link
  1854.     // twice). Do nothing.
  1855.     if (path == page.name)
  1856.       return;
  1857.  
  1858.     // If there is no path, the current location is chrome://settings/.
  1859.     // Override this with the new page.
  1860.     var historyFunction = path ? window.history.pushState :
  1861.                                  window.history.replaceState;
  1862.     historyFunction.call(window.history,
  1863.                          {pageName: page.name},
  1864.                          page.title,
  1865.                          '/' + page.name);
  1866.     // Update tab title.
  1867.     document.title = page.title;
  1868.   };
  1869.  
  1870.   /**
  1871.    * Shows a registered Overlay page. Does not update history.
  1872.    * @param {string} overlayName Page name.
  1873.    * @param {OptionPage} rootPage The currently visible root-level page.
  1874.    * @return {boolean} whether we showed an overlay.
  1875.    */
  1876.   OptionsPage.showOverlay_ = function(overlayName, rootPage) {
  1877.     var overlay = this.registeredOverlayPages[overlayName.toLowerCase()];
  1878.     if (!overlay || !overlay.canShowPage())
  1879.       return false;
  1880.  
  1881.     if ((!rootPage || !rootPage.sticky) && overlay.parentPage)
  1882.       this.showPageByName(overlay.parentPage.name, false);
  1883.  
  1884.     if (!overlay.visible) {
  1885.       overlay.visible = true;
  1886.       if (overlay.didShowPage) overlay.didShowPage();
  1887.     }
  1888.  
  1889.     return true;
  1890.   };
  1891.  
  1892.   /**
  1893.    * Returns whether or not an overlay is visible.
  1894.    * @return {boolean} True if an overlay is visible.
  1895.    * @private
  1896.    */
  1897.   OptionsPage.isOverlayVisible_ = function() {
  1898.     return this.getVisibleOverlay_() != null;
  1899.   };
  1900.  
  1901.   /**
  1902.    * Returns the currently visible overlay, or null if no page is visible.
  1903.    * @return {OptionPage} The visible overlay.
  1904.    */
  1905.   OptionsPage.getVisibleOverlay_ = function() {
  1906.     for (var name in this.registeredOverlayPages) {
  1907.       var page = this.registeredOverlayPages[name];
  1908.       if (page.visible)
  1909.         return page;
  1910.     }
  1911.     return null;
  1912.   };
  1913.  
  1914.   /**
  1915.    * Closes the visible overlay. Updates the history state after closing the
  1916.    * overlay.
  1917.    */
  1918.   OptionsPage.closeOverlay = function() {
  1919.     var overlay = this.getVisibleOverlay_();
  1920.     if (!overlay)
  1921.       return;
  1922.  
  1923.     overlay.visible = false;
  1924.     if (overlay.didClosePage) overlay.didClosePage();
  1925.     this.updateHistoryState_();
  1926.   };
  1927.  
  1928.   /**
  1929.    * Hides the visible overlay. Does not affect the history state.
  1930.    * @private
  1931.    */
  1932.   OptionsPage.hideOverlay_ = function() {
  1933.     var overlay = this.getVisibleOverlay_();
  1934.     if (overlay)
  1935.       overlay.visible = false;
  1936.   };
  1937.  
  1938.   /**
  1939.    * Returns the topmost visible page (overlays excluded).
  1940.    * @return {OptionPage} The topmost visible page aside any overlay.
  1941.    * @private
  1942.    */
  1943.   OptionsPage.getTopmostVisibleNonOverlayPage_ = function() {
  1944.     var topPage = null;
  1945.     for (var name in this.registeredPages) {
  1946.       var page = this.registeredPages[name];
  1947.       if (page.visible &&
  1948.           (!topPage || page.nestingLevel > topPage.nestingLevel))
  1949.         topPage = page;
  1950.     }
  1951.  
  1952.     return topPage;
  1953.   };
  1954.  
  1955.   /**
  1956.    * Returns the topmost visible page, or null if no page is visible.
  1957.    * @return {OptionPage} The topmost visible page.
  1958.    */
  1959.   OptionsPage.getTopmostVisiblePage = function() {
  1960.     // Check overlays first since they're top-most if visible.
  1961.     return this.getVisibleOverlay_() || this.getTopmostVisibleNonOverlayPage_();
  1962.   };
  1963.  
  1964.   /**
  1965.    * Closes the topmost open subpage, if any.
  1966.    * @private
  1967.    */
  1968.   OptionsPage.closeTopSubPage_ = function() {
  1969.     var topPage = this.getTopmostVisiblePage();
  1970.     if (topPage && !topPage.isOverlay && topPage.parentPage) {
  1971.       if (topPage.willHidePage)
  1972.         topPage.willHidePage();
  1973.       topPage.visible = false;
  1974.     }
  1975.  
  1976.     this.updateHistoryState_();
  1977.   };
  1978.  
  1979.   /**
  1980.    * Closes all subpages below the given level.
  1981.    * @param {number} level The nesting level to close below.
  1982.    */
  1983.   OptionsPage.closeSubPagesToLevel = function(level) {
  1984.     var topPage = this.getTopmostVisiblePage();
  1985.     while (topPage && topPage.nestingLevel > level) {
  1986.       if (topPage.willHidePage)
  1987.         topPage.willHidePage();
  1988.       topPage.visible = false;
  1989.       topPage = topPage.parentPage;
  1990.     }
  1991.  
  1992.     this.updateHistoryState_();
  1993.   };
  1994.  
  1995.   /**
  1996.    * Updates managed banner visibility state based on the topmost page.
  1997.    */
  1998.   OptionsPage.updateManagedBannerVisibility = function() {
  1999.     var topPage = this.getTopmostVisiblePage();
  2000.     if (topPage)
  2001.       topPage.updateManagedBannerVisibility();
  2002.   };
  2003.  
  2004.   /**
  2005.   * Shows the tab contents for the given navigation tab.
  2006.   * @param {!Element} tab The tab that the user clicked.
  2007.   */
  2008.   OptionsPage.showTab = function(tab) {
  2009.     // Search parents until we find a tab, or the nav bar itself. This allows
  2010.     // tabs to have child nodes, e.g. labels in separately-styled spans.
  2011.     while (tab && !tab.classList.contains('subpages-nav-tabs') &&
  2012.            !tab.classList.contains('tab')) {
  2013.       tab = tab.parentNode;
  2014.     }
  2015.     if (!tab || !tab.classList.contains('tab'))
  2016.       return;
  2017.  
  2018.     // Find tab bar of the tab.
  2019.     var tabBar = tab;
  2020.     while (tabBar && !tabBar.classList.contains('subpages-nav-tabs')) {
  2021.       tabBar = tabBar.parentNode;
  2022.     }
  2023.     if (!tabBar)
  2024.       return;
  2025.  
  2026.     if (tabBar.activeNavTab != null) {
  2027.       tabBar.activeNavTab.classList.remove('active-tab');
  2028.       $(tabBar.activeNavTab.getAttribute('tab-contents')).classList.
  2029.           remove('active-tab-contents');
  2030.     }
  2031.  
  2032.     tab.classList.add('active-tab');
  2033.     $(tab.getAttribute('tab-contents')).classList.add('active-tab-contents');
  2034.     tabBar.activeNavTab = tab;
  2035.   };
  2036.  
  2037.   /**
  2038.    * Registers new options page.
  2039.    * @param {OptionsPage} page Page to register.
  2040.    */
  2041.   OptionsPage.register = function(page) {
  2042.     this.registeredPages[page.name.toLowerCase()] = page;
  2043.     // Create and add new page <li> element to navbar.
  2044.     var pageNav = document.createElement('li');
  2045.     pageNav.id = page.name + 'PageNav';
  2046.     pageNav.className = 'navbar-item';
  2047.     pageNav.setAttribute('pageName', page.name);
  2048.     pageNav.setAttribute('role', 'tab');
  2049.     pageNav.textContent = page.pageDiv.querySelector('h1').textContent;
  2050.     pageNav.tabIndex = -1;
  2051.     pageNav.onclick = function(event) {
  2052.       OptionsPage.navigateToPage(this.getAttribute('pageName'));
  2053.     };
  2054.     pageNav.onkeydown = function(event) {
  2055.         if ((event.keyCode == 37 || event.keyCode==38) &&
  2056.              this.previousSibling && this.previousSibling.onkeydown) {
  2057.         // Left and up arrow moves back one tab.
  2058.         OptionsPage.navigateToPage(
  2059.             this.previousSibling.getAttribute('pageName'));
  2060.         this.previousSibling.focus();
  2061.         } else if ((event.keyCode == 39 || event.keyCode == 40) &&
  2062.                     this.nextSibling) {
  2063.         // Right and down arrows move forward one tab.
  2064.         OptionsPage.navigateToPage(this.nextSibling.getAttribute('pageName'));
  2065.         this.nextSibling.focus();
  2066.       }
  2067.     };
  2068.     pageNav.onkeypress = function(event) {
  2069.       // Enter or space
  2070.       if (event.keyCode == 13 || event.keyCode == 32) {
  2071.         OptionsPage.navigateToPage(this.getAttribute('pageName'));
  2072.       }
  2073.     };
  2074.     var navbar = $('navbar');
  2075.     navbar.appendChild(pageNav);
  2076.     page.tab = pageNav;
  2077.     page.initializePage();
  2078.   };
  2079.  
  2080.   /**
  2081.    * Find an enclosing section for an element if it exists.
  2082.    * @param {Element} element Element to search.
  2083.    * @return {OptionPage} The section element, or null.
  2084.    * @private
  2085.    */
  2086.   OptionsPage.findSectionForNode_ = function(node) {
  2087.     while (node = node.parentNode) {
  2088.       if (node.nodeName == 'SECTION')
  2089.         return node;
  2090.     }
  2091.     return null;
  2092.   };
  2093.  
  2094.   /**
  2095.    * Registers a new Sub-page.
  2096.    * @param {OptionsPage} subPage Sub-page to register.
  2097.    * @param {OptionsPage} parentPage Associated parent page for this page.
  2098.    * @param {Array} associatedControls Array of control elements that lead to
  2099.    *     this sub-page. The first item is typically a button in a root-level
  2100.    *     page. There may be additional buttons for nested sub-pages.
  2101.    */
  2102.   OptionsPage.registerSubPage = function(subPage,
  2103.                                          parentPage,
  2104.                                          associatedControls) {
  2105.     this.registeredPages[subPage.name.toLowerCase()] = subPage;
  2106.     subPage.parentPage = parentPage;
  2107.     if (associatedControls) {
  2108.       subPage.associatedControls = associatedControls;
  2109.       if (associatedControls.length) {
  2110.         subPage.associatedSection =
  2111.             this.findSectionForNode_(associatedControls[0]);
  2112.       }
  2113.     }
  2114.     subPage.tab = undefined;
  2115.     subPage.initializePage();
  2116.   };
  2117.  
  2118.   /**
  2119.    * Registers a new Overlay page.
  2120.    * @param {OptionsPage} overlay Overlay to register.
  2121.    * @param {OptionsPage} parentPage Associated parent page for this overlay.
  2122.    * @param {Array} associatedControls Array of control elements associated with
  2123.    *   this page.
  2124.    */
  2125.   OptionsPage.registerOverlay = function(overlay,
  2126.                                          parentPage,
  2127.                                          associatedControls) {
  2128.     this.registeredOverlayPages[overlay.name.toLowerCase()] = overlay;
  2129.     overlay.parentPage = parentPage;
  2130.     if (associatedControls) {
  2131.       overlay.associatedControls = associatedControls;
  2132.       if (associatedControls.length) {
  2133.         overlay.associatedSection =
  2134.             this.findSectionForNode_(associatedControls[0]);
  2135.       }
  2136.     }
  2137.  
  2138.     // Reverse the button strip for views. See the documentation of
  2139.     // reverseButtonStrip_() for an explanation of why this is necessary.
  2140.     if (cr.isViews)
  2141.       this.reverseButtonStrip_(overlay);
  2142.  
  2143.     overlay.tab = undefined;
  2144.     overlay.isOverlay = true;
  2145.     overlay.initializePage();
  2146.   };
  2147.  
  2148.   /**
  2149.    * Reverses the child elements of a button strip. This is necessary because
  2150.    * WebKit does not alter the tab order for elements that are visually reversed
  2151.    * using -webkit-box-direction: reverse, and the button order is reversed for
  2152.    * views.  See https://bugs.webkit.org/show_bug.cgi?id=62664 for more
  2153.    * information.
  2154.    * @param {Object} overlay The overlay containing the button strip to reverse.
  2155.    * @private
  2156.    */
  2157.   OptionsPage.reverseButtonStrip_ = function(overlay) {
  2158.     var buttonStrips = overlay.pageDiv.querySelectorAll('.button-strip');
  2159.  
  2160.     // Reverse all button-strips in the overlay.
  2161.     for (var j = 0; j < buttonStrips.length; j++) {
  2162.       var buttonStrip = buttonStrips[j];
  2163.  
  2164.       var childNodes = buttonStrip.childNodes;
  2165.       for (var i = childNodes.length - 1; i >= 0; i--)
  2166.         buttonStrip.appendChild(childNodes[i]);
  2167.     }
  2168.   };
  2169.  
  2170.   /**
  2171.    * Callback for window.onpopstate.
  2172.    * @param {Object} data State data pushed into history.
  2173.    */
  2174.   OptionsPage.setState = function(data) {
  2175.     if (data && data.pageName) {
  2176.       // It's possible an overlay may be the last top-level page shown.
  2177.       if (this.isOverlayVisible_() &&
  2178.           !this.registeredOverlayPages[data.pageName.toLowerCase()]) {
  2179.         this.hideOverlay_();
  2180.       }
  2181.  
  2182.       this.showPageByName(data.pageName, false);
  2183.     }
  2184.   };
  2185.  
  2186.   /**
  2187.    * Callback for window.onbeforeunload. Used to notify overlays that they will
  2188.    * be closed.
  2189.    */
  2190.   OptionsPage.willClose = function() {
  2191.     var overlay = this.getVisibleOverlay_();
  2192.     if (overlay && overlay.didClosePage)
  2193.       overlay.didClosePage();
  2194.   };
  2195.  
  2196.   /**
  2197.    * Freezes/unfreezes the scroll position of given level's page container.
  2198.    * @param {boolean} freeze Whether the page should be frozen.
  2199.    * @param {number} level The level to freeze/unfreeze.
  2200.    * @private
  2201.    */
  2202.   OptionsPage.setPageFrozenAtLevel_ = function(freeze, level) {
  2203.     var container = level == 0 ? $('page-container')
  2204.                                : $('subpage-sheet-container-' + level);
  2205.  
  2206.     if (container.classList.contains('frozen') == freeze)
  2207.       return;
  2208.  
  2209.     if (freeze) {
  2210.       // Lock the width, since auto width computation may change.
  2211.       container.style.width = window.getComputedStyle(container).width;
  2212.       container.oldScrollTop = document.body.scrollTop;
  2213.       container.classList.add('frozen');
  2214.       var verticalPosition =
  2215.           container.getBoundingClientRect().top - container.oldScrollTop;
  2216.       container.style.top = verticalPosition + 'px';
  2217.       this.updateFrozenElementHorizontalPosition_(container);
  2218.     } else {
  2219.       container.classList.remove('frozen');
  2220.       container.style.top = '';
  2221.       container.style.left = '';
  2222.       container.style.right = '';
  2223.       container.style.width = '';
  2224.     }
  2225.   };
  2226.  
  2227.   /**
  2228.    * Freezes/unfreezes the scroll position of visible pages based on the current
  2229.    * page stack.
  2230.    */
  2231.   OptionsPage.updatePageFreezeStates = function() {
  2232.     var topPage = OptionsPage.getTopmostVisiblePage();
  2233.     if (!topPage)
  2234.       return;
  2235.     var nestingLevel = topPage.isOverlay ? 100 : topPage.nestingLevel;
  2236.     for (var i = 0; i <= SUBPAGE_SHEET_COUNT; i++) {
  2237.       this.setPageFrozenAtLevel_(i < nestingLevel, i);
  2238.     }
  2239.   };
  2240.  
  2241.   /**
  2242.    * Initializes the complete options page.  This will cause all C++ handlers to
  2243.    * be invoked to do final setup.
  2244.    */
  2245.   OptionsPage.initialize = function() {
  2246.     chrome.send('coreOptionsInitialize');
  2247.     this.initialized_ = true;
  2248.  
  2249.     document.addEventListener('scroll', this.handleScroll_.bind(this));
  2250.     window.addEventListener('resize', this.handleResize_.bind(this));
  2251.  
  2252.     if (!document.documentElement.classList.contains('hide-menu')) {
  2253.       // Close subpages if the user clicks on the html body. Listen in the
  2254.       // capturing phase so that we can stop the click from doing anything.
  2255.       document.body.addEventListener('click',
  2256.                                      this.bodyMouseEventHandler_.bind(this),
  2257.                                      true);
  2258.       // We also need to cancel mousedowns on non-subpage content.
  2259.       document.body.addEventListener('mousedown',
  2260.                                      this.bodyMouseEventHandler_.bind(this),
  2261.                                      true);
  2262.  
  2263.       var self = this;
  2264.       // Hook up the close buttons.
  2265.       subpageCloseButtons = document.querySelectorAll('.close-subpage');
  2266.       for (var i = 0; i < subpageCloseButtons.length; i++) {
  2267.         subpageCloseButtons[i].onclick = function() {
  2268.           self.closeTopSubPage_();
  2269.         };
  2270.       };
  2271.  
  2272.       // Install handler for key presses.
  2273.       document.addEventListener('keydown',
  2274.                                 this.keyDownEventHandler_.bind(this));
  2275.  
  2276.       document.addEventListener('focus', this.manageFocusChange_.bind(this),
  2277.                                 true);
  2278.     }
  2279.  
  2280.     // Calculate and store the horizontal locations of elements that may be
  2281.     // frozen later.
  2282.     var sidebarWidth =
  2283.         parseInt(window.getComputedStyle($('mainview')).webkitPaddingStart, 10);
  2284.     $('page-container').horizontalOffset = sidebarWidth +
  2285.         parseInt(window.getComputedStyle(
  2286.             $('mainview-content')).webkitPaddingStart, 10);
  2287.     for (var level = 1; level <= SUBPAGE_SHEET_COUNT; level++) {
  2288.       var containerId = 'subpage-sheet-container-' + level;
  2289.       $(containerId).horizontalOffset = sidebarWidth;
  2290.     }
  2291.     $('subpage-backdrop').horizontalOffset = sidebarWidth;
  2292.     // Trigger the resize handler manually to set the initial state.
  2293.     this.handleResize_(null);
  2294.   };
  2295.  
  2296.   /**
  2297.    * Does a bounds check for the element on the given x, y client coordinates.
  2298.    * @param {Element} e The DOM element.
  2299.    * @param {number} x The client X to check.
  2300.    * @param {number} y The client Y to check.
  2301.    * @return {boolean} True if the point falls within the element's bounds.
  2302.    * @private
  2303.    */
  2304.   OptionsPage.elementContainsPoint_ = function(e, x, y) {
  2305.     var clientRect = e.getBoundingClientRect();
  2306.     return x >= clientRect.left && x <= clientRect.right &&
  2307.         y >= clientRect.top && y <= clientRect.bottom;
  2308.   };
  2309.  
  2310.   /**
  2311.    * Called when focus changes; ensures that focus doesn't move outside
  2312.    * the topmost subpage/overlay.
  2313.    * @param {Event} e The focus change event.
  2314.    * @private
  2315.    */
  2316.   OptionsPage.manageFocusChange_ = function(e) {
  2317.     var focusableItemsRoot;
  2318.     var topPage = this.getTopmostVisiblePage();
  2319.     if (!topPage)
  2320.       return;
  2321.  
  2322.     if (topPage.isOverlay) {
  2323.       // If an overlay is visible, that defines the tab loop.
  2324.       focusableItemsRoot = topPage.pageDiv;
  2325.     } else {
  2326.       // If a subpage is visible, use its parent as the tab loop constraint.
  2327.       // (The parent is used because it contains the close button.)
  2328.       if (topPage.nestingLevel > 0)
  2329.         focusableItemsRoot = topPage.pageDiv.parentNode;
  2330.     }
  2331.  
  2332.     if (focusableItemsRoot && !focusableItemsRoot.contains(e.target))
  2333.       topPage.focusFirstElement();
  2334.   };
  2335.  
  2336.   /**
  2337.    * Called when the page is scrolled; moves elements that are position:fixed
  2338.    * but should only behave as if they are fixed for vertical scrolling.
  2339.    * @param {Event} e The scroll event.
  2340.    * @private
  2341.    */
  2342.   OptionsPage.handleScroll_ = function(e) {
  2343.     var scrollHorizontalOffset = document.body.scrollLeft;
  2344.     // position:fixed doesn't seem to work for horizontal scrolling in RTL mode,
  2345.     // so only adjust in LTR mode (where scroll values will be positive).
  2346.     if (scrollHorizontalOffset >= 0) {
  2347.       $('navbar-container').style.left = -scrollHorizontalOffset + 'px';
  2348.       var subpageBackdrop = $('subpage-backdrop');
  2349.       subpageBackdrop.style.left = subpageBackdrop.horizontalOffset -
  2350.           scrollHorizontalOffset + 'px';
  2351.       this.updateAllFrozenElementPositions_();
  2352.     }
  2353.   };
  2354.  
  2355.   /**
  2356.    * Updates all frozen pages to match the horizontal scroll position.
  2357.    * @private
  2358.    */
  2359.   OptionsPage.updateAllFrozenElementPositions_ = function() {
  2360.     var frozenElements = document.querySelectorAll('.frozen');
  2361.     for (var i = 0; i < frozenElements.length; i++) {
  2362.       this.updateFrozenElementHorizontalPosition_(frozenElements[i]);
  2363.     }
  2364.   };
  2365.  
  2366.   /**
  2367.    * Updates the given frozen element to match the horizontal scroll position.
  2368.    * @param {HTMLElement} e The frozen element to update
  2369.    * @private
  2370.    */
  2371.   OptionsPage.updateFrozenElementHorizontalPosition_ = function(e) {
  2372.     if (document.documentElement.dir == 'rtl')
  2373.       e.style.right = e.horizontalOffset + 'px';
  2374.     else
  2375.       e.style.left = e.horizontalOffset - document.body.scrollLeft + 'px';
  2376.   };
  2377.  
  2378.   /**
  2379.    * Called when the page is resized; adjusts the size of elements that depend
  2380.    * on the veiwport.
  2381.    * @param {Event} e The resize event.
  2382.    * @private
  2383.    */
  2384.   OptionsPage.handleResize_ = function(e) {
  2385.     // Set an explicit height equal to the viewport on all the subpage
  2386.     // containers shorter than the viewport. This is used instead of
  2387.     // min-height: 100% so that there is an explicit height for the subpages'
  2388.     // min-height: 100%.
  2389.     var viewportHeight = document.documentElement.clientHeight;
  2390.     var subpageContainers =
  2391.         document.querySelectorAll('.subpage-sheet-container');
  2392.     for (var i = 0; i < subpageContainers.length; i++) {
  2393.       if (subpageContainers[i].scrollHeight > viewportHeight)
  2394.         subpageContainers[i].style.removeProperty('height');
  2395.       else
  2396.         subpageContainers[i].style.height = viewportHeight + 'px';
  2397.     }
  2398.   };
  2399.  
  2400.   /**
  2401.    * A function to handle mouse events (mousedown or click) on the html body by
  2402.    * closing subpages and/or stopping event propagation.
  2403.    * @return {Event} a mousedown or click event.
  2404.    * @private
  2405.    */
  2406.   OptionsPage.bodyMouseEventHandler_ = function(event) {
  2407.     // Do nothing if a subpage isn't showing.
  2408.     var topPage = this.getTopmostVisiblePage();
  2409.     if (!topPage || topPage.isOverlay || !topPage.parentPage)
  2410.       return;
  2411.  
  2412.     // Don't close subpages if a user is clicking in a select element.
  2413.     // This is necessary because WebKit sends click events with strange
  2414.     // coordinates when a user selects a new entry in a select element.
  2415.     // See: http://crbug.com/87199
  2416.     if (event.srcElement.nodeName == 'SELECT')
  2417.       return;
  2418.  
  2419.     // Do nothing if the client coordinates are not within the source element.
  2420.     // This occurs if the user toggles a checkbox by pressing spacebar.
  2421.     // This is a workaround to prevent keyboard events from closing the window.
  2422.     // See: crosbug.com/15678
  2423.     if (event.clientX == -document.body.scrollLeft &&
  2424.         event.clientY == -document.body.scrollTop) {
  2425.       return;
  2426.     }
  2427.  
  2428.     // Don't interfere with navbar clicks.
  2429.     if ($('navbar').contains(event.target))
  2430.       return;
  2431.  
  2432.     // Figure out which page the click happened in.
  2433.     for (var level = topPage.nestingLevel; level >= 0; level--) {
  2434.       var clickIsWithinLevel = level == 0 ? true :
  2435.           OptionsPage.elementContainsPoint_(
  2436.               $('subpage-sheet-' + level), event.clientX, event.clientY);
  2437.  
  2438.       if (!clickIsWithinLevel)
  2439.         continue;
  2440.  
  2441.       // Event was within the topmost page; do nothing.
  2442.       if (topPage.nestingLevel == level)
  2443.         return;
  2444.  
  2445.       // Block propgation of both clicks and mousedowns, but only close subpages
  2446.       // on click.
  2447.       if (event.type == 'click')
  2448.         this.closeSubPagesToLevel(level);
  2449.       event.stopPropagation();
  2450.       event.preventDefault();
  2451.       return;
  2452.     }
  2453.   };
  2454.  
  2455.   /**
  2456.    * A function to handle key press events.
  2457.    * @return {Event} a keydown event.
  2458.    * @private
  2459.    */
  2460.   OptionsPage.keyDownEventHandler_ = function(event) {
  2461.     // Close the top overlay or sub-page on esc.
  2462.     if (event.keyCode == 27) {  // Esc
  2463.       if (this.isOverlayVisible_())
  2464.         this.closeOverlay();
  2465.       else
  2466.         this.closeTopSubPage_();
  2467.     }
  2468.   };
  2469.  
  2470.   OptionsPage.setClearPluginLSODataEnabled = function(enabled) {
  2471.     if (enabled) {
  2472.       document.documentElement.setAttribute(
  2473.           'flashPluginSupportsClearSiteData', '');
  2474.     } else {
  2475.       document.documentElement.removeAttribute(
  2476.           'flashPluginSupportsClearSiteData');
  2477.     }
  2478.   };
  2479.  
  2480.   /**
  2481.    * Re-initializes the C++ handlers if necessary. This is called if the
  2482.    * handlers are torn down and recreated but the DOM may not have been (in
  2483.    * which case |initialize| won't be called again). If |initialize| hasn't been
  2484.    * called, this does nothing (since it will be later, once the DOM has
  2485.    * finished loading).
  2486.    */
  2487.   OptionsPage.reinitializeCore = function() {
  2488.     if (this.initialized_)
  2489.       chrome.send('coreOptionsInitialize');
  2490.   }
  2491.  
  2492.   OptionsPage.prototype = {
  2493.     __proto__: cr.EventTarget.prototype,
  2494.  
  2495.     /**
  2496.      * The parent page of this option page, or null for top-level pages.
  2497.      * @type {OptionsPage}
  2498.      */
  2499.     parentPage: null,
  2500.  
  2501.     /**
  2502.      * The section on the parent page that is associated with this page.
  2503.      * Can be null.
  2504.      * @type {Element}
  2505.      */
  2506.     associatedSection: null,
  2507.  
  2508.     /**
  2509.      * An array of controls that are associated with this page.  The first
  2510.      * control should be located on a top-level page.
  2511.      * @type {OptionsPage}
  2512.      */
  2513.     associatedControls: null,
  2514.  
  2515.     /**
  2516.      * Initializes page content.
  2517.      */
  2518.     initializePage: function() {},
  2519.  
  2520.     /**
  2521.      * Updates managed banner visibility state. This function iterates over
  2522.      * all input fields of a window and if any of these is marked as managed
  2523.      * it triggers the managed banner to be visible. The banner can be enforced
  2524.      * being on through the managed flag of this class but it can not be forced
  2525.      * being off if managed items exist.
  2526.      */
  2527.     updateManagedBannerVisibility: function() {
  2528.       var bannerDiv = $('managed-prefs-banner');
  2529.  
  2530.       var controlledByPolicy = false;
  2531.       var controlledByExtension = false;
  2532.       var inputElements = this.pageDiv.querySelectorAll('input[controlled-by]');
  2533.       for (var i = 0, len = inputElements.length; i < len; i++) {
  2534.         if (inputElements[i].controlledBy == 'policy')
  2535.           controlledByPolicy = true;
  2536.         else if (inputElements[i].controlledBy == 'extension')
  2537.           controlledByExtension = true;
  2538.       }
  2539.       if (!controlledByPolicy && !controlledByExtension) {
  2540.         bannerDiv.hidden = true;
  2541.       } else {
  2542.         bannerDiv.hidden = false;
  2543.         var height = window.getComputedStyle(bannerDiv).height;
  2544.         if (controlledByPolicy && !controlledByExtension) {
  2545.           $('managed-prefs-text').textContent =
  2546.               templateData.policyManagedPrefsBannerText;
  2547.         } else if (!controlledByPolicy && controlledByExtension) {
  2548.           $('managed-prefs-text').textContent =
  2549.               templateData.extensionManagedPrefsBannerText;
  2550.         } else if (controlledByPolicy && controlledByExtension) {
  2551.           $('managed-prefs-text').textContent =
  2552.               templateData.policyAndExtensionManagedPrefsBannerText;
  2553.         }
  2554.       }
  2555.     },
  2556.  
  2557.     /**
  2558.      * Gets page visibility state.
  2559.      */
  2560.     get visible() {
  2561.       return !this.pageDiv.hidden;
  2562.     },
  2563.  
  2564.     /**
  2565.      * Sets page visibility.
  2566.      */
  2567.     set visible(visible) {
  2568.       if ((this.visible && visible) || (!this.visible && !visible))
  2569.         return;
  2570.  
  2571.       this.setContainerVisibility_(visible);
  2572.       if (visible) {
  2573.         this.pageDiv.hidden = false;
  2574.  
  2575.         if (this.tab) {
  2576.           this.tab.classList.add('navbar-item-selected');
  2577.           this.tab.setAttribute('aria-selected', 'true');
  2578.           this.tab.tabIndex = 0;
  2579.         }
  2580.       } else {
  2581.         this.pageDiv.hidden = true;
  2582.  
  2583.         if (this.tab) {
  2584.           this.tab.classList.remove('navbar-item-selected');
  2585.           this.tab.setAttribute('aria-selected', 'false');
  2586.           this.tab.tabIndex = -1;
  2587.         }
  2588.       }
  2589.  
  2590.       OptionsPage.updatePageFreezeStates();
  2591.  
  2592.       // The managed prefs banner is global, so after any visibility change
  2593.       // update it based on the topmost page, not necessarily this page
  2594.       // (e.g., if an ancestor is made visible after a child).
  2595.       OptionsPage.updateManagedBannerVisibility();
  2596.  
  2597.       // A subpage was shown or hidden.
  2598.       if (!this.isOverlay && this.nestingLevel > 0) {
  2599.         OptionsPage.updateSubpageBackdrop_();
  2600.         OptionsPage.updateScrollPosition_();
  2601.       }
  2602.  
  2603.       cr.dispatchPropertyChange(this, 'visible', visible, !visible);
  2604.     },
  2605.  
  2606.     /**
  2607.      * Shows or hides this page's container.
  2608.      * @param {boolean} visible Whether the container should be visible or not.
  2609.      * @private
  2610.      */
  2611.     setContainerVisibility_: function(visible) {
  2612.       var container = null;
  2613.       if (this.isOverlay) {
  2614.         container = $('overlay');
  2615.       } else {
  2616.         var nestingLevel = this.nestingLevel;
  2617.         if (nestingLevel > 0)
  2618.           container = $('subpage-sheet-container-' + nestingLevel);
  2619.       }
  2620.       var isSubpage = !this.isOverlay;
  2621.  
  2622.       if (!container)
  2623.         return;
  2624.  
  2625.       if (container.hidden != visible) {
  2626.         if (visible) {
  2627.           // If the container is set hidden and then immediately set visible
  2628.           // again, the fadeCompleted_ callback would cause it to be erroneously
  2629.           // hidden again. Removing the transparent tag avoids that.
  2630.           container.classList.remove('transparent');
  2631.         }
  2632.         return;
  2633.       }
  2634.  
  2635.       if (visible) {
  2636.         container.hidden = false;
  2637.         if (isSubpage) {
  2638.           var computedStyle = window.getComputedStyle(container);
  2639.           container.style.WebkitPaddingStart =
  2640.               parseInt(computedStyle.WebkitPaddingStart, 10) + 100 + 'px';
  2641.         }
  2642.         // Separate animating changes from the removal of display:none.
  2643.         window.setTimeout(function() {
  2644.           container.classList.remove('transparent');
  2645.           if (isSubpage)
  2646.             container.style.WebkitPaddingStart = '';
  2647.         });
  2648.       } else {
  2649.         var self = this;
  2650.         container.addEventListener('webkitTransitionEnd', function f(e) {
  2651.           if (e.propertyName != 'opacity')
  2652.             return;
  2653.           container.removeEventListener('webkitTransitionEnd', f);
  2654.           self.fadeCompleted_(container);
  2655.         });
  2656.         container.classList.add('transparent');
  2657.       }
  2658.     },
  2659.  
  2660.     /**
  2661.      * Called when a container opacity transition finishes.
  2662.      * @param {HTMLElement} container The container element.
  2663.      * @private
  2664.      */
  2665.     fadeCompleted_: function(container) {
  2666.       if (container.classList.contains('transparent'))
  2667.         container.hidden = true;
  2668.     },
  2669.  
  2670.     /**
  2671.      * Focuses the first control on the page.
  2672.      */
  2673.     focusFirstElement: function() {
  2674.       // Sets focus on the first interactive element in the page.
  2675.       var focusElement =
  2676.           this.pageDiv.querySelector('button, input, list, select');
  2677.       if (focusElement)
  2678.         focusElement.focus();
  2679.     },
  2680.  
  2681.     /**
  2682.      * The nesting level of this page.
  2683.      * @type {number} The nesting level of this page (0 for top-level page)
  2684.      */
  2685.     get nestingLevel() {
  2686.       var level = 0;
  2687.       var parent = this.parentPage;
  2688.       while (parent) {
  2689.         level++;
  2690.         parent = parent.parentPage;
  2691.       }
  2692.       return level;
  2693.     },
  2694.  
  2695.     /**
  2696.      * Whether the page is considered 'sticky', such that it will
  2697.      * remain a top-level page even if sub-pages change.
  2698.      * @type {boolean} True if this page is sticky.
  2699.      */
  2700.     get sticky() {
  2701.       return false;
  2702.     },
  2703.  
  2704.     /**
  2705.      * Checks whether this page is an ancestor of the given page in terms of
  2706.      * subpage nesting.
  2707.      * @param {OptionsPage} page
  2708.      * @return {boolean} True if this page is nested under |page|
  2709.      */
  2710.     isAncestorOfPage: function(page) {
  2711.       var parent = page.parentPage;
  2712.       while (parent) {
  2713.         if (parent == this)
  2714.           return true;
  2715.         parent = parent.parentPage;
  2716.       }
  2717.       return false;
  2718.     },
  2719.  
  2720.     /**
  2721.      * Whether it should be possible to show the page.
  2722.      * @return {boolean} True if the page should be shown
  2723.      */
  2724.     canShowPage: function() {
  2725.       return true;
  2726.     },
  2727.   };
  2728.  
  2729.   // Export
  2730.   return {
  2731.     OptionsPage: OptionsPage
  2732.   };
  2733. });
  2734.  
  2735.  
  2736. // Copyright (c) 2011 The Chromium Authors. All rights reserved.
  2737. // Use of this source code is governed by a BSD-style license that can be
  2738. // found in the LICENSE file.
  2739.  
  2740. cr.define('options', function() {
  2741.   const Tree = cr.ui.Tree;
  2742.   const TreeItem = cr.ui.TreeItem;
  2743.  
  2744.   /**
  2745.    * Creates a new tree item for certificate data.
  2746.    * @param {Object=} data Data used to create a certificate tree item.
  2747.    * @constructor
  2748.    * @extends {TreeItem}
  2749.    */
  2750.   function CertificateTreeItem(data) {
  2751.     // TODO(mattm): other columns
  2752.     var treeItem = new TreeItem({
  2753.       label: data.name,
  2754.       data: data
  2755.     });
  2756.     treeItem.__proto__ = CertificateTreeItem.prototype;
  2757.  
  2758.     if (data.icon) {
  2759.       treeItem.icon = data.icon;
  2760.     }
  2761.  
  2762.     if (data.untrusted) {
  2763.       var badge = document.createElement('span');
  2764.       badge.setAttribute('class', 'certUntrusted');
  2765.       badge.textContent = localStrings.getString("badgeCertUntrusted");
  2766.       treeItem.labelElement.insertBefore(
  2767.           badge, treeItem.labelElement.firstChild);
  2768.     }
  2769.  
  2770.     return treeItem;
  2771.   }
  2772.  
  2773.   CertificateTreeItem.prototype = {
  2774.     __proto__: TreeItem.prototype,
  2775.  
  2776.     /**
  2777.      * The tree path id/.
  2778.      * @type {string}
  2779.      */
  2780.     get pathId() {
  2781.       var parent = this.parentItem;
  2782.       if (parent && parent instanceof CertificateTreeItem) {
  2783.         return parent.pathId + ',' + this.data.id;
  2784.       } else {
  2785.         return this.data.id;
  2786.       }
  2787.     }
  2788.   };
  2789.  
  2790.   /**
  2791.    * Creates a new cookies tree.
  2792.    * @param {Object=} opt_propertyBag Optional properties.
  2793.    * @constructor
  2794.    * @extends {Tree}
  2795.    */
  2796.   var CertificatesTree = cr.ui.define('tree');
  2797.  
  2798.   CertificatesTree.prototype = {
  2799.     __proto__: Tree.prototype,
  2800.  
  2801.     /** @inheritDoc */
  2802.     decorate: function() {
  2803.       Tree.prototype.decorate.call(this);
  2804.       this.treeLookup_ = {};
  2805.     },
  2806.  
  2807.     /** @inheritDoc */
  2808.     addAt: function(child, index) {
  2809.       Tree.prototype.addAt.call(this, child, index);
  2810.       if (child.data && child.data.id)
  2811.         this.treeLookup_[child.data.id] = child;
  2812.     },
  2813.  
  2814.     /** @inheritDoc */
  2815.     remove: function(child) {
  2816.       Tree.prototype.remove.call(this, child);
  2817.       if (child.data && child.data.id)
  2818.         delete this.treeLookup_[child.data.id];
  2819.     },
  2820.  
  2821.     /**
  2822.      * Clears the tree.
  2823.      */
  2824.     clear: function() {
  2825.       // Remove all fields without recreating the object since other code
  2826.       // references it.
  2827.       for (var id in this.treeLookup_){
  2828.         delete this.treeLookup_[id];
  2829.       }
  2830.       this.textContent = '';
  2831.     },
  2832.  
  2833.     /**
  2834.      * Populate the tree.
  2835.      * @param {Array} nodesData Nodes data array.
  2836.      */
  2837.     populate: function(nodesData) {
  2838.       this.clear();
  2839.  
  2840.       for (var i = 0; i < nodesData.length; ++i) {
  2841.         var subnodes = nodesData[i]['subnodes'];
  2842.         delete nodesData[i]['subnodes'];
  2843.  
  2844.         var item = new CertificateTreeItem(nodesData[i]);
  2845.         this.addAt(item, i);
  2846.  
  2847.         for (var j = 0; j < subnodes.length; ++j) {
  2848.           var subitem = new CertificateTreeItem(subnodes[j]);
  2849.           item.addAt(subitem, j);
  2850.         }
  2851.         // Make tree expanded by default.
  2852.         item.expanded = true;
  2853.       }
  2854.  
  2855.       cr.dispatchSimpleEvent(this, 'change');
  2856.     },
  2857.   };
  2858.  
  2859.   return {
  2860.     CertificatesTree: CertificatesTree
  2861.   };
  2862. });
  2863.  
  2864.  
  2865.   // Copyright (c) 2011 The Chromium Authors. All rights reserved.
  2866. // Use of this source code is governed by a BSD-style license that can be
  2867. // found in the LICENSE file.
  2868.  
  2869. cr.define('options', function() {
  2870.  
  2871.   var OptionsPage = options.OptionsPage;
  2872.  
  2873.   /////////////////////////////////////////////////////////////////////////////
  2874.   // CertificateManagerTab class:
  2875.  
  2876.   /**
  2877.    * blah
  2878.    * @param {!string} id The id of this tab.
  2879.    */
  2880.   function CertificateManagerTab(id) {
  2881.     this.tree = $(id + '-tree');
  2882.  
  2883.     options.CertificatesTree.decorate(this.tree);
  2884.     this.tree.addEventListener('change',
  2885.         this.handleCertificatesTreeChange_.bind(this));
  2886.  
  2887.     var tree = this.tree;
  2888.  
  2889.     this.viewButton = $(id + '-view');
  2890.     this.viewButton.onclick = function(e) {
  2891.       var selected = tree.selectedItem;
  2892.       chrome.send('viewCertificate', [selected.data.id]);
  2893.     }
  2894.  
  2895.     this.editButton = $(id + '-edit');
  2896.     if (this.editButton !== null) {
  2897.       if (id == 'serverCertsTab') {
  2898.         this.editButton.onclick = function(e) {
  2899.           var selected = tree.selectedItem;
  2900.           chrome.send('editServerCertificate', [selected.data.id]);
  2901.         }
  2902.       } else if (id == 'caCertsTab') {
  2903.         this.editButton.onclick = function(e) {
  2904.           var data = tree.selectedItem.data;
  2905.           CertificateEditCaTrustOverlay.show(data.id, data.name);
  2906.         }
  2907.       }
  2908.     }
  2909.  
  2910.     this.backupButton = $(id + '-backup');
  2911.     if (this.backupButton !== null) {
  2912.       this.backupButton.onclick = function(e) {
  2913.         var selected = tree.selectedItem;
  2914.         chrome.send('exportPersonalCertificate', [selected.data.id]);
  2915.       }
  2916.     }
  2917.  
  2918.     this.backupAllButton = $(id + '-backup-all');
  2919.     if (this.backupAllButton !== null) {
  2920.       this.backupAllButton.onclick = function(e) {
  2921.         chrome.send('exportAllPersonalCertificates', []);
  2922.       }
  2923.     }
  2924.  
  2925.     this.importButton = $(id + '-import');
  2926.     if (this.importButton !== null) {
  2927.       if (id == 'personalCertsTab') {
  2928.         this.importButton.onclick = function(e) {
  2929.           chrome.send('importPersonalCertificate', [false]);
  2930.         }
  2931.       } else if (id == 'serverCertsTab') {
  2932.         this.importButton.onclick = function(e) {
  2933.           chrome.send('importServerCertificate', []);
  2934.         }
  2935.       } else if (id == 'caCertsTab') {
  2936.         this.importButton.onclick = function(e) {
  2937.           chrome.send('importCaCertificate', []);
  2938.         }
  2939.       }
  2940.     }
  2941.  
  2942.     this.importAndBindButton = $(id + '-import-and-bind');
  2943.     if (this.importAndBindButton !== null) {
  2944.       if (id == 'personalCertsTab') {
  2945.         this.importAndBindButton.onclick = function(e) {
  2946.           chrome.send('importPersonalCertificate', [true]);
  2947.         }
  2948.       }
  2949.     }
  2950.  
  2951.     this.exportButton = $(id + '-export');
  2952.     if (this.exportButton !== null) {
  2953.       this.exportButton.onclick = function(e) {
  2954.         var selected = tree.selectedItem;
  2955.         chrome.send('exportCertificate', [selected.data.id]);
  2956.       }
  2957.     }
  2958.  
  2959.     this.deleteButton = $(id + '-delete');
  2960.     this.deleteButton.onclick = function(e) {
  2961.       var data = tree.selectedItem.data;
  2962.       AlertOverlay.show(
  2963.           localStrings.getStringF(id + 'DeleteConfirm', data.name),
  2964.           localStrings.getString(id + 'DeleteImpact'),
  2965.           localStrings.getString('ok'),
  2966.           localStrings.getString('cancel'),
  2967.           function() {
  2968.             tree.selectedItem = null;
  2969.             chrome.send('deleteCertificate', [data.id]);
  2970.           });
  2971.     }
  2972.   }
  2973.  
  2974.   CertificateManagerTab.prototype = {
  2975.  
  2976.     /**
  2977.      * Update button state.
  2978.      * @private
  2979.      * @param {!Object} data The data of the selected item.
  2980.      */
  2981.     updateButtonState: function(data) {
  2982.       var isCert = !!data && data.id.substr(0, 5) == 'cert-';
  2983.       var readOnly = !!data && data.readonly;
  2984.       var hasChildren = this.tree.items.length > 0;
  2985.       this.viewButton.disabled = !isCert;
  2986.       if (this.editButton !== null)
  2987.         this.editButton.disabled = !isCert;
  2988.       if (this.backupButton !== null)
  2989.         this.backupButton.disabled = !isCert;
  2990.       if (this.backupAllButton !== null)
  2991.         this.backupAllButton.disabled = !hasChildren;
  2992.       if (this.exportButton !== null)
  2993.         this.exportButton.disabled = !isCert;
  2994.       this.deleteButton.disabled = !isCert || readOnly;
  2995.     },
  2996.  
  2997.     /**
  2998.      * Handles certificate tree selection change.
  2999.      * @private
  3000.      * @param {!Event} e The change event object.
  3001.      */
  3002.     handleCertificatesTreeChange_: function(e) {
  3003.       var data = null;
  3004.       if (this.tree.selectedItem) {
  3005.         data = this.tree.selectedItem.data;
  3006.       }
  3007.  
  3008.       this.updateButtonState(data);
  3009.     },
  3010.  
  3011.   }
  3012.  
  3013.   // TODO(xiyuan): Use notification from backend instead of polling.
  3014.   // TPM token check polling timer.
  3015.   var tpmPollingTimer;
  3016.  
  3017.   // Initiate tpm token check if needed.
  3018.   function checkTpmToken() {
  3019.     var importAndBindButton = $('personalCertsTab-import-and-bind');
  3020.  
  3021.     if (importAndBindButton && importAndBindButton.disabled)
  3022.       chrome.send('checkTpmTokenReady');
  3023.   }
  3024.  
  3025.   // Stop tpm polling timer.
  3026.   function stopTpmTokenCheckPolling() {
  3027.     if (tpmPollingTimer) {
  3028.       window.clearTimeout(tpmPollingTimer);
  3029.       tpmPollingTimer = undefined;
  3030.     }
  3031.   }
  3032.  
  3033.   /////////////////////////////////////////////////////////////////////////////
  3034.   // CertificateManager class:
  3035.  
  3036.   /**
  3037.    * Encapsulated handling of ChromeOS accounts options page.
  3038.    * @constructor
  3039.    */
  3040.   function CertificateManager(model) {
  3041.     OptionsPage.call(this, 'certificates',
  3042.                      templateData.certificateManagerPageTabTitle,
  3043.                      'certificateManagerPage');
  3044.   }
  3045.  
  3046.   cr.addSingletonGetter(CertificateManager);
  3047.  
  3048.   CertificateManager.prototype = {
  3049.     __proto__: OptionsPage.prototype,
  3050.  
  3051.     initializePage: function() {
  3052.       OptionsPage.prototype.initializePage.call(this);
  3053.  
  3054.       this.personalTab = new CertificateManagerTab('personalCertsTab');
  3055.       this.serverTab = new CertificateManagerTab('serverCertsTab');
  3056.       this.caTab = new CertificateManagerTab('caCertsTab');
  3057.       this.otherTab = new CertificateManagerTab('otherCertsTab');
  3058.  
  3059.       this.addEventListener('visibleChange', this.handleVisibleChange_);
  3060.     },
  3061.  
  3062.     initalized_: false,
  3063.  
  3064.     /**
  3065.      * Handler for OptionsPage's visible property change event.
  3066.      * @private
  3067.      * @param {Event} e Property change event.
  3068.      */
  3069.     handleVisibleChange_: function(e) {
  3070.       if (!this.initalized_ && this.visible) {
  3071.         this.initalized_ = true;
  3072.         OptionsPage.showTab($('personal-certs-nav-tab'));
  3073.         chrome.send('populateCertificateManager');
  3074.       }
  3075.  
  3076.       if (cr.isChromeOS) {
  3077.         // Ensure TPM token check on visible and stop polling when hidden.
  3078.         if (this.visible)
  3079.           checkTpmToken();
  3080.         else
  3081.           stopTpmTokenCheckPolling();
  3082.       }
  3083.     }
  3084.   };
  3085.  
  3086.   // CertificateManagerHandler callbacks.
  3087.   CertificateManager.onPopulateTree = function(args) {
  3088.     $(args[0]).populate(args[1]);
  3089.   };
  3090.  
  3091.   CertificateManager.exportPersonalAskPassword = function(args) {
  3092.     CertificateBackupOverlay.show();
  3093.   };
  3094.  
  3095.   CertificateManager.importPersonalAskPassword = function(args) {
  3096.     CertificateRestoreOverlay.show();
  3097.   };
  3098.  
  3099.   CertificateManager.onCheckTpmTokenReady = function(ready) {
  3100.     var importAndBindButton = $('personalCertsTab-import-and-bind');
  3101.     if (importAndBindButton) {
  3102.       importAndBindButton.disabled = !ready;
  3103.  
  3104.       // Check again after 5 seconds if Tpm is not ready and certificate manager
  3105.       // is still visible.
  3106.       if (!ready && CertificateManager.getInstance().visible)
  3107.         tpmPollingTimer = window.setTimeout(checkTpmToken, 5000);
  3108.     }
  3109.   };
  3110.  
  3111.   // Export
  3112.   return {
  3113.     CertificateManagerTab: CertificateManagerTab,
  3114.     CertificateManager: CertificateManager
  3115.   };
  3116.  
  3117. });
  3118.  
  3119.   // Copyright (c) 2011 The Chromium Authors. All rights reserved.
  3120. // Use of this source code is governed by a BSD-style license that can be
  3121. // found in the LICENSE file.
  3122.  
  3123. cr.define('options', function() {
  3124.   const OptionsPage = options.OptionsPage;
  3125.  
  3126.   /**
  3127.    * CertificateRestoreOverlay class
  3128.    * Encapsulated handling of the 'enter restore password' overlay page.
  3129.    * @class
  3130.    */
  3131.   function CertificateRestoreOverlay() {
  3132.     OptionsPage.call(this, 'certificateRestore',
  3133.                      '',
  3134.                      'certificateRestoreOverlay');
  3135.   }
  3136.  
  3137.   cr.addSingletonGetter(CertificateRestoreOverlay);
  3138.  
  3139.   CertificateRestoreOverlay.prototype = {
  3140.     __proto__: OptionsPage.prototype,
  3141.  
  3142.     /**
  3143.      * Initializes the page.
  3144.      */
  3145.     initializePage: function() {
  3146.       OptionsPage.prototype.initializePage.call(this);
  3147.  
  3148.       var self = this;
  3149.       $('certificateRestoreCancelButton').onclick = function(event) {
  3150.         self.cancelRestore_();
  3151.       }
  3152.       $('certificateRestoreOkButton').onclick = function(event) {
  3153.         self.finishRestore_();
  3154.       }
  3155.  
  3156.       self.clearInputFields_();
  3157.     },
  3158.  
  3159.     /**
  3160.      * Clears any uncommitted input, and dismisses the overlay.
  3161.      * @private
  3162.      */
  3163.     dismissOverlay_: function() {
  3164.       this.clearInputFields_();
  3165.       OptionsPage.closeOverlay();
  3166.     },
  3167.  
  3168.     /**
  3169.      * Attempt the restore operation.
  3170.      * The overlay will be left up with inputs disabled until the backend
  3171.      * finishes and dismisses it.
  3172.      * @private
  3173.      */
  3174.     finishRestore_: function() {
  3175.       chrome.send('importPersonalCertificatePasswordSelected',
  3176.                   [$('certificateRestorePassword').value]);
  3177.       $('certificateRestoreCancelButton').disabled = true;
  3178.       $('certificateRestoreOkButton').disabled = true;
  3179.     },
  3180.  
  3181.     /**
  3182.      * Cancel the restore operation.
  3183.      * @private
  3184.      */
  3185.     cancelRestore_: function() {
  3186.       chrome.send('cancelImportExportCertificate');
  3187.       this.dismissOverlay_();
  3188.     },
  3189.  
  3190.     /**
  3191.      * Clears the value of each input field.
  3192.      * @private
  3193.      */
  3194.     clearInputFields_: function() {
  3195.       $('certificateRestorePassword').value = '';
  3196.       $('certificateRestoreCancelButton').disabled = false;
  3197.       $('certificateRestoreOkButton').disabled = false;
  3198.     },
  3199.   };
  3200.  
  3201.   CertificateRestoreOverlay.show = function() {
  3202.     CertificateRestoreOverlay.getInstance().clearInputFields_();
  3203.     OptionsPage.navigateToPage('certificateRestore');
  3204.   };
  3205.  
  3206.   CertificateRestoreOverlay.dismiss = function() {
  3207.     CertificateRestoreOverlay.getInstance().dismissOverlay_();
  3208.   };
  3209.  
  3210.   // Export
  3211.   return {
  3212.     CertificateRestoreOverlay: CertificateRestoreOverlay
  3213.   };
  3214.  
  3215. });
  3216.  
  3217.   // Copyright (c) 2010 The Chromium Authors. All rights reserved.
  3218. // Use of this source code is governed by a BSD-style license that can be
  3219. // found in the LICENSE file.
  3220.  
  3221. cr.define('options', function() {
  3222.   const OptionsPage = options.OptionsPage;
  3223.  
  3224.   /**
  3225.    * CertificateBackupOverlay class
  3226.    * Encapsulated handling of the 'enter backup password' overlay page.
  3227.    * @class
  3228.    */
  3229.   function CertificateBackupOverlay() {
  3230.     OptionsPage.call(this, 'certificateBackupOverlay',
  3231.                      '',
  3232.                      'certificateBackupOverlay');
  3233.   }
  3234.  
  3235.   cr.addSingletonGetter(CertificateBackupOverlay);
  3236.  
  3237.   CertificateBackupOverlay.prototype = {
  3238.     __proto__: OptionsPage.prototype,
  3239.  
  3240.     /**
  3241.      * Initializes the page.
  3242.      */
  3243.     initializePage: function() {
  3244.       OptionsPage.prototype.initializePage.call(this);
  3245.  
  3246.       var self = this;
  3247.       $('certificateBackupCancelButton').onclick = function(event) {
  3248.         self.cancelBackup_();
  3249.       }
  3250.       $('certificateBackupOkButton').onclick = function(event) {
  3251.         self.finishBackup_();
  3252.       }
  3253.       $('certificateBackupPassword').oninput =
  3254.       $('certificateBackupPassword2').oninput = function(event) {
  3255.         self.comparePasswords_();
  3256.       }
  3257.  
  3258.       self.clearInputFields_();
  3259.     },
  3260.  
  3261.     /**
  3262.      * Clears any uncommitted input, and dismisses the overlay.
  3263.      * @private
  3264.      */
  3265.     dismissOverlay_: function() {
  3266.       this.clearInputFields_();
  3267.       OptionsPage.closeOverlay();
  3268.     },
  3269.  
  3270.     /**
  3271.      * Attempt the Backup operation.
  3272.      * The overlay will be left up with inputs disabled until the backend
  3273.      * finishes and dismisses it.
  3274.      * @private
  3275.      */
  3276.     finishBackup_: function() {
  3277.       chrome.send('exportPersonalCertificatePasswordSelected',
  3278.                   [$('certificateBackupPassword').value]);
  3279.       $('certificateBackupCancelButton').disabled = true;
  3280.       $('certificateBackupOkButton').disabled = true;
  3281.       $('certificateBackupPassword').disabled = true;
  3282.       $('certificateBackupPassword2').disabled = true;
  3283.     },
  3284.  
  3285.     /**
  3286.      * Cancel the Backup operation.
  3287.      * @private
  3288.      */
  3289.     cancelBackup_: function() {
  3290.       chrome.send('cancelImportExportCertificate');
  3291.       this.dismissOverlay_();
  3292.     },
  3293.  
  3294.     /**
  3295.      * Compares the password fields and sets the button state appropriately.
  3296.      * @private
  3297.      */
  3298.     comparePasswords_: function() {
  3299.       var password1 = $('certificateBackupPassword').value;
  3300.       var password2 = $('certificateBackupPassword2').value;
  3301.       $('certificateBackupOkButton').disabled =
  3302.           !password1 || password1 != password2;
  3303.     },
  3304.  
  3305.     /**
  3306.      * Clears the value of each input field.
  3307.      * @private
  3308.      */
  3309.     clearInputFields_: function() {
  3310.       $('certificateBackupPassword').value = '';
  3311.       $('certificateBackupPassword2').value = '';
  3312.       $('certificateBackupPassword').disabled = false;
  3313.       $('certificateBackupPassword2').disabled = false;
  3314.       $('certificateBackupCancelButton').disabled = false;
  3315.       $('certificateBackupOkButton').disabled = true;
  3316.     },
  3317.   };
  3318.  
  3319.   CertificateBackupOverlay.show = function() {
  3320.     CertificateBackupOverlay.getInstance().clearInputFields_();
  3321.     OptionsPage.navigateToPage('certificateBackupOverlay');
  3322.   };
  3323.  
  3324.   CertificateBackupOverlay.dismiss = function() {
  3325.     CertificateBackupOverlay.getInstance().dismissOverlay_();
  3326.   };
  3327.  
  3328.   // Export
  3329.   return {
  3330.     CertificateBackupOverlay: CertificateBackupOverlay
  3331.   };
  3332. });
  3333.  
  3334.   // Copyright (c) 2011 The Chromium Authors. All rights reserved.
  3335. // Use of this source code is governed by a BSD-style license that can be
  3336. // found in the LICENSE file.
  3337.  
  3338. cr.define('options', function() {
  3339.   const OptionsPage = options.OptionsPage;
  3340.  
  3341.   /**
  3342.    * CertificateEditCaTrustOverlay class
  3343.    * Encapsulated handling of the 'edit ca trust' and 'import ca' overlay pages.
  3344.    * @class
  3345.    */
  3346.   function CertificateEditCaTrustOverlay() {
  3347.     OptionsPage.call(this, 'certificateEditCaTrustOverlay',
  3348.                      '',
  3349.                      'certificateEditCaTrustOverlay');
  3350.   }
  3351.  
  3352.   cr.addSingletonGetter(CertificateEditCaTrustOverlay);
  3353.  
  3354.   CertificateEditCaTrustOverlay.prototype = {
  3355.     __proto__: OptionsPage.prototype,
  3356.  
  3357.     /**
  3358.      * Dismisses the overlay.
  3359.      * @private
  3360.      */
  3361.     dismissOverlay_: function() {
  3362.       OptionsPage.closeOverlay();
  3363.     },
  3364.  
  3365.     /**
  3366.      * Enables or disables input fields.
  3367.      * @private
  3368.      */
  3369.     enableInputs_: function(enabled) {
  3370.       $('certificateCaTrustSSLCheckbox').disabled =
  3371.       $('certificateCaTrustEmailCheckbox').disabled =
  3372.       $('certificateCaTrustObjSignCheckbox').disabled =
  3373.       $('certificateEditCaTrustCancelButton').disabled =
  3374.       $('certificateEditCaTrustOkButton').disabled = !enabled;
  3375.     },
  3376.  
  3377.     /**
  3378.      * Attempt the Edit operation.
  3379.      * The overlay will be left up with inputs disabled until the backend
  3380.      * finishes and dismisses it.
  3381.      * @private
  3382.      */
  3383.     finishEdit_: function() {
  3384.       // TODO(mattm): Send checked values as booleans.  For now send them as
  3385.       // strings, since WebUIBindings::send does not support any other types :(
  3386.       chrome.send('editCaCertificateTrust',
  3387.                   [this.certId,
  3388.                    $('certificateCaTrustSSLCheckbox').checked.toString(),
  3389.                    $('certificateCaTrustEmailCheckbox').checked.toString(),
  3390.                    $('certificateCaTrustObjSignCheckbox').checked.toString()]);
  3391.       this.enableInputs_(false);
  3392.     },
  3393.  
  3394.     /**
  3395.      * Cancel the Edit operation.
  3396.      * @private
  3397.      */
  3398.     cancelEdit_: function() {
  3399.       this.dismissOverlay_();
  3400.     },
  3401.  
  3402.     /**
  3403.      * Attempt the Import operation.
  3404.      * The overlay will be left up with inputs disabled until the backend
  3405.      * finishes and dismisses it.
  3406.      * @private
  3407.      */
  3408.     finishImport_: function() {
  3409.       // TODO(mattm): Send checked values as booleans.  For now send them as
  3410.       // strings, since WebUIBindings::send does not support any other types :(
  3411.       chrome.send('importCaCertificateTrustSelected',
  3412.                   [$('certificateCaTrustSSLCheckbox').checked.toString(),
  3413.                    $('certificateCaTrustEmailCheckbox').checked.toString(),
  3414.                    $('certificateCaTrustObjSignCheckbox').checked.toString()]);
  3415.       this.enableInputs_(false);
  3416.     },
  3417.  
  3418.     /**
  3419.      * Cancel the Import operation.
  3420.      * @private
  3421.      */
  3422.     cancelImport_: function() {
  3423.       chrome.send('cancelImportExportCertificate');
  3424.       this.dismissOverlay_();
  3425.     },
  3426.   };
  3427.  
  3428.   /**
  3429.    * Callback from CertificateManagerHandler with the trust values.
  3430.    * @param {boolean} trustSSL The initial value of SSL trust checkbox.
  3431.    * @param {boolean} trustEmail The initial value of Email trust checkbox.
  3432.    * @param {boolean} trustObjSign The initial value of Object Signing trust
  3433.    */
  3434.   CertificateEditCaTrustOverlay.populateTrust = function(
  3435.       trustSSL, trustEmail, trustObjSign) {
  3436.     $('certificateCaTrustSSLCheckbox').checked = trustSSL;
  3437.     $('certificateCaTrustEmailCheckbox').checked = trustEmail;
  3438.     $('certificateCaTrustObjSignCheckbox').checked = trustObjSign;
  3439.     CertificateEditCaTrustOverlay.getInstance().enableInputs_(true);
  3440.   }
  3441.  
  3442.   /**
  3443.    * Show the Edit CA Trust overlay.
  3444.    * @param {string} certId The id of the certificate to be passed to the
  3445.    * certificate manager model.
  3446.    * @param {string} certName The display name of the certificate.
  3447.    * checkbox.
  3448.    */
  3449.   CertificateEditCaTrustOverlay.show = function(certId, certName) {
  3450.     var self = CertificateEditCaTrustOverlay.getInstance();
  3451.     self.certId = certId;
  3452.     $('certificateEditCaTrustCancelButton').onclick = function(event) {
  3453.       self.cancelEdit_();
  3454.     }
  3455.     $('certificateEditCaTrustOkButton').onclick = function(event) {
  3456.       self.finishEdit_();
  3457.     }
  3458.     $('certificateEditCaTrustDescription').textContent =
  3459.         localStrings.getStringF('certificateEditCaTrustDescriptionFormat',
  3460.                                 certName);
  3461.     self.enableInputs_(false);
  3462.     OptionsPage.navigateToPage('certificateEditCaTrustOverlay');
  3463.     chrome.send('getCaCertificateTrust', [certId]);
  3464.   }
  3465.  
  3466.   /**
  3467.    * Show the Import CA overlay.
  3468.    * @param {string} certId The id of the certificate to be passed to the
  3469.    * certificate manager model.
  3470.    * @param {string} certName The display name of the certificate.
  3471.    * checkbox.
  3472.    */
  3473.   CertificateEditCaTrustOverlay.showImport = function(certName) {
  3474.     var self = CertificateEditCaTrustOverlay.getInstance();
  3475.     // TODO(mattm): do we want a view certificate button here like firefox has?
  3476.     $('certificateEditCaTrustCancelButton').onclick = function(event) {
  3477.       self.cancelImport_();
  3478.     }
  3479.     $('certificateEditCaTrustOkButton').onclick = function(event) {
  3480.       self.finishImport_();
  3481.     }
  3482.     $('certificateEditCaTrustDescription').textContent =
  3483.         localStrings.getStringF('certificateImportCaDescriptionFormat',
  3484.                                 certName);
  3485.     CertificateEditCaTrustOverlay.populateTrust(false, false, false);
  3486.     OptionsPage.navigateToPage('certificateEditCaTrustOverlay');
  3487.   }
  3488.  
  3489.   CertificateEditCaTrustOverlay.dismiss = function() {
  3490.     CertificateEditCaTrustOverlay.getInstance().dismissOverlay_();
  3491.   };
  3492.  
  3493.   // Export
  3494.   return {
  3495.     CertificateEditCaTrustOverlay: CertificateEditCaTrustOverlay
  3496.   };
  3497. });
  3498.  
  3499.   // Copyright (c) 2010 The Chromium Authors. All rights reserved.
  3500. // Use of this source code is governed by a BSD-style license that can be
  3501. // found in the LICENSE file.
  3502.  
  3503. cr.define('options', function() {
  3504.  
  3505.   var OptionsPage = options.OptionsPage;
  3506.  
  3507.   /**
  3508.    * CertificateImportErrorOverlay class
  3509.    * Displays a list of certificates and errors.
  3510.    * @class
  3511.    */
  3512.   function CertificateImportErrorOverlay() {
  3513.     OptionsPage.call(this, 'certificateImportErrorOverlay', '',
  3514.                      'certificateImportErrorOverlay');
  3515.   }
  3516.  
  3517.   cr.addSingletonGetter(CertificateImportErrorOverlay);
  3518.  
  3519.   CertificateImportErrorOverlay.prototype = {
  3520.     // Inherit CertificateImportErrorOverlay from OptionsPage.
  3521.     __proto__: OptionsPage.prototype,
  3522.  
  3523.     /**
  3524.      * Initialize the page.
  3525.      */
  3526.     initializePage: function() {
  3527.       // Call base class implementation to start preference initialization.
  3528.       OptionsPage.prototype.initializePage.call(this);
  3529.  
  3530.       $('certificateImportErrorOverlayOk').onclick = function(event) {
  3531.         OptionsPage.closeOverlay();
  3532.       };
  3533.     },
  3534.   };
  3535.  
  3536.   /**
  3537.    * Show an alert overlay with the given message, button titles, and
  3538.    * callbacks.
  3539.    * @param {string} title The alert title to display to the user.
  3540.    * @param {string} message The alert message to display to the user.
  3541.    * @param {Array} certErrors The list of cert errors.  Each error should have
  3542.    *                           a .name and .error attribute.
  3543.    */
  3544.   CertificateImportErrorOverlay.show = function(title, message, certErrors) {
  3545.     $('certificateImportErrorOverlayTitle').textContent = title;
  3546.     $('certificateImportErrorOverlayMessage').textContent = message;
  3547.  
  3548.     ul = $('certificateImportErrorOverlayCertErrors');
  3549.     ul.innerHTML = '';
  3550.     for (var i = 0; i < certErrors.length; ++i) {
  3551.       li = document.createElement("li");
  3552.       li.textContent = localStrings.getStringF('certificateImportErrorFormat',
  3553.                                                certErrors[i].name,
  3554.                                                certErrors[i].error);
  3555.       ul.appendChild(li);
  3556.     }
  3557.  
  3558.     OptionsPage.navigateToPage('certificateImportErrorOverlay');
  3559.   }
  3560.  
  3561.   // Export
  3562.   return {
  3563.     CertificateImportErrorOverlay: CertificateImportErrorOverlay
  3564.   };
  3565.  
  3566. });
  3567.  
  3568.   var CertificateManager = options.CertificateManager;
  3569.   var CertificateRestoreOverlay = options.CertificateRestoreOverlay;
  3570.   var CertificateBackupOverlay = options.CertificateBackupOverlay;
  3571.   var CertificateEditCaTrustOverlay = options.CertificateEditCaTrustOverlay;
  3572.   var CertificateImportErrorOverlay = options.CertificateImportErrorOverlay;
  3573. // Copyright (c) 2011 The Chromium Authors. All rights reserved.
  3574. // Use of this source code is governed by a BSD-style license that can be
  3575. // found in the LICENSE file.
  3576.  
  3577. cr.define('options', function() {
  3578.  
  3579. var OptionsPage = options.OptionsPage;
  3580.  
  3581.   //
  3582.   // AdvancedOptions class
  3583.   // Encapsulated handling of advanced options page.
  3584.   //
  3585.   function AdvancedOptions() {
  3586.     OptionsPage.call(this, 'advanced', templateData.advancedPageTabTitle,
  3587.                      'advancedPage');
  3588.   }
  3589.  
  3590.   cr.addSingletonGetter(AdvancedOptions);
  3591.  
  3592.   AdvancedOptions.prototype = {
  3593.     // Inherit AdvancedOptions from OptionsPage.
  3594.     __proto__: options.OptionsPage.prototype,
  3595.  
  3596.     /**
  3597.      * Initializes the page.
  3598.      */
  3599.     initializePage: function() {
  3600.       // Call base class implementation to starts preference initialization.
  3601.       OptionsPage.prototype.initializePage.call(this);
  3602.  
  3603.       // Set up click handlers for buttons.
  3604.       $('privacyContentSettingsButton').onclick = function(event) {
  3605.         OptionsPage.navigateToPage('content');
  3606.         OptionsPage.showTab($('cookies-nav-tab'));
  3607.         chrome.send('coreOptionsUserMetricsAction',
  3608.             ['Options_ContentSettings']);
  3609.       };
  3610.       $('privacyClearDataButton').onclick = function(event) {
  3611.         OptionsPage.navigateToPage('clearBrowserData');
  3612.         chrome.send('coreOptionsUserMetricsAction', ['Options_ClearData']);
  3613.       };
  3614.  
  3615.       // 'metricsReportingEnabled' element is only present on Chrome branded
  3616.       // builds.
  3617.       if ($('metricsReportingEnabled')) {
  3618.         $('metricsReportingEnabled').onclick = function(event) {
  3619.           chrome.send('metricsReportingCheckboxAction',
  3620.               [String(event.target.checked)]);
  3621.         };
  3622.       }
  3623.  
  3624.       if (!cr.isChromeOS) {
  3625.         $('autoOpenFileTypesResetToDefault').onclick = function(event) {
  3626.           chrome.send('autoOpenFileTypesAction');
  3627.         };
  3628.       }
  3629.  
  3630.       $('fontSettingsCustomizeFontsButton').onclick = function(event) {
  3631.         OptionsPage.navigateToPage('fonts');
  3632.         chrome.send('coreOptionsUserMetricsAction', ['Options_FontSettings']);
  3633.       };
  3634.       $('defaultFontSize').onchange = function(event) {
  3635.         chrome.send('defaultFontSizeAction',
  3636.             [String(event.target.options[event.target.selectedIndex].value)]);
  3637.       };
  3638.       $('defaultZoomFactor').onchange = function(event) {
  3639.         chrome.send('defaultZoomFactorAction',
  3640.             [String(event.target.options[event.target.selectedIndex].value)]);
  3641.       };
  3642.  
  3643.       $('language-button').onclick = function(event) {
  3644.         OptionsPage.navigateToPage('languages');
  3645.         chrome.send('coreOptionsUserMetricsAction',
  3646.             ['Options_LanuageAndSpellCheckSettings']);
  3647.       };
  3648.  
  3649.       if (cr.isWindows || cr.isMac) {
  3650.         $('certificatesManageButton').onclick = function(event) {
  3651.           chrome.send('showManageSSLCertificates');
  3652.         };
  3653.       } else {
  3654.         $('certificatesManageButton').onclick = function(event) {
  3655.           OptionsPage.navigateToPage('certificates');
  3656.           chrome.send('coreOptionsUserMetricsAction',
  3657.                       ['Options_ManageSSLCertificates']);
  3658.         };
  3659.       }
  3660.  
  3661.       if (!cr.isChromeOS) {
  3662.         $('proxiesConfigureButton').onclick = function(event) {
  3663.           chrome.send('showNetworkProxySettings');
  3664.         };
  3665.         $('downloadLocationChangeButton').onclick = function(event) {
  3666.           chrome.send('selectDownloadLocation');
  3667.         };
  3668.         // This text field is always disabled. Setting ".disabled = true" isn't
  3669.         // enough, since a policy can disable it but shouldn't re-enable when
  3670.         // it is removed.
  3671.         $('downloadLocationPath').setDisabled('readonly', true);
  3672.       }
  3673.  
  3674.       $('sslCheckRevocation').onclick = function(event) {
  3675.         chrome.send('checkRevocationCheckboxAction',
  3676.             [String($('sslCheckRevocation').checked)]);
  3677.       };
  3678.  
  3679.       if ($('backgroundModeCheckbox')) {
  3680.         $('backgroundModeCheckbox').onclick = function(event) {
  3681.           chrome.send('backgroundModeAction',
  3682.               [String($('backgroundModeCheckbox').checked)]);
  3683.         };
  3684.       }
  3685.  
  3686.       // 'cloudPrintProxyEnabled' is true for Chrome branded builds on
  3687.       // certain platforms, or could be enabled by a lab.
  3688.       if (!cr.isChromeOS) {
  3689.         $('cloudPrintProxySetupButton').onclick = function(event) {
  3690.           if ($('cloudPrintProxyManageButton').style.display == 'none') {
  3691.             // Disable the button, set it's text to the intermediate state.
  3692.             $('cloudPrintProxySetupButton').textContent =
  3693.               localStrings.getString('cloudPrintProxyEnablingButton');
  3694.             $('cloudPrintProxySetupButton').disabled = true;
  3695.             chrome.send('showCloudPrintSetupDialog');
  3696.           } else {
  3697.             chrome.send('disableCloudPrintProxy');
  3698.           }
  3699.         };
  3700.       }
  3701.       $('cloudPrintProxyManageButton').onclick = function(event) {
  3702.         chrome.send('showCloudPrintManagePage');
  3703.       };
  3704.  
  3705.   }
  3706.   };
  3707.  
  3708.   //
  3709.   // Chrome callbacks
  3710.   //
  3711.  
  3712.   // Set the checked state of the metrics reporting checkbox.
  3713.   AdvancedOptions.SetMetricsReportingCheckboxState = function(
  3714.       checked, disabled) {
  3715.     $('metricsReportingEnabled').checked = checked;
  3716.     $('metricsReportingEnabled').disabled = disabled;
  3717.     if (disabled)
  3718.       $('metricsReportingEnabledText').className = 'disable-services-span';
  3719.   }
  3720.  
  3721.   AdvancedOptions.SetMetricsReportingSettingVisibility = function(visible) {
  3722.     if (visible) {
  3723.       $('metricsReportingSetting').style.display = 'block';
  3724.     } else {
  3725.       $('metricsReportingSetting').style.display = 'none';
  3726.     }
  3727.   }
  3728.  
  3729.   // Set the font size selected item.
  3730.   AdvancedOptions.SetFontSize = function(font_size_value) {
  3731.     var selectCtl = $('defaultFontSize');
  3732.     for (var i = 0; i < selectCtl.options.length; i++) {
  3733.       if (selectCtl.options[i].value == font_size_value) {
  3734.         selectCtl.selectedIndex = i;
  3735.         if ($('Custom'))
  3736.           selectCtl.remove($('Custom').index);
  3737.         return;
  3738.       }
  3739.     }
  3740.  
  3741.     // Add/Select Custom Option in the font size label list.
  3742.     if (!$('Custom')) {
  3743.       var option = new Option(localStrings.getString('fontSizeLabelCustom'),
  3744.                               -1, false, true);
  3745.       option.setAttribute("id", "Custom");
  3746.       selectCtl.add(option);
  3747.     }
  3748.     $('Custom').selected = true;
  3749.   };
  3750.  
  3751.   /**
  3752.     * Populate the page zoom selector with values received from the caller.
  3753.     * @param {Array} items An array of items to populate the selector.
  3754.     *     each object is an array with three elements as follows:
  3755.     *       0: The title of the item (string).
  3756.     *       1: The value of the item (number).
  3757.     *       2: Whether the item should be selected (boolean).
  3758.     */
  3759.   AdvancedOptions.SetupPageZoomSelector = function(items) {
  3760.     var element = $('defaultZoomFactor');
  3761.  
  3762.     // Remove any existing content.
  3763.     element.textContent = '';
  3764.  
  3765.     // Insert new child nodes into select element.
  3766.     var value, title, selected;
  3767.     for (var i = 0; i < items.length; i++) {
  3768.       title = items[i][0];
  3769.       value = items[i][1];
  3770.       selected = items[i][2];
  3771.       element.appendChild(new Option(title, value, false, selected));
  3772.     }
  3773.   };
  3774.  
  3775.   // Set the enabled state for the autoOpenFileTypesResetToDefault button.
  3776.   AdvancedOptions.SetAutoOpenFileTypesDisabledAttribute = function(disabled) {
  3777.     if (!cr.isChromeOS) {
  3778.       $('autoOpenFileTypesResetToDefault').disabled = disabled;
  3779.  
  3780.       if (disabled)
  3781.         $('auto-open-file-types-label').classList.add('disabled');
  3782.       else
  3783.         $('auto-open-file-types-label').classList.remove('disabled');
  3784.     }
  3785.   };
  3786.  
  3787.   // Set the enabled state for the proxy settings button.
  3788.   AdvancedOptions.SetupProxySettingsSection = function(disabled, label) {
  3789.     if (!cr.isChromeOS) {
  3790.       $('proxiesConfigureButton').disabled = disabled;
  3791.       $('proxiesLabel').textContent = label;
  3792.     }
  3793.   };
  3794.  
  3795.   // Set the checked state for the sslCheckRevocation checkbox.
  3796.   AdvancedOptions.SetCheckRevocationCheckboxState = function(
  3797.       checked, disabled) {
  3798.     $('sslCheckRevocation').checked = checked;
  3799.     $('sslCheckRevocation').disabled = disabled;
  3800.   };
  3801.  
  3802.   // Set the checked state for the backgroundModeCheckbox element.
  3803.   AdvancedOptions.SetBackgroundModeCheckboxState = function(checked) {
  3804.     $('backgroundModeCheckbox').checked = checked;
  3805.   };
  3806.  
  3807.   // Set the Cloud Print proxy UI to enabled, disabled, or processing.
  3808.   AdvancedOptions.SetupCloudPrintProxySection = function(
  3809.         disabled, label, allowed) {
  3810.     if (!cr.isChromeOS) {
  3811.       $('cloudPrintProxyLabel').textContent = label;
  3812.       if (disabled || !allowed) {
  3813.         $('cloudPrintProxySetupButton').textContent =
  3814.           localStrings.getString('cloudPrintProxyDisabledButton');
  3815.         $('cloudPrintProxyManageButton').style.display = 'none';
  3816.       } else {
  3817.         $('cloudPrintProxySetupButton').textContent =
  3818.           localStrings.getString('cloudPrintProxyEnabledButton');
  3819.         $('cloudPrintProxyManageButton').style.display = 'inline';
  3820.       }
  3821.       $('cloudPrintProxySetupButton').disabled = !allowed;
  3822.     }
  3823.   };
  3824.  
  3825.   AdvancedOptions.RemoveCloudPrintProxySection = function() {
  3826.     if (!cr.isChromeOS) {
  3827.       var proxySectionElm = $('cloud-print-proxy-section');
  3828.       if (proxySectionElm)
  3829.         proxySectionElm.parentNode.removeChild(proxySectionElm);
  3830.     }
  3831.   };
  3832.  
  3833.   // Export
  3834.   return {
  3835.     AdvancedOptions: AdvancedOptions
  3836.   };
  3837.  
  3838. });
  3839.  
  3840. // Copyright (c) 2010 The Chromium Authors. All rights reserved.
  3841. // Use of this source code is governed by a BSD-style license that can be
  3842. // found in the LICENSE file.
  3843.  
  3844. cr.define('options', function() {
  3845.   var OptionsPage = options.OptionsPage;
  3846.  
  3847.   /**
  3848.    * AlertOverlay class
  3849.    * Encapsulated handling of a generic alert.
  3850.    * @class
  3851.    */
  3852.   function AlertOverlay() {
  3853.     OptionsPage.call(this, 'alertOverlay', '', 'alertOverlay');
  3854.   }
  3855.  
  3856.   cr.addSingletonGetter(AlertOverlay);
  3857.  
  3858.   AlertOverlay.prototype = {
  3859.     // Inherit AlertOverlay from OptionsPage.
  3860.     __proto__: OptionsPage.prototype,
  3861.  
  3862.     /**
  3863.      * Whether the page can be shown. Used to make sure the page is only
  3864.      * shown via AlertOverlay.Show(), and not via the address bar.
  3865.      * @private
  3866.      */
  3867.     canShow_: false,
  3868.  
  3869.     /**
  3870.      * Initialize the page.
  3871.      */
  3872.     initializePage: function() {
  3873.       // Call base class implementation to start preference initialization.
  3874.       OptionsPage.prototype.initializePage.call(this);
  3875.  
  3876.       var self = this;
  3877.       $('alertOverlayOk').onclick = function(event) {
  3878.         self.handleOK_();
  3879.       };
  3880.  
  3881.       $('alertOverlayCancel').onclick = function(event) {
  3882.         self.handleCancel_();
  3883.       };
  3884.     },
  3885.  
  3886.     /**
  3887.      * Handle the 'ok' button.  Clear the overlay and call the ok callback if
  3888.      * available.
  3889.      * @private
  3890.      */
  3891.     handleOK_: function() {
  3892.       OptionsPage.closeOverlay();
  3893.       if (this.okCallback != undefined) {
  3894.         this.okCallback.call();
  3895.       }
  3896.     },
  3897.  
  3898.     /**
  3899.      * Handle the 'cancel' button.  Clear the overlay and call the cancel
  3900.      * callback if available.
  3901.      * @private
  3902.      */
  3903.     handleCancel_: function() {
  3904.       OptionsPage.closeOverlay();
  3905.       if (this.cancelCallback != undefined) {
  3906.         this.cancelCallback.call();
  3907.       }
  3908.     },
  3909.  
  3910.     /**
  3911.      * The page is getting hidden. Don't let it be shown again.
  3912.      */
  3913.     willHidePage: function() {
  3914.       canShow_ = false;
  3915.     },
  3916.  
  3917.     /** @inheritDoc */
  3918.     canShowPage: function() {
  3919.       return this.canShow_;
  3920.     },
  3921.   };
  3922.  
  3923.   /**
  3924.    * Show an alert overlay with the given message, button titles, and
  3925.    * callbacks.
  3926.    * @param {string} title The alert title to display to the user.
  3927.    * @param {string} message The alert message to display to the user.
  3928.    * @param {string} okTitle The title of the OK button. If undefined or empty,
  3929.    *     no button is shown.
  3930.    * @param {string} cancelTitle The title of the cancel button. If undefined or
  3931.    *     empty, no button is shown.
  3932.    * @param {function} okCallback A function to be called when the user presses
  3933.    *     the ok button.  The alert window will be closed automatically.  Can be
  3934.    *     undefined.
  3935.    * @param {function} cancelCallback A function to be called when the user
  3936.    *     presses the cancel button.  The alert window will be closed
  3937.    *     automatically.  Can be undefined.
  3938.    */
  3939.   AlertOverlay.show = function(
  3940.       title, message, okTitle, cancelTitle, okCallback, cancelCallback) {
  3941.     if (title != undefined) {
  3942.       $('alertOverlayTitle').textContent = title;
  3943.       $('alertOverlayTitle').style.display = 'block';
  3944.     } else {
  3945.       $('alertOverlayTitle').style.display = 'none';
  3946.     }
  3947.  
  3948.     if (message != undefined) {
  3949.       $('alertOverlayMessage').textContent = message;
  3950.       $('alertOverlayMessage').style.display = 'block';
  3951.     } else {
  3952.       $('alertOverlayMessage').style.display = 'none';
  3953.     }
  3954.  
  3955.     if (okTitle != undefined && okTitle != '') {
  3956.       $('alertOverlayOk').textContent = okTitle;
  3957.       $('alertOverlayOk').style.display = 'block';
  3958.     } else {
  3959.       $('alertOverlayOk').style.display = 'none';
  3960.     }
  3961.  
  3962.     if (cancelTitle != undefined && cancelTitle != '') {
  3963.       $('alertOverlayCancel').textContent = cancelTitle;
  3964.       $('alertOverlayCancel').style.display = 'inline';
  3965.     } else {
  3966.       $('alertOverlayCancel').style.display = 'none';
  3967.     }
  3968.  
  3969.     var alertOverlay = AlertOverlay.getInstance();
  3970.     alertOverlay.okCallback = okCallback;
  3971.     alertOverlay.cancelCallback = cancelCallback;
  3972.     alertOverlay.canShow_ = true;
  3973.  
  3974.     // Intentionally don't show the URL in the location bar as we don't want
  3975.     // people trying to navigate here by hand.
  3976.     OptionsPage.showPageByName('alertOverlay', false);
  3977.   }
  3978.  
  3979.   // Export
  3980.   return {
  3981.     AlertOverlay: AlertOverlay
  3982.   };
  3983. });
  3984.  
  3985. // Copyright (c) 2011 The Chromium Authors. All rights reserved.
  3986. // Use of this source code is governed by a BSD-style license that can be
  3987. // found in the LICENSE file.
  3988.  
  3989. cr.define('options', function() {
  3990.   const ArrayDataModel = cr.ui.ArrayDataModel;
  3991.   const List = cr.ui.List;
  3992.   const ListItem = cr.ui.ListItem;
  3993.  
  3994.   /**
  3995.    * Creates a new autocomplete list item.
  3996.    * @param {Object} pageInfo The page this item represents.
  3997.    * @constructor
  3998.    * @extends {cr.ui.ListItem}
  3999.    */
  4000.   function AutocompleteListItem(pageInfo) {
  4001.     var el = cr.doc.createElement('div');
  4002.     el.pageInfo_ = pageInfo;
  4003.     AutocompleteListItem.decorate(el);
  4004.     return el;
  4005.   }
  4006.  
  4007.   /**
  4008.    * Decorates an element as an autocomplete list item.
  4009.    * @param {!HTMLElement} el The element to decorate.
  4010.    */
  4011.   AutocompleteListItem.decorate = function(el) {
  4012.     el.__proto__ = AutocompleteListItem.prototype;
  4013.     el.decorate();
  4014.   };
  4015.  
  4016.   AutocompleteListItem.prototype = {
  4017.     __proto__: ListItem.prototype,
  4018.  
  4019.     /** @inheritDoc */
  4020.     decorate: function() {
  4021.       ListItem.prototype.decorate.call(this);
  4022.  
  4023.       var title = this.pageInfo_['title'];
  4024.       var url = this.pageInfo_['displayURL'];
  4025.       var titleEl = this.ownerDocument.createElement('span');
  4026.       titleEl.className = 'title';
  4027.       titleEl.textContent = title || url;
  4028.       this.appendChild(titleEl);
  4029.  
  4030.       if (title && title.length > 0 && url != title) {
  4031.         var separatorEl = this.ownerDocument.createTextNode(' - ');
  4032.         this.appendChild(separatorEl);
  4033.  
  4034.         var urlEl = this.ownerDocument.createElement('span');
  4035.         urlEl.className = 'url';
  4036.         urlEl.textContent = url;
  4037.         this.appendChild(urlEl);
  4038.       }
  4039.     },
  4040.   };
  4041.  
  4042.   /**
  4043.    * Creates a new autocomplete list popup.
  4044.    * @constructor
  4045.    * @extends {cr.ui.List}
  4046.    */
  4047.   var AutocompleteList = cr.ui.define('list');
  4048.  
  4049.   AutocompleteList.prototype = {
  4050.     __proto__: List.prototype,
  4051.  
  4052.     /**
  4053.      * The text field the autocomplete popup is currently attached to, if any.
  4054.      * @type {HTMLElement}
  4055.      * @private
  4056.      */
  4057.     targetInput_: null,
  4058.  
  4059.     /**
  4060.      * Keydown event listener to attach to a text field.
  4061.      * @type {Function}
  4062.      * @private
  4063.      */
  4064.     textFieldKeyHandler_: null,
  4065.  
  4066.     /**
  4067.      * Input event listener to attach to a text field.
  4068.      * @type {Function}
  4069.      * @private
  4070.      */
  4071.     textFieldInputHandler_: null,
  4072.  
  4073.     /**
  4074.      * A function to call when new suggestions are needed.
  4075.      * @type {Function}
  4076.      * @private
  4077.      */
  4078.     suggestionUpdateRequestCallback_: null,
  4079.  
  4080.     /** @inheritDoc */
  4081.     decorate: function() {
  4082.       List.prototype.decorate.call(this);
  4083.       this.classList.add('autocomplete-suggestions');
  4084.       this.selectionModel = new cr.ui.ListSingleSelectionModel;
  4085.  
  4086.       this.textFieldKeyHandler_ = this.handleAutocompleteKeydown_.bind(this);
  4087.       var self = this;
  4088.       this.textFieldInputHandler_ = function(e) {
  4089.         if (self.suggestionUpdateRequestCallback_)
  4090.           self.suggestionUpdateRequestCallback_(self.targetInput_.value);
  4091.       };
  4092.       this.addEventListener('change', function(e) {
  4093.         var input = self.targetInput;
  4094.         if (!input || !self.selectedItem)
  4095.           return;
  4096.         input.value = self.selectedItem['url'];
  4097.         // Programatically change the value won't trigger a change event, but
  4098.         // clients are likely to want to know when changes happen, so fire one.
  4099.         var changeEvent = document.createEvent('Event');
  4100.         changeEvent.initEvent('change', true, true);
  4101.         input.dispatchEvent(changeEvent);
  4102.       });
  4103.       // Start hidden; adding suggestions will unhide.
  4104.       this.hidden = true;
  4105.     },
  4106.  
  4107.     /** @inheritDoc */
  4108.     createItem: function(pageInfo) {
  4109.       return new AutocompleteListItem(pageInfo);
  4110.     },
  4111.  
  4112.     /**
  4113.      * The suggestions to show.
  4114.      * @type {Array}
  4115.      */
  4116.     set suggestions(suggestions) {
  4117.       this.dataModel = new ArrayDataModel(suggestions);
  4118.       this.hidden = !this.targetInput_ || suggestions.length == 0;
  4119.     },
  4120.  
  4121.     /**
  4122.      * A function to call when the attached input field's contents change.
  4123.      * The function should take one string argument, which will be the text
  4124.      * to autocomplete from.
  4125.      * @type {Function}
  4126.      */
  4127.     set suggestionUpdateRequestCallback(callback) {
  4128.       this.suggestionUpdateRequestCallback_ = callback;
  4129.     },
  4130.  
  4131.     /**
  4132.      * Attaches the popup to the given input element. Requires
  4133.      * that the input be wrapped in a block-level container of the same width.
  4134.      * @param {HTMLElement} input The input element to attach to.
  4135.      */
  4136.     attachToInput: function(input) {
  4137.       if (this.targetInput_ == input)
  4138.         return;
  4139.  
  4140.       this.detach();
  4141.       this.targetInput_ = input;
  4142.       this.style.width = input.getBoundingClientRect().width + 'px';
  4143.       this.hidden = false;  // Necessary for positionPopupAroundElement to work.
  4144.       cr.ui.positionPopupAroundElement(input, this, cr.ui.AnchorType.BELOW)
  4145.       // Start hidden; when the data model gets results the list will show.
  4146.       this.hidden = true;
  4147.  
  4148.       input.addEventListener('keydown', this.textFieldKeyHandler_, true);
  4149.       input.addEventListener('input', this.textFieldInputHandler_);
  4150.     },
  4151.  
  4152.     /**
  4153.      * Detaches the autocomplete popup from its current input element, if any.
  4154.      */
  4155.     detach: function() {
  4156.       var input = this.targetInput_
  4157.       if (!input)
  4158.         return;
  4159.  
  4160.       input.removeEventListener('keydown', this.textFieldKeyHandler_);
  4161.       input.removeEventListener('input', this.textFieldInputHandler_);
  4162.       this.targetInput_ = null;
  4163.       this.suggestions = [];
  4164.     },
  4165.  
  4166.     /**
  4167.      * Makes sure that the suggestion list matches the width of the input it is.
  4168.      * attached to. Should be called any time the input is resized.
  4169.      */
  4170.     syncWidthToInput: function() {
  4171.       var input = this.targetInput_
  4172.       if (input)
  4173.         this.style.width = input.getBoundingClientRect().width + 'px';
  4174.     },
  4175.  
  4176.     /**
  4177.      * The text field the autocomplete popup is currently attached to, if any.
  4178.      * @return {HTMLElement}
  4179.      */
  4180.     get targetInput() {
  4181.       return this.targetInput_;
  4182.     },
  4183.  
  4184.     /**
  4185.      * Handles input field key events that should be interpreted as autocomplete
  4186.      * commands.
  4187.      * @param {Event} event The keydown event.
  4188.      * @private
  4189.      */
  4190.     handleAutocompleteKeydown_: function(event) {
  4191.       if (this.hidden)
  4192.         return;
  4193.       var handled = false;
  4194.       switch (event.keyIdentifier) {
  4195.         case 'U+001B':  // Esc
  4196.           this.suggestions = [];
  4197.           handled = true;
  4198.           break;
  4199.         case 'Enter':
  4200.           var hadSelection = this.selectedItem != null;
  4201.           this.suggestions = [];
  4202.           // Only count the event as handled if a selection is being commited.
  4203.           handled = hadSelection;
  4204.           break;
  4205.         case 'Up':
  4206.         case 'Down':
  4207.           this.dispatchEvent(event);
  4208.           handled = true;
  4209.           break;
  4210.       }
  4211.       // Don't let arrow keys affect the text field, or bubble up to, e.g.,
  4212.       // an enclosing list item.
  4213.       if (handled) {
  4214.         event.preventDefault();
  4215.         event.stopPropagation();
  4216.       }
  4217.     },
  4218.   };
  4219.  
  4220.   return {
  4221.     AutocompleteList: AutocompleteList
  4222.   };
  4223. });
  4224.  
  4225. // Copyright (c) 2011 The Chromium Authors. All rights reserved.
  4226. // Use of this source code is governed by a BSD-style license that can be
  4227. // found in the LICENSE file.
  4228.  
  4229. cr.define('options', function() {
  4230.   const OptionsPage = options.OptionsPage;
  4231.   const ArrayDataModel = cr.ui.ArrayDataModel;
  4232.  
  4233.   // The GUID of the loaded address.
  4234.   var guid;
  4235.  
  4236.   /**
  4237.    * AutofillEditAddressOverlay class
  4238.    * Encapsulated handling of the 'Add Page' overlay page.
  4239.    * @class
  4240.    */
  4241.   function AutofillEditAddressOverlay() {
  4242.     OptionsPage.call(this, 'autofillEditAddress',
  4243.                      templateData.autofillEditAddressTitle,
  4244.                      'autofill-edit-address-overlay');
  4245.   }
  4246.  
  4247.   cr.addSingletonGetter(AutofillEditAddressOverlay);
  4248.  
  4249.   AutofillEditAddressOverlay.prototype = {
  4250.     __proto__: OptionsPage.prototype,
  4251.  
  4252.     /**
  4253.      * Initializes the page.
  4254.      */
  4255.     initializePage: function() {
  4256.       OptionsPage.prototype.initializePage.call(this);
  4257.  
  4258.       this.createMultiValueLists_();
  4259.  
  4260.       var self = this;
  4261.       $('autofill-edit-address-cancel-button').onclick = function(event) {
  4262.         self.dismissOverlay_();
  4263.       }
  4264.       $('autofill-edit-address-apply-button').onclick = function(event) {
  4265.         self.saveAddress_();
  4266.         self.dismissOverlay_();
  4267.       }
  4268.  
  4269.       self.guid = '';
  4270.       self.populateCountryList_();
  4271.       self.clearInputFields_();
  4272.       self.connectInputEvents_();
  4273.     },
  4274.  
  4275.     /**
  4276.      * Creates, decorates and initializes the multi-value lists for full name,
  4277.      * phone, and email.
  4278.      * @private
  4279.      */
  4280.     createMultiValueLists_: function() {
  4281.       var list = $('full-name-list');
  4282.       options.autofillOptions.AutofillNameValuesList.decorate(list);
  4283.       list.autoExpands = true;
  4284.  
  4285.       list = $('phone-list');
  4286.       options.autofillOptions.AutofillPhoneValuesList.decorate(list);
  4287.       list.autoExpands = true;
  4288.  
  4289.       list = $('email-list');
  4290.       options.autofillOptions.AutofillValuesList.decorate(list);
  4291.       list.autoExpands = true;
  4292.     },
  4293.  
  4294.     /**
  4295.      * Updates the data model for the list named |listName| with the values from
  4296.      * |entries|.
  4297.      * @param {String} listName The id of the list.
  4298.      * @param {Array} entries The list of items to be added to the list.
  4299.      */
  4300.     setMultiValueList_: function(listName, entries) {
  4301.       // Add data entries.
  4302.       var list = $(listName);
  4303.       list.dataModel = new ArrayDataModel(entries);
  4304.  
  4305.       // Add special entry for adding new values.
  4306.       list.dataModel.splice(list.dataModel.length, 0, null);
  4307.  
  4308.       // Update the status of the 'OK' button.
  4309.       this.inputFieldChanged_();
  4310.  
  4311.       var self = this;
  4312.       list.dataModel.addEventListener(
  4313.         'splice', function(event) { self.inputFieldChanged_(); });
  4314.       list.dataModel.addEventListener(
  4315.         'change', function(event) { self.inputFieldChanged_(); });
  4316.     },
  4317.  
  4318.     /**
  4319.      * Updates the data model for the name list with the values from |entries|.
  4320.      * @param {Array} names The list of names to be added to the list.
  4321.      */
  4322.     setNameList_: function(names) {
  4323.       // Add the given |names| as backing data for the list.
  4324.       var list = $('full-name-list');
  4325.       list.dataModel = new ArrayDataModel(names);
  4326.  
  4327.       // Add special entry for adding new values.
  4328.       list.dataModel.splice(list.dataModel.length, 0, null);
  4329.  
  4330.       var self = this;
  4331.       list.dataModel.addEventListener(
  4332.         'splice', function(event) { self.inputFieldChanged_(); });
  4333.       list.dataModel.addEventListener(
  4334.         'change', function(event) { self.inputFieldChanged_(); });
  4335.     },
  4336.  
  4337.     /**
  4338.      * Clears any uncommitted input, resets the stored GUID and dismisses the
  4339.      * overlay.
  4340.      * @private
  4341.      */
  4342.     dismissOverlay_: function() {
  4343.       this.clearInputFields_();
  4344.       this.guid = '';
  4345.       OptionsPage.closeOverlay();
  4346.     },
  4347.  
  4348.     /**
  4349.      * Aggregates the values in the input fields into an array and sends the
  4350.      * array to the Autofill handler.
  4351.      * @private
  4352.      */
  4353.     saveAddress_: function() {
  4354.       var address = new Array();
  4355.       address[0] = this.guid;
  4356.       var list = $('full-name-list');
  4357.       address[1] = list.dataModel.slice(0, list.dataModel.length - 1);
  4358.       address[2] = $('company-name').value;
  4359.       address[3] = $('addr-line-1').value;
  4360.       address[4] = $('addr-line-2').value;
  4361.       address[5] = $('city').value;
  4362.       address[6] = $('state').value;
  4363.       address[7] = $('postal-code').value;
  4364.       address[8] = $('country').value;
  4365.       list = $('phone-list');
  4366.       address[9] = list.dataModel.slice(0, list.dataModel.length - 1);
  4367.       list = $('email-list');
  4368.       address[10] = list.dataModel.slice(0, list.dataModel.length - 1);
  4369.  
  4370.       chrome.send('setAddress', address);
  4371.     },
  4372.  
  4373.     /**
  4374.      * Connects each input field to the inputFieldChanged_() method that enables
  4375.      * or disables the 'Ok' button based on whether all the fields are empty or
  4376.      * not.
  4377.      * @private
  4378.      */
  4379.     connectInputEvents_: function() {
  4380.       var self = this;
  4381.       $('company-name').oninput = $('addr-line-1').oninput =
  4382.       $('addr-line-2').oninput = $('city').oninput = $('state').oninput =
  4383.       $('postal-code').oninput = function(event) {
  4384.         self.inputFieldChanged_();
  4385.       }
  4386.  
  4387.       $('country').onchange = function(event) {
  4388.         self.countryChanged_();
  4389.       }
  4390.     },
  4391.  
  4392.     /**
  4393.      * Checks the values of each of the input fields and disables the 'Ok'
  4394.      * button if all of the fields are empty.
  4395.      * @private
  4396.      */
  4397.     inputFieldChanged_: function() {
  4398.       // Length of lists are tested for <= 1 due to the "add" placeholder item
  4399.       // in the list.
  4400.       var disabled =
  4401.           $('full-name-list').items.length <= 1 &&
  4402.           !$('company-name').value &&
  4403.           !$('addr-line-1').value && !$('addr-line-2').value &&
  4404.           !$('city').value && !$('state').value && !$('postal-code').value &&
  4405.           !$('country').value && $('phone-list').items.length <= 1 &&
  4406.           $('email-list').items.length <= 1;
  4407.       $('autofill-edit-address-apply-button').disabled = disabled;
  4408.     },
  4409.  
  4410.     /**
  4411.      * Updates the postal code and state field labels appropriately for the
  4412.      * selected country.
  4413.      * @private
  4414.      */
  4415.     countryChanged_: function() {
  4416.       var countryCode = $('country').value;
  4417.       if (!countryCode)
  4418.         countryCode = templateData.defaultCountryCode;
  4419.  
  4420.       var details = templateData.autofillCountryData[countryCode];
  4421.       var postal = $('postal-code-label');
  4422.       postal.textContent = details['postalCodeLabel'];
  4423.       $('state-label').textContent = details['stateLabel'];
  4424.  
  4425.       // Also update the 'Ok' button as needed.
  4426.       this.inputFieldChanged_();
  4427.     },
  4428.  
  4429.     /**
  4430.      * Populates the country <select> list.
  4431.      * @private
  4432.      */
  4433.     populateCountryList_: function() {
  4434.       var countryData = templateData.autofillCountryData;
  4435.       var defaultCountryCode = templateData.defaultCountryCode;
  4436.  
  4437.       // Build an array of the country names and their corresponding country
  4438.       // codes, so that we can sort and insert them in order.
  4439.       var countries = [];
  4440.       for (var countryCode in countryData) {
  4441.         var country = {
  4442.           countryCode: countryCode,
  4443.           name: countryData[countryCode]['name']
  4444.         };
  4445.         countries.push(country);
  4446.       }
  4447.  
  4448.       // Sort the countries in alphabetical order by name.
  4449.       countries = countries.sort(function(a, b) {
  4450.         return a.name < b.name ? -1 : 1;
  4451.       });
  4452.  
  4453.       // Insert the empty and default countries at the beginning of the array.
  4454.       var emptyCountry = {
  4455.         countryCode: '',
  4456.         name: ''
  4457.       };
  4458.       var defaultCountry = {
  4459.         countryCode: defaultCountryCode,
  4460.         name: countryData[defaultCountryCode]['name']
  4461.       };
  4462.       var separator = {
  4463.         countryCode: '',
  4464.         name: '---',
  4465.         disabled: true
  4466.       }
  4467.       countries.unshift(emptyCountry, defaultCountry, separator);
  4468.  
  4469.       // Add the countries to the country <select> list.
  4470.       var countryList = $('country');
  4471.       for (var i = 0; i < countries.length; i++) {
  4472.         var country = new Option(countries[i].name, countries[i].countryCode);
  4473.         country.disabled = countries[i].disabled;
  4474.         countryList.appendChild(country)
  4475.       }
  4476.     },
  4477.  
  4478.     /**
  4479.      * Clears the value of each input field.
  4480.      * @private
  4481.      */
  4482.     clearInputFields_: function() {
  4483.       this.setNameList_([]);
  4484.       $('company-name').value = '';
  4485.       $('addr-line-1').value = '';
  4486.       $('addr-line-2').value = '';
  4487.       $('city').value = '';
  4488.       $('state').value = '';
  4489.       $('postal-code').value = '';
  4490.       $('country').value = '';
  4491.       this.setMultiValueList_('phone-list', []);
  4492.       this.setMultiValueList_('email-list', []);
  4493.  
  4494.       this.countryChanged_();
  4495.     },
  4496.  
  4497.     /**
  4498.      * Loads the address data from |address|, sets the input fields based on
  4499.      * this data and stores the GUID of the address.
  4500.      * @private
  4501.      */
  4502.     loadAddress_: function(address) {
  4503.       this.setInputFields_(address);
  4504.       this.inputFieldChanged_();
  4505.       this.guid = address['guid'];
  4506.     },
  4507.  
  4508.     /**
  4509.      * Sets the value of each input field according to |address|
  4510.      * @private
  4511.      */
  4512.     setInputFields_: function(address) {
  4513.       this.setNameList_(address['fullName']);
  4514.       $('company-name').value = address['companyName'];
  4515.       $('addr-line-1').value = address['addrLine1'];
  4516.       $('addr-line-2').value = address['addrLine2'];
  4517.       $('city').value = address['city'];
  4518.       $('state').value = address['state'];
  4519.       $('postal-code').value = address['postalCode'];
  4520.       $('country').value = address['country'];
  4521.       this.setMultiValueList_('phone-list', address['phone']);
  4522.       this.setMultiValueList_('email-list', address['email']);
  4523.  
  4524.       this.countryChanged_();
  4525.     },
  4526.   };
  4527.  
  4528.   AutofillEditAddressOverlay.clearInputFields = function() {
  4529.     AutofillEditAddressOverlay.getInstance().clearInputFields_();
  4530.   };
  4531.  
  4532.   AutofillEditAddressOverlay.loadAddress = function(address) {
  4533.     AutofillEditAddressOverlay.getInstance().loadAddress_(address);
  4534.   };
  4535.  
  4536.   AutofillEditAddressOverlay.setTitle = function(title) {
  4537.     $('autofill-address-title').textContent = title;
  4538.   };
  4539.  
  4540.   AutofillEditAddressOverlay.setValidatedPhoneNumbers = function(numbers) {
  4541.     AutofillEditAddressOverlay.getInstance().setMultiValueList_('phone-list',
  4542.                                                                 numbers);
  4543.   };
  4544.  
  4545.   // Export
  4546.   return {
  4547.     AutofillEditAddressOverlay: AutofillEditAddressOverlay
  4548.   };
  4549. });
  4550.  
  4551. // Copyright (c) 2011 The Chromium Authors. All rights reserved.
  4552. // Use of this source code is governed by a BSD-style license that can be
  4553. // found in the LICENSE file.
  4554.  
  4555. cr.define('options', function() {
  4556.   const OptionsPage = options.OptionsPage;
  4557.  
  4558.   // The GUID of the loaded credit card.
  4559.   var guid_;
  4560.  
  4561.   /**
  4562.    * AutofillEditCreditCardOverlay class
  4563.    * Encapsulated handling of the 'Add Page' overlay page.
  4564.    * @class
  4565.    */
  4566.   function AutofillEditCreditCardOverlay() {
  4567.     OptionsPage.call(this, 'autofillEditCreditCard',
  4568.                      templateData.autofillEditCreditCardTitle,
  4569.                      'autofill-edit-credit-card-overlay');
  4570.   }
  4571.  
  4572.   cr.addSingletonGetter(AutofillEditCreditCardOverlay);
  4573.  
  4574.   AutofillEditCreditCardOverlay.prototype = {
  4575.     __proto__: OptionsPage.prototype,
  4576.  
  4577.     /**
  4578.      * Initializes the page.
  4579.      */
  4580.     initializePage: function() {
  4581.       OptionsPage.prototype.initializePage.call(this);
  4582.  
  4583.       var self = this;
  4584.       $('autofill-edit-credit-card-cancel-button').onclick = function(event) {
  4585.         self.dismissOverlay_();
  4586.       }
  4587.       $('autofill-edit-credit-card-apply-button').onclick = function(event) {
  4588.         self.saveCreditCard_();
  4589.         self.dismissOverlay_();
  4590.       }
  4591.  
  4592.       self.guid_ = '';
  4593.       self.hasEditedNumber_ = false;
  4594.       self.clearInputFields_();
  4595.       self.connectInputEvents_();
  4596.       self.setDefaultSelectOptions_();
  4597.     },
  4598.  
  4599.     /**
  4600.      * Clears any uncommitted input, and dismisses the overlay.
  4601.      * @private
  4602.      */
  4603.     dismissOverlay_: function() {
  4604.       this.clearInputFields_();
  4605.       this.guid_ = '';
  4606.       this.hasEditedNumber_ = false;
  4607.       OptionsPage.closeOverlay();
  4608.     },
  4609.  
  4610.     /**
  4611.      * Aggregates the values in the input fields into an array and sends the
  4612.      * array to the Autofill handler.
  4613.      * @private
  4614.      */
  4615.     saveCreditCard_: function() {
  4616.       var creditCard = new Array(5);
  4617.       creditCard[0] = this.guid_;
  4618.       creditCard[1] = $('name-on-card').value;
  4619.       creditCard[2] = $('credit-card-number').value;
  4620.       creditCard[3] = $('expiration-month').value;
  4621.       creditCard[4] = $('expiration-year').value;
  4622.       chrome.send('setCreditCard', creditCard);
  4623.     },
  4624.  
  4625.     /**
  4626.      * Connects each input field to the inputFieldChanged_() method that enables
  4627.      * or disables the 'Ok' button based on whether all the fields are empty or
  4628.      * not.
  4629.      * @private
  4630.      */
  4631.     connectInputEvents_: function() {
  4632.       var ccNumber = $('credit-card-number');
  4633.       $('name-on-card').oninput = ccNumber.oninput =
  4634.           $('expiration-month').onchange = $('expiration-year').onchange =
  4635.               this.inputFieldChanged_.bind(this);
  4636.     },
  4637.  
  4638.     /**
  4639.      * Checks the values of each of the input fields and disables the 'Ok'
  4640.      * button if all of the fields are empty.
  4641.      * @param {Event} opt_event Optional data for the 'input' event.
  4642.      * @private
  4643.      */
  4644.     inputFieldChanged_: function(opt_event) {
  4645.       var disabled = !$('name-on-card').value && !$('credit-card-number').value;
  4646.       $('autofill-edit-credit-card-apply-button').disabled = disabled;
  4647.     },
  4648.  
  4649.     /**
  4650.      * Sets the default values of the options in the 'Expiration date' select
  4651.      * controls.
  4652.      * @private
  4653.      */
  4654.     setDefaultSelectOptions_: function() {
  4655.       // Set the 'Expiration month' default options.
  4656.       var expirationMonth = $('expiration-month');
  4657.       expirationMonth.options.length = 0;
  4658.       for (var i = 1; i <= 12; ++i) {
  4659.         var text;
  4660.         if (i < 10)
  4661.           text = '0' + i;
  4662.         else
  4663.           text = i;
  4664.  
  4665.         var option = document.createElement('option');
  4666.         option.text = text;
  4667.         option.value = text;
  4668.         expirationMonth.add(option, null);
  4669.       }
  4670.  
  4671.       // Set the 'Expiration year' default options.
  4672.       var expirationYear = $('expiration-year');
  4673.       expirationYear.options.length = 0;
  4674.  
  4675.       var date = new Date();
  4676.       var year = parseInt(date.getFullYear());
  4677.       for (var i = 0; i < 10; ++i) {
  4678.         var text = year + i;
  4679.         var option = document.createElement('option');
  4680.         option.text = text;
  4681.         option.value = text;
  4682.         expirationYear.add(option, null);
  4683.       }
  4684.     },
  4685.  
  4686.     /**
  4687.      * Clears the value of each input field.
  4688.      * @private
  4689.      */
  4690.     clearInputFields_: function() {
  4691.       $('name-on-card').value = '';
  4692.       $('credit-card-number').value = '';
  4693.       $('expiration-month').selectedIndex = 0;
  4694.       $('expiration-year').selectedIndex = 0;
  4695.  
  4696.       // Reset the enabled status of the 'Ok' button.
  4697.       this.inputFieldChanged_();
  4698.     },
  4699.  
  4700.     /**
  4701.      * Sets the value of each input field according to |creditCard|
  4702.      * @private
  4703.      */
  4704.     setInputFields_: function(creditCard) {
  4705.       $('name-on-card').value = creditCard['nameOnCard'];
  4706.       $('credit-card-number').value = creditCard['creditCardNumber'];
  4707.  
  4708.       // The options for the year select control may be out-dated at this point,
  4709.       // e.g. the user opened the options page before midnight on New Year's Eve
  4710.       // and then loaded a credit card profile to edit in the new year, so
  4711.       // reload the select options just to be safe.
  4712.       this.setDefaultSelectOptions_();
  4713.  
  4714.       var idx = parseInt(creditCard['expirationMonth'], 10);
  4715.       $('expiration-month').selectedIndex = idx - 1;
  4716.  
  4717.       expYear = creditCard['expirationYear'];
  4718.       var date = new Date();
  4719.       var year = parseInt(date.getFullYear());
  4720.       for (var i = 0; i < 10; ++i) {
  4721.         var text = year + i;
  4722.         if (expYear == String(text))
  4723.           $('expiration-year').selectedIndex = i;
  4724.       }
  4725.     },
  4726.  
  4727.     /**
  4728.      * Loads the credit card data from |creditCard|, sets the input fields based
  4729.      * on this data and stores the GUID of the credit card.
  4730.      * @private
  4731.      */
  4732.     loadCreditCard_: function(creditCard) {
  4733.       this.setInputFields_(creditCard);
  4734.       this.inputFieldChanged_();
  4735.       this.guid_ = creditCard['guid'];
  4736.     },
  4737.   };
  4738.  
  4739.   AutofillEditCreditCardOverlay.clearInputFields = function(title) {
  4740.     AutofillEditCreditCardOverlay.getInstance().clearInputFields_();
  4741.   };
  4742.  
  4743.   AutofillEditCreditCardOverlay.loadCreditCard = function(creditCard) {
  4744.     AutofillEditCreditCardOverlay.getInstance().loadCreditCard_(creditCard);
  4745.   };
  4746.  
  4747.   AutofillEditCreditCardOverlay.setTitle = function(title) {
  4748.     $('autofill-credit-card-title').textContent = title;
  4749.   };
  4750.  
  4751.   // Export
  4752.   return {
  4753.     AutofillEditCreditCardOverlay: AutofillEditCreditCardOverlay
  4754.   };
  4755. });
  4756.  
  4757. // Copyright (c) 2011 The Chromium Authors. All rights reserved.
  4758. // Use of this source code is governed by a BSD-style license that can be
  4759. // found in the LICENSE file.
  4760.  
  4761. cr.define('options.autofillOptions', function() {
  4762.   const DeletableItem = options.DeletableItem;
  4763.   const DeletableItemList = options.DeletableItemList;
  4764.   const InlineEditableItem = options.InlineEditableItem;
  4765.   const InlineEditableItemList = options.InlineEditableItemList;
  4766.  
  4767.   function AutofillEditProfileButton(guid, edit) {
  4768.     var editButtonEl = document.createElement('button');
  4769.     editButtonEl.className = 'raw-button custom-appearance';
  4770.     editButtonEl.textContent =
  4771.         templateData.autofillEditProfileButton;
  4772.     editButtonEl.onclick = function(e) { edit(guid); };
  4773.  
  4774.     // Don't select the row when clicking the button.
  4775.     editButtonEl.onmousedown = function(e) {
  4776.       e.stopPropagation();
  4777.     };
  4778.  
  4779.     return editButtonEl;
  4780.   }
  4781.  
  4782.   /**
  4783.    * Creates a new address list item.
  4784.    * @param {Array} entry An array of the form [guid, label].
  4785.    * @constructor
  4786.    * @extends {options.DeletableItem}
  4787.    */
  4788.   function AddressListItem(entry) {
  4789.     var el = cr.doc.createElement('div');
  4790.     el.guid = entry[0];
  4791.     el.label = entry[1];
  4792.     el.__proto__ = AddressListItem.prototype;
  4793.     el.decorate();
  4794.  
  4795.     return el;
  4796.   }
  4797.  
  4798.   AddressListItem.prototype = {
  4799.     __proto__: DeletableItem.prototype,
  4800.  
  4801.     /** @inheritDoc */
  4802.     decorate: function() {
  4803.       DeletableItem.prototype.decorate.call(this);
  4804.  
  4805.       // The stored label.
  4806.       var label = this.ownerDocument.createElement('div');
  4807.       label.className = 'autofill-list-item';
  4808.       label.textContent = this.label;
  4809.       this.contentElement.appendChild(label);
  4810.  
  4811.       // The 'Edit' button.
  4812.       var editButtonEl = new AutofillEditProfileButton(
  4813.         this.guid,
  4814.         AutofillOptions.loadAddressEditor);
  4815.       this.contentElement.appendChild(editButtonEl);
  4816.     },
  4817.   };
  4818.  
  4819.   /**
  4820.    * Creates a new credit card list item.
  4821.    * @param {Array} entry An array of the form [guid, label, icon].
  4822.    * @constructor
  4823.    * @extends {options.DeletableItem}
  4824.    */
  4825.   function CreditCardListItem(entry) {
  4826.     var el = cr.doc.createElement('div');
  4827.     el.guid = entry[0];
  4828.     el.label = entry[1];
  4829.     el.icon = entry[2];
  4830.     el.description = entry[3];
  4831.     el.__proto__ = CreditCardListItem.prototype;
  4832.     el.decorate();
  4833.  
  4834.     return el;
  4835.   }
  4836.  
  4837.   CreditCardListItem.prototype = {
  4838.     __proto__: DeletableItem.prototype,
  4839.  
  4840.     /** @inheritDoc */
  4841.     decorate: function() {
  4842.       DeletableItem.prototype.decorate.call(this);
  4843.  
  4844.       // The stored label.
  4845.       var label = this.ownerDocument.createElement('div');
  4846.       label.className = 'autofill-list-item';
  4847.       label.textContent = this.label;
  4848.       this.contentElement.appendChild(label);
  4849.  
  4850.       // The credit card icon.
  4851.       var icon = this.ownerDocument.createElement('image');
  4852.       icon.src = this.icon;
  4853.       icon.alt = this.description;
  4854.       this.contentElement.appendChild(icon);
  4855.  
  4856.       // The 'Edit' button.
  4857.       var editButtonEl = new AutofillEditProfileButton(
  4858.         this.guid,
  4859.         AutofillOptions.loadCreditCardEditor);
  4860.       this.contentElement.appendChild(editButtonEl);
  4861.     },
  4862.   };
  4863.  
  4864.   /**
  4865.    * Creates a new value list item.
  4866.    * @param {AutofillValuesList} list The parent list of this item.
  4867.    * @param {String} entry A string value.
  4868.    * @constructor
  4869.    * @extends {options.InlineEditableItem}
  4870.    */
  4871.   function ValuesListItem(list, entry) {
  4872.     var el = cr.doc.createElement('div');
  4873.     el.list = list;
  4874.     el.value = entry ? entry : '';
  4875.     el.__proto__ = ValuesListItem.prototype;
  4876.     el.decorate();
  4877.  
  4878.     return el;
  4879.   }
  4880.  
  4881.   ValuesListItem.prototype = {
  4882.     __proto__: InlineEditableItem.prototype,
  4883.  
  4884.     /** @inheritDoc */
  4885.     decorate: function() {
  4886.       InlineEditableItem.prototype.decorate.call(this);
  4887.  
  4888.       // Note: This must be set prior to calling |createEditableTextCell|.
  4889.       this.isPlaceholder = !this.value;
  4890.  
  4891.       // The stored value.
  4892.       var cell = this.createEditableTextCell(this.value);
  4893.       this.contentElement.appendChild(cell);
  4894.       this.input = cell.querySelector('input');
  4895.  
  4896.       if (this.isPlaceholder) {
  4897.         this.input.placeholder = this.list.getAttribute('placeholder');
  4898.         this.deletable = false;
  4899.       }
  4900.  
  4901.       this.addEventListener('commitedit', this.onEditCommitted_);
  4902.     },
  4903.  
  4904.     /**
  4905.      * @return This item's value.
  4906.      * @protected
  4907.      */
  4908.     value_: function() {
  4909.       return this.input.value;
  4910.     },
  4911.  
  4912.     /**
  4913.      * @param {Object} value The value to test.
  4914.      * @return true if the given value is non-empty.
  4915.      * @protected
  4916.      */
  4917.     valueIsNonEmpty_: function(value) {
  4918.       return !!value;
  4919.     },
  4920.  
  4921.     /**
  4922.      * @return true if value1 is logically equal to value2.
  4923.      */
  4924.     valuesAreEqual_: function(value1, value2) {
  4925.       return value1 === value2;
  4926.     },
  4927.  
  4928.     /**
  4929.      * Clears the item's value.
  4930.      * @protected
  4931.      */
  4932.     clearValue_: function() {
  4933.       this.input.value = '';
  4934.     },
  4935.  
  4936.     /**
  4937.      * Called when committing an edit.
  4938.      * If this is an "Add ..." item, committing a non-empty value adds that
  4939.      * value to the end of the values list, but also leaves this "Add ..." item
  4940.      * in place.
  4941.      * @param {Event} e The end event.
  4942.      * @private
  4943.      */
  4944.     onEditCommitted_: function(e) {
  4945.       var value = this.value_();
  4946.       var i = this.list.items.indexOf(this);
  4947.       if (i < this.list.dataModel.length &&
  4948.           this.valuesAreEqual_(value, this.list.dataModel.item(i))) {
  4949.         return;
  4950.       }
  4951.  
  4952.       var entries = this.list.dataModel.slice();
  4953.       if (this.valueIsNonEmpty_(value) &&
  4954.           !entries.some(this.valuesAreEqual_.bind(this, value))) {
  4955.         // Update with new value.
  4956.         if (this.isPlaceholder) {
  4957.           // It is important that updateIndex is done before validateAndSave.
  4958.           // Otherwise we can not be sure about AddRow index.
  4959.           this.list.dataModel.updateIndex(i);
  4960.           this.list.validateAndSave(i, 0, value);
  4961.         } else {
  4962.           this.list.validateAndSave(i, 1, value);
  4963.         }
  4964.       } else {
  4965.         // Reject empty values and duplicates.
  4966.         if (!this.isPlaceholder)
  4967.           this.list.dataModel.splice(i, 1);
  4968.         else
  4969.           this.clearValue_();
  4970.       }
  4971.     },
  4972.   };
  4973.  
  4974.   /**
  4975.    * Creates a new name value list item.
  4976.    * @param {AutofillNameValuesList} list The parent list of this item.
  4977.    * @param {array} entry An array of [first, middle, last] names.
  4978.    * @constructor
  4979.    * @extends {options.ValuesListItem}
  4980.    */
  4981.   function NameListItem(list, entry) {
  4982.     var el = cr.doc.createElement('div');
  4983.     el.list = list;
  4984.     el.first = entry ? entry[0] : '';
  4985.     el.middle = entry ? entry[1] : '';
  4986.     el.last = entry ? entry[2] : '';
  4987.     el.__proto__ = NameListItem.prototype;
  4988.     el.decorate();
  4989.  
  4990.     return el;
  4991.   }
  4992.  
  4993.   NameListItem.prototype = {
  4994.     __proto__: ValuesListItem.prototype,
  4995.  
  4996.     /** @inheritDoc */
  4997.     decorate: function() {
  4998.       InlineEditableItem.prototype.decorate.call(this);
  4999.  
  5000.       // Note: This must be set prior to calling |createEditableTextCell|.
  5001.       this.isPlaceholder = !this.first && !this.middle && !this.last;
  5002.  
  5003.       // The stored value.
  5004.       // For the simulated static "input element" to display correctly, the
  5005.       // value must not be empty.  We use a space to force the UI to render
  5006.       // correctly when the value is logically empty.
  5007.       var cell = this.createEditableTextCell(this.first);
  5008.       this.contentElement.appendChild(cell);
  5009.       this.firstNameInput = cell.querySelector('input');
  5010.  
  5011.       cell = this.createEditableTextCell(this.middle);
  5012.       this.contentElement.appendChild(cell);
  5013.       this.middleNameInput = cell.querySelector('input');
  5014.  
  5015.       cell = this.createEditableTextCell(this.last);
  5016.       this.contentElement.appendChild(cell);
  5017.       this.lastNameInput = cell.querySelector('input');
  5018.  
  5019.       if (this.isPlaceholder) {
  5020.         this.firstNameInput.placeholder =
  5021.             templateData.autofillAddFirstNamePlaceholder;
  5022.         this.middleNameInput.placeholder =
  5023.             templateData.autofillAddMiddleNamePlaceholder;
  5024.         this.lastNameInput.placeholder =
  5025.             templateData.autofillAddLastNamePlaceholder;
  5026.         this.deletable = false;
  5027.       }
  5028.  
  5029.       this.addEventListener('commitedit', this.onEditCommitted_);
  5030.     },
  5031.  
  5032.     /** @inheritDoc */
  5033.     value_: function() {
  5034.       return [ this.firstNameInput.value,
  5035.                this.middleNameInput.value,
  5036.                this.lastNameInput.value ];
  5037.     },
  5038.  
  5039.     /** @inheritDoc */
  5040.     valueIsNonEmpty_: function(value) {
  5041.       return value[0] || value[1] || value[2];
  5042.     },
  5043.  
  5044.     /** @inheritDoc */
  5045.     valuesAreEqual_: function(value1, value2) {
  5046.       // First, check for null values.
  5047.       if (!value1 || !value2)
  5048.         return value1 == value2;
  5049.  
  5050.       return value1[0] === value2[0] &&
  5051.              value1[1] === value2[1] &&
  5052.              value1[2] === value2[2];
  5053.     },
  5054.  
  5055.     /** @inheritDoc */
  5056.     clearValue_: function() {
  5057.       this.firstNameInput.value = '';
  5058.       this.middleNameInput.value = '';
  5059.       this.lastNameInput.value = '';
  5060.     },
  5061.   };
  5062.  
  5063.   /**
  5064.    * Base class for shared implementation between address and credit card lists.
  5065.    * @constructor
  5066.    * @extends {options.DeletableItemList}
  5067.    */
  5068.   var AutofillProfileList = cr.ui.define('list');
  5069.  
  5070.   AutofillProfileList.prototype = {
  5071.     __proto__: DeletableItemList.prototype,
  5072.  
  5073.     decorate:  function() {
  5074.       DeletableItemList.prototype.decorate.call(this);
  5075.  
  5076.       this.addEventListener('blur', this.onBlur_);
  5077.     },
  5078.  
  5079.     /**
  5080.      * When the list loses focus, unselect all items in the list.
  5081.      * @private
  5082.      */
  5083.     onBlur_: function() {
  5084.       this.selectionModel.unselectAll();
  5085.     },
  5086.   };
  5087.  
  5088.   /**
  5089.    * Create a new address list.
  5090.    * @constructor
  5091.    * @extends {options.AutofillProfileList}
  5092.    */
  5093.   var AutofillAddressList = cr.ui.define('list');
  5094.  
  5095.   AutofillAddressList.prototype = {
  5096.     __proto__: AutofillProfileList.prototype,
  5097.  
  5098.     decorate: function() {
  5099.       AutofillProfileList.prototype.decorate.call(this);
  5100.     },
  5101.  
  5102.     /** @inheritDoc */
  5103.     activateItemAtIndex: function(index) {
  5104.       AutofillOptions.loadAddressEditor(this.dataModel.item(index)[0]);
  5105.     },
  5106.  
  5107.     /** @inheritDoc */
  5108.     createItem: function(entry) {
  5109.       return new AddressListItem(entry);
  5110.     },
  5111.  
  5112.     /** @inheritDoc */
  5113.     deleteItemAtIndex: function(index) {
  5114.       AutofillOptions.removeAddress(this.dataModel.item(index)[0]);
  5115.     },
  5116.   };
  5117.  
  5118.   /**
  5119.    * Create a new credit card list.
  5120.    * @constructor
  5121.    * @extends {options.DeletableItemList}
  5122.    */
  5123.   var AutofillCreditCardList = cr.ui.define('list');
  5124.  
  5125.   AutofillCreditCardList.prototype = {
  5126.     __proto__: AutofillProfileList.prototype,
  5127.  
  5128.     decorate: function() {
  5129.       AutofillProfileList.prototype.decorate.call(this);
  5130.     },
  5131.  
  5132.     /** @inheritDoc */
  5133.     activateItemAtIndex: function(index) {
  5134.       AutofillOptions.loadCreditCardEditor(this.dataModel.item(index)[0]);
  5135.     },
  5136.  
  5137.     /** @inheritDoc */
  5138.     createItem: function(entry) {
  5139.       return new CreditCardListItem(entry);
  5140.     },
  5141.  
  5142.     /** @inheritDoc */
  5143.     deleteItemAtIndex: function(index) {
  5144.       AutofillOptions.removeCreditCard(this.dataModel.item(index)[0]);
  5145.     },
  5146.   };
  5147.  
  5148.   /**
  5149.    * Create a new value list.
  5150.    * @constructor
  5151.    * @extends {options.InlineEditableItemList}
  5152.    */
  5153.   var AutofillValuesList = cr.ui.define('list');
  5154.  
  5155.   AutofillValuesList.prototype = {
  5156.     __proto__: InlineEditableItemList.prototype,
  5157.  
  5158.     /** @inheritDoc */
  5159.     createItem: function(entry) {
  5160.       return new ValuesListItem(this, entry);
  5161.     },
  5162.  
  5163.     /** @inheritDoc */
  5164.     deleteItemAtIndex: function(index) {
  5165.       this.dataModel.splice(index, 1);
  5166.     },
  5167.  
  5168.     /** @inheritDoc */
  5169.     shouldFocusPlaceholder: function() {
  5170.       return false;
  5171.     },
  5172.  
  5173.     /**
  5174.      * Called when the list hierarchy as a whole loses or gains focus.
  5175.      * If the list was focused in response to a mouse click, call into the
  5176.      * superclass's implementation.  If the list was focused in response to a
  5177.      * keyboard navigation, focus the first item.
  5178.      * If the list loses focus, unselect all the elements.
  5179.      * @param {Event} e The change event.
  5180.      * @private
  5181.      */
  5182.     handleListFocusChange_: function(e) {
  5183.       // We check to see whether there is a selected item as a proxy for
  5184.       // distinguishing between mouse- and keyboard-originated focus events.
  5185.       var selectedItem = this.selectedItem;
  5186.       if (selectedItem)
  5187.         InlineEditableItemList.prototype.handleListFocusChange_.call(this, e);
  5188.  
  5189.       if (!e.newValue) {
  5190.         // When the list loses focus, unselect all the elements.
  5191.         this.selectionModel.unselectAll();
  5192.       } else {
  5193.         // When the list gains focus, select the first item if nothing else is
  5194.         // selected.
  5195.         var firstItem = this.getListItemByIndex(0);
  5196.         if (!selectedItem && firstItem && e.newValue)
  5197.           firstItem.handleFocus_();
  5198.       }
  5199.     },
  5200.  
  5201.     /**
  5202.      * Called when a new list item should be validated; subclasses are
  5203.      * responsible for implementing if validation is required.
  5204.      * @param {number} index The index of the item that was inserted or changed.
  5205.      * @param {number} remove The number items to remove.
  5206.      * @param {string} value The value of the item to insert.
  5207.      */
  5208.     validateAndSave: function(index, remove, value) {
  5209.       this.dataModel.splice(index, remove, value);
  5210.     },
  5211.   };
  5212.  
  5213.   /**
  5214.    * Create a new value list for phone number validation.
  5215.    * @constructor
  5216.    * @extends {options.AutofillValuesList}
  5217.    */
  5218.   var AutofillNameValuesList = cr.ui.define('list');
  5219.  
  5220.   AutofillNameValuesList.prototype = {
  5221.     __proto__: AutofillValuesList.prototype,
  5222.  
  5223.     /** @inheritDoc */
  5224.     createItem: function(entry) {
  5225.       return new NameListItem(this, entry);
  5226.     },
  5227.   };
  5228.  
  5229.   /**
  5230.    * Create a new value list for phone number validation.
  5231.    * @constructor
  5232.    * @extends {options.AutofillValuesList}
  5233.    */
  5234.   var AutofillPhoneValuesList = cr.ui.define('list');
  5235.  
  5236.   AutofillPhoneValuesList.prototype = {
  5237.     __proto__: AutofillValuesList.prototype,
  5238.  
  5239.     /** @inheritDoc */
  5240.     validateAndSave: function(index, remove, value) {
  5241.       var numbers = this.dataModel.slice(0, this.dataModel.length - 1);
  5242.       numbers.splice(index, remove, value);
  5243.       var info = new Array();
  5244.       info[0] = index;
  5245.       info[1] = numbers;
  5246.       info[2] = $('country').value;
  5247.       chrome.send('validatePhoneNumbers', info);
  5248.     },
  5249.   };
  5250.  
  5251.   return {
  5252.     AddressListItem: AddressListItem,
  5253.     CreditCardListItem: CreditCardListItem,
  5254.     ValuesListItem: ValuesListItem,
  5255.     NameListItem: NameListItem,
  5256.     AutofillAddressList: AutofillAddressList,
  5257.     AutofillCreditCardList: AutofillCreditCardList,
  5258.     AutofillValuesList: AutofillValuesList,
  5259.     AutofillNameValuesList: AutofillNameValuesList,
  5260.     AutofillPhoneValuesList: AutofillPhoneValuesList,
  5261.   };
  5262. });
  5263.  
  5264. // Copyright (c) 2011 The Chromium Authors. All rights reserved.
  5265. // Use of this source code is governed by a BSD-style license that can be
  5266. // found in the LICENSE file.
  5267.  
  5268. cr.define('options', function() {
  5269.   const OptionsPage = options.OptionsPage;
  5270.   const ArrayDataModel = cr.ui.ArrayDataModel;
  5271.  
  5272.   /////////////////////////////////////////////////////////////////////////////
  5273.   // AutofillOptions class:
  5274.  
  5275.   /**
  5276.    * Encapsulated handling of Autofill options page.
  5277.    * @constructor
  5278.    */
  5279.   function AutofillOptions() {
  5280.     OptionsPage.call(this,
  5281.                      'autofill',
  5282.                      templateData.autofillOptionsPageTabTitle,
  5283.                      'autofill-options');
  5284.   }
  5285.  
  5286.   cr.addSingletonGetter(AutofillOptions);
  5287.  
  5288.   AutofillOptions.prototype = {
  5289.     __proto__: OptionsPage.prototype,
  5290.  
  5291.     /**
  5292.      * The address list.
  5293.      * @type {DeletableItemList}
  5294.      * @private
  5295.      */
  5296.     addressList_: null,
  5297.  
  5298.     /**
  5299.      * The credit card list.
  5300.      * @type {DeletableItemList}
  5301.      * @private
  5302.      */
  5303.     creditCardList_: null,
  5304.  
  5305.     initializePage: function() {
  5306.       OptionsPage.prototype.initializePage.call(this);
  5307.  
  5308.       this.createAddressList_();
  5309.       this.createCreditCardList_();
  5310.  
  5311.       var self = this;
  5312.       $('autofill-add-address').onclick = function(event) {
  5313.         self.showAddAddressOverlay_();
  5314.       };
  5315.       $('autofill-add-creditcard').onclick = function(event) {
  5316.         self.showAddCreditCardOverlay_();
  5317.       };
  5318.  
  5319.       // TODO(jhawkins): What happens when Autofill is disabled whilst on the
  5320.       // Autofill options page?
  5321.     },
  5322.  
  5323.     /**
  5324.      * Creates, decorates and initializes the address list.
  5325.      * @private
  5326.      */
  5327.     createAddressList_: function() {
  5328.       this.addressList_ = $('address-list');
  5329.       options.autofillOptions.AutofillAddressList.decorate(this.addressList_);
  5330.       this.addressList_.autoExpands = true;
  5331.     },
  5332.  
  5333.     /**
  5334.      * Creates, decorates and initializes the credit card list.
  5335.      * @private
  5336.      */
  5337.     createCreditCardList_: function() {
  5338.       this.creditCardList_ = $('creditcard-list');
  5339.       options.autofillOptions.AutofillCreditCardList.decorate(
  5340.           this.creditCardList_);
  5341.       this.creditCardList_.autoExpands = true;
  5342.     },
  5343.  
  5344.     /**
  5345.      * Shows the 'Add address' overlay, specifically by loading the
  5346.      * 'Edit address' overlay, emptying the input fields and modifying the
  5347.      * overlay title.
  5348.      * @private
  5349.      */
  5350.     showAddAddressOverlay_: function() {
  5351.       var title = localStrings.getString('addAddressTitle');
  5352.       AutofillEditAddressOverlay.setTitle(title);
  5353.       AutofillEditAddressOverlay.clearInputFields();
  5354.       OptionsPage.navigateToPage('autofillEditAddress');
  5355.     },
  5356.  
  5357.     /**
  5358.      * Shows the 'Add credit card' overlay, specifically by loading the
  5359.      * 'Edit credit card' overlay, emptying the input fields and modifying the
  5360.      * overlay title.
  5361.      * @private
  5362.      */
  5363.     showAddCreditCardOverlay_: function() {
  5364.       var title = localStrings.getString('addCreditCardTitle');
  5365.       AutofillEditCreditCardOverlay.setTitle(title);
  5366.       AutofillEditCreditCardOverlay.clearInputFields();
  5367.       OptionsPage.navigateToPage('autofillEditCreditCard');
  5368.     },
  5369.  
  5370.     /**
  5371.      * Updates the data model for the address list with the values from
  5372.      * |entries|.
  5373.      * @param {Array} entries The list of addresses.
  5374.      */
  5375.     setAddressList_: function(entries) {
  5376.       this.addressList_.dataModel = new ArrayDataModel(entries);
  5377.     },
  5378.  
  5379.     /**
  5380.      * Updates the data model for the credit card list with the values from
  5381.      * |entries|.
  5382.      * @param {Array} entries The list of credit cards.
  5383.      */
  5384.     setCreditCardList_: function(entries) {
  5385.       this.creditCardList_.dataModel = new ArrayDataModel(entries);
  5386.     },
  5387.  
  5388.     /**
  5389.      * Removes the Autofill address represented by |guid|.
  5390.      * @param {String} guid The GUID of the address to remove.
  5391.      * @private
  5392.      */
  5393.     removeAddress_: function(guid) {
  5394.       chrome.send('removeAddress', [guid]);
  5395.     },
  5396.  
  5397.     /**
  5398.      * Removes the Autofill credit card represented by |guid|.
  5399.      * @param {String} guid The GUID of the credit card to remove.
  5400.      * @private
  5401.      */
  5402.     removeCreditCard_: function(guid) {
  5403.       chrome.send('removeCreditCard', [guid]);
  5404.     },
  5405.  
  5406.     /**
  5407.      * Requests profile data for the address represented by |guid| from the
  5408.      * PersonalDataManager. Once the data is loaded, the AutofillOptionsHandler
  5409.      * calls showEditAddressOverlay().
  5410.      * @param {String} guid The GUID of the address to edit.
  5411.      * @private
  5412.      */
  5413.     loadAddressEditor_: function(guid) {
  5414.       chrome.send('loadAddressEditor', [guid]);
  5415.     },
  5416.  
  5417.     /**
  5418.      * Requests profile data for the credit card represented by |guid| from the
  5419.      * PersonalDataManager. Once the data is loaded, the AutofillOptionsHandler
  5420.      * calls showEditCreditCardOverlay().
  5421.      * @param {String} guid The GUID of the credit card to edit.
  5422.      * @private
  5423.      */
  5424.     loadCreditCardEditor_: function(guid) {
  5425.       chrome.send('loadCreditCardEditor', [guid]);
  5426.     },
  5427.  
  5428.     /**
  5429.      * Shows the 'Edit address' overlay, using the data in |address| to fill the
  5430.      * input fields. |address| is a list with one item, an associative array
  5431.      * that contains the address data.
  5432.      * @private
  5433.      */
  5434.     showEditAddressOverlay_: function(address) {
  5435.       var title = localStrings.getString('editAddressTitle');
  5436.       AutofillEditAddressOverlay.setTitle(title);
  5437.       AutofillEditAddressOverlay.loadAddress(address);
  5438.       OptionsPage.navigateToPage('autofillEditAddress');
  5439.     },
  5440.  
  5441.     /**
  5442.      * Shows the 'Edit credit card' overlay, using the data in |credit_card| to
  5443.      * fill the input fields. |address| is a list with one item, an associative
  5444.      * array that contains the credit card data.
  5445.      * @private
  5446.      */
  5447.     showEditCreditCardOverlay_: function(creditCard) {
  5448.       var title = localStrings.getString('editCreditCardTitle');
  5449.       AutofillEditCreditCardOverlay.setTitle(title);
  5450.       AutofillEditCreditCardOverlay.loadCreditCard(creditCard);
  5451.       OptionsPage.navigateToPage('autofillEditCreditCard');
  5452.     },
  5453.   };
  5454.  
  5455.   AutofillOptions.setAddressList = function(entries) {
  5456.     AutofillOptions.getInstance().setAddressList_(entries);
  5457.   };
  5458.  
  5459.   AutofillOptions.setCreditCardList = function(entries) {
  5460.     AutofillOptions.getInstance().setCreditCardList_(entries);
  5461.   };
  5462.  
  5463.   AutofillOptions.removeAddress = function(guid) {
  5464.     AutofillOptions.getInstance().removeAddress_(guid);
  5465.   };
  5466.  
  5467.   AutofillOptions.removeCreditCard = function(guid) {
  5468.     AutofillOptions.getInstance().removeCreditCard_(guid);
  5469.   };
  5470.  
  5471.   AutofillOptions.loadAddressEditor = function(guid) {
  5472.     AutofillOptions.getInstance().loadAddressEditor_(guid);
  5473.   };
  5474.  
  5475.   AutofillOptions.loadCreditCardEditor = function(guid) {
  5476.     AutofillOptions.getInstance().loadCreditCardEditor_(guid);
  5477.   };
  5478.  
  5479.   AutofillOptions.editAddress = function(address) {
  5480.     AutofillOptions.getInstance().showEditAddressOverlay_(address);
  5481.   };
  5482.  
  5483.   AutofillOptions.editCreditCard = function(creditCard) {
  5484.     AutofillOptions.getInstance().showEditCreditCardOverlay_(creditCard);
  5485.   };
  5486.  
  5487.   // Export
  5488.   return {
  5489.     AutofillOptions: AutofillOptions
  5490.   };
  5491.  
  5492. });
  5493.  
  5494.  
  5495. // Copyright (c) 2011 The Chromium Authors. All rights reserved.
  5496. // Use of this source code is governed by a BSD-style license that can be
  5497. // found in the LICENSE file.
  5498.  
  5499. cr.define('options', function() {
  5500.   const OptionsPage = options.OptionsPage;
  5501.   const ArrayDataModel = cr.ui.ArrayDataModel;
  5502.  
  5503.   //
  5504.   // BrowserOptions class
  5505.   // Encapsulated handling of browser options page.
  5506.   //
  5507.   function BrowserOptions() {
  5508.     OptionsPage.call(this, 'browser',
  5509.                      templateData.browserPageTabTitle,
  5510.                      'browserPage');
  5511.   }
  5512.  
  5513.   cr.addSingletonGetter(BrowserOptions);
  5514.  
  5515.   BrowserOptions.prototype = {
  5516.     // Inherit BrowserOptions from OptionsPage.
  5517.     __proto__: options.OptionsPage.prototype,
  5518.  
  5519.     startup_pages_pref_: {
  5520.       'name': 'session.urls_to_restore_on_startup',
  5521.       'disabled': false
  5522.     },
  5523.  
  5524.     /**
  5525.      * At autocomplete list that can be attached to a text field during editing.
  5526.      * @type {HTMLElement}
  5527.      * @private
  5528.      */
  5529.     autocompleteList_: null,
  5530.  
  5531.     // The cached value of the instant.confirm_dialog_shown preference.
  5532.     instantConfirmDialogShown_: false,
  5533.  
  5534.     /**
  5535.      * Initialize BrowserOptions page.
  5536.      */
  5537.     initializePage: function() {
  5538.       // Call base class implementation to start preference initialization.
  5539.       OptionsPage.prototype.initializePage.call(this);
  5540.  
  5541.       // Wire up controls.
  5542.       $('startupUseCurrentButton').onclick = function(event) {
  5543.         chrome.send('setStartupPagesToCurrentPages');
  5544.       };
  5545.       $('defaultSearchManageEnginesButton').onclick = function(event) {
  5546.         OptionsPage.navigateToPage('searchEngines');
  5547.         chrome.send('coreOptionsUserMetricsAction',
  5548.             ['Options_ManageSearchEngines']);
  5549.       };
  5550.       $('defaultSearchEngine').onchange = this.setDefaultSearchEngine_;
  5551.  
  5552.       var self = this;
  5553.       $('instantEnabledCheckbox').customChangeHandler = function(event) {
  5554.         if (this.checked) {
  5555.           if (self.instantConfirmDialogShown_)
  5556.             chrome.send('enableInstant');
  5557.           else
  5558.             OptionsPage.navigateToPage('instantConfirm');
  5559.         } else {
  5560.           chrome.send('disableInstant');
  5561.         }
  5562.         return true;
  5563.       };
  5564.  
  5565.       $('instantFieldTrialCheckbox').addEventListener('change',
  5566.           function(event) {
  5567.             this.checked = true;
  5568.             chrome.send('disableInstant');
  5569.           });
  5570.  
  5571.       Preferences.getInstance().addEventListener('instant.confirm_dialog_shown',
  5572.           this.onInstantConfirmDialogShownChanged_.bind(this));
  5573.  
  5574.       Preferences.getInstance().addEventListener('instant.enabled',
  5575.           this.onInstantEnabledChanged_.bind(this));
  5576.  
  5577.       Preferences.getInstance().addEventListener(
  5578.           $('homepageUseNTPButton').pref,
  5579.           this.onHomepageUseNTPChanged_);
  5580.       var homepageField = $('homepageURL');
  5581.       homepageField.addEventListener('focus', function(event) {
  5582.         self.autocompleteList_.attachToInput(homepageField);
  5583.       });
  5584.       homepageField.addEventListener('blur', function(event) {
  5585.         self.autocompleteList_.detach();
  5586.       });
  5587.       homepageField.addEventListener('keydown', function(event) {
  5588.         // Remove focus when the user hits enter since people expect feedback
  5589.         // indicating that they are done editing.
  5590.         if (event.keyIdentifier == 'Enter')
  5591.           homepageField.blur();
  5592.       });
  5593.  
  5594.       // Text fields may change widths when the window changes size, so make
  5595.       // sure the suggestion list stays in sync.
  5596.       window.addEventListener('resize', function() {
  5597.         self.autocompleteList_.syncWidthToInput();
  5598.       });
  5599.  
  5600.       // Ensure that changes are committed when closing the page.
  5601.       window.addEventListener('unload', function() {
  5602.         if (document.activeElement == homepageField)
  5603.           homepageField.blur();
  5604.       });
  5605.  
  5606.       if (!cr.isChromeOS) {
  5607.         $('defaultBrowserUseAsDefaultButton').onclick = function(event) {
  5608.           chrome.send('becomeDefaultBrowser');
  5609.         };
  5610.       }
  5611.  
  5612.       var startupPagesList = $('startupPagesList');
  5613.       options.browser_options.StartupPageList.decorate(startupPagesList);
  5614.       startupPagesList.autoExpands = true;
  5615.  
  5616.       // Check if we are in the guest mode.
  5617.       if (cr.commandLine.options['--bwsi']) {
  5618.         // Hide the startup section.
  5619.         $('startupSection').hidden = true;
  5620.       } else {
  5621.         // Initialize control enabled states.
  5622.         Preferences.getInstance().addEventListener('session.restore_on_startup',
  5623.             this.updateCustomStartupPageControlStates_.bind(this));
  5624.         Preferences.getInstance().addEventListener(
  5625.             this.startup_pages_pref_.name,
  5626.             this.handleStartupPageListChange_.bind(this));
  5627.  
  5628.         this.updateCustomStartupPageControlStates_();
  5629.       }
  5630.  
  5631.       var suggestionList = new options.AutocompleteList();
  5632.       suggestionList.autoExpands = true;
  5633.       suggestionList.suggestionUpdateRequestCallback =
  5634.           this.requestAutocompleteSuggestions_.bind(this);
  5635.       $('main-content').appendChild(suggestionList);
  5636.       this.autocompleteList_ = suggestionList;
  5637.       startupPagesList.autocompleteList = suggestionList;
  5638.     },
  5639.  
  5640.     /**
  5641.      * Called when the value of the instant.confirm_dialog_shown preference
  5642.      * changes. Cache this value.
  5643.      * @param {Event} event Change event.
  5644.      * @private
  5645.      */
  5646.     onInstantConfirmDialogShownChanged_: function(event) {
  5647.       this.instantConfirmDialogShown_ = event.value['value'];
  5648.     },
  5649.  
  5650.     /**
  5651.      * Called when the value of the instant.enabled preference changes. Request
  5652.      * the state of the Instant field trial experiment.
  5653.      * @param {Event} event Change event.
  5654.      * @private
  5655.      */
  5656.     onInstantEnabledChanged_: function(event) {
  5657.       chrome.send('getInstantFieldTrialStatus');
  5658.     },
  5659.  
  5660.     /**
  5661.      * Called to set the Instant field trial status.
  5662.      * @param {boolean} enabled If true, the experiment is enabled.
  5663.      * @private
  5664.      */
  5665.     setInstantFieldTrialStatus_: function(enabled) {
  5666.       $('instantEnabledCheckbox').hidden = enabled;
  5667.       $('instantFieldTrialCheckbox').hidden = !enabled;
  5668.       $('instantLabel').htmlFor = enabled ? 'instantFieldTrialCheckbox'
  5669.                                           : 'instantEnabledCheckbox';
  5670.     },
  5671.  
  5672.     /**
  5673.      * Called when the value of the homepage-use-NTP pref changes.
  5674.      * Updates the disabled state of the homepage text field.
  5675.      * Notice that the text field can be disabled for other reasons too
  5676.      * (it can be managed by policy, for instance).
  5677.      * @param {Event} event Change event.
  5678.      * @private
  5679.      */
  5680.     onHomepageUseNTPChanged_: function(event) {
  5681.       var homepageField = $('homepageURL');
  5682.       var homepageUseURLButton = $('homepageUseURLButton');
  5683.       homepageField.setDisabled('radioNotSelected',
  5684.                                 !homepageUseURLButton.checked);
  5685.     },
  5686.  
  5687.     /**
  5688.      * Update the Default Browsers section based on the current state.
  5689.      * @param {string} statusString Description of the current default state.
  5690.      * @param {boolean} isDefault Whether or not the browser is currently
  5691.      *     default.
  5692.      * @param {boolean} canBeDefault Whether or not the browser can be default.
  5693.      * @private
  5694.      */
  5695.     updateDefaultBrowserState_: function(statusString, isDefault,
  5696.                                          canBeDefault) {
  5697.       var label = $('defaultBrowserState');
  5698.       label.textContent = statusString;
  5699.  
  5700.       $('defaultBrowserUseAsDefaultButton').disabled = !canBeDefault ||
  5701.                                                        isDefault;
  5702.     },
  5703.  
  5704.     /**
  5705.      * Clears the search engine popup.
  5706.      * @private
  5707.      */
  5708.     clearSearchEngines_: function() {
  5709.       $('defaultSearchEngine').textContent = '';
  5710.     },
  5711.  
  5712.     /**
  5713.      * Updates the search engine popup with the given entries.
  5714.      * @param {Array} engines List of available search engines.
  5715.      * @param {number} defaultValue The value of the current default engine.
  5716.      * @param {boolean} defaultManaged Whether the default search provider is
  5717.      *     managed. If true, the default search provider can't be changed.
  5718.      */
  5719.     updateSearchEngines_: function(engines, defaultValue, defaultManaged) {
  5720.       this.clearSearchEngines_();
  5721.       engineSelect = $('defaultSearchEngine');
  5722.       engineSelect.disabled = defaultManaged;
  5723.       engineCount = engines.length;
  5724.       var defaultIndex = -1;
  5725.       for (var i = 0; i < engineCount; i++) {
  5726.         var engine = engines[i];
  5727.         var option = new Option(engine['name'], engine['index']);
  5728.         if (defaultValue == option.value)
  5729.           defaultIndex = i;
  5730.         engineSelect.appendChild(option);
  5731.       }
  5732.       if (defaultIndex >= 0)
  5733.         engineSelect.selectedIndex = defaultIndex;
  5734.     },
  5735.  
  5736.     /**
  5737.      * Returns true if the custom startup page control block should
  5738.      * be enabled.
  5739.      * @returns {boolean} Whether the startup page controls should be
  5740.      *     enabled.
  5741.      */
  5742.     shouldEnableCustomStartupPageControls: function(pages) {
  5743.       return $('startupShowPagesButton').checked &&
  5744.           !this.startup_pages_pref_.disabled;
  5745.     },
  5746.  
  5747.     /**
  5748.      * Updates the startup pages list with the given entries.
  5749.      * @param {Array} pages List of startup pages.
  5750.      * @private
  5751.      */
  5752.     updateStartupPages_: function(pages) {
  5753.       var model = new ArrayDataModel(pages);
  5754.       // Add a "new page" row.
  5755.       model.push({
  5756.         'modelIndex': '-1'
  5757.       });
  5758.       $('startupPagesList').dataModel = model;
  5759.     },
  5760.  
  5761.     /**
  5762.      * Sets the enabled state of the custom startup page list controls
  5763.      * based on the current startup radio button selection.
  5764.      * @private
  5765.      */
  5766.     updateCustomStartupPageControlStates_: function() {
  5767.       var disable = !this.shouldEnableCustomStartupPageControls();
  5768.       var startupPagesList = $('startupPagesList');
  5769.       startupPagesList.disabled = disable;
  5770.       // Explicitly set disabled state for input text elements.
  5771.       var inputs = startupPagesList.querySelectorAll("input[type='text']");
  5772.       for (var i = 0; i < inputs.length; i++)
  5773.         inputs[i].disabled = disable;
  5774.       $('startupUseCurrentButton').disabled = disable;
  5775.     },
  5776.  
  5777.     /**
  5778.      * Handle change events of the preference
  5779.      * 'session.urls_to_restore_on_startup'.
  5780.      * @param {event} preference changed event.
  5781.      * @private
  5782.      */
  5783.     handleStartupPageListChange_: function(event) {
  5784.       this.startup_pages_pref_.disabled = event.value['disabled'];
  5785.       this.updateCustomStartupPageControlStates_();
  5786.     },
  5787.  
  5788.     /**
  5789.      * Set the default search engine based on the popup selection.
  5790.      */
  5791.     setDefaultSearchEngine_: function() {
  5792.       var engineSelect = $('defaultSearchEngine');
  5793.       var selectedIndex = engineSelect.selectedIndex;
  5794.       if (selectedIndex >= 0) {
  5795.         var selection = engineSelect.options[selectedIndex];
  5796.         chrome.send('setDefaultSearchEngine', [String(selection.value)]);
  5797.       }
  5798.     },
  5799.  
  5800.     /**
  5801.      * Sends an asynchronous request for new autocompletion suggestions for the
  5802.      * the given query. When new suggestions are available, the C++ handler will
  5803.      * call updateAutocompleteSuggestions_.
  5804.      * @param {string} query List of autocomplete suggestions.
  5805.      * @private
  5806.      */
  5807.     requestAutocompleteSuggestions_: function(query) {
  5808.       chrome.send('requestAutocompleteSuggestions', [query]);
  5809.     },
  5810.  
  5811.     /**
  5812.      * Updates the autocomplete suggestion list with the given entries.
  5813.      * @param {Array} pages List of autocomplete suggestions.
  5814.      * @private
  5815.      */
  5816.     updateAutocompleteSuggestions_: function(suggestions) {
  5817.       var list = this.autocompleteList_;
  5818.       // If the trigger for this update was a value being selected from the
  5819.       // current list, do nothing.
  5820.       if (list.targetInput && list.selectedItem &&
  5821.           list.selectedItem['url'] == list.targetInput.value)
  5822.         return;
  5823.       list.suggestions = suggestions;
  5824.     },
  5825.   };
  5826.  
  5827.   BrowserOptions.updateDefaultBrowserState = function(statusString, isDefault,
  5828.                                                       canBeDefault) {
  5829.     if (!cr.isChromeOS) {
  5830.       BrowserOptions.getInstance().updateDefaultBrowserState_(statusString,
  5831.                                                               isDefault,
  5832.                                                               canBeDefault);
  5833.     }
  5834.   };
  5835.  
  5836.   BrowserOptions.updateSearchEngines = function(engines, defaultValue,
  5837.                                                 defaultManaged) {
  5838.     BrowserOptions.getInstance().updateSearchEngines_(engines, defaultValue,
  5839.                                                       defaultManaged);
  5840.   };
  5841.  
  5842.   BrowserOptions.updateStartupPages = function(pages) {
  5843.     BrowserOptions.getInstance().updateStartupPages_(pages);
  5844.   };
  5845.  
  5846.   BrowserOptions.updateAutocompleteSuggestions = function(suggestions) {
  5847.     BrowserOptions.getInstance().updateAutocompleteSuggestions_(suggestions);
  5848.   };
  5849.  
  5850.   BrowserOptions.setInstantFieldTrialStatus = function(enabled) {
  5851.     BrowserOptions.getInstance().setInstantFieldTrialStatus_(enabled);
  5852.   };
  5853.  
  5854.   // Export
  5855.   return {
  5856.     BrowserOptions: BrowserOptions
  5857.   };
  5858.  
  5859. });
  5860.  
  5861. // Copyright (c) 2011 The Chromium Authors. All rights reserved.
  5862. // Use of this source code is governed by a BSD-style license that can be
  5863. // found in the LICENSE file.
  5864.  
  5865. cr.define('options.browser_options', function() {
  5866.   const AutocompleteList = options.AutocompleteList;
  5867.   const InlineEditableItem = options.InlineEditableItem;
  5868.   const InlineEditableItemList = options.InlineEditableItemList;
  5869.  
  5870.   /**
  5871.    * Creates a new startup page list item.
  5872.    * @param {Object} pageInfo The page this item represents.
  5873.    * @constructor
  5874.    * @extends {cr.ui.ListItem}
  5875.    */
  5876.   function StartupPageListItem(pageInfo) {
  5877.     var el = cr.doc.createElement('div');
  5878.     el.pageInfo_ = pageInfo;
  5879.     StartupPageListItem.decorate(el);
  5880.     return el;
  5881.   }
  5882.  
  5883.   /**
  5884.    * Decorates an element as a startup page list item.
  5885.    * @param {!HTMLElement} el The element to decorate.
  5886.    */
  5887.   StartupPageListItem.decorate = function(el) {
  5888.     el.__proto__ = StartupPageListItem.prototype;
  5889.     el.decorate();
  5890.   };
  5891.  
  5892.   StartupPageListItem.prototype = {
  5893.     __proto__: InlineEditableItem.prototype,
  5894.  
  5895.     /**
  5896.      * Input field for editing the page url.
  5897.      * @type {HTMLElement}
  5898.      * @private
  5899.      */
  5900.     urlField_: null,
  5901.  
  5902.     /** @inheritDoc */
  5903.     decorate: function() {
  5904.       InlineEditableItem.prototype.decorate.call(this);
  5905.  
  5906.       var pageInfo = this.pageInfo_;
  5907.  
  5908.       if (pageInfo['modelIndex'] == '-1') {
  5909.         this.isPlaceholder = true;
  5910.         pageInfo['title'] = localStrings.getString('startupAddLabel');
  5911.         pageInfo['url'] = '';
  5912.       }
  5913.  
  5914.       var titleEl = this.ownerDocument.createElement('div');
  5915.       titleEl.className = 'title';
  5916.       titleEl.classList.add('favicon-cell');
  5917.       titleEl.classList.add('weakrtl');
  5918.       titleEl.textContent = pageInfo['title'];
  5919.       if (!this.isPlaceholder) {
  5920.         titleEl.style.backgroundImage = url('chrome://favicon/' +
  5921.                                             pageInfo['url']);
  5922.         titleEl.title = pageInfo['tooltip'];
  5923.       }
  5924.  
  5925.       this.contentElement.appendChild(titleEl);
  5926.  
  5927.       var urlEl = this.createEditableTextCell(pageInfo['url']);
  5928.       urlEl.className = 'url';
  5929.       urlEl.classList.add('weakrtl');
  5930.       this.contentElement.appendChild(urlEl);
  5931.  
  5932.       var urlField = urlEl.querySelector('input')
  5933.       urlField.required = true;
  5934.       urlField.className = 'weakrtl';
  5935.       this.urlField_ = urlField;
  5936.  
  5937.       this.addEventListener('commitedit', this.onEditCommitted_);
  5938.  
  5939.       var self = this;
  5940.       urlField.addEventListener('focus', function(event) {
  5941.         self.parentNode.autocompleteList.attachToInput(urlField);
  5942.       });
  5943.       urlField.addEventListener('blur', function(event) {
  5944.         self.parentNode.autocompleteList.detach();
  5945.       });
  5946.  
  5947.       if (!this.isPlaceholder)
  5948.         this.draggable = true;
  5949.     },
  5950.  
  5951.     /** @inheritDoc */
  5952.     get currentInputIsValid() {
  5953.       return this.urlField_.validity.valid;
  5954.     },
  5955.  
  5956.     /** @inheritDoc */
  5957.     get hasBeenEdited() {
  5958.       return this.urlField_.value != this.pageInfo_['url'];
  5959.     },
  5960.  
  5961.     /**
  5962.      * Called when committing an edit; updates the model.
  5963.      * @param {Event} e The end event.
  5964.      * @private
  5965.      */
  5966.     onEditCommitted_: function(e) {
  5967.       var url = this.urlField_.value;
  5968.       if (this.isPlaceholder)
  5969.         chrome.send('addStartupPage', [url]);
  5970.       else
  5971.         chrome.send('editStartupPage', [this.pageInfo_['modelIndex'], url]);
  5972.     },
  5973.   };
  5974.  
  5975.   var StartupPageList = cr.ui.define('list');
  5976.  
  5977.   StartupPageList.prototype = {
  5978.     __proto__: InlineEditableItemList.prototype,
  5979.  
  5980.     /**
  5981.      * An autocomplete suggestion list for URL editing.
  5982.      * @type {AutocompleteList}
  5983.      */
  5984.     autocompleteList: null,
  5985.  
  5986.     /**
  5987.      * The drop position information: "below" or "above".
  5988.      */
  5989.     dropPos: null,
  5990.  
  5991.     /** @inheritDoc */
  5992.     decorate: function() {
  5993.       InlineEditableItemList.prototype.decorate.call(this);
  5994.  
  5995.       // Listen to drag and drop events.
  5996.       this.addEventListener('dragstart', this.handleDragStart_.bind(this));
  5997.       this.addEventListener('dragenter', this.handleDragEnter_.bind(this));
  5998.       this.addEventListener('dragover', this.handleDragOver_.bind(this));
  5999.       this.addEventListener('drop', this.handleDrop_.bind(this));
  6000.       this.addEventListener('dragleave', this.handleDragLeave_.bind(this));
  6001.       this.addEventListener('dragend', this.handleDragEnd_.bind(this));
  6002.     },
  6003.  
  6004.     /** @inheritDoc */
  6005.     createItem: function(pageInfo) {
  6006.       var item = new StartupPageListItem(pageInfo);
  6007.       item.urlField_.disabled = this.disabled;
  6008.       return item;
  6009.     },
  6010.  
  6011.     /** @inheritDoc */
  6012.     deleteItemAtIndex: function(index) {
  6013.       chrome.send('removeStartupPages', [String(index)]);
  6014.     },
  6015.  
  6016.     /*
  6017.      * Computes the target item of drop event.
  6018.      * @param {Event} e The drop or dragover event.
  6019.      * @private
  6020.      */
  6021.     getTargetFromDropEvent_ : function(e) {
  6022.       var target = e.target;
  6023.       // e.target may be an inner element of the list item
  6024.       while (target != null && !(target instanceof StartupPageListItem)) {
  6025.         target = target.parentNode;
  6026.       }
  6027.       return target;
  6028.     },
  6029.  
  6030.     /*
  6031.      * Handles the dragstart event.
  6032.      * @param {Event} e The dragstart event.
  6033.      * @private
  6034.      */
  6035.     handleDragStart_: function(e) {
  6036.       // Prevent dragging if the list is disabled.
  6037.       if (this.disabled) {
  6038.         e.preventDefault();
  6039.         return false;
  6040.       }
  6041.  
  6042.       var target = e.target;
  6043.       // StartupPageListItem should be the only draggable element type in the
  6044.       // page but let's make sure.
  6045.       if (target instanceof StartupPageListItem) {
  6046.         this.draggedItem = target;
  6047.         this.draggedItem.editable = false;
  6048.         e.dataTransfer.effectAllowed = 'move';
  6049.         // We need to put some kind of data in the drag or it will be
  6050.         // ignored.  Use the URL in case the user drags to a text field or the
  6051.         // desktop.
  6052.         e.dataTransfer.setData('text/plain', target.urlField_.value);
  6053.       }
  6054.     },
  6055.  
  6056.     /*
  6057.      * Handles the dragenter event.
  6058.      * @param {Event} e The dragenter event.
  6059.      * @private
  6060.      */
  6061.     handleDragEnter_: function(e) {
  6062.       e.preventDefault();
  6063.     },
  6064.  
  6065.     /*
  6066.      * Handles the dragover event.
  6067.      * @param {Event} e The dragover event.
  6068.      * @private
  6069.      */
  6070.     handleDragOver_: function(e) {
  6071.       var dropTarget = this.getTargetFromDropEvent_(e);
  6072.       // Determines whether the drop target is to accept the drop.
  6073.       // The drop is only successful on another StartupPageListItem.
  6074.       if (!(dropTarget instanceof StartupPageListItem) ||
  6075.           dropTarget == this.draggedItem || dropTarget.isPlaceholder) {
  6076.         this.hideDropMarker_();
  6077.         return;
  6078.       }
  6079.       // Compute the drop postion. Should we move the dragged item to
  6080.       // below or above the drop target?
  6081.       var rect = dropTarget.getBoundingClientRect();
  6082.       var dy = e.clientY - rect.top;
  6083.       var yRatio = dy / rect.height;
  6084.       var dropPos = yRatio <= .5 ? 'above' : 'below';
  6085.       this.dropPos = dropPos;
  6086.       this.showDropMarker_(dropTarget, dropPos);
  6087.       e.preventDefault();
  6088.     },
  6089.  
  6090.     /*
  6091.      * Handles the drop event.
  6092.      * @param {Event} e The drop event.
  6093.      * @private
  6094.      */
  6095.     handleDrop_: function(e) {
  6096.       var dropTarget = this.getTargetFromDropEvent_(e);
  6097.       this.hideDropMarker_();
  6098.  
  6099.       // Insert the selection at the new position.
  6100.       var newIndex = this.dataModel.indexOf(dropTarget.pageInfo_);
  6101.       if (this.dropPos == 'below')
  6102.         newIndex += 1;
  6103.  
  6104.       var selected = this.selectionModel.selectedIndexes;
  6105.       var stringized_selected = [];
  6106.       for (var j = 0; j < selected.length; j++)
  6107.         stringized_selected.push(String(selected[j]));
  6108.  
  6109.       chrome.send('dragDropStartupPage',
  6110.           [String(newIndex), stringized_selected] );
  6111.     },
  6112.  
  6113.     /*
  6114.      * Handles the dragleave event.
  6115.      * @param {Event} e The dragleave event
  6116.      * @private
  6117.      */
  6118.     handleDragLeave_: function(e) {
  6119.       this.hideDropMarker_();
  6120.     },
  6121.  
  6122.     /**
  6123.      * Handles the dragend event.
  6124.      * @param {Event} e The dragend event
  6125.      * @private
  6126.      */
  6127.     handleDragEnd_: function(e) {
  6128.       this.draggedItem.editable = true;
  6129.       this.draggedItem.updateEditState();
  6130.     },
  6131.  
  6132.     /*
  6133.      * Shows and positions the marker to indicate the drop target.
  6134.      * @param {HTMLElement} target The current target list item of drop
  6135.      * @param {string} pos 'below' or 'above'
  6136.      * @private
  6137.      */
  6138.     showDropMarker_ : function(target, pos) {
  6139.       window.clearTimeout(this.hideDropMarkerTimer_);
  6140.       var marker = $('startupPagesListDropmarker');
  6141.       var rect = target.getBoundingClientRect();
  6142.       var markerHeight = 6;
  6143.       if (pos == 'above') {
  6144.         marker.style.top = (rect.top - markerHeight/2) + 'px';
  6145.       } else {
  6146.         marker.style.top = (rect.bottom - markerHeight/2) + 'px';
  6147.       }
  6148.       marker.style.width = rect.width + 'px';
  6149.       marker.style.left = rect.left + 'px';
  6150.       marker.style.display = 'block';
  6151.     },
  6152.  
  6153.     /*
  6154.      * Hides the drop marker.
  6155.      * @private
  6156.      */
  6157.     hideDropMarker_ : function() {
  6158.       // Hide the marker in a timeout to reduce flickering as we move between
  6159.       // valid drop targets.
  6160.       window.clearTimeout(this.hideDropMarkerTimer_);
  6161.       this.hideDropMarkerTimer_ = window.setTimeout(function() {
  6162.         $('startupPagesListDropmarker').style.display = '';
  6163.       }, 100);
  6164.     },
  6165.   };
  6166.  
  6167.   return {
  6168.     StartupPageList: StartupPageList
  6169.   };
  6170. });
  6171.  
  6172. // Copyright (c) 2011 The Chromium Authors. All rights reserved.
  6173. // Use of this source code is governed by a BSD-style license that can be
  6174. // found in the LICENSE file.
  6175.  
  6176. cr.define('options', function() {
  6177.   var OptionsPage = options.OptionsPage;
  6178.  
  6179.   /**
  6180.    * ClearBrowserDataOverlay class
  6181.    * Encapsulated handling of the 'Clear Browser Data' overlay page.
  6182.    * @class
  6183.    */
  6184.   function ClearBrowserDataOverlay() {
  6185.     OptionsPage.call(this, 'clearBrowserData',
  6186.                      templateData.clearBrowserDataOverlayTabTitle,
  6187.                      'clearBrowserDataOverlay');
  6188.   }
  6189.  
  6190.   cr.addSingletonGetter(ClearBrowserDataOverlay);
  6191.  
  6192.   ClearBrowserDataOverlay.prototype = {
  6193.     // Inherit ClearBrowserDataOverlay from OptionsPage.
  6194.     __proto__: OptionsPage.prototype,
  6195.  
  6196.     /**
  6197.      * Initialize the page.
  6198.      */
  6199.     initializePage: function() {
  6200.       // Call base class implementation to starts preference initialization.
  6201.       OptionsPage.prototype.initializePage.call(this);
  6202.  
  6203.       var f = this.updateCommitButtonState_.bind(this);
  6204.       var types = ['browser.clear_data.browsing_history',
  6205.                    'browser.clear_data.download_history',
  6206.                    'browser.clear_data.cache',
  6207.                    'browser.clear_data.cookies',
  6208.                    'browser.clear_data.passwords',
  6209.                    'browser.clear_data.form_data'];
  6210.       types.forEach(function(type) {
  6211.           Preferences.getInstance().addEventListener(type, f);
  6212.       });
  6213.  
  6214.       var checkboxes = document.querySelectorAll(
  6215.           '#cbdContentArea input[type=checkbox]');
  6216.       for (var i = 0; i < checkboxes.length; i++) {
  6217.         checkboxes[i].onclick = f;
  6218.       }
  6219.       this.updateCommitButtonState_();
  6220.  
  6221.       $('clearBrowserDataDismiss').onclick = function(event) {
  6222.         ClearBrowserDataOverlay.dismiss();
  6223.       };
  6224.       $('clearBrowserDataCommit').onclick = function(event) {
  6225.         chrome.send('performClearBrowserData');
  6226.       };
  6227.     },
  6228.  
  6229.     // Set the enabled state of the commit button.
  6230.     updateCommitButtonState_: function() {
  6231.       var checkboxes = document.querySelectorAll(
  6232.           '#cbdContentArea input[type=checkbox]');
  6233.       var isChecked = false;
  6234.       for (var i = 0; i < checkboxes.length; i++) {
  6235.         if (checkboxes[i].checked) {
  6236.           isChecked = true;
  6237.           break;
  6238.         }
  6239.       }
  6240.       $('clearBrowserDataCommit').disabled = !isChecked;
  6241.     },
  6242.   };
  6243.  
  6244.   //
  6245.   // Chrome callbacks
  6246.   //
  6247.   ClearBrowserDataOverlay.setClearingState = function(state) {
  6248.     $('deleteBrowsingHistoryCheckbox').disabled = state;
  6249.     $('deleteDownloadHistoryCheckbox').disabled = state;
  6250.     $('deleteCacheCheckbox').disabled = state;
  6251.     $('deleteCookiesCheckbox').disabled = state;
  6252.     $('deletePasswordsCheckbox').disabled = state;
  6253.     $('deleteFormDataCheckbox').disabled = state;
  6254.     $('clearBrowserDataTimePeriod').disabled = state;
  6255.     $('cbdThrobber').style.visibility = state ? 'visible' : 'hidden';
  6256.  
  6257.     if (state)
  6258.       $('clearBrowserDataCommit').disabled = true;
  6259.     else
  6260.       ClearBrowserDataOverlay.getInstance().updateCommitButtonState_();
  6261.   };
  6262.  
  6263.   ClearBrowserDataOverlay.doneClearing = function() {
  6264.     // The delay gives the user some feedback that the clearing
  6265.     // actually worked. Otherwise the dialog just vanishes instantly in most
  6266.     // cases.
  6267.     window.setTimeout(function() {
  6268.       ClearBrowserDataOverlay.dismiss();
  6269.     }, 200);
  6270.   };
  6271.  
  6272.   ClearBrowserDataOverlay.dismiss = function() {
  6273.     OptionsPage.closeOverlay();
  6274.     this.setClearingState(false);
  6275.   };
  6276.  
  6277.   // Export
  6278.   return {
  6279.     ClearBrowserDataOverlay: ClearBrowserDataOverlay
  6280.   };
  6281. });
  6282.  
  6283. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  6284. // Use of this source code is governed by a BSD-style license that can be
  6285. // found in the LICENSE file.
  6286.  
  6287. cr.define('options', function() {
  6288.  
  6289.   var OptionsPage = options.OptionsPage;
  6290.  
  6291.   //////////////////////////////////////////////////////////////////////////////
  6292.   // ContentSettings class:
  6293.  
  6294.   /**
  6295.    * Encapsulated handling of content settings page.
  6296.    * @constructor
  6297.    */
  6298.   function ContentSettings() {
  6299.     this.activeNavTab = null;
  6300.     OptionsPage.call(this, 'content', templateData.contentSettingsPageTabTitle,
  6301.                      'content-settings-page');
  6302.   }
  6303.  
  6304.   cr.addSingletonGetter(ContentSettings);
  6305.  
  6306.   ContentSettings.prototype = {
  6307.     __proto__: OptionsPage.prototype,
  6308.  
  6309.     initializePage: function() {
  6310.       OptionsPage.prototype.initializePage.call(this);
  6311.  
  6312.       chrome.send('getContentFilterSettings');
  6313.  
  6314.       var exceptionsButtons =
  6315.           this.pageDiv.querySelectorAll('.exceptions-list-button');
  6316.       for (var i = 0; i < exceptionsButtons.length; i++) {
  6317.         exceptionsButtons[i].onclick = function(event) {
  6318.           var page = ContentSettingsExceptionsArea.getInstance();
  6319.           page.showList(
  6320.               event.target.getAttribute('contentType'));
  6321.           OptionsPage.navigateToPage('contentExceptions');
  6322.           // Add on the proper hash for the content type, and store that in the
  6323.           // history so back/forward and tab restore works.
  6324.           var hash = event.target.getAttribute('contentType');
  6325.           window.history.replaceState({pageName: page.name}, page.title,
  6326.                                       '/' + page.name + "#" + hash);
  6327.         };
  6328.       }
  6329.  
  6330.       var manageHandlersButton = $('manage-handlers-button');
  6331.       if (manageHandlersButton) {
  6332.         manageHandlersButton.onclick = function(event) {
  6333.           OptionsPage.navigateToPage('handlers');
  6334.         };
  6335.       }
  6336.  
  6337.       var manageIntentsButton = $('manage-intents-button');
  6338.       if (manageIntentsButton) {
  6339.         manageIntentsButton.onclick = function(event) {
  6340.           OptionsPage.navigateToPage('intents');
  6341.         };
  6342.       }
  6343.  
  6344.       // Cookies filter page ---------------------------------------------------
  6345.       $('show-cookies-button').onclick = function(event) {
  6346.         chrome.send('coreOptionsUserMetricsAction', ['Options_ShowCookies']);
  6347.         OptionsPage.navigateToPage('cookies');
  6348.       };
  6349.  
  6350.       if (!templateData.enable_web_intents && $('intent-section'))
  6351.         $('intent-section').hidden = true;
  6352.     },
  6353.   };
  6354.  
  6355.   ContentSettings.updateHandlersEnabledRadios = function(enabled) {
  6356.     var selector = '#content-settings-page input[type=radio][value=' +
  6357.         (enabled ? 'allow' : 'block') + '].handler-radio';
  6358.     document.querySelector(selector).checked = true;
  6359.   };
  6360.  
  6361.   /**
  6362.    * Sets the values for all the content settings radios.
  6363.    * @param {Object} dict A mapping from radio groups to the checked value for
  6364.    *     that group.
  6365.    */
  6366.   ContentSettings.setContentFilterSettingsValue = function(dict) {
  6367.     for (var group in dict) {
  6368.       document.querySelector('input[type=radio][name=' + group + '][value=' +
  6369.                              dict[group]['value'] + ']').checked = true;
  6370.       var radios = document.querySelectorAll('input[type=radio][name=' +
  6371.                                              group + ']');
  6372.       var managedBy = dict[group]['managedBy'];
  6373.       for (var i = 0, len = radios.length; i < len; i++) {
  6374.         radios[i].disabled = (managedBy != 'default');
  6375.         radios[i].controlledBy = managedBy;
  6376.       }
  6377.     }
  6378.     OptionsPage.updateManagedBannerVisibility();
  6379.   };
  6380.  
  6381.   /**
  6382.    * Initializes an exceptions list.
  6383.    * @param {string} type The content type that we are setting exceptions for.
  6384.    * @param {Array} list An array of pairs, where the first element of each pair
  6385.    *     is the filter string, and the second is the setting (allow/block).
  6386.    */
  6387.   ContentSettings.setExceptions = function(type, list) {
  6388.     var exceptionsList =
  6389.         document.querySelector('div[contentType=' + type + ']' +
  6390.                                ' list[mode=normal]');
  6391.     exceptionsList.setExceptions(list);
  6392.   };
  6393.  
  6394.   ContentSettings.setHandlers = function(list) {
  6395.     $('handlers-list').setHandlers(list);
  6396.   };
  6397.  
  6398.   ContentSettings.setIgnoredHandlers = function(list) {
  6399.     $('ignored-handlers-list').setHandlers(list);
  6400.   };
  6401.  
  6402.   ContentSettings.setOTRExceptions = function(type, list) {
  6403.     var exceptionsList =
  6404.         document.querySelector('div[contentType=' + type + ']' +
  6405.                                ' list[mode=otr]');
  6406.  
  6407.     exceptionsList.parentNode.hidden = false;
  6408.     exceptionsList.setExceptions(list);
  6409.   };
  6410.  
  6411.   /**
  6412.    * The browser's response to a request to check the validity of a given URL
  6413.    * pattern.
  6414.    * @param {string} type The content type.
  6415.    * @param {string} mode The browser mode.
  6416.    * @param {string} pattern The pattern.
  6417.    * @param {bool} valid Whether said pattern is valid in the context of
  6418.    *     a content exception setting.
  6419.    */
  6420.   ContentSettings.patternValidityCheckComplete =
  6421.       function(type, mode, pattern, valid) {
  6422.     var exceptionsList =
  6423.         document.querySelector('div[contentType=' + type + '] ' +
  6424.                                'list[mode=' + mode + ']');
  6425.     exceptionsList.patternValidityCheckComplete(pattern, valid);
  6426.   };
  6427.  
  6428.   // Export
  6429.   return {
  6430.     ContentSettings: ContentSettings
  6431.   };
  6432.  
  6433. });
  6434.  
  6435. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  6436. // Use of this source code is governed by a BSD-style license that can be
  6437. // found in the LICENSE file.
  6438.  
  6439. cr.define('options.contentSettings', function() {
  6440.   const InlineEditableItemList = options.InlineEditableItemList;
  6441.   const InlineEditableItem = options.InlineEditableItem;
  6442.   const ArrayDataModel = cr.ui.ArrayDataModel;
  6443.  
  6444.   /**
  6445.    * Creates a new exceptions list item.
  6446.    * @param {string} contentType The type of the list.
  6447.    * @param {string} mode The browser mode, 'otr' or 'normal'.
  6448.    * @param {boolean} enableAskOption Whether to show an 'ask every time'
  6449.    *     option in the select.
  6450.    * @param {Object} exception A dictionary that contains the data of the
  6451.    *     exception.
  6452.    * @constructor
  6453.    * @extends {options.InlineEditableItem}
  6454.    */
  6455.   function ExceptionsListItem(contentType, mode, enableAskOption, exception) {
  6456.     var el = cr.doc.createElement('div');
  6457.     el.mode = mode;
  6458.     el.contentType = contentType;
  6459.     el.enableAskOption = enableAskOption;
  6460.     el.dataItem = exception;
  6461.     el.__proto__ = ExceptionsListItem.prototype;
  6462.     el.decorate();
  6463.  
  6464.     return el;
  6465.   }
  6466.  
  6467.   ExceptionsListItem.prototype = {
  6468.     __proto__: InlineEditableItem.prototype,
  6469.  
  6470.     /**
  6471.      * Called when an element is decorated as a list item.
  6472.      */
  6473.     decorate: function() {
  6474.       InlineEditableItem.prototype.decorate.call(this);
  6475.  
  6476.       this.isPlaceholder = !this.pattern;
  6477.       var patternCell = this.createEditableTextCell(this.pattern);
  6478.       patternCell.className = 'exception-pattern';
  6479.       patternCell.classList.add('weakrtl');
  6480.       this.contentElement.appendChild(patternCell);
  6481.       if (this.pattern)
  6482.         this.patternLabel = patternCell.querySelector('.static-text');
  6483.       var input = patternCell.querySelector('input');
  6484.  
  6485.       // TODO(stuartmorgan): Create an createEditableSelectCell abstracting
  6486.       // this code.
  6487.       // Setting label for display mode. |pattern| will be null for the 'add new
  6488.       // exception' row.
  6489.       if (this.pattern) {
  6490.         var settingLabel = cr.doc.createElement('span');
  6491.         settingLabel.textContent = this.settingForDisplay();
  6492.         settingLabel.className = 'exception-setting';
  6493.         settingLabel.setAttribute('displaymode', 'static');
  6494.         this.contentElement.appendChild(settingLabel);
  6495.         this.settingLabel = settingLabel;
  6496.       }
  6497.  
  6498.       // Setting select element for edit mode.
  6499.       var select = cr.doc.createElement('select');
  6500.       var optionAllow = cr.doc.createElement('option');
  6501.       optionAllow.textContent = templateData.allowException;
  6502.       optionAllow.value = 'allow';
  6503.       select.appendChild(optionAllow);
  6504.  
  6505.       if (this.enableAskOption) {
  6506.         var optionAsk = cr.doc.createElement('option');
  6507.         optionAsk.textContent = templateData.askException;
  6508.         optionAsk.value = 'ask';
  6509.         select.appendChild(optionAsk);
  6510.       }
  6511.  
  6512.       if (this.contentType == 'cookies') {
  6513.         var optionSession = cr.doc.createElement('option');
  6514.         optionSession.textContent = templateData.sessionException;
  6515.         optionSession.value = 'session';
  6516.         select.appendChild(optionSession);
  6517.       }
  6518.  
  6519.       if (this.contentType != 'fullscreen') {
  6520.         var optionBlock = cr.doc.createElement('option');
  6521.         optionBlock.textContent = templateData.blockException;
  6522.         optionBlock.value = 'block';
  6523.         select.appendChild(optionBlock);
  6524.       }
  6525.  
  6526.       this.contentElement.appendChild(select);
  6527.       select.className = 'exception-setting';
  6528.       if (this.pattern)
  6529.         select.setAttribute('displaymode', 'edit');
  6530.  
  6531.       // Used to track whether the URL pattern in the input is valid.
  6532.       // This will be true if the browser process has informed us that the
  6533.       // current text in the input is valid. Changing the text resets this to
  6534.       // false, and getting a response from the browser sets it back to true.
  6535.       // It starts off as false for empty string (new exceptions) or true for
  6536.       // already-existing exceptions (which we assume are valid).
  6537.       this.inputValidityKnown = this.pattern;
  6538.       // This one tracks the actual validity of the pattern in the input. This
  6539.       // starts off as true so as not to annoy the user when he adds a new and
  6540.       // empty input.
  6541.       this.inputIsValid = true;
  6542.  
  6543.       this.input = input;
  6544.       this.select = select;
  6545.  
  6546.       this.updateEditables();
  6547.  
  6548.       // Editing notifications and geolocation is disabled for now.
  6549.       if (this.contentType == 'notifications' ||
  6550.           this.contentType == 'location') {
  6551.         this.editable = false;
  6552.       }
  6553.  
  6554.       // If the source of the content setting exception is not the user
  6555.       // preference, then the content settings exception is managed and the user
  6556.       // can't edit it.
  6557.       if (this.dataItem.source  &&
  6558.           this.dataItem.source != 'preference') {
  6559.         this.setAttribute('managedby', this.dataItem.source);
  6560.         this.deletable = false;
  6561.         this.editable = false;
  6562.       }
  6563.  
  6564.       var listItem = this;
  6565.       // Handle events on the editable nodes.
  6566.       input.oninput = function(event) {
  6567.         listItem.inputValidityKnown = false;
  6568.         chrome.send('checkExceptionPatternValidity',
  6569.                     [listItem.contentType, listItem.mode, input.value]);
  6570.       };
  6571.  
  6572.       // Listen for edit events.
  6573.       this.addEventListener('canceledit', this.onEditCancelled_);
  6574.       this.addEventListener('commitedit', this.onEditCommitted_);
  6575.     },
  6576.  
  6577.     /**
  6578.      * The pattern (e.g., a URL) for the exception.
  6579.      * @type {string}
  6580.      */
  6581.     get pattern() {
  6582.       return this.dataItem['displayPattern'];
  6583.     },
  6584.     set pattern(pattern) {
  6585.       this.dataItem['displayPattern'] = pattern;
  6586.     },
  6587.  
  6588.     /**
  6589.      * The setting (allow/block) for the exception.
  6590.      * @type {string}
  6591.      */
  6592.     get setting() {
  6593.       return this.dataItem['setting'];
  6594.     },
  6595.     set setting(setting) {
  6596.       this.dataItem['setting'] = setting;
  6597.     },
  6598.  
  6599.     /**
  6600.      * Gets a human-readable setting string.
  6601.      * @type {string}
  6602.      */
  6603.     settingForDisplay: function() {
  6604.       var setting = this.setting;
  6605.       if (setting == 'allow')
  6606.         return templateData.allowException;
  6607.       else if (setting == 'block')
  6608.         return templateData.blockException;
  6609.       else if (setting == 'ask')
  6610.         return templateData.askException;
  6611.       else if (setting == 'session')
  6612.         return templateData.sessionException;
  6613.     },
  6614.  
  6615.     /**
  6616.      * Update this list item to reflect whether the input is a valid pattern.
  6617.      * @param {boolean} valid Whether said pattern is valid in the context of
  6618.      *     a content exception setting.
  6619.      */
  6620.     setPatternValid: function(valid) {
  6621.       if (valid || !this.input.value)
  6622.         this.input.setCustomValidity('');
  6623.       else
  6624.         this.input.setCustomValidity(' ');
  6625.       this.inputIsValid = valid;
  6626.       this.inputValidityKnown = true;
  6627.     },
  6628.  
  6629.     /**
  6630.      * Set the <input> to its original contents. Used when the user quits
  6631.      * editing.
  6632.      */
  6633.     resetInput: function() {
  6634.       this.input.value = this.pattern;
  6635.     },
  6636.  
  6637.     /**
  6638.      * Copy the data model values to the editable nodes.
  6639.      */
  6640.     updateEditables: function() {
  6641.       this.resetInput();
  6642.  
  6643.       var settingOption =
  6644.           this.select.querySelector('[value=\'' + this.setting + '\']');
  6645.       if (settingOption)
  6646.         settingOption.selected = true;
  6647.     },
  6648.  
  6649.     /** @inheritDoc */
  6650.     get currentInputIsValid() {
  6651.       return this.inputValidityKnown && this.inputIsValid;
  6652.     },
  6653.  
  6654.     /** @inheritDoc */
  6655.     get hasBeenEdited() {
  6656.       var livePattern = this.input.value;
  6657.       var liveSetting = this.select.value;
  6658.       return livePattern != this.pattern || liveSetting != this.setting;
  6659.     },
  6660.  
  6661.     /**
  6662.      * Called when committing an edit.
  6663.      * @param {Event} e The end event.
  6664.      * @private
  6665.      */
  6666.     onEditCommitted_: function(e) {
  6667.       var newPattern = this.input.value;
  6668.       var newSetting = this.select.value;
  6669.  
  6670.       this.finishEdit(newPattern, newSetting);
  6671.     },
  6672.  
  6673.     /**
  6674.      * Called when cancelling an edit; resets the control states.
  6675.      * @param {Event} e The cancel event.
  6676.      * @private
  6677.      */
  6678.     onEditCancelled_: function() {
  6679.       this.updateEditables();
  6680.       this.setPatternValid(true);
  6681.     },
  6682.  
  6683.     /**
  6684.      * Editing is complete; update the model.
  6685.      * @param {string} newPattern The pattern that the user entered.
  6686.      * @param {string} newSetting The setting the user chose.
  6687.      */
  6688.     finishEdit: function(newPattern, newSetting) {
  6689.       this.patternLabel.textContent = newPattern;
  6690.       this.settingLabel.textContent = this.settingForDisplay();
  6691.       var oldPattern = this.pattern;
  6692.       this.pattern = newPattern;
  6693.       this.setting = newSetting;
  6694.  
  6695.       // TODO(estade): this will need to be updated if geolocation/notifications
  6696.       // become editable.
  6697.       if (oldPattern != newPattern) {
  6698.         chrome.send('removeException',
  6699.                     [this.contentType, this.mode, oldPattern]);
  6700.       }
  6701.  
  6702.       chrome.send('setException',
  6703.                   [this.contentType, this.mode, newPattern, newSetting]);
  6704.     }
  6705.   };
  6706.  
  6707.   /**
  6708.    * Creates a new list item for the Add New Item row, which doesn't represent
  6709.    * an actual entry in the exceptions list but allows the user to add new
  6710.    * exceptions.
  6711.    * @param {string} contentType The type of the list.
  6712.    * @param {string} mode The browser mode, 'otr' or 'normal'.
  6713.    * @param {boolean} enableAskOption Whether to show an 'ask every time'
  6714.    *     option in the select.
  6715.    * @constructor
  6716.    * @extends {cr.ui.ExceptionsListItem}
  6717.    */
  6718.   function ExceptionsAddRowListItem(contentType, mode, enableAskOption) {
  6719.     var el = cr.doc.createElement('div');
  6720.     el.mode = mode;
  6721.     el.contentType = contentType;
  6722.     el.enableAskOption = enableAskOption;
  6723.     el.dataItem = [];
  6724.     el.__proto__ = ExceptionsAddRowListItem.prototype;
  6725.     el.decorate();
  6726.  
  6727.     return el;
  6728.   }
  6729.  
  6730.   ExceptionsAddRowListItem.prototype = {
  6731.     __proto__: ExceptionsListItem.prototype,
  6732.  
  6733.     decorate: function() {
  6734.       ExceptionsListItem.prototype.decorate.call(this);
  6735.  
  6736.       this.input.placeholder = templateData.addNewExceptionInstructions;
  6737.  
  6738.       // Do we always want a default of allow?
  6739.       this.setting = 'allow';
  6740.     },
  6741.  
  6742.     /**
  6743.      * Clear the <input> and let the placeholder text show again.
  6744.      */
  6745.     resetInput: function() {
  6746.       this.input.value = '';
  6747.     },
  6748.  
  6749.     /** @inheritDoc */
  6750.     get hasBeenEdited() {
  6751.       return this.input.value != '';
  6752.     },
  6753.  
  6754.     /**
  6755.      * Editing is complete; update the model. As long as the pattern isn't
  6756.      * empty, we'll just add it.
  6757.      * @param {string} newPattern The pattern that the user entered.
  6758.      * @param {string} newSetting The setting the user chose.
  6759.      */
  6760.     finishEdit: function(newPattern, newSetting) {
  6761.       this.resetInput();
  6762.       chrome.send('setException',
  6763.                   [this.contentType, this.mode, newPattern, newSetting]);
  6764.     },
  6765.   };
  6766.  
  6767.   /**
  6768.    * Creates a new exceptions list.
  6769.    * @constructor
  6770.    * @extends {cr.ui.List}
  6771.    */
  6772.   var ExceptionsList = cr.ui.define('list');
  6773.  
  6774.   ExceptionsList.prototype = {
  6775.     __proto__: InlineEditableItemList.prototype,
  6776.  
  6777.     /**
  6778.      * Called when an element is decorated as a list.
  6779.      */
  6780.     decorate: function() {
  6781.       InlineEditableItemList.prototype.decorate.call(this);
  6782.  
  6783.       this.classList.add('settings-list');
  6784.  
  6785.       for (var parentNode = this.parentNode; parentNode;
  6786.            parentNode = parentNode.parentNode) {
  6787.         if (parentNode.hasAttribute('contentType')) {
  6788.           this.contentType = parentNode.getAttribute('contentType');
  6789.           break;
  6790.         }
  6791.       }
  6792.  
  6793.       this.mode = this.getAttribute('mode');
  6794.  
  6795.       var exceptionList = this;
  6796.  
  6797.       // Whether the exceptions in this list allow an 'Ask every time' option.
  6798.       this.enableAskOption = this.contentType == 'plugins';
  6799.  
  6800.       this.autoExpands = true;
  6801.       this.reset();
  6802.     },
  6803.  
  6804.     /**
  6805.      * Creates an item to go in the list.
  6806.      * @param {Object} entry The element from the data model for this row.
  6807.      */
  6808.     createItem: function(entry) {
  6809.       if (entry) {
  6810.         return new ExceptionsListItem(this.contentType,
  6811.                                       this.mode,
  6812.                                       this.enableAskOption,
  6813.                                       entry);
  6814.       } else {
  6815.         var addRowItem = new ExceptionsAddRowListItem(this.contentType,
  6816.                                                       this.mode,
  6817.                                                       this.enableAskOption);
  6818.         addRowItem.deletable = false;
  6819.         return addRowItem;
  6820.       }
  6821.     },
  6822.  
  6823.     /**
  6824.      * Sets the exceptions in the js model.
  6825.      * @param {Object} entries A list of dictionaries of values, each dictionary
  6826.      *     represents an exception.
  6827.      */
  6828.     setExceptions: function(entries) {
  6829.       var deleteCount = this.dataModel.length;
  6830.  
  6831.       if (this.isEditable()) {
  6832.         // We don't want to remove the Add New Exception row.
  6833.         deleteCount = deleteCount - 1;
  6834.       }
  6835.  
  6836.       var args = [0, deleteCount];
  6837.       args.push.apply(args, entries);
  6838.       this.dataModel.splice.apply(this.dataModel, args);
  6839.     },
  6840.  
  6841.     /**
  6842.      * The browser has finished checking a pattern for validity. Update the
  6843.      * list item to reflect this.
  6844.      * @param {string} pattern The pattern.
  6845.      * @param {bool} valid Whether said pattern is valid in the context of
  6846.      *     a content exception setting.
  6847.      */
  6848.     patternValidityCheckComplete: function(pattern, valid) {
  6849.       var listItems = this.items;
  6850.       for (var i = 0; i < listItems.length; i++) {
  6851.         var listItem = listItems[i];
  6852.         // Don't do anything for messages for the item if it is not the intended
  6853.         // recipient, or if the response is stale (i.e. the input value has
  6854.         // changed since we sent the request to analyze it).
  6855.         if (pattern == listItem.input.value)
  6856.           listItem.setPatternValid(valid);
  6857.       }
  6858.     },
  6859.  
  6860.     /**
  6861.      * Returns whether the rows are editable in this list.
  6862.      */
  6863.     isEditable: function() {
  6864.       // Editing notifications and geolocation is disabled for now.
  6865.       return !(this.contentType == 'notifications' ||
  6866.                this.contentType == 'location' ||
  6867.                this.contentType == 'fullscreen');
  6868.     },
  6869.  
  6870.     /**
  6871.      * Removes all exceptions from the js model.
  6872.      */
  6873.     reset: function() {
  6874.       if (this.isEditable()) {
  6875.         // The null creates the Add New Exception row.
  6876.         this.dataModel = new ArrayDataModel([null]);
  6877.       } else {
  6878.         this.dataModel = new ArrayDataModel([]);
  6879.       }
  6880.     },
  6881.  
  6882.     /** @inheritDoc */
  6883.     deleteItemAtIndex: function(index) {
  6884.       var listItem = this.getListItemByIndex(index);
  6885.       if (listItem.undeletable)
  6886.         return;
  6887.  
  6888.       var dataItem = listItem.dataItem;
  6889.       var args = [listItem.contentType];
  6890.       if (listItem.contentType == 'location')
  6891.         args.push(dataItem['origin'], dataItem['embeddingOrigin']);
  6892.       else if (listItem.contentType == 'notifications')
  6893.         args.push(dataItem['origin'], dataItem['setting']);
  6894.       else
  6895.         args.push(listItem.mode, listItem.pattern);
  6896.  
  6897.       chrome.send('removeException', args);
  6898.     },
  6899.   };
  6900.  
  6901.   var OptionsPage = options.OptionsPage;
  6902.  
  6903.   /**
  6904.    * Encapsulated handling of content settings list subpage.
  6905.    * @constructor
  6906.    */
  6907.   function ContentSettingsExceptionsArea() {
  6908.     OptionsPage.call(this, 'contentExceptions',
  6909.                      templateData.contentSettingsPageTabTitle,
  6910.                      'content-settings-exceptions-area');
  6911.   }
  6912.  
  6913.   cr.addSingletonGetter(ContentSettingsExceptionsArea);
  6914.  
  6915.   ContentSettingsExceptionsArea.prototype = {
  6916.     __proto__: OptionsPage.prototype,
  6917.  
  6918.     initializePage: function() {
  6919.       OptionsPage.prototype.initializePage.call(this);
  6920.  
  6921.       var exceptionsLists = this.pageDiv.querySelectorAll('list');
  6922.       for (var i = 0; i < exceptionsLists.length; i++) {
  6923.         options.contentSettings.ExceptionsList.decorate(exceptionsLists[i]);
  6924.       }
  6925.  
  6926.       ContentSettingsExceptionsArea.hideOTRLists();
  6927.  
  6928.       // If the user types in the URL without a hash, show just cookies.
  6929.       this.showList('cookies');
  6930.     },
  6931.  
  6932.     /**
  6933.      * Shows one list and hides all others.
  6934.      * @param {string} type The content type.
  6935.      */
  6936.     showList: function(type) {
  6937.       var header = this.pageDiv.querySelector('h1');
  6938.       header.textContent = templateData[type + '_header'];
  6939.  
  6940.       var divs = this.pageDiv.querySelectorAll('div[contentType]');
  6941.       for (var i = 0; i < divs.length; i++) {
  6942.         if (divs[i].getAttribute('contentType') == type)
  6943.           divs[i].hidden = false;
  6944.         else
  6945.           divs[i].hidden = true;
  6946.       }
  6947.     },
  6948.  
  6949.     /**
  6950.      * Called after the page has been shown. Show the content type for the
  6951.      * location's hash.
  6952.      */
  6953.     didShowPage: function() {
  6954.       var hash = location.hash;
  6955.       if (hash)
  6956.         this.showList(hash.slice(1));
  6957.     },
  6958.   };
  6959.  
  6960.   /**
  6961.    * Called when the last incognito window is closed.
  6962.    */
  6963.   ContentSettingsExceptionsArea.OTRProfileDestroyed = function() {
  6964.     this.hideOTRLists();
  6965.   };
  6966.  
  6967.   /**
  6968.    * Clears and hides the incognito exceptions lists.
  6969.    */
  6970.   ContentSettingsExceptionsArea.hideOTRLists = function() {
  6971.     var otrLists = document.querySelectorAll('list[mode=otr]');
  6972.  
  6973.     for (var i = 0; i < otrLists.length; i++) {
  6974.       otrLists[i].reset();
  6975.       otrLists[i].parentNode.hidden = true;
  6976.     }
  6977.   };
  6978.  
  6979.   return {
  6980.     ExceptionsListItem: ExceptionsListItem,
  6981.     ExceptionsAddRowListItem: ExceptionsAddRowListItem,
  6982.     ExceptionsList: ExceptionsList,
  6983.     ContentSettingsExceptionsArea: ContentSettingsExceptionsArea,
  6984.   };
  6985. });
  6986.  
  6987. // Copyright (c) 2011 The Chromium Authors. All rights reserved.
  6988. // Use of this source code is governed by a BSD-style license that can be
  6989. // found in the LICENSE file.
  6990.  
  6991. cr.define('options', function() {
  6992.  
  6993.   //////////////////////////////////////////////////////////////////////////////
  6994.   // ContentSettingsRadio class:
  6995.  
  6996.   // Define a constructor that uses an input element as its underlying element.
  6997.   var ContentSettingsRadio = cr.ui.define('input');
  6998.  
  6999.   ContentSettingsRadio.prototype = {
  7000.     __proto__: HTMLInputElement.prototype,
  7001.  
  7002.     /**
  7003.      * Initialization function for the cr.ui framework.
  7004.      */
  7005.     decorate: function() {
  7006.       this.type = 'radio';
  7007.       var self = this;
  7008.  
  7009.       this.addEventListener('change',
  7010.           function(e) {
  7011.             chrome.send('setContentFilter', [this.name, this.value]);
  7012.           });
  7013.     },
  7014.   };
  7015.  
  7016.   /**
  7017.    * Whether the content setting is controlled by something else than the user's
  7018.    * settings (either 'policy' or 'extension').
  7019.    * @type {string}
  7020.    */
  7021.   cr.defineProperty(ContentSettingsRadio, 'controlledBy', cr.PropertyKind.ATTR);
  7022.  
  7023.   //////////////////////////////////////////////////////////////////////////////
  7024.   // HandlersEnabledRadio class:
  7025.  
  7026.   // Define a constructor that uses an input element as its underlying element.
  7027.   var HandlersEnabledRadio = cr.ui.define('input');
  7028.  
  7029.   HandlersEnabledRadio.prototype = {
  7030.     __proto__: HTMLInputElement.prototype,
  7031.  
  7032.     /**
  7033.      * Initialization function for the cr.ui framework.
  7034.      */
  7035.     decorate: function() {
  7036.       this.type = 'radio';
  7037.       var self = this;
  7038.  
  7039.       this.addEventListener('change',
  7040.           function(e) {
  7041.             chrome.send('setHandlersEnabled', [this.value == 'allow']);
  7042.           });
  7043.     },
  7044.   };
  7045.  
  7046.   // Export
  7047.   return {
  7048.     ContentSettingsRadio: ContentSettingsRadio,
  7049.     HandlersEnabledRadio: HandlersEnabledRadio
  7050.   };
  7051.  
  7052. });
  7053.  
  7054.  
  7055. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  7056. // Use of this source code is governed by a BSD-style license that can be
  7057. // found in the LICENSE file.
  7058.  
  7059. cr.define('options', function() {
  7060.   const DeletableItemList = options.DeletableItemList;
  7061.   const DeletableItem = options.DeletableItem;
  7062.   const ArrayDataModel = cr.ui.ArrayDataModel;
  7063.   const ListSingleSelectionModel = cr.ui.ListSingleSelectionModel;
  7064.  
  7065.   // This structure maps the various cookie type names from C++ (hence the
  7066.   // underscores) to arrays of the different types of data each has, along with
  7067.   // the i18n name for the description of that data type.
  7068.   const cookieInfo = {
  7069.     'cookie': [ ['name', 'label_cookie_name'],
  7070.                 ['content', 'label_cookie_content'],
  7071.                 ['domain', 'label_cookie_domain'],
  7072.                 ['path', 'label_cookie_path'],
  7073.                 ['sendfor', 'label_cookie_send_for'],
  7074.                 ['accessibleToScript', 'label_cookie_accessible_to_script'],
  7075.                 ['created', 'label_cookie_created'],
  7076.                 ['expires', 'label_cookie_expires'] ],
  7077.     'app_cache': [ ['manifest', 'label_app_cache_manifest'],
  7078.                    ['size', 'label_local_storage_size'],
  7079.                    ['created', 'label_cookie_created'],
  7080.                    ['accessed', 'label_cookie_last_accessed'] ],
  7081.     'database': [ ['name', 'label_cookie_name'],
  7082.                   ['desc', 'label_webdb_desc'],
  7083.                   ['size', 'label_local_storage_size'],
  7084.                   ['modified', 'label_local_storage_last_modified'] ],
  7085.     'local_storage': [ ['origin', 'label_local_storage_origin'],
  7086.                        ['size', 'label_local_storage_size'],
  7087.                        ['modified', 'label_local_storage_last_modified'] ],
  7088.     'indexed_db': [ ['origin', 'label_indexed_db_origin'],
  7089.                     ['size', 'label_indexed_db_size'],
  7090.                     ['modified', 'label_indexed_db_last_modified'] ],
  7091.     'file_system': [ ['origin', 'label_file_system_origin'],
  7092.                      ['persistent', 'label_file_system_persistent_usage' ],
  7093.                      ['temporary', 'label_file_system_temporary_usage' ] ],
  7094.   };
  7095.  
  7096.   const localStrings = new LocalStrings();
  7097.  
  7098.   /**
  7099.    * Returns the item's height, like offsetHeight but such that it works better
  7100.    * when the page is zoomed. See the similar calculation in @{code cr.ui.List}.
  7101.    * This version also accounts for the animation done in this file.
  7102.    * @param {Element} item The item to get the height of.
  7103.    * @return {number} The height of the item, calculated with zooming in mind.
  7104.    */
  7105.   function getItemHeight(item) {
  7106.     var height = item.style.height;
  7107.     // Use the fixed animation target height if set, in case the element is
  7108.     // currently being animated and we'd get an intermediate height below.
  7109.     if (height && height.substr(-2) == 'px')
  7110.       return parseInt(height.substr(0, height.length - 2));
  7111.     return item.getBoundingClientRect().height;
  7112.   }
  7113.  
  7114.   /**
  7115.    * Create tree nodes for the objects in the data array, and insert them all
  7116.    * into the given list using its @{code splice} method at the given index.
  7117.    * @param {Array.<Object>} data The data objects for the nodes to add.
  7118.    * @param {number} start The index at which to start inserting the nodes.
  7119.    * @return {Array.<CookieTreeNode>} An array of CookieTreeNodes added.
  7120.    */
  7121.   function spliceTreeNodes(data, start, list) {
  7122.     var nodes = data.map(function(x) { return new CookieTreeNode(x); });
  7123.     // Insert [start, 0] at the beginning of the array of nodes, making it
  7124.     // into the arguments we want to pass to @{code list.splice} below.
  7125.     nodes.splice(0, 0, start, 0);
  7126.     list.splice.apply(list, nodes);
  7127.     // Remove the [start, 0] prefix and return the array of nodes.
  7128.     nodes.splice(0, 2);
  7129.     return nodes;
  7130.   }
  7131.  
  7132.   var parentLookup = {};
  7133.   var lookupRequests = {};
  7134.  
  7135.   /**
  7136.    * Creates a new list item for sites data. Note that these are created and
  7137.    * destroyed lazily as they scroll into and out of view, so they must be
  7138.    * stateless. We cache the expanded item in @{code CookiesList} though, so it
  7139.    * can keep state. (Mostly just which item is selected.)
  7140.    * @param {Object} origin Data used to create a cookie list item.
  7141.    * @param {CookiesList} list The list that will contain this item.
  7142.    * @constructor
  7143.    * @extends {DeletableItem}
  7144.    */
  7145.   function CookieListItem(origin, list) {
  7146.     var listItem = new DeletableItem(null);
  7147.     listItem.__proto__ = CookieListItem.prototype;
  7148.  
  7149.     listItem.origin = origin;
  7150.     listItem.list = list;
  7151.     listItem.decorate();
  7152.  
  7153.     // This hooks up updateOrigin() to the list item, makes the top-level
  7154.     // tree nodes (i.e., origins) register their IDs in parentLookup, and
  7155.     // causes them to request their children if they have none. Note that we
  7156.     // have special logic in the setter for the parent property to make sure
  7157.     // that we can still garbage collect list items when they scroll out of
  7158.     // view, even though it appears that we keep a direct reference.
  7159.     if (origin) {
  7160.       origin.parent = listItem;
  7161.       origin.updateOrigin();
  7162.     }
  7163.  
  7164.     return listItem;
  7165.   }
  7166.  
  7167.   CookieListItem.prototype = {
  7168.     __proto__: DeletableItem.prototype,
  7169.  
  7170.     /** @inheritDoc */
  7171.     decorate: function() {
  7172.       this.siteChild = this.ownerDocument.createElement('div');
  7173.       this.siteChild.className = 'cookie-site';
  7174.       this.dataChild = this.ownerDocument.createElement('div');
  7175.       this.dataChild.className = 'cookie-data';
  7176.       this.sizeChild = this.ownerDocument.createElement('div');
  7177.       this.sizeChild.className = 'cookie-size';
  7178.       this.itemsChild = this.ownerDocument.createElement('div');
  7179.       this.itemsChild.className = 'cookie-items';
  7180.       this.infoChild = this.ownerDocument.createElement('div');
  7181.       this.infoChild.className = 'cookie-details';
  7182.       this.infoChild.hidden = true;
  7183.       var remove = this.ownerDocument.createElement('button');
  7184.       remove.textContent = localStrings.getString('remove_cookie');
  7185.       remove.onclick = this.removeCookie_.bind(this);
  7186.       this.infoChild.appendChild(remove);
  7187.       var content = this.contentElement;
  7188.       content.appendChild(this.siteChild);
  7189.       content.appendChild(this.dataChild);
  7190.       content.appendChild(this.sizeChild);
  7191.       content.appendChild(this.itemsChild);
  7192.       this.itemsChild.appendChild(this.infoChild);
  7193.       if (this.origin && this.origin.data) {
  7194.         this.siteChild.textContent = this.origin.data.title;
  7195.         this.siteChild.setAttribute('title', this.origin.data.title);
  7196.       }
  7197.       this.itemList_ = [];
  7198.     },
  7199.  
  7200.     /** @type {boolean} */
  7201.     get expanded() {
  7202.       return this.expanded_;
  7203.     },
  7204.     set expanded(expanded) {
  7205.       if (this.expanded_ == expanded)
  7206.         return;
  7207.       this.expanded_ = expanded;
  7208.       if (expanded) {
  7209.         var oldExpanded = this.list.expandedItem;
  7210.         this.list.expandedItem = this;
  7211.         this.updateItems_();
  7212.         if (oldExpanded)
  7213.           oldExpanded.expanded = false;
  7214.         this.classList.add('show-items');
  7215.       } else {
  7216.         if (this.list.expandedItem == this) {
  7217.           this.list.leadItemHeight = 0;
  7218.           this.list.expandedItem = null;
  7219.         }
  7220.         this.style.height = '';
  7221.         this.itemsChild.style.height = '';
  7222.         this.classList.remove('show-items');
  7223.       }
  7224.     },
  7225.  
  7226.     /**
  7227.      * The callback for the "remove" button shown when an item is selected.
  7228.      * Requests that the currently selected cookie be removed.
  7229.      * @private
  7230.      */
  7231.     removeCookie_: function() {
  7232.       if (this.selectedIndex_ >= 0) {
  7233.         var item = this.itemList_[this.selectedIndex_];
  7234.         if (item && item.node)
  7235.           chrome.send('removeCookie', [item.node.pathId]);
  7236.       }
  7237.     },
  7238.  
  7239.     /**
  7240.      * Disable animation within this cookie list item, in preparation for making
  7241.      * changes that will need to be animated. Makes it possible to measure the
  7242.      * contents without displaying them, to set animation targets.
  7243.      * @private
  7244.      */
  7245.     disableAnimation_: function() {
  7246.       this.itemsHeight_ = getItemHeight(this.itemsChild);
  7247.       this.classList.add('measure-items');
  7248.     },
  7249.  
  7250.     /**
  7251.      * Enable animation after changing the contents of this cookie list item.
  7252.      * See @{code disableAnimation_}.
  7253.      * @private
  7254.      */
  7255.     enableAnimation_: function() {
  7256.       if (!this.classList.contains('measure-items'))
  7257.         this.disableAnimation_();
  7258.       this.itemsChild.style.height = '';
  7259.       // This will force relayout in order to calculate the new heights.
  7260.       var itemsHeight = getItemHeight(this.itemsChild);
  7261.       var fixedHeight = getItemHeight(this) + itemsHeight - this.itemsHeight_;
  7262.       this.itemsChild.style.height = this.itemsHeight_ + 'px';
  7263.       // Force relayout before enabling animation, so that if we have
  7264.       // changed things since the last layout, they will not be animated
  7265.       // during subsequent layouts.
  7266.       this.itemsChild.offsetHeight;
  7267.       this.classList.remove('measure-items');
  7268.       this.itemsChild.style.height = itemsHeight + 'px';
  7269.       this.style.height = fixedHeight + 'px';
  7270.       if (this.expanded)
  7271.         this.list.leadItemHeight = fixedHeight;
  7272.     },
  7273.  
  7274.     /**
  7275.      * Updates the origin summary to reflect changes in its items.
  7276.      * Both CookieListItem and CookieTreeNode implement this API.
  7277.      * This implementation scans the descendants to update the text.
  7278.      */
  7279.     updateOrigin: function() {
  7280.       var info = {
  7281.         cookies: 0,
  7282.         database: false,
  7283.         localStorage: false,
  7284.         appCache: false,
  7285.         indexedDb: false,
  7286.         fileSystem: false,
  7287.       };
  7288.       if (this.origin)
  7289.         this.origin.collectSummaryInfo(info);
  7290.       var list = [];
  7291.       if (info.cookies > 1)
  7292.         list.push(localStrings.getStringF('cookie_plural', info.cookies));
  7293.       else if (info.cookies > 0)
  7294.         list.push(localStrings.getString('cookie_singular'));
  7295.       if (info.database || info.indexedDb)
  7296.         list.push(localStrings.getString('cookie_database_storage'));
  7297.       if (info.localStorage)
  7298.         list.push(localStrings.getString('cookie_local_storage'));
  7299.       if (info.appCache)
  7300.         list.push(localStrings.getString('cookie_app_cache'));
  7301.       if (info.fileSystem)
  7302.         list.push(localStrings.getString('cookie_file_system'));
  7303.       var text = '';
  7304.       for (var i = 0; i < list.length; ++i)
  7305.         if (text.length > 0)
  7306.           text += ', ' + list[i];
  7307.         else
  7308.           text = list[i];
  7309.       this.dataChild.textContent = text;
  7310.       if (info.quota && info.quota.totalUsage) {
  7311.         this.sizeChild.textContent = info.quota.totalUsage;
  7312.       }
  7313.  
  7314.       if (this.expanded)
  7315.         this.updateItems_();
  7316.     },
  7317.  
  7318.     /**
  7319.      * Updates the items section to reflect changes, animating to the new state.
  7320.      * Removes existing contents and calls @{code CookieTreeNode.createItems}.
  7321.      * @private
  7322.      */
  7323.     updateItems_: function() {
  7324.       this.disableAnimation_();
  7325.       this.itemsChild.textContent = '';
  7326.       this.infoChild.hidden = true;
  7327.       this.selectedIndex_ = -1;
  7328.       this.itemList_ = [];
  7329.       if (this.origin)
  7330.         this.origin.createItems(this);
  7331.       this.itemsChild.appendChild(this.infoChild);
  7332.       this.enableAnimation_();
  7333.     },
  7334.  
  7335.     /**
  7336.      * Append a new cookie node "bubble" to this list item.
  7337.      * @param {CookieTreeNode} node The cookie node to add a bubble for.
  7338.      * @param {Element} div The DOM element for the bubble itself.
  7339.      * @return {number} The index the bubble was added at.
  7340.      */
  7341.     appendItem: function(node, div) {
  7342.       this.itemList_.push({node: node, div: div});
  7343.       this.itemsChild.appendChild(div);
  7344.       return this.itemList_.length - 1;
  7345.     },
  7346.  
  7347.     /**
  7348.      * The currently selected cookie node ("cookie bubble") index.
  7349.      * @type {number}
  7350.      * @private
  7351.      */
  7352.     selectedIndex_: -1,
  7353.  
  7354.     /**
  7355.      * Get the currently selected cookie node ("cookie bubble") index.
  7356.      * @type {number}
  7357.      */
  7358.     get selectedIndex() {
  7359.       return this.selectedIndex_;
  7360.     },
  7361.  
  7362.     /**
  7363.      * Set the currently selected cookie node ("cookie bubble") index to
  7364.      * @{code itemIndex}, unselecting any previously selected node first.
  7365.      * @param {number} itemIndex The index to set as the selected index.
  7366.      */
  7367.     set selectedIndex(itemIndex) {
  7368.       // Get the list index up front before we change anything.
  7369.       var index = this.list.getIndexOfListItem(this);
  7370.       // Unselect any previously selected item.
  7371.       if (this.selectedIndex_ >= 0) {
  7372.         var item = this.itemList_[this.selectedIndex_];
  7373.         if (item && item.div)
  7374.           item.div.removeAttribute('selected');
  7375.       }
  7376.       // Special case: decrementing -1 wraps around to the end of the list.
  7377.       if (itemIndex == -2)
  7378.         itemIndex = this.itemList_.length - 1;
  7379.       // Check if we're going out of bounds and hide the item details.
  7380.       if (itemIndex < 0 || itemIndex >= this.itemList_.length) {
  7381.         this.selectedIndex_ = -1;
  7382.         this.disableAnimation_();
  7383.         this.infoChild.hidden = true;
  7384.         this.enableAnimation_();
  7385.         return;
  7386.       }
  7387.       // Set the new selected item and show the item details for it.
  7388.       this.selectedIndex_ = itemIndex;
  7389.       this.itemList_[itemIndex].div.setAttribute('selected', '');
  7390.       this.disableAnimation_();
  7391.       this.itemList_[itemIndex].node.setDetailText(this.infoChild,
  7392.                                                    this.list.infoNodes);
  7393.       this.infoChild.hidden = false;
  7394.       this.enableAnimation_();
  7395.       // If we're near the bottom of the list this may cause the list item to go
  7396.       // beyond the end of the visible area. Fix it after the animation is done.
  7397.       var list = this.list;
  7398.       window.setTimeout(function() { list.scrollIndexIntoView(index); }, 150);
  7399.     },
  7400.   };
  7401.  
  7402.   /**
  7403.    * {@code CookieTreeNode}s mirror the structure of the cookie tree lazily, and
  7404.    * contain all the actual data used to generate the {@code CookieListItem}s.
  7405.    * @param {Object} data The data object for this node.
  7406.    * @constructor
  7407.    */
  7408.   function CookieTreeNode(data) {
  7409.     this.data = data;
  7410.     this.children = [];
  7411.   }
  7412.  
  7413.   CookieTreeNode.prototype = {
  7414.     /**
  7415.      * Insert the given list of cookie tree nodes at the given index.
  7416.      * Both CookiesList and CookieTreeNode implement this API.
  7417.      * @param {Array.<Object>} data The data objects for the nodes to add.
  7418.      * @param {number} start The index at which to start inserting the nodes.
  7419.      */
  7420.     insertAt: function(data, start) {
  7421.       var nodes = spliceTreeNodes(data, start, this.children);
  7422.       for (var i = 0; i < nodes.length; i++)
  7423.         nodes[i].parent = this;
  7424.       this.updateOrigin();
  7425.     },
  7426.  
  7427.     /**
  7428.      * Remove a cookie tree node from the given index.
  7429.      * Both CookiesList and CookieTreeNode implement this API.
  7430.      * @param {number} index The index of the tree node to remove.
  7431.      */
  7432.     remove: function(index) {
  7433.       if (index < this.children.length) {
  7434.         this.children.splice(index, 1);
  7435.         this.updateOrigin();
  7436.       }
  7437.     },
  7438.  
  7439.     /**
  7440.      * Clears all children.
  7441.      * Both CookiesList and CookieTreeNode implement this API.
  7442.      * It is used by CookiesList.loadChildren().
  7443.      */
  7444.     clear: function() {
  7445.       // We might leave some garbage in parentLookup for removed children.
  7446.       // But that should be OK because parentLookup is cleared when we
  7447.       // reload the tree.
  7448.       this.children = [];
  7449.       this.updateOrigin();
  7450.     },
  7451.  
  7452.     /**
  7453.      * The counter used by startBatchUpdates() and endBatchUpdates().
  7454.      * @type {number}
  7455.      */
  7456.     batchCount_: 0,
  7457.  
  7458.     /**
  7459.      * See cr.ui.List.startBatchUpdates().
  7460.      * Both CookiesList (via List) and CookieTreeNode implement this API.
  7461.      */
  7462.     startBatchUpdates: function() {
  7463.       this.batchCount_++;
  7464.     },
  7465.  
  7466.     /**
  7467.      * See cr.ui.List.endBatchUpdates().
  7468.      * Both CookiesList (via List) and CookieTreeNode implement this API.
  7469.      */
  7470.     endBatchUpdates: function() {
  7471.       if (!--this.batchCount_)
  7472.         this.updateOrigin();
  7473.     },
  7474.  
  7475.     /**
  7476.      * Requests updating the origin summary to reflect changes in this item.
  7477.      * Both CookieListItem and CookieTreeNode implement this API.
  7478.      */
  7479.     updateOrigin: function() {
  7480.       if (!this.batchCount_ && this.parent)
  7481.         this.parent.updateOrigin();
  7482.     },
  7483.  
  7484.     /**
  7485.      * Summarize the information in this node and update @{code info}.
  7486.      * This will recurse into child nodes to summarize all descendants.
  7487.      * @param {Object} info The info object from @{code updateOrigin}.
  7488.      */
  7489.     collectSummaryInfo: function(info) {
  7490.       if (this.children.length > 0) {
  7491.         for (var i = 0; i < this.children.length; ++i)
  7492.           this.children[i].collectSummaryInfo(info);
  7493.       } else if (this.data && !this.data.hasChildren) {
  7494.         if (this.data.type == 'cookie') {
  7495.           info.cookies++;
  7496.         } else if (this.data.type == 'database') {
  7497.           info.database = true;
  7498.         } else if (this.data.type == 'local_storage') {
  7499.           info.localStorage = true;
  7500.         } else if (this.data.type == 'app_cache') {
  7501.           info.appCache = true;
  7502.         } else if (this.data.type == 'indexed_db') {
  7503.           info.indexedDb = true;
  7504.         } else if (this.data.type == 'file_system') {
  7505.           info.fileSystem = true;
  7506.         } else if (this.data.type == 'quota') {
  7507.           info.quota = this.data;
  7508.         }
  7509.       }
  7510.     },
  7511.  
  7512.     /**
  7513.      * Create the cookie "bubbles" for this node, recursing into children
  7514.      * if there are any. Append the cookie bubbles to @{code item}.
  7515.      * @param {CookieListItem} item The cookie list item to create items in.
  7516.      */
  7517.     createItems: function(item) {
  7518.       if (this.children.length > 0) {
  7519.         for (var i = 0; i < this.children.length; ++i)
  7520.           this.children[i].createItems(item);
  7521.       } else if (this.data && !this.data.hasChildren) {
  7522.         var text = '';
  7523.         switch (this.data.type) {
  7524.           case 'cookie':
  7525.           case 'database':
  7526.             text = this.data.name;
  7527.             break;
  7528.           case 'local_storage':
  7529.             text = localStrings.getString('cookie_local_storage');
  7530.             break;
  7531.           case 'app_cache':
  7532.             text = localStrings.getString('cookie_app_cache');
  7533.             break;
  7534.           case 'indexed_db':
  7535.             text = localStrings.getString('cookie_indexed_db');
  7536.             break;
  7537.           case 'file_system':
  7538.             text = localStrings.getString('cookie_file_system');
  7539.             break;
  7540.         }
  7541.         if (!text)
  7542.           return;
  7543.         var div = item.ownerDocument.createElement('div');
  7544.         div.className = 'cookie-item';
  7545.         // Help out screen readers and such: this is a clickable thing.
  7546.         div.setAttribute('role', 'button');
  7547.         div.textContent = text;
  7548.         var index = item.appendItem(this, div);
  7549.         div.onclick = function() {
  7550.           if (item.selectedIndex == index)
  7551.             item.selectedIndex = -1;
  7552.           else
  7553.             item.selectedIndex = index;
  7554.         };
  7555.       }
  7556.     },
  7557.  
  7558.     /**
  7559.      * Set the detail text to be displayed to that of this cookie tree node.
  7560.      * Uses preallocated DOM elements for each cookie node type from @{code
  7561.      * infoNodes}, and inserts the appropriate elements to @{code element}.
  7562.      * @param {Element} element The DOM element to insert elements to.
  7563.      * @param {Object.<string, {table: Element, info: Object.<string,
  7564.      *     Element>}>} infoNodes The map from cookie node types to maps from
  7565.      *     cookie attribute names to DOM elements to display cookie attribute
  7566.      *     values, created by @{code CookiesList.decorate}.
  7567.      */
  7568.     setDetailText: function(element, infoNodes) {
  7569.       var table;
  7570.       if (this.data && !this.data.hasChildren) {
  7571.         if (cookieInfo[this.data.type]) {
  7572.           var info = cookieInfo[this.data.type];
  7573.           var nodes = infoNodes[this.data.type].info;
  7574.           for (var i = 0; i < info.length; ++i) {
  7575.             var name = info[i][0];
  7576.             if (name != 'id' && this.data[name])
  7577.               nodes[name].textContent = this.data[name];
  7578.             else
  7579.               nodes[name].textContent = '';
  7580.           }
  7581.           table = infoNodes[this.data.type].table;
  7582.         }
  7583.       }
  7584.       while (element.childNodes.length > 1)
  7585.         element.removeChild(element.firstChild);
  7586.       if (table)
  7587.         element.insertBefore(table, element.firstChild);
  7588.     },
  7589.  
  7590.     /**
  7591.      * The parent of this cookie tree node.
  7592.      * @type {?CookieTreeNode|CookieListItem}
  7593.      */
  7594.     get parent(parent) {
  7595.       // See below for an explanation of this special case.
  7596.       if (typeof this.parent_ == 'number')
  7597.         return this.list_.getListItemByIndex(this.parent_);
  7598.       return this.parent_;
  7599.     },
  7600.     set parent(parent) {
  7601.       if (parent == this.parent)
  7602.         return;
  7603.       if (parent instanceof CookieListItem) {
  7604.         // If the parent is to be a CookieListItem, then we keep the reference
  7605.         // to it by its containing list and list index, rather than directly.
  7606.         // This allows the list items to be garbage collected when they scroll
  7607.         // out of view (except the expanded item, which we cache). This is
  7608.         // transparent except in the setter and getter, where we handle it.
  7609.         this.parent_ = parent.listIndex;
  7610.         this.list_ = parent.list;
  7611.         parent.addEventListener('listIndexChange',
  7612.                                 this.parentIndexChanged_.bind(this));
  7613.       } else {
  7614.         this.parent_ = parent;
  7615.       }
  7616.       if (this.data && this.data.id) {
  7617.         if (parent)
  7618.           parentLookup[this.data.id] = this;
  7619.         else
  7620.           delete parentLookup[this.data.id];
  7621.       }
  7622.       if (this.data && this.data.hasChildren &&
  7623.           !this.children.length && !lookupRequests[this.data.id]) {
  7624.         lookupRequests[this.data.id] = true;
  7625.         chrome.send('loadCookie', [this.pathId]);
  7626.       }
  7627.     },
  7628.  
  7629.     /**
  7630.      * Called when the parent is a CookieListItem whose index has changed.
  7631.      * See the code above that avoids keeping a direct reference to
  7632.      * CookieListItem parents, to allow them to be garbage collected.
  7633.      * @private
  7634.      */
  7635.     parentIndexChanged_: function(event) {
  7636.       if (typeof this.parent_ == 'number') {
  7637.         this.parent_ = event.newValue;
  7638.         // We set a timeout to update the origin, rather than doing it right
  7639.         // away, because this callback may occur while the list items are
  7640.         // being repopulated following a scroll event. Calling updateOrigin()
  7641.         // immediately could trigger relayout that would reset the scroll
  7642.         // position within the list, among other things.
  7643.         window.setTimeout(this.updateOrigin.bind(this), 0);
  7644.       }
  7645.     },
  7646.  
  7647.     /**
  7648.      * The cookie tree path id.
  7649.      * @type {string}
  7650.      */
  7651.     get pathId() {
  7652.       var parent = this.parent;
  7653.       if (parent && parent instanceof CookieTreeNode)
  7654.         return parent.pathId + ',' + this.data.id;
  7655.       return this.data.id;
  7656.     },
  7657.   };
  7658.  
  7659.   /**
  7660.    * Creates a new cookies list.
  7661.    * @param {Object=} opt_propertyBag Optional properties.
  7662.    * @constructor
  7663.    * @extends {DeletableItemList}
  7664.    */
  7665.   var CookiesList = cr.ui.define('list');
  7666.  
  7667.   CookiesList.prototype = {
  7668.     __proto__: DeletableItemList.prototype,
  7669.  
  7670.     /** @inheritDoc */
  7671.     decorate: function() {
  7672.       DeletableItemList.prototype.decorate.call(this);
  7673.       this.classList.add('cookie-list');
  7674.       this.data_ = [];
  7675.       this.dataModel = new ArrayDataModel(this.data_);
  7676.       this.addEventListener('keydown', this.handleKeyLeftRight_.bind(this));
  7677.       var sm = new ListSingleSelectionModel();
  7678.       sm.addEventListener('change', this.cookieSelectionChange_.bind(this));
  7679.       sm.addEventListener('leadIndexChange', this.cookieLeadChange_.bind(this));
  7680.       this.selectionModel = sm;
  7681.       this.infoNodes = {};
  7682.       var doc = this.ownerDocument;
  7683.       // Create a table for each type of site data (e.g. cookies, databases,
  7684.       // etc.) and save it so that we can reuse it for all origins.
  7685.       for (var type in cookieInfo) {
  7686.         var table = doc.createElement('table');
  7687.         table.className = 'cookie-details-table';
  7688.         var tbody = doc.createElement('tbody');
  7689.         table.appendChild(tbody);
  7690.         var info = {};
  7691.         for (var i = 0; i < cookieInfo[type].length; i++) {
  7692.           var tr = doc.createElement('tr');
  7693.           var name = doc.createElement('td');
  7694.           var data = doc.createElement('td');
  7695.           var pair = cookieInfo[type][i];
  7696.           name.className = 'cookie-details-label';
  7697.           name.textContent = localStrings.getString(pair[1]);
  7698.           data.className = 'cookie-details-value';
  7699.           data.textContent = '';
  7700.           tr.appendChild(name);
  7701.           tr.appendChild(data);
  7702.           tbody.appendChild(tr);
  7703.           info[pair[0]] = data;
  7704.         }
  7705.         this.infoNodes[type] = {table: table, info: info};
  7706.       }
  7707.     },
  7708.  
  7709.     /**
  7710.      * Handles key down events and looks for left and right arrows, then
  7711.      * dispatches to the currently expanded item, if any.
  7712.      * @param {Event} e The keydown event.
  7713.      * @private
  7714.      */
  7715.     handleKeyLeftRight_: function(e) {
  7716.       var id = e.keyIdentifier;
  7717.       if ((id == 'Left' || id == 'Right') && this.expandedItem) {
  7718.         var cs = this.ownerDocument.defaultView.getComputedStyle(this);
  7719.         var rtl = cs.direction == 'rtl';
  7720.         if ((!rtl && id == 'Left') || (rtl && id == 'Right'))
  7721.           this.expandedItem.selectedIndex--;
  7722.         else
  7723.           this.expandedItem.selectedIndex++;
  7724.         this.scrollIndexIntoView(this.expandedItem.listIndex);
  7725.         // Prevent the page itself from scrolling.
  7726.         e.preventDefault();
  7727.       }
  7728.     },
  7729.  
  7730.     /**
  7731.      * Called on selection model selection changes.
  7732.      * @param {Event} ce The selection change event.
  7733.      * @private
  7734.      */
  7735.     cookieSelectionChange_: function(ce) {
  7736.       ce.changes.forEach(function(change) {
  7737.           var listItem = this.getListItemByIndex(change.index);
  7738.           if (listItem) {
  7739.             if (!change.selected) {
  7740.               // We set a timeout here, rather than setting the item unexpanded
  7741.               // immediately, so that if another item gets set expanded right
  7742.               // away, it will be expanded before this item is unexpanded. It
  7743.               // will notice that, and unexpand this item in sync with its own
  7744.               // expansion. Later, this callback will end up having no effect.
  7745.               window.setTimeout(function() {
  7746.                 if (!listItem.selected || !listItem.lead)
  7747.                   listItem.expanded = false;
  7748.               }, 0);
  7749.             } else if (listItem.lead) {
  7750.               listItem.expanded = true;
  7751.             }
  7752.           }
  7753.         }, this);
  7754.     },
  7755.  
  7756.     /**
  7757.      * Called on selection model lead changes.
  7758.      * @param {Event} pe The lead change event.
  7759.      * @private
  7760.      */
  7761.     cookieLeadChange_: function(pe) {
  7762.       if (pe.oldValue != -1) {
  7763.         var listItem = this.getListItemByIndex(pe.oldValue);
  7764.         if (listItem) {
  7765.           // See cookieSelectionChange_ above for why we use a timeout here.
  7766.           window.setTimeout(function() {
  7767.             if (!listItem.lead || !listItem.selected)
  7768.               listItem.expanded = false;
  7769.           }, 0);
  7770.         }
  7771.       }
  7772.       if (pe.newValue != -1) {
  7773.         var listItem = this.getListItemByIndex(pe.newValue);
  7774.         if (listItem && listItem.selected)
  7775.           listItem.expanded = true;
  7776.       }
  7777.     },
  7778.  
  7779.     /**
  7780.      * The currently expanded item. Used by CookieListItem above.
  7781.      * @type {?CookieListItem}
  7782.      */
  7783.     expandedItem: null,
  7784.  
  7785.     // from cr.ui.List
  7786.     /** @inheritDoc */
  7787.     createItem: function(data) {
  7788.       // We use the cached expanded item in order to allow it to maintain some
  7789.       // state (like its fixed height, and which bubble is selected).
  7790.       if (this.expandedItem && this.expandedItem.origin == data)
  7791.         return this.expandedItem;
  7792.       return new CookieListItem(data, this);
  7793.     },
  7794.  
  7795.     // from options.DeletableItemList
  7796.     /** @inheritDoc */
  7797.     deleteItemAtIndex: function(index) {
  7798.       var item = this.data_[index];
  7799.       if (item) {
  7800.         var pathId = item.pathId;
  7801.         if (pathId)
  7802.           chrome.send('removeCookie', [pathId]);
  7803.       }
  7804.     },
  7805.  
  7806.     /**
  7807.      * Insert the given list of cookie tree nodes at the given index.
  7808.      * Both CookiesList and CookieTreeNode implement this API.
  7809.      * @param {Array.<Object>} data The data objects for the nodes to add.
  7810.      * @param {number} start The index at which to start inserting the nodes.
  7811.      */
  7812.     insertAt: function(data, start) {
  7813.       spliceTreeNodes(data, start, this.dataModel);
  7814.     },
  7815.  
  7816.     /**
  7817.      * Remove a cookie tree node from the given index.
  7818.      * Both CookiesList and CookieTreeNode implement this API.
  7819.      * @param {number} index The index of the tree node to remove.
  7820.      */
  7821.     remove: function(index) {
  7822.       if (index < this.data_.length)
  7823.         this.dataModel.splice(index, 1);
  7824.     },
  7825.  
  7826.     /**
  7827.      * Clears the list.
  7828.      * Both CookiesList and CookieTreeNode implement this API.
  7829.      * It is used by CookiesList.loadChildren().
  7830.      */
  7831.     clear: function() {
  7832.       parentLookup = {};
  7833.       this.data_ = [];
  7834.       this.dataModel = new ArrayDataModel(this.data_);
  7835.       this.redraw();
  7836.     },
  7837.  
  7838.     /**
  7839.      * Add tree nodes by given parent.
  7840.      * @param {Object} parent The parent node.
  7841.      * @param {number} start The index at which to start inserting the nodes.
  7842.      * @param {Array} nodesData Nodes data array.
  7843.      * @private
  7844.      */
  7845.     addByParent_: function(parent, start, nodesData) {
  7846.       if (!parent)
  7847.         return;
  7848.  
  7849.       parent.startBatchUpdates();
  7850.       parent.insertAt(nodesData, start);
  7851.       parent.endBatchUpdates();
  7852.  
  7853.       cr.dispatchSimpleEvent(this, 'change');
  7854.     },
  7855.  
  7856.     /**
  7857.      * Add tree nodes by parent id.
  7858.      * This is used by cookies_view.js.
  7859.      * @param {string} parentId Id of the parent node.
  7860.      * @param {number} start The index at which to start inserting the nodes.
  7861.      * @param {Array} nodesData Nodes data array.
  7862.      */
  7863.     addByParentId: function(parentId, start, nodesData) {
  7864.       var parent = parentId ? parentLookup[parentId] : this;
  7865.       this.addByParent_(parent, start, nodesData);
  7866.     },
  7867.  
  7868.     /**
  7869.      * Removes tree nodes by parent id.
  7870.      * This is used by cookies_view.js.
  7871.      * @param {string} parentId Id of the parent node.
  7872.      * @param {number} start The index at which to start removing the nodes.
  7873.      * @param {number} count Number of nodes to remove.
  7874.      */
  7875.     removeByParentId: function(parentId, start, count) {
  7876.       var parent = parentId ? parentLookup[parentId] : this;
  7877.       if (!parent)
  7878.         return;
  7879.  
  7880.       parent.startBatchUpdates();
  7881.       while (count-- > 0)
  7882.         parent.remove(start);
  7883.       parent.endBatchUpdates();
  7884.  
  7885.       cr.dispatchSimpleEvent(this, 'change');
  7886.     },
  7887.  
  7888.     /**
  7889.      * Loads the immediate children of given parent node.
  7890.      * This is used by cookies_view.js.
  7891.      * @param {string} parentId Id of the parent node.
  7892.      * @param {Array} children The immediate children of parent node.
  7893.      */
  7894.     loadChildren: function(parentId, children) {
  7895.       if (parentId)
  7896.         delete lookupRequests[parentId];
  7897.       var parent = parentId ? parentLookup[parentId] : this;
  7898.       if (!parent)
  7899.         return;
  7900.  
  7901.       parent.startBatchUpdates();
  7902.       parent.clear();
  7903.       this.addByParent_(parent, 0, children);
  7904.       parent.endBatchUpdates();
  7905.     },
  7906.   };
  7907.  
  7908.   return {
  7909.     CookiesList: CookiesList
  7910.   };
  7911. });
  7912.  
  7913. // Copyright (c) 2011 The Chromium Authors. All rights reserved.
  7914. // Use of this source code is governed by a BSD-style license that can be
  7915. // found in the LICENSE file.
  7916.  
  7917. cr.define('options', function() {
  7918.  
  7919.   var OptionsPage = options.OptionsPage;
  7920.  
  7921.   /////////////////////////////////////////////////////////////////////////////
  7922.   // CookiesView class:
  7923.  
  7924.   /**
  7925.    * Encapsulated handling of the cookies and other site data page.
  7926.    * @constructor
  7927.    */
  7928.   function CookiesView(model) {
  7929.     OptionsPage.call(this, 'cookies',
  7930.                      templateData.cookiesViewPageTabTitle,
  7931.                      'cookiesViewPage');
  7932.   }
  7933.  
  7934.   cr.addSingletonGetter(CookiesView);
  7935.  
  7936.   CookiesView.prototype = {
  7937.     __proto__: OptionsPage.prototype,
  7938.  
  7939.     /**
  7940.      * The timer id of the timer set on search query change events.
  7941.      * @type {number}
  7942.      * @private
  7943.      */
  7944.     queryDelayTimerId_: 0,
  7945.  
  7946.     /**
  7947.      * The most recent search query, or null if the query is empty.
  7948.      * @type {?string}
  7949.      * @private
  7950.      */
  7951.     lastQuery_ : null,
  7952.  
  7953.     initializePage: function() {
  7954.       OptionsPage.prototype.initializePage.call(this);
  7955.  
  7956.       $('cookies-search-box').addEventListener('search',
  7957.           this.handleSearchQueryChange_.bind(this));
  7958.  
  7959.       $('remove-all-cookies-button').onclick = function(e) {
  7960.         chrome.send('removeAllCookies', []);
  7961.       };
  7962.  
  7963.       var cookiesList = $('cookies-list');
  7964.       options.CookiesList.decorate(cookiesList);
  7965.       window.addEventListener('resize', this.handleResize_.bind(this));
  7966.  
  7967.       this.addEventListener('visibleChange', this.handleVisibleChange_);
  7968.     },
  7969.  
  7970.     /**
  7971.      * Search cookie using text in |cookies-search-box|.
  7972.      */
  7973.     searchCookie: function() {
  7974.       this.queryDelayTimerId_ = 0;
  7975.       var filter = $('cookies-search-box').value;
  7976.       if (this.lastQuery_ != filter) {
  7977.         this.lastQuery_ = filter;
  7978.         chrome.send('updateCookieSearchResults', [filter]);
  7979.       }
  7980.     },
  7981.  
  7982.     /**
  7983.      * Handles search query changes.
  7984.      * @param {!Event} e The event object.
  7985.      * @private
  7986.      */
  7987.     handleSearchQueryChange_: function(e) {
  7988.       if (this.queryDelayTimerId_)
  7989.         window.clearTimeout(this.queryDelayTimerId_);
  7990.  
  7991.       this.queryDelayTimerId_ = window.setTimeout(
  7992.           this.searchCookie.bind(this), 500);
  7993.     },
  7994.  
  7995.     initialized_: false,
  7996.  
  7997.     /**
  7998.      * Handler for OptionsPage's visible property change event.
  7999.      * @param {Event} e Property change event.
  8000.      * @private
  8001.      */
  8002.     handleVisibleChange_: function(e) {
  8003.       if (!this.visible)
  8004.         return;
  8005.  
  8006.       // Resize the cookies list whenever the options page becomes visible.
  8007.       this.handleResize_(null);
  8008.       if (!this.initialized_) {
  8009.         this.initialized_ = true;
  8010.         this.searchCookie();
  8011.       } else {
  8012.         $('cookies-list').redraw();
  8013.       }
  8014.  
  8015.       $('cookies-search-box').focus();
  8016.     },
  8017.  
  8018.     /**
  8019.      * Handler for when the window changes size. Resizes the cookies list to
  8020.      * match the window height.
  8021.      * @param {?Event} e Window resize event, or null if called directly.
  8022.      * @private
  8023.      */
  8024.     handleResize_: function(e) {
  8025.       if (!this.visible)
  8026.         return;
  8027.       var cookiesList = $('cookies-list');
  8028.       // 25 pixels from the window bottom seems like a visually pleasing amount.
  8029.       var height = window.innerHeight - cookiesList.offsetTop - 25;
  8030.       cookiesList.style.height = height + 'px';
  8031.     },
  8032.   };
  8033.  
  8034.   // CookiesViewHandler callbacks.
  8035.   CookiesView.onTreeItemAdded = function(args) {
  8036.     $('cookies-list').addByParentId(args[0], args[1], args[2]);
  8037.   };
  8038.  
  8039.   CookiesView.onTreeItemRemoved = function(args) {
  8040.     $('cookies-list').removeByParentId(args[0], args[1], args[2]);
  8041.   };
  8042.  
  8043.   CookiesView.loadChildren = function(args) {
  8044.     $('cookies-list').loadChildren(args[0], args[1]);
  8045.   };
  8046.  
  8047.   // Export
  8048.   return {
  8049.     CookiesView: CookiesView
  8050.   };
  8051.  
  8052. });
  8053.  
  8054. // Copyright (c) 2011 The Chromium Authors. All rights reserved.
  8055. // Use of this source code is governed by a BSD-style license that can be
  8056. // found in the LICENSE file.
  8057.  
  8058. cr.define('options', function() {
  8059.   'use strict';
  8060.  
  8061.   /**
  8062.    * A lookup helper function to find the first node that has an id (starting
  8063.    * at |node| and going up the parent chain).
  8064.    * @param {Element} node The node to start looking at.
  8065.    */
  8066.   function findIdNode(node) {
  8067.     while (node && !node.id) {
  8068.       node = node.parentNode;
  8069.     }
  8070.     return node;
  8071.   }
  8072.  
  8073.   /**
  8074.    * Creates a new list of extensions.
  8075.    * @param {Object=} opt_propertyBag Optional properties.
  8076.    * @constructor
  8077.    * @extends {cr.ui.div}
  8078.    */
  8079.   var ExtensionsList = cr.ui.define('div');
  8080.  
  8081.   var handlersInstalled = false;
  8082.  
  8083.   /**
  8084.    * @type {Object.<string, boolean>} A map from extension id to a boolean
  8085.    *     indicating whether its details section is expanded. This persists
  8086.    *     between calls to decorate.
  8087.    */
  8088.   var showingDetails = {};
  8089.  
  8090.   /**
  8091.    * @type {Object.<string, boolean>} A map from extension id to a boolean
  8092.    *     indicating whether the incognito warning is showing. This persists
  8093.    *     between calls to decorate.
  8094.    */
  8095.   var showingWarning = {};
  8096.  
  8097.   ExtensionsList.prototype = {
  8098.     __proto__: HTMLDivElement.prototype,
  8099.  
  8100.     /** @inheritDoc */
  8101.     decorate: function() {
  8102.       this.initControlsAndHandlers_();
  8103.  
  8104.       this.deleteExistingExtensionNodes_();
  8105.  
  8106.       this.showExtensionNodes_();
  8107.     },
  8108.  
  8109.     /**
  8110.      * Initializes the controls (toggle section and button) and installs
  8111.      * handlers.
  8112.      * @private
  8113.      */
  8114.     initControlsAndHandlers_: function() {
  8115.       // Make sure developer mode section is set correctly as per saved setting.
  8116.       var toggleButton = $('toggle-dev-on');
  8117.       var toggleSection = $('dev');
  8118.       if (this.data_.developerMode) {
  8119.         toggleSection.classList.add('dev-open');
  8120.         toggleSection.classList.remove('dev-closed');
  8121.         toggleButton.checked = true;
  8122.       } else {
  8123.         toggleSection.classList.remove('dev-open');
  8124.         toggleSection.classList.add('dev-closed');
  8125.       }
  8126.  
  8127.       // Instal global event handlers.
  8128.       if (!handlersInstalled) {
  8129.         var searchPage = SearchPage.getInstance();
  8130.         searchPage.addEventListener('searchChanged',
  8131.                                     this.searchChangedHandler_.bind(this));
  8132.  
  8133.         // Support full keyboard accessibility without making things ugly
  8134.         // for users who click, by hiding some focus outlines when the user
  8135.         // clicks anywhere, but showing them when the user presses any key.
  8136.         this.ownerDocument.body.classList.add('hide-some-focus-outlines');
  8137.         this.ownerDocument.addEventListener('click', (function(e) {
  8138.           this.ownerDocument.body.classList.add('hide-some-focus-outlines');
  8139.           return true;
  8140.         }).bind(this), true);
  8141.         this.ownerDocument.addEventListener('keydown', (function(e) {
  8142.           this.ownerDocument.body.classList.remove('hide-some-focus-outlines');
  8143.           return true;
  8144.         }).bind(this), true);
  8145.  
  8146.         handlersInstalled = true;
  8147.       }
  8148.     },
  8149.  
  8150.     /**
  8151.      * Deletes the existing Extension nodes from the page to make room for new
  8152.      * ones.
  8153.      * @private
  8154.      */
  8155.      deleteExistingExtensionNodes_: function() {
  8156.       while (this.hasChildNodes()){
  8157.         this.removeChild(this.firstChild);
  8158.       }
  8159.     },
  8160.  
  8161.     /**
  8162.      * Handles decorating the details section.
  8163.      * @param {Element} details The div that the details should be attached to.
  8164.      * @param {Object} extension The extension we are showing the details for.
  8165.      * @private
  8166.      */
  8167.      showExtensionNodes_: function() {
  8168.       // Iterate over the extension data and add each item to the list.
  8169.       for (var i = 0; i < this.data_.extensions.length; i++) {
  8170.         var extension = this.data_.extensions[i];
  8171.         var id = extension.id;
  8172.  
  8173.         var wrapper = this.ownerDocument.createElement('div');
  8174.  
  8175.         var expanded = showingDetails[id];
  8176.         var butterbar = showingWarning[id];
  8177.  
  8178.         wrapper.classList.add(expanded ? 'extension-list-item-expanded' :
  8179.                                          'extension-list-item-collaped');
  8180.         if (!extension.enabled)
  8181.           wrapper.classList.add('disabled');
  8182.         wrapper.id = id;
  8183.         this.appendChild(wrapper);
  8184.  
  8185.         var vboxOuter = this.ownerDocument.createElement('div');
  8186.         vboxOuter.classList.add('vbox');
  8187.         vboxOuter.classList.add('extension-list-item');
  8188.         wrapper.appendChild(vboxOuter);
  8189.  
  8190.         var hbox = this.ownerDocument.createElement('div');
  8191.         hbox.classList.add('hbox');
  8192.         vboxOuter.appendChild(hbox);
  8193.  
  8194.         // Add a container div for the zippy, so we can extend the hit area.
  8195.         var container = this.ownerDocument.createElement('div');
  8196.         // Clicking anywhere on the div expands/collapses the details.
  8197.         container.classList.add('extension-zippy-container');
  8198.         container.title = expanded ?
  8199.             localStrings.getString('extensionSettingsHideDetails') :
  8200.             localStrings.getString('extensionSettingsShowDetails');
  8201.         container.tabIndex = 0;
  8202.         container.setAttribute('role', 'button');
  8203.         container.setAttribute('aria-controls', extension.id + '_details');
  8204.         container.setAttribute('aria-expanded', expanded);
  8205.         container.addEventListener('click', this.handleZippyClick_.bind(this));
  8206.         container.addEventListener('keydown',
  8207.                                    this.handleZippyKeyDown_.bind(this));
  8208.         hbox.appendChild(container);
  8209.  
  8210.         // On the far left we have the zippy icon.
  8211.         var div = this.ownerDocument.createElement('div');
  8212.         div.id = id + '_zippy';
  8213.         div.classList.add('extension-zippy-default');
  8214.         div.classList.add(expanded ? 'extension-zippy-expanded' :
  8215.                                      'extension-zippy-collapsed');
  8216.         container.appendChild(div);
  8217.  
  8218.         // Next to it, we have the extension icon.
  8219.         var icon = this.ownerDocument.createElement('img');
  8220.         icon.classList.add('extension-icon');
  8221.         icon.src = extension.icon;
  8222.         hbox.appendChild(icon);
  8223.  
  8224.         // Start a vertical box for showing the details.
  8225.         var vbox = this.ownerDocument.createElement('div');
  8226.         vbox.classList.add('vbox');
  8227.         vbox.classList.add('stretch');
  8228.         vbox.classList.add('details-view');
  8229.         hbox.appendChild(vbox);
  8230.  
  8231.         div = this.ownerDocument.createElement('div');
  8232.         vbox.appendChild(div);
  8233.  
  8234.         // Title comes next.
  8235.         var title = this.ownerDocument.createElement('span');
  8236.         title.classList.add('extension-title');
  8237.         title.textContent = extension.name;
  8238.         vbox.appendChild(title);
  8239.  
  8240.         // Followed by version.
  8241.         var version = this.ownerDocument.createElement('span');
  8242.         version.classList.add('extension-version');
  8243.         version.textContent = extension.version;
  8244.         vbox.appendChild(version);
  8245.  
  8246.         // And the additional info label (unpacked/crashed).
  8247.         if (extension.terminated || extension.isUnpacked) {
  8248.           var version = this.ownerDocument.createElement('span');
  8249.           version.classList.add('extension-version');
  8250.           version.textContent = extension.terminated ?
  8251.               localStrings.getString('extensionSettingsCrashMessage') :
  8252.               localStrings.getString('extensionSettingsInDevelopment');
  8253.           vbox.appendChild(version);
  8254.         }
  8255.  
  8256.         div = this.ownerDocument.createElement('div');
  8257.         vbox.appendChild(div);
  8258.  
  8259.         // And below that we have description (if provided).
  8260.         if (extension.description.length > 0) {
  8261.           var description = this.ownerDocument.createElement('span');
  8262.           description.classList.add('extension-description');
  8263.           description.textContent = extension.description;
  8264.           vbox.appendChild(description);
  8265.         }
  8266.  
  8267.         // Immediately following the description, we have the
  8268.         // Options link (optional).
  8269.         if (extension.options_url) {
  8270.           var link = this.ownerDocument.createElement('a');
  8271.           link.classList.add('extension-links-trailing');
  8272.           link.textContent = localStrings.getString('extensionSettingsOptions');
  8273.           link.href = '#';
  8274.           link.addEventListener('click', this.handleOptions_.bind(this));
  8275.           vbox.appendChild(link);
  8276.         }
  8277.  
  8278.         // Then the optional Visit Website link.
  8279.         if (extension.homepageUrl) {
  8280.           var link = this.ownerDocument.createElement('a');
  8281.           link.classList.add('extension-links-trailing');
  8282.           link.textContent =
  8283.               localStrings.getString('extensionSettingsVisitWebsite');
  8284.           link.href = extension.homepageUrl;
  8285.           vbox.appendChild(link);
  8286.         }
  8287.  
  8288.         if (extension.warnings.length > 0) {
  8289.           var warningsDiv = this.ownerDocument.createElement('div');
  8290.           warningsDiv.classList.add('extension-warnings');
  8291.  
  8292.           var warningsHeader = this.ownerDocument.createElement('span');
  8293.           warningsHeader.classList.add('extension-warnings-title');
  8294.           warningsHeader.textContent =
  8295.               localStrings.getString('extensionSettingsWarningsTitle');
  8296.           warningsDiv.appendChild(warningsHeader);
  8297.  
  8298.           var warningList = this.ownerDocument.createElement('ul');
  8299.           for (var j = 0; j < extension.warnings.length; ++j) {
  8300.             var warningEntry = this.ownerDocument.createElement('li');
  8301.             warningEntry.textContent = extension.warnings[j];
  8302.             warningList.appendChild(warningEntry);
  8303.           }
  8304.           warningsDiv.appendChild(warningList);
  8305.  
  8306.           vbox.appendChild(warningsDiv);
  8307.         }
  8308.  
  8309.         // And now the details section that is normally hidden.
  8310.         var details = this.ownerDocument.createElement('div');
  8311.         details.classList.add('vbox');
  8312.         vbox.appendChild(details);
  8313.  
  8314.         this.decorateDetailsSection_(details, extension, expanded, butterbar);
  8315.  
  8316.         // And on the right of the details we have the Enable/Enabled checkbox.
  8317.         div = this.ownerDocument.createElement('div');
  8318.         hbox.appendChild(div);
  8319.  
  8320.         var section = this.ownerDocument.createElement('section');
  8321.         section.classList.add('extension-enabling');
  8322.         div.appendChild(section);
  8323.  
  8324.         if (!extension.terminated) {
  8325.           // The Enable checkbox.
  8326.           var input = this.ownerDocument.createElement('input');
  8327.           input.addEventListener('click', this.handleEnable_.bind(this));
  8328.           input.type = 'checkbox';
  8329.           input.name = 'toggle-' + id;
  8330.           input.disabled = !extension.mayDisable;
  8331.           if (extension.enabled)
  8332.             input.checked = true;
  8333.           input.id = 'toggle-' + id;
  8334.           section.appendChild(input);
  8335.           var label = this.ownerDocument.createElement('label');
  8336.           label.classList.add('extension-enabling-label');
  8337.           if (extension.enabled)
  8338.             label.classList.add('extension-enabling-label-bold');
  8339.           label.htmlFor = 'toggle-' + id;
  8340.           label.id = 'toggle-' + id + '-label';
  8341.           if (extension.enabled) {
  8342.             // Enabled (with a d).
  8343.             label.textContent =
  8344.                 localStrings.getString('extensionSettingsEnabled');
  8345.           } else {
  8346.             // Enable (no d).
  8347.             label.textContent =
  8348.                 localStrings.getString('extensionSettingsEnable');
  8349.           }
  8350.           section.appendChild(label);
  8351.         } else {
  8352.           // Extension has been terminated, show a Reload link.
  8353.           var link = this.ownerDocument.createElement('a');
  8354.           link.classList.add('extension-links-trailing');
  8355.           link.id = extension.id;
  8356.           link.textContent =
  8357.               localStrings.getString('extensionSettingsReload');
  8358.           link.href = '#';
  8359.           link.addEventListener('click', this.handleReload_.bind(this));
  8360.           section.appendChild(link);
  8361.         }
  8362.  
  8363.         // And, on the far right we have the uninstall button.
  8364.         var button = this.ownerDocument.createElement('button');
  8365.         button.classList.add('extension-delete');
  8366.         button.id = id;
  8367.         if (!extension.mayDisable)
  8368.           button.disabled = true;
  8369.         button.textContent = localStrings.getString('extensionSettingsRemove');
  8370.         button.addEventListener('click', this.handleUninstall_.bind(this));
  8371.         hbox.appendChild(button);
  8372.       }
  8373.  
  8374.       // Do one pass to find what the size of the checkboxes should be.
  8375.       var minCheckboxWidth = Infinity;
  8376.       var maxCheckboxWidth = 0;
  8377.       for (var i = 0; i < this.data_.extensions.length; ++i) {
  8378.         var label = $('toggle-' + this.data_.extensions[i].id + '-label');
  8379.         if (label.offsetWidth > maxCheckboxWidth)
  8380.           maxCheckboxWidth = label.offsetWidth;
  8381.         if (label.offsetWidth < minCheckboxWidth)
  8382.           minCheckboxWidth = label.offsetWidth;
  8383.       }
  8384.  
  8385.       // Do another pass, making sure checkboxes line up.
  8386.       var difference = maxCheckboxWidth - minCheckboxWidth;
  8387.       for (var i = 0; i < this.data_.extensions.length; ++i) {
  8388.         var label = $('toggle-' + this.data_.extensions[i].id + '-label');
  8389.         if (label.offsetWidth < maxCheckboxWidth)
  8390.           label.style.WebkitMarginEnd = difference.toString() + 'px';
  8391.       }
  8392.     },
  8393.  
  8394.     /**
  8395.      * Handles decorating the details section.
  8396.      * @param {Element} details The div that the details should be attached to.
  8397.      * @param {Object} extension The extension we are shoting the details for.
  8398.      * @param {boolean} expanded Whether to show the details expanded or not.
  8399.      * @param {boolean} showButterbar Whether to show the incognito warning or
  8400.      *                  not.
  8401.      * @private
  8402.      */
  8403.     decorateDetailsSection_: function(details, extension,
  8404.                                       expanded, showButterbar) {
  8405.       // This container div is needed because vbox display
  8406.       // overrides display:hidden.
  8407.       var detailsContents = this.ownerDocument.createElement('div');
  8408.       detailsContents.classList.add(expanded ? 'extension-details-visible' :
  8409.                                                'extension-details-hidden');
  8410.       detailsContents.id = extension.id + '_details';
  8411.       details.appendChild(detailsContents);
  8412.  
  8413.       var div = this.ownerDocument.createElement('div');
  8414.       div.classList.add('informative-text');
  8415.       detailsContents.appendChild(div);
  8416.  
  8417.       // Keep track of how many items we'll show in the details section.
  8418.       var itemsShown = 0;
  8419.  
  8420.       if (this.data_.developerMode) {
  8421.         // First we have the id.
  8422.         var content = this.ownerDocument.createElement('div');
  8423.         content.textContent =
  8424.             localStrings.getString('extensionSettingsExtensionId') +
  8425.                                    ' ' + extension.id;
  8426.         div.appendChild(content);
  8427.         itemsShown++;
  8428.  
  8429.         // Then, the path, if provided by unpacked extension.
  8430.         if (extension.isUnpacked) {
  8431.           content = this.ownerDocument.createElement('div');
  8432.           content.textContent =
  8433.               localStrings.getString('extensionSettingsExtensionPath') +
  8434.                                      ' ' + extension.path;
  8435.           div.appendChild(content);
  8436.           itemsShown++;
  8437.         }
  8438.  
  8439.         // Then, the 'managed, cannot uninstall/disable' message.
  8440.         if (!extension.mayDisable) {
  8441.           content = this.ownerDocument.createElement('div');
  8442.           content.textContent =
  8443.               localStrings.getString('extensionSettingsPolicyControlled');
  8444.           div.appendChild(content);
  8445.           itemsShown++;
  8446.         }
  8447.  
  8448.         // Then active views:
  8449.         if (extension.views.length > 0) {
  8450.           var table = this.ownerDocument.createElement('table');
  8451.           table.classList.add('extension-inspect-table');
  8452.           div.appendChild(table);
  8453.           var tr = this.ownerDocument.createElement('tr');
  8454.           table.appendChild(tr);
  8455.           var td = this.ownerDocument.createElement('td');
  8456.           td.classList.add('extension-inspect-left-column');
  8457.           tr.appendChild(td);
  8458.           var span = this.ownerDocument.createElement('span');
  8459.           td.appendChild(span);
  8460.           span.textContent =
  8461.               localStrings.getString('extensionSettingsInspectViews');
  8462.  
  8463.           td = this.ownerDocument.createElement('td');
  8464.           for (var i = 0; i < extension.views.length; ++i) {
  8465.             // Then active views:
  8466.             content = this.ownerDocument.createElement('div');
  8467.             var link = this.ownerDocument.createElement('a');
  8468.             link.classList.add('extension-links-view');
  8469.             link.textContent = extension.views[i].path;
  8470.             link.id = extension.id;
  8471.             link.href = '#';
  8472.             link.addEventListener('click', this.sendInspectMessage_.bind(this));
  8473.             content.appendChild(link);
  8474.  
  8475.             if (extension.views[i].incognito) {
  8476.               var incognito = this.ownerDocument.createElement('span');
  8477.               incognito.classList.add('extension-links-view');
  8478.               incognito.textContent =
  8479.                   localStrings.getString('viewIncognito');
  8480.               content.appendChild(incognito);
  8481.             }
  8482.  
  8483.             td.appendChild(content);
  8484.             tr.appendChild(td);
  8485.  
  8486.             itemsShown++;
  8487.           }
  8488.         }
  8489.       }
  8490.  
  8491.       var content = this.ownerDocument.createElement('div');
  8492.       detailsContents.appendChild(content);
  8493.  
  8494.       // Then Reload:
  8495.       if (extension.enabled && extension.allow_reload) {
  8496.         this.addLinkTo_(content,
  8497.                         localStrings.getString('extensionSettingsReload'),
  8498.                         extension.id,
  8499.                         this.handleReload_.bind(this));
  8500.         itemsShown++;
  8501.       }
  8502.  
  8503.       // Then Show (Browser Action) Button:
  8504.       if (extension.enabled && extension.enable_show_button) {
  8505.         this.addLinkTo_(content,
  8506.                         localStrings.getString('extensionSettingsShowButton'),
  8507.                         extension.id,
  8508.                         this.handleShowButton_.bind(this));
  8509.         itemsShown++;
  8510.       }
  8511.  
  8512.       if (extension.enabled) {
  8513.         // The 'allow in incognito' checkbox.
  8514.         var label = this.ownerDocument.createElement('label');
  8515.         label.classList.add('extension-checkbox-label');
  8516.         content.appendChild(label);
  8517.         var input = this.ownerDocument.createElement('input');
  8518.         input.addEventListener('click',
  8519.                                this.handleToggleEnableIncognito_.bind(this));
  8520.         input.id = extension.id;
  8521.         input.type = 'checkbox';
  8522.         if (extension.enabledIncognito)
  8523.           input.checked = true;
  8524.         label.appendChild(input);
  8525.         var span = this.ownerDocument.createElement('span');
  8526.         span.classList.add('extension-checkbox-span');
  8527.         span.textContent =
  8528.             localStrings.getString('extensionSettingsEnableIncognito');
  8529.         label.appendChild(span);
  8530.         itemsShown++;
  8531.       }
  8532.  
  8533.       if (extension.enabled && extension.wantsFileAccess) {
  8534.         // The 'allow access to file URLs' checkbox.
  8535.         label = this.ownerDocument.createElement('label');
  8536.         label.classList.add('extension-checkbox-label');
  8537.         content.appendChild(label);
  8538.         var input = this.ownerDocument.createElement('input');
  8539.         input.addEventListener('click',
  8540.                                this.handleToggleAllowFileUrls_.bind(this));
  8541.         input.id = extension.id;
  8542.         input.type = 'checkbox';
  8543.         if (extension.allowFileAccess)
  8544.           input.checked = true;
  8545.         label.appendChild(input);
  8546.         var span = this.ownerDocument.createElement('span');
  8547.         span.classList.add('extension-checkbox-span');
  8548.         span.textContent =
  8549.             localStrings.getString('extensionSettingsAllowFileAccess');
  8550.         label.appendChild(span);
  8551.         itemsShown++;
  8552.       }
  8553.  
  8554.       if (extension.enabled && !extension.is_hosted_app) {
  8555.         // And add a hidden warning message for allowInIncognito.
  8556.         content = this.ownerDocument.createElement('div');
  8557.         content.id = extension.id + '_incognitoWarning';
  8558.         content.classList.add('butter-bar');
  8559.         content.hidden = !showButterbar;
  8560.         detailsContents.appendChild(content);
  8561.  
  8562.         var span = this.ownerDocument.createElement('span');
  8563.         span.innerHTML =
  8564.             localStrings.getString('extensionSettingsIncognitoWarning');
  8565.         content.appendChild(span);
  8566.         itemsShown++;
  8567.       }
  8568.  
  8569.       var zippy = extension.id + '_zippy';
  8570.       $(zippy).hidden = !itemsShown;
  8571.  
  8572.       // If this isn't expanded now, make sure the newly-added controls
  8573.       // are not part of the tab order.
  8574.       if (!expanded) {
  8575.         var detailsControls = details.querySelectorAll('a, input');
  8576.         for (var i = 0; i < detailsControls.length; i++)
  8577.           detailsControls[i].tabIndex = -1;
  8578.       }
  8579.     },
  8580.  
  8581.     /**
  8582.      * A helper function to add contextual actions for extensions (action links)
  8583.      * to the page.
  8584.      */
  8585.     addLinkTo_: function(parent, linkText, id, handler) {
  8586.       var link = this.ownerDocument.createElement('a');
  8587.       link.className = 'extension-links-trailing';
  8588.       link.textContent = linkText;
  8589.       link.id = id;
  8590.       link.href = '#';
  8591.       link.addEventListener('click', handler);
  8592.       parent.appendChild(link);
  8593.     },
  8594.  
  8595.     /**
  8596.      * A lookup helper function to find an extension based on an id.
  8597.      * @param {string} id The |id| of the extension to look up.
  8598.      * @private
  8599.      */
  8600.     getExtensionWithId_: function(id) {
  8601.       for (var i = 0; i < this.data_.extensions.length; ++i) {
  8602.         if (this.data_.extensions[i].id == id)
  8603.           return this.data_.extensions[i];
  8604.       }
  8605.       return null;
  8606.     },
  8607.  
  8608.     /**
  8609.      * Handles a key down on the zippy icon.
  8610.      * @param {Event} e Key event.
  8611.      * @private
  8612.      */
  8613.     handleZippyKeyDown_: function(e) {
  8614.       if (e.keyCode == 13 || e.keyCode == 32)  // Enter or Space.
  8615.         this.handleZippyClick_(e);
  8616.     },
  8617.  
  8618.     /**
  8619.      * Handles the mouseclick on the zippy icon (that expands and collapses the
  8620.      * details section).
  8621.      * @param {Event} e Mouse event.
  8622.      * @private
  8623.      */
  8624.     handleZippyClick_: function(e) {
  8625.       var node = findIdNode(e.target.parentNode);
  8626.       var iter = this.firstChild;
  8627.       while (iter) {
  8628.         var zippy = $(iter.id + '_zippy');
  8629.         var details = $(iter.id + '_details');
  8630.         var container = zippy.parentElement;
  8631.         if (iter.id == node.id) {
  8632.           // Toggle visibility.
  8633.           if (iter.classList.contains('extension-list-item-expanded')) {
  8634.             // Hide yo kids! Hide yo wife!
  8635.             showingDetails[iter.id] = false;
  8636.             zippy.classList.remove('extension-zippy-expanded');
  8637.             zippy.classList.add('extension-zippy-collapsed');
  8638.             details.classList.remove('extension-details-visible');
  8639.             details.classList.add('extension-details-hidden');
  8640.             iter.classList.remove('extension-list-item-expanded');
  8641.             iter.classList.add('extension-list-item-collaped');
  8642.             container.setAttribute('aria-expanded', 'false');
  8643.             container.title =
  8644.                 localStrings.getString('extensionSettingsShowDetails');
  8645.             var detailsControls = details.querySelectorAll('a, input');
  8646.             for (var i = 0; i < detailsControls.length; i++)
  8647.               detailsControls[i].tabIndex = -1;
  8648.  
  8649.             // Hide yo incognito warning.
  8650.             var butterBar =
  8651.                 this.querySelector('#' + iter.id + '_incognitoWarning');
  8652.             if (butterBar !== null) {
  8653.               butterBar.hidden = true;
  8654.               showingWarning[iter.id] = false;
  8655.             }
  8656.           } else {
  8657.             // Show the contents.
  8658.             showingDetails[iter.id] = true;
  8659.             zippy.classList.remove('extension-zippy-collapsed');
  8660.             zippy.classList.add('extension-zippy-expanded');
  8661.             details.classList.remove('extension-details-hidden');
  8662.             details.classList.add('extension-details-visible');
  8663.             iter.classList.remove('extension-list-item-collaped');
  8664.             iter.classList.add('extension-list-item-expanded');
  8665.             container.setAttribute('aria-expanded', 'true');
  8666.             container.title =
  8667.                 localStrings.getString('extensionSettingsHideDetails');
  8668.             var detailsControls = details.querySelectorAll('a, input');
  8669.             for (var i = 0; i < detailsControls.length; i++)
  8670.               detailsControls[i].tabIndex = 0;
  8671.           }
  8672.         }
  8673.         iter = iter.nextSibling;
  8674.       }
  8675.     },
  8676.  
  8677.     /**
  8678.      * Handles the 'searchChanged' event. This is used to limit the number of
  8679.      * items to show in the list, when the user is searching for items with the
  8680.      * search box. Otherwise, if one match is found, the whole list of
  8681.      * extensions would be shown when we only want the matching items to be
  8682.      * found.
  8683.      * @param {Event} e Change event.
  8684.      * @private
  8685.      */
  8686.     searchChangedHandler_: function(e) {
  8687.       var searchString = e.searchText;
  8688.       var child = this.firstChild;
  8689.       while (child) {
  8690.         var extension = this.getExtensionWithId_(child.id);
  8691.         if (searchString.length == 0) {
  8692.           // Show all.
  8693.           child.classList.remove('search-suppress');
  8694.         } else {
  8695.           // If the search string does not appear within the text of the
  8696.           // extension, then hide it.
  8697.           if ((extension.name.toLowerCase().indexOf(searchString) < 0) &&
  8698.               (extension.version.toLowerCase().indexOf(searchString) < 0) &&
  8699.               (extension.description.toLowerCase().indexOf(searchString) < 0)) {
  8700.             // Hide yo extension!
  8701.             child.classList.add('search-suppress');
  8702.           } else {
  8703.             // Show yourself!
  8704.             child.classList.remove('search-suppress');
  8705.           }
  8706.         }
  8707.         child = child.nextSibling;
  8708.       }
  8709.     },
  8710.  
  8711.     /**
  8712.      * Handles the Reload Extension functionality.
  8713.      * @param {Event} e Change event.
  8714.      * @private
  8715.      */
  8716.     handleReload_: function(e) {
  8717.       var node = findIdNode(e.target);
  8718.       chrome.send('extensionSettingsReload', [node.id]);
  8719.     },
  8720.  
  8721.     /**
  8722.      * Handles the Show (Browser Action) Button functionality.
  8723.      * @param {Event} e Change event.
  8724.      * @private
  8725.      */
  8726.     handleShowButton_: function(e) {
  8727.       var node = findIdNode(e.target);
  8728.       chrome.send('extensionSettingsShowButton', [node.id]);
  8729.     },
  8730.  
  8731.     /**
  8732.      * Handles the Enable/Disable Extension functionality.
  8733.      * @param {Event} e Change event.
  8734.      * @private
  8735.      */
  8736.     handleEnable_: function(e) {
  8737.       var node = findIdNode(e.target.parentNode);
  8738.       var extension = this.getExtensionWithId_(node.id);
  8739.       chrome.send('extensionSettingsEnable',
  8740.                   [node.id, extension.enabled ? 'false' : 'true']);
  8741.       chrome.send('extensionSettingsRequestExtensionsData');
  8742.     },
  8743.  
  8744.     /**
  8745.      * Handles the Uninstall Extension functionality.
  8746.      * @param {Event} e Change event.
  8747.      * @private
  8748.      */
  8749.     handleUninstall_: function(e) {
  8750.       var node = findIdNode(e.target.parentNode);
  8751.       chrome.send('extensionSettingsUninstall', [node.id]);
  8752.       chrome.send('extensionSettingsRequestExtensionsData');
  8753.     },
  8754.  
  8755.     /**
  8756.      * Handles the View Options link.
  8757.      * @param {Event} e Change event.
  8758.      * @private
  8759.      */
  8760.     handleOptions_: function(e) {
  8761.       var node = findIdNode(e.target.parentNode);
  8762.       var extension = this.getExtensionWithId_(node.id);
  8763.       chrome.send('extensionSettingsOptions', [extension.id]);
  8764.       e.preventDefault();
  8765.     },
  8766.  
  8767.     /**
  8768.      * Handles the Enable Extension In Incognito functionality.
  8769.      * @param {Event} e Change event.
  8770.      * @private
  8771.      */
  8772.     handleToggleEnableIncognito_: function(e) {
  8773.       var node = findIdNode(e.target);
  8774.       var butterBar = document.getElementById(node.id + '_incognitoWarning');
  8775.       butterBar.hidden = !e.target.checked;
  8776.       showingWarning[node.id] = e.target.checked;
  8777.       chrome.send('extensionSettingsEnableIncognito',
  8778.                   [node.id, String(e.target.checked)]);
  8779.     },
  8780.  
  8781.     /**
  8782.      * Handles the Allow On File URLs functionality.
  8783.      * @param {Event} e Change event.
  8784.      * @private
  8785.      */
  8786.     handleToggleAllowFileUrls_: function(e) {
  8787.       var node = findIdNode(e.target);
  8788.       chrome.send('extensionSettingsAllowFileAccess',
  8789.                   [node.id, String(e.target.checked)]);
  8790.     },
  8791.  
  8792.     /**
  8793.      * Tell the C++ ExtensionDOMHandler to inspect the page detailed in
  8794.      * |viewData|.
  8795.      * @param {Event} e Change event.
  8796.      * @private
  8797.      */
  8798.     sendInspectMessage_: function(e) {
  8799.       var extension = this.getExtensionWithId_(e.srcElement.id);
  8800.       for (var i = 0; i < extension.views.length; ++i) {
  8801.         if (extension.views[i].path == e.srcElement.innerText) {
  8802.           // TODO(aa): This is ghetto, but WebUIBindings doesn't support sending
  8803.           // anything other than arrays of strings, and this is all going to get
  8804.           // replaced with V8 extensions soon anyway.
  8805.           chrome.send('extensionSettingsInspect', [
  8806.             String(extension.views[i].renderProcessId),
  8807.             String(extension.views[i].renderViewId)
  8808.           ]);
  8809.         }
  8810.       }
  8811.     },
  8812.   };
  8813.  
  8814.   return {
  8815.     ExtensionsList: ExtensionsList
  8816.   };
  8817. });
  8818.  
  8819. // Copyright (c) 2011 The Chromium Authors. All rights reserved.
  8820. // Use of this source code is governed by a BSD-style license that can be
  8821. // found in the LICENSE file.
  8822.  
  8823. // Used for observing function of the backend datasource for this page by
  8824. // tests.
  8825. var webui_responded_ = false;
  8826.  
  8827. cr.define('options', function() {
  8828.   var OptionsPage = options.OptionsPage;
  8829.   var ExtensionsList = options.ExtensionsList;
  8830.  
  8831.   /**
  8832.    * ExtensionSettings class
  8833.    * Encapsulated handling of the 'Manage Extensions' page.
  8834.    * @class
  8835.    */
  8836.   function ExtensionSettings() {
  8837.     OptionsPage.call(this, 'extensions',
  8838.                      templateData.extensionSettingsTabTitle,
  8839.                      'extension-settings');
  8840.   }
  8841.  
  8842.   cr.addSingletonGetter(ExtensionSettings);
  8843.  
  8844.   ExtensionSettings.prototype = {
  8845.     __proto__: OptionsPage.prototype,
  8846.  
  8847.     /**
  8848.      * Initialize the page.
  8849.      */
  8850.     initializePage: function() {
  8851.       OptionsPage.prototype.initializePage.call(this);
  8852.  
  8853.       // This will request the data to show on the page and will get a response
  8854.       // back in returnExtensionsData.
  8855.       chrome.send('extensionSettingsRequestExtensionsData');
  8856.  
  8857.       // Set up the developer mode button.
  8858.       var toggleDevMode = $('toggle-dev-on');
  8859.       toggleDevMode.addEventListener('click',
  8860.           this.handleToggleDevMode_.bind(this));
  8861.  
  8862.       // Setup the gallery related links and text.
  8863.       $('suggest-gallery').innerHTML =
  8864.           localStrings.getString('extensionSettingsSuggestGallery');
  8865.       $('get-more-extensions').innerHTML =
  8866.           localStrings.getString('extensionSettingsGetMoreExtensions');
  8867.  
  8868.       // Set up the three dev mode buttons (load unpacked, pack and update).
  8869.       $('load-unpacked').addEventListener('click',
  8870.           this.handleLoadUnpackedExtension_.bind(this));
  8871.       $('pack-extension').addEventListener('click',
  8872.           this.handlePackExtension_.bind(this));
  8873.       $('update-extensions-now').addEventListener('click',
  8874.           this.handleUpdateExtensionNow_.bind(this));
  8875.     },
  8876.  
  8877.     /**
  8878.      * Utility function which asks the C++ to show a platform-specific file
  8879.      * select dialog, and fire |callback| with the |filePath| that resulted.
  8880.      * |selectType| can be either 'file' or 'folder'. |operation| can be 'load',
  8881.      * 'packRoot', or 'pem' which are signals to the C++ to do some
  8882.      * operation-specific configuration.
  8883.      * @private
  8884.      */
  8885.     showFileDialog_: function(selectType, operation, callback) {
  8886.       handleFilePathSelected = function(filePath) {
  8887.         callback(filePath);
  8888.         handleFilePathSelected = function() {};
  8889.       };
  8890.  
  8891.       chrome.send('extensionSettingsSelectFilePath', [selectType, operation]);
  8892.     },
  8893.  
  8894.     /**
  8895.      * Handles the Load Unpacked Extension button.
  8896.      * @param {Event} e Change event.
  8897.      * @private
  8898.      */
  8899.     handleLoadUnpackedExtension_: function(e) {
  8900.       this.showFileDialog_('folder', 'load', function(filePath) {
  8901.         chrome.send('extensionSettingsLoad', [String(filePath)]);
  8902.       });
  8903.  
  8904.       chrome.send('coreOptionsUserMetricsAction',
  8905.                   ['Options_LoadUnpackedExtension']);
  8906.     },
  8907.  
  8908.     /**
  8909.      * Handles the Pack Extension button.
  8910.      * @param {Event} e Change event.
  8911.      * @private
  8912.      */
  8913.     handlePackExtension_: function(e) {
  8914.       OptionsPage.navigateToPage('packExtensionOverlay');
  8915.       chrome.send('coreOptionsUserMetricsAction', ['Options_PackExtension']);
  8916.     },
  8917.  
  8918.     /**
  8919.      * Handles the Update Extension Now button.
  8920.      * @param {Event} e Change event.
  8921.      * @private
  8922.      */
  8923.     handleUpdateExtensionNow_: function(e) {
  8924.       chrome.send('extensionSettingsAutoupdate', []);
  8925.     },
  8926.  
  8927.     /**
  8928.      * Handles the Toggle Dev Mode button.
  8929.      * @param {Event} e Change event.
  8930.      * @private
  8931.      */
  8932.     handleToggleDevMode_: function(e) {
  8933.       var dev = $('dev');
  8934.       if (!dev.classList.contains('dev-open')) {
  8935.         // Make the Dev section visible.
  8936.         dev.classList.add('dev-open');
  8937.         dev.classList.remove('dev-closed');
  8938.  
  8939.         $('load-unpacked').classList.add('dev-button-visible');
  8940.         $('load-unpacked').classList.remove('dev-button-hidden');
  8941.         $('pack-extension').classList.add('dev-button-visible');
  8942.         $('pack-extension').classList.remove('dev-button-hidden');
  8943.         $('update-extensions-now').classList.add('dev-button-visible');
  8944.         $('update-extensions-now').classList.remove('dev-button-hidden');
  8945.       } else {
  8946.         // Hide the Dev section.
  8947.         dev.classList.add('dev-closed');
  8948.         dev.classList.remove('dev-open');
  8949.  
  8950.         $('load-unpacked').classList.add('dev-button-hidden');
  8951.         $('load-unpacked').classList.remove('dev-button-visible');
  8952.         $('pack-extension').classList.add('dev-button-hidden');
  8953.         $('pack-extension').classList.remove('dev-button-visible');
  8954.         $('update-extensions-now').classList.add('dev-button-hidden');
  8955.         $('update-extensions-now').classList.remove('dev-button-visible');
  8956.       }
  8957.  
  8958.       chrome.send('extensionSettingsToggleDeveloperMode', []);
  8959.     },
  8960.   };
  8961.  
  8962.   /**
  8963.    * Called by the dom_ui_ to re-populate the page with data representing
  8964.    * the current state of installed extensions.
  8965.    */
  8966.   ExtensionSettings.returnExtensionsData = function(extensionsData) {
  8967.     webui_responded_ = true;
  8968.  
  8969.     $('no-extensions').hidden = true;
  8970.     $('suggest-gallery').hidden = true;
  8971.     $('get-more-extensions-container').hidden = true;
  8972.  
  8973.     if (extensionsData.extensions.length > 0) {
  8974.       // Enforce order specified in the data or (if equal) then sort by
  8975.       // extension name (case-insensitive).
  8976.       extensionsData.extensions.sort(function(a, b) {
  8977.         if (a.order == b.order) {
  8978.           a = a.name.toLowerCase();
  8979.           b = b.name.toLowerCase();
  8980.           return a < b ? -1 : (a > b ? 1 : 0);
  8981.         } else {
  8982.           return a.order < b.order ? -1 : 1;
  8983.         }
  8984.       });
  8985.  
  8986.       $('get-more-extensions-container').hidden = false;
  8987.     } else {
  8988.       $('no-extensions').hidden = false;
  8989.       $('suggest-gallery').hidden = false;
  8990.     }
  8991.  
  8992.     ExtensionsList.prototype.data_ = extensionsData;
  8993.  
  8994.     var extensionList = $('extension-settings-list');
  8995.     ExtensionsList.decorate(extensionList);
  8996.   }
  8997.  
  8998.   // Export
  8999.   return {
  9000.     ExtensionSettings: ExtensionSettings
  9001.   };
  9002. });
  9003.  
  9004. // Copyright (c) 2011 The Chromium Authors. All rights reserved.
  9005. // Use of this source code is governed by a BSD-style license that can be
  9006. // found in the LICENSE file.
  9007.  
  9008. cr.define('options', function() {
  9009.  
  9010.   var OptionsPage = options.OptionsPage;
  9011.  
  9012.   /**
  9013.    * This is the absolute difference maintained between standard and
  9014.    * fixed-width font sizes. Refer http://crbug.com/91922.
  9015.    */
  9016.   const SIZE_DIFFERENCE_FIXED_STANDARD = 3;
  9017.  
  9018.   /**
  9019.    * FontSettings class
  9020.    * Encapsulated handling of the 'Fonts and Encoding' page.
  9021.    * @class
  9022.    */
  9023.   function FontSettings() {
  9024.     OptionsPage.call(this,
  9025.                      'fonts',
  9026.                      templateData.fontSettingsPageTabTitle,
  9027.                      'font-settings');
  9028.   }
  9029.  
  9030.   cr.addSingletonGetter(FontSettings);
  9031.  
  9032.   FontSettings.prototype = {
  9033.     __proto__: OptionsPage.prototype,
  9034.  
  9035.     /**
  9036.      * Initialize the page.
  9037.      */
  9038.     initializePage: function() {
  9039.       OptionsPage.prototype.initializePage.call(this);
  9040.  
  9041.       var standardFontRange = $('standard-font-size');
  9042.       standardFontRange.valueMap = [9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 20,
  9043.           22, 24, 26, 28, 30, 32, 34, 36, 40, 44, 48, 56, 64, 72];
  9044.       standardFontRange.continuous = false;
  9045.       standardFontRange.notifyChange = this.standardRangeChanged_.bind(this);
  9046.       standardFontRange.notifyPrefChange =
  9047.           this.standardFontSizeChanged_.bind(this);
  9048.  
  9049.       var minimumFontRange = $('minimum-font-size');
  9050.       minimumFontRange.valueMap = [6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,
  9051.           18, 20, 22, 24];
  9052.       minimumFontRange.continuous = false;
  9053.       minimumFontRange.notifyChange = this.minimumRangeChanged_.bind(this);
  9054.       minimumFontRange.notifyPrefChange =
  9055.           this.minimumFontSizeChanged_.bind(this);
  9056.  
  9057.       var placeholder = localStrings.getString('fontSettingsPlaceholder');
  9058.       var elements = [$('standard-font-family'), $('serif-font-family'),
  9059.                       $('sans-serif-font-family'), $('fixed-font-family'),
  9060.                       $('font-encoding')];
  9061.       elements.forEach(function(el) {
  9062.         el.appendChild(new Option(placeholder));
  9063.         el.setDisabled('noFontsAvailable', true);
  9064.       });
  9065.     },
  9066.  
  9067.     /**
  9068.      * Called by the options page when this page has been shown.
  9069.      */
  9070.     didShowPage: function() {
  9071.       // The fonts list may be large so we only load it when this page is
  9072.       // loaded for the first time.  This makes opening the options window
  9073.       // faster and consume less memory if the user never opens the fonts
  9074.       // dialog.
  9075.       if (!this.hasShown) {
  9076.         chrome.send('fetchFontsData');
  9077.         this.hasShown = true;
  9078.       }
  9079.     },
  9080.  
  9081.     /**
  9082.      * Called as the user changes the standard font size.  This allows for
  9083.      * reflecting the change in the UI before the preference has been changed.
  9084.      * @param {Element} el The slider input element.
  9085.      * @param {number} value The mapped value currently set by the slider.
  9086.      * @private
  9087.      */
  9088.     standardRangeChanged_: function(el, value) {
  9089.       var fontSampleEl = $('standard-font-sample');
  9090.       this.setUpFontSample_(fontSampleEl, value, fontSampleEl.style.fontFamily,
  9091.                             true);
  9092.  
  9093.       fontSampleEl = $('serif-font-sample');
  9094.       this.setUpFontSample_(fontSampleEl, value, fontSampleEl.style.fontFamily,
  9095.                             true);
  9096.  
  9097.       fontSampleEl = $('sans-serif-font-sample');
  9098.       this.setUpFontSample_(fontSampleEl, value, fontSampleEl.style.fontFamily,
  9099.                             true);
  9100.  
  9101.       fontSampleEl = $('fixed-font-sample');
  9102.       this.setUpFontSample_(fontSampleEl,
  9103.                             value - SIZE_DIFFERENCE_FIXED_STANDARD,
  9104.                             fontSampleEl.style.fontFamily, false);
  9105.     },
  9106.  
  9107.     /**
  9108.      * Sets the 'default_fixed_font_size' preference when the standard font
  9109.      * size has been changed by the user.
  9110.      * @param {Element} el The slider input element.
  9111.      * @param {number} value The mapped value that has been saved.
  9112.      * @private
  9113.      */
  9114.     standardFontSizeChanged_: function(el, value) {
  9115.       Preferences.setIntegerPref('webkit.webprefs.default_fixed_font_size',
  9116.                                  value - SIZE_DIFFERENCE_FIXED_STANDARD, '');
  9117.     },
  9118.  
  9119.     /**
  9120.      * Called as the user changes the miniumum font size.  This allows for
  9121.      * reflecting the change in the UI before the preference has been changed.
  9122.      * @param {Element} el The slider input element.
  9123.      * @param {number} value The mapped value currently set by the slider.
  9124.      * @private
  9125.      */
  9126.     minimumRangeChanged_: function(el, value) {
  9127.       var fontSampleEl = $('minimum-font-sample');
  9128.       this.setUpFontSample_(fontSampleEl, value, fontSampleEl.style.fontFamily,
  9129.                             true);
  9130.     },
  9131.  
  9132.     /**
  9133.      * Sets the 'minimum_logical_font_size' preference when the minimum font
  9134.      * size has been changed by the user.
  9135.      * @param {Element} el The slider input element.
  9136.      * @param {number} value The mapped value that has been saved.
  9137.      * @private
  9138.      */
  9139.     minimumFontSizeChanged_: function(el, value) {
  9140.       Preferences.setIntegerPref('webkit.webprefs.minimum_logical_font_size',
  9141.           value, '');
  9142.     },
  9143.  
  9144.     /**
  9145.      * Sets the text, font size and font family of the sample text.
  9146.      * @param {Element} el The div containing the sample text.
  9147.      * @param {number} size The font size of the sample text.
  9148.      * @param {string} font The font family of the sample text.
  9149.      * @param {bool} showSize True if the font size should appear in the sample.
  9150.      * @private
  9151.      */
  9152.     setUpFontSample_: function(el, size, font, showSize) {
  9153.       var prefix = showSize ? (size + ': ') : '';
  9154.       el.textContent = prefix +
  9155.           localStrings.getString('fontSettingsLoremIpsum');
  9156.       el.style.fontSize = size + 'px';
  9157.       if (font)
  9158.         el.style.fontFamily = font;
  9159.     },
  9160.  
  9161.     /**
  9162.      * Populates a select list and selects the specified item.
  9163.      * @param {Element} element The select element to populate.
  9164.      * @param {Array} items The array of items from which to populate.
  9165.      * @param {string} selectedValue The selected item.
  9166.      * @private
  9167.      */
  9168.     populateSelect_: function(element, items, selectedValue) {
  9169.       // Remove any existing content.
  9170.       element.textContent = '';
  9171.  
  9172.       // Insert new child nodes into select element.
  9173.       var value, text, selected, option;
  9174.       for (var i = 0; i < items.length; i++) {
  9175.         value = items[i][0];
  9176.         text = items[i][1];
  9177.         if (text) {
  9178.           selected = value == selectedValue;
  9179.           element.appendChild(new Option(text, value, false, selected));
  9180.         } else {
  9181.           element.appendChild(document.createElement('hr'));
  9182.         }
  9183.       }
  9184.  
  9185.       element.setDisabled('noFontsAvailable', false);
  9186.     }
  9187.   };
  9188.  
  9189.   // Chrome callbacks
  9190.   FontSettings.setFontsData = function(fonts, encodings, selectedValues) {
  9191.     FontSettings.getInstance().populateSelect_($('standard-font-family'), fonts,
  9192.                                                selectedValues[0]);
  9193.     FontSettings.getInstance().populateSelect_($('serif-font-family'), fonts,
  9194.                                                selectedValues[1]);
  9195.     FontSettings.getInstance().populateSelect_($('sans-serif-font-family'),
  9196.                                                fonts, selectedValues[2]);
  9197.     FontSettings.getInstance().populateSelect_($('fixed-font-family'), fonts,
  9198.                                                selectedValues[3]);
  9199.     FontSettings.getInstance().populateSelect_($('font-encoding'), encodings,
  9200.                                                selectedValues[4]);
  9201.   };
  9202.  
  9203.   FontSettings.setUpStandardFontSample = function(font, size) {
  9204.     FontSettings.getInstance().setUpFontSample_($('standard-font-sample'), size,
  9205.                                                 font, true);
  9206.   };
  9207.  
  9208.   FontSettings.setUpSerifFontSample = function(font, size) {
  9209.     FontSettings.getInstance().setUpFontSample_($('serif-font-sample'), size,
  9210.                                                 font, true);
  9211.   };
  9212.  
  9213.   FontSettings.setUpSansSerifFontSample = function(font, size) {
  9214.     FontSettings.getInstance().setUpFontSample_($('sans-serif-font-sample'),
  9215.                                                 size, font, true);
  9216.   };
  9217.  
  9218.   FontSettings.setUpFixedFontSample = function(font, size) {
  9219.     FontSettings.getInstance().setUpFontSample_($('fixed-font-sample'),
  9220.                                                 size, font, false);
  9221.   };
  9222.  
  9223.   FontSettings.setUpMinimumFontSample = function(size) {
  9224.     // If size is less than 6, represent it as six in the sample to account
  9225.     // for the minimum logical font size.
  9226.     if (size < 6)
  9227.       size = 6;
  9228.     FontSettings.getInstance().setUpFontSample_($('minimum-font-sample'), size,
  9229.                                                 null, true);
  9230.   };
  9231.  
  9232.   // Export
  9233.   return {
  9234.     FontSettings: FontSettings
  9235.   };
  9236. });
  9237.  
  9238.  
  9239. // Copyright (c) 2011 The Chromium Authors. All rights reserved.
  9240. // Use of this source code is governed by a BSD-style license that can be
  9241. // found in the LICENSE file.
  9242.  
  9243. cr.define('options', function() {
  9244.   const OptionsPage = options.OptionsPage;
  9245.  
  9246.   /////////////////////////////////////////////////////////////////////////////
  9247.   // HandlerOptions class:
  9248.  
  9249.   /**
  9250.    * Encapsulated handling of handler options page.
  9251.    * @constructor
  9252.    */
  9253.   function HandlerOptions() {
  9254.     this.activeNavTab = null;
  9255.     OptionsPage.call(this,
  9256.                      'handlers',
  9257.                      templateData.handlersPageTabTitle,
  9258.                      'handler-options');
  9259.   }
  9260.  
  9261.   cr.addSingletonGetter(HandlerOptions);
  9262.  
  9263.   HandlerOptions.prototype = {
  9264.     __proto__: OptionsPage.prototype,
  9265.  
  9266.     /**
  9267.      * The handlers list.
  9268.      * @type {DeletableItemList}
  9269.      * @private
  9270.      */
  9271.     handlersList_: null,
  9272.  
  9273.     /** @inheritDoc */
  9274.     initializePage: function() {
  9275.       OptionsPage.prototype.initializePage.call(this);
  9276.  
  9277.       this.createHandlersList_();
  9278.     },
  9279.  
  9280.     /**
  9281.      * Creates, decorates and initializes the handlers list.
  9282.      * @private
  9283.      */
  9284.     createHandlersList_: function() {
  9285.       this.handlersList_ = $('handlers-list');
  9286.       options.HandlersList.decorate(this.handlersList_);
  9287.       this.handlersList_.autoExpands = true;
  9288.  
  9289.       this.ignoredHandlersList_ = $('ignored-handlers-list');
  9290.       options.IgnoredHandlersList.decorate(this.ignoredHandlersList_);
  9291.       this.ignoredHandlersList_.autoExpands = true;
  9292.     },
  9293.   };
  9294.  
  9295.   /**
  9296.    * Sets the list of handlers shown by the view.
  9297.    * @param handlers to be shown in the view.
  9298.    */
  9299.   HandlerOptions.setHandlers = function(handlers) {
  9300.     $('handlers-list').setHandlers(handlers);
  9301.   };
  9302.  
  9303.   /**
  9304.    * Sets the list of ignored handlers shown by the view.
  9305.    * @param handlers to be shown in the view.
  9306.    */
  9307.   HandlerOptions.setIgnoredHandlers = function(handlers) {
  9308.     $('ignored-handlers-section').hidden = handlers.length == 0;
  9309.     $('ignored-handlers-list').setHandlers(handlers);
  9310.   };
  9311.  
  9312.   return {
  9313.     HandlerOptions: HandlerOptions
  9314.   };
  9315. });
  9316.  
  9317.   // Copyright (c) 2011 The Chromium Authors. All rights reserved.
  9318. // Use of this source code is governed by a BSD-style license that can be
  9319. // found in the LICENSE file.
  9320.  
  9321. cr.define('options', function() {
  9322.   const ArrayDataModel = cr.ui.ArrayDataModel;
  9323.   const List = cr.ui.List;
  9324.   const ListItem = cr.ui.ListItem;
  9325.   const HandlerOptions = options.HandlerOptions;
  9326.   const DeletableItem = options.DeletableItem;
  9327.   const DeletableItemList = options.DeletableItemList;
  9328.  
  9329.   const localStrings = new LocalStrings();
  9330.  
  9331.   /**
  9332.    * Creates a new ignored protocol / content handler list item.
  9333.    *
  9334.    * Accepts values in the form
  9335.    *   ['mailto', 'http://www.thesite.com/%s', 'The title of the protocol'],
  9336.    * @param {Object} entry A dictionary describing the handlers for a given
  9337.    *     protocol.
  9338.    * @constructor
  9339.    * @extends {cr.ui.DeletableItemList}
  9340.    */
  9341.   function IgnoredHandlersListItem(entry) {
  9342.     var el = cr.doc.createElement('div');
  9343.     el.dataItem = entry;
  9344.     el.__proto__ = IgnoredHandlersListItem.prototype;
  9345.     el.decorate();
  9346.     return el;
  9347.   }
  9348.  
  9349.   IgnoredHandlersListItem.prototype = {
  9350.     __proto__: DeletableItem.prototype,
  9351.  
  9352.     /** @inheritDoc */
  9353.     decorate: function() {
  9354.       DeletableItem.prototype.decorate.call(this);
  9355.  
  9356.       // Protocol.
  9357.       var protocolElement = document.createElement('div');
  9358.       protocolElement.textContent = this.dataItem[0];
  9359.       protocolElement.className = 'handlers-type-column';
  9360.       this.contentElement_.appendChild(protocolElement);
  9361.  
  9362.       // Site title.
  9363.       var titleElement = document.createElement('div');
  9364.       titleElement.textContent = this.dataItem[2];
  9365.       titleElement.className = 'handlers-site-column';
  9366.       titleElement.title = this.dataItem[1];
  9367.       this.contentElement_.appendChild(titleElement);
  9368.     },
  9369.   };
  9370.  
  9371.  
  9372.   var IgnoredHandlersList = cr.ui.define('list');
  9373.  
  9374.   IgnoredHandlersList.prototype = {
  9375.     __proto__: DeletableItemList.prototype,
  9376.  
  9377.     createItem: function(entry) {
  9378.       return new IgnoredHandlersListItem(entry);
  9379.     },
  9380.  
  9381.     deleteItemAtIndex: function(index) {
  9382.       chrome.send('removeIgnoredHandler', [this.dataModel.item(index)]);
  9383.     },
  9384.  
  9385.     /**
  9386.      * The length of the list.
  9387.      */
  9388.     get length() {
  9389.       return this.dataModel.length;
  9390.     },
  9391.  
  9392.     /**
  9393.      * Set the protocol handlers displayed by this list.  See
  9394.      * IgnoredHandlersListItem for an example of the format the list should
  9395.      * take.
  9396.      *
  9397.      * @param {Object} list A list of ignored protocol handlers.
  9398.      */
  9399.     setHandlers: function(list) {
  9400.       this.dataModel = new ArrayDataModel(list);
  9401.     },
  9402.   };
  9403.  
  9404.  
  9405.  
  9406.   /**
  9407.    * Creates a new protocol / content handler list item.
  9408.    *
  9409.    * Accepts values in the form
  9410.    * { protocol: 'mailto',
  9411.    *   handlers: [
  9412.    *     ['mailto', 'http://www.thesite.com/%s', 'The title of the protocol'],
  9413.    *     ...,
  9414.    *   ],
  9415.    * }
  9416.    * @param {Object} entry A dictionary describing the handlers for a given
  9417.    *     protocol.
  9418.    * @constructor
  9419.    * @extends {cr.ui.ListItem}
  9420.    */
  9421.   function HandlerListItem(entry) {
  9422.     var el = cr.doc.createElement('div');
  9423.     el.dataItem = entry;
  9424.     el.__proto__ = HandlerListItem.prototype;
  9425.     el.decorate();
  9426.     return el;
  9427.   }
  9428.  
  9429.   HandlerListItem.prototype = {
  9430.     __proto__: ListItem.prototype,
  9431.  
  9432.     buildWidget_: function(data, delegate) {
  9433.       // Protocol.
  9434.       var protocolElement = document.createElement('div');
  9435.       protocolElement.textContent = data.protocol;
  9436.       protocolElement.className = 'handlers-type-column';
  9437.       this.appendChild(protocolElement);
  9438.  
  9439.       // Handler selection.
  9440.       var handlerElement = document.createElement('div');
  9441.       var selectElement = document.createElement('select');
  9442.       var defaultOptionElement = document.createElement('option');
  9443.       defaultOptionElement.selected = data.default_handler == -1;
  9444.       defaultOptionElement.textContent =
  9445.           localStrings.getString('handlers_none_handler');
  9446.       defaultOptionElement.value = -1;
  9447.       selectElement.appendChild(defaultOptionElement);
  9448.  
  9449.       for (var i = 0; i < data.handlers.length; ++i) {
  9450.         var optionElement = document.createElement('option');
  9451.         optionElement.selected = i == data.default_handler;
  9452.         optionElement.textContent = data.handlers[i][2];
  9453.         optionElement.value = i;
  9454.         selectElement.appendChild(optionElement);
  9455.       }
  9456.  
  9457.       selectElement.addEventListener('change', function (e) {
  9458.         var index = e.target.value;
  9459.         if (index == -1) {
  9460.           this.classList.add('none');
  9461.           delegate.clearDefault(data.protocol);
  9462.         } else {
  9463.           handlerElement.classList.remove('none');
  9464.           delegate.setDefault(data.handlers[index]);
  9465.         }
  9466.       });
  9467.       handlerElement.appendChild(selectElement);
  9468.       handlerElement.className = 'handlers-site-column';
  9469.       if (data.default_handler == -1)
  9470.         this.classList.add('none');
  9471.       this.appendChild(handlerElement);
  9472.  
  9473.       // Remove link.
  9474.       var removeElement = document.createElement('div');
  9475.       removeElement.textContent =
  9476.           localStrings.getString('handlers_remove_link');
  9477.       removeElement.addEventListener('click', function (e) {
  9478.         var value = selectElement ? selectElement.value : 0;
  9479.         delegate.removeHandler(value, data.handlers[value]);
  9480.       });
  9481.       removeElement.className = 'handlers-remove-column handlers-remove-link';
  9482.       this.appendChild(removeElement);
  9483.     },
  9484.  
  9485.     /** @inheritDoc */
  9486.     decorate: function() {
  9487.       ListItem.prototype.decorate.call(this);
  9488.  
  9489.       var self = this;
  9490.       var delegate = {
  9491.         removeHandler: function(index, handler) {
  9492.           chrome.send('removeHandler', [handler]);
  9493.         },
  9494.         setDefault: function(handler) {
  9495.           chrome.send('setDefault', [handler]);
  9496.         },
  9497.         clearDefault: function(protocol) {
  9498.           chrome.send('clearDefault', [protocol]);
  9499.         },
  9500.       };
  9501.  
  9502.       this.buildWidget_(this.dataItem, delegate);
  9503.     },
  9504.   };
  9505.  
  9506.   /**
  9507.    * Create a new passwords list.
  9508.    * @constructor
  9509.    * @extends {cr.ui.List}
  9510.    */
  9511.   var HandlersList = cr.ui.define('list');
  9512.  
  9513.   HandlersList.prototype = {
  9514.     __proto__: List.prototype,
  9515.  
  9516.     /** @inheritDoc */
  9517.     createItem: function(entry) {
  9518.       return new HandlerListItem(entry);
  9519.     },
  9520.  
  9521.     /**
  9522.      * The length of the list.
  9523.      */
  9524.     get length() {
  9525.       return this.dataModel.length;
  9526.     },
  9527.  
  9528.     /**
  9529.      * Set the protocol handlers displayed by this list.
  9530.      * See HandlerListItem for an example of the format the list should take.
  9531.      *
  9532.      * @param {Object} list A list of protocols with their registered handlers.
  9533.      */
  9534.     setHandlers: function(list) {
  9535.       this.dataModel = new ArrayDataModel(list);
  9536.     },
  9537.   };
  9538.  
  9539.   return {
  9540.     IgnoredHandlersListItem: IgnoredHandlersListItem,
  9541.     IgnoredHandlersList: IgnoredHandlersList,
  9542.     HandlerListItem: HandlerListItem,
  9543.     HandlersList: HandlersList,
  9544.   };
  9545. });
  9546. // Copyright (c) 2011 The Chromium Authors. All rights reserved.
  9547. // Use of this source code is governed by a BSD-style license that can be
  9548. // found in the LICENSE file.
  9549.  
  9550. cr.define('options', function() {
  9551.   var OptionsPage = options.OptionsPage;
  9552.  
  9553.   /**
  9554.    * ImportDataOverlay class
  9555.    * Encapsulated handling of the 'Import Data' overlay page.
  9556.    * @class
  9557.    */
  9558.   function ImportDataOverlay() {
  9559.     OptionsPage.call(this,
  9560.                      'importData',
  9561.                      templateData.importDataOverlayTabTitle,
  9562.                      'import-data-overlay');
  9563.   }
  9564.  
  9565.   cr.addSingletonGetter(ImportDataOverlay);
  9566.  
  9567.   ImportDataOverlay.prototype = {
  9568.     // Inherit from OptionsPage.
  9569.     __proto__: OptionsPage.prototype,
  9570.  
  9571.     /**
  9572.      * Initialize the page.
  9573.      */
  9574.     initializePage: function() {
  9575.       // Call base class implementation to start preference initialization.
  9576.       OptionsPage.prototype.initializePage.call(this);
  9577.  
  9578.       var self = this;
  9579.       var checkboxes =
  9580.           document.querySelectorAll('#import-checkboxes input[type=checkbox]');
  9581.       for (var i = 0; i < checkboxes.length; i++) {
  9582.         checkboxes[i].onchange = function() {
  9583.           self.validateCommitButton_();
  9584.         };
  9585.       }
  9586.  
  9587.       $('import-browsers').onchange = function() {
  9588.         self.updateCheckboxes_();
  9589.         self.validateCommitButton_();
  9590.       };
  9591.  
  9592.       $('import-data-commit').onclick = function() {
  9593.         chrome.send('importData', [
  9594.             String($('import-browsers').selectedIndex),
  9595.             String($('import-history').checked),
  9596.             String($('import-favorites').checked),
  9597.             String($('import-passwords').checked),
  9598.             String($('import-search').checked)]);
  9599.       };
  9600.  
  9601.       $('import-data-cancel').onclick = function() {
  9602.         ImportDataOverlay.dismiss();
  9603.       };
  9604.  
  9605.       $('import-data-show-bookmarks-bar').onchange = function() {
  9606.         // Note: The callback 'toggleShowBookmarksBar' is handled within the
  9607.         // browser options handler -- rather than the import data handler --
  9608.         // as the implementation is shared by several clients.
  9609.         chrome.send('toggleShowBookmarksBar');
  9610.       }
  9611.  
  9612.       $('import-data-confirm').onclick = function() {
  9613.         ImportDataOverlay.dismiss();
  9614.       };
  9615.  
  9616.       // Form controls are disabled until the profile list has been loaded.
  9617.       self.setControlsSensitive_(false);
  9618.     },
  9619.  
  9620.     /**
  9621.      * Set enabled and checked state of the commit button.
  9622.      * @private
  9623.      */
  9624.     validateCommitButton_: function() {
  9625.       var somethingToImport =
  9626.           $('import-history').checked || $('import-favorites').checked ||
  9627.           $('import-passwords').checked || $('import-search').checked;
  9628.       $('import-data-commit').disabled = !somethingToImport;
  9629.     },
  9630.  
  9631.     /**
  9632.      * Sets the sensitivity of all the checkboxes and the commit button.
  9633.      * @private
  9634.      */
  9635.     setControlsSensitive_: function(sensitive) {
  9636.       var checkboxes =
  9637.           document.querySelectorAll('#import-checkboxes input[type=checkbox]');
  9638.       for (var i = 0; i < checkboxes.length; i++)
  9639.         this.setUpCheckboxState_(checkboxes[i], sensitive);
  9640.       $('import-data-commit').disabled = !sensitive;
  9641.     },
  9642.  
  9643.     /**
  9644.      * Set enabled and checked states a checkbox element.
  9645.      * @param {Object} checkbox A checkbox element.
  9646.      * @param {boolean} enabled The enabled state of the chekbox.
  9647.      * @private
  9648.      */
  9649.     setUpCheckboxState_: function(checkbox, enabled) {
  9650.        checkbox.setDisabled("noProfileData", !enabled);
  9651.     },
  9652.  
  9653.     /**
  9654.      * Update the enabled and checked states of all checkboxes.
  9655.      * @private
  9656.      */
  9657.     updateCheckboxes_: function() {
  9658.       var index = $('import-browsers').selectedIndex;
  9659.       var browserProfile;
  9660.       if (this.browserProfiles.length > index)
  9661.         browserProfile = this.browserProfiles[index];
  9662.       var importOptions = ['history', 'favorites', 'passwords', 'search'];
  9663.       for (var i = 0; i < importOptions.length; i++) {
  9664.         var checkbox = $('import-' + importOptions[i]);
  9665.         var enable = browserProfile && browserProfile[importOptions[i]];
  9666.         this.setUpCheckboxState_(checkbox, enable);
  9667.       }
  9668.     },
  9669.  
  9670.     /**
  9671.      * Update the supported browsers popup with given entries.
  9672.      * @param {array} browsers List of supported browsers name.
  9673.      * @private
  9674.      */
  9675.     updateSupportedBrowsers_: function(browsers) {
  9676.       this.browserProfiles = browsers;
  9677.       var browserSelect = $('import-browsers');
  9678.       browserSelect.remove(0);  // Remove the 'Loading...' option.
  9679.       browserSelect.textContent = '';
  9680.       var browserCount = browsers.length;
  9681.  
  9682.       if (browserCount == 0) {
  9683.         var option = new Option(templateData.noProfileFound, 0);
  9684.         browserSelect.appendChild(option);
  9685.  
  9686.         this.setControlsSensitive_(false);
  9687.       } else {
  9688.         this.setControlsSensitive_(true);
  9689.         for (var i = 0; i < browserCount; i++) {
  9690.           var browser = browsers[i]
  9691.           var option = new Option(browser['name'], browser['index']);
  9692.           browserSelect.appendChild(option);
  9693.         }
  9694.  
  9695.         this.updateCheckboxes_();
  9696.         this.validateCommitButton_();
  9697.       }
  9698.     },
  9699.  
  9700.     /**
  9701.     * Clear import prefs set when user checks/unchecks a checkbox so that each
  9702.     * checkbox goes back to the default "checked" state (or alternatively, to
  9703.     * the state set by a recommended policy).
  9704.     * @private
  9705.     */
  9706.     clearUserPrefs_: function() {
  9707.       var importPrefs = ['import_history',
  9708.                          'import_bookmarks',
  9709.                          'import_saved_passwords',
  9710.                          'import_search_engine'];
  9711.       for (var i = 0; i < importPrefs.length; i++)
  9712.         Preferences.clearPref(importPrefs[i], undefined);
  9713.     },
  9714.   };
  9715.  
  9716.   ImportDataOverlay.clearUserPrefs = function() {
  9717.     ImportDataOverlay.getInstance().clearUserPrefs_();
  9718.   };
  9719.  
  9720.   /**
  9721.    * Update the supported browsers popup with given entries.
  9722.    * @param {array} list of supported browsers name.
  9723.    */
  9724.   ImportDataOverlay.updateSupportedBrowsers = function(browsers) {
  9725.     ImportDataOverlay.getInstance().updateSupportedBrowsers_(browsers);
  9726.   };
  9727.  
  9728.   /**
  9729.    * Update the UI to reflect whether an import operation is in progress.
  9730.    * @param {boolean} state True if an import operation is in progress.
  9731.    */
  9732.   ImportDataOverlay.setImportingState = function(state) {
  9733.     var checkboxes =
  9734.         document.querySelectorAll('#import-checkboxes input[type=checkbox]');
  9735.     for (var i = 0; i < checkboxes.length; i++)
  9736.         checkboxes[i].setDisabled("Importing", state);
  9737.     if (!state)
  9738.       ImportDataOverlay.getInstance().updateCheckboxes_();
  9739.     $('import-browsers').disabled = state;
  9740.     $('import-throbber').style.visibility = state ? "visible" : "hidden";
  9741.     ImportDataOverlay.getInstance().validateCommitButton_();
  9742.   };
  9743.  
  9744.   /**
  9745.    * Remove the import overlay from display.
  9746.    */
  9747.   ImportDataOverlay.dismiss = function() {
  9748.     ImportDataOverlay.clearUserPrefs();
  9749.     OptionsPage.closeOverlay();
  9750.   };
  9751.  
  9752.   /**
  9753.    * Show a message confirming the success of the import operation.
  9754.    */
  9755.   ImportDataOverlay.confirmSuccess = function() {
  9756.     var showBookmarksMessage = $('import-favorites').checked;
  9757.     ImportDataOverlay.setImportingState(false);
  9758.     $('import-data-configure').hidden = true;
  9759.     $('import-data-success').hidden = false;
  9760.     $('import-find-your-bookmarks').hidden = !showBookmarksMessage;
  9761.   };
  9762.  
  9763.   // Export
  9764.   return {
  9765.     ImportDataOverlay: ImportDataOverlay
  9766.   };
  9767. });
  9768.  
  9769. // Copyright (c) 2011 The Chromium Authors. All rights reserved.
  9770. // Use of this source code is governed by a BSD-style license that can be
  9771. // found in the LICENSE file.
  9772.  
  9773. cr.define('options', function() {
  9774.   var OptionsPage = options.OptionsPage;
  9775.  
  9776.   function InstantConfirmOverlay() {
  9777.     OptionsPage.call(this, 'instantConfirm',
  9778.                      templateData.instantConfirmTitle,
  9779.                      'instantConfirmOverlay');
  9780.   };
  9781.  
  9782.   cr.addSingletonGetter(InstantConfirmOverlay);
  9783.  
  9784.   InstantConfirmOverlay.prototype = {
  9785.     // Inherit from OptionsPage.
  9786.     __proto__: OptionsPage.prototype,
  9787.  
  9788.     initializePage: function() {
  9789.       OptionsPage.prototype.initializePage.call(this);
  9790.  
  9791.       $('instantConfirmCancel').onclick = function() {
  9792.         OptionsPage.closeOverlay();
  9793.         $('instantEnabledCheckbox').checked = false;
  9794.       };
  9795.  
  9796.       $('instantConfirmOk').onclick = function() {
  9797.         OptionsPage.closeOverlay();
  9798.         chrome.send('enableInstant');
  9799.       };
  9800.     },
  9801.   };
  9802.  
  9803.   // Export
  9804.   return {
  9805.     InstantConfirmOverlay: InstantConfirmOverlay
  9806.   };
  9807. });
  9808.  
  9809.  
  9810. // Copyright (c) 2011 The Chromium Authors. All rights reserved.
  9811. // Use of this source code is governed by a BSD-style license that can be
  9812. // found in the LICENSE file.
  9813.  
  9814. ///////////////////////////////////////////////////////////////////////////////
  9815. // AddLanguageOverlay class:
  9816.  
  9817. cr.define('options', function() {
  9818.   const OptionsPage = options.OptionsPage;
  9819.  
  9820.   /**
  9821.    * Encapsulated handling of ChromeOS add language overlay page.
  9822.    * @constructor
  9823.    */
  9824.   function AddLanguageOverlay() {
  9825.     OptionsPage.call(this, 'addLanguage',
  9826.                      localStrings.getString('add_button'),
  9827.                      'add-language-overlay-page');
  9828.   }
  9829.  
  9830.   cr.addSingletonGetter(AddLanguageOverlay);
  9831.  
  9832.   AddLanguageOverlay.prototype = {
  9833.     // Inherit AddLanguageOverlay from OptionsPage.
  9834.     __proto__: OptionsPage.prototype,
  9835.  
  9836.     /**
  9837.      * Initializes AddLanguageOverlay page.
  9838.      * Calls base class implementation to starts preference initialization.
  9839.      */
  9840.     initializePage: function() {
  9841.       // Call base class implementation to starts preference initialization.
  9842.       OptionsPage.prototype.initializePage.call(this);
  9843.  
  9844.       // Set up the cancel button.
  9845.       $('add-language-overlay-cancel-button').onclick = function(e) {
  9846.         OptionsPage.closeOverlay();
  9847.       };
  9848.  
  9849.       // Create the language list with which users can add a language.
  9850.       var addLanguageList = $('add-language-overlay-language-list');
  9851.       var languageListData = templateData.languageList;
  9852.       for (var i = 0; i < languageListData.length; i++) {
  9853.         var language = languageListData[i];
  9854.         var displayText = language.displayName;
  9855.         // If the native name is different, add it.
  9856.         if (language.displayName != language.nativeDisplayName) {
  9857.           displayText += ' - ' + language.nativeDisplayName;
  9858.         }
  9859.  
  9860.         if (cr.isChromeOS) {
  9861.           var button = document.createElement('button');
  9862.           button.className = 'link-button';
  9863.           button.textContent = displayText;
  9864.           button.languageCode = language.code;
  9865.           var li = document.createElement('li');
  9866.           li.languageCode = language.code;
  9867.           li.appendChild(button);
  9868.           addLanguageList.appendChild(li);
  9869.         } else {
  9870.           var option = document.createElement('option');
  9871.           option.value = language.code;
  9872.           option.textContent = displayText;
  9873.           addLanguageList.appendChild(option);
  9874.         }
  9875.       }
  9876.     },
  9877.   };
  9878.  
  9879.   return {
  9880.     AddLanguageOverlay: AddLanguageOverlay
  9881.   };
  9882. });
  9883.  
  9884. // Copyright (c) 2011 The Chromium Authors. All rights reserved.
  9885. // Use of this source code is governed by a BSD-style license that can be
  9886. // found in the LICENSE file.
  9887.  
  9888. cr.define('options', function() {
  9889.   const ArrayDataModel = cr.ui.ArrayDataModel;
  9890.   const DeletableItem = options.DeletableItem;
  9891.   const DeletableItemList = options.DeletableItemList;
  9892.   const List = cr.ui.List;
  9893.   const ListItem = cr.ui.ListItem;
  9894.   const ListSingleSelectionModel = cr.ui.ListSingleSelectionModel;
  9895.  
  9896.   /**
  9897.    * Creates a new Language list item.
  9898.    * @param {String} languageCode the languageCode.
  9899.    * @constructor
  9900.    * @extends {DeletableItem.ListItem}
  9901.    */
  9902.   function LanguageListItem(languageCode) {
  9903.     var el = cr.doc.createElement('li');
  9904.     el.__proto__ = LanguageListItem.prototype;
  9905.     el.languageCode_ = languageCode;
  9906.     el.decorate();
  9907.     return el;
  9908.   };
  9909.  
  9910.   LanguageListItem.prototype = {
  9911.     __proto__: DeletableItem.prototype,
  9912.  
  9913.     /**
  9914.      * The language code of this language.
  9915.      * @type {String}
  9916.      * @private
  9917.      */
  9918.     languageCode_: null,
  9919.  
  9920.     /** @inheritDoc */
  9921.     decorate: function() {
  9922.       DeletableItem.prototype.decorate.call(this);
  9923.  
  9924.       var languageCode = this.languageCode_;
  9925.       var languageOptions = options.LanguageOptions.getInstance();
  9926.       this.deletable = languageOptions.languageIsDeletable(languageCode);
  9927.       this.languageCode = languageCode;
  9928.       this.languageName = cr.doc.createElement('div');
  9929.       this.languageName.className = 'language-name';
  9930.       this.languageName.textContent =
  9931.           LanguageList.getDisplayNameFromLanguageCode(languageCode);
  9932.       this.contentElement.appendChild(this.languageName);
  9933.       this.title =
  9934.           LanguageList.getNativeDisplayNameFromLanguageCode(languageCode);
  9935.       this.draggable = true;
  9936.     },
  9937.   };
  9938.  
  9939.   /**
  9940.    * Creates a new language list.
  9941.    * @param {Object=} opt_propertyBag Optional properties.
  9942.    * @constructor
  9943.    * @extends {cr.ui.List}
  9944.    */
  9945.   var LanguageList = cr.ui.define('list');
  9946.  
  9947.   /**
  9948.    * Gets display name from the given language code.
  9949.    * @param {string} languageCode Language code (ex. "fr").
  9950.    */
  9951.   LanguageList.getDisplayNameFromLanguageCode = function(languageCode) {
  9952.     // Build the language code to display name dictionary at first time.
  9953.     if (!this.languageCodeToDisplayName_) {
  9954.       this.languageCodeToDisplayName_ = {};
  9955.       var languageList = templateData.languageList;
  9956.       for (var i = 0; i < languageList.length; i++) {
  9957.         var language = languageList[i];
  9958.         this.languageCodeToDisplayName_[language.code] = language.displayName;
  9959.       }
  9960.     }
  9961.  
  9962.     return this.languageCodeToDisplayName_[languageCode];
  9963.   }
  9964.  
  9965.   /**
  9966.    * Gets native display name from the given language code.
  9967.    * @param {string} languageCode Language code (ex. "fr").
  9968.    */
  9969.   LanguageList.getNativeDisplayNameFromLanguageCode = function(languageCode) {
  9970.     // Build the language code to display name dictionary at first time.
  9971.     if (!this.languageCodeToNativeDisplayName_) {
  9972.       this.languageCodeToNativeDisplayName_ = {};
  9973.       var languageList = templateData.languageList;
  9974.       for (var i = 0; i < languageList.length; i++) {
  9975.         var language = languageList[i];
  9976.         this.languageCodeToNativeDisplayName_[language.code] =
  9977.             language.nativeDisplayName;
  9978.       }
  9979.     }
  9980.  
  9981.     return this.languageCodeToNativeDisplayName_[languageCode];
  9982.   }
  9983.  
  9984.   /**
  9985.    * Returns true if the given language code is valid.
  9986.    * @param {string} languageCode Language code (ex. "fr").
  9987.    */
  9988.   LanguageList.isValidLanguageCode = function(languageCode) {
  9989.     // Having the display name for the language code means that the
  9990.     // language code is valid.
  9991.     if (LanguageList.getDisplayNameFromLanguageCode(languageCode)) {
  9992.       return true;
  9993.     }
  9994.     return false;
  9995.   }
  9996.  
  9997.   LanguageList.prototype = {
  9998.     __proto__: DeletableItemList.prototype,
  9999.  
  10000.     // The list item being dragged.
  10001.     draggedItem: null,
  10002.     // The drop position information: "below" or "above".
  10003.     dropPos: null,
  10004.     // The preference is a CSV string that describes preferred languages
  10005.     // in Chrome OS. The language list is used for showing the language
  10006.     // list in "Language and Input" options page.
  10007.     preferredLanguagesPref: 'settings.language.preferred_languages',
  10008.     // The preference is a CSV string that describes accept languages used
  10009.     // for content negotiation. To be more precise, the list will be used
  10010.     // in "Accept-Language" header in HTTP requests.
  10011.     acceptLanguagesPref: 'intl.accept_languages',
  10012.  
  10013.     /** @inheritDoc */
  10014.     decorate: function() {
  10015.       DeletableItemList.prototype.decorate.call(this);
  10016.       this.selectionModel = new ListSingleSelectionModel;
  10017.  
  10018.       // HACK(arv): http://crbug.com/40902
  10019.       window.addEventListener('resize', this.redraw.bind(this));
  10020.  
  10021.       // Listen to pref change.
  10022.       if (cr.isChromeOS) {
  10023.         Preferences.getInstance().addEventListener(this.preferredLanguagesPref,
  10024.             this.handlePreferredLanguagesPrefChange_.bind(this));
  10025.       } else {
  10026.         Preferences.getInstance().addEventListener(this.acceptLanguagesPref,
  10027.             this.handleAcceptLanguagesPrefChange_.bind(this));
  10028.       }
  10029.  
  10030.       // Listen to drag and drop events.
  10031.       this.addEventListener('dragstart', this.handleDragStart_.bind(this));
  10032.       this.addEventListener('dragenter', this.handleDragEnter_.bind(this));
  10033.       this.addEventListener('dragover', this.handleDragOver_.bind(this));
  10034.       this.addEventListener('drop', this.handleDrop_.bind(this));
  10035.       this.addEventListener('dragleave', this.handleDragLeave_.bind(this));
  10036.     },
  10037.  
  10038.     createItem: function(languageCode) {
  10039.       return new LanguageListItem(languageCode);
  10040.     },
  10041.  
  10042.     /*
  10043.      * For each item, determines whether it's deletable.
  10044.      */
  10045.     updateDeletable: function() {
  10046.       var items = this.items;
  10047.       for (var i = 0; i < items.length; ++i) {
  10048.         var item = items[i];
  10049.         var languageCode = item.languageCode;
  10050.         var languageOptions = options.LanguageOptions.getInstance();
  10051.         item.deletable = languageOptions.languageIsDeletable(languageCode);
  10052.       }
  10053.     },
  10054.  
  10055.     /*
  10056.      * Adds a language to the language list.
  10057.      * @param {string} languageCode language code (ex. "fr").
  10058.      */
  10059.     addLanguage: function(languageCode) {
  10060.       // It shouldn't happen but ignore the language code if it's
  10061.       // null/undefined, or already present.
  10062.       if (!languageCode || this.dataModel.indexOf(languageCode) >= 0) {
  10063.         return;
  10064.       }
  10065.       this.dataModel.push(languageCode);
  10066.       // Select the last item, which is the language added.
  10067.       this.selectionModel.selectedIndex = this.dataModel.length - 1;
  10068.  
  10069.       this.savePreference_();
  10070.     },
  10071.  
  10072.     /*
  10073.      * Gets the language codes of the currently listed languages.
  10074.      */
  10075.     getLanguageCodes: function() {
  10076.       return this.dataModel.slice();
  10077.     },
  10078.  
  10079.     /*
  10080.      * Gets the language code of the selected language.
  10081.      */
  10082.     getSelectedLanguageCode: function() {
  10083.       return this.selectedItem;
  10084.     },
  10085.  
  10086.     /*
  10087.      * Selects the language by the given language code.
  10088.      * @returns {boolean} True if the operation is successful.
  10089.      */
  10090.     selectLanguageByCode: function(languageCode) {
  10091.       var index = this.dataModel.indexOf(languageCode);
  10092.       if (index >= 0) {
  10093.         this.selectionModel.selectedIndex = index;
  10094.         return true;
  10095.       }
  10096.       return false;
  10097.     },
  10098.  
  10099.     /** @inheritDoc */
  10100.     deleteItemAtIndex: function(index) {
  10101.       if (index >= 0) {
  10102.         this.dataModel.splice(index, 1);
  10103.         // Once the selected item is removed, there will be no selected item.
  10104.         // Select the item pointed by the lead index.
  10105.         index = this.selectionModel.leadIndex;
  10106.         this.savePreference_();
  10107.       }
  10108.       return index;
  10109.     },
  10110.  
  10111.     /*
  10112.      * Computes the target item of drop event.
  10113.      * @param {Event} e The drop or dragover event.
  10114.      * @private
  10115.      */
  10116.     getTargetFromDropEvent_ : function(e) {
  10117.       var target = e.target;
  10118.       // e.target may be an inner element of the list item
  10119.       while (target != null && !(target instanceof ListItem)) {
  10120.         target = target.parentNode;
  10121.       }
  10122.       return target;
  10123.     },
  10124.  
  10125.     /*
  10126.      * Handles the dragstart event.
  10127.      * @param {Event} e The dragstart event.
  10128.      * @private
  10129.      */
  10130.     handleDragStart_: function(e) {
  10131.       var target = e.target;
  10132.       // ListItem should be the only draggable element type in the page,
  10133.       // but just in case.
  10134.       if (target instanceof ListItem) {
  10135.         this.draggedItem = target;
  10136.         e.dataTransfer.effectAllowed = 'move';
  10137.         // We need to put some kind of data in the drag or it will be
  10138.         // ignored.  Use the display name in case the user drags to a text
  10139.         // field or the desktop.
  10140.         e.dataTransfer.setData('text/plain', target.title);
  10141.       }
  10142.     },
  10143.  
  10144.     /*
  10145.      * Handles the dragenter event.
  10146.      * @param {Event} e The dragenter event.
  10147.      * @private
  10148.      */
  10149.     handleDragEnter_: function(e) {
  10150.       e.preventDefault();
  10151.     },
  10152.  
  10153.     /*
  10154.      * Handles the dragover event.
  10155.      * @param {Event} e The dragover event.
  10156.      * @private
  10157.      */
  10158.     handleDragOver_: function(e) {
  10159.       var dropTarget = this.getTargetFromDropEvent_(e);
  10160.       // Determines whether the drop target is to accept the drop.
  10161.       // The drop is only successful on another ListItem.
  10162.       if (!(dropTarget instanceof ListItem) ||
  10163.           dropTarget == this.draggedItem) {
  10164.         this.hideDropMarker_();
  10165.         return;
  10166.       }
  10167.       // Compute the drop postion. Should we move the dragged item to
  10168.       // below or above the drop target?
  10169.       var rect = dropTarget.getBoundingClientRect();
  10170.       var dy = e.clientY - rect.top;
  10171.       var yRatio = dy / rect.height;
  10172.       var dropPos = yRatio <= .5 ? 'above' : 'below';
  10173.       this.dropPos = dropPos;
  10174.       this.showDropMarker_(dropTarget, dropPos);
  10175.       e.preventDefault();
  10176.     },
  10177.  
  10178.     /*
  10179.      * Handles the drop event.
  10180.      * @param {Event} e The drop event.
  10181.      * @private
  10182.      */
  10183.     handleDrop_: function(e) {
  10184.       var dropTarget = this.getTargetFromDropEvent_(e);
  10185.       this.hideDropMarker_();
  10186.  
  10187.       // Delete the language from the original position.
  10188.       var languageCode = this.draggedItem.languageCode;
  10189.       var originalIndex = this.dataModel.indexOf(languageCode);
  10190.       this.dataModel.splice(originalIndex, 1);
  10191.       // Insert the language to the new position.
  10192.       var newIndex = this.dataModel.indexOf(dropTarget.languageCode);
  10193.       if (this.dropPos == 'below')
  10194.         newIndex += 1;
  10195.       this.dataModel.splice(newIndex, 0, languageCode);
  10196.       // The cursor should move to the moved item.
  10197.       this.selectionModel.selectedIndex = newIndex;
  10198.       // Save the preference.
  10199.       this.savePreference_();
  10200.     },
  10201.  
  10202.     /*
  10203.      * Handles the dragleave event.
  10204.      * @param {Event} e The dragleave event
  10205.      * @private
  10206.      */
  10207.     handleDragLeave_ : function(e) {
  10208.       this.hideDropMarker_();
  10209.     },
  10210.  
  10211.     /*
  10212.      * Shows and positions the marker to indicate the drop target.
  10213.      * @param {HTMLElement} target The current target list item of drop
  10214.      * @param {string} pos 'below' or 'above'
  10215.      * @private
  10216.      */
  10217.     showDropMarker_ : function(target, pos) {
  10218.       window.clearTimeout(this.hideDropMarkerTimer_);
  10219.       var marker = $('language-options-list-dropmarker');
  10220.       var rect = target.getBoundingClientRect();
  10221.       var markerHeight = 8;
  10222.       if (pos == 'above') {
  10223.         marker.style.top = (rect.top - markerHeight/2) + 'px';
  10224.       } else {
  10225.         marker.style.top = (rect.bottom - markerHeight/2) + 'px';
  10226.       }
  10227.       marker.style.width = rect.width + 'px';
  10228.       marker.style.left = rect.left + 'px';
  10229.       marker.style.display = 'block';
  10230.     },
  10231.  
  10232.     /*
  10233.      * Hides the drop marker.
  10234.      * @private
  10235.      */
  10236.     hideDropMarker_ : function() {
  10237.       // Hide the marker in a timeout to reduce flickering as we move between
  10238.       // valid drop targets.
  10239.       window.clearTimeout(this.hideDropMarkerTimer_);
  10240.       this.hideDropMarkerTimer_ = window.setTimeout(function() {
  10241.         $('language-options-list-dropmarker').style.display = '';
  10242.       }, 100);
  10243.     },
  10244.  
  10245.     /**
  10246.      * Handles preferred languages pref change.
  10247.      * @param {Event} e The change event object.
  10248.      * @private
  10249.      */
  10250.     handlePreferredLanguagesPrefChange_: function(e) {
  10251.       var languageCodesInCsv = e.value.value;
  10252.       var languageCodes = languageCodesInCsv.split(',');
  10253.  
  10254.       // Add the UI language to the initial list of languages.  This is to avoid
  10255.       // a bug where the UI language would be removed from the preferred
  10256.       // language list by sync on first login.
  10257.       // See: crosbug.com/14283
  10258.       languageCodes.push(navigator.language);
  10259.       languageCodes = this.filterBadLanguageCodes_(languageCodes);
  10260.       this.load_(languageCodes);
  10261.     },
  10262.  
  10263.     /**
  10264.      * Handles accept languages pref change.
  10265.      * @param {Event} e The change event object.
  10266.      * @private
  10267.      */
  10268.     handleAcceptLanguagesPrefChange_: function(e) {
  10269.       var languageCodesInCsv = e.value.value;
  10270.       var languageCodes = this.filterBadLanguageCodes_(
  10271.           languageCodesInCsv.split(','));
  10272.       this.load_(languageCodes);
  10273.     },
  10274.  
  10275.     /**
  10276.      * Loads given language list.
  10277.      * @param {Array} languageCodes List of language codes.
  10278.      * @private
  10279.      */
  10280.     load_: function(languageCodes) {
  10281.       // Preserve the original selected index. See comments below.
  10282.       var originalSelectedIndex = (this.selectionModel ?
  10283.                                    this.selectionModel.selectedIndex : -1);
  10284.       this.dataModel = new ArrayDataModel(languageCodes);
  10285.       if (originalSelectedIndex >= 0 &&
  10286.           originalSelectedIndex < this.dataModel.length) {
  10287.         // Restore the original selected index if the selected index is
  10288.         // valid after the data model is loaded. This is neeeded to keep
  10289.         // the selected language after the languge is added or removed.
  10290.         this.selectionModel.selectedIndex = originalSelectedIndex;
  10291.         // The lead index should be updated too.
  10292.         this.selectionModel.leadIndex = originalSelectedIndex;
  10293.       } else if (this.dataModel.length > 0){
  10294.         // Otherwise, select the first item if it's not empty.
  10295.         // Note that ListSingleSelectionModel won't select an item
  10296.         // automatically, hence we manually select the first item here.
  10297.         this.selectionModel.selectedIndex = 0;
  10298.       }
  10299.     },
  10300.  
  10301.     /**
  10302.      * Saves the preference.
  10303.      */
  10304.     savePreference_: function() {
  10305.       // Encode the language codes into a CSV string.
  10306.       if (cr.isChromeOS)
  10307.         Preferences.setStringPref(this.preferredLanguagesPref,
  10308.                                   this.dataModel.slice().join(','));
  10309.       // Save the same language list as accept languages preference as
  10310.       // well, but we need to expand the language list, to make it more
  10311.       // acceptable. For instance, some web sites don't understand 'en-US'
  10312.       // but 'en'. See crosbug.com/9884.
  10313.       var acceptLanguages = this.expandLanguageCodes(this.dataModel.slice());
  10314.       Preferences.setStringPref(this.acceptLanguagesPref,
  10315.                                 acceptLanguages.join(','));
  10316.       cr.dispatchSimpleEvent(this, 'save');
  10317.     },
  10318.  
  10319.     /**
  10320.      * Expands language codes to make these more suitable for Accept-Language.
  10321.      * Example: ['en-US', 'ja', 'en-CA'] => ['en-US', 'en', 'ja', 'en-CA'].
  10322.      * 'en' won't appear twice as this function eliminates duplicates.
  10323.      * @param {Array} languageCodes List of language codes.
  10324.      * @private
  10325.      */
  10326.     expandLanguageCodes: function(languageCodes) {
  10327.       var expandedLanguageCodes = [];
  10328.       var seen = {};  // Used to eliminiate duplicates.
  10329.       for (var i = 0; i < languageCodes.length; i++) {
  10330.         var languageCode = languageCodes[i];
  10331.         if (!(languageCode in seen)) {
  10332.           expandedLanguageCodes.push(languageCode);
  10333.           seen[languageCode] = true;
  10334.         }
  10335.         var parts = languageCode.split('-');
  10336.         if (!(parts[0] in seen)) {
  10337.           expandedLanguageCodes.push(parts[0]);
  10338.           seen[parts[0]] = true;
  10339.         }
  10340.       }
  10341.       return expandedLanguageCodes;
  10342.     },
  10343.  
  10344.     /**
  10345.      * Filters bad language codes in case bad language codes are
  10346.      * stored in the preference. Removes duplicates as well.
  10347.      * @param {Array} languageCodes List of language codes.
  10348.      * @private
  10349.      */
  10350.     filterBadLanguageCodes_: function(languageCodes) {
  10351.       var filteredLanguageCodes = [];
  10352.       var seen = {};
  10353.       for (var i = 0; i < languageCodes.length; i++) {
  10354.         // Check if the the language code is valid, and not
  10355.         // duplicate. Otherwise, skip it.
  10356.         if (LanguageList.isValidLanguageCode(languageCodes[i]) &&
  10357.             !(languageCodes[i] in seen)) {
  10358.           filteredLanguageCodes.push(languageCodes[i]);
  10359.           seen[languageCodes[i]] = true;
  10360.         }
  10361.       }
  10362.       return filteredLanguageCodes;
  10363.     },
  10364.   };
  10365.  
  10366.   return {
  10367.     LanguageList: LanguageList,
  10368.     LanguageListItem: LanguageListItem
  10369.   };
  10370. });
  10371.  
  10372. // Copyright (c) 2011 The Chromium Authors. All rights reserved.
  10373. // Use of this source code is governed by a BSD-style license that can be
  10374. // found in the LICENSE file.
  10375.  
  10376. // TODO(kochi): Generalize the notification as a component and put it
  10377. // in js/cr/ui/notification.js .
  10378.  
  10379. cr.define('options', function() {
  10380.   const OptionsPage = options.OptionsPage;
  10381.   const LanguageList = options.LanguageList;
  10382.  
  10383.   // Some input methods like Chinese Pinyin have config pages.
  10384.   // This is the map of the input method names to their config page names.
  10385.   const INPUT_METHOD_ID_TO_CONFIG_PAGE_NAME = {
  10386.     'mozc': 'languageMozc',
  10387.     'mozc-chewing': 'languageChewing',
  10388.     'mozc-dv': 'languageMozc',
  10389.     'mozc-hangul': 'languageHangul',
  10390.     'mozc-jp': 'languageMozc',
  10391.     'pinyin': 'languagePinyin',
  10392.     'pinyin-dv': 'languagePinyin',
  10393.   };
  10394.  
  10395.   /////////////////////////////////////////////////////////////////////////////
  10396.   // LanguageOptions class:
  10397.  
  10398.   /**
  10399.    * Encapsulated handling of ChromeOS language options page.
  10400.    * @constructor
  10401.    */
  10402.   function LanguageOptions(model) {
  10403.     OptionsPage.call(this, 'languages', templateData.languagePageTabTitle,
  10404.                      'languagePage');
  10405.   }
  10406.  
  10407.   cr.addSingletonGetter(LanguageOptions);
  10408.  
  10409.   // Inherit LanguageOptions from OptionsPage.
  10410.   LanguageOptions.prototype = {
  10411.     __proto__: OptionsPage.prototype,
  10412.  
  10413.     /**
  10414.      * Initializes LanguageOptions page.
  10415.      * Calls base class implementation to starts preference initialization.
  10416.      */
  10417.     initializePage: function() {
  10418.       OptionsPage.prototype.initializePage.call(this);
  10419.  
  10420.       var languageOptionsList = $('language-options-list');
  10421.       LanguageList.decorate(languageOptionsList);
  10422.  
  10423.       languageOptionsList.addEventListener('change',
  10424.           this.handleLanguageOptionsListChange_.bind(this));
  10425.       languageOptionsList.addEventListener('save',
  10426.           this.handleLanguageOptionsListSave_.bind(this));
  10427.  
  10428.       this.addEventListener('visibleChange',
  10429.                             this.handleVisibleChange_.bind(this));
  10430.  
  10431.       if (cr.isChromeOS) {
  10432.         this.initializeInputMethodList_();
  10433.         this.initializeLanguageCodeToInputMethodIdsMap_();
  10434.       }
  10435.       Preferences.getInstance().addEventListener(this.spellCheckDictionaryPref,
  10436.           this.handleSpellCheckDictionaryPrefChange_.bind(this));
  10437.  
  10438.       // Set up add button.
  10439.       $('language-options-add-button').onclick = function(e) {
  10440.         // Add the language without showing the overlay if it's specified in
  10441.         // the URL hash (ex. lang_add=ja).  Used for automated testing.
  10442.         var match = document.location.hash.match(/\blang_add=([\w-]+)/);
  10443.         if (match) {
  10444.           var addLanguageCode = match[1];
  10445.           $('language-options-list').addLanguage(addLanguageCode);
  10446.         } else {
  10447.           OptionsPage.navigateToPage('addLanguage');
  10448.         }
  10449.       };
  10450.  
  10451.       if (cr.isChromeOS) {
  10452.         // Listen to user clicks on the add language list.
  10453.         var addLanguageList = $('add-language-overlay-language-list');
  10454.         addLanguageList.addEventListener('click',
  10455.             this.handleAddLanguageListClick_.bind(this));
  10456.       } else {
  10457.         // Listen to add language dialog ok button.
  10458.         var addLanguageOkButton = $('add-language-overlay-ok-button');
  10459.         addLanguageOkButton.addEventListener('click',
  10460.             this.handleAddLanguageOkButtonClick_.bind(this));
  10461.  
  10462.         // Show experimental features if enabled.
  10463.         if (templateData.experimentalSpellCheckFeatures == 'true')
  10464.           $('auto-spell-correction-option').hidden = false;
  10465.  
  10466.         // Handle spell check enable/disable.
  10467.         Preferences.getInstance().addEventListener(this.enableSpellCheckPref,
  10468.             this.updateEnableSpellCheck_.bind(this));
  10469.       }
  10470.  
  10471.       // Listen to user clicks on the "Change touch keyboard settings..."
  10472.       // button (if it exists).
  10473.       var virtualKeyboardButton = $('language-options-virtual-keyboard');
  10474.       if (virtualKeyboardButton) {
  10475.         // TODO(yusukes): would be better to hide the button if no virtual
  10476.         // keyboard is registered.
  10477.         virtualKeyboardButton.onclick = function(e) {
  10478.           OptionsPage.navigateToPage('virtualKeyboards');
  10479.         };
  10480.       }
  10481.     },
  10482.  
  10483.     // The preference is a boolean that enables/disables spell checking.
  10484.     enableSpellCheckPref: 'browser.enable_spellchecking',
  10485.     // The preference is a CSV string that describes preload engines
  10486.     // (i.e. active input methods).
  10487.     preloadEnginesPref: 'settings.language.preload_engines',
  10488.     // The list of preload engines, like ['mozc', 'pinyin'].
  10489.     preloadEngines_: [],
  10490.     // The preference is a string that describes the spell check
  10491.     // dictionary language, like "en-US".
  10492.     spellCheckDictionaryPref: 'spellcheck.dictionary',
  10493.     spellCheckDictionary_: "",
  10494.     // The map of language code to input method IDs, like:
  10495.     // {'ja': ['mozc', 'mozc-jp'], 'zh-CN': ['pinyin'], ...}
  10496.     languageCodeToInputMethodIdsMap_: {},
  10497.  
  10498.     /**
  10499.      * Initializes the input method list.
  10500.      */
  10501.     initializeInputMethodList_: function() {
  10502.       var inputMethodList = $('language-options-input-method-list');
  10503.       var inputMethodListData = templateData.inputMethodList;
  10504.  
  10505.       // Add all input methods, but make all of them invisible here. We'll
  10506.       // change the visibility in handleLanguageOptionsListChange_() based
  10507.       // on the selected language. Note that we only have less than 100
  10508.       // input methods, so creating DOM nodes at once here should be ok.
  10509.       for (var i = 0; i < inputMethodListData.length; i++) {
  10510.         var inputMethod = inputMethodListData[i];
  10511.         var input = document.createElement('input');
  10512.         input.type = 'checkbox';
  10513.         input.inputMethodId = inputMethod.id;
  10514.         // Listen to user clicks.
  10515.         input.addEventListener('click',
  10516.                                this.handleCheckboxClick_.bind(this));
  10517.         var label = document.createElement('label');
  10518.         label.appendChild(input);
  10519.         // Adding a space between the checkbox and the text. This is a bit
  10520.         // dirty, but we rely on a space character for all other checkboxes.
  10521.         label.appendChild(document.createTextNode(
  10522.             ' ' + inputMethod.displayName));
  10523.         label.style.display = 'none';
  10524.         label.languageCodeSet = inputMethod.languageCodeSet;
  10525.         // Add the configure button if the config page is present for this
  10526.         // input method.
  10527.         if (inputMethod.id in INPUT_METHOD_ID_TO_CONFIG_PAGE_NAME) {
  10528.           var pageName = INPUT_METHOD_ID_TO_CONFIG_PAGE_NAME[inputMethod.id];
  10529.           var button = this.createConfigureInputMethodButton_(inputMethod.id,
  10530.                                                               pageName);
  10531.           label.appendChild(button);
  10532.         }
  10533.  
  10534.         inputMethodList.appendChild(label);
  10535.       }
  10536.       // Listen to pref change once the input method list is initialized.
  10537.       Preferences.getInstance().addEventListener(this.preloadEnginesPref,
  10538.           this.handlePreloadEnginesPrefChange_.bind(this));
  10539.     },
  10540.  
  10541.     /**
  10542.      * Creates a configure button for the given input method ID.
  10543.      * @param {string} inputMethodId Input method ID (ex. "pinyin").
  10544.      * @param {string} pageName Name of the config page (ex. "languagePinyin").
  10545.      * @private
  10546.      */
  10547.     createConfigureInputMethodButton_: function(inputMethodId, pageName) {
  10548.       var button = document.createElement('button');
  10549.       button.textContent = localStrings.getString('configure');
  10550.       button.onclick = function(e) {
  10551.         // Prevent the default action (i.e. changing the checked property
  10552.         // of the checkbox). The button click here should not be handled
  10553.         // as checkbox click.
  10554.         e.preventDefault();
  10555.         chrome.send('inputMethodOptionsOpen', [inputMethodId]);
  10556.         OptionsPage.navigateToPage(pageName);
  10557.       }
  10558.       return button;
  10559.     },
  10560.  
  10561.     /**
  10562.      * Handles OptionsPage's visible property change event.
  10563.      * @param {Event} e Property change event.
  10564.      * @private
  10565.      */
  10566.     handleVisibleChange_: function(e) {
  10567.       if (this.visible) {
  10568.         $('language-options-list').redraw();
  10569.         chrome.send('languageOptionsOpen');
  10570.       }
  10571.     },
  10572.  
  10573.     /**
  10574.      * Handles languageOptionsList's change event.
  10575.      * @param {Event} e Change event.
  10576.      * @private
  10577.      */
  10578.     handleLanguageOptionsListChange_: function(e) {
  10579.       var languageOptionsList = $('language-options-list');
  10580.       var languageCode = languageOptionsList.getSelectedLanguageCode();
  10581.       // Select the language if it's specified in the URL hash (ex. lang=ja).
  10582.       // Used for automated testing.
  10583.       var match = document.location.hash.match(/\blang=([\w-]+)/);
  10584.       if (match) {
  10585.         var specifiedLanguageCode = match[1];
  10586.         if (languageOptionsList.selectLanguageByCode(specifiedLanguageCode)) {
  10587.           languageCode = specifiedLanguageCode;
  10588.         }
  10589.       }
  10590.       this.updateSelectedLanguageName_(languageCode);
  10591.       if (cr.isWindows || cr.isChromeOS)
  10592.         this.updateUiLanguageButton_(languageCode);
  10593.       this.updateSpellCheckLanguageButton_(languageCode);
  10594.       if (cr.isChromeOS)
  10595.         this.updateInputMethodList_(languageCode);
  10596.       this.updateLanguageListInAddLanguageOverlay_();
  10597.     },
  10598.  
  10599.     /**
  10600.      * Handles languageOptionsList's save event.
  10601.      * @param {Event} e Save event.
  10602.      * @private
  10603.      */
  10604.     handleLanguageOptionsListSave_: function(e) {
  10605.       if (cr.isChromeOS) {
  10606.         // Sort the preload engines per the saved languages before save.
  10607.         this.preloadEngines_ = this.sortPreloadEngines_(this.preloadEngines_);
  10608.         this.savePreloadEnginesPref_();
  10609.       }
  10610.     },
  10611.  
  10612.     /**
  10613.      * Sorts preloadEngines_ by languageOptionsList's order.
  10614.      * @param {Array} preloadEngines List of preload engines.
  10615.      * @return {Array} Returns sorted preloadEngines.
  10616.      * @private
  10617.      */
  10618.     sortPreloadEngines_: function(preloadEngines) {
  10619.       // For instance, suppose we have two languages and associated input
  10620.       // methods:
  10621.       //
  10622.       // - Korean: hangul
  10623.       // - Chinese: pinyin
  10624.       //
  10625.       // The preloadEngines preference should look like "hangul,pinyin".
  10626.       // If the user reverse the order, the preference should be reorderd
  10627.       // to "pinyin,hangul".
  10628.       var languageOptionsList = $('language-options-list');
  10629.       var languageCodes = languageOptionsList.getLanguageCodes();
  10630.  
  10631.       // Convert the list into a dictonary for simpler lookup.
  10632.       var preloadEngineSet = {};
  10633.       for (var i = 0; i < preloadEngines.length; i++) {
  10634.         preloadEngineSet[preloadEngines[i]] = true;
  10635.       }
  10636.  
  10637.       // Create the new preload engine list per the language codes.
  10638.       var newPreloadEngines = [];
  10639.       for (var i = 0; i < languageCodes.length; i++) {
  10640.         var languageCode = languageCodes[i];
  10641.         var inputMethodIds = this.languageCodeToInputMethodIdsMap_[
  10642.             languageCode];
  10643.         // Check if we have active input methods associated with the language.
  10644.         for (var j = 0; j < inputMethodIds.length; j++) {
  10645.           var inputMethodId = inputMethodIds[j];
  10646.           if (inputMethodId in preloadEngineSet) {
  10647.             // If we have, add it to the new engine list.
  10648.             newPreloadEngines.push(inputMethodId);
  10649.             // And delete it from the set. This is necessary as one input
  10650.             // method can be associated with more than one language thus
  10651.             // we should avoid having duplicates in the new list.
  10652.             delete preloadEngineSet[inputMethodId];
  10653.           }
  10654.         }
  10655.       }
  10656.  
  10657.       return newPreloadEngines;
  10658.     },
  10659.  
  10660.     /**
  10661.      * Initializes the map of language code to input method IDs.
  10662.      * @private
  10663.      */
  10664.     initializeLanguageCodeToInputMethodIdsMap_: function() {
  10665.       var inputMethodList = templateData.inputMethodList;
  10666.       for (var i = 0; i < inputMethodList.length; i++) {
  10667.         var inputMethod = inputMethodList[i];
  10668.         for (var languageCode in inputMethod.languageCodeSet) {
  10669.           if (languageCode in this.languageCodeToInputMethodIdsMap_) {
  10670.             this.languageCodeToInputMethodIdsMap_[languageCode].push(
  10671.                 inputMethod.id);
  10672.           } else {
  10673.             this.languageCodeToInputMethodIdsMap_[languageCode] =
  10674.                 [inputMethod.id];
  10675.           }
  10676.         }
  10677.       }
  10678.     },
  10679.  
  10680.     /**
  10681.      * Updates the currently selected language name.
  10682.      * @param {string} languageCode Language code (ex. "fr").
  10683.      * @private
  10684.      */
  10685.     updateSelectedLanguageName_: function(languageCode) {
  10686.       var languageDisplayName = LanguageList.getDisplayNameFromLanguageCode(
  10687.           languageCode);
  10688.       var languageNativeDisplayName =
  10689.           LanguageList.getNativeDisplayNameFromLanguageCode(languageCode);
  10690.       // If the native name is different, add it.
  10691.       if (languageDisplayName != languageNativeDisplayName) {
  10692.         languageDisplayName += ' - ' + languageNativeDisplayName;
  10693.       }
  10694.       // Update the currently selected language name.
  10695.       var languageName = $('language-options-language-name');
  10696.       if (languageDisplayName) {
  10697.         languageName.hidden = false;
  10698.         languageName.textContent = languageDisplayName;
  10699.       } else {
  10700.         languageName.hidden = true;
  10701.       }
  10702.     },
  10703.  
  10704.     /**
  10705.      * Updates the UI language button.
  10706.      * @param {string} languageCode Language code (ex. "fr").
  10707.      * @private
  10708.      */
  10709.     updateUiLanguageButton_: function(languageCode) {
  10710.       var uiLanguageButton = $('language-options-ui-language-button');
  10711.       // Check if the language code matches the current UI language.
  10712.       if (languageCode == templateData.currentUiLanguageCode) {
  10713.         // If it matches, the button just says that the UI language is
  10714.         // currently in use.
  10715.         uiLanguageButton.textContent =
  10716.             localStrings.getString('is_displayed_in_this_language');
  10717.         // Make it look like a text label.
  10718.         uiLanguageButton.className = 'text-button';
  10719.         // Remove the event listner.
  10720.         uiLanguageButton.onclick = undefined;
  10721.       } else if (languageCode in templateData.uiLanguageCodeSet) {
  10722.         // If the language is supported as UI language, users can click on
  10723.         // the button to change the UI language.
  10724.         if (cr.commandLine.options['--bwsi']) {
  10725.           // In the guest mode for ChromeOS, changing UI language does not make
  10726.           // sense because it does not take effect after browser restart.
  10727.           uiLanguageButton.hidden = true;
  10728.         } else {
  10729.           uiLanguageButton.textContent =
  10730.               localStrings.getString('display_in_this_language');
  10731.           uiLanguageButton.className = '';
  10732.           // Send the change request to Chrome.
  10733.           uiLanguageButton.onclick = function(e) {
  10734.             chrome.send('uiLanguageChange', [languageCode]);
  10735.           }
  10736.         }
  10737.         if (cr.isChromeOS) {
  10738.           $('language-options-ui-restart-button').onclick = function(e) {
  10739.             chrome.send('uiLanguageRestart');
  10740.           }
  10741.         }
  10742.       } else {
  10743.         // If the language is not supported as UI language, the button
  10744.         // just says that Chromium OS cannot be displayed in this language.
  10745.         uiLanguageButton.textContent =
  10746.             localStrings.getString('cannot_be_displayed_in_this_language');
  10747.         uiLanguageButton.className = 'text-button';
  10748.         uiLanguageButton.onclick = undefined;
  10749.       }
  10750.       uiLanguageButton.style.display = 'block';
  10751.       $('language-options-ui-notification-bar').style.display = 'none';
  10752.     },
  10753.  
  10754.     /**
  10755.      * Updates the spell check language button.
  10756.      * @param {string} languageCode Language code (ex. "fr").
  10757.      * @private
  10758.      */
  10759.     updateSpellCheckLanguageButton_: function(languageCode) {
  10760.       var display = 'block';
  10761.       var spellCheckLanguageButton = $(
  10762.           'language-options-spell-check-language-button');
  10763.       // Check if the language code matches the current spell check language.
  10764.       if (languageCode == this.spellCheckDictionary_) {
  10765.         // If it matches, the button just says that the spell check language is
  10766.         // currently in use.
  10767.         spellCheckLanguageButton.textContent =
  10768.             localStrings.getString('is_used_for_spell_checking');
  10769.         // Make it look like a text label.
  10770.         spellCheckLanguageButton.className = 'text-button';
  10771.         // Remove the event listner.
  10772.         spellCheckLanguageButton.onclick = undefined;
  10773.       } else if (languageCode in templateData.spellCheckLanguageCodeSet) {
  10774.         // If the language is supported as spell check language, users can
  10775.         // click on the button to change the spell check language.
  10776.         spellCheckLanguageButton.textContent =
  10777.             localStrings.getString('use_this_for_spell_checking');
  10778.         spellCheckLanguageButton.className = '';
  10779.         spellCheckLanguageButton.languageCode = languageCode;
  10780.         // Add an event listner to the click event.
  10781.         spellCheckLanguageButton.addEventListener('click',
  10782.             this.handleSpellCheckLanguageButtonClick_.bind(this));
  10783.       } else if (!languageCode) {
  10784.         display = 'none';
  10785.       } else {
  10786.         // If the language is not supported as spell check language, the
  10787.         // button just says that this language cannot be used for spell
  10788.         // checking.
  10789.         spellCheckLanguageButton.textContent =
  10790.             localStrings.getString('cannot_be_used_for_spell_checking');
  10791.         spellCheckLanguageButton.className = 'text-button';
  10792.         spellCheckLanguageButton.onclick = undefined;
  10793.       }
  10794.       spellCheckLanguageButton.style.display = display;
  10795.       $('language-options-ui-notification-bar').style.display = 'none';
  10796.     },
  10797.  
  10798.     /**
  10799.      * Updates the input method list.
  10800.      * @param {string} languageCode Language code (ex. "fr").
  10801.      * @private
  10802.      */
  10803.     updateInputMethodList_: function(languageCode) {
  10804.       // Give one of the checkboxes or buttons focus, if it's specified in the
  10805.       // URL hash (ex. focus=mozc). Used for automated testing.
  10806.       var focusInputMethodId = -1;
  10807.       var match = document.location.hash.match(/\bfocus=([\w:-]+)\b/);
  10808.       if (match) {
  10809.         focusInputMethodId = match[1];
  10810.       }
  10811.       // Change the visibility of the input method list. Input methods that
  10812.       // matches |languageCode| will become visible.
  10813.       var inputMethodList = $('language-options-input-method-list');
  10814.       var labels = inputMethodList.querySelectorAll('label');
  10815.       for (var i = 0; i < labels.length; i++) {
  10816.         var label = labels[i];
  10817.         if (languageCode in label.languageCodeSet) {
  10818.           label.style.display = 'block';
  10819.           var input = label.childNodes[0];
  10820.           // Give it focus if the ID matches.
  10821.           if (input.inputMethodId == focusInputMethodId) {
  10822.             input.focus();
  10823.           }
  10824.         } else {
  10825.           label.style.display = 'none';
  10826.         }
  10827.       }
  10828.  
  10829.       if (focusInputMethodId == 'add') {
  10830.         $('language-options-add-button').focus();
  10831.       }
  10832.     },
  10833.  
  10834.     /**
  10835.      * Updates the language list in the add language overlay.
  10836.      * @param {string} languageCode Language code (ex. "fr").
  10837.      * @private
  10838.      */
  10839.     updateLanguageListInAddLanguageOverlay_: function(languageCode) {
  10840.       // Change the visibility of the language list in the add language
  10841.       // overlay. Languages that are already active will become invisible,
  10842.       // so that users don't add the same language twice.
  10843.       var languageOptionsList = $('language-options-list');
  10844.       var languageCodes = languageOptionsList.getLanguageCodes();
  10845.       var languageCodeSet = {};
  10846.       for (var i = 0; i < languageCodes.length; i++) {
  10847.         languageCodeSet[languageCodes[i]] = true;
  10848.       }
  10849.       var addLanguageList = $('add-language-overlay-language-list');
  10850.       var lis = addLanguageList.querySelectorAll('li');
  10851.       for (var i = 0; i < lis.length; i++) {
  10852.         // The first child button knows the language code.
  10853.         var button = lis[i].childNodes[0];
  10854.         if (button.languageCode in languageCodeSet) {
  10855.           lis[i].style.display = 'none';
  10856.         } else {
  10857.           lis[i].style.display = 'block';
  10858.         }
  10859.       }
  10860.     },
  10861.  
  10862.     /**
  10863.      * Handles preloadEnginesPref change.
  10864.      * @param {Event} e Change event.
  10865.      * @private
  10866.      */
  10867.     handlePreloadEnginesPrefChange_: function(e) {
  10868.       var value = e.value.value;
  10869.       this.preloadEngines_ = this.filterBadPreloadEngines_(value.split(','));
  10870.       this.updateCheckboxesFromPreloadEngines_();
  10871.       $('language-options-list').updateDeletable();
  10872.     },
  10873.  
  10874.     /**
  10875.      * Handles input method checkbox's click event.
  10876.      * @param {Event} e Click event.
  10877.      * @private
  10878.      */
  10879.     handleCheckboxClick_ : function(e) {
  10880.       var checkbox = e.target;
  10881.       if (this.preloadEngines_.length == 1 && !checkbox.checked) {
  10882.         // Don't allow disabling the last input method.
  10883.         this.showNotification_(
  10884.             localStrings.getString('please_add_another_input_method'),
  10885.             localStrings.getString('ok_button'));
  10886.         checkbox.checked = true;
  10887.         return;
  10888.       }
  10889.       if (checkbox.checked) {
  10890.         chrome.send('inputMethodEnable', [checkbox.inputMethodId]);
  10891.       } else {
  10892.         chrome.send('inputMethodDisable', [checkbox.inputMethodId]);
  10893.       }
  10894.       this.updatePreloadEnginesFromCheckboxes_();
  10895.       this.preloadEngines_ = this.sortPreloadEngines_(this.preloadEngines_);
  10896.       this.savePreloadEnginesPref_();
  10897.     },
  10898.  
  10899.     /**
  10900.      * Handles add language list's click event.
  10901.      * @param {Event} e Click event.
  10902.      */
  10903.     handleAddLanguageListClick_ : function(e) {
  10904.       var languageOptionsList = $('language-options-list');
  10905.       var languageCode = e.target.languageCode;
  10906.       // languageCode can be undefined, if click was made on some random
  10907.       // place in the overlay, rather than a button. Ignore it.
  10908.       if (!languageCode) {
  10909.         return;
  10910.       }
  10911.       languageOptionsList.addLanguage(languageCode);
  10912.       var inputMethodIds = this.languageCodeToInputMethodIdsMap_[languageCode];
  10913.       // Enable the first input method for the language added.
  10914.       if (inputMethodIds && inputMethodIds[0] &&
  10915.           // Don't add the input method it's already present. This can
  10916.           // happen if the same input method is shared among multiple
  10917.           // languages (ex. English US keyboard is used for English US and
  10918.           // Filipino).
  10919.           this.preloadEngines_.indexOf(inputMethodIds[0]) == -1) {
  10920.         this.preloadEngines_.push(inputMethodIds[0]);
  10921.         this.updateCheckboxesFromPreloadEngines_();
  10922.         this.savePreloadEnginesPref_();
  10923.       }
  10924.       OptionsPage.closeOverlay();
  10925.     },
  10926.  
  10927.     /**
  10928.      * Handles add language dialog ok button.
  10929.      */
  10930.     handleAddLanguageOkButtonClick_ : function() {
  10931.       var languagesSelect = $('add-language-overlay-language-list');
  10932.       var selectedIndex = languagesSelect.selectedIndex;
  10933.       if (selectedIndex >= 0) {
  10934.         var selection = languagesSelect.options[selectedIndex];
  10935.         $('language-options-list').addLanguage(String(selection.value));
  10936.         OptionsPage.closeOverlay();
  10937.       }
  10938.     },
  10939.  
  10940.     /**
  10941.      * Checks if languageCode is deletable or not.
  10942.      * @param {String} languageCode the languageCode to check for deletability.
  10943.      */
  10944.     languageIsDeletable: function(languageCode) {
  10945.       // Don't allow removing the language if it's as UI language.
  10946.       if (languageCode == templateData.currentUiLanguageCode)
  10947.         return false;
  10948.       return (!cr.isChromeOS ||
  10949.               this.canDeleteLanguage_(languageCode));
  10950.     },
  10951.  
  10952.     /**
  10953.      * Handles browse.enable_spellchecking change.
  10954.      * @param {Event} e Change event.
  10955.      * @private
  10956.      */
  10957.      updateEnableSpellCheck_: function() {
  10958.        var value = !$('enable-spell-check').checked;
  10959.  
  10960.        $('language-options-spell-check-language-button').disabled = value;
  10961.      },
  10962.  
  10963.     /**
  10964.      * Handles spellCheckDictionaryPref change.
  10965.      * @param {Event} e Change event.
  10966.      * @private
  10967.      */
  10968.     handleSpellCheckDictionaryPrefChange_: function(e) {
  10969.       var languageCode = e.value.value
  10970.       this.spellCheckDictionary_ = languageCode;
  10971.       var languageOptionsList = $('language-options-list');
  10972.       var selectedLanguageCode = languageOptionsList.getSelectedLanguageCode();
  10973.       this.updateSpellCheckLanguageButton_(selectedLanguageCode);
  10974.     },
  10975.  
  10976.     /**
  10977.      * Handles spellCheckLanguageButton click.
  10978.      * @param {Event} e Click event.
  10979.      * @private
  10980.      */
  10981.     handleSpellCheckLanguageButtonClick_: function(e) {
  10982.       var languageCode = e.target.languageCode;
  10983.       // Save the preference.
  10984.       Preferences.setStringPref(this.spellCheckDictionaryPref,
  10985.                                 languageCode);
  10986.       chrome.send('spellCheckLanguageChange', [languageCode]);
  10987.     },
  10988.  
  10989.     /**
  10990.      * Checks whether it's possible to remove the language specified by
  10991.      * languageCode and returns true if possible. This function returns false
  10992.      * if the removal causes the number of preload engines to be zero.
  10993.      *
  10994.      * @param {string} languageCode Language code (ex. "fr").
  10995.      * @return {boolean} Returns true on success.
  10996.      * @private
  10997.      */
  10998.     canDeleteLanguage_: function(languageCode) {
  10999.       // First create the set of engines to be removed from input methods
  11000.       // associated with the language code.
  11001.       var enginesToBeRemovedSet = {};
  11002.       var inputMethodIds = this.languageCodeToInputMethodIdsMap_[languageCode];
  11003.       for (var i = 0; i < inputMethodIds.length; i++) {
  11004.         enginesToBeRemovedSet[inputMethodIds[i]] = true;
  11005.       }
  11006.  
  11007.       // Then eliminate engines that are also used for other active languages.
  11008.       // For instance, if "xkb:us::eng" is used for both English and Filipino.
  11009.       var languageCodes = $('language-options-list').getLanguageCodes();
  11010.       for (var i = 0; i < languageCodes.length; i++) {
  11011.         // Skip the target language code.
  11012.         if (languageCodes[i] == languageCode) {
  11013.           continue;
  11014.         }
  11015.         // Check if input methods used in this language are included in
  11016.         // enginesToBeRemovedSet. If so, eliminate these from the set, so
  11017.         // we don't remove this time.
  11018.         var inputMethodIdsForAnotherLanguage =
  11019.             this.languageCodeToInputMethodIdsMap_[languageCodes[i]];
  11020.         for (var j = 0; j < inputMethodIdsForAnotherLanguage.length; j++) {
  11021.           var inputMethodId = inputMethodIdsForAnotherLanguage[j];
  11022.           if (inputMethodId in enginesToBeRemovedSet) {
  11023.             delete enginesToBeRemovedSet[inputMethodId];
  11024.           }
  11025.         }
  11026.       }
  11027.  
  11028.       // Update the preload engine list with the to-be-removed set.
  11029.       var newPreloadEngines = [];
  11030.       for (var i = 0; i < this.preloadEngines_.length; i++) {
  11031.         if (!(this.preloadEngines_[i] in enginesToBeRemovedSet)) {
  11032.           newPreloadEngines.push(this.preloadEngines_[i]);
  11033.         }
  11034.       }
  11035.       // Don't allow this operation if it causes the number of preload
  11036.       // engines to be zero.
  11037.       return (newPreloadEngines.length > 0);
  11038.     },
  11039.  
  11040.     /**
  11041.      * Saves the preload engines preference.
  11042.      * @private
  11043.      */
  11044.     savePreloadEnginesPref_: function() {
  11045.       Preferences.setStringPref(this.preloadEnginesPref,
  11046.                                 this.preloadEngines_.join(','));
  11047.     },
  11048.  
  11049.     /**
  11050.      * Updates the checkboxes in the input method list from the preload
  11051.      * engines preference.
  11052.      * @private
  11053.      */
  11054.     updateCheckboxesFromPreloadEngines_: function() {
  11055.       // Convert the list into a dictonary for simpler lookup.
  11056.       var dictionary = {};
  11057.       for (var i = 0; i < this.preloadEngines_.length; i++) {
  11058.         dictionary[this.preloadEngines_[i]] = true;
  11059.       }
  11060.  
  11061.       var inputMethodList = $('language-options-input-method-list');
  11062.       var checkboxes = inputMethodList.querySelectorAll('input');
  11063.       for (var i = 0; i < checkboxes.length; i++) {
  11064.         checkboxes[i].checked = (checkboxes[i].inputMethodId in dictionary);
  11065.       }
  11066.     },
  11067.  
  11068.     /**
  11069.      * Updates the preload engines preference from the checkboxes in the
  11070.      * input method list.
  11071.      * @private
  11072.      */
  11073.     updatePreloadEnginesFromCheckboxes_: function() {
  11074.       this.preloadEngines_ = [];
  11075.       var inputMethodList = $('language-options-input-method-list');
  11076.       var checkboxes = inputMethodList.querySelectorAll('input');
  11077.       for (var i = 0; i < checkboxes.length; i++) {
  11078.         if (checkboxes[i].checked) {
  11079.           this.preloadEngines_.push(checkboxes[i].inputMethodId);
  11080.         }
  11081.       }
  11082.       var languageOptionsList = $('language-options-list');
  11083.       languageOptionsList.updateDeletable();
  11084.     },
  11085.  
  11086.     /**
  11087.      * Filters bad preload engines in case bad preload engines are
  11088.      * stored in the preference. Removes duplicates as well.
  11089.      * @param {Array} preloadEngines List of preload engines.
  11090.      * @private
  11091.      */
  11092.     filterBadPreloadEngines_: function(preloadEngines) {
  11093.       // Convert the list into a dictonary for simpler lookup.
  11094.       var dictionary = {};
  11095.       for (var i = 0; i < templateData.inputMethodList.length; i++) {
  11096.         dictionary[templateData.inputMethodList[i].id] = true;
  11097.       }
  11098.  
  11099.       var filteredPreloadEngines = [];
  11100.       var seen = {};
  11101.       for (var i = 0; i < preloadEngines.length; i++) {
  11102.         // Check if the preload engine is present in the
  11103.         // dictionary, and not duplicate. Otherwise, skip it.
  11104.         if (preloadEngines[i] in dictionary && !(preloadEngines[i] in seen)) {
  11105.           filteredPreloadEngines.push(preloadEngines[i]);
  11106.           seen[preloadEngines[i]] = true;
  11107.         }
  11108.       }
  11109.       return filteredPreloadEngines;
  11110.     },
  11111.  
  11112.     // TODO(kochi): This is an adapted copy from new_tab.js.
  11113.     // If this will go as final UI, refactor this to share the component with
  11114.     // new new tab page.
  11115.     /**
  11116.      * Shows notification
  11117.      * @private
  11118.      */
  11119.     notificationTimeout_: null,
  11120.     showNotification_ : function(text, actionText, opt_delay) {
  11121.       var notificationElement = $('notification');
  11122.       var actionLink = notificationElement.querySelector('.link-color');
  11123.       var delay = opt_delay || 10000;
  11124.  
  11125.       function show() {
  11126.         window.clearTimeout(this.notificationTimeout_);
  11127.         notificationElement.classList.add('show');
  11128.         document.body.classList.add('notification-shown');
  11129.       }
  11130.  
  11131.       function hide() {
  11132.         window.clearTimeout(this.notificationTimeout_);
  11133.         notificationElement.classList.remove('show');
  11134.         document.body.classList.remove('notification-shown');
  11135.         // Prevent tabbing to the hidden link.
  11136.         actionLink.tabIndex = -1;
  11137.         // Setting tabIndex to -1 only prevents future tabbing to it. If,
  11138.         // however, the user switches window or a tab and then moves back to
  11139.         // this tab the element may gain focus. We therefore make sure that we
  11140.         // blur the element so that the element focus is not restored when
  11141.         // coming back to this window.
  11142.         actionLink.blur();
  11143.       }
  11144.  
  11145.       function delayedHide() {
  11146.         this.notificationTimeout_ = window.setTimeout(hide, delay);
  11147.       }
  11148.  
  11149.       notificationElement.firstElementChild.textContent = text;
  11150.       actionLink.textContent = actionText;
  11151.  
  11152.       actionLink.onclick = hide;
  11153.       actionLink.onkeydown = function(e) {
  11154.         if (e.keyIdentifier == 'Enter') {
  11155.           hide();
  11156.         }
  11157.       };
  11158.       notificationElement.onmouseover = show;
  11159.       notificationElement.onmouseout = delayedHide;
  11160.       actionLink.onfocus = show;
  11161.       actionLink.onblur = delayedHide;
  11162.       // Enable tabbing to the link now that it is shown.
  11163.       actionLink.tabIndex = 0;
  11164.  
  11165.       show();
  11166.       delayedHide();
  11167.     }
  11168.   };
  11169.  
  11170.   /**
  11171.    * Chrome callback for when the UI language preference is saved.
  11172.    */
  11173.   LanguageOptions.uiLanguageSaved = function() {
  11174.     $('language-options-ui-language-button').style.display = 'none';
  11175.     $('language-options-ui-notification-bar').style.display = 'block';
  11176.   };
  11177.  
  11178.   // Export
  11179.   return {
  11180.     LanguageOptions: LanguageOptions
  11181.   };
  11182. });
  11183.  
  11184. // Copyright (c) 2011 The Chromium Authors. All rights reserved.
  11185. // Use of this source code is governed by a BSD-style license that can be
  11186. // found in the LICENSE file.
  11187.  
  11188. cr.define('options', function() {
  11189.   var OptionsPage = options.OptionsPage;
  11190.   var ArrayDataModel = cr.ui.ArrayDataModel;
  11191.  
  11192.   const localStrings = new LocalStrings();
  11193.  
  11194.   /**
  11195.    * ManageProfileOverlay class
  11196.    * Encapsulated handling of the 'Manage profile...' overlay page.
  11197.    * @constructor
  11198.    * @class
  11199.    */
  11200.   function ManageProfileOverlay() {
  11201.     OptionsPage.call(this,
  11202.                      'manageProfile',
  11203.                      templateData.manageProfileOverlayTabTitle,
  11204.                      'manage-profile-overlay');
  11205.   };
  11206.  
  11207.   cr.addSingletonGetter(ManageProfileOverlay);
  11208.  
  11209.   ManageProfileOverlay.prototype = {
  11210.     // Inherit from OptionsPage.
  11211.     __proto__: OptionsPage.prototype,
  11212.  
  11213.     // Info about the currently managed/deleted profile.
  11214.     profileInfo_: null,
  11215.  
  11216.     // An object containing all known profile names.
  11217.     profileNames_: {},
  11218.  
  11219.     // The currently selected icon in the icon grid.
  11220.     iconGridSelectedURL_: null,
  11221.  
  11222.     /**
  11223.      * Initialize the page.
  11224.      */
  11225.     initializePage: function() {
  11226.       // Call base class implementation to start preference initialization.
  11227.       OptionsPage.prototype.initializePage.call(this);
  11228.  
  11229.       var self = this;
  11230.       var iconGrid = $('manage-profile-icon-grid');
  11231.       options.ProfilesIconGrid.decorate(iconGrid);
  11232.       iconGrid.addEventListener('change', function(e) {
  11233.         self.onIconGridSelectionChanged_();
  11234.       });
  11235.  
  11236.       $('manage-profile-name').oninput = this.onNameChanged_.bind(this);
  11237.       $('manage-profile-cancel').onclick =
  11238.           $('delete-profile-cancel').onclick = function(event) {
  11239.         OptionsPage.closeOverlay();
  11240.       };
  11241.       $('manage-profile-ok').onclick = function(event) {
  11242.         OptionsPage.closeOverlay();
  11243.         self.submitManageChanges_();
  11244.       };
  11245.       $('delete-profile-ok').onclick = function(event) {
  11246.         OptionsPage.closeOverlay();
  11247.         chrome.send('deleteProfile', [self.profileInfo_.filePath]);
  11248.       };
  11249.     },
  11250.  
  11251.     /** @inheritDoc */
  11252.     didShowPage: function() {
  11253.       chrome.send('requestDefaultProfileIcons');
  11254.  
  11255.       // Use the hash to specify the profile index.
  11256.       var hash = location.hash;
  11257.       if (hash) {
  11258.         $('manage-profile-overlay-manage').hidden = false;
  11259.         $('manage-profile-overlay-delete').hidden = true;
  11260.         ManageProfileOverlay.getInstance().hideErrorBubble_();
  11261.  
  11262.         chrome.send('requestProfileInfo', [parseInt(hash.slice(1), 10)]);
  11263.       }
  11264.  
  11265.       $('manage-profile-name').focus();
  11266.     },
  11267.  
  11268.     /**
  11269.      * Set the profile info used in the dialog.
  11270.      * @param {Object} profileInfo An object of the form:
  11271.      *     profileInfo = {
  11272.      *       name: "Profile Name",
  11273.      *       iconURL: "chrome://path/to/icon/image",
  11274.      *       filePath: "/path/to/profile/data/on/disk"
  11275.      *       isCurrentProfile: false,
  11276.      *     };
  11277.      * @private
  11278.      */
  11279.     setProfileInfo_: function(profileInfo) {
  11280.       this.iconGridSelectedURL_ = profileInfo.iconURL;
  11281.       this.profileInfo_ = profileInfo;
  11282.       $('manage-profile-name').value = profileInfo.name;
  11283.       $('manage-profile-icon-grid').selectedItem = profileInfo.iconURL;
  11284.     },
  11285.  
  11286.     /**
  11287.      * Sets the name of the currently edited profile.
  11288.      * @private
  11289.      */
  11290.     setProfileName_: function(name) {
  11291.       if (this.profileInfo_)
  11292.         this.profileInfo_.name = name;
  11293.       $('manage-profile-name').value = name;
  11294.     },
  11295.  
  11296.     /**
  11297.      * Set an array of default icon URLs. These will be added to the grid that
  11298.      * the user will use to choose their profile icon.
  11299.      * @param {Array.<string>} iconURLs An array of icon URLs.
  11300.      * @private
  11301.      */
  11302.     receiveDefaultProfileIcons_: function(iconURLs) {
  11303.       $('manage-profile-icon-grid').dataModel = new ArrayDataModel(iconURLs);
  11304.  
  11305.       // Changing the dataModel resets the selectedItem. Re-select it, if there
  11306.       // is one.
  11307.       if (this.profileInfo_)
  11308.         $('manage-profile-icon-grid').selectedItem = this.profileInfo_.iconURL;
  11309.  
  11310.       var grid = $('manage-profile-icon-grid');
  11311.       // Recalculate the measured item size.
  11312.       grid.measured_ = null;
  11313.       grid.columns = 0;
  11314.       grid.redraw();
  11315.     },
  11316.  
  11317.     /**
  11318.      * Set a dictionary of all profile names. These are used to prevent the
  11319.      * user from naming two profiles the same.
  11320.      * @param {Object} profileNames A dictionary of profile names.
  11321.      * @private
  11322.      */
  11323.     receiveProfileNames_: function(profileNames) {
  11324.       this.profileNames_ = profileNames;
  11325.     },
  11326.  
  11327.     /**
  11328.      * Display the error bubble, with |errorText| in the bubble.
  11329.      * @param {string} errorText The localized string id to display as an error.
  11330.      * @private
  11331.      */
  11332.     showErrorBubble_: function(errorText) {
  11333.       var nameErrorEl = $('manage-profile-error-bubble');
  11334.       nameErrorEl.hidden = false;
  11335.       nameErrorEl.textContent = localStrings.getString(errorText);
  11336.  
  11337.       $('manage-profile-ok').disabled = true;
  11338.     },
  11339.  
  11340.     /**
  11341.      * Hide the error bubble.
  11342.      * @private
  11343.      */
  11344.     hideErrorBubble_: function() {
  11345.       $('manage-profile-error-bubble').hidden = true;
  11346.       $('manage-profile-ok').disabled = false;
  11347.     },
  11348.  
  11349.     /**
  11350.      * oninput callback for <input> field.
  11351.      * @param event The event object
  11352.      * @private
  11353.      */
  11354.     onNameChanged_: function(event) {
  11355.       var newName = event.target.value;
  11356.       var oldName = this.profileInfo_.name;
  11357.  
  11358.       if (newName == oldName) {
  11359.         this.hideErrorBubble_();
  11360.       } else if (this.profileNames_[newName] != undefined) {
  11361.         this.showErrorBubble_('manageProfilesDuplicateNameError');
  11362.       } else {
  11363.         this.hideErrorBubble_();
  11364.  
  11365.         var nameIsValid = $('manage-profile-name').validity.valid;
  11366.         $('manage-profile-ok').disabled = !nameIsValid;
  11367.       }
  11368.     },
  11369.  
  11370.     /**
  11371.      * Called when the user clicks "OK". Saves the newly changed profile info.
  11372.      * @private
  11373.      */
  11374.     submitManageChanges_: function() {
  11375.       var name = $('manage-profile-name').value;
  11376.       var iconURL = $('manage-profile-icon-grid').selectedItem;
  11377.       chrome.send('setProfileNameAndIcon',
  11378.                   [this.profileInfo_.filePath, name, iconURL]);
  11379.     },
  11380.  
  11381.     /**
  11382.      * Called when the selected icon in the icon grid changes.
  11383.      * @private
  11384.      */
  11385.     onIconGridSelectionChanged_: function() {
  11386.       var iconURL = $('manage-profile-icon-grid').selectedItem;
  11387.       if (!iconURL || iconURL == this.iconGridSelectedURL_)
  11388.         return;
  11389.       this.iconGridSelectedURL_ = iconURL;
  11390.       chrome.send('profileIconSelectionChanged',
  11391.                   [this.profileInfo_.filePath, iconURL]);
  11392.     },
  11393.  
  11394.     /**
  11395.      * Display the "Manage Profile" dialog.
  11396.      * @param {Object} profileInfo The profile object of the profile to manage.
  11397.      * @private
  11398.      */
  11399.     showManageDialog_: function(profileInfo) {
  11400.       ManageProfileOverlay.setProfileInfo(profileInfo);
  11401.       $('manage-profile-overlay-manage').hidden = false;
  11402.       $('manage-profile-overlay-delete').hidden = true;
  11403.       ManageProfileOverlay.getInstance().hideErrorBubble_();
  11404.  
  11405.       // Intentionally don't show the URL in the location bar as we don't want
  11406.       // people trying to navigate here by hand.
  11407.       OptionsPage.showPageByName('manageProfile', false);
  11408.     },