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