// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. cr.exportPath('options'); /** * @typedef {{actionLinkText: (string|undefined), * childUser: (boolean|undefined), * hasError: (boolean|undefined), * hasUnrecoverableError: (boolean|undefined), * managed: (boolean|undefined), * setupCompleted: (boolean|undefined), * setupInProgress: (boolean|undefined), * signedIn: (boolean|undefined), * signinAllowed: (boolean|undefined), * signoutAllowed: (boolean|undefined), * statusText: (string|undefined), * supervisedUser: (boolean|undefined), * syncSystemEnabled: (boolean|undefined)}} * @see chrome/browser/ui/webui/options/browser_options_handler.cc */ options.SyncStatus; /** * @typedef {{id: string, name: string}} */ options.ExtensionData; /** * @typedef {{name: string, * filePath: string, * isCurrentProfile: boolean, * isSupervised: boolean, * isChild: boolean, * iconUrl: string}} * @see chrome/browser/ui/webui/options/browser_options_handler.cc */ options.Profile; cr.define('options', function() { var OptionsPage = options.OptionsPage; var Page = cr.ui.pageManager.Page; var PageManager = cr.ui.pageManager.PageManager; var ArrayDataModel = cr.ui.ArrayDataModel; var RepeatingButton = cr.ui.RepeatingButton; var HotwordSearchSettingIndicator = options.HotwordSearchSettingIndicator; var NetworkPredictionOptions = { ALWAYS: 0, WIFI_ONLY: 1, NEVER: 2, DEFAULT: 1 }; /** * Encapsulated handling of browser options page. * @constructor * @extends {cr.ui.pageManager.Page} */ function BrowserOptions() { Page.call(this, 'settings', loadTimeData.getString('settingsTitle'), 'settings'); } cr.addSingletonGetter(BrowserOptions); /** * @param {HTMLElement} section The section to show or hide. * @return {boolean} Whether the section should be shown. * @private */ BrowserOptions.shouldShowSection_ = function(section) { // If the section is hidden or hiding, it should be shown. return section.style.height == '' || section.style.height == '0px'; }; BrowserOptions.prototype = { __proto__: Page.prototype, /** * Keeps track of whether the user is signed in or not. * @private {boolean} */ signedIn_: false, /** * Indicates whether signing out is allowed or whether a complete profile * wipe is required to remove the current enterprise account. * @private {boolean} */ signoutAllowed_: true, /** * Keeps track of whether |onShowHomeButtonChanged_| has been called. See * |onShowHomeButtonChanged_|. * @private {boolean} */ onShowHomeButtonChangedCalled_: false, /** * Track if page initialization is complete. All C++ UI handlers have the * chance to manipulate page content within their InitializePage methods. * This flag is set to true after all initializers have been called. * @private {boolean} */ initializationComplete_: false, /** * Current status of "Resolve Timezone by Geolocation" checkbox. * @private {boolean} */ resolveTimezoneByGeolocation_: false, /** * True if system timezone is managed by policy. * @private {boolean} */ systemTimezoneIsManaged_: false, /** * Cached bluetooth adapter state. * @private {?chrome.bluetooth.AdapterState} */ bluetoothAdapterState_: null, /** @override */ initializePage: function() { Page.prototype.initializePage.call(this); var self = this; if (window.top != window) { // The options page is not in its own window. document.body.classList.add('uber-frame'); PageManager.horizontalOffset = 155; } // Ensure that navigation events are unblocked on uber page. A reload of // the settings page while an overlay is open would otherwise leave uber // page in a blocked state, where tab switching is not possible. uber.invokeMethodOnParent('stopInterceptingEvents'); window.addEventListener('message', this.handleWindowMessage_.bind(this)); if (loadTimeData.getBoolean('allowAdvancedSettings')) { $('advanced-settings-expander').onclick = function(e) { var showAdvanced = BrowserOptions.shouldShowSection_($('advanced-settings')); if (showAdvanced) { chrome.send('coreOptionsUserMetricsAction', ['Options_ShowAdvancedSettings']); } self.toggleSectionWithAnimation_( $('advanced-settings'), $('advanced-settings-container')); // If the click was triggered using the keyboard and it showed the // section (rather than hiding it), focus the first element in the // container. if (e.detail == 0 && showAdvanced) { var focusElement = $('advanced-settings-container').querySelector( 'button, input, list, select, a[href]'); if (focusElement) focusElement.focus(); } }; } else { $('advanced-settings-footer').hidden = true; $('advanced-settings').hidden = true; } $('advanced-settings').addEventListener('webkitTransitionEnd', this.updateAdvancedSettingsExpander_.bind(this)); if (loadTimeData.getBoolean('showAbout')) { $('about-button').hidden = false; $('about-button').addEventListener('click', function() { PageManager.showPageByName('help'); chrome.send('coreOptionsUserMetricsAction', ['Options_About']); }); } if (cr.isChromeOS) { UIAccountTweaks.applyGuestSessionVisibility(document); UIAccountTweaks.applyPublicSessionVisibility(document); if (loadTimeData.getBoolean('secondaryUser')) $('secondary-user-banner').hidden = false; } // Sync (Sign in) section. this.updateSyncState_(/** @type {options.SyncStatus} */( loadTimeData.getValue('syncData'))); $('start-stop-sync').onclick = function(event) { if (self.signedIn_) { if (self.signoutAllowed_) SyncSetupOverlay.showStopSyncingUI(); else chrome.send('showDisconnectManagedProfileDialog'); } else if (cr.isChromeOS) { SyncSetupOverlay.showSetupUI(); } else { SyncSetupOverlay.startSignIn(false /* creatingSupervisedUser */); } }; $('customize-sync').onclick = function(event) { SyncSetupOverlay.showSetupUI(); }; // Internet connection section (ChromeOS only). if (cr.isChromeOS) { options.network.NetworkList.decorate($('network-list')); // Show that the network settings are shared if this is a secondary user // in a multi-profile session. if (loadTimeData.getBoolean('secondaryUser')) { var networkIndicator = document.querySelector( '#network-section-header > .controlled-setting-indicator'); networkIndicator.setAttribute('controlled-by', 'shared'); networkIndicator.location = cr.ui.ArrowLocation.TOP_START; } } // On Startup section. Preferences.getInstance().addEventListener('session.restore_on_startup', this.onRestoreOnStartupChanged_.bind(this)); Preferences.getInstance().addEventListener( 'session.startup_urls', function(event) { $('startup-set-pages').disabled = event.value.disabled; }); $('startup-set-pages').onclick = function() { PageManager.showPageByName('startup'); }; // Appearance section. Preferences.getInstance().addEventListener('browser.show_home_button', this.onShowHomeButtonChanged_.bind(this)); Preferences.getInstance().addEventListener('homepage', this.onHomePageChanged_.bind(this)); Preferences.getInstance().addEventListener('homepage_is_newtabpage', this.onHomePageIsNtpChanged_.bind(this)); $('change-home-page').onclick = function(event) { PageManager.showPageByName('homePageOverlay'); chrome.send('coreOptionsUserMetricsAction', ['Options_Homepage_ShowSettings']); }; HotwordSearchSettingIndicator.decorate( $('hotword-search-setting-indicator')); HotwordSearchSettingIndicator.decorate( $('hotword-no-dsp-search-setting-indicator')); var hotwordIndicator = $('hotword-always-on-search-setting-indicator'); HotwordSearchSettingIndicator.decorate(hotwordIndicator); hotwordIndicator.disabledOnErrorSection = $('hotword-always-on-search-checkbox'); chrome.send('requestHotwordAvailable'); chrome.send('requestGoogleNowAvailable'); if ($('set-wallpaper')) { $('set-wallpaper').onclick = function(event) { chrome.send('openWallpaperManager'); chrome.send('coreOptionsUserMetricsAction', ['Options_OpenWallpaperManager']); }; } // Control the hotword-always-on pref with the Hotword Audio // Verification app. $('hotword-always-on-search-checkbox').customChangeHandler = function(event) { if (!$('hotword-always-on-search-checkbox').checked) return false; $('hotword-always-on-search-checkbox').checked = false; chrome.send('launchHotwordAudioVerificationApp', [false]); return true; }; // Open the Hotword Audio Verification app to retrain a voice model. $('hotword-retrain-link').onclick = function(event) { chrome.send('launchHotwordAudioVerificationApp', [true]); }; Preferences.getInstance().addEventListener( 'hotword.always_on_search_enabled', this.onHotwordAlwaysOnChanged_.bind(this)); $('themes-gallery').onclick = function(event) { window.open(loadTimeData.getString('themesGalleryURL')); chrome.send('coreOptionsUserMetricsAction', ['Options_ThemesGallery']); }; $('themes-reset').onclick = function(event) { chrome.send('themesReset'); }; if (loadTimeData.getBoolean('profileIsSupervised')) { if ($('themes-native-button')) { $('themes-native-button').disabled = true; $('themes-native-button').hidden = true; } // Supervised users have just one default theme, even on Linux. So use // the same button for Linux as for the other platforms. $('themes-reset').textContent = loadTimeData.getString('themesReset'); } // Device section (ChromeOS only). if (cr.isChromeOS) { if (loadTimeData.getBoolean('showPowerStatus')) { $('power-settings-link').onclick = function(evt) { PageManager.showPageByName('power-overlay'); chrome.send('coreOptionsUserMetricsAction', ['Options_ShowPowerSettings']); }; $('power-row').hidden = false; } $('keyboard-settings-button').onclick = function(evt) { PageManager.showPageByName('keyboard-overlay'); chrome.send('coreOptionsUserMetricsAction', ['Options_ShowKeyboardSettings']); }; $('pointer-settings-button').onclick = function(evt) { PageManager.showPageByName('pointer-overlay'); chrome.send('coreOptionsUserMetricsAction', ['Options_ShowTouchpadSettings']); }; } // Search section. $('manage-default-search-engines').onclick = function(event) { PageManager.showPageByName('searchEngines'); chrome.send('coreOptionsUserMetricsAction', ['Options_ManageSearchEngines']); }; $('default-search-engine').addEventListener('change', this.setDefaultSearchEngine_); // Users section. if (loadTimeData.valueExists('profilesInfo')) { $('profiles-section').hidden = false; this.maybeShowUserSection_(); var profilesList = $('profiles-list'); options.browser_options.ProfileList.decorate(profilesList); profilesList.autoExpands = true; // The profiles info data in |loadTimeData| might be stale. this.setProfilesInfo_(/** @type {!Array} */( loadTimeData.getValue('profilesInfo'))); chrome.send('requestProfilesInfo'); profilesList.addEventListener('change', this.setProfileViewButtonsStatus_); $('profiles-create').onclick = function(event) { chrome.send('metricsHandler:recordAction', ['Options_ShowCreateProfileDlg']); ManageProfileOverlay.showCreateDialog(); }; if (OptionsPage.isSettingsApp()) { $('profiles-app-list-switch').onclick = function(event) { var selectedProfile = self.getSelectedProfileItem_(); chrome.send('switchAppListProfile', [selectedProfile.filePath]); }; } $('profiles-manage').onclick = function(event) { chrome.send('metricsHandler:recordAction', ['Options_ShowEditProfileDlg']); ManageProfileOverlay.showManageDialog(); }; $('profiles-delete').onclick = function(event) { var selectedProfile = self.getSelectedProfileItem_(); if (selectedProfile) { chrome.send('metricsHandler:recordAction', ['Options_ShowDeleteProfileDlg']); ManageProfileOverlay.showDeleteDialog(selectedProfile); } }; if (loadTimeData.getBoolean('profileIsSupervised')) { $('profiles-create').disabled = true; } if (!loadTimeData.getBoolean('allowProfileDeletion')) { $('profiles-delete').disabled = true; $('profiles-list').canDeleteItems = false; } } if (cr.isChromeOS) { // Username (canonical email) of the currently logged in user or // |kGuestUser| if a guest session is active. this.username_ = loadTimeData.getString('username'); this.updateAccountPicture_(); $('account-picture').onclick = this.showImagerPickerOverlay_; $('change-picture-caption').onclick = this.showImagerPickerOverlay_; $('manage-accounts-button').onclick = function(event) { PageManager.showPageByName('accounts'); chrome.send('coreOptionsUserMetricsAction', ['Options_ManageAccounts']); }; } else { $('import-data').onclick = function(event) { ImportDataOverlay.show(); chrome.send('coreOptionsUserMetricsAction', ['Import_ShowDlg']); }; if ($('themes-native-button')) { $('themes-native-button').onclick = function(event) { chrome.send('themesSetNative'); }; } } // Date and time section (CrOS only). if (cr.isChromeOS) { if ($('set-time-button')) $('set-time-button').onclick = this.handleSetTime_.bind(this); // Timezone if (loadTimeData.getBoolean('enableTimeZoneTrackingOption')) { $('resolve-timezone-by-geolocation-selection').hidden = false; this.resolveTimezoneByGeolocation_ = loadTimeData.getBoolean( 'resolveTimezoneByGeolocationInitialValue'); this.updateTimezoneSectionState_(); Preferences.getInstance().addEventListener( 'settings.resolve_timezone_by_geolocation', this.onResolveTimezoneByGeolocationChanged_.bind(this)); } } // Default browser section. if (!cr.isChromeOS) { if (!loadTimeData.getBoolean('showSetDefault')) { $('set-default-browser-section').hidden = true; } $('set-as-default-browser').onclick = function(event) { chrome.send('becomeDefaultBrowser'); }; } // Privacy section. $('privacyContentSettingsButton').onclick = function(event) { PageManager.showPageByName('content'); OptionsPage.showTab($('cookies-nav-tab')); chrome.send('coreOptionsUserMetricsAction', ['Options_ContentSettings']); }; $('privacyClearDataButton').onclick = function(event) { PageManager.showPageByName('clearBrowserData'); chrome.send('coreOptionsUserMetricsAction', ['Options_ClearData']); }; $('privacyClearDataButton').hidden = OptionsPage.isSettingsApp(); if ($('metrics-reporting-enabled')) { $('metrics-reporting-enabled').checked = loadTimeData.getBoolean('metricsReportingEnabledAtStart'); // A browser restart is never needed to toggle metrics reporting, // and is only needed to toggle crash reporting when using Breakpad. // Crashpad, used on Mac, does not require a browser restart. var togglingMetricsRequiresRestart = !cr.isMac && !cr.isChromeOS; $('metrics-reporting-enabled').onclick = function(event) { chrome.send('metricsReportingCheckboxChanged', [Boolean(event.currentTarget.checked)]); if (cr.isChromeOS) { // 'metricsReportingEnabled' element is only present on Chrome // branded builds, and the 'metricsReportingCheckboxAction' message // is only handled on ChromeOS. chrome.send('metricsReportingCheckboxAction', [String(event.currentTarget.checked)]); } if (togglingMetricsRequiresRestart) { $('metrics-reporting-reset-restart').hidden = loadTimeData.getBoolean('metricsReportingEnabledAtStart') == $('metrics-reporting-enabled').checked; } }; // Initialize restart button if needed. if (togglingMetricsRequiresRestart) { // The localized string has the | symbol on each side of the text that // needs to be made into a button to restart Chrome. We parse the text // and build the button from that. var restartTextFragments = loadTimeData.getString('metricsReportingResetRestart').split('|'); // Assume structure is something like "starting text |link text| // ending text" where both starting text and ending text may or may // not be present, but the split should always be in three pieces. var restartElements = $('metrics-reporting-reset-restart').querySelectorAll('*'); for (var i = 0; i < restartTextFragments.length; i++) { restartElements[i].textContent = restartTextFragments[i]; } restartElements[1].onclick = function(event) { chrome.send('restartBrowser'); }; } } $('networkPredictionOptions').onchange = function(event) { var value = (event.target.checked ? NetworkPredictionOptions.WIFI_ONLY : NetworkPredictionOptions.NEVER); var metric = event.target.metric; Preferences.setIntegerPref( 'net.network_prediction_options', value, true, metric); }; if (loadTimeData.valueExists('showWakeOnWifi') && loadTimeData.getBoolean('showWakeOnWifi')) { $('wake-on-wifi').hidden = false; } // Bluetooth (CrOS only). if (cr.isChromeOS) { // Request the intial bluetooth adapter state. var adapterStateChanged = this.onBluetoothAdapterStateChanged_.bind(this); chrome.bluetooth.getAdapterState(adapterStateChanged); // Set up observers. chrome.bluetooth.onAdapterStateChanged.addListener(adapterStateChanged); var deviceAddedOrChanged = this.onBluetoothDeviceAddedOrChanged_.bind(this); chrome.bluetooth.onDeviceAdded.addListener(deviceAddedOrChanged); chrome.bluetooth.onDeviceChanged.addListener(deviceAddedOrChanged); chrome.bluetooth.onDeviceRemoved.addListener( this.onBluetoothDeviceRemoved_.bind(this)); chrome.bluetoothPrivate.onPairing.addListener( this.onBluetoothPrivatePairing_.bind(this)); // Initialize UI. options.system.bluetooth.BluetoothDeviceList.decorate( $('bluetooth-paired-devices-list')); $('bluetooth-add-device').onclick = this.handleAddBluetoothDevice_.bind(this); $('enable-bluetooth').onchange = function(event) { var state = $('enable-bluetooth').checked; chrome.bluetoothPrivate.setAdapterState({powered: state}, function() { if (chrome.runtime.lastError) { console.error('Error enabling bluetooth:', chrome.runtime.lastError.message); } }); }; $('bluetooth-reconnect-device').onclick = function(event) { chrome.send('coreOptionsUserMetricsAction', ['Options_BluetoothConnectPairedDevice']); var device = $('bluetooth-paired-devices-list').selectedItem; BluetoothPairing.connect(device); }; $('bluetooth-paired-devices-list').addEventListener('change', function() { var item = $('bluetooth-paired-devices-list').selectedItem; var disabled = !item || item.connected || !item.connectable; $('bluetooth-reconnect-device').disabled = disabled; }); } // Passwords and Forms section. $('autofill-settings').onclick = function(event) { PageManager.showPageByName('autofill'); chrome.send('coreOptionsUserMetricsAction', ['Options_ShowAutofillSettings']); }; $('manage-passwords').onclick = function(event) { PageManager.showPageByName('passwords'); OptionsPage.showTab($('passwords-nav-tab')); chrome.send('coreOptionsUserMetricsAction', ['Options_ShowPasswordManager']); }; if (cr.isChromeOS && UIAccountTweaks.loggedInAsGuest()) { // Disable and turn off Autofill in guest mode. var autofillEnabled = $('autofill-enabled'); autofillEnabled.disabled = true; autofillEnabled.checked = false; cr.dispatchSimpleEvent(autofillEnabled, 'change'); $('autofill-settings').disabled = true; // Disable and turn off Password Manager in guest mode. var passwordManagerEnabled = $('password-manager-enabled'); passwordManagerEnabled.disabled = true; passwordManagerEnabled.checked = false; cr.dispatchSimpleEvent(passwordManagerEnabled, 'change'); $('manage-passwords').disabled = true; } if (cr.isMac) { $('mac-passwords-warning').hidden = !loadTimeData.getBoolean('multiple_profiles'); } // Network section. if (!cr.isChromeOS) { $('proxiesConfigureButton').onclick = function(event) { chrome.send('showNetworkProxySettings'); }; } // Device control section. if (cr.isChromeOS && UIAccountTweaks.currentUserIsOwner() && loadTimeData.getBoolean('consumerManagementEnabled')) { $('device-control-section').hidden = false; $('consumer-management-button').onclick = function(event) { PageManager.showPageByName('consumer-management-overlay'); }; } // Easy Unlock section. if (loadTimeData.getBoolean('easyUnlockAllowed')) { $('easy-unlock-section').hidden = false; $('easy-unlock-setup-button').onclick = function(event) { chrome.send('launchEasyUnlockSetup'); }; $('easy-unlock-turn-off-button').onclick = function(event) { PageManager.showPageByName('easyUnlockTurnOffOverlay'); }; } $('easy-unlock-enable-proximity-detection').hidden = !loadTimeData.getBoolean('easyUnlockProximityDetectionAllowed'); // Web Content section. $('fontSettingsCustomizeFontsButton').onclick = function(event) { PageManager.showPageByName('fonts'); chrome.send('coreOptionsUserMetricsAction', ['Options_ShowFontSettings']); }; $('defaultFontSize').onchange = function(event) { var value = event.target.options[event.target.selectedIndex].value; Preferences.setIntegerPref( 'webkit.webprefs.default_fixed_font_size', value - OptionsPage.SIZE_DIFFERENCE_FIXED_STANDARD, true); chrome.send('defaultFontSizeAction', [String(value)]); }; $('defaultZoomFactor').onchange = function(event) { chrome.send('defaultZoomFactorAction', [String(event.target.options[event.target.selectedIndex].value)]); }; // Languages section. var showLanguageOptions = function(event) { PageManager.showPageByName('languages'); chrome.send('coreOptionsUserMetricsAction', ['Options_LanuageAndSpellCheckSettings']); }; $('language-button').onclick = showLanguageOptions; $('manage-languages').onclick = showLanguageOptions; // Downloads section. Preferences.getInstance().addEventListener('download.default_directory', this.onDefaultDownloadDirectoryChanged_.bind(this)); $('downloadLocationChangeButton').onclick = function(event) { chrome.send('selectDownloadLocation'); }; if (cr.isChromeOS) { $('disable-drive-row').hidden = UIAccountTweaks.loggedInAsSupervisedUser(); } $('autoOpenFileTypesResetToDefault').onclick = function(event) { chrome.send('autoOpenFileTypesAction'); }; // HTTPS/SSL section. if (cr.isWindows || cr.isMac) { $('certificatesManageButton').onclick = function(event) { chrome.send('showManageSSLCertificates'); }; } else { $('certificatesManageButton').onclick = function(event) { PageManager.showPageByName('certificates'); chrome.send('coreOptionsUserMetricsAction', ['Options_ManageSSLCertificates']); }; } if (loadTimeData.getBoolean('cloudPrintShowMDnsOptions')) { $('cloudprint-options-mdns').hidden = false; $('cloudPrintDevicesPageButton').onclick = function() { chrome.send('showCloudPrintDevicesPage'); }; } // Accessibility section (CrOS only). if (cr.isChromeOS) { var updateAccessibilitySettingsButton = function() { $('accessibility-settings').hidden = !($('accessibility-spoken-feedback-check').checked); }; Preferences.getInstance().addEventListener( 'settings.accessibility', updateAccessibilitySettingsButton); $('accessibility-learn-more').onclick = function(unused_event) { chrome.send('coreOptionsUserMetricsAction', ['Options_AccessibilityLearnMore']); }; $('accessibility-settings-button').onclick = function(unused_event) { window.open(loadTimeData.getString('accessibilitySettingsURL')); }; $('accessibility-spoken-feedback-check').onchange = updateAccessibilitySettingsButton; updateAccessibilitySettingsButton(); var updateScreenMagnifierCenterFocus = function() { $('accessibility-screen-magnifier-center-focus-check').disabled = !$('accessibility-screen-magnifier-check').checked; }; Preferences.getInstance().addEventListener( $('accessibility-screen-magnifier-check').getAttribute('pref'), updateScreenMagnifierCenterFocus); var updateDelayDropdown = function() { $('accessibility-autoclick-dropdown').disabled = !$('accessibility-autoclick-check').checked; }; Preferences.getInstance().addEventListener( $('accessibility-autoclick-check').getAttribute('pref'), updateDelayDropdown); $('experimental-accessibility-features').hidden = !loadTimeData.getBoolean('enableExperimentalAccessibilityFeatures'); } // Display management section (CrOS only). if (cr.isChromeOS) { $('display-options').onclick = function(event) { PageManager.showPageByName('display'); chrome.send('coreOptionsUserMetricsAction', ['Options_Display']); }; } // Factory reset section (CrOS only). if (cr.isChromeOS) { $('factory-reset-restart').onclick = function(event) { PageManager.showPageByName('factoryResetData'); chrome.send('onPowerwashDialogShow'); }; } // System section. if (!cr.isChromeOS) { var updateGpuRestartButton = function() { $('gpu-mode-reset-restart').hidden = loadTimeData.getBoolean('gpuEnabledAtStart') == $('gpu-mode-checkbox').checked; }; Preferences.getInstance().addEventListener( $('gpu-mode-checkbox').getAttribute('pref'), updateGpuRestartButton); $('gpu-mode-reset-restart-button').onclick = function(event) { chrome.send('restartBrowser'); }; updateGpuRestartButton(); } // Reset profile settings section. $('reset-profile-settings').onclick = function(event) { PageManager.showPageByName('resetProfileSettings'); }; // Extension controlled UI. this.addExtensionControlledBox_('search-section-content', 'search-engine-controlled', true); this.addExtensionControlledBox_('extension-controlled-container', 'homepage-controlled', true); this.addExtensionControlledBox_('startup-section-content', 'startpage-controlled', false); this.addExtensionControlledBox_('newtab-section-content', 'newtab-controlled', false); this.addExtensionControlledBox_('proxy-section-content', 'proxy-controlled', true); document.body.addEventListener('click', function(e) { var target = assertInstanceof(e.target, Node); var button = findAncestor(target, function(el) { return el.tagName == 'BUTTON' && el.dataset.extensionId !== undefined && el.dataset.extensionId.length; }); if (button) chrome.send('disableExtension', [button.dataset.extensionId]); }); }, /** @override */ didShowPage: function() { $('search-field').focus(); }, /** * Called after all C++ UI handlers have called InitializePage to notify * that initialization is complete. * @private */ notifyInitializationComplete_: function() { this.initializationComplete_ = true; cr.dispatchSimpleEvent(document, 'initializationComplete'); }, /** * Event listener for the 'session.restore_on_startup' pref. * @param {Event} event The preference change event. * @private */ onRestoreOnStartupChanged_: function(event) { /** @const */ var showHomePageValue = 0; if (event.value.value == showHomePageValue) { // If the user previously selected "Show the homepage", the // preference will already be migrated to "Open a specific page". So // the only way to reach this code is if the 'restore on startup' // preference is managed. assert(event.value.controlledBy); // Select "open the following pages" and lock down the list of URLs // to reflect the intention of the policy. $('startup-show-pages').checked = true; StartupOverlay.getInstance().setControlsDisabled(true); } else { // Re-enable the controls in the startup overlay if necessary. StartupOverlay.getInstance().updateControlStates(); } }, /** * Handler for messages sent from the main uber page. * @param {Event} e The 'message' event from the uber page. * @private */ handleWindowMessage_: function(e) { if ((/** @type {{method: string}} */(e.data)).method == 'frameSelected') $('search-field').focus(); }, /** * Animatedly changes height |from| a px number |to| a px number. * @param {HTMLElement} section The section to animate. * @param {HTMLElement} container The container of |section|. * @param {boolean} showing Whether to go from 0 -> container height or * container height -> 0. * @private */ animatedSectionHeightChange_: function(section, container, showing) { // If the section is already animating, dispatch a synthetic transition // end event as the upcoming code will cancel the current one. if (section.classList.contains('sliding')) cr.dispatchSimpleEvent(section, 'webkitTransitionEnd'); this.addTransitionEndListener_(section); section.hidden = false; section.style.height = (showing ? 0 : container.offsetHeight) + 'px'; section.classList.add('sliding'); // Force a style recalc before starting the animation. /** @suppress {suspiciousCode} */ section.offsetHeight; section.style.height = (showing ? container.offsetHeight : 0) + 'px'; }, /** * Shows the given section. * @param {HTMLElement} section The section to be shown. * @param {HTMLElement} container The container for the section. Must be * inside of |section|. * @param {boolean} animate Indicate if the expansion should be animated. * @private */ showSection_: function(section, container, animate) { if (section == $('advanced-settings') && !loadTimeData.getBoolean('allowAdvancedSettings')) { return; } // Delay starting the transition if animating so that hidden change will // be processed. if (animate) { this.animatedSectionHeightChange_(section, container, true); } else { section.hidden = false; section.style.height = 'auto'; } }, /** * Shows the given section, with animation. * @param {HTMLElement} section The section to be shown. * @param {HTMLElement} container The container for the section. Must be * inside of |section|. * @private */ showSectionWithAnimation_: function(section, container) { this.showSection_(section, container, /* animate */ true); }, /** * Hides the given |section| with animation. * @param {HTMLElement} section The section to be hidden. * @param {HTMLElement} container The container for the section. Must be * inside of |section|. * @private */ hideSectionWithAnimation_: function(section, container) { this.animatedSectionHeightChange_(section, container, false); }, /** * Toggles the visibility of |section| in an animated way. * @param {HTMLElement} section The section to be toggled. * @param {HTMLElement} container The container for the section. Must be * inside of |section|. * @private */ toggleSectionWithAnimation_: function(section, container) { if (BrowserOptions.shouldShowSection_(section)) this.showSectionWithAnimation_(section, container); else this.hideSectionWithAnimation_(section, container); }, /** * Scrolls the settings page to make the section visible auto-expanding * advanced settings if required. The transition is not animated. This * method is used to ensure that a section associated with an overlay * is visible when the overlay is closed. * @param {!Element} section The section to make visible. * @private */ scrollToSection_: function(section) { var advancedSettings = $('advanced-settings'); var container = $('advanced-settings-container'); var expander = $('advanced-settings-expander'); if (!expander.hidden && advancedSettings.hidden && section.parentNode == container) { this.showSection_($('advanced-settings'), $('advanced-settings-container'), /* animate */ false); this.updateAdvancedSettingsExpander_(); } if (!this.initializationComplete_) { var self = this; var callback = function() { document.removeEventListener('initializationComplete', callback); self.scrollToSection_(section); }; document.addEventListener('initializationComplete', callback); return; } var pageContainer = $('page-container'); // pageContainer.offsetTop is relative to the screen. var pageTop = pageContainer.offsetTop; var sectionBottom = section.offsetTop + section.offsetHeight; // section.offsetTop is relative to the 'page-container'. var sectionTop = section.offsetTop; if (pageTop + sectionBottom > document.body.scrollHeight || pageTop + sectionTop < 0) { // Currently not all layout updates are guaranteed to precede the // initializationComplete event (for example 'set-as-default-browser' // button) leaving some uncertainty in the optimal scroll position. // The section is placed approximately in the middle of the screen. var top = Math.min(0, document.body.scrollHeight / 2 - sectionBottom); pageContainer.style.top = top + 'px'; pageContainer.oldScrollTop = -top; } }, /** * Adds a |webkitTransitionEnd| listener to the given section so that * it can be animated. The listener will only be added to a given section * once, so this can be called as multiple times. * @param {HTMLElement} section The section to be animated. * @private */ addTransitionEndListener_: function(section) { if (section.hasTransitionEndListener_) return; section.addEventListener('webkitTransitionEnd', this.onTransitionEnd_.bind(this)); section.hasTransitionEndListener_ = true; }, /** * Called after an animation transition has ended. * @param {Event} event The webkitTransitionEnd event. NOTE: May be * synthetic. * @private */ onTransitionEnd_: function(event) { if (event.propertyName && event.propertyName != 'height') { // If not a synthetic event or a real transition we care about, bail. return; } var section = event.target; section.classList.remove('sliding'); if (!event.propertyName) { // Only real transitions past this point. return; } if (section.style.height == '0px') { // Hide the content so it can't get tab focus. section.hidden = true; section.style.height = ''; } else { // Set the section height to 'auto' to allow for size changes // (due to font change or dynamic content). section.style.height = 'auto'; } }, /** @private */ updateAdvancedSettingsExpander_: function() { var expander = $('advanced-settings-expander'); if (BrowserOptions.shouldShowSection_($('advanced-settings'))) expander.textContent = loadTimeData.getString('showAdvancedSettings'); else expander.textContent = loadTimeData.getString('hideAdvancedSettings'); }, /** * Updates the sync section with the given state. * @param {options.SyncStatus} syncData A bunch of data records that * describe the status of the sync system. * @private */ updateSyncState_: function(syncData) { if (!syncData.signinAllowed && (!syncData.supervisedUser || !cr.isChromeOS)) { $('sync-section').hidden = true; this.maybeShowUserSection_(); return; } $('sync-section').hidden = false; this.maybeShowUserSection_(); if (cr.isChromeOS && syncData.supervisedUser && !syncData.childUser) { var subSection = $('sync-section').firstChild; while (subSection) { if (subSection.nodeType == Node.ELEMENT_NODE) subSection.hidden = true; subSection = subSection.nextSibling; } $('account-picture-wrapper').hidden = false; $('sync-general').hidden = false; $('sync-status').hidden = true; return; } // If the user gets signed out while the advanced sync settings dialog is // visible, say, due to a dashboard clear, close the dialog. // However, if the user gets signed out as a result of abandoning first // time sync setup, do not call closeOverlay as it will redirect the // browser to the main settings page and override any in-progress // user-initiated navigation. See crbug.com/278030. // Note: SyncSetupOverlay.closeOverlay is a no-op if the overlay is // already hidden. if (this.signedIn_ && !syncData.signedIn && !syncData.setupInProgress) SyncSetupOverlay.closeOverlay(); this.signedIn_ = !!syncData.signedIn; // Display the "advanced settings" button if we're signed in and sync is // not managed/disabled. If the user is signed in, but sync is disabled, // this button is used to re-enable sync. var customizeSyncButton = $('customize-sync'); customizeSyncButton.hidden = !this.signedIn_ || syncData.managed || !syncData.syncSystemEnabled; // Only modify the customize button's text if the new text is different. // Otherwise, it can affect search-highlighting in the settings page. // See http://crbug.com/268265. var customizeSyncButtonNewText = syncData.setupCompleted ? loadTimeData.getString('customizeSync') : loadTimeData.getString('syncButtonTextStart'); if (customizeSyncButton.textContent != customizeSyncButtonNewText) customizeSyncButton.textContent = customizeSyncButtonNewText; // Disable the "sign in" button if we're currently signing in, or if we're // already signed in and signout is not allowed. var signInButton = $('start-stop-sync'); signInButton.disabled = syncData.setupInProgress; this.signoutAllowed_ = !!syncData.signoutAllowed; if (!syncData.signoutAllowed) $('start-stop-sync-indicator').setAttribute('controlled-by', 'policy'); else $('start-stop-sync-indicator').removeAttribute('controlled-by'); // Hide the "sign in" button on Chrome OS, and show it on desktop Chrome // (except for supervised users, which can't change their signed-in // status). signInButton.hidden = cr.isChromeOS || syncData.supervisedUser; signInButton.textContent = this.signedIn_ ? loadTimeData.getString('syncButtonTextStop') : syncData.setupInProgress ? loadTimeData.getString('syncButtonTextInProgress') : loadTimeData.getString('syncButtonTextSignIn'); $('start-stop-sync-indicator').hidden = signInButton.hidden; // TODO(estade): can this just be textContent? $('sync-status-text').innerHTML = syncData.statusText; var statusSet = syncData.statusText.length != 0; $('sync-overview').hidden = statusSet || (cr.isChromeOS && UIAccountTweaks.loggedInAsPublicAccount()); $('sync-status').hidden = !statusSet; $('sync-action-link').textContent = syncData.actionLinkText; // Don't show the action link if it is empty or undefined. $('sync-action-link').hidden = syncData.actionLinkText.length == 0; $('sync-action-link').disabled = syncData.managed || !syncData.syncSystemEnabled; // On Chrome OS, sign out the user and sign in again to get fresh // credentials on auth errors. $('sync-action-link').onclick = function(event) { if (cr.isChromeOS && syncData.hasError) SyncSetupOverlay.doSignOutOnAuthError(); else SyncSetupOverlay.showSetupUI(); }; if (syncData.hasError) $('sync-status').classList.add('sync-error'); else $('sync-status').classList.remove('sync-error'); // Disable the "customize / set up sync" button if sync has an // unrecoverable error. Also disable the button if sync has not been set // up and the user is being presented with a link to re-auth. // See crbug.com/289791. customizeSyncButton.disabled = syncData.hasUnrecoverableError || (!syncData.setupCompleted && !$('sync-action-link').hidden); }, /** * Update the UI depending on whether Easy Unlock is enabled for the current * profile. * @param {boolean} isEnabled True if the feature is enabled for the current * profile. */ updateEasyUnlock_: function(isEnabled) { $('easy-unlock-disabled').hidden = isEnabled; $('easy-unlock-enabled').hidden = !isEnabled; if (!isEnabled && EasyUnlockTurnOffOverlay.getInstance().visible) { EasyUnlockTurnOffOverlay.dismiss(); } }, /** * Update the UI depending on whether the current profile manages any * supervised users. * @param {boolean} show True if the current profile manages any supervised * users. */ updateManagesSupervisedUsers_: function(show) { $('profiles-supervised-dashboard-tip').hidden = !show; this.maybeShowUserSection_(); }, /** * Get the start/stop sync button DOM element. Used for testing. * @return {Element} The start/stop sync button. * @private */ getStartStopSyncButton_: function() { return $('start-stop-sync'); }, /** * Event listener for the 'show home button' preference. Shows/hides the * UI for changing the home page with animation, unless this is the first * time this function is called, in which case there is no animation. * @param {Event} event The preference change event. */ onShowHomeButtonChanged_: function(event) { var section = $('change-home-page-section'); if (this.onShowHomeButtonChangedCalled_) { var container = $('change-home-page-section-container'); if (event.value.value) this.showSectionWithAnimation_(section, container); else this.hideSectionWithAnimation_(section, container); } else { section.hidden = !event.value.value; this.onShowHomeButtonChangedCalled_ = true; } }, /** * Activates the Hotword section from the System settings page. * @param {string} sectionId The id of the section to display. * @param {string} indicatorId The id of the indicator to display. * @param {string=} opt_error The error message to display. * @private */ showHotwordCheckboxAndIndicator_: function(sectionId, indicatorId, opt_error) { $(sectionId).hidden = false; $(indicatorId).setError(opt_error); if (opt_error) $(indicatorId).updateBasedOnError(); }, /** * Activates the Hotword section from the System settings page. * @param {string=} opt_error The error message to display. * @private */ showHotwordSection_: function(opt_error) { this.showHotwordCheckboxAndIndicator_( 'hotword-search', 'hotword-search-setting-indicator', opt_error); }, /** * Activates the Always-On Hotword sections from the * System settings page. * @param {string=} opt_error The error message to display. * @private */ showHotwordAlwaysOnSection_: function(opt_error) { this.showHotwordCheckboxAndIndicator_( 'hotword-always-on-search', 'hotword-always-on-search-setting-indicator', opt_error); }, /** * Activates the Hotword section on devices with no DSP * from the System settings page. * @param {string=} opt_error The error message to display. * @private */ showHotwordNoDspSection_: function(opt_error) { this.showHotwordCheckboxAndIndicator_( 'hotword-no-dsp-search', 'hotword-no-dsp-search-setting-indicator', opt_error); }, /** * Controls the visibility of all the hotword sections. * @param {boolean} visible Whether to show hotword sections. * @private */ setAllHotwordSectionsVisible_: function(visible) { $('hotword-search').hidden = !visible; $('hotword-always-on-search').hidden = !visible; $('hotword-no-dsp-search').hidden = !visible; $('audio-history').hidden = !visible; }, /** * Shows or hides the hotword retrain link * @param {boolean} visible Whether to show the link. * @private */ setHotwordRetrainLinkVisible_: function(visible) { $('hotword-retrain-link').hidden = !visible; }, /** * Event listener for the 'hotword always on search enabled' preference. * Updates the visibility of the 'retrain' link. * @param {Event} event The preference change event. * @private */ onHotwordAlwaysOnChanged_: function(event) { this.setHotwordRetrainLinkVisible_(event.value.value); }, /** * Controls the visibility of the Now settings. * @param {boolean} visible Whether to show Now settings. * @private */ setNowSectionVisible_: function(visible) { $('google-now-launcher').hidden = !visible; }, /** * Activates the Audio History section of the Settings page. * @param {boolean} visible Whether the audio history section is visible. * @param {string} labelText Text describing current audio history state. * @private */ setAudioHistorySectionVisible_: function(visible, labelText) { $('audio-history').hidden = !visible; $('audio-history-label').textContent = labelText; }, /** * Event listener for the 'homepage is NTP' preference. Updates the label * next to the 'Change' button. * @param {Event} event The preference change event. */ onHomePageIsNtpChanged_: function(event) { if (!event.value.uncommitted) { $('home-page-url').hidden = event.value.value; $('home-page-ntp').hidden = !event.value.value; } }, /** * Event listener for changes to the homepage preference. Updates the label * next to the 'Change' button. * @param {Event} event The preference change event. */ onHomePageChanged_: function(event) { if (!event.value.uncommitted) $('home-page-url').textContent = this.stripHttp_(event.value.value); }, /** * Removes the 'http://' from a URL, like the omnibox does. If the string * doesn't start with 'http://' it is returned unchanged. * @param {string} url The url to be processed * @return {string} The url with the 'http://' removed. */ stripHttp_: function(url) { return url.replace(/^http:\/\//, ''); }, /** * Called when the value of the download.default_directory preference * changes. * @param {Event} event Change event. * @private */ onDefaultDownloadDirectoryChanged_: function(event) { $('downloadLocationPath').value = event.value.value; if (cr.isChromeOS) { // On ChromeOS, replace /special/drive-/root with "Google Drive" // for remote files, /home/chronos/user/Downloads or // /home/chronos/u-/Downloads with "Downloads" for local paths, // and '/' with ' \u203a ' (angled quote sign) everywhere. The modified // path is used only for display purpose. var path = $('downloadLocationPath').value; path = path.replace(/^\/special\/drive[^\/]*\/root/, 'Google Drive'); path = path.replace(/^\/home\/chronos\/(user|u-[^\/]*)\//, ''); path = path.replace(/\//g, ' \u203a '); $('downloadLocationPath').value = path; } $('download-location-label').classList.toggle('disabled', event.value.disabled); $('downloadLocationChangeButton').disabled = event.value.disabled; }, /** * Update the Default Browsers section based on the current state. * @param {string} statusString Description of the current default state. * @param {boolean} isDefault Whether or not the browser is currently * default. * @param {boolean} canBeDefault Whether or not the browser can be default. * @private */ updateDefaultBrowserState_: function(statusString, isDefault, canBeDefault) { if (!cr.isChromeOS) { var label = $('default-browser-state'); label.textContent = statusString; $('set-as-default-browser').hidden = !canBeDefault || isDefault; } }, /** * Clears the search engine popup. * @private */ clearSearchEngines_: function() { $('default-search-engine').textContent = ''; }, /** * Updates the search engine popup with the given entries. * @param {Array} engines List of available search engines. * @param {number} defaultValue The value of the current default engine. * @param {boolean} defaultManaged Whether the default search provider is * managed. If true, the default search provider can't be changed. * @private */ updateSearchEngines_: function(engines, defaultValue, defaultManaged) { this.clearSearchEngines_(); var engineSelect = $('default-search-engine'); engineSelect.disabled = defaultManaged; if (defaultManaged && defaultValue == -1) return; var engineCount = engines.length; var defaultIndex = -1; for (var i = 0; i < engineCount; i++) { var engine = engines[i]; var option = new Option(engine.name, engine.index); if (defaultValue == option.value) defaultIndex = i; engineSelect.appendChild(option); } if (defaultIndex >= 0) engineSelect.selectedIndex = defaultIndex; }, /** * Set the default search engine based on the popup selection. * @private */ setDefaultSearchEngine_: function() { var engineSelect = $('default-search-engine'); var selectedIndex = engineSelect.selectedIndex; if (selectedIndex >= 0) { var selection = engineSelect.options[selectedIndex]; chrome.send('setDefaultSearchEngine', [String(selection.value)]); } }, /** * Get the selected profile item from the profile list. This also works * correctly if the list is not displayed. * @return {?Object} The profile item object, or null if nothing is * selected. * @private */ getSelectedProfileItem_: function() { var profilesList = $('profiles-list'); if (profilesList.hidden) { if (profilesList.dataModel.length > 0) return profilesList.dataModel.item(0); } else { return profilesList.selectedItem; } return null; }, /** * Helper function to set the status of profile view buttons to disabled or * enabled, depending on the number of profiles and selection status of the * profiles list. * @private */ setProfileViewButtonsStatus_: function() { var profilesList = $('profiles-list'); var selectedProfile = profilesList.selectedItem; var hasSelection = selectedProfile != null; var hasSingleProfile = profilesList.dataModel.length == 1; $('profiles-manage').disabled = !hasSelection || !selectedProfile.isCurrentProfile; if (hasSelection && !selectedProfile.isCurrentProfile) $('profiles-manage').title = loadTimeData.getString('currentUserOnly'); else $('profiles-manage').title = ''; $('profiles-delete').disabled = !profilesList.canDeleteItems || !hasSelection; if (OptionsPage.isSettingsApp()) { $('profiles-app-list-switch').disabled = !hasSelection || selectedProfile.isCurrentProfile; } var importData = $('import-data'); if (importData) { importData.disabled = $('import-data').disabled = hasSelection && !selectedProfile.isCurrentProfile; } }, /** * Display the correct dialog layout, depending on how many profiles are * available. * @param {number} numProfiles The number of profiles to display. * @private */ setProfileViewSingle_: function(numProfiles) { // Always show the profiles list when using the new Profiles UI. var usingNewProfilesUI = loadTimeData.getBoolean('usingNewProfilesUI'); var showSingleProfileView = !usingNewProfilesUI && numProfiles == 1; $('profiles-list').hidden = showSingleProfileView; $('profiles-single-message').hidden = !showSingleProfileView; $('profiles-manage').hidden = showSingleProfileView || OptionsPage.isSettingsApp(); $('profiles-delete').textContent = showSingleProfileView ? loadTimeData.getString('profilesDeleteSingle') : loadTimeData.getString('profilesDelete'); if (OptionsPage.isSettingsApp()) $('profiles-app-list-switch').hidden = showSingleProfileView; }, /** * Adds all |profiles| to the list. * @param {!Array} profiles An array of profile info * objects. * @private */ setProfilesInfo_: function(profiles) { this.setProfileViewSingle_(profiles.length); // add it to the list, even if the list is hidden so we can access it // later. $('profiles-list').dataModel = new ArrayDataModel(profiles); // Received new data. If showing the "manage" overlay, keep it up to // date. If showing the "delete" overlay, close it. if (ManageProfileOverlay.getInstance().visible && !$('manage-profile-overlay-manage').hidden) { ManageProfileOverlay.showManageDialog(false); } else { ManageProfileOverlay.getInstance().visible = false; } this.setProfileViewButtonsStatus_(); }, /** * Reports supervised user import errors to the SupervisedUserImportOverlay. * @param {string} error The error message to display. * @private */ showSupervisedUserImportError_: function(error) { SupervisedUserImportOverlay.onError(error); }, /** * Reports successful importing of a supervised user to * the SupervisedUserImportOverlay. * @private */ showSupervisedUserImportSuccess_: function() { SupervisedUserImportOverlay.onSuccess(); }, /** * Reports an error to the "create" overlay during profile creation. * @param {string} error The error message to display. * @private */ showCreateProfileError_: function(error) { CreateProfileOverlay.onError(error); }, /** * Sends a warning message to the "create" overlay during profile creation. * @param {string} warning The warning message to display. * @private */ showCreateProfileWarning_: function(warning) { CreateProfileOverlay.onWarning(warning); }, /** * Reports successful profile creation to the "create" overlay. * @param {options.Profile} profileInfo An object of the form: * profileInfo = { * name: "Profile Name", * filePath: "/path/to/profile/data/on/disk" * isSupervised: (true|false), * }; * @private */ showCreateProfileSuccess_: function(profileInfo) { CreateProfileOverlay.onSuccess(profileInfo); }, /** * Returns the currently active profile for this browser window. * @return {options.Profile} A profile info object. * @private */ getCurrentProfile_: function() { for (var i = 0; i < $('profiles-list').dataModel.length; i++) { var profile = $('profiles-list').dataModel.item(i); if (profile.isCurrentProfile) return profile; } assertNotReached('There should always be a current profile.'); }, /** * Propmpts user to confirm deletion of the profile for this browser * window. * @private */ deleteCurrentProfile_: function() { ManageProfileOverlay.showDeleteDialog(this.getCurrentProfile_()); }, /** * @param {boolean} enabled */ setNativeThemeButtonEnabled_: function(enabled) { var button = $('themes-native-button'); if (button) button.disabled = !enabled; }, /** * @param {boolean} enabled */ setThemesResetButtonEnabled_: function(enabled) { $('themes-reset').disabled = !enabled; }, /** * @param {boolean} managed */ setAccountPictureManaged_: function(managed) { var picture = $('account-picture'); if (managed || UIAccountTweaks.loggedInAsGuest()) { picture.disabled = true; ChangePictureOptions.closeOverlay(); } else { picture.disabled = false; } // Create a synthetic pref change event decorated as // CoreOptionsHandler::CreateValueForPref() does. var event = new Event('account-picture'); if (managed) event.value = { controlledBy: 'policy' }; else event.value = {}; $('account-picture-indicator').handlePrefChange(event); }, /** * (Re)loads IMG element with current user account picture. * @private */ updateAccountPicture_: function() { var picture = $('account-picture'); if (picture) { picture.src = 'chrome://userimage/' + this.username_ + '?id=' + Date.now(); } }, /** * @param {boolean} managed */ setWallpaperManaged_: function(managed) { if (managed) $('set-wallpaper').disabled = true; else this.enableElementIfPossible_(getRequiredElement('set-wallpaper')); // Create a synthetic pref change event decorated as // CoreOptionsHandler::CreateValueForPref() does. var event = new Event('wallpaper'); event.value = managed ? { controlledBy: 'policy' } : {}; $('wallpaper-indicator').handlePrefChange(event); }, /** * This enables or disables dependent settings in timezone section. * @private */ updateTimezoneSectionState_: function() { if (this.systemTimezoneIsManaged_) { $('resolve-timezone-by-geolocation-selection').disabled = true; $('resolve-timezone-by-geolocation').onclick = function(event) {}; } else { this.enableElementIfPossible_( getRequiredElement('resolve-timezone-by-geolocation-selection')); $('resolve-timezone-by-geolocation').onclick = function(event) { $('timezone-value-select').disabled = event.currentTarget.checked; }; $('timezone-value-select').disabled = this.resolveTimezoneByGeolocation_; } }, /** * This is called from chromium code when system timezone "managed" state * is changed. Enables or disables dependent settings. * @param {boolean} managed Is true when system Timezone is managed by * enterprise policy. False otherwize. */ setSystemTimezoneManaged_: function(managed) { this.systemTimezoneIsManaged_ = managed; this.updateTimezoneSectionState_(); }, /** * This is Preferences event listener, which is called when * kResolveTimezoneByGeolocation preference is changed. * Enables or disables dependent settings. * @param {Event} value New preference state. */ onResolveTimezoneByGeolocationChanged_: function(value) { this.resolveTimezoneByGeolocation_ = value.value.value; this.updateTimezoneSectionState_(); }, /** * Handle the 'add device' button click. * @private */ handleAddBluetoothDevice_: function() { chrome.send('coreOptionsUserMetricsAction', ['Options_BluetoothShowAddDevice']); PageManager.showPageByName('bluetooth', false); }, /** * Enables or disables the Manage SSL Certificates button. * @private */ enableCertificateButton_: function(enabled) { $('certificatesManageButton').disabled = !enabled; }, /** * Enables or disables the Chrome OS display settings button and overlay. * @param {boolean} enabled * @param {boolean} showUnifiedDesktop * @param {boolean} multiDisplayLayout * @private */ enableDisplaySettings_: function( enabled, showUnifiedDesktop, multiDisplayLayout) { if (cr.isChromeOS) { $('display-options').disabled = !enabled; DisplayOptions.getInstance().setEnabled( enabled, showUnifiedDesktop, multiDisplayLayout); } }, /** * Enables factory reset section. * @private */ enableFactoryResetSection_: function() { $('factory-reset-section').hidden = false; }, /** * Set the checked state of the metrics reporting checkbox. * @private */ setMetricsReportingCheckboxState_: function(checked, policyManaged, ownerManaged) { $('metrics-reporting-enabled').checked = checked; $('metrics-reporting-enabled').disabled = policyManaged || ownerManaged; // If checkbox gets disabled then add an attribute for displaying the // special icon. Otherwise remove the indicator attribute. if (policyManaged) { $('metrics-reporting-disabled-icon').setAttribute('controlled-by', 'policy'); } else if (ownerManaged) { $('metrics-reporting-disabled-icon').setAttribute('controlled-by', 'owner'); } else { $('metrics-reporting-disabled-icon').removeAttribute('controlled-by'); } }, /** * @private */ setMetricsReportingSettingVisibility_: function(visible) { if (visible) $('metrics-reporting-setting').style.display = 'block'; else $('metrics-reporting-setting').style.display = 'none'; }, /** * Set network prediction checkbox value. * * @param {{value: number, disabled: boolean}} pref Information about * network prediction options. |pref.value| is the value of network * prediction options. |pref.disabled| shows if the pref is not user * modifiable. * @private */ setNetworkPredictionValue_: function(pref) { var checkbox = $('networkPredictionOptions'); checkbox.disabled = pref.disabled || loadTimeData.getBoolean('profileIsGuest'); checkbox.checked = (pref.value != NetworkPredictionOptions.NEVER); }, /** * Set the font size selected item. This item actually reflects two * preferences: the default font size and the default fixed font size. * * @param {{value: number, disabled: boolean, controlledBy: string}} pref * Information about the font size preferences. |pref.value| is the * value of the default font size pref. |pref.disabled| is true if * either pref not user modifiable. |pref.controlledBy| is the source of * the pref value(s) if either pref is currently not controlled by the * user. * @private */ setFontSize_: function(pref) { var selectCtl = $('defaultFontSize'); selectCtl.disabled = pref.disabled; // Create a synthetic pref change event decorated as // CoreOptionsHandler::CreateValueForPref() does. var event = new Event('synthetic-font-size'); event.value = { value: pref.value, controlledBy: pref.controlledBy, disabled: pref.disabled }; $('font-size-indicator').handlePrefChange(event); for (var i = 0; i < selectCtl.options.length; i++) { if (selectCtl.options[i].value == pref.value) { selectCtl.selectedIndex = i; if ($('Custom')) selectCtl.remove($('Custom').index); return; } } // Add/Select Custom Option in the font size label list. if (!$('Custom')) { var option = new Option(loadTimeData.getString('fontSizeLabelCustom'), -1, false, true); option.setAttribute('id', 'Custom'); selectCtl.add(option); } $('Custom').selected = true; }, /** * Populate the page zoom selector with values received from the caller. * @param {Array} items An array of items to populate the selector. * each object is an array with three elements as follows: * 0: The title of the item (string). * 1: The value of the item (number). * 2: Whether the item should be selected (boolean). * @private */ setupPageZoomSelector_: function(items) { var element = $('defaultZoomFactor'); // Remove any existing content. element.textContent = ''; // Insert new child nodes into select element. var value, title, selected; for (var i = 0; i < items.length; i++) { title = items[i][0]; value = items[i][1]; selected = items[i][2]; element.appendChild(new Option(title, value, false, selected)); } }, /** * Shows/hides the autoOpenFileTypesResetToDefault button and label, with * animation. * @param {boolean} display Whether to show the button and label or not. * @private */ setAutoOpenFileTypesDisplayed_: function(display) { if ($('advanced-settings').hidden) { // If the Advanced section is hidden, don't animate the transition. $('auto-open-file-types-section').hidden = !display; } else { if (display) { this.showSectionWithAnimation_( $('auto-open-file-types-section'), $('auto-open-file-types-container')); } else { this.hideSectionWithAnimation_( $('auto-open-file-types-section'), $('auto-open-file-types-container')); } } }, /** * Set the enabled state for the proxy settings button and its associated * message when extension controlled. * @param {boolean} disabled Whether the button should be disabled. * @param {boolean} extensionControlled Whether the proxy is extension * controlled. * @private */ setupProxySettingsButton_: function(disabled, extensionControlled) { if (!cr.isChromeOS) { $('proxiesConfigureButton').disabled = disabled; $('proxiesLabel').textContent = loadTimeData.getString(extensionControlled ? 'proxiesLabelExtension' : 'proxiesLabelSystem'); } }, /** * Set the initial state of the spoken feedback checkbox. * @private */ setSpokenFeedbackCheckboxState_: function(checked) { $('accessibility-spoken-feedback-check').checked = checked; }, /** * Set the initial state of the high contrast checkbox. * @private */ setHighContrastCheckboxState_: function(checked) { $('accessibility-high-contrast-check').checked = checked; }, /** * Set the initial state of the virtual keyboard checkbox. * @private */ setVirtualKeyboardCheckboxState_: function(checked) { // TODO(zork): Update UI }, /** * Show/hide mouse settings slider. * @private */ showMouseControls_: function(show) { $('mouse-settings').hidden = !show; }, /** * Adds hidden warning boxes for settings potentially controlled by * extensions. * @param {string} parentDiv The div name to append the bubble to. * @param {string} bubbleId The ID to use for the bubble. * @param {boolean} first Add as first node if true, otherwise last. * @private */ addExtensionControlledBox_: function(parentDiv, bubbleId, first) { var bubble = $('extension-controlled-warning-template').cloneNode(true); bubble.id = bubbleId; var parent = $(parentDiv); if (first) parent.insertBefore(bubble, parent.firstChild); else parent.appendChild(bubble); }, /** * Adds a bubble showing that an extension is controlling a particular * setting. * @param {string} parentDiv The div name to append the bubble to. * @param {string} bubbleId The ID to use for the bubble. * @param {string} extensionId The ID of the controlling extension. * @param {string} extensionName The name of the controlling extension. * @private */ toggleExtensionControlledBox_: function( parentDiv, bubbleId, extensionId, extensionName) { var bubble = $(bubbleId); assert(bubble); bubble.hidden = extensionId.length == 0; if (bubble.hidden) return; // Set the extension image. var div = bubble.firstElementChild; div.style.backgroundImage = 'url(chrome://extension-icon/' + extensionId + '/24/1)'; // Set the bubble label. var label = loadTimeData.getStringF('extensionControlled', extensionName); var docFrag = parseHtmlSubset('
' + label + '
', ['B', 'DIV']); div.innerHTML = docFrag.firstChild.innerHTML; // Wire up the button to disable the right extension. var button = div.nextElementSibling; button.dataset.extensionId = extensionId; }, /** * Toggles the warning boxes that show which extension is controlling * various settings of Chrome. * @param {{searchEngine: options.ExtensionData, * homePage: options.ExtensionData, * startUpPage: options.ExtensionData, * newTabPage: options.ExtensionData, * proxy: options.ExtensionData}} details A dictionary of ID+name * pairs for each of the settings controlled by an extension. * @private */ toggleExtensionIndicators_: function(details) { this.toggleExtensionControlledBox_('search-section-content', 'search-engine-controlled', details.searchEngine.id, details.searchEngine.name); this.toggleExtensionControlledBox_('extension-controlled-container', 'homepage-controlled', details.homePage.id, details.homePage.name); this.toggleExtensionControlledBox_('startup-section-content', 'startpage-controlled', details.startUpPage.id, details.startUpPage.name); this.toggleExtensionControlledBox_('newtab-section-content', 'newtab-controlled', details.newTabPage.id, details.newTabPage.name); this.toggleExtensionControlledBox_('proxy-section-content', 'proxy-controlled', details.proxy.id, details.proxy.name); // The proxy section contains just the warning box and nothing else, so // if we're hiding the proxy warning box, we should also hide its header // section. $('proxy-section').hidden = details.proxy.id.length == 0; }, /** * Show/hide touchpad-related settings. * @private */ showTouchpadControls_: function(show) { $('touchpad-settings').hidden = !show; $('accessibility-tap-dragging').hidden = !show; }, /** * Sets the state of the checkbox indicating if Bluetooth is turned on. The * state of the "Find devices" button and the list of discovered devices may * also be affected by a change to the state. * @param {boolean} checked Flag Indicating if Bluetooth is turned on. * @private */ setBluetoothState_: function(checked) { $('enable-bluetooth').checked = checked; $('bluetooth-paired-devices-list').parentNode.hidden = !checked; $('bluetooth-add-device').hidden = !checked; $('bluetooth-reconnect-device').hidden = !checked; }, /** * Process a bluetooth.onAdapterStateChanged event. * @param {!chrome.bluetooth.AdapterState} state * @private */ onBluetoothAdapterStateChanged_: function(state) { if (!state || !state.available) { this.bluetoothAdapterState_ = null; $('bluetooth-devices').hidden = true; return; } $('bluetooth-devices').hidden = false; this.bluetoothAdapterState_ = state; this.setBluetoothState_(state.powered); // Flush the device lists. $('bluetooth-paired-devices-list').clear(); $('bluetooth-unpaired-devices-list').clear(); if (state.powered) { options.BluetoothOptions.updateDiscoveryState(state.discovering); // Update the device lists. chrome.bluetooth.getDevices(function(devices) { for (var device of devices) this.updateBluetoothDevicesList_(device); }.bind(this)); } else { options.BluetoothOptions.dismissOverlay(); } }, /** * Process a bluetooth.onDeviceAdded or onDeviceChanged event and update the * device list. * @param {!chrome.bluetooth.Device} device * @private */ onBluetoothDeviceAddedOrChanged_: function(device) { this.updateBluetoothDevicesList_(device); }, /** * Process a bluetooth.onDeviceRemoved event and update the device list. * @param {!chrome.bluetooth.Device} device * @private */ onBluetoothDeviceRemoved_: function(device) { this.removeBluetoothDevice_(device.address); }, /** * Process a bluetoothPrivate onPairing event and update the device list. * @param {!chrome.bluetoothPrivate.PairingEvent} pairing_event * @private */ onBluetoothPrivatePairing_: function(pairing_event) { this.updateBluetoothDevicesList_(pairing_event.device); BluetoothPairing.onBluetoothPairingEvent(pairing_event); }, /** * Add |device| to the appropriate list of Bluetooth devices. * @param {!chrome.bluetooth.Device} device * @private */ addBluetoothDeviceToList_: function(device) { // Display the "connecting" (already paired or not yet paired) and the // paired devices in the same list. if (device.paired || device.connecting) $('bluetooth-paired-devices-list').appendDevice(device); else $('bluetooth-unpaired-devices-list').appendDevice(device); }, /** * Add |device| to the appropriate list of Bluetooth devices or update the * entry if a device with a matching |address| property exists. * @param {!chrome.bluetooth.Device} device * @private */ updateBluetoothDevicesList_: function(device) { // Display the "connecting" (already paired or not yet paired) and the // paired devices in the same list. if (device.paired || device.connecting) { // Test to see if the device is currently in the unpaired list, in which // case it should be removed from that list. var index = $('bluetooth-unpaired-devices-list').find(device.address); if (index != undefined) $('bluetooth-unpaired-devices-list').deleteItemAtIndex(index); } else { // Test to see if the device is currently in the paired list, in which // case it should be removed from that list. var index = $('bluetooth-paired-devices-list').find(device.address); if (index != undefined) $('bluetooth-paired-devices-list').deleteItemAtIndex(index); } this.addBluetoothDeviceToList_(device); }, /** * Removes an element from the list of available devices. * @param {string} address Unique address of the device. * @private */ removeBluetoothDevice_: function(address) { var index = $('bluetooth-unpaired-devices-list').find(address); if (index != undefined) { $('bluetooth-unpaired-devices-list').deleteItemAtIndex(index); } else { index = $('bluetooth-paired-devices-list').find(address); if (index != undefined) $('bluetooth-paired-devices-list').deleteItemAtIndex(index); } }, /** * Shows the overlay dialog for changing the user avatar image. * @private */ showImagerPickerOverlay_: function() { PageManager.showPageByName('changePicture'); }, /** * Shows (or not) the "User" section of the settings page based on whether * any of the sub-sections are present (or not). * @private */ maybeShowUserSection_: function() { $('sync-users-section').hidden = $('profiles-section').hidden && $('sync-section').hidden && $('profiles-supervised-dashboard-tip').hidden; }, /** * Updates the date and time section with time sync information. * @param {boolean} canSetTime Whether the system time can be set. * @private */ setCanSetTime_: function(canSetTime) { // If the time has been network-synced, it cannot be set manually. $('set-time').hidden = !canSetTime; }, /** * Handle the 'set date and time' button click. * @private */ handleSetTime_: function() { chrome.send('showSetTime'); }, /** * Enables the given element if possible; on Chrome OS, it won't enable * an element that must stay disabled for the session type. * @param {!Element} element Element to enable. */ enableElementIfPossible_: function(element) { if (cr.isChromeOS) UIAccountTweaks.enableElementIfPossible(element); else element.disabled = false; }, }; // Forward public APIs to private implementations. cr.makePublic(BrowserOptions, [ 'deleteCurrentProfile', 'enableCertificateButton', 'enableDisplaySettings', 'enableFactoryResetSection', 'getCurrentProfile', 'getStartStopSyncButton', 'notifyInitializationComplete', 'removeBluetoothDevice', 'scrollToSection', 'setAccountPictureManaged', 'setWallpaperManaged', 'setAutoOpenFileTypesDisplayed', 'setCanSetTime', 'setFontSize', 'setHotwordRetrainLinkVisible', 'setNativeThemeButtonEnabled', 'setNetworkPredictionValue', 'setNowSectionVisible', 'setHighContrastCheckboxState', 'setAllHotwordSectionsVisible', 'setMetricsReportingCheckboxState', 'setMetricsReportingSettingVisibility', 'setProfilesInfo', 'setSpokenFeedbackCheckboxState', 'setSystemTimezoneManaged', 'setThemesResetButtonEnabled', 'setVirtualKeyboardCheckboxState', 'setupPageZoomSelector', 'setupProxySettingsButton', 'setAudioHistorySectionVisible', 'showCreateProfileError', 'showCreateProfileSuccess', 'showCreateProfileWarning', 'showHotwordAlwaysOnSection', 'showHotwordNoDspSection', 'showHotwordSection', 'showMouseControls', 'showSupervisedUserImportError', 'showSupervisedUserImportSuccess', 'showTouchpadControls', 'toggleExtensionIndicators', 'updateAccountPicture', 'updateDefaultBrowserState', 'updateEasyUnlock', 'updateManagesSupervisedUsers', 'updateSearchEngines', 'updateSyncState', ]); if (cr.isChromeOS) { /** * Returns username (canonical email) of the user logged in (ChromeOS only). * @return {string} user email. */ // TODO(jhawkins): Investigate the use case for this method. BrowserOptions.getLoggedInUsername = function() { return BrowserOptions.getInstance().username_; }; /** * Shows different button text for each consumer management enrollment * status. * @enum {string} status Consumer management service status string. */ BrowserOptions.setConsumerManagementStatus = function(status) { var button = $('consumer-management-button'); if (status == 'StatusUnknown') { button.hidden = true; return; } button.hidden = false; /** @type {string} */ var strId; switch (status) { case ConsumerManagementOverlay.Status.STATUS_UNENROLLED: strId = 'consumerManagementEnrollButton'; button.disabled = false; ConsumerManagementOverlay.setStatus(status); break; case ConsumerManagementOverlay.Status.STATUS_ENROLLING: strId = 'consumerManagementEnrollingButton'; button.disabled = true; break; case ConsumerManagementOverlay.Status.STATUS_ENROLLED: strId = 'consumerManagementUnenrollButton'; button.disabled = false; ConsumerManagementOverlay.setStatus(status); break; case ConsumerManagementOverlay.Status.STATUS_UNENROLLING: strId = 'consumerManagementUnenrollingButton'; button.disabled = true; break; } button.textContent = loadTimeData.getString(strId); }; /** * Hides Android Apps settings when they are not available (ChromeOS only). */ BrowserOptions.hideAndroidAppsSection = function() { var section = $('andorid-apps-section'); if (section) section.hidden = true; }; } // Export return { BrowserOptions: BrowserOptions }; });