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.define('options', function() {
6 var OptionsPage = options.OptionsPage;
7 var ArrayDataModel = cr.ui.ArrayDataModel;
8 var RepeatingButton = cr.ui.RepeatingButton;
11 // BrowserOptions class
12 // Encapsulated handling of browser options page.
14 function BrowserOptions() {
15 OptionsPage.call(this, 'settings', loadTimeData.getString('settingsTitle'),
19 cr.addSingletonGetter(BrowserOptions);
21 BrowserOptions.prototype = {
22 __proto__: options.OptionsPage.prototype,
25 * Keeps track of whether the user is signed in or not.
32 * Keeps track of whether |onShowHomeButtonChanged_| has been called. See
33 * |onShowHomeButtonChanged_|.
37 onShowHomeButtonChangedCalled_: false,
40 * Track if page initialization is complete. All C++ UI handlers have the
41 * chance to manipulate page content within their InitializePage methods.
42 * This flag is set to true after all initializers have been called.
46 initializationComplete_: false,
49 initializePage: function() {
50 OptionsPage.prototype.initializePage.call(this);
53 // Ensure that navigation events are unblocked on uber page. A reload of
54 // the settings page while an overlay is open would otherwise leave uber
55 // page in a blocked state, where tab switching is not possible.
56 uber.invokeMethodOnParent('stopInterceptingEvents');
58 window.addEventListener('message', this.handleWindowMessage_.bind(this));
60 $('advanced-settings-expander').onclick = function() {
61 self.toggleSectionWithAnimation_(
62 $('advanced-settings'),
63 $('advanced-settings-container'));
65 // If the link was focused (i.e., it was activated using the keyboard)
66 // and it was used to show the section (rather than hiding it), focus
67 // the first element in the container.
68 if (document.activeElement === $('advanced-settings-expander') &&
69 $('advanced-settings').style.height === '') {
70 var focusElement = $('advanced-settings-container').querySelector(
71 'button, input, list, select, a[href]');
77 $('advanced-settings').addEventListener('webkitTransitionEnd',
78 this.updateAdvancedSettingsExpander_.bind(this));
81 UIAccountTweaks.applyGuestModeVisibility(document);
83 // Sync (Sign in) section.
84 this.updateSyncState_(loadTimeData.getValue('syncData'));
86 $('start-stop-sync').onclick = function(event) {
88 SyncSetupOverlay.showStopSyncingUI();
89 else if (cr.isChromeOS)
90 SyncSetupOverlay.showSetupUI();
92 SyncSetupOverlay.startSignIn();
94 $('customize-sync').onclick = function(event) {
95 SyncSetupOverlay.showSetupUI();
98 // Internet connection section (ChromeOS only).
100 options.network.NetworkList.decorate($('network-list'));
101 options.network.NetworkList.refreshNetworkData(
102 loadTimeData.getValue('networkData'));
105 // On Startup section.
106 Preferences.getInstance().addEventListener('session.restore_on_startup',
107 this.onRestoreOnStartupChanged_.bind(this));
108 Preferences.getInstance().addEventListener(
109 'session.startup_urls',
111 $('startup-set-pages').disabled = event.value.disabled;
114 $('startup-set-pages').onclick = function() {
115 OptionsPage.navigateToPage('startup');
118 // Appearance section.
119 Preferences.getInstance().addEventListener('browser.show_home_button',
120 this.onShowHomeButtonChanged_.bind(this));
122 Preferences.getInstance().addEventListener('homepage',
123 this.onHomePageChanged_.bind(this));
124 Preferences.getInstance().addEventListener('homepage_is_newtabpage',
125 this.onHomePageIsNtpChanged_.bind(this));
127 $('change-home-page').onclick = function(event) {
128 OptionsPage.navigateToPage('homePageOverlay');
131 if ($('set-wallpaper')) {
132 $('set-wallpaper').onclick = function(event) {
133 chrome.send('openWallpaperManager');
137 $('themes-gallery').onclick = function(event) {
138 window.open(loadTimeData.getString('themesGalleryURL'));
140 $('themes-reset').onclick = function(event) {
141 chrome.send('themesReset');
144 if (loadTimeData.getBoolean('profileIsManaged')) {
145 if ($('themes-native-button')) {
146 $('themes-native-button').disabled = true;
147 $('themes-native-button').hidden = true;
149 // Supervised users have just one default theme, even on Linux. So use
150 // the same button for Linux as for the other platforms.
151 $('themes-reset').textContent = loadTimeData.getString('themesReset');
154 // Device section (ChromeOS only).
156 $('keyboard-settings-button').onclick = function(evt) {
157 OptionsPage.navigateToPage('keyboard-overlay');
159 $('pointer-settings-button').onclick = function(evt) {
160 OptionsPage.navigateToPage('pointer-overlay');
165 $('manage-default-search-engines').onclick = function(event) {
166 OptionsPage.navigateToPage('searchEngines');
167 chrome.send('coreOptionsUserMetricsAction',
168 ['Options_ManageSearchEngines']);
170 $('default-search-engine').addEventListener('change',
171 this.setDefaultSearchEngine_);
174 if (loadTimeData.valueExists('profilesInfo')) {
175 $('profiles-section').hidden = false;
177 var profilesList = $('profiles-list');
178 options.browser_options.ProfileList.decorate(profilesList);
179 profilesList.autoExpands = true;
181 // The profiles info data in |loadTimeData| might be stale.
182 this.setProfilesInfo_(loadTimeData.getValue('profilesInfo'));
183 chrome.send('requestProfilesInfo');
185 profilesList.addEventListener('change',
186 this.setProfileViewButtonsStatus_);
187 $('profiles-create').onclick = function(event) {
188 ManageProfileOverlay.showCreateDialog();
190 if (OptionsPage.isSettingsApp()) {
191 $('profiles-app-list-switch').onclick = function(event) {
192 var selectedProfile = self.getSelectedProfileItem_();
193 chrome.send('switchAppListProfile', [selectedProfile.filePath]);
196 $('profiles-manage').onclick = function(event) {
197 ManageProfileOverlay.showManageDialog();
199 $('profiles-delete').onclick = function(event) {
200 var selectedProfile = self.getSelectedProfileItem_();
202 ManageProfileOverlay.showDeleteDialog(selectedProfile);
204 if (loadTimeData.getBoolean('profileIsManaged')) {
205 $('profiles-create').disabled = true;
206 $('profiles-delete').disabled = true;
207 $('profiles-list').canDeleteItems = false;
212 if (!UIAccountTweaks.loggedInAsGuest()) {
213 var pictureWrapper = $('account-picture-wrapper');
214 pictureWrapper.setAttribute('role', 'button');
215 pictureWrapper.tabIndex = 0;
216 function activate(event) {
217 if (event.type == 'click' ||
218 (event.type == 'keydown' && event.keyCode == 32)) {
219 OptionsPage.navigateToPage('changePicture');
222 pictureWrapper.onclick = activate;
223 pictureWrapper.addEventListener('keydown', activate);
227 // Username (canonical email) of the currently logged in user or
228 // |kGuestUser| if a guest session is active.
229 this.username_ = loadTimeData.getString('username');
231 this.updateAccountPicture_();
233 $('account-picture-wrapper').oncontextmenu = function(e) {
237 $('manage-accounts-button').onclick = function(event) {
238 OptionsPage.navigateToPage('accounts');
239 chrome.send('coreOptionsUserMetricsAction',
240 ['Options_ManageAccounts']);
243 $('import-data').onclick = function(event) {
244 ImportDataOverlay.show();
245 chrome.send('coreOptionsUserMetricsAction', ['Import_ShowDlg']);
248 if ($('themes-native-button')) {
249 $('themes-native-button').onclick = function(event) {
250 chrome.send('themesSetNative');
255 // Default browser section.
256 if (!cr.isChromeOS) {
257 $('set-as-default-browser').onclick = function(event) {
258 chrome.send('becomeDefaultBrowser');
261 $('auto-launch').onclick = this.handleAutoLaunchChanged_;
265 $('privacyContentSettingsButton').onclick = function(event) {
266 OptionsPage.navigateToPage('content');
267 OptionsPage.showTab($('cookies-nav-tab'));
268 chrome.send('coreOptionsUserMetricsAction',
269 ['Options_ContentSettings']);
271 $('privacyClearDataButton').onclick = function(event) {
272 OptionsPage.navigateToPage('clearBrowserData');
273 chrome.send('coreOptionsUserMetricsAction', ['Options_ClearData']);
275 $('privacyClearDataButton').hidden = OptionsPage.isSettingsApp();
276 // 'metricsReportingEnabled' element is only present on Chrome branded
278 if ($('metricsReportingEnabled')) {
279 $('metricsReportingEnabled').onclick = function(event) {
280 chrome.send('metricsReportingCheckboxAction',
281 [String(event.currentTarget.checked)]);
285 // Bluetooth (CrOS only).
287 options.system.bluetooth.BluetoothDeviceList.decorate(
288 $('bluetooth-paired-devices-list'));
290 $('bluetooth-add-device').onclick =
291 this.handleAddBluetoothDevice_.bind(this);
293 $('enable-bluetooth').onchange = function(event) {
294 var state = $('enable-bluetooth').checked;
295 chrome.send('bluetoothEnableChange', [Boolean(state)]);
298 $('bluetooth-reconnect-device').onclick = function(event) {
299 var device = $('bluetooth-paired-devices-list').selectedItem;
300 var address = device.address;
301 chrome.send('updateBluetoothDevice', [address, 'connect']);
302 OptionsPage.closeOverlay();
305 $('bluetooth-paired-devices-list').addEventListener('change',
307 var item = $('bluetooth-paired-devices-list').selectedItem;
308 var disabled = !item || item.connected || !item.connectable;
309 $('bluetooth-reconnect-device').disabled = disabled;
313 // Passwords and Forms section.
314 $('autofill-settings').onclick = function(event) {
315 OptionsPage.navigateToPage('autofill');
316 chrome.send('coreOptionsUserMetricsAction',
317 ['Options_ShowAutofillSettings']);
319 $('manage-passwords').onclick = function(event) {
320 OptionsPage.navigateToPage('passwords');
321 OptionsPage.showTab($('passwords-nav-tab'));
322 chrome.send('coreOptionsUserMetricsAction',
323 ['Options_ShowPasswordManager']);
325 if (cr.isChromeOS && UIAccountTweaks.loggedInAsGuest()) {
326 // Disable and turn off Autofill in guest mode.
327 var autofillEnabled = $('autofill-enabled');
328 autofillEnabled.disabled = true;
329 autofillEnabled.checked = false;
330 cr.dispatchSimpleEvent(autofillEnabled, 'change');
331 $('autofill-settings').disabled = true;
333 // Disable and turn off Password Manager in guest mode.
334 var passwordManagerEnabled = $('password-manager-enabled');
335 passwordManagerEnabled.disabled = true;
336 passwordManagerEnabled.checked = false;
337 cr.dispatchSimpleEvent(passwordManagerEnabled, 'change');
338 $('manage-passwords').disabled = true;
342 $('mac-passwords-warning').hidden =
343 !loadTimeData.getBoolean('multiple_profiles');
347 if (!cr.isChromeOS) {
348 $('proxiesConfigureButton').onclick = function(event) {
349 chrome.send('showNetworkProxySettings');
353 // Web Content section.
354 $('fontSettingsCustomizeFontsButton').onclick = function(event) {
355 OptionsPage.navigateToPage('fonts');
356 chrome.send('coreOptionsUserMetricsAction', ['Options_FontSettings']);
358 $('defaultFontSize').onchange = function(event) {
359 var value = event.target.options[event.target.selectedIndex].value;
360 Preferences.setIntegerPref(
361 'webkit.webprefs.default_fixed_font_size',
362 value - OptionsPage.SIZE_DIFFERENCE_FIXED_STANDARD, true);
363 chrome.send('defaultFontSizeAction', [String(value)]);
365 $('defaultZoomFactor').onchange = function(event) {
366 chrome.send('defaultZoomFactorAction',
367 [String(event.target.options[event.target.selectedIndex].value)]);
370 // Languages section.
371 var showLanguageOptions = function(event) {
372 OptionsPage.navigateToPage('languages');
373 chrome.send('coreOptionsUserMetricsAction',
374 ['Options_LanuageAndSpellCheckSettings']);
376 $('language-button').onclick = showLanguageOptions;
377 $('manage-languages').onclick = showLanguageOptions;
379 // Downloads section.
380 Preferences.getInstance().addEventListener('download.default_directory',
381 this.onDefaultDownloadDirectoryChanged_.bind(this));
382 $('downloadLocationChangeButton').onclick = function(event) {
383 chrome.send('selectDownloadLocation');
385 if (!cr.isChromeOS) {
386 $('autoOpenFileTypesResetToDefault').onclick = function(event) {
387 chrome.send('autoOpenFileTypesAction');
390 $('disable-drive-row').hidden =
391 UIAccountTweaks.loggedInAsLocallyManagedUser();
394 // HTTPS/SSL section.
395 if (cr.isWindows || cr.isMac) {
396 $('certificatesManageButton').onclick = function(event) {
397 chrome.send('showManageSSLCertificates');
400 $('certificatesManageButton').onclick = function(event) {
401 OptionsPage.navigateToPage('certificates');
402 chrome.send('coreOptionsUserMetricsAction',
403 ['Options_ManageSSLCertificates']);
407 // Cloud Print section.
408 // 'cloudPrintProxyEnabled' is true for Chrome branded builds on
409 // certain platforms, or could be enabled by a lab.
410 if (!cr.isChromeOS) {
411 $('cloudPrintConnectorSetupButton').onclick = function(event) {
412 if ($('cloudPrintManageButton').style.display == 'none') {
413 // Disable the button, set its text to the intermediate state.
414 $('cloudPrintConnectorSetupButton').textContent =
415 loadTimeData.getString('cloudPrintConnectorEnablingButton');
416 $('cloudPrintConnectorSetupButton').disabled = true;
417 chrome.send('showCloudPrintSetupDialog');
419 chrome.send('disableCloudPrintConnector');
423 $('cloudPrintManageButton').onclick = function(event) {
424 chrome.send('showCloudPrintManagePage');
427 if (loadTimeData.getBoolean('cloudPrintShowMDnsOptions')) {
428 $('cloudprint-options-mdns').hidden = false;
429 $('cloudprint-options-nomdns').hidden = true;
430 $('cloudPrintDevicesPageButton').onclick = function() {
431 chrome.send('showCloudPrintDevicesPage');
435 // Accessibility section (CrOS only).
437 $('accessibility-spoken-feedback-check').onchange = function(event) {
438 chrome.send('spokenFeedbackChange',
439 [$('accessibility-spoken-feedback-check').checked]);
442 $('accessibility-high-contrast-check').onchange = function(event) {
443 chrome.send('highContrastChange',
444 [$('accessibility-high-contrast-check').checked]);
446 $('accessibility-sticky-keys').hidden =
447 !loadTimeData.getBoolean('enableStickyKeys');
450 // Display management section (CrOS only).
452 $('display-options').onclick = function(event) {
453 OptionsPage.navigateToPage('display');
454 chrome.send('coreOptionsUserMetricsAction',
455 ['Options_Display']);
459 // Factory reset section (CrOS only).
461 $('factory-reset-restart').onclick = function(event) {
462 OptionsPage.navigateToPage('factoryResetData');
467 if (!cr.isChromeOS) {
468 var updateGpuRestartButton = function() {
469 $('gpu-mode-reset-restart').hidden =
470 loadTimeData.getBoolean('gpuEnabledAtStart') ==
471 $('gpu-mode-checkbox').checked;
473 Preferences.getInstance().addEventListener(
474 $('gpu-mode-checkbox').getAttribute('pref'),
475 updateGpuRestartButton);
476 $('gpu-mode-reset-restart-button').onclick = function(event) {
477 chrome.send('restartBrowser');
479 updateGpuRestartButton();
482 // Reset profile settings section.
483 $('reset-profile-settings').onclick = function(event) {
484 OptionsPage.navigateToPage('resetProfileSettings');
489 didShowPage: function() {
490 $('search-field').focus();
494 * Called after all C++ UI handlers have called InitializePage to notify
495 * that initialization is complete.
498 notifyInitializationComplete_: function() {
499 this.initializationComplete_ = true;
500 cr.dispatchSimpleEvent(document, 'initializationComplete');
504 * Event listener for the 'session.restore_on_startup' pref.
505 * @param {Event} event The preference change event.
508 onRestoreOnStartupChanged_: function(event) {
509 /** @const */ var showHomePageValue = 0;
511 if (event.value.value == showHomePageValue) {
512 // If the user previously selected "Show the homepage", the
513 // preference will already be migrated to "Open a specific page". So
514 // the only way to reach this code is if the 'restore on startup'
515 // preference is managed.
516 assert(event.value.controlledBy);
518 // Select "open the following pages" and lock down the list of URLs
519 // to reflect the intention of the policy.
520 $('startup-show-pages').checked = true;
521 StartupOverlay.getInstance().setControlsDisabled(true);
523 // Re-enable the controls in the startup overlay if necessary.
524 StartupOverlay.getInstance().updateControlStates();
529 * Handler for messages sent from the main uber page.
530 * @param {Event} e The 'message' event from the uber page.
533 handleWindowMessage_: function(e) {
534 if (e.data.method == 'frameSelected')
535 $('search-field').focus();
539 * Shows the given section.
540 * @param {HTMLElement} section The section to be shown.
541 * @param {HTMLElement} container The container for the section. Must be
542 * inside of |section|.
543 * @param {boolean} animate Indicate if the expansion should be animated.
546 showSection_: function(section, container, animate) {
548 this.addTransitionEndListener_(section);
551 section.hidden = false;
553 var expander = function() {
554 // Reveal the section using a WebKit transition if animating.
556 section.classList.add('sliding');
557 section.style.height = container.offsetHeight + 'px';
559 section.style.height = 'auto';
563 // Delay starting the transition if animating so that hidden change will
566 setTimeout(expander, 0);
572 * Shows the given section, with animation.
573 * @param {HTMLElement} section The section to be shown.
574 * @param {HTMLElement} container The container for the section. Must be
575 * inside of |section|.
578 showSectionWithAnimation_: function(section, container) {
579 this.showSection_(section, container, /*animate */ true);
583 * See showSectionWithAnimation_.
585 hideSectionWithAnimation_: function(section, container) {
586 this.addTransitionEndListener_(section);
588 // Before we start hiding the section, we need to set
589 // the height to a pixel value.
590 section.style.height = container.offsetHeight + 'px';
592 // Delay starting the transition so that the height change will be
594 setTimeout(function() {
595 // Hide the section using a WebKit transition.
596 section.classList.add('sliding');
597 section.style.height = '';
602 * See showSectionWithAnimation_.
604 toggleSectionWithAnimation_: function(section, container) {
605 if (section.style.height == '')
606 this.showSectionWithAnimation_(section, container);
608 this.hideSectionWithAnimation_(section, container);
612 * Scrolls the settings page to make the section visible auto-expanding
613 * advanced settings if required. The transition is not animated. This
614 * method is used to ensure that a section associated with an overlay
615 * is visible when the overlay is closed.
616 * @param {!Element} section The section to make visible.
619 scrollToSection_: function(section) {
620 var advancedSettings = $('advanced-settings');
621 var container = $('advanced-settings-container');
622 if (advancedSettings.hidden && section.parentNode == container) {
623 this.showSection_($('advanced-settings'),
624 $('advanced-settings-container'),
625 /* animate */ false);
626 this.updateAdvancedSettingsExpander_();
629 if (!this.initializationComplete_) {
631 var callback = function() {
632 document.removeEventListener('initializationComplete', callback);
633 self.scrollToSection_(section);
635 document.addEventListener('initializationComplete', callback);
639 var pageContainer = $('page-container');
640 // pageContainer.offsetTop is relative to the screen.
641 var pageTop = pageContainer.offsetTop;
642 var sectionBottom = section.offsetTop + section.offsetHeight;
643 // section.offsetTop is relative to the 'page-container'.
644 var sectionTop = section.offsetTop;
645 if (pageTop + sectionBottom > document.body.scrollHeight ||
646 pageTop + sectionTop < 0) {
647 pageContainer.oldScrollTop = -pageTop;
648 // Currently not all layout updates are guaranteed to precede the
649 // initializationComplete event (for example 'set-as-default-browser'
650 // button) leaving some uncertainty in the optimal scroll position.
651 // The section is placed approximately in the middle of the screen.
652 pageContainer.style.top = document.body.scrollHeight / 2 -
653 sectionBottom + 'px';
658 * Adds a |webkitTransitionEnd| listener to the given section so that
659 * it can be animated. The listener will only be added to a given section
660 * once, so this can be called as multiple times.
661 * @param {HTMLElement} section The section to be animated.
664 addTransitionEndListener_: function(section) {
665 if (section.hasTransitionEndListener_)
668 section.addEventListener('webkitTransitionEnd',
669 this.onTransitionEnd_.bind(this));
670 section.hasTransitionEndListener_ = true;
674 * Called after an animation transition has ended.
677 onTransitionEnd_: function(event) {
678 if (event.propertyName != 'height')
681 var section = event.target;
683 // Disable WebKit transitions.
684 section.classList.remove('sliding');
686 if (section.style.height == '') {
687 // Hide the content so it can't get tab focus.
688 section.hidden = true;
690 // Set the section height to 'auto' to allow for size changes
691 // (due to font change or dynamic content).
692 section.style.height = 'auto';
696 updateAdvancedSettingsExpander_: function() {
697 var expander = $('advanced-settings-expander');
698 if ($('advanced-settings').style.height == '')
699 expander.textContent = loadTimeData.getString('showAdvancedSettings');
701 expander.textContent = loadTimeData.getString('hideAdvancedSettings');
705 * Updates the sync section with the given state.
706 * @param {Object} syncData A bunch of data records that describe the status
707 * of the sync system.
710 updateSyncState_: function(syncData) {
711 if (!syncData.signinAllowed &&
712 (!syncData.supervisedUser || !cr.isChromeOS)) {
713 $('sync-section').hidden = true;
717 $('sync-section').hidden = false;
719 var subSection = $('sync-section').firstChild;
721 if (subSection.nodeType == Node.ELEMENT_NODE)
722 subSection.hidden = syncData.supervisedUser;
723 subSection = subSection.nextSibling;
726 if (syncData.supervisedUser) {
727 $('account-picture-wrapper').hidden = false;
728 $('sync-general').hidden = false;
729 $('sync-status').hidden = true;
733 // If the user gets signed out while the advanced sync settings dialog is
734 // visible, say, due to a dashboard clear, close the dialog.
735 // However, if the user gets signed out as a result of abandoning first
736 // time sync setup, do not call closeOverlay as it will redirect the
737 // browser to the main settings page and override any in-progress
738 // user-initiated navigation. See crbug.com/278030.
739 // Note: SyncSetupOverlay.closeOverlay is a no-op if the overlay is
741 if (this.signedIn_ && !syncData.signedIn && !syncData.setupInProgress)
742 SyncSetupOverlay.closeOverlay();
744 this.signedIn_ = syncData.signedIn;
746 // Display the "advanced settings" button if we're signed in and sync is
747 // not managed/disabled. If the user is signed in, but sync is disabled,
748 // this button is used to re-enable sync.
749 var customizeSyncButton = $('customize-sync');
750 customizeSyncButton.hidden = !this.signedIn_ ||
752 !syncData.syncSystemEnabled;
754 // Only modify the customize button's text if the new text is different.
755 // Otherwise, it can affect search-highlighting in the settings page.
756 // See http://crbug.com/268265.
757 var customizeSyncButtonNewText = syncData.setupCompleted ?
758 loadTimeData.getString('customizeSync') :
759 loadTimeData.getString('syncButtonTextStart');
760 if (customizeSyncButton.textContent != customizeSyncButtonNewText)
761 customizeSyncButton.textContent = customizeSyncButtonNewText;
763 // Disable the "sign in" button if we're currently signing in, or if we're
764 // already signed in and signout is not allowed.
765 var signInButton = $('start-stop-sync');
766 signInButton.disabled = syncData.setupInProgress ||
767 !syncData.signoutAllowed;
768 if (!syncData.signoutAllowed)
769 $('start-stop-sync-indicator').setAttribute('controlled-by', 'policy');
771 $('start-stop-sync-indicator').removeAttribute('controlled-by');
773 // Hide the "sign in" button on Chrome OS, and show it on desktop Chrome.
774 signInButton.hidden = cr.isChromeOS;
776 signInButton.textContent =
778 loadTimeData.getString('syncButtonTextStop') :
779 syncData.setupInProgress ?
780 loadTimeData.getString('syncButtonTextInProgress') :
781 loadTimeData.getString('syncButtonTextSignIn');
782 $('start-stop-sync-indicator').hidden = signInButton.hidden;
784 // TODO(estade): can this just be textContent?
785 $('sync-status-text').innerHTML = syncData.statusText;
786 var statusSet = syncData.statusText.length != 0;
787 $('sync-overview').hidden = statusSet;
788 $('sync-status').hidden = !statusSet;
790 $('sync-action-link').textContent = syncData.actionLinkText;
791 // Don't show the action link if it is empty or undefined.
792 $('sync-action-link').hidden = syncData.actionLinkText.length == 0;
793 $('sync-action-link').disabled = syncData.managed ||
794 !syncData.syncSystemEnabled;
796 // On Chrome OS, sign out the user and sign in again to get fresh
797 // credentials on auth errors.
798 $('sync-action-link').onclick = function(event) {
799 if (cr.isChromeOS && syncData.hasError)
800 SyncSetupOverlay.doSignOutOnAuthError();
802 SyncSetupOverlay.showSetupUI();
805 if (syncData.hasError)
806 $('sync-status').classList.add('sync-error');
808 $('sync-status').classList.remove('sync-error');
810 // Disable the "customize / set up sync" button if sync has an
811 // unrecoverable error. Also disable the button if sync has not been set
812 // up and the user is being presented with a link to re-auth.
813 // See crbug.com/289791.
814 customizeSyncButton.disabled =
815 syncData.hasUnrecoverableError ||
816 (!syncData.setupCompleted && !$('sync-action-link').hidden);
818 // Move #enable-auto-login-checkbox to a different location on CrOS.
820 $('sync-general').insertBefore($('sync-status').nextSibling,
821 $('enable-auto-login-checkbox'));
823 $('enable-auto-login-checkbox').hidden = !syncData.autoLoginVisible;
827 * Update the UI depending on whether the current profile manages any
829 * @param {boolean} value True if the current profile manages any supervised
832 updateManagesSupervisedUsers_: function(value) {
833 $('profiles-supervised-dashboard-tip').hidden = !value;
837 * Get the start/stop sync button DOM element. Used for testing.
838 * @return {DOMElement} The start/stop sync button.
841 getStartStopSyncButton_: function() {
842 return $('start-stop-sync');
846 * Event listener for the 'show home button' preference. Shows/hides the
847 * UI for changing the home page with animation, unless this is the first
848 * time this function is called, in which case there is no animation.
849 * @param {Event} event The preference change event.
851 onShowHomeButtonChanged_: function(event) {
852 var section = $('change-home-page-section');
853 if (this.onShowHomeButtonChangedCalled_) {
854 var container = $('change-home-page-section-container');
855 if (event.value.value)
856 this.showSectionWithAnimation_(section, container);
858 this.hideSectionWithAnimation_(section, container);
860 section.hidden = !event.value.value;
861 this.onShowHomeButtonChangedCalled_ = true;
866 * Event listener for the 'homepage is NTP' preference. Updates the label
867 * next to the 'Change' button.
868 * @param {Event} event The preference change event.
870 onHomePageIsNtpChanged_: function(event) {
871 if (!event.value.uncommitted) {
872 $('home-page-url').hidden = event.value.value;
873 $('home-page-ntp').hidden = !event.value.value;
878 * Event listener for changes to the homepage preference. Updates the label
879 * next to the 'Change' button.
880 * @param {Event} event The preference change event.
882 onHomePageChanged_: function(event) {
883 if (!event.value.uncommitted)
884 $('home-page-url').textContent = this.stripHttp_(event.value.value);
888 * Removes the 'http://' from a URL, like the omnibox does. If the string
889 * doesn't start with 'http://' it is returned unchanged.
890 * @param {string} url The url to be processed
891 * @return {string} The url with the 'http://' removed.
893 stripHttp_: function(url) {
894 return url.replace(/^http:\/\//, '');
898 * Shows the autoLaunch preference and initializes its checkbox value.
899 * @param {bool} enabled Whether autolaunch is enabled or or not.
902 updateAutoLaunchState_: function(enabled) {
903 $('auto-launch-option').hidden = false;
904 $('auto-launch').checked = enabled;
908 * Called when the value of the download.default_directory preference
910 * @param {Event} event Change event.
913 onDefaultDownloadDirectoryChanged_: function(event) {
914 $('downloadLocationPath').value = event.value.value;
916 // On ChromeOS, replace /special/drive/root with Drive for drive paths,
917 // /home/chronos/user/Downloads or /home/chronos/u-<hash>/Downloads
918 // with Downloads for local paths, and '/' with ' \u203a ' (angled quote
919 // sign) everywhere. The modified path is used only for display purpose.
920 var path = $('downloadLocationPath').value;
921 path = path.replace(/^\/special\/drive\/root/, 'Google Drive');
922 path = path.replace(/^\/home\/chronos\/(user|u-[^\/]*)\//, '');
923 path = path.replace(/\//g, ' \u203a ');
924 $('downloadLocationPath').value = path;
926 $('download-location-label').classList.toggle('disabled',
927 event.value.disabled);
928 $('downloadLocationChangeButton').disabled = event.value.disabled;
932 * Update the Default Browsers section based on the current state.
933 * @param {string} statusString Description of the current default state.
934 * @param {boolean} isDefault Whether or not the browser is currently
936 * @param {boolean} canBeDefault Whether or not the browser can be default.
939 updateDefaultBrowserState_: function(statusString, isDefault,
941 if (!cr.isChromeOS) {
942 var label = $('default-browser-state');
943 label.textContent = statusString;
945 $('set-as-default-browser').hidden = !canBeDefault || isDefault;
950 * Clears the search engine popup.
953 clearSearchEngines_: function() {
954 $('default-search-engine').textContent = '';
958 * Updates the search engine popup with the given entries.
959 * @param {Array} engines List of available search engines.
960 * @param {number} defaultValue The value of the current default engine.
961 * @param {boolean} defaultManaged Whether the default search provider is
962 * managed. If true, the default search provider can't be changed.
965 updateSearchEngines_: function(engines, defaultValue, defaultManaged) {
966 this.clearSearchEngines_();
967 engineSelect = $('default-search-engine');
968 engineSelect.disabled = defaultManaged;
969 if (defaultManaged && defaultValue == -1)
971 engineCount = engines.length;
972 var defaultIndex = -1;
973 for (var i = 0; i < engineCount; i++) {
974 var engine = engines[i];
975 var option = new Option(engine.name, engine.index);
976 if (defaultValue == option.value)
978 engineSelect.appendChild(option);
980 if (defaultIndex >= 0)
981 engineSelect.selectedIndex = defaultIndex;
985 * Set the default search engine based on the popup selection.
988 setDefaultSearchEngine_: function() {
989 var engineSelect = $('default-search-engine');
990 var selectedIndex = engineSelect.selectedIndex;
991 if (selectedIndex >= 0) {
992 var selection = engineSelect.options[selectedIndex];
993 chrome.send('setDefaultSearchEngine', [String(selection.value)]);
998 * Sets or clear whether Chrome should Auto-launch on computer startup.
1001 handleAutoLaunchChanged_: function() {
1002 chrome.send('toggleAutoLaunch', [$('auto-launch').checked]);
1006 * Get the selected profile item from the profile list. This also works
1007 * correctly if the list is not displayed.
1008 * @return {Object} the profile item object, or null if nothing is selected.
1011 getSelectedProfileItem_: function() {
1012 var profilesList = $('profiles-list');
1013 if (profilesList.hidden) {
1014 if (profilesList.dataModel.length > 0)
1015 return profilesList.dataModel.item(0);
1017 return profilesList.selectedItem;
1023 * Helper function to set the status of profile view buttons to disabled or
1024 * enabled, depending on the number of profiles and selection status of the
1028 setProfileViewButtonsStatus_: function() {
1029 var profilesList = $('profiles-list');
1030 var selectedProfile = profilesList.selectedItem;
1031 var hasSelection = selectedProfile != null;
1032 var hasSingleProfile = profilesList.dataModel.length == 1;
1033 var isManaged = loadTimeData.getBoolean('profileIsManaged');
1034 $('profiles-manage').disabled = !hasSelection ||
1035 !selectedProfile.isCurrentProfile;
1036 if (hasSelection && !selectedProfile.isCurrentProfile)
1037 $('profiles-manage').title = loadTimeData.getString('currentUserOnly');
1039 $('profiles-manage').title = '';
1040 $('profiles-delete').disabled = isManaged ||
1041 (!hasSelection && !hasSingleProfile);
1042 if (OptionsPage.isSettingsApp()) {
1043 $('profiles-app-list-switch').disabled = !hasSelection ||
1044 selectedProfile.isCurrentProfile;
1046 var importData = $('import-data');
1048 importData.disabled = $('import-data').disabled = hasSelection &&
1049 !selectedProfile.isCurrentProfile;
1054 * Display the correct dialog layout, depending on how many profiles are
1056 * @param {number} numProfiles The number of profiles to display.
1059 setProfileViewSingle_: function(numProfiles) {
1060 var hasSingleProfile = numProfiles == 1;
1061 $('profiles-list').hidden = hasSingleProfile;
1062 $('profiles-single-message').hidden = !hasSingleProfile;
1063 $('profiles-manage').hidden =
1064 hasSingleProfile || OptionsPage.isSettingsApp();
1065 $('profiles-delete').textContent = hasSingleProfile ?
1066 loadTimeData.getString('profilesDeleteSingle') :
1067 loadTimeData.getString('profilesDelete');
1068 if (OptionsPage.isSettingsApp())
1069 $('profiles-app-list-switch').hidden = hasSingleProfile;
1073 * Adds all |profiles| to the list.
1074 * @param {Array.<Object>} profiles An array of profile info objects.
1075 * each object is of the form:
1077 * name: "Profile Name",
1078 * iconURL: "chrome://path/to/icon/image",
1079 * filePath: "/path/to/profile/data/on/disk",
1080 * isCurrentProfile: false
1084 setProfilesInfo_: function(profiles) {
1085 this.setProfileViewSingle_(profiles.length);
1086 // add it to the list, even if the list is hidden so we can access it
1088 $('profiles-list').dataModel = new ArrayDataModel(profiles);
1090 // Received new data. If showing the "manage" overlay, keep it up to
1091 // date. If showing the "delete" overlay, close it.
1092 if (ManageProfileOverlay.getInstance().visible &&
1093 !$('manage-profile-overlay-manage').hidden) {
1094 ManageProfileOverlay.showManageDialog();
1096 ManageProfileOverlay.getInstance().visible = false;
1099 this.setProfileViewButtonsStatus_();
1103 * Reports managed user import errors to the ManagedUserImportOverlay.
1104 * @param {string} error The error message to display.
1107 showManagedUserImportError_: function(error) {
1108 ManagedUserImportOverlay.onError(error);
1112 * Reports successful importing of a managed user to
1113 * the ManagedUserImportOverlay.
1116 showManagedUserImportSuccess_: function() {
1117 ManagedUserImportOverlay.onSuccess();
1121 * Reports an error to the "create" overlay during profile creation.
1122 * @param {string} error The error message to display.
1125 showCreateProfileError_: function(error) {
1126 CreateProfileOverlay.onError(error);
1130 * Sends a warning message to the "create" overlay during profile creation.
1131 * @param {string} warning The warning message to display.
1134 showCreateProfileWarning_: function(warning) {
1135 CreateProfileOverlay.onWarning(warning);
1139 * Reports successful profile creation to the "create" overlay.
1140 * @param {Object} profileInfo An object of the form:
1142 * name: "Profile Name",
1143 * filePath: "/path/to/profile/data/on/disk"
1144 * isManaged: (true|false),
1148 showCreateProfileSuccess_: function(profileInfo) {
1149 CreateProfileOverlay.onSuccess(profileInfo);
1153 * Returns the currently active profile for this browser window.
1154 * @return {Object} A profile info object.
1157 getCurrentProfile_: function() {
1158 for (var i = 0; i < $('profiles-list').dataModel.length; i++) {
1159 var profile = $('profiles-list').dataModel.item(i);
1160 if (profile.isCurrentProfile)
1165 'There should always be a current profile, but none found.');
1168 setNativeThemeButtonEnabled_: function(enabled) {
1169 var button = $('themes-native-button');
1171 button.disabled = !enabled;
1174 setThemesResetButtonEnabled_: function(enabled) {
1175 $('themes-reset').disabled = !enabled;
1179 * (Re)loads IMG element with current user account picture.
1182 updateAccountPicture_: function() {
1183 var picture = $('account-picture');
1185 picture.src = 'chrome://userimage/' + this.username_ + '?id=' +
1191 * Handle the 'add device' button click.
1194 handleAddBluetoothDevice_: function() {
1195 chrome.send('findBluetoothDevices');
1196 OptionsPage.showPageByName('bluetooth', false);
1200 * Enables factory reset section.
1203 enableFactoryResetSection_: function() {
1204 $('factory-reset-section').hidden = false;
1208 * Set the checked state of the metrics reporting checkbox.
1211 setMetricsReportingCheckboxState_: function(checked, disabled) {
1212 $('metricsReportingEnabled').checked = checked;
1213 $('metricsReportingEnabled').disabled = disabled;
1219 setMetricsReportingSettingVisibility_: function(visible) {
1221 $('metricsReportingSetting').style.display = 'block';
1223 $('metricsReportingSetting').style.display = 'none';
1227 * Set the visibility of the password generation checkbox.
1230 setPasswordGenerationSettingVisibility_: function(visible) {
1232 $('password-generation-checkbox').style.display = 'block';
1234 $('password-generation-checkbox').style.display = 'none';
1238 * Set the font size selected item. This item actually reflects two
1239 * preferences: the default font size and the default fixed font size.
1241 * @param {Object} pref Information about the font size preferences.
1242 * @param {number} pref.value The value of the default font size pref.
1243 * @param {boolean} pref.disabled True if either pref not user modifiable.
1244 * @param {string} pref.controlledBy The source of the pref value(s) if
1245 * either pref is currently not controlled by the user.
1248 setFontSize_: function(pref) {
1249 var selectCtl = $('defaultFontSize');
1250 selectCtl.disabled = pref.disabled;
1251 // Create a synthetic pref change event decorated as
1252 // CoreOptionsHandler::CreateValueForPref() does.
1253 var event = new Event('synthetic-font-size');
1256 controlledBy: pref.controlledBy,
1257 disabled: pref.disabled
1259 $('font-size-indicator').handlePrefChange(event);
1261 for (var i = 0; i < selectCtl.options.length; i++) {
1262 if (selectCtl.options[i].value == pref.value) {
1263 selectCtl.selectedIndex = i;
1265 selectCtl.remove($('Custom').index);
1270 // Add/Select Custom Option in the font size label list.
1272 var option = new Option(loadTimeData.getString('fontSizeLabelCustom'),
1274 option.setAttribute('id', 'Custom');
1275 selectCtl.add(option);
1277 $('Custom').selected = true;
1281 * Populate the page zoom selector with values received from the caller.
1282 * @param {Array} items An array of items to populate the selector.
1283 * each object is an array with three elements as follows:
1284 * 0: The title of the item (string).
1285 * 1: The value of the item (number).
1286 * 2: Whether the item should be selected (boolean).
1289 setupPageZoomSelector_: function(items) {
1290 var element = $('defaultZoomFactor');
1292 // Remove any existing content.
1293 element.textContent = '';
1295 // Insert new child nodes into select element.
1296 var value, title, selected;
1297 for (var i = 0; i < items.length; i++) {
1298 title = items[i][0];
1299 value = items[i][1];
1300 selected = items[i][2];
1301 element.appendChild(new Option(title, value, false, selected));
1306 * Shows/hides the autoOpenFileTypesResetToDefault button and label, with
1308 * @param {boolean} display Whether to show the button and label or not.
1311 setAutoOpenFileTypesDisplayed_: function(display) {
1315 if ($('advanced-settings').hidden) {
1316 // If the Advanced section is hidden, don't animate the transition.
1317 $('auto-open-file-types-section').hidden = !display;
1320 this.showSectionWithAnimation_(
1321 $('auto-open-file-types-section'),
1322 $('auto-open-file-types-container'));
1324 this.hideSectionWithAnimation_(
1325 $('auto-open-file-types-section'),
1326 $('auto-open-file-types-container'));
1332 * Set the enabled state for the proxy settings button.
1335 setupProxySettingsSection_: function(disabled, extensionControlled) {
1336 if (!cr.isChromeOS) {
1337 $('proxiesConfigureButton').disabled = disabled;
1338 $('proxiesLabel').textContent =
1339 loadTimeData.getString(extensionControlled ?
1340 'proxiesLabelExtension' : 'proxiesLabelSystem');
1345 * Set the Cloud Print proxy UI to enabled, disabled, or processing.
1348 setupCloudPrintConnectorSection_: function(disabled, label, allowed) {
1349 if (!cr.isChromeOS) {
1350 $('cloudPrintConnectorLabel').textContent = label;
1351 if (disabled || !allowed) {
1352 $('cloudPrintConnectorSetupButton').textContent =
1353 loadTimeData.getString('cloudPrintConnectorDisabledButton');
1354 $('cloudPrintManageButton').style.display = 'none';
1356 $('cloudPrintConnectorSetupButton').textContent =
1357 loadTimeData.getString('cloudPrintConnectorEnabledButton');
1358 $('cloudPrintManageButton').style.display = 'inline';
1360 $('cloudPrintConnectorSetupButton').disabled = !allowed;
1367 removeCloudPrintConnectorSection_: function() {
1368 if (!cr.isChromeOS) {
1369 var connectorSectionElm = $('cloud-print-connector-section');
1370 if (connectorSectionElm)
1371 connectorSectionElm.parentNode.removeChild(connectorSectionElm);
1376 * Set the initial state of the spoken feedback checkbox.
1379 setSpokenFeedbackCheckboxState_: function(checked) {
1380 $('accessibility-spoken-feedback-check').checked = checked;
1384 * Set the initial state of the high contrast checkbox.
1387 setHighContrastCheckboxState_: function(checked) {
1388 $('accessibility-high-contrast-check').checked = checked;
1392 * Set the initial state of the virtual keyboard checkbox.
1395 setVirtualKeyboardCheckboxState_: function(checked) {
1396 // TODO(zork): Update UI
1400 * Show/hide mouse settings slider.
1403 showMouseControls_: function(show) {
1404 $('mouse-settings').hidden = !show;
1408 * Show/hide touchpad-related settings.
1411 showTouchpadControls_: function(show) {
1412 $('touchpad-settings').hidden = !show;
1413 $('accessibility-tap-dragging').hidden = !show;
1417 * Activate the Bluetooth settings section on the System settings page.
1420 showBluetoothSettings_: function() {
1421 $('bluetooth-devices').hidden = false;
1425 * Dectivates the Bluetooth settings section from the System settings page.
1428 hideBluetoothSettings_: function() {
1429 $('bluetooth-devices').hidden = true;
1433 * Sets the state of the checkbox indicating if Bluetooth is turned on. The
1434 * state of the "Find devices" button and the list of discovered devices may
1435 * also be affected by a change to the state.
1436 * @param {boolean} checked Flag Indicating if Bluetooth is turned on.
1439 setBluetoothState_: function(checked) {
1440 $('enable-bluetooth').checked = checked;
1441 $('bluetooth-paired-devices-list').parentNode.hidden = !checked;
1442 $('bluetooth-add-device').hidden = !checked;
1443 $('bluetooth-reconnect-device').hidden = !checked;
1444 // Flush list of previously discovered devices if bluetooth is turned off.
1446 $('bluetooth-paired-devices-list').clear();
1447 $('bluetooth-unpaired-devices-list').clear();
1449 chrome.send('getPairedBluetoothDevices');
1454 * Adds an element to the list of available Bluetooth devices. If an element
1455 * with a matching address is found, the existing element is updated.
1456 * @param {{name: string,
1459 * connected: boolean}} device
1460 * Decription of the Bluetooth device.
1463 addBluetoothDevice_: function(device) {
1464 var list = $('bluetooth-unpaired-devices-list');
1465 // Display the "connecting" (already paired or not yet paired) and the
1466 // paired devices in the same list.
1467 if (device.paired || device.connecting) {
1468 // Test to see if the device is currently in the unpaired list, in which
1469 // case it should be removed from that list.
1470 var index = $('bluetooth-unpaired-devices-list').find(device.address);
1471 if (index != undefined)
1472 $('bluetooth-unpaired-devices-list').deleteItemAtIndex(index);
1473 list = $('bluetooth-paired-devices-list');
1475 // Test to see if the device is currently in the paired list, in which
1476 // case it should be removed from that list.
1477 var index = $('bluetooth-paired-devices-list').find(device.address);
1478 if (index != undefined)
1479 $('bluetooth-paired-devices-list').deleteItemAtIndex(index);
1481 list.appendDevice(device);
1483 // One device can be in the process of pairing. If found, display
1484 // the Bluetooth pairing overlay.
1486 BluetoothPairing.showDialog(device);
1490 * Removes an element from the list of available devices.
1491 * @param {string} address Unique address of the device.
1494 removeBluetoothDevice_: function(address) {
1495 var index = $('bluetooth-unpaired-devices-list').find(address);
1496 if (index != undefined) {
1497 $('bluetooth-unpaired-devices-list').deleteItemAtIndex(index);
1499 index = $('bluetooth-paired-devices-list').find(address);
1500 if (index != undefined)
1501 $('bluetooth-paired-devices-list').deleteItemAtIndex(index);
1506 //Forward public APIs to private implementations.
1508 'addBluetoothDevice',
1509 'enableFactoryResetSection',
1510 'getCurrentProfile',
1511 'getStartStopSyncButton',
1512 'hideBluetoothSettings',
1513 'notifyInitializationComplete',
1514 'removeBluetoothDevice',
1515 'removeCloudPrintConnectorSection',
1517 'setAutoOpenFileTypesDisplayed',
1518 'setBluetoothState',
1520 'setNativeThemeButtonEnabled',
1521 'setHighContrastCheckboxState',
1522 'setMetricsReportingCheckboxState',
1523 'setMetricsReportingSettingVisibility',
1524 'setPasswordGenerationSettingVisibility',
1526 'setSpokenFeedbackCheckboxState',
1527 'setThemesResetButtonEnabled',
1528 'setVirtualKeyboardCheckboxState',
1529 'setupCloudPrintConnectorSection',
1530 'setupPageZoomSelector',
1531 'setupProxySettingsSection',
1532 'showBluetoothSettings',
1533 'showCreateProfileError',
1534 'showCreateProfileSuccess',
1535 'showCreateProfileWarning',
1536 'showManagedUserImportError',
1537 'showManagedUserImportSuccess',
1538 'showMouseControls',
1539 'showTouchpadControls',
1540 'updateAccountPicture',
1541 'updateAutoLaunchState',
1542 'updateDefaultBrowserState',
1543 'updateManagesSupervisedUsers',
1544 'updateSearchEngines',
1545 'updateStartupPages',
1547 ].forEach(function(name) {
1548 BrowserOptions[name] = function() {
1549 var instance = BrowserOptions.getInstance();
1550 return instance[name + '_'].apply(instance, arguments);
1554 if (cr.isChromeOS) {
1556 * Returns username (canonical email) of the user logged in (ChromeOS only).
1557 * @return {string} user email.
1559 // TODO(jhawkins): Investigate the use case for this method.
1560 BrowserOptions.getLoggedInUsername = function() {
1561 return BrowserOptions.getInstance().username_;
1567 BrowserOptions: BrowserOptions