Advertisement
Guest User

Script

a guest
Mar 27th, 2012
1,111
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 490.37 KB | None | 0 0
  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. },
  11409.  
  11410. /**
  11411. * Display the "Delete Profile" dialog.
  11412. * @param {Object} profileInfo The profile object of the profile to delete.
  11413. * @private
  11414. */
  11415. showDeleteDialog_: function(profileInfo) {
  11416. ManageProfileOverlay.setProfileInfo(profileInfo);
  11417. $('manage-profile-overlay-manage').hidden = true;
  11418. $('manage-profile-overlay-delete').hidden = false;
  11419. $('delete-profile-message').textContent =
  11420. localStrings.getStringF('deleteProfileMessage', profileInfo.name);
  11421. $('delete-profile-message').style.backgroundImage = 'url("' +
  11422. profileInfo.iconURL + '")';
  11423.  
  11424. // Intentionally don't show the URL in the location bar as we don't want
  11425. // people trying to navigate here by hand.
  11426. OptionsPage.showPageByName('manageProfile', false);
  11427. },
  11428. };
  11429.  
  11430. // Forward public APIs to private implementations.
  11431. [
  11432. 'receiveDefaultProfileIcons',
  11433. 'receiveProfileNames',
  11434. 'setProfileInfo',
  11435. 'setProfileName',
  11436. 'showManageDialog',
  11437. 'showDeleteDialog',
  11438. ].forEach(function(name) {
  11439. ManageProfileOverlay[name] = function(value) {
  11440. ManageProfileOverlay.getInstance()[name + '_'](value);
  11441. };
  11442. });
  11443.  
  11444. // Export
  11445. return {
  11446. ManageProfileOverlay: ManageProfileOverlay
  11447. };
  11448. });
  11449.  
  11450. // Copyright (c) 2011 The Chromium Authors. All rights reserved.
  11451. // Use of this source code is governed by a BSD-style license that can be
  11452. // found in the LICENSE file.
  11453.  
  11454. cr.define('options', function() {
  11455. const OptionsPage = options.OptionsPage;
  11456.  
  11457. /**
  11458. * PackExtensionOverlay class
  11459. * Encapsulated handling of the 'Pack Extension' overlay page.
  11460. * @constructor
  11461. */
  11462. function PackExtensionOverlay() {
  11463. OptionsPage.call(this, 'packExtensionOverlay',
  11464. templateData.packExtensionOverlayTabTitle,
  11465. 'packExtensionOverlay');
  11466. }
  11467.  
  11468. cr.addSingletonGetter(PackExtensionOverlay);
  11469.  
  11470. PackExtensionOverlay.prototype = {
  11471. // Inherit PackExtensionOverlay from OptionsPage.
  11472. __proto__: OptionsPage.prototype,
  11473.  
  11474. /**
  11475. * Initialize the page.
  11476. */
  11477. initializePage: function() {
  11478. // Call base class implementation to starts preference initialization.
  11479. OptionsPage.prototype.initializePage.call(this);
  11480.  
  11481. $('packExtensionDismiss').onclick = function(event) {
  11482. OptionsPage.closeOverlay();
  11483. };
  11484. $('packExtensionCommit').onclick = function(event) {
  11485. var extensionPath = $('extensionRootDir').value;
  11486. var privateKeyPath = $('extensionPrivateKey').value;
  11487. chrome.send('pack', [extensionPath, privateKeyPath]);
  11488. };
  11489. $('browseExtensionDir').addEventListener('click',
  11490. this.handleBrowseExtensionDir_.bind(this));
  11491. $('browsePrivateKey').addEventListener('click',
  11492. this.handleBrowsePrivateKey_.bind(this));
  11493. },
  11494.  
  11495. /**
  11496. * Utility function which asks the C++ to show a platform-specific file
  11497. * select dialog, and fire |callback| with the |filePath| that resulted.
  11498. * |selectType| can be either 'file' or 'folder'. |operation| can be 'load',
  11499. * 'packRoot', or 'pem' which are signals to the C++ to do some
  11500. * operation-specific configuration.
  11501. * @private
  11502. */
  11503. showFileDialog_: function(selectType, operation, callback) {
  11504. handleFilePathSelected = function(filePath) {
  11505. callback(filePath);
  11506. handleFilePathSelected = function() {};
  11507. };
  11508.  
  11509. chrome.send('extensionSettingsSelectFilePath', [selectType, operation]);
  11510. },
  11511.  
  11512. /**
  11513. * Handles the showing of the extension directory browser.
  11514. * @param {Event} e Change event.
  11515. * @private
  11516. */
  11517. handleBrowseExtensionDir_: function(e) {
  11518. this.showFileDialog_('folder', 'load', function(filePath) {
  11519. $('extensionRootDir').value = filePath;
  11520. });
  11521. },
  11522.  
  11523. /**
  11524. * Handles the showing of the extension private key file.
  11525. * @param {Event} e Change event.
  11526. * @private
  11527. */
  11528. handleBrowsePrivateKey_: function(e) {
  11529. this.showFileDialog_('file', 'load', function(filePath) {
  11530. $('extensionPrivateKey').value = filePath;
  11531. });
  11532. },
  11533. };
  11534.  
  11535. // Export
  11536. return {
  11537. PackExtensionOverlay: PackExtensionOverlay
  11538. };
  11539. });
  11540.  
  11541. // Copyright (c) 2011 The Chromium Authors. All rights reserved.
  11542. // Use of this source code is governed by a BSD-style license that can be
  11543. // found in the LICENSE file.
  11544.  
  11545. cr.define('options', function() {
  11546. const OptionsPage = options.OptionsPage;
  11547. const ArrayDataModel = cr.ui.ArrayDataModel;
  11548.  
  11549. /////////////////////////////////////////////////////////////////////////////
  11550. // PasswordManager class:
  11551.  
  11552. /**
  11553. * Encapsulated handling of password and exceptions page.
  11554. * @constructor
  11555. */
  11556. function PasswordManager() {
  11557. this.activeNavTab = null;
  11558. OptionsPage.call(this,
  11559. 'passwords',
  11560. templateData.passwordsPageTabTitle,
  11561. 'password-manager');
  11562. }
  11563.  
  11564. cr.addSingletonGetter(PasswordManager);
  11565.  
  11566. PasswordManager.prototype = {
  11567. __proto__: OptionsPage.prototype,
  11568.  
  11569. /**
  11570. * The saved passwords list.
  11571. * @type {DeletableItemList}
  11572. * @private
  11573. */
  11574. savedPasswordsList_: null,
  11575.  
  11576. /**
  11577. * The password exceptions list.
  11578. * @type {DeletableItemList}
  11579. * @private
  11580. */
  11581. passwordExceptionsList_: null,
  11582.  
  11583. /**
  11584. * The timer id of the timer set on search query change events.
  11585. * @type {number}
  11586. * @private
  11587. */
  11588. queryDelayTimerId_: 0,
  11589.  
  11590. /**
  11591. * The most recent search query, or null if the query is empty.
  11592. * @type {?string}
  11593. * @private
  11594. */
  11595. lastQuery_: null,
  11596.  
  11597. /** @inheritDoc */
  11598. initializePage: function() {
  11599. OptionsPage.prototype.initializePage.call(this);
  11600.  
  11601. $('password-search-box').addEventListener('search',
  11602. this.handleSearchQueryChange_.bind(this));
  11603.  
  11604. this.createSavedPasswordsList_();
  11605. this.createPasswordExceptionsList_();
  11606. },
  11607.  
  11608. /** @inheritDoc */
  11609. canShowPage: function() {
  11610. return !PersonalOptions.disablePasswordManagement();
  11611. },
  11612.  
  11613. /** @inheritDoc */
  11614. didShowPage: function() {
  11615. // Updating the password lists may cause a blocking platform dialog pop up
  11616. // (Mac, Linux), so we delay this operation until the page is shown.
  11617. chrome.send('updatePasswordLists');
  11618. $('password-search-box').focus();
  11619. },
  11620.  
  11621. /**
  11622. * Creates, decorates and initializes the saved passwords list.
  11623. * @private
  11624. */
  11625. createSavedPasswordsList_: function() {
  11626. this.savedPasswordsList_ = $('saved-passwords-list');
  11627. options.passwordManager.PasswordsList.decorate(this.savedPasswordsList_);
  11628. this.savedPasswordsList_.autoExpands = true;
  11629. },
  11630.  
  11631. /**
  11632. * Creates, decorates and initializes the password exceptions list.
  11633. * @private
  11634. */
  11635. createPasswordExceptionsList_: function() {
  11636. this.passwordExceptionsList_ = $('password-exceptions-list');
  11637. options.passwordManager.PasswordExceptionsList.decorate(
  11638. this.passwordExceptionsList_);
  11639. this.passwordExceptionsList_.autoExpands = true;
  11640. },
  11641.  
  11642. /**
  11643. * Handles search query changes.
  11644. * @param {!Event} e The event object.
  11645. * @private
  11646. */
  11647. handleSearchQueryChange_: function(e) {
  11648. if (this.queryDelayTimerId_)
  11649. window.clearTimeout(this.queryDelayTimerId_);
  11650.  
  11651. // Searching cookies uses a timeout of 500ms. We use a shorter timeout
  11652. // because there are probably fewer passwords and we want the UI to be
  11653. // snappier since users will expect that it's "less work."
  11654. this.queryDelayTimerId_ = window.setTimeout(
  11655. this.searchPasswords_.bind(this), 250);
  11656. },
  11657.  
  11658. /**
  11659. * Search passwords using text in |password-search-box|.
  11660. * @private
  11661. */
  11662. searchPasswords_: function() {
  11663. this.queryDelayTimerId_ = 0;
  11664. var filter = $('password-search-box').value;
  11665. filter = (filter == '') ? null : filter;
  11666. if (this.lastQuery_ != filter) {
  11667. this.lastQuery_ = filter;
  11668. // Searching for passwords has the side effect of requerying the
  11669. // underlying password store. This is done intentionally, as on OS X and
  11670. // Linux they can change from outside and we won't be notified of it.
  11671. chrome.send('updatePasswordLists');
  11672. }
  11673. },
  11674.  
  11675. /**
  11676. * Updates the visibility of the list and empty list placeholder.
  11677. * @param {!List} list The list to toggle visilibility for.
  11678. */
  11679. updateListVisibility_: function(list) {
  11680. var empty = list.dataModel.length == 0;
  11681. var listPlaceHolderID = list.id + '-empty-placeholder';
  11682. list.hidden = empty;
  11683. $(listPlaceHolderID).hidden = !empty;
  11684. },
  11685.  
  11686. /**
  11687. * Updates the data model for the saved passwords list with the values from
  11688. * |entries|.
  11689. * @param {Array} entries The list of saved password data.
  11690. */
  11691. setSavedPasswordsList_: function(entries) {
  11692. if (this.lastQuery_) {
  11693. // Implement password searching here in javascript, rather than in C++.
  11694. // The number of saved passwords shouldn't be too big for us to handle.
  11695. var query = this.lastQuery_;
  11696. var filter = function(entry, index, list) {
  11697. // Search both URL and username.
  11698. if (entry[0].indexOf(query) >= 0 || entry[1].indexOf(query) >= 0) {
  11699. // Keep the original index so we can delete correctly. See also
  11700. // deleteItemAtIndex() in password_manager_list.js that uses this.
  11701. entry[3] = index;
  11702. return true;
  11703. }
  11704. return false;
  11705. };
  11706. entries = entries.filter(filter);
  11707. }
  11708. this.savedPasswordsList_.dataModel = new ArrayDataModel(entries);
  11709. this.updateListVisibility_(this.savedPasswordsList_);
  11710. },
  11711.  
  11712. /**
  11713. * Updates the data model for the password exceptions list with the values
  11714. * from |entries|.
  11715. * @param {Array} entries The list of password exception data.
  11716. */
  11717. setPasswordExceptionsList_: function(entries) {
  11718. this.passwordExceptionsList_.dataModel = new ArrayDataModel(entries);
  11719. this.updateListVisibility_(this.passwordExceptionsList_);
  11720. },
  11721. };
  11722.  
  11723. /**
  11724. * Call to remove a saved password.
  11725. * @param rowIndex indicating the row to remove.
  11726. */
  11727. PasswordManager.removeSavedPassword = function(rowIndex) {
  11728. chrome.send('removeSavedPassword', [String(rowIndex)]);
  11729. };
  11730.  
  11731. /**
  11732. * Call to remove a password exception.
  11733. * @param rowIndex indicating the row to remove.
  11734. */
  11735. PasswordManager.removePasswordException = function(rowIndex) {
  11736. chrome.send('removePasswordException', [String(rowIndex)]);
  11737. };
  11738.  
  11739. /**
  11740. * Call to remove all saved passwords.
  11741. * @param tab contentType of the tab currently on.
  11742. */
  11743. PasswordManager.removeAllPasswords = function() {
  11744. chrome.send('removeAllSavedPasswords');
  11745. };
  11746.  
  11747. /**
  11748. * Call to remove all saved passwords.
  11749. * @param tab contentType of the tab currently on.
  11750. */
  11751. PasswordManager.removeAllPasswordExceptions = function() {
  11752. chrome.send('removeAllPasswordExceptions');
  11753. };
  11754.  
  11755. PasswordManager.setSavedPasswordsList = function(entries) {
  11756. PasswordManager.getInstance().setSavedPasswordsList_(entries);
  11757. };
  11758.  
  11759. PasswordManager.setPasswordExceptionsList = function(entries) {
  11760. PasswordManager.getInstance().setPasswordExceptionsList_(entries);
  11761. };
  11762.  
  11763. // Export
  11764. return {
  11765. PasswordManager: PasswordManager
  11766. };
  11767.  
  11768. });
  11769.  
  11770. // Copyright (c) 2011 The Chromium Authors. All rights reserved.
  11771. // Use of this source code is governed by a BSD-style license that can be
  11772. // found in the LICENSE file.
  11773.  
  11774. cr.define('options.passwordManager', function() {
  11775. const ArrayDataModel = cr.ui.ArrayDataModel;
  11776. const DeletableItemList = options.DeletableItemList;
  11777. const DeletableItem = options.DeletableItem;
  11778. const List = cr.ui.List;
  11779.  
  11780. /**
  11781. * Creates a new passwords list item.
  11782. * @param {Array} entry An array of the form [url, username, password]. When
  11783. * the list has been filtered, a fourth element [index] may be present.
  11784. * @constructor
  11785. * @extends {cr.ui.ListItem}
  11786. */
  11787. function PasswordListItem(entry, showPasswords) {
  11788. var el = cr.doc.createElement('div');
  11789. el.dataItem = entry;
  11790. el.__proto__ = PasswordListItem.prototype;
  11791. el.decorate(showPasswords);
  11792.  
  11793. return el;
  11794. }
  11795.  
  11796. PasswordListItem.prototype = {
  11797. __proto__: DeletableItem.prototype,
  11798.  
  11799. /** @inheritDoc */
  11800. decorate: function(showPasswords) {
  11801. DeletableItem.prototype.decorate.call(this);
  11802.  
  11803. // The URL of the site.
  11804. var urlLabel = this.ownerDocument.createElement('div');
  11805. urlLabel.classList.add('favicon-cell');
  11806. urlLabel.classList.add('weakrtl');
  11807. urlLabel.classList.add('url');
  11808. urlLabel.setAttribute('title', this.url);
  11809. urlLabel.textContent = this.url;
  11810. urlLabel.style.backgroundImage = url('chrome://favicon/' + this.url);
  11811. this.contentElement.appendChild(urlLabel);
  11812.  
  11813. // The stored username.
  11814. var usernameLabel = this.ownerDocument.createElement('div');
  11815. usernameLabel.className = 'name';
  11816. usernameLabel.textContent = this.username;
  11817. this.contentElement.appendChild(usernameLabel);
  11818.  
  11819. // The stored password.
  11820. var passwordInputDiv = this.ownerDocument.createElement('div');
  11821. passwordInputDiv.className = 'password';
  11822.  
  11823. // The password input field.
  11824. var passwordInput = this.ownerDocument.createElement('input');
  11825. passwordInput.type = 'password';
  11826. passwordInput.className = 'inactive-password';
  11827. passwordInput.readOnly = true;
  11828. passwordInput.value = showPasswords ? this.password : '********';
  11829. passwordInputDiv.appendChild(passwordInput);
  11830.  
  11831. // The show/hide button.
  11832. if (showPasswords) {
  11833. var button = this.ownerDocument.createElement('button');
  11834. button.hidden = true;
  11835. button.classList.add('password-button');
  11836. button.textContent = localStrings.getString('passwordShowButton');
  11837. button.addEventListener('click', this.onClick_, true);
  11838. passwordInputDiv.appendChild(button);
  11839. }
  11840.  
  11841. this.contentElement.appendChild(passwordInputDiv);
  11842. },
  11843.  
  11844. /** @inheritDoc */
  11845. selectionChanged: function() {
  11846. var passwordInput = this.querySelector('input[type=password]');
  11847. var textInput = this.querySelector('input[type=text]');
  11848. var input = passwordInput || textInput;
  11849. var button = input.nextSibling;
  11850. // |button| doesn't exist when passwords can't be shown.
  11851. if (!button)
  11852. return;
  11853. if (this.selected) {
  11854. input.classList.remove('inactive-password');
  11855. button.hidden = false;
  11856. } else {
  11857. input.classList.add('inactive-password');
  11858. button.hidden = true;
  11859. }
  11860. },
  11861.  
  11862. /**
  11863. * On-click event handler. Swaps the type of the input field from password
  11864. * to text and back.
  11865. * @private
  11866. */
  11867. onClick_: function(event) {
  11868. // The password is the input element previous to the button.
  11869. var button = event.currentTarget;
  11870. var passwordInput = button.previousSibling;
  11871. if (passwordInput.type == 'password') {
  11872. passwordInput.type = 'text';
  11873. button.textContent = localStrings.getString('passwordHideButton');
  11874. } else {
  11875. passwordInput.type = 'password';
  11876. button.textContent = localStrings.getString('passwordShowButton');
  11877. }
  11878. },
  11879.  
  11880. /**
  11881. * Get and set the URL for the entry.
  11882. * @type {string}
  11883. */
  11884. get url() {
  11885. return this.dataItem[0];
  11886. },
  11887. set url(url) {
  11888. this.dataItem[0] = url;
  11889. },
  11890.  
  11891. /**
  11892. * Get and set the username for the entry.
  11893. * @type {string}
  11894. */
  11895. get username() {
  11896. return this.dataItem[1];
  11897. },
  11898. set username(username) {
  11899. this.dataItem[1] = username;
  11900. },
  11901.  
  11902. /**
  11903. * Get and set the password for the entry.
  11904. * @type {string}
  11905. */
  11906. get password() {
  11907. return this.dataItem[2];
  11908. },
  11909. set password(password) {
  11910. this.dataItem[2] = password;
  11911. },
  11912. };
  11913.  
  11914. /**
  11915. * Creates a new PasswordExceptions list item.
  11916. * @param {Array} entry A pair of the form [url, username].
  11917. * @constructor
  11918. * @extends {Deletable.ListItem}
  11919. */
  11920. function PasswordExceptionsListItem(entry) {
  11921. var el = cr.doc.createElement('div');
  11922. el.dataItem = entry;
  11923. el.__proto__ = PasswordExceptionsListItem.prototype;
  11924. el.decorate();
  11925.  
  11926. return el;
  11927. }
  11928.  
  11929. PasswordExceptionsListItem.prototype = {
  11930. __proto__: DeletableItem.prototype,
  11931.  
  11932. /**
  11933. * Call when an element is decorated as a list item.
  11934. */
  11935. decorate: function() {
  11936. DeletableItem.prototype.decorate.call(this);
  11937.  
  11938. // The URL of the site.
  11939. var urlLabel = this.ownerDocument.createElement('div');
  11940. urlLabel.className = 'url';
  11941. urlLabel.classList.add('favicon-cell');
  11942. urlLabel.classList.add('weakrtl');
  11943. urlLabel.textContent = this.url;
  11944. urlLabel.style.backgroundImage = url('chrome://favicon/' + this.url);
  11945. this.contentElement.appendChild(urlLabel);
  11946. },
  11947.  
  11948. /**
  11949. * Get the url for the entry.
  11950. * @type {string}
  11951. */
  11952. get url() {
  11953. return this.dataItem;
  11954. },
  11955. set url(url) {
  11956. this.dataItem = url;
  11957. },
  11958. };
  11959.  
  11960. /**
  11961. * Create a new passwords list.
  11962. * @constructor
  11963. * @extends {cr.ui.List}
  11964. */
  11965. var PasswordsList = cr.ui.define('list');
  11966.  
  11967. PasswordsList.prototype = {
  11968. __proto__: DeletableItemList.prototype,
  11969.  
  11970. /**
  11971. * Whether passwords can be revealed or not.
  11972. * @type {boolean}
  11973. * @private
  11974. */
  11975. showPasswords_: true,
  11976.  
  11977. /** @inheritDoc */
  11978. decorate: function() {
  11979. DeletableItemList.prototype.decorate.call(this);
  11980. Preferences.getInstance().addEventListener(
  11981. "profile.password_manager_allow_show_passwords",
  11982. this.onPreferenceChanged_.bind(this));
  11983. },
  11984.  
  11985. /**
  11986. * Listener for changes on the preference.
  11987. * @param {Event} event The preference update event.
  11988. * @private
  11989. */
  11990. onPreferenceChanged_: function(event) {
  11991. this.showPasswords_ = event.value.value;
  11992. this.redraw();
  11993. },
  11994.  
  11995. /** @inheritDoc */
  11996. createItem: function(entry) {
  11997. return new PasswordListItem(entry, this.showPasswords_);
  11998. },
  11999.  
  12000. /** @inheritDoc */
  12001. deleteItemAtIndex: function(index) {
  12002. var item = this.dataModel.item(index);
  12003. if (item && item.length > 3) {
  12004. // The fourth element, if present, is the original index to delete.
  12005. index = item[3];
  12006. }
  12007. PasswordManager.removeSavedPassword(index);
  12008. },
  12009.  
  12010. /**
  12011. * The length of the list.
  12012. */
  12013. get length() {
  12014. return this.dataModel.length;
  12015. },
  12016. };
  12017.  
  12018. /**
  12019. * Create a new passwords list.
  12020. * @constructor
  12021. * @extends {cr.ui.List}
  12022. */
  12023. var PasswordExceptionsList = cr.ui.define('list');
  12024.  
  12025. PasswordExceptionsList.prototype = {
  12026. __proto__: DeletableItemList.prototype,
  12027.  
  12028. /** @inheritDoc */
  12029. createItem: function(entry) {
  12030. return new PasswordExceptionsListItem(entry);
  12031. },
  12032.  
  12033. /** @inheritDoc */
  12034. deleteItemAtIndex: function(index) {
  12035. PasswordManager.removePasswordException(index);
  12036. },
  12037.  
  12038. /**
  12039. * The length of the list.
  12040. */
  12041. get length() {
  12042. return this.dataModel.length;
  12043. },
  12044. };
  12045.  
  12046. return {
  12047. PasswordListItem: PasswordListItem,
  12048. PasswordExceptionsListItem: PasswordExceptionsListItem,
  12049. PasswordsList: PasswordsList,
  12050. PasswordExceptionsList: PasswordExceptionsList,
  12051. };
  12052. });
  12053.  
  12054. // Copyright (c) 2011 The Chromium Authors. All rights reserved.
  12055. // Use of this source code is governed by a BSD-style license that can be
  12056. // found in the LICENSE file.
  12057.  
  12058. cr.define('options', function() {
  12059.  
  12060. var OptionsPage = options.OptionsPage;
  12061. var ArrayDataModel = cr.ui.ArrayDataModel;
  12062.  
  12063. /**
  12064. * Encapsulated handling of personal options page.
  12065. * @constructor
  12066. */
  12067. function PersonalOptions() {
  12068. OptionsPage.call(this, 'personal',
  12069. templateData.personalPageTabTitle,
  12070. 'personal-page');
  12071. if (cr.isChromeOS) {
  12072. // Username (canonical email) of the currently logged in user or
  12073. // |kGuestUser| if a guest session is active.
  12074. this.username_ = localStrings.getString('username');
  12075. }
  12076. }
  12077.  
  12078. cr.addSingletonGetter(PersonalOptions);
  12079.  
  12080. PersonalOptions.prototype = {
  12081. // Inherit PersonalOptions from OptionsPage.
  12082. __proto__: options.OptionsPage.prototype,
  12083.  
  12084. // State variables.
  12085. syncEnabled: false,
  12086. syncSetupCompleted: false,
  12087.  
  12088. // Initialize PersonalOptions page.
  12089. initializePage: function() {
  12090. // Call base class implementation to start preference initialization.
  12091. OptionsPage.prototype.initializePage.call(this);
  12092.  
  12093. var self = this;
  12094.  
  12095. // Sync.
  12096. $('sync-action-link').onclick = function(event) {
  12097. SyncSetupOverlay.showErrorUI();
  12098. };
  12099. $('start-stop-sync').onclick = function(event) {
  12100. if (self.syncSetupCompleted)
  12101. SyncSetupOverlay.showStopSyncingUI();
  12102. else
  12103. SyncSetupOverlay.showSetupUI();
  12104. };
  12105. $('customize-sync').onclick = function(event) {
  12106. SyncSetupOverlay.showSetupUI();
  12107. };
  12108.  
  12109. // Profiles.
  12110. var profilesList = $('profiles-list');
  12111. options.personal_options.ProfileList.decorate(profilesList);
  12112. profilesList.autoExpands = true;
  12113.  
  12114. profilesList.onchange = self.setProfileViewButtonsStatus_;
  12115. $('profiles-create').onclick = function(event) {
  12116. chrome.send('createProfile');
  12117. };
  12118. $('profiles-manage').onclick = function(event) {
  12119. var selectedProfile = self.getSelectedProfileItem_();
  12120. if (selectedProfile)
  12121. ManageProfileOverlay.showManageDialog(selectedProfile);
  12122. };
  12123. $('profiles-delete').onclick = function(event) {
  12124. var selectedProfile = self.getSelectedProfileItem_();
  12125. if (selectedProfile)
  12126. ManageProfileOverlay.showDeleteDialog(selectedProfile);
  12127. };
  12128.  
  12129. // Passwords.
  12130. $('manage-passwords').onclick = function(event) {
  12131. OptionsPage.navigateToPage('passwords');
  12132. OptionsPage.showTab($('passwords-nav-tab'));
  12133. chrome.send('coreOptionsUserMetricsAction',
  12134. ['Options_ShowPasswordManager']);
  12135. };
  12136.  
  12137. // Autofill.
  12138. $('autofill-settings').onclick = function(event) {
  12139. OptionsPage.navigateToPage('autofill');
  12140. chrome.send('coreOptionsUserMetricsAction',
  12141. ['Options_ShowAutofillSettings']);
  12142. };
  12143. if (cr.isChromeOS && cr.commandLine.options['--bwsi']) {
  12144. // Hide Autofill options for the guest user.
  12145. $('autofill-section').hidden = true;
  12146. }
  12147.  
  12148. // Appearance.
  12149. $('themes-reset').onclick = function(event) {
  12150. chrome.send('themesReset');
  12151. };
  12152.  
  12153. if (!cr.isChromeOS) {
  12154. $('import-data').onclick = function(event) {
  12155. // Make sure that any previous import success message is hidden, and
  12156. // we're showing the UI to import further data.
  12157. $('import-data-configure').hidden = false;
  12158. $('import-data-success').hidden = true;
  12159. OptionsPage.navigateToPage('importData');
  12160. chrome.send('coreOptionsUserMetricsAction', ['Import_ShowDlg']);
  12161. };
  12162.  
  12163. if ($('themes-GTK-button')) {
  12164. $('themes-GTK-button').onclick = function(event) {
  12165. chrome.send('themesSetGTK');
  12166. };
  12167. }
  12168. } else {
  12169. $('change-picture-button').onclick = function(event) {
  12170. OptionsPage.navigateToPage('changePicture');
  12171. };
  12172. this.updateAccountPicture_();
  12173.  
  12174. if (cr.commandLine.options['--bwsi']) {
  12175. // Disable the screen lock checkbox and change-picture-button in
  12176. // guest mode.
  12177. $('enable-screen-lock').disabled = true;
  12178. $('change-picture-button').disabled = true;
  12179. }
  12180. }
  12181.  
  12182. if (PersonalOptions.disablePasswordManagement()) {
  12183. // Disable the Password Manager in guest mode.
  12184. $('passwords-offersave').disabled = true;
  12185. $('passwords-neversave').disabled = true;
  12186. $('passwords-offersave').value = false;
  12187. $('passwords-neversave').value = true;
  12188. $('manage-passwords').disabled = true;
  12189. }
  12190.  
  12191. $('mac-passwords-warning').hidden =
  12192. !(localStrings.getString('macPasswordsWarning'));
  12193.  
  12194. if (PersonalOptions.disableAutofillManagement()) {
  12195. $('autofill-settings').disabled = true;
  12196.  
  12197. // Disable and turn off autofill.
  12198. var autofillEnabled = $('autofill-enabled');
  12199. autofillEnabled.disabled = true;
  12200. autofillEnabled.checked = false;
  12201. cr.dispatchSimpleEvent(autofillEnabled, 'change');
  12202. }
  12203. },
  12204.  
  12205. setSyncEnabled_: function(enabled) {
  12206. this.syncEnabled = enabled;
  12207. },
  12208.  
  12209. setAutoLoginVisible_ : function(visible) {
  12210. $('enable-auto-login-checkbox').hidden = !visible;
  12211. },
  12212.  
  12213. setSyncSetupCompleted_: function(completed) {
  12214. this.syncSetupCompleted = completed;
  12215. $('customize-sync').hidden = !completed;
  12216. },
  12217.  
  12218. setSyncStatus_: function(status) {
  12219. var statusSet = status != '';
  12220. $('sync-overview').hidden = statusSet;
  12221. $('sync-status').hidden = !statusSet;
  12222. $('sync-status-text').innerHTML = status;
  12223. },
  12224.  
  12225. setSyncStatusErrorVisible_: function(visible) {
  12226. visible ? $('sync-status').classList.add('sync-error') :
  12227. $('sync-status').classList.remove('sync-error');
  12228. },
  12229.  
  12230. setCustomizeSyncButtonEnabled_: function(enabled) {
  12231. $('customize-sync').disabled = !enabled;
  12232. },
  12233.  
  12234. setSyncActionLinkEnabled_: function(enabled) {
  12235. $('sync-action-link').disabled = !enabled;
  12236. },
  12237.  
  12238. setSyncActionLinkLabel_: function(status) {
  12239. $('sync-action-link').textContent = status;
  12240.  
  12241. // link-button does is not zero-area when the contents of the button are
  12242. // empty, so explicitly hide the element.
  12243. $('sync-action-link').hidden = !status.length;
  12244. },
  12245.  
  12246. /**
  12247. * Display or hide the profiles section of the page. This is used for
  12248. * multi-profile settings.
  12249. * @param {boolean} visible True to show the section.
  12250. * @private
  12251. */
  12252. setProfilesSectionVisible_: function(visible) {
  12253. $('profiles-section').hidden = !visible;
  12254. },
  12255.  
  12256. /**
  12257. * Get the selected profile item from the profile list. This also works
  12258. * correctly if the list is not displayed.
  12259. * @return {Object} the profile item object, or null if nothing is selected.
  12260. * @private
  12261. */
  12262. getSelectedProfileItem_: function() {
  12263. var profilesList = $('profiles-list');
  12264. if (profilesList.hidden) {
  12265. if (profilesList.dataModel.length > 0)
  12266. return profilesList.dataModel.item(0);
  12267. } else {
  12268. return profilesList.selectedItem;
  12269. }
  12270. return null;
  12271. },
  12272.  
  12273. /**
  12274. * Helper function to set the status of profile view buttons to disabled or
  12275. * enabled, depending on the number of profiles and selection status of the
  12276. * profiles list.
  12277. */
  12278. setProfileViewButtonsStatus_: function() {
  12279. var profilesList = $('profiles-list');
  12280. var selectedProfile = profilesList.selectedItem;
  12281. var hasSelection = selectedProfile != null;
  12282. var hasSingleProfile = profilesList.dataModel.length == 1;
  12283. $('profiles-manage').disabled = !hasSelection ||
  12284. !selectedProfile.isCurrentProfile;
  12285. $('profiles-delete').disabled = !hasSelection && !hasSingleProfile;
  12286. },
  12287.  
  12288. /**
  12289. * Display the correct dialog layout, depending on how many profiles are
  12290. * available.
  12291. * @param {number} numProfiles The number of profiles to display.
  12292. */
  12293. setProfileViewSingle_: function(numProfiles) {
  12294. var hasSingleProfile = numProfiles == 1;
  12295. $('profiles-list').hidden = hasSingleProfile;
  12296. $('profiles-single-message').hidden = !hasSingleProfile;
  12297. $('profiles-manage').hidden = hasSingleProfile;
  12298. $('profiles-delete').textContent = hasSingleProfile ?
  12299. templateData.profilesDeleteSingle :
  12300. templateData.profilesDelete;
  12301. },
  12302.  
  12303. /**
  12304. * Adds all |profiles| to the list.
  12305. * @param {Array.<Object>} An array of profile info objects.
  12306. * each object is of the form:
  12307. * profileInfo = {
  12308. * name: "Profile Name",
  12309. * iconURL: "chrome://path/to/icon/image",
  12310. * filePath: "/path/to/profile/data/on/disk",
  12311. * isCurrentProfile: false
  12312. * };
  12313. */
  12314. setProfilesInfo_: function(profiles) {
  12315. this.setProfileViewSingle_(profiles.length);
  12316. // add it to the list, even if the list is hidden so we can access it
  12317. // later.
  12318. $('profiles-list').dataModel = new ArrayDataModel(profiles);
  12319. this.setProfileViewButtonsStatus_();
  12320. },
  12321.  
  12322. setStartStopButtonVisible_: function(visible) {
  12323. $('start-stop-sync').hidden = !visible;
  12324. },
  12325.  
  12326. setStartStopButtonEnabled_: function(enabled) {
  12327. $('start-stop-sync').disabled = !enabled;
  12328. },
  12329.  
  12330. setStartStopButtonLabel_: function(label) {
  12331. $('start-stop-sync').textContent = label;
  12332. },
  12333.  
  12334. setGtkThemeButtonEnabled_: function(enabled) {
  12335. if (!cr.isChromeOS && navigator.platform.match(/linux|BSD/i)) {
  12336. $('themes-GTK-button').disabled = !enabled;
  12337. }
  12338. },
  12339.  
  12340. setThemesResetButtonEnabled_: function(enabled) {
  12341. $('themes-reset').disabled = !enabled;
  12342. },
  12343.  
  12344. hideSyncSection_: function() {
  12345. $('sync-section').hidden = true;
  12346. },
  12347.  
  12348. /**
  12349. * Get the start/stop sync button DOM element.
  12350. * @return {DOMElement} The start/stop sync button.
  12351. * @private
  12352. */
  12353. getStartStopSyncButton_: function() {
  12354. return $('start-stop-sync');
  12355. },
  12356.  
  12357. /**
  12358. * (Re)loads IMG element with current user account picture.
  12359. */
  12360. updateAccountPicture_: function() {
  12361. $('account-picture').src =
  12362. 'chrome://userimage/' + this.username_ +
  12363. '?id=' + (new Date()).getTime();
  12364. },
  12365. };
  12366.  
  12367. /**
  12368. * Returns whether the user should be able to manage (view and edit) their
  12369. * stored passwords. Password management is disabled in guest mode.
  12370. * @return {boolean} True if password management should be disabled.
  12371. */
  12372. PersonalOptions.disablePasswordManagement = function() {
  12373. return cr.commandLine.options['--bwsi'];
  12374. };
  12375.  
  12376. /**
  12377. * Returns whether the user should be able to manage autofill settings.
  12378. * @return {boolean} True if password management should be disabled.
  12379. */
  12380. PersonalOptions.disableAutofillManagement = function() {
  12381. return cr.commandLine.options['--bwsi'];
  12382. };
  12383.  
  12384. if (cr.isChromeOS) {
  12385. /**
  12386. * Returns username (canonical email) of the user logged in (ChromeOS only).
  12387. * @return {string} user email.
  12388. */
  12389. PersonalOptions.getLoggedInUsername = function() {
  12390. return PersonalOptions.getInstance().username_;
  12391. };
  12392. }
  12393.  
  12394. // Forward public APIs to private implementations.
  12395. [
  12396. 'getStartStopSyncButton',
  12397. 'hideSyncSection',
  12398. 'setAutoLoginVisible',
  12399. 'setCustomizeSyncButtonEnabled',
  12400. 'setGtkThemeButtonEnabled',
  12401. 'setProfilesInfo',
  12402. 'setProfilesSectionVisible',
  12403. 'setStartStopButtonEnabled',
  12404. 'setStartStopButtonLabel',
  12405. 'setStartStopButtonVisible',
  12406. 'setSyncActionLinkEnabled',
  12407. 'setSyncActionLinkLabel',
  12408. 'setSyncEnabled',
  12409. 'setSyncSetupCompleted',
  12410. 'setSyncStatus',
  12411. 'setSyncStatusErrorVisible',
  12412. 'setThemesResetButtonEnabled',
  12413. 'updateAccountPicture',
  12414. ].forEach(function(name) {
  12415. PersonalOptions[name] = function(value) {
  12416. return PersonalOptions.getInstance()[name + '_'](value);
  12417. };
  12418. });
  12419.  
  12420. // Export
  12421. return {
  12422. PersonalOptions: PersonalOptions
  12423. };
  12424.  
  12425. });
  12426.  
  12427. // Copyright (c) 2011 The Chromium Authors. All rights reserved.
  12428. // Use of this source code is governed by a BSD-style license that can be
  12429. // found in the LICENSE file.
  12430.  
  12431. cr.define('options.personal_options', function() {
  12432. const DeletableItem = options.DeletableItem;
  12433. const DeletableItemList = options.DeletableItemList;
  12434. const ListSingleSelectionModel = cr.ui.ListSingleSelectionModel;
  12435.  
  12436. var localStrings = new LocalStrings();
  12437.  
  12438. /**
  12439. * Creates a new profile list item.
  12440. * @param {Object} profileInfo The profile this item respresents.
  12441. * @constructor
  12442. * @extends {cr.ui.DeletableItem}
  12443. */
  12444. function ProfileListItem(profileInfo) {
  12445. var el = cr.doc.createElement('div');
  12446. el.profileInfo_ = profileInfo;
  12447. ProfileListItem.decorate(el);
  12448. return el;
  12449. }
  12450.  
  12451. /**
  12452. * Decorates an element as a profile list item.
  12453. * @param {!HTMLElement} el The element to decorate.
  12454. */
  12455. ProfileListItem.decorate = function(el) {
  12456. el.__proto__ = ProfileListItem.prototype;
  12457. el.decorate();
  12458. };
  12459.  
  12460. ProfileListItem.prototype = {
  12461. __proto__: DeletableItem.prototype,
  12462.  
  12463. /**
  12464. * Get the filepath for this profile list item.
  12465. * @return the file path of this item.
  12466. */
  12467. get profilePath() {
  12468. return this.profileInfo_.filePath;
  12469. },
  12470.  
  12471. /** @inheritDoc */
  12472. decorate: function() {
  12473. DeletableItem.prototype.decorate.call(this);
  12474.  
  12475. var profileInfo = this.profileInfo_;
  12476.  
  12477. var iconEl = this.ownerDocument.createElement('img');
  12478. iconEl.className = 'profile-img';
  12479. iconEl.src = profileInfo.iconURL;
  12480. this.contentElement.appendChild(iconEl);
  12481.  
  12482. var nameEl = this.ownerDocument.createElement('div');
  12483. if (profileInfo.isCurrentProfile)
  12484. nameEl.classList.add('profile-item-current');
  12485. this.contentElement.appendChild(nameEl);
  12486.  
  12487. var displayName = profileInfo.name;
  12488. if (profileInfo.isCurrentProfile)
  12489. displayName = localStrings.getStringF(
  12490. 'profilesListItemCurrent',
  12491. profileInfo.name)
  12492. nameEl.textContent = displayName;
  12493. },
  12494. };
  12495.  
  12496. var ProfileList = cr.ui.define('list');
  12497.  
  12498. ProfileList.prototype = {
  12499. __proto__: DeletableItemList.prototype,
  12500.  
  12501. /** @inheritDoc */
  12502. decorate: function() {
  12503. DeletableItemList.prototype.decorate.call(this);
  12504. this.selectionModel = new ListSingleSelectionModel();
  12505. },
  12506.  
  12507. /** @inheritDoc */
  12508. createItem: function(pageInfo) {
  12509. var item = new ProfileListItem(pageInfo);
  12510. return item;
  12511. },
  12512.  
  12513. /** @inheritDoc */
  12514. deleteItemAtIndex: function(index) {
  12515. ManageProfileOverlay.showDeleteDialog(this.dataModel.item(index));
  12516. },
  12517.  
  12518. /** @inheritDoc */
  12519. activateItemAtIndex: function(index) {
  12520. // Don't allow the user to edit a profile that is not current.
  12521. var profileInfo = this.dataModel.item(index);
  12522. if (profileInfo.isCurrentProfile)
  12523. ManageProfileOverlay.showManageDialog(profileInfo);
  12524. },
  12525. };
  12526.  
  12527. return {
  12528. ProfileList: ProfileList
  12529. };
  12530. });
  12531.  
  12532.  
  12533. // Copyright (c) 2011 The Chromium Authors. All rights reserved.
  12534. // Use of this source code is governed by a BSD-style license that can be
  12535. // found in the LICENSE file.
  12536.  
  12537. cr.define('options', function() {
  12538. const ListItem = cr.ui.ListItem;
  12539. const Grid = cr.ui.Grid;
  12540. const ListSingleSelectionModel = cr.ui.ListSingleSelectionModel;
  12541.  
  12542. /**
  12543. * Creates a new profile icon grid item.
  12544. * @param {Object} iconURL The profile icon URL.
  12545. * @constructor
  12546. * @extends {cr.ui.GridItem}
  12547. */
  12548. function ProfilesIconGridItem(iconURL) {
  12549. var el = cr.doc.createElement('span');
  12550. el.iconURL_ = iconURL;
  12551. ProfilesIconGridItem.decorate(el);
  12552. return el;
  12553. }
  12554.  
  12555. /**
  12556. * Decorates an element as a profile grid item.
  12557. * @param {!HTMLElement} el The element to decorate.
  12558. */
  12559. ProfilesIconGridItem.decorate = function(el) {
  12560. el.__proto__ = ProfilesIconGridItem.prototype;
  12561. el.decorate();
  12562. };
  12563.  
  12564. ProfilesIconGridItem.prototype = {
  12565. __proto__: ListItem.prototype,
  12566.  
  12567. /** @inheritDoc */
  12568. decorate: function() {
  12569. ListItem.prototype.decorate.call(this);
  12570. var imageEl = cr.doc.createElement('img');
  12571. imageEl.className = 'profile-icon';
  12572. imageEl.src = this.iconURL_;
  12573. this.appendChild(imageEl);
  12574.  
  12575. this.className = 'profile-icon-grid-item';
  12576. },
  12577. };
  12578.  
  12579. var ProfilesIconGrid = cr.ui.define('grid');
  12580.  
  12581. ProfilesIconGrid.prototype = {
  12582. __proto__: Grid.prototype,
  12583.  
  12584. /** @inheritDoc */
  12585. decorate: function() {
  12586. Grid.prototype.decorate.call(this);
  12587. this.selectionModel = new ListSingleSelectionModel();
  12588. },
  12589.  
  12590. /** @inheritDoc */
  12591. createItem: function(iconURL) {
  12592. return new ProfilesIconGridItem(iconURL);
  12593. },
  12594. };
  12595.  
  12596. return {
  12597. ProfilesIconGrid: ProfilesIconGrid
  12598. };
  12599. });
  12600.  
  12601.  
  12602. // Copyright (c) 2011 The Chromium Authors. All rights reserved.
  12603. // Use of this source code is governed by a BSD-style license that can be
  12604. // found in the LICENSE file.
  12605.  
  12606. cr.define('options', function() {
  12607. const OptionsPage = options.OptionsPage;
  12608. const ArrayDataModel = cr.ui.ArrayDataModel;
  12609.  
  12610. /**
  12611. * Encapsulated handling of search engine management page.
  12612. * @constructor
  12613. */
  12614. function SearchEngineManager() {
  12615. this.activeNavTab = null;
  12616. OptionsPage.call(this, 'searchEngines',
  12617. templateData.searchEngineManagerPageTabTitle,
  12618. 'search-engine-manager-page');
  12619. }
  12620.  
  12621. cr.addSingletonGetter(SearchEngineManager);
  12622.  
  12623. SearchEngineManager.prototype = {
  12624. __proto__: OptionsPage.prototype,
  12625.  
  12626. /**
  12627. * List for default search engine options.
  12628. * @private
  12629. */
  12630. defaultsList_: null,
  12631.  
  12632. /**
  12633. * List for other search engine options.
  12634. * @private
  12635. */
  12636. othersList_: null,
  12637.  
  12638. /**
  12639. * List for extension keywords.
  12640. * @private
  12641. extensionList_ : null,
  12642.  
  12643. /** inheritDoc */
  12644. initializePage: function() {
  12645. OptionsPage.prototype.initializePage.call(this);
  12646.  
  12647. this.defaultsList_ = $('default-search-engine-list');
  12648. this.setUpList_(this.defaultsList_);
  12649.  
  12650. this.othersList_ = $('other-search-engine-list');
  12651. this.setUpList_(this.othersList_);
  12652.  
  12653. this.extensionList_ = $('extension-keyword-list');
  12654. this.setUpList_(this.extensionList_);
  12655. },
  12656.  
  12657. /**
  12658. * Sets up the given list as a search engine list
  12659. * @param {List} list The list to set up.
  12660. * @private
  12661. */
  12662. setUpList_: function(list) {
  12663. options.search_engines.SearchEngineList.decorate(list);
  12664. list.autoExpands = true;
  12665. },
  12666.  
  12667. /**
  12668. * Updates the search engine list with the given entries.
  12669. * @private
  12670. * @param {Array} defaultEngines List of possible default search engines.
  12671. * @param {Array} otherEngines List of other search engines.
  12672. * @param {Array} keywords List of keywords from extensions.
  12673. */
  12674. updateSearchEngineList_: function(defaultEngines, otherEngines, keywords) {
  12675. this.defaultsList_.dataModel = new ArrayDataModel(defaultEngines);
  12676.  
  12677. otherEngines = otherEngines.map(function(x) {
  12678. return [x, x['name'].toLocaleLowerCase()];
  12679. }).sort(function(a,b){
  12680. return a[1].localeCompare(b[1]);
  12681. }).map(function(x){
  12682. return x[0];
  12683. });
  12684.  
  12685. var othersModel = new ArrayDataModel(otherEngines);
  12686. // Add a "new engine" row.
  12687. othersModel.push({
  12688. 'modelIndex': '-1',
  12689. 'canBeEdited': true
  12690. });
  12691. this.othersList_.dataModel = othersModel;
  12692.  
  12693. if (keywords.length > 0) {
  12694. $('extension-keyword-div').hidden = false;
  12695. var extensionsModel = new ArrayDataModel(keywords);
  12696. this.extensionList_.dataModel = extensionsModel;
  12697. } else {
  12698. $('extension-keyword-div').hidden = true;
  12699. }
  12700. },
  12701. };
  12702.  
  12703. SearchEngineManager.updateSearchEngineList = function(defaultEngines,
  12704. otherEngines,
  12705. keywords) {
  12706. SearchEngineManager.getInstance().updateSearchEngineList_(defaultEngines,
  12707. otherEngines,
  12708. keywords);
  12709. };
  12710.  
  12711. SearchEngineManager.validityCheckCallback = function(validity, modelIndex) {
  12712. // Forward to both lists; the one without a matching modelIndex will ignore
  12713. // it.
  12714. SearchEngineManager.getInstance().defaultsList_.validationComplete(
  12715. validity, modelIndex);
  12716. SearchEngineManager.getInstance().othersList_.validationComplete(
  12717. validity, modelIndex);
  12718. };
  12719.  
  12720. // Export
  12721. return {
  12722. SearchEngineManager: SearchEngineManager
  12723. };
  12724.  
  12725. });
  12726.  
  12727.  
  12728. // Copyright (c) 2011 The Chromium Authors. All rights reserved.
  12729. // Use of this source code is governed by a BSD-style license that can be
  12730. // found in the LICENSE file.
  12731.  
  12732. cr.define('options.search_engines', function() {
  12733. const InlineEditableItemList = options.InlineEditableItemList;
  12734. const InlineEditableItem = options.InlineEditableItem;
  12735. const ListSelectionController = cr.ui.ListSelectionController;
  12736.  
  12737. /**
  12738. * Creates a new search engine list item.
  12739. * @param {Object} searchEnigne The search engine this represents.
  12740. * @constructor
  12741. * @extends {cr.ui.ListItem}
  12742. */
  12743. function SearchEngineListItem(searchEngine) {
  12744. var el = cr.doc.createElement('div');
  12745. el.searchEngine_ = searchEngine;
  12746. SearchEngineListItem.decorate(el);
  12747. return el;
  12748. }
  12749.  
  12750. /**
  12751. * Decorates an element as a search engine list item.
  12752. * @param {!HTMLElement} el The element to decorate.
  12753. */
  12754. SearchEngineListItem.decorate = function(el) {
  12755. el.__proto__ = SearchEngineListItem.prototype;
  12756. el.decorate();
  12757. };
  12758.  
  12759. SearchEngineListItem.prototype = {
  12760. __proto__: InlineEditableItem.prototype,
  12761.  
  12762. /**
  12763. * Input field for editing the engine name.
  12764. * @type {HTMLElement}
  12765. * @private
  12766. */
  12767. nameField_: null,
  12768.  
  12769. /**
  12770. * Input field for editing the engine keyword.
  12771. * @type {HTMLElement}
  12772. * @private
  12773. */
  12774. keywordField_: null,
  12775.  
  12776. /**
  12777. * Input field for editing the engine url.
  12778. * @type {HTMLElement}
  12779. * @private
  12780. */
  12781. urlField_: null,
  12782.  
  12783. /**
  12784. * Whether or not an input validation request is currently outstanding.
  12785. * @type {boolean}
  12786. * @private
  12787. */
  12788. waitingForValidation_: false,
  12789.  
  12790. /**
  12791. * Whether or not the current set of input is known to be valid.
  12792. * @type {boolean}
  12793. * @private
  12794. */
  12795. currentlyValid_: false,
  12796.  
  12797. /** @inheritDoc */
  12798. decorate: function() {
  12799. InlineEditableItem.prototype.decorate.call(this);
  12800.  
  12801. var engine = this.searchEngine_;
  12802.  
  12803. if (engine['modelIndex'] == '-1') {
  12804. this.isPlaceholder = true;
  12805. engine['name'] = '';
  12806. engine['keyword'] = '';
  12807. engine['url'] = '';
  12808. }
  12809.  
  12810. this.currentlyValid_ = !this.isPlaceholder;
  12811.  
  12812. if (engine['default'])
  12813. this.classList.add('default');
  12814.  
  12815. this.deletable = engine['canBeRemoved'];
  12816.  
  12817. // Construct the name column.
  12818. var nameColEl = this.ownerDocument.createElement('div');
  12819. nameColEl.className = 'name-column';
  12820. nameColEl.classList.add('weakrtl');
  12821. this.contentElement.appendChild(nameColEl);
  12822.  
  12823. // Add the favicon.
  12824. var faviconDivEl = this.ownerDocument.createElement('div');
  12825. faviconDivEl.className = 'favicon';
  12826. var imgEl = this.ownerDocument.createElement('img');
  12827. imgEl.src = 'chrome://favicon/iconurl/' + engine['iconURL'];
  12828. faviconDivEl.appendChild(imgEl);
  12829. nameColEl.appendChild(faviconDivEl);
  12830.  
  12831. var nameEl = this.createEditableTextCell(engine['displayName']);
  12832. nameEl.classList.add('weakrtl');
  12833. nameColEl.appendChild(nameEl);
  12834.  
  12835. // Then the keyword column.
  12836. var keywordEl = this.createEditableTextCell(engine['keyword']);
  12837. keywordEl.className = 'keyword-column';
  12838. keywordEl.classList.add('weakrtl');
  12839. this.contentElement.appendChild(keywordEl);
  12840.  
  12841. // And the URL column.
  12842. var urlEl = this.createEditableTextCell(engine['url']);
  12843. var urlWithButtonEl = this.ownerDocument.createElement('div');
  12844. urlWithButtonEl.appendChild(urlEl);
  12845. urlWithButtonEl.className = 'url-column';
  12846. urlWithButtonEl.classList.add('weakrtl');
  12847. this.contentElement.appendChild(urlWithButtonEl);
  12848. // Add the Make Default button. Temporary until drag-and-drop re-ordering
  12849. // is implemented. When this is removed, remove the extra div above.
  12850. if (engine['canBeDefault']) {
  12851. var makeDefaultButtonEl = this.ownerDocument.createElement('button');
  12852. makeDefaultButtonEl.className = 'raw-button custom-appearance';
  12853. makeDefaultButtonEl.textContent =
  12854. templateData.makeDefaultSearchEngineButton;
  12855. makeDefaultButtonEl.onclick = function(e) {
  12856. chrome.send('managerSetDefaultSearchEngine', [engine['modelIndex']]);
  12857. };
  12858. // Don't select the row when clicking the button.
  12859. makeDefaultButtonEl.onmousedown = function(e) {
  12860. e.stopPropagation();
  12861. };
  12862. urlWithButtonEl.appendChild(makeDefaultButtonEl);
  12863. }
  12864.  
  12865. // Do final adjustment to the input fields.
  12866. this.nameField_ = nameEl.querySelector('input');
  12867. // The editable field uses the raw name, not the display name.
  12868. this.nameField_.value = engine['name'];
  12869. this.keywordField_ = keywordEl.querySelector('input');
  12870. this.urlField_ = urlEl.querySelector('input');
  12871.  
  12872. if (engine['urlLocked'])
  12873. this.urlField_.disabled = true;
  12874.  
  12875. if (this.isPlaceholder) {
  12876. this.nameField_.placeholder =
  12877. localStrings.getString('searchEngineTableNamePlaceholder');
  12878. this.keywordField_.placeholder =
  12879. localStrings.getString('searchEngineTableKeywordPlaceholder');
  12880. this.urlField_.placeholder =
  12881. localStrings.getString('searchEngineTableURLPlaceholder');
  12882. }
  12883.  
  12884. var fields = [ this.nameField_, this.keywordField_, this.urlField_ ];
  12885. for (var i = 0; i < fields.length; i++) {
  12886. fields[i].oninput = this.startFieldValidation_.bind(this);
  12887. }
  12888.  
  12889. // Listen for edit events.
  12890. if (engine['canBeEdited']) {
  12891. this.addEventListener('edit', this.onEditStarted_.bind(this));
  12892. this.addEventListener('canceledit', this.onEditCancelled_.bind(this));
  12893. this.addEventListener('commitedit', this.onEditCommitted_.bind(this));
  12894. } else {
  12895. this.editable = false;
  12896. }
  12897. },
  12898.  
  12899. /** @inheritDoc */
  12900. get currentInputIsValid() {
  12901. return !this.waitingForValidation_ && this.currentlyValid_;
  12902. },
  12903.  
  12904. /** @inheritDoc */
  12905. get hasBeenEdited() {
  12906. var engine = this.searchEngine_;
  12907. return this.nameField_.value != engine['name'] ||
  12908. this.keywordField_.value != engine['keyword'] ||
  12909. this.urlField_.value != engine['url'];
  12910. },
  12911.  
  12912. /**
  12913. * Called when entering edit mode; starts an edit session in the model.
  12914. * @param {Event} e The edit event.
  12915. * @private
  12916. */
  12917. onEditStarted_: function(e) {
  12918. var editIndex = this.searchEngine_['modelIndex'];
  12919. chrome.send('editSearchEngine', [String(editIndex)]);
  12920. this.startFieldValidation_();
  12921. },
  12922.  
  12923. /**
  12924. * Called when committing an edit; updates the model.
  12925. * @param {Event} e The end event.
  12926. * @private
  12927. */
  12928. onEditCommitted_: function(e) {
  12929. chrome.send('searchEngineEditCompleted', this.getInputFieldValues_());
  12930. },
  12931.  
  12932. /**
  12933. * Called when cancelling an edit; informs the model and resets the control
  12934. * states.
  12935. * @param {Event} e The cancel event.
  12936. * @private
  12937. */
  12938. onEditCancelled_: function() {
  12939. chrome.send('searchEngineEditCancelled');
  12940.  
  12941. // The name field has been automatically set to match the display name,
  12942. // but it should use the raw name instead.
  12943. this.nameField_.value = this.searchEngine_['name'];
  12944. this.currentlyValid_ = !this.isPlaceholder;
  12945. },
  12946.  
  12947. /**
  12948. * Returns the input field values as an array suitable for passing to
  12949. * chrome.send. The order of the array is important.
  12950. * @private
  12951. * @return {array} The current input field values.
  12952. */
  12953. getInputFieldValues_: function() {
  12954. return [ this.nameField_.value,
  12955. this.keywordField_.value,
  12956. this.urlField_.value ];
  12957. },
  12958.  
  12959. /**
  12960. * Begins the process of asynchronously validing the input fields.
  12961. * @private
  12962. */
  12963. startFieldValidation_: function() {
  12964. this.waitingForValidation_ = true;
  12965. var args = this.getInputFieldValues_();
  12966. args.push(this.searchEngine_['modelIndex']);
  12967. chrome.send('checkSearchEngineInfoValidity', args);
  12968. },
  12969.  
  12970. /**
  12971. * Callback for the completion of an input validition check.
  12972. * @param {Object} validity A dictionary of validitation results.
  12973. */
  12974. validationComplete: function(validity) {
  12975. this.waitingForValidation_ = false;
  12976. // TODO(stuartmorgan): Implement the full validation UI with
  12977. // checkmark/exclamation mark icons and tooltips showing the errors.
  12978. if (validity['name']) {
  12979. this.nameField_.setCustomValidity('');
  12980. } else {
  12981. this.nameField_.setCustomValidity(
  12982. templateData.editSearchEngineInvalidTitleToolTip);
  12983. }
  12984.  
  12985. if (validity['keyword']) {
  12986. this.keywordField_.setCustomValidity('');
  12987. } else {
  12988. this.keywordField_.setCustomValidity(
  12989. templateData.editSearchEngineInvalidKeywordToolTip);
  12990. }
  12991.  
  12992. if (validity['url']) {
  12993. this.urlField_.setCustomValidity('');
  12994. } else {
  12995. this.urlField_.setCustomValidity(
  12996. templateData.editSearchEngineInvalidURLToolTip);
  12997. }
  12998.  
  12999. this.currentlyValid_ = validity['name'] && validity['keyword'] &&
  13000. validity['url'];
  13001. },
  13002. };
  13003.  
  13004. var SearchEngineList = cr.ui.define('list');
  13005.  
  13006. SearchEngineList.prototype = {
  13007. __proto__: InlineEditableItemList.prototype,
  13008.  
  13009. /** @inheritDoc */
  13010. createItem: function(searchEngine) {
  13011. return new SearchEngineListItem(searchEngine);
  13012. },
  13013.  
  13014. /** @inheritDoc */
  13015. deleteItemAtIndex: function(index) {
  13016. var modelIndex = this.dataModel.item(index)['modelIndex']
  13017. chrome.send('removeSearchEngine', [String(modelIndex)]);
  13018. },
  13019.  
  13020. /**
  13021. * Passes the results of an input validation check to the requesting row
  13022. * if it's still being edited.
  13023. * @param {number} modelIndex The model index of the item that was checked.
  13024. * @param {Object} validity A dictionary of validitation results.
  13025. */
  13026. validationComplete: function(validity, modelIndex) {
  13027. // If it's not still being edited, it no longer matters.
  13028. var currentSelection = this.selectedItem;
  13029. if (!currentSelection)
  13030. return;
  13031. var listItem = this.getListItem(currentSelection);
  13032. if (listItem.editing && currentSelection['modelIndex'] == modelIndex)
  13033. listItem.validationComplete(validity);
  13034. },
  13035. };
  13036.  
  13037. // Export
  13038. return {
  13039. SearchEngineList: SearchEngineList
  13040. };
  13041.  
  13042. });
  13043.  
  13044.  
  13045. // Copyright (c) 2011 The Chromium Authors. All rights reserved.
  13046. // Use of this source code is governed by a BSD-style license that can be
  13047. // found in the LICENSE file.
  13048.  
  13049. cr.define('options', function() {
  13050. const OptionsPage = options.OptionsPage;
  13051.  
  13052. /**
  13053. * Encapsulated handling of a search bubble.
  13054. * @constructor
  13055. */
  13056. function SearchBubble(text) {
  13057. var el = cr.doc.createElement('div');
  13058. SearchBubble.decorate(el);
  13059. el.textContent = text;
  13060. return el;
  13061. }
  13062.  
  13063. SearchBubble.decorate = function(el) {
  13064. el.__proto__ = SearchBubble.prototype;
  13065. el.decorate();
  13066. };
  13067.  
  13068. SearchBubble.prototype = {
  13069. __proto__: HTMLDivElement.prototype,
  13070.  
  13071. decorate: function() {
  13072. this.className = 'search-bubble';
  13073.  
  13074. // We create a timer to periodically update the position of the bubbles.
  13075. // While this isn't all that desirable, it's the only sure-fire way of
  13076. // making sure the bubbles stay in the correct location as sections
  13077. // may dynamically change size at any time.
  13078. var self = this;
  13079. this.intervalId = setInterval(this.updatePosition.bind(this), 250);
  13080. },
  13081.  
  13082. /**
  13083. * Attach the bubble to the element.
  13084. */
  13085. attachTo: function(element) {
  13086. var parent = element.parentElement;
  13087. if (!parent)
  13088. return;
  13089. if (parent.tagName == 'TD') {
  13090. // To make absolute positioning work inside a table cell we need
  13091. // to wrap the bubble div into another div with position:relative.
  13092. // This only works properly if the element is the first child of the
  13093. // table cell which is true for all options pages.
  13094. this.wrapper = cr.doc.createElement('div');
  13095. this.wrapper.className = 'search-bubble-wrapper';
  13096. this.wrapper.appendChild(this);
  13097. parent.insertBefore(this.wrapper, element);
  13098. } else {
  13099. parent.insertBefore(this, element);
  13100. }
  13101. },
  13102.  
  13103. /**
  13104. * Clear the interval timer and remove the element from the page.
  13105. */
  13106. dispose: function() {
  13107. clearInterval(this.intervalId);
  13108.  
  13109. var child = this.wrapper || this;
  13110. var parent = child.parentNode;
  13111. if (parent)
  13112. parent.removeChild(child);
  13113. },
  13114.  
  13115. /**
  13116. * Update the position of the bubble. Called at creation time and then
  13117. * periodically while the bubble remains visible.
  13118. */
  13119. updatePosition: function() {
  13120. // This bubble is 'owned' by the next sibling.
  13121. var owner = (this.wrapper || this).nextSibling;
  13122.  
  13123. // If there isn't an offset parent, we have nothing to do.
  13124. if (!owner.offsetParent)
  13125. return;
  13126.  
  13127. // Position the bubble below the location of the owner.
  13128. var left = owner.offsetLeft + owner.offsetWidth / 2 -
  13129. this.offsetWidth / 2;
  13130. var top = owner.offsetTop + owner.offsetHeight;
  13131.  
  13132. // Update the position in the CSS. Cache the last values for
  13133. // best performance.
  13134. if (left != this.lastLeft) {
  13135. this.style.left = left + 'px';
  13136. this.lastLeft = left;
  13137. }
  13138. if (top != this.lastTop) {
  13139. this.style.top = top + 'px';
  13140. this.lastTop = top;
  13141. }
  13142. }
  13143. }
  13144.  
  13145. /**
  13146. * Encapsulated handling of the search page.
  13147. * @constructor
  13148. */
  13149. function SearchPage() {
  13150. OptionsPage.call(this, 'search', templateData.searchPageTabTitle,
  13151. 'searchPage');
  13152. }
  13153.  
  13154. cr.addSingletonGetter(SearchPage);
  13155.  
  13156. SearchPage.prototype = {
  13157. // Inherit SearchPage from OptionsPage.
  13158. __proto__: OptionsPage.prototype,
  13159.  
  13160. /**
  13161. * A boolean to prevent recursion. Used by setSearchText_().
  13162. * @type {Boolean}
  13163. * @private
  13164. */
  13165. insideSetSearchText_: false,
  13166.  
  13167. /**
  13168. * Initialize the page.
  13169. */
  13170. initializePage: function() {
  13171. // Call base class implementation to start preference initialization.
  13172. OptionsPage.prototype.initializePage.call(this);
  13173.  
  13174. var self = this;
  13175.  
  13176. // Create a search field element.
  13177. var searchField = document.createElement('input');
  13178. searchField.id = 'search-field';
  13179. searchField.type = 'search';
  13180. searchField.incremental = true;
  13181. searchField.placeholder = localStrings.getString('searchPlaceholder');
  13182. searchField.setAttribute('aria-label', searchField.placeholder);
  13183. this.searchField = searchField;
  13184.  
  13185. // Replace the contents of the navigation tab with the search field.
  13186. self.tab.textContent = '';
  13187. self.tab.appendChild(searchField);
  13188. self.tab.onclick = self.tab.onkeydown = self.tab.onkeypress = undefined;
  13189. self.tab.tabIndex = -1;
  13190. self.tab.setAttribute('role', '');
  13191.  
  13192. // Don't allow the focus on the search navbar. http://crbug.com/77989
  13193. self.tab.onfocus = self.tab.blur;
  13194.  
  13195. // Handle search events. (No need to throttle, WebKit's search field
  13196. // will do that automatically.)
  13197. searchField.onsearch = function(e) {
  13198. self.setSearchText_(this.value);
  13199. };
  13200.  
  13201. // We update the history stack every time the search field blurs. This way
  13202. // we get a history entry for each search, roughly, but not each letter
  13203. // typed.
  13204. searchField.onblur = function(e) {
  13205. var query = SearchPage.canonicalizeQuery(searchField.value);
  13206. if (!query)
  13207. return;
  13208.  
  13209. // Don't push the same page onto the history stack more than once (if
  13210. // the user clicks in the search field and away several times).
  13211. var currentHash = location.hash;
  13212. var newHash = '#' + escape(query);
  13213. if (currentHash == newHash)
  13214. return;
  13215.  
  13216. // If there is no hash on the current URL, the history entry has no
  13217. // search query. Replace the history entry with no search with an entry
  13218. // that does have a search. Otherwise, add it onto the history stack.
  13219. var historyFunction = currentHash ? window.history.pushState :
  13220. window.history.replaceState;
  13221. historyFunction.call(
  13222. window.history,
  13223. {pageName: self.name},
  13224. self.title,
  13225. '/' + self.name + newHash);
  13226. };
  13227.  
  13228. // Install handler for key presses.
  13229. document.addEventListener('keydown',
  13230. this.keyDownEventHandler_.bind(this));
  13231.  
  13232. // Focus the search field by default.
  13233. searchField.focus();
  13234. },
  13235.  
  13236. /**
  13237. * @inheritDoc
  13238. */
  13239. get sticky() {
  13240. return true;
  13241. },
  13242.  
  13243. /**
  13244. * Called after this page has shown.
  13245. */
  13246. didShowPage: function() {
  13247. // This method is called by the Options page after all pages have
  13248. // had their visibilty attribute set. At this point we can perform the
  13249. // search specific DOM manipulation.
  13250. this.setSearchActive_(true);
  13251. },
  13252.  
  13253. /**
  13254. * Called before this page will be hidden.
  13255. */
  13256. willHidePage: function() {
  13257. // This method is called by the Options page before all pages have
  13258. // their visibilty attribute set. Before that happens, we need to
  13259. // undo the search specific DOM manipulation that was performed in
  13260. // didShowPage.
  13261. this.setSearchActive_(false);
  13262. },
  13263.  
  13264. /**
  13265. * Update the UI to reflect whether we are in a search state.
  13266. * @param {boolean} active True if we are on the search page.
  13267. * @private
  13268. */
  13269. setSearchActive_: function(active) {
  13270. // It's fine to exit if search wasn't active and we're not going to
  13271. // activate it now.
  13272. if (!this.searchActive_ && !active)
  13273. return;
  13274.  
  13275. this.searchActive_ = active;
  13276.  
  13277. if (active) {
  13278. var hash = location.hash;
  13279. if (hash)
  13280. this.searchField.value = unescape(hash.slice(1));
  13281. } else {
  13282. // Just wipe out any active search text since it's no longer relevant.
  13283. this.searchField.value = '';
  13284. }
  13285.  
  13286. var pagesToSearch = this.getSearchablePages_();
  13287. for (var key in pagesToSearch) {
  13288. var page = pagesToSearch[key];
  13289.  
  13290. if (!active)
  13291. page.visible = false;
  13292.  
  13293. // Update the visible state of all top-level elements that are not
  13294. // sections (ie titles, button strips). We do this before changing
  13295. // the page visibility to avoid excessive re-draw.
  13296. for (var i = 0, childDiv; childDiv = page.pageDiv.children[i]; i++) {
  13297. if (childDiv.classList.contains('displaytable')) {
  13298. childDiv.setAttribute('searching', active ? 'true' : 'false');
  13299. for (var j = 0, subDiv; subDiv = childDiv.children[j]; j++) {
  13300. if (active) {
  13301. if (subDiv.tagName != 'SECTION')
  13302. subDiv.classList.add('search-hidden');
  13303. } else {
  13304. subDiv.classList.remove('search-hidden');
  13305. }
  13306. }
  13307. } else {
  13308. if (active)
  13309. childDiv.classList.add('search-hidden');
  13310. else
  13311. childDiv.classList.remove('search-hidden');
  13312. }
  13313. }
  13314.  
  13315. if (active) {
  13316. // When search is active, remove the 'hidden' tag. This tag may have
  13317. // been added by the OptionsPage.
  13318. page.pageDiv.hidden = false;
  13319. }
  13320. }
  13321.  
  13322. if (active) {
  13323. this.setSearchText_(this.searchField.value);
  13324. } else {
  13325. // After hiding all page content, remove any search results.
  13326. this.unhighlightMatches_();
  13327. this.removeSearchBubbles_();
  13328. }
  13329. },
  13330.  
  13331. /**
  13332. * Set the current search criteria.
  13333. * @param {string} text Search text.
  13334. * @private
  13335. */
  13336. setSearchText_: function(text) {
  13337. // Prevent recursive execution of this method.
  13338. if (this.insideSetSearchText_) return;
  13339. this.insideSetSearchText_ = true;
  13340.  
  13341. // Cleanup the search query string.
  13342. text = SearchPage.canonicalizeQuery(text);
  13343.  
  13344. // Notify listeners about the new search query, some pages may wish to
  13345. // show/hide elements based on the query.
  13346. var event = new cr.Event('searchChanged');
  13347. event.searchText = text;
  13348. this.dispatchEvent(event);
  13349.  
  13350. // Toggle the search page if necessary.
  13351. if (text.length) {
  13352. if (!this.searchActive_)
  13353. OptionsPage.navigateToPage(this.name);
  13354. } else {
  13355. if (this.searchActive_)
  13356. OptionsPage.showDefaultPage();
  13357.  
  13358. this.insideSetSearchText_ = false;
  13359. return;
  13360. }
  13361.  
  13362. var foundMatches = false;
  13363. var bubbleControls = [];
  13364.  
  13365. // Remove any prior search results.
  13366. this.unhighlightMatches_();
  13367. this.removeSearchBubbles_();
  13368.  
  13369. // Generate search text by applying lowercase and escaping any characters
  13370. // that would be problematic for regular expressions.
  13371. var searchText =
  13372. text.toLowerCase().replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
  13373.  
  13374. // Generate a regular expression and replace string for hilighting
  13375. // search terms.
  13376. var regEx = new RegExp('(' + searchText + ')', 'ig');
  13377. var replaceString = '<span class="search-highlighted">$1</span>';
  13378.  
  13379. // Initialize all sections. If the search string matches a title page,
  13380. // show sections for that page.
  13381. var page, pageMatch, childDiv, length;
  13382. var pagesToSearch = this.getSearchablePages_();
  13383. for (var key in pagesToSearch) {
  13384. page = pagesToSearch[key];
  13385. pageMatch = false;
  13386. if (searchText.length) {
  13387. pageMatch = this.performReplace_(regEx, replaceString, page.tab);
  13388. }
  13389. if (pageMatch)
  13390. foundMatches = true;
  13391. var elements = page.pageDiv.querySelectorAll('.displaytable > section');
  13392. for (var i = 0, node; node = elements[i]; i++) {
  13393. if (pageMatch)
  13394. node.classList.remove('search-hidden');
  13395. else
  13396. node.classList.add('search-hidden');
  13397. }
  13398. }
  13399.  
  13400. if (searchText.length) {
  13401. // Search all top-level sections for anchored string matches.
  13402. for (var key in pagesToSearch) {
  13403. page = pagesToSearch[key];
  13404. var elements =
  13405. page.pageDiv.querySelectorAll('.displaytable > section');
  13406. for (var i = 0, node; node = elements[i]; i++) {
  13407. if (this.performReplace_(regEx, replaceString, node)) {
  13408. node.classList.remove('search-hidden');
  13409. foundMatches = true;
  13410. }
  13411. }
  13412. }
  13413.  
  13414. // Search all sub-pages, generating an array of top-level sections that
  13415. // we need to make visible.
  13416. var subPagesToSearch = this.getSearchableSubPages_();
  13417. var control, node;
  13418. for (var key in subPagesToSearch) {
  13419. page = subPagesToSearch[key];
  13420. if (this.performReplace_(regEx, replaceString, page.pageDiv)) {
  13421. // Reveal the section for this search result.
  13422. section = page.associatedSection;
  13423. if (section)
  13424. section.classList.remove('search-hidden');
  13425.  
  13426. // Identify any controls that should have bubbles.
  13427. var controls = page.associatedControls;
  13428. if (controls) {
  13429. length = controls.length;
  13430. for (var i = 0; i < length; i++)
  13431. bubbleControls.push(controls[i]);
  13432. }
  13433.  
  13434. foundMatches = true;
  13435. }
  13436. }
  13437. }
  13438.  
  13439. // Configure elements on the search results page based on search results.
  13440. if (foundMatches)
  13441. $('searchPageNoMatches').classList.add('search-hidden');
  13442. else
  13443. $('searchPageNoMatches').classList.remove('search-hidden');
  13444.  
  13445. // Create search balloons for sub-page results.
  13446. length = bubbleControls.length;
  13447. for (var i = 0; i < length; i++)
  13448. this.createSearchBubble_(bubbleControls[i], text);
  13449.  
  13450. // Cleanup the recursion-prevention variable.
  13451. this.insideSetSearchText_ = false;
  13452. },
  13453.  
  13454. /**
  13455. * Performs a string replacement based on a regex and replace string.
  13456. * @param {RegEx} regex A regular expression for finding search matches.
  13457. * @param {String} replace A string to apply the replace operation.
  13458. * @param {Element} element An HTML container element.
  13459. * @returns {Boolean} true if the element was changed.
  13460. * @private
  13461. */
  13462. performReplace_: function(regex, replace, element) {
  13463. var found = false;
  13464. var div, child, tmp;
  13465.  
  13466. // Walk the tree, searching each TEXT node.
  13467. var walker = document.createTreeWalker(element,
  13468. NodeFilter.SHOW_TEXT,
  13469. null,
  13470. false);
  13471. var node = walker.nextNode();
  13472. while (node) {
  13473. // Perform a search and replace on the text node value.
  13474. var newValue = node.nodeValue.replace(regex, replace);
  13475. if (newValue != node.nodeValue) {
  13476. // The text node has changed so that means we found at least one
  13477. // match.
  13478. found = true;
  13479.  
  13480. // Create a temporary div element and set the innerHTML to the new
  13481. // value.
  13482. div = document.createElement('div');
  13483. div.innerHTML = newValue;
  13484.  
  13485. // Insert all the child nodes of the temporary div element into the
  13486. // document, before the original node.
  13487. child = div.firstChild;
  13488. while (child = div.firstChild) {
  13489. node.parentNode.insertBefore(child, node);
  13490. };
  13491.  
  13492. // Delete the old text node and advance the walker to the next
  13493. // node.
  13494. tmp = node;
  13495. node = walker.nextNode();
  13496. tmp.parentNode.removeChild(tmp);
  13497. } else {
  13498. node = walker.nextNode();
  13499. }
  13500. }
  13501.  
  13502. return found;
  13503. },
  13504.  
  13505. /**
  13506. * Removes all search highlight tags from the document.
  13507. * @private
  13508. */
  13509. unhighlightMatches_: function() {
  13510. // Find all search highlight elements.
  13511. var elements = document.querySelectorAll('.search-highlighted');
  13512.  
  13513. // For each element, remove the highlighting.
  13514. var parent, i;
  13515. for (var i = 0, node; node = elements[i]; i++) {
  13516. parent = node.parentNode;
  13517.  
  13518. // Replace the highlight element with the first child (the text node).
  13519. parent.replaceChild(node.firstChild, node);
  13520.  
  13521. // Normalize the parent so that multiple text nodes will be combined.
  13522. parent.normalize();
  13523. }
  13524. },
  13525.  
  13526. /**
  13527. * Creates a search result bubble attached to an element.
  13528. * @param {Element} element An HTML element, usually a button.
  13529. * @param {string} text A string to show in the bubble.
  13530. * @private
  13531. */
  13532. createSearchBubble_: function(element, text) {
  13533. // avoid appending multiple bubbles to a button.
  13534. var sibling = element.previousElementSibling;
  13535. if (sibling && (sibling.classList.contains('search-bubble') ||
  13536. sibling.classList.contains('search-bubble-wrapper')))
  13537. return;
  13538.  
  13539. var parent = element.parentElement;
  13540. if (parent) {
  13541. var bubble = new SearchBubble(text);
  13542. bubble.attachTo(element);
  13543. bubble.updatePosition();
  13544. }
  13545. },
  13546.  
  13547. /**
  13548. * Removes all search match bubbles.
  13549. * @private
  13550. */
  13551. removeSearchBubbles_: function() {
  13552. var elements = document.querySelectorAll('.search-bubble');
  13553. var length = elements.length;
  13554. for (var i = 0; i < length; i++)
  13555. elements[i].dispose();
  13556. },
  13557.  
  13558. /**
  13559. * Builds a list of top-level pages to search. Omits the search page and
  13560. * all sub-pages.
  13561. * @returns {Array} An array of pages to search.
  13562. * @private
  13563. */
  13564. getSearchablePages_: function() {
  13565. var name, page, pages = [];
  13566. for (name in OptionsPage.registeredPages) {
  13567. if (name != this.name) {
  13568. page = OptionsPage.registeredPages[name];
  13569. if (!page.parentPage)
  13570. pages.push(page);
  13571. }
  13572. }
  13573. return pages;
  13574. },
  13575.  
  13576. /**
  13577. * Builds a list of sub-pages (and overlay pages) to search. Ignore pages
  13578. * that have no associated controls.
  13579. * @returns {Array} An array of pages to search.
  13580. * @private
  13581. */
  13582. getSearchableSubPages_: function() {
  13583. var name, pageInfo, page, pages = [];
  13584. for (name in OptionsPage.registeredPages) {
  13585. page = OptionsPage.registeredPages[name];
  13586. if (page.parentPage && page.associatedSection)
  13587. pages.push(page);
  13588. }
  13589. for (name in OptionsPage.registeredOverlayPages) {
  13590. page = OptionsPage.registeredOverlayPages[name];
  13591. if (page.associatedSection && page.pageDiv != undefined)
  13592. pages.push(page);
  13593. }
  13594. return pages;
  13595. },
  13596.  
  13597. /**
  13598. * A function to handle key press events.
  13599. * @return {Event} a keydown event.
  13600. * @private
  13601. */
  13602. keyDownEventHandler_: function(event) {
  13603. const ESCAPE_KEY_CODE = 27;
  13604. const FORWARD_SLASH_KEY_CODE = 191;
  13605.  
  13606. switch(event.keyCode) {
  13607. case ESCAPE_KEY_CODE:
  13608. if (event.target == this.searchField) {
  13609. this.setSearchText_('');
  13610. this.searchField.blur();
  13611. event.stopPropagation();
  13612. event.preventDefault();
  13613. }
  13614. break;
  13615. case FORWARD_SLASH_KEY_CODE:
  13616. if (!/INPUT|SELECT|BUTTON|TEXTAREA/.test(event.target.tagName) &&
  13617. !event.ctrlKey && !event.altKey) {
  13618. this.searchField.focus();
  13619. event.stopPropagation();
  13620. event.preventDefault();
  13621. }
  13622. break;
  13623. }
  13624. },
  13625. };
  13626.  
  13627. /**
  13628. * Standardizes a user-entered text query by removing extra whitespace.
  13629. * @param {string} The user-entered text.
  13630. * @return {string} The trimmed query.
  13631. */
  13632. SearchPage.canonicalizeQuery = function(text) {
  13633. // Trim beginning and ending whitespace.
  13634. return text.replace(/^\s+|\s+$/g, '');
  13635. };
  13636.  
  13637. // Export
  13638. return {
  13639. SearchPage: SearchPage
  13640. };
  13641.  
  13642. });
  13643.  
  13644. // Copyright (c) 2011 The Chromium Authors. All rights reserved.
  13645. // Use of this source code is governed by a BSD-style license that can be
  13646. // found in the LICENSE file.
  13647.  
  13648. cr.define('options', function() {
  13649. const OptionsPage = options.OptionsPage;
  13650.  
  13651. // Variable to track if a captcha challenge was issued. If this gets set to
  13652. // true, it stays that way until we are told about successful login from
  13653. // the browser. This means subsequent errors (like invalid password) are
  13654. // rendered in the captcha state, which is basically identical except we
  13655. // don't show the top error blurb "Error Signing in" or the "Create
  13656. // account" link.
  13657. var captchaChallengeActive_ = false;
  13658.  
  13659. // True if the synced account uses a custom passphrase.
  13660. var usePassphrase_ = false;
  13661.  
  13662. // True if the synced account uses 'encrypt everything'.
  13663. var useEncryptEverything_ = false;
  13664.  
  13665. /**
  13666. * SyncSetupOverlay class
  13667. * Encapsulated handling of the 'Sync Setup' overlay page.
  13668. * @class
  13669. */
  13670. function SyncSetupOverlay() {
  13671. OptionsPage.call(this, 'syncSetup',
  13672. templateData.syncSetupOverlayTitle,
  13673. 'sync-setup-overlay');
  13674. }
  13675.  
  13676. cr.addSingletonGetter(SyncSetupOverlay);
  13677.  
  13678. SyncSetupOverlay.prototype = {
  13679. __proto__: OptionsPage.prototype,
  13680.  
  13681. /**
  13682. * Initializes the page.
  13683. */
  13684. initializePage: function() {
  13685. OptionsPage.prototype.initializePage.call(this);
  13686.  
  13687. var self = this;
  13688. $('gaia-login-form').onsubmit = function() {
  13689. self.sendCredentialsAndClose_();
  13690. return false;
  13691. };
  13692. $('google-option').onchange = $('explicit-option').onchange = function() {
  13693. self.onPassphraseRadioChanged_();
  13694. };
  13695. $('choose-datatypes-cancel').onclick =
  13696. $('sync-setup-cancel').onclick =
  13697. $('confirm-everything-cancel').onclick =
  13698. $('stop-syncing-cancel').onclick = function() {
  13699. self.closeOverlay_();
  13700. };
  13701. $('confirm-everything-ok').onclick = function() {
  13702. self.sendConfiguration_();
  13703. };
  13704. $('stop-syncing-ok').onclick = function() {
  13705. chrome.send('stopSyncing');
  13706. self.closeOverlay_();
  13707. };
  13708. },
  13709.  
  13710. showOverlay_: function() {
  13711. OptionsPage.navigateToPage('syncSetup');
  13712. },
  13713.  
  13714. closeOverlay_: function() {
  13715. OptionsPage.closeOverlay();
  13716. },
  13717.  
  13718. /** @inheritDoc */
  13719. didShowPage: function() {
  13720. chrome.send('SyncSetupAttachHandler');
  13721. },
  13722.  
  13723. /** @inheritDoc */
  13724. didClosePage: function() {
  13725. chrome.send('SyncSetupDidClosePage');
  13726. },
  13727.  
  13728. getEncryptionRadioCheckedValue_: function() {
  13729. var f = $('choose-data-types-form');
  13730. for (var i = 0; i < f.encrypt.length; ++i) {
  13731. if (f.encrypt[i].checked) {
  13732. return f.encrypt[i].value;
  13733. }
  13734. }
  13735.  
  13736. return undefined;
  13737. },
  13738.  
  13739. getPassphraseRadioCheckedValue_: function() {
  13740. var f = $('choose-data-types-form');
  13741. for (var i = 0; i < f.option.length; ++i) {
  13742. if (f.option[i].checked) {
  13743. return f.option[i].value;
  13744. }
  13745. }
  13746.  
  13747. return undefined;
  13748. },
  13749.  
  13750. disableEncryptionRadioGroup_: function() {
  13751. var f = $('choose-data-types-form');
  13752. for (var i = 0; i < f.encrypt.length; ++i)
  13753. f.encrypt[i].disabled = true;
  13754. },
  13755.  
  13756. onPassphraseRadioChanged_: function() {
  13757. var visible = this.getPassphraseRadioCheckedValue_() == "explicit";
  13758. $('sync-custom-passphrase').hidden = !visible;
  13759. },
  13760.  
  13761. checkAllDataTypeCheckboxes_: function() {
  13762. var checkboxes = document.getElementsByName("dataTypeCheckbox");
  13763. for (var i = 0; i < checkboxes.length; i++) {
  13764. // Only check the visible ones (since there's no way to uncheck
  13765. // the invisible ones).
  13766. if (checkboxes[i].parentElement.className == "sync-item-show") {
  13767. checkboxes[i].checked = true;
  13768. }
  13769. }
  13770. },
  13771.  
  13772. setDataTypeCheckboxesEnabled_: function(enabled) {
  13773. var checkboxes = document.getElementsByName("dataTypeCheckbox");
  13774. var labels = document.getElementsByName("dataTypeLabel");
  13775. for (var i = 0; i < checkboxes.length; i++) {
  13776. checkboxes[i].disabled = !enabled;
  13777. if (checkboxes[i].disabled) {
  13778. labels[i].className = "sync-label-inactive";
  13779. } else {
  13780. labels[i].className = "sync-label-active";
  13781. }
  13782. }
  13783. },
  13784.  
  13785. setCheckboxesToKeepEverythingSynced_: function(value) {
  13786. this.setDataTypeCheckboxesEnabled_(!value);
  13787. if (value)
  13788. this.checkAllDataTypeCheckboxes_();
  13789. },
  13790.  
  13791. // Returns true if at least one data type is enabled and no data types are
  13792. // checked. (If all data type checkboxes are disabled, it's because "keep
  13793. // everything synced" is checked.)
  13794. noDataTypesChecked_: function() {
  13795. var checkboxes = document.getElementsByName("dataTypeCheckbox");
  13796. var atLeastOneChecked = false;
  13797. var atLeastOneEnabled = false;
  13798. for (var i = 0; i < checkboxes.length; i++) {
  13799. if (!checkboxes[i].disabled &&
  13800. checkboxes[i].parentElement.className == "sync-item-show") {
  13801. atLeastOneEnabled = true;
  13802. if (checkboxes[i].checked) {
  13803. atLeastOneChecked = true;
  13804. }
  13805. }
  13806. }
  13807.  
  13808. return atLeastOneEnabled && !atLeastOneChecked;
  13809. },
  13810.  
  13811. checkPassphraseMatch_: function() {
  13812. var emptyError = $('empty-error');
  13813. var mismatchError = $('mismatch-error');
  13814. emptyError.hidden = true;
  13815. mismatchError.hidden = true;
  13816.  
  13817. var f = $('choose-data-types-form');
  13818. if (this.getPassphraseRadioCheckedValue_() != "explicit" ||
  13819. $('google-option').disabled)
  13820. return true;
  13821.  
  13822. var customPassphrase = $('custom-passphrase');
  13823. if (customPassphrase.value.length == 0) {
  13824. emptyError.hidden = false;
  13825. return false;
  13826. }
  13827.  
  13828. var confirmPassphrase = $('confirm-passphrase');
  13829. if (confirmPassphrase.value != customPassphrase.value) {
  13830. mismatchError.hidden = false;
  13831. return false;
  13832. }
  13833.  
  13834. return true;
  13835. },
  13836.  
  13837. sendConfiguration_: function() {
  13838. // Trying to submit, so hide previous errors.
  13839. $('error-text').hidden = true;
  13840.  
  13841. if (this.noDataTypesChecked_()) {
  13842. $('error-text').hidden = false;
  13843. return;
  13844. }
  13845.  
  13846. var f = $('choose-data-types-form');
  13847.  
  13848. var syncAll = $('sync-select-datatypes').selectedIndex == 0;
  13849. var encryptAllData = this.getEncryptionRadioCheckedValue_() == 'all';
  13850.  
  13851. var usePassphrase;
  13852. var customPassphrase;
  13853. var googlePassphrase = false;
  13854. if (!$('sync-existing-passphrase-container').hidden) {
  13855. // If we were prompted for an existing passphrase, use it.
  13856. customPassphrase = f.passphrase.value;
  13857. usePassphrase = true;
  13858. // If we were displaying the "enter your old google password" prompt,
  13859. // then that means this is the user's google password.
  13860. googlePassphrase = !$('google-passphrase-needed-body').hidden;
  13861. // We allow an empty passphrase, in case the user has disabled
  13862. // all their encrypted datatypes. In that case, the PSS will accept
  13863. // the passphrase and finish configuration. If the user has enabled
  13864. // encrypted datatypes, the PSS will prompt again specifying that the
  13865. // passphrase failed.
  13866. } else if (!$('google-option').disabled &&
  13867. this.getPassphraseRadioCheckedValue_() == 'explicit') {
  13868. // The user is setting a custom passphrase for the first time.
  13869. if (!this.checkPassphraseMatch_())
  13870. return;
  13871. customPassphrase = $('custom-passphrase').value;
  13872. usePassphrase = true;
  13873. } else {
  13874. // The user is not setting a custom passphrase.
  13875. usePassphrase = false;
  13876. }
  13877.  
  13878. // Don't allow the user to tweak the settings once we send the
  13879. // configuration to the backend.
  13880. this.setInputElementsDisabledState_(true);
  13881. this.animateDisableLink_($('use-default-link'), true, null);
  13882.  
  13883. // These values need to be kept in sync with where they are read in
  13884. // SyncSetupFlow::GetDataTypeChoiceData().
  13885. var result = JSON.stringify({
  13886. "syncAllDataTypes": syncAll,
  13887. "syncBookmarks": syncAll || $('bookmarks-checkbox').checked,
  13888. "syncPreferences": syncAll || $('preferences-checkbox').checked,
  13889. "syncThemes": syncAll || $('themes-checkbox').checked,
  13890. "syncPasswords": syncAll || $('passwords-checkbox').checked,
  13891. "syncAutofill": syncAll || $('autofill-checkbox').checked,
  13892. "syncExtensions": syncAll || $('extensions-checkbox').checked,
  13893. "syncTypedUrls": syncAll || $('typed-urls-checkbox').checked,
  13894. "syncApps": syncAll || $('apps-checkbox').checked,
  13895. "syncSessions": syncAll || $('sessions-checkbox').checked,
  13896. "encryptAllData": encryptAllData,
  13897. "usePassphrase": usePassphrase,
  13898. "isGooglePassphrase": googlePassphrase,
  13899. "passphrase": customPassphrase
  13900. });
  13901. chrome.send('SyncSetupConfigure', [result]);
  13902. },
  13903.  
  13904. /**
  13905. * Sets the disabled property of all input elements within the 'Customize
  13906. * Sync Preferences' screen. This is used to prohibit the user from changing
  13907. * the inputs after confirming the customized sync preferences, or resetting
  13908. * the state when re-showing the dialog.
  13909. * @param disabled True if controls should be set to disabled.
  13910. * @private
  13911. */
  13912. setInputElementsDisabledState_: function(disabled) {
  13913. var configureElements =
  13914. $('customize-sync-preferences').querySelectorAll('input');
  13915. for (var i = 0; i < configureElements.length; i++)
  13916. configureElements[i].disabled = disabled;
  13917. $('sync-select-datatypes').disabled = disabled;
  13918.  
  13919. var self = this;
  13920. this.animateDisableLink_($('customize-link'), disabled, function() {
  13921. self.showCustomizePage_(null, true);
  13922. });
  13923. },
  13924.  
  13925. /**
  13926. * Animate a link being enabled/disabled. The link is hidden by animating
  13927. * its opacity, but to ensure the user doesn't click it during that time,
  13928. * its onclick handler is changed to null as well.
  13929. * @param elt The anchor element to enable/disable.
  13930. * @param disabled True if the link should be disabled.
  13931. * @param enabledFunction The onclick handler when the link is enabled.
  13932. * @private
  13933. */
  13934. animateDisableLink_: function(elt, disabled, enabledFunction) {
  13935. if (disabled) {
  13936. elt.classList.add('transparent');
  13937. elt.onclick = null;
  13938. elt.addEventListener('webkitTransitionEnd', function f(e) {
  13939. if (e.propertyName != 'opacity')
  13940. return;
  13941. elt.removeEventListener('webkitTransitionEnd', f);
  13942. elt.classList.remove('transparent');
  13943. elt.hidden = true;
  13944. });
  13945. } else {
  13946. elt.hidden = false;
  13947. elt.onclick = enabledFunction;
  13948. }
  13949. },
  13950.  
  13951. setChooseDataTypesCheckboxes_: function(args) {
  13952. var datatypeSelect = document.getElementById('sync-select-datatypes');
  13953. datatypeSelect.selectedIndex = args.syncAllDataTypes ? 0 : 1;
  13954.  
  13955. $('bookmarks-checkbox').checked = args.syncBookmarks;
  13956. $('preferences-checkbox').checked = args.syncPreferences;
  13957. $('themes-checkbox').checked = args.syncThemes;
  13958.  
  13959. if (args.passwordsRegistered) {
  13960. $('passwords-checkbox').checked = args.syncPasswords;
  13961. $('passwords-item').className = "sync-item-show";
  13962. } else {
  13963. $('passwords-item').className = "sync-item-hide";
  13964. }
  13965. if (args.autofillRegistered) {
  13966. $('autofill-checkbox').checked = args.syncAutofill;
  13967. $('autofill-item').className = "sync-item-show";
  13968. } else {
  13969. $('autofill-item').className = "sync-item-hide";
  13970. }
  13971. if (args.extensionsRegistered) {
  13972. $('extensions-checkbox').checked = args.syncExtensions;
  13973. $('extensions-item').className = "sync-item-show";
  13974. } else {
  13975. $('extensions-item').className = "sync-item-hide";
  13976. }
  13977. if (args.typedUrlsRegistered) {
  13978. $('typed-urls-checkbox').checked = args.syncTypedUrls;
  13979. $('omnibox-item').className = "sync-item-show";
  13980. } else {
  13981. $('omnibox-item').className = "sync-item-hide";
  13982. }
  13983. if (args.appsRegistered) {
  13984. $('apps-checkbox').checked = args.syncApps;
  13985. $('apps-item').className = "sync-item-show";
  13986. } else {
  13987. $('apps-item').className = "sync-item-hide";
  13988. }
  13989. if (args.sessionsRegistered) {
  13990. $('sessions-checkbox').checked = args.syncSessions;
  13991. $('sessions-item').className = "sync-item-show";
  13992. } else {
  13993. $('sessions-item').className = "sync-item-hide";
  13994. }
  13995.  
  13996. this.setCheckboxesToKeepEverythingSynced_(args.syncAllDataTypes);
  13997. },
  13998.  
  13999. setEncryptionRadios_: function(args) {
  14000. if (args['encryptAllData']) {
  14001. $('encrypt-all-option').checked = true;
  14002. this.disableEncryptionRadioGroup_();
  14003. } else {
  14004. $('encrypt-sensitive-option').checked = true;
  14005. }
  14006. },
  14007.  
  14008. setPassphraseRadios_: function(args) {
  14009. if (args['usePassphrase']) {
  14010. $('explicit-option').checked = true;
  14011.  
  14012. // The passphrase, once set, cannot be unset, but we show a reset link.
  14013. $('explicit-option').disabled = true;
  14014. $('google-option').disabled = true;
  14015. $('sync-custom-passphrase').hidden = true;
  14016. } else {
  14017. $('google-option').checked = true;
  14018. }
  14019. },
  14020.  
  14021. setCheckboxesAndErrors_: function(args) {
  14022. this.setChooseDataTypesCheckboxes_(args);
  14023. this.setEncryptionRadios_(args);
  14024. this.setPassphraseRadios_(args);
  14025. },
  14026.  
  14027. showConfigure_: function(args) {
  14028. var datatypeSelect = document.getElementById('sync-select-datatypes');
  14029. var self = this;
  14030. datatypeSelect.onchange = function() {
  14031. var syncAll = this.selectedIndex == 0;
  14032. self.setCheckboxesToKeepEverythingSynced_(syncAll);
  14033. };
  14034.  
  14035. this.resetPage_('sync-setup-configure');
  14036. $('sync-setup-configure').hidden = false;
  14037.  
  14038. // onsubmit is changed when submitting a passphrase. Reset it to its
  14039. // default.
  14040. $('choose-data-types-form').onsubmit = function() {
  14041. self.sendConfiguration_();
  14042. return false;
  14043. };
  14044.  
  14045. if (args) {
  14046. if (!args['encryptionEnabled'])
  14047. $('customize-sync-encryption').hidden = true;
  14048. this.setCheckboxesAndErrors_(args);
  14049.  
  14050. this.useEncryptEverything_ = args['encryptAllData'];
  14051.  
  14052. // Whether to display the 'Sync everything' confirmation page or the
  14053. // customize data types page.
  14054. var syncAllDataTypes = args['syncAllDataTypes'];
  14055. this.usePassphrase_ = args['usePassphrase'];
  14056. if (args['showSyncEverythingPage'] == false || this.usePassphrase_ ||
  14057. syncAllDataTypes == false || args['show_passphrase']) {
  14058. this.showCustomizePage_(args, syncAllDataTypes);
  14059. } else {
  14060. this.showSyncEverythingPage_();
  14061. }
  14062. }
  14063. },
  14064.  
  14065. showSyncEverythingPage_: function() {
  14066. $('confirm-sync-preferences').hidden = false;
  14067. $('customize-sync-preferences').hidden = true;
  14068.  
  14069. // Reset the selection to 'Sync everything'.
  14070. $('sync-select-datatypes').selectedIndex = 0;
  14071.  
  14072. // The default state is to sync everything.
  14073. this.setCheckboxesToKeepEverythingSynced_(true);
  14074.  
  14075. // Encrypt passwords is the default, but don't set it if the previously
  14076. // synced account is already set to encrypt everything.
  14077. if (!this.useEncryptEverything_)
  14078. $('encrypt-sensitive-option').checked = true;
  14079.  
  14080. // If the account is not synced with a custom passphrase, reset the
  14081. // passphrase radio when switching to the 'Sync everything' page.
  14082. if (!this.usePassphrase_) {
  14083. $('google-option').checked = true;
  14084. $('sync-custom-passphrase').hidden = true;
  14085. }
  14086.  
  14087. $('confirm-everything-ok').focus();
  14088. },
  14089.  
  14090. /**
  14091. * Reveals the UI for entering a custom passphrase during initial setup.
  14092. * This happens if the user has previously enabled a custom passphrase on a
  14093. * different machine.
  14094. * @param {Array} args The args that contain the passphrase UI
  14095. * configuration.
  14096. * @private
  14097. */
  14098. showPassphraseContainer_: function(args) {
  14099. // Once we require a passphrase, we prevent the user from returning to
  14100. // the Sync Everything pane.
  14101. $('use-default-link').hidden = true;
  14102. $('sync-custom-passphrase-container').hidden = true;
  14103. $('sync-existing-passphrase-container').hidden = false;
  14104.  
  14105. $('passphrase-rejected-body').hidden = true;
  14106. $('normal-body').hidden = true;
  14107. $('google-passphrase-needed-body').hidden = true;
  14108. // Display the correct prompt to the user depending on what type of
  14109. // passphrase is needed.
  14110. if (args["need_google_passphrase"])
  14111. $('google-passphrase-needed-body').hidden = false;
  14112. else if (args["passphrase_creation_rejected"])
  14113. $('passphrase-rejected-body').hidden = false;
  14114. else
  14115. $('normal-body').hidden = false;
  14116.  
  14117. $('incorrect-passphrase').hidden = !args["passphrase_setting_rejected"];
  14118.  
  14119. $('sync-passphrase-warning').hidden = false;
  14120.  
  14121. $('passphrase').focus();
  14122. },
  14123.  
  14124. showCustomizePage_: function(args, syncEverything) {
  14125. $('confirm-sync-preferences').hidden = true;
  14126. $('customize-sync-preferences').hidden = false;
  14127.  
  14128. $('sync-custom-passphrase-container').hidden = false;
  14129. $('sync-existing-passphrase-container').hidden = true;
  14130.  
  14131. // If the user has selected the 'Customize' page on initial set up, it's
  14132. // likely he intends to change the data types. Select the
  14133. // 'Choose data types' option in this case.
  14134. var index = syncEverything ? 0 : 1;
  14135. document.getElementById('sync-select-datatypes').selectedIndex = index;
  14136. this.setDataTypeCheckboxesEnabled_(!syncEverything);
  14137.  
  14138. // The passphrase input may need to take over focus from the OK button, so
  14139. // set focus before that logic.
  14140. $('choose-datatypes-ok').focus();
  14141.  
  14142. if (args && args['show_passphrase']) {
  14143. this.showPassphraseContainer_(args);
  14144. } else {
  14145. // We only show the "Use Default" link if we're not prompting for an
  14146. // existing passphrase.
  14147. var self = this;
  14148. this.animateDisableLink_($('use-default-link'), false, function() {
  14149. self.showSyncEverythingPage_();
  14150. });
  14151. }
  14152. },
  14153.  
  14154. attach_: function() {
  14155. chrome.send('SyncSetupAttachHandler');
  14156. },
  14157.  
  14158. showSyncSetupPage_: function(page, args) {
  14159. if (page == 'settingUp') {
  14160. this.setThrobbersVisible_(true);
  14161. return;
  14162. } else {
  14163. this.setThrobbersVisible_(false);
  14164. }
  14165.  
  14166. // Hide an existing visible overlay.
  14167. var overlay = $('sync-setup-overlay');
  14168. for (var i = 0; i < overlay.children.length; i++)
  14169. overlay.children[i].hidden = true;
  14170.  
  14171. this.setInputElementsDisabledState_(false);
  14172.  
  14173. if (page == 'login')
  14174. this.showGaiaLogin_(args);
  14175. else if (page == 'configure' || page == 'passphrase')
  14176. this.showConfigure_(args);
  14177.  
  14178. if (page == 'done')
  14179. this.closeOverlay_();
  14180. else
  14181. this.showOverlay_();
  14182. },
  14183.  
  14184. /**
  14185. * Changes the visibility of throbbers on this page.
  14186. * @param {boolean} visible Whether or not to set all throbber nodes
  14187. * visible.
  14188. */
  14189. setThrobbersVisible_: function(visible) {
  14190. var throbbers = document.getElementsByClassName("throbber");
  14191. for (var i = 0; i < throbbers.length; i++)
  14192. throbbers[i].style.visibility = visible ? "visible" : "hidden";
  14193. },
  14194.  
  14195. loginSetFocus_: function() {
  14196. var email = $('gaia-email');
  14197. var passwd = $('gaia-passwd');
  14198. if (email && (email.value == null || email.value == "")) {
  14199. email.focus();
  14200. } else if (passwd) {
  14201. passwd.focus();
  14202. }
  14203. },
  14204.  
  14205. /**
  14206. * Get the login email text input DOM element.
  14207. * @return {DOMElement} The login email text input.
  14208. * @private
  14209. */
  14210. getLoginEmail_: function() {
  14211. return $('gaia-email');
  14212. },
  14213.  
  14214. /**
  14215. * Get the login password text input DOM element.
  14216. * @return {DOMElement} The login password text input.
  14217. * @private
  14218. */
  14219. getLoginPasswd_: function() {
  14220. return $('gaia-passwd');
  14221. },
  14222.  
  14223. /**
  14224. * Get the sign in button DOM element.
  14225. * @return {DOMElement} The sign in button.
  14226. * @private
  14227. */
  14228. getSignInButton_: function() {
  14229. return $('sign-in');
  14230. },
  14231.  
  14232. showAccessCodeRequired_: function() {
  14233. $('password-row').hidden = true;
  14234. $('email-row').hidden = true;
  14235.  
  14236. $('access-code-input-row').hidden = false;
  14237. $('access-code').disabled = false;
  14238. $('access-code').focus();
  14239. },
  14240.  
  14241. showCaptcha_: function(args) {
  14242. this.captchaChallengeActive_ = true;
  14243.  
  14244. // The captcha takes up lots of space, so make room.
  14245. $('top-blurb-error').hidden = true;
  14246. $('create-account-div').hidden = true;
  14247.  
  14248. // It's showtime for the captcha now.
  14249. $('captcha-div').hidden = false;
  14250. $('gaia-email').disabled = true;
  14251. $('gaia-passwd').disabled = false;
  14252. $('captcha-value').disabled = false;
  14253. $('captcha-wrapper').style.backgroundImage = url(args.captchaUrl);
  14254. },
  14255.  
  14256. /**
  14257. * Reset the state of all descendant elements of a root element to their
  14258. * initial state.
  14259. * The initial state is specified by adding a class to the descendant
  14260. * element in sync_setup_overlay.html.
  14261. * @param pageElementId The root page element id.
  14262. * @private
  14263. */
  14264. resetPage_: function(pageElementId) {
  14265. var page = $(pageElementId);
  14266. var forEach = function(arr, fn) {
  14267. var length = arr.length;
  14268. for (var i = 0; i < length; i++) {
  14269. fn(arr[i]);
  14270. }
  14271. };
  14272.  
  14273. forEach(page.getElementsByClassName('reset-hidden'),
  14274. function(elt) { elt.hidden = true; });
  14275. forEach(page.getElementsByClassName('reset-shown'),
  14276. function(elt) { elt.hidden = false; });
  14277. forEach(page.getElementsByClassName('reset-disabled'),
  14278. function(elt) { elt.disabled = true; });
  14279. forEach(page.getElementsByClassName('reset-enabled'),
  14280. function(elt) { elt.disabled = false; });
  14281. forEach(page.getElementsByClassName('reset-value'),
  14282. function(elt) { elt.value = ''; });
  14283. forEach(page.getElementsByClassName('reset-opaque'),
  14284. function(elt) { elt.classList.remove('transparent'); });
  14285. },
  14286.  
  14287. showGaiaLogin_: function(args) {
  14288. this.resetPage_('sync-setup-login');
  14289. $('sync-setup-login').hidden = false;
  14290.  
  14291. var f = $('gaia-login-form');
  14292. var email = $('gaia-email');
  14293. var passwd = $('gaia-passwd');
  14294. if (f) {
  14295. if (args.user != undefined) {
  14296. if (email.value != args.user)
  14297. passwd.value = ""; // Reset the password field
  14298. email.value = args.user;
  14299. }
  14300.  
  14301. if (!args.editable_user) {
  14302. email.hidden = true;
  14303. var span = $('email-readonly');
  14304. span.textContent = email.value;
  14305. span.hidden = false;
  14306. $('create-account-div').hidden = true;
  14307. }
  14308.  
  14309. f.accessCode.disabled = true;
  14310. }
  14311.  
  14312. if (1 == args.error) {
  14313. var access_code = document.getElementById('access-code');
  14314. if (access_code.value && access_code.value != "") {
  14315. $('errormsg-0-access-code').hidden = false;
  14316. this.showAccessCodeRequired_();
  14317. } else {
  14318. $('errormsg-1-password').hidden = false;
  14319. }
  14320. this.setBlurbError_(args.error_message);
  14321. } else if (3 == args.error) {
  14322. $('errormsg-0-connection').hidden = false;
  14323. this.setBlurbError_(args.error_message);
  14324. } else if (4 == args.error) {
  14325. this.showCaptcha_(args);
  14326. } else if (7 == args.error) {
  14327. this.setBlurbError_(localStrings.getString('serviceUnavailableError'));
  14328. } else if (8 == args.error) {
  14329. this.showAccessCodeRequired_();
  14330. } else if (args.error_message) {
  14331. this.setBlurbError_(args.error_message);
  14332. }
  14333.  
  14334. if (args.fatalError) {
  14335. $('errormsg-fatal').hidden = false;
  14336. $('sign-in').disabled = true;
  14337. return;
  14338. }
  14339.  
  14340. $('sign-in').disabled = false;
  14341. $('sign-in').value = templateData['signin'];
  14342. this.loginSetFocus_();
  14343. },
  14344.  
  14345. resetErrorVisibility_: function() {
  14346. $("errormsg-0-email").hidden = true;
  14347. $("errormsg-0-password").hidden = true;
  14348. $("errormsg-1-password").hidden = true;
  14349. $("errormsg-0-connection").hidden = true;
  14350. $("errormsg-0-access-code").hidden = true;
  14351. },
  14352.  
  14353. setBlurbError_: function(error_message) {
  14354. if (this.captchaChallengeActive_)
  14355. return; // No blurb in captcha challenge mode.
  14356.  
  14357. if (error_message) {
  14358. $('error-signing-in').hidden = true;
  14359. $('error-custom').hidden = false;
  14360. $('error-custom').textContent = error_message;
  14361. } else {
  14362. $('error-signing-in').hidden = false;
  14363. $('error-custom').hidden = true;
  14364. }
  14365.  
  14366. $('top-blurb-error').hidden = false;
  14367. $('gaia-email').disabled = false;
  14368. $('gaia-passwd').disabled = false;
  14369. },
  14370.  
  14371. matchesASPRegex_: function(toMatch) {
  14372. var noSpaces = /[a-z]{16}/;
  14373. var withSpaces = /([a-z]{4}\s){3}[a-z]{4}/;
  14374. if (toMatch.match(noSpaces) || toMatch.match(withSpaces))
  14375. return true;
  14376. return false;
  14377. },
  14378.  
  14379. setErrorVisibility_: function() {
  14380. this.resetErrorVisibility_();
  14381. var f = $('gaia-login-form');
  14382. var email = $('gaia-email');
  14383. var passwd = $('gaia-passwd');
  14384. if (null == email.value || "" == email.value) {
  14385. $('errormsg-0-email').hidden = false;
  14386. this.setBlurbError_();
  14387. return false;
  14388. }
  14389. // Don't enforce password being non-blank when checking access code (it
  14390. // will have been cleared when the page was displayed).
  14391. if (f.accessCode.disabled && (null == passwd.value ||
  14392. "" == passwd.value)) {
  14393. $('errormsg-0-password').hidden = false;
  14394. this.setBlurbError_();
  14395. return false;
  14396. }
  14397. if (!f.accessCode.disabled && (null == f.accessCode.value ||
  14398. "" == f.accessCode.value)) {
  14399. $('errormsg-0-password').hidden = false;
  14400. return false;
  14401. }
  14402.  
  14403. if (f.accessCode.disabled && this.matchesASPRegex_(passwd.value) &&
  14404. $('asp-warning-div').hidden) {
  14405. $('asp-warning-div').hidden = false;
  14406. $('gaia-passwd').value = "";
  14407. return false;
  14408. }
  14409.  
  14410. return true;
  14411. },
  14412.  
  14413. sendCredentialsAndClose_: function() {
  14414. if (!this.setErrorVisibility_()) {
  14415. return false;
  14416. }
  14417.  
  14418. $('gaia-email').disabled = true;
  14419. $('gaia-passwd').disabled = true;
  14420. $('captcha-value').disabled = true;
  14421. $('access-code').disabled = true;
  14422.  
  14423. this.setThrobbersVisible_(true);
  14424.  
  14425. var f = $('gaia-login-form');
  14426. var email = $('gaia-email');
  14427. var passwd = $('gaia-passwd');
  14428. var result = JSON.stringify({"user" : email.value,
  14429. "pass" : passwd.value,
  14430. "captcha" : f.captchaValue.value,
  14431. "access_code" : f.accessCode.value});
  14432. $('sign-in').disabled = true;
  14433. chrome.send('SyncSetupSubmitAuth', [result]);
  14434. },
  14435.  
  14436. showSuccessAndClose_: function() {
  14437. $('sign-in').value = localStrings.getString('loginSuccess');
  14438. setTimeout(this.closeOverlay_, 1600);
  14439. },
  14440.  
  14441. showSuccessAndSettingUp_: function() {
  14442. $('sign-in').value = localStrings.getString('settingUp');
  14443. $('top-blurb-error').hidden = true;
  14444. },
  14445.  
  14446. /**
  14447. * Displays the stop syncing dialog.
  14448. * @private
  14449. */
  14450. showStopSyncingUI_: function() {
  14451. // Hide any visible children of the overlay.
  14452. var overlay = $('sync-setup-overlay');
  14453. for (var i = 0; i < overlay.children.length; i++)
  14454. overlay.children[i].hidden = true;
  14455.  
  14456. // Bypass OptionsPage.navigateToPage because it will call didShowPage
  14457. // which will set its own visible page, based on the flow state.
  14458. this.visible = true;
  14459.  
  14460. $('sync-setup-stop-syncing').hidden = false;
  14461. $('stop-syncing-cancel').focus();
  14462. },
  14463.  
  14464. /**
  14465. * Steps into the appropriate Sync Setup error UI.
  14466. * @private
  14467. */
  14468. showErrorUI_: function() {
  14469. chrome.send('SyncSetupShowErrorUI');
  14470. },
  14471.  
  14472. /**
  14473. * Determines the appropriate page to show in the Sync Setup UI based on
  14474. * the state of the Sync backend.
  14475. * @private
  14476. */
  14477. showSetupUI_: function() {
  14478. chrome.send('SyncSetupShowSetupUI');
  14479. },
  14480.  
  14481. /**
  14482. * Hides the outer elements of the login UI. This is used by the sync promo
  14483. * to customize the look of the login box.
  14484. */
  14485. hideOuterLoginUI_: function() {
  14486. $('sync-setup-overlay-title').hidden = true;
  14487. $('sync-setup-cancel').hidden = true;
  14488. }
  14489. };
  14490.  
  14491. // These get methods should only be called by the WebUI tests.
  14492. SyncSetupOverlay.getLoginEmail = function() {
  14493. return SyncSetupOverlay.getInstance().getLoginEmail_();
  14494. };
  14495.  
  14496. SyncSetupOverlay.getLoginPasswd = function() {
  14497. return SyncSetupOverlay.getInstance().getLoginPasswd_();
  14498. };
  14499.  
  14500. SyncSetupOverlay.getSignInButton = function() {
  14501. return SyncSetupOverlay.getInstance().getSignInButton_();
  14502. };
  14503.  
  14504. // These methods are for general consumption.
  14505. SyncSetupOverlay.showErrorUI = function() {
  14506. SyncSetupOverlay.getInstance().showErrorUI_();
  14507. };
  14508.  
  14509. SyncSetupOverlay.showSetupUI = function() {
  14510. SyncSetupOverlay.getInstance().showSetupUI_();
  14511. };
  14512.  
  14513. SyncSetupOverlay.showSyncSetupPage = function(page, args) {
  14514. SyncSetupOverlay.getInstance().showSyncSetupPage_(page, args);
  14515. };
  14516.  
  14517. SyncSetupOverlay.showSuccessAndClose = function() {
  14518. SyncSetupOverlay.getInstance().showSuccessAndClose_();
  14519. };
  14520.  
  14521. SyncSetupOverlay.showSuccessAndSettingUp = function() {
  14522. SyncSetupOverlay.getInstance().showSuccessAndSettingUp_();
  14523. };
  14524.  
  14525. SyncSetupOverlay.showStopSyncingUI = function() {
  14526. SyncSetupOverlay.getInstance().showStopSyncingUI_();
  14527. };
  14528.  
  14529. // Export
  14530. return {
  14531. SyncSetupOverlay: SyncSetupOverlay
  14532. };
  14533. });
  14534.  
  14535. // Copyright (c) 2011 The Chromium Authors. All rights reserved.
  14536. // Use of this source code is governed by a BSD-style license that can be
  14537. // found in the LICENSE file.
  14538.  
  14539. var AddLanguageOverlay = options.AddLanguageOverlay;
  14540. var AdvancedOptions = options.AdvancedOptions;
  14541. var AlertOverlay = options.AlertOverlay;
  14542. var AutofillEditAddressOverlay = options.AutofillEditAddressOverlay;
  14543. var AutofillEditCreditCardOverlay = options.AutofillEditCreditCardOverlay;
  14544. var AutofillOptions = options.AutofillOptions;
  14545. var BrowserOptions = options.BrowserOptions;
  14546. var ClearBrowserDataOverlay = options.ClearBrowserDataOverlay;
  14547. var ContentSettings = options.ContentSettings;
  14548. var ContentSettingsExceptionsArea =
  14549. options.contentSettings.ContentSettingsExceptionsArea;
  14550. var CookiesView = options.CookiesView;
  14551. var ExtensionSettings = options.ExtensionSettings;
  14552. var FontSettings = options.FontSettings;
  14553. var HandlerOptions = options.HandlerOptions;
  14554. var ImportDataOverlay = options.ImportDataOverlay;
  14555. var IntentsView = options.IntentsView;
  14556. var InstantConfirmOverlay = options.InstantConfirmOverlay;
  14557. var LanguageOptions = options.LanguageOptions;
  14558. var OptionsPage = options.OptionsPage;
  14559. var PackExtensionOverlay = options.PackExtensionOverlay;
  14560. var PasswordManager = options.PasswordManager;
  14561. var PersonalOptions = options.PersonalOptions;
  14562. var Preferences = options.Preferences;
  14563. var ManageProfileOverlay = options.ManageProfileOverlay;
  14564. var ProxyOptions = options.ProxyOptions;
  14565. var SearchEngineManager = options.SearchEngineManager;
  14566. var SearchPage = options.SearchPage;
  14567. var SyncSetupOverlay = options.SyncSetupOverlay;
  14568. var VirtualKeyboardManager = options.VirtualKeyboardManager;
  14569.  
  14570. /**
  14571. * DOMContentLoaded handler, sets up the page.
  14572. */
  14573. function load() {
  14574. // Decorate the existing elements in the document.
  14575. cr.ui.decorate('input[pref][type=checkbox]', options.PrefCheckbox);
  14576. cr.ui.decorate('input[pref][type=number]', options.PrefNumber);
  14577. cr.ui.decorate('input[pref][type=radio]', options.PrefRadio);
  14578. cr.ui.decorate('input[pref][type=range]', options.PrefRange);
  14579. cr.ui.decorate('select[pref]', options.PrefSelect);
  14580. cr.ui.decorate('input[pref][type=text]', options.PrefTextField);
  14581. cr.ui.decorate('input[pref][type=url]', options.PrefTextField);
  14582. cr.ui.decorate('button[pref]', options.PrefButton);
  14583. cr.ui.decorate('#content-settings-page input[type=radio]:not(.handler-radio)',
  14584. options.ContentSettingsRadio);
  14585. cr.ui.decorate('#content-settings-page input[type=radio].handler-radio',
  14586. options.HandlersEnabledRadio);
  14587. cr.ui.decorate('span.controlled-setting-indicator',
  14588. options.ControlledSettingIndicator);
  14589.  
  14590. var menuOffPattern = /(^\?|&)menu=off($|&)/;
  14591. var menuDisabled = menuOffPattern.test(window.location.search);
  14592. // document.documentElement.setAttribute('hide-menu', menuDisabled);
  14593. // We can't use an attribute on the html element because of webkit bug
  14594. // 12519. Instead, we add a class.
  14595. if (menuDisabled)
  14596. document.documentElement.classList.add('hide-menu');
  14597.  
  14598. localStrings = new LocalStrings();
  14599.  
  14600. OptionsPage.register(SearchPage.getInstance());
  14601.  
  14602. OptionsPage.register(BrowserOptions.getInstance());
  14603. OptionsPage.registerSubPage(SearchEngineManager.getInstance(),
  14604. BrowserOptions.getInstance(),
  14605. [$('defaultSearchManageEnginesButton')]);
  14606. OptionsPage.register(PersonalOptions.getInstance());
  14607. OptionsPage.registerSubPage(AutofillOptions.getInstance(),
  14608. PersonalOptions.getInstance(),
  14609. [$('autofill-settings')]);
  14610. OptionsPage.registerSubPage(PasswordManager.getInstance(),
  14611. PersonalOptions.getInstance(),
  14612. [$('manage-passwords')]);
  14613. if (cr.isChromeOS) {
  14614. OptionsPage.register(SystemOptions.getInstance());
  14615. OptionsPage.registerSubPage(AboutPage.getInstance(),
  14616. SystemOptions.getInstance());
  14617. OptionsPage.registerSubPage(LanguageOptions.getInstance(),
  14618. SystemOptions.getInstance(),
  14619. [$('language-button')]);
  14620. OptionsPage.registerSubPage(
  14621. new OptionsPage('languageChewing',
  14622. templateData.languageChewingPageTabTitle,
  14623. 'languageChewingPage'),
  14624. LanguageOptions.getInstance());
  14625. OptionsPage.registerSubPage(
  14626. new OptionsPage('languageHangul',
  14627. templateData.languageHangulPageTabTitle,
  14628. 'languageHangulPage'),
  14629. LanguageOptions.getInstance());
  14630. OptionsPage.registerSubPage(
  14631. new OptionsPage('languageMozc',
  14632. templateData.languageMozcPageTabTitle,
  14633. 'languageMozcPage'),
  14634. LanguageOptions.getInstance());
  14635. OptionsPage.registerSubPage(
  14636. new OptionsPage('languagePinyin',
  14637. templateData.languagePinyinPageTabTitle,
  14638. 'languagePinyinPage'),
  14639. LanguageOptions.getInstance());
  14640. // Only use the VirtualKeyboardManager if the keyboard DOM elements (which
  14641. // it will assume exists) are present (i.e. if we were built with
  14642. // USE_VIRTUAL_KEYBOARD).
  14643. if ($('language-options-virtual-keyboard')) {
  14644. OptionsPage.registerSubPage(VirtualKeyboardManager.getInstance(),
  14645. LanguageOptions.getInstance());
  14646. }
  14647. OptionsPage.register(InternetOptions.getInstance());
  14648. }
  14649. OptionsPage.register(AdvancedOptions.getInstance());
  14650. OptionsPage.registerSubPage(ContentSettings.getInstance(),
  14651. AdvancedOptions.getInstance(),
  14652. [$('privacyContentSettingsButton')]);
  14653. OptionsPage.registerSubPage(ContentSettingsExceptionsArea.getInstance(),
  14654. ContentSettings.getInstance());
  14655. OptionsPage.registerSubPage(CookiesView.getInstance(),
  14656. ContentSettings.getInstance(),
  14657. [$('privacyContentSettingsButton'),
  14658. $('show-cookies-button')]);
  14659. // If HandlerOptions is null it means it got compiled out.
  14660. if (HandlerOptions) {
  14661. OptionsPage.registerSubPage(HandlerOptions.getInstance(),
  14662. ContentSettings.getInstance(),
  14663. [$('manage-handlers-button')]);
  14664. }
  14665. if (IntentsView && $('manage-intents-button')) {
  14666. OptionsPage.registerSubPage(IntentsView.getInstance(),
  14667. ContentSettings.getInstance(),
  14668. [$('manage-intents-button')]);
  14669. }
  14670. OptionsPage.registerSubPage(FontSettings.getInstance(),
  14671. AdvancedOptions.getInstance(),
  14672. [$('fontSettingsCustomizeFontsButton')]);
  14673. if (!cr.isChromeOS) {
  14674. OptionsPage.registerSubPage(LanguageOptions.getInstance(),
  14675. AdvancedOptions.getInstance(),
  14676. [$('language-button')]);
  14677. }
  14678. if (!cr.isWindows && !cr.isMac) {
  14679. OptionsPage.registerSubPage(CertificateManager.getInstance(),
  14680. AdvancedOptions.getInstance(),
  14681. [$('certificatesManageButton')]);
  14682. OptionsPage.registerOverlay(CertificateRestoreOverlay.getInstance(),
  14683. CertificateManager.getInstance());
  14684. OptionsPage.registerOverlay(CertificateBackupOverlay.getInstance(),
  14685. CertificateManager.getInstance());
  14686. OptionsPage.registerOverlay(CertificateEditCaTrustOverlay.getInstance(),
  14687. CertificateManager.getInstance());
  14688. OptionsPage.registerOverlay(CertificateImportErrorOverlay.getInstance(),
  14689. CertificateManager.getInstance());
  14690. }
  14691. OptionsPage.registerOverlay(AddLanguageOverlay.getInstance(),
  14692. LanguageOptions.getInstance());
  14693. OptionsPage.registerOverlay(AlertOverlay.getInstance());
  14694. OptionsPage.registerOverlay(AutofillEditAddressOverlay.getInstance(),
  14695. AutofillOptions.getInstance());
  14696. OptionsPage.registerOverlay(AutofillEditCreditCardOverlay.getInstance(),
  14697. AutofillOptions.getInstance());
  14698. OptionsPage.registerOverlay(ClearBrowserDataOverlay.getInstance(),
  14699. AdvancedOptions.getInstance(),
  14700. [$('privacyClearDataButton')]);
  14701. OptionsPage.registerOverlay(ImportDataOverlay.getInstance(),
  14702. PersonalOptions.getInstance());
  14703. OptionsPage.registerOverlay(InstantConfirmOverlay.getInstance(),
  14704. BrowserOptions.getInstance());
  14705. OptionsPage.registerOverlay(SyncSetupOverlay.getInstance(),
  14706. PersonalOptions.getInstance());
  14707. OptionsPage.registerOverlay(ManageProfileOverlay.getInstance(),
  14708. PersonalOptions.getInstance());
  14709.  
  14710. OptionsPage.register(ExtensionSettings.getInstance());
  14711. OptionsPage.registerOverlay(PackExtensionOverlay.getInstance(),
  14712. ExtensionSettings.getInstance());
  14713.  
  14714. if (cr.isChromeOS) {
  14715. OptionsPage.register(AccountsOptions.getInstance());
  14716. OptionsPage.registerSubPage(ProxyOptions.getInstance(),
  14717. InternetOptions.getInstance());
  14718. OptionsPage.registerSubPage(ChangePictureOptions.getInstance(),
  14719. PersonalOptions.getInstance(),
  14720. [$('change-picture-button')]);
  14721. OptionsPage.registerOverlay(DetailsInternetPage.getInstance(),
  14722. InternetOptions.getInstance());
  14723.  
  14724. var languageModifierKeysOverlay = new OptionsPage(
  14725. 'languageCustomizeModifierKeysOverlay',
  14726. localStrings.getString('languageCustomizeModifierKeysOverlay'),
  14727. 'languageCustomizeModifierKeysOverlay')
  14728. $('languageCustomizeModifierKeysOverleyDismissButton').onclick =
  14729. function() {
  14730. OptionsPage.closeOverlay();
  14731. };
  14732. OptionsPage.registerOverlay(languageModifierKeysOverlay,
  14733. SystemOptions.getInstance(),
  14734. [$('modifier-keys-button')]);
  14735. }
  14736.  
  14737. Preferences.getInstance().initialize();
  14738. OptionsPage.initialize();
  14739.  
  14740. var path = document.location.pathname;
  14741.  
  14742. if (path.length > 1) {
  14743. // Skip starting slash and remove trailing slash (if any).
  14744. var pageName = path.slice(1).replace(/\/$/, '');
  14745. // Proxy page is now per network and only reachable from internet details.
  14746. if (pageName != 'proxy') {
  14747. // Show page, but don't update history (there's already an entry for it).
  14748. OptionsPage.showPageByName(pageName, false);
  14749. }
  14750. } else {
  14751. OptionsPage.showDefaultPage();
  14752. }
  14753.  
  14754. var subpagesNavTabs = document.querySelectorAll('.subpages-nav-tabs');
  14755. for(var i = 0; i < subpagesNavTabs.length; i++) {
  14756. subpagesNavTabs[i].onclick = function(event) {
  14757. OptionsPage.showTab(event.srcElement);
  14758. }
  14759. }
  14760.  
  14761. // Allow platform specific CSS rules.
  14762. cr.enablePlatformSpecificCSSRules();
  14763.  
  14764. if (navigator.plugins['Shockwave Flash'])
  14765. document.documentElement.setAttribute('hasFlashPlugin', '');
  14766.  
  14767. // Clicking on the Settings title brings up the 'Basics' page.
  14768. $('navbar-content-title').onclick = function() {
  14769. OptionsPage.navigateToPage(BrowserOptions.getInstance().name);
  14770. };
  14771. }
  14772.  
  14773. document.addEventListener('DOMContentLoaded', load);
  14774.  
  14775. window.onpopstate = function(e) {
  14776. options.OptionsPage.setState(e.state);
  14777. };
  14778.  
  14779. window.onbeforeunload = function() {
  14780. options.OptionsPage.willClose();
  14781. };
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement