• 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 OptionsPage = options.OptionsPage;
7  var ArrayDataModel = cr.ui.ArrayDataModel;
8  var RepeatingButton = cr.ui.RepeatingButton;
9  var HotwordSearchSettingIndicator = options.HotwordSearchSettingIndicator;
10
11  //
12  // BrowserOptions class
13  // Encapsulated handling of browser options page.
14  //
15  function BrowserOptions() {
16    OptionsPage.call(this, 'settings', loadTimeData.getString('settingsTitle'),
17                     'settings');
18  }
19
20  cr.addSingletonGetter(BrowserOptions);
21
22  /**
23   * @param {HTMLElement} section The section to show or hide.
24   * @return {boolean} Whether the section should be shown.
25   * @private
26   */
27  BrowserOptions.shouldShowSection_ = function(section) {
28    // If the section is hidden or hiding, it should be shown.
29    return section.style.height == '' || section.style.height == '0px';
30  };
31
32  BrowserOptions.prototype = {
33    __proto__: options.OptionsPage.prototype,
34
35    /**
36     * Keeps track of whether the user is signed in or not.
37     * @type {boolean}
38     * @private
39     */
40    signedIn_: false,
41
42    /**
43     * Indicates whether signing out is allowed or whether a complete profile
44     * wipe is required to remove the current enterprise account.
45     * @type {boolean}
46     * @private
47     */
48    signoutAllowed_: true,
49
50    /**
51     * Keeps track of whether |onShowHomeButtonChanged_| has been called. See
52     * |onShowHomeButtonChanged_|.
53     * @type {boolean}
54     * @private
55     */
56    onShowHomeButtonChangedCalled_: false,
57
58    /**
59     * Track if page initialization is complete.  All C++ UI handlers have the
60     * chance to manipulate page content within their InitializePage methods.
61     * This flag is set to true after all initializers have been called.
62     * @type {boolean}
63     * @private
64     */
65    initializationComplete_: false,
66
67    /**
68     * When a section is waiting to change its height, this will be a number.
69     * Otherwise it'll be null.
70     * @type {?number}
71     * @private
72     */
73    sectionHeightChangeTimeout_: null,
74
75    /** @override */
76    initializePage: function() {
77      OptionsPage.prototype.initializePage.call(this);
78      var self = this;
79
80      // Ensure that navigation events are unblocked on uber page. A reload of
81      // the settings page while an overlay is open would otherwise leave uber
82      // page in a blocked state, where tab switching is not possible.
83      uber.invokeMethodOnParent('stopInterceptingEvents');
84
85      window.addEventListener('message', this.handleWindowMessage_.bind(this));
86
87      if (loadTimeData.getBoolean('allowAdvancedSettings')) {
88        $('advanced-settings-expander').onclick = function() {
89          var showAdvanced =
90              BrowserOptions.shouldShowSection_($('advanced-settings'));
91          if (showAdvanced) {
92            chrome.send('coreOptionsUserMetricsAction',
93                        ['Options_ShowAdvancedSettings']);
94          }
95          self.toggleSectionWithAnimation_(
96              $('advanced-settings'),
97              $('advanced-settings-container'));
98
99          // If the link was focused (i.e., it was activated using the keyboard)
100          // and it was used to show the section (rather than hiding it), focus
101          // the first element in the container.
102          if (document.activeElement === $('advanced-settings-expander') &&
103              showAdvanced) {
104            var focusElement = $('advanced-settings-container').querySelector(
105                'button, input, list, select, a[href]');
106            if (focusElement)
107              focusElement.focus();
108          }
109        };
110      } else {
111        $('advanced-settings-expander').hidden = true;
112        $('advanced-settings').hidden = true;
113      }
114
115      $('advanced-settings').addEventListener('webkitTransitionEnd',
116          this.updateAdvancedSettingsExpander_.bind(this));
117
118      if (cr.isChromeOS) {
119        UIAccountTweaks.applyGuestModeVisibility(document);
120        if (loadTimeData.getBoolean('secondaryUser'))
121          $('secondary-user-banner').hidden = false;
122      }
123
124      // Sync (Sign in) section.
125      this.updateSyncState_(loadTimeData.getValue('syncData'));
126
127      $('start-stop-sync').onclick = function(event) {
128        if (self.signedIn_) {
129          if (self.signoutAllowed_)
130            SyncSetupOverlay.showStopSyncingUI();
131          else
132            chrome.send('showDisconnectManagedProfileDialog');
133        } else if (cr.isChromeOS) {
134          SyncSetupOverlay.showSetupUI();
135        } else {
136          SyncSetupOverlay.startSignIn();
137        }
138      };
139      $('customize-sync').onclick = function(event) {
140        SyncSetupOverlay.showSetupUI();
141      };
142
143      // Internet connection section (ChromeOS only).
144      if (cr.isChromeOS) {
145        options.network.NetworkList.decorate($('network-list'));
146        // Show that the network settings are shared if this is a secondary user
147        // in a multi-profile session.
148        if (loadTimeData.getBoolean('secondaryUser')) {
149          var networkIndicator = document.querySelector(
150              '#network-section-header > .controlled-setting-indicator');
151          networkIndicator.setAttribute('controlled-by', 'shared');
152          networkIndicator.location = cr.ui.ArrowLocation.TOP_START;
153        }
154        options.network.NetworkList.refreshNetworkData(
155            loadTimeData.getValue('networkData'));
156      }
157
158      // On Startup section.
159      Preferences.getInstance().addEventListener('session.restore_on_startup',
160          this.onRestoreOnStartupChanged_.bind(this));
161      Preferences.getInstance().addEventListener(
162          'session.startup_urls',
163          function(event) {
164            $('startup-set-pages').disabled = event.value.disabled;
165          });
166
167      $('startup-set-pages').onclick = function() {
168        OptionsPage.navigateToPage('startup');
169      };
170
171      // Appearance section.
172      Preferences.getInstance().addEventListener('browser.show_home_button',
173          this.onShowHomeButtonChanged_.bind(this));
174
175      Preferences.getInstance().addEventListener('homepage',
176          this.onHomePageChanged_.bind(this));
177      Preferences.getInstance().addEventListener('homepage_is_newtabpage',
178          this.onHomePageIsNtpChanged_.bind(this));
179
180      $('change-home-page').onclick = function(event) {
181        OptionsPage.navigateToPage('homePageOverlay');
182        chrome.send('coreOptionsUserMetricsAction',
183                    ['Options_Homepage_ShowSettings']);
184      };
185
186      chrome.send('requestHotwordAvailable');
187      var hotwordIndicator = $('hotword-search-setting-indicator');
188      HotwordSearchSettingIndicator.decorate(hotwordIndicator);
189      hotwordIndicator.disabledOnErrorSection = $('hotword-search-enable');
190
191      if ($('set-wallpaper')) {
192        $('set-wallpaper').onclick = function(event) {
193          chrome.send('openWallpaperManager');
194          chrome.send('coreOptionsUserMetricsAction',
195                      ['Options_OpenWallpaperManager']);
196        };
197      }
198
199      $('themes-gallery').onclick = function(event) {
200        window.open(loadTimeData.getString('themesGalleryURL'));
201        chrome.send('coreOptionsUserMetricsAction',
202                    ['Options_ThemesGallery']);
203      };
204      $('themes-reset').onclick = function(event) {
205        chrome.send('themesReset');
206      };
207
208      if (loadTimeData.getBoolean('profileIsManaged')) {
209        if ($('themes-native-button')) {
210          $('themes-native-button').disabled = true;
211          $('themes-native-button').hidden = true;
212        }
213        // Supervised users have just one default theme, even on Linux. So use
214        // the same button for Linux as for the other platforms.
215        $('themes-reset').textContent = loadTimeData.getString('themesReset');
216      }
217
218      // Device section (ChromeOS only).
219      if (cr.isChromeOS) {
220        $('keyboard-settings-button').onclick = function(evt) {
221          OptionsPage.navigateToPage('keyboard-overlay');
222          chrome.send('coreOptionsUserMetricsAction',
223                      ['Options_ShowKeyboardSettings']);
224        };
225        $('pointer-settings-button').onclick = function(evt) {
226          OptionsPage.navigateToPage('pointer-overlay');
227          chrome.send('coreOptionsUserMetricsAction',
228                      ['Options_ShowTouchpadSettings']);
229        };
230      }
231
232      // Search section.
233      $('manage-default-search-engines').onclick = function(event) {
234        OptionsPage.navigateToPage('searchEngines');
235        chrome.send('coreOptionsUserMetricsAction',
236                    ['Options_ManageSearchEngines']);
237      };
238      $('default-search-engine').addEventListener('change',
239          this.setDefaultSearchEngine_);
240
241      // Users section.
242      if (loadTimeData.valueExists('profilesInfo')) {
243        $('profiles-section').hidden = false;
244        this.maybeShowUserSection_();
245
246        var profilesList = $('profiles-list');
247        options.browser_options.ProfileList.decorate(profilesList);
248        profilesList.autoExpands = true;
249
250        // The profiles info data in |loadTimeData| might be stale.
251        this.setProfilesInfo_(loadTimeData.getValue('profilesInfo'));
252        chrome.send('requestProfilesInfo');
253
254        profilesList.addEventListener('change',
255            this.setProfileViewButtonsStatus_);
256        $('profiles-create').onclick = function(event) {
257          ManageProfileOverlay.showCreateDialog();
258        };
259        if (OptionsPage.isSettingsApp()) {
260          $('profiles-app-list-switch').onclick = function(event) {
261            var selectedProfile = self.getSelectedProfileItem_();
262            chrome.send('switchAppListProfile', [selectedProfile.filePath]);
263          };
264        }
265        $('profiles-manage').onclick = function(event) {
266          ManageProfileOverlay.showManageDialog();
267        };
268        $('profiles-delete').onclick = function(event) {
269          var selectedProfile = self.getSelectedProfileItem_();
270          if (selectedProfile)
271            ManageProfileOverlay.showDeleteDialog(selectedProfile);
272        };
273        if (loadTimeData.getBoolean('profileIsManaged')) {
274          $('profiles-create').disabled = true;
275          $('profiles-delete').disabled = true;
276          $('profiles-list').canDeleteItems = false;
277        }
278      }
279
280      if (cr.isChromeOS) {
281        // Username (canonical email) of the currently logged in user or
282        // |kGuestUser| if a guest session is active.
283        this.username_ = loadTimeData.getString('username');
284
285        this.updateAccountPicture_();
286
287        $('account-picture').onclick = this.showImagerPickerOverlay_;
288        $('change-picture-caption').onclick = this.showImagerPickerOverlay_;
289
290        $('manage-accounts-button').onclick = function(event) {
291          OptionsPage.navigateToPage('accounts');
292          chrome.send('coreOptionsUserMetricsAction',
293              ['Options_ManageAccounts']);
294        };
295
296        document.querySelector(
297            '#enable-screen-lock + span > .controlled-setting-indicator').
298            setAttribute('textshared',
299                         loadTimeData.getString('screenLockShared'));
300      } else {
301        $('import-data').onclick = function(event) {
302          ImportDataOverlay.show();
303          chrome.send('coreOptionsUserMetricsAction', ['Import_ShowDlg']);
304        };
305
306        if ($('themes-native-button')) {
307          $('themes-native-button').onclick = function(event) {
308            chrome.send('themesSetNative');
309          };
310        }
311      }
312
313      // Date and time section (CrOS only).
314      if ($('set-time-button'))
315        $('set-time-button').onclick = this.handleSetTime_.bind(this);
316
317      // Default browser section.
318      if (!cr.isChromeOS) {
319        if (!loadTimeData.getBoolean('showSetDefault')) {
320          $('set-default-browser-section').hidden = true;
321        }
322        $('set-as-default-browser').onclick = function(event) {
323          chrome.send('becomeDefaultBrowser');
324        };
325
326        $('auto-launch').onclick = this.handleAutoLaunchChanged_;
327      }
328
329      // Privacy section.
330      $('privacyContentSettingsButton').onclick = function(event) {
331        OptionsPage.navigateToPage('content');
332        OptionsPage.showTab($('cookies-nav-tab'));
333        chrome.send('coreOptionsUserMetricsAction',
334            ['Options_ContentSettings']);
335      };
336      $('privacyClearDataButton').onclick = function(event) {
337        OptionsPage.navigateToPage('clearBrowserData');
338        chrome.send('coreOptionsUserMetricsAction', ['Options_ClearData']);
339      };
340      $('privacyClearDataButton').hidden = OptionsPage.isSettingsApp();
341      // 'metricsReportingEnabled' element is only present on Chrome branded
342      // builds, and the 'metricsReportingCheckboxAction' message is only
343      // handled on ChromeOS.
344      if ($('metricsReportingEnabled') && cr.isChromeOS) {
345        $('metricsReportingEnabled').onclick = function(event) {
346          chrome.send('metricsReportingCheckboxAction',
347              [String(event.currentTarget.checked)]);
348        };
349      }
350
351      // Bluetooth (CrOS only).
352      if (cr.isChromeOS) {
353        options.system.bluetooth.BluetoothDeviceList.decorate(
354            $('bluetooth-paired-devices-list'));
355
356        $('bluetooth-add-device').onclick =
357            this.handleAddBluetoothDevice_.bind(this);
358
359        $('enable-bluetooth').onchange = function(event) {
360          var state = $('enable-bluetooth').checked;
361          chrome.send('bluetoothEnableChange', [Boolean(state)]);
362        };
363
364        $('bluetooth-reconnect-device').onclick = function(event) {
365          var device = $('bluetooth-paired-devices-list').selectedItem;
366          var address = device.address;
367          chrome.send('updateBluetoothDevice', [address, 'connect']);
368          OptionsPage.closeOverlay();
369        };
370
371        $('bluetooth-paired-devices-list').addEventListener('change',
372            function() {
373          var item = $('bluetooth-paired-devices-list').selectedItem;
374          var disabled = !item || item.connected || !item.connectable;
375          $('bluetooth-reconnect-device').disabled = disabled;
376        });
377      }
378
379      // Passwords and Forms section.
380      $('autofill-settings').onclick = function(event) {
381        OptionsPage.navigateToPage('autofill');
382        chrome.send('coreOptionsUserMetricsAction',
383            ['Options_ShowAutofillSettings']);
384      };
385      $('manage-passwords').onclick = function(event) {
386        OptionsPage.navigateToPage('passwords');
387        OptionsPage.showTab($('passwords-nav-tab'));
388        chrome.send('coreOptionsUserMetricsAction',
389            ['Options_ShowPasswordManager']);
390      };
391      if (cr.isChromeOS && UIAccountTweaks.loggedInAsGuest()) {
392        // Disable and turn off Autofill in guest mode.
393        var autofillEnabled = $('autofill-enabled');
394        autofillEnabled.disabled = true;
395        autofillEnabled.checked = false;
396        cr.dispatchSimpleEvent(autofillEnabled, 'change');
397        $('autofill-settings').disabled = true;
398
399        // Disable and turn off Password Manager in guest mode.
400        var passwordManagerEnabled = $('password-manager-enabled');
401        passwordManagerEnabled.disabled = true;
402        passwordManagerEnabled.checked = false;
403        cr.dispatchSimpleEvent(passwordManagerEnabled, 'change');
404        $('manage-passwords').disabled = true;
405      }
406
407      if (cr.isMac) {
408        $('mac-passwords-warning').hidden =
409            !loadTimeData.getBoolean('multiple_profiles');
410      }
411
412      // Network section.
413      if (!cr.isChromeOS) {
414        $('proxiesConfigureButton').onclick = function(event) {
415          chrome.send('showNetworkProxySettings');
416        };
417      }
418
419      // Security section.
420      if (cr.isChromeOS &&
421          loadTimeData.getBoolean('consumerManagementEnabled')) {
422        $('security-section').hidden = false;
423        $('consumer-management-enroll-button').onclick = function(event) {
424          chrome.send('enrollConsumerManagement');
425        };
426      }
427
428      // Easy Unlock section.
429      if (loadTimeData.getBoolean('easyUnlockEnabled')) {
430        $('easy-unlock-section').hidden = false;
431        $('easy-unlock-setup-button').onclick = function(event) {
432          chrome.send('launchEasyUnlockSetup');
433        };
434      }
435
436      // Web Content section.
437      $('fontSettingsCustomizeFontsButton').onclick = function(event) {
438        OptionsPage.navigateToPage('fonts');
439        chrome.send('coreOptionsUserMetricsAction', ['Options_FontSettings']);
440      };
441      $('defaultFontSize').onchange = function(event) {
442        var value = event.target.options[event.target.selectedIndex].value;
443        Preferences.setIntegerPref(
444             'webkit.webprefs.default_fixed_font_size',
445             value - OptionsPage.SIZE_DIFFERENCE_FIXED_STANDARD, true);
446        chrome.send('defaultFontSizeAction', [String(value)]);
447      };
448      $('defaultZoomFactor').onchange = function(event) {
449        chrome.send('defaultZoomFactorAction',
450            [String(event.target.options[event.target.selectedIndex].value)]);
451      };
452
453      // Languages section.
454      var showLanguageOptions = function(event) {
455        OptionsPage.navigateToPage('languages');
456        chrome.send('coreOptionsUserMetricsAction',
457            ['Options_LanuageAndSpellCheckSettings']);
458      };
459      $('language-button').onclick = showLanguageOptions;
460      $('manage-languages').onclick = showLanguageOptions;
461
462      // Downloads section.
463      Preferences.getInstance().addEventListener('download.default_directory',
464          this.onDefaultDownloadDirectoryChanged_.bind(this));
465      $('downloadLocationChangeButton').onclick = function(event) {
466        chrome.send('selectDownloadLocation');
467      };
468      if (cr.isChromeOS) {
469        $('disable-drive-row').hidden =
470            UIAccountTweaks.loggedInAsLocallyManagedUser();
471      }
472      $('autoOpenFileTypesResetToDefault').onclick = function(event) {
473        chrome.send('autoOpenFileTypesAction');
474      };
475
476      // HTTPS/SSL section.
477      if (cr.isWindows || cr.isMac) {
478        $('certificatesManageButton').onclick = function(event) {
479          chrome.send('showManageSSLCertificates');
480        };
481      } else {
482        $('certificatesManageButton').onclick = function(event) {
483          OptionsPage.navigateToPage('certificates');
484          chrome.send('coreOptionsUserMetricsAction',
485                      ['Options_ManageSSLCertificates']);
486        };
487      }
488
489      if (loadTimeData.getBoolean('cloudPrintShowMDnsOptions')) {
490        $('cloudprint-options-mdns').hidden = false;
491        $('cloudPrintDevicesPageButton').onclick = function() {
492          chrome.send('showCloudPrintDevicesPage');
493        };
494      }
495
496      // Accessibility section (CrOS only).
497      if (cr.isChromeOS) {
498        var updateAccessibilitySettingsButton = function() {
499          $('accessibility-settings').hidden =
500              !($('accessibility-spoken-feedback-check').checked);
501        };
502        Preferences.getInstance().addEventListener(
503            'settings.accessibility',
504            updateAccessibilitySettingsButton);
505        $('accessibility-learn-more').onclick = function(unused_event) {
506          window.open(loadTimeData.getString('accessibilityLearnMoreURL'));
507          chrome.send('coreOptionsUserMetricsAction',
508                      ['Options_AccessibilityLearnMore']);
509        };
510        $('accessibility-settings-button').onclick = function(unused_event) {
511          window.open(loadTimeData.getString('accessibilitySettingsURL'));
512        };
513        $('accessibility-spoken-feedback-check').onchange = function(
514            unused_event) {
515          chrome.send('spokenFeedbackChange',
516                      [$('accessibility-spoken-feedback-check').checked]);
517          updateAccessibilitySettingsButton();
518        };
519        updateAccessibilitySettingsButton();
520
521        $('accessibility-high-contrast-check').onchange = function(
522            unused_event) {
523          chrome.send('highContrastChange',
524                      [$('accessibility-high-contrast-check').checked]);
525        };
526
527        var updateDelayDropdown = function() {
528          $('accessibility-autoclick-dropdown').disabled =
529              !$('accessibility-autoclick-check').checked;
530        };
531        Preferences.getInstance().addEventListener(
532            $('accessibility-autoclick-check').getAttribute('pref'),
533            updateDelayDropdown);
534      }
535
536      // Display management section (CrOS only).
537      if (cr.isChromeOS) {
538        $('display-options').onclick = function(event) {
539          OptionsPage.navigateToPage('display');
540          chrome.send('coreOptionsUserMetricsAction',
541                      ['Options_Display']);
542        };
543      }
544
545      // Factory reset section (CrOS only).
546      if (cr.isChromeOS) {
547        $('factory-reset-restart').onclick = function(event) {
548          OptionsPage.navigateToPage('factoryResetData');
549          chrome.send('onPowerwashDialogShow');
550        };
551      }
552
553      // System section.
554      if (!cr.isChromeOS) {
555        var updateGpuRestartButton = function() {
556          $('gpu-mode-reset-restart').hidden =
557              loadTimeData.getBoolean('gpuEnabledAtStart') ==
558              $('gpu-mode-checkbox').checked;
559        };
560        Preferences.getInstance().addEventListener(
561            $('gpu-mode-checkbox').getAttribute('pref'),
562            updateGpuRestartButton);
563        $('gpu-mode-reset-restart-button').onclick = function(event) {
564          chrome.send('restartBrowser');
565        };
566        updateGpuRestartButton();
567      }
568
569      // Reset profile settings section.
570      $('reset-profile-settings').onclick = function(event) {
571        OptionsPage.navigateToPage('resetProfileSettings');
572      };
573      $('reset-profile-settings-section').hidden =
574          !loadTimeData.getBoolean('enableResetProfileSettings');
575
576      // Extension controlled UI.
577      this.addExtensionControlledBox_('search-section-content',
578                                      'search-engine-controlled',
579                                      true);
580      this.addExtensionControlledBox_('extension-controlled-container',
581                                      'homepage-controlled',
582                                      true);
583      this.addExtensionControlledBox_('startup-section-content',
584                                      'startpage-controlled',
585                                      false);
586      this.addExtensionControlledBox_('newtab-section-content',
587                                      'newtab-controlled',
588                                      false);
589      this.addExtensionControlledBox_('proxy-section-content',
590                                      'proxy-controlled',
591                                      true);
592
593      document.body.addEventListener('click', function(e) {
594        var button = findAncestor(e.target, function(el) {
595          return el.tagName == 'BUTTON' &&
596                 el.dataset.extensionId !== undefined &&
597                 el.dataset.extensionId.length;
598        });
599        if (button)
600          chrome.send('disableExtension', [button.dataset.extensionId]);
601      });
602    },
603
604    /** @override */
605    didShowPage: function() {
606      $('search-field').focus();
607    },
608
609   /**
610    * Called after all C++ UI handlers have called InitializePage to notify
611    * that initialization is complete.
612    * @private
613    */
614    notifyInitializationComplete_: function() {
615      this.initializationComplete_ = true;
616      cr.dispatchSimpleEvent(document, 'initializationComplete');
617    },
618
619    /**
620     * Event listener for the 'session.restore_on_startup' pref.
621     * @param {Event} event The preference change event.
622     * @private
623     */
624    onRestoreOnStartupChanged_: function(event) {
625      /** @const */ var showHomePageValue = 0;
626
627      if (event.value.value == showHomePageValue) {
628        // If the user previously selected "Show the homepage", the
629        // preference will already be migrated to "Open a specific page". So
630        // the only way to reach this code is if the 'restore on startup'
631        // preference is managed.
632        assert(event.value.controlledBy);
633
634        // Select "open the following pages" and lock down the list of URLs
635        // to reflect the intention of the policy.
636        $('startup-show-pages').checked = true;
637        StartupOverlay.getInstance().setControlsDisabled(true);
638      } else {
639        // Re-enable the controls in the startup overlay if necessary.
640        StartupOverlay.getInstance().updateControlStates();
641      }
642    },
643
644    /**
645     * Handler for messages sent from the main uber page.
646     * @param {Event} e The 'message' event from the uber page.
647     * @private
648     */
649    handleWindowMessage_: function(e) {
650      if (e.data.method == 'frameSelected')
651        $('search-field').focus();
652    },
653
654    /**
655     * Animatedly changes height |from| a px number |to| a px number.
656     * @param {HTMLElement} section The section to animate.
657     * @param {HTMLElement} container The container of |section|.
658     * @param {boolean} showing Whether to go from 0 -> container height or
659     *     container height -> 0.
660     * @private
661     */
662    animatedSectionHeightChange_: function(section, container, showing) {
663      // If the section is already animating, dispatch a synthetic transition
664      // end event as the upcoming code will cancel the current one.
665      if (section.classList.contains('sliding'))
666        cr.dispatchSimpleEvent(section, 'webkitTransitionEnd');
667
668      this.addTransitionEndListener_(section);
669
670      section.hidden = false;
671      section.style.height = (showing ? 0 : container.offsetHeight) + 'px';
672      section.classList.add('sliding');
673
674      if (this.sectionHeightChangeTimeout_ !== null)
675        clearTimeout(this.sectionHeightChangeTimeout_);
676
677      this.sectionHeightChangeTimeout_ = setTimeout(function() {
678        section.style.height = (showing ? container.offsetHeight : 0) + 'px';
679        this.sectionHeightChangeTimeout_ = null;
680      });
681    },
682
683    /**
684     * Shows the given section.
685     * @param {HTMLElement} section The section to be shown.
686     * @param {HTMLElement} container The container for the section. Must be
687     *     inside of |section|.
688     * @param {boolean} animate Indicate if the expansion should be animated.
689     * @private
690     */
691    showSection_: function(section, container, animate) {
692      // Delay starting the transition if animating so that hidden change will
693      // be processed.
694      if (animate) {
695        this.animatedSectionHeightChange_(section, container, true);
696      } else {
697        section.hidden = false;
698        section.style.height = 'auto';
699      }
700    },
701
702    /**
703     * Shows the given section, with animation.
704     * @param {HTMLElement} section The section to be shown.
705     * @param {HTMLElement} container The container for the section. Must be
706     *     inside of |section|.
707     * @private
708     */
709    showSectionWithAnimation_: function(section, container) {
710      this.showSection_(section, container, /* animate */ true);
711    },
712
713    /**
714     * Hides the given |section| with animation.
715     * @param {HTMLElement} section The section to be hidden.
716     * @param {HTMLElement} container The container for the section. Must be
717     *     inside of |section|.
718     * @private
719     */
720    hideSectionWithAnimation_: function(section, container) {
721      this.animatedSectionHeightChange_(section, container, false);
722    },
723
724    /**
725     * Toggles the visibility of |section| in an animated way.
726     * @param {HTMLElement} section The section to be toggled.
727     * @param {HTMLElement} container The container for the section. Must be
728     *     inside of |section|.
729     * @private
730     */
731    toggleSectionWithAnimation_: function(section, container) {
732      if (BrowserOptions.shouldShowSection_(section))
733        this.showSectionWithAnimation_(section, container);
734      else
735        this.hideSectionWithAnimation_(section, container);
736    },
737
738    /**
739     * Scrolls the settings page to make the section visible auto-expanding
740     * advanced settings if required.  The transition is not animated.  This
741     * method is used to ensure that a section associated with an overlay
742     * is visible when the overlay is closed.
743     * @param {!Element} section  The section to make visible.
744     * @private
745     */
746    scrollToSection_: function(section) {
747      var advancedSettings = $('advanced-settings');
748      var container = $('advanced-settings-container');
749      var expander = $('advanced-settings-expander');
750      if (!expander.hidden &&
751          advancedSettings.hidden &&
752          section.parentNode == container) {
753        this.showSection_($('advanced-settings'),
754                          $('advanced-settings-container'),
755                          /* animate */ false);
756        this.updateAdvancedSettingsExpander_();
757      }
758
759      if (!this.initializationComplete_) {
760        var self = this;
761        var callback = function() {
762           document.removeEventListener('initializationComplete', callback);
763           self.scrollToSection_(section);
764        };
765        document.addEventListener('initializationComplete', callback);
766        return;
767      }
768
769      var pageContainer = $('page-container');
770      // pageContainer.offsetTop is relative to the screen.
771      var pageTop = pageContainer.offsetTop;
772      var sectionBottom = section.offsetTop + section.offsetHeight;
773      // section.offsetTop is relative to the 'page-container'.
774      var sectionTop = section.offsetTop;
775      if (pageTop + sectionBottom > document.body.scrollHeight ||
776          pageTop + sectionTop < 0) {
777        // Currently not all layout updates are guaranteed to precede the
778        // initializationComplete event (for example 'set-as-default-browser'
779        // button) leaving some uncertainty in the optimal scroll position.
780        // The section is placed approximately in the middle of the screen.
781        var top = Math.min(0, document.body.scrollHeight / 2 - sectionBottom);
782        pageContainer.style.top = top + 'px';
783        pageContainer.oldScrollTop = -top;
784      }
785    },
786
787    /**
788     * Adds a |webkitTransitionEnd| listener to the given section so that
789     * it can be animated. The listener will only be added to a given section
790     * once, so this can be called as multiple times.
791     * @param {HTMLElement} section The section to be animated.
792     * @private
793     */
794    addTransitionEndListener_: function(section) {
795      if (section.hasTransitionEndListener_)
796        return;
797
798      section.addEventListener('webkitTransitionEnd',
799          this.onTransitionEnd_.bind(this));
800      section.hasTransitionEndListener_ = true;
801    },
802
803    /**
804     * Called after an animation transition has ended.
805     * @param {Event} The webkitTransitionEnd event. NOTE: May be synthetic.
806     * @private
807     */
808    onTransitionEnd_: function(event) {
809      if (event.propertyName && event.propertyName != 'height') {
810        // If not a synthetic event or a real transition we care about, bail.
811        return;
812      }
813
814      var section = event.target;
815      section.classList.remove('sliding');
816
817      if (!event.propertyName) {
818        // Only real transitions past this point.
819        return;
820      }
821
822      if (section.style.height == '0px') {
823        // Hide the content so it can't get tab focus.
824        section.hidden = true;
825        section.style.height = '';
826      } else {
827        // Set the section height to 'auto' to allow for size changes
828        // (due to font change or dynamic content).
829        section.style.height = 'auto';
830      }
831    },
832
833    /** @private */
834    updateAdvancedSettingsExpander_: function() {
835      var expander = $('advanced-settings-expander');
836      if (BrowserOptions.shouldShowSection_($('advanced-settings')))
837        expander.textContent = loadTimeData.getString('showAdvancedSettings');
838      else
839        expander.textContent = loadTimeData.getString('hideAdvancedSettings');
840    },
841
842    /**
843     * Updates the sync section with the given state.
844     * @param {Object} syncData A bunch of data records that describe the status
845     *     of the sync system.
846     * @private
847     */
848    updateSyncState_: function(syncData) {
849      if (!syncData.signinAllowed &&
850          (!syncData.supervisedUser || !cr.isChromeOS)) {
851        $('sync-section').hidden = true;
852        this.maybeShowUserSection_();
853        return;
854      }
855
856      $('sync-section').hidden = false;
857      this.maybeShowUserSection_();
858
859      if (cr.isChromeOS && syncData.supervisedUser) {
860        var subSection = $('sync-section').firstChild;
861        while (subSection) {
862          if (subSection.nodeType == Node.ELEMENT_NODE)
863            subSection.hidden = true;
864          subSection = subSection.nextSibling;
865        }
866
867        $('account-picture-wrapper').hidden = false;
868        $('sync-general').hidden = false;
869        $('sync-status').hidden = true;
870
871        return;
872      }
873
874      // If the user gets signed out while the advanced sync settings dialog is
875      // visible, say, due to a dashboard clear, close the dialog.
876      // However, if the user gets signed out as a result of abandoning first
877      // time sync setup, do not call closeOverlay as it will redirect the
878      // browser to the main settings page and override any in-progress
879      // user-initiated navigation. See crbug.com/278030.
880      // Note: SyncSetupOverlay.closeOverlay is a no-op if the overlay is
881      // already hidden.
882      if (this.signedIn_ && !syncData.signedIn && !syncData.setupInProgress)
883        SyncSetupOverlay.closeOverlay();
884
885      this.signedIn_ = syncData.signedIn;
886
887      // Display the "advanced settings" button if we're signed in and sync is
888      // not managed/disabled. If the user is signed in, but sync is disabled,
889      // this button is used to re-enable sync.
890      var customizeSyncButton = $('customize-sync');
891      customizeSyncButton.hidden = !this.signedIn_ ||
892                                   syncData.managed ||
893                                   !syncData.syncSystemEnabled;
894
895      // Only modify the customize button's text if the new text is different.
896      // Otherwise, it can affect search-highlighting in the settings page.
897      // See http://crbug.com/268265.
898      var customizeSyncButtonNewText = syncData.setupCompleted ?
899          loadTimeData.getString('customizeSync') :
900          loadTimeData.getString('syncButtonTextStart');
901      if (customizeSyncButton.textContent != customizeSyncButtonNewText)
902        customizeSyncButton.textContent = customizeSyncButtonNewText;
903
904      // Disable the "sign in" button if we're currently signing in, or if we're
905      // already signed in and signout is not allowed.
906      var signInButton = $('start-stop-sync');
907      signInButton.disabled = syncData.setupInProgress;
908      this.signoutAllowed_ = syncData.signoutAllowed;
909      if (!syncData.signoutAllowed)
910        $('start-stop-sync-indicator').setAttribute('controlled-by', 'policy');
911      else
912        $('start-stop-sync-indicator').removeAttribute('controlled-by');
913
914      // Hide the "sign in" button on Chrome OS, and show it on desktop Chrome.
915      signInButton.hidden = cr.isChromeOS;
916
917      signInButton.textContent =
918          this.signedIn_ ?
919              loadTimeData.getString('syncButtonTextStop') :
920              syncData.setupInProgress ?
921                  loadTimeData.getString('syncButtonTextInProgress') :
922                  loadTimeData.getString('syncButtonTextSignIn');
923      $('start-stop-sync-indicator').hidden = signInButton.hidden;
924
925      // TODO(estade): can this just be textContent?
926      $('sync-status-text').innerHTML = syncData.statusText;
927      var statusSet = syncData.statusText.length != 0;
928      $('sync-overview').hidden = statusSet;
929      $('sync-status').hidden = !statusSet;
930
931      $('sync-action-link').textContent = syncData.actionLinkText;
932      // Don't show the action link if it is empty or undefined.
933      $('sync-action-link').hidden = syncData.actionLinkText.length == 0;
934      $('sync-action-link').disabled = syncData.managed ||
935                                       !syncData.syncSystemEnabled;
936
937      // On Chrome OS, sign out the user and sign in again to get fresh
938      // credentials on auth errors.
939      $('sync-action-link').onclick = function(event) {
940        if (cr.isChromeOS && syncData.hasError)
941          SyncSetupOverlay.doSignOutOnAuthError();
942        else
943          SyncSetupOverlay.showSetupUI();
944      };
945
946      if (syncData.hasError)
947        $('sync-status').classList.add('sync-error');
948      else
949        $('sync-status').classList.remove('sync-error');
950
951      // Disable the "customize / set up sync" button if sync has an
952      // unrecoverable error. Also disable the button if sync has not been set
953      // up and the user is being presented with a link to re-auth.
954      // See crbug.com/289791.
955      customizeSyncButton.disabled =
956          syncData.hasUnrecoverableError ||
957          (!syncData.setupCompleted && !$('sync-action-link').hidden);
958    },
959
960    /**
961     * Update the UI depending on whether the current profile has a pairing for
962     * Easy Unlock.
963     * @param {boolean} hasPairing True if the current profile has a pairing.
964     */
965    updateEasyUnlock_: function(hasPairing) {
966      $('easy-unlock-setup').hidden = hasPairing;
967      $('easy-unlock-enable').hidden = !hasPairing;
968    },
969
970    /**
971     * Update the UI depending on whether the current profile manages any
972     * supervised users.
973     * @param {boolean} show True if the current profile manages any supervised
974     *     users.
975     */
976    updateManagesSupervisedUsers_: function(show) {
977      $('profiles-supervised-dashboard-tip').hidden = !show;
978      this.maybeShowUserSection_();
979    },
980
981    /**
982     * Get the start/stop sync button DOM element. Used for testing.
983     * @return {DOMElement} The start/stop sync button.
984     * @private
985     */
986    getStartStopSyncButton_: function() {
987      return $('start-stop-sync');
988    },
989
990    /**
991     * Event listener for the 'show home button' preference. Shows/hides the
992     * UI for changing the home page with animation, unless this is the first
993     * time this function is called, in which case there is no animation.
994     * @param {Event} event The preference change event.
995     */
996    onShowHomeButtonChanged_: function(event) {
997      var section = $('change-home-page-section');
998      if (this.onShowHomeButtonChangedCalled_) {
999        var container = $('change-home-page-section-container');
1000        if (event.value.value)
1001          this.showSectionWithAnimation_(section, container);
1002        else
1003          this.hideSectionWithAnimation_(section, container);
1004      } else {
1005        section.hidden = !event.value.value;
1006        this.onShowHomeButtonChangedCalled_ = true;
1007      }
1008    },
1009
1010    /**
1011     * Activates the Hotword section from the System settings page.
1012     * @param {string} opt_error The error message to display.
1013     * @param {string} opt_help_link The link to a troubleshooting page.
1014     * @private
1015     */
1016    showHotwordSection_: function(opt_error, opt_help_link) {
1017      $('hotword-search').hidden = false;
1018      $('hotword-search-setting-indicator').errorText = opt_error;
1019      $('hotword-search-setting-indicator').helpLink = opt_help_link;
1020    },
1021
1022    /**
1023     * Event listener for the 'homepage is NTP' preference. Updates the label
1024     * next to the 'Change' button.
1025     * @param {Event} event The preference change event.
1026     */
1027    onHomePageIsNtpChanged_: function(event) {
1028      if (!event.value.uncommitted) {
1029        $('home-page-url').hidden = event.value.value;
1030        $('home-page-ntp').hidden = !event.value.value;
1031      }
1032    },
1033
1034    /**
1035     * Event listener for changes to the homepage preference. Updates the label
1036     * next to the 'Change' button.
1037     * @param {Event} event The preference change event.
1038     */
1039    onHomePageChanged_: function(event) {
1040      if (!event.value.uncommitted)
1041        $('home-page-url').textContent = this.stripHttp_(event.value.value);
1042    },
1043
1044    /**
1045     * Removes the 'http://' from a URL, like the omnibox does. If the string
1046     * doesn't start with 'http://' it is returned unchanged.
1047     * @param {string} url The url to be processed
1048     * @return {string} The url with the 'http://' removed.
1049     */
1050    stripHttp_: function(url) {
1051      return url.replace(/^http:\/\//, '');
1052    },
1053
1054   /**
1055    * Shows the autoLaunch preference and initializes its checkbox value.
1056    * @param {bool} enabled Whether autolaunch is enabled or or not.
1057    * @private
1058    */
1059    updateAutoLaunchState_: function(enabled) {
1060      $('auto-launch-option').hidden = false;
1061      $('auto-launch').checked = enabled;
1062    },
1063
1064    /**
1065     * Called when the value of the download.default_directory preference
1066     * changes.
1067     * @param {Event} event Change event.
1068     * @private
1069     */
1070    onDefaultDownloadDirectoryChanged_: function(event) {
1071      $('downloadLocationPath').value = event.value.value;
1072      if (cr.isChromeOS) {
1073        // On ChromeOS, replace /special/drive-<hash>/root with "Google Drive"
1074        // for remote files, /home/chronos/user/Downloads or
1075        // /home/chronos/u-<hash>/Downloads with "Downloads" for local paths,
1076        // and '/' with ' \u203a ' (angled quote sign) everywhere. The modified
1077        // path is used only for display purpose.
1078        var path = $('downloadLocationPath').value;
1079        path = path.replace(/^\/special\/drive[^\/]*\/root/, 'Google Drive');
1080        path = path.replace(/^\/home\/chronos\/(user|u-[^\/]*)\//, '');
1081        path = path.replace(/\//g, ' \u203a ');
1082        $('downloadLocationPath').value = path;
1083      }
1084      $('download-location-label').classList.toggle('disabled',
1085                                                    event.value.disabled);
1086      $('downloadLocationChangeButton').disabled = event.value.disabled;
1087    },
1088
1089    /**
1090     * Update the Default Browsers section based on the current state.
1091     * @param {string} statusString Description of the current default state.
1092     * @param {boolean} isDefault Whether or not the browser is currently
1093     *     default.
1094     * @param {boolean} canBeDefault Whether or not the browser can be default.
1095     * @private
1096     */
1097    updateDefaultBrowserState_: function(statusString, isDefault,
1098                                         canBeDefault) {
1099      if (!cr.isChromeOS) {
1100        var label = $('default-browser-state');
1101        label.textContent = statusString;
1102
1103        $('set-as-default-browser').hidden = !canBeDefault || isDefault;
1104      }
1105    },
1106
1107    /**
1108     * Clears the search engine popup.
1109     * @private
1110     */
1111    clearSearchEngines_: function() {
1112      $('default-search-engine').textContent = '';
1113    },
1114
1115    /**
1116     * Updates the search engine popup with the given entries.
1117     * @param {Array} engines List of available search engines.
1118     * @param {number} defaultValue The value of the current default engine.
1119     * @param {boolean} defaultManaged Whether the default search provider is
1120     *     managed. If true, the default search provider can't be changed.
1121     * @private
1122     */
1123    updateSearchEngines_: function(engines, defaultValue, defaultManaged) {
1124      this.clearSearchEngines_();
1125      engineSelect = $('default-search-engine');
1126      engineSelect.disabled = defaultManaged;
1127      if (defaultManaged && defaultValue == -1)
1128        return;
1129      engineCount = engines.length;
1130      var defaultIndex = -1;
1131      for (var i = 0; i < engineCount; i++) {
1132        var engine = engines[i];
1133        var option = new Option(engine.name, engine.index);
1134        if (defaultValue == option.value)
1135          defaultIndex = i;
1136        engineSelect.appendChild(option);
1137      }
1138      if (defaultIndex >= 0)
1139        engineSelect.selectedIndex = defaultIndex;
1140    },
1141
1142    /**
1143     * Set the default search engine based on the popup selection.
1144     * @private
1145     */
1146    setDefaultSearchEngine_: function() {
1147      var engineSelect = $('default-search-engine');
1148      var selectedIndex = engineSelect.selectedIndex;
1149      if (selectedIndex >= 0) {
1150        var selection = engineSelect.options[selectedIndex];
1151        chrome.send('setDefaultSearchEngine', [String(selection.value)]);
1152      }
1153    },
1154
1155   /**
1156     * Sets or clear whether Chrome should Auto-launch on computer startup.
1157     * @private
1158     */
1159    handleAutoLaunchChanged_: function() {
1160      chrome.send('toggleAutoLaunch', [$('auto-launch').checked]);
1161    },
1162
1163    /**
1164     * Get the selected profile item from the profile list. This also works
1165     * correctly if the list is not displayed.
1166     * @return {Object} the profile item object, or null if nothing is selected.
1167     * @private
1168     */
1169    getSelectedProfileItem_: function() {
1170      var profilesList = $('profiles-list');
1171      if (profilesList.hidden) {
1172        if (profilesList.dataModel.length > 0)
1173          return profilesList.dataModel.item(0);
1174      } else {
1175        return profilesList.selectedItem;
1176      }
1177      return null;
1178    },
1179
1180    /**
1181     * Helper function to set the status of profile view buttons to disabled or
1182     * enabled, depending on the number of profiles and selection status of the
1183     * profiles list.
1184     * @private
1185     */
1186    setProfileViewButtonsStatus_: function() {
1187      var profilesList = $('profiles-list');
1188      var selectedProfile = profilesList.selectedItem;
1189      var hasSelection = selectedProfile != null;
1190      var hasSingleProfile = profilesList.dataModel.length == 1;
1191      var isManaged = loadTimeData.getBoolean('profileIsManaged');
1192      $('profiles-manage').disabled = !hasSelection ||
1193          !selectedProfile.isCurrentProfile;
1194      if (hasSelection && !selectedProfile.isCurrentProfile)
1195        $('profiles-manage').title = loadTimeData.getString('currentUserOnly');
1196      else
1197        $('profiles-manage').title = '';
1198      $('profiles-delete').disabled = isManaged ||
1199                                      (!hasSelection && !hasSingleProfile);
1200      if (OptionsPage.isSettingsApp()) {
1201        $('profiles-app-list-switch').disabled = !hasSelection ||
1202            selectedProfile.isCurrentProfile;
1203      }
1204      var importData = $('import-data');
1205      if (importData) {
1206        importData.disabled = $('import-data').disabled = hasSelection &&
1207          !selectedProfile.isCurrentProfile;
1208      }
1209    },
1210
1211    /**
1212     * Display the correct dialog layout, depending on how many profiles are
1213     * available.
1214     * @param {number} numProfiles The number of profiles to display.
1215     * @private
1216     */
1217    setProfileViewSingle_: function(numProfiles) {
1218      var hasSingleProfile = numProfiles == 1;
1219      $('profiles-list').hidden = hasSingleProfile;
1220      $('profiles-single-message').hidden = !hasSingleProfile;
1221      $('profiles-manage').hidden =
1222          hasSingleProfile || OptionsPage.isSettingsApp();
1223      $('profiles-delete').textContent = hasSingleProfile ?
1224          loadTimeData.getString('profilesDeleteSingle') :
1225          loadTimeData.getString('profilesDelete');
1226      if (OptionsPage.isSettingsApp())
1227        $('profiles-app-list-switch').hidden = hasSingleProfile;
1228    },
1229
1230    /**
1231     * Adds all |profiles| to the list.
1232     * @param {Array.<Object>} profiles An array of profile info objects.
1233     *     each object is of the form:
1234     *       profileInfo = {
1235     *         name: "Profile Name",
1236     *         iconURL: "chrome://path/to/icon/image",
1237     *         filePath: "/path/to/profile/data/on/disk",
1238     *         isCurrentProfile: false,
1239     *         isManaged: false
1240     *       };
1241     * @private
1242     */
1243    setProfilesInfo_: function(profiles) {
1244      this.setProfileViewSingle_(profiles.length);
1245      // add it to the list, even if the list is hidden so we can access it
1246      // later.
1247      $('profiles-list').dataModel = new ArrayDataModel(profiles);
1248
1249      // Received new data. If showing the "manage" overlay, keep it up to
1250      // date. If showing the "delete" overlay, close it.
1251      if (ManageProfileOverlay.getInstance().visible &&
1252          !$('manage-profile-overlay-manage').hidden) {
1253        ManageProfileOverlay.showManageDialog();
1254      } else {
1255        ManageProfileOverlay.getInstance().visible = false;
1256      }
1257
1258      this.setProfileViewButtonsStatus_();
1259    },
1260
1261    /**
1262     * Reports managed user import errors to the ManagedUserImportOverlay.
1263     * @param {string} error The error message to display.
1264     * @private
1265     */
1266    showManagedUserImportError_: function(error) {
1267      ManagedUserImportOverlay.onError(error);
1268    },
1269
1270    /**
1271     * Reports successful importing of a managed user to
1272     * the ManagedUserImportOverlay.
1273     * @private
1274     */
1275    showManagedUserImportSuccess_: function() {
1276      ManagedUserImportOverlay.onSuccess();
1277    },
1278
1279    /**
1280     * Reports an error to the "create" overlay during profile creation.
1281     * @param {string} error The error message to display.
1282     * @private
1283     */
1284    showCreateProfileError_: function(error) {
1285      CreateProfileOverlay.onError(error);
1286    },
1287
1288    /**
1289    * Sends a warning message to the "create" overlay during profile creation.
1290    * @param {string} warning The warning message to display.
1291    * @private
1292    */
1293    showCreateProfileWarning_: function(warning) {
1294      CreateProfileOverlay.onWarning(warning);
1295    },
1296
1297    /**
1298    * Reports successful profile creation to the "create" overlay.
1299     * @param {Object} profileInfo An object of the form:
1300     *     profileInfo = {
1301     *       name: "Profile Name",
1302     *       filePath: "/path/to/profile/data/on/disk"
1303     *       isManaged: (true|false),
1304     *     };
1305    * @private
1306    */
1307    showCreateProfileSuccess_: function(profileInfo) {
1308      CreateProfileOverlay.onSuccess(profileInfo);
1309    },
1310
1311    /**
1312     * Returns the currently active profile for this browser window.
1313     * @return {Object} A profile info object.
1314     * @private
1315     */
1316    getCurrentProfile_: function() {
1317      for (var i = 0; i < $('profiles-list').dataModel.length; i++) {
1318        var profile = $('profiles-list').dataModel.item(i);
1319        if (profile.isCurrentProfile)
1320          return profile;
1321      }
1322
1323      assert(false,
1324             'There should always be a current profile, but none found.');
1325    },
1326
1327    /**
1328     * Propmpts user to confirm deletion of the profile for this browser
1329     * window.
1330     * @private
1331     */
1332    deleteCurrentProfile_: function() {
1333      ManageProfileOverlay.showDeleteDialog(this.getCurrentProfile_());
1334    },
1335
1336    setNativeThemeButtonEnabled_: function(enabled) {
1337      var button = $('themes-native-button');
1338      if (button)
1339        button.disabled = !enabled;
1340    },
1341
1342    setThemesResetButtonEnabled_: function(enabled) {
1343      $('themes-reset').disabled = !enabled;
1344    },
1345
1346    setAccountPictureManaged_: function(managed) {
1347      var picture = $('account-picture');
1348      if (managed || UIAccountTweaks.loggedInAsGuest()) {
1349        picture.disabled = true;
1350        ChangePictureOptions.closeOverlay();
1351      } else {
1352        picture.disabled = false;
1353      }
1354
1355      // Create a synthetic pref change event decorated as
1356      // CoreOptionsHandler::CreateValueForPref() does.
1357      var event = new Event('account-picture');
1358      if (managed)
1359        event.value = { controlledBy: 'policy' };
1360      else
1361        event.value = {};
1362      $('account-picture-indicator').handlePrefChange(event);
1363    },
1364
1365    /**
1366     * (Re)loads IMG element with current user account picture.
1367     * @private
1368     */
1369    updateAccountPicture_: function() {
1370      var picture = $('account-picture');
1371      if (picture) {
1372        picture.src = 'chrome://userimage/' + this.username_ + '?id=' +
1373            Date.now();
1374      }
1375    },
1376
1377    setWallpaperManaged_: function(managed) {
1378      var button = $('set-wallpaper');
1379      button.disabled = !!managed;
1380
1381      // Create a synthetic pref change event decorated as
1382      // CoreOptionsHandler::CreateValueForPref() does.
1383      var event = new Event('wallpaper');
1384      if (managed)
1385        event.value = { controlledBy: 'policy' };
1386      else
1387        event.value = {};
1388      $('wallpaper-indicator').handlePrefChange(event);
1389    },
1390
1391    /**
1392     * Handle the 'add device' button click.
1393     * @private
1394     */
1395    handleAddBluetoothDevice_: function() {
1396      chrome.send('findBluetoothDevices');
1397      OptionsPage.showPageByName('bluetooth', false);
1398    },
1399
1400    /**
1401     * Enables or disables the Manage SSL Certificates button.
1402     * @private
1403     */
1404    enableCertificateButton_: function(enabled) {
1405      $('certificatesManageButton').disabled = !enabled;
1406    },
1407
1408    /**
1409     * Enables factory reset section.
1410     * @private
1411     */
1412    enableFactoryResetSection_: function() {
1413      $('factory-reset-section').hidden = false;
1414    },
1415
1416    /**
1417     * Set the checked state of the metrics reporting checkbox.
1418     * @private
1419     */
1420    setMetricsReportingCheckboxState_: function(checked, disabled) {
1421      $('metricsReportingEnabled').checked = checked;
1422      $('metricsReportingEnabled').disabled = disabled;
1423    },
1424
1425    /**
1426     * @private
1427     */
1428    setMetricsReportingSettingVisibility_: function(visible) {
1429      if (visible)
1430        $('metricsReportingSetting').style.display = 'block';
1431      else
1432        $('metricsReportingSetting').style.display = 'none';
1433    },
1434
1435    /**
1436     * Set the font size selected item. This item actually reflects two
1437     * preferences: the default font size and the default fixed font size.
1438     *
1439     * @param {Object} pref Information about the font size preferences.
1440     * @param {number} pref.value The value of the default font size pref.
1441     * @param {boolean} pref.disabled True if either pref not user modifiable.
1442     * @param {string} pref.controlledBy The source of the pref value(s) if
1443     *     either pref is currently not controlled by the user.
1444     * @private
1445     */
1446    setFontSize_: function(pref) {
1447      var selectCtl = $('defaultFontSize');
1448      selectCtl.disabled = pref.disabled;
1449      // Create a synthetic pref change event decorated as
1450      // CoreOptionsHandler::CreateValueForPref() does.
1451      var event = new Event('synthetic-font-size');
1452      event.value = {
1453        value: pref.value,
1454        controlledBy: pref.controlledBy,
1455        disabled: pref.disabled
1456      };
1457      $('font-size-indicator').handlePrefChange(event);
1458
1459      for (var i = 0; i < selectCtl.options.length; i++) {
1460        if (selectCtl.options[i].value == pref.value) {
1461          selectCtl.selectedIndex = i;
1462          if ($('Custom'))
1463            selectCtl.remove($('Custom').index);
1464          return;
1465        }
1466      }
1467
1468      // Add/Select Custom Option in the font size label list.
1469      if (!$('Custom')) {
1470        var option = new Option(loadTimeData.getString('fontSizeLabelCustom'),
1471                                -1, false, true);
1472        option.setAttribute('id', 'Custom');
1473        selectCtl.add(option);
1474      }
1475      $('Custom').selected = true;
1476    },
1477
1478    /**
1479     * Populate the page zoom selector with values received from the caller.
1480     * @param {Array} items An array of items to populate the selector.
1481     *     each object is an array with three elements as follows:
1482     *       0: The title of the item (string).
1483     *       1: The value of the item (number).
1484     *       2: Whether the item should be selected (boolean).
1485     * @private
1486     */
1487    setupPageZoomSelector_: function(items) {
1488      var element = $('defaultZoomFactor');
1489
1490      // Remove any existing content.
1491      element.textContent = '';
1492
1493      // Insert new child nodes into select element.
1494      var value, title, selected;
1495      for (var i = 0; i < items.length; i++) {
1496        title = items[i][0];
1497        value = items[i][1];
1498        selected = items[i][2];
1499        element.appendChild(new Option(title, value, false, selected));
1500      }
1501    },
1502
1503    /**
1504     * Shows/hides the autoOpenFileTypesResetToDefault button and label, with
1505     * animation.
1506     * @param {boolean} display Whether to show the button and label or not.
1507     * @private
1508     */
1509    setAutoOpenFileTypesDisplayed_: function(display) {
1510      if ($('advanced-settings').hidden) {
1511        // If the Advanced section is hidden, don't animate the transition.
1512        $('auto-open-file-types-section').hidden = !display;
1513      } else {
1514        if (display) {
1515          this.showSectionWithAnimation_(
1516              $('auto-open-file-types-section'),
1517              $('auto-open-file-types-container'));
1518        } else {
1519          this.hideSectionWithAnimation_(
1520              $('auto-open-file-types-section'),
1521              $('auto-open-file-types-container'));
1522        }
1523      }
1524    },
1525
1526    /**
1527     * Set the enabled state for the proxy settings button and its associated
1528     * message when extension controlled.
1529     * @param {boolean} disabled Whether the button should be disabled.
1530     * @param {boolean} extensionControlled Whether the proxy is extension
1531     *     controlled.
1532     * @private
1533     */
1534    setupProxySettingsButton_: function(disabled, extensionControlled) {
1535      if (!cr.isChromeOS) {
1536        $('proxiesConfigureButton').disabled = disabled;
1537        $('proxiesLabel').textContent =
1538            loadTimeData.getString(extensionControlled ?
1539                'proxiesLabelExtension' : 'proxiesLabelSystem');
1540      }
1541    },
1542
1543    /**
1544     * Set the initial state of the spoken feedback checkbox.
1545     * @private
1546     */
1547    setSpokenFeedbackCheckboxState_: function(checked) {
1548      $('accessibility-spoken-feedback-check').checked = checked;
1549    },
1550
1551    /**
1552     * Set the initial state of the high contrast checkbox.
1553     * @private
1554     */
1555    setHighContrastCheckboxState_: function(checked) {
1556      $('accessibility-high-contrast-check').checked = checked;
1557    },
1558
1559    /**
1560     * Set the initial state of the virtual keyboard checkbox.
1561     * @private
1562     */
1563    setVirtualKeyboardCheckboxState_: function(checked) {
1564      // TODO(zork): Update UI
1565    },
1566
1567    /**
1568     * Show/hide mouse settings slider.
1569     * @private
1570     */
1571    showMouseControls_: function(show) {
1572      $('mouse-settings').hidden = !show;
1573    },
1574
1575    /**
1576     * Adds hidden warning boxes for settings potentially controlled by
1577     * extensions.
1578     * @param {string} parentDiv The div name to append the bubble to.
1579     * @param {string} bubbleId The ID to use for the bubble.
1580     * @param {boolean} first Add as first node if true, otherwise last.
1581     * @private
1582     */
1583    addExtensionControlledBox_: function(parentDiv, bubbleId, first) {
1584      var bubble = $('extension-controlled-warning-template').cloneNode(true);
1585      bubble.id = bubbleId;
1586      var parent = $(parentDiv);
1587      if (first)
1588        parent.insertBefore(bubble, parent.firstChild);
1589      else
1590        parent.appendChild(bubble);
1591    },
1592
1593    /**
1594     * Adds a bubble showing that an extension is controlling a particular
1595     * setting.
1596     * @param {string} parentDiv The div name to append the bubble to.
1597     * @param {string} bubbleId The ID to use for the bubble.
1598     * @param {string} extensionId The ID of the controlling extension.
1599     * @param {string} extensionName The name of the controlling extension.
1600     * @private
1601     */
1602    toggleExtensionControlledBox_: function(
1603        parentDiv, bubbleId, extensionId, extensionName) {
1604      var bubble = $(bubbleId);
1605      assert(bubble);
1606      bubble.hidden = extensionId.length == 0;
1607      if (bubble.hidden)
1608        return;
1609
1610      // Set the extension image.
1611      var div = bubble.firstElementChild;
1612      div.style.backgroundImage =
1613          'url(chrome://extension-icon/' + extensionId + '/24/1)';
1614
1615      // Set the bubble label.
1616      var label = loadTimeData.getStringF('extensionControlled', extensionName);
1617      var docFrag = parseHtmlSubset('<div>' + label + '</div>', ['B', 'DIV']);
1618      div.innerHTML = docFrag.firstChild.innerHTML;
1619
1620      // Wire up the button to disable the right extension.
1621      var button = div.nextElementSibling;
1622      button.dataset.extensionId = extensionId;
1623    },
1624
1625    /**
1626     * Toggles the warning boxes that show which extension is controlling
1627     * various settings of Chrome.
1628     * @param {object} details A dictionary of ID+name pairs for each of the
1629     *     settings controlled by an extension.
1630     * @private
1631     */
1632    toggleExtensionIndicators_: function(details) {
1633      this.toggleExtensionControlledBox_('search-section-content',
1634                                         'search-engine-controlled',
1635                                         details.searchEngine.id,
1636                                         details.searchEngine.name);
1637      this.toggleExtensionControlledBox_('extension-controlled-container',
1638                                         'homepage-controlled',
1639                                         details.homePage.id,
1640                                         details.homePage.name);
1641      this.toggleExtensionControlledBox_('startup-section-content',
1642                                         'startpage-controlled',
1643                                         details.startUpPage.id,
1644                                         details.startUpPage.name);
1645      this.toggleExtensionControlledBox_('newtab-section-content',
1646                                         'newtab-controlled',
1647                                         details.newTabPage.id,
1648                                         details.newTabPage.name);
1649      this.toggleExtensionControlledBox_('proxy-section-content',
1650                                         'proxy-controlled',
1651                                         details.proxy.id,
1652                                         details.proxy.name);
1653
1654      // The proxy section contains just the warning box and nothing else, so
1655      // if we're hiding the proxy warning box, we should also hide its header
1656      // section.
1657      $('proxy-section').hidden = details.proxy.id.length == 0;
1658    },
1659
1660
1661    /**
1662     * Show/hide touchpad-related settings.
1663     * @private
1664     */
1665    showTouchpadControls_: function(show) {
1666      $('touchpad-settings').hidden = !show;
1667      $('accessibility-tap-dragging').hidden = !show;
1668    },
1669
1670    /**
1671     * Activate the Bluetooth settings section on the System settings page.
1672     * @private
1673     */
1674    showBluetoothSettings_: function() {
1675      $('bluetooth-devices').hidden = false;
1676    },
1677
1678    /**
1679     * Dectivates the Bluetooth settings section from the System settings page.
1680     * @private
1681     */
1682    hideBluetoothSettings_: function() {
1683      $('bluetooth-devices').hidden = true;
1684    },
1685
1686    /**
1687     * Sets the state of the checkbox indicating if Bluetooth is turned on. The
1688     * state of the "Find devices" button and the list of discovered devices may
1689     * also be affected by a change to the state.
1690     * @param {boolean} checked Flag Indicating if Bluetooth is turned on.
1691     * @private
1692     */
1693    setBluetoothState_: function(checked) {
1694      $('enable-bluetooth').checked = checked;
1695      $('bluetooth-paired-devices-list').parentNode.hidden = !checked;
1696      $('bluetooth-add-device').hidden = !checked;
1697      $('bluetooth-reconnect-device').hidden = !checked;
1698      // Flush list of previously discovered devices if bluetooth is turned off.
1699      if (!checked) {
1700        $('bluetooth-paired-devices-list').clear();
1701        $('bluetooth-unpaired-devices-list').clear();
1702      } else {
1703        chrome.send('getPairedBluetoothDevices');
1704      }
1705    },
1706
1707    /**
1708     * Adds an element to the list of available Bluetooth devices. If an element
1709     * with a matching address is found, the existing element is updated.
1710     * @param {{name: string,
1711     *          address: string,
1712     *          paired: boolean,
1713     *          connected: boolean}} device
1714     *     Decription of the Bluetooth device.
1715     * @private
1716     */
1717    addBluetoothDevice_: function(device) {
1718      var list = $('bluetooth-unpaired-devices-list');
1719      // Display the "connecting" (already paired or not yet paired) and the
1720      // paired devices in the same list.
1721      if (device.paired || device.connecting) {
1722        // Test to see if the device is currently in the unpaired list, in which
1723        // case it should be removed from that list.
1724        var index = $('bluetooth-unpaired-devices-list').find(device.address);
1725        if (index != undefined)
1726          $('bluetooth-unpaired-devices-list').deleteItemAtIndex(index);
1727        list = $('bluetooth-paired-devices-list');
1728      } else {
1729        // Test to see if the device is currently in the paired list, in which
1730        // case it should be removed from that list.
1731        var index = $('bluetooth-paired-devices-list').find(device.address);
1732        if (index != undefined)
1733          $('bluetooth-paired-devices-list').deleteItemAtIndex(index);
1734      }
1735      list.appendDevice(device);
1736
1737      // One device can be in the process of pairing.  If found, display
1738      // the Bluetooth pairing overlay.
1739      if (device.pairing)
1740        BluetoothPairing.showDialog(device);
1741    },
1742
1743    /**
1744     * Removes an element from the list of available devices.
1745     * @param {string} address Unique address of the device.
1746     * @private
1747     */
1748    removeBluetoothDevice_: function(address) {
1749      var index = $('bluetooth-unpaired-devices-list').find(address);
1750      if (index != undefined) {
1751        $('bluetooth-unpaired-devices-list').deleteItemAtIndex(index);
1752      } else {
1753        index = $('bluetooth-paired-devices-list').find(address);
1754        if (index != undefined)
1755          $('bluetooth-paired-devices-list').deleteItemAtIndex(index);
1756      }
1757    },
1758
1759    /**
1760     * Shows the overlay dialog for changing the user avatar image.
1761     * @private
1762     */
1763    showImagerPickerOverlay_: function() {
1764      OptionsPage.navigateToPage('changePicture');
1765    },
1766
1767    /**
1768     * Shows (or not) the "User" section of the settings page based on whether
1769     * any of the sub-sections are present (or not).
1770     * @private
1771     */
1772    maybeShowUserSection_: function() {
1773      $('sync-users-section').hidden =
1774          $('profiles-section').hidden &&
1775          $('sync-section').hidden &&
1776          $('profiles-supervised-dashboard-tip').hidden;
1777    },
1778
1779    /**
1780     * Updates the date and time section with time sync information.
1781     * @param {boolean} canSetTime Whether the system time can be set.
1782     * @private
1783     */
1784    setCanSetTime_: function(canSetTime) {
1785      // If the time has been network-synced, it cannot be set manually.
1786      $('time-synced-explanation').hidden = canSetTime;
1787      $('set-time').hidden = !canSetTime;
1788    },
1789
1790    /**
1791     * Handle the 'set date and time' button click.
1792     * @private
1793     */
1794    handleSetTime_: function() {
1795      chrome.send('showSetTime');
1796    },
1797  };
1798
1799  //Forward public APIs to private implementations.
1800  [
1801    'addBluetoothDevice',
1802    'deleteCurrentProfile',
1803    'enableCertificateButton',
1804    'enableFactoryResetSection',
1805    'getCurrentProfile',
1806    'getStartStopSyncButton',
1807    'hideBluetoothSettings',
1808    'notifyInitializationComplete',
1809    'removeBluetoothDevice',
1810    'scrollToSection',
1811    'setAccountPictureManaged',
1812    'setWallpaperManaged',
1813    'setAutoOpenFileTypesDisplayed',
1814    'setBluetoothState',
1815    'setCanSetTime',
1816    'setFontSize',
1817    'setNativeThemeButtonEnabled',
1818    'setHighContrastCheckboxState',
1819    'setMetricsReportingCheckboxState',
1820    'setMetricsReportingSettingVisibility',
1821    'setProfilesInfo',
1822    'setSpokenFeedbackCheckboxState',
1823    'setThemesResetButtonEnabled',
1824    'setVirtualKeyboardCheckboxState',
1825    'setupPageZoomSelector',
1826    'setupProxySettingsButton',
1827    'showBluetoothSettings',
1828    'showCreateProfileError',
1829    'showCreateProfileSuccess',
1830    'showCreateProfileWarning',
1831    'showHotwordSection',
1832    'showManagedUserImportError',
1833    'showManagedUserImportSuccess',
1834    'showMouseControls',
1835    'showTouchpadControls',
1836    'toggleExtensionIndicators',
1837    'updateAccountPicture',
1838    'updateAutoLaunchState',
1839    'updateDefaultBrowserState',
1840    'updateEasyUnlock',
1841    'updateManagesSupervisedUsers',
1842    'updateSearchEngines',
1843    'updateStartupPages',
1844    'updateSyncState',
1845  ].forEach(function(name) {
1846    BrowserOptions[name] = function() {
1847      var instance = BrowserOptions.getInstance();
1848      return instance[name + '_'].apply(instance, arguments);
1849    };
1850  });
1851
1852  if (cr.isChromeOS) {
1853    /**
1854     * Returns username (canonical email) of the user logged in (ChromeOS only).
1855     * @return {string} user email.
1856     */
1857    // TODO(jhawkins): Investigate the use case for this method.
1858    BrowserOptions.getLoggedInUsername = function() {
1859      return BrowserOptions.getInstance().username_;
1860    };
1861  }
1862
1863  // Export
1864  return {
1865    BrowserOptions: BrowserOptions
1866  };
1867});
1868