• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright (c) 2012 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
5cr.define('options', function() {
6  var Preferences = options.Preferences;
7
8  /**
9   * A controlled setting indicator that can be placed on a setting as an
10   * indicator that the value is controlled by some external entity such as
11   * policy or an extension.
12   * @constructor
13   * @extends {HTMLSpanElement}
14   */
15  var ControlledSettingIndicator = cr.ui.define('span');
16
17  ControlledSettingIndicator.prototype = {
18    __proto__: cr.ui.BubbleButton.prototype,
19
20    /**
21     * Decorates the base element to show the proper icon.
22     */
23    decorate: function() {
24      cr.ui.BubbleButton.prototype.decorate.call(this);
25      this.classList.add('controlled-setting-indicator');
26
27      // If there is a pref, track its controlledBy and recommendedValue
28      // properties in order to be able to bring up the correct bubble.
29      if (this.pref) {
30        Preferences.getInstance().addEventListener(
31            this.pref, this.handlePrefChange.bind(this));
32        this.resetHandler = this.clearAssociatedPref_;
33      }
34    },
35
36    /**
37     * The given handler will be called when the user clicks on the 'reset to
38     * recommended value' link shown in the indicator bubble. The |this| object
39     * will be the indicator itself.
40     * @param {function()} handler The handler to be called.
41     */
42    set resetHandler(handler) {
43      this.resetHandler_ = handler;
44    },
45
46    /**
47     * Clears the preference associated with this indicator.
48     * @private
49     */
50    clearAssociatedPref_: function() {
51      Preferences.clearPref(this.pref, !this.dialogPref);
52    },
53
54    /* Handle changes to the associated pref by hiding any currently visible
55     * bubble and updating the controlledBy property.
56     * @param {Event} event Pref change event.
57     */
58    handlePrefChange: function(event) {
59      OptionsPage.hideBubble();
60      if (event.value.controlledBy) {
61        if (!this.value || String(event.value.value) == this.value) {
62          this.controlledBy = event.value.controlledBy;
63          if (event.value.extension) {
64            if (this.pref == 'session.restore_on_startup' ||
65                this.pref == 'homepage_is_newtabpage') {
66              // Special case for the restore on startup, which is implied
67              // by the startup pages settings being controlled by an
68              // extension, and same for the home page as NTP, so we don't want
69              // to show two buttons in these cases.
70              // TODO(mad): Find a better way to handle this.
71              this.controlledBy = null;
72            } else {
73              this.extensionId = event.value.extension.id;
74              this.extensionIcon = event.value.extension.icon;
75              this.extensionName = event.value.extension.name;
76            }
77          }
78        } else {
79          this.controlledBy = null;
80        }
81      } else if (event.value.recommendedValue != undefined) {
82        this.controlledBy =
83            !this.value || String(event.value.recommendedValue) == this.value ?
84            'hasRecommendation' : null;
85      } else {
86        this.controlledBy = null;
87      }
88    },
89
90    /**
91     * Open or close a bubble with further information about the pref.
92     * @private
93     */
94    toggleBubble_: function() {
95      if (this.showingBubble) {
96        OptionsPage.hideBubble();
97      } else {
98        var self = this;
99
100        // Construct the bubble text.
101        if (this.hasAttribute('plural')) {
102          var defaultStrings = {
103            'policy': loadTimeData.getString('controlledSettingsPolicy'),
104            'extension': loadTimeData.getString('controlledSettingsExtension'),
105            'extensionWithName': loadTimeData.getString(
106                'controlledSettingsExtensionWithName'),
107          };
108        } else {
109          var defaultStrings = {
110            'policy': loadTimeData.getString('controlledSettingPolicy'),
111            'extension': loadTimeData.getString('controlledSettingExtension'),
112            'extensionWithName': loadTimeData.getString(
113                'controlledSettingExtensionWithName'),
114            'recommended':
115                loadTimeData.getString('controlledSettingRecommended'),
116            'hasRecommendation':
117                loadTimeData.getString('controlledSettingHasRecommendation'),
118          };
119          if (cr.isChromeOS) {
120            defaultStrings.owner =
121                loadTimeData.getString('controlledSettingOwner');
122          }
123        }
124
125        // No controller, no bubble.
126        if (!this.controlledBy || !(this.controlledBy in defaultStrings))
127          return;
128
129        var text = defaultStrings[this.controlledBy];
130        if (this.controlledBy == 'extension' && this.extensionName)
131          text = defaultStrings.extensionWithName;
132
133        // Apply text overrides.
134        if (this.hasAttribute('text' + this.controlledBy))
135          text = this.getAttribute('text' + this.controlledBy);
136
137        // Create the DOM tree.
138        var content = document.createElement('div');
139        content.className = 'controlled-setting-bubble-content';
140        content.setAttribute('controlled-by', this.controlledBy);
141        content.textContent = text;
142
143        if (this.controlledBy == 'hasRecommendation' && this.resetHandler_ &&
144            !this.readOnly) {
145          var container = document.createElement('div');
146          var action = document.createElement('button');
147          action.classList.add('link-button');
148          action.classList.add('controlled-setting-bubble-action');
149          action.textContent =
150              loadTimeData.getString('controlledSettingFollowRecommendation');
151          action.addEventListener('click', function(event) {
152            self.resetHandler_();
153          });
154          container.appendChild(action);
155          content.appendChild(container);
156        } else if (this.controlledBy == 'extension' && this.extensionName) {
157          var extensionContainer =
158              $('extension-controlled-settings-bubble-template').
159                  cloneNode(true);
160          // No need for an id anymore, and thus remove to avoid id collision.
161          extensionContainer.removeAttribute('id');
162          extensionContainer.hidden = false;
163
164          var extensionName = extensionContainer.querySelector(
165              '.controlled-setting-bubble-extension-name');
166          extensionName.textContent = this.extensionName;
167          extensionName.style.backgroundImage =
168              'url("' + this.extensionIcon + '")';
169
170          var manageLink = extensionContainer.querySelector(
171              '.controlled-setting-bubble-extension-manage-link');
172          manageLink.onclick = function() {
173            uber.invokeMethodOnWindow(
174                window.top, 'showPage', {pageId: 'extensions'});
175          };
176
177          var disableButton = extensionContainer.querySelector('button');
178          var extensionId = this.extensionId;
179          disableButton.onclick = function() {
180            chrome.send('disableExtension', [extensionId]);
181          };
182          content.appendChild(extensionContainer);
183        }
184
185        OptionsPage.showBubble(content, this.image, this, this.location);
186      }
187    },
188  };
189
190  /**
191   * The name of the associated preference.
192   * @type {string}
193   */
194  cr.defineProperty(ControlledSettingIndicator, 'pref', cr.PropertyKind.ATTR);
195
196  /**
197   * Whether this indicator is part of a dialog. If so, changes made to the
198   * associated preference take effect in the settings UI immediately but are
199   * only actually committed when the user confirms the dialog. If the user
200   * cancels the dialog instead, the changes are rolled back in the settings UI
201   * and never committed.
202   * @type {boolean}
203   */
204  cr.defineProperty(ControlledSettingIndicator, 'dialogPref',
205                    cr.PropertyKind.BOOL_ATTR);
206
207  /**
208   * The value of the associated preference that the indicator represents. If
209   * this is not set, the indicator will be visible whenever any value is
210   * enforced or recommended. If it is set, the indicator will be visible only
211   * when the enforced or recommended value matches the value it represents.
212   * This allows multiple indicators to be created for a set of radio buttons,
213   * ensuring that only one of them is visible at a time.
214   */
215  cr.defineProperty(ControlledSettingIndicator, 'value',
216                    cr.PropertyKind.ATTR);
217
218  /**
219   * The status of the associated preference:
220   * - 'policy':            A specific value is enfoced by policy.
221   * - 'extension':         A specific value is enforced by an extension.
222   * - 'recommended':       A value is recommended by policy. The user could
223   *                        override this recommendation but has not done so.
224   * - 'hasRecommendation': A value is recommended by policy. The user has
225   *                        overridden this recommendation.
226   * - unset:               The value is controlled by the user alone.
227   * @type {string}
228   */
229  cr.defineProperty(ControlledSettingIndicator, 'controlledBy',
230                    cr.PropertyKind.ATTR);
231
232  // Export.
233  return {
234    ControlledSettingIndicator: ControlledSettingIndicator
235  };
236});
237