blob: d0711d0f8a1db7b2c80aa7412e23e0dcfbaa1ec2 [file] [log] [blame]
// 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.define('options', function() {
var OptionsPage = options.OptionsPage;
var ArrayDataModel = cr.ui.ArrayDataModel;
var RepeatingButton = cr.ui.RepeatingButton;
//
// BrowserOptions class
// Encapsulated handling of browser options page.
//
function BrowserOptions() {
OptionsPage.call(this, 'settings', loadTimeData.getString('settingsTitle'),
'settings');
}
cr.addSingletonGetter(BrowserOptions);
BrowserOptions.prototype = {
__proto__: options.OptionsPage.prototype,
/**
* Keeps track of whether the user is signed in or not.
* @type {boolean}
* @private
*/
signedIn_: false,
/**
* Keeps track of whether |onShowHomeButtonChanged_| has been called. See
* |onShowHomeButtonChanged_|.
* @type {boolean}
* @private
*/
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.
* @type {boolean}
* @private
*/
initializationComplete_: false,
/** @override */
initializePage: function() {
OptionsPage.prototype.initializePage.call(this);
var self = this;
// 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));
$('advanced-settings-expander').onclick = function() {
self.toggleSectionWithAnimation_(
$('advanced-settings'),
$('advanced-settings-container'));
// If the link was focused (i.e., it was activated using the keyboard)
// and it was used to show the section (rather than hiding it), focus
// the first element in the container.
if (document.activeElement === $('advanced-settings-expander') &&
$('advanced-settings').style.height === '') {
var focusElement = $('advanced-settings-container').querySelector(
'button, input, list, select, a[href]');
if (focusElement)
focusElement.focus();
}
}
$('advanced-settings').addEventListener('webkitTransitionEnd',
this.updateAdvancedSettingsExpander_.bind(this));
if (cr.isChromeOS)
UIAccountTweaks.applyGuestModeVisibility(document);
// Sync (Sign in) section.
this.updateSyncState_(loadTimeData.getValue('syncData'));
$('start-stop-sync').onclick = function(event) {
if (self.signedIn_)
SyncSetupOverlay.showStopSyncingUI();
else if (cr.isChromeOS)
SyncSetupOverlay.showSetupUI();
else
SyncSetupOverlay.startSignIn();
};
$('customize-sync').onclick = function(event) {
SyncSetupOverlay.showSetupUI();
};
// Internet connection section (ChromeOS only).
if (cr.isChromeOS) {
options.network.NetworkList.decorate($('network-list'));
options.network.NetworkList.refreshNetworkData(
loadTimeData.getValue('networkData'));
}
// 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() {
OptionsPage.navigateToPage('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) {
OptionsPage.navigateToPage('homePageOverlay');
};
if ($('set-wallpaper')) {
$('set-wallpaper').onclick = function(event) {
chrome.send('openWallpaperManager');
};
}
$('themes-gallery').onclick = function(event) {
window.open(loadTimeData.getString('themesGalleryURL'));
};
$('themes-reset').onclick = function(event) {
chrome.send('themesReset');
};
if (loadTimeData.getBoolean('profileIsManaged')) {
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) {
$('keyboard-settings-button').onclick = function(evt) {
OptionsPage.navigateToPage('keyboard-overlay');
};
$('pointer-settings-button').onclick = function(evt) {
OptionsPage.navigateToPage('pointer-overlay');
};
}
// Search section.
$('manage-default-search-engines').onclick = function(event) {
OptionsPage.navigateToPage('searchEngines');
chrome.send('coreOptionsUserMetricsAction',
['Options_ManageSearchEngines']);
};
$('default-search-engine').addEventListener('change',
this.setDefaultSearchEngine_);
// Users section.
if (loadTimeData.valueExists('profilesInfo')) {
$('profiles-section').hidden = false;
var profilesList = $('profiles-list');
options.browser_options.ProfileList.decorate(profilesList);
profilesList.autoExpands = true;
// The profiles info data in |loadTimeData| might be stale.
this.setProfilesInfo_(loadTimeData.getValue('profilesInfo'));
chrome.send('requestProfilesInfo');
profilesList.addEventListener('change',
this.setProfileViewButtonsStatus_);
$('profiles-create').onclick = function(event) {
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) {
ManageProfileOverlay.showManageDialog();
};
$('profiles-delete').onclick = function(event) {
var selectedProfile = self.getSelectedProfileItem_();
if (selectedProfile)
ManageProfileOverlay.showDeleteDialog(selectedProfile);
};
if (loadTimeData.getBoolean('profileIsManaged')) {
$('profiles-create').disabled = true;
$('profiles-delete').disabled = true;
$('profiles-list').canDeleteItems = false;
}
}
if (cr.isChromeOS) {
if (!UIAccountTweaks.loggedInAsGuest()) {
var pictureWrapper = $('account-picture-wrapper');
pictureWrapper.setAttribute('role', 'button');
pictureWrapper.tabIndex = 0;
function activate(event) {
if (event.type == 'click' ||
(event.type == 'keydown' && event.keyCode == 32)) {
OptionsPage.navigateToPage('changePicture');
}
};
pictureWrapper.onclick = activate;
pictureWrapper.addEventListener('keydown', activate);
}
// 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-wrapper').oncontextmenu = function(e) {
e.preventDefault();
};
$('manage-accounts-button').onclick = function(event) {
OptionsPage.navigateToPage('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');
};
}
}
// Default browser section.
if (!cr.isChromeOS) {
$('set-as-default-browser').onclick = function(event) {
chrome.send('becomeDefaultBrowser');
};
$('auto-launch').onclick = this.handleAutoLaunchChanged_;
}
// Privacy section.
$('privacyContentSettingsButton').onclick = function(event) {
OptionsPage.navigateToPage('content');
OptionsPage.showTab($('cookies-nav-tab'));
chrome.send('coreOptionsUserMetricsAction',
['Options_ContentSettings']);
};
$('privacyClearDataButton').onclick = function(event) {
OptionsPage.navigateToPage('clearBrowserData');
chrome.send('coreOptionsUserMetricsAction', ['Options_ClearData']);
};
$('privacyClearDataButton').hidden = OptionsPage.isSettingsApp();
// 'metricsReportingEnabled' element is only present on Chrome branded
// builds.
if ($('metricsReportingEnabled')) {
$('metricsReportingEnabled').onclick = function(event) {
chrome.send('metricsReportingCheckboxAction',
[String(event.currentTarget.checked)]);
};
}
// Bluetooth (CrOS only).
if (cr.isChromeOS) {
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.send('bluetoothEnableChange', [Boolean(state)]);
};
$('bluetooth-reconnect-device').onclick = function(event) {
var device = $('bluetooth-paired-devices-list').selectedItem;
var address = device.address;
chrome.send('updateBluetoothDevice', [address, 'connect']);
OptionsPage.closeOverlay();
};
$('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) {
OptionsPage.navigateToPage('autofill');
chrome.send('coreOptionsUserMetricsAction',
['Options_ShowAutofillSettings']);
};
$('manage-passwords').onclick = function(event) {
OptionsPage.navigateToPage('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');
};
}
// Web Content section.
$('fontSettingsCustomizeFontsButton').onclick = function(event) {
OptionsPage.navigateToPage('fonts');
chrome.send('coreOptionsUserMetricsAction', ['Options_FontSettings']);
};
$('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) {
OptionsPage.navigateToPage('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) {
$('autoOpenFileTypesResetToDefault').onclick = function(event) {
chrome.send('autoOpenFileTypesAction');
};
} else {
$('disable-drive-row').hidden =
UIAccountTweaks.loggedInAsLocallyManagedUser();
}
// HTTPS/SSL section.
if (cr.isWindows || cr.isMac) {
$('certificatesManageButton').onclick = function(event) {
chrome.send('showManageSSLCertificates');
};
} else {
$('certificatesManageButton').onclick = function(event) {
OptionsPage.navigateToPage('certificates');
chrome.send('coreOptionsUserMetricsAction',
['Options_ManageSSLCertificates']);
};
}
// Cloud Print section.
// 'cloudPrintProxyEnabled' is true for Chrome branded builds on
// certain platforms, or could be enabled by a lab.
if (!cr.isChromeOS) {
$('cloudPrintConnectorSetupButton').onclick = function(event) {
if ($('cloudPrintManageButton').style.display == 'none') {
// Disable the button, set its text to the intermediate state.
$('cloudPrintConnectorSetupButton').textContent =
loadTimeData.getString('cloudPrintConnectorEnablingButton');
$('cloudPrintConnectorSetupButton').disabled = true;
chrome.send('showCloudPrintSetupDialog');
} else {
chrome.send('disableCloudPrintConnector');
}
};
}
$('cloudPrintManageButton').onclick = function(event) {
chrome.send('showCloudPrintManagePage');
};
if (loadTimeData.getBoolean('cloudPrintShowMDnsOptions')) {
$('cloudprint-options-mdns').hidden = false;
$('cloudprint-options-nomdns').hidden = true;
$('cloudPrintDevicesPageButton').onclick = function() {
chrome.send('showCloudPrintDevicesPage');
};
}
// Accessibility section (CrOS only).
if (cr.isChromeOS) {
$('accessibility-spoken-feedback-check').onchange = function(event) {
chrome.send('spokenFeedbackChange',
[$('accessibility-spoken-feedback-check').checked]);
};
$('accessibility-high-contrast-check').onchange = function(event) {
chrome.send('highContrastChange',
[$('accessibility-high-contrast-check').checked]);
};
$('accessibility-sticky-keys').hidden =
!loadTimeData.getBoolean('enableStickyKeys');
}
// Display management section (CrOS only).
if (cr.isChromeOS) {
$('display-options').onclick = function(event) {
OptionsPage.navigateToPage('display');
chrome.send('coreOptionsUserMetricsAction',
['Options_Display']);
};
}
// Factory reset section (CrOS only).
if (cr.isChromeOS) {
$('factory-reset-restart').onclick = function(event) {
OptionsPage.navigateToPage('factoryResetData');
};
}
// 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) {
OptionsPage.navigateToPage('resetProfileSettings');
};
},
/** @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 (e.data.method == 'frameSelected')
$('search-field').focus();
},
/**
* 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 (animate)
this.addTransitionEndListener_(section);
// Unhide
section.hidden = false;
var expander = function() {
// Reveal the section using a WebKit transition if animating.
if (animate) {
section.classList.add('sliding');
section.style.height = container.offsetHeight + 'px';
} else {
section.style.height = 'auto';
}
};
// Delay starting the transition if animating so that hidden change will
// be processed.
if (animate)
setTimeout(expander, 0);
else
expander();
},
/**
* 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);
},
/**
* See showSectionWithAnimation_.
*/
hideSectionWithAnimation_: function(section, container) {
this.addTransitionEndListener_(section);
// Before we start hiding the section, we need to set
// the height to a pixel value.
section.style.height = container.offsetHeight + 'px';
// Delay starting the transition so that the height change will be
// processed.
setTimeout(function() {
// Hide the section using a WebKit transition.
section.classList.add('sliding');
section.style.height = '';
}, 0);
},
/**
* See showSectionWithAnimation_.
*/
toggleSectionWithAnimation_: function(section, container) {
if (section.style.height == '')
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');
if (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) {
pageContainer.oldScrollTop = -pageTop;
// 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.
pageContainer.style.top = document.body.scrollHeight / 2 -
sectionBottom + 'px';
}
},
/**
* 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.
* @private
*/
onTransitionEnd_: function(event) {
if (event.propertyName != 'height')
return;
var section = event.target;
// Disable WebKit transitions.
section.classList.remove('sliding');
if (section.style.height == '') {
// Hide the content so it can't get tab focus.
section.hidden = true;
} else {
// Set the section height to 'auto' to allow for size changes
// (due to font change or dynamic content).
section.style.height = 'auto';
}
},
updateAdvancedSettingsExpander_: function() {
var expander = $('advanced-settings-expander');
if ($('advanced-settings').style.height == '')
expander.textContent = loadTimeData.getString('showAdvancedSettings');
else
expander.textContent = loadTimeData.getString('hideAdvancedSettings');
},
/**
* Updates the sync section with the given state.
* @param {Object} 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;
return;
}
$('sync-section').hidden = false;
var subSection = $('sync-section').firstChild;
while (subSection) {
if (subSection.nodeType == Node.ELEMENT_NODE)
subSection.hidden = syncData.supervisedUser;
subSection = subSection.nextSibling;
}
if (syncData.supervisedUser) {
$('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 ||
!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.
signInButton.hidden = cr.isChromeOS;
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;
$('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);
// Move #enable-auto-login-checkbox to a different location on CrOS.
if (cr.isChromeOs) {
$('sync-general').insertBefore($('sync-status').nextSibling,
$('enable-auto-login-checkbox'));
}
$('enable-auto-login-checkbox').hidden = !syncData.autoLoginVisible;
},
/**
* Update the UI depending on whether the current profile manages any
* supervised users.
* @param {boolean} value True if the current profile manages any supervised
* users.
*/
updateManagesSupervisedUsers_: function(value) {
$('profiles-supervised-dashboard-tip').hidden = !value;
},
/**
* Get the start/stop sync button DOM element. Used for testing.
* @return {DOMElement} 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;
}
},
/**
* 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:\/\//, '');
},
/**
* Shows the autoLaunch preference and initializes its checkbox value.
* @param {bool} enabled Whether autolaunch is enabled or or not.
* @private
*/
updateAutoLaunchState_: function(enabled) {
$('auto-launch-option').hidden = false;
$('auto-launch').checked = enabled;
},
/**
* 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 Drive for drive paths,
// /home/chronos/user/Downloads or /home/chronos/u-<hash>/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_();
engineSelect = $('default-search-engine');
engineSelect.disabled = defaultManaged;
if (defaultManaged && defaultValue == -1)
return;
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)]);
}
},
/**
* Sets or clear whether Chrome should Auto-launch on computer startup.
* @private
*/
handleAutoLaunchChanged_: function() {
chrome.send('toggleAutoLaunch', [$('auto-launch').checked]);
},
/**
* 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;
var isManaged = loadTimeData.getBoolean('profileIsManaged');
$('profiles-manage').disabled = !hasSelection ||
!selectedProfile.isCurrentProfile;
if (hasSelection && !selectedProfile.isCurrentProfile)
$('profiles-manage').title = loadTimeData.getString('currentUserOnly');
else
$('profiles-manage').title = '';
$('profiles-delete').disabled = isManaged ||
(!hasSelection && !hasSingleProfile);
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) {
var hasSingleProfile = numProfiles == 1;
$('profiles-list').hidden = hasSingleProfile;
$('profiles-single-message').hidden = !hasSingleProfile;
$('profiles-manage').hidden =
hasSingleProfile || OptionsPage.isSettingsApp();
$('profiles-delete').textContent = hasSingleProfile ?
loadTimeData.getString('profilesDeleteSingle') :
loadTimeData.getString('profilesDelete');
if (OptionsPage.isSettingsApp())
$('profiles-app-list-switch').hidden = hasSingleProfile;
},
/**
* Adds all |profiles| to the list.
* @param {Array.<Object>} profiles An array of profile info objects.
* each object is of the form:
* profileInfo = {
* name: "Profile Name",
* iconURL: "chrome://path/to/icon/image",
* filePath: "/path/to/profile/data/on/disk",
* isCurrentProfile: false
* };
* @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();
} else {
ManageProfileOverlay.getInstance().visible = false;
}
this.setProfileViewButtonsStatus_();
},
/**
* Reports managed user import errors to the ManagedUserImportOverlay.
* @param {string} error The error message to display.
* @private
*/
showManagedUserImportError_: function(error) {
ManagedUserImportOverlay.onError(error);
},
/**
* Reports successful importing of a managed user to
* the ManagedUserImportOverlay.
* @private
*/
showManagedUserImportSuccess_: function() {
ManagedUserImportOverlay.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 {Object} profileInfo An object of the form:
* profileInfo = {
* name: "Profile Name",
* filePath: "/path/to/profile/data/on/disk"
* isManaged: (true|false),
* };
* @private
*/
showCreateProfileSuccess_: function(profileInfo) {
CreateProfileOverlay.onSuccess(profileInfo);
},
/**
* Returns the currently active profile for this browser window.
* @return {Object} 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;
}
assert(false,
'There should always be a current profile, but none found.');
},
setNativeThemeButtonEnabled_: function(enabled) {
var button = $('themes-native-button');
if (button)
button.disabled = !enabled;
},
setThemesResetButtonEnabled_: function(enabled) {
$('themes-reset').disabled = !enabled;
},
/**
* (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();
}
},
/**
* Handle the 'add device' button click.
* @private
*/
handleAddBluetoothDevice_: function() {
chrome.send('findBluetoothDevices');
OptionsPage.showPageByName('bluetooth', false);
},
/**
* 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, disabled) {
$('metricsReportingEnabled').checked = checked;
$('metricsReportingEnabled').disabled = disabled;
},
/**
* @private
*/
setMetricsReportingSettingVisibility_: function(visible) {
if (visible)
$('metricsReportingSetting').style.display = 'block';
else
$('metricsReportingSetting').style.display = 'none';
},
/**
* Set the visibility of the password generation checkbox.
* @private
*/
setPasswordGenerationSettingVisibility_: function(visible) {
if (visible)
$('password-generation-checkbox').style.display = 'block';
else
$('password-generation-checkbox').style.display = 'none';
},
/**
* Set the font size selected item. This item actually reflects two
* preferences: the default font size and the default fixed font size.
*
* @param {Object} pref Information about the font size preferences.
* @param {number} pref.value The value of the default font size pref.
* @param {boolean} pref.disabled True if either pref not user modifiable.
* @param {string} pref.controlledBy 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 (cr.isChromeOS)
return;
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.
* @private
*/
setupProxySettingsSection_: function(disabled, extensionControlled) {
if (!cr.isChromeOS) {
$('proxiesConfigureButton').disabled = disabled;
$('proxiesLabel').textContent =
loadTimeData.getString(extensionControlled ?
'proxiesLabelExtension' : 'proxiesLabelSystem');
}
},
/**
* Set the Cloud Print proxy UI to enabled, disabled, or processing.
* @private
*/
setupCloudPrintConnectorSection_: function(disabled, label, allowed) {
if (!cr.isChromeOS) {
$('cloudPrintConnectorLabel').textContent = label;
if (disabled || !allowed) {
$('cloudPrintConnectorSetupButton').textContent =
loadTimeData.getString('cloudPrintConnectorDisabledButton');
$('cloudPrintManageButton').style.display = 'none';
} else {
$('cloudPrintConnectorSetupButton').textContent =
loadTimeData.getString('cloudPrintConnectorEnabledButton');
$('cloudPrintManageButton').style.display = 'inline';
}
$('cloudPrintConnectorSetupButton').disabled = !allowed;
}
},
/**
* @private
*/
removeCloudPrintConnectorSection_: function() {
if (!cr.isChromeOS) {
var connectorSectionElm = $('cloud-print-connector-section');
if (connectorSectionElm)
connectorSectionElm.parentNode.removeChild(connectorSectionElm);
}
},
/**
* 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;
},
/**
* Show/hide touchpad-related settings.
* @private
*/
showTouchpadControls_: function(show) {
$('touchpad-settings').hidden = !show;
$('accessibility-tap-dragging').hidden = !show;
},
/**
* Activate the Bluetooth settings section on the System settings page.
* @private
*/
showBluetoothSettings_: function() {
$('bluetooth-devices').hidden = false;
},
/**
* Dectivates the Bluetooth settings section from the System settings page.
* @private
*/
hideBluetoothSettings_: function() {
$('bluetooth-devices').hidden = true;
},
/**
* 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;
// Flush list of previously discovered devices if bluetooth is turned off.
if (!checked) {
$('bluetooth-paired-devices-list').clear();
$('bluetooth-unpaired-devices-list').clear();
} else {
chrome.send('getPairedBluetoothDevices');
}
},
/**
* Adds an element to the list of available Bluetooth devices. If an element
* with a matching address is found, the existing element is updated.
* @param {{name: string,
* address: string,
* paired: boolean,
* connected: boolean}} device
* Decription of the Bluetooth device.
* @private
*/
addBluetoothDevice_: function(device) {
var list = $('bluetooth-unpaired-devices-list');
// 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);
list = $('bluetooth-paired-devices-list');
} 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);
}
list.appendDevice(device);
// One device can be in the process of pairing. If found, display
// the Bluetooth pairing overlay.
if (device.pairing)
BluetoothPairing.showDialog(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);
}
}
};
//Forward public APIs to private implementations.
[
'addBluetoothDevice',
'enableFactoryResetSection',
'getCurrentProfile',
'getStartStopSyncButton',
'hideBluetoothSettings',
'notifyInitializationComplete',
'removeBluetoothDevice',
'removeCloudPrintConnectorSection',
'scrollToSection',
'setAutoOpenFileTypesDisplayed',
'setBluetoothState',
'setFontSize',
'setNativeThemeButtonEnabled',
'setHighContrastCheckboxState',
'setMetricsReportingCheckboxState',
'setMetricsReportingSettingVisibility',
'setPasswordGenerationSettingVisibility',
'setProfilesInfo',
'setSpokenFeedbackCheckboxState',
'setThemesResetButtonEnabled',
'setVirtualKeyboardCheckboxState',
'setupCloudPrintConnectorSection',
'setupPageZoomSelector',
'setupProxySettingsSection',
'showBluetoothSettings',
'showCreateProfileError',
'showCreateProfileSuccess',
'showCreateProfileWarning',
'showManagedUserImportError',
'showManagedUserImportSuccess',
'showMouseControls',
'showTouchpadControls',
'updateAccountPicture',
'updateAutoLaunchState',
'updateDefaultBrowserState',
'updateManagesSupervisedUsers',
'updateSearchEngines',
'updateStartupPages',
'updateSyncState',
].forEach(function(name) {
BrowserOptions[name] = function() {
var instance = BrowserOptions.getInstance();
return instance[name + '_'].apply(instance, arguments);
};
});
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_;
};
}
// Export
return {
BrowserOptions: BrowserOptions
};
});