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