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