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