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.
5 cr.exportPath('options');
8 * @typedef {{actionLinkText: (string|undefined),
9 * childUser: (boolean|undefined),
10 * hasError: (boolean|undefined),
11 * hasUnrecoverableError: (boolean|undefined),
12 * managed: (boolean|undefined),
13 * setupCompleted: (boolean|undefined),
14 * setupInProgress: (boolean|undefined),
15 * signedIn: (boolean|undefined),
16 * signinAllowed: (boolean|undefined),
17 * signoutAllowed: (boolean|undefined),
18 * statusText: (string|undefined),
19 * supervisedUser: (boolean|undefined),
20 * syncSystemEnabled: (boolean|undefined)}}
21 * @see chrome/browser/ui/webui/options/browser_options_handler.cc
26 * @typedef {{id: string, name: string}}
28 options.ExtensionData;
31 * @typedef {{name: string,
33 * isCurrentProfile: boolean,
34 * isSupervised: boolean,
37 * @see chrome/browser/ui/webui/options/browser_options_handler.cc
41 cr.define('options', function() {
42 var OptionsPage = options.OptionsPage;
43 var Page = cr.ui.pageManager.Page;
44 var PageManager = cr.ui.pageManager.PageManager;
45 var ArrayDataModel = cr.ui.ArrayDataModel;
46 var RepeatingButton = cr.ui.RepeatingButton;
47 var HotwordSearchSettingIndicator = options.HotwordSearchSettingIndicator;
48 var NetworkPredictionOptions = {
56 * Encapsulated handling of browser options page.
58 * @extends {cr.ui.pageManager.Page}
60 function BrowserOptions() {
61 Page.call(this, 'settings', loadTimeData.getString('settingsTitle'),
65 cr.addSingletonGetter(BrowserOptions);
68 * @param {HTMLElement} section The section to show or hide.
69 * @return {boolean} Whether the section should be shown.
72 BrowserOptions.shouldShowSection_ = function(section) {
73 // If the section is hidden or hiding, it should be shown.
74 return section.style.height == '' || section.style.height == '0px';
77 BrowserOptions.prototype = {
78 __proto__: Page.prototype,
81 * Keeps track of whether the user is signed in or not.
88 * Indicates whether signing out is allowed or whether a complete profile
89 * wipe is required to remove the current enterprise account.
93 signoutAllowed_: true,
96 * Keeps track of whether |onShowHomeButtonChanged_| has been called. See
97 * |onShowHomeButtonChanged_|.
101 onShowHomeButtonChangedCalled_: false,
104 * Track if page initialization is complete. All C++ UI handlers have the
105 * chance to manipulate page content within their InitializePage methods.
106 * This flag is set to true after all initializers have been called.
110 initializationComplete_: false,
113 * Current status of "Resolve Timezone by Geolocation" checkbox.
116 resolveTimezoneByGeolocation_: false,
119 * True if system timezone is managed by policy.
122 systemTimezoneIsManaged_: false,
125 initializePage: function() {
126 Page.prototype.initializePage.call(this);
129 if (window.top != window) {
130 // The options page is not in its own window.
131 document.body.classList.add('uber-frame');
132 PageManager.horizontalOffset = 155;
135 // Ensure that navigation events are unblocked on uber page. A reload of
136 // the settings page while an overlay is open would otherwise leave uber
137 // page in a blocked state, where tab switching is not possible.
138 uber.invokeMethodOnParent('stopInterceptingEvents');
140 window.addEventListener('message', this.handleWindowMessage_.bind(this));
142 if (loadTimeData.getBoolean('allowAdvancedSettings')) {
143 $('advanced-settings-expander').onclick = function(e) {
145 BrowserOptions.shouldShowSection_($('advanced-settings'));
147 chrome.send('coreOptionsUserMetricsAction',
148 ['Options_ShowAdvancedSettings']);
150 self.toggleSectionWithAnimation_(
151 $('advanced-settings'),
152 $('advanced-settings-container'));
154 // If the click was triggered using the keyboard and it showed the
155 // section (rather than hiding it), focus the first element in the
157 if (e.detail == 0 && showAdvanced) {
158 var focusElement = $('advanced-settings-container').querySelector(
159 'button, input, list, select, a[href]');
161 focusElement.focus();
165 $('advanced-settings-footer').hidden = true;
166 $('advanced-settings').hidden = true;
169 $('advanced-settings').addEventListener('webkitTransitionEnd',
170 this.updateAdvancedSettingsExpander_.bind(this));
172 if (loadTimeData.getBoolean('showAbout')) {
173 $('about-button').hidden = false;
174 $('about-button').addEventListener('click', function() {
175 PageManager.showPageByName('help');
176 chrome.send('coreOptionsUserMetricsAction',
182 UIAccountTweaks.applyGuestSessionVisibility(document);
183 UIAccountTweaks.applyPublicSessionVisibility(document);
184 if (loadTimeData.getBoolean('secondaryUser'))
185 $('secondary-user-banner').hidden = false;
188 // Sync (Sign in) section.
189 this.updateSyncState_(loadTimeData.getValue('syncData'));
191 $('start-stop-sync').onclick = function(event) {
192 if (self.signedIn_) {
193 if (self.signoutAllowed_)
194 SyncSetupOverlay.showStopSyncingUI();
196 chrome.send('showDisconnectManagedProfileDialog');
197 } else if (cr.isChromeOS) {
198 SyncSetupOverlay.showSetupUI();
200 SyncSetupOverlay.startSignIn();
203 $('customize-sync').onclick = function(event) {
204 SyncSetupOverlay.showSetupUI();
207 // Internet connection section (ChromeOS only).
209 options.network.NetworkList.decorate($('network-list'));
210 // Show that the network settings are shared if this is a secondary user
211 // in a multi-profile session.
212 if (loadTimeData.getBoolean('secondaryUser')) {
213 var networkIndicator = document.querySelector(
214 '#network-section-header > .controlled-setting-indicator');
215 networkIndicator.setAttribute('controlled-by', 'shared');
216 networkIndicator.location = cr.ui.ArrowLocation.TOP_START;
220 // On Startup section.
221 Preferences.getInstance().addEventListener('session.restore_on_startup',
222 this.onRestoreOnStartupChanged_.bind(this));
223 Preferences.getInstance().addEventListener(
224 'session.startup_urls',
226 $('startup-set-pages').disabled = event.value.disabled;
229 $('startup-set-pages').onclick = function() {
230 PageManager.showPageByName('startup');
233 // Appearance section.
234 Preferences.getInstance().addEventListener('browser.show_home_button',
235 this.onShowHomeButtonChanged_.bind(this));
237 Preferences.getInstance().addEventListener('homepage',
238 this.onHomePageChanged_.bind(this));
239 Preferences.getInstance().addEventListener('homepage_is_newtabpage',
240 this.onHomePageIsNtpChanged_.bind(this));
242 $('change-home-page').onclick = function(event) {
243 PageManager.showPageByName('homePageOverlay');
244 chrome.send('coreOptionsUserMetricsAction',
245 ['Options_Homepage_ShowSettings']);
248 HotwordSearchSettingIndicator.decorate(
249 $('hotword-search-setting-indicator'));
250 HotwordSearchSettingIndicator.decorate(
251 $('hotword-no-dsp-search-setting-indicator'));
252 var hotwordIndicator = $('hotword-always-on-search-setting-indicator');
253 HotwordSearchSettingIndicator.decorate(hotwordIndicator);
254 hotwordIndicator.disabledOnErrorSection =
255 $('hotword-always-on-search-checkbox');
256 chrome.send('requestHotwordAvailable');
258 chrome.send('requestGoogleNowAvailable');
260 if ($('set-wallpaper')) {
261 $('set-wallpaper').onclick = function(event) {
262 chrome.send('openWallpaperManager');
263 chrome.send('coreOptionsUserMetricsAction',
264 ['Options_OpenWallpaperManager']);
268 // Control the hotword-always-on pref with the Hotword Audio
270 $('hotword-always-on-search-checkbox').customChangeHandler =
272 if (!$('hotword-always-on-search-checkbox').checked)
275 $('hotword-always-on-search-checkbox').checked = false;
276 chrome.send('launchHotwordAudioVerificationApp', [false]);
280 // Open the Hotword Audio Verification app to retrain a voice model.
281 $('hotword-retrain-link').onclick = function(event) {
282 chrome.send('launchHotwordAudioVerificationApp', [true]);
284 Preferences.getInstance().addEventListener(
285 'hotword.always_on_search_enabled',
286 this.onHotwordAlwaysOnChanged_.bind(this));
288 $('themes-gallery').onclick = function(event) {
289 window.open(loadTimeData.getString('themesGalleryURL'));
290 chrome.send('coreOptionsUserMetricsAction',
291 ['Options_ThemesGallery']);
293 $('themes-reset').onclick = function(event) {
294 chrome.send('themesReset');
297 if (loadTimeData.getBoolean('profileIsSupervised')) {
298 if ($('themes-native-button')) {
299 $('themes-native-button').disabled = true;
300 $('themes-native-button').hidden = true;
302 // Supervised users have just one default theme, even on Linux. So use
303 // the same button for Linux as for the other platforms.
304 $('themes-reset').textContent = loadTimeData.getString('themesReset');
307 // Device section (ChromeOS only).
309 if (loadTimeData.getBoolean('showPowerStatus')) {
310 $('power-settings-link').onclick = function(evt) {
311 PageManager.showPageByName('power-overlay');
312 chrome.send('coreOptionsUserMetricsAction',
313 ['Options_ShowPowerSettings']);
315 $('power-row').hidden = false;
317 $('keyboard-settings-button').onclick = function(evt) {
318 PageManager.showPageByName('keyboard-overlay');
319 chrome.send('coreOptionsUserMetricsAction',
320 ['Options_ShowKeyboardSettings']);
322 $('pointer-settings-button').onclick = function(evt) {
323 PageManager.showPageByName('pointer-overlay');
324 chrome.send('coreOptionsUserMetricsAction',
325 ['Options_ShowTouchpadSettings']);
330 $('manage-default-search-engines').onclick = function(event) {
331 PageManager.showPageByName('searchEngines');
332 chrome.send('coreOptionsUserMetricsAction',
333 ['Options_ManageSearchEngines']);
335 $('default-search-engine').addEventListener('change',
336 this.setDefaultSearchEngine_);
339 if (loadTimeData.valueExists('profilesInfo')) {
340 $('profiles-section').hidden = false;
341 this.maybeShowUserSection_();
343 var profilesList = $('profiles-list');
344 options.browser_options.ProfileList.decorate(profilesList);
345 profilesList.autoExpands = true;
347 // The profiles info data in |loadTimeData| might be stale.
348 this.setProfilesInfo_(loadTimeData.getValue('profilesInfo'));
349 chrome.send('requestProfilesInfo');
351 profilesList.addEventListener('change',
352 this.setProfileViewButtonsStatus_);
353 $('profiles-create').onclick = function(event) {
354 chrome.send('metricsHandler:recordAction',
355 ['Options_ShowCreateProfileDlg']);
356 ManageProfileOverlay.showCreateDialog();
358 if (OptionsPage.isSettingsApp()) {
359 $('profiles-app-list-switch').onclick = function(event) {
360 var selectedProfile = self.getSelectedProfileItem_();
361 chrome.send('switchAppListProfile', [selectedProfile.filePath]);
364 $('profiles-manage').onclick = function(event) {
365 chrome.send('metricsHandler:recordAction',
366 ['Options_ShowEditProfileDlg']);
367 ManageProfileOverlay.showManageDialog();
369 $('profiles-delete').onclick = function(event) {
370 var selectedProfile = self.getSelectedProfileItem_();
371 if (selectedProfile) {
372 chrome.send('metricsHandler:recordAction',
373 ['Options_ShowDeleteProfileDlg']);
374 ManageProfileOverlay.showDeleteDialog(selectedProfile);
377 if (loadTimeData.getBoolean('profileIsSupervised')) {
378 $('profiles-create').disabled = true;
380 if (!loadTimeData.getBoolean('allowProfileDeletion')) {
381 $('profiles-delete').disabled = true;
382 $('profiles-list').canDeleteItems = false;
387 // Username (canonical email) of the currently logged in user or
388 // |kGuestUser| if a guest session is active.
389 this.username_ = loadTimeData.getString('username');
391 this.updateAccountPicture_();
393 $('account-picture').onclick = this.showImagerPickerOverlay_;
394 $('change-picture-caption').onclick = this.showImagerPickerOverlay_;
396 $('manage-accounts-button').onclick = function(event) {
397 PageManager.showPageByName('accounts');
398 chrome.send('coreOptionsUserMetricsAction',
399 ['Options_ManageAccounts']);
402 $('import-data').onclick = function(event) {
403 ImportDataOverlay.show();
404 chrome.send('coreOptionsUserMetricsAction', ['Import_ShowDlg']);
407 if ($('themes-native-button')) {
408 $('themes-native-button').onclick = function(event) {
409 chrome.send('themesSetNative');
414 // Date and time section (CrOS only).
416 if ($('set-time-button'))
417 $('set-time-button').onclick = this.handleSetTime_.bind(this);
420 if (loadTimeData.getBoolean('enableTimeZoneTrackingOption')) {
421 $('resolve-timezone-by-geolocation-selection').hidden = false;
422 this.resolveTimezoneByGeolocation_ = loadTimeData.getBoolean(
423 'resolveTimezoneByGeolocationInitialValue');
424 this.updateTimezoneSectionState_();
425 Preferences.getInstance().addEventListener(
426 'settings.resolve_timezone_by_geolocation',
427 this.onResolveTimezoneByGeolocationChanged_.bind(this));
431 // Default browser section.
432 if (!cr.isChromeOS) {
433 if (!loadTimeData.getBoolean('showSetDefault')) {
434 $('set-default-browser-section').hidden = true;
436 $('set-as-default-browser').onclick = function(event) {
437 chrome.send('becomeDefaultBrowser');
440 $('auto-launch').onclick = this.handleAutoLaunchChanged_;
444 $('privacyContentSettingsButton').onclick = function(event) {
445 PageManager.showPageByName('content');
446 OptionsPage.showTab($('cookies-nav-tab'));
447 chrome.send('coreOptionsUserMetricsAction',
448 ['Options_ContentSettings']);
450 $('privacyClearDataButton').onclick = function(event) {
451 PageManager.showPageByName('clearBrowserData');
452 chrome.send('coreOptionsUserMetricsAction', ['Options_ClearData']);
454 $('privacyClearDataButton').hidden = OptionsPage.isSettingsApp();
455 // 'metricsReportingEnabled' element is only present on Chrome branded
456 // builds, and the 'metricsReportingCheckboxAction' message is only
457 // handled on ChromeOS.
458 if ($('metrics-reporting-enabled') && cr.isChromeOS) {
459 $('metrics-reporting-enabled').onclick = function(event) {
460 chrome.send('metricsReportingCheckboxAction',
461 [String(event.currentTarget.checked)]);
464 if ($('metrics-reporting-enabled') && !cr.isChromeOS) {
465 // The localized string has the | symbol on each side of the text that
466 // needs to be made into a button to restart Chrome. We parse the text
467 // and build the button from that.
468 var restartTextFragments =
469 loadTimeData.getString('metricsReportingResetRestart').split('|');
470 // Assume structure is something like "starting text |link text| ending
471 // text" where both starting text and ending text may or may not be
472 // present, but the split should always be in three pieces.
473 var restartElements =
474 $('metrics-reporting-reset-restart').querySelectorAll('*');
475 for (var i = 0; i < restartTextFragments.length; i++) {
476 restartElements[i].textContent = restartTextFragments[i];
478 restartElements[1].onclick = function(event) {
479 chrome.send('restartBrowser');
481 $('metrics-reporting-enabled').onclick = function(event) {
482 chrome.send('metricsReportingCheckboxChanged',
483 [Boolean(event.currentTarget.checked)]);
485 // A browser restart is never needed to toggle metrics reporting,
486 // and is only needed to toggle crash reporting when using Breakpad.
487 // Crashpad, used on Mac, does not require a browser restart.
490 $('metrics-reporting-reset-restart').hidden =
491 loadTimeData.getBoolean('metricsReportingEnabledAtStart') ==
492 $('metrics-reporting-enabled').checked;
494 $('metrics-reporting-enabled').checked =
495 loadTimeData.getBoolean('metricsReportingEnabledAtStart');
497 $('networkPredictionOptions').onchange = function(event) {
498 var value = (event.target.checked ?
499 NetworkPredictionOptions.WIFI_ONLY :
500 NetworkPredictionOptions.NEVER);
501 var metric = event.target.metric;
502 Preferences.setIntegerPref(
503 'net.network_prediction_options',
508 if (loadTimeData.valueExists('showWakeOnWifi') &&
509 loadTimeData.getBoolean('showWakeOnWifi')) {
510 $('wake-on-wifi').hidden = false;
513 // Bluetooth (CrOS only).
515 options.system.bluetooth.BluetoothDeviceList.decorate(
516 $('bluetooth-paired-devices-list'));
518 $('bluetooth-add-device').onclick =
519 this.handleAddBluetoothDevice_.bind(this);
521 $('enable-bluetooth').onchange = function(event) {
522 var state = $('enable-bluetooth').checked;
523 chrome.send('bluetoothEnableChange', [Boolean(state)]);
526 $('bluetooth-reconnect-device').onclick = function(event) {
527 chrome.send('coreOptionsUserMetricsAction',
528 ['Options_BluetoothConnectPairedDevice']);
529 var device = $('bluetooth-paired-devices-list').selectedItem;
530 var address = device.address;
531 chrome.send('updateBluetoothDevice', [address, 'connect']);
532 PageManager.closeOverlay();
535 $('bluetooth-paired-devices-list').addEventListener('change',
537 var item = $('bluetooth-paired-devices-list').selectedItem;
538 var disabled = !item || item.connected || !item.connectable;
539 $('bluetooth-reconnect-device').disabled = disabled;
543 // Passwords and Forms section.
544 $('autofill-settings').onclick = function(event) {
545 PageManager.showPageByName('autofill');
546 chrome.send('coreOptionsUserMetricsAction',
547 ['Options_ShowAutofillSettings']);
549 $('manage-passwords').onclick = function(event) {
550 PageManager.showPageByName('passwords');
551 OptionsPage.showTab($('passwords-nav-tab'));
552 chrome.send('coreOptionsUserMetricsAction',
553 ['Options_ShowPasswordManager']);
555 if (cr.isChromeOS && UIAccountTweaks.loggedInAsGuest()) {
556 // Disable and turn off Autofill in guest mode.
557 var autofillEnabled = $('autofill-enabled');
558 autofillEnabled.disabled = true;
559 autofillEnabled.checked = false;
560 cr.dispatchSimpleEvent(autofillEnabled, 'change');
561 $('autofill-settings').disabled = true;
563 // Disable and turn off Password Manager in guest mode.
564 var passwordManagerEnabled = $('password-manager-enabled');
565 passwordManagerEnabled.disabled = true;
566 passwordManagerEnabled.checked = false;
567 cr.dispatchSimpleEvent(passwordManagerEnabled, 'change');
568 $('manage-passwords').disabled = true;
572 $('mac-passwords-warning').hidden =
573 !loadTimeData.getBoolean('multiple_profiles');
577 if (!cr.isChromeOS) {
578 $('proxiesConfigureButton').onclick = function(event) {
579 chrome.send('showNetworkProxySettings');
583 // Device control section.
585 UIAccountTweaks.currentUserIsOwner() &&
586 loadTimeData.getBoolean('consumerManagementEnabled')) {
587 $('device-control-section').hidden = false;
588 $('consumer-management-button').onclick = function(event) {
589 PageManager.showPageByName('consumer-management-overlay');
593 // Easy Unlock section.
594 if (loadTimeData.getBoolean('easyUnlockAllowed')) {
595 $('easy-unlock-section').hidden = false;
596 $('easy-unlock-setup-button').onclick = function(event) {
597 chrome.send('launchEasyUnlockSetup');
599 $('easy-unlock-turn-off-button').onclick = function(event) {
600 PageManager.showPageByName('easyUnlockTurnOffOverlay');
603 $('easy-unlock-enable-proximity-detection').hidden =
604 !loadTimeData.getBoolean('easyUnlockProximityDetectionAllowed');
606 // Web Content section.
607 $('fontSettingsCustomizeFontsButton').onclick = function(event) {
608 PageManager.showPageByName('fonts');
609 chrome.send('coreOptionsUserMetricsAction',
610 ['Options_ShowFontSettings']);
612 $('defaultFontSize').onchange = function(event) {
613 var value = event.target.options[event.target.selectedIndex].value;
614 Preferences.setIntegerPref(
615 'webkit.webprefs.default_fixed_font_size',
616 value - OptionsPage.SIZE_DIFFERENCE_FIXED_STANDARD, true);
617 chrome.send('defaultFontSizeAction', [String(value)]);
619 $('defaultZoomFactor').onchange = function(event) {
620 chrome.send('defaultZoomFactorAction',
621 [String(event.target.options[event.target.selectedIndex].value)]);
624 // Languages section.
625 var showLanguageOptions = function(event) {
626 PageManager.showPageByName('languages');
627 chrome.send('coreOptionsUserMetricsAction',
628 ['Options_LanuageAndSpellCheckSettings']);
630 $('language-button').onclick = showLanguageOptions;
631 $('manage-languages').onclick = showLanguageOptions;
633 // Downloads section.
634 Preferences.getInstance().addEventListener('download.default_directory',
635 this.onDefaultDownloadDirectoryChanged_.bind(this));
636 $('downloadLocationChangeButton').onclick = function(event) {
637 chrome.send('selectDownloadLocation');
640 $('disable-drive-row').hidden =
641 UIAccountTweaks.loggedInAsSupervisedUser();
643 $('autoOpenFileTypesResetToDefault').onclick = function(event) {
644 chrome.send('autoOpenFileTypesAction');
647 // HTTPS/SSL section.
648 if (cr.isWindows || cr.isMac) {
649 $('certificatesManageButton').onclick = function(event) {
650 chrome.send('showManageSSLCertificates');
653 $('certificatesManageButton').onclick = function(event) {
654 PageManager.showPageByName('certificates');
655 chrome.send('coreOptionsUserMetricsAction',
656 ['Options_ManageSSLCertificates']);
660 if (loadTimeData.getBoolean('cloudPrintShowMDnsOptions')) {
661 $('cloudprint-options-mdns').hidden = false;
662 $('cloudPrintDevicesPageButton').onclick = function() {
663 chrome.send('showCloudPrintDevicesPage');
667 // Accessibility section (CrOS only).
669 var updateAccessibilitySettingsButton = function() {
670 $('accessibility-settings').hidden =
671 !($('accessibility-spoken-feedback-check').checked);
673 Preferences.getInstance().addEventListener(
674 'settings.accessibility',
675 updateAccessibilitySettingsButton);
676 $('accessibility-learn-more').onclick = function(unused_event) {
677 window.open(loadTimeData.getString('accessibilityLearnMoreURL'));
678 chrome.send('coreOptionsUserMetricsAction',
679 ['Options_AccessibilityLearnMore']);
681 $('accessibility-settings-button').onclick = function(unused_event) {
682 window.open(loadTimeData.getString('accessibilitySettingsURL'));
684 $('accessibility-spoken-feedback-check').onchange =
685 updateAccessibilitySettingsButton;
686 updateAccessibilitySettingsButton();
688 var updateScreenMagnifierCenterFocus = function() {
689 $('accessibility-screen-magnifier-center-focus-check').disabled =
690 !$('accessibility-screen-magnifier-check').checked;
692 Preferences.getInstance().addEventListener(
693 $('accessibility-screen-magnifier-check').getAttribute('pref'),
694 updateScreenMagnifierCenterFocus);
696 var updateDelayDropdown = function() {
697 $('accessibility-autoclick-dropdown').disabled =
698 !$('accessibility-autoclick-check').checked;
700 Preferences.getInstance().addEventListener(
701 $('accessibility-autoclick-check').getAttribute('pref'),
702 updateDelayDropdown);
705 // Display management section (CrOS only).
707 $('display-options').onclick = function(event) {
708 PageManager.showPageByName('display');
709 chrome.send('coreOptionsUserMetricsAction',
710 ['Options_Display']);
714 // Factory reset section (CrOS only).
716 $('factory-reset-restart').onclick = function(event) {
717 PageManager.showPageByName('factoryResetData');
718 chrome.send('onPowerwashDialogShow');
723 if (!cr.isChromeOS) {
724 var updateGpuRestartButton = function() {
725 $('gpu-mode-reset-restart').hidden =
726 loadTimeData.getBoolean('gpuEnabledAtStart') ==
727 $('gpu-mode-checkbox').checked;
729 Preferences.getInstance().addEventListener(
730 $('gpu-mode-checkbox').getAttribute('pref'),
731 updateGpuRestartButton);
732 $('gpu-mode-reset-restart-button').onclick = function(event) {
733 chrome.send('restartBrowser');
735 updateGpuRestartButton();
738 // Reset profile settings section.
739 $('reset-profile-settings').onclick = function(event) {
740 PageManager.showPageByName('resetProfileSettings');
743 // Extension controlled UI.
744 this.addExtensionControlledBox_('search-section-content',
745 'search-engine-controlled',
747 this.addExtensionControlledBox_('extension-controlled-container',
748 'homepage-controlled',
750 this.addExtensionControlledBox_('startup-section-content',
751 'startpage-controlled',
753 this.addExtensionControlledBox_('newtab-section-content',
756 this.addExtensionControlledBox_('proxy-section-content',
760 document.body.addEventListener('click', function(e) {
761 var target = assertInstanceof(e.target, Node);
762 var button = findAncestor(target, function(el) {
763 return el.tagName == 'BUTTON' &&
764 el.dataset.extensionId !== undefined &&
765 el.dataset.extensionId.length;
768 chrome.send('disableExtension', [button.dataset.extensionId]);
773 didShowPage: function() {
774 $('search-field').focus();
778 * Called after all C++ UI handlers have called InitializePage to notify
779 * that initialization is complete.
782 notifyInitializationComplete_: function() {
783 this.initializationComplete_ = true;
784 cr.dispatchSimpleEvent(document, 'initializationComplete');
788 * Event listener for the 'session.restore_on_startup' pref.
789 * @param {Event} event The preference change event.
792 onRestoreOnStartupChanged_: function(event) {
793 /** @const */ var showHomePageValue = 0;
795 if (event.value.value == showHomePageValue) {
796 // If the user previously selected "Show the homepage", the
797 // preference will already be migrated to "Open a specific page". So
798 // the only way to reach this code is if the 'restore on startup'
799 // preference is managed.
800 assert(event.value.controlledBy);
802 // Select "open the following pages" and lock down the list of URLs
803 // to reflect the intention of the policy.
804 $('startup-show-pages').checked = true;
805 StartupOverlay.getInstance().setControlsDisabled(true);
807 // Re-enable the controls in the startup overlay if necessary.
808 StartupOverlay.getInstance().updateControlStates();
813 * Handler for messages sent from the main uber page.
814 * @param {Event} e The 'message' event from the uber page.
817 handleWindowMessage_: function(e) {
818 if ((/** @type {{method: string}} */(e.data)).method == 'frameSelected')
819 $('search-field').focus();
823 * Animatedly changes height |from| a px number |to| a px number.
824 * @param {HTMLElement} section The section to animate.
825 * @param {HTMLElement} container The container of |section|.
826 * @param {boolean} showing Whether to go from 0 -> container height or
827 * container height -> 0.
830 animatedSectionHeightChange_: function(section, container, showing) {
831 // If the section is already animating, dispatch a synthetic transition
832 // end event as the upcoming code will cancel the current one.
833 if (section.classList.contains('sliding'))
834 cr.dispatchSimpleEvent(section, 'webkitTransitionEnd');
836 this.addTransitionEndListener_(section);
838 section.hidden = false;
839 section.style.height = (showing ? 0 : container.offsetHeight) + 'px';
840 section.classList.add('sliding');
842 // Force a style recalc before starting the animation.
843 /** @suppress {suspiciousCode} */
844 section.offsetHeight;
846 section.style.height = (showing ? container.offsetHeight : 0) + 'px';
850 * Shows the given section.
851 * @param {HTMLElement} section The section to be shown.
852 * @param {HTMLElement} container The container for the section. Must be
853 * inside of |section|.
854 * @param {boolean} animate Indicate if the expansion should be animated.
857 showSection_: function(section, container, animate) {
858 if (section == $('advanced-settings') &&
859 !loadTimeData.getBoolean('allowAdvancedSettings')) {
862 // Delay starting the transition if animating so that hidden change will
865 this.animatedSectionHeightChange_(section, container, true);
867 section.hidden = false;
868 section.style.height = 'auto';
873 * Shows the given section, with animation.
874 * @param {HTMLElement} section The section to be shown.
875 * @param {HTMLElement} container The container for the section. Must be
876 * inside of |section|.
879 showSectionWithAnimation_: function(section, container) {
880 this.showSection_(section, container, /* animate */ true);
884 * Hides the given |section| with animation.
885 * @param {HTMLElement} section The section to be hidden.
886 * @param {HTMLElement} container The container for the section. Must be
887 * inside of |section|.
890 hideSectionWithAnimation_: function(section, container) {
891 this.animatedSectionHeightChange_(section, container, false);
895 * Toggles the visibility of |section| in an animated way.
896 * @param {HTMLElement} section The section to be toggled.
897 * @param {HTMLElement} container The container for the section. Must be
898 * inside of |section|.
901 toggleSectionWithAnimation_: function(section, container) {
902 if (BrowserOptions.shouldShowSection_(section))
903 this.showSectionWithAnimation_(section, container);
905 this.hideSectionWithAnimation_(section, container);
909 * Scrolls the settings page to make the section visible auto-expanding
910 * advanced settings if required. The transition is not animated. This
911 * method is used to ensure that a section associated with an overlay
912 * is visible when the overlay is closed.
913 * @param {!Element} section The section to make visible.
916 scrollToSection_: function(section) {
917 var advancedSettings = $('advanced-settings');
918 var container = $('advanced-settings-container');
919 var expander = $('advanced-settings-expander');
920 if (!expander.hidden &&
921 advancedSettings.hidden &&
922 section.parentNode == container) {
923 this.showSection_($('advanced-settings'),
924 $('advanced-settings-container'),
925 /* animate */ false);
926 this.updateAdvancedSettingsExpander_();
929 if (!this.initializationComplete_) {
931 var callback = function() {
932 document.removeEventListener('initializationComplete', callback);
933 self.scrollToSection_(section);
935 document.addEventListener('initializationComplete', callback);
939 var pageContainer = $('page-container');
940 // pageContainer.offsetTop is relative to the screen.
941 var pageTop = pageContainer.offsetTop;
942 var sectionBottom = section.offsetTop + section.offsetHeight;
943 // section.offsetTop is relative to the 'page-container'.
944 var sectionTop = section.offsetTop;
945 if (pageTop + sectionBottom > document.body.scrollHeight ||
946 pageTop + sectionTop < 0) {
947 // Currently not all layout updates are guaranteed to precede the
948 // initializationComplete event (for example 'set-as-default-browser'
949 // button) leaving some uncertainty in the optimal scroll position.
950 // The section is placed approximately in the middle of the screen.
951 var top = Math.min(0, document.body.scrollHeight / 2 - sectionBottom);
952 pageContainer.style.top = top + 'px';
953 pageContainer.oldScrollTop = -top;
958 * Adds a |webkitTransitionEnd| listener to the given section so that
959 * it can be animated. The listener will only be added to a given section
960 * once, so this can be called as multiple times.
961 * @param {HTMLElement} section The section to be animated.
964 addTransitionEndListener_: function(section) {
965 if (section.hasTransitionEndListener_)
968 section.addEventListener('webkitTransitionEnd',
969 this.onTransitionEnd_.bind(this));
970 section.hasTransitionEndListener_ = true;
974 * Called after an animation transition has ended.
975 * @param {Event} event The webkitTransitionEnd event. NOTE: May be
979 onTransitionEnd_: function(event) {
980 if (event.propertyName && event.propertyName != 'height') {
981 // If not a synthetic event or a real transition we care about, bail.
985 var section = event.target;
986 section.classList.remove('sliding');
988 if (!event.propertyName) {
989 // Only real transitions past this point.
993 if (section.style.height == '0px') {
994 // Hide the content so it can't get tab focus.
995 section.hidden = true;
996 section.style.height = '';
998 // Set the section height to 'auto' to allow for size changes
999 // (due to font change or dynamic content).
1000 section.style.height = 'auto';
1005 updateAdvancedSettingsExpander_: function() {
1006 var expander = $('advanced-settings-expander');
1007 if (BrowserOptions.shouldShowSection_($('advanced-settings')))
1008 expander.textContent = loadTimeData.getString('showAdvancedSettings');
1010 expander.textContent = loadTimeData.getString('hideAdvancedSettings');
1014 * Updates the sync section with the given state.
1015 * @param {options.SyncStatus} syncData A bunch of data records that
1016 * describe the status of the sync system.
1019 updateSyncState_: function(syncData) {
1020 if (!syncData.signinAllowed &&
1021 (!syncData.supervisedUser || !cr.isChromeOS)) {
1022 $('sync-section').hidden = true;
1023 this.maybeShowUserSection_();
1027 $('sync-section').hidden = false;
1028 this.maybeShowUserSection_();
1030 if (cr.isChromeOS && syncData.supervisedUser && !syncData.childUser) {
1031 var subSection = $('sync-section').firstChild;
1032 while (subSection) {
1033 if (subSection.nodeType == Node.ELEMENT_NODE)
1034 subSection.hidden = true;
1035 subSection = subSection.nextSibling;
1038 $('account-picture-wrapper').hidden = false;
1039 $('sync-general').hidden = false;
1040 $('sync-status').hidden = true;
1045 // If the user gets signed out while the advanced sync settings dialog is
1046 // visible, say, due to a dashboard clear, close the dialog.
1047 // However, if the user gets signed out as a result of abandoning first
1048 // time sync setup, do not call closeOverlay as it will redirect the
1049 // browser to the main settings page and override any in-progress
1050 // user-initiated navigation. See crbug.com/278030.
1051 // Note: SyncSetupOverlay.closeOverlay is a no-op if the overlay is
1053 if (this.signedIn_ && !syncData.signedIn && !syncData.setupInProgress)
1054 SyncSetupOverlay.closeOverlay();
1056 this.signedIn_ = !!syncData.signedIn;
1058 // Display the "advanced settings" button if we're signed in and sync is
1059 // not managed/disabled. If the user is signed in, but sync is disabled,
1060 // this button is used to re-enable sync.
1061 var customizeSyncButton = $('customize-sync');
1062 customizeSyncButton.hidden = !this.signedIn_ ||
1064 !syncData.syncSystemEnabled;
1066 // Only modify the customize button's text if the new text is different.
1067 // Otherwise, it can affect search-highlighting in the settings page.
1068 // See http://crbug.com/268265.
1069 var customizeSyncButtonNewText = syncData.setupCompleted ?
1070 loadTimeData.getString('customizeSync') :
1071 loadTimeData.getString('syncButtonTextStart');
1072 if (customizeSyncButton.textContent != customizeSyncButtonNewText)
1073 customizeSyncButton.textContent = customizeSyncButtonNewText;
1075 // Disable the "sign in" button if we're currently signing in, or if we're
1076 // already signed in and signout is not allowed.
1077 var signInButton = $('start-stop-sync');
1078 signInButton.disabled = syncData.setupInProgress;
1079 this.signoutAllowed_ = !!syncData.signoutAllowed;
1080 if (!syncData.signoutAllowed)
1081 $('start-stop-sync-indicator').setAttribute('controlled-by', 'policy');
1083 $('start-stop-sync-indicator').removeAttribute('controlled-by');
1085 // Hide the "sign in" button on Chrome OS, and show it on desktop Chrome
1086 // (except for supervised users, which can't change their signed-in
1088 signInButton.hidden = cr.isChromeOS || syncData.supervisedUser;
1090 signInButton.textContent =
1092 loadTimeData.getString('syncButtonTextStop') :
1093 syncData.setupInProgress ?
1094 loadTimeData.getString('syncButtonTextInProgress') :
1095 loadTimeData.getString('syncButtonTextSignIn');
1096 $('start-stop-sync-indicator').hidden = signInButton.hidden;
1098 // TODO(estade): can this just be textContent?
1099 $('sync-status-text').innerHTML = syncData.statusText;
1100 var statusSet = syncData.statusText.length != 0;
1101 $('sync-overview').hidden =
1103 (cr.isChromeOS && UIAccountTweaks.loggedInAsPublicAccount());
1104 $('sync-status').hidden = !statusSet;
1106 $('sync-action-link').textContent = syncData.actionLinkText;
1107 // Don't show the action link if it is empty or undefined.
1108 $('sync-action-link').hidden = syncData.actionLinkText.length == 0;
1109 $('sync-action-link').disabled = syncData.managed ||
1110 !syncData.syncSystemEnabled;
1112 // On Chrome OS, sign out the user and sign in again to get fresh
1113 // credentials on auth errors.
1114 $('sync-action-link').onclick = function(event) {
1115 if (cr.isChromeOS && syncData.hasError)
1116 SyncSetupOverlay.doSignOutOnAuthError();
1118 SyncSetupOverlay.showSetupUI();
1121 if (syncData.hasError)
1122 $('sync-status').classList.add('sync-error');
1124 $('sync-status').classList.remove('sync-error');
1126 // Disable the "customize / set up sync" button if sync has an
1127 // unrecoverable error. Also disable the button if sync has not been set
1128 // up and the user is being presented with a link to re-auth.
1129 // See crbug.com/289791.
1130 customizeSyncButton.disabled =
1131 syncData.hasUnrecoverableError ||
1132 (!syncData.setupCompleted && !$('sync-action-link').hidden);
1136 * Update the UI depending on whether Easy Unlock is enabled for the current
1138 * @param {boolean} isEnabled True if the feature is enabled for the current
1141 updateEasyUnlock_: function(isEnabled) {
1142 $('easy-unlock-disabled').hidden = isEnabled;
1143 $('easy-unlock-enabled').hidden = !isEnabled;
1144 if (!isEnabled && EasyUnlockTurnOffOverlay.getInstance().visible) {
1145 EasyUnlockTurnOffOverlay.dismiss();
1150 * Update the UI depending on whether the current profile manages any
1152 * @param {boolean} show True if the current profile manages any supervised
1155 updateManagesSupervisedUsers_: function(show) {
1156 $('profiles-supervised-dashboard-tip').hidden = !show;
1157 this.maybeShowUserSection_();
1161 * Get the start/stop sync button DOM element. Used for testing.
1162 * @return {Element} The start/stop sync button.
1165 getStartStopSyncButton_: function() {
1166 return $('start-stop-sync');
1170 * Event listener for the 'show home button' preference. Shows/hides the
1171 * UI for changing the home page with animation, unless this is the first
1172 * time this function is called, in which case there is no animation.
1173 * @param {Event} event The preference change event.
1175 onShowHomeButtonChanged_: function(event) {
1176 var section = $('change-home-page-section');
1177 if (this.onShowHomeButtonChangedCalled_) {
1178 var container = $('change-home-page-section-container');
1179 if (event.value.value)
1180 this.showSectionWithAnimation_(section, container);
1182 this.hideSectionWithAnimation_(section, container);
1184 section.hidden = !event.value.value;
1185 this.onShowHomeButtonChangedCalled_ = true;
1190 * Activates the Hotword section from the System settings page.
1191 * @param {string} sectionId The id of the section to display.
1192 * @param {string} indicatorId The id of the indicator to display.
1193 * @param {string=} opt_error The error message to display.
1196 showHotwordCheckboxAndIndicator_: function(sectionId, indicatorId,
1198 $(sectionId).hidden = false;
1199 $(indicatorId).setError(opt_error);
1201 $(indicatorId).updateBasedOnError();
1205 * Activates the Hotword section from the System settings page.
1206 * @param {string=} opt_error The error message to display.
1209 showHotwordSection_: function(opt_error) {
1210 this.showHotwordCheckboxAndIndicator_(
1212 'hotword-search-setting-indicator',
1217 * Activates the Always-On Hotword sections from the
1218 * System settings page.
1219 * @param {string=} opt_error The error message to display.
1222 showHotwordAlwaysOnSection_: function(opt_error) {
1223 this.showHotwordCheckboxAndIndicator_(
1224 'hotword-always-on-search',
1225 'hotword-always-on-search-setting-indicator',
1230 * Activates the Hotword section on devices with no DSP
1231 * from the System settings page.
1232 * @param {string=} opt_error The error message to display.
1235 showHotwordNoDspSection_: function(opt_error) {
1236 this.showHotwordCheckboxAndIndicator_(
1237 'hotword-no-dsp-search',
1238 'hotword-no-dsp-search-setting-indicator',
1243 * Controls the visibility of all the hotword sections.
1244 * @param {boolean} visible Whether to show hotword sections.
1247 setAllHotwordSectionsVisible_: function(visible) {
1248 $('hotword-search').hidden = !visible;
1249 $('hotword-always-on-search').hidden = !visible;
1250 $('hotword-no-dsp-search').hidden = !visible;
1251 $('audio-history').hidden = !visible;
1255 * Shows or hides the hotword retrain link
1256 * @param {boolean} visible Whether to show the link.
1259 setHotwordRetrainLinkVisible_: function(visible) {
1260 $('hotword-retrain-link').hidden = !visible;
1264 * Event listener for the 'hotword always on search enabled' preference.
1265 * Updates the visibility of the 'retrain' link.
1266 * @param {Event} event The preference change event.
1269 onHotwordAlwaysOnChanged_: function(event) {
1270 this.setHotwordRetrainLinkVisible_(event.value.value);
1274 * Controls the visibility of the Now settings.
1275 * @param {boolean} visible Whether to show Now settings.
1278 setNowSectionVisible_: function(visible) {
1279 $('google-now-launcher').hidden = !visible;
1283 * Activates the Audio History section of the Settings page.
1284 * @param {boolean} visible Whether the audio history section is visible.
1285 * @param {string} labelText Text describing current audio history state.
1288 setAudioHistorySectionVisible_: function(visible, labelText) {
1289 $('audio-history').hidden = !visible;
1290 $('audio-history-label').textContent = labelText;
1294 * Event listener for the 'homepage is NTP' preference. Updates the label
1295 * next to the 'Change' button.
1296 * @param {Event} event The preference change event.
1298 onHomePageIsNtpChanged_: function(event) {
1299 if (!event.value.uncommitted) {
1300 $('home-page-url').hidden = event.value.value;
1301 $('home-page-ntp').hidden = !event.value.value;
1306 * Event listener for changes to the homepage preference. Updates the label
1307 * next to the 'Change' button.
1308 * @param {Event} event The preference change event.
1310 onHomePageChanged_: function(event) {
1311 if (!event.value.uncommitted)
1312 $('home-page-url').textContent = this.stripHttp_(event.value.value);
1316 * Removes the 'http://' from a URL, like the omnibox does. If the string
1317 * doesn't start with 'http://' it is returned unchanged.
1318 * @param {string} url The url to be processed
1319 * @return {string} The url with the 'http://' removed.
1321 stripHttp_: function(url) {
1322 return url.replace(/^http:\/\//, '');
1326 * Shows the autoLaunch preference and initializes its checkbox value.
1327 * @param {boolean} enabled Whether autolaunch is enabled or or not.
1330 updateAutoLaunchState_: function(enabled) {
1331 $('auto-launch-option').hidden = false;
1332 $('auto-launch').checked = enabled;
1336 * Called when the value of the download.default_directory preference
1338 * @param {Event} event Change event.
1341 onDefaultDownloadDirectoryChanged_: function(event) {
1342 $('downloadLocationPath').value = event.value.value;
1343 if (cr.isChromeOS) {
1344 // On ChromeOS, replace /special/drive-<hash>/root with "Google Drive"
1345 // for remote files, /home/chronos/user/Downloads or
1346 // /home/chronos/u-<hash>/Downloads with "Downloads" for local paths,
1347 // and '/' with ' \u203a ' (angled quote sign) everywhere. The modified
1348 // path is used only for display purpose.
1349 var path = $('downloadLocationPath').value;
1350 path = path.replace(/^\/special\/drive[^\/]*\/root/, 'Google Drive');
1351 path = path.replace(/^\/home\/chronos\/(user|u-[^\/]*)\//, '');
1352 path = path.replace(/\//g, ' \u203a ');
1353 $('downloadLocationPath').value = path;
1355 $('download-location-label').classList.toggle('disabled',
1356 event.value.disabled);
1357 $('downloadLocationChangeButton').disabled = event.value.disabled;
1361 * Update the Default Browsers section based on the current state.
1362 * @param {string} statusString Description of the current default state.
1363 * @param {boolean} isDefault Whether or not the browser is currently
1365 * @param {boolean} canBeDefault Whether or not the browser can be default.
1368 updateDefaultBrowserState_: function(statusString, isDefault,
1370 if (!cr.isChromeOS) {
1371 var label = $('default-browser-state');
1372 label.textContent = statusString;
1374 $('set-as-default-browser').hidden = !canBeDefault || isDefault;
1379 * Clears the search engine popup.
1382 clearSearchEngines_: function() {
1383 $('default-search-engine').textContent = '';
1387 * Updates the search engine popup with the given entries.
1388 * @param {Array} engines List of available search engines.
1389 * @param {number} defaultValue The value of the current default engine.
1390 * @param {boolean} defaultManaged Whether the default search provider is
1391 * managed. If true, the default search provider can't be changed.
1394 updateSearchEngines_: function(engines, defaultValue, defaultManaged) {
1395 this.clearSearchEngines_();
1396 var engineSelect = $('default-search-engine');
1397 engineSelect.disabled = defaultManaged;
1398 if (defaultManaged && defaultValue == -1)
1400 var engineCount = engines.length;
1401 var defaultIndex = -1;
1402 for (var i = 0; i < engineCount; i++) {
1403 var engine = engines[i];
1404 var option = new Option(engine.name, engine.index);
1405 if (defaultValue == option.value)
1407 engineSelect.appendChild(option);
1409 if (defaultIndex >= 0)
1410 engineSelect.selectedIndex = defaultIndex;
1414 * Set the default search engine based on the popup selection.
1417 setDefaultSearchEngine_: function() {
1418 var engineSelect = $('default-search-engine');
1419 var selectedIndex = engineSelect.selectedIndex;
1420 if (selectedIndex >= 0) {
1421 var selection = engineSelect.options[selectedIndex];
1422 chrome.send('setDefaultSearchEngine', [String(selection.value)]);
1427 * Sets or clear whether Chrome should Auto-launch on computer startup.
1430 handleAutoLaunchChanged_: function() {
1431 chrome.send('toggleAutoLaunch', [$('auto-launch').checked]);
1435 * Get the selected profile item from the profile list. This also works
1436 * correctly if the list is not displayed.
1437 * @return {?Object} The profile item object, or null if nothing is
1441 getSelectedProfileItem_: function() {
1442 var profilesList = $('profiles-list');
1443 if (profilesList.hidden) {
1444 if (profilesList.dataModel.length > 0)
1445 return profilesList.dataModel.item(0);
1447 return profilesList.selectedItem;
1453 * Helper function to set the status of profile view buttons to disabled or
1454 * enabled, depending on the number of profiles and selection status of the
1458 setProfileViewButtonsStatus_: function() {
1459 var profilesList = $('profiles-list');
1460 var selectedProfile = profilesList.selectedItem;
1461 var hasSelection = selectedProfile != null;
1462 var hasSingleProfile = profilesList.dataModel.length == 1;
1463 $('profiles-manage').disabled = !hasSelection ||
1464 !selectedProfile.isCurrentProfile;
1465 if (hasSelection && !selectedProfile.isCurrentProfile)
1466 $('profiles-manage').title = loadTimeData.getString('currentUserOnly');
1468 $('profiles-manage').title = '';
1469 $('profiles-delete').disabled = !profilesList.canDeleteItems ||
1470 (!hasSelection && !hasSingleProfile);
1471 if (OptionsPage.isSettingsApp()) {
1472 $('profiles-app-list-switch').disabled = !hasSelection ||
1473 selectedProfile.isCurrentProfile;
1475 var importData = $('import-data');
1477 importData.disabled = $('import-data').disabled = hasSelection &&
1478 !selectedProfile.isCurrentProfile;
1483 * Display the correct dialog layout, depending on how many profiles are
1485 * @param {number} numProfiles The number of profiles to display.
1488 setProfileViewSingle_: function(numProfiles) {
1489 // Always show the profiles list when using the new Profiles UI.
1490 var usingNewProfilesUI = loadTimeData.getBoolean('usingNewProfilesUI');
1491 var showSingleProfileView = !usingNewProfilesUI && numProfiles == 1;
1492 $('profiles-list').hidden = showSingleProfileView;
1493 $('profiles-single-message').hidden = !showSingleProfileView;
1494 $('profiles-manage').hidden =
1495 showSingleProfileView || OptionsPage.isSettingsApp();
1496 $('profiles-delete').textContent = showSingleProfileView ?
1497 loadTimeData.getString('profilesDeleteSingle') :
1498 loadTimeData.getString('profilesDelete');
1499 if (OptionsPage.isSettingsApp())
1500 $('profiles-app-list-switch').hidden = showSingleProfileView;
1504 * Adds all |profiles| to the list.
1505 * @param {Array<!options.Profile>} profiles An array of profile info
1509 setProfilesInfo_: function(profiles) {
1510 this.setProfileViewSingle_(profiles.length);
1511 // add it to the list, even if the list is hidden so we can access it
1513 $('profiles-list').dataModel = new ArrayDataModel(profiles);
1515 // Received new data. If showing the "manage" overlay, keep it up to
1516 // date. If showing the "delete" overlay, close it.
1517 if (ManageProfileOverlay.getInstance().visible &&
1518 !$('manage-profile-overlay-manage').hidden) {
1519 ManageProfileOverlay.showManageDialog(false);
1521 ManageProfileOverlay.getInstance().visible = false;
1524 this.setProfileViewButtonsStatus_();
1528 * Reports supervised user import errors to the SupervisedUserImportOverlay.
1529 * @param {string} error The error message to display.
1532 showSupervisedUserImportError_: function(error) {
1533 SupervisedUserImportOverlay.onError(error);
1537 * Reports successful importing of a supervised user to
1538 * the SupervisedUserImportOverlay.
1541 showSupervisedUserImportSuccess_: function() {
1542 SupervisedUserImportOverlay.onSuccess();
1546 * Reports an error to the "create" overlay during profile creation.
1547 * @param {string} error The error message to display.
1550 showCreateProfileError_: function(error) {
1551 CreateProfileOverlay.onError(error);
1555 * Sends a warning message to the "create" overlay during profile creation.
1556 * @param {string} warning The warning message to display.
1559 showCreateProfileWarning_: function(warning) {
1560 CreateProfileOverlay.onWarning(warning);
1564 * Reports successful profile creation to the "create" overlay.
1565 * @param {options.Profile} profileInfo An object of the form:
1567 * name: "Profile Name",
1568 * filePath: "/path/to/profile/data/on/disk"
1569 * isSupervised: (true|false),
1573 showCreateProfileSuccess_: function(profileInfo) {
1574 CreateProfileOverlay.onSuccess(profileInfo);
1578 * Returns the currently active profile for this browser window.
1579 * @return {options.Profile} A profile info object.
1582 getCurrentProfile_: function() {
1583 for (var i = 0; i < $('profiles-list').dataModel.length; i++) {
1584 var profile = $('profiles-list').dataModel.item(i);
1585 if (profile.isCurrentProfile)
1589 assertNotReached('There should always be a current profile.');
1593 * Propmpts user to confirm deletion of the profile for this browser
1597 deleteCurrentProfile_: function() {
1598 ManageProfileOverlay.showDeleteDialog(this.getCurrentProfile_());
1602 * @param {boolean} enabled
1604 setNativeThemeButtonEnabled_: function(enabled) {
1605 var button = $('themes-native-button');
1607 button.disabled = !enabled;
1611 * @param {boolean} enabled
1613 setThemesResetButtonEnabled_: function(enabled) {
1614 $('themes-reset').disabled = !enabled;
1618 * @param {boolean} managed
1620 setAccountPictureManaged_: function(managed) {
1621 var picture = $('account-picture');
1622 if (managed || UIAccountTweaks.loggedInAsGuest()) {
1623 picture.disabled = true;
1624 ChangePictureOptions.closeOverlay();
1626 picture.disabled = false;
1629 // Create a synthetic pref change event decorated as
1630 // CoreOptionsHandler::CreateValueForPref() does.
1631 var event = new Event('account-picture');
1633 event.value = { controlledBy: 'policy' };
1636 $('account-picture-indicator').handlePrefChange(event);
1640 * (Re)loads IMG element with current user account picture.
1643 updateAccountPicture_: function() {
1644 var picture = $('account-picture');
1646 picture.src = 'chrome://userimage/' + this.username_ + '?id=' +
1652 * @param {boolean} managed
1654 setWallpaperManaged_: function(managed) {
1656 $('set-wallpaper').disabled = true;
1658 this.enableElementIfPossible_(getRequiredElement('set-wallpaper'));
1660 // Create a synthetic pref change event decorated as
1661 // CoreOptionsHandler::CreateValueForPref() does.
1662 var event = new Event('wallpaper');
1663 event.value = managed ? { controlledBy: 'policy' } : {};
1664 $('wallpaper-indicator').handlePrefChange(event);
1668 * This enables or disables dependent settings in timezone section.
1671 updateTimezoneSectionState_: function() {
1672 if (this.systemTimezoneIsManaged_) {
1673 $('resolve-timezone-by-geolocation-selection').disabled = true;
1674 $('resolve-timezone-by-geolocation').onclick = function(event) {};
1676 this.enableElementIfPossible_(
1677 getRequiredElement('resolve-timezone-by-geolocation-selection'));
1678 $('resolve-timezone-by-geolocation').onclick = function(event) {
1679 $('timezone-value-select').disabled = event.currentTarget.checked;
1681 $('timezone-value-select').disabled =
1682 this.resolveTimezoneByGeolocation_;
1687 * This is called from chromium code when system timezone "managed" state
1688 * is changed. Enables or disables dependent settings.
1689 * @param {boolean} managed Is true when system Timezone is managed by
1690 * enterprise policy. False otherwize.
1692 setSystemTimezoneManaged_: function(managed) {
1693 this.systemTimezoneIsManaged_ = managed;
1694 this.updateTimezoneSectionState_();
1698 * This is Preferences event listener, which is called when
1699 * kResolveTimezoneByGeolocation preference is changed.
1700 * Enables or disables dependent settings.
1701 * @param {Event} value New preference state.
1703 onResolveTimezoneByGeolocationChanged_: function(value) {
1704 this.resolveTimezoneByGeolocation_ = value.value.value;
1705 this.updateTimezoneSectionState_();
1709 * Handle the 'add device' button click.
1712 handleAddBluetoothDevice_: function() {
1713 chrome.send('coreOptionsUserMetricsAction',
1714 ['Options_BluetoothShowAddDevice']);
1715 chrome.send('findBluetoothDevices');
1716 PageManager.showPageByName('bluetooth', false);
1720 * Enables or disables the Manage SSL Certificates button.
1723 enableCertificateButton_: function(enabled) {
1724 $('certificatesManageButton').disabled = !enabled;
1728 * Enables or disables the Chrome OS display settings button and overlay.
1731 enableDisplaySettings_: function(enabled, showUnifiedDesktop) {
1732 if (cr.isChromeOS) {
1733 $('display-options').disabled = !enabled;
1734 DisplayOptions.getInstance().setEnabled(enabled, showUnifiedDesktop);
1739 * Enables factory reset section.
1742 enableFactoryResetSection_: function() {
1743 $('factory-reset-section').hidden = false;
1747 * Set the checked state of the metrics reporting checkbox.
1750 setMetricsReportingCheckboxState_: function(checked, disabled) {
1751 $('metrics-reporting-enabled').checked = checked;
1752 $('metrics-reporting-enabled').disabled = disabled;
1754 // If checkbox gets disabled then add an attribute for displaying the
1755 // special icon. Otherwise remove the indicator attribute.
1757 $('metrics-reporting-disabled-icon').setAttribute('controlled-by',
1760 $('metrics-reporting-disabled-icon').removeAttribute('controlled-by');
1767 setMetricsReportingSettingVisibility_: function(visible) {
1769 $('metrics-reporting-setting').style.display = 'block';
1771 $('metrics-reporting-setting').style.display = 'none';
1775 * Set network prediction checkbox value.
1777 * @param {{value: number, disabled: boolean}} pref Information about
1778 * network prediction options. |pref.value| is the value of network
1779 * prediction options. |pref.disabled| shows if the pref is not user
1783 setNetworkPredictionValue_: function(pref) {
1784 var checkbox = $('networkPredictionOptions');
1785 checkbox.disabled = pref.disabled;
1786 checkbox.checked = (pref.value != NetworkPredictionOptions.NEVER);
1790 * Set the font size selected item. This item actually reflects two
1791 * preferences: the default font size and the default fixed font size.
1793 * @param {{value: number, disabled: boolean, controlledBy: string}} pref
1794 * Information about the font size preferences. |pref.value| is the
1795 * value of the default font size pref. |pref.disabled| is true if
1796 * either pref not user modifiable. |pref.controlledBy| is the source of
1797 * the pref value(s) if either pref is currently not controlled by the
1801 setFontSize_: function(pref) {
1802 var selectCtl = $('defaultFontSize');
1803 selectCtl.disabled = pref.disabled;
1804 // Create a synthetic pref change event decorated as
1805 // CoreOptionsHandler::CreateValueForPref() does.
1806 var event = new Event('synthetic-font-size');
1809 controlledBy: pref.controlledBy,
1810 disabled: pref.disabled
1812 $('font-size-indicator').handlePrefChange(event);
1814 for (var i = 0; i < selectCtl.options.length; i++) {
1815 if (selectCtl.options[i].value == pref.value) {
1816 selectCtl.selectedIndex = i;
1818 selectCtl.remove($('Custom').index);
1823 // Add/Select Custom Option in the font size label list.
1825 var option = new Option(loadTimeData.getString('fontSizeLabelCustom'),
1827 option.setAttribute('id', 'Custom');
1828 selectCtl.add(option);
1830 $('Custom').selected = true;
1834 * Populate the page zoom selector with values received from the caller.
1835 * @param {Array} items An array of items to populate the selector.
1836 * each object is an array with three elements as follows:
1837 * 0: The title of the item (string).
1838 * 1: The value of the item (number).
1839 * 2: Whether the item should be selected (boolean).
1842 setupPageZoomSelector_: function(items) {
1843 var element = $('defaultZoomFactor');
1845 // Remove any existing content.
1846 element.textContent = '';
1848 // Insert new child nodes into select element.
1849 var value, title, selected;
1850 for (var i = 0; i < items.length; i++) {
1851 title = items[i][0];
1852 value = items[i][1];
1853 selected = items[i][2];
1854 element.appendChild(new Option(title, value, false, selected));
1859 * Shows/hides the autoOpenFileTypesResetToDefault button and label, with
1861 * @param {boolean} display Whether to show the button and label or not.
1864 setAutoOpenFileTypesDisplayed_: function(display) {
1865 if ($('advanced-settings').hidden) {
1866 // If the Advanced section is hidden, don't animate the transition.
1867 $('auto-open-file-types-section').hidden = !display;
1870 this.showSectionWithAnimation_(
1871 $('auto-open-file-types-section'),
1872 $('auto-open-file-types-container'));
1874 this.hideSectionWithAnimation_(
1875 $('auto-open-file-types-section'),
1876 $('auto-open-file-types-container'));
1882 * Set the enabled state for the proxy settings button and its associated
1883 * message when extension controlled.
1884 * @param {boolean} disabled Whether the button should be disabled.
1885 * @param {boolean} extensionControlled Whether the proxy is extension
1889 setupProxySettingsButton_: function(disabled, extensionControlled) {
1890 if (!cr.isChromeOS) {
1891 $('proxiesConfigureButton').disabled = disabled;
1892 $('proxiesLabel').textContent =
1893 loadTimeData.getString(extensionControlled ?
1894 'proxiesLabelExtension' : 'proxiesLabelSystem');
1899 * Set the initial state of the spoken feedback checkbox.
1902 setSpokenFeedbackCheckboxState_: function(checked) {
1903 $('accessibility-spoken-feedback-check').checked = checked;
1907 * Set the initial state of the high contrast checkbox.
1910 setHighContrastCheckboxState_: function(checked) {
1911 $('accessibility-high-contrast-check').checked = checked;
1915 * Set the initial state of the virtual keyboard checkbox.
1918 setVirtualKeyboardCheckboxState_: function(checked) {
1919 // TODO(zork): Update UI
1923 * Show/hide mouse settings slider.
1926 showMouseControls_: function(show) {
1927 $('mouse-settings').hidden = !show;
1931 * Adds hidden warning boxes for settings potentially controlled by
1933 * @param {string} parentDiv The div name to append the bubble to.
1934 * @param {string} bubbleId The ID to use for the bubble.
1935 * @param {boolean} first Add as first node if true, otherwise last.
1938 addExtensionControlledBox_: function(parentDiv, bubbleId, first) {
1939 var bubble = $('extension-controlled-warning-template').cloneNode(true);
1940 bubble.id = bubbleId;
1941 var parent = $(parentDiv);
1943 parent.insertBefore(bubble, parent.firstChild);
1945 parent.appendChild(bubble);
1949 * Adds a bubble showing that an extension is controlling a particular
1951 * @param {string} parentDiv The div name to append the bubble to.
1952 * @param {string} bubbleId The ID to use for the bubble.
1953 * @param {string} extensionId The ID of the controlling extension.
1954 * @param {string} extensionName The name of the controlling extension.
1957 toggleExtensionControlledBox_: function(
1958 parentDiv, bubbleId, extensionId, extensionName) {
1959 var bubble = $(bubbleId);
1961 bubble.hidden = extensionId.length == 0;
1965 // Set the extension image.
1966 var div = bubble.firstElementChild;
1967 div.style.backgroundImage =
1968 'url(chrome://extension-icon/' + extensionId + '/24/1)';
1970 // Set the bubble label.
1971 var label = loadTimeData.getStringF('extensionControlled', extensionName);
1972 var docFrag = parseHtmlSubset('<div>' + label + '</div>', ['B', 'DIV']);
1973 div.innerHTML = docFrag.firstChild.innerHTML;
1975 // Wire up the button to disable the right extension.
1976 var button = div.nextElementSibling;
1977 button.dataset.extensionId = extensionId;
1981 * Toggles the warning boxes that show which extension is controlling
1982 * various settings of Chrome.
1983 * @param {{searchEngine: options.ExtensionData,
1984 * homePage: options.ExtensionData,
1985 * startUpPage: options.ExtensionData,
1986 * newTabPage: options.ExtensionData,
1987 * proxy: options.ExtensionData}} details A dictionary of ID+name
1988 * pairs for each of the settings controlled by an extension.
1991 toggleExtensionIndicators_: function(details) {
1992 this.toggleExtensionControlledBox_('search-section-content',
1993 'search-engine-controlled',
1994 details.searchEngine.id,
1995 details.searchEngine.name);
1996 this.toggleExtensionControlledBox_('extension-controlled-container',
1997 'homepage-controlled',
1998 details.homePage.id,
1999 details.homePage.name);
2000 this.toggleExtensionControlledBox_('startup-section-content',
2001 'startpage-controlled',
2002 details.startUpPage.id,
2003 details.startUpPage.name);
2004 this.toggleExtensionControlledBox_('newtab-section-content',
2005 'newtab-controlled',
2006 details.newTabPage.id,
2007 details.newTabPage.name);
2008 this.toggleExtensionControlledBox_('proxy-section-content',
2011 details.proxy.name);
2013 // The proxy section contains just the warning box and nothing else, so
2014 // if we're hiding the proxy warning box, we should also hide its header
2016 $('proxy-section').hidden = details.proxy.id.length == 0;
2021 * Show/hide touchpad-related settings.
2024 showTouchpadControls_: function(show) {
2025 $('touchpad-settings').hidden = !show;
2026 $('accessibility-tap-dragging').hidden = !show;
2030 * Activate the Bluetooth settings section on the System settings page.
2033 showBluetoothSettings_: function() {
2034 $('bluetooth-devices').hidden = false;
2038 * Dectivates the Bluetooth settings section from the System settings page.
2041 hideBluetoothSettings_: function() {
2042 $('bluetooth-devices').hidden = true;
2046 * Sets the state of the checkbox indicating if Bluetooth is turned on. The
2047 * state of the "Find devices" button and the list of discovered devices may
2048 * also be affected by a change to the state.
2049 * @param {boolean} checked Flag Indicating if Bluetooth is turned on.
2052 setBluetoothState_: function(checked) {
2053 $('enable-bluetooth').checked = checked;
2054 $('bluetooth-paired-devices-list').parentNode.hidden = !checked;
2055 $('bluetooth-add-device').hidden = !checked;
2056 $('bluetooth-reconnect-device').hidden = !checked;
2057 // Flush list of previously discovered devices if bluetooth is turned off.
2059 $('bluetooth-paired-devices-list').clear();
2060 $('bluetooth-unpaired-devices-list').clear();
2062 chrome.send('getPairedBluetoothDevices');
2067 * Adds an element to the list of available Bluetooth devices. If an element
2068 * with a matching address is found, the existing element is updated.
2069 * @param {{name: string,
2072 * connected: boolean}} device
2073 * Decription of the Bluetooth device.
2076 addBluetoothDevice_: function(device) {
2077 var list = $('bluetooth-unpaired-devices-list');
2078 // Display the "connecting" (already paired or not yet paired) and the
2079 // paired devices in the same list.
2080 if (device.paired || device.connecting) {
2081 // Test to see if the device is currently in the unpaired list, in which
2082 // case it should be removed from that list.
2083 var index = $('bluetooth-unpaired-devices-list').find(device.address);
2084 if (index != undefined)
2085 $('bluetooth-unpaired-devices-list').deleteItemAtIndex(index);
2086 list = $('bluetooth-paired-devices-list');
2088 // Test to see if the device is currently in the paired list, in which
2089 // case it should be removed from that list.
2090 var index = $('bluetooth-paired-devices-list').find(device.address);
2091 if (index != undefined)
2092 $('bluetooth-paired-devices-list').deleteItemAtIndex(index);
2094 list.appendDevice(device);
2096 // One device can be in the process of pairing. If found, display
2097 // the Bluetooth pairing overlay.
2099 BluetoothPairing.showDialog(device);
2103 * Removes an element from the list of available devices.
2104 * @param {string} address Unique address of the device.
2107 removeBluetoothDevice_: function(address) {
2108 var index = $('bluetooth-unpaired-devices-list').find(address);
2109 if (index != undefined) {
2110 $('bluetooth-unpaired-devices-list').deleteItemAtIndex(index);
2112 index = $('bluetooth-paired-devices-list').find(address);
2113 if (index != undefined)
2114 $('bluetooth-paired-devices-list').deleteItemAtIndex(index);
2119 * Shows the overlay dialog for changing the user avatar image.
2122 showImagerPickerOverlay_: function() {
2123 PageManager.showPageByName('changePicture');
2127 * Shows (or not) the "User" section of the settings page based on whether
2128 * any of the sub-sections are present (or not).
2131 maybeShowUserSection_: function() {
2132 $('sync-users-section').hidden =
2133 $('profiles-section').hidden &&
2134 $('sync-section').hidden &&
2135 $('profiles-supervised-dashboard-tip').hidden;
2139 * Updates the date and time section with time sync information.
2140 * @param {boolean} canSetTime Whether the system time can be set.
2143 setCanSetTime_: function(canSetTime) {
2144 // If the time has been network-synced, it cannot be set manually.
2145 $('set-time').hidden = !canSetTime;
2149 * Handle the 'set date and time' button click.
2152 handleSetTime_: function() {
2153 chrome.send('showSetTime');
2157 * Enables the given element if possible; on Chrome OS, it won't enable
2158 * an element that must stay disabled for the session type.
2159 * @param {!Element} element Element to enable.
2161 enableElementIfPossible_: function(element) {
2163 UIAccountTweaks.enableElementIfPossible(element);
2165 element.disabled = false;
2169 //Forward public APIs to private implementations.
2170 cr.makePublic(BrowserOptions, [
2171 'addBluetoothDevice',
2172 'deleteCurrentProfile',
2173 'enableCertificateButton',
2174 'enableDisplaySettings',
2175 'enableFactoryResetSection',
2176 'getCurrentProfile',
2177 'getStartStopSyncButton',
2178 'hideBluetoothSettings',
2179 'notifyInitializationComplete',
2180 'removeBluetoothDevice',
2182 'setAccountPictureManaged',
2183 'setWallpaperManaged',
2184 'setAutoOpenFileTypesDisplayed',
2185 'setBluetoothState',
2188 'setHotwordRetrainLinkVisible',
2189 'setNativeThemeButtonEnabled',
2190 'setNetworkPredictionValue',
2191 'setNowSectionVisible',
2192 'setHighContrastCheckboxState',
2193 'setAllHotwordSectionsVisible',
2194 'setMetricsReportingCheckboxState',
2195 'setMetricsReportingSettingVisibility',
2197 'setSpokenFeedbackCheckboxState',
2198 'setSystemTimezoneManaged',
2199 'setThemesResetButtonEnabled',
2200 'setVirtualKeyboardCheckboxState',
2201 'setupPageZoomSelector',
2202 'setupProxySettingsButton',
2203 'setAudioHistorySectionVisible',
2204 'showBluetoothSettings',
2205 'showCreateProfileError',
2206 'showCreateProfileSuccess',
2207 'showCreateProfileWarning',
2208 'showHotwordAlwaysOnSection',
2209 'showHotwordNoDspSection',
2210 'showHotwordSection',
2211 'showMouseControls',
2212 'showSupervisedUserImportError',
2213 'showSupervisedUserImportSuccess',
2214 'showTouchpadControls',
2215 'toggleExtensionIndicators',
2216 'updateAccountPicture',
2217 'updateAutoLaunchState',
2218 'updateDefaultBrowserState',
2220 'updateManagesSupervisedUsers',
2221 'updateSearchEngines',
2225 if (cr.isChromeOS) {
2227 * Returns username (canonical email) of the user logged in (ChromeOS only).
2228 * @return {string} user email.
2230 // TODO(jhawkins): Investigate the use case for this method.
2231 BrowserOptions.getLoggedInUsername = function() {
2232 return BrowserOptions.getInstance().username_;
2236 * Shows different button text for each consumer management enrollment
2238 * @enum {string} status Consumer management service status string.
2240 BrowserOptions.setConsumerManagementStatus = function(status) {
2241 var button = $('consumer-management-button');
2242 if (status == 'StatusUnknown') {
2243 button.hidden = true;
2247 button.hidden = false;
2250 case ConsumerManagementOverlay.Status.STATUS_UNENROLLED:
2251 strId = 'consumerManagementEnrollButton';
2252 button.disabled = false;
2253 ConsumerManagementOverlay.setStatus(status);
2255 case ConsumerManagementOverlay.Status.STATUS_ENROLLING:
2256 strId = 'consumerManagementEnrollingButton';
2257 button.disabled = true;
2259 case ConsumerManagementOverlay.Status.STATUS_ENROLLED:
2260 strId = 'consumerManagementUnenrollButton';
2261 button.disabled = false;
2262 ConsumerManagementOverlay.setStatus(status);
2264 case ConsumerManagementOverlay.Status.STATUS_UNENROLLING:
2265 strId = 'consumerManagementUnenrollingButton';
2266 button.disabled = true;
2269 button.textContent = loadTimeData.getString(strId);
2275 BrowserOptions: BrowserOptions