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