diff options
author | jhawkins@chromium.org <jhawkins@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-12-14 18:47:26 +0000 |
---|---|---|
committer | jhawkins@chromium.org <jhawkins@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-12-14 18:47:26 +0000 |
commit | 846c3eceac6eb556f77218c50ab16244ebbb9146 (patch) | |
tree | 259ee74cab54e263c15b06eec22dcca2d9abf2a8 | |
parent | 34608cfed25d4ed2275af1ceb7ff978d0a48ef17 (diff) | |
download | chromium_src-846c3eceac6eb556f77218c50ab16244ebbb9146.zip chromium_src-846c3eceac6eb556f77218c50ab16244ebbb9146.tar.gz chromium_src-846c3eceac6eb556f77218c50ab16244ebbb9146.tar.bz2 |
Options2: Pull the trigger.
Options2 is a copy of the resources for and implementation of chrome://settings that will be pared down significantly for UberPage (see bug).
BUG=100885
TEST=none
R=csilv
Review URL: http://codereview.chromium.org/8895023
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@114462 0039d316-1c4b-4281-b951-d872f2087c98
274 files changed, 44270 insertions, 7 deletions
diff --git a/base/path_service.cc b/base/path_service.cc index 256318c..47278c3 100644 --- a/base/path_service.cc +++ b/base/path_service.cc @@ -162,7 +162,7 @@ bool PathService::GetFromOverrides(int key, FilePath* result) { PathData* path_data = GetPathData(); base::AutoLock scoped_lock(path_data->lock); - // check for an overriden version. + // check for an overridden version. PathMap::const_iterator it = path_data->overrides.find(key); if (it != path_data->overrides.end()) { *result = it->second; @@ -230,13 +230,13 @@ bool PathService::Override(int key, const FilePath& path) { // Make sure the directory exists. We need to do this before we translate // this to the absolute path because on POSIX, AbsolutePath fails if called - // on a non-existant path. + // on a non-existent path. if (!file_util::PathExists(file_path) && !file_util::CreateDirectory(file_path)) return false; // We need to have an absolute path, as extensions and plugins don't like - // relative paths, and will glady crash the browser in CHECK()s if they get a + // relative paths, and will gladly crash the browser in CHECK()s if they get a // relative path. if (!file_util::AbsolutePath(&file_path)) return false; diff --git a/chrome/browser/resources/options2/OWNERS b/chrome/browser/resources/options2/OWNERS new file mode 100644 index 0000000..67257e6 --- /dev/null +++ b/chrome/browser/resources/options2/OWNERS @@ -0,0 +1,4 @@ +csilv@chromium.org +estade@chromium.org +jhawkins@chromium.org +stuartmorgan@chromium.org diff --git a/chrome/browser/resources/options2/about_page.css b/chrome/browser/resources/options2/about_page.css new file mode 100644 index 0000000..a64f9b8 --- /dev/null +++ b/chrome/browser/resources/options2/about_page.css @@ -0,0 +1,34 @@ +#aboutPage { + -webkit-user-select: text; +} + +.loading { + font-style: italic; +} + +#channelSelect { + margin-bottom: 5px; +} + +#channelWarning { + color: red; + font-weight: bold; +} + +.update-icon { + width: 17px; + height: 17px; + display: inline-block; + vertical-align: middle; + background-repeat: no-repeat; +} + +.update-icon.fail { + background-image: url('../../../app/theme/update_fail.png'); +} +.update-icon.available { + background-image: url('../../../app/theme/update_available.png'); +} +.update-icon.up-to-date { + background-image: url('../../../app/theme/update_uptodate.png'); +} diff --git a/chrome/browser/resources/options2/about_page.html b/chrome/browser/resources/options2/about_page.html new file mode 100644 index 0000000..3ed27e1 --- /dev/null +++ b/chrome/browser/resources/options2/about_page.html @@ -0,0 +1,119 @@ +<div id="aboutPage" class="page" hidden> + <h1 i18n-content="product"></h1> + <div id="aboutPageLessInfo"> + <section> + <div> + <!-- White space is significant between spans. --> + <div> + <span i18n-content="browser"></span> + <span i18n-content="browser_version"></span> + </div> + <div> +<if expr="not pp_ifdef('chromeos')"> + <span i18n-content="os"></span> <span id="osVersion0"> +</if> +<if expr="pp_ifdef('chromeos')"> + <span i18n-content="platform"></span> <span id="osVersion0"> +</if> + <span class="loading" i18n-content="loading"></span></span></div> + <div><span i18n-content="firmware"></span> <span id="osFirmware0"> + <span class="loading" i18n-content="loading"></span></span></div> + <div> + <button id="moreInfoButton" class="link-button" + i18n-content="more_info"></button> + </div> + </div> + </section> + </div> + <div id="aboutPageMoreInfo" hidden> + <section> + <h3 i18n-content="channel"></h3> + <div> + <select id="channelSelect"> + <option value="stable-channel" i18n-content="stable"></option> + <option value="beta-channel" i18n-content="beta"></option> + <option value="dev-channel" i18n-content="dev"></option> + </select> + <div id="channelWarningBlock" hidden> + <div id="channelWarning" i18n-content="channel_warning_header"></div> + <div i18n-content="channel_warning_text"></div> + </div> + </div> + </section> + <section> + <h3 i18n-content="browser"></h3> + <div i18n-content="browser_version"></div> + </section> + <section> +<if expr="not pp_ifdef('chromeos')"> + <h3 i18n-content="os"></h3> +</if> +<if expr="pp_ifdef('chromeos')"> + <h3 i18n-content="platform"></h3> +</if> + <div id="osVersion1"> + <span class="loading" i18n-content="loading"></span> + </div> + </section> + <section> + <h3 i18n-content="firmware"></h3> + <div id="osFirmware1"> + <span class="loading" i18n-content="loading"></span> + </section> + <section> + <h3>WebKit</h3> + <div i18n-content="webkit_version"></div> + </section> + <section> + <h3 i18n-content="js_engine"></h3> + <div i18n-content="js_engine_version"></div> + </section> + <section> + <h3 i18n-content="user_agent"></h3> + <div i18n-content="user_agent_info"></div> + </section> + <section> + <h3 i18n-content="command_line"></h3> + <div i18n-content="command_line_info"></div> + </section> + </div> + <section> + <div> + <div i18n-content="copyright"></div> + <div> + <!-- Odd formatting to avoid unwanted spaces between elements. --> + <span i18n-content="license_content_0"> + </span><a target="_blank" + i18n-values="href:license_link_0" + i18n-content="license_link_content_0"> + </a><span i18n-content="license_content_1"> + </span><a target="_blank" + i18n-values="href:license_link_1" + i18n-content="license_link_content_1"> + </a><span i18n-content="license_content_2"> + </span> + </div> + <div> + <span i18n-content="cros_license_content_0"> + </span><a target="_blank" + i18n-values="href:cros_license_link_0" + i18n-content="cros_license_link_content_0"> + </a><span i18n-content="cros_license_content_1"> + </span> + </div> + </div> + </section> + <section> + <div> + <div> + <div id="updateIcon" class="update-icon up-to-date"></div> + <span id="updateStatus" i18n-content="update_status"></span> + </div> + <div> + <!-- TODO seanparent: fill in last checked. --> + <!-- <span i18n-content="last_check"></span> --> + <button id="checkNow" i18n-content="check_now" disabled></button> + </div> + </div> + </section> +</div> diff --git a/chrome/browser/resources/options2/about_page.js b/chrome/browser/resources/options2/about_page.js new file mode 100644 index 0000000..6f608f0 --- /dev/null +++ b/chrome/browser/resources/options2/about_page.js @@ -0,0 +1,220 @@ +// Copyright (c) 2011 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() { + const OptionsPage = options.OptionsPage; + + /** + * The number of milliseconds used for showing a message. + * @type {number} + */ + const MESSAGE_DELAY_MS = 1000; // 1 sec. + + /** + * Encapsulated handling of about page. + */ + function AboutPage() { + OptionsPage.call(this, 'about', templateData.aboutPageTabTitle, + 'aboutPage'); + } + + cr.addSingletonGetter(AboutPage); + + AboutPage.prototype = { + // Inherit AboutPage from OptionsPage. + __proto__: OptionsPage.prototype, + + /** + * The queue is used for updating the status message with delay, like: + * [["Check for update...", 1000], ["Chrome OS is up to date", 0]] + * @type {!Array.<!Array>} + */ + statusMessageQueue_: [], + + /** + * True if the status message queue flush started. + * @type {boolean} + */ + statusMessageQueueFlushStarted_: false, + + /** + * The selected release channel. + * @type {string} + */ + selectedChannel_: '', + + // Initialize AboutPage. + initializePage: function() { + // Call base class implementation to start preference initialization. + OptionsPage.prototype.initializePage.call(this); + + $('checkNow').onclick = function(event) { + chrome.send('CheckNow'); + }; + + $('moreInfoButton').onclick = function(event) { + $('aboutPageLessInfo').hidden = true; + $('aboutPageMoreInfo').hidden = false; + }; + + if (!AccountsOptions.currentUserIsOwner()) { + $('channelSelect').disabled = true; + } else { + var self = this; + $('channelSelect').onchange = function(event) { + self.selectedOptionOnChange_(event.target.value); + }; + } + + // Notify the handler that the page is ready. + chrome.send('PageReady'); + }, + + // Update the Default Browsers section based on the current state. + updateOSVersion_: function(versionString) { + $('osVersion0').textContent = versionString; + $('osVersion1').textContent = versionString; + }, + + updateOSFirmware_: function(firmwareString) { + $('osFirmware0').textContent = firmwareString; + $('osFirmware1').textContent = firmwareString; + }, + + /** + * Updates the status message like "Checking for update...". + * @param {string} message The message to be shown. + * @param {boolean} insertDelay show the message for a while. + * @private + */ + updateStatus_: function(message, insertDelay) { + // Add the message to the queue with delay if needed. + // The delay is inserted so users can read the message. + var delayMs = insertDelay ? MESSAGE_DELAY_MS : 0; + this.statusMessageQueue_.push([message, delayMs]); + // Start the periodic flusher if not started. + if (this.statusMessageQueueFlushStarted_ == false) { + this.flushStatusMessageQueuePeriodically_(); + } + }, + + /** + * Flushes the status message queue periodically using a timer. + * @private + */ + flushStatusMessageQueuePeriodically_: function() { + // Stop the periodic flusher if the queue becomes empty. + if (this.statusMessageQueue_.length == 0) { + this.statusMessageQueueFlushStarted_ = false; + return; + } + this.statusMessageQueueFlushStarted_ = true; + + // Update the status message. + var pair = this.statusMessageQueue_.shift(); + var message = pair[0]; + var delayMs = pair[1]; + $('updateStatus').textContent = message; + + // Schedule the next flush with delay as needed. + var self = this; + window.setTimeout( + function() { self.flushStatusMessageQueuePeriodically_() }, + delayMs); + }, + + updateEnable_: function(enable) { + $('checkNow').disabled = !enable; + }, + + setReleaseChannel_: function(channel) { + // Write the value into the pref which will end up in the policy. + // Eventually, the update engine will use the policy value as the + // source truth for the update channel (see http://crosbug/17015). + Preferences.setStringPref("cros.system.releaseChannel", channel); + this.selectedChannel_ = channel; + chrome.send('SetReleaseTrack', [channel]); + }, + + selectedOptionOnChange_: function(value) { + if (value == 'dev-channel') { + // Open confirm dialog. + var self = this; + AlertOverlay.show( + localStrings.getString('channel_warning_header'), + localStrings.getString('channel_warning_text'), + localStrings.getString('ok'), + localStrings.getString('cancel'), + function() { + // Ok, so set release track and update selected channel. + $('channelWarningBlock').hidden = false; + self.setReleaseChannel_(value); }, + function() { + // Cancel, so switch back to previous selected channel. + self.updateSelectedOption_(self.selectedChannel_); } + ); + } else { + $('channelWarningBlock').hidden = true; + this.setReleaseChannel_(value); + } + }, + + // Updates the selected option in 'channelSelect' <select> element. + updateSelectedOption_: function(value) { + var options = $('channelSelect').querySelectorAll('option'); + for (var i = 0; i < options.length; i++) { + var option = options[i]; + if (option.value == value) { + option.selected = true; + this.selectedChannel_ = value; + } + } + if (value == 'dev-channel') + $('channelWarningBlock').hidden = false; + }, + + // Changes the "check now" button to "restart now" button. + changeToRestartButton_: function() { + $('checkNow').textContent = localStrings.getString('restart_now'); + $('checkNow').disabled = false; + $('checkNow').onclick = function(event) { + chrome.send('RestartNow'); + }; + }, + }; + + AboutPage.updateOSVersionCallback = function(versionString) { + AboutPage.getInstance().updateOSVersion_(versionString); + }; + + AboutPage.updateOSFirmwareCallback = function(firmwareString) { + AboutPage.getInstance().updateOSFirmware_(firmwareString); + }; + + AboutPage.updateStatusCallback = function(message, insertDelay) { + AboutPage.getInstance().updateStatus_(message, insertDelay); + }; + + AboutPage.updateEnableCallback = function(enable) { + AboutPage.getInstance().updateEnable_(enable); + }; + + AboutPage.updateSelectedOptionCallback = function(value) { + AboutPage.getInstance().updateSelectedOption_(value); + }; + + AboutPage.setUpdateImage = function(state) { + $('updateIcon').className= 'update-icon ' + state; + }; + + AboutPage.changeToRestartButton = function() { + AboutPage.getInstance().changeToRestartButton_(); + }; + + // Export + return { + AboutPage: AboutPage + }; + +}); diff --git a/chrome/browser/resources/options2/advanced_options.css b/chrome/browser/resources/options2/advanced_options.css new file mode 100644 index 0000000..bf53be2 --- /dev/null +++ b/chrome/browser/resources/options2/advanced_options.css @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2011 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. + */ + +#advancedPage .section-group:not(:first-child) { + margin-top: 10px; +} + +#advancedPage .section-group:not(:last-child) { + margin-bottom: 10px; +} + +#advancedPage select, +#advancedPage .web-content-select-label { + min-width: 145px; +} + +#advancedPage .web-content-select-label > span:only-of-type { + display: inline-block; + min-width: 100px; +} + +#download-location-group { + margin: 10px 0 5px; + min-width: 470px; +} + +#fontSettingsCustomizeFontsButton, +#privacyClearDataButton { + margin-left: 10px; +} diff --git a/chrome/browser/resources/options2/advanced_options.html b/chrome/browser/resources/options2/advanced_options.html new file mode 100644 index 0000000..525eb99 --- /dev/null +++ b/chrome/browser/resources/options2/advanced_options.html @@ -0,0 +1,232 @@ +<div id="advancedPage" class="page" hidden> + <h1 i18n-content="advancedPage"></h1> + <div class="displaytable"> + <section> + <h3 i18n-content="advancedSectionTitlePrivacy"></h3> + <div> + <div class="section-group"> + <button id="privacyContentSettingsButton" + i18n-content="privacyContentSettingsButton"></button> + <button id="privacyClearDataButton" + i18n-content="privacyClearDataButton"></button> + </div> + <div i18n-content="improveBrowsingExperience" + class="informational-text"> + </div> + <div> + <span i18n-content="disableWebServices" + class="informational-text"> + </span> + <a target="_blank" i18n-content="learnMore" + i18n-values="href:privacyLearnMoreURL"></a> + </div> + <div class="checkbox"> + <label> + <input id="alternateErrorPagesEnabled" + pref="alternate_error_pages.enabled" + metric="Options_LinkDoctorCheckbox" type="checkbox"> + <span i18n-content="linkDoctorPref"></span> + </label> + </div> + <div class="checkbox"> + <label> + <input id="searchSuggestEnabled" pref="search.suggest_enabled" + metric="Options_UseSuggestCheckbox" type="checkbox"> + <span i18n-content="suggestPref"></span> + </label> + </div> + <div class="checkbox"> + <label> + <input id="dnsPrefetchingEnabled" pref="dns_prefetching.enabled" + metric="Options_DnsPrefetchCheckbox" type="checkbox"> + <span i18n-content="networkPredictionEnabledDescription"></span> + </label> + </div> + <div class="checkbox"> + <label> + <input id="safeBrowsingEnabled" pref="safebrowsing.enabled" + metric="Options_SafeBrowsingCheckbox" type="checkbox"> + <span i18n-content="safeBrowsingEnableProtection"></span> + </label> + </div> +<if expr="pp_ifdef('_google_chrome') and pp_ifdef('chromeos')"> + <div id="metricsReportingSetting" class="checkbox"> + <label> + <input id="metricsReportingEnabled" + pref="cros.metrics.reportingEnabled" type="checkbox"> + <span id="metricsReportingEnabledText" + i18n-content="enableLogging"> + </span> + </label> + </div> +</if> +<if expr="pp_ifdef('_google_chrome') and not pp_ifdef('chromeos')"> + <div id="metricsReportingSetting" class="checkbox"> + <label> + <input id="metricsReportingEnabled" type="checkbox"> + <span i18n-content="enableLogging"></span> + </label> + </div> +</if> + </div> + </section> + <section> + <h3 i18n-content="advancedSectionTitleContent"></h3> + <div> + <div class="section-group"> + <label class="web-content-select-label"> + <span i18n-content="defaultFontSizeLabel"></span> + <select id="defaultFontSize"> + <option value="9" i18n-content="fontSizeLabelVerySmall"> + </option> + <option value="12" i18n-content="fontSizeLabelSmall"></option> + <option value="16" i18n-content="fontSizeLabelMedium"></option> + <option value="20" i18n-content="fontSizeLabelLarge"></option> + <option value="24" i18n-content="fontSizeLabelVeryLarge"> + </option> + </select> + </label> + <button id="fontSettingsCustomizeFontsButton" + i18n-content="fontSettingsCustomizeFontsButton"></button> + </div> + <div class="section-group"> + <label class="web-content-select-label"> + <span i18n-content="defaultZoomFactorLabel"></span> + <select id="defaultZoomFactor" dataType="double"></select> + </label> + </div> +<if expr="not pp_ifdef('chromeos') or os == 'win32'"> + <div class="section-group"> +</if> +<if expr="not pp_ifdef('chromeos')"> + <button id="language-button" + i18n-content="languageAndSpellCheckSettingsButton"></button> +</if> +<if expr="not pp_ifdef('chromeos') or os == 'win32'"> + </div> +</if> +<if expr="os == 'darwin'"> + <div class="checkbox"> + <label> + <input id="tabsToLinksPref" pref="webkit.webprefs.tabs_to_links" + metric="Options_TabsToLinks" type="checkbox"> + <span i18n-content="tabsToLinksPref"></span> + </label> + </div> +</if> + </div> + </section> +<if expr="not pp_ifdef('chromeos')"> + <section> + <h3 i18n-content="advancedSectionTitleNetwork"></h3> + <div> + <div id="proxiesLabel"></div> + <div class="section-group"> + <button id="proxiesConfigureButton" + i18n-content="proxiesConfigureButton"></button> + </div> + </div> + </section> +</if> + <section> + <h3 i18n-content="advancedSectionTitleTranslate"></h3> + <div class="checkbox"> + <label> + <input id="enableTranslate" pref="translate.enabled" + metric="Options_Translate" type="checkbox"> + <span i18n-content="translateEnableTranslate"></span> + </label> + </div> + </section> +<if expr="not pp_ifdef('chromeos')"> + <section> + <h3 i18n-content="downloadLocationGroupName"></h3> + <div> + <div id="download-location-group"> + <label> + <span i18n-content="downloadLocationBrowseTitle"></span> + <input id="downloadLocationPath" class="weakrtl" type="text" + pref="download.default_directory" size="36"> + </label> + <button id="downloadLocationChangeButton" + pref="download.prompt_for_download" + i18n-content="downloadLocationChangeButton"></button> + </div> + <div class="checkbox"> + <label> + <input type="checkbox" + pref="download.prompt_for_download" + metric="Options_AskForSaveLocation"> + <span i18n-content="downloadLocationAskForSaveLocation"></span> + </label> + </div> + <div id="auto-open-file-types-label" + i18n-content="autoOpenFileTypesInfo"></div> + <div class="section-group"> + <button id="autoOpenFileTypesResetToDefault" + i18n-content="autoOpenFileTypesResetToDefault"></button> + </div> + </div> + </section> +</if> + <section> + <h3 i18n-content="advancedSectionTitleSecurity"></h3> + <div> + <div class="section-group"> + <button id="certificatesManageButton" + i18n-content="certificatesManageButton"></button> + </div> + <div class="checkbox"> + <label> + <input id="sslCheckRevocation" type="checkbox"> + <span i18n-content="sslCheckRevocation"></span> + </label> + </div> + </div> + </section> +<if expr="not pp_ifdef('chromeos')"> + <section id="cloud-print-proxy-section"> + <h3 i18n-content="advancedSectionTitleCloudPrint"></h3> + <div> + <div id="cloudPrintProxyLabel" + i18n-content="cloudPrintProxyDisabledLabel"></div> + <div class="section-group"> + <button id="cloudPrintProxySetupButton" + i18n-content="cloudPrintProxyDisabledButton"></button> + <button id="cloudPrintProxyManageButton" + i18n-content="cloudPrintProxyEnabledManageButton"></button> + </div> + </div> + </section> +</if> +<if expr="pp_ifdef('chromeos')"> + <section id="cloud-print-proxy-section"> + <h3 i18n-content="advancedSectionTitleCloudPrint"></h3> + <div> + <div> + <span i18n-content="cloudPrintChromeosOptionLabel" + class="informational-text"> + </span> + <a target="_blank" i18n-content="learnMore" + i18n-values="href:cloudPrintLearnMoreURL"></a> + </div> + <div class="section-group"> + <button id="cloudPrintProxyManageButton" + i18n-content="cloudPrintChromeosOptionButton"></button> + </div> + </div> + </section> +</if> +<if expr="os != 'darwin' and not pp_ifdef('chromeos')"> + <section id="background-section"> + <h3 i18n-content="advancedSectionTitleBackground"></h3> + <div class="checkbox"> + <label> + <input id="backgroundModeCheckbox" type="checkbox"> + <span i18n-content="backgroundModeCheckbox"></span> + </label> + </div> + </section> +</if> + </div> +</div> diff --git a/chrome/browser/resources/options2/advanced_options.js b/chrome/browser/resources/options2/advanced_options.js new file mode 100644 index 0000000..fb1a809 --- /dev/null +++ b/chrome/browser/resources/options2/advanced_options.js @@ -0,0 +1,266 @@ +// Copyright (c) 2011 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; + + // + // AdvancedOptions class + // Encapsulated handling of advanced options page. + // + function AdvancedOptions() { + OptionsPage.call(this, 'advanced', templateData.advancedPageTabTitle, + 'advancedPage'); + } + + cr.addSingletonGetter(AdvancedOptions); + + AdvancedOptions.prototype = { + // Inherit AdvancedOptions from OptionsPage. + __proto__: options.OptionsPage.prototype, + + /** + * Initializes the page. + */ + initializePage: function() { + // Call base class implementation to starts preference initialization. + OptionsPage.prototype.initializePage.call(this); + + // Set up click handlers for buttons. + $('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']); + }; + + // 'metricsReportingEnabled' element is only present on Chrome branded + // builds. + if ($('metricsReportingEnabled')) { + $('metricsReportingEnabled').onclick = function(event) { + chrome.send('metricsReportingCheckboxAction', + [String(event.target.checked)]); + }; + } + + if (!cr.isChromeOS) { + $('autoOpenFileTypesResetToDefault').onclick = function(event) { + chrome.send('autoOpenFileTypesAction'); + }; + } + + $('fontSettingsCustomizeFontsButton').onclick = function(event) { + OptionsPage.navigateToPage('fonts'); + chrome.send('coreOptionsUserMetricsAction', ['Options_FontSettings']); + }; + $('defaultFontSize').onchange = function(event) { + chrome.send('defaultFontSizeAction', + [String(event.target.options[event.target.selectedIndex].value)]); + }; + $('defaultZoomFactor').onchange = function(event) { + chrome.send('defaultZoomFactorAction', + [String(event.target.options[event.target.selectedIndex].value)]); + }; + + $('language-button').onclick = function(event) { + OptionsPage.navigateToPage('languages'); + chrome.send('coreOptionsUserMetricsAction', + ['Options_LanuageAndSpellCheckSettings']); + }; + + 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']); + }; + } + + if (!cr.isChromeOS) { + $('proxiesConfigureButton').onclick = function(event) { + chrome.send('showNetworkProxySettings'); + }; + $('downloadLocationChangeButton').onclick = function(event) { + chrome.send('selectDownloadLocation'); + }; + // This text field is always disabled. Setting ".disabled = true" isn't + // enough, since a policy can disable it but shouldn't re-enable when + // it is removed. + $('downloadLocationPath').setDisabled('readonly', true); + } + + $('sslCheckRevocation').onclick = function(event) { + chrome.send('checkRevocationCheckboxAction', + [String($('sslCheckRevocation').checked)]); + }; + + if ($('backgroundModeCheckbox')) { + $('backgroundModeCheckbox').onclick = function(event) { + chrome.send('backgroundModeAction', + [String($('backgroundModeCheckbox').checked)]); + }; + } + + // 'cloudPrintProxyEnabled' is true for Chrome branded builds on + // certain platforms, or could be enabled by a lab. + if (!cr.isChromeOS) { + $('cloudPrintProxySetupButton').onclick = function(event) { + if ($('cloudPrintProxyManageButton').style.display == 'none') { + // Disable the button, set it's text to the intermediate state. + $('cloudPrintProxySetupButton').textContent = + localStrings.getString('cloudPrintProxyEnablingButton'); + $('cloudPrintProxySetupButton').disabled = true; + chrome.send('showCloudPrintSetupDialog'); + } else { + chrome.send('disableCloudPrintProxy'); + } + }; + } + $('cloudPrintProxyManageButton').onclick = function(event) { + chrome.send('showCloudPrintManagePage'); + }; + + } + }; + + // + // Chrome callbacks + // + + // Set the checked state of the metrics reporting checkbox. + AdvancedOptions.SetMetricsReportingCheckboxState = function( + checked, disabled) { + $('metricsReportingEnabled').checked = checked; + $('metricsReportingEnabled').disabled = disabled; + if (disabled) + $('metricsReportingEnabledText').className = 'disable-services-span'; + } + + AdvancedOptions.SetMetricsReportingSettingVisibility = function(visible) { + if (visible) { + $('metricsReportingSetting').style.display = 'block'; + } else { + $('metricsReportingSetting').style.display = 'none'; + } + } + + // Set the font size selected item. + AdvancedOptions.SetFontSize = function(font_size_value) { + var selectCtl = $('defaultFontSize'); + for (var i = 0; i < selectCtl.options.length; i++) { + if (selectCtl.options[i].value == font_size_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(localStrings.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). + */ + AdvancedOptions.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)); + } + }; + + // Set the enabled state for the autoOpenFileTypesResetToDefault button. + AdvancedOptions.SetAutoOpenFileTypesDisabledAttribute = function(disabled) { + if (!cr.isChromeOS) { + $('autoOpenFileTypesResetToDefault').disabled = disabled; + + if (disabled) + $('auto-open-file-types-label').classList.add('disabled'); + else + $('auto-open-file-types-label').classList.remove('disabled'); + } + }; + + // Set the enabled state for the proxy settings button. + AdvancedOptions.SetupProxySettingsSection = function(disabled, label) { + if (!cr.isChromeOS) { + $('proxiesConfigureButton').disabled = disabled; + $('proxiesLabel').textContent = label; + } + }; + + // Set the checked state for the sslCheckRevocation checkbox. + AdvancedOptions.SetCheckRevocationCheckboxState = function( + checked, disabled) { + $('sslCheckRevocation').checked = checked; + $('sslCheckRevocation').disabled = disabled; + }; + + // Set the checked state for the backgroundModeCheckbox element. + AdvancedOptions.SetBackgroundModeCheckboxState = function(checked) { + $('backgroundModeCheckbox').checked = checked; + }; + + // Set the Cloud Print proxy UI to enabled, disabled, or processing. + AdvancedOptions.SetupCloudPrintProxySection = function( + disabled, label, allowed) { + if (!cr.isChromeOS) { + $('cloudPrintProxyLabel').textContent = label; + if (disabled || !allowed) { + $('cloudPrintProxySetupButton').textContent = + localStrings.getString('cloudPrintProxyDisabledButton'); + $('cloudPrintProxyManageButton').style.display = 'none'; + } else { + $('cloudPrintProxySetupButton').textContent = + localStrings.getString('cloudPrintProxyEnabledButton'); + $('cloudPrintProxyManageButton').style.display = 'inline'; + } + $('cloudPrintProxySetupButton').disabled = !allowed; + } + }; + + AdvancedOptions.RemoveCloudPrintProxySection = function() { + if (!cr.isChromeOS) { + var proxySectionElm = $('cloud-print-proxy-section'); + if (proxySectionElm) + proxySectionElm.parentNode.removeChild(proxySectionElm); + } + }; + + // Export + return { + AdvancedOptions: AdvancedOptions + }; + +}); diff --git a/chrome/browser/resources/options2/alert_overlay.css b/chrome/browser/resources/options2/alert_overlay.css new file mode 100644 index 0000000..de5c236 --- /dev/null +++ b/chrome/browser/resources/options2/alert_overlay.css @@ -0,0 +1,3 @@ +#alertOverlayMessage { + width: 400px; +} diff --git a/chrome/browser/resources/options2/alert_overlay.html b/chrome/browser/resources/options2/alert_overlay.html new file mode 100644 index 0000000..3de00e7 --- /dev/null +++ b/chrome/browser/resources/options2/alert_overlay.html @@ -0,0 +1,12 @@ +<div id="alertOverlay" class="page" hidden> + <h1 id="alertOverlayTitle"></h1> + <div class="content-area"> + <div id="alertOverlayMessage"></div> + </div> + <div class="action-area"> + <div class="button-strip"> + <button id="alertOverlayCancel" type="reset"></button> + <button id="alertOverlayOk" type="submit"></button> + </div> + </div> +</div> diff --git a/chrome/browser/resources/options2/alert_overlay.js b/chrome/browser/resources/options2/alert_overlay.js new file mode 100644 index 0000000..eb1fb8e --- /dev/null +++ b/chrome/browser/resources/options2/alert_overlay.js @@ -0,0 +1,144 @@ +// Copyright (c) 2010 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; + + /** + * AlertOverlay class + * Encapsulated handling of a generic alert. + * @class + */ + function AlertOverlay() { + OptionsPage.call(this, 'alertOverlay', '', 'alertOverlay'); + } + + cr.addSingletonGetter(AlertOverlay); + + AlertOverlay.prototype = { + // Inherit AlertOverlay from OptionsPage. + __proto__: OptionsPage.prototype, + + /** + * Whether the page can be shown. Used to make sure the page is only + * shown via AlertOverlay.Show(), and not via the address bar. + * @private + */ + canShow_: false, + + /** + * Initialize the page. + */ + initializePage: function() { + // Call base class implementation to start preference initialization. + OptionsPage.prototype.initializePage.call(this); + + var self = this; + $('alertOverlayOk').onclick = function(event) { + self.handleOK_(); + }; + + $('alertOverlayCancel').onclick = function(event) { + self.handleCancel_(); + }; + }, + + /** + * Handle the 'ok' button. Clear the overlay and call the ok callback if + * available. + * @private + */ + handleOK_: function() { + OptionsPage.closeOverlay(); + if (this.okCallback != undefined) { + this.okCallback.call(); + } + }, + + /** + * Handle the 'cancel' button. Clear the overlay and call the cancel + * callback if available. + * @private + */ + handleCancel_: function() { + OptionsPage.closeOverlay(); + if (this.cancelCallback != undefined) { + this.cancelCallback.call(); + } + }, + + /** + * The page is getting hidden. Don't let it be shown again. + */ + willHidePage: function() { + canShow_ = false; + }, + + /** @inheritDoc */ + canShowPage: function() { + return this.canShow_; + }, + }; + + /** + * Show an alert overlay with the given message, button titles, and + * callbacks. + * @param {string} title The alert title to display to the user. + * @param {string} message The alert message to display to the user. + * @param {string} okTitle The title of the OK button. If undefined or empty, + * no button is shown. + * @param {string} cancelTitle The title of the cancel button. If undefined or + * empty, no button is shown. + * @param {function} okCallback A function to be called when the user presses + * the ok button. The alert window will be closed automatically. Can be + * undefined. + * @param {function} cancelCallback A function to be called when the user + * presses the cancel button. The alert window will be closed + * automatically. Can be undefined. + */ + AlertOverlay.show = function( + title, message, okTitle, cancelTitle, okCallback, cancelCallback) { + if (title != undefined) { + $('alertOverlayTitle').textContent = title; + $('alertOverlayTitle').style.display = 'block'; + } else { + $('alertOverlayTitle').style.display = 'none'; + } + + if (message != undefined) { + $('alertOverlayMessage').textContent = message; + $('alertOverlayMessage').style.display = 'block'; + } else { + $('alertOverlayMessage').style.display = 'none'; + } + + if (okTitle != undefined && okTitle != '') { + $('alertOverlayOk').textContent = okTitle; + $('alertOverlayOk').style.display = 'block'; + } else { + $('alertOverlayOk').style.display = 'none'; + } + + if (cancelTitle != undefined && cancelTitle != '') { + $('alertOverlayCancel').textContent = cancelTitle; + $('alertOverlayCancel').style.display = 'inline'; + } else { + $('alertOverlayCancel').style.display = 'none'; + } + + var alertOverlay = AlertOverlay.getInstance(); + alertOverlay.okCallback = okCallback; + alertOverlay.cancelCallback = cancelCallback; + alertOverlay.canShow_ = true; + + // Intentionally don't show the URL in the location bar as we don't want + // people trying to navigate here by hand. + OptionsPage.showPageByName('alertOverlay', false); + } + + // Export + return { + AlertOverlay: AlertOverlay + }; +}); diff --git a/chrome/browser/resources/options2/autocomplete_list.js b/chrome/browser/resources/options2/autocomplete_list.js new file mode 100644 index 0000000..2aeb195 --- /dev/null +++ b/chrome/browser/resources/options2/autocomplete_list.js @@ -0,0 +1,239 @@ +// Copyright (c) 2011 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() { + const ArrayDataModel = cr.ui.ArrayDataModel; + const List = cr.ui.List; + const ListItem = cr.ui.ListItem; + + /** + * Creates a new autocomplete list item. + * @param {Object} pageInfo The page this item represents. + * @constructor + * @extends {cr.ui.ListItem} + */ + function AutocompleteListItem(pageInfo) { + var el = cr.doc.createElement('div'); + el.pageInfo_ = pageInfo; + AutocompleteListItem.decorate(el); + return el; + } + + /** + * Decorates an element as an autocomplete list item. + * @param {!HTMLElement} el The element to decorate. + */ + AutocompleteListItem.decorate = function(el) { + el.__proto__ = AutocompleteListItem.prototype; + el.decorate(); + }; + + AutocompleteListItem.prototype = { + __proto__: ListItem.prototype, + + /** @inheritDoc */ + decorate: function() { + ListItem.prototype.decorate.call(this); + + var title = this.pageInfo_['title']; + var url = this.pageInfo_['displayURL']; + var titleEl = this.ownerDocument.createElement('span'); + titleEl.className = 'title'; + titleEl.textContent = title || url; + this.appendChild(titleEl); + + if (title && title.length > 0 && url != title) { + var separatorEl = this.ownerDocument.createTextNode(' - '); + this.appendChild(separatorEl); + + var urlEl = this.ownerDocument.createElement('span'); + urlEl.className = 'url'; + urlEl.textContent = url; + this.appendChild(urlEl); + } + }, + }; + + /** + * Creates a new autocomplete list popup. + * @constructor + * @extends {cr.ui.List} + */ + var AutocompleteList = cr.ui.define('list'); + + AutocompleteList.prototype = { + __proto__: List.prototype, + + /** + * The text field the autocomplete popup is currently attached to, if any. + * @type {HTMLElement} + * @private + */ + targetInput_: null, + + /** + * Keydown event listener to attach to a text field. + * @type {Function} + * @private + */ + textFieldKeyHandler_: null, + + /** + * Input event listener to attach to a text field. + * @type {Function} + * @private + */ + textFieldInputHandler_: null, + + /** + * A function to call when new suggestions are needed. + * @type {Function} + * @private + */ + suggestionUpdateRequestCallback_: null, + + /** @inheritDoc */ + decorate: function() { + List.prototype.decorate.call(this); + this.classList.add('autocomplete-suggestions'); + this.selectionModel = new cr.ui.ListSingleSelectionModel; + + this.textFieldKeyHandler_ = this.handleAutocompleteKeydown_.bind(this); + var self = this; + this.textFieldInputHandler_ = function(e) { + if (self.suggestionUpdateRequestCallback_) + self.suggestionUpdateRequestCallback_(self.targetInput_.value); + }; + this.addEventListener('change', function(e) { + var input = self.targetInput; + if (!input || !self.selectedItem) + return; + input.value = self.selectedItem['url']; + // Programatically change the value won't trigger a change event, but + // clients are likely to want to know when changes happen, so fire one. + var changeEvent = document.createEvent('Event'); + changeEvent.initEvent('change', true, true); + input.dispatchEvent(changeEvent); + }); + // Start hidden; adding suggestions will unhide. + this.hidden = true; + }, + + /** @inheritDoc */ + createItem: function(pageInfo) { + return new AutocompleteListItem(pageInfo); + }, + + /** + * The suggestions to show. + * @type {Array} + */ + set suggestions(suggestions) { + this.dataModel = new ArrayDataModel(suggestions); + this.hidden = !this.targetInput_ || suggestions.length == 0; + }, + + /** + * A function to call when the attached input field's contents change. + * The function should take one string argument, which will be the text + * to autocomplete from. + * @type {Function} + */ + set suggestionUpdateRequestCallback(callback) { + this.suggestionUpdateRequestCallback_ = callback; + }, + + /** + * Attaches the popup to the given input element. Requires + * that the input be wrapped in a block-level container of the same width. + * @param {HTMLElement} input The input element to attach to. + */ + attachToInput: function(input) { + if (this.targetInput_ == input) + return; + + this.detach(); + this.targetInput_ = input; + this.style.width = input.getBoundingClientRect().width + 'px'; + this.hidden = false; // Necessary for positionPopupAroundElement to work. + cr.ui.positionPopupAroundElement(input, this, cr.ui.AnchorType.BELOW) + // Start hidden; when the data model gets results the list will show. + this.hidden = true; + + input.addEventListener('keydown', this.textFieldKeyHandler_, true); + input.addEventListener('input', this.textFieldInputHandler_); + }, + + /** + * Detaches the autocomplete popup from its current input element, if any. + */ + detach: function() { + var input = this.targetInput_ + if (!input) + return; + + input.removeEventListener('keydown', this.textFieldKeyHandler_); + input.removeEventListener('input', this.textFieldInputHandler_); + this.targetInput_ = null; + this.suggestions = []; + }, + + /** + * Makes sure that the suggestion list matches the width of the input it is. + * attached to. Should be called any time the input is resized. + */ + syncWidthToInput: function() { + var input = this.targetInput_ + if (input) + this.style.width = input.getBoundingClientRect().width + 'px'; + }, + + /** + * The text field the autocomplete popup is currently attached to, if any. + * @return {HTMLElement} + */ + get targetInput() { + return this.targetInput_; + }, + + /** + * Handles input field key events that should be interpreted as autocomplete + * commands. + * @param {Event} event The keydown event. + * @private + */ + handleAutocompleteKeydown_: function(event) { + if (this.hidden) + return; + var handled = false; + switch (event.keyIdentifier) { + case 'U+001B': // Esc + this.suggestions = []; + handled = true; + break; + case 'Enter': + var hadSelection = this.selectedItem != null; + this.suggestions = []; + // Only count the event as handled if a selection is being commited. + handled = hadSelection; + break; + case 'Up': + case 'Down': + this.dispatchEvent(event); + handled = true; + break; + } + // Don't let arrow keys affect the text field, or bubble up to, e.g., + // an enclosing list item. + if (handled) { + event.preventDefault(); + event.stopPropagation(); + } + }, + }; + + return { + AutocompleteList: AutocompleteList + }; +}); diff --git a/chrome/browser/resources/options2/autofill_edit_address_overlay.html b/chrome/browser/resources/options2/autofill_edit_address_overlay.html new file mode 100644 index 0000000..43a13c7 --- /dev/null +++ b/chrome/browser/resources/options2/autofill_edit_address_overlay.html @@ -0,0 +1,99 @@ +<div id="autofill-edit-address-overlay" class="page" hidden> + <h1 id="autofill-address-title"></h1> + <div class="content-area"> + <div id="autofill-name-labels"> + <label for="first-name"> + <span i18n-content="autofillFirstNameLabel"></span> + </label> + <label for="middle-name"> + <span i18n-content="autofillMiddleNameLabel"></span> + </label> + <label for="last-name"> + <span i18n-content="autofillLastNameLabel"></span> + </label> + </div> + <list id="full-name-list"></list> + <div class="input"> + <label> + <div><span i18n-content="autofillCompanyNameLabel"></span></div> + <input id="company-name" type="text" class="autofill-form"> + </label> + </div> + <div class="input"> + <label> + <div><span i18n-content="autofillAddrLine1Label"></span></div> + <input id="addr-line-1" type="text" class="autofill-form"> + </label> + </div> + <div class="input"> + <label> + <div><span i18n-content="autofillAddrLine2Label"></span></div> + <input id="addr-line-2" type="text" class="autofill-form"> + </label> + </div> + <div class="table"> + <div class="row"> + <div class="input cell"> + <label for="city"> + <span i18n-content="autofillCityLabel"></span> + </label> + </div> + <div class="input cell"> + <label id="state-label" for="state"></label> + </div> + <div class="input cell"> + <label id="postal-code-label" for="postal-code"></label> + </div> + </div> + <div class="row"> + <div class="input cell"> + <input id="city" type="text" class="autofill-form"> + </div> + <div class="input cell"> + <input id="state" type="text" class="autofill-form"> + </div> + <div class="input cell"> + <input id="postal-code" type="text" class="autofill-form"> + </div> + </div> + </div> + <div class="input"> + <label> + <div> + <span i18n-content="autofillCountryLabel"></span> + </div> + <select id="country"></select> + </label> + </div> + <div class="table"> + <div class="row"> + <div class="input cell"> + <label for="phone-list"> + <span i18n-content="autofillPhoneLabel"></span> + </label> + </div> + <div class="input cell"> + <label for="email-list"> + <span i18n-content="autofillEmailLabel"></span> + </label> + </div> + </div> + <div class="row"> + <div class="input cell"> + <list id="phone-list" + i18n-values="placeholder:autofillAddPhonePlaceholder"></list> + </div> + <div class="input cell"> + <list id="email-list" + i18n-values="placeholder:autofillAddEmailPlaceholder"></list> + </div> + </div> + </div> + </div> + <div class="action-area button-strip"> + <button id="autofill-edit-address-cancel-button" type="reset" + i18n-content="cancel"></button> + <button id="autofill-edit-address-apply-button" type="submit" + i18n-content="ok" disabled></button> + </div> +</div> diff --git a/chrome/browser/resources/options2/autofill_edit_address_overlay.js b/chrome/browser/resources/options2/autofill_edit_address_overlay.js new file mode 100644 index 0000000..f41fd8c --- /dev/null +++ b/chrome/browser/resources/options2/autofill_edit_address_overlay.js @@ -0,0 +1,325 @@ +// Copyright (c) 2011 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() { + const OptionsPage = options.OptionsPage; + const ArrayDataModel = cr.ui.ArrayDataModel; + + // The GUID of the loaded address. + var guid; + + /** + * AutofillEditAddressOverlay class + * Encapsulated handling of the 'Add Page' overlay page. + * @class + */ + function AutofillEditAddressOverlay() { + OptionsPage.call(this, 'autofillEditAddress', + templateData.autofillEditAddressTitle, + 'autofill-edit-address-overlay'); + } + + cr.addSingletonGetter(AutofillEditAddressOverlay); + + AutofillEditAddressOverlay.prototype = { + __proto__: OptionsPage.prototype, + + /** + * Initializes the page. + */ + initializePage: function() { + OptionsPage.prototype.initializePage.call(this); + + this.createMultiValueLists_(); + + var self = this; + $('autofill-edit-address-cancel-button').onclick = function(event) { + self.dismissOverlay_(); + } + $('autofill-edit-address-apply-button').onclick = function(event) { + self.saveAddress_(); + self.dismissOverlay_(); + } + + self.guid = ''; + self.populateCountryList_(); + self.clearInputFields_(); + self.connectInputEvents_(); + }, + + /** + * Creates, decorates and initializes the multi-value lists for full name, + * phone, and email. + * @private + */ + createMultiValueLists_: function() { + var list = $('full-name-list'); + options.autofillOptions.AutofillNameValuesList.decorate(list); + list.autoExpands = true; + + list = $('phone-list'); + options.autofillOptions.AutofillPhoneValuesList.decorate(list); + list.autoExpands = true; + + list = $('email-list'); + options.autofillOptions.AutofillValuesList.decorate(list); + list.autoExpands = true; + }, + + /** + * Updates the data model for the list named |listName| with the values from + * |entries|. + * @param {String} listName The id of the list. + * @param {Array} entries The list of items to be added to the list. + */ + setMultiValueList_: function(listName, entries) { + // Add data entries. + var list = $(listName); + list.dataModel = new ArrayDataModel(entries); + + // Add special entry for adding new values. + list.dataModel.splice(list.dataModel.length, 0, null); + + // Update the status of the 'OK' button. + this.inputFieldChanged_(); + + var self = this; + list.dataModel.addEventListener( + 'splice', function(event) { self.inputFieldChanged_(); }); + list.dataModel.addEventListener( + 'change', function(event) { self.inputFieldChanged_(); }); + }, + + /** + * Updates the data model for the name list with the values from |entries|. + * @param {Array} names The list of names to be added to the list. + */ + setNameList_: function(names) { + // Add the given |names| as backing data for the list. + var list = $('full-name-list'); + list.dataModel = new ArrayDataModel(names); + + // Add special entry for adding new values. + list.dataModel.splice(list.dataModel.length, 0, null); + + var self = this; + list.dataModel.addEventListener( + 'splice', function(event) { self.inputFieldChanged_(); }); + list.dataModel.addEventListener( + 'change', function(event) { self.inputFieldChanged_(); }); + }, + + /** + * Clears any uncommitted input, resets the stored GUID and dismisses the + * overlay. + * @private + */ + dismissOverlay_: function() { + this.clearInputFields_(); + this.guid = ''; + OptionsPage.closeOverlay(); + }, + + /** + * Aggregates the values in the input fields into an array and sends the + * array to the Autofill handler. + * @private + */ + saveAddress_: function() { + var address = new Array(); + address[0] = this.guid; + var list = $('full-name-list'); + address[1] = list.dataModel.slice(0, list.dataModel.length - 1); + address[2] = $('company-name').value; + address[3] = $('addr-line-1').value; + address[4] = $('addr-line-2').value; + address[5] = $('city').value; + address[6] = $('state').value; + address[7] = $('postal-code').value; + address[8] = $('country').value; + list = $('phone-list'); + address[9] = list.dataModel.slice(0, list.dataModel.length - 1); + list = $('email-list'); + address[10] = list.dataModel.slice(0, list.dataModel.length - 1); + + chrome.send('setAddress', address); + }, + + /** + * Connects each input field to the inputFieldChanged_() method that enables + * or disables the 'Ok' button based on whether all the fields are empty or + * not. + * @private + */ + connectInputEvents_: function() { + var self = this; + $('company-name').oninput = $('addr-line-1').oninput = + $('addr-line-2').oninput = $('city').oninput = $('state').oninput = + $('postal-code').oninput = function(event) { + self.inputFieldChanged_(); + } + + $('country').onchange = function(event) { + self.countryChanged_(); + } + }, + + /** + * Checks the values of each of the input fields and disables the 'Ok' + * button if all of the fields are empty. + * @private + */ + inputFieldChanged_: function() { + // Length of lists are tested for <= 1 due to the "add" placeholder item + // in the list. + var disabled = + $('full-name-list').items.length <= 1 && + !$('company-name').value && + !$('addr-line-1').value && !$('addr-line-2').value && + !$('city').value && !$('state').value && !$('postal-code').value && + !$('country').value && $('phone-list').items.length <= 1 && + $('email-list').items.length <= 1; + $('autofill-edit-address-apply-button').disabled = disabled; + }, + + /** + * Updates the postal code and state field labels appropriately for the + * selected country. + * @private + */ + countryChanged_: function() { + var countryCode = $('country').value; + if (!countryCode) + countryCode = templateData.defaultCountryCode; + + var details = templateData.autofillCountryData[countryCode]; + var postal = $('postal-code-label'); + postal.textContent = details['postalCodeLabel']; + $('state-label').textContent = details['stateLabel']; + + // Also update the 'Ok' button as needed. + this.inputFieldChanged_(); + }, + + /** + * Populates the country <select> list. + * @private + */ + populateCountryList_: function() { + var countryData = templateData.autofillCountryData; + var defaultCountryCode = templateData.defaultCountryCode; + + // Build an array of the country names and their corresponding country + // codes, so that we can sort and insert them in order. + var countries = []; + for (var countryCode in countryData) { + var country = { + countryCode: countryCode, + name: countryData[countryCode]['name'] + }; + countries.push(country); + } + + // Sort the countries in alphabetical order by name. + countries = countries.sort(function(a, b) { + return a.name < b.name ? -1 : 1; + }); + + // Insert the empty and default countries at the beginning of the array. + var emptyCountry = { + countryCode: '', + name: '' + }; + var defaultCountry = { + countryCode: defaultCountryCode, + name: countryData[defaultCountryCode]['name'] + }; + var separator = { + countryCode: '', + name: '---', + disabled: true + } + countries.unshift(emptyCountry, defaultCountry, separator); + + // Add the countries to the country <select> list. + var countryList = $('country'); + for (var i = 0; i < countries.length; i++) { + var country = new Option(countries[i].name, countries[i].countryCode); + country.disabled = countries[i].disabled; + countryList.appendChild(country) + } + }, + + /** + * Clears the value of each input field. + * @private + */ + clearInputFields_: function() { + this.setNameList_([]); + $('company-name').value = ''; + $('addr-line-1').value = ''; + $('addr-line-2').value = ''; + $('city').value = ''; + $('state').value = ''; + $('postal-code').value = ''; + $('country').value = ''; + this.setMultiValueList_('phone-list', []); + this.setMultiValueList_('email-list', []); + + this.countryChanged_(); + }, + + /** + * Loads the address data from |address|, sets the input fields based on + * this data and stores the GUID of the address. + * @private + */ + loadAddress_: function(address) { + this.setInputFields_(address); + this.inputFieldChanged_(); + this.guid = address['guid']; + }, + + /** + * Sets the value of each input field according to |address| + * @private + */ + setInputFields_: function(address) { + this.setNameList_(address['fullName']); + $('company-name').value = address['companyName']; + $('addr-line-1').value = address['addrLine1']; + $('addr-line-2').value = address['addrLine2']; + $('city').value = address['city']; + $('state').value = address['state']; + $('postal-code').value = address['postalCode']; + $('country').value = address['country']; + this.setMultiValueList_('phone-list', address['phone']); + this.setMultiValueList_('email-list', address['email']); + + this.countryChanged_(); + }, + }; + + AutofillEditAddressOverlay.clearInputFields = function() { + AutofillEditAddressOverlay.getInstance().clearInputFields_(); + }; + + AutofillEditAddressOverlay.loadAddress = function(address) { + AutofillEditAddressOverlay.getInstance().loadAddress_(address); + }; + + AutofillEditAddressOverlay.setTitle = function(title) { + $('autofill-address-title').textContent = title; + }; + + AutofillEditAddressOverlay.setValidatedPhoneNumbers = function(numbers) { + AutofillEditAddressOverlay.getInstance().setMultiValueList_('phone-list', + numbers); + }; + + // Export + return { + AutofillEditAddressOverlay: AutofillEditAddressOverlay + }; +}); diff --git a/chrome/browser/resources/options2/autofill_edit_creditcard_overlay.html b/chrome/browser/resources/options2/autofill_edit_creditcard_overlay.html new file mode 100644 index 0000000..fa3825a --- /dev/null +++ b/chrome/browser/resources/options2/autofill_edit_creditcard_overlay.html @@ -0,0 +1,30 @@ +<div id="autofill-edit-credit-card-overlay" class="page" hidden> + <h1 id="autofill-credit-card-title"></h1> + <div class="content-area"> + <div class="input"> + <label> + <span i18n-content="nameOnCardLabel"></span><br> + <input id="name-on-card" type="text"> + </label> + </div> + <div class="input"> + <label> + <span i18n-content="creditCardNumberLabel"></span><br> + <input id="credit-card-number" type="text"> + </label> + </div> + <div class="input"> + <label> + <span i18n-content="creditCardExpirationDateLabel"></span><br> + <select id="expiration-month"></select> + <select id="expiration-year"></select> + </label> + </div> + </div> + <div class="action-area button-strip"> + <button id="autofill-edit-credit-card-cancel-button" type="reset" + i18n-content="cancel"></button> + <button id="autofill-edit-credit-card-apply-button" type="submit" + i18n-content="ok" disabled></button> + </div> +</div> diff --git a/chrome/browser/resources/options2/autofill_edit_creditcard_overlay.js b/chrome/browser/resources/options2/autofill_edit_creditcard_overlay.js new file mode 100644 index 0000000..8402a10 --- /dev/null +++ b/chrome/browser/resources/options2/autofill_edit_creditcard_overlay.js @@ -0,0 +1,205 @@ +// Copyright (c) 2011 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() { + const OptionsPage = options.OptionsPage; + + // The GUID of the loaded credit card. + var guid_; + + /** + * AutofillEditCreditCardOverlay class + * Encapsulated handling of the 'Add Page' overlay page. + * @class + */ + function AutofillEditCreditCardOverlay() { + OptionsPage.call(this, 'autofillEditCreditCard', + templateData.autofillEditCreditCardTitle, + 'autofill-edit-credit-card-overlay'); + } + + cr.addSingletonGetter(AutofillEditCreditCardOverlay); + + AutofillEditCreditCardOverlay.prototype = { + __proto__: OptionsPage.prototype, + + /** + * Initializes the page. + */ + initializePage: function() { + OptionsPage.prototype.initializePage.call(this); + + var self = this; + $('autofill-edit-credit-card-cancel-button').onclick = function(event) { + self.dismissOverlay_(); + } + $('autofill-edit-credit-card-apply-button').onclick = function(event) { + self.saveCreditCard_(); + self.dismissOverlay_(); + } + + self.guid_ = ''; + self.hasEditedNumber_ = false; + self.clearInputFields_(); + self.connectInputEvents_(); + self.setDefaultSelectOptions_(); + }, + + /** + * Clears any uncommitted input, and dismisses the overlay. + * @private + */ + dismissOverlay_: function() { + this.clearInputFields_(); + this.guid_ = ''; + this.hasEditedNumber_ = false; + OptionsPage.closeOverlay(); + }, + + /** + * Aggregates the values in the input fields into an array and sends the + * array to the Autofill handler. + * @private + */ + saveCreditCard_: function() { + var creditCard = new Array(5); + creditCard[0] = this.guid_; + creditCard[1] = $('name-on-card').value; + creditCard[2] = $('credit-card-number').value; + creditCard[3] = $('expiration-month').value; + creditCard[4] = $('expiration-year').value; + chrome.send('setCreditCard', creditCard); + }, + + /** + * Connects each input field to the inputFieldChanged_() method that enables + * or disables the 'Ok' button based on whether all the fields are empty or + * not. + * @private + */ + connectInputEvents_: function() { + var ccNumber = $('credit-card-number'); + $('name-on-card').oninput = ccNumber.oninput = + $('expiration-month').onchange = $('expiration-year').onchange = + this.inputFieldChanged_.bind(this); + }, + + /** + * Checks the values of each of the input fields and disables the 'Ok' + * button if all of the fields are empty. + * @param {Event} opt_event Optional data for the 'input' event. + * @private + */ + inputFieldChanged_: function(opt_event) { + var disabled = !$('name-on-card').value && !$('credit-card-number').value; + $('autofill-edit-credit-card-apply-button').disabled = disabled; + }, + + /** + * Sets the default values of the options in the 'Expiration date' select + * controls. + * @private + */ + setDefaultSelectOptions_: function() { + // Set the 'Expiration month' default options. + var expirationMonth = $('expiration-month'); + expirationMonth.options.length = 0; + for (var i = 1; i <= 12; ++i) { + var text; + if (i < 10) + text = '0' + i; + else + text = i; + + var option = document.createElement('option'); + option.text = text; + option.value = text; + expirationMonth.add(option, null); + } + + // Set the 'Expiration year' default options. + var expirationYear = $('expiration-year'); + expirationYear.options.length = 0; + + var date = new Date(); + var year = parseInt(date.getFullYear()); + for (var i = 0; i < 10; ++i) { + var text = year + i; + var option = document.createElement('option'); + option.text = text; + option.value = text; + expirationYear.add(option, null); + } + }, + + /** + * Clears the value of each input field. + * @private + */ + clearInputFields_: function() { + $('name-on-card').value = ''; + $('credit-card-number').value = ''; + $('expiration-month').selectedIndex = 0; + $('expiration-year').selectedIndex = 0; + + // Reset the enabled status of the 'Ok' button. + this.inputFieldChanged_(); + }, + + /** + * Sets the value of each input field according to |creditCard| + * @private + */ + setInputFields_: function(creditCard) { + $('name-on-card').value = creditCard['nameOnCard']; + $('credit-card-number').value = creditCard['creditCardNumber']; + + // The options for the year select control may be out-dated at this point, + // e.g. the user opened the options page before midnight on New Year's Eve + // and then loaded a credit card profile to edit in the new year, so + // reload the select options just to be safe. + this.setDefaultSelectOptions_(); + + var idx = parseInt(creditCard['expirationMonth'], 10); + $('expiration-month').selectedIndex = idx - 1; + + expYear = creditCard['expirationYear']; + var date = new Date(); + var year = parseInt(date.getFullYear()); + for (var i = 0; i < 10; ++i) { + var text = year + i; + if (expYear == String(text)) + $('expiration-year').selectedIndex = i; + } + }, + + /** + * Loads the credit card data from |creditCard|, sets the input fields based + * on this data and stores the GUID of the credit card. + * @private + */ + loadCreditCard_: function(creditCard) { + this.setInputFields_(creditCard); + this.inputFieldChanged_(); + this.guid_ = creditCard['guid']; + }, + }; + + AutofillEditCreditCardOverlay.clearInputFields = function(title) { + AutofillEditCreditCardOverlay.getInstance().clearInputFields_(); + }; + + AutofillEditCreditCardOverlay.loadCreditCard = function(creditCard) { + AutofillEditCreditCardOverlay.getInstance().loadCreditCard_(creditCard); + }; + + AutofillEditCreditCardOverlay.setTitle = function(title) { + $('autofill-credit-card-title').textContent = title; + }; + + // Export + return { + AutofillEditCreditCardOverlay: AutofillEditCreditCardOverlay + }; +}); diff --git a/chrome/browser/resources/options2/autofill_options.css b/chrome/browser/resources/options2/autofill_options.css new file mode 100644 index 0000000..55521b27 --- /dev/null +++ b/chrome/browser/resources/options2/autofill_options.css @@ -0,0 +1,37 @@ +.autofill-list-item { + -webkit-box-flex: 1; + -webkit-padding-start: 8px; + overflow: hidden; + text-overflow: ellipsis; +} + +.autofill-list-item + img { + -webkit-padding-end: 20px; + vertical-align: middle; +} + +#autofill-options > div:last-child { + margin-top: 15px; +} + +#autofill-options > div.settings-list > div:last-child { + border-top: 1px solid #d9d9d9; + padding: 5px 10px; +} + +#autofill-add-address, +#autofill-add-creditcard { + margin: 2px 0; +} + +.autofill-list-item + button, +.autofill-list-item + img + button { + background: #8aaaed !important; /* Gets overwritten by raw-button:hover */ + color: #fff; + margin-top: 0; +} + +#address-list > div:not(:hover) * button, +#creditcard-list > div:not(:hover) * button { + display: none; +} diff --git a/chrome/browser/resources/options2/autofill_options.html b/chrome/browser/resources/options2/autofill_options.html new file mode 100644 index 0000000..bfbd175 --- /dev/null +++ b/chrome/browser/resources/options2/autofill_options.html @@ -0,0 +1,38 @@ +<div id="autofill-options" class="page" hidden> + <h1 i18n-content="autofillOptionsPage"></h1> +<if expr="os == 'darwin'"> + <div class="checkbox"> + <label> + <input pref="autofill.auxiliary_profiles_enabled" type="checkbox" + metric="Options_AutofillAuxiliaryProfiles"> + <span i18n-content="auxiliaryProfilesEnabled"></span> + </label> + </div> +</if> + <h3 i18n-content="autofillAddresses"></h3> + <div class="settings-list"> + <list id="address-list"></list> + <div> + <button id="autofill-add-address" i18n-content="autofillAddAddress"> + </button> + </div> + </div> + <h3 i18n-content="autofillCreditCards"></h3> + <div class="settings-list"> + <list id="creditcard-list"></list> + <div> + <button id="autofill-add-creditcard" + i18n-content="autofillAddCreditCard"></button> + </div> + </div> + <div> +<if expr="pp_ifdef('chromeos')"> + <a href="https://www.google.com/support/chromeos/bin/answer.py?answer=142893" + target="_blank" i18n-content="helpButton"></a> +</if> +<if expr="not pp_ifdef('chromeos')"> + <a href="https://www.google.com/support/chrome/bin/answer.py?answer=142893" + target="_blank" i18n-content="helpButton"></a> +</if> + </div> +</div> diff --git a/chrome/browser/resources/options2/autofill_options.js b/chrome/browser/resources/options2/autofill_options.js new file mode 100644 index 0000000..0b3a305 --- /dev/null +++ b/chrome/browser/resources/options2/autofill_options.js @@ -0,0 +1,230 @@ +// Copyright (c) 2011 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() { + const OptionsPage = options.OptionsPage; + const ArrayDataModel = cr.ui.ArrayDataModel; + + ///////////////////////////////////////////////////////////////////////////// + // AutofillOptions class: + + /** + * Encapsulated handling of Autofill options page. + * @constructor + */ + function AutofillOptions() { + OptionsPage.call(this, + 'autofill', + templateData.autofillOptionsPageTabTitle, + 'autofill-options'); + } + + cr.addSingletonGetter(AutofillOptions); + + AutofillOptions.prototype = { + __proto__: OptionsPage.prototype, + + /** + * The address list. + * @type {DeletableItemList} + * @private + */ + addressList_: null, + + /** + * The credit card list. + * @type {DeletableItemList} + * @private + */ + creditCardList_: null, + + initializePage: function() { + OptionsPage.prototype.initializePage.call(this); + + this.createAddressList_(); + this.createCreditCardList_(); + + var self = this; + $('autofill-add-address').onclick = function(event) { + self.showAddAddressOverlay_(); + }; + $('autofill-add-creditcard').onclick = function(event) { + self.showAddCreditCardOverlay_(); + }; + + // TODO(jhawkins): What happens when Autofill is disabled whilst on the + // Autofill options page? + }, + + /** + * Creates, decorates and initializes the address list. + * @private + */ + createAddressList_: function() { + this.addressList_ = $('address-list'); + options.autofillOptions.AutofillAddressList.decorate(this.addressList_); + this.addressList_.autoExpands = true; + }, + + /** + * Creates, decorates and initializes the credit card list. + * @private + */ + createCreditCardList_: function() { + this.creditCardList_ = $('creditcard-list'); + options.autofillOptions.AutofillCreditCardList.decorate( + this.creditCardList_); + this.creditCardList_.autoExpands = true; + }, + + /** + * Shows the 'Add address' overlay, specifically by loading the + * 'Edit address' overlay, emptying the input fields and modifying the + * overlay title. + * @private + */ + showAddAddressOverlay_: function() { + var title = localStrings.getString('addAddressTitle'); + AutofillEditAddressOverlay.setTitle(title); + AutofillEditAddressOverlay.clearInputFields(); + OptionsPage.navigateToPage('autofillEditAddress'); + }, + + /** + * Shows the 'Add credit card' overlay, specifically by loading the + * 'Edit credit card' overlay, emptying the input fields and modifying the + * overlay title. + * @private + */ + showAddCreditCardOverlay_: function() { + var title = localStrings.getString('addCreditCardTitle'); + AutofillEditCreditCardOverlay.setTitle(title); + AutofillEditCreditCardOverlay.clearInputFields(); + OptionsPage.navigateToPage('autofillEditCreditCard'); + }, + + /** + * Updates the data model for the address list with the values from + * |entries|. + * @param {Array} entries The list of addresses. + */ + setAddressList_: function(entries) { + this.addressList_.dataModel = new ArrayDataModel(entries); + }, + + /** + * Updates the data model for the credit card list with the values from + * |entries|. + * @param {Array} entries The list of credit cards. + */ + setCreditCardList_: function(entries) { + this.creditCardList_.dataModel = new ArrayDataModel(entries); + }, + + /** + * Removes the Autofill address represented by |guid|. + * @param {String} guid The GUID of the address to remove. + * @private + */ + removeAddress_: function(guid) { + chrome.send('removeAddress', [guid]); + }, + + /** + * Removes the Autofill credit card represented by |guid|. + * @param {String} guid The GUID of the credit card to remove. + * @private + */ + removeCreditCard_: function(guid) { + chrome.send('removeCreditCard', [guid]); + }, + + /** + * Requests profile data for the address represented by |guid| from the + * PersonalDataManager. Once the data is loaded, the AutofillOptionsHandler + * calls showEditAddressOverlay(). + * @param {String} guid The GUID of the address to edit. + * @private + */ + loadAddressEditor_: function(guid) { + chrome.send('loadAddressEditor', [guid]); + }, + + /** + * Requests profile data for the credit card represented by |guid| from the + * PersonalDataManager. Once the data is loaded, the AutofillOptionsHandler + * calls showEditCreditCardOverlay(). + * @param {String} guid The GUID of the credit card to edit. + * @private + */ + loadCreditCardEditor_: function(guid) { + chrome.send('loadCreditCardEditor', [guid]); + }, + + /** + * Shows the 'Edit address' overlay, using the data in |address| to fill the + * input fields. |address| is a list with one item, an associative array + * that contains the address data. + * @private + */ + showEditAddressOverlay_: function(address) { + var title = localStrings.getString('editAddressTitle'); + AutofillEditAddressOverlay.setTitle(title); + AutofillEditAddressOverlay.loadAddress(address); + OptionsPage.navigateToPage('autofillEditAddress'); + }, + + /** + * Shows the 'Edit credit card' overlay, using the data in |credit_card| to + * fill the input fields. |address| is a list with one item, an associative + * array that contains the credit card data. + * @private + */ + showEditCreditCardOverlay_: function(creditCard) { + var title = localStrings.getString('editCreditCardTitle'); + AutofillEditCreditCardOverlay.setTitle(title); + AutofillEditCreditCardOverlay.loadCreditCard(creditCard); + OptionsPage.navigateToPage('autofillEditCreditCard'); + }, + }; + + AutofillOptions.setAddressList = function(entries) { + AutofillOptions.getInstance().setAddressList_(entries); + }; + + AutofillOptions.setCreditCardList = function(entries) { + AutofillOptions.getInstance().setCreditCardList_(entries); + }; + + AutofillOptions.removeAddress = function(guid) { + AutofillOptions.getInstance().removeAddress_(guid); + }; + + AutofillOptions.removeCreditCard = function(guid) { + AutofillOptions.getInstance().removeCreditCard_(guid); + }; + + AutofillOptions.loadAddressEditor = function(guid) { + AutofillOptions.getInstance().loadAddressEditor_(guid); + }; + + AutofillOptions.loadCreditCardEditor = function(guid) { + AutofillOptions.getInstance().loadCreditCardEditor_(guid); + }; + + AutofillOptions.editAddress = function(address) { + AutofillOptions.getInstance().showEditAddressOverlay_(address); + }; + + AutofillOptions.editCreditCard = function(creditCard) { + AutofillOptions.getInstance().showEditCreditCardOverlay_(creditCard); + }; + + // Export + return { + AutofillOptions: AutofillOptions + }; + +}); + diff --git a/chrome/browser/resources/options2/autofill_options_list.js b/chrome/browser/resources/options2/autofill_options_list.js new file mode 100644 index 0000000..c9ed67c --- /dev/null +++ b/chrome/browser/resources/options2/autofill_options_list.js @@ -0,0 +1,506 @@ +// Copyright (c) 2011 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.autofillOptions', function() { + const DeletableItem = options.DeletableItem; + const DeletableItemList = options.DeletableItemList; + const InlineEditableItem = options.InlineEditableItem; + const InlineEditableItemList = options.InlineEditableItemList; + + function AutofillEditProfileButton(guid, edit) { + var editButtonEl = document.createElement('button'); + editButtonEl.className = 'raw-button custom-appearance'; + editButtonEl.textContent = + templateData.autofillEditProfileButton; + editButtonEl.onclick = function(e) { edit(guid); }; + + // Don't select the row when clicking the button. + editButtonEl.onmousedown = function(e) { + e.stopPropagation(); + }; + + return editButtonEl; + } + + /** + * Creates a new address list item. + * @param {Array} entry An array of the form [guid, label]. + * @constructor + * @extends {options.DeletableItem} + */ + function AddressListItem(entry) { + var el = cr.doc.createElement('div'); + el.guid = entry[0]; + el.label = entry[1]; + el.__proto__ = AddressListItem.prototype; + el.decorate(); + + return el; + } + + AddressListItem.prototype = { + __proto__: DeletableItem.prototype, + + /** @inheritDoc */ + decorate: function() { + DeletableItem.prototype.decorate.call(this); + + // The stored label. + var label = this.ownerDocument.createElement('div'); + label.className = 'autofill-list-item'; + label.textContent = this.label; + this.contentElement.appendChild(label); + + // The 'Edit' button. + var editButtonEl = new AutofillEditProfileButton( + this.guid, + AutofillOptions.loadAddressEditor); + this.contentElement.appendChild(editButtonEl); + }, + }; + + /** + * Creates a new credit card list item. + * @param {Array} entry An array of the form [guid, label, icon]. + * @constructor + * @extends {options.DeletableItem} + */ + function CreditCardListItem(entry) { + var el = cr.doc.createElement('div'); + el.guid = entry[0]; + el.label = entry[1]; + el.icon = entry[2]; + el.description = entry[3]; + el.__proto__ = CreditCardListItem.prototype; + el.decorate(); + + return el; + } + + CreditCardListItem.prototype = { + __proto__: DeletableItem.prototype, + + /** @inheritDoc */ + decorate: function() { + DeletableItem.prototype.decorate.call(this); + + // The stored label. + var label = this.ownerDocument.createElement('div'); + label.className = 'autofill-list-item'; + label.textContent = this.label; + this.contentElement.appendChild(label); + + // The credit card icon. + var icon = this.ownerDocument.createElement('image'); + icon.src = this.icon; + icon.alt = this.description; + this.contentElement.appendChild(icon); + + // The 'Edit' button. + var editButtonEl = new AutofillEditProfileButton( + this.guid, + AutofillOptions.loadCreditCardEditor); + this.contentElement.appendChild(editButtonEl); + }, + }; + + /** + * Creates a new value list item. + * @param {AutofillValuesList} list The parent list of this item. + * @param {String} entry A string value. + * @constructor + * @extends {options.InlineEditableItem} + */ + function ValuesListItem(list, entry) { + var el = cr.doc.createElement('div'); + el.list = list; + el.value = entry ? entry : ''; + el.__proto__ = ValuesListItem.prototype; + el.decorate(); + + return el; + } + + ValuesListItem.prototype = { + __proto__: InlineEditableItem.prototype, + + /** @inheritDoc */ + decorate: function() { + InlineEditableItem.prototype.decorate.call(this); + + // Note: This must be set prior to calling |createEditableTextCell|. + this.isPlaceholder = !this.value; + + // The stored value. + var cell = this.createEditableTextCell(this.value); + this.contentElement.appendChild(cell); + this.input = cell.querySelector('input'); + + if (this.isPlaceholder) { + this.input.placeholder = this.list.getAttribute('placeholder'); + this.deletable = false; + } + + this.addEventListener('commitedit', this.onEditCommitted_); + }, + + /** + * @return This item's value. + * @protected + */ + value_: function() { + return this.input.value; + }, + + /** + * @param {Object} value The value to test. + * @return true if the given value is non-empty. + * @protected + */ + valueIsNonEmpty_: function(value) { + return !!value; + }, + + /** + * @return true if value1 is logically equal to value2. + */ + valuesAreEqual_: function(value1, value2) { + return value1 === value2; + }, + + /** + * Clears the item's value. + * @protected + */ + clearValue_: function() { + this.input.value = ''; + }, + + /** + * Called when committing an edit. + * If this is an "Add ..." item, committing a non-empty value adds that + * value to the end of the values list, but also leaves this "Add ..." item + * in place. + * @param {Event} e The end event. + * @private + */ + onEditCommitted_: function(e) { + var value = this.value_(); + var i = this.list.items.indexOf(this); + if (i < this.list.dataModel.length && + this.valuesAreEqual_(value, this.list.dataModel.item(i))) { + return; + } + + var entries = this.list.dataModel.slice(); + if (this.valueIsNonEmpty_(value) && + !entries.some(this.valuesAreEqual_.bind(this, value))) { + // Update with new value. + if (this.isPlaceholder) { + // It is important that updateIndex is done before validateAndSave. + // Otherwise we can not be sure about AddRow index. + this.list.dataModel.updateIndex(i); + this.list.validateAndSave(i, 0, value); + } else { + this.list.validateAndSave(i, 1, value); + } + } else { + // Reject empty values and duplicates. + if (!this.isPlaceholder) + this.list.dataModel.splice(i, 1); + else + this.clearValue_(); + } + }, + }; + + /** + * Creates a new name value list item. + * @param {AutofillNameValuesList} list The parent list of this item. + * @param {array} entry An array of [first, middle, last] names. + * @constructor + * @extends {options.ValuesListItem} + */ + function NameListItem(list, entry) { + var el = cr.doc.createElement('div'); + el.list = list; + el.first = entry ? entry[0] : ''; + el.middle = entry ? entry[1] : ''; + el.last = entry ? entry[2] : ''; + el.__proto__ = NameListItem.prototype; + el.decorate(); + + return el; + } + + NameListItem.prototype = { + __proto__: ValuesListItem.prototype, + + /** @inheritDoc */ + decorate: function() { + InlineEditableItem.prototype.decorate.call(this); + + // Note: This must be set prior to calling |createEditableTextCell|. + this.isPlaceholder = !this.first && !this.middle && !this.last; + + // The stored value. + // For the simulated static "input element" to display correctly, the + // value must not be empty. We use a space to force the UI to render + // correctly when the value is logically empty. + var cell = this.createEditableTextCell(this.first); + this.contentElement.appendChild(cell); + this.firstNameInput = cell.querySelector('input'); + + cell = this.createEditableTextCell(this.middle); + this.contentElement.appendChild(cell); + this.middleNameInput = cell.querySelector('input'); + + cell = this.createEditableTextCell(this.last); + this.contentElement.appendChild(cell); + this.lastNameInput = cell.querySelector('input'); + + if (this.isPlaceholder) { + this.firstNameInput.placeholder = + templateData.autofillAddFirstNamePlaceholder; + this.middleNameInput.placeholder = + templateData.autofillAddMiddleNamePlaceholder; + this.lastNameInput.placeholder = + templateData.autofillAddLastNamePlaceholder; + this.deletable = false; + } + + this.addEventListener('commitedit', this.onEditCommitted_); + }, + + /** @inheritDoc */ + value_: function() { + return [ this.firstNameInput.value, + this.middleNameInput.value, + this.lastNameInput.value ]; + }, + + /** @inheritDoc */ + valueIsNonEmpty_: function(value) { + return value[0] || value[1] || value[2]; + }, + + /** @inheritDoc */ + valuesAreEqual_: function(value1, value2) { + // First, check for null values. + if (!value1 || !value2) + return value1 == value2; + + return value1[0] === value2[0] && + value1[1] === value2[1] && + value1[2] === value2[2]; + }, + + /** @inheritDoc */ + clearValue_: function() { + this.firstNameInput.value = ''; + this.middleNameInput.value = ''; + this.lastNameInput.value = ''; + }, + }; + + /** + * Base class for shared implementation between address and credit card lists. + * @constructor + * @extends {options.DeletableItemList} + */ + var AutofillProfileList = cr.ui.define('list'); + + AutofillProfileList.prototype = { + __proto__: DeletableItemList.prototype, + + decorate: function() { + DeletableItemList.prototype.decorate.call(this); + + this.addEventListener('blur', this.onBlur_); + }, + + /** + * When the list loses focus, unselect all items in the list. + * @private + */ + onBlur_: function() { + this.selectionModel.unselectAll(); + }, + }; + + /** + * Create a new address list. + * @constructor + * @extends {options.AutofillProfileList} + */ + var AutofillAddressList = cr.ui.define('list'); + + AutofillAddressList.prototype = { + __proto__: AutofillProfileList.prototype, + + decorate: function() { + AutofillProfileList.prototype.decorate.call(this); + }, + + /** @inheritDoc */ + activateItemAtIndex: function(index) { + AutofillOptions.loadAddressEditor(this.dataModel.item(index)[0]); + }, + + /** @inheritDoc */ + createItem: function(entry) { + return new AddressListItem(entry); + }, + + /** @inheritDoc */ + deleteItemAtIndex: function(index) { + AutofillOptions.removeAddress(this.dataModel.item(index)[0]); + }, + }; + + /** + * Create a new credit card list. + * @constructor + * @extends {options.DeletableItemList} + */ + var AutofillCreditCardList = cr.ui.define('list'); + + AutofillCreditCardList.prototype = { + __proto__: AutofillProfileList.prototype, + + decorate: function() { + AutofillProfileList.prototype.decorate.call(this); + }, + + /** @inheritDoc */ + activateItemAtIndex: function(index) { + AutofillOptions.loadCreditCardEditor(this.dataModel.item(index)[0]); + }, + + /** @inheritDoc */ + createItem: function(entry) { + return new CreditCardListItem(entry); + }, + + /** @inheritDoc */ + deleteItemAtIndex: function(index) { + AutofillOptions.removeCreditCard(this.dataModel.item(index)[0]); + }, + }; + + /** + * Create a new value list. + * @constructor + * @extends {options.InlineEditableItemList} + */ + var AutofillValuesList = cr.ui.define('list'); + + AutofillValuesList.prototype = { + __proto__: InlineEditableItemList.prototype, + + /** @inheritDoc */ + createItem: function(entry) { + return new ValuesListItem(this, entry); + }, + + /** @inheritDoc */ + deleteItemAtIndex: function(index) { + this.dataModel.splice(index, 1); + }, + + /** @inheritDoc */ + shouldFocusPlaceholder: function() { + return false; + }, + + /** + * Called when the list hierarchy as a whole loses or gains focus. + * If the list was focused in response to a mouse click, call into the + * superclass's implementation. If the list was focused in response to a + * keyboard navigation, focus the first item. + * If the list loses focus, unselect all the elements. + * @param {Event} e The change event. + * @private + */ + handleListFocusChange_: function(e) { + // We check to see whether there is a selected item as a proxy for + // distinguishing between mouse- and keyboard-originated focus events. + var selectedItem = this.selectedItem; + if (selectedItem) + InlineEditableItemList.prototype.handleListFocusChange_.call(this, e); + + if (!e.newValue) { + // When the list loses focus, unselect all the elements. + this.selectionModel.unselectAll(); + } else { + // When the list gains focus, select the first item if nothing else is + // selected. + var firstItem = this.getListItemByIndex(0); + if (!selectedItem && firstItem && e.newValue) + firstItem.handleFocus_(); + } + }, + + /** + * Called when a new list item should be validated; subclasses are + * responsible for implementing if validation is required. + * @param {number} index The index of the item that was inserted or changed. + * @param {number} remove The number items to remove. + * @param {string} value The value of the item to insert. + */ + validateAndSave: function(index, remove, value) { + this.dataModel.splice(index, remove, value); + }, + }; + + /** + * Create a new value list for phone number validation. + * @constructor + * @extends {options.AutofillValuesList} + */ + var AutofillNameValuesList = cr.ui.define('list'); + + AutofillNameValuesList.prototype = { + __proto__: AutofillValuesList.prototype, + + /** @inheritDoc */ + createItem: function(entry) { + return new NameListItem(this, entry); + }, + }; + + /** + * Create a new value list for phone number validation. + * @constructor + * @extends {options.AutofillValuesList} + */ + var AutofillPhoneValuesList = cr.ui.define('list'); + + AutofillPhoneValuesList.prototype = { + __proto__: AutofillValuesList.prototype, + + /** @inheritDoc */ + validateAndSave: function(index, remove, value) { + var numbers = this.dataModel.slice(0, this.dataModel.length - 1); + numbers.splice(index, remove, value); + var info = new Array(); + info[0] = index; + info[1] = numbers; + info[2] = $('country').value; + chrome.send('validatePhoneNumbers', info); + }, + }; + + return { + AddressListItem: AddressListItem, + CreditCardListItem: CreditCardListItem, + ValuesListItem: ValuesListItem, + NameListItem: NameListItem, + AutofillAddressList: AutofillAddressList, + AutofillCreditCardList: AutofillCreditCardList, + AutofillValuesList: AutofillValuesList, + AutofillNameValuesList: AutofillNameValuesList, + AutofillPhoneValuesList: AutofillPhoneValuesList, + }; +}); diff --git a/chrome/browser/resources/options2/autofill_overlay.css b/chrome/browser/resources/options2/autofill_overlay.css new file mode 100644 index 0000000..1bc2db4 --- /dev/null +++ b/chrome/browser/resources/options2/autofill_overlay.css @@ -0,0 +1,95 @@ +#autofill-edit-address-overlay { + min-width: 510px; +} + +#autofill-edit-credit-card-overlay { + min-width: 500px; +} + +div.table { + display: table; +} + +div.cell { + display: table-cell; +} + +div.row { + display: table-row; +} + +div.input { + padding: 2px; +} + +/* Size to match large name fields. */ +#company-name, #addr-line-1, #addr-line-2 { + width: 206px; +} + +#country { + max-width: 450px; +} + +#autofill-edit-address-overlay list { + /* Min height is a multiple of the list item height (32) */ + min-height: 32px; + width: 176px; +} + +#autofill-edit-address-overlay list div.static-text { + -webkit-box-flex: 1; + -webkit-border-radius: 2px; + -webkit-padding-start: 4px; + -webkit-padding-end: 4px; + border: 1px solid darkGray; + /* Set the line-height and min-height to match the height of an input element, + * so that even empty cells renderer with the correct height. + */ + line-height: 1.75em; + min-height: 1.75em; + width: 141px; +} + +#autofill-edit-address-overlay list input { + width: 151px; +} + +#autofill-name-labels { + -webkit-box-orient: horizontal; + /* Set the margin to compensate for each list item's close button and + * padding. + */ + -webkit-margin-end: 25px; + display: -webkit-box; +} + +#autofill-name-labels label { + -webkit-box-flex: 1; + display: block; + /* Set the minimum width to the size of an input element, so that all boxes + * have an equal amount of flex space to work with. + */ + min-width: 141px; +} + +#autofill-edit-address-overlay list#full-name-list div.static-text { + width: 131px; +} + +#autofill-edit-address-overlay list#full-name-list input { + width: 141px; +} + +#autofill-edit-address-overlay list#full-name-list { + width: 100%; +} + +#full-name-list div[role="listitem"] > div { + -webkit-box-orient: horizontal; + display: -webkit-box; +} + +#full-name-list div[role="listitem"] > div > div { + -webkit-box-flex: 1; +} diff --git a/chrome/browser/resources/options2/browser_options.html b/chrome/browser/resources/options2/browser_options.html new file mode 100644 index 0000000..2af7b17 --- /dev/null +++ b/chrome/browser/resources/options2/browser_options.html @@ -0,0 +1,128 @@ +<div id="browserPage" class="page" hidden> + <h1 i18n-content="browserPage"></h1> + <div class="displaytable"> + <section id="startupSection"> + <h3 i18n-content="startupGroupName"></h3> + <div> + <div class="radio"> + <label> + <input type="radio" name="startup" value="0" + pref="session.restore_on_startup" + metric="Options_Startup_Homepage"> + <span i18n-content="startupShowDefaultAndNewTab"></span> + </label> + </div> + <div class="radio"> + <label> + <input type="radio" name="startup" value="1" + pref="session.restore_on_startup" + metric="Options_Startup_LastSession"> + <span i18n-content="startupShowLastSession"></span> + </label> + </div> + <div class="radio"> + <label> + <input id="startupShowPagesButton" type="radio" name="startup" + pref="session.restore_on_startup" + value="4" metric="Options_Startup_Custom"> + <span id="startupShowPagesLabel" + i18n-content="startupShowPages"></span> + </label> + </div> + <div class="suboption"> + <div id="startupPageManagement" class="settings-list"> + <list id="startupPagesList"></list> + <div> + <button id="startupUseCurrentButton" + i18n-content="startupUseCurrent"></button> + </div> + </div> + <div id="startupPagesListDropmarker"></div> + </div> + </div> + </section> + <section> + <h3 i18n-content="homepageGroupName"></h3> + <div> + <div class="radio"> + <label> + <input id="homepageUseNTPButton" type="radio" name="homepage" + value="true" metric="Options_Homepage_IsNewTabPage" + pref="homepage_is_newtabpage"> + <span i18n-content="homepageUseNewTab"></span> + </label> + </div> + <div id="customHomePageGroup"> + <div class="radio"> + <label> + <input id="homepageUseURLButton" type="radio" name="homepage" + value="false" metric="Options_Homepage_IsNewTabPage" + pref="homepage_is_newtabpage"> + <span i18n-content="homepageUseURL"></span> + </label> + </div> + <div> + <input id="homepageURL" class="weakrtl favicon-cell" type="url" + data-type="url" pref="homepage"> + </div> + </div> + </div> + </section> + <section> + <h3 i18n-content="toolbarGroupName"></h3> + <div> + <div class="checkbox"> + <label> + <input id="toolbarShowHomeButton" pref="browser.show_home_button" + metric="Options_Homepage_HomeButton" type="checkbox"> + <span i18n-content="toolbarShowHomeButton"></span> + </label> + </div> + <div class="checkbox"> + <label> + <input id="toolbarShowBookmarksBar" + pref="bookmark_bar.show_on_all_tabs" + metric="Options_ShowBookmarksBar" type="checkbox"> + <span i18n-content="toolbarShowBookmarksBar"></span> + </label> + </div> + </div> + </section> + <section> + <h3 i18n-content="defaultSearchGroupName"></h3> + <div id="defaultSearchEngineGroup"> + <div> + <select id="defaultSearchEngine" class="weakrtl"></select> + <button id="defaultSearchManageEnginesButton" + i18n-content="defaultSearchManageEngines"></button> + </div> + <div id="instantOption" class="checkbox"> + <label id="instantLabel"> + <!-- TODO(estade): metric? --> + <input id="instantFieldTrialCheckbox" type="checkbox" + checked="checked" hidden> + <input id="instantEnabledCheckbox" type="checkbox" + pref="instant.enabled"> + <span i18n-content="instantName"></span> + </label> + </div> + <div class="suboption informational-text"> + <span i18n-content="instantWarningText"></span> + <a target="_blank" i18n-values="href:instantLearnMoreLink" + i18n-content="learnMore"></a> + </div> + </div> + </section> +<if expr="not pp_ifdef('chromeos')"> + <section> + <h3 i18n-content="defaultBrowserGroupName"></h3> + <div> + <button id="defaultBrowserUseAsDefaultButton" + i18n-content="defaultBrowserUseAsDefault"></button> + <div id="defaultBrowserState" + i18n-content="defaultBrowserUnknown"></div> + </div> + </section> +</if> + </div> +</div> diff --git a/chrome/browser/resources/options2/browser_options.js b/chrome/browser/resources/options2/browser_options.js new file mode 100644 index 0000000..ba59d3d --- /dev/null +++ b/chrome/browser/resources/options2/browser_options.js @@ -0,0 +1,366 @@ +// Copyright (c) 2011 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() { + const OptionsPage = options.OptionsPage; + const ArrayDataModel = cr.ui.ArrayDataModel; + + // + // BrowserOptions class + // Encapsulated handling of browser options page. + // + function BrowserOptions() { + OptionsPage.call(this, 'browser', + templateData.browserPageTabTitle, + 'browserPage'); + } + + cr.addSingletonGetter(BrowserOptions); + + BrowserOptions.prototype = { + // Inherit BrowserOptions from OptionsPage. + __proto__: options.OptionsPage.prototype, + + startup_pages_pref_: { + 'name': 'session.urls_to_restore_on_startup', + 'disabled': false + }, + + /** + * At autocomplete list that can be attached to a text field during editing. + * @type {HTMLElement} + * @private + */ + autocompleteList_: null, + + // The cached value of the instant.confirm_dialog_shown preference. + instantConfirmDialogShown_: false, + + /** + * Initialize BrowserOptions page. + */ + initializePage: function() { + // Call base class implementation to start preference initialization. + OptionsPage.prototype.initializePage.call(this); + + // Wire up controls. + $('startupUseCurrentButton').onclick = function(event) { + chrome.send('setStartupPagesToCurrentPages'); + }; + $('defaultSearchManageEnginesButton').onclick = function(event) { + OptionsPage.navigateToPage('searchEngines'); + chrome.send('coreOptionsUserMetricsAction', + ['Options_ManageSearchEngines']); + }; + $('defaultSearchEngine').onchange = this.setDefaultSearchEngine_; + + var self = this; + $('instantEnabledCheckbox').customChangeHandler = function(event) { + if (this.checked) { + if (self.instantConfirmDialogShown_) + chrome.send('enableInstant'); + else + OptionsPage.navigateToPage('instantConfirm'); + } else { + chrome.send('disableInstant'); + } + return true; + }; + + $('instantFieldTrialCheckbox').addEventListener('change', + function(event) { + this.checked = true; + chrome.send('disableInstant'); + }); + + Preferences.getInstance().addEventListener('instant.confirm_dialog_shown', + this.onInstantConfirmDialogShownChanged_.bind(this)); + + Preferences.getInstance().addEventListener('instant.enabled', + this.onInstantEnabledChanged_.bind(this)); + + Preferences.getInstance().addEventListener( + $('homepageUseNTPButton').pref, + this.onHomepageUseNTPChanged_); + var homepageField = $('homepageURL'); + homepageField.addEventListener('focus', function(event) { + self.autocompleteList_.attachToInput(homepageField); + }); + homepageField.addEventListener('blur', function(event) { + self.autocompleteList_.detach(); + }); + homepageField.addEventListener('keydown', function(event) { + // Remove focus when the user hits enter since people expect feedback + // indicating that they are done editing. + if (event.keyIdentifier == 'Enter') + homepageField.blur(); + }); + + // Text fields may change widths when the window changes size, so make + // sure the suggestion list stays in sync. + window.addEventListener('resize', function() { + self.autocompleteList_.syncWidthToInput(); + }); + + // Ensure that changes are committed when closing the page. + window.addEventListener('unload', function() { + if (document.activeElement == homepageField) + homepageField.blur(); + }); + + if (!cr.isChromeOS) { + $('defaultBrowserUseAsDefaultButton').onclick = function(event) { + chrome.send('becomeDefaultBrowser'); + }; + } + + var startupPagesList = $('startupPagesList'); + options.browser_options.StartupPageList.decorate(startupPagesList); + startupPagesList.autoExpands = true; + + // Check if we are in the guest mode. + if (cr.commandLine && cr.commandLine.options['--bwsi']) { + // Hide the startup section. + $('startupSection').hidden = true; + } else { + // Initialize control enabled states. + Preferences.getInstance().addEventListener('session.restore_on_startup', + this.updateCustomStartupPageControlStates_.bind(this)); + Preferences.getInstance().addEventListener( + this.startup_pages_pref_.name, + this.handleStartupPageListChange_.bind(this)); + + this.updateCustomStartupPageControlStates_(); + } + + var suggestionList = new options.AutocompleteList(); + suggestionList.autoExpands = true; + suggestionList.suggestionUpdateRequestCallback = + this.requestAutocompleteSuggestions_.bind(this); + $('main-content').appendChild(suggestionList); + this.autocompleteList_ = suggestionList; + startupPagesList.autocompleteList = suggestionList; + }, + + /** + * Called when the value of the instant.confirm_dialog_shown preference + * changes. Cache this value. + * @param {Event} event Change event. + * @private + */ + onInstantConfirmDialogShownChanged_: function(event) { + this.instantConfirmDialogShown_ = event.value['value']; + }, + + /** + * Called when the value of the instant.enabled preference changes. Request + * the state of the Instant field trial experiment. + * @param {Event} event Change event. + * @private + */ + onInstantEnabledChanged_: function(event) { + chrome.send('getInstantFieldTrialStatus'); + }, + + /** + * Called to set the Instant field trial status. + * @param {boolean} enabled If true, the experiment is enabled. + * @private + */ + setInstantFieldTrialStatus_: function(enabled) { + $('instantEnabledCheckbox').hidden = enabled; + $('instantFieldTrialCheckbox').hidden = !enabled; + $('instantLabel').htmlFor = enabled ? 'instantFieldTrialCheckbox' + : 'instantEnabledCheckbox'; + }, + + /** + * Called when the value of the homepage-use-NTP pref changes. + * Updates the disabled state of the homepage text field. + * Notice that the text field can be disabled for other reasons too + * (it can be managed by policy, for instance). + * @param {Event} event Change event. + * @private + */ + onHomepageUseNTPChanged_: function(event) { + var homepageField = $('homepageURL'); + var homepageUseURLButton = $('homepageUseURLButton'); + homepageField.setDisabled('radioNotSelected', + !homepageUseURLButton.checked); + }, + + /** + * 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) { + var label = $('defaultBrowserState'); + label.textContent = statusString; + + $('defaultBrowserUseAsDefaultButton').disabled = !canBeDefault || + isDefault; + }, + + /** + * Clears the search engine popup. + * @private + */ + clearSearchEngines_: function() { + $('defaultSearchEngine').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. + */ + updateSearchEngines_: function(engines, defaultValue, defaultManaged) { + this.clearSearchEngines_(); + engineSelect = $('defaultSearchEngine'); + engineSelect.disabled = defaultManaged; + 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; + }, + + /** + * Returns true if the custom startup page control block should + * be enabled. + * @returns {boolean} Whether the startup page controls should be + * enabled. + */ + shouldEnableCustomStartupPageControls: function(pages) { + return $('startupShowPagesButton').checked && + !this.startup_pages_pref_.disabled; + }, + + /** + * Updates the startup pages list with the given entries. + * @param {Array} pages List of startup pages. + * @private + */ + updateStartupPages_: function(pages) { + var model = new ArrayDataModel(pages); + // Add a "new page" row. + model.push({ + 'modelIndex': '-1' + }); + $('startupPagesList').dataModel = model; + }, + + /** + * Sets the enabled state of the custom startup page list controls + * based on the current startup radio button selection. + * @private + */ + updateCustomStartupPageControlStates_: function() { + var disable = !this.shouldEnableCustomStartupPageControls(); + var startupPagesList = $('startupPagesList'); + startupPagesList.disabled = disable; + startupPagesList.setAttribute('tabindex', disable ? -1 : 0); + // Explicitly set disabled state for input text elements. + var inputs = startupPagesList.querySelectorAll("input[type='text']"); + for (var i = 0; i < inputs.length; i++) + inputs[i].disabled = disable; + $('startupUseCurrentButton').disabled = disable; + }, + + /** + * Handle change events of the preference + * 'session.urls_to_restore_on_startup'. + * @param {event} preference changed event. + * @private + */ + handleStartupPageListChange_: function(event) { + this.startup_pages_pref_.disabled = event.value['disabled']; + this.updateCustomStartupPageControlStates_(); + }, + + /** + * Set the default search engine based on the popup selection. + */ + setDefaultSearchEngine_: function() { + var engineSelect = $('defaultSearchEngine'); + var selectedIndex = engineSelect.selectedIndex; + if (selectedIndex >= 0) { + var selection = engineSelect.options[selectedIndex]; + chrome.send('setDefaultSearchEngine', [String(selection.value)]); + } + }, + + /** + * Sends an asynchronous request for new autocompletion suggestions for the + * the given query. When new suggestions are available, the C++ handler will + * call updateAutocompleteSuggestions_. + * @param {string} query List of autocomplete suggestions. + * @private + */ + requestAutocompleteSuggestions_: function(query) { + chrome.send('requestAutocompleteSuggestions', [query]); + }, + + /** + * Updates the autocomplete suggestion list with the given entries. + * @param {Array} pages List of autocomplete suggestions. + * @private + */ + updateAutocompleteSuggestions_: function(suggestions) { + var list = this.autocompleteList_; + // If the trigger for this update was a value being selected from the + // current list, do nothing. + if (list.targetInput && list.selectedItem && + list.selectedItem['url'] == list.targetInput.value) + return; + list.suggestions = suggestions; + }, + }; + + BrowserOptions.updateDefaultBrowserState = function(statusString, isDefault, + canBeDefault) { + if (!cr.isChromeOS) { + BrowserOptions.getInstance().updateDefaultBrowserState_(statusString, + isDefault, + canBeDefault); + } + }; + + BrowserOptions.updateSearchEngines = function(engines, defaultValue, + defaultManaged) { + BrowserOptions.getInstance().updateSearchEngines_(engines, defaultValue, + defaultManaged); + }; + + BrowserOptions.updateStartupPages = function(pages) { + BrowserOptions.getInstance().updateStartupPages_(pages); + }; + + BrowserOptions.updateAutocompleteSuggestions = function(suggestions) { + BrowserOptions.getInstance().updateAutocompleteSuggestions_(suggestions); + }; + + BrowserOptions.setInstantFieldTrialStatus = function(enabled) { + BrowserOptions.getInstance().setInstantFieldTrialStatus_(enabled); + }; + + // Export + return { + BrowserOptions: BrowserOptions + }; + +}); diff --git a/chrome/browser/resources/options2/browser_options_page.css b/chrome/browser/resources/options2/browser_options_page.css new file mode 100644 index 0000000..2be1710 --- /dev/null +++ b/chrome/browser/resources/options2/browser_options_page.css @@ -0,0 +1,95 @@ +#startupPageManagement.settings-list > :last-child { + border-top: 1px solid #d9d9d9; + padding: 5px 10px; +} + +#startupPagesList { + min-height: 64px; +} + +#startupPagesList .title { + width: 40%; +} + +#startupPagesList .url { + -webkit-box-flex: 1; + color: #666; +} + +#startupPagesList > * { + max-width: 700px; +} + +#startupPagesListDropmarker { + background-clip: padding-box; + background-color: hsl(214, 91%, 65%); + border-bottom-color: transparent; + border-radius: 0; + border-top-color: transparent; + border: 2px solid hsl(214, 91%, 65%); + box-sizing: border-box; + display: none; + height: 6px; + overflow: hidden; + pointer-events: none; + position: fixed; + z-index: 10; +} + +#customHomePageGroup { + display: -webkit-box; + -webkit-box-orient: horizontal; +} + +#customHomePageGroup > :last-child { + -webkit-margin-start: 1ex; + -webkit-box-flex: 1; + position: relative; +} + +#homepageURL { + box-sizing: border-box; + padding-top: 3px; + width: 100%; +} + +#defaultSearchEngineGroup { + display: -webkit-box; + -webkit-box-orient: vertical; +} + +#defaultSearchEngineGroup > div { + display: -webkit-box; + -webkit-box-orient: horizontal; +} + +#defaultSearchEngine { + display: block; + -webkit-box-flex: 1; + max-width: 200px; +} + +#defaultSearchManageEnginesButton { + margin-top: 0px; + -webkit-margin-start: 10px; +} + +#defaultBrowserState { + margin-top: 6px; +} + +#instantOption { + margin-bottom: 3px; + margin-top: 10px +} + +#instantConfirmText { + font-family: inherit; + white-space: pre-wrap; + width: 500px; +} + +#instantConfirmLearnMore { + position: absolute; + bottom: 18px; +} diff --git a/chrome/browser/resources/options2/browser_options_startup_page_list.js b/chrome/browser/resources/options2/browser_options_startup_page_list.js new file mode 100644 index 0000000..cd10729 --- /dev/null +++ b/chrome/browser/resources/options2/browser_options_startup_page_list.js @@ -0,0 +1,310 @@ +// Copyright (c) 2011 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.browser_options', function() { + const AutocompleteList = options.AutocompleteList; + const InlineEditableItem = options.InlineEditableItem; + const InlineEditableItemList = options.InlineEditableItemList; + + /** + * Creates a new startup page list item. + * @param {Object} pageInfo The page this item represents. + * @constructor + * @extends {cr.ui.ListItem} + */ + function StartupPageListItem(pageInfo) { + var el = cr.doc.createElement('div'); + el.pageInfo_ = pageInfo; + StartupPageListItem.decorate(el); + return el; + } + + /** + * Decorates an element as a startup page list item. + * @param {!HTMLElement} el The element to decorate. + */ + StartupPageListItem.decorate = function(el) { + el.__proto__ = StartupPageListItem.prototype; + el.decorate(); + }; + + StartupPageListItem.prototype = { + __proto__: InlineEditableItem.prototype, + + /** + * Input field for editing the page url. + * @type {HTMLElement} + * @private + */ + urlField_: null, + + /** @inheritDoc */ + decorate: function() { + InlineEditableItem.prototype.decorate.call(this); + + var pageInfo = this.pageInfo_; + + if (pageInfo['modelIndex'] == '-1') { + this.isPlaceholder = true; + pageInfo['title'] = localStrings.getString('startupAddLabel'); + pageInfo['url'] = ''; + } + + var titleEl = this.ownerDocument.createElement('div'); + titleEl.className = 'title'; + titleEl.classList.add('favicon-cell'); + titleEl.classList.add('weakrtl'); + titleEl.textContent = pageInfo['title']; + if (!this.isPlaceholder) { + titleEl.style.backgroundImage = url('chrome://favicon/' + + pageInfo['url']); + titleEl.title = pageInfo['tooltip']; + } + + this.contentElement.appendChild(titleEl); + + var urlEl = this.createEditableTextCell(pageInfo['url']); + urlEl.className = 'url'; + urlEl.classList.add('weakrtl'); + this.contentElement.appendChild(urlEl); + + var urlField = urlEl.querySelector('input') + urlField.required = true; + urlField.className = 'weakrtl'; + this.urlField_ = urlField; + + this.addEventListener('commitedit', this.onEditCommitted_); + + var self = this; + urlField.addEventListener('focus', function(event) { + self.parentNode.autocompleteList.attachToInput(urlField); + }); + urlField.addEventListener('blur', function(event) { + self.parentNode.autocompleteList.detach(); + }); + + if (!this.isPlaceholder) + this.draggable = true; + }, + + /** @inheritDoc */ + get currentInputIsValid() { + return this.urlField_.validity.valid; + }, + + /** @inheritDoc */ + get hasBeenEdited() { + return this.urlField_.value != this.pageInfo_['url']; + }, + + /** + * Called when committing an edit; updates the model. + * @param {Event} e The end event. + * @private + */ + onEditCommitted_: function(e) { + var url = this.urlField_.value; + if (this.isPlaceholder) + chrome.send('addStartupPage', [url]); + else + chrome.send('editStartupPage', [this.pageInfo_['modelIndex'], url]); + }, + }; + + var StartupPageList = cr.ui.define('list'); + + StartupPageList.prototype = { + __proto__: InlineEditableItemList.prototype, + + /** + * An autocomplete suggestion list for URL editing. + * @type {AutocompleteList} + */ + autocompleteList: null, + + /** + * The drop position information: "below" or "above". + */ + dropPos: null, + + /** @inheritDoc */ + decorate: function() { + InlineEditableItemList.prototype.decorate.call(this); + + // Listen to drag and drop events. + this.addEventListener('dragstart', this.handleDragStart_.bind(this)); + this.addEventListener('dragenter', this.handleDragEnter_.bind(this)); + this.addEventListener('dragover', this.handleDragOver_.bind(this)); + this.addEventListener('drop', this.handleDrop_.bind(this)); + this.addEventListener('dragleave', this.handleDragLeave_.bind(this)); + this.addEventListener('dragend', this.handleDragEnd_.bind(this)); + }, + + /** @inheritDoc */ + createItem: function(pageInfo) { + var item = new StartupPageListItem(pageInfo); + item.urlField_.disabled = this.disabled; + return item; + }, + + /** @inheritDoc */ + deleteItemAtIndex: function(index) { + chrome.send('removeStartupPages', [String(index)]); + }, + + /* + * Computes the target item of drop event. + * @param {Event} e The drop or dragover event. + * @private + */ + getTargetFromDropEvent_ : function(e) { + var target = e.target; + // e.target may be an inner element of the list item + while (target != null && !(target instanceof StartupPageListItem)) { + target = target.parentNode; + } + return target; + }, + + /* + * Handles the dragstart event. + * @param {Event} e The dragstart event. + * @private + */ + handleDragStart_: function(e) { + // Prevent dragging if the list is disabled. + if (this.disabled) { + e.preventDefault(); + return false; + } + + var target = e.target; + // StartupPageListItem should be the only draggable element type in the + // page but let's make sure. + if (target instanceof StartupPageListItem) { + this.draggedItem = target; + this.draggedItem.editable = false; + e.dataTransfer.effectAllowed = 'move'; + // We need to put some kind of data in the drag or it will be + // ignored. Use the URL in case the user drags to a text field or the + // desktop. + e.dataTransfer.setData('text/plain', target.urlField_.value); + } + }, + + /* + * Handles the dragenter event. + * @param {Event} e The dragenter event. + * @private + */ + handleDragEnter_: function(e) { + e.preventDefault(); + }, + + /* + * Handles the dragover event. + * @param {Event} e The dragover event. + * @private + */ + handleDragOver_: function(e) { + var dropTarget = this.getTargetFromDropEvent_(e); + // Determines whether the drop target is to accept the drop. + // The drop is only successful on another StartupPageListItem. + if (!(dropTarget instanceof StartupPageListItem) || + dropTarget == this.draggedItem || dropTarget.isPlaceholder) { + this.hideDropMarker_(); + return; + } + // Compute the drop postion. Should we move the dragged item to + // below or above the drop target? + var rect = dropTarget.getBoundingClientRect(); + var dy = e.clientY - rect.top; + var yRatio = dy / rect.height; + var dropPos = yRatio <= .5 ? 'above' : 'below'; + this.dropPos = dropPos; + this.showDropMarker_(dropTarget, dropPos); + e.preventDefault(); + }, + + /* + * Handles the drop event. + * @param {Event} e The drop event. + * @private + */ + handleDrop_: function(e) { + var dropTarget = this.getTargetFromDropEvent_(e); + this.hideDropMarker_(); + + // Insert the selection at the new position. + var newIndex = this.dataModel.indexOf(dropTarget.pageInfo_); + if (this.dropPos == 'below') + newIndex += 1; + + var selected = this.selectionModel.selectedIndexes; + var stringized_selected = []; + for (var j = 0; j < selected.length; j++) + stringized_selected.push(String(selected[j])); + + chrome.send('dragDropStartupPage', + [String(newIndex), stringized_selected] ); + }, + + /* + * Handles the dragleave event. + * @param {Event} e The dragleave event + * @private + */ + handleDragLeave_: function(e) { + this.hideDropMarker_(); + }, + + /** + * Handles the dragend event. + * @param {Event} e The dragend event + * @private + */ + handleDragEnd_: function(e) { + this.draggedItem.editable = true; + this.draggedItem.updateEditState(); + }, + + /* + * Shows and positions the marker to indicate the drop target. + * @param {HTMLElement} target The current target list item of drop + * @param {string} pos 'below' or 'above' + * @private + */ + showDropMarker_ : function(target, pos) { + window.clearTimeout(this.hideDropMarkerTimer_); + var marker = $('startupPagesListDropmarker'); + var rect = target.getBoundingClientRect(); + var markerHeight = 6; + if (pos == 'above') { + marker.style.top = (rect.top - markerHeight/2) + 'px'; + } else { + marker.style.top = (rect.bottom - markerHeight/2) + 'px'; + } + marker.style.width = rect.width + 'px'; + marker.style.left = rect.left + 'px'; + marker.style.display = 'block'; + }, + + /* + * Hides the drop marker. + * @private + */ + hideDropMarker_ : function() { + // Hide the marker in a timeout to reduce flickering as we move between + // valid drop targets. + window.clearTimeout(this.hideDropMarkerTimer_); + this.hideDropMarkerTimer_ = window.setTimeout(function() { + $('startupPagesListDropmarker').style.display = ''; + }, 100); + }, + }; + + return { + StartupPageList: StartupPageList + }; +}); diff --git a/chrome/browser/resources/options2/certificate_backup_overlay.html b/chrome/browser/resources/options2/certificate_backup_overlay.html new file mode 100644 index 0000000..b0bcea3 --- /dev/null +++ b/chrome/browser/resources/options2/certificate_backup_overlay.html @@ -0,0 +1,38 @@ +<div id="certificateBackupOverlay" class="page" hidden> + <h1 i18n-content="certificateExportPasswordDescription"></h1> + <div class="content-area"> + <table> + <tr> + <td> + <label for="certificateBackupPassword"> + <span i18n-content="certificatePasswordLabel"></span> + </label> + </td> + <td> + <input id="certificateBackupPassword" type="password"> + </td> + </tr> + <tr> + <td> + <label for="certificateBackupPassword2"> + <span i18n-content="certificateConfirmPasswordLabel"></span> + </label> + </td> + <td> + <input id="certificateBackupPassword2" type="password"> + </td> + </tr> + </table> + <p> + <span i18n-content="certificateExportPasswordHelp"></span> + </p> + </div> + <div class="action-area"> + <div class="button-strip"> + <button id="certificateBackupCancelButton" type="reset" + i18n-content="cancel"></button> + <button id="certificateBackupOkButton" type="submit" i18n-content="ok" + disabled></button> + </div> + </div> +</div> diff --git a/chrome/browser/resources/options2/certificate_backup_overlay.js b/chrome/browser/resources/options2/certificate_backup_overlay.js new file mode 100644 index 0000000..e700347 --- /dev/null +++ b/chrome/browser/resources/options2/certificate_backup_overlay.js @@ -0,0 +1,116 @@ +// Copyright (c) 2010 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() { + const OptionsPage = options.OptionsPage; + + /** + * CertificateBackupOverlay class + * Encapsulated handling of the 'enter backup password' overlay page. + * @class + */ + function CertificateBackupOverlay() { + OptionsPage.call(this, 'certificateBackupOverlay', + '', + 'certificateBackupOverlay'); + } + + cr.addSingletonGetter(CertificateBackupOverlay); + + CertificateBackupOverlay.prototype = { + __proto__: OptionsPage.prototype, + + /** + * Initializes the page. + */ + initializePage: function() { + OptionsPage.prototype.initializePage.call(this); + + var self = this; + $('certificateBackupCancelButton').onclick = function(event) { + self.cancelBackup_(); + } + $('certificateBackupOkButton').onclick = function(event) { + self.finishBackup_(); + } + $('certificateBackupPassword').oninput = + $('certificateBackupPassword2').oninput = function(event) { + self.comparePasswords_(); + } + + self.clearInputFields_(); + }, + + /** + * Clears any uncommitted input, and dismisses the overlay. + * @private + */ + dismissOverlay_: function() { + this.clearInputFields_(); + OptionsPage.closeOverlay(); + }, + + /** + * Attempt the Backup operation. + * The overlay will be left up with inputs disabled until the backend + * finishes and dismisses it. + * @private + */ + finishBackup_: function() { + chrome.send('exportPersonalCertificatePasswordSelected', + [$('certificateBackupPassword').value]); + $('certificateBackupCancelButton').disabled = true; + $('certificateBackupOkButton').disabled = true; + $('certificateBackupPassword').disabled = true; + $('certificateBackupPassword2').disabled = true; + }, + + /** + * Cancel the Backup operation. + * @private + */ + cancelBackup_: function() { + chrome.send('cancelImportExportCertificate'); + this.dismissOverlay_(); + }, + + /** + * Compares the password fields and sets the button state appropriately. + * @private + */ + comparePasswords_: function() { + var password1 = $('certificateBackupPassword').value; + var password2 = $('certificateBackupPassword2').value; + $('certificateBackupOkButton').disabled = + !password1 || password1 != password2; + }, + + /** + * Clears the value of each input field. + * @private + */ + clearInputFields_: function() { + $('certificateBackupPassword').value = ''; + $('certificateBackupPassword2').value = ''; + $('certificateBackupPassword').disabled = false; + $('certificateBackupPassword2').disabled = false; + $('certificateBackupCancelButton').disabled = false; + $('certificateBackupOkButton').disabled = true; + }, + }; + + CertificateBackupOverlay.show = function() { + CertificateBackupOverlay.getInstance().clearInputFields_(); + OptionsPage.navigateToPage('certificateBackupOverlay'); + }; + + CertificateBackupOverlay.dismiss = function() { + CertificateBackupOverlay.getInstance().dismissOverlay_(); + }; + + // Export + return { + CertificateBackupOverlay: CertificateBackupOverlay + }; +}); diff --git a/chrome/browser/resources/options2/certificate_edit_ca_trust_overlay.html b/chrome/browser/resources/options2/certificate_edit_ca_trust_overlay.html new file mode 100644 index 0000000..b45bfc9 --- /dev/null +++ b/chrome/browser/resources/options2/certificate_edit_ca_trust_overlay.html @@ -0,0 +1,33 @@ +<div id="certificateEditCaTrustOverlay" class="page" hidden> + <div class="content-area"> + <p> + <span id="certificateEditCaTrustDescription"></span> + </p> + <p> + <span i18n-content="certificateEditTrustLabel"></span> + <br> + <label id="certificateCaTrustSSLLabel"> + <input id="certificateCaTrustSSLCheckbox" type="checkbox"> + <span i18n-content="certificateCaTrustSSLLabel"></span> + </label> + <br> + <label id="certificateCaTrustEmailLabel"> + <input id="certificateCaTrustEmailCheckbox" type="checkbox"> + <span i18n-content="certificateCaTrustEmailLabel"></span> + </label> + <br> + <label id="certificateCaTrustObjSignLabel"> + <input id="certificateCaTrustObjSignCheckbox" type="checkbox"> + <span i18n-content="certificateCaTrustObjSignLabel"></span> + </label> + </p> + </div> + <div class="action-area"> + <div class="button-strip"> + <button id="certificateEditCaTrustCancelButton" type="reset" + i18n-content="cancel"></button> + <button id="certificateEditCaTrustOkButton" type="submit" + i18n-content="ok"></button> + </div> + </div> +</div> diff --git a/chrome/browser/resources/options2/certificate_edit_ca_trust_overlay.js b/chrome/browser/resources/options2/certificate_edit_ca_trust_overlay.js new file mode 100644 index 0000000..86fa5f7 --- /dev/null +++ b/chrome/browser/resources/options2/certificate_edit_ca_trust_overlay.js @@ -0,0 +1,164 @@ +// Copyright (c) 2011 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() { + const OptionsPage = options.OptionsPage; + + /** + * CertificateEditCaTrustOverlay class + * Encapsulated handling of the 'edit ca trust' and 'import ca' overlay pages. + * @class + */ + function CertificateEditCaTrustOverlay() { + OptionsPage.call(this, 'certificateEditCaTrustOverlay', + '', + 'certificateEditCaTrustOverlay'); + } + + cr.addSingletonGetter(CertificateEditCaTrustOverlay); + + CertificateEditCaTrustOverlay.prototype = { + __proto__: OptionsPage.prototype, + + /** + * Dismisses the overlay. + * @private + */ + dismissOverlay_: function() { + OptionsPage.closeOverlay(); + }, + + /** + * Enables or disables input fields. + * @private + */ + enableInputs_: function(enabled) { + $('certificateCaTrustSSLCheckbox').disabled = + $('certificateCaTrustEmailCheckbox').disabled = + $('certificateCaTrustObjSignCheckbox').disabled = + $('certificateEditCaTrustCancelButton').disabled = + $('certificateEditCaTrustOkButton').disabled = !enabled; + }, + + /** + * Attempt the Edit operation. + * The overlay will be left up with inputs disabled until the backend + * finishes and dismisses it. + * @private + */ + finishEdit_: function() { + // TODO(mattm): Send checked values as booleans. For now send them as + // strings, since WebUIBindings::send does not support any other types :( + chrome.send('editCaCertificateTrust', + [this.certId, + $('certificateCaTrustSSLCheckbox').checked.toString(), + $('certificateCaTrustEmailCheckbox').checked.toString(), + $('certificateCaTrustObjSignCheckbox').checked.toString()]); + this.enableInputs_(false); + }, + + /** + * Cancel the Edit operation. + * @private + */ + cancelEdit_: function() { + this.dismissOverlay_(); + }, + + /** + * Attempt the Import operation. + * The overlay will be left up with inputs disabled until the backend + * finishes and dismisses it. + * @private + */ + finishImport_: function() { + // TODO(mattm): Send checked values as booleans. For now send them as + // strings, since WebUIBindings::send does not support any other types :( + chrome.send('importCaCertificateTrustSelected', + [$('certificateCaTrustSSLCheckbox').checked.toString(), + $('certificateCaTrustEmailCheckbox').checked.toString(), + $('certificateCaTrustObjSignCheckbox').checked.toString()]); + this.enableInputs_(false); + }, + + /** + * Cancel the Import operation. + * @private + */ + cancelImport_: function() { + chrome.send('cancelImportExportCertificate'); + this.dismissOverlay_(); + }, + }; + + /** + * Callback from CertificateManagerHandler with the trust values. + * @param {boolean} trustSSL The initial value of SSL trust checkbox. + * @param {boolean} trustEmail The initial value of Email trust checkbox. + * @param {boolean} trustObjSign The initial value of Object Signing trust + */ + CertificateEditCaTrustOverlay.populateTrust = function( + trustSSL, trustEmail, trustObjSign) { + $('certificateCaTrustSSLCheckbox').checked = trustSSL; + $('certificateCaTrustEmailCheckbox').checked = trustEmail; + $('certificateCaTrustObjSignCheckbox').checked = trustObjSign; + CertificateEditCaTrustOverlay.getInstance().enableInputs_(true); + } + + /** + * Show the Edit CA Trust overlay. + * @param {string} certId The id of the certificate to be passed to the + * certificate manager model. + * @param {string} certName The display name of the certificate. + * checkbox. + */ + CertificateEditCaTrustOverlay.show = function(certId, certName) { + var self = CertificateEditCaTrustOverlay.getInstance(); + self.certId = certId; + $('certificateEditCaTrustCancelButton').onclick = function(event) { + self.cancelEdit_(); + } + $('certificateEditCaTrustOkButton').onclick = function(event) { + self.finishEdit_(); + } + $('certificateEditCaTrustDescription').textContent = + localStrings.getStringF('certificateEditCaTrustDescriptionFormat', + certName); + self.enableInputs_(false); + OptionsPage.navigateToPage('certificateEditCaTrustOverlay'); + chrome.send('getCaCertificateTrust', [certId]); + } + + /** + * Show the Import CA overlay. + * @param {string} certId The id of the certificate to be passed to the + * certificate manager model. + * @param {string} certName The display name of the certificate. + * checkbox. + */ + CertificateEditCaTrustOverlay.showImport = function(certName) { + var self = CertificateEditCaTrustOverlay.getInstance(); + // TODO(mattm): do we want a view certificate button here like firefox has? + $('certificateEditCaTrustCancelButton').onclick = function(event) { + self.cancelImport_(); + } + $('certificateEditCaTrustOkButton').onclick = function(event) { + self.finishImport_(); + } + $('certificateEditCaTrustDescription').textContent = + localStrings.getStringF('certificateImportCaDescriptionFormat', + certName); + CertificateEditCaTrustOverlay.populateTrust(false, false, false); + OptionsPage.navigateToPage('certificateEditCaTrustOverlay'); + } + + CertificateEditCaTrustOverlay.dismiss = function() { + CertificateEditCaTrustOverlay.getInstance().dismissOverlay_(); + }; + + // Export + return { + CertificateEditCaTrustOverlay: CertificateEditCaTrustOverlay + }; +}); diff --git a/chrome/browser/resources/options2/certificate_import_error_overlay.html b/chrome/browser/resources/options2/certificate_import_error_overlay.html new file mode 100644 index 0000000..02a7e2d --- /dev/null +++ b/chrome/browser/resources/options2/certificate_import_error_overlay.html @@ -0,0 +1,13 @@ +<div id="certificateImportErrorOverlay" class="page" hidden> + <h1 id="certificateImportErrorOverlayTitle"></h1> + <div class="content-area"> + <div id="certificateImportErrorOverlayMessage"></div> + <ul id="certificateImportErrorOverlayCertErrors"></ul> + </div> + <div class="action-area"> + <div class="button-strip"> + <button id="certificateImportErrorOverlayOk" type="submit" + i18n-content="ok"></button> + </div> + </div> +</div> diff --git a/chrome/browser/resources/options2/certificate_import_error_overlay.js b/chrome/browser/resources/options2/certificate_import_error_overlay.js new file mode 100644 index 0000000..efc99ff --- /dev/null +++ b/chrome/browser/resources/options2/certificate_import_error_overlay.js @@ -0,0 +1,68 @@ +// Copyright (c) 2010 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; + + /** + * CertificateImportErrorOverlay class + * Displays a list of certificates and errors. + * @class + */ + function CertificateImportErrorOverlay() { + OptionsPage.call(this, 'certificateImportErrorOverlay', '', + 'certificateImportErrorOverlay'); + } + + cr.addSingletonGetter(CertificateImportErrorOverlay); + + CertificateImportErrorOverlay.prototype = { + // Inherit CertificateImportErrorOverlay from OptionsPage. + __proto__: OptionsPage.prototype, + + /** + * Initialize the page. + */ + initializePage: function() { + // Call base class implementation to start preference initialization. + OptionsPage.prototype.initializePage.call(this); + + $('certificateImportErrorOverlayOk').onclick = function(event) { + OptionsPage.closeOverlay(); + }; + }, + }; + + /** + * Show an alert overlay with the given message, button titles, and + * callbacks. + * @param {string} title The alert title to display to the user. + * @param {string} message The alert message to display to the user. + * @param {Array} certErrors The list of cert errors. Each error should have + * a .name and .error attribute. + */ + CertificateImportErrorOverlay.show = function(title, message, certErrors) { + $('certificateImportErrorOverlayTitle').textContent = title; + $('certificateImportErrorOverlayMessage').textContent = message; + + ul = $('certificateImportErrorOverlayCertErrors'); + ul.innerHTML = ''; + for (var i = 0; i < certErrors.length; ++i) { + li = document.createElement("li"); + li.textContent = localStrings.getStringF('certificateImportErrorFormat', + certErrors[i].name, + certErrors[i].error); + ul.appendChild(li); + } + + OptionsPage.navigateToPage('certificateImportErrorOverlay'); + } + + // Export + return { + CertificateImportErrorOverlay: CertificateImportErrorOverlay + }; + +}); diff --git a/chrome/browser/resources/options2/certificate_manager.css b/chrome/browser/resources/options2/certificate_manager.css new file mode 100644 index 0000000..a88d231 --- /dev/null +++ b/chrome/browser/resources/options2/certificate_manager.css @@ -0,0 +1,15 @@ +/* +Copyright (c) 2010 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. +*/ + +.certificate-tree-table { + width: 100%; +} + +.certificate-tree { + /* TODO(mattm): BLAH. Make this not statically sized. */ + height: 300px; +} + diff --git a/chrome/browser/resources/options2/certificate_manager.html b/chrome/browser/resources/options2/certificate_manager.html new file mode 100644 index 0000000..68ea65a --- /dev/null +++ b/chrome/browser/resources/options2/certificate_manager.html @@ -0,0 +1,129 @@ +<div id="certificateManagerPage" class="page" hidden> + <h1 i18n-content="certificateManagerPage"></h1> + <!-- Navigation tabs --> + <div class="subpages-nav-tabs"> + <span id="personal-certs-nav-tab" class="tab" + tab-contents="personalCertsTab"> + <span class="tab-label" + i18n-content="personalCertsTabTitle"></span> + <span class="active-tab-label" + i18n-content="personalCertsTabTitle"></span> + </span> + <span id="server-certs-nav-tab" class="tab" + tab-contents="serverCertsTab"> + <span class="tab-label" + i18n-content="serverCertsTabTitle"></span> + <span class="active-tab-label" i18n-content="serverCertsTabTitle"></span> + </span> + <span id="ca-certs-nav-tab" class="tab" + tab-contents="caCertsTab"> + <span class="tab-label" i18n-content="caCertsTabTitle"></span> + <span class="active-tab-label" i18n-content="caCertsTabTitle"></span> + </span> + <span id="other-certs-nav-tab" class="tab" + tab-contents="otherCertsTab"> + <span class="tab-label" + i18n-content="unknownCertsTabTitle"></span> + <span class="active-tab-label" i18n-content="unknownCertsTabTitle"></span> + </span> + </div> + <!-- TODO(mattm): get rid of use of tables? --> + <!-- Tab contents --> + <div id="personalCertsTab" class="subpages-tab-contents"> + <table class="certificate-tree-table"> + <tr><td> + <span i18n-content="personalCertsTabDescription"></span> + </td></tr> + <tr><td> + <tree id="personalCertsTab-tree" class="certificate-tree" + icon-visibility="parent"></tree> + </td></tr> + <tr><td> + <button id="personalCertsTab-view" i18n-content="view_certificate" + disabled></button> + <!-- TODO(mattm): + <button id="personalCertsTab-backup-all" + i18n-content="export_all_certificates" + disabled></button> + --> + <button id="personalCertsTab-import" i18n-content="import_certificate" + ></button> + <if expr="pp_ifdef('chromeos')"> + <button id="personalCertsTab-import-and-bind" + i18n-content="importAndBindCertificate" disabled></button> + </if> + <button id="personalCertsTab-backup" i18n-content="export_certificate" + disabled></button> + <button id="personalCertsTab-delete" i18n-content="delete_certificate" + disabled></button> + </td></tr> + </table> + </div> + <div id="serverCertsTab" class="subpages-tab-contents"> + <table class="certificate-tree-table"> + <tr><td> + <span i18n-content="serverCertsTabDescription"></span> + </td></tr> + <tr><td> + <tree id="serverCertsTab-tree" class="certificate-tree" + icon-visibility="parent"></tree> + </td></tr> + <tr><td> + <button id="serverCertsTab-view" i18n-content="view_certificate" + disabled></button> + <!-- TODO(mattm): + <button id="serverCertsTab-edit" i18n-content="edit_certificate" + disabled></button> + --> + <button id="serverCertsTab-import" i18n-content="import_certificate" + ></button> + <button id="serverCertsTab-export" i18n-content="export_certificate" + disabled></button> + <button id="serverCertsTab-delete" i18n-content="delete_certificate" + disabled></button> + </td></tr> + </table> + </div> + <div id="caCertsTab" class="subpages-tab-contents"> + <table class="certificate-tree-table"> + <tr><td> + <span i18n-content="caCertsTabDescription"></span> + </td></tr> + <tr><td> + <tree id="caCertsTab-tree" class="certificate-tree" + icon-visibility="parent"></tree> + </td></tr> + <tr><td> + <button id="caCertsTab-view" i18n-content="view_certificate" + disabled></button> + <button id="caCertsTab-edit" i18n-content="edit_certificate" + disabled></button> + <button id="caCertsTab-import" i18n-content="import_certificate" + ></button> + <button id="caCertsTab-export" i18n-content="export_certificate" + disabled></button> + <button id="caCertsTab-delete" i18n-content="delete_certificate" + disabled></button> + </td></tr> + </table> + </div> + <div id="otherCertsTab" class="subpages-tab-contents"> + <table class="certificate-tree-table"> + <tr><td> + <span i18n-content="unknownCertsTabDescription"></span> + </td></tr> + <tr><td> + <tree id="otherCertsTab-tree" class="certificate-tree" + icon-visibility="parent"></tree> + </td></tr> + <tr><td> + <button id="otherCertsTab-view" i18n-content="view_certificate" + disabled></button> + <button id="otherCertsTab-export" i18n-content="export_certificate" + disabled></button> + <button id="otherCertsTab-delete" i18n-content="delete_certificate" + disabled></button> + </td></tr> + </table> + </div> +</div> diff --git a/chrome/browser/resources/options2/certificate_manager.js b/chrome/browser/resources/options2/certificate_manager.js new file mode 100644 index 0000000..59a7efd --- /dev/null +++ b/chrome/browser/resources/options2/certificate_manager.js @@ -0,0 +1,253 @@ +// Copyright (c) 2011 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; + + ///////////////////////////////////////////////////////////////////////////// + // CertificateManagerTab class: + + /** + * blah + * @param {!string} id The id of this tab. + */ + function CertificateManagerTab(id) { + this.tree = $(id + '-tree'); + + options.CertificatesTree.decorate(this.tree); + this.tree.addEventListener('change', + this.handleCertificatesTreeChange_.bind(this)); + + var tree = this.tree; + + this.viewButton = $(id + '-view'); + this.viewButton.onclick = function(e) { + var selected = tree.selectedItem; + chrome.send('viewCertificate', [selected.data.id]); + } + + this.editButton = $(id + '-edit'); + if (this.editButton !== null) { + if (id == 'serverCertsTab') { + this.editButton.onclick = function(e) { + var selected = tree.selectedItem; + chrome.send('editServerCertificate', [selected.data.id]); + } + } else if (id == 'caCertsTab') { + this.editButton.onclick = function(e) { + var data = tree.selectedItem.data; + CertificateEditCaTrustOverlay.show(data.id, data.name); + } + } + } + + this.backupButton = $(id + '-backup'); + if (this.backupButton !== null) { + this.backupButton.onclick = function(e) { + var selected = tree.selectedItem; + chrome.send('exportPersonalCertificate', [selected.data.id]); + } + } + + this.backupAllButton = $(id + '-backup-all'); + if (this.backupAllButton !== null) { + this.backupAllButton.onclick = function(e) { + chrome.send('exportAllPersonalCertificates', []); + } + } + + this.importButton = $(id + '-import'); + if (this.importButton !== null) { + if (id == 'personalCertsTab') { + this.importButton.onclick = function(e) { + chrome.send('importPersonalCertificate', [false]); + } + } else if (id == 'serverCertsTab') { + this.importButton.onclick = function(e) { + chrome.send('importServerCertificate', []); + } + } else if (id == 'caCertsTab') { + this.importButton.onclick = function(e) { + chrome.send('importCaCertificate', []); + } + } + } + + this.importAndBindButton = $(id + '-import-and-bind'); + if (this.importAndBindButton !== null) { + if (id == 'personalCertsTab') { + this.importAndBindButton.onclick = function(e) { + chrome.send('importPersonalCertificate', [true]); + } + } + } + + this.exportButton = $(id + '-export'); + if (this.exportButton !== null) { + this.exportButton.onclick = function(e) { + var selected = tree.selectedItem; + chrome.send('exportCertificate', [selected.data.id]); + } + } + + this.deleteButton = $(id + '-delete'); + this.deleteButton.onclick = function(e) { + var data = tree.selectedItem.data; + AlertOverlay.show( + localStrings.getStringF(id + 'DeleteConfirm', data.name), + localStrings.getString(id + 'DeleteImpact'), + localStrings.getString('ok'), + localStrings.getString('cancel'), + function() { + tree.selectedItem = null; + chrome.send('deleteCertificate', [data.id]); + }); + } + } + + CertificateManagerTab.prototype = { + + /** + * Update button state. + * @private + * @param {!Object} data The data of the selected item. + */ + updateButtonState: function(data) { + var isCert = !!data && data.id.substr(0, 5) == 'cert-'; + var readOnly = !!data && data.readonly; + var hasChildren = this.tree.items.length > 0; + this.viewButton.disabled = !isCert; + if (this.editButton !== null) + this.editButton.disabled = !isCert; + if (this.backupButton !== null) + this.backupButton.disabled = !isCert; + if (this.backupAllButton !== null) + this.backupAllButton.disabled = !hasChildren; + if (this.exportButton !== null) + this.exportButton.disabled = !isCert; + this.deleteButton.disabled = !isCert || readOnly; + }, + + /** + * Handles certificate tree selection change. + * @private + * @param {!Event} e The change event object. + */ + handleCertificatesTreeChange_: function(e) { + var data = null; + if (this.tree.selectedItem) { + data = this.tree.selectedItem.data; + } + + this.updateButtonState(data); + }, + + } + + // TODO(xiyuan): Use notification from backend instead of polling. + // TPM token check polling timer. + var tpmPollingTimer; + + // Initiate tpm token check if needed. + function checkTpmToken() { + var importAndBindButton = $('personalCertsTab-import-and-bind'); + + if (importAndBindButton && importAndBindButton.disabled) + chrome.send('checkTpmTokenReady'); + } + + // Stop tpm polling timer. + function stopTpmTokenCheckPolling() { + if (tpmPollingTimer) { + window.clearTimeout(tpmPollingTimer); + tpmPollingTimer = undefined; + } + } + + ///////////////////////////////////////////////////////////////////////////// + // CertificateManager class: + + /** + * Encapsulated handling of ChromeOS accounts options page. + * @constructor + */ + function CertificateManager(model) { + OptionsPage.call(this, 'certificates', + templateData.certificateManagerPageTabTitle, + 'certificateManagerPage'); + } + + cr.addSingletonGetter(CertificateManager); + + CertificateManager.prototype = { + __proto__: OptionsPage.prototype, + + initializePage: function() { + OptionsPage.prototype.initializePage.call(this); + + this.personalTab = new CertificateManagerTab('personalCertsTab'); + this.serverTab = new CertificateManagerTab('serverCertsTab'); + this.caTab = new CertificateManagerTab('caCertsTab'); + this.otherTab = new CertificateManagerTab('otherCertsTab'); + + this.addEventListener('visibleChange', this.handleVisibleChange_); + }, + + initalized_: false, + + /** + * Handler for OptionsPage's visible property change event. + * @private + * @param {Event} e Property change event. + */ + handleVisibleChange_: function(e) { + if (!this.initalized_ && this.visible) { + this.initalized_ = true; + OptionsPage.showTab($('personal-certs-nav-tab')); + chrome.send('populateCertificateManager'); + } + + if (cr.isChromeOS) { + // Ensure TPM token check on visible and stop polling when hidden. + if (this.visible) + checkTpmToken(); + else + stopTpmTokenCheckPolling(); + } + } + }; + + // CertificateManagerHandler callbacks. + CertificateManager.onPopulateTree = function(args) { + $(args[0]).populate(args[1]); + }; + + CertificateManager.exportPersonalAskPassword = function(args) { + CertificateBackupOverlay.show(); + }; + + CertificateManager.importPersonalAskPassword = function(args) { + CertificateRestoreOverlay.show(); + }; + + CertificateManager.onCheckTpmTokenReady = function(ready) { + var importAndBindButton = $('personalCertsTab-import-and-bind'); + if (importAndBindButton) { + importAndBindButton.disabled = !ready; + + // Check again after 5 seconds if Tpm is not ready and certificate manager + // is still visible. + if (!ready && CertificateManager.getInstance().visible) + tpmPollingTimer = window.setTimeout(checkTpmToken, 5000); + } + }; + + // Export + return { + CertificateManagerTab: CertificateManagerTab, + CertificateManager: CertificateManager + }; + +}); diff --git a/chrome/browser/resources/options2/certificate_restore_overlay.html b/chrome/browser/resources/options2/certificate_restore_overlay.html new file mode 100644 index 0000000..30b380d --- /dev/null +++ b/chrome/browser/resources/options2/certificate_restore_overlay.html @@ -0,0 +1,17 @@ +<div id="certificateRestoreOverlay" class="page" hidden> + <h1 i18n-content="certificateRestorePasswordDescription"></h1> + <div class="content-area"> + <label id="certificateRestorePasswordLabel"> + <span i18n-content="certificatePasswordLabel"></span> + <input id="certificateRestorePassword" type="password"> + </label> + </div> + <div class="action-area"> + <div class="button-strip"> + <button id="certificateRestoreCancelButton" type="reset" + i18n-content="cancel"></button> + <button id="certificateRestoreOkButton" type="submit" i18n-content="ok"> + </button> + </div> + </div> +</div> diff --git a/chrome/browser/resources/options2/certificate_restore_overlay.js b/chrome/browser/resources/options2/certificate_restore_overlay.js new file mode 100644 index 0000000..d76a329 --- /dev/null +++ b/chrome/browser/resources/options2/certificate_restore_overlay.js @@ -0,0 +1,97 @@ +// Copyright (c) 2011 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() { + const OptionsPage = options.OptionsPage; + + /** + * CertificateRestoreOverlay class + * Encapsulated handling of the 'enter restore password' overlay page. + * @class + */ + function CertificateRestoreOverlay() { + OptionsPage.call(this, 'certificateRestore', + '', + 'certificateRestoreOverlay'); + } + + cr.addSingletonGetter(CertificateRestoreOverlay); + + CertificateRestoreOverlay.prototype = { + __proto__: OptionsPage.prototype, + + /** + * Initializes the page. + */ + initializePage: function() { + OptionsPage.prototype.initializePage.call(this); + + var self = this; + $('certificateRestoreCancelButton').onclick = function(event) { + self.cancelRestore_(); + } + $('certificateRestoreOkButton').onclick = function(event) { + self.finishRestore_(); + } + + self.clearInputFields_(); + }, + + /** + * Clears any uncommitted input, and dismisses the overlay. + * @private + */ + dismissOverlay_: function() { + this.clearInputFields_(); + OptionsPage.closeOverlay(); + }, + + /** + * Attempt the restore operation. + * The overlay will be left up with inputs disabled until the backend + * finishes and dismisses it. + * @private + */ + finishRestore_: function() { + chrome.send('importPersonalCertificatePasswordSelected', + [$('certificateRestorePassword').value]); + $('certificateRestoreCancelButton').disabled = true; + $('certificateRestoreOkButton').disabled = true; + }, + + /** + * Cancel the restore operation. + * @private + */ + cancelRestore_: function() { + chrome.send('cancelImportExportCertificate'); + this.dismissOverlay_(); + }, + + /** + * Clears the value of each input field. + * @private + */ + clearInputFields_: function() { + $('certificateRestorePassword').value = ''; + $('certificateRestoreCancelButton').disabled = false; + $('certificateRestoreOkButton').disabled = false; + }, + }; + + CertificateRestoreOverlay.show = function() { + CertificateRestoreOverlay.getInstance().clearInputFields_(); + OptionsPage.navigateToPage('certificateRestore'); + }; + + CertificateRestoreOverlay.dismiss = function() { + CertificateRestoreOverlay.getInstance().dismissOverlay_(); + }; + + // Export + return { + CertificateRestoreOverlay: CertificateRestoreOverlay + }; + +}); diff --git a/chrome/browser/resources/options2/certificate_tree.css b/chrome/browser/resources/options2/certificate_tree.css new file mode 100644 index 0000000..566eb21 --- /dev/null +++ b/chrome/browser/resources/options2/certificate_tree.css @@ -0,0 +1,8 @@ +span.certUntrusted { + background-color: pink; + border: 1px solid red; + border-radius: 3px; + margin-right: 3px; + padding-left: 1px; + padding-right: 1px; +} diff --git a/chrome/browser/resources/options2/certificate_tree.js b/chrome/browser/resources/options2/certificate_tree.js new file mode 100644 index 0000000..ee5d075 --- /dev/null +++ b/chrome/browser/resources/options2/certificate_tree.js @@ -0,0 +1,128 @@ +// Copyright (c) 2011 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() { + const Tree = cr.ui.Tree; + const TreeItem = cr.ui.TreeItem; + + /** + * Creates a new tree item for certificate data. + * @param {Object=} data Data used to create a certificate tree item. + * @constructor + * @extends {TreeItem} + */ + function CertificateTreeItem(data) { + // TODO(mattm): other columns + var treeItem = new TreeItem({ + label: data.name, + data: data + }); + treeItem.__proto__ = CertificateTreeItem.prototype; + + if (data.icon) { + treeItem.icon = data.icon; + } + + if (data.untrusted) { + var badge = document.createElement('span'); + badge.setAttribute('class', 'certUntrusted'); + badge.textContent = localStrings.getString("badgeCertUntrusted"); + treeItem.labelElement.insertBefore( + badge, treeItem.labelElement.firstChild); + } + + return treeItem; + } + + CertificateTreeItem.prototype = { + __proto__: TreeItem.prototype, + + /** + * The tree path id/. + * @type {string} + */ + get pathId() { + var parent = this.parentItem; + if (parent && parent instanceof CertificateTreeItem) { + return parent.pathId + ',' + this.data.id; + } else { + return this.data.id; + } + } + }; + + /** + * Creates a new cookies tree. + * @param {Object=} opt_propertyBag Optional properties. + * @constructor + * @extends {Tree} + */ + var CertificatesTree = cr.ui.define('tree'); + + CertificatesTree.prototype = { + __proto__: Tree.prototype, + + /** @inheritDoc */ + decorate: function() { + Tree.prototype.decorate.call(this); + this.treeLookup_ = {}; + }, + + /** @inheritDoc */ + addAt: function(child, index) { + Tree.prototype.addAt.call(this, child, index); + if (child.data && child.data.id) + this.treeLookup_[child.data.id] = child; + }, + + /** @inheritDoc */ + remove: function(child) { + Tree.prototype.remove.call(this, child); + if (child.data && child.data.id) + delete this.treeLookup_[child.data.id]; + }, + + /** + * Clears the tree. + */ + clear: function() { + // Remove all fields without recreating the object since other code + // references it. + for (var id in this.treeLookup_){ + delete this.treeLookup_[id]; + } + this.textContent = ''; + }, + + /** + * Populate the tree. + * @param {Array} nodesData Nodes data array. + */ + populate: function(nodesData) { + this.clear(); + + for (var i = 0; i < nodesData.length; ++i) { + var subnodes = nodesData[i]['subnodes']; + delete nodesData[i]['subnodes']; + + var item = new CertificateTreeItem(nodesData[i]); + this.addAt(item, i); + + for (var j = 0; j < subnodes.length; ++j) { + var subitem = new CertificateTreeItem(subnodes[j]); + item.addAt(subitem, j); + } + // Make tree expanded by default. + item.expanded = true; + } + + cr.dispatchSimpleEvent(this, 'change'); + }, + }; + + return { + CertificatesTree: CertificatesTree + }; +}); + diff --git a/chrome/browser/resources/options2/chromeos/accounts_options.html b/chrome/browser/resources/options2/chromeos/accounts_options.html new file mode 100644 index 0000000..b324c4f --- /dev/null +++ b/chrome/browser/resources/options2/chromeos/accounts_options.html @@ -0,0 +1,63 @@ +<div id="accountsPage" class="page" hidden> + <h1 i18n-content="accountsPage"></h1> + <div class="displaytable"> + <section> + <div class="option"> + <div id="ownerOnlyWarning" hidden> + <span i18n-content="owner_only"></span> + <span i18n-content="owner_user_id"></span> + </div> + <table class="option-control-table"> + <tr> + <td class="option-name"> + <div class="checkbox"> + <label> + <input id="allowBwsiCheck" pref="cros.accounts.allowBWSI" + type="checkbox"> + <span i18n-content="allow_BWSI"></span> + </label> + </div> + </td> + </tr> + <tr> + <td class="option-name"> + <div class="checkbox"> + <label> + <input id="showUserNamesCheck" + pref="cros.accounts.showUserNamesOnSignIn" type="checkbox"> + <span i18n-content="show_user_on_signin"></span> + </label> + </div> + </td> + </tr> + <tr> + <td class="option-name"> + <div class="checkbox"> + <label> + <input id="useWhitelistCheck" pref="cros.accounts.allowGuest" + type="checkbox" inverted_pref> + <span i18n-content="use_whitelist"></span> + </label> + </div> + </td> + </tr> + <tr><td> </td></tr> + <tr><td> + <table class="user-list-table"> + <tr><td> + <list id="userList"></list> + </td></tr> + <tr><td class="user-name-edit-row"> + <label><span i18n-content="add_users"></span><br> + <input id="userNameEdit" type="text" + i18n-values="placeholder:username_edit_hint"> + </span> + </label> + </td></tr> + </table> + </td></tr> + </table> + </div> + </section> + </div> +</div> diff --git a/chrome/browser/resources/options2/chromeos/accounts_options.js b/chrome/browser/resources/options2/chromeos/accounts_options.js new file mode 100644 index 0000000..af6bf9c --- /dev/null +++ b/chrome/browser/resources/options2/chromeos/accounts_options.js @@ -0,0 +1,165 @@ +// Copyright (c) 2011 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; + + ///////////////////////////////////////////////////////////////////////////// + // AccountsOptions class: + + /** + * Encapsulated handling of ChromeOS accounts options page. + * @constructor + */ + function AccountsOptions(model) { + OptionsPage.call(this, 'accounts', templateData.accountsPageTabTitle, + 'accountsPage'); + // Whether to show the whitelist. + this.showWhitelist_ = false; + } + + cr.addSingletonGetter(AccountsOptions); + + AccountsOptions.prototype = { + // Inherit AccountsOptions from OptionsPage. + __proto__: OptionsPage.prototype, + + /** + * Initializes AccountsOptions page. + */ + initializePage: function() { + // Call base class implementation to starts preference initialization. + OptionsPage.prototype.initializePage.call(this); + + // Set up accounts page. + var userList = $('userList'); + userList.addEventListener('remove', this.handleRemoveUser_); + + var userNameEdit = $('userNameEdit'); + options.accounts.UserNameEdit.decorate(userNameEdit); + userNameEdit.addEventListener('add', this.handleAddUser_); + + // If the current user is not the owner, show some warning, + // and do not show the user list. + this.showWhitelist_ = AccountsOptions.currentUserIsOwner(); + if (this.showWhitelist_) { + options.accounts.UserList.decorate(userList); + } else { + if (!AccountsOptions.whitelistIsManaged()) { + $('ownerOnlyWarning').hidden = false; + } else { + this.managed = true; + } + } + + this.addEventListener('visibleChange', this.handleVisibleChange_); + + $('useWhitelistCheck').addEventListener('change', + this.handleUseWhitelistCheckChange_.bind(this)); + + Preferences.getInstance().addEventListener( + $('useWhitelistCheck').pref, + this.handleUseWhitelistPrefChange_.bind(this)); + }, + + /** + * Update user list control state. + * @private + */ + updateControls_: function() { + $('userList').disabled = + $('userNameEdit').disabled = !this.showWhitelist_ || + AccountsOptions.whitelistIsManaged() || + !$('useWhitelistCheck').checked; + }, + + /** + * Handler for OptionsPage's visible property change event. + * @private + * @param {Event} e Property change event. + */ + handleVisibleChange_: function(e) { + if (this.visible) { + this.updateControls_(); + if (this.showWhitelist_) + $('userList').redraw(); + } + }, + + /** + * Handler for allow guest check change. + * @private + */ + handleUseWhitelistCheckChange_: function(e) { + // Whitelist existing users when guest login is being disabled. + if ($('useWhitelistCheck').checked) { + chrome.send('whitelistExistingUsers', []); + } + + this.updateControls_(); + }, + + /** + * handler for allow guest pref change. + * @private + */ + handleUseWhitelistPrefChange_: function(e) { + this.updateControls_(); + }, + + /** + * Handler for "add" event fired from userNameEdit. + * @private + * @param {Event} e Add event fired from userNameEdit. + */ + handleAddUser_: function(e) { + chrome.send('whitelistUser', [e.user.email, e.user.name]); + }, + + /** + * Handler for "remove" event fired from userList. + * @private + * @param {Event} e Remove event fired from userList. + */ + handleRemoveUser_: function(e) { + chrome.send('unwhitelistUser', [e.user.username]); + } + }; + + /** + * Returns whether the current user is owner or not. + */ + AccountsOptions.currentUserIsOwner = function() { + return localStrings.getString('current_user_is_owner') == 'true'; + }; + + /** + * Returns whether we're currently in guest mode. + */ + AccountsOptions.loggedInAsGuest = function() { + return localStrings.getString('logged_in_as_guest') == 'true'; + }; + + /** + * Returns whether the whitelist is managed by policy or not. + */ + AccountsOptions.whitelistIsManaged = function() { + return localStrings.getString('whitelist_is_managed') == 'true'; + }; + + /** + * Update account picture. + * @param {string} username User for which to update the image. + */ + AccountsOptions.updateAccountPicture = function(username) { + if (this.showWhitelist_) + $('userList').updateAccountPicture(username); + }; + + // Export + return { + AccountsOptions: AccountsOptions + }; + +}); diff --git a/chrome/browser/resources/options2/chromeos/accounts_options_page.css b/chrome/browser/resources/options2/chromeos/accounts_options_page.css new file mode 100644 index 0000000..e32d550 --- /dev/null +++ b/chrome/browser/resources/options2/chromeos/accounts_options_page.css @@ -0,0 +1,87 @@ +.user-list-table { + border: 1px solid lightgrey; + border-collapse: collapse; + border-spacing: 0; +} + +.user-name-edit-row { + border: 1px solid lightgrey; + background-color: #ebeffa; + padding: 5px; +} + +.user-list-item { + padding: 2px; +} + +.user-icon { + border: 1px solid black; + width: 26px; + height: 26px; +} + +.user-email-label { + -webkit-margin-start: 10px; +} + +.user-name-label { + color: darkgray; + -webkit-margin-start: 10px; +} + +.user-email-name-block { + -webkit-box-flex: 1; + max-width: 318px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.remove-user-button { + background-image: url(chrome://theme/IDR_CLOSE_BAR); + width: 16px; + height: 16px; +} + +.remove-user-button:hover { + background-image: url(chrome://theme/IDR_CLOSE_BAR_H); +} + +#userList { + padding: 5px; + width: 366px; + height: 166px; +} + +#userList[disabled], +#userList[disabled] > [selected], +#userList[disabled] > :hover { + border-color: hsl(0, 0%, 85%); +} + +#userList[disabled] > [selected], +#userList[disabled] > :hover { + background-color: hsl(0,0%,90%); +} + +#userList[disabled] .remove-user-button { + visibility: hidden; +} + +#userNameEdit { + border: 1px solid lightgrey; + width: 366px; +} + +#ownerOnlyWarning { + margin-top: 10px; + margin-bottom: 10px; + padding-bottom: 1px; + -webkit-padding-start: 20px; + background-repeat: no-repeat; + background-image: url('warning.png'); +} + +input#userNameEdit:invalid { + background-color: #ff6666; +} diff --git a/chrome/browser/resources/options2/chromeos/accounts_user_list.js b/chrome/browser/resources/options2/chromeos/accounts_user_list.js new file mode 100644 index 0000000..3fe33b7 --- /dev/null +++ b/chrome/browser/resources/options2/chromeos/accounts_user_list.js @@ -0,0 +1,194 @@ +// Copyright (c) 2011 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.accounts', function() { + const List = cr.ui.List; + const ListItem = cr.ui.ListItem; + const ArrayDataModel = cr.ui.ArrayDataModel; + + /** + * Creates a new user list. + * @param {Object=} opt_propertyBag Optional properties. + * @constructor + * @extends {cr.ui.List} + */ + var UserList = cr.ui.define('list'); + + UserList.prototype = { + __proto__: List.prototype, + + pref: 'cros.accounts.users', + + /** @inheritDoc */ + decorate: function() { + List.prototype.decorate.call(this); + + // HACK(arv): http://crbug.com/40902 + window.addEventListener('resize', this.redraw.bind(this)); + + var self = this; + + // Listens to pref changes. + Preferences.getInstance().addEventListener(this.pref, + function(event) { + self.load_(event.value); + }); + }, + + createItem: function(user) { + return new UserListItem(user); + }, + + /** + * Finds the index of user by given username (canonicalized email). + * @private + * @param {string} username The username to look for. + * @return {number} The index of the found user or -1 if not found. + */ + indexOf_: function(username) { + var dataModel = this.dataModel; + if (!dataModel) + return -1; + + var length = dataModel.length; + for (var i = 0; i < length; ++i) { + var user = dataModel.item(i); + if (user.username == username) { + return i; + } + } + + return -1; + }, + + /** + * Update given user's account picture. + * @param {string} username User for which to update the image. + */ + updateAccountPicture: function(username) { + var index = this.indexOf_(username); + if (index >= 0) { + var item = this.getListItemByIndex(index); + if (item) + item.updatePicture(); + } + }, + + /** + * Loads given user list. + * @param {Array.<Object>} users An array of user info objects. + * @private + */ + load_: function(users) { + this.dataModel = new ArrayDataModel(users); + }, + + /** + * Removes given user from the list. + * @param {Object} user User info object to be removed from user list. + * @private + */ + removeUser_: function(user) { + var e = new Event('remove'); + e.user = user; + this.dispatchEvent(e); + } + }; + + /** + * Whether the user list is disabled. Only used for display purpose. + * @type {boolean} + */ + cr.defineProperty(UserList, 'disabled', cr.PropertyKind.BOOL_ATTR); + + /** + * Creates a new user list item. + * @param user The user account this represents. + * @constructor + * @extends {cr.ui.ListItem} + */ + function UserListItem(user) { + var el = cr.doc.createElement('div'); + el.user = user; + UserListItem.decorate(el); + return el; + } + + /** + * Decorates an element as a user account item. + * @param {!HTMLElement} el The element to decorate. + */ + UserListItem.decorate = function(el) { + el.__proto__ = UserListItem.prototype; + el.decorate(); + }; + + UserListItem.prototype = { + __proto__: ListItem.prototype, + + /** @inheritDoc */ + decorate: function() { + ListItem.prototype.decorate.call(this); + + this.className = 'user-list-item'; + + this.icon_ = this.ownerDocument.createElement('img'); + this.icon_.className = 'user-icon'; + this.updatePicture(); + + var labelEmail = this.ownerDocument.createElement('span'); + labelEmail.className = 'user-email-label'; + labelEmail.textContent = this.user.email; + + var labelName = this.ownerDocument.createElement('span'); + labelName.className = 'user-name-label'; + labelName.textContent = this.user.owner ? + localStrings.getStringF('username_format', this.user.name) : + this.user.name; + + var emailNameBlock = this.ownerDocument.createElement('div'); + emailNameBlock.className = 'user-email-name-block'; + emailNameBlock.appendChild(labelEmail); + emailNameBlock.appendChild(labelName); + emailNameBlock.title = this.user.owner ? + localStrings.getStringF('username_format', this.user.email) : + this.user.email; + + this.appendChild(this.icon_); + this.appendChild(emailNameBlock); + + if (!this.user.owner) { + var removeButton = this.ownerDocument.createElement('button'); + removeButton.className = + 'raw-button remove-user-button custom-appearance'; + removeButton.addEventListener( + 'click', this.handleRemoveButtonClick_.bind(this)); + this.appendChild(removeButton); + } + }, + + /** + * Handles click on the remove button. + * @param {Event} e Click event. + * @private + */ + handleRemoveButtonClick_: function(e) { + // Handle left button click + if (e.button == 0) + this.parentNode.removeUser_(this.user); + }, + + /** + * Reloads user picture. + */ + updatePicture: function() { + this.icon_.src = 'chrome://userimage/' + this.user.username + + '?id=' + (new Date()).getTime(); + } + }; + + return { + UserList: UserList + }; +}); diff --git a/chrome/browser/resources/options2/chromeos/accounts_user_name_edit.js b/chrome/browser/resources/options2/chromeos/accounts_user_name_edit.js new file mode 100644 index 0000000..025db25 --- /dev/null +++ b/chrome/browser/resources/options2/chromeos/accounts_user_name_edit.js @@ -0,0 +1,121 @@ +// Copyright (c) 2011 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.accounts', function() { + const Event = cr.Event; + + // Email alias only, assuming it's a gmail address. + // e.g. 'john' + // {name: 'john', email: 'john@gmail.com'} + const format1String = + '^\\s*([\\w\\.!#\\$%&\'\\*\\+-\\/=\\?\\^`\\{\\|\\}~]+)\\s*$'; + // Email address only. + // e.g. 'john@chromium.org' + // {name: 'john', email: 'john@chromium.org'} + const format2String = + '^\\s*([\\w\\.!#\\$%&\'\\*\\+-\\/=\\?\\^`\\{\\|\\}~]+)@' + + '([A-Za-z0-9\-]{2,63}\\..+)\\s*$'; + // Full format. + // e.g. '"John Doe" <john@chromium.org>' + // {name: 'John doe', email: 'john@chromium.org'} + const format3String = + '^\\s*"{0,1}([^"]+)"{0,1}\\s*' + + '<([\\w\\.!#\\$%&\'\\*\\+-\\/=\\?\\^`\\{\\|\\}~]+@' + + '[A-Za-z0-9\-]{2,63}\\..+)>\\s*$'; + + /** + * Creates a new user name edit element. + * @param {Object=} opt_propertyBag Optional properties. + * @constructor + * @extends {HTMLInputElement} + */ + var UserNameEdit = cr.ui.define('input'); + + UserNameEdit.prototype = { + __proto__: HTMLInputElement.prototype, + + /** + * Called when an element is decorated as a user name edit. + */ + decorate: function() { + this.pattern = format1String + '|' + format2String + '|' + + format3String; + + this.onkeypress = this.handleKeyPress_.bind(this); + }, + + + /** + * Parses given str for user info. + * + * Note that the email parsing is based on RFC 5322 and does not support + * IMA (Internationalized Email Address). We take only the following chars + * as valid for an email alias (aka local-part): + * - Letters: a–z, A–Z + * - Digits: 0-9 + * - Characters: ! # $ % & ' * + - / = ? ^ _ ` { | } ~ + * - Dot: . (Note that we did not cover the cases that dot should not + * appear as first or last character and should not appear two or + * more times in a row.) + * + * @param {string} str A string to parse. + * @return {{name: string, email: string}} User info parsed from the string. + */ + parse: function(str) { + const format1 = new RegExp(format1String); + const format2 = new RegExp(format2String); + const format3 = new RegExp(format3String); + + var matches = format1.exec(str); + if (matches) { + return { + name: matches[1], + email: matches[1] + '@gmail.com' + }; + } + + matches = format2.exec(str); + if (matches) { + return { + name: matches[1], + email: matches[1] + '@' + matches[2] + }; + } + + matches = format3.exec(str); + if (matches) { + return { + name: matches[1], + email: matches[2] + }; + } + + return null; + }, + + /** + * Handler for key press event. + * @private + * @param {!Event} e The keypress event object. + */ + handleKeyPress_: function(e) { + // Enter + if (e.keyCode == 13) { + var user = this.parse(this.value); + if (user) { + var e = new Event('add'); + e.user = user; + this.dispatchEvent(e); + } + + this.select(); + } + } + }; + + return { + UserNameEdit: UserNameEdit + }; +}); + diff --git a/chrome/browser/resources/options2/chromeos/bluetooth_device_list.js b/chrome/browser/resources/options2/chromeos/bluetooth_device_list.js new file mode 100644 index 0000000..6187cbb --- /dev/null +++ b/chrome/browser/resources/options2/chromeos/bluetooth_device_list.js @@ -0,0 +1,232 @@ +// Copyright (c) 2011 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.system.bluetooth', function() { + const ArrayDataModel = cr.ui.ArrayDataModel; + const DeletableItem = options.DeletableItem; + const DeletableItemList = options.DeletableItemList; + + /** + * Bluetooth settings constants. + */ + function Constants() {} + + /** + * Enumeration of supported device types. + * @enum {string} + */ + // TODO(kevers): Prune list based on the set of devices that will be + // supported for V1 of the feature. The set will likely be restricted to + // mouse and keyboard. Others are temporarily included for testing device + // discovery. + Constants.DEVICE_TYPE = { + COMPUTER: 'computer', + HEADSET: 'headset', + KEYBOARD: 'input-keyboard', + MOUSE: 'input-mouse', + PHONE: 'phone', + }; + + /** + * Creates a new bluetooth list item. + * @param {{name: string, + * address: string, + * icon: Constants.DEVICE_TYPE, + * paired: boolean, + * connected: boolean, + * pairing: string|undefined, + * passkey: number|undefined, + * entered: number|undefined}} device + * Description of the Bluetooth device. + * @constructor + * @extends {options.DeletableItem} + */ + function BluetoothListItem(device) { + var el = cr.doc.createElement('div'); + el.__proto__ = BluetoothListItem.prototype; + el.data = {}; + for (var key in device) + el.data[key] = device[key]; + el.decorate(); + // Only show the close button for paired devices. + el.deletable = device.paired; + return el; + } + + BluetoothListItem.prototype = { + __proto__: DeletableItem.prototype, + + /** + * Description of the Bluetooth device. + * @type {{name: string, + * address: string, + * icon: Constants.DEVICE_TYPE, + * paired: boolean, + * connected: boolean, + * pairing: string|undefined, + * passkey: number|undefined, + * entered: number|undefined}} + */ + data: null, + + /** @inheritDoc */ + decorate: function() { + DeletableItem.prototype.decorate.call(this); + var label = this.ownerDocument.createElement('div'); + label.className = 'bluetooth-device-label'; + this.classList.add('bluetooth-device'); + this.connected = this.data.connected; + // Though strictly speaking, a connected device will also be paired, we + // are interested in tracking paired devices that are not connected. + this.paired = this.data.paired && !this.data.connected; + this.connecting = !!this.data.pairing; + var content = this.data.name; + // Update label for devices that are paired but not connected. + if (this.paired) { + content = content + ' (' + + templateData['bluetoothDeviceNotConnected'] + ')'; + } + label.textContent = content; + this.contentElement.appendChild(label); + }, + }; + + /** + * Class for displaying a list of Bluetooth devices. + * @constructor + * @extends {options.DeletableItemList} + */ + var BluetoothDeviceList = cr.ui.define('list'); + + BluetoothDeviceList.prototype = { + __proto__: DeletableItemList.prototype, + + /** @inheritDoc */ + decorate: function() { + DeletableItemList.prototype.decorate.call(this); + this.addEventListener('blur', this.onBlur_); + this.addEventListener('change', this.onChange_); + this.clear(); + }, + + /** + * When the list loses focus, unselect all items in the list. + * @private + */ + onBlur_: function() { + // TODO(kevers): Should this be pushed up to the list class? + this.selectionModel.unselectAll(); + }, + + /** + * Updates the state of the button for adding a Bluetooth device in + * response to a change in the selected item. + * @private + */ + onChange_: function() { + var item = this.selectedItem; + var disabled = !item || item.paired || item.conencted; + $('bluetooth-add-device-apply-button').disabled = disabled; + }, + + /** + * Adds a bluetooth device to the list of available devices. A check is + * made to see if the device is already in the list, in which case the + * existing device is updated. + * @param {{name: string, + * address: string, + * icon: Constants.DEVICE_TYPE, + * paired: boolean, + * connected: boolean, + * pairing: string|undefined, + * passkey: number|undefined, + * entered: number|undefined}} device + * Description of the bluetooth device. + * @return {boolean} True if the devies was successfully added or updated. + */ + appendDevice: function(device) { + if (!this.isSupported_(device)) + return false; + var index = this.find(device.address); + if (index == undefined) { + this.dataModel.push(device); + this.redraw(); + } else { + this.dataModel.splice(index, 1, device); + this.redrawItem(index); + } + return true; + }, + + /** + * Perges all devices from the list. + */ + clear: function() { + this.dataModel = new ArrayDataModel([]); + this.redraw(); + }, + + /** + * Returns the index of the list entry with the matching address. + * @param {string} address Unique address of the Bluetooth device. + * @return {number|undefined} Index of the matching entry or + * undefined if no match found. + */ + find: function(address) { + var size = this.dataModel.length; + for (var i = 0; i < size; i++) { + var entry = this.dataModel.item(i); + if (entry.address == address) + return i; + } + }, + + /** @inheritDoc */ + createItem: function(entry) { + return new BluetoothListItem(entry); + }, + + /** @inheritDoc */ + deleteItemAtIndex: function(index) { + var item = this.dataModel.item(index); + if (item && (item.connected || item.paired)) { + // Inform the bluetooth adapter that we are disconnecting the device. + chrome.send('updateBluetoothDevice', + [item.address, item.connected ? 'disconnect' : 'forget']); + } + this.dataModel.splice(index, 1); + // Invalidate the list since it has a stale cache after a splice + // involving a deletion. + this.invalidate(); + this.redraw(); + }, + + /** + * Tests if the bluetooth device is supported based on the type of device. + * @param {Object.<string,string>} device Desription of the device. + * @return {boolean} true if the device is supported. + * @private + */ + isSupported_: function(device) { + var target = device.icon; + for (var key in Constants.DEVICE_TYPE) { + if (Constants.DEVICE_TYPE[key] == target) + return true; + } + return false; + } + }; + + cr.defineProperty(BluetoothListItem, 'connected', cr.PropertyKind.BOOL_ATTR); + + cr.defineProperty(BluetoothListItem, 'paired', cr.PropertyKind.BOOL_ATTR); + + cr.defineProperty(BluetoothListItem, 'connecting', cr.PropertyKind.BOOL_ATTR); + + return { + BluetoothListItem: BluetoothListItem, + BluetoothDeviceList: BluetoothDeviceList, + Constants: Constants + }; +}); diff --git a/chrome/browser/resources/options2/chromeos/bluetooth_list_element.js b/chrome/browser/resources/options2/chromeos/bluetooth_list_element.js new file mode 100644 index 0000000..1b6ef82 --- /dev/null +++ b/chrome/browser/resources/options2/chromeos/bluetooth_list_element.js @@ -0,0 +1,387 @@ +// Copyright (c) 2011 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.system.bluetooth', function() { + /** + * Bluetooth settings constants. + */ + function Constants() {} + + /** + * Enumeration of supported device types. Each device type has an + * associated icon and CSS style. + * @enum {string} + */ + Constants.DEVICE_TYPE = { + COMPUTER: 'computer', + HEADSET: 'headset', + KEYBOARD: 'input-keyboard', + MOUSE: 'input-mouse', + PHONE: 'phone', + }; + + /** + * Enumeration of possible states for a bluetooth device. The value + * associated with each state maps to a localized string in the global + * variable 'templateData'. + * @enum {string} + */ + Constants.DEVICE_STATUS = { + CONNECTED: 'bluetoothDeviceConnected', + CONNECTING: 'bluetoothDeviceConnecting', + FAILED_PAIRING: 'bluetoothDeviceFailedPairing', + NOT_PAIRED: 'bluetoothDeviceNotPaired', + PAIRED: 'bluetoothDevicePaired' + }; + + /** + * Enumeration of possible states during pairing. The value associated + * with each state maps to a loalized string in the global variable + * 'tempateData'. + * @enum {string} + */ + Constants.PAIRING = { + CONFIRM_PASSKEY: 'bluetoothConfirmPasskey', + ENTER_PASSKEY: 'bluetoothEnterPasskey', + FAILED_CONNECT_INSTRUCTIONS: 'bluetoothFailedPairingInstructions', + REMOTE_PASSKEY: 'bluetoothRemotePasskey' + }; + + /** + * Creates an element for storing a list of bluetooth devices. + * @param {Object=} opt_propertyBag Optional properties. + * @constructor + * @extends {HTMLDivElement} + */ + var BluetoothListElement = cr.ui.define('div'); + + BluetoothListElement.prototype = { + __proto__: HTMLDivElement.prototype, + + /** @inheritDoc */ + decorate: function() { + }, + + /** + * Loads given list of bluetooth devices. This list will comprise of + * devices that are currently connected. New devices are discovered + * via the 'Find devices' button. + * @param {Array} devices An array of bluetooth devices. + */ + load: function(devices) { + this.textContent = ''; + for (var i = 0; i < devices.length; i++) { + if (this.isSupported_(devices[i])) + this.appendChild(new BluetoothItem(devices[i])); + } + }, + + /** + * Adds a bluetooth device to the list of available devices. A check is + * made to see if the device is already in the list, in which case the + * existing device is updated. + * @param {Object.<string,string>} device Description of the bluetooth + * device. + * @return {boolean} True if the devies was successfully added or updated. + */ + appendDevice: function(device) { + if (!this.isSupported_(device)) + return false; + var item = new BluetoothItem(device); + var existing = this.findDevice(device.address); + if (existing) + this.replaceChild(item, existing); + else + this.appendChild(item); + return true; + }, + + /** + * Scans the list of elements corresponding to discovered Bluetooth + * devices for one with a matching address. + * @param {string} address The address of the device. + * @return {Element|undefined} Element corresponding to the device address + * or undefined if no corresponding element is found. + */ + findDevice: function(address) { + var candidate = this.firstChild; + while (candidate) { + if (candidate.data.address == address) + return candidate; + candidate = candidate.nextSibling; + } + }, + + /** + * Tests if the bluetooth device is supported based on the type of device. + * @param {Object.<string,string>} device Desription of the device. + * @return {boolean} true if the device is supported. + * @private + */ + isSupported_: function(device) { + var target = device.icon; + for (var key in Constants.DEVICE_TYPE) { + if (Constants.DEVICE_TYPE[key] == target) + return true; + } + return false; + } + }; + + /** + * Creates an element in the list of bluetooth devices. + * @param {{name: string, + * address: string, + * icon: Constants.DEVICE_TYPE, + * paired: boolean, + * connected: boolean, + * pairing: string|undefined, + * passkey: number|undefined, + * entered: number|undefined}} device + * Decription of the bluetooth device. + * @constructor + */ + function BluetoothItem(device) { + var el = $('bluetooth-item-template').cloneNode(true); + el.__proto__ = BluetoothItem.prototype; + el.removeAttribute('id'); + el.hidden = false; + el.data = {}; + for (var key in device) + el.data[key] = device[key]; + el.decorate(); + return el; + } + + BluetoothItem.prototype = { + __proto__: HTMLDivElement.prototype, + + /** @inheritDoc */ + decorate: function() { + this.className = 'bluetooth-item'; + this.connected = this.data.connected; + // Though strictly speaking, a connected device will also be paired, + // we are interested in tracking paired devices that are not connected. + this.paired = this.data.paired && !this.data.connected; + this.connecting = !!this.data.pairing; + this.addLabels_(); + this.addButtons_(); + }, + + /** + * Retrieves the descendent element with the matching class name. + * @param {string} className The class name for the target element. + * @return {Element|undefined} Returns the matching element if + * found and unique. + * @private + */ + getNodeByClass_:function(className) { + var elements = this.getElementsByClassName(className); + if (elements && elements.length == 1) + return elements[0]; + }, + + /** + * Sets the text content for an element. + * @param {string} className The class name of the target element. + * @param {string} label Text content for the element. + * @private + */ + setLabel_: function(className, label) { + var el = this.getNodeByClass_(className); + el.textContent = label; + }, + + /** + * Adds an element containing the display name, status and device pairing + * instructions. + * @private + */ + addLabels_: function() { + this.setLabel_('network-name-label', this.data.name); + var status; + if (this.data.connected) + status = Constants.DEVICE_STATUS.CONNECTED; + else if (this.data.pairing) + status = Constants.DEVICE_STATUS.CONNECTING; + if (status) { + var statusMessage = templateData[status]; + if (statusMessage) + this.setLabel_('network-status-label', statusMessage); + if (this.connecting) { + var spinner = this.getNodeByClass_('inline-spinner'); + spinner.hidden = false; + } + } + if (this.data.pairing) + this.addPairingInstructions_(); + }, + + /** + * Adds instructions on how to complete the pairing process. + * @param {!Element} textDiv Target element for inserting the instructions. + * @private + */ + addPairingInstructions_: function() { + var instructionsEl = this.getNodeByClass_('bluetooth-instructions'); + var message = templateData[this.data.pairing]; + var array = this.formatInstructions_(message); + for (var i = 0; i < array.length; i++) { + instructionsEl.appendChild(array[i]); + } + if (this.data.pairing == Constants.PAIRING.ENTER_PASSKEY) { + var input = this.ownerDocument.createElement('input'); + input.type = 'text'; + input.className = 'bluetooth-passkey-field'; + instructionsEl.appendChild(input); + } + }, + + /** + * Formats the pairing instruction, which may contain labels for + * substitution. The label '%1' is replaced with the passkey, and '%2' + * is replaced with the name of the bluetooth device. Formatting of the + * passkey depends on the type of validation. + * @param {string} instructions The source instructions to format. + * @return {Array.<Element>} Array of formatted elements. + */ + formatInstructions_: function(instructions) { + var array = []; + var index = instructions.indexOf('%'); + if (index >= 0) { + array.push(this.createTextElement_(instructions.substring(0, index))); + var labelPlaceholder = instructions.charAt(index + 1); + // ... handle the placeholder + switch (labelPlaceholder) { + case '1': + array.push(this.createPasskeyElement_()); + break; + case '2': + array.push(this.createTextElement_(this.data.name)); + } + array = array.concat(this.formatInstructions_(instructions.substring( + index + 2))); + } else { + array.push(this.createTextElement_(instructions)); + } + return array; + }, + + /** + * Formats an element for displaying the passkey. + * @return {Element} Element containing the passkey. + */ + createPasskeyElement_: function() { + var passkeyEl = document.createElement('div'); + if (this.data.pairing == Constants.PAIRING.REMOTE_PASSKEY) { + passkeyEl.className = 'bluetooth-remote-passkey'; + var key = String(this.data.passkey); + var progress = this.data.entered; + for (var i = 0; i < key.length; i++) { + var keyEl = document.createElement('div'); + keyEl.textContent = key.charAt(i); + keyEl.className = 'bluetooth-passkey-char'; + if (i < progress) + keyEl.classList.add('key-typed'); + passkeyEl.appendChild(keyEl); + } + // Add return key symbol. + var keyEl = document.createElement('div'); + keyEl.className = 'bluetooth-passkey-char'; + keyEl.textContent = '\u23ce'; + passkeyEl.appendChild(keyEl); + } else { + passkeyEl.className = 'bluetooth-confirm-passkey'; + passkeyEl.textContent = this.data.passkey; + } + return passkeyEl; + }, + + /** + * Adds a text element. + * @param {string} text The text content of the new element. + * @param {string=} opt_style Optional parameter for the CSS class for + * formatting the text element. + * @return {Element} Element containing the text. + */ + createTextElement_: function(text, array, opt_style) { + var el = this.ownerDocument.createElement('span'); + el.textContent = text; + if (opt_style) + el.className = opt_style; + return el; + }, + + /** + * Adds buttons for updating the connectivity of a device. + * @private. + */ + addButtons_: function() { + var buttonsDiv = this.getNodeByClass_('bluetooth-button-group'); + var buttonLabelKey = null; + var callbackType = null; + if (this.connected) { + buttonLabelKey = 'bluetoothDisconnectDevice'; + callbackType = 'disconnect'; + } else if (this.paired) { + buttonLabelKey = 'bluetoothForgetDevice'; + callbackType = 'forget'; + } else if (this.connecting) { + if (this.data.pairing == Constants.PAIRING.CONFIRM_PASSKEY) { + buttonLabelKey = 'bluetoothRejectPasskey'; + callbackType = 'reject'; + } else { + buttonLabelKey = 'bluetoothCancel'; + callbackType = 'cancel'; + } + } else { + buttonLabelKey = 'bluetoothConnectDevice'; + callbackType = 'connect'; + } + if (buttonLabelKey && callbackType) { + var buttonEl = this.ownerDocument.createElement('button'); + buttonEl.textContent = localStrings.getString(buttonLabelKey); + var self = this; + var callback = function(e) { + chrome.send('updateBluetoothDevice', + [self.data.address, callbackType]); + } + buttonEl.addEventListener('click', callback); + buttonsDiv.appendChild(buttonEl); + } + if (this.data.pairing == Constants.PAIRING.CONFIRM_PASSKEY || + this.data.pairing == Constants.PAIRING.ENTER_PASSKEY) { + var buttonEl = this.ownerDocument.createElement('button'); + buttonEl.className = 'accept-pairing-button'; + var msg = this.data.pairing == Constants.PAIRING.CONFIRM_PASSKEY ? + 'bluetoothAcceptPasskey' : 'bluetoothConnectDevice'; + buttonEl.textContent = localStrings.getString(msg); + var self = this; + var callback = function(e) { + var passkey = self.data.passkey; + if (self.data.pairing == Constants.PAIRING.ENTER_PASSKEY) { + var passkeyField = self.getNodeByClass_('bluetooth-passkey-field'); + passkey = passkeyField.value; + } + chrome.send('updateBluetoothDevice', + [self.data.address, 'connect', String(passkey)]); + } + buttonEl.addEventListener('click', callback); + buttonsDiv.insertBefore(buttonEl, buttonsDiv.firstChild); + } + this.appendChild(buttonsDiv); + } + }; + + cr.defineProperty(BluetoothItem, 'connected', cr.PropertyKind.BOOL_ATTR); + + cr.defineProperty(BluetoothItem, 'paired', cr.PropertyKind.BOOL_ATTR); + + cr.defineProperty(BluetoothItem, 'connecting', cr.PropertyKind.BOOL_ATTR); + + return { + Constants: Constants, + BluetoothListElement: BluetoothListElement + }; +}); diff --git a/chrome/browser/resources/options2/chromeos/cellular_plan_element.js b/chrome/browser/resources/options2/chromeos/cellular_plan_element.js new file mode 100644 index 0000000..2eaa7d3 --- /dev/null +++ b/chrome/browser/resources/options2/chromeos/cellular_plan_element.js @@ -0,0 +1,132 @@ +// Copyright (c) 2011 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.internet', function() { + /** + * Creates a new network list div. + * @param {Object=} opt_propertyBag Optional properties. + * @constructor + * @extends {HTMLDivElement} + */ + var CellularPlanElement = cr.ui.define('div'); + + CellularPlanElement.prototype = { + __proto__: HTMLDivElement.prototype, + + /** @inheritDoc */ + decorate: function() { + }, + + /** + * Loads given network list. + * @param {Array} networks An array of network object. + */ + load: function(plans) { + this.textContent = ''; + if (!plans || !plans.length) { + var noplansDiv = this.ownerDocument.createElement('div'); + noplansDiv.textContent = localStrings.getString('noPlansFound'); + this.appendChild(detailsTable); + } else { + for (var i = 0; i < plans.length; ++i) { + this.appendChild(new CellularPlanItem(i, plans[i])); + } + } + } + }; + + /** + * Creates a new network item. + * @param {Object} network The network this represents. + * @constructor + * @extends {HTMLDivElement} + */ + function CellularPlanItem(idx, plan) { + var el = cr.doc.createElement('div'); + el.data = { + idx: idx, + planType: plan.planType, + name: plan.name, + planSummary: plan.planSummary, + dataRemaining: plan.dataRemaining, + planExpires: plan.planExpires, + warning: plan.warning + }; + CellularPlanItem.decorate(el); + return el; + } + + + /** + * Decorates an element as a network item. + * @param {!HTMLElement} el The element to decorate. + */ + CellularPlanItem.decorate = function(el) { + el.__proto__ = CellularPlanItem.prototype; + el.decorate(); + }; + + CellularPlanItem.prototype = { + __proto__: HTMLDivElement.prototype, + + /** @inheritDoc */ + decorate: function() { + this.className = 'cellular-plan'; + var detailsTable = this.createTable_('details-plan-table', + 'option-control-table'); + this.addRow_(detailsTable, 'plan-details-info', + 'option-name', 'planSummary', this.data.planSummary); + this.addRow_(detailsTable, 'plan-details-info', + 'option-name', null, localStrings.getString('planName'), + 'option-value', 'planName', this.data.name); + this.addRow_(detailsTable, 'plan-details-info', + 'option-name', null, localStrings.getString('dataRemaining'), + 'option-value', 'dataRemaining', this.data.dataRemaining); + this.addRow_(detailsTable, 'plan-details-info', + 'option-name', null, localStrings.getString('planExpires'), + 'option-value', 'dataRemaining', this.data.planExpires); + if (this.data.warning && this.data.warning != "") { + this.addRow_(detailsTable, 'plan-details-info', + 'option-name', 'planWarning', this.data.warning); + } + this.appendChild(detailsTable); + this.appendChild(this.ownerDocument.createElement('hr')); + }, + + createTable_: function(tableId, tableClass) { + var table = this.ownerDocument.createElement('table'); + table.id = tableId; + table.className = tableClass; + return table; + }, + + addRow_: function(table, rowClass, col1Class, col1Id, col1Value, + col2Class, col2Id, col2Value) { + var row = this.ownerDocument.createElement('tr'); + if (rowClass) + row.className = rowClass; + var col1 = this.ownerDocument.createElement('td'); + col1.className = col1Class; + if (col1Id) + col1.id = col1Id; + col1.textContent = col1Value; + if (!col2Class) + col1.setAttribute('colspan','2'); + row.appendChild(col1); + if (col2Class) { + var col2 = this.ownerDocument.createElement('td'); + col2.className = col2Class; + if (col2Id) + col2.id = col2Id; + col2.textContent = col2Value; + row.appendChild(col2); + } + table.appendChild(row); + } + }; + + return { + CellularPlanElement: CellularPlanElement + }; +}); diff --git a/chrome/browser/resources/options2/chromeos/change_picture_options.css b/chrome/browser/resources/options2/chromeos/change_picture_options.css new file mode 100644 index 0000000..ced9728 --- /dev/null +++ b/chrome/browser/resources/options2/chromeos/change_picture_options.css @@ -0,0 +1,32 @@ +#images-grid { + -webkit-user-drag: none; + -webkit-user-select: none; + margin: 10px; + outline: none; + padding: 10px; +} + +#images-grid * { + margin: 0; + padding: 0; +} + +#images-grid img { + background-color: white; + height: 64px; + vertical-align: middle; + width: 64px; +} + +#images-grid [role=listitem] { + border-radius: 4px; + border: 1px solid rgba(0, 0, 0, 0.15); + display: inline-block; + margin: 10px; + padding: 3px; +} + +#images-grid [selected] { + border: 2px solid #06c; + padding: 2px; +} diff --git a/chrome/browser/resources/options2/chromeos/change_picture_options.html b/chrome/browser/resources/options2/chromeos/change_picture_options.html new file mode 100644 index 0000000..cafb7e6 --- /dev/null +++ b/chrome/browser/resources/options2/chromeos/change_picture_options.html @@ -0,0 +1,5 @@ +<div id="change-picture-page" class="page" hidden> + <h1 i18n-content="changePicturePage"></h1> + <span i18n-content="changePicturePageDescription"></span> + <grid id="images-grid"></grid> +</div> diff --git a/chrome/browser/resources/options2/chromeos/change_picture_options.js b/chrome/browser/resources/options2/chromeos/change_picture_options.js new file mode 100644 index 0000000..b986743 --- /dev/null +++ b/chrome/browser/resources/options2/chromeos/change_picture_options.js @@ -0,0 +1,267 @@ +// Copyright (c) 2011 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 UserImagesGrid = options.UserImagesGrid; + var ButtonImages = UserImagesGrid.ButtonImages; + + /** + * Array of button URLs used on this page. + * @type {Array.<string>} + */ + const ButtonImageUrls = [ + ButtonImages.TAKE_PHOTO, + ButtonImages.CHOOSE_FILE + ]; + + ///////////////////////////////////////////////////////////////////////////// + // ChangePictureOptions class: + + /** + * Encapsulated handling of ChromeOS change picture options page. + * @constructor + */ + function ChangePictureOptions() { + OptionsPage.call( + this, + 'changePicture', + localStrings.getString('changePicturePage'), + 'change-picture-page'); + } + + cr.addSingletonGetter(ChangePictureOptions); + + ChangePictureOptions.prototype = { + // Inherit ChangePictureOptions from OptionsPage. + __proto__: options.OptionsPage.prototype, + + /** + * Initializes ChangePictureOptions page. + */ + initializePage: function() { + // Call base class implementation to start preferences initialization. + OptionsPage.prototype.initializePage.call(this); + + var imageGrid = $('images-grid'); + UserImagesGrid.decorate(imageGrid); + + imageGrid.addEventListener('change', + this.handleImageSelected_.bind(this)); + imageGrid.addEventListener('activate', + this.handleImageActivated_.bind(this)); + imageGrid.addEventListener('dblclick', + this.handleImageDblClick_.bind(this)); + + // Add the "Choose file" button. + imageGrid.addItem(ButtonImages.CHOOSE_FILE, + localStrings.getString('chooseFile'), + this.handleChooseFile_.bind(this)); + + // Profile image data. + this.profileImage_ = imageGrid.addItem( + ButtonImages.PROFILE_PICTURE, + localStrings.getString('profilePhotoLoading')); + + // Old user image data (if present). + this.oldImage_ = null; + + chrome.send('onChangePicturePageInitialized'); + }, + + /** + * Called right after the page has been shown to user. + */ + didShowPage: function() { + $('images-grid').updateAndFocus(); + chrome.send('onChangePicturePageShown'); + }, + + /** + * Called right before the page is hidden. + */ + willHidePage: function() { + var imageGrid = $('images-grid'); + imageGrid.blur(); // Make sure the image grid is not active. + if (this.oldImage_) { + imageGrid.removeItem(this.oldImage_); + this.oldImage_ = null; + } + }, + + /** + * Closes current page, returning back to Personal Stuff page. + * @private + */ + closePage_: function() { + OptionsPage.navigateToPage('personal'); + }, + + /** + * Handles "Take photo" button activation. + * @private + */ + handleTakePhoto_: function() { + chrome.send('takePhoto'); + this.closePage_(); + }, + + /** + * Handles "Choose a file" button activation. + * @private + */ + handleChooseFile_: function() { + chrome.send('chooseFile'); + this.closePage_(); + }, + + /** + * Handles image selection change. + * @private + */ + handleImageSelected_: function() { + var imageGrid = $('images-grid'); + var url = imageGrid.selectedItemUrl; + // Ignore deselection, selection change caused by program itself and + // selection of one of the action buttons. + if (url && + !imageGrid.inProgramSelection && + ButtonImageUrls.indexOf(url) == -1) { + chrome.send('selectImage', [url]); + } + }, + + /** + * Handles image activation (by pressing Enter). + * @private + */ + handleImageActivated_: function() { + switch ($('images-grid').selectedItemUrl) { + case ButtonImages.TAKE_PHOTO: + this.handleTakePhoto_(); + break; + case ButtonImages.CHOOSE_FILE: + this.handleChooseFile_(); + break; + default: + this.closePage_(); + break; + } + }, + + /** + * Handles double click on the image grid. + * @param {Event} e Double click Event. + */ + handleImageDblClick_: function(e) { + // Close page unless the click target is the grid itself or + // any of the buttons. + var url = e.target.src; + if (url && ButtonImageUrls.indexOf(url) == -1) + this.closePage_(); + }, + + /** + * URL of the current user image. + * @type {string} + */ + get currentUserImageUrl() { + return 'chrome://userimage/' + PersonalOptions.getLoggedInUsername() + + '?id=' + (new Date()).getTime(); + }, + + /** + * Notifies about camera presence change. + * @param {boolean} present Whether a camera is present or not. + * @private + */ + setCameraPresent_: function(present) { + var imageGrid = $('images-grid'); + if (present && !this.takePhotoButton_) { + this.takePhotoButton_ = imageGrid.addItem( + ButtonImages.TAKE_PHOTO, + localStrings.getString('takePhoto'), + this.handleTakePhoto_.bind(this), + 1); + } else if (!present && this.takePhotoButton_) { + imageGrid.removeItem(this.takePhotoButton_); + this.takePhotoButton_ = null; + } + }, + + /** + * Adds or updates old user image taken from file/camera (neither a profile + * image nor a default one). + * @private + */ + setOldImage_: function() { + var imageGrid = $('images-grid'); + var url = this.currentUserImageUrl; + if (this.oldImage_) { + this.oldImage_ = imageGrid.updateItem(this.oldImage_, url); + } else { + // Insert next to the profile image. + var pos = imageGrid.indexOf(this.profileImage_) + 1; + this.oldImage_ = imageGrid.addItem(url, undefined, undefined, pos); + imageGrid.selectedItem = this.oldImage_; + } + }, + + /** + * Updates user's profile image. + * @param {string} imageUrl Profile image, encoded as data URL. + * @param {boolean} select If true, profile image should be selected. + * @private + */ + setProfileImage_: function(imageUrl, select) { + var imageGrid = $('images-grid'); + this.profileImage_ = imageGrid.updateItem( + this.profileImage_, imageUrl, localStrings.getString('profilePhoto')); + if (select) + imageGrid.selectedItem = this.profileImage_; + }, + + /** + * Selects user image with the given URL. + * @param {string} url URL of the image to select. + * @private + */ + setSelectedImage_: function(url) { + $('images-grid').selectedItemUrl = url; + }, + + /** + * Appends default images to the image grid. Should only be called once. + * @param {Array.<string>} images An array of URLs to default images. + * @private + */ + setDefaultImages_: function(images) { + var imageGrid = $('images-grid'); + for (var i = 0, url; url = images[i]; i++) { + imageGrid.addItem(url); + } + }, + }; + + // Forward public APIs to private implementations. + [ + 'setCameraPresent', + 'setDefaultImages', + 'setOldImage', + 'setProfileImage', + 'setSelectedImage', + ].forEach(function(name) { + ChangePictureOptions[name] = function(value1, value2) { + ChangePictureOptions.getInstance()[name + '_'](value1, value2); + }; + }); + + // Export + return { + ChangePictureOptions: ChangePictureOptions + }; + +}); + diff --git a/chrome/browser/resources/options2/chromeos/internet_detail.html b/chrome/browser/resources/options2/chromeos/internet_detail.html new file mode 100644 index 0000000..d722066 --- /dev/null +++ b/chrome/browser/resources/options2/chromeos/internet_detail.html @@ -0,0 +1,364 @@ +<div id="detailsInternetPage" class="page" hidden> + <h1 id="inetTitle"></h1> + <!-- Navigation tabs --> + <div id="details-tab-strip" class="subpages-nav-tabs"> + <span id="wifiNetworkNavTab" class="tab wifi-details" + tab-contents="wifiNetworkTab"> + <span class="tab-label" + i18n-content="wifiNetworkTabLabel"></span> + <span class="active-tab-label" i18n-content="wifiNetworkTabLabel"></span> + </span> + <span id="vpnNavTab" class="tab vpn-details" + tab-contents="vpnTab"> + <span class="tab-label" + i18n-content="vpnTabLabel"></span> + <span class="active-tab-label" i18n-content="vpnTabLabel"></span> + </span> + <span id="cellularPlanNavTab" class="tab cellular-details cdma-only" + tab-contents="cellularPlanTab"> + <span class="tab-label" + i18n-content="cellularPlanTabLabel"></span> + <span class="active-tab-label" i18n-content="cellularPlanTabLabel"></span> + </span> + <span id="cellularConnNavTab" class="tab cellular-details" + tab-contents="cellularConnTab"> + <span class="tab-label" + i18n-content="cellularConnTabLabel"></span> + <span class="active-tab-label" i18n-content="cellularConnTabLabel"></span> + </span> + <span id="cellularDeviceNavTab" class="tab cellular-details" + tab-contents="cellularDeviceTab"> + <span class="tab-label" + i18n-content="cellularDeviceTabLabel"></span> + <span class="active-tab-label" + i18n-content="cellularDeviceTabLabel"></span> + </span> + <span id="internetNavTab" class="tab" tab-contents="internetTab"> + <span class="tab-label" i18n-content="networkTabLabel"></span> + <span class="active-tab-label" i18n-content="networkTabLabel"></span> + </span> + <span id="security-nav-tab" class="tab cellular-details gsm-only" + tab-contents="security-tab"> + <span class="tab-label" i18n-content="securityTabLabel"></span> + <span class="active-tab-label" i18n-content="securityTabLabel"></span> + </span> + </div> + <div id="wifiNetworkTab" class="subpages-tab-contents wifi-details"> + <section> + <table class="ssid-table"> + <tr> + <td class="option-name" i18n-content="inetSsid"></td> + <td id="inetSsid" class="option-value"></td> + </tr> + </table> + </section> + <section> + <table class="option-control-table"> + <tr class="prefer-network"> + <td> + <div class="checkbox"> + <label> + <input id="preferNetworkWifi" type="checkbox"> + <span i18n-content="inetPreferredNetwork"></span> + </label> + <span class="controlled-setting-indicator" data="preferred" + for="preferNetworkWifi"></span> + </div> + </td> + </tr> + <tr class="auto-connect-network"> + <td> + <div class="checkbox"> + <label> + <input id="autoConnectNetworkWifi" type="checkbox"> + <span i18n-content="inetAutoConnectNetwork"></span> + </label> + <span class="controlled-setting-indicator" data="autoConnect" + for="autoConnectNetworkWifi"></span> + </div> + </td> + </tr> + </table> + </section> + <section id="passwordNetwork" class="password-details"> + <table class="option-control-table"> + <tr> + <td class="option-name" i18n-content="inetPassProtected"></td> + </tr> + </table> + </section> + <section id="sharedNetwork" class="shared-network"> + <table class="option-control-table"> + <tr> + <td class="option-name" i18n-content="inetNetworkShared"></td> + </tr> + </table> + </section> + </div> + <div id="vpnTab" class="subpages-tab-contents vpn-details"> + <section> + <table class="option-control-table"> + <tr> + <td class="option-name" i18n-content="inetServiceName"></td> + <td id="inetServiceName" class="option-value"></td> + </tr> + <tr> + <td class="option-name" i18n-content="inetServerHostname"></td> + <td id="inetServerHostname" class="option-value"></td> + </tr> + <tr> + <td class="option-name" i18n-content="inetProviderType"></td> + <td id="inetProviderType" class="option-value"></td> + </tr> + <tr> + <td class="option-name" i18n-content="inetUsername"></td> + <td id="inetUsername" class="option-value"></td> + </tr> + </table> + </section> + </div> + <div id="cellularPlanTab" + class="subpages-tab-contents cellular-details cdma-only"> + <section> + <div> + <table id="details-plan-table" class="option-control-table"> + <tr class="plan-loading-info"> + <td i18n-content="planLoading" class="option-value"></td> + </tr> + <tr class="no-plan-info"> + <td i18n-content="noPlansFound" class="option-value"></td> + </tr> + </table> + </div> + <div id="planList"></div> + </section> + <section class="plan-details-info"> + <div class="checkbox"> + <label> + <input id="showPlanNotifications" type="checkbox" + pref="settings.internet.mobile.show_plan_notifications"> + <span i18n-content="showPlanNotifications"></span> + </label> + </div> + </section> + </div> + <div id="cellularConnTab" class="subpages-tab-contents cellular-details"> + <section id="cellularNetworkOptions"> + <table class="option-control-table"> + <tr> + <td class="option-name" i18n-content="serviceName"></td> + <td id="serviceName" class="option-value"></td> + </tr> + <tr> + <td class="option-name" i18n-content="networkTechnology"></td> + <td id="networkTechnology" class="option-value"></td> + </tr> + <tr> + <td class="option-name" i18n-content="activationState"></td> + <td id="activationState" class="option-value"></td> + </tr> + <tr> + <td class="option-name" i18n-content="roamingState"></td> + <td id="roamingState" class="option-value"></td> + </tr> + <tr> + <td class="option-name" i18n-content="restrictedPool"></td> + <td id="restrictedPool" class="option-value"></td> + </tr> + <tr class="gsm-only"> + <td class="option-name" i18n-content="operatorName"></td> + <td id="operatorName" class="option-value"></td> + </tr> + <tr class="gsm-only"> + <td class="option-name" i18n-content="operatorCode"></td> + <td id="operatorCode" class="option-value"></td> + </tr> + <tr> + <td class="option-name" i18n-content="errorState"></td> + <td id="errorState" class="option-value"></td> + </tr> + <tr class="gsm-only apn-list-view"> + <td class="option-name" i18n-content="cellularApnLabel"></td> + <td id="cellularApnLabel" class="option-value"> + <select id="selectApn"> + <option value="-1" i18n-content="cellularApnOther"> + </option> + </select> + <span class="controlled-setting-indicator" data="providerApnList" + for="selectApn"></span> + </td> + </tr> + <tr class="gsm-only apn-details-view"> + <td class="option-name" i18n-content="cellularApnLabel"></td> + <td id="cellularApnLabel" class="option-value"> + <input id="cellularApn" type="text"> + </td> + </tr> + <tr class="gsm-only apn-details-view"> + <td class="option-name" i18n-content="cellularApnUsername"></td> + <td id="cellularApnUsernameLabel" class="option-value"> + <input id="cellularApnUsername" type="text"> + </td> + </tr> + <tr class="gsm-only apn-details-view"> + <td class="option-name" i18n-content="cellularApnPassword"></td> + <td id="cellularApnPasswordLabel" class="option-value"> + <input id="cellularApnPassword" type="password"> + </td> + </tr> + <tr class="gsm-only apn-details-view"> + <td class="option-name"></td> + <td class="option-value"> + <button id="cellularApnUseDefault" + i18n-content="cellularApnUseDefault"></button> + <button id="cellularApnSet" + i18n-content="cellularApnSet"></button> + <button id="cellularApnCancel" + i18n-content="cellularApnCancel"></button> + </td> + </tr> + <tr> + <td colspan="2"> + <div class="checkbox"> + <label> + <input id="autoConnectNetworkCellular" type="checkbox"> + <span i18n-content="inetAutoConnectNetwork"></span> + </label> + <span class="controlled-setting-indicator" data="autoConnect" + for="autoConnectNetworkCellular"></span> + </div> + </td> + </tr> + </table> + </section> + </div> + <div id="cellularDeviceTab" class="subpages-tab-contents cellular-details"> + <section id="cellularDeviceOptions"> + <table class="option-control-table"> + <tr> + <td class="option-name" i18n-content="manufacturer"></td> + <td id="manufacturer" class="option-value"></td> + </tr> + <tr> + <td class="option-name" i18n-content="modelId"></td> + <td id="modelId" class="option-value"></td> + </tr> + <tr> + <td class="option-name" i18n-content="firmwareRevision"></td> + <td id="firmwareRevision" class="option-value"></td> + </tr> + <tr> + <td class="option-name" i18n-content="hardwareRevision"></td> + <td id="hardwareRevision" class="option-value"></td> + </tr> + <tr> + <td class="option-name" i18n-content="prlVersion"></td> + <td id="prlVersion" class="option-value"></td> + </tr> + <tr> + <td class="option-name">MEID:</td> + <td id="meid" class="option-value"></td> + </tr> + <tr> + <td class="option-name">ESN:</td> + <td id="esn" class="option-value"></td> + </tr> + <tr> + <td class="option-name">IMEI:</td> + <td id="imei" class="option-value"></td> + </tr> + <tr class="gsm-only"> + <td class="option-name">IMSI:</td> + <td id="imsi" class="option-value"></td> + </tr> + <tr> + <td class="option-name">MDN:</td> + <td id="mdn" class="option-value"></td> + </tr> + <tr> + <td class="option-name">MIN:</td> + <td id="min" class="option-value"></td> + </tr> + </table> + </section> + </div> + <div id="internetTab" class="subpages-tab-contents"> + <section id="advancedSection"> + <table class="option-control-table"> + <tr> + <td class="option-name" i18n-content="connectionState"></td> + <td id="connectionState" class="option-value"></td> + </tr> + <tr id="hardwareAddressRow"> + <td class="option-name" i18n-content="hardwareAddress"></td> + <td id="hardwareAddress" class="option-value"></td> + </tr> + </table> + </section> + <section id="ipconfigSection"> + <div> + <div id="ipTypeDHCPDiv" class="radio"> + <label> + <input type="radio" name="iptype" id="ipTypeDHCP" value="1"> + <span i18n-content="useDHCP"></span> + </label> + <span class="controlled-setting-indicator" data="ipconfigDHCP" + for="ipTypeDHCP"></span> + </div> + <div id="ipTypeStaticDiv" class="radio"> + <label> + <input type="radio" name="iptype" id="ipTypeStatic" value="0"> + <span i18n-content="useStaticIP"></span> + </label> + <span class="controlled-setting-indicator" data="ipconfigStatic" + for="ipTypeStatic"></span> + </div> + <div class="suboption"> + <div id="ipConfigManagement" class="settings-list"> + <list id="ipConfigList"></list> + </div> + </div> + </div> + </section> + <section id="change-proxy-section"> + <div> + <button id="change-proxy-button" i18n-content="changeProxyButton"> + </button> + </div> + </section> + </div> + <div id="security-tab" + class="subpages-tab-contents cellular-details gsm-only"> + <div id="cellular-security-options"> + <section> + <div id="sim-pin-lock" class="checkbox"> + <label> + <input id="sim-card-lock-enabled" type="checkbox"> + <span i18n-content="lockSimCard"></span> + </label> + <span class="controlled-setting-indicator" data="simCardLockEnabled" + for="sim-card-lock-enabled"></span> + </div> + </section> + <section> + <div id="change-pin-area"> + <button id="change-pin" i18n-content="changePinButton"></button> + <span class="controlled-setting-indicator" data="simCardLockEnabled" + for="change-pin"></span> + </div> + </section> + </div> + </div> + <div class="action-area bottom-strip button-strip"> + <button id="detailsInternetDismiss" + i18n-content="detailsInternetDismiss"></button> + <button id="detailsInternetLogin" + i18n-content="connect_button"></button> + <button id="detailsInternetDisconnect" + i18n-content="disconnect_button"></button> + <button id="activateDetails" + i18n-content="activate_button"></button> + <button id="buyplanDetails" + i18n-content="buyplan_button"></button> + </div> +</div> diff --git a/chrome/browser/resources/options2/chromeos/internet_detail.js b/chrome/browser/resources/options2/chromeos/internet_detail.js new file mode 100644 index 0000000..5fb1e4e --- /dev/null +++ b/chrome/browser/resources/options2/chromeos/internet_detail.js @@ -0,0 +1,204 @@ +// Copyright (c) 2011 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.internet', function() { + var OptionsPage = options.OptionsPage; + + /* + * Helper function to set hidden attribute on given element list. + * @param {Array} elements List of elements to be updated. + * @param {bool} hidden New hidden value. + */ + function updateHidden(elements, hidden) { + for (var i = 0, el; el = elements[i]; i++) { + el.hidden = hidden; + } + } + + ///////////////////////////////////////////////////////////////////////////// + // DetailsInternetPage class: + + /** + * Encapsulated handling of ChromeOS internet details overlay page. + * @constructor + */ + function DetailsInternetPage() { + OptionsPage.call(this, 'detailsInternetPage', null, 'detailsInternetPage'); + } + + cr.addSingletonGetter(DetailsInternetPage); + + DetailsInternetPage.prototype = { + __proto__: OptionsPage.prototype, + + /** + * Initializes DetailsInternetPage page. + * Calls base class implementation to starts preference initialization. + */ + initializePage: function() { + OptionsPage.prototype.initializePage.call(this); + }, + + /** + * Update details page controls. + * @private + */ + updateControls_: function() { + // Only show ipconfig section if network is connected OR if nothing on + // this device is connected. This is so that you can fix the ip configs + // if you can't connect to any network. + // TODO(chocobo): Once ipconfig is moved to flimflam service objects, + // we need to redo this logic to allow configuration of all networks. + $('ipconfigSection').hidden = !this.connected && this.deviceConnected; + + // Network type related. + updateHidden( + cr.doc.querySelectorAll('#detailsInternetPage .cellular-details'), + !this.cellular); + updateHidden( + cr.doc.querySelectorAll('#detailsInternetPage .wifi-details'), + !this.wireless); + updateHidden( + cr.doc.querySelectorAll('#detailsInternetPage .vpn-details'), + !this.vpn); + + // Cell plan related. + $('planList').hidden = this.cellplanloading; + updateHidden( + cr.doc.querySelectorAll('#detailsInternetPage .no-plan-info'), + !this.cellular || this.cellplanloading || this.hascellplan); + updateHidden( + cr.doc.querySelectorAll('#detailsInternetPage .plan-loading-info'), + !this.cellular || this.nocellplan || this.hascellplan); + updateHidden( + cr.doc.querySelectorAll('#detailsInternetPage .plan-details-info'), + !this.cellular || this.nocellplan || this.cellplanloading); + updateHidden( + cr.doc.querySelectorAll('#detailsInternetPage .gsm-only'), + !this.cellular || !this.gsm); + updateHidden( + cr.doc.querySelectorAll('#detailsInternetPage .cdma-only'), + !this.cellular || this.gsm); + updateHidden( + cr.doc.querySelectorAll('#detailsInternetPage .apn-list-view'), + !this.cellular || !this.gsm); + updateHidden( + cr.doc.querySelectorAll('#detailsInternetPage .apn-details-view'), + true); + + // Password and shared. + updateHidden( + cr.doc.querySelectorAll('#detailsInternetPage .password-details'), + !this.wireless || !this.password); + updateHidden( + cr.doc.querySelectorAll('#detailsInternetPage .shared-network'), + !this.shared); + updateHidden( + cr.doc.querySelectorAll('#detailsInternetPage .prefer-network'), + !this.showPreferred); + } + }; + + /** + * Whether the underlying network is connected. Only used for display purpose. + * @type {boolean} + */ + cr.defineProperty(DetailsInternetPage, 'connected', + cr.PropertyKind.JS, + DetailsInternetPage.prototype.updateControls_); + + /** + * Whether the underlying network is wifi. Only used for display purpose. + * @type {boolean} + */ + cr.defineProperty(DetailsInternetPage, 'wireless', + cr.PropertyKind.JS, + DetailsInternetPage.prototype.updateControls_); + + /** + * Whether the underlying network shared wifi. Only used for display purpose. + * @type {boolean} + */ + cr.defineProperty(DetailsInternetPage, 'shared', + cr.PropertyKind.JS, + DetailsInternetPage.prototype.updateControls_); + + /** + * Whether the underlying network is a vpn. Only used for display purpose. + * @type {boolean} + */ + cr.defineProperty(DetailsInternetPage, 'vpn', + cr.PropertyKind.JS, + DetailsInternetPage.prototype.updateControls_); + + /** + * Whether the underlying network is ethernet. Only used for display purpose. + * @type {boolean} + */ + cr.defineProperty(DetailsInternetPage, 'ethernet', + cr.PropertyKind.JS, + DetailsInternetPage.prototype.updateControls_); + + /** + * Whether the underlying network is cellular. Only used for display purpose. + * @type {boolean} + */ + cr.defineProperty(DetailsInternetPage, 'cellular', + cr.PropertyKind.JS, + DetailsInternetPage.prototype.updateControls_); + + /** + * Whether the network is loading cell plan. Only used for display purpose. + * @type {boolean} + */ + cr.defineProperty(DetailsInternetPage, 'cellplanloading', + cr.PropertyKind.JS, + DetailsInternetPage.prototype.updateControls_); + + /** + * Whether the network has cell plan(s). Only used for display purpose. + * @type {boolean} + */ + cr.defineProperty(DetailsInternetPage, 'hascellplan', + cr.PropertyKind.JS, + DetailsInternetPage.prototype.updateControls_); + + /** + * Whether the network has no cell plan. Only used for display purpose. + * @type {boolean} + */ + cr.defineProperty(DetailsInternetPage, 'nocellplan', + cr.PropertyKind.JS, + DetailsInternetPage.prototype.updateControls_); + + /** + * Whether the network is gsm. Only used for display purpose. + * @type {boolean} + */ + cr.defineProperty(DetailsInternetPage, 'gsm', + cr.PropertyKind.JS, + DetailsInternetPage.prototype.updateControls_); + + /** + * Whether show password details for network. Only used for display purpose. + * @type {boolean} + */ + cr.defineProperty(DetailsInternetPage, 'password', + cr.PropertyKind.JS, + DetailsInternetPage.prototype.updateControls_); + + // TODO(xiyuan): Check to see if it is safe to remove these attributes. + cr.defineProperty(DetailsInternetPage, 'hasactiveplan', + cr.PropertyKind.JS); + cr.defineProperty(DetailsInternetPage, 'activated', + cr.PropertyKind.JS); + cr.defineProperty(DetailsInternetPage, 'connecting', + cr.PropertyKind.JS); + cr.defineProperty(DetailsInternetPage, 'connected', + cr.PropertyKind.JS); + + return { + DetailsInternetPage: DetailsInternetPage + }; +}); diff --git a/chrome/browser/resources/options2/chromeos/internet_detail_ip_config_list.js b/chrome/browser/resources/options2/chromeos/internet_detail_ip_config_list.js new file mode 100644 index 0000000..fe4cb9e --- /dev/null +++ b/chrome/browser/resources/options2/chromeos/internet_detail_ip_config_list.js @@ -0,0 +1,99 @@ +// Copyright (c) 2011 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.internet', function() { + const InlineEditableItem = options.InlineEditableItem; + const InlineEditableItemList = options.InlineEditableItemList; + + /** + * Creates a new ip config list item. + * @param {Object} fieldInfo The ip config field this item represents. + * @constructor + * @extends {cr.ui.ListItem} + */ + function IPConfigListItem(fieldInfo) { + var el = cr.doc.createElement('div'); + el.fieldInfo_ = fieldInfo; + IPConfigListItem.decorate(el); + return el; + } + + /** + * Decorates an element as a ip config list item. + * @param {!HTMLElement} el The element to decorate. + */ + IPConfigListItem.decorate = function(el) { + el.__proto__ = IPConfigListItem.prototype; + el.decorate(); + }; + + IPConfigListItem.prototype = { + __proto__: InlineEditableItem.prototype, + + /** + * Input field for editing the ip config values. + * @type {HTMLElement} + * @private + */ + valueField_: null, + + /** @inheritDoc */ + decorate: function() { + InlineEditableItem.prototype.decorate.call(this); + this.deletable = false; + + var fieldInfo = this.fieldInfo_; + + var nameEl = this.ownerDocument.createElement('div'); + nameEl.className = 'name'; + nameEl.textContent = fieldInfo['name']; + + this.contentElement.appendChild(nameEl); + + var valueEl = this.createEditableTextCell(fieldInfo['value']); + valueEl.className = 'value'; + this.contentElement.appendChild(valueEl); + + var valueField = valueEl.querySelector('input') + valueField.required = true; + this.valueField_ = valueField; + + this.addEventListener('commitedit', this.onEditCommitted_); + }, + + /** @inheritDoc */ + get currentInputIsValid() { + return this.valueField_.validity.valid; + }, + + /** @inheritDoc */ + get hasBeenEdited() { + return this.valueField_.value != this.fieldInfo_['value']; + }, + + /** + * Called when committing an edit; updates the model. + * @param {Event} e The end event. + * @private + */ + onEditCommitted_: function(e) { + this.fieldInfo_['value'] = this.valueField_.value; + }, + }; + + var IPConfigList = cr.ui.define('list'); + + IPConfigList.prototype = { + __proto__: InlineEditableItemList.prototype, + + /** @inheritDoc */ + createItem: function(fieldInfo) { + return new IPConfigListItem(fieldInfo); + }, + }; + + return { + IPConfigList: IPConfigList + }; +}); diff --git a/chrome/browser/resources/options2/chromeos/internet_network_element.js b/chrome/browser/resources/options2/chromeos/internet_network_element.js new file mode 100644 index 0000000..bba5a54 --- /dev/null +++ b/chrome/browser/resources/options2/chromeos/internet_network_element.js @@ -0,0 +1,316 @@ +// Copyright (c) 2011 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.internet', function() { + + /** + * Network settings constants. These enums usually match their C++ + * counterparts. + */ + function Constants() {} + // Minimum length for wireless network password. + Constants.MIN_WIRELESS_PASSWORD_LENGTH = 5; + // Minimum length for SSID name. + Constants.MIN_WIRELESS_SSID_LENGTH = 1; + // Cellular activation states: + Constants.ACTIVATION_STATE_UNKNOWN = 0; + Constants.ACTIVATION_STATE_ACTIVATED = 1; + Constants.ACTIVATION_STATE_ACTIVATING = 2; + Constants.ACTIVATION_STATE_NOT_ACTIVATED = 3; + Constants.ACTIVATION_STATE_PARTIALLY_ACTIVATED = 4; + // Network types: + Constants.TYPE_UNKNOWN = 0; + Constants.TYPE_ETHERNET = 1; + Constants.TYPE_WIFI = 2; + Constants.TYPE_WIMAX = 3; + Constants.TYPE_BLUETOOTH = 4; + Constants.TYPE_CELLULAR = 5; + Constants.TYPE_VPN = 6; + // ONC sources: + Constants.ONC_SOURCE_USER_IMPORT = 1; + Constants.ONC_SOURCE_DEVICE_POLICY = 2; + Constants.ONC_SOURCE_USER_POLICY = 3; + + /** + * Creates a new network list div. + * @param {Object=} opt_propertyBag Optional properties. + * @constructor + * @extends {HTMLDivElement} + */ + var NetworkElement = cr.ui.define('div'); + + NetworkElement.prototype = { + __proto__: HTMLDivElement.prototype, + + /** @inheritDoc */ + decorate: function() { + this.addEventListener('click', this.handleClick_); + }, + + /** + * Loads given network list. + * @param {Array} networks An array of network object. + */ + load: function(networks) { + this.textContent = ''; + + for (var i = 0; i < networks.length; ++i) { + this.appendChild(new NetworkItem(networks[i])); + } + }, + + /** + * Handles click on network list and triggers actions when clicked on + * a NetworkListItem button. + * @private + * @param {!Event} e The click event object. + */ + handleClick_: function(e) { + // We shouldn't respond to click events selecting an input, + // so return on those. + if (e.target.tagName == 'INPUT') { + return; + } + // Handle left button click + if (e.button == 0) { + var el = e.target; + // If click is on action buttons of a network item. + if (!(el.buttonType && el.networkType && el.servicePath)) { + if (el.buttonType) { + return; + } + // If click is on a network item or its label, walk up the DOM tree + // to find the network item. + var item = el; + while (item && !item.data) { + item = item.parentNode; + } + if (item.connecting) + return; + + if (item) { + var data = item.data; + // Don't try to connect to Ethernet or unactivated Cellular. + if (data && (data.networkType == 1 || + (data.networkType == 5 && data.activation_state != 1))) + return; + // If clicked on other networks item. + if (data && data.servicePath == '?') { + chrome.send('buttonClickCallback', + [String(data.networkType), + data.servicePath, + 'connect']); + } + } + } + } + } + }; + + /** + * Creates a new network item. + * @param {Object} network The network this represents. + * @constructor + * @extends {HTMLDivElement} + */ + function NetworkItem(network) { + var el = cr.doc.createElement('div'); + el.data = network; + NetworkItem.decorate(el); + return el; + } + + + /** + * Decorates an element as a network item. + * @param {!HTMLElement} el The element to decorate. + */ + NetworkItem.decorate = function(el) { + el.__proto__ = NetworkItem.prototype; + el.decorate(); + }; + + NetworkItem.prototype = { + __proto__: HTMLDivElement.prototype, + + /** @inheritDoc */ + decorate: function() { + this.className = 'network-item'; + this.connectable = this.data.connectable; + this.connected = this.data.connected; + this.connecting = this.data.connecting; + this.other = this.data.servicePath == '?'; + this.id = this.data.servicePath; + + // Insert a div holding the policy-managed indicator. + var policyIndicator = this.ownerDocument.createElement('div'); + policyIndicator.className = 'controlled-setting-indicator'; + cr.ui.decorate(policyIndicator, options.ControlledSettingIndicator); + + if (this.data.policyManaged) { + policyIndicator.controlledBy = 'policy'; + policyIndicator.setAttribute('textPolicy', + localStrings.getString('managedNetwork')); + } + this.appendChild(policyIndicator); + + // textDiv holds icon, name and status text. + var textDiv = this.ownerDocument.createElement('div'); + textDiv.className = 'network-item-text'; + if (this.data.iconURL) { + textDiv.style.backgroundImage = url(this.data.iconURL); + } + + var nameEl = this.ownerDocument.createElement('div'); + nameEl.className = 'network-name-label'; + nameEl.textContent = this.data.networkName; + textDiv.appendChild(nameEl); + + if (this.other) { + // No status and buttons for "Other..." + this.appendChild(textDiv); + return; + } + + // Only show status text if not empty. + if (this.data.networkStatus) { + var statusEl = this.ownerDocument.createElement('div'); + statusEl.className = 'network-status-label'; + statusEl.textContent = this.data.networkStatus; + textDiv.appendChild(statusEl); + } + + this.appendChild(textDiv); + + var spacerDiv = this.ownerDocument.createElement('div'); + spacerDiv.className = 'network-item-box-spacer'; + this.appendChild(spacerDiv); + + var buttonsDiv = this.ownerDocument.createElement('div'); + var self = this; + if (!this.data.remembered) { + var no_plan = + this.data.networkType == Constants.TYPE_CELLULAR && + this.data.needs_new_plan; + var show_activate = + (this.data.networkType == Constants.TYPE_CELLULAR && + this.data.activation_state != + Constants.ACTIVATION_STATE_ACTIVATED && + this.data.activation_state != + Constants.ACTIVATION_STATE_ACTIVATING); + + // Show [Activate] button for non-activated Cellular network. + if (show_activate || no_plan) { + var button_name = no_plan ? 'buyplan_button' : 'activate_button'; + buttonsDiv.appendChild( + this.createButton_(button_name, 'activate', + function(e) { + chrome.send('buttonClickCallback', + [String(self.data.networkType), + self.data.servicePath, + 'activate']); + })); + } + // Show disconnect button if not ethernet. + if (this.data.networkType != Constants.TYPE_ETHERNET && + this.data.connected) { + buttonsDiv.appendChild( + this.createButton_('disconnect_button', 'disconnect', + function(e) { + chrome.send('buttonClickCallback', + [String(self.data.networkType), + self.data.servicePath, + 'disconnect']); + })); + } + if (!this.data.connected && !this.data.connecting) { + // connect button (if not ethernet and not showing activate button) + if (this.data.networkType != Constants.TYPE_ETHERNET && + !show_activate && !no_plan) { + buttonsDiv.appendChild( + this.createButton_('connect_button', 'connect', + function(e) { + chrome.send('buttonClickCallback', + [String(self.data.networkType), + self.data.servicePath, + 'connect']); + })); + } + } + if (this.data.connected || + this.data.networkType == Constants.TYPE_ETHERNET || + this.data.networkType == Constants.TYPE_VPN || + this.data.networkType == Constants.TYPE_WIFI || + this.data.networkType == Constants.TYPE_CELLULAR) { + buttonsDiv.appendChild( + this.createButton_('options_button', 'options', + function(e) { + options.ProxyOptions.getInstance().setNetworkName( + self.data.networkName); + chrome.send('buttonClickCallback', + [String(self.data.networkType), + self.data.servicePath, + 'options']); + })); + } + } else { + // Put "Forget this network" button. + var button = this.createButton_('forget_button', 'forget', + function(e) { + chrome.send('buttonClickCallback', + [String(self.data.networkType), + self.data.servicePath, + 'forget']); + }); + button.disabled = this.data.policyManaged; + buttonsDiv.appendChild(button); + } + this.appendChild(buttonsDiv); + }, + + /** + * Creates a button for interacting with a network. + * @param {Object} name The name of the localStrings to use for the text. + * @param {Object} type The type of button. + */ + createButton_: function(name, type, callback) { + var buttonEl = this.ownerDocument.createElement('button'); + buttonEl.buttonType = type; + buttonEl.textContent = localStrings.getString(name); + buttonEl.addEventListener('click', callback); + return buttonEl; + } + }; + + /** + * Whether the underlying network is connected. Only used for display purpose. + * @type {boolean} + */ + cr.defineProperty(NetworkItem, 'connected', cr.PropertyKind.BOOL_ATTR); + + /** + * Whether the underlying network is currently connecting. + * Only used for display purpose. + * @type {boolean} + */ + cr.defineProperty(NetworkItem, 'connecting', cr.PropertyKind.BOOL_ATTR); + + /** + * Whether the underlying network is an other network for adding networks. + * Only used for display purpose. + * @type {boolean} + */ + cr.defineProperty(NetworkItem, 'other', cr.PropertyKind.BOOL_ATTR); + + /** + * Whether the underlying network is connectable. + * @type {boolean} + */ + cr.defineProperty(NetworkItem, 'connectable', cr.PropertyKind.BOOL_ATTR); + + return { + Constants: Constants, + NetworkElement: NetworkElement + }; +}); diff --git a/chrome/browser/resources/options2/chromeos/internet_options.html b/chrome/browser/resources/options2/chromeos/internet_options.html new file mode 100644 index 0000000..651540a --- /dev/null +++ b/chrome/browser/resources/options2/chromeos/internet_options.html @@ -0,0 +1,59 @@ +<div id="internetPage" class="page hide-indicators" hidden> + <h1 i18n-content="internetPage"></h1> + <div id="locked-network-banner" hidden> + <span id="locked-network-icon"></span> + <span id="access-locked-text" i18n-content="accessLockedMsg"></span> + </div> + <div class="displaytable"> + <section id="wireless-buttons"> + <h3 i18n-content="generalNetworkingTitle"></h3> + <div id="network-general-div"> + <div id="networking-controls" class="section-group"> + <button id="enable-wifi" hidden + i18n-content="enableWifi"></button> + <button id="disable-wifi" hidden + i18n-content="disableWifi"></button> + <button id="enable-cellular" hidden + i18n-content="enableCellular"></button> + <button id="disable-cellular" hidden + i18n-content="disableCellular"></button> + </div> + <div id="shared-proxies" class="checkbox"> + <label> + <input id="use-shared-proxies" type="checkbox" + pref="settings.use_shared_proxies"> + <span i18n-content="useSharedProxies"></span> + </label> + </div> + <div id="internet-owner-only-warning" hidden> + <span i18n-content="ownerOnly"></span> + <span i18n-content="ownerUserId"></span> + </div> + <div id="data-roaming" class="checkbox"> + <label> + <input id="enable-data-roaming" + pref="cros.signed.data_roaming_enabled" + metric="Options_Internet_DataRoaming" type="checkbox"> + <span i18n-content="enableDataRoaming"></span> + </label> + </div> + </div> + </section> + <section id="wired-section"> + <h3 i18n-content="wired_title" class="network-title"></h3> + <div id="wired-list" class="networks"></div> + </section> + <section id="wireless-section"> + <h3 i18n-content="wireless_title" class="network-title"></h3> + <div id="wireless-list" class="networks"></div> + </section> + <section id="vpn-section"> + <h3 i18n-content="vpn_title" class="network-title"></h3> + <div id="vpn-list" class="networks"></div> + </section> + <section id="remembered-section"> + <h3 i18n-content="remembered_title" class="network-title"></h3> + <div id="remembered-list" class="networks"></div> + </section> + </div> +</div> diff --git a/chrome/browser/resources/options2/chromeos/internet_options.js b/chrome/browser/resources/options2/chromeos/internet_options.js new file mode 100644 index 0000000..6391c9e --- /dev/null +++ b/chrome/browser/resources/options2/chromeos/internet_options.js @@ -0,0 +1,709 @@ +// Copyright (c) 2011 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; + const ArrayDataModel = cr.ui.ArrayDataModel; + + ///////////////////////////////////////////////////////////////////////////// + // InternetOptions class: + + /** + * Encapsulated handling of ChromeOS internet options page. + * @constructor + */ + function InternetOptions() { + OptionsPage.call(this, 'internet', templateData.internetPageTabTitle, + 'internetPage'); + } + + cr.addSingletonGetter(InternetOptions); + + // Inherit InternetOptions from OptionsPage. + InternetOptions.prototype = { + __proto__: OptionsPage.prototype, + + /** + * Initializes InternetOptions page. + * Calls base class implementation to starts preference initialization. + */ + initializePage: function() { + OptionsPage.prototype.initializePage.call(this); + + if (templateData.accessLocked) { + this.accesslocked = true; + } + + options.internet.NetworkElement.decorate($('wired-list')); + $('wired-list').load(templateData.wiredList); + options.internet.NetworkElement.decorate($('wireless-list')); + $('wireless-list').load(templateData.wirelessList); + options.internet.NetworkElement.decorate($('vpn-list')); + $('vpn-list').load(templateData.vpnList); + options.internet.NetworkElement.decorate($('remembered-list')); + $('remembered-list').load(templateData.rememberedList); + + this.updatePolicyIndicatorVisibility_(); + + options.internet.CellularPlanElement.decorate($('planList')); + + $('wired-section').hidden = (templateData.wiredList.length == 0); + $('wireless-section').hidden = (templateData.wirelessList.length == 0); + $('vpn-section').hidden = (templateData.vpnList.length == 0); + $('remembered-section').hidden = + (templateData.rememberedList.length == 0); + InternetOptions.setupAttributes(templateData); + $('detailsInternetDismiss').addEventListener('click', function(event) { + InternetOptions.setDetails(); + }); + $('detailsInternetLogin').addEventListener('click', function(event) { + InternetOptions.setDetails(); + InternetOptions.loginFromDetails(); + }); + $('detailsInternetDisconnect').addEventListener('click', function(event) { + InternetOptions.setDetails(); + InternetOptions.disconnectNetwork(); + }); + $('activateDetails').addEventListener('click', function(event) { + InternetOptions.activateFromDetails(); + }); + $('enable-wifi').addEventListener('click', function(event) { + event.target.disabled = true; + chrome.send('enableWifi', []); + }); + $('disable-wifi').addEventListener('click', function(event) { + event.target.disabled = true; + chrome.send('disableWifi', []); + }); + $('enable-cellular').addEventListener('click', function(event) { + event.target.disabled = true; + chrome.send('enableCellular', []); + }); + $('disable-cellular').addEventListener('click', function(event) { + event.target.disabled = true; + chrome.send('disableCellular', []); + }); + $('change-proxy-button').addEventListener('click', function(event) { + OptionsPage.closeOverlay(); + OptionsPage.showPageByName('proxy', false); + chrome.send('coreOptionsUserMetricsAction', + ['Options_ShowProxySettings']); + }); + $('buyplanDetails').addEventListener('click', function(event) { + chrome.send('buyDataPlan', []); + OptionsPage.closeOverlay(); + }); + $('cellularApnUseDefault').addEventListener('click', function(event) { + var data = $('connectionState').data; + var apnSelector = $('selectApn'); + + if (data.userApnIndex != -1) { + apnSelector.remove(data.userApnIndex); + data.userApnIndex = -1; + } + + if (data.providerApnList.value.length > 0) { + var iApn = 0; + data.apn.apn = data.providerApnList.value[iApn].apn; + data.apn.username = data.providerApnList.value[iApn].username; + data.apn.password = data.providerApnList.value[iApn].password; + chrome.send('setApn', [String(data.servicePath), + String(data.apn.apn), + String(data.apn.username), + String(data.apn.password)]); + apnSelector.selectedIndex = iApn; + data.selectedApn = iApn; + } else { + data.apn.apn = ''; + data.apn.username = ''; + data.apn.password = ''; + apnSelector.selectedIndex = -1; + data.selectedApn = -1; + } + + InternetOptions.prototype.updateHidden_( + cr.doc.querySelectorAll('.apn-list-view'), + false); + InternetOptions.prototype.updateHidden_( + cr.doc.querySelectorAll('.apn-details-view'), + true); + }); + $('cellularApnSet').addEventListener('click', function(event) { + if ($('cellularApn').value == '') + return; + + var data = $('connectionState').data; + var apnSelector = $('selectApn'); + + data.apn.apn = String($('cellularApn').value); + data.apn.username = String($('cellularApnUsername').value); + data.apn.password = String($('cellularApnPassword').value); + chrome.send('setApn', [String(data.servicePath), + String(data.apn.apn), + String(data.apn.username), + String(data.apn.password)]); + + if (data.userApnIndex != -1) { + apnSelector.remove(data.userApnIndex); + data.userApnIndex = -1; + } + + var option = document.createElement('option'); + option.textContent = data.apn.apn; + option.value = -1; + option.selected = true; + apnSelector.add(option, apnSelector[apnSelector.length - 1]); + data.userApnIndex = apnSelector.length - 2 + data.selectedApn = data.userApnIndex; + + InternetOptions.prototype.updateHidden_( + cr.doc.querySelectorAll('.apn-list-view'), + false); + InternetOptions.prototype.updateHidden_( + cr.doc.querySelectorAll('.apn-details-view'), + true); + }); + $('cellularApnCancel').addEventListener('click', function(event) { + $('selectApn').selectedIndex = $('connectionState').data.selectedApn; + + InternetOptions.prototype.updateHidden_( + cr.doc.querySelectorAll('.apn-list-view'), + false); + InternetOptions.prototype.updateHidden_( + cr.doc.querySelectorAll('.apn-details-view'), + true); + }); + $('selectApn').addEventListener('change', function(event) { + var data = $('connectionState').data; + var apnSelector = $('selectApn'); + if (apnSelector[apnSelector.selectedIndex].value != -1) { + var apnList = data.providerApnList.value; + chrome.send('setApn', [String(data.servicePath), + String(apnList[apnSelector.selectedIndex].apn), + String(apnList[apnSelector.selectedIndex].username), + String(apnList[apnSelector.selectedIndex].password) + ]); + data.selectedApn = apnSelector.selectedIndex; + } else if (apnSelector.selectedIndex == data.userApnIndex) { + chrome.send('setApn', [String(data.servicePath), + String(data.apn.apn), + String(data.apn.username), + String(data.apn.password)]); + data.selectedApn = apnSelector.selectedIndex; + } else { + $('cellularApn').value = data.apn.apn; + $('cellularApnUsername').value = data.apn.username; + $('cellularApnPassword').value = data.apn.password; + + InternetOptions.prototype.updateHidden_( + cr.doc.querySelectorAll('.apn-list-view'), + true); + InternetOptions.prototype.updateHidden_( + cr.doc.querySelectorAll('.apn-details-view'), + false); + } + }); + $('sim-card-lock-enabled').addEventListener('click', function(event) { + var newValue = $('sim-card-lock-enabled').checked; + // Leave value as is because user needs to enter PIN code first. + // When PIN will be entered and value changed, + // we'll update UI to reflect that change. + $('sim-card-lock-enabled').checked = !newValue; + chrome.send('setSimCardLock', [newValue]); + }); + $('change-pin').addEventListener('click', function(event) { + chrome.send('changePin'); + }); + this.showNetworkDetails_(); + }, + + showNetworkDetails_: function() { + var params = parseQueryParams(window.location); + var servicePath = params.servicePath; + var networkType = params.networkType; + if (!servicePath || !servicePath.length || + !networkType || !networkType.length) + return; + var networkName = params.networkName; + if (networkName) + options.ProxyOptions.getInstance().setNetworkName(networkName); + chrome.send('buttonClickCallback', + [networkType, servicePath, "options"]); + }, + + updateHidden_: function(elements, hidden) { + for (var i = 0, el; el = elements[i]; i++) { + el.hidden = hidden; + } + }, + + /** + * Update internet page controls. + * @private + */ + updateControls_: function() { + accesslocked = this.accesslocked; + + $('locked-network-banner').hidden = !accesslocked; + $('wireless-buttons').hidden = accesslocked; + $('wired-section').hidden = accesslocked; + $('wireless-section').hidden = accesslocked; + $('vpn-section').hidden = accesslocked; + $('remembered-section').hidden = accesslocked; + + // Don't change hidden attribute on OptionsPage divs directly because it + // is used in supporting infrastructure now. + if (accesslocked && DetailsInternetPage.getInstance().visible) + this.closeOverlay(); + }, + + /** + * Updates the policy indicator visibility. Space is only allocated for the + * policy indicators if there is at least one visible. + * @private + */ + updatePolicyIndicatorVisibility_: function() { + var page = $('internetPage'); + if (page.querySelectorAll( + '.network-item > .controlled-setting-indicator[controlled-by]') + .length) { + page.classList.remove('hide-indicators'); + } else { + page.classList.add('hide-indicators'); + } + } + }; + + /** + * Whether access to this page is locked. + * @type {boolean} + */ + cr.defineProperty(InternetOptions, 'accesslocked', cr.PropertyKind.JS, + InternetOptions.prototype.updateControls_); + + InternetOptions.loginFromDetails = function () { + var data = $('connectionState').data; + var servicePath = data.servicePath; + chrome.send('buttonClickCallback', [String(data.type), + servicePath, + 'connect']); + OptionsPage.closeOverlay(); + }; + + InternetOptions.disconnectNetwork = function () { + var data = $('connectionState').data; + var servicePath = data.servicePath; + chrome.send('buttonClickCallback', [String(data.type), + servicePath, + 'disconnect']); + OptionsPage.closeOverlay(); + }; + + InternetOptions.activateFromDetails = function () { + var data = $('connectionState').data; + var servicePath = data.servicePath; + if (data.type == options.internet.Constants.TYPE_CELLULAR) { + chrome.send('buttonClickCallback', [String(data.type), + String(servicePath), + 'activate']); + } + OptionsPage.closeOverlay(); + }; + + InternetOptions.setDetails = function () { + var data = $('connectionState').data; + var servicePath = data.servicePath; + if (data.type == options.internet.Constants.TYPE_WIFI) { + chrome.send('setPreferNetwork', + [String(servicePath), + $('preferNetworkWifi').checked ? "true" : "false"]); + chrome.send('setAutoConnect', + [String(servicePath), + $('autoConnectNetworkWifi').checked ? "true" : "false"]); + } else if (data.type == options.internet.Constants.TYPE_CELLULAR) { + chrome.send('setAutoConnect', + [String(servicePath), + $('autoConnectNetworkCellular').checked ? "true" : "false"]); + } + + var ipConfigList = $('ipConfigList'); + chrome.send('setIPConfig',[String(servicePath), + $('ipTypeDHCP').checked ? "true" : "false", + ipConfigList.dataModel.item(0).value, + ipConfigList.dataModel.item(1).value, + ipConfigList.dataModel.item(2).value, + ipConfigList.dataModel.item(3).value]); + OptionsPage.closeOverlay(); + }; + + InternetOptions.setupAttributes = function(data) { + var buttons = $('wireless-buttons'); + if (data.wifiEnabled) { + $('disable-wifi').disabled = data.wifiBusy; + $('disable-wifi').hidden = false; + $('enable-wifi').hidden = true; + } else { + $('enable-wifi').disabled = data.wifiBusy; + $('enable-wifi').hidden = false; + $('disable-wifi').hidden = true; + } + if (data.cellularAvailable) { + if (data.cellularEnabled) { + $('disable-cellular').disabled = data.cellularBusy; + $('disable-cellular').hidden = false; + $('enable-cellular').hidden = true; + } else { + $('enable-cellular').disabled = data.cellularBusy; + $('enable-cellular').hidden = false; + $('disable-cellular').hidden = true; + } + if (!AccountsOptions.currentUserIsOwner()) + $('internet-owner-only-warning').hidden = false; + $('data-roaming').hidden = false; + } else { + $('enable-cellular').hidden = true; + $('disable-cellular').hidden = true; + $('data-roaming').hidden = true; + } + }; + + // + //Chrome callbacks + // + InternetOptions.refreshNetworkData = function (data) { + var self = InternetOptions.getInstance(); + if (data.accessLocked) { + self.accesslocked = true; + return; + } + self.accesslocked = false; + $('wired-list').load(data.wiredList); + $('wireless-list').load(data.wirelessList); + $('vpn-list').load(data.vpnList); + $('remembered-list').load(data.rememberedList); + + self.updatePolicyIndicatorVisibility_(); + + $('wired-section').hidden = (data.wiredList.length == 0); + $('wireless-section').hidden = (data.wirelessList.length == 0); + $('vpn-section').hidden = (data.vpnList.length == 0); + InternetOptions.setupAttributes(data); + $('remembered-section').hidden = (data.rememberedList.length == 0); + }; + + // TODO(xiyuan): This function seems belonging to DetailsInternetPage. + InternetOptions.updateCellularPlans = function (data) { + var detailsPage = DetailsInternetPage.getInstance(); + detailsPage.cellplanloading = false; + if (data.plans && data.plans.length) { + detailsPage.nocellplan = false + detailsPage.hascellplan = true; + $('planList').load(data.plans); + } else { + detailsPage.nocellplan = true; + detailsPage.hascellplan = false; + } + + detailsPage.hasactiveplan = !data.needsPlan; + detailsPage.activated = data.activated; + if (!data.activated) + $('detailsInternetLogin').hidden = true; + + $('buyplanDetails').hidden = !data.showBuyButton; + $('activateDetails').hidden = !data.showActivateButton; + }; + + InternetOptions.updateSecurityTab = function(requirePin) { + $('sim-card-lock-enabled').checked = requirePin; + $('change-pin').hidden = !requirePin; + }; + + InternetOptions.showDetailedInfo = function (data) { + var detailsPage = DetailsInternetPage.getInstance(); + // TODO(chocobo): Is this hack to cache the data here reasonable? + $('connectionState').data = data; + $('buyplanDetails').hidden = true; + $('activateDetails').hidden = true; + $('detailsInternetLogin').hidden = data.connected; + if (data.type == options.internet.Constants.TYPE_ETHERNET) + $('detailsInternetDisconnect').hidden = true; + else + $('detailsInternetDisconnect').hidden = !data.connected; + + detailsPage.deviceConnected = data.deviceConnected; + detailsPage.connecting = data.connecting; + detailsPage.connected = data.connected; + if (data.connected) { + $('inetTitle').textContent = localStrings.getString('inetStatus'); + } else { + $('inetTitle').textContent = localStrings.getString('inetConnect'); + } + $('connectionState').textContent = data.connectionState; + + var inetAddress = ''; + var inetSubnetAddress = ''; + var inetGateway = ''; + var inetDns = ''; + $('ipTypeDHCP').checked = true; + if (data.ipconfigStatic.value) { + inetAddress = data.ipconfigStatic.value.address; + inetSubnetAddress = data.ipconfigStatic.value.subnetAddress; + inetGateway = data.ipconfigStatic.value.gateway; + inetDns = data.ipconfigStatic.value.dns; + $('ipTypeStatic').checked = true; + } else if (data.ipconfigDHCP.value) { + inetAddress = data.ipconfigDHCP.value.address; + inetSubnetAddress = data.ipconfigDHCP.value.subnetAddress; + inetGateway = data.ipconfigDHCP.value.gateway; + inetDns = data.ipconfigDHCP.value.dns; + } + + // Hide the dhcp/static radio if needed. + $('ipTypeDHCPDiv').hidden = !data.showStaticIPConfig; + $('ipTypeStaticDiv').hidden = !data.showStaticIPConfig; + + // Hide change-proxy-button and change-proxy-section if not showing proxy. + $('change-proxy-button').hidden = !data.showProxy; + $('change-proxy-section').hidden = !data.showProxy; + + var ipConfigList = $('ipConfigList'); + ipConfigList.disabled = + $('ipTypeDHCP').checked || data.ipconfigStatic.controlledBy || + !data.showStaticIPConfig; + options.internet.IPConfigList.decorate(ipConfigList); + ipConfigList.autoExpands = true; + var model = new ArrayDataModel([]); + model.push({ + 'property': 'inetAddress', + 'name': localStrings.getString('inetAddress'), + 'value': inetAddress, + }); + model.push({ + 'property': 'inetSubnetAddress', + 'name': localStrings.getString('inetSubnetAddress'), + 'value': inetSubnetAddress, + }); + model.push({ + 'property': 'inetGateway', + 'name': localStrings.getString('inetGateway'), + 'value': inetGateway, + }); + model.push({ + 'property': 'inetDns', + 'name': localStrings.getString('inetDns'), + 'value': inetDns, + }); + ipConfigList.dataModel = model; + + $('ipTypeDHCP').addEventListener('click', function(event) { + // disable ipConfigList and switch back to dhcp values (if any) + if (data.ipconfigDHCP.value) { + var config = data.ipconfigDHCP.value; + ipConfigList.dataModel.item(0).value = config.address; + ipConfigList.dataModel.item(1).value = config.subnetAddress; + ipConfigList.dataModel.item(2).value = config.gateway; + ipConfigList.dataModel.item(3).value = config.dns; + } + ipConfigList.dataModel.updateIndex(0); + ipConfigList.dataModel.updateIndex(1); + ipConfigList.dataModel.updateIndex(2); + ipConfigList.dataModel.updateIndex(3); + // Unselect all so we don't keep the currently selected field editable. + ipConfigList.selectionModel.unselectAll(); + ipConfigList.disabled = true; + }); + + $('ipTypeStatic').addEventListener('click', function(event) { + // enable ipConfigList + ipConfigList.disabled = false; + ipConfigList.focus(); + ipConfigList.selectionModel.selectedIndex = 0; + }); + + if (data.hardwareAddress) { + $('hardwareAddress').textContent = data.hardwareAddress; + $('hardwareAddressRow').style.display = 'table-row'; + } else { + // This is most likely a device without a hardware address. + $('hardwareAddressRow').style.display = 'none'; + } + if (data.type == options.internet.Constants.TYPE_WIFI) { + OptionsPage.showTab($('wifiNetworkNavTab')); + detailsPage.wireless = true; + detailsPage.vpn = false; + detailsPage.ethernet = false; + detailsPage.cellular = false; + detailsPage.gsm = false; + detailsPage.shared = data.shared; + $('inetSsid').textContent = data.ssid; + detailsPage.showPreferred = data.showPreferred; + $('preferNetworkWifi').checked = data.preferred.value; + $('preferNetworkWifi').disabled = !data.remembered; + $('autoConnectNetworkWifi').checked = data.autoConnect.value; + $('autoConnectNetworkWifi').disabled = !data.remembered; + detailsPage.password = data.encrypted; + } else if(data.type == options.internet.Constants.TYPE_CELLULAR) { + if (!data.gsm) + OptionsPage.showTab($('cellularPlanNavTab')); + else + OptionsPage.showTab($('cellularConnNavTab')); + detailsPage.ethernet = false; + detailsPage.wireless = false; + detailsPage.vpn = false; + detailsPage.cellular = true; + if (data.carrierUrl) { + var a = $('carrierUrl'); + if (!a) { + a = document.createElement('a'); + $('serviceName').appendChild(a); + a.id = 'carrierUrl'; + a.target = "_blank"; + } + a.href = data.carrierUrl; + a.textContent = data.serviceName; + } else { + $('serviceName').textContent = data.serviceName; + } + $('networkTechnology').textContent = data.networkTechnology; + $('activationState').textContent = data.activationState; + $('roamingState').textContent = data.roamingState; + $('restrictedPool').textContent = data.restrictedPool; + $('errorState').textContent = data.errorState; + $('manufacturer').textContent = data.manufacturer; + $('modelId').textContent = data.modelId; + $('firmwareRevision').textContent = data.firmwareRevision; + $('hardwareRevision').textContent = data.hardwareRevision; + $('prlVersion').textContent = data.prlVersion; + $('meid').textContent = data.meid; + $('imei').textContent = data.imei; + $('mdn').textContent = data.mdn; + $('esn').textContent = data.esn; + $('min').textContent = data.min; + detailsPage.gsm = data.gsm; + if (data.gsm) { + $('operatorName').textContent = data.operatorName; + $('operatorCode').textContent = data.operatorCode; + $('imsi').textContent = data.imsi; + + var apnSelector = $('selectApn'); + // Clear APN lists, keep only last element that "other". + while (apnSelector.length != 1) + apnSelector.remove(0); + var otherOption = apnSelector[0]; + data.selectedApn = -1; + data.userApnIndex = -1; + var apnList = data.providerApnList.value; + for (var i = 0; i < apnList.length; i++) { + var option = document.createElement('option'); + var name = apnList[i].localizedName; + if (name == '' && apnList[i].name != '') + name = apnList[i].name; + if (name == '') + name = apnList[i].apn; + else + name = name + ' (' + apnList[i].apn + ')'; + option.textContent = name; + option.value = i; + if ((data.apn.apn == apnList[i].apn && + data.apn.username == apnList[i].username && + data.apn.password == apnList[i].password) || + (data.apn.apn == '' && + data.lastGoodApn.apn == apnList[i].apn && + data.lastGoodApn.username == apnList[i].username && + data.lastGoodApn.password == apnList[i].password)) { + data.selectedApn = i; + } + // Insert new option before "other" option. + apnSelector.add(option, otherOption); + } + if (data.selectedApn == -1 && data.apn.apn != '') { + var option = document.createElement('option'); + option.textContent = data.apn.apn; + option.value = -1; + apnSelector.add(option, otherOption); + data.selectedApn = apnSelector.length - 2; + data.userApnIndex = data.selectedApn; + } + apnSelector.selectedIndex = data.selectedApn; + InternetOptions.prototype.updateHidden_( + cr.doc.querySelectorAll('.apn-list-view'), + false); + InternetOptions.prototype.updateHidden_( + cr.doc.querySelectorAll('.apn-details-view'), + true); + + InternetOptions.updateSecurityTab(data.simCardLockEnabled.value); + } + $('autoConnectNetworkCellular').checked = data.autoConnect.value; + $('autoConnectNetworkCellular').disabled = false; + + $('buyplanDetails').hidden = !data.showBuyButton; + $('activateDetails').hidden = !data.showActivateButton; + if (data.showActivateButton) { + $('detailsInternetLogin').hidden = true; + } + + detailsPage.hascellplan = false; + if (data.connected) { + detailsPage.nocellplan = false; + detailsPage.cellplanloading = true; + chrome.send('refreshCellularPlan', [data.servicePath]) + } else { + detailsPage.nocellplan = true; + detailsPage.cellplanloading = false; + } + } else if (data.type == options.internet.Constants.TYPE_VPN) { + OptionsPage.showTab($('vpnNavTab')); + detailsPage.wireless = false; + detailsPage.vpn = true; + detailsPage.ethernet = false; + detailsPage.cellular = false; + detailsPage.gsm = false; + $('inetServiceName').textContent = data.service_name; + $('inetServerHostname').textContent = data.server_hostname; + $('inetProviderType').textContent = data.provider_type; + $('inetUsername').textContent = data.username; + } else { + OptionsPage.showTab($('internetNavTab')); + detailsPage.ethernet = true; + detailsPage.wireless = false; + detailsPage.vpn = false; + detailsPage.cellular = false; + detailsPage.gsm = false; + } + + // Update controlled option indicators. + indicators = cr.doc.querySelectorAll( + '#detailsInternetPage .controlled-setting-indicator'); + for (var i = 0; i < indicators.length; i++) { + var dataProperty = indicators[i].getAttribute('data'); + if (dataProperty && data[dataProperty]) { + var controlledBy = data[dataProperty].controlledBy; + if (controlledBy) { + indicators[i].controlledBy = controlledBy; + var forElement = $(indicators[i].getAttribute('for')); + if (forElement) + forElement.disabled = true; + if (forElement.type == 'radio' && !forElement.checked) + indicators[i].hidden = true; + } else { + indicators[i].controlledBy = null; + } + } + } + + // Don't show page name in address bar and in history to prevent people + // navigate here by hand and solve issue with page session restore. + OptionsPage.showPageByName('detailsInternetPage', false); + }; + + InternetOptions.invalidNetworkSettings = function () { + alert(localStrings.getString('invalidNetworkSettings')); + }; + + // Export + return { + InternetOptions: InternetOptions + }; +}); diff --git a/chrome/browser/resources/options2/chromeos/internet_options_page.css b/chrome/browser/resources/options2/chromeos/internet_options_page.css new file mode 100644 index 0000000..c7ab791 --- /dev/null +++ b/chrome/browser/resources/options2/chromeos/internet_options_page.css @@ -0,0 +1,171 @@ +#inetTitle { + border: none; +} + +#network-general-div { + -webkit-margin-start: 15px; +} + +#networking-controls { + display: -webkit-box; +} + +.networks { + -webkit-margin-start: 15px; + padding: 2px; +} + +.network-password { + left: 0px; + position: relative; +} + +.network-password > input, .network-password > select { + width: 200px; +} + +.network-item { + -webkit-box-align: center; + -webkit-padding-start: 10px; + border: 1px solid rgba(255,255,255,0); /* transparent white */ + border-radius: 2px; + display: -webkit-box; + height: 35px; +} + +.network-item:not([connecting]):hover { + border-color: hsl(214, 91%, 85%); + background-color: hsl(214, 91%, 97%); +} + +.network-item[connected] { + background-image: -webkit-linear-gradient(rgba(255, 255, 255, 0.8), + rgba(255, 255, 255, 0)); + background-color: hsl(214,91%,89%); + border-color: hsl(214, 91%, 65%); +} + +.network-item[connected]:hover { + background-color: hsl(214, 91%, 87%); + border-color: hsl(214, 91%, 65%); +} + +.network-item[connecting] { + background-color: hsl(214, 91%, 97%); + border-color: hsl(214, 91%, 85%); +} + +.network-item > .controlled-setting-indicator { + -webkit-margin-end: 5px; + width: 16px; +} + +.hide-indicators .network-item > .controlled-setting-indicator { + display: none; +} + +.network-item-text { + -webkit-padding-start: 30px; + background: left center no-repeat; + cursor: default; + display: table-cell; + height: 32px; + line-height: 100%; + max-width: 320px; + overflow: hidden; + vertical-align: middle; +} + +html[dir='rtl'] .network-item-text { + background: right center no-repeat; +} + +.network-item[connected] > * > .network-name-label { + font-weight: bold; +} + +.network-status-label { + color: grey; +} + +.network-item > * > button { + min-width: 100px; + visibility: hidden; + margin-right: 5px; +} + +.network-item:hover > * > button, +.network-item[connected] > * > button { + visibility: visible; +} + +.network-item-box-spacer { + -webkit-box-flex: 1; +} + +.displaytable > section > .network-title { + vertical-align: top; + padding-top: 20px; +} + +#detailsInternetPage { + min-width: 440px; + min-height: 420px; + padding-bottom: 40px; + position: relative; +} + +#details-plan-table { + width: 100%; +} + +#planSummary { + width: 350px; + padding-bottom: 5px; +} + +#planWarning { + width: 350px; + padding-top: 5px; + font-weight: bold; +} + +#locked-network-banner { + height: 31px; + width: 100%; + margin: 0; + padding-top: 10px; + vertical-align: middle; +} + +#locked-network-icon { + background-image: url("chrome://theme/IDR_WARNING"); + background-repeat: no-repeat; + background-position:center; + display: inline-block; + padding: 5px; + height: 21px; + vertical-align: middle; + width: 24px; +} + +#access-locked-text { + vertical-align: middle; +} + +#internet-owner-only-warning { + margin: 10px 0; + padding-bottom: 1px; + -webkit-padding-start: 20px; + background-repeat: no-repeat; + background-image: url('warning.png'); +} + +#ipConfigList .name { + width: 40%; +} + +#ipConfigList .value { + -webkit-box-flex: 1; + color: #666; +} diff --git a/chrome/browser/resources/options2/chromeos/language_chewing_options.html b/chrome/browser/resources/options2/chromeos/language_chewing_options.html new file mode 100644 index 0000000..f21e603 --- /dev/null +++ b/chrome/browser/resources/options2/chromeos/language_chewing_options.html @@ -0,0 +1,141 @@ +<div id="languageChewingPage" class="page" hidden> + <h1 i18n-content="languageChewingPage"></h1> + <section> + <table class="option-control-table"> + <tr> + <td class="option-name" colspan="2"> + <div class="checkbox"> + <label> + <input id="chewing-auto-shift-cur" type="checkbox" + pref="settings.language.chewing_auto_shift_cur"> + <span i18n-content="Chewing_autoShiftCur"></span> + </label> + </div> + </td> + </tr> + <tr> + <td class="option-name" colspan="2"> + <div class="checkbox"> + <label> + <input id="chewing-add-phrase-direction" type="checkbox" + pref="settings.language.chewing_add_phrase_direction"> + <span i18n-content="Chewing_addPhraseDirection"></span> + </label> + </div> + </td> + </tr> + <!-- Temporarily disabled. (crosbug.com/14185) + <tr> + <td class="option-name" colspan="2"> + <div class="checkbox"> + <label> + <input id="chewing-easy-symbol-input" type="checkbox" + pref="settings.language.chewing_easy_symbol_input"> + <span i18n-content="Chewing_easySymbolInput"></span> + </label> + </div> + </td> + </tr> + --> + <tr> + <td class="option-name" colspan="2"> + <div class="checkbox"> + <label> + <input id="chewing-esc-clean-all-buf" type="checkbox" + pref="settings.language.chewing_esc_clean_all_buf"> + <span i18n-content="Chewing_escCleanAllBuf"></span> + </label> + </div> + </td> + </tr> + <tr> + <td class="option-name" colspan="2"> + <div class="checkbox"> + <label> + <input id="chewing-force-lowercase-english" type="checkbox" + pref="settings.language.chewing_force_lowercase_english"> + <span i18n-content="Chewing_forceLowercaseEnglish"></span> + </label> + </div> + </td> + </tr> + <!-- Temporarily disabled. (crosbug.com/14185) + <tr> + <td class="option-name" colspan="2"> + <div class="checkbox"> + <label> + <input id="chewing-plain-zhuyin" type="checkbox" + pref="settings.language.chewing_plain_zhuyin"> + <span i18n-content="Chewing_plainZhuyin"></span> + </label> + </div> + </td> + </tr> + --> + <tr> + <td class="option-name" colspan="2"> + <div class="checkbox"> + <label> + <input id="chewing-phrase-choice-rearward" type="checkbox" + pref="settings.language.chewing_phrase_choice_rearward"> + <span i18n-content="Chewing_phraseChoiceRearward"></span> + </label> + </div> + </td> + </tr> + <tr> + <td class="option-name" colspan="2"> + <div class="checkbox"> + <label> + <input id="chewing-space-as-selection" type="checkbox" + pref="settings.language.chewing_space_as_selection"> + <span i18n-content="Chewing_spaceAsSelection"></span> + </label> + </div> + </td> + </tr> + <tr> + <td class="option-name" i18n-content="Chewing_maxChiSymbolLen"></td> + <td class="option-value"> + <input id="chewing-max-chi-symbol-len" class="control" type="range" + pref="settings.language.chewing_max_chi_symbol_len" + i18n-values="min:Chewing_maxChiSymbolLenMin; + max:Chewing_maxChiSymbolLenMax"> + <span id="chewing-max-chi-symbol-len-value"></span> + </td> + </tr> + <tr> + <td class="option-name" i18n-content="Chewing_candPerPage"></td> + <td class="option-value"> + <select id="chewing-cand-per-page" class="control" data-type="number" + pref="settings.language.chewing_cand_per_page" + i18n-options="Chewing_candPerPageValue"></select> + </td> + </tr> + <tr> + <td class="option-name" i18n-content="Chewing_KBType"></td> + <td class="option-value"> + <select id="chewing-keyboard-type" class="control" data-type="string" + pref="settings.language.chewing_keyboard_type" + i18n-options="Chewing_KBTypeValue"></select> + </td> + </tr> + <tr> + <td class="option-name" i18n-content="Chewing_selKeys"></td> + <td class="option-value"> + <select id="chewing-sel-keys" class="control" data-type="string" + pref="settings.language.chewing_sel_keys" + i18n-options="Chewing_selKeysValue"></select> + </td> + </tr> + <tr> + <td class="option-name" i18n-content="Chewing_hsuSelKeyType"></td> + <td class="option-value"> + <select id="chewing-sel-key-type" class="control" data-type="number" + pref="settings.language.chewing_hsu_sel_key_type" + i18n-options="Chewing_hsuSelKeyTypeValue"></select> + </td> + </tr> + </table> + </section> +</div> diff --git a/chrome/browser/resources/options2/chromeos/language_customize_modifier_keys_overlay.html b/chrome/browser/resources/options2/chromeos/language_customize_modifier_keys_overlay.html new file mode 100644 index 0000000..3adb231 --- /dev/null +++ b/chrome/browser/resources/options2/chromeos/language_customize_modifier_keys_overlay.html @@ -0,0 +1,37 @@ +<div id="languageCustomizeModifierKeysOverlay" class="page" hidden> + <div class="content-area"> + <table class="option-control-table"> + <tr> + <td class="option-name" i18n-content="xkbRemapSearchKeyToContent"></td> + <td class="option-value"> + <select id="xkb-remap-search-key-to" class="control" + data-type="number" i18n-options="xkbRemapSearchKeyToValue" + pref="settings.language.xkb_remap_search_key_to"></select> + </td> + </tr> + <tr> + <td class="option-name" + i18n-content="xkbRemapControlKeyToContent"></td> + <td class="option-value"> + <select id="xkb-remap-control-key-to" class="control" + data-type="number" i18n-options="xkbRemapControlKeyToValue" + pref="settings.language.xkb_remap_control_key_to"></select> + </td> + </tr> + <tr> + <td class="option-name" i18n-content="xkbRemapAltKeyToContent"></td> + <td class="option-value"> + <select id="xkb-remap-alt-key-to" class="control" data-type="number" + pref="settings.language.xkb_remap_alt_key_to" + i18n-options="xkbRemapAltKeyToValue"></select> + </td> + </tr> + </table> + </div> + <div class="action-area"> + <div class="button-strip"> + <button id="languageCustomizeModifierKeysOverleyDismissButton" + i18n-content="close"></button> + </div> + </div> +</div> diff --git a/chrome/browser/resources/options2/chromeos/language_hangul_options.html b/chrome/browser/resources/options2/chromeos/language_hangul_options.html new file mode 100644 index 0000000..f70ba76 --- /dev/null +++ b/chrome/browser/resources/options2/chromeos/language_hangul_options.html @@ -0,0 +1,18 @@ +<div id="languageHangulPage" class="page" hidden> + <h1 i18n-content="languageHangulPage"></h1> + <section> + <div class="option"> + <table class="option-control-table"> + <tr> + <td class="option-name" i18n-content="hangul_keyboard_layout"></td> + <td class="option-value"> + <select id="keyboard-layout-select" class="control" + data-type="string" + pref="settings.language.hangul_keyboard" + i18n-options="HangulkeyboardLayoutList"></select> + </td> + </tr> + </table> + </div> + </section> +</div> diff --git a/chrome/browser/resources/options2/chromeos/language_mozc_options.html b/chrome/browser/resources/options2/chromeos/language_mozc_options.html new file mode 100644 index 0000000..498b317 --- /dev/null +++ b/chrome/browser/resources/options2/chromeos/language_mozc_options.html @@ -0,0 +1,135 @@ +<div id="languageMozcPage" class="page" hidden> + <h1 i18n-content="languageMozcPage"></h1> + <section> + <table class="option-control-table"> + <tr> + <td class="option-name" colspan="2"> + <div class="checkbox"> + <label> + <input id="mozc-incognito-mode" + pref="settings.language.mozc_incognito_mode" + type="checkbox"> + <span i18n-content="mozc_incognito_mode"></span> + </label> + </div> + </td> + </tr> + <tr> + <td class="option-name" colspan="2"> + <div class="checkbox"> + <label> + <input id="mozc-use-auto-ime-turn-off" + pref="settings.language.mozc_use_auto_ime_turn_off" + type="checkbox"> + <span i18n-content="mozc_use_auto_ime_turn_off"></span> + </label> + </div> + </td> + </tr> + <tr> + <td class="option-name" colspan="2"> + <div class="checkbox"> + <label> + <input id="mozc-use-history-suggest" type="checkbox" + pref="settings.language.mozc_use_history_suggest"> + <span i18n-content="mozc_use_history_suggest"></span> + </label> + </div> + </td> + </tr> + <tr> + <td class="option-name" colspan="2"> + <div class="checkbox"> + <label> + <input id="mozc-use-dictionary-suggest" type="checkbox" + pref="settings.language.mozc_use_dictionary_suggest"> + <span i18n-content="mozc_use_dictionary_suggest"></span> + </label> + </div> + </td> + </tr> + <tr> + <td class="option-name" i18n-content="mozc_preedit_method"></td> + <td class="option-value"> + <select id="mozc-preedit-method" class="control" data-type="string" + pref="settings.language.mozc_preedit_method" + i18n-options="mozc_preedit_methodValue"></select> + </td> + </tr> + <tr> + <td class="option-name" i18n-content="mozc_session_keymap"></td> + <td class="option-value"> + <select id="mozc-session-keymap" class="control" data-type="string" + pref="settings.language.mozc_session_keymap" + i18n-options="mozc_session_keymapValue"></select> + </td> + </tr> + <tr> + <td class="option-name" i18n-content="mozc_punctuation_method"></td> + <td class="option-value"> + <select id="mozc-punctuation-method" class="control" + pref="settings.language.mozc_punctuation_method" + data-type="string" i18n-options="mozc_punctuation_methodValue"> + </select> + </td> + </tr> + <tr> + <td class="option-name" i18n-content="mozc_symbol_method"></td> + <td class="option-value"> + <select id="mozc-symbol-method" class="control" data-type="string" + pref="settings.language.mozc_symbol_method" + i18n-options="mozc_symbol_methodValue"></select> + </td> + </tr> + <tr> + <td class="option-name" i18n-content="mozc_space_character_form"> + </td> + <td class="option-value"> + <select id="mozc-space-character-form" class="control" + pref="settings.language.mozc_space_character_form" + data-type="string" i18n-options="mozc_space_character_formValue"> + </select> + </td> + </tr> + <tr> + <td class="option-name" i18n-content="mozc_history_learning_level"> + </td> + <td class="option-value"> + <select id="mozc-history-learning-level" class="control" + pref="settings.language.mozc_history_learning_level" + data-type="string" + i18n-options="mozc_history_learning_levelValue"></select> + </td> + </tr> + <tr> + <td class="option-name" i18n-content="mozc_shift_key_mode_switch"> + </td> + <td class="option-value"> + <select id="mozc-shift-key-mode-switch" class="control" + pref="settings.language.mozc_shift_key_mode_switch" + data-type="string" i18n-options="mozc_shift_key_mode_switchValue"> + </select> + </td> + </tr> + <tr> + <td class="option-name" i18n-content="mozc_numpad_character_form"> + </td> + <td class="option-value"> + <select id="mozc-numpad-character-form" class="control" + pref="settings.language.mozc_numpad_character_form" + data-type="string" i18n-options="mozc_numpad_character_formValue"> + </select> + </td> + </tr> + <tr> + <td class="option-name" i18n-content="mozc_suggestions_size"> + </td> + <td class="option-value"> + <select id="mozc-suggestions-size" class="control" data-type="number" + pref="settings.language.mozc_suggestions_size" + i18n-options="mozc_suggestions_sizeValue"></select> + </td> + </tr> + </table> + </section> +</div> diff --git a/chrome/browser/resources/options2/chromeos/language_pinyin_options.html b/chrome/browser/resources/options2/chromeos/language_pinyin_options.html new file mode 100644 index 0000000..ad72fa4 --- /dev/null +++ b/chrome/browser/resources/options2/chromeos/language_pinyin_options.html @@ -0,0 +1,148 @@ +<div id="languagePinyinPage" class="page" hidden> + <h1 i18n-content="languagePinyinPage"></h1> + <section> + <table class="option-control-table"> + <tr> + <td class="option-name" colspan="2"> + <div class="checkbox"> + <labl> + <input id="pinyin-correct-pinyin" + pref="settings.language.pinyin_correct_pinyin" + type="checkbox"> + <span i18n-content="PinyinCorrectPinyin"></span> + </label> + </div> + </td> + </tr> + <tr> + <td class="option-name" colspan="2"> + <div class="checkbox"> + <label> + <input id="pinyin-fuzzy-pinyin" + pref="settings.language.pinyin_fuzzy_pinyin" + type="checkbox"> + <span i18n-content="PinyinFuzzyPinyin"></span> + </label> + </div> + </td> + </tr> + <tr> + <td class="option-name" colspan="2"> + <div class="checkbox"> + <label> + <input id="pinyin-shift-select-candidate" + pref="settings.language.pinyin_shift_select_candidate" + type="checkbox"> + <span i18n-content="PinyinShiftSelectCandidate"></span> + </label> + </div> + </td> + </tr> + <tr> + <td class="option-name" colspan="2"> + <div class="checkbox"> + <label> + <input id="pinyin-minus-equal-page" + pref="settings.language.pinyin_minus_equal_page" + type="checkbox"> + <span i18n-content="PinyinMinusEqualPage"></span> + </label> + </div> + </td> + </tr> + <tr> + <td class="option-name" colspan="2"> + <div class="checkbox"> + <label> + <input id="pinyin-comma-period-page" + pref="settings.language.pinyin_comma_period_page" + type="checkbox"> + <span i18n-content="PinyinCommaPeriodPage"></span> + </label> + </div> + </td> + </tr> + <tr> + <td class="option-name" colspan="2"> + <div class="checkbox"> + <label> + <input id="pinyin-auto-commit" + pref="settings.language.pinyin_auto_commit" + type="checkbox"> + <span i18n-content="PinyinAutoCommit"></span> + </label> + </div> + </td> + </tr> + <tr> + <td class="option-name" colspan="2"> + <div class="checkbox"> + <label> + <input id="pinyin-double-pinyin" + pref="settings.language.pinyin_double_pinyin" + type="checkbox"> + <span i18n-content="PinyinDoublePinyin"></span> + </label> + </div> + </td> + </tr> + <tr> + <td class="option-name" colspan="2"> + <div class="checkbox"> + <label> + <input id="pinyin-init-chinese" + pref="settings.language.pinyin_init_chinese" + type="checkbox"> + <span i18n-content="PinyinInitChinese"></span> + </label> + </div> + </td> + </tr> + <tr> + <td class="option-name" colspan="2"> + <div class="checkbox"> + <label> + <input id="pinyin-init-full" + pref="settings.language.pinyin_init_full" + type="checkbox"> + <span i18n-content="PinyinInitFull"></span> + </label> + </div> + </td> + </tr> + <tr> + <td class="option-name" colspan="2"> + <div class="checkbox"> + <label> + <input id="pinyin-init-full-punct" + pref="settings.language.pinyin_init_full_punct" + type="checkbox"> + <span i18n-content="PinyinInitFullPunct"></span> + </label> + </div> + </td> + </tr> + <tr> + <td class="option-name" colspan="2"> + <div class="checkbox"> + <label> + <input id="pinyin-init-simplified-chinese" + pref="settings.language.pinyin_init_simplified_chinese" + type="checkbox"> + <span i18n-content="PinyinInitSimplifiedChinese"></span> + </label> + </div> + </td> + </tr> + <tr> + <td class="option-name" i18n-content="PinyinDoublePinyinSchema"></td> + <td class="option-value"> + <select id="pinyin-double-pinyin-schema" class="control" + pref="settings.language.pinyin_double_pinyin_schema" + data-type="string" + i18n-options="PinyinDoublePinyinSchemaValue"></select> + </td> + </tr> + </table> + </section> +</div> diff --git a/chrome/browser/resources/options2/chromeos/proxy.css b/chrome/browser/resources/options2/chromeos/proxy.css new file mode 100644 index 0000000..28ee236 --- /dev/null +++ b/chrome/browser/resources/options2/chromeos/proxy.css @@ -0,0 +1,10 @@ +#ignoredHostList { + border: solid 1px #999999; + height: 100px; + -webkit-margin-start: 0px; +} + +#newHost { + -webkit-margin-start: 0px; + margin-top: 8px; +}
\ No newline at end of file diff --git a/chrome/browser/resources/options2/chromeos/proxy.html b/chrome/browser/resources/options2/chromeos/proxy.html new file mode 100644 index 0000000..0449c7b --- /dev/null +++ b/chrome/browser/resources/options2/chromeos/proxy.html @@ -0,0 +1,141 @@ +<div id="proxyPage" class="page" hidden> + <div id="info-banner" class="managed-prefs-banner" hidden> + <span id="banner-icon" class="managed-prefs-icon"></span> + <span id="banner-text" class="managed-prefs-text"></span> + </div> + <h1 id="proxy-page-title" i18n-content="proxyPage"></h1> + <section> + <h3 i18n-content="proxy_config_title"></h3> + <div> + <div class="radio"> + <label> + <input id="directProxy" type="radio" name="proxytype" value="1" + pref="cros.session.proxy.type"> + <span i18n-content="proxyDirectInternetConnection"></span> + </label> + </div> + <div class="radio"> + <label> + <input id="manualProxy" type="radio" name="proxytype" value="2" + pref="cros.session.proxy.type"> + <span i18n-content="proxyManual"></span> + </label> + </div> + <div class="checkbox"> + <label> + <input id="proxyAllProtocols" type="checkbox" + pref="cros.session.proxy.single"> + <span i18n-content="sameProxyProtocols"></span> + </label> + </div> + <div id="singleProxy"> + <table> + <tr> + <td> + <span i18n-content="httpProxy"></span> + <input id="proxyHostSingleName" type="text" size="30" + pref="cros.session.proxy.singlehttp" disabled> + </td> + <td> + <span i18n-content="proxyPort"></span> + <input id="proxyHostSinglePort" type="text" data-type="number" + size="5" pref="cros.session.proxy.singlehttpport" disabled> + </td> + </tr> + </table> + </div> + <div id="multiProxy"> + <table> + <tr> + <td> + <span i18n-content="httpProxy"></span> + </td> + <td> + <input id="proxyHostName" type="text" size="30" + pref="cros.session.proxy.httpurl" disabled> + </td> + <td> + <span i18n-content="proxyPort"></span> + </td> + <td> + <input id="proxyHostPort" type="text" data-type="number" size="5" + pref="cros.session.proxy.httpport" disabled> + </td> + </tr> + <tr> + <td> + <span i18n-content="secureHttpProxy"></span> + </td> + <td> + <input id="secureProxyHostName" type="text" size="30" + pref="cros.session.proxy.httpsurl" disabled> + </td> + <td> + <span i18n-content="proxyPort"></span> + </td> + <td> + <input id="secureProxyPort" type="text" data-type="number" size="5" + pref="cros.session.proxy.httpsport" disabled> + </td> + </tr> + <tr> + <td> + <span i18n-content="ftpProxy"></span> + </td> + <td> + <input id="ftpProxy" type="text" size="30" + pref="cros.session.proxy.ftpurl" disabled> + </td> + <td> + <span i18n-content="proxyPort"></span> + </td> + <td> + <input id="ftpProxyPort" type="text" data-type="number" size="5" + pref="cros.session.proxy.ftpport" disabled> + </td> + </tr> + <tr> + <td> + <span i18n-content="socksHost"></span> + </td> + <td> + <input id="socksHost" type="text" size="30" + pref="cros.session.proxy.socks" disabled> + </td> + <td> + <span i18n-content="proxyPort"></span> + </td> + <td> + <input id="socksPort" type="text" data-type="number" size="5" + pref="cros.session.proxy.socksport" disabled> + </td> + </tr> + </table> + </div> + <div class="radio"> + <label> + <input id="autoProxy" type="radio" name="proxytype" value="3" + pref="cros.session.proxy.type"> + <span i18n-content="proxyAutomatic"></span> + </label> + </div> + <div> + <label> + <span i18n-content="proxyConfigUrl"></span> + <input id="proxyConfig" type="url" size="60" + pref="cros.session.proxy.pacurl"> + </label> + </div> + </div> + </section> + <section id="advancedConfig"> + <h3 i18n-content="advanced_proxy_config"></h3> + <div class="option"> + <div i18n-content="proxyBypass"></div> + <list id="ignoredHostList"></list> + <input id="newHost" type="url" size="30"> + <button id="addHost" i18n-content="addHost"></button> + <button id="removeHost" i18n-content="removeHost"></button> + </div> + </section> +</div> diff --git a/chrome/browser/resources/options2/chromeos/proxy_options.js b/chrome/browser/resources/options2/chromeos/proxy_options.js new file mode 100644 index 0000000..f42a08d --- /dev/null +++ b/chrome/browser/resources/options2/chromeos/proxy_options.js @@ -0,0 +1,244 @@ +// Copyright (c) 2011 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 Preferences = options.Preferences; + + ///////////////////////////////////////////////////////////////////////////// + // ProxyOptions class: + + /** + * Encapsulated handling of ChromeOS proxy options page. + * @constructor + */ + function ProxyOptions(model) { + OptionsPage.call(this, 'proxy', localStrings.getString('proxyPage'), + 'proxyPage'); + } + + cr.addSingletonGetter(ProxyOptions); + + /** + * UI pref change handler. + */ + function handlePrefUpdate(e) { + ProxyOptions.getInstance().updateControls(); + } + + /** + * Monitor pref change of given element. + */ + function observePrefsUI(el) { + Preferences.getInstance().addEventListener(el.pref, handlePrefUpdate); + } + + ProxyOptions.prototype = { + // Inherit ProxyOptions from OptionsPage. + __proto__: OptionsPage.prototype, + + /** + * Initializes ProxyOptions page. + */ + initializePage: function() { + // Call base class implementation to start preference initialization. + OptionsPage.prototype.initializePage.call(this); + + // Set up ignored page. + options.proxyexceptions.ProxyExceptions.decorate($('ignoredHostList')); + + this.addEventListener('visibleChange', this.handleVisibleChange_); + $('removeHost').addEventListener('click', this.handleRemoveExceptions_); + $('addHost').addEventListener('click', this.handleAddException_); + $('directProxy').addEventListener('click', this.disableManual_); + $('manualProxy').addEventListener('click', this.enableManual_); + $('autoProxy').addEventListener('click', this.disableManual_); + $('proxyAllProtocols').addEventListener('click', this.toggleSingle_); + + observePrefsUI($('directProxy')); + observePrefsUI($('manualProxy')); + observePrefsUI($('autoProxy')); + observePrefsUI($('proxyAllProtocols')); + }, + + proxyListInitialized_: false, + + /** + * Update controls state. + * @public + */ + updateControls: function() { + this.updateBannerVisibility_(); + this.toggleSingle_(); + if ($('manualProxy').checked) { + this.enableManual_(); + } else { + this.disableManual_(); + } + if (!this.proxyListInitialized_ && this.visible) { + this.proxyListInitialized_ = true; + $('ignoredHostList').redraw(); + } + }, + + /** + * Handler for OptionsPage's visible property change event. + * @private + * @param {Event} e Property change event. + */ + handleVisibleChange_: function(e) { + this.updateControls(); + }, + + /** + * Updates info banner visibility state. This function shows the banner + * if proxy is managed or shared-proxies is off for shared network. + * @private + */ + updateBannerVisibility_: function() { + var bannerDiv = $('info-banner'); + // Remove class and listener for click event in case they were added + // before and updateBannerVisibility_ is called repeatedly. + bannerDiv.classList.remove("clickable"); + bannerDiv.removeEventListener('click', this.handleSharedProxiesHint_); + + // Show banner and determine its message if necessary. + var controlledBy = $('directProxy').controlledBy; + if (controlledBy == '') { + bannerDiv.hidden = true; + } else { + bannerDiv.hidden = false; + // controlledBy must match strings loaded in proxy_handler.cc and + // set in proxy_cros_settings_provider.cc. + $('banner-text').textContent = localStrings.getString(controlledBy); + if (controlledBy == "enableSharedProxiesBannerText") { + bannerDiv.classList.add("clickable"); + bannerDiv.addEventListener('click', this.handleSharedProxiesHint_); + } + } + }, + + /** + * Handler for "click" event on yellow banner with enable-shared-proxies + * hint. + * @private + * @param {Event} e Click event fired from info-banner. + */ + handleSharedProxiesHint_: function(e) { + OptionsPage.navigateToPage("internet"); + }, + + /** + * Handler for when the user clicks on the checkbox to allow a + * single proxy usage. + * @private + * @param {Event} e Click Event. + */ + toggleSingle_: function(e) { + if ($('proxyAllProtocols').checked) { + $('multiProxy').style.display = 'none'; + $('singleProxy').style.display = 'block'; + } else { + $('multiProxy').style.display = 'block'; + $('singleProxy').style.display = 'none'; + } + }, + + /** + * Handler for selecting a radio button that will disable the manual + * controls. + * @private + * @param {Event} e Click event. + */ + disableManual_: function(e) { + $('advancedConfig').hidden = true; + $('proxyAllProtocols').disabled = true; + $('proxyHostName').disabled = true; + $('proxyHostPort').disabled = true; + $('proxyHostSingleName').disabled = true; + $('proxyHostSinglePort').disabled = true; + $('secureProxyHostName').disabled = true; + $('secureProxyPort').disabled = true; + $('ftpProxy').disabled = true; + $('ftpProxyPort').disabled = true; + $('socksHost').disabled = true; + $('socksPort').disabled = true; + $('proxyConfig').disabled = $('autoProxy').disabled || + !$('autoProxy').checked; + }, + + /** + * Handler for selecting a radio button that will enable the manual + * controls. + * @private + * @param {Event} e Click event. + */ + enableManual_: function(e) { + $('advancedConfig').hidden = false; + $('ignoredHostList').redraw(); + var all_disabled = $('manualProxy').disabled; + $('newHost').disabled = all_disabled; + $('removeHost').disabled = all_disabled; + $('addHost').disabled = all_disabled; + $('proxyAllProtocols').disabled = all_disabled; + $('proxyHostName').disabled = all_disabled; + $('proxyHostPort').disabled = all_disabled; + $('proxyHostSingleName').disabled = all_disabled; + $('proxyHostSinglePort').disabled = all_disabled; + $('secureProxyHostName').disabled = all_disabled; + $('secureProxyPort').disabled = all_disabled; + $('ftpProxy').disabled = all_disabled; + $('ftpProxyPort').disabled = all_disabled; + $('socksHost').disabled = all_disabled; + $('socksPort').disabled = all_disabled; + $('proxyConfig').disabled = true; + }, + + /** + * Handler for "add" event fired from userNameEdit. + * @private + * @param {Event} e Add event fired from userNameEdit. + */ + handleAddException_: function(e) { + var exception = $('newHost').value; + $('newHost').value = ''; + + exception = exception.trim(); + if (exception) + $('ignoredHostList').addException(exception); + }, + + /** + * Handler for when the remove button is clicked + * @private + */ + handleRemoveExceptions_: function(e) { + var selectedItems = $('ignoredHostList').selectedItems; + for (var x = 0; x < selectedItems.length; x++) { + $('ignoredHostList').removeException(selectedItems[x]); + } + }, + + /** + * Sets proxy page title using given network name. + * @param {string} network The network name to use in page title. + * @public + */ + setNetworkName: function(network) { + $('proxy-page-title').textContent = + localStrings.getStringF('proxyPageTitleFormat', network); + } + }; + + ProxyOptions.setNetworkName = function(network) { + ProxyOptions.getInstance().setNetworkName(network); + }; + + // Export + return { + ProxyOptions: ProxyOptions + }; + +}); diff --git a/chrome/browser/resources/options2/chromeos/proxy_rules_list.js b/chrome/browser/resources/options2/chromeos/proxy_rules_list.js new file mode 100644 index 0000000..7154495 --- /dev/null +++ b/chrome/browser/resources/options2/chromeos/proxy_rules_list.js @@ -0,0 +1,139 @@ +// Copyright (c) 2011 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.proxyexceptions', function() { + const List = cr.ui.List; + const ListItem = cr.ui.ListItem; + const ArrayDataModel = cr.ui.ArrayDataModel; + + /** + * Creates a new exception list. + * @param {Object=} opt_propertyBag Optional properties. + * @constructor + * @extends {cr.ui.List} + */ + var ProxyExceptions = cr.ui.define('list'); + + ProxyExceptions.prototype = { + __proto__: List.prototype, + + pref: 'cros.session.proxy.ignorelist', + + /** @inheritDoc */ + decorate: function() { + List.prototype.decorate.call(this); + + // HACK(arv): http://crbug.com/40902 + window.addEventListener('resize', this.redraw.bind(this)); + + this.addEventListener('click', this.handleClick_); + + var self = this; + + // Listens to pref changes. + Preferences.getInstance().addEventListener(this.pref, + function(event) { + self.load_(event.value); + }); + }, + + createItem: function(exception) { + return new ProxyExceptionsItem(exception); + }, + + /** + * Adds given exception to model and update backend. + * @param {Object} exception A exception to be added to exception list. + */ + addException: function(exception) { + this.dataModel.push(exception); + this.updateBackend_(); + }, + + /** + * Removes given exception from model and update backend. + */ + removeException: function(exception) { + var dataModel = this.dataModel; + + var index = dataModel.indexOf(exception); + if (index >= 0) { + dataModel.splice(index, 1); + this.updateBackend_(); + } + }, + + /** + * Handles the clicks on the list and triggers exception removal if the + * click is on the remove exception button. + * @private + * @param {!Event} e The click event object. + */ + handleClick_: function(e) { + // Handle left button click + if (e.button == 0) { + var el = e.target; + if (el.className == 'remove-exception-button') { + this.removeException(el.parentNode.exception); + } + } + }, + + /** + * Loads given exception list. + * @param {Array} exceptions An array of exception object. + */ + load_: function(exceptions) { + this.dataModel = new ArrayDataModel(exceptions); + }, + + /** + * Updates backend. + */ + updateBackend_: function() { + Preferences.setListPref(this.pref, this.dataModel.slice()); + } + }; + + /** + * Creates a new exception list item. + * @param exception The exception account this represents. + * @constructor + * @extends {cr.ui.ListItem} + */ + function ProxyExceptionsItem(exception) { + var el = cr.doc.createElement('div'); + el.exception = exception; + ProxyExceptionsItem.decorate(el); + return el; + } + + /** + * Decorates an element as a exception account item. + * @param {!HTMLElement} el The element to decorate. + */ + ProxyExceptionsItem.decorate = function(el) { + el.__proto__ = ProxyExceptionsItem.prototype; + el.decorate(); + }; + + ProxyExceptionsItem.prototype = { + __proto__: ListItem.prototype, + + /** @inheritDoc */ + decorate: function() { + ListItem.prototype.decorate.call(this); + this.className = 'exception-list-item'; + + var labelException = this.ownerDocument.createElement('span'); + labelException.className = ''; + labelException.textContent = this.exception; + this.appendChild(labelException); + } + }; + + return { + ProxyExceptions: ProxyExceptions + }; +}); diff --git a/chrome/browser/resources/options2/chromeos/system_options.html b/chrome/browser/resources/options2/chromeos/system_options.html new file mode 100644 index 0000000..bab4667 --- /dev/null +++ b/chrome/browser/resources/options2/chromeos/system_options.html @@ -0,0 +1,125 @@ +<div id="systemPage" class="page" hidden> + <h1 i18n-content="systemPage"></h1> + <div class="displaytable"> + <section> + <h3 i18n-content="datetimeTitle"></h3> + <div class="option-control-table"> + <span class="option-name" i18n-content="timezone"></span> + <div id="timezone-value"> + <select id="timezone-select" class="control" + i18n-options="timezoneList" + data-type="string" + pref="cros.system.timezone"></select> + </div> + <div class="checkbox"> + <label> + <input id="use-24hour-clock" + pref="settings.clock.use_24hour_clock" + type="checkbox"> + <span i18n-content="use24HourClock"></span> + </label> + </div> + </div> + </section> + <section> + <h3 i18n-content="screen"></h3> + <div id="brightness-value"> + <span i18n-content="brightness"></span> + <button id="brightness-decrease-button" + i18n-content="brightnessDecrease"></button> + <button id="brightness-increase-button" + i18n-content="brightnessIncrease"></button> + </div> + </section> + <section id="touchpad-controls" hidden> + <h3 i18n-content="touchpad"></h3> + <div class="option-control-table"> + <span class="option-name" i18n-content="sensitivity"></span> + <div id="touchpad-value"> + <div id="slider-control"> + <input id="sensitivity-range" type="range" min="1" max="5" + pref="settings.touchpad.sensitivity2" class="touch-slider"> + <div> + <span i18n-content="sensitivityLess"></span> + <span i18n-content="sensitivityMore" + class="touchpad-sensitivity-more"></span> + </div> + </div> + </div> + <div id="tap-to-click" class="checkbox"> + <label> + <input id="tap-to-click-check" + pref="settings.touchpad.enable_tap_to_click" + type="checkbox"> + <span i18n-content="enableTapToClick"></span> + </label> + </div> + </div> + </section> + <!-- By default, the bluetooth section is hidden. It is only + visible if the command line flag --enable_bluetooth is set. --> + <section id="bluetooth-devices" hidden> + <h3 i18n-content="bluetooth"></h3> + <div id="bluetooth-options-div"> + <div id="bluetooth-buttons"> + <button id="enable-bluetooth" i18n-content="enableBluetooth" hidden> + <button id="disable-bluetooth" i18n-content="disableBluetooth"> + </div> + <div id="no-bluetooth-devices-label" + i18n-content="noBluetoothDevicesFound"> + </div> + <div id="bluetooth-device-list"> + <!-- A list of connected devices is inserted here on page load. --> + <!-- The list of devices is updated asynchronously on clicking + 'Find Devices'. --> + </div> + <!-- Template for items in list of Bluetooth devices --> + <div id="bluetooth-item-template" class="bluetooth-item" hidden> + <div class="bluetooth-item-text"> + <div class="network-name-label"></div> + <div class="bluetooth-status"> + <span class="network-status-label"></span> + <div class="inline-spinner" hidden></div> + </div> + <div class="bluetooth-instructions"></div> + </div> + <div class="bluetooth-button-group"></div> + </div> + <div id = "bluetooth-finder-container"> + <button id="bluetooth-find-devices" + i18n-content="findBluetoothDevices"></button> + <div id="bluetooth-scanning-icon" + class="inline-spinner transparent"></div> + <span id="bluetooth-scanning-label" class="transparent" + i18n-content="bluetoothScanning"></span> + </div> + </div> + </section> + <section> + <h3 i18n-content="language"></h3> + <div class="option-control-table"> + <div class="option-name"> + <button id="language-button" i18n-content="languageCustomize"> + </button> + </div> + <div class="option-name"> + <button id="modifier-keys-button" + i18n-content="modifierKeysCustomize"></button> + </div> + </div> + </section> + <section> + <h3 i18n-content="accessibilityTitle"></h3> + <div class="option-control-table"> + <div class="option-name"> + <div class="checkbox"> + <label> + <input id="accesibility-check" type="checkbox"> + <span i18n-content="accessibility"></span> + </label> + </div> + </div> + </div> + </section> + </div> +</div> diff --git a/chrome/browser/resources/options2/chromeos/system_options.js b/chrome/browser/resources/options2/chromeos/system_options.js new file mode 100644 index 0000000..0a9faeb --- /dev/null +++ b/chrome/browser/resources/options2/chromeos/system_options.js @@ -0,0 +1,202 @@ +// Copyright (c) 2011 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 RepeatingButton = cr.ui.RepeatingButton; + + ///////////////////////////////////////////////////////////////////////////// + // SystemOptions class: + + /** + * Encapsulated handling of ChromeOS system options page. + * @constructor + */ + + function SystemOptions() { + OptionsPage.call(this, 'system', templateData.systemPageTabTitle, + 'systemPage'); + } + + cr.addSingletonGetter(SystemOptions); + + // Inherit SystemOptions from OptionsPage. + SystemOptions.prototype = { + __proto__: options.OptionsPage.prototype, + + /** + * Initializes SystemOptions page. + * Calls base class implementation to starts preference initialization. + */ + initializePage: function() { + OptionsPage.prototype.initializePage.call(this); + + // Disable time-related settings if we're not logged in as a real user. + if (AccountsOptions.loggedInAsGuest()) { + var timezone = $('timezone-select'); + if (timezone) + timezone.disabled = true; + var use_24hour_clock = $('use-24hour-clock'); + if (use_24hour_clock) + use_24hour_clock.disabled = true; + } + + options.system.bluetooth.BluetoothListElement.decorate( + $('bluetooth-device-list')); + + // TODO(kevers): Populate list of connected bluetooth devices. + // Set state of 'Enable bluetooth' checkbox. + $('bluetooth-find-devices').onclick = function(event) { + findBluetoothDevices_(); + }; + $('enable-bluetooth').onclick = function(event) { + chrome.send('bluetoothEnableChange', [Boolean(true)]); + }; + $('disable-bluetooth').onclick = function(event) { + chrome.send('bluetoothEnableChange', [Boolean(false)]); + }; + $('language-button').onclick = function(event) { + OptionsPage.navigateToPage('language'); + }; + $('modifier-keys-button').onclick = function(event) { + OptionsPage.navigateToPage('languageCustomizeModifierKeysOverlay'); + }; + $('accesibility-check').onchange = function(event) { + chrome.send('accessibilityChange', + [String($('accesibility-check').checked)]); + }; + initializeBrightnessButton_('brightness-decrease-button', + 'decreaseScreenBrightness'); + initializeBrightnessButton_('brightness-increase-button', + 'increaseScreenBrightness'); + } + }; + + /** + * Initializes a button for controlling screen brightness. + * @param {string} id Button ID. + * @param {string} callback Name of the callback function. + */ + function initializeBrightnessButton_(id, callback) { + var button = $(id); + cr.ui.decorate(button, RepeatingButton); + button.repeatInterval = 300; + button.addEventListener(RepeatingButton.Event.BUTTON_HELD, function(e) { + chrome.send(callback); + }); + } + + /** + * Scan for bluetooth devices. + * @private + */ + function findBluetoothDevices_() { + setVisibility_('bluetooth-scanning-label', true); + setVisibility_('bluetooth-scanning-icon', true); + + // Remove devices that are not currently connected. + var devices = $('bluetooth-device-list').childNodes; + for (var i = devices.length - 1; i >= 0; i--) { + var device = devices.item(i); + var data = device.data; + if (!data || data.status !== 'connected') + $('bluetooth-device-list').removeChild(device); + } + chrome.send('findBluetoothDevices'); + } + + /** + * Sets the visibility of an element. + * @param {string} id The id of the element. + * @param {boolean} visible True if the element should be made visible. + * @private + */ + function setVisibility_(id, visible) { + if (visible) + $(id).classList.remove("transparent"); + else + $(id).classList.add("transparent"); + } + + // + // Chrome callbacks + // + + /** + * Set the initial state of the accessibility checkbox. + */ + SystemOptions.SetAccessibilityCheckboxState = function(checked) { + $('accesibility-check').checked = checked; + }; + + /** + * Activate the bluetooth settings section on the System settings page. + */ + SystemOptions.showBluetoothSettings = function() { + $('bluetooth-devices').hidden = false; + }; + + /** + * 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. + */ + SystemOptions.setBluetoothState = function(checked) { + $('disable-bluetooth').hidden = !checked; + $('enable-bluetooth').hidden = checked; + $('bluetooth-finder-container').hidden = !checked; + $('no-bluetooth-devices-label').hidden = !checked; + if (!checked) { + setVisibility_('bluetooth-scanning-label', false); + setVisibility_('bluetooth-scanning-icon', false); + } + // Flush list of previously discovered devices if bluetooth is turned off. + if (!checked) { + var devices = $('bluetooth-device-list').childNodes; + for (var i = devices.length - 1; i >= 0; i--) { + var device = devices.item(i); + $('bluetooth-device-list').removeChild(device); + } + } + } + + /** + * 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, + * icon: string, + * paired: boolean, + * connected: boolean}} device + * Decription of the bluetooth device. + */ + SystemOptions.addBluetoothDevice = function(device) { + if ($('bluetooth-device-list').appendDevice(device)) + $('no-bluetooth-devices-label').hidden = true; + }; + + /** + * Hides the scanning label and icon that are used to indicate that a device + * search is in progress. + */ + SystemOptions.notifyBluetoothSearchComplete = function() { + setVisibility_('bluetooth-scanning-label', false); + setVisibility_('bluetooth-scanning-icon', false); + }; + + /** + * Displays the Touchpad Controls section when we detect a touchpad. + */ + SystemOptions.showTouchpadControls = function() { + $('touchpad-controls').hidden = false; + }; + + // Export + return { + SystemOptions: SystemOptions + }; + +}); diff --git a/chrome/browser/resources/options2/chromeos/system_options_page.css b/chrome/browser/resources/options2/chromeos/system_options_page.css new file mode 100644 index 0000000..d38ebdc --- /dev/null +++ b/chrome/browser/resources/options2/chromeos/system_options_page.css @@ -0,0 +1,167 @@ +.touchpad-sensitivity-more { + float: right; +} + +html[dir=rtl] .touchpad-sensitivity-more { + float: left; +} + +.option-name { + display: inline; +} + +#timezone-value { + display: inline-block; + vertical-align: baseline; +} + +#touchpad-value, +#slider-control { + display: inline-block; + vertical-align: top; +} + +#bluetooth-options-div { + -webkit-box-orient: vertical; + display: -webkit-box; +} + +#no-bluetooth-devices-label { + -webkit-margin-after: 5px; + -webkit-margin-before: 5px; + color: gray; +} + +#bluetooth-finder-container, +#bluetooth-scanning-status { + -webkit-box-orient: horizontal; + display: -webkit-box; + vertical-align: baseline; +} + +#bluetooth-scanning-label, +#bluetooth-scanning-icon { + -webkit-transition: 250ms opacity; +} + +#bluetooth-scanning-label { + -webkit-margin-start: 5px; + color: gray; +} + +#bluetooth-scanning-icon { + -webkit-margin-start: 10px; + vertical-align: middle; +} + +#bluetooth-device-list { + display: table; + width: 100%; +} + +#bluetooth-device-list > * { + display: table-row; +} + +#bluetooth-device-list > * > * { + border-bottom: 4px solid rgba(255,255,255,1); + display: table-cell; +} + +.bluetooth-item > * > button { + visibility: hidden; + width: 100%; +} + +.bluetooth-item:first-child > * { + border-top: 4px solid rgba(255,255,255,1); +} + +.bluetooth-item:hover > * > button, +.bluetooth-item[connected] > * > button, +.bluetooth-item[connecting] > * > button { + visibility: visible; +} + +.bluetooth-item[connected] { + background-color: hsl(214, 91%, 89%); + background-image: -webkit-linear-gradient(rgba(255, 255, 255, 0.8), + rgba(255, 255, 255, 0)); + border-color: hsl(214, 91%, 65%); +} + +.bluetooth-item[paired] { + color: gray; +} + +.bluetooth-item:hover, +.bluetooth-item[connecting] { + background-color: hsl(214, 91%, 97%); + border-color: hsl(214, 91%, 65%); +} + +.bluetooth-item-text { + -webkit-padding-after: 3px; + -webkit-padding-before: 3px; + -webkit-padding-end: 5px; + -webkit-padding-start: 5px; + width: 100%; +} + +.bluetooth-item-text > * > .inline-spinner { + -webkit-margin-start: 5px; + -webkit-transform: translateY(3px); +} + +.bluetooth-instructions { + -webkit-margin-after: 5px; + -webkit-margin-before: 5px; + display: block; + line-height: 120%; +} + +.bluetooth-remote-passkey { + -webkit-box-align: baseline; + -webkit-box-orient: horizontal; + display: -webkit-inline-box; + margin-bottom: 5px; + margin-top: 5px; +} + +.bluetooth-confirm-passkey { + display: inline; + font-weight: bold; +} + +.bluetooth-passkey-char { + -webkit-margin-end: 3px; + -webkit-margin-start: 3px; + border: 1px solid black; + display: -webkit-box; + font-weight: bold; + padding: 2px; +} + +.bluetooth-passkey-char:first-child { + -webkit-margin-start: 10px; +} + +.bluetooth-passkey-char:last-child { + -webkit-margin-end: 10px; +} + +.bluetooth-passkey-char.key-typed { + background-color: hsl(214, 91%, 50%); + color: white; +} + +.bluetooth-passkey-field { + -webkit-margin-start: 10px; + width: 100px; +} + +.bluetooth-button-group { + -webkit-padding-end: 5px; + vertical-align: middle; +} + diff --git a/chrome/browser/resources/options2/chromeos/virtual_keyboard.css b/chrome/browser/resources/options2/chromeos/virtual_keyboard.css new file mode 100644 index 0000000..0752b3b --- /dev/null +++ b/chrome/browser/resources/options2/chromeos/virtual_keyboard.css @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2011 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. + */ + +.virtual-keyboard-column-headers { + display: -webkit-box; + font-size: 13px; + font-weight: bold; +} + +.virtual-keyboard-layout-column { + width: 250px; + -webkit-margin-end: 10px; + -webkit-margin-start: 14px; +} + +#virtual-keyboard-manager list { + border-radius: 2px; + border: solid 1px #D9D9D9; + margin-bottom: 10px; + margin-top: 4px; +} diff --git a/chrome/browser/resources/options2/chromeos/virtual_keyboard.html b/chrome/browser/resources/options2/chromeos/virtual_keyboard.html new file mode 100644 index 0000000..c2c15fb --- /dev/null +++ b/chrome/browser/resources/options2/chromeos/virtual_keyboard.html @@ -0,0 +1,13 @@ +<div id="virtual-keyboard-manager" class="page" hidden> + <h1 i18n-content="virtualKeyboardPage"></h1> + <div class="virtual-keyboard-column-headers"> + <div class="virtual-keyboard-layout-column"> + <h3 i18n-content="virtualKeyboardLayoutColumnTitle"></h3> + </div> + <div class="virtual-keyboard-keyboard-column"> + <h3 i18n-content="virtualKeyboardKeyboardColumnTitle"></h3> + </div> + </div> + <list id="virtual-keyboard-per-layout-list"></list> + <!-- TODO(yusukes): Add virtual-keyboards-per-site elements. --> +</div> diff --git a/chrome/browser/resources/options2/chromeos/virtual_keyboard.js b/chrome/browser/resources/options2/chromeos/virtual_keyboard.js new file mode 100644 index 0000000..359d190 --- /dev/null +++ b/chrome/browser/resources/options2/chromeos/virtual_keyboard.js @@ -0,0 +1,92 @@ +// Copyright (c) 2011 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() { + const OptionsPage = options.OptionsPage; + + ///////////////////////////////////////////////////////////////////////////// + // VirtualKeyboardManager class: + + /** + * Virtual keyboard management page. + * @constructor + */ + function VirtualKeyboardManager() { + this.activeNavTab = null; + OptionsPage.call(this, + 'virtualKeyboards', + // The templateData.virtualKeyboardPageTabTitle is added + // in OptionsPageUIHandler::RegisterTitle(). + templateData.virtualKeyboardPageTabTitle, + 'virtual-keyboard-manager'); + } + + cr.addSingletonGetter(VirtualKeyboardManager); + + VirtualKeyboardManager.prototype = { + __proto__: OptionsPage.prototype, + + /** + * The virtual keyboards list. + * @type {ItemList} + * @private + */ + virtualKeyboardsList_: null, + + /** @inheritDoc */ + initializePage: function() { + OptionsPage.prototype.initializePage.call(this); + this.createVirtualKeyboardsList_(); + }, + + /** @inheritDoc */ + didShowPage: function() { + chrome.send('updateVirtualKeyboardList'); + }, + + /** + * Creates, decorates and initializes the keyboards list. + * @private + */ + createVirtualKeyboardsList_: function() { + this.virtualKeyboardsList_ = $('virtual-keyboard-per-layout-list'); + options.VirtualKeyboardsList.decorate(this.virtualKeyboardsList_); + this.virtualKeyboardsList_.autoExpands = true; + }, + }; + + /** + * Sets the list of virtual keyboards shown in the view. This function is + * called by C++ code (e.g. chrome/browser/ui/webui/options/chromeos/). + * @param {Object} list A list of layouts with their registered virtual + * keyboards. + */ + VirtualKeyboardManager.updateVirtualKeyboardList = function(list) { + // See virtual_keyboard_list.js for an example of the format the list should + // take. + var filteredList = list.filter(function(element, index, array) { + // Don't show a layout which is supported by only one virtual keyboard + // extension. + return element.supportedKeyboards.length > 1; + }); + + // Sort the drop-down menu items by name. + filteredList.forEach(function(e) { + e.supportedKeyboards.sort(function(e1, e2) { + return e1.name > e2.name; + }); + }); + + // Sort the list by layout name. + $('virtual-keyboard-per-layout-list').setVirtualKeyboardList( + filteredList.sort(function(e1, e2) { + return e1.layoutName > e2.layoutName; + })); + }; + + // Export + return { + VirtualKeyboardManager: VirtualKeyboardManager, + }; +}); diff --git a/chrome/browser/resources/options2/chromeos/virtual_keyboard_list.js b/chrome/browser/resources/options2/chromeos/virtual_keyboard_list.js new file mode 100644 index 0000000..650de1a --- /dev/null +++ b/chrome/browser/resources/options2/chromeos/virtual_keyboard_list.js @@ -0,0 +1,146 @@ +// Copyright (c) 2011 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() { + const ArrayDataModel = cr.ui.ArrayDataModel; + const List = cr.ui.List; + const ListItem = cr.ui.ListItem; + const VirtualKeyboardOptions = options.VirtualKeyboardOptions; + + const localStrings = new LocalStrings(); + + /** + * Creates a virtual keyboard list item. + * + * Accepts values in the form + * { layout: 'us(dvorak)', + * layoutName: 'US Dvorak layout', + * preferredKeyboard: 'http://...', [optional] + * supportedKeyboards: [ + * { name: 'Simple Virtual Keyboard', + * isSystem: true, + * url: 'http://...' }, + * { name: '3rd party Virtual Keyboard', + * isSystem: false, + * url: 'http://...' }, + * ..., + * ] + * } + * @param {Object} entry A dictionary describing the virtual keyboards for a + * given layout. + * @constructor + * @extends {cr.ui.ListItem} + */ + function VirtualKeyboardListItem(entry) { + var el = cr.doc.createElement('div'); + el.dataItem = entry; + el.__proto__ = VirtualKeyboardListItem.prototype; + el.decorate(); + return el; + } + + VirtualKeyboardListItem.prototype = { + __proto__: ListItem.prototype, + + buildWidget_: function(data, delegate) { + // Layout name. + var layoutNameElement = document.createElement('div'); + layoutNameElement.textContent = data.layoutName; + layoutNameElement.className = 'virtual-keyboard-layout-column'; + this.appendChild(layoutNameElement); + + // Virtual keyboard selection. + var keyboardElement = document.createElement('div'); + var selectElement = document.createElement('select'); + var defaultOptionElement = document.createElement('option'); + defaultOptionElement.selected = (data.preferredKeyboard == null); + defaultOptionElement.textContent = + localStrings.getString('defaultVirtualKeyboard'); + defaultOptionElement.value = -1; + selectElement.appendChild(defaultOptionElement); + + for (var i = 0; i < data.supportedKeyboards.length; ++i) { + var optionElement = document.createElement('option'); + optionElement.selected = + (data.preferredKeyboard != null && + data.preferredKeyboard == data.supportedKeyboards[i].url); + optionElement.textContent = data.supportedKeyboards[i].name; + optionElement.value = i; + selectElement.appendChild(optionElement); + } + + selectElement.addEventListener('change', function(e) { + var index = e.target.value; + if (index == -1) { + // The 'Default' menu item is selected. Delete the preference. + delegate.clearPreference(data.layout); + } else { + delegate.setPreference( + data.layout, data.supportedKeyboards[index].url); + } + }); + + keyboardElement.appendChild(selectElement); + keyboardElement.className = 'virtual-keyboard-keyboard-column'; + this.appendChild(keyboardElement); + }, + + /** @inheritDoc */ + decorate: function() { + ListItem.prototype.decorate.call(this); + + var delegate = { + clearPreference: function(layout) { + // Call a C++ function in chrome/browser/ui/webui/options/chromeos/. + chrome.send('clearVirtualKeyboardPreference', [layout]); + }, + setPreference: function(layout, url) { + chrome.send('setVirtualKeyboardPreference', [layout, url]); + }, + }; + + this.buildWidget_(this.dataItem, delegate); + }, + }; + + /** + * Create a new virtual keyboard list. + * @constructor + * @extends {cr.ui.List} + */ + var VirtualKeyboardsList = cr.ui.define('list'); + + VirtualKeyboardsList.prototype = { + __proto__: List.prototype, + + /** @inheritDoc */ + createItem: function(entry) { + return new VirtualKeyboardListItem(entry); + }, + + /** + * The length of the list. + */ + get length() { + return this.dataModel.length; + }, + + /** + * Set the virtual keyboards displayed by this list. + * See VirtualKeyboardListItem for an example of the format the list should + * take. + * + * @param {Object} list A list of layouts with their registered virtual + * keyboards. + */ + setVirtualKeyboardList: function(list) { + this.dataModel = new ArrayDataModel(list); + }, + }; + + return { + VirtualKeyboardListItem: VirtualKeyboardListItem, + VirtualKeyboardsList: VirtualKeyboardsList, + }; +}); diff --git a/chrome/browser/resources/options2/clear_browser_data_overlay.css b/chrome/browser/resources/options2/clear_browser_data_overlay.css new file mode 100644 index 0000000..b2cf716 --- /dev/null +++ b/chrome/browser/resources/options2/clear_browser_data_overlay.css @@ -0,0 +1,24 @@ +/* +Copyright (c) 2011 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. +*/ + +#clearBrowserDataOverlay { + min-width: 500px; +} + +#clearBrowserDataOverlay > .content-area label { + margin: 5px 0; +} + +#clear-data-checkboxes { + -webkit-padding-start: 8px; + margin: 5px 0; +} + +#cbdThrobber { + margin: 4px 10px; + vertical-align: middle; + visibility: hidden; +} diff --git a/chrome/browser/resources/options2/clear_browser_data_overlay.html b/chrome/browser/resources/options2/clear_browser_data_overlay.html new file mode 100644 index 0000000..26ee6e1 --- /dev/null +++ b/chrome/browser/resources/options2/clear_browser_data_overlay.html @@ -0,0 +1,73 @@ +<div id="clearBrowserDataOverlay" class="page" hidden> + <h1 i18n-content="clearBrowserDataOverlay"></h1> + <div id="cbdContentArea" class="content-area"> + <span i18n-content="clearBrowserDataLabel"></span> + <select id="clearBrowserDataTimePeriod" + i18n-options="clearBrowserDataTimeList" + pref="browser.clear_data.time_period" + data-type="number"> + </select> + <div id="clear-data-checkboxes"> + <div class="checkbox"> + <label> + <input id="deleteBrowsingHistoryCheckbox" + pref="browser.clear_data.browsing_history" type="checkbox"> + <span i18n-content="deleteBrowsingHistoryCheckbox"></span> + </label> + </div> + <div class="checkbox"> + <label> + <input id="deleteDownloadHistoryCheckbox" + pref="browser.clear_data.download_history" type="checkbox"> + <span i18n-content="deleteDownloadHistoryCheckbox"></span> + </label> + </div> + <div class="checkbox"> + <label> + <input id="deleteCacheCheckbox" + pref="browser.clear_data.cache" type="checkbox"> + <span i18n-content="deleteCacheCheckbox"></span> + </label> + </div> + <div class="checkbox"> + <label> + <input id="deleteCookiesCheckbox" + pref="browser.clear_data.cookies" type="checkbox"> + <span i18n-content="deleteCookiesFlashCheckbox" + class="clear-plugin-lso-data-enabled"></span> + <span i18n-content="deleteCookiesCheckbox" + class="clear-plugin-lso-data-disabled"></span> + </label> + </div> + <div class="checkbox"> + <label> + <input id="deletePasswordsCheckbox" + pref="browser.clear_data.passwords" type="checkbox"> + <span i18n-content="deletePasswordsCheckbox"></span> + </label> + </div> + <div class="checkbox"> + <label> + <input id="deleteFormDataCheckbox" + pref="browser.clear_data.form_data" type="checkbox"> + <span i18n-content="deleteFormDataCheckbox"></span> + </label> + </div> + </div> + </div> + <div class="action-area"> + <div class="flash-plugin-area"> + <a target="_blank" i18n-content="flash_storage_settings" + i18n-values="href:flash_storage_url"></a> + </div> + <div class="action-area-right"> + <div id="cbdThrobber" class="throbber"></div> + <div class="button-strip"> + <button id="clearBrowserDataDismiss" i18n-content="cancel"></button> + <button id="clearBrowserDataCommit" + i18n-content="clearBrowserDataCommit"> + </button> + </div> + </div> + </div> +</div> diff --git a/chrome/browser/resources/options2/clear_browser_data_overlay.js b/chrome/browser/resources/options2/clear_browser_data_overlay.js new file mode 100644 index 0000000..8533ccc --- /dev/null +++ b/chrome/browser/resources/options2/clear_browser_data_overlay.js @@ -0,0 +1,110 @@ +// Copyright (c) 2011 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; + + /** + * ClearBrowserDataOverlay class + * Encapsulated handling of the 'Clear Browser Data' overlay page. + * @class + */ + function ClearBrowserDataOverlay() { + OptionsPage.call(this, 'clearBrowserData', + templateData.clearBrowserDataOverlayTabTitle, + 'clearBrowserDataOverlay'); + } + + cr.addSingletonGetter(ClearBrowserDataOverlay); + + ClearBrowserDataOverlay.prototype = { + // Inherit ClearBrowserDataOverlay from OptionsPage. + __proto__: OptionsPage.prototype, + + /** + * Initialize the page. + */ + initializePage: function() { + // Call base class implementation to starts preference initialization. + OptionsPage.prototype.initializePage.call(this); + + var f = this.updateCommitButtonState_.bind(this); + var types = ['browser.clear_data.browsing_history', + 'browser.clear_data.download_history', + 'browser.clear_data.cache', + 'browser.clear_data.cookies', + 'browser.clear_data.passwords', + 'browser.clear_data.form_data']; + types.forEach(function(type) { + Preferences.getInstance().addEventListener(type, f); + }); + + var checkboxes = document.querySelectorAll( + '#cbdContentArea input[type=checkbox]'); + for (var i = 0; i < checkboxes.length; i++) { + checkboxes[i].onclick = f; + } + this.updateCommitButtonState_(); + + $('clearBrowserDataDismiss').onclick = function(event) { + ClearBrowserDataOverlay.dismiss(); + }; + $('clearBrowserDataCommit').onclick = function(event) { + chrome.send('performClearBrowserData'); + }; + }, + + // Set the enabled state of the commit button. + updateCommitButtonState_: function() { + var checkboxes = document.querySelectorAll( + '#cbdContentArea input[type=checkbox]'); + var isChecked = false; + for (var i = 0; i < checkboxes.length; i++) { + if (checkboxes[i].checked) { + isChecked = true; + break; + } + } + $('clearBrowserDataCommit').disabled = !isChecked; + }, + }; + + // + // Chrome callbacks + // + ClearBrowserDataOverlay.setClearingState = function(state) { + $('deleteBrowsingHistoryCheckbox').disabled = state; + $('deleteDownloadHistoryCheckbox').disabled = state; + $('deleteCacheCheckbox').disabled = state; + $('deleteCookiesCheckbox').disabled = state; + $('deletePasswordsCheckbox').disabled = state; + $('deleteFormDataCheckbox').disabled = state; + $('clearBrowserDataTimePeriod').disabled = state; + $('cbdThrobber').style.visibility = state ? 'visible' : 'hidden'; + + if (state) + $('clearBrowserDataCommit').disabled = true; + else + ClearBrowserDataOverlay.getInstance().updateCommitButtonState_(); + }; + + ClearBrowserDataOverlay.doneClearing = function() { + // The delay gives the user some feedback that the clearing + // actually worked. Otherwise the dialog just vanishes instantly in most + // cases. + window.setTimeout(function() { + ClearBrowserDataOverlay.dismiss(); + }, 200); + }; + + ClearBrowserDataOverlay.dismiss = function() { + OptionsPage.closeOverlay(); + this.setClearingState(false); + }; + + // Export + return { + ClearBrowserDataOverlay: ClearBrowserDataOverlay + }; +}); diff --git a/chrome/browser/resources/options2/content_settings.css b/chrome/browser/resources/options2/content_settings.css new file mode 100644 index 0000000..625ab18 --- /dev/null +++ b/chrome/browser/resources/options2/content_settings.css @@ -0,0 +1,74 @@ +/* +Copyright (c) 2011 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. +*/ + +.exception-pattern { + -webkit-box-flex: 1; + -webkit-margin-end: 10px; + -webkit-margin-start: 14px; +} + +.exception-setting { + display: inline-block; + width: 120px; +} + +select.exception-setting { + vertical-align: middle; +} + +#exception-column-headers { + -webkit-margin-start: 17px; + display: -webkit-box; + margin-top: 17px; +} + +#exception-column-headers > div { + font-weight: bold; +} + +#exception-pattern-column { + -webkit-box-flex: 1; +} + +#exception-behavior-column { + width: 145px; +} + +.otr-explanation { + font-style: italic; +} + +#content-settings-exceptions-area list { + margin-bottom: 10px; + margin-top: 4px; +} + +#disable-plugins-container { + margin: 7px 0px; +} + +div[role="listitem"][managedby] { + color: #666; + font-style: italic; + position: relative; +} + +.settings-list div[role="listitem"][managedby="policy"], +.settings-list div[role="listitem"][managedby="extension"] { + background: -webkit-linear-gradient(#fff1b5, #fae692); + border-top: 0; + border-bottom: 1px solid #c9bd8d; +} + +list div[role="listitem"][managedby="policy"] .close-button { + background-image: url("chrome://theme/IDR_MANAGED"); + opacity: 1; +} + +list div[role="listitem"][managedby="extension"] .close-button { + background-image: url("chrome://theme/IDR_EXTENSIONS_SECTION_SMALL"); + opacity: 1; +} diff --git a/chrome/browser/resources/options2/content_settings.html b/chrome/browser/resources/options2/content_settings.html new file mode 100644 index 0000000..6d735f3 --- /dev/null +++ b/chrome/browser/resources/options2/content_settings.html @@ -0,0 +1,279 @@ +<div id="content-settings-page" class="page" hidden> + <h1 i18n-content="contentSettingsPage"></h1> + <div class="displaytable"> + <!-- Cookie filter tab contents --> + <section> + <h3 i18n-content="cookies_tab_label"></h3> + <div> + <div class="radio"> + <label> + <input type="radio" name="cookies" value="allow"> + <span i18n-content="cookies_allow"></span> + </label> + </div> + <div class="radio"> + <label> + <input type="radio" name="cookies" value="session"> + <span i18n-content="cookies_session_only"></span> + </label> + </div> + <div class="radio"> + <label> + <input type="radio" name="cookies" value="block"> + <span i18n-content="cookies_block"></span> + </label> + </div> + <div class="checkbox"> + <label> + <input pref="profile.block_third_party_cookies" type="checkbox"> + <span i18n-content="cookies_block_3rd_party"></span> + </label> + </div> + <div class="checkbox"> + <label> + <input id="clear-cookies-on-exit" + pref="profile.clear_site_data_on_exit" type="checkbox"> + <span i18n-content="cookies_lso_clear_when_close" + class="clear-plugin-lso-data-enabled"></span> + <span i18n-content="cookies_clear_when_close" + class="clear-plugin-lso-data-disabled"></span> + </label> + </div> + <button class="exceptions-list-button" contentType="cookies" + i18n-content="manage_exceptions"></button> + <button id="show-cookies-button" + i18n-content="cookies_show_cookies"></button> + </div> + </section> + <!-- Image filter --> + <section> + <h3 i18n-content="images_tab_label"></h3> + <div> + <div class="radio"> + <label> + <input type="radio" name="images" value="allow"> + <span i18n-content="images_allow"></span> + </label> + </div> + <div class="radio"> + <label> + <input type="radio" name="images" value="block"> + <span i18n-content="images_block"></span> + </label> + </div> + <button class="exceptions-list-button" contentType="images" + i18n-content="manage_exceptions"></button> + </div> + </section> + <!-- JavaScript filter --> + <section> + <h3 i18n-content="javascript_tab_label"></h3> + <div> + <div class="radio"> + <label> + <input type="radio" name="javascript" value="allow"> + <span i18n-content="javascript_allow"></span> + </label> + </div> + <div class="radio"> + <label> + <input type="radio" name="javascript" value="block"> + <span i18n-content="javascript_block"></span> + </label> + </div> + <button class="exceptions-list-button" contentType="javascript" + i18n-content="manage_exceptions"></button> + </div> + </section> + <!-- Handlers settings --> + <if expr="pp_ifdef('enable_register_protocol_handler')"> + <section id="handlers-section"> + <h3 i18n-content="handlers_tab_label"></h3> + <div> + <div class="radio"> + <label> + <input type="radio" name="handlers" value="allow" + class="handler-radio"> + <span i18n-content="handlers_allow"></span> + </label> + </div> + <div class="radio"> + <label> + <input type="radio" name="handlers" value="block" + class="handler-radio"> + <span i18n-content="handlers_block"></span> + </label> + </div> + <button id="manage-handlers-button" contentType="handlers" + i18n-content="manage_handlers"></button> + </div> + </section> + </if> + <!-- Plug-ins filter --> + <section> + <h3 i18n-content="plugins_tab_label"></h3> + <div> + <div class="radio"> + <label> + <input type="radio" name="plugins" value="allow"> + <span i18n-content="plugins_allow"></span> + </label> + </div> + <div id="click_to_play" class="radio"> + <label> + <input type="radio" name="plugins" value="ask"> + <span i18n-content="plugins_ask"></span> + </label> + </div> + <div class="radio"> + <label> + <input type="radio" name="plugins" value="block"> + <span i18n-content="plugins_block"></span> + </label> + </div> + <button class="exceptions-list-button" contentType="plugins" + i18n-content="manage_exceptions"></button> + <div id="disable-plugins-container"> + <a href="about:plugins" i18n-content="disableIndividualPlugins" + target="_blank"></a> + </div> + </div> + </section> + <!-- Pop-ups filter --> + <section> + <h3 i18n-content="popups_tab_label" class="content-settings-header"></h3> + <div> + <div class="radio"> + <label> + <input type="radio" name="popups" value="allow"> + <span i18n-content="popups_allow"></span> + </label> + </div> + <div class="radio"> + <label> + <input type="radio" name="popups" value="block"> + <span i18n-content="popups_block"></span> + </label> + </div> + <button class="exceptions-list-button" contentType="popups" + i18n-content="manage_exceptions"></button> + </div> + </section> + <!-- Location filter --> + <section> + <h3 i18n-content="location_tab_label"></h3> + <div> + <div class="radio"> + <label> + <input type="radio" name="location" value="allow"> + <span i18n-content="location_allow"></span> + </label> + </div> + <div class="radio"> + <label> + <input type="radio" name="location" value="ask"> + <span i18n-content="location_ask"></span> + </label> + </div> + <div class="radio"> + <label> + <input type="radio" name="location" value="block"> + <span i18n-content="location_block"></span> + </label> + </div> + <button class="exceptions-list-button" contentType="location" + i18n-content="manage_exceptions"></button> + </div> + </section> + <!-- Notifications filter tab contents --> + <section> + <h3 i18n-content="notifications_tab_label"></h3> + <div> + <div class="radio"> + <label> + <input type="radio" name="notifications" value="allow"> + <span i18n-content="notifications_allow"></span> + </label> + </div> + <div class="radio"> + <label> + <input type="radio" name="notifications" value="ask"> + <span i18n-content="notifications_ask"></span> + </label> + </div> + <div class="radio"> + <label> + <input type="radio" name="notifications" value="block"> + <span i18n-content="notifications_block"></span> + </label> + </div> + <button class="exceptions-list-button" contentType="notifications" + i18n-content="manage_exceptions"></button> + </div> + </section> + <!-- Fullscreen filter --> + <section> + <h3 i18n-content="fullscreen_tab_label"></h3> + <div> + <button class="exceptions-list-button" contentType="fullscreen" + i18n-content="manage_exceptions"></button> + </div> + </section> + <!-- Mouse Lock filter --> + <section> + <h3 i18n-content="mouselock_tab_label"></h3> + <div> + <div class="radio"> + <label> + <input type="radio" name="mouselock" value="allow"> + <span i18n-content="mouselock_allow"></span> + </label> + </div> + <div class="radio"> + <label> + <input type="radio" name="mouselock" value="ask"> + <span i18n-content="mouselock_ask"></span> + </label> + </div> + <div class="radio"> + <label> + <input type="radio" name="mouselock" value="block"> + <span i18n-content="mouselock_block"></span> + </label> + </div> + <button class="exceptions-list-button" contentType="mouselock" + i18n-content="manage_exceptions"></button> + </div> + </section> + <!-- Intent registration filter tab contents --> + <if expr="pp_ifdef('enable_web_intents')"> + <section id="intents-section"> + <h3 i18n-content="intentsTabLabel" class="content-settings-header"></h3> + <div> + <div class="radio"> + <label> + <input type="radio" name="intents" value="allow"> + <span i18n-content="intentsAllow"></span> + </label> + </div> + <div class="radio"> + <label> + <input type="radio" name="intents" value="ask"> + <span i18n-content="intentsAsk"></span> + </label> + </div> + <div class="radio"> + <label> + <input type="radio" name="intents" value="block"> + <span i18n-content="intentsBlock"></span> + </label> + </div> + <button class="exceptions-list-button" contentType="intents" + i18n-content="manage_exceptions"></button> + <button id="manage-intents-button" contentType="intents" + i18n-content="manageIntents"></button> + </div> + </section> + </if> + </div> +</div> diff --git a/chrome/browser/resources/options2/content_settings.js b/chrome/browser/resources/options2/content_settings.js new file mode 100644 index 0000000..94faa90 --- /dev/null +++ b/chrome/browser/resources/options2/content_settings.js @@ -0,0 +1,154 @@ +// Copyright (c) 2011 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; + + ////////////////////////////////////////////////////////////////////////////// + // ContentSettings class: + + /** + * Encapsulated handling of content settings page. + * @constructor + */ + function ContentSettings() { + this.activeNavTab = null; + OptionsPage.call(this, 'content', templateData.contentSettingsPageTabTitle, + 'content-settings-page'); + } + + cr.addSingletonGetter(ContentSettings); + + ContentSettings.prototype = { + __proto__: OptionsPage.prototype, + + initializePage: function() { + OptionsPage.prototype.initializePage.call(this); + + chrome.send('getContentFilterSettings'); + + var exceptionsButtons = + this.pageDiv.querySelectorAll('.exceptions-list-button'); + for (var i = 0; i < exceptionsButtons.length; i++) { + exceptionsButtons[i].onclick = function(event) { + var page = ContentSettingsExceptionsArea.getInstance(); + page.showList( + event.target.getAttribute('contentType')); + OptionsPage.navigateToPage('contentExceptions'); + // Add on the proper hash for the content type, and store that in the + // history so back/forward and tab restore works. + var hash = event.target.getAttribute('contentType'); + window.history.replaceState({pageName: page.name}, page.title, + '/' + page.name + "#" + hash); + }; + } + + var manageHandlersButton = $('manage-handlers-button'); + if (manageHandlersButton) { + manageHandlersButton.onclick = function(event) { + OptionsPage.navigateToPage('handlers'); + }; + } + + var manageIntentsButton = $('manage-intents-button'); + if (manageIntentsButton) { + manageIntentsButton.onclick = function(event) { + OptionsPage.navigateToPage('intents'); + }; + } + + // Cookies filter page --------------------------------------------------- + $('show-cookies-button').onclick = function(event) { + chrome.send('coreOptionsUserMetricsAction', ['Options_ShowCookies']); + OptionsPage.navigateToPage('cookies'); + }; + + if (!templateData.enable_click_to_play) + $('click_to_play').hidden = true; + + if (!templateData.enable_web_intents && $('intent-section')) + $('intent-section').hidden = true; + }, + }; + + ContentSettings.updateHandlersEnabledRadios = function(enabled) { + var selector = '#content-settings-page input[type=radio][value=' + + (enabled ? 'allow' : 'block') + '].handler-radio'; + document.querySelector(selector).checked = true; + }; + + /** + * Sets the values for all the content settings radios. + * @param {Object} dict A mapping from radio groups to the checked value for + * that group. + */ + ContentSettings.setContentFilterSettingsValue = function(dict) { + for (var group in dict) { + document.querySelector('input[type=radio][name=' + group + '][value=' + + dict[group]['value'] + ']').checked = true; + var radios = document.querySelectorAll('input[type=radio][name=' + + group + ']'); + var managedBy = dict[group]['managedBy']; + for (var i = 0, len = radios.length; i < len; i++) { + radios[i].disabled = (managedBy != 'default'); + radios[i].controlledBy = managedBy; + } + } + OptionsPage.updateManagedBannerVisibility(); + }; + + /** + * Initializes an exceptions list. + * @param {string} type The content type that we are setting exceptions for. + * @param {Array} list An array of pairs, where the first element of each pair + * is the filter string, and the second is the setting (allow/block). + */ + ContentSettings.setExceptions = function(type, list) { + var exceptionsList = + document.querySelector('div[contentType=' + type + ']' + + ' list[mode=normal]'); + exceptionsList.setExceptions(list); + }; + + ContentSettings.setHandlers = function(list) { + $('handlers-list').setHandlers(list); + }; + + ContentSettings.setIgnoredHandlers = function(list) { + $('ignored-handlers-list').setHandlers(list); + }; + + ContentSettings.setOTRExceptions = function(type, list) { + var exceptionsList = + document.querySelector('div[contentType=' + type + ']' + + ' list[mode=otr]'); + + exceptionsList.parentNode.hidden = false; + exceptionsList.setExceptions(list); + }; + + /** + * The browser's response to a request to check the validity of a given URL + * pattern. + * @param {string} type The content type. + * @param {string} mode The browser mode. + * @param {string} pattern The pattern. + * @param {bool} valid Whether said pattern is valid in the context of + * a content exception setting. + */ + ContentSettings.patternValidityCheckComplete = + function(type, mode, pattern, valid) { + var exceptionsList = + document.querySelector('div[contentType=' + type + '] ' + + 'list[mode=' + mode + ']'); + exceptionsList.patternValidityCheckComplete(pattern, valid); + }; + + // Export + return { + ContentSettings: ContentSettings + }; + +}); diff --git a/chrome/browser/resources/options2/content_settings_exceptions_area.html b/chrome/browser/resources/options2/content_settings_exceptions_area.html new file mode 100644 index 0000000..4e95c04 --- /dev/null +++ b/chrome/browser/resources/options2/content_settings_exceptions_area.html @@ -0,0 +1,73 @@ +<div id="content-settings-exceptions-area" class="page" hidden> + <h1></h1> + <div id="exception-column-headers"> + <div id="exception-pattern-column" i18n-content="exceptionPatternHeader"> + </div> + <div id="exception-behavior-column" i18n-content="exceptionBehaviorHeader"> + </div> + </div> + <div contentType="cookies"> + <list mode="normal"></list> + <div> + <span class="otr-explanation" + i18n-content="otr_exceptions_explanation"></span> + <list mode="otr"></list> + </div> + <div class="flash-plugin-area"> + <a i18n-values="href:flash_storage_url" + i18n-content="flash_storage_settings" target="_blank"></a> + </div> + </div> + <div contentType="images"> + <list mode="normal"></list> + <div> + <span class="otr-explanation" + i18n-content="otr_exceptions_explanation"></span> + <list mode="otr"></list> + </div> + </div> + <div contentType="javascript"> + <list mode="normal"></list> + <div> + <span class="otr-explanation" + i18n-content="otr_exceptions_explanation"></span> + <list mode="otr"></list> + </div> + </div> + <div contentType="plugins"> + <list mode="normal"></list> + <div> + <span class="otr-explanation" + i18n-content="otr_exceptions_explanation"></span> + <list mode="otr"></list> + </div> + </div> + <div contentType="popups"> + <list mode="normal"></list> + <div> + <span class="otr-explanation" + i18n-content="otr_exceptions_explanation"></span> + <list mode="otr"></list> + </div> + </div> + <div contentType="location"> + <list mode="normal"></list> + </div> + <div contentType="notifications"> + <list mode="normal"></list> + </div> + <div contentType="intents"> + <list mode="normal"></list> + </div> + <div contentType="fullscreen"> + <list mode="normal"></list> + <div> + <span class="otr-explanation" + i18n-content="otr_exceptions_explanation"></span> + <list mode="otr"></list> + </div> + </div> + <div contentType="mouselock"> + <list mode="normal"></list> + </div> +</div> diff --git a/chrome/browser/resources/options2/content_settings_exceptions_area.js b/chrome/browser/resources/options2/content_settings_exceptions_area.js new file mode 100644 index 0000000..7adde68 --- /dev/null +++ b/chrome/browser/resources/options2/content_settings_exceptions_area.js @@ -0,0 +1,552 @@ +// Copyright (c) 2011 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.contentSettings', function() { + const InlineEditableItemList = options.InlineEditableItemList; + const InlineEditableItem = options.InlineEditableItem; + const ArrayDataModel = cr.ui.ArrayDataModel; + + /** + * Creates a new exceptions list item. + * @param {string} contentType The type of the list. + * @param {string} mode The browser mode, 'otr' or 'normal'. + * @param {boolean} enableAskOption Whether to show an 'ask every time' + * option in the select. + * @param {Object} exception A dictionary that contains the data of the + * exception. + * @constructor + * @extends {options.InlineEditableItem} + */ + function ExceptionsListItem(contentType, mode, enableAskOption, exception) { + var el = cr.doc.createElement('div'); + el.mode = mode; + el.contentType = contentType; + el.enableAskOption = enableAskOption; + el.dataItem = exception; + el.__proto__ = ExceptionsListItem.prototype; + el.decorate(); + + return el; + } + + ExceptionsListItem.prototype = { + __proto__: InlineEditableItem.prototype, + + /** + * Called when an element is decorated as a list item. + */ + decorate: function() { + InlineEditableItem.prototype.decorate.call(this); + + this.isPlaceholder = !this.pattern; + var patternCell = this.createEditableTextCell(this.pattern); + patternCell.className = 'exception-pattern'; + patternCell.classList.add('weakrtl'); + this.contentElement.appendChild(patternCell); + if (this.pattern) + this.patternLabel = patternCell.querySelector('.static-text'); + var input = patternCell.querySelector('input'); + + // TODO(stuartmorgan): Create an createEditableSelectCell abstracting + // this code. + // Setting label for display mode. |pattern| will be null for the 'add new + // exception' row. + if (this.pattern) { + var settingLabel = cr.doc.createElement('span'); + settingLabel.textContent = this.settingForDisplay(); + settingLabel.className = 'exception-setting'; + settingLabel.setAttribute('displaymode', 'static'); + this.contentElement.appendChild(settingLabel); + this.settingLabel = settingLabel; + } + + // Setting select element for edit mode. + var select = cr.doc.createElement('select'); + var optionAllow = cr.doc.createElement('option'); + optionAllow.textContent = templateData.allowException; + optionAllow.value = 'allow'; + select.appendChild(optionAllow); + + if (this.enableAskOption) { + var optionAsk = cr.doc.createElement('option'); + optionAsk.textContent = templateData.askException; + optionAsk.value = 'ask'; + select.appendChild(optionAsk); + } + + if (this.contentType == 'cookies') { + var optionSession = cr.doc.createElement('option'); + optionSession.textContent = templateData.sessionException; + optionSession.value = 'session'; + select.appendChild(optionSession); + } + + if (this.contentType != 'fullscreen') { + var optionBlock = cr.doc.createElement('option'); + optionBlock.textContent = templateData.blockException; + optionBlock.value = 'block'; + select.appendChild(optionBlock); + } + + this.contentElement.appendChild(select); + select.className = 'exception-setting'; + if (this.pattern) + select.setAttribute('displaymode', 'edit'); + + // Used to track whether the URL pattern in the input is valid. + // This will be true if the browser process has informed us that the + // current text in the input is valid. Changing the text resets this to + // false, and getting a response from the browser sets it back to true. + // It starts off as false for empty string (new exceptions) or true for + // already-existing exceptions (which we assume are valid). + this.inputValidityKnown = this.pattern; + // This one tracks the actual validity of the pattern in the input. This + // starts off as true so as not to annoy the user when he adds a new and + // empty input. + this.inputIsValid = true; + + this.input = input; + this.select = select; + + this.updateEditables(); + + // Editing notifications and geolocation is disabled for now. + if (this.contentType == 'notifications' || + this.contentType == 'location') { + this.editable = false; + } + + // If the source of the content setting exception is not the user + // preference, then the content settings exception is managed and the user + // can't edit it. + if (this.dataItem.source && + this.dataItem.source != 'preference') { + this.setAttribute('managedby', this.dataItem.source); + this.deletable = false; + this.editable = false; + } + + var listItem = this; + // Handle events on the editable nodes. + input.oninput = function(event) { + listItem.inputValidityKnown = false; + chrome.send('checkExceptionPatternValidity', + [listItem.contentType, listItem.mode, input.value]); + }; + + // Listen for edit events. + this.addEventListener('canceledit', this.onEditCancelled_); + this.addEventListener('commitedit', this.onEditCommitted_); + }, + + /** + * The pattern (e.g., a URL) for the exception. + * @type {string} + */ + get pattern() { + return this.dataItem['displayPattern']; + }, + set pattern(pattern) { + this.dataItem['displayPattern'] = pattern; + }, + + /** + * The setting (allow/block) for the exception. + * @type {string} + */ + get setting() { + return this.dataItem['setting']; + }, + set setting(setting) { + this.dataItem['setting'] = setting; + }, + + /** + * Gets a human-readable setting string. + * @type {string} + */ + settingForDisplay: function() { + var setting = this.setting; + if (setting == 'allow') + return templateData.allowException; + else if (setting == 'block') + return templateData.blockException; + else if (setting == 'ask') + return templateData.askException; + else if (setting == 'session') + return templateData.sessionException; + }, + + /** + * Update this list item to reflect whether the input is a valid pattern. + * @param {boolean} valid Whether said pattern is valid in the context of + * a content exception setting. + */ + setPatternValid: function(valid) { + if (valid || !this.input.value) + this.input.setCustomValidity(''); + else + this.input.setCustomValidity(' '); + this.inputIsValid = valid; + this.inputValidityKnown = true; + }, + + /** + * Set the <input> to its original contents. Used when the user quits + * editing. + */ + resetInput: function() { + this.input.value = this.pattern; + }, + + /** + * Copy the data model values to the editable nodes. + */ + updateEditables: function() { + this.resetInput(); + + var settingOption = + this.select.querySelector('[value=\'' + this.setting + '\']'); + if (settingOption) + settingOption.selected = true; + }, + + /** @inheritDoc */ + get currentInputIsValid() { + return this.inputValidityKnown && this.inputIsValid; + }, + + /** @inheritDoc */ + get hasBeenEdited() { + var livePattern = this.input.value; + var liveSetting = this.select.value; + return livePattern != this.pattern || liveSetting != this.setting; + }, + + /** + * Called when committing an edit. + * @param {Event} e The end event. + * @private + */ + onEditCommitted_: function(e) { + var newPattern = this.input.value; + var newSetting = this.select.value; + + this.finishEdit(newPattern, newSetting); + }, + + /** + * Called when cancelling an edit; resets the control states. + * @param {Event} e The cancel event. + * @private + */ + onEditCancelled_: function() { + this.updateEditables(); + this.setPatternValid(true); + }, + + /** + * Editing is complete; update the model. + * @param {string} newPattern The pattern that the user entered. + * @param {string} newSetting The setting the user chose. + */ + finishEdit: function(newPattern, newSetting) { + this.patternLabel.textContent = newPattern; + this.settingLabel.textContent = this.settingForDisplay(); + var oldPattern = this.pattern; + this.pattern = newPattern; + this.setting = newSetting; + + // TODO(estade): this will need to be updated if geolocation/notifications + // become editable. + if (oldPattern != newPattern) { + chrome.send('removeException', + [this.contentType, this.mode, oldPattern]); + } + + chrome.send('setException', + [this.contentType, this.mode, newPattern, newSetting]); + } + }; + + /** + * Creates a new list item for the Add New Item row, which doesn't represent + * an actual entry in the exceptions list but allows the user to add new + * exceptions. + * @param {string} contentType The type of the list. + * @param {string} mode The browser mode, 'otr' or 'normal'. + * @param {boolean} enableAskOption Whether to show an 'ask every time' + * option in the select. + * @constructor + * @extends {cr.ui.ExceptionsListItem} + */ + function ExceptionsAddRowListItem(contentType, mode, enableAskOption) { + var el = cr.doc.createElement('div'); + el.mode = mode; + el.contentType = contentType; + el.enableAskOption = enableAskOption; + el.dataItem = []; + el.__proto__ = ExceptionsAddRowListItem.prototype; + el.decorate(); + + return el; + } + + ExceptionsAddRowListItem.prototype = { + __proto__: ExceptionsListItem.prototype, + + decorate: function() { + ExceptionsListItem.prototype.decorate.call(this); + + this.input.placeholder = templateData.addNewExceptionInstructions; + + // Do we always want a default of allow? + this.setting = 'allow'; + }, + + /** + * Clear the <input> and let the placeholder text show again. + */ + resetInput: function() { + this.input.value = ''; + }, + + /** @inheritDoc */ + get hasBeenEdited() { + return this.input.value != ''; + }, + + /** + * Editing is complete; update the model. As long as the pattern isn't + * empty, we'll just add it. + * @param {string} newPattern The pattern that the user entered. + * @param {string} newSetting The setting the user chose. + */ + finishEdit: function(newPattern, newSetting) { + this.resetInput(); + chrome.send('setException', + [this.contentType, this.mode, newPattern, newSetting]); + }, + }; + + /** + * Creates a new exceptions list. + * @constructor + * @extends {cr.ui.List} + */ + var ExceptionsList = cr.ui.define('list'); + + ExceptionsList.prototype = { + __proto__: InlineEditableItemList.prototype, + + /** + * Called when an element is decorated as a list. + */ + decorate: function() { + InlineEditableItemList.prototype.decorate.call(this); + + this.classList.add('settings-list'); + + for (var parentNode = this.parentNode; parentNode; + parentNode = parentNode.parentNode) { + if (parentNode.hasAttribute('contentType')) { + this.contentType = parentNode.getAttribute('contentType'); + break; + } + } + + this.mode = this.getAttribute('mode'); + + var exceptionList = this; + + // Whether the exceptions in this list allow an 'Ask every time' option. + this.enableAskOption = (this.contentType == 'plugins' && + templateData.enable_click_to_play); + + this.autoExpands = true; + this.reset(); + }, + + /** + * Creates an item to go in the list. + * @param {Object} entry The element from the data model for this row. + */ + createItem: function(entry) { + if (entry) { + return new ExceptionsListItem(this.contentType, + this.mode, + this.enableAskOption, + entry); + } else { + var addRowItem = new ExceptionsAddRowListItem(this.contentType, + this.mode, + this.enableAskOption); + addRowItem.deletable = false; + return addRowItem; + } + }, + + /** + * Sets the exceptions in the js model. + * @param {Object} entries A list of dictionaries of values, each dictionary + * represents an exception. + */ + setExceptions: function(entries) { + var deleteCount = this.dataModel.length; + + if (this.isEditable()) { + // We don't want to remove the Add New Exception row. + deleteCount = deleteCount - 1; + } + + var args = [0, deleteCount]; + args.push.apply(args, entries); + this.dataModel.splice.apply(this.dataModel, args); + }, + + /** + * The browser has finished checking a pattern for validity. Update the + * list item to reflect this. + * @param {string} pattern The pattern. + * @param {bool} valid Whether said pattern is valid in the context of + * a content exception setting. + */ + patternValidityCheckComplete: function(pattern, valid) { + var listItems = this.items; + for (var i = 0; i < listItems.length; i++) { + var listItem = listItems[i]; + // Don't do anything for messages for the item if it is not the intended + // recipient, or if the response is stale (i.e. the input value has + // changed since we sent the request to analyze it). + if (pattern == listItem.input.value) + listItem.setPatternValid(valid); + } + }, + + /** + * Returns whether the rows are editable in this list. + */ + isEditable: function() { + // Editing notifications and geolocation is disabled for now. + return !(this.contentType == 'notifications' || + this.contentType == 'location' || + this.contentType == 'fullscreen'); + }, + + /** + * Removes all exceptions from the js model. + */ + reset: function() { + if (this.isEditable()) { + // The null creates the Add New Exception row. + this.dataModel = new ArrayDataModel([null]); + } else { + this.dataModel = new ArrayDataModel([]); + } + }, + + /** @inheritDoc */ + deleteItemAtIndex: function(index) { + var listItem = this.getListItemByIndex(index); + if (listItem.undeletable) + return; + + var dataItem = listItem.dataItem; + var args = [listItem.contentType]; + if (listItem.contentType == 'location') + args.push(dataItem['origin'], dataItem['embeddingOrigin']); + else if (listItem.contentType == 'notifications') + args.push(dataItem['origin'], dataItem['setting']); + else + args.push(listItem.mode, listItem.pattern); + + chrome.send('removeException', args); + }, + }; + + var OptionsPage = options.OptionsPage; + + /** + * Encapsulated handling of content settings list subpage. + * @constructor + */ + function ContentSettingsExceptionsArea() { + OptionsPage.call(this, 'contentExceptions', + templateData.contentSettingsPageTabTitle, + 'content-settings-exceptions-area'); + } + + cr.addSingletonGetter(ContentSettingsExceptionsArea); + + ContentSettingsExceptionsArea.prototype = { + __proto__: OptionsPage.prototype, + + initializePage: function() { + OptionsPage.prototype.initializePage.call(this); + + var exceptionsLists = this.pageDiv.querySelectorAll('list'); + for (var i = 0; i < exceptionsLists.length; i++) { + options.contentSettings.ExceptionsList.decorate(exceptionsLists[i]); + } + + ContentSettingsExceptionsArea.hideOTRLists(); + + // If the user types in the URL without a hash, show just cookies. + this.showList('cookies'); + }, + + /** + * Shows one list and hides all others. + * @param {string} type The content type. + */ + showList: function(type) { + var header = this.pageDiv.querySelector('h1'); + header.textContent = templateData[type + '_header']; + + var divs = this.pageDiv.querySelectorAll('div[contentType]'); + for (var i = 0; i < divs.length; i++) { + if (divs[i].getAttribute('contentType') == type) + divs[i].hidden = false; + else + divs[i].hidden = true; + } + }, + + /** + * Called after the page has been shown. Show the content type for the + * location's hash. + */ + didShowPage: function() { + var hash = location.hash; + if (hash) + this.showList(hash.slice(1)); + }, + }; + + /** + * Called when the last incognito window is closed. + */ + ContentSettingsExceptionsArea.OTRProfileDestroyed = function() { + this.hideOTRLists(); + }; + + /** + * Clears and hides the incognito exceptions lists. + */ + ContentSettingsExceptionsArea.hideOTRLists = function() { + var otrLists = document.querySelectorAll('list[mode=otr]'); + + for (var i = 0; i < otrLists.length; i++) { + otrLists[i].reset(); + otrLists[i].parentNode.hidden = true; + } + }; + + return { + ExceptionsListItem: ExceptionsListItem, + ExceptionsAddRowListItem: ExceptionsAddRowListItem, + ExceptionsList: ExceptionsList, + ContentSettingsExceptionsArea: ContentSettingsExceptionsArea, + }; +}); diff --git a/chrome/browser/resources/options2/content_settings_ui.js b/chrome/browser/resources/options2/content_settings_ui.js new file mode 100644 index 0000000..dc7b0c4 --- /dev/null +++ b/chrome/browser/resources/options2/content_settings_ui.js @@ -0,0 +1,67 @@ +// Copyright (c) 2011 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() { + + ////////////////////////////////////////////////////////////////////////////// + // ContentSettingsRadio class: + + // Define a constructor that uses an input element as its underlying element. + var ContentSettingsRadio = cr.ui.define('input'); + + ContentSettingsRadio.prototype = { + __proto__: HTMLInputElement.prototype, + + /** + * Initialization function for the cr.ui framework. + */ + decorate: function() { + this.type = 'radio'; + var self = this; + + this.addEventListener('change', + function(e) { + chrome.send('setContentFilter', [this.name, this.value]); + }); + }, + }; + + /** + * Whether the content setting is controlled by something else than the user's + * settings (either 'policy' or 'extension'). + * @type {string} + */ + cr.defineProperty(ContentSettingsRadio, 'controlledBy', cr.PropertyKind.ATTR); + + ////////////////////////////////////////////////////////////////////////////// + // HandlersEnabledRadio class: + + // Define a constructor that uses an input element as its underlying element. + var HandlersEnabledRadio = cr.ui.define('input'); + + HandlersEnabledRadio.prototype = { + __proto__: HTMLInputElement.prototype, + + /** + * Initialization function for the cr.ui framework. + */ + decorate: function() { + this.type = 'radio'; + var self = this; + + this.addEventListener('change', + function(e) { + chrome.send('setHandlersEnabled', [this.value == 'allow']); + }); + }, + }; + + // Export + return { + ContentSettingsRadio: ContentSettingsRadio, + HandlersEnabledRadio: HandlersEnabledRadio + }; + +}); + diff --git a/chrome/browser/resources/options2/controlled_setting.js b/chrome/browser/resources/options2/controlled_setting.js new file mode 100644 index 0000000..0ca4e31 --- /dev/null +++ b/chrome/browser/resources/options2/controlled_setting.js @@ -0,0 +1,138 @@ +// Copyright (c) 2011 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 Preferences = options.Preferences; + + /** + * A controlled setting indicator that can be placed on a setting as an + * indicator that the value is controlled by some external entity such as + * policy or an extension. + * @constructor + * @extends {HTMLSpanElement} + */ + var ControlledSettingIndicator = cr.ui.define('span'); + + ControlledSettingIndicator.prototype = { + __proto__: HTMLSpanElement.prototype, + + /** + * Decorates the base element to show the proper icon. + */ + decorate: function() { + var self = this; + var doc = self.ownerDocument; + + // Create the details and summary elements. + var detailsContainer = doc.createElement('details'); + detailsContainer.appendChild(doc.createElement('summary')); + + // This should really create a div element, but that breaks :hover. See + // https://bugs.webkit.org/show_bug.cgi?id=72957 + var bubbleContainer = doc.createElement('p'); + bubbleContainer.className = 'controlled-setting-bubble'; + detailsContainer.appendChild(bubbleContainer); + + self.appendChild(detailsContainer); + + // If there is a pref, track its controlledBy property in order to be able + // to bring up the correct bubble. + if (this.hasAttribute('pref')) { + Preferences.getInstance().addEventListener( + this.getAttribute('pref'), + function(event) { + if (event.value) { + var controlledBy = event.value['controlledBy']; + self.controlledBy = controlledBy ? controlledBy : null; + } + }); + } + + self.addEventListener('click', self.show_); + }, + + + /** + * Closes the bubble. + */ + close: function() { + this.querySelector('details').removeAttribute('open'); + this.ownerDocument.removeEventListener('click', this.closeHandler_, true); + }, + + /** + * Constructs the bubble DOM tree and shows it. + * @private + */ + show_: function() { + var self = this; + var doc = self.ownerDocument; + + // Clear out the old bubble contents. + var bubbleContainer = this.querySelector('.controlled-setting-bubble'); + if (bubbleContainer) { + while (bubbleContainer.hasChildNodes()) + bubbleContainer.removeChild(bubbleContainer.lastChild); + } + + // Work out the bubble text. + defaultStrings = { + 'policy' : localStrings.getString('controlledSettingPolicy'), + 'extension' : localStrings.getString('controlledSettingExtension'), + 'recommended' : localStrings.getString('controlledSettingRecommended'), + }; + + // No controller, no bubble. + if (!self.controlledBy || !self.controlledBy in defaultStrings) + return; + + var text = defaultStrings[self.controlledBy]; + + // Apply text overrides. + if (self.hasAttribute('text' + self.controlledBy)) + text = self.getAttribute('text' + self.controlledBy); + + // Create the DOM tree. + var bubbleText = doc.createElement('p'); + bubbleText.className = 'controlled-setting-bubble-text'; + bubbleText.textContent = text; + + var pref = self.getAttribute('pref'); + if (self.controlledBy == 'recommended' && pref) { + var container = doc.createElement('div'); + var action = doc.createElement('button'); + action.classList.add('link-button'); + action.classList.add('controlled-setting-bubble-action'); + action.textContent = + localStrings.getString('controlledSettingApplyRecommendation'); + action.addEventListener( + 'click', + function(e) { + Preferences.clearPref(pref); + }); + container.appendChild(action); + bubbleText.appendChild(container); + } + + bubbleContainer.appendChild(bubbleText); + + // One-time bubble-closing event handler. + self.closeHandler_ = this.close.bind(this); + doc.addEventListener('click', self.closeHandler_, true); + } + }; + + /** + * The controlling entity of the setting. Can take the values "policy", + * "extension", "recommended" or be unset. + */ + cr.defineProperty(ControlledSettingIndicator, 'controlledBy', + cr.PropertyKind.ATTR, + ControlledSettingIndicator.prototype.close); + + // Export. + return { + ControlledSettingIndicator : ControlledSettingIndicator + }; +}); diff --git a/chrome/browser/resources/options2/cookies_list.js b/chrome/browser/resources/options2/cookies_list.js new file mode 100644 index 0000000..4e70d1d --- /dev/null +++ b/chrome/browser/resources/options2/cookies_list.js @@ -0,0 +1,853 @@ +// Copyright (c) 2011 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() { + const DeletableItemList = options.DeletableItemList; + const DeletableItem = options.DeletableItem; + const ArrayDataModel = cr.ui.ArrayDataModel; + const ListSingleSelectionModel = cr.ui.ListSingleSelectionModel; + + // This structure maps the various cookie type names from C++ (hence the + // underscores) to arrays of the different types of data each has, along with + // the i18n name for the description of that data type. + const cookieInfo = { + 'cookie': [ ['name', 'label_cookie_name'], + ['content', 'label_cookie_content'], + ['domain', 'label_cookie_domain'], + ['path', 'label_cookie_path'], + ['sendfor', 'label_cookie_send_for'], + ['accessibleToScript', 'label_cookie_accessible_to_script'], + ['created', 'label_cookie_created'], + ['expires', 'label_cookie_expires'] ], + 'app_cache': [ ['manifest', 'label_app_cache_manifest'], + ['size', 'label_local_storage_size'], + ['created', 'label_cookie_created'], + ['accessed', 'label_cookie_last_accessed'] ], + 'database': [ ['name', 'label_cookie_name'], + ['desc', 'label_webdb_desc'], + ['size', 'label_local_storage_size'], + ['modified', 'label_local_storage_last_modified'] ], + 'local_storage': [ ['origin', 'label_local_storage_origin'], + ['size', 'label_local_storage_size'], + ['modified', 'label_local_storage_last_modified'] ], + 'indexed_db': [ ['origin', 'label_indexed_db_origin'], + ['size', 'label_indexed_db_size'], + ['modified', 'label_indexed_db_last_modified'] ], + 'file_system': [ ['origin', 'label_file_system_origin'], + ['persistent', 'label_file_system_persistent_usage' ], + ['temporary', 'label_file_system_temporary_usage' ] ], + }; + + const localStrings = new LocalStrings(); + + /** + * Returns the item's height, like offsetHeight but such that it works better + * when the page is zoomed. See the similar calculation in @{code cr.ui.List}. + * This version also accounts for the animation done in this file. + * @param {Element} item The item to get the height of. + * @return {number} The height of the item, calculated with zooming in mind. + */ + function getItemHeight(item) { + var height = item.style.height; + // Use the fixed animation target height if set, in case the element is + // currently being animated and we'd get an intermediate height below. + if (height && height.substr(-2) == 'px') + return parseInt(height.substr(0, height.length - 2)); + return item.getBoundingClientRect().height; + } + + /** + * Create tree nodes for the objects in the data array, and insert them all + * into the given list using its @{code splice} method at the given index. + * @param {Array.<Object>} data The data objects for the nodes to add. + * @param {number} start The index at which to start inserting the nodes. + * @return {Array.<CookieTreeNode>} An array of CookieTreeNodes added. + */ + function spliceTreeNodes(data, start, list) { + var nodes = data.map(function(x) { return new CookieTreeNode(x); }); + // Insert [start, 0] at the beginning of the array of nodes, making it + // into the arguments we want to pass to @{code list.splice} below. + nodes.splice(0, 0, start, 0); + list.splice.apply(list, nodes); + // Remove the [start, 0] prefix and return the array of nodes. + nodes.splice(0, 2); + return nodes; + } + + var parentLookup = {}; + var lookupRequests = {}; + + /** + * Creates a new list item for sites data. Note that these are created and + * destroyed lazily as they scroll into and out of view, so they must be + * stateless. We cache the expanded item in @{code CookiesList} though, so it + * can keep state. (Mostly just which item is selected.) + * @param {Object} origin Data used to create a cookie list item. + * @param {CookiesList} list The list that will contain this item. + * @constructor + * @extends {DeletableItem} + */ + function CookieListItem(origin, list) { + var listItem = new DeletableItem(null); + listItem.__proto__ = CookieListItem.prototype; + + listItem.origin = origin; + listItem.list = list; + listItem.decorate(); + + // This hooks up updateOrigin() to the list item, makes the top-level + // tree nodes (i.e., origins) register their IDs in parentLookup, and + // causes them to request their children if they have none. Note that we + // have special logic in the setter for the parent property to make sure + // that we can still garbage collect list items when they scroll out of + // view, even though it appears that we keep a direct reference. + if (origin) { + origin.parent = listItem; + origin.updateOrigin(); + } + + return listItem; + } + + CookieListItem.prototype = { + __proto__: DeletableItem.prototype, + + /** @inheritDoc */ + decorate: function() { + this.siteChild = this.ownerDocument.createElement('div'); + this.siteChild.className = 'cookie-site'; + this.dataChild = this.ownerDocument.createElement('div'); + this.dataChild.className = 'cookie-data'; + this.sizeChild = this.ownerDocument.createElement('div'); + this.sizeChild.className = 'cookie-size'; + this.itemsChild = this.ownerDocument.createElement('div'); + this.itemsChild.className = 'cookie-items'; + this.infoChild = this.ownerDocument.createElement('div'); + this.infoChild.className = 'cookie-details'; + this.infoChild.hidden = true; + var remove = this.ownerDocument.createElement('button'); + remove.textContent = localStrings.getString('remove_cookie'); + remove.onclick = this.removeCookie_.bind(this); + this.infoChild.appendChild(remove); + var content = this.contentElement; + content.appendChild(this.siteChild); + content.appendChild(this.dataChild); + content.appendChild(this.sizeChild); + content.appendChild(this.itemsChild); + this.itemsChild.appendChild(this.infoChild); + if (this.origin && this.origin.data) { + this.siteChild.textContent = this.origin.data.title; + this.siteChild.setAttribute('title', this.origin.data.title); + } + this.itemList_ = []; + }, + + /** @type {boolean} */ + get expanded() { + return this.expanded_; + }, + set expanded(expanded) { + if (this.expanded_ == expanded) + return; + this.expanded_ = expanded; + if (expanded) { + var oldExpanded = this.list.expandedItem; + this.list.expandedItem = this; + this.updateItems_(); + if (oldExpanded) + oldExpanded.expanded = false; + this.classList.add('show-items'); + } else { + if (this.list.expandedItem == this) { + this.list.expandedItem = null; + } + this.style.height = ''; + this.itemsChild.style.height = ''; + this.classList.remove('show-items'); + } + }, + + /** + * The callback for the "remove" button shown when an item is selected. + * Requests that the currently selected cookie be removed. + * @private + */ + removeCookie_: function() { + if (this.selectedIndex_ >= 0) { + var item = this.itemList_[this.selectedIndex_]; + if (item && item.node) + chrome.send('removeCookie', [item.node.pathId]); + } + }, + + /** + * Disable animation within this cookie list item, in preparation for making + * changes that will need to be animated. Makes it possible to measure the + * contents without displaying them, to set animation targets. + * @private + */ + disableAnimation_: function() { + this.itemsHeight_ = getItemHeight(this.itemsChild); + this.classList.add('measure-items'); + }, + + /** + * Enable animation after changing the contents of this cookie list item. + * See @{code disableAnimation_}. + * @private + */ + enableAnimation_: function() { + if (!this.classList.contains('measure-items')) + this.disableAnimation_(); + this.itemsChild.style.height = ''; + // This will force relayout in order to calculate the new heights. + var itemsHeight = getItemHeight(this.itemsChild); + var fixedHeight = getItemHeight(this) + itemsHeight - this.itemsHeight_; + this.itemsChild.style.height = this.itemsHeight_ + 'px'; + // Force relayout before enabling animation, so that if we have + // changed things since the last layout, they will not be animated + // during subsequent layouts. + this.itemsChild.offsetHeight; + this.classList.remove('measure-items'); + this.itemsChild.style.height = itemsHeight + 'px'; + this.style.height = fixedHeight + 'px'; + }, + + /** + * Updates the origin summary to reflect changes in its items. + * Both CookieListItem and CookieTreeNode implement this API. + * This implementation scans the descendants to update the text. + */ + updateOrigin: function() { + var info = { + cookies: 0, + database: false, + localStorage: false, + appCache: false, + indexedDb: false, + fileSystem: false, + }; + if (this.origin) + this.origin.collectSummaryInfo(info); + var list = []; + if (info.cookies > 1) + list.push(localStrings.getStringF('cookie_plural', info.cookies)); + else if (info.cookies > 0) + list.push(localStrings.getString('cookie_singular')); + if (info.database || info.indexedDb) + list.push(localStrings.getString('cookie_database_storage')); + if (info.localStorage) + list.push(localStrings.getString('cookie_local_storage')); + if (info.appCache) + list.push(localStrings.getString('cookie_app_cache')); + if (info.fileSystem) + list.push(localStrings.getString('cookie_file_system')); + var text = ''; + for (var i = 0; i < list.length; ++i) + if (text.length > 0) + text += ', ' + list[i]; + else + text = list[i]; + this.dataChild.textContent = text; + if (info.quota && info.quota.totalUsage) { + this.sizeChild.textContent = info.quota.totalUsage; + } + + if (this.expanded) + this.updateItems_(); + }, + + /** + * Updates the items section to reflect changes, animating to the new state. + * Removes existing contents and calls @{code CookieTreeNode.createItems}. + * @private + */ + updateItems_: function() { + this.disableAnimation_(); + this.itemsChild.textContent = ''; + this.infoChild.hidden = true; + this.selectedIndex_ = -1; + this.itemList_ = []; + if (this.origin) + this.origin.createItems(this); + this.itemsChild.appendChild(this.infoChild); + this.enableAnimation_(); + }, + + /** + * Append a new cookie node "bubble" to this list item. + * @param {CookieTreeNode} node The cookie node to add a bubble for. + * @param {Element} div The DOM element for the bubble itself. + * @return {number} The index the bubble was added at. + */ + appendItem: function(node, div) { + this.itemList_.push({node: node, div: div}); + this.itemsChild.appendChild(div); + return this.itemList_.length - 1; + }, + + /** + * The currently selected cookie node ("cookie bubble") index. + * @type {number} + * @private + */ + selectedIndex_: -1, + + /** + * Get the currently selected cookie node ("cookie bubble") index. + * @type {number} + */ + get selectedIndex() { + return this.selectedIndex_; + }, + + /** + * Set the currently selected cookie node ("cookie bubble") index to + * @{code itemIndex}, unselecting any previously selected node first. + * @param {number} itemIndex The index to set as the selected index. + */ + set selectedIndex(itemIndex) { + // Get the list index up front before we change anything. + var index = this.list.getIndexOfListItem(this); + // Unselect any previously selected item. + if (this.selectedIndex_ >= 0) { + var item = this.itemList_[this.selectedIndex_]; + if (item && item.div) + item.div.removeAttribute('selected'); + } + // Special case: decrementing -1 wraps around to the end of the list. + if (itemIndex == -2) + itemIndex = this.itemList_.length - 1; + // Check if we're going out of bounds and hide the item details. + if (itemIndex < 0 || itemIndex >= this.itemList_.length) { + this.selectedIndex_ = -1; + this.disableAnimation_(); + this.infoChild.hidden = true; + this.enableAnimation_(); + return; + } + // Set the new selected item and show the item details for it. + this.selectedIndex_ = itemIndex; + this.itemList_[itemIndex].div.setAttribute('selected', ''); + this.disableAnimation_(); + this.itemList_[itemIndex].node.setDetailText(this.infoChild, + this.list.infoNodes); + this.infoChild.hidden = false; + this.enableAnimation_(); + // If we're near the bottom of the list this may cause the list item to go + // beyond the end of the visible area. Fix it after the animation is done. + var list = this.list; + window.setTimeout(function() { list.scrollIndexIntoView(index); }, 150); + }, + }; + + /** + * {@code CookieTreeNode}s mirror the structure of the cookie tree lazily, and + * contain all the actual data used to generate the {@code CookieListItem}s. + * @param {Object} data The data object for this node. + * @constructor + */ + function CookieTreeNode(data) { + this.data = data; + this.children = []; + } + + CookieTreeNode.prototype = { + /** + * Insert the given list of cookie tree nodes at the given index. + * Both CookiesList and CookieTreeNode implement this API. + * @param {Array.<Object>} data The data objects for the nodes to add. + * @param {number} start The index at which to start inserting the nodes. + */ + insertAt: function(data, start) { + var nodes = spliceTreeNodes(data, start, this.children); + for (var i = 0; i < nodes.length; i++) + nodes[i].parent = this; + this.updateOrigin(); + }, + + /** + * Remove a cookie tree node from the given index. + * Both CookiesList and CookieTreeNode implement this API. + * @param {number} index The index of the tree node to remove. + */ + remove: function(index) { + if (index < this.children.length) { + this.children.splice(index, 1); + this.updateOrigin(); + } + }, + + /** + * Clears all children. + * Both CookiesList and CookieTreeNode implement this API. + * It is used by CookiesList.loadChildren(). + */ + clear: function() { + // We might leave some garbage in parentLookup for removed children. + // But that should be OK because parentLookup is cleared when we + // reload the tree. + this.children = []; + this.updateOrigin(); + }, + + /** + * The counter used by startBatchUpdates() and endBatchUpdates(). + * @type {number} + */ + batchCount_: 0, + + /** + * See cr.ui.List.startBatchUpdates(). + * Both CookiesList (via List) and CookieTreeNode implement this API. + */ + startBatchUpdates: function() { + this.batchCount_++; + }, + + /** + * See cr.ui.List.endBatchUpdates(). + * Both CookiesList (via List) and CookieTreeNode implement this API. + */ + endBatchUpdates: function() { + if (!--this.batchCount_) + this.updateOrigin(); + }, + + /** + * Requests updating the origin summary to reflect changes in this item. + * Both CookieListItem and CookieTreeNode implement this API. + */ + updateOrigin: function() { + if (!this.batchCount_ && this.parent) + this.parent.updateOrigin(); + }, + + /** + * Summarize the information in this node and update @{code info}. + * This will recurse into child nodes to summarize all descendants. + * @param {Object} info The info object from @{code updateOrigin}. + */ + collectSummaryInfo: function(info) { + if (this.children.length > 0) { + for (var i = 0; i < this.children.length; ++i) + this.children[i].collectSummaryInfo(info); + } else if (this.data && !this.data.hasChildren) { + if (this.data.type == 'cookie') { + info.cookies++; + } else if (this.data.type == 'database') { + info.database = true; + } else if (this.data.type == 'local_storage') { + info.localStorage = true; + } else if (this.data.type == 'app_cache') { + info.appCache = true; + } else if (this.data.type == 'indexed_db') { + info.indexedDb = true; + } else if (this.data.type == 'file_system') { + info.fileSystem = true; + } else if (this.data.type == 'quota') { + info.quota = this.data; + } + } + }, + + /** + * Create the cookie "bubbles" for this node, recursing into children + * if there are any. Append the cookie bubbles to @{code item}. + * @param {CookieListItem} item The cookie list item to create items in. + */ + createItems: function(item) { + if (this.children.length > 0) { + for (var i = 0; i < this.children.length; ++i) + this.children[i].createItems(item); + } else if (this.data && !this.data.hasChildren) { + var text = ''; + switch (this.data.type) { + case 'cookie': + case 'database': + text = this.data.name; + break; + case 'local_storage': + text = localStrings.getString('cookie_local_storage'); + break; + case 'app_cache': + text = localStrings.getString('cookie_app_cache'); + break; + case 'indexed_db': + text = localStrings.getString('cookie_indexed_db'); + break; + case 'file_system': + text = localStrings.getString('cookie_file_system'); + break; + } + if (!text) + return; + var div = item.ownerDocument.createElement('div'); + div.className = 'cookie-item'; + // Help out screen readers and such: this is a clickable thing. + div.setAttribute('role', 'button'); + div.textContent = text; + var index = item.appendItem(this, div); + div.onclick = function() { + if (item.selectedIndex == index) + item.selectedIndex = -1; + else + item.selectedIndex = index; + }; + } + }, + + /** + * Set the detail text to be displayed to that of this cookie tree node. + * Uses preallocated DOM elements for each cookie node type from @{code + * infoNodes}, and inserts the appropriate elements to @{code element}. + * @param {Element} element The DOM element to insert elements to. + * @param {Object.<string, {table: Element, info: Object.<string, + * Element>}>} infoNodes The map from cookie node types to maps from + * cookie attribute names to DOM elements to display cookie attribute + * values, created by @{code CookiesList.decorate}. + */ + setDetailText: function(element, infoNodes) { + var table; + if (this.data && !this.data.hasChildren) { + if (cookieInfo[this.data.type]) { + var info = cookieInfo[this.data.type]; + var nodes = infoNodes[this.data.type].info; + for (var i = 0; i < info.length; ++i) { + var name = info[i][0]; + if (name != 'id' && this.data[name]) + nodes[name].textContent = this.data[name]; + } + table = infoNodes[this.data.type].table; + } + } + while (element.childNodes.length > 1) + element.removeChild(element.firstChild); + if (table) + element.insertBefore(table, element.firstChild); + }, + + /** + * The parent of this cookie tree node. + * @type {?CookieTreeNode|CookieListItem} + */ + get parent(parent) { + // See below for an explanation of this special case. + if (typeof this.parent_ == 'number') + return this.list_.getListItemByIndex(this.parent_); + return this.parent_; + }, + set parent(parent) { + if (parent == this.parent) + return; + if (parent instanceof CookieListItem) { + // If the parent is to be a CookieListItem, then we keep the reference + // to it by its containing list and list index, rather than directly. + // This allows the list items to be garbage collected when they scroll + // out of view (except the expanded item, which we cache). This is + // transparent except in the setter and getter, where we handle it. + this.parent_ = parent.listIndex; + this.list_ = parent.list; + parent.addEventListener('listIndexChange', + this.parentIndexChanged_.bind(this)); + } else { + this.parent_ = parent; + } + if (this.data && this.data.id) { + if (parent) + parentLookup[this.data.id] = this; + else + delete parentLookup[this.data.id]; + } + if (this.data && this.data.hasChildren && + !this.children.length && !lookupRequests[this.data.id]) { + lookupRequests[this.data.id] = true; + chrome.send('loadCookie', [this.pathId]); + } + }, + + /** + * Called when the parent is a CookieListItem whose index has changed. + * See the code above that avoids keeping a direct reference to + * CookieListItem parents, to allow them to be garbage collected. + * @private + */ + parentIndexChanged_: function(event) { + if (typeof this.parent_ == 'number') { + this.parent_ = event.newValue; + // We set a timeout to update the origin, rather than doing it right + // away, because this callback may occur while the list items are + // being repopulated following a scroll event. Calling updateOrigin() + // immediately could trigger relayout that would reset the scroll + // position within the list, among other things. + window.setTimeout(this.updateOrigin.bind(this), 0); + } + }, + + /** + * The cookie tree path id. + * @type {string} + */ + get pathId() { + var parent = this.parent; + if (parent && parent instanceof CookieTreeNode) + return parent.pathId + ',' + this.data.id; + return this.data.id; + }, + }; + + /** + * Creates a new cookies list. + * @param {Object=} opt_propertyBag Optional properties. + * @constructor + * @extends {DeletableItemList} + */ + var CookiesList = cr.ui.define('list'); + + CookiesList.prototype = { + __proto__: DeletableItemList.prototype, + + /** @inheritDoc */ + decorate: function() { + DeletableItemList.prototype.decorate.call(this); + this.classList.add('cookie-list'); + this.data_ = []; + this.dataModel = new ArrayDataModel(this.data_); + this.addEventListener('keydown', this.handleKeyLeftRight_.bind(this)); + var sm = new ListSingleSelectionModel(); + sm.addEventListener('change', this.cookieSelectionChange_.bind(this)); + sm.addEventListener('leadIndexChange', this.cookieLeadChange_.bind(this)); + this.selectionModel = sm; + this.infoNodes = {}; + this.fixedHeight = false; + var doc = this.ownerDocument; + // Create a table for each type of site data (e.g. cookies, databases, + // etc.) and save it so that we can reuse it for all origins. + for (var type in cookieInfo) { + var table = doc.createElement('table'); + table.className = 'cookie-details-table'; + var tbody = doc.createElement('tbody'); + table.appendChild(tbody); + var info = {}; + for (var i = 0; i < cookieInfo[type].length; i++) { + var tr = doc.createElement('tr'); + var name = doc.createElement('td'); + var data = doc.createElement('td'); + var pair = cookieInfo[type][i]; + name.className = 'cookie-details-label'; + name.textContent = localStrings.getString(pair[1]); + data.className = 'cookie-details-value'; + data.textContent = ''; + tr.appendChild(name); + tr.appendChild(data); + tbody.appendChild(tr); + info[pair[0]] = data; + } + this.infoNodes[type] = {table: table, info: info}; + } + }, + + /** + * Handles key down events and looks for left and right arrows, then + * dispatches to the currently expanded item, if any. + * @param {Event} e The keydown event. + * @private + */ + handleKeyLeftRight_: function(e) { + var id = e.keyIdentifier; + if ((id == 'Left' || id == 'Right') && this.expandedItem) { + var cs = this.ownerDocument.defaultView.getComputedStyle(this); + var rtl = cs.direction == 'rtl'; + if ((!rtl && id == 'Left') || (rtl && id == 'Right')) + this.expandedItem.selectedIndex--; + else + this.expandedItem.selectedIndex++; + this.scrollIndexIntoView(this.expandedItem.listIndex); + // Prevent the page itself from scrolling. + e.preventDefault(); + } + }, + + /** + * Called on selection model selection changes. + * @param {Event} ce The selection change event. + * @private + */ + cookieSelectionChange_: function(ce) { + ce.changes.forEach(function(change) { + var listItem = this.getListItemByIndex(change.index); + if (listItem) { + if (!change.selected) { + // We set a timeout here, rather than setting the item unexpanded + // immediately, so that if another item gets set expanded right + // away, it will be expanded before this item is unexpanded. It + // will notice that, and unexpand this item in sync with its own + // expansion. Later, this callback will end up having no effect. + window.setTimeout(function() { + if (!listItem.selected || !listItem.lead) + listItem.expanded = false; + }, 0); + } else if (listItem.lead) { + listItem.expanded = true; + } + } + }, this); + }, + + /** + * Called on selection model lead changes. + * @param {Event} pe The lead change event. + * @private + */ + cookieLeadChange_: function(pe) { + if (pe.oldValue != -1) { + var listItem = this.getListItemByIndex(pe.oldValue); + if (listItem) { + // See cookieSelectionChange_ above for why we use a timeout here. + window.setTimeout(function() { + if (!listItem.lead || !listItem.selected) + listItem.expanded = false; + }, 0); + } + } + if (pe.newValue != -1) { + var listItem = this.getListItemByIndex(pe.newValue); + if (listItem && listItem.selected) + listItem.expanded = true; + } + }, + + /** + * The currently expanded item. Used by CookieListItem above. + * @type {?CookieListItem} + */ + expandedItem: null, + + // from cr.ui.List + /** @inheritDoc */ + createItem: function(data) { + // We use the cached expanded item in order to allow it to maintain some + // state (like its fixed height, and which bubble is selected). + if (this.expandedItem && this.expandedItem.origin == data) + return this.expandedItem; + return new CookieListItem(data, this); + }, + + // from options.DeletableItemList + /** @inheritDoc */ + deleteItemAtIndex: function(index) { + var item = this.data_[index]; + if (item) { + var pathId = item.pathId; + if (pathId) + chrome.send('removeCookie', [pathId]); + } + }, + + /** + * Insert the given list of cookie tree nodes at the given index. + * Both CookiesList and CookieTreeNode implement this API. + * @param {Array.<Object>} data The data objects for the nodes to add. + * @param {number} start The index at which to start inserting the nodes. + */ + insertAt: function(data, start) { + spliceTreeNodes(data, start, this.dataModel); + }, + + /** + * Remove a cookie tree node from the given index. + * Both CookiesList and CookieTreeNode implement this API. + * @param {number} index The index of the tree node to remove. + */ + remove: function(index) { + if (index < this.data_.length) + this.dataModel.splice(index, 1); + }, + + /** + * Clears the list. + * Both CookiesList and CookieTreeNode implement this API. + * It is used by CookiesList.loadChildren(). + */ + clear: function() { + parentLookup = {}; + this.data_ = []; + this.dataModel = new ArrayDataModel(this.data_); + this.redraw(); + }, + + /** + * Add tree nodes by given parent. + * @param {Object} parent The parent node. + * @param {number} start The index at which to start inserting the nodes. + * @param {Array} nodesData Nodes data array. + * @private + */ + addByParent_: function(parent, start, nodesData) { + if (!parent) + return; + + parent.startBatchUpdates(); + parent.insertAt(nodesData, start); + parent.endBatchUpdates(); + + cr.dispatchSimpleEvent(this, 'change'); + }, + + /** + * Add tree nodes by parent id. + * This is used by cookies_view.js. + * @param {string} parentId Id of the parent node. + * @param {number} start The index at which to start inserting the nodes. + * @param {Array} nodesData Nodes data array. + */ + addByParentId: function(parentId, start, nodesData) { + var parent = parentId ? parentLookup[parentId] : this; + this.addByParent_(parent, start, nodesData); + }, + + /** + * Removes tree nodes by parent id. + * This is used by cookies_view.js. + * @param {string} parentId Id of the parent node. + * @param {number} start The index at which to start removing the nodes. + * @param {number} count Number of nodes to remove. + */ + removeByParentId: function(parentId, start, count) { + var parent = parentId ? parentLookup[parentId] : this; + if (!parent) + return; + + parent.startBatchUpdates(); + while (count-- > 0) + parent.remove(start); + parent.endBatchUpdates(); + + cr.dispatchSimpleEvent(this, 'change'); + }, + + /** + * Loads the immediate children of given parent node. + * This is used by cookies_view.js. + * @param {string} parentId Id of the parent node. + * @param {Array} children The immediate children of parent node. + */ + loadChildren: function(parentId, children) { + if (parentId) + delete lookupRequests[parentId]; + var parent = parentId ? parentLookup[parentId] : this; + if (!parent) + return; + + parent.startBatchUpdates(); + parent.clear(); + this.addByParent_(parent, 0, children); + parent.endBatchUpdates(); + }, + }; + + return { + CookiesList: CookiesList + }; +}); diff --git a/chrome/browser/resources/options2/cookies_view.css b/chrome/browser/resources/options2/cookies_view.css new file mode 100644 index 0000000..2aab6b2 --- /dev/null +++ b/chrome/browser/resources/options2/cookies_view.css @@ -0,0 +1,182 @@ +/* +Copyright (c) 2011 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. +*/ + +/* styles for the cookies list elements in cookies_view.html */ +#remove-all-cookies-search-column { + bottom: 10px; + position: absolute; + right: 0; +} + +html[dir=rtl] #remove-all-cookies-search-column { + left: 0; + right: auto; +} + +#cookies-column-headers { + position: relative; + width: 100%; +} + +#cookies-column-headers h3 { + font-size: 105%; + font-weight: bold; + margin: 10px 0; +} + +/* notice the width and padding for these columns match up with those below */ +#cookies-site-column { + display: inline-block; + font-weight: bold; + width: 11em; +} + +#cookies-data-column { + -webkit-padding-start: 7px; + display: inline-block; + font-weight: bold; +} + +#cookies-list { + border: 1px solid #D9D9D9; + margin: 0; +} + + +/* enable animating the height of items */ +list.cookie-list .deletable-item { + -webkit-transition: height .15s ease-in-out; +} + +/* disable webkit-box display */ +list.cookie-list .deletable-item > :first-child { + display: block; +} + +/* force the X for deleting an origin to stay at the top */ +list.cookie-list > .deletable-item > .close-button { + position: absolute; + right: 2px; + top: 8px; +} + +html[dir=rtl] list.cookie-list > .deletable-item > .close-button { + left: 2px; + right: auto; +} + + +/* styles for the site (aka origin) and its summary */ +.cookie-site { + /* notice that the width, margin, and padding match up with those above */ + -webkit-margin-end: 2px; + -webkit-padding-start: 5px; + display: inline-block; + overflow: hidden; + text-overflow: ellipsis; + width: 11em; +} + +list.cookie-list > .deletable-item[selected] .cookie-site { + -webkit-user-select: text; +} + +.cookie-data { + display: inline-block; +} + +.cookie-size { + display: inline-block; + float: right; + margin-right: 3em; +} + +list.cookie-list > .deletable-item[selected] .cookie-data { + -webkit-user-select: text; +} + + +/* styles for the individual items (cookies, etc.) */ +.cookie-items { + /* notice that the margin and padding match up with those above */ + -webkit-margin-start: 11em; + -webkit-padding-start: 7px; + -webkit-transition: .15s ease-in-out; + height: 0; + opacity: 0; + /* make the cookie items wrap correctly */ + white-space: normal; +} + +.measure-items .cookie-items { + -webkit-transition: none; + height: auto; + visibility: hidden; +} + +.show-items .cookie-items { + opacity: 1; +} + +.cookie-items .cookie-item { + background: #E0E9F5; + border-radius: 5px; + border: 1px solid #8392AE; + display: inline-block; + font-size: 85%; + height: auto; + margin: 2px 4px 2px 0; + max-width: 100px; + min-width: 40px; + overflow: hidden; + padding: 0 3px; + text-align: center; + text-overflow: ellipsis; +} + +.cookie-items .cookie-item:hover { + background: #EEF3F9; + border-color: #647187; +} + +.cookie-items .cookie-item[selected] { + background: #F5F8F8; + border-color: #B2B2B2; +} + +.cookie-items .cookie-item[selected]:hover { + background: #F5F8F8; + border-color: #647187; +} + + +/* styles for the cookie details box */ +.cookie-details { + background: #F5F8F8; + border-radius: 5px; + border: 1px solid #B2B2B2; + margin-top: 2px; + padding: 5px; +} + +list.cookie-list > .deletable-item[selected] .cookie-details { + -webkit-user-select: text; +} + +.cookie-details-table { + table-layout: fixed; + width: 100%; +} + +.cookie-details-label { + vertical-align: top; + white-space: pre; + width: 10em; +} + +.cookie-details-value { + word-wrap: break-word; +} diff --git a/chrome/browser/resources/options2/cookies_view.html b/chrome/browser/resources/options2/cookies_view.html new file mode 100644 index 0000000..1a58487 --- /dev/null +++ b/chrome/browser/resources/options2/cookies_view.html @@ -0,0 +1,15 @@ +<div id="cookiesViewPage" class="page" hidden> + <h1 i18n-content="cookiesViewPage"></h1> + <div id="cookies-column-headers"> + <div id="cookies-site-column"><h3 i18n-content="cookie_domain"></h3></div> + <div id="cookies-data-column"><h3 i18n-content="cookie_local_data"></h3></div> + <div id="remove-all-cookies-search-column"> + <button id="remove-all-cookies-button" + i18n-content="remove_all_cookie"></button> + <input id="cookies-search-box" type="search" + i18n-values="placeholder:search_cookies" incremental + results="10" autosave="org.chromium.options.cookies.search"> + </div> + </div> + <list id="cookies-list"></list> +</div> diff --git a/chrome/browser/resources/options2/cookies_view.js b/chrome/browser/resources/options2/cookies_view.js new file mode 100644 index 0000000..dafa0cd --- /dev/null +++ b/chrome/browser/resources/options2/cookies_view.js @@ -0,0 +1,140 @@ +// Copyright (c) 2011 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; + + ///////////////////////////////////////////////////////////////////////////// + // CookiesView class: + + /** + * Encapsulated handling of the cookies and other site data page. + * @constructor + */ + function CookiesView(model) { + OptionsPage.call(this, 'cookies', + templateData.cookiesViewPageTabTitle, + 'cookiesViewPage'); + } + + cr.addSingletonGetter(CookiesView); + + CookiesView.prototype = { + __proto__: OptionsPage.prototype, + + /** + * The timer id of the timer set on search query change events. + * @type {number} + * @private + */ + queryDelayTimerId_: 0, + + /** + * The most recent search query, or null if the query is empty. + * @type {?string} + * @private + */ + lastQuery_ : null, + + initializePage: function() { + OptionsPage.prototype.initializePage.call(this); + + $('cookies-search-box').addEventListener('search', + this.handleSearchQueryChange_.bind(this)); + + $('remove-all-cookies-button').onclick = function(e) { + chrome.send('removeAllCookies', []); + }; + + var cookiesList = $('cookies-list'); + options.CookiesList.decorate(cookiesList); + window.addEventListener('resize', this.handleResize_.bind(this)); + + this.addEventListener('visibleChange', this.handleVisibleChange_); + }, + + /** + * Search cookie using text in |cookies-search-box|. + */ + searchCookie: function() { + this.queryDelayTimerId_ = 0; + var filter = $('cookies-search-box').value; + if (this.lastQuery_ != filter) { + this.lastQuery_ = filter; + chrome.send('updateCookieSearchResults', [filter]); + } + }, + + /** + * Handles search query changes. + * @param {!Event} e The event object. + * @private + */ + handleSearchQueryChange_: function(e) { + if (this.queryDelayTimerId_) + window.clearTimeout(this.queryDelayTimerId_); + + this.queryDelayTimerId_ = window.setTimeout( + this.searchCookie.bind(this), 500); + }, + + initialized_: false, + + /** + * Handler for OptionsPage's visible property change event. + * @param {Event} e Property change event. + * @private + */ + handleVisibleChange_: function(e) { + if (!this.visible) + return; + + // Resize the cookies list whenever the options page becomes visible. + this.handleResize_(null); + if (!this.initialized_) { + this.initialized_ = true; + this.searchCookie(); + } else { + $('cookies-list').redraw(); + } + + $('cookies-search-box').focus(); + }, + + /** + * Handler for when the window changes size. Resizes the cookies list to + * match the window height. + * @param {?Event} e Window resize event, or null if called directly. + * @private + */ + handleResize_: function(e) { + if (!this.visible) + return; + var cookiesList = $('cookies-list'); + // 25 pixels from the window bottom seems like a visually pleasing amount. + var height = window.innerHeight - cookiesList.offsetTop - 25; + cookiesList.style.height = height + 'px'; + }, + }; + + // CookiesViewHandler callbacks. + CookiesView.onTreeItemAdded = function(args) { + $('cookies-list').addByParentId(args[0], args[1], args[2]); + }; + + CookiesView.onTreeItemRemoved = function(args) { + $('cookies-list').removeByParentId(args[0], args[1], args[2]); + }; + + CookiesView.loadChildren = function(args) { + $('cookies-list').loadChildren(args[0], args[1]); + }; + + // Export + return { + CookiesView: CookiesView + }; + +}); diff --git a/chrome/browser/resources/options2/deletable_item_list.js b/chrome/browser/resources/options2/deletable_item_list.js new file mode 100644 index 0000000..80d4963 --- /dev/null +++ b/chrome/browser/resources/options2/deletable_item_list.js @@ -0,0 +1,185 @@ +// Copyright (c) 2011 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() { + const List = cr.ui.List; + const ListItem = cr.ui.ListItem; + + /** + * Creates a deletable list item, which has a button that will trigger a call + * to deleteItemAtIndex(index) in the list. + */ + var DeletableItem = cr.ui.define('li'); + + DeletableItem.prototype = { + __proto__: ListItem.prototype, + + /** + * The element subclasses should populate with content. + * @type {HTMLElement} + * @private + */ + contentElement_: null, + + /** + * The close button element. + * @type {HTMLElement} + * @private + */ + closeButtonElement_: null, + + /** + * Whether or not this item can be deleted. + * @type {boolean} + * @private + */ + deletable_: true, + + /** @inheritDoc */ + decorate: function() { + ListItem.prototype.decorate.call(this); + + this.classList.add('deletable-item'); + + this.contentElement_ = this.ownerDocument.createElement('div'); + this.appendChild(this.contentElement_); + + this.closeButtonElement_ = this.ownerDocument.createElement('button'); + this.closeButtonElement_.className = + 'raw-button close-button custom-appearance'; + this.closeButtonElement_.addEventListener('mousedown', + this.handleMouseDownUpOnClose_); + this.closeButtonElement_.addEventListener('mouseup', + this.handleMouseDownUpOnClose_); + this.closeButtonElement_.addEventListener('focus', + this.handleFocus_.bind(this)); + this.appendChild(this.closeButtonElement_); + }, + + /** + * Returns the element subclasses should add content to. + * @return {HTMLElement} The element subclasses should popuplate. + */ + get contentElement() { + return this.contentElement_; + }, + + /* Gets/sets the deletable property. An item that is not deletable doesn't + * show the delete button (although space is still reserved for it). + */ + get deletable() { + return this.deletable_; + }, + set deletable(value) { + this.deletable_ = value; + this.closeButtonElement_.disabled = !value; + }, + + /** + * Called when a focusable child element receives focus. Selects this item + * in the list selection model. + * @private + */ + handleFocus_: function() { + var list = this.parentNode; + var index = list.getIndexOfListItem(this); + list.selectionModel.selectedIndex = index; + list.selectionModel.anchorIndex = index; + }, + + /** + * Don't let the list have a crack at the event. We don't want clicking the + * close button to change the selection of the list. + * @param {Event} e The mouse down/up event object. + * @private + */ + handleMouseDownUpOnClose_: function(e) { + if (!e.target.disabled) + e.stopPropagation(); + }, + }; + + var DeletableItemList = cr.ui.define('list'); + + DeletableItemList.prototype = { + __proto__: List.prototype, + + /** @inheritDoc */ + decorate: function() { + List.prototype.decorate.call(this); + this.addEventListener('click', this.handleClick_); + this.addEventListener('keydown', this.handleKeyDown_); + }, + + /** + * Callback for onclick events. + * @param {Event} e The click event object. + * @private + */ + handleClick_: function(e) { + if (this.disabled) + return; + + var target = e.target; + if (target.classList.contains('close-button')) { + var listItem = this.getListItemAncestor(target); + var selected = this.selectionModel.selectedIndexes; + + // Check if the list item that contains the close button being clicked + // is not in the list of selected items. Only delete this item in that + // case. + var idx = this.getIndexOfListItem(listItem); + if (selected.indexOf(idx) == -1) { + this.deleteItemAtIndex(idx); + } else { + this.deleteSelectedItems_(); + } + } + }, + + /** + * Callback for keydown events. + * @param {Event} e The keydown event object. + * @private + */ + handleKeyDown_: function(e) { + // Map delete (and backspace on Mac) to item deletion (unless focus is + // in an input field, where it's intended for text editing). + if ((e.keyCode == 46 || (e.keyCode == 8 && cr.isMac)) && + e.target.tagName != 'INPUT') { + this.deleteSelectedItems_(); + // Prevent the browser from going back. + e.preventDefault(); + } + }, + + /** + * Deletes all the currently selected items that are deletable. + * @private + */ + deleteSelectedItems_: function() { + var selected = this.selectionModel.selectedIndexes; + // Reverse through the list of selected indexes to maintain the + // correct index values after deletion. + for (var j = selected.length - 1; j >= 0; j--) { + var index = selected[j]; + if (this.getListItemByIndex(index).deletable) + this.deleteItemAtIndex(index); + } + }, + + /** + * Called when an item should be deleted; subclasses are responsible for + * implementing. + * @param {number} index The index of the item that is being deleted. + */ + deleteItemAtIndex: function(index) { + }, + }; + + return { + DeletableItemList: DeletableItemList, + DeletableItem: DeletableItem, + }; +}); diff --git a/chrome/browser/resources/options2/extension_list.js b/chrome/browser/resources/options2/extension_list.js new file mode 100644 index 0000000..5cbad97 --- /dev/null +++ b/chrome/browser/resources/options2/extension_list.js @@ -0,0 +1,764 @@ +// Copyright (c) 2011 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() { + 'use strict'; + + /** + * A lookup helper function to find the first node that has an id (starting + * at |node| and going up the parent chain). + * @param {Element} node The node to start looking at. + */ + function findIdNode(node) { + while (node && !node.id) { + node = node.parentNode; + } + return node; + } + + /** + * Creates a new list of extensions. + * @param {Object=} opt_propertyBag Optional properties. + * @constructor + * @extends {cr.ui.div} + */ + var ExtensionsList = cr.ui.define('div'); + + var handlersInstalled = false; + + /** + * @type {Object.<string, boolean>} A map from extension id to a boolean + * indicating whether its details section is expanded. This persists + * between calls to decorate. + */ + var showingDetails = {}; + + /** + * @type {Object.<string, boolean>} A map from extension id to a boolean + * indicating whether the incognito warning is showing. This persists + * between calls to decorate. + */ + var showingWarning = {}; + + ExtensionsList.prototype = { + __proto__: HTMLDivElement.prototype, + + /** @inheritDoc */ + decorate: function() { + this.initControlsAndHandlers_(); + + this.deleteExistingExtensionNodes_(); + + this.showExtensionNodes_(); + }, + + /** + * Initializes the controls (toggle section and button) and installs + * handlers. + * @private + */ + initControlsAndHandlers_: function() { + // Make sure developer mode section is set correctly as per saved setting. + var toggleButton = $('toggle-dev-on'); + var toggleSection = $('dev'); + if (this.data_.developerMode) { + toggleSection.classList.add('dev-open'); + toggleSection.classList.remove('dev-closed'); + toggleButton.checked = true; + } else { + toggleSection.classList.remove('dev-open'); + toggleSection.classList.add('dev-closed'); + } + + // Instal global event handlers. + if (!handlersInstalled) { + var searchPage = SearchPage.getInstance(); + searchPage.addEventListener('searchChanged', + this.searchChangedHandler_.bind(this)); + + // Support full keyboard accessibility without making things ugly + // for users who click, by hiding some focus outlines when the user + // clicks anywhere, but showing them when the user presses any key. + this.ownerDocument.body.classList.add('hide-some-focus-outlines'); + this.ownerDocument.addEventListener('click', (function(e) { + this.ownerDocument.body.classList.add('hide-some-focus-outlines'); + return true; + }).bind(this), true); + this.ownerDocument.addEventListener('keydown', (function(e) { + this.ownerDocument.body.classList.remove('hide-some-focus-outlines'); + return true; + }).bind(this), true); + + handlersInstalled = true; + } + }, + + /** + * Deletes the existing Extension nodes from the page to make room for new + * ones. + * @private + */ + deleteExistingExtensionNodes_: function() { + while (this.hasChildNodes()){ + this.removeChild(this.firstChild); + } + }, + + /** + * Handles decorating the details section. + * @param {Element} details The div that the details should be attached to. + * @param {Object} extension The extension we are showing the details for. + * @private + */ + showExtensionNodes_: function() { + // Iterate over the extension data and add each item to the list. + for (var i = 0; i < this.data_.extensions.length; i++) { + var extension = this.data_.extensions[i]; + var id = extension.id; + + var wrapper = this.ownerDocument.createElement('div'); + + var expanded = showingDetails[id]; + var butterbar = showingWarning[id]; + + wrapper.classList.add(expanded ? 'extension-list-item-expanded' : + 'extension-list-item-collaped'); + if (!extension.enabled) + wrapper.classList.add('disabled'); + wrapper.id = id; + this.appendChild(wrapper); + + var vboxOuter = this.ownerDocument.createElement('div'); + vboxOuter.classList.add('vbox'); + vboxOuter.classList.add('extension-list-item'); + wrapper.appendChild(vboxOuter); + + var hbox = this.ownerDocument.createElement('div'); + hbox.classList.add('hbox'); + vboxOuter.appendChild(hbox); + + // Add a container div for the zippy, so we can extend the hit area. + var container = this.ownerDocument.createElement('div'); + // Clicking anywhere on the div expands/collapses the details. + container.classList.add('extension-zippy-container'); + container.title = expanded ? + localStrings.getString('extensionSettingsHideDetails') : + localStrings.getString('extensionSettingsShowDetails'); + container.tabIndex = 0; + container.setAttribute('role', 'button'); + container.setAttribute('aria-controls', extension.id + '_details'); + container.setAttribute('aria-expanded', expanded); + container.addEventListener('click', this.handleZippyClick_.bind(this)); + container.addEventListener('keydown', + this.handleZippyKeyDown_.bind(this)); + hbox.appendChild(container); + + // On the far left we have the zippy icon. + var div = this.ownerDocument.createElement('div'); + div.id = id + '_zippy'; + div.classList.add('extension-zippy-default'); + div.classList.add(expanded ? 'extension-zippy-expanded' : + 'extension-zippy-collapsed'); + container.appendChild(div); + + // Next to it, we have the extension icon. + var icon = this.ownerDocument.createElement('img'); + icon.classList.add('extension-icon'); + icon.src = extension.icon; + hbox.appendChild(icon); + + // Start a vertical box for showing the details. + var vbox = this.ownerDocument.createElement('div'); + vbox.classList.add('vbox'); + vbox.classList.add('stretch'); + vbox.classList.add('details-view'); + hbox.appendChild(vbox); + + div = this.ownerDocument.createElement('div'); + vbox.appendChild(div); + + // Title comes next. + var title = this.ownerDocument.createElement('span'); + title.classList.add('extension-title'); + title.textContent = extension.name; + vbox.appendChild(title); + + // Followed by version. + var version = this.ownerDocument.createElement('span'); + version.classList.add('extension-version'); + version.textContent = extension.version; + vbox.appendChild(version); + + // And the additional info label (unpacked/crashed). + if (extension.terminated || extension.isUnpacked) { + var version = this.ownerDocument.createElement('span'); + version.classList.add('extension-version'); + version.textContent = extension.terminated ? + localStrings.getString('extensionSettingsCrashMessage') : + localStrings.getString('extensionSettingsInDevelopment'); + vbox.appendChild(version); + } + + div = this.ownerDocument.createElement('div'); + vbox.appendChild(div); + + // And below that we have description (if provided). + if (extension.description.length > 0) { + var description = this.ownerDocument.createElement('span'); + description.classList.add('extension-description'); + description.textContent = extension.description; + vbox.appendChild(description); + } + + // Immediately following the description, we have the + // Options link (optional). + if (extension.options_url) { + var link = this.ownerDocument.createElement('a'); + link.classList.add('extension-links-trailing'); + link.textContent = localStrings.getString('extensionSettingsOptions'); + link.href = '#'; + link.addEventListener('click', this.handleOptions_.bind(this)); + vbox.appendChild(link); + } + + // Then the optional Visit Website link. + if (extension.homepageUrl) { + var link = this.ownerDocument.createElement('a'); + link.classList.add('extension-links-trailing'); + link.textContent = + localStrings.getString('extensionSettingsVisitWebsite'); + link.href = extension.homepageUrl; + vbox.appendChild(link); + } + + if (extension.warnings.length > 0) { + var warningsDiv = this.ownerDocument.createElement('div'); + warningsDiv.classList.add('extension-warnings'); + + var warningsHeader = this.ownerDocument.createElement('span'); + warningsHeader.classList.add('extension-warnings-title'); + warningsHeader.textContent = + localStrings.getString('extensionSettingsWarningsTitle'); + warningsDiv.appendChild(warningsHeader); + + var warningList = this.ownerDocument.createElement('ul'); + for (var j = 0; j < extension.warnings.length; ++j) { + var warningEntry = this.ownerDocument.createElement('li'); + warningEntry.textContent = extension.warnings[j]; + warningList.appendChild(warningEntry); + } + warningsDiv.appendChild(warningList); + + vbox.appendChild(warningsDiv); + } + + // And now the details section that is normally hidden. + var details = this.ownerDocument.createElement('div'); + details.classList.add('vbox'); + vbox.appendChild(details); + + this.decorateDetailsSection_(details, extension, expanded, butterbar); + + // And on the right of the details we have the Enable/Enabled checkbox. + div = this.ownerDocument.createElement('div'); + hbox.appendChild(div); + + var section = this.ownerDocument.createElement('section'); + section.classList.add('extension-enabling'); + div.appendChild(section); + + if (!extension.terminated) { + // The Enable checkbox. + var input = this.ownerDocument.createElement('input'); + input.addEventListener('click', this.handleEnable_.bind(this)); + input.type = 'checkbox'; + input.name = 'toggle-' + id; + input.disabled = !extension.mayDisable; + if (extension.enabled) + input.checked = true; + input.id = 'toggle-' + id; + section.appendChild(input); + var label = this.ownerDocument.createElement('label'); + label.classList.add('extension-enabling-label'); + if (extension.enabled) + label.classList.add('extension-enabling-label-bold'); + label.htmlFor = 'toggle-' + id; + label.id = 'toggle-' + id + '-label'; + if (extension.enabled) { + // Enabled (with a d). + label.textContent = + localStrings.getString('extensionSettingsEnabled'); + } else { + // Enable (no d). + label.textContent = + localStrings.getString('extensionSettingsEnable'); + } + section.appendChild(label); + } else { + // Extension has been terminated, show a Reload link. + var link = this.ownerDocument.createElement('a'); + link.classList.add('extension-links-trailing'); + link.id = extension.id; + link.textContent = + localStrings.getString('extensionSettingsReload'); + link.href = '#'; + link.addEventListener('click', this.handleReload_.bind(this)); + section.appendChild(link); + } + + // And, on the far right we have the uninstall button. + var button = this.ownerDocument.createElement('button'); + button.classList.add('extension-delete'); + button.id = id; + if (!extension.mayDisable) + button.disabled = true; + button.textContent = localStrings.getString('extensionSettingsRemove'); + button.addEventListener('click', this.handleUninstall_.bind(this)); + hbox.appendChild(button); + } + + // Do one pass to find what the size of the checkboxes should be. + var minCheckboxWidth = Infinity; + var maxCheckboxWidth = 0; + for (var i = 0; i < this.data_.extensions.length; ++i) { + var label = $('toggle-' + this.data_.extensions[i].id + '-label'); + if (label.offsetWidth > maxCheckboxWidth) + maxCheckboxWidth = label.offsetWidth; + if (label.offsetWidth < minCheckboxWidth) + minCheckboxWidth = label.offsetWidth; + } + + // Do another pass, making sure checkboxes line up. + var difference = maxCheckboxWidth - minCheckboxWidth; + for (var i = 0; i < this.data_.extensions.length; ++i) { + var label = $('toggle-' + this.data_.extensions[i].id + '-label'); + if (label.offsetWidth < maxCheckboxWidth) + label.style.WebkitMarginEnd = difference.toString() + 'px'; + } + }, + + /** + * Handles decorating the details section. + * @param {Element} details The div that the details should be attached to. + * @param {Object} extension The extension we are shoting the details for. + * @param {boolean} expanded Whether to show the details expanded or not. + * @param {boolean} showButterbar Whether to show the incognito warning or + * not. + * @private + */ + decorateDetailsSection_: function(details, extension, + expanded, showButterbar) { + // This container div is needed because vbox display + // overrides display:hidden. + var detailsContents = this.ownerDocument.createElement('div'); + detailsContents.classList.add(expanded ? 'extension-details-visible' : + 'extension-details-hidden'); + detailsContents.id = extension.id + '_details'; + details.appendChild(detailsContents); + + var div = this.ownerDocument.createElement('div'); + div.classList.add('informative-text'); + detailsContents.appendChild(div); + + // Keep track of how many items we'll show in the details section. + var itemsShown = 0; + + if (this.data_.developerMode) { + // First we have the id. + var content = this.ownerDocument.createElement('div'); + content.textContent = + localStrings.getString('extensionSettingsExtensionId') + + ' ' + extension.id; + div.appendChild(content); + itemsShown++; + + // Then, the path, if provided by unpacked extension. + if (extension.isUnpacked) { + content = this.ownerDocument.createElement('div'); + content.textContent = + localStrings.getString('extensionSettingsExtensionPath') + + ' ' + extension.path; + div.appendChild(content); + itemsShown++; + } + + // Then, the 'managed, cannot uninstall/disable' message. + if (!extension.mayDisable) { + content = this.ownerDocument.createElement('div'); + content.textContent = + localStrings.getString('extensionSettingsPolicyControlled'); + div.appendChild(content); + itemsShown++; + } + + // Then active views: + if (extension.views.length > 0) { + var table = this.ownerDocument.createElement('table'); + table.classList.add('extension-inspect-table'); + div.appendChild(table); + var tr = this.ownerDocument.createElement('tr'); + table.appendChild(tr); + var td = this.ownerDocument.createElement('td'); + td.classList.add('extension-inspect-left-column'); + tr.appendChild(td); + var span = this.ownerDocument.createElement('span'); + td.appendChild(span); + span.textContent = + localStrings.getString('extensionSettingsInspectViews'); + + td = this.ownerDocument.createElement('td'); + for (var i = 0; i < extension.views.length; ++i) { + // Then active views: + content = this.ownerDocument.createElement('div'); + var link = this.ownerDocument.createElement('a'); + link.classList.add('extension-links-view'); + link.textContent = extension.views[i].path; + link.id = extension.id; + link.href = '#'; + link.addEventListener('click', this.sendInspectMessage_.bind(this)); + content.appendChild(link); + + if (extension.views[i].incognito) { + var incognito = this.ownerDocument.createElement('span'); + incognito.classList.add('extension-links-view'); + incognito.textContent = + localStrings.getString('viewIncognito'); + content.appendChild(incognito); + } + + td.appendChild(content); + tr.appendChild(td); + + itemsShown++; + } + } + } + + var content = this.ownerDocument.createElement('div'); + detailsContents.appendChild(content); + + // Then Reload: + if (extension.enabled && extension.allow_reload) { + this.addLinkTo_(content, + localStrings.getString('extensionSettingsReload'), + extension.id, + this.handleReload_.bind(this)); + itemsShown++; + } + + // Then Show (Browser Action) Button: + if (extension.enabled && extension.enable_show_button) { + this.addLinkTo_(content, + localStrings.getString('extensionSettingsShowButton'), + extension.id, + this.handleShowButton_.bind(this)); + itemsShown++; + } + + if (extension.enabled) { + // The 'allow in incognito' checkbox. + var label = this.ownerDocument.createElement('label'); + label.classList.add('extension-checkbox-label'); + content.appendChild(label); + var input = this.ownerDocument.createElement('input'); + input.addEventListener('click', + this.handleToggleEnableIncognito_.bind(this)); + input.id = extension.id; + input.type = 'checkbox'; + if (extension.enabledIncognito) + input.checked = true; + label.appendChild(input); + var span = this.ownerDocument.createElement('span'); + span.classList.add('extension-checkbox-span'); + span.textContent = + localStrings.getString('extensionSettingsEnableIncognito'); + label.appendChild(span); + itemsShown++; + } + + if (extension.enabled && extension.wantsFileAccess) { + // The 'allow access to file URLs' checkbox. + label = this.ownerDocument.createElement('label'); + label.classList.add('extension-checkbox-label'); + content.appendChild(label); + var input = this.ownerDocument.createElement('input'); + input.addEventListener('click', + this.handleToggleAllowFileUrls_.bind(this)); + input.id = extension.id; + input.type = 'checkbox'; + if (extension.allowFileAccess) + input.checked = true; + label.appendChild(input); + var span = this.ownerDocument.createElement('span'); + span.classList.add('extension-checkbox-span'); + span.textContent = + localStrings.getString('extensionSettingsAllowFileAccess'); + label.appendChild(span); + itemsShown++; + } + + if (extension.enabled && !extension.is_hosted_app) { + // And add a hidden warning message for allowInIncognito. + content = this.ownerDocument.createElement('div'); + content.id = extension.id + '_incognitoWarning'; + content.classList.add('butter-bar'); + content.hidden = !showButterbar; + detailsContents.appendChild(content); + + var span = this.ownerDocument.createElement('span'); + span.innerHTML = + localStrings.getString('extensionSettingsIncognitoWarning'); + content.appendChild(span); + itemsShown++; + } + + var zippy = extension.id + '_zippy'; + $(zippy).hidden = !itemsShown; + + // If this isn't expanded now, make sure the newly-added controls + // are not part of the tab order. + if (!expanded) { + var detailsControls = details.querySelectorAll('a, input'); + for (var i = 0; i < detailsControls.length; i++) + detailsControls[i].tabIndex = -1; + } + }, + + /** + * A helper function to add contextual actions for extensions (action links) + * to the page. + */ + addLinkTo_: function(parent, linkText, id, handler) { + var link = this.ownerDocument.createElement('a'); + link.className = 'extension-links-trailing'; + link.textContent = linkText; + link.id = id; + link.href = '#'; + link.addEventListener('click', handler); + parent.appendChild(link); + }, + + /** + * A lookup helper function to find an extension based on an id. + * @param {string} id The |id| of the extension to look up. + * @private + */ + getExtensionWithId_: function(id) { + for (var i = 0; i < this.data_.extensions.length; ++i) { + if (this.data_.extensions[i].id == id) + return this.data_.extensions[i]; + } + return null; + }, + + /** + * Handles a key down on the zippy icon. + * @param {Event} e Key event. + * @private + */ + handleZippyKeyDown_: function(e) { + if (e.keyCode == 13 || e.keyCode == 32) // Enter or Space. + this.handleZippyClick_(e); + }, + + /** + * Handles the mouseclick on the zippy icon (that expands and collapses the + * details section). + * @param {Event} e Mouse event. + * @private + */ + handleZippyClick_: function(e) { + var node = findIdNode(e.target.parentNode); + var iter = this.firstChild; + while (iter) { + var zippy = $(iter.id + '_zippy'); + var details = $(iter.id + '_details'); + var container = zippy.parentElement; + if (iter.id == node.id) { + // Toggle visibility. + if (iter.classList.contains('extension-list-item-expanded')) { + // Hide yo kids! Hide yo wife! + showingDetails[iter.id] = false; + zippy.classList.remove('extension-zippy-expanded'); + zippy.classList.add('extension-zippy-collapsed'); + details.classList.remove('extension-details-visible'); + details.classList.add('extension-details-hidden'); + iter.classList.remove('extension-list-item-expanded'); + iter.classList.add('extension-list-item-collaped'); + container.setAttribute('aria-expanded', 'false'); + container.title = + localStrings.getString('extensionSettingsShowDetails'); + var detailsControls = details.querySelectorAll('a, input'); + for (var i = 0; i < detailsControls.length; i++) + detailsControls[i].tabIndex = -1; + + // Hide yo incognito warning. + var butterBar = + this.querySelector('#' + iter.id + '_incognitoWarning'); + if (butterBar !== null) { + butterBar.hidden = true; + showingWarning[iter.id] = false; + } + } else { + // Show the contents. + showingDetails[iter.id] = true; + zippy.classList.remove('extension-zippy-collapsed'); + zippy.classList.add('extension-zippy-expanded'); + details.classList.remove('extension-details-hidden'); + details.classList.add('extension-details-visible'); + iter.classList.remove('extension-list-item-collaped'); + iter.classList.add('extension-list-item-expanded'); + container.setAttribute('aria-expanded', 'true'); + container.title = + localStrings.getString('extensionSettingsHideDetails'); + var detailsControls = details.querySelectorAll('a, input'); + for (var i = 0; i < detailsControls.length; i++) + detailsControls[i].tabIndex = 0; + } + } + iter = iter.nextSibling; + } + }, + + /** + * Handles the 'searchChanged' event. This is used to limit the number of + * items to show in the list, when the user is searching for items with the + * search box. Otherwise, if one match is found, the whole list of + * extensions would be shown when we only want the matching items to be + * found. + * @param {Event} e Change event. + * @private + */ + searchChangedHandler_: function(e) { + var searchString = e.searchText; + var child = this.firstChild; + while (child) { + var extension = this.getExtensionWithId_(child.id); + if (searchString.length == 0) { + // Show all. + child.classList.remove('search-suppress'); + } else { + // If the search string does not appear within the text of the + // extension, then hide it. + if ((extension.name.toLowerCase().indexOf(searchString) < 0) && + (extension.version.toLowerCase().indexOf(searchString) < 0) && + (extension.description.toLowerCase().indexOf(searchString) < 0)) { + // Hide yo extension! + child.classList.add('search-suppress'); + } else { + // Show yourself! + child.classList.remove('search-suppress'); + } + } + child = child.nextSibling; + } + }, + + /** + * Handles the Reload Extension functionality. + * @param {Event} e Change event. + * @private + */ + handleReload_: function(e) { + var node = findIdNode(e.target); + chrome.send('extensionSettingsReload', [node.id]); + }, + + /** + * Handles the Show (Browser Action) Button functionality. + * @param {Event} e Change event. + * @private + */ + handleShowButton_: function(e) { + var node = findIdNode(e.target); + chrome.send('extensionSettingsShowButton', [node.id]); + }, + + /** + * Handles the Enable/Disable Extension functionality. + * @param {Event} e Change event. + * @private + */ + handleEnable_: function(e) { + var node = findIdNode(e.target.parentNode); + var extension = this.getExtensionWithId_(node.id); + chrome.send('extensionSettingsEnable', + [node.id, extension.enabled ? 'false' : 'true']); + chrome.send('extensionSettingsRequestExtensionsData'); + }, + + /** + * Handles the Uninstall Extension functionality. + * @param {Event} e Change event. + * @private + */ + handleUninstall_: function(e) { + var node = findIdNode(e.target.parentNode); + chrome.send('extensionSettingsUninstall', [node.id]); + chrome.send('extensionSettingsRequestExtensionsData'); + }, + + /** + * Handles the View Options link. + * @param {Event} e Change event. + * @private + */ + handleOptions_: function(e) { + var node = findIdNode(e.target.parentNode); + var extension = this.getExtensionWithId_(node.id); + chrome.send('extensionSettingsOptions', [extension.id]); + e.preventDefault(); + }, + + /** + * Handles the Enable Extension In Incognito functionality. + * @param {Event} e Change event. + * @private + */ + handleToggleEnableIncognito_: function(e) { + var node = findIdNode(e.target); + var butterBar = document.getElementById(node.id + '_incognitoWarning'); + butterBar.hidden = !e.target.checked; + showingWarning[node.id] = e.target.checked; + chrome.send('extensionSettingsEnableIncognito', + [node.id, String(e.target.checked)]); + }, + + /** + * Handles the Allow On File URLs functionality. + * @param {Event} e Change event. + * @private + */ + handleToggleAllowFileUrls_: function(e) { + var node = findIdNode(e.target); + chrome.send('extensionSettingsAllowFileAccess', + [node.id, String(e.target.checked)]); + }, + + /** + * Tell the C++ ExtensionDOMHandler to inspect the page detailed in + * |viewData|. + * @param {Event} e Change event. + * @private + */ + sendInspectMessage_: function(e) { + var extension = this.getExtensionWithId_(e.srcElement.id); + for (var i = 0; i < extension.views.length; ++i) { + if (extension.views[i].path == e.srcElement.innerText) { + // TODO(aa): This is ghetto, but WebUIBindings doesn't support sending + // anything other than arrays of strings, and this is all going to get + // replaced with V8 extensions soon anyway. + chrome.send('extensionSettingsInspect', [ + String(extension.views[i].renderProcessId), + String(extension.views[i].renderViewId) + ]); + } + } + }, + }; + + return { + ExtensionsList: ExtensionsList + }; +}); diff --git a/chrome/browser/resources/options2/extension_settings.css b/chrome/browser/resources/options2/extension_settings.css new file mode 100644 index 0000000..35ca397 --- /dev/null +++ b/chrome/browser/resources/options2/extension_settings.css @@ -0,0 +1,274 @@ +/* +Copyright (c) 2011 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. +*/ + +.details-view { + -webkit-padding-end: 10px; +} + +.extension-list-item { + padding-bottom: 7px; + padding-top: 7px; + width: 100%; + -webkit-user-select: auto; +} + +/* Get rid of display: table, which causes width issues. */ +#extension-settings .displaytable { + display: block; +} +/* Get rid of display: table row, which causes width issues. */ +#extension-settings .displaytable > section { + display: block; +} +/* Get rid of display: table cell, which causes width issues. */ +#extension-settings .displaytable > section > * { + display: block; +} + +.extension-settings-content { + border-bottom : 0px solid #eee; + margin-top: 3px; +} + +#extension-settings-list { + min-height: 0; + overflow-y: hidden; +} + +/* Get rid of the light-blue background on list item hover. */ +#extension-settings-list:not([disabled]) > :hover { + background-color: white; + border-color: #CDCDCD; +} + +.butter-bar { + background: #FFF299; + padding: 2px 5px; + border-radius: 3px; + white-space: normal; +} + +.search-suppress { + display: none; + height: 0; +} + +.extension-list-item-collaped { + height: auto; + margin-bottom: 16px; + -webkit-transition: padding 300ms, overflow 300ms, opacity 700ms; +} + +.extension-list-item-expanded { + height: auto; + margin-bottom: 16px; + overflow: visible; + -webkit-transition: padding 300ms, overflow 300ms, opacity 700ms; +} + +.extension-settings { + overflow-x: hidden; +} + +.extension-icon { + height: 48px; + vertical-align: text-top; + width: 48px; + -webkit-padding-start: 15px; + -webkit-padding-end: 15px; + -webkit-user-select: none; +} + +.extension-title { + font-size: 16px; + font-weight: 500; + -webkit-padding-end: 20px; +} + +.extension-version { + font-size: 13px; + font-weight: 400; + -webkit-padding-end: 7px; +} + +.extension-description { + font-size: 13px; + white-space: normal; + -webkit-padding-end: 5px; +} + +.extension-checkbox-span { + -webkit-margin-start: 7px; +} + +.extension-checkbox-label { + -webkit-margin-end: 10px; +} + +.extension-delete { + -webkit-margin-start: 5px; +} + +.extension-details-hidden { + opacity: 0; + max-height: 0; + -webkit-transition: max-height 400ms, opacity 200ms; +} + +.extension-details-visible { + opacity: 1; + max-height: 1000px; + -webkit-transition: max-height 200ms, opacity 300ms; +} + +.extension-links-view { + -webkit-padding-start: 15px; +} + +.extension-links-trailing { + -webkit-padding-end: 7px; +} + +.extension-zippy-container { + cursor: pointer; + width: 20px; + -webkit-user-select: none; +} + +.extension-warnings-title { + color: red; +} + +.extension-warnings { + margin-top: 6px; +} + +.extension-warnings ul { + margin: 0; +} + +.extension-warnings > * { + white-space: normal; +} + +.informative-text { + color: gray; +} + +.extension-zippy-default { + background-image: url('zippy.png'); + background-repeat: no-repeat; + background-position: center top; + position: absolute; + left: 12px; + top: 25px; + width: 6px; + height: 16px; + opacity: .25; +} + +.extension-zippy-collapsed { + -webkit-transition: -webkit-transform 100ms; + -webkit-transform: rotate(0deg); +} + +.extension-zippy-collapsed:hover { + opacity: .5; + -webkit-transform: rotate(5deg); + -webkit-transition: -webkit-transform 100ms, opacity 100ms; +} + +.extension-zippy-expanded { + -webkit-transition: -webkit-transform 100ms; + -webkit-transform: rotate(90deg); +} + +.extension-zippy-expanded:hover { + -webkit-transition: -webkit-transform 100ms; + -webkit-transform: rotate(85deg); +} + +.extension-enabling { + position: relative; + top: 3px; +} + +.extension-enabling-label { + color: black; + -webkit-padding-start: 3px; + -webkit-padding-end: 9px; +} + +.extension-enabling-label-bold { + font-weight: bold; +} + +.extension-inspect-table { + padding: 0; + border-spacing: 0; +} + +.extension-inspect-left-column { + vertical-align: text-top; +} + +/* Dev */ + +.dev-open { + border-bottom: 1px solid rgb(205, 205, 205); + height: 32px; + padding-bottom: 7px; + padding-top: 13px; + -webkit-padding-start: 4px; + -webkit-padding-end: 3px; + -webkit-transition: padding 300ms, height 300ms, opacity 700ms; +} +.dev-closed { + height: 0; + opacity: 0; + padding-top: 9px; + -webkit-padding-start: 4px; + -webkit-padding-end: 3px; + -webkit-transition: padding 300ms, height 700ms, opacity 200ms; +} + +.dev-button-visible { + display: inherit; + opacity: 1; + -webkit-transition: opacity 200ms; +} + +.dev-button-hidden { + display: none; +} + +#suggest-gallery { + -webkit-padding-start: 10px; +} + +#dev-toggle { + display: block; + text-align: end; + margin-top: -28px; + -webkit-margin-end: 8px; +} + +#get-more-extensions-container { + display: -webkit-box; +} + +#get-more-extensions { + padding-top: 5px; + font-size: 15px; + -webkit-padding-start: 10px; +} + +/* Support full keyboard accessibility without making things ugly + for users who click, by hiding some focus outlines when the user + clicks anywhere, but showing them when the user presses any key. */ +body.hide-some-focus-outlines .extension-zippy-container { + outline: none; +} diff --git a/chrome/browser/resources/options2/extension_settings.html b/chrome/browser/resources/options2/extension_settings.html new file mode 100644 index 0000000..2858316 --- /dev/null +++ b/chrome/browser/resources/options2/extension_settings.html @@ -0,0 +1,40 @@ +<div id="extension-settings" class="page" hidden> + <h1 i18n-content="extensionSettings"></h1> + <div id="dev-toggle"> + <input id="toggle-dev-on" type="checkbox" value="off"></input> + <label for="toggle-dev-on" i18n-content="extensionSettingsDeveloperMode" /> + </div> + <div class="displaytable"> + <div id="dev" class="dev-closed"> + <table id="dev-table" width="100%"> + <tr> + <td> + <button id="load-unpacked" + i18n-content="extensionSettingsLoadUnpackedButton"></button> + <button id="pack-extension" + i18n-content="extensionSettingsPackButton"></button> + </td> + <td align="right"> + <button id="update-extensions-now" + i18n-content="extensionSettingsUpdateButton"></button> + </td> + </tr> + </table> + </div> + <section class="extension-settings-content"> + <div class="extension-settings"> + <list id="extension-settings-list"></list> + </div> + </section> + </div> + <section> + <div><strong id="no-extensions" + i18n-content="extensionSettingsNoExtensions" + hidden="true"></strong></div> + <div id="suggest-gallery" hidden="true"></div> + <div id="get-more-extensions-container" hidden="true"> + <img src="chrome://theme/IDR_WEBSTORE_ICON_32"> + <div id="get-more-extensions"></div> + </div> + </section> +</div> diff --git a/chrome/browser/resources/options2/extension_settings.js b/chrome/browser/resources/options2/extension_settings.js new file mode 100644 index 0000000..e88ba85 --- /dev/null +++ b/chrome/browser/resources/options2/extension_settings.js @@ -0,0 +1,184 @@ +// Copyright (c) 2011 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. + +// Used for observing function of the backend datasource for this page by +// tests. +var webui_responded_ = false; + +cr.define('options', function() { + var OptionsPage = options.OptionsPage; + var ExtensionsList = options.ExtensionsList; + + /** + * ExtensionSettings class + * Encapsulated handling of the 'Manage Extensions' page. + * @class + */ + function ExtensionSettings() { + OptionsPage.call(this, 'extensions', + templateData.extensionSettingsTabTitle, + 'extension-settings'); + } + + cr.addSingletonGetter(ExtensionSettings); + + ExtensionSettings.prototype = { + __proto__: OptionsPage.prototype, + + /** + * Initialize the page. + */ + initializePage: function() { + OptionsPage.prototype.initializePage.call(this); + + // This will request the data to show on the page and will get a response + // back in returnExtensionsData. + chrome.send('extensionSettingsRequestExtensionsData'); + + // Set up the developer mode button. + var toggleDevMode = $('toggle-dev-on'); + toggleDevMode.addEventListener('click', + this.handleToggleDevMode_.bind(this)); + + // Setup the gallery related links and text. + $('suggest-gallery').innerHTML = + localStrings.getString('extensionSettingsSuggestGallery'); + $('get-more-extensions').innerHTML = + localStrings.getString('extensionSettingsGetMoreExtensions'); + + // Set up the three dev mode buttons (load unpacked, pack and update). + $('load-unpacked').addEventListener('click', + this.handleLoadUnpackedExtension_.bind(this)); + $('pack-extension').addEventListener('click', + this.handlePackExtension_.bind(this)); + $('update-extensions-now').addEventListener('click', + this.handleUpdateExtensionNow_.bind(this)); + }, + + /** + * Utility function which asks the C++ to show a platform-specific file + * select dialog, and fire |callback| with the |filePath| that resulted. + * |selectType| can be either 'file' or 'folder'. |operation| can be 'load', + * 'packRoot', or 'pem' which are signals to the C++ to do some + * operation-specific configuration. + * @private + */ + showFileDialog_: function(selectType, operation, callback) { + handleFilePathSelected = function(filePath) { + callback(filePath); + handleFilePathSelected = function() {}; + }; + + chrome.send('extensionSettingsSelectFilePath', [selectType, operation]); + }, + + /** + * Handles the Load Unpacked Extension button. + * @param {Event} e Change event. + * @private + */ + handleLoadUnpackedExtension_: function(e) { + this.showFileDialog_('folder', 'load', function(filePath) { + chrome.send('extensionSettingsLoad', [String(filePath)]); + }); + + chrome.send('coreOptionsUserMetricsAction', + ['Options_LoadUnpackedExtension']); + }, + + /** + * Handles the Pack Extension button. + * @param {Event} e Change event. + * @private + */ + handlePackExtension_: function(e) { + OptionsPage.navigateToPage('packExtensionOverlay'); + chrome.send('coreOptionsUserMetricsAction', ['Options_PackExtension']); + }, + + /** + * Handles the Update Extension Now button. + * @param {Event} e Change event. + * @private + */ + handleUpdateExtensionNow_: function(e) { + chrome.send('extensionSettingsAutoupdate', []); + }, + + /** + * Handles the Toggle Dev Mode button. + * @param {Event} e Change event. + * @private + */ + handleToggleDevMode_: function(e) { + var dev = $('dev'); + if (!dev.classList.contains('dev-open')) { + // Make the Dev section visible. + dev.classList.add('dev-open'); + dev.classList.remove('dev-closed'); + + $('load-unpacked').classList.add('dev-button-visible'); + $('load-unpacked').classList.remove('dev-button-hidden'); + $('pack-extension').classList.add('dev-button-visible'); + $('pack-extension').classList.remove('dev-button-hidden'); + $('update-extensions-now').classList.add('dev-button-visible'); + $('update-extensions-now').classList.remove('dev-button-hidden'); + } else { + // Hide the Dev section. + dev.classList.add('dev-closed'); + dev.classList.remove('dev-open'); + + $('load-unpacked').classList.add('dev-button-hidden'); + $('load-unpacked').classList.remove('dev-button-visible'); + $('pack-extension').classList.add('dev-button-hidden'); + $('pack-extension').classList.remove('dev-button-visible'); + $('update-extensions-now').classList.add('dev-button-hidden'); + $('update-extensions-now').classList.remove('dev-button-visible'); + } + + chrome.send('extensionSettingsToggleDeveloperMode', []); + }, + }; + + /** + * Called by the dom_ui_ to re-populate the page with data representing + * the current state of installed extensions. + */ + ExtensionSettings.returnExtensionsData = function(extensionsData) { + webui_responded_ = true; + + $('no-extensions').hidden = true; + $('suggest-gallery').hidden = true; + $('get-more-extensions-container').hidden = true; + + if (extensionsData.extensions.length > 0) { + // Enforce order specified in the data or (if equal) then sort by + // extension name (case-insensitive). + extensionsData.extensions.sort(function(a, b) { + if (a.order == b.order) { + a = a.name.toLowerCase(); + b = b.name.toLowerCase(); + return a < b ? -1 : (a > b ? 1 : 0); + } else { + return a.order < b.order ? -1 : 1; + } + }); + + $('get-more-extensions-container').hidden = false; + } else { + $('no-extensions').hidden = false; + $('suggest-gallery').hidden = false; + } + + ExtensionsList.prototype.data_ = extensionsData; + + var extensionList = $('extension-settings-list'); + ExtensionsList.decorate(extensionList); + } + + // Export + return { + ExtensionSettings: ExtensionSettings + }; +}); diff --git a/chrome/browser/resources/options2/font_settings.css b/chrome/browser/resources/options2/font_settings.css new file mode 100644 index 0000000..6d90dbb --- /dev/null +++ b/chrome/browser/resources/options2/font_settings.css @@ -0,0 +1,41 @@ +#font-settings > section { + overflow: hidden; +} + +#font-settings input[type="range"] { + width: 100%; +} + +#minimum-font-sample { + height: 35px; + overflow: hidden; + width: 270px; +} + +.font-input-div { + -webkit-margin-end: 3em; + width: 12em; +} + +.font-input-div > div > select { + margin-bottom: 10px; +} + +.font-input { + width: 100%; +} + +.font-sample-div { + height: 70px; + overflow: hidden; + width: 270px; + direction: ltr; +} + +.font-settings-huge { + float: right; +} + +html[dir=rtl] .font-settings-huge { + float: left; +} diff --git a/chrome/browser/resources/options2/font_settings.html b/chrome/browser/resources/options2/font_settings.html new file mode 100644 index 0000000..c025230 --- /dev/null +++ b/chrome/browser/resources/options2/font_settings.html @@ -0,0 +1,82 @@ +<div id="font-settings" class="page" hidden> + <h1 i18n-content="fontSettingsPage"></h1> + <section> + <h3 i18n-content="fontSettingsStandard"></h3> + <div class="font-input-div"> + <div> + <select id="standard-font-family" class="font-input" data-type="string" + pref="webkit.webprefs.standard_font_family" + metric="Options_ChangeStandardFont"></select> + </div> + <div> + <input id="standard-font-size" type="range" min="0" max="24" + pref="webkit.webprefs.default_font_size"> + <div> + <span i18n-content="fontSettingsSizeTiny"></span> + <span i18n-content="fontSettingsSizeHuge" class="font-settings-huge"> + </span> + </div> + </div> + </div> + <div id="standard-font-sample" class="font-sample-div"></div> + </section> + <section> + <h3 i18n-content="fontSettingsSerif"></h3> + <div class="font-input-div"> + <div> + <select id="serif-font-family" class="font-input" data-type="string" + pref="webkit.webprefs.serif_font_family" + metric="Options_ChangeSerifFont"></select> + </div> + </div> + <div id="serif-font-sample" class="font-sample-div"></div> + </section> + <section> + <h3 i18n-content="fontSettingsSansSerif"></h3> + <div class="font-input-div"> + <div> + <select id="sans-serif-font-family" class="font-input" + data-type="string" + pref="webkit.webprefs.sansserif_font_family" + metric="Options_ChangeSansSerifFont"></select> + </div> + </div> + <div id="sans-serif-font-sample" class="font-sample-div"></div> + </section> + <section> + <h3 i18n-content="fontSettingsFixedWidth"></h3> + <div class="font-input-div"> + <div> + <select id="fixed-font-family" class="font-input" data-type="string" + pref="webkit.webprefs.fixed_font_family" + metric="Options_ChangeFixedFont"></select> + </div> + </div> + <div id="fixed-font-sample" class="font-sample-div"></div> + </section> + <section> + <h3 i18n-content="fontSettingsMinimumSize"></h3> + <div class="font-input-div"> + <div> + <input id="minimum-font-size" type="range" min="0" max="15" + pref="webkit.webprefs.minimum_font_size"> + <div> + <span i18n-content="fontSettingsSizeTiny"></span> + <span i18n-content="fontSettingsSizeHuge" class="font-settings-huge"> + </span> + </div> + </div> + </div> + <div id="minimum-font-sample" class="font-sample-div"></div> + </section> + <section> + <h3 i18n-content="fontSettingsEncoding"></h3> + <div class="font-input-div"> + <div> + <select id="font-encoding" data-type="string" + pref="intl.charset_default" + metric="Options_ChangeFontEncoding"></select> + </div> + </div> + </section> +</div> diff --git a/chrome/browser/resources/options2/font_settings.js b/chrome/browser/resources/options2/font_settings.js new file mode 100644 index 0000000..c00f525 --- /dev/null +++ b/chrome/browser/resources/options2/font_settings.js @@ -0,0 +1,234 @@ +// Copyright (c) 2011 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; + + /** + * This is the absolute difference maintained between standard and + * fixed-width font sizes. Refer http://crbug.com/91922. + */ + const SIZE_DIFFERENCE_FIXED_STANDARD = 3; + + /** + * FontSettings class + * Encapsulated handling of the 'Fonts and Encoding' page. + * @class + */ + function FontSettings() { + OptionsPage.call(this, + 'fonts', + templateData.fontSettingsPageTabTitle, + 'font-settings'); + } + + cr.addSingletonGetter(FontSettings); + + FontSettings.prototype = { + __proto__: OptionsPage.prototype, + + /** + * Initialize the page. + */ + initializePage: function() { + OptionsPage.prototype.initializePage.call(this); + + var standardFontRange = $('standard-font-size'); + standardFontRange.valueMap = [9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 20, + 22, 24, 26, 28, 30, 32, 34, 36, 40, 44, 48, 56, 64, 72]; + standardFontRange.continuous = false; + standardFontRange.notifyChange = this.standardRangeChanged_.bind(this); + standardFontRange.notifyPrefChange = + this.standardFontSizeChanged_.bind(this); + + var minimumFontRange = $('minimum-font-size'); + minimumFontRange.valueMap = [6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, + 18, 20, 22, 24]; + minimumFontRange.continuous = false; + minimumFontRange.notifyChange = this.minimumRangeChanged_.bind(this); + minimumFontRange.notifyPrefChange = + this.minimumFontSizeChanged_.bind(this); + + var placeholder = localStrings.getString('fontSettingsPlaceholder'); + var elements = [$('standard-font-family'), $('serif-font-family'), + $('sans-serif-font-family'), $('fixed-font-family'), + $('font-encoding')]; + elements.forEach(function(el) { + el.appendChild(new Option(placeholder)); + el.setDisabled('noFontsAvailable', true); + }); + }, + + /** + * Called by the options page when this page has been shown. + */ + didShowPage: function() { + // The fonts list may be large so we only load it when this page is + // loaded for the first time. This makes opening the options window + // faster and consume less memory if the user never opens the fonts + // dialog. + if (!this.hasShown) { + chrome.send('fetchFontsData'); + this.hasShown = true; + } + }, + + /** + * Called as the user changes the standard font size. This allows for + * reflecting the change in the UI before the preference has been changed. + * @param {Element} el The slider input element. + * @param {number} value The mapped value currently set by the slider. + * @private + */ + standardRangeChanged_: function(el, value) { + var fontSampleEl = $('standard-font-sample'); + this.setUpFontSample_(fontSampleEl, value, fontSampleEl.style.fontFamily, + true); + + fontSampleEl = $('serif-font-sample'); + this.setUpFontSample_(fontSampleEl, value, fontSampleEl.style.fontFamily, + true); + + fontSampleEl = $('sans-serif-font-sample'); + this.setUpFontSample_(fontSampleEl, value, fontSampleEl.style.fontFamily, + true); + + fontSampleEl = $('fixed-font-sample'); + this.setUpFontSample_(fontSampleEl, + value - SIZE_DIFFERENCE_FIXED_STANDARD, + fontSampleEl.style.fontFamily, false); + }, + + /** + * Sets the 'default_fixed_font_size' preference when the standard font + * size has been changed by the user. + * @param {Element} el The slider input element. + * @param {number} value The mapped value that has been saved. + * @private + */ + standardFontSizeChanged_: function(el, value) { + Preferences.setIntegerPref('webkit.webprefs.default_fixed_font_size', + value - SIZE_DIFFERENCE_FIXED_STANDARD, ''); + }, + + /** + * Called as the user changes the miniumum font size. This allows for + * reflecting the change in the UI before the preference has been changed. + * @param {Element} el The slider input element. + * @param {number} value The mapped value currently set by the slider. + * @private + */ + minimumRangeChanged_: function(el, value) { + var fontSampleEl = $('minimum-font-sample'); + this.setUpFontSample_(fontSampleEl, value, fontSampleEl.style.fontFamily, + true); + }, + + /** + * Sets the 'minimum_logical_font_size' preference when the minimum font + * size has been changed by the user. + * @param {Element} el The slider input element. + * @param {number} value The mapped value that has been saved. + * @private + */ + minimumFontSizeChanged_: function(el, value) { + Preferences.setIntegerPref('webkit.webprefs.minimum_logical_font_size', + value, ''); + }, + + /** + * Sets the text, font size and font family of the sample text. + * @param {Element} el The div containing the sample text. + * @param {number} size The font size of the sample text. + * @param {string} font The font family of the sample text. + * @param {bool} showSize True if the font size should appear in the sample. + * @private + */ + setUpFontSample_: function(el, size, font, showSize) { + var prefix = showSize ? (size + ': ') : ''; + el.textContent = prefix + + localStrings.getString('fontSettingsLoremIpsum'); + el.style.fontSize = size + 'px'; + if (font) + el.style.fontFamily = font; + }, + + /** + * Populates a select list and selects the specified item. + * @param {Element} element The select element to populate. + * @param {Array} items The array of items from which to populate. + * @param {string} selectedValue The selected item. + * @private + */ + populateSelect_: function(element, items, selectedValue) { + // Remove any existing content. + element.textContent = ''; + + // Insert new child nodes into select element. + var value, text, selected, option; + for (var i = 0; i < items.length; i++) { + value = items[i][0]; + text = items[i][1]; + if (text) { + selected = value == selectedValue; + element.appendChild(new Option(text, value, false, selected)); + } else { + element.appendChild(document.createElement('hr')); + } + } + + element.setDisabled('noFontsAvailable', false); + } + }; + + // Chrome callbacks + FontSettings.setFontsData = function(fonts, encodings, selectedValues) { + FontSettings.getInstance().populateSelect_($('standard-font-family'), fonts, + selectedValues[0]); + FontSettings.getInstance().populateSelect_($('serif-font-family'), fonts, + selectedValues[1]); + FontSettings.getInstance().populateSelect_($('sans-serif-font-family'), + fonts, selectedValues[2]); + FontSettings.getInstance().populateSelect_($('fixed-font-family'), fonts, + selectedValues[3]); + FontSettings.getInstance().populateSelect_($('font-encoding'), encodings, + selectedValues[4]); + }; + + FontSettings.setUpStandardFontSample = function(font, size) { + FontSettings.getInstance().setUpFontSample_($('standard-font-sample'), size, + font, true); + }; + + FontSettings.setUpSerifFontSample = function(font, size) { + FontSettings.getInstance().setUpFontSample_($('serif-font-sample'), size, + font, true); + }; + + FontSettings.setUpSansSerifFontSample = function(font, size) { + FontSettings.getInstance().setUpFontSample_($('sans-serif-font-sample'), + size, font, true); + }; + + FontSettings.setUpFixedFontSample = function(font, size) { + FontSettings.getInstance().setUpFontSample_($('fixed-font-sample'), + size, font, false); + }; + + FontSettings.setUpMinimumFontSample = function(size) { + // If size is less than 6, represent it as six in the sample to account + // for the minimum logical font size. + if (size < 6) + size = 6; + FontSettings.getInstance().setUpFontSample_($('minimum-font-sample'), size, + null, true); + }; + + // Export + return { + FontSettings: FontSettings + }; +}); + diff --git a/chrome/browser/resources/options2/handler_options.css b/chrome/browser/resources/options2/handler_options.css new file mode 100644 index 0000000..0f23446 --- /dev/null +++ b/chrome/browser/resources/options2/handler_options.css @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2011 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. + */ + +.handlers-column-headers { + display: -webkit-box; + font-size: 13px; + font-weight: bold; +} + +.handlers-type-column { + width: 100px; + -webkit-margin-end: 10px; + -webkit-margin-start: 14px; +} + +.handlers-site-column { + max-width: 180px; +} + +.handlers-site-column select { + max-width: 170px; +} + +.handlers-remove-column { + -webkit-box-flex: 1; +} + +.handlers-remove-link { + color: #555; + cursor: pointer; + opacity: 0; + padding-left: 14px; + text-decoration: underline; + -webkit-transition: 150ms opacity; +} + +div > .handlers-remove-column { + opacity: 0; +} + +div:not(.none):hover > .handlers-remove-column { + opacity: 1; +} + +#handlers { + min-height: 250px; +} + +#handler-options list { + border-radius: 2px; + border: solid 1px #D9D9D9; + margin-bottom: 10px; + margin-top: 4px; +} diff --git a/chrome/browser/resources/options2/handler_options.html b/chrome/browser/resources/options2/handler_options.html new file mode 100644 index 0000000..21284ca --- /dev/null +++ b/chrome/browser/resources/options2/handler_options.html @@ -0,0 +1,28 @@ +<div id="handler-options" class="page" hidden> + <h1 i18n-content="handlersPage"></h1> + <h3 i18n-content="handlers_active_heading"></h3> + <div class="handlers-column-headers"> + <div class="handlers-type-column"> + <div i18n-content="handlers_type_column_header"></div> + </div> + <div class="handlers-site-column"> + <div i18n-content="handlers_site_column_header"></div> + </div> + <div class="handlers-remove-column"></div> + </div> + <list id="handlers-list"></list> + + <div id="ignored-handlers-section"> + <h3 i18n-content="handlers_ignored_heading"></h3> + <div class="handlers-column-headers"> + <div class="handlers-type-column"> + <h3 i18n-content="handlers_type_column_header"></h3> + </div> + <div class="handlers-site-column"> + <h3 i18n-content="handlers_site_column_header"></h3> + </div> + <div class="handlers-remove-column"></div> + </div> + <list id="ignored-handlers-list"></list> + </div> +</div> diff --git a/chrome/browser/resources/options2/handler_options.js b/chrome/browser/resources/options2/handler_options.js new file mode 100644 index 0000000..039d9eb --- /dev/null +++ b/chrome/browser/resources/options2/handler_options.js @@ -0,0 +1,77 @@ +// Copyright (c) 2011 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() { + const OptionsPage = options.OptionsPage; + + ///////////////////////////////////////////////////////////////////////////// + // HandlerOptions class: + + /** + * Encapsulated handling of handler options page. + * @constructor + */ + function HandlerOptions() { + this.activeNavTab = null; + OptionsPage.call(this, + 'handlers', + templateData.handlersPageTabTitle, + 'handler-options'); + } + + cr.addSingletonGetter(HandlerOptions); + + HandlerOptions.prototype = { + __proto__: OptionsPage.prototype, + + /** + * The handlers list. + * @type {DeletableItemList} + * @private + */ + handlersList_: null, + + /** @inheritDoc */ + initializePage: function() { + OptionsPage.prototype.initializePage.call(this); + + this.createHandlersList_(); + }, + + /** + * Creates, decorates and initializes the handlers list. + * @private + */ + createHandlersList_: function() { + this.handlersList_ = $('handlers-list'); + options.HandlersList.decorate(this.handlersList_); + this.handlersList_.autoExpands = true; + + this.ignoredHandlersList_ = $('ignored-handlers-list'); + options.IgnoredHandlersList.decorate(this.ignoredHandlersList_); + this.ignoredHandlersList_.autoExpands = true; + }, + }; + + /** + * Sets the list of handlers shown by the view. + * @param handlers to be shown in the view. + */ + HandlerOptions.setHandlers = function(handlers) { + $('handlers-list').setHandlers(handlers); + }; + + /** + * Sets the list of ignored handlers shown by the view. + * @param handlers to be shown in the view. + */ + HandlerOptions.setIgnoredHandlers = function(handlers) { + $('ignored-handlers-section').hidden = handlers.length == 0; + $('ignored-handlers-list').setHandlers(handlers); + }; + + return { + HandlerOptions: HandlerOptions + }; +}); diff --git a/chrome/browser/resources/options2/handler_options_list.js b/chrome/browser/resources/options2/handler_options_list.js new file mode 100644 index 0000000..661956c --- /dev/null +++ b/chrome/browser/resources/options2/handler_options_list.js @@ -0,0 +1,229 @@ +// Copyright (c) 2011 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() { + const ArrayDataModel = cr.ui.ArrayDataModel; + const List = cr.ui.List; + const ListItem = cr.ui.ListItem; + const HandlerOptions = options.HandlerOptions; + const DeletableItem = options.DeletableItem; + const DeletableItemList = options.DeletableItemList; + + const localStrings = new LocalStrings(); + + /** + * Creates a new ignored protocol / content handler list item. + * + * Accepts values in the form + * ['mailto', 'http://www.thesite.com/%s', 'The title of the protocol'], + * @param {Object} entry A dictionary describing the handlers for a given + * protocol. + * @constructor + * @extends {cr.ui.DeletableItemList} + */ + function IgnoredHandlersListItem(entry) { + var el = cr.doc.createElement('div'); + el.dataItem = entry; + el.__proto__ = IgnoredHandlersListItem.prototype; + el.decorate(); + return el; + } + + IgnoredHandlersListItem.prototype = { + __proto__: DeletableItem.prototype, + + /** @inheritDoc */ + decorate: function() { + DeletableItem.prototype.decorate.call(this); + + // Protocol. + var protocolElement = document.createElement('div'); + protocolElement.textContent = this.dataItem[0]; + protocolElement.className = 'handlers-type-column'; + this.contentElement_.appendChild(protocolElement); + + // Site title. + var titleElement = document.createElement('div'); + titleElement.textContent = this.dataItem[2]; + titleElement.className = 'handlers-site-column'; + titleElement.title = this.dataItem[1]; + this.contentElement_.appendChild(titleElement); + }, + }; + + + var IgnoredHandlersList = cr.ui.define('list'); + + IgnoredHandlersList.prototype = { + __proto__: DeletableItemList.prototype, + + createItem: function(entry) { + return new IgnoredHandlersListItem(entry); + }, + + deleteItemAtIndex: function(index) { + chrome.send('removeIgnoredHandler', [this.dataModel.item(index)]); + }, + + /** + * The length of the list. + */ + get length() { + return this.dataModel.length; + }, + + /** + * Set the protocol handlers displayed by this list. See + * IgnoredHandlersListItem for an example of the format the list should + * take. + * + * @param {Object} list A list of ignored protocol handlers. + */ + setHandlers: function(list) { + this.dataModel = new ArrayDataModel(list); + }, + }; + + + + /** + * Creates a new protocol / content handler list item. + * + * Accepts values in the form + * { protocol: 'mailto', + * handlers: [ + * ['mailto', 'http://www.thesite.com/%s', 'The title of the protocol'], + * ..., + * ], + * } + * @param {Object} entry A dictionary describing the handlers for a given + * protocol. + * @constructor + * @extends {cr.ui.ListItem} + */ + function HandlerListItem(entry) { + var el = cr.doc.createElement('div'); + el.dataItem = entry; + el.__proto__ = HandlerListItem.prototype; + el.decorate(); + return el; + } + + HandlerListItem.prototype = { + __proto__: ListItem.prototype, + + buildWidget_: function(data, delegate) { + // Protocol. + var protocolElement = document.createElement('div'); + protocolElement.textContent = data.protocol; + protocolElement.className = 'handlers-type-column'; + this.appendChild(protocolElement); + + // Handler selection. + var handlerElement = document.createElement('div'); + var selectElement = document.createElement('select'); + var defaultOptionElement = document.createElement('option'); + defaultOptionElement.selected = data.default_handler == -1; + defaultOptionElement.textContent = + localStrings.getString('handlers_none_handler'); + defaultOptionElement.value = -1; + selectElement.appendChild(defaultOptionElement); + + for (var i = 0; i < data.handlers.length; ++i) { + var optionElement = document.createElement('option'); + optionElement.selected = i == data.default_handler; + optionElement.textContent = data.handlers[i][2]; + optionElement.value = i; + selectElement.appendChild(optionElement); + } + + selectElement.addEventListener('change', function (e) { + var index = e.target.value; + if (index == -1) { + this.classList.add('none'); + delegate.clearDefault(data.protocol); + } else { + handlerElement.classList.remove('none'); + delegate.setDefault(data.handlers[index]); + } + }); + handlerElement.appendChild(selectElement); + handlerElement.className = 'handlers-site-column'; + if (data.default_handler == -1) + this.classList.add('none'); + this.appendChild(handlerElement); + + // Remove link. + var removeElement = document.createElement('div'); + removeElement.textContent = + localStrings.getString('handlers_remove_link'); + removeElement.addEventListener('click', function (e) { + var value = selectElement ? selectElement.value : 0; + delegate.removeHandler(value, data.handlers[value]); + }); + removeElement.className = 'handlers-remove-column handlers-remove-link'; + this.appendChild(removeElement); + }, + + /** @inheritDoc */ + decorate: function() { + ListItem.prototype.decorate.call(this); + + var self = this; + var delegate = { + removeHandler: function(index, handler) { + chrome.send('removeHandler', [handler]); + }, + setDefault: function(handler) { + chrome.send('setDefault', [handler]); + }, + clearDefault: function(protocol) { + chrome.send('clearDefault', [protocol]); + }, + }; + + this.buildWidget_(this.dataItem, delegate); + }, + }; + + /** + * Create a new passwords list. + * @constructor + * @extends {cr.ui.List} + */ + var HandlersList = cr.ui.define('list'); + + HandlersList.prototype = { + __proto__: List.prototype, + + /** @inheritDoc */ + createItem: function(entry) { + return new HandlerListItem(entry); + }, + + /** + * The length of the list. + */ + get length() { + return this.dataModel.length; + }, + + /** + * Set the protocol handlers displayed by this list. + * See HandlerListItem for an example of the format the list should take. + * + * @param {Object} list A list of protocols with their registered handlers. + */ + setHandlers: function(list) { + this.dataModel = new ArrayDataModel(list); + }, + }; + + return { + IgnoredHandlersListItem: IgnoredHandlersListItem, + IgnoredHandlersList: IgnoredHandlersList, + HandlerListItem: HandlerListItem, + HandlersList: HandlersList, + }; +}); diff --git a/chrome/browser/resources/options2/import_data_overlay.css b/chrome/browser/resources/options2/import_data_overlay.css new file mode 100644 index 0000000..9db63f8 --- /dev/null +++ b/chrome/browser/resources/options2/import_data_overlay.css @@ -0,0 +1,23 @@ +#import-from-div { + margin-bottom: 20px; +} + +#import-checkboxes > div:not(:first-child) { + -webkit-padding-start: 8px; + margin: 5px 0; +} + +#import-throbber { + margin: 4px 10px; + vertical-align: middle; + visibility: hidden; +} + +#import-success-header { + font-size: 1.2em; +} + +#import-success-image { + text-align: center; + margin: 20px; +} diff --git a/chrome/browser/resources/options2/import_data_overlay.html b/chrome/browser/resources/options2/import_data_overlay.html new file mode 100644 index 0000000..355eced --- /dev/null +++ b/chrome/browser/resources/options2/import_data_overlay.html @@ -0,0 +1,70 @@ +<div id="import-data-overlay" class="page" hidden> + <h1 i18n-content="importDataOverlay"></h1> + <div id="import-data-configure"> + <div class="content-area"> + <div id="import-from-div"> + <span i18n-content="importFromLabel"></span> + <select id="import-browsers"> + <option i18n-content="importLoading"></option> + </select> + </div> + <div id="import-checkboxes"> + <div i18n-content="importDescription"></div> + <div> + <input id="import-history" type="checkbox" pref="import_history"> + <label for="import-history" i18n-content="importHistory"></label> + </div> + <div> + <input id="import-favorites" type="checkbox" pref="import_bookmarks"> + <label for="import-favorites" i18n-content="importFavorites"></label> + </div> + <div> + <input id="import-passwords" type="checkbox" + pref="import_saved_passwords"> + <label for="import-passwords" i18n-content="importPasswords"></label> + </div> + <div> + <input id="import-search" type="checkbox" pref="import_search_engine"> + <label for="import-search" i18n-content="importSearch"></label> + </div> + </div> + </div> + <div class="action-area"> + <div class="action-area-right"> + <div id="import-throbber" class="throbber"></div> + <div class="button-strip"> + <button id="import-data-cancel" i18n-content="cancel"></button> + <button id="import-data-commit" i18n-content="importCommit"></button> + </div> + </div> + </div> + </div> + <div id="import-data-success" hidden> + <div class="content-area"> + <div id="import-success-header"> + <strong i18n-content="importSucceeded"></strong> + </div> + <div id="import-success-image"> + <img src="success-large.png" /> + </div> + <div id="import-find-your-bookmarks"> + <span i18n-content="findYourImportedBookmarks"></span> + <div class="checkbox"> + <label> + <input id="import-data-show-bookmarks-bar" + pref="bookmark_bar.show_on_all_tabs" + metric="Options_ShowBookmarksBar" type="checkbox"> + <span i18n-content="toolbarShowBookmarksBar"></span> + </label> + </div> + </div> + </div> + <div class="action-area"> + <div class="action-area-right"> + <div class="button-strip"> + <button id="import-data-confirm" i18n-content="ok"></button> + </div> + </div> + </div> + </div> +</div> diff --git a/chrome/browser/resources/options2/import_data_overlay.js b/chrome/browser/resources/options2/import_data_overlay.js new file mode 100644 index 0000000..2671aad --- /dev/null +++ b/chrome/browser/resources/options2/import_data_overlay.js @@ -0,0 +1,222 @@ +// Copyright (c) 2011 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; + + /** + * ImportDataOverlay class + * Encapsulated handling of the 'Import Data' overlay page. + * @class + */ + function ImportDataOverlay() { + OptionsPage.call(this, + 'importData', + templateData.importDataOverlayTabTitle, + 'import-data-overlay'); + } + + cr.addSingletonGetter(ImportDataOverlay); + + ImportDataOverlay.prototype = { + // Inherit from OptionsPage. + __proto__: OptionsPage.prototype, + + /** + * Initialize the page. + */ + initializePage: function() { + // Call base class implementation to start preference initialization. + OptionsPage.prototype.initializePage.call(this); + + var self = this; + var checkboxes = + document.querySelectorAll('#import-checkboxes input[type=checkbox]'); + for (var i = 0; i < checkboxes.length; i++) { + checkboxes[i].onchange = function() { + self.validateCommitButton_(); + }; + } + + $('import-browsers').onchange = function() { + self.updateCheckboxes_(); + self.validateCommitButton_(); + }; + + $('import-data-commit').onclick = function() { + chrome.send('importData', [ + String($('import-browsers').selectedIndex), + String($('import-history').checked), + String($('import-favorites').checked), + String($('import-passwords').checked), + String($('import-search').checked)]); + }; + + $('import-data-cancel').onclick = function() { + ImportDataOverlay.dismiss(); + }; + + $('import-data-show-bookmarks-bar').onchange = function() { + // Note: The callback 'toggleShowBookmarksBar' is handled within the + // browser options handler -- rather than the import data handler -- + // as the implementation is shared by several clients. + chrome.send('toggleShowBookmarksBar'); + } + + $('import-data-confirm').onclick = function() { + ImportDataOverlay.dismiss(); + }; + + // Form controls are disabled until the profile list has been loaded. + self.setControlsSensitive_(false); + }, + + /** + * Set enabled and checked state of the commit button. + * @private + */ + validateCommitButton_: function() { + var somethingToImport = + $('import-history').checked || $('import-favorites').checked || + $('import-passwords').checked || $('import-search').checked; + $('import-data-commit').disabled = !somethingToImport; + }, + + /** + * Sets the sensitivity of all the checkboxes and the commit button. + * @private + */ + setControlsSensitive_: function(sensitive) { + var checkboxes = + document.querySelectorAll('#import-checkboxes input[type=checkbox]'); + for (var i = 0; i < checkboxes.length; i++) + this.setUpCheckboxState_(checkboxes[i], sensitive); + $('import-data-commit').disabled = !sensitive; + }, + + /** + * Set enabled and checked states a checkbox element. + * @param {Object} checkbox A checkbox element. + * @param {boolean} enabled The enabled state of the chekbox. + * @private + */ + setUpCheckboxState_: function(checkbox, enabled) { + checkbox.setDisabled("noProfileData", !enabled); + }, + + /** + * Update the enabled and checked states of all checkboxes. + * @private + */ + updateCheckboxes_: function() { + var index = $('import-browsers').selectedIndex; + var browserProfile; + if (this.browserProfiles.length > index) + browserProfile = this.browserProfiles[index]; + var importOptions = ['history', 'favorites', 'passwords', 'search']; + for (var i = 0; i < importOptions.length; i++) { + var checkbox = $('import-' + importOptions[i]); + var enable = browserProfile && browserProfile[importOptions[i]]; + this.setUpCheckboxState_(checkbox, enable); + } + }, + + /** + * Update the supported browsers popup with given entries. + * @param {array} browsers List of supported browsers name. + * @private + */ + updateSupportedBrowsers_: function(browsers) { + this.browserProfiles = browsers; + var browserSelect = $('import-browsers'); + browserSelect.remove(0); // Remove the 'Loading...' option. + browserSelect.textContent = ''; + var browserCount = browsers.length; + + if (browserCount == 0) { + var option = new Option(templateData.noProfileFound, 0); + browserSelect.appendChild(option); + + this.setControlsSensitive_(false); + } else { + this.setControlsSensitive_(true); + for (var i = 0; i < browserCount; i++) { + var browser = browsers[i] + var option = new Option(browser['name'], browser['index']); + browserSelect.appendChild(option); + } + + this.updateCheckboxes_(); + this.validateCommitButton_(); + } + }, + + /** + * Clear import prefs set when user checks/unchecks a checkbox so that each + * checkbox goes back to the default "checked" state (or alternatively, to + * the state set by a recommended policy). + * @private + */ + clearUserPrefs_: function() { + var importPrefs = ['import_history', + 'import_bookmarks', + 'import_saved_passwords', + 'import_search_engine']; + for (var i = 0; i < importPrefs.length; i++) + Preferences.clearPref(importPrefs[i], undefined); + }, + }; + + ImportDataOverlay.clearUserPrefs = function() { + ImportDataOverlay.getInstance().clearUserPrefs_(); + }; + + /** + * Update the supported browsers popup with given entries. + * @param {array} list of supported browsers name. + */ + ImportDataOverlay.updateSupportedBrowsers = function(browsers) { + ImportDataOverlay.getInstance().updateSupportedBrowsers_(browsers); + }; + + /** + * Update the UI to reflect whether an import operation is in progress. + * @param {boolean} state True if an import operation is in progress. + */ + ImportDataOverlay.setImportingState = function(state) { + var checkboxes = + document.querySelectorAll('#import-checkboxes input[type=checkbox]'); + for (var i = 0; i < checkboxes.length; i++) + checkboxes[i].setDisabled("Importing", state); + if (!state) + ImportDataOverlay.getInstance().updateCheckboxes_(); + $('import-browsers').disabled = state; + $('import-throbber').style.visibility = state ? "visible" : "hidden"; + ImportDataOverlay.getInstance().validateCommitButton_(); + }; + + /** + * Remove the import overlay from display. + */ + ImportDataOverlay.dismiss = function() { + ImportDataOverlay.clearUserPrefs(); + OptionsPage.closeOverlay(); + }; + + /** + * Show a message confirming the success of the import operation. + */ + ImportDataOverlay.confirmSuccess = function() { + var showBookmarksMessage = $('import-favorites').checked; + ImportDataOverlay.setImportingState(false); + $('import-data-configure').hidden = true; + $('import-data-success').hidden = false; + $('import-find-your-bookmarks').hidden = !showBookmarksMessage; + }; + + // Export + return { + ImportDataOverlay: ImportDataOverlay + }; +}); diff --git a/chrome/browser/resources/options2/inline_editable_list.js b/chrome/browser/resources/options2/inline_editable_list.js new file mode 100644 index 0000000..8aed93b --- /dev/null +++ b/chrome/browser/resources/options2/inline_editable_list.js @@ -0,0 +1,414 @@ +// Copyright (c) 2011 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() { + const DeletableItem = options.DeletableItem; + const DeletableItemList = options.DeletableItemList; + + /** + * Creates a new list item with support for inline editing. + * @constructor + * @extends {options.DeletableListItem} + */ + function InlineEditableItem() { + var el = cr.doc.createElement('div'); + InlineEditableItem.decorate(el); + return el; + } + + /** + * Decorates an element as a inline-editable list item. Note that this is + * a subclass of DeletableItem. + * @param {!HTMLElement} el The element to decorate. + */ + InlineEditableItem.decorate = function(el) { + el.__proto__ = InlineEditableItem.prototype; + el.decorate(); + }; + + InlineEditableItem.prototype = { + __proto__: DeletableItem.prototype, + + /** + * Whether or not this item can be edited. + * @type {boolean} + * @private + */ + editable_: true, + + /** + * Whether or not this is a placeholder for adding a new item. + * @type {boolean} + * @private + */ + isPlaceholder_: false, + + /** + * Fields associated with edit mode. + * @type {array} + * @private + */ + editFields_: null, + + /** + * Whether or not the current edit should be considered cancelled, rather + * than committed, when editing ends. + * @type {boolean} + * @private + */ + editCancelled_: true, + + /** + * The editable item corresponding to the last click, if any. Used to decide + * initial focus when entering edit mode. + * @type {HTMLElement} + * @private + */ + editClickTarget_: null, + + /** @inheritDoc */ + decorate: function() { + DeletableItem.prototype.decorate.call(this); + + this.editFields_ = []; + this.addEventListener('mousedown', this.handleMouseDown_); + this.addEventListener('keydown', this.handleKeyDown_); + this.addEventListener('leadChange', this.handleLeadChange_); + }, + + /** @inheritDoc */ + selectionChanged: function() { + this.updateEditState(); + }, + + /** + * Called when this element gains or loses 'lead' status. Updates editing + * mode accordingly. + * @private + */ + handleLeadChange_: function() { + this.updateEditState(); + }, + + /** + * Updates the edit state based on the current selected and lead states. + */ + updateEditState: function() { + if (this.editable) + this.editing = this.selected && this.lead; + }, + + /** + * Whether the user is currently editing the list item. + * @type {boolean} + */ + get editing() { + return this.hasAttribute('editing'); + }, + set editing(editing) { + if (this.editing == editing) + return; + + if (editing) + this.setAttribute('editing', ''); + else + this.removeAttribute('editing'); + + if (editing) { + this.editCancelled_ = false; + + cr.dispatchSimpleEvent(this, 'edit', true); + + var focusElement = this.editClickTarget_ || this.initialFocusElement; + this.editClickTarget_ = null; + + // When this is called in response to the selectedChange event, + // the list grabs focus immediately afterwards. Thus we must delay + // our focus grab. + var self = this; + if (focusElement) { + window.setTimeout(function() { + // Make sure we are still in edit mode by the time we execute. + if (self.editing) { + focusElement.focus(); + focusElement.select(); + } + }, 50); + } + } else { + if (!this.editCancelled_ && this.hasBeenEdited && + this.currentInputIsValid) { + if (this.isPlaceholder) + this.parentNode.focusPlaceholder = true; + + this.updateStaticValues_(); + cr.dispatchSimpleEvent(this, 'commitedit', true); + } else { + this.resetEditableValues_(); + cr.dispatchSimpleEvent(this, 'canceledit', true); + } + } + }, + + /** + * Whether the item is editable. + * @type {boolean} + */ + get editable() { + return this.editable_; + }, + set editable(editable) { + this.editable_ = editable; + if (!editable) + this.editing = false; + }, + + /** + * Whether the item is a new item placeholder. + * @type {boolean} + */ + get isPlaceholder() { + return this.isPlaceholder_; + }, + set isPlaceholder(isPlaceholder) { + this.isPlaceholder_ = isPlaceholder; + if (isPlaceholder) + this.deletable = false; + }, + + /** + * The HTML element that should have focus initially when editing starts, + * if a specific element wasn't clicked. + * Defaults to the first <input> element; can be overriden by subclasses if + * a different element should be focused. + * @type {HTMLElement} + */ + get initialFocusElement() { + return this.contentElement.querySelector('input'); + }, + + /** + * Whether the input in currently valid to submit. If this returns false + * when editing would be submitted, either editing will not be ended, + * or it will be cancelled, depending on the context. + * Can be overrided by subclasses to perform input validation. + * @type {boolean} + */ + get currentInputIsValid() { + return true; + }, + + /** + * Returns true if the item has been changed by an edit. + * Can be overrided by subclasses to return false when nothing has changed + * to avoid unnecessary commits. + * @type {boolean} + */ + get hasBeenEdited() { + return true; + }, + + /** + * Returns a div containing an <input>, as well as static text if + * isPlaceholder is not true. + * @param {string} text The text of the cell. + * @return {HTMLElement} The HTML element for the cell. + * @private + */ + createEditableTextCell: function(text) { + var container = this.ownerDocument.createElement('div'); + + if (!this.isPlaceholder) { + var textEl = this.ownerDocument.createElement('div'); + textEl.className = 'static-text'; + textEl.textContent = text; + textEl.setAttribute('displaymode', 'static'); + container.appendChild(textEl); + } + + var inputEl = this.ownerDocument.createElement('input'); + inputEl.type = 'text'; + inputEl.value = text; + if (!this.isPlaceholder) { + inputEl.setAttribute('displaymode', 'edit'); + inputEl.staticVersion = textEl; + } else { + // At this point |this| is not attached to the parent list yet, so give + // a short timeout in order for the attachment to occur. + var self = this; + window.setTimeout(function() { + var list = self.parentNode; + if (list && list.focusPlaceholder) { + list.focusPlaceholder = false; + if (list.shouldFocusPlaceholder()) + inputEl.focus(); + } + }, 50); + } + + inputEl.addEventListener('focus', this.handleFocus_.bind(this)); + container.appendChild(inputEl); + this.editFields_.push(inputEl); + + return container; + }, + + /** + * Resets the editable version of any controls created by createEditable* + * to match the static text. + * @private + */ + resetEditableValues_: function() { + var editFields = this.editFields_; + for (var i = 0; i < editFields.length; i++) { + var staticLabel = editFields[i].staticVersion; + if (!staticLabel && !this.isPlaceholder) + continue; + + if (editFields[i].tagName == 'INPUT') { + editFields[i].value = + this.isPlaceholder ? '' : staticLabel.textContent; + } + // Add more tag types here as new createEditable* methods are added. + + editFields[i].setCustomValidity(''); + } + }, + + /** + * Sets the static version of any controls created by createEditable* + * to match the current value of the editable version. Called on commit so + * that there's no flicker of the old value before the model updates. + * @private + */ + updateStaticValues_: function() { + var editFields = this.editFields_; + for (var i = 0; i < editFields.length; i++) { + var staticLabel = editFields[i].staticVersion; + if (!staticLabel) + continue; + + if (editFields[i].tagName == 'INPUT') + staticLabel.textContent = editFields[i].value; + // Add more tag types here as new createEditable* methods are added. + } + }, + + /** + * Called a key is pressed. Handles committing and cancelling edits. + * @param {Event} e The key down event. + * @private + */ + handleKeyDown_: function(e) { + if (!this.editing) + return; + + var endEdit = false; + switch (e.keyIdentifier) { + case 'U+001B': // Esc + this.editCancelled_ = true; + endEdit = true; + break; + case 'Enter': + if (this.currentInputIsValid) + endEdit = true; + break; + } + + if (endEdit) { + // Blurring will trigger the edit to end; see InlineEditableItemList. + this.ownerDocument.activeElement.blur(); + // Make sure that handled keys aren't passed on and double-handled. + // (e.g., esc shouldn't both cancel an edit and close a subpage) + e.stopPropagation(); + } + }, + + /** + * Called when the list item is clicked. If the click target corresponds to + * an editable item, stores that item to focus when edit mode is started. + * @param {Event} e The mouse down event. + * @private + */ + handleMouseDown_: function(e) { + if (!this.editable || this.editing) + return; + + var clickTarget = e.target; + var editFields = this.editFields_; + for (var i = 0; i < editFields.length; i++) { + if (editFields[i] == clickTarget || + editFields[i].staticVersion == clickTarget) { + this.editClickTarget_ = editFields[i]; + return; + } + } + }, + }; + + /** + * Takes care of committing changes to inline editable list items when the + * window loses focus. + */ + function handleWindowBlurs() { + window.addEventListener('blur', function(e) { + var itemAncestor = findAncestor(document.activeElement, function(node) { + return node instanceof InlineEditableItem; + }); + if (itemAncestor); + document.activeElement.blur(); + }); + } + handleWindowBlurs(); + + var InlineEditableItemList = cr.ui.define('list'); + + InlineEditableItemList.prototype = { + __proto__: DeletableItemList.prototype, + + /** + * Focuses the input element of the placeholder if true. + * @type {boolean} + */ + focusPlaceholder: false, + + /** @inheritDoc */ + decorate: function() { + DeletableItemList.prototype.decorate.call(this); + this.setAttribute('inlineeditable', ''); + this.addEventListener('hasElementFocusChange', + this.handleListFocusChange_); + }, + + /** + * Called when the list hierarchy as a whole loses or gains focus; starts + * or ends editing for the lead item if necessary. + * @param {Event} e The change event. + * @private + */ + handleListFocusChange_: function(e) { + var leadItem = this.getListItemByIndex(this.selectionModel.leadIndex); + if (leadItem) { + if (e.newValue) + leadItem.updateEditState(); + else + leadItem.editing = false; + } + }, + + /** + * May be overridden by subclasses to disable focusing the placeholder. + * @return true if the placeholder element should be focused on edit commit. + */ + shouldFocusPlaceholder: function() { + return true; + }, + }; + + // Export + return { + InlineEditableItem: InlineEditableItem, + InlineEditableItemList: InlineEditableItemList, + }; +}); diff --git a/chrome/browser/resources/options2/instant_confirm_overlay.html b/chrome/browser/resources/options2/instant_confirm_overlay.html new file mode 100644 index 0000000..1e724ef --- /dev/null +++ b/chrome/browser/resources/options2/instant_confirm_overlay.html @@ -0,0 +1,16 @@ +<div id="instantConfirmOverlay" class="page" hidden> + <h1 i18n-content="instantConfirmTitle"></h1> + <!-- The text has line breaks, so we must use a pre. --> + <div class="content-area"> + <pre id="instantConfirmText" i18n-content="instantConfirmMessage"></pre> + <a id="instantConfirmLearnMore" target="_blank" i18n-content="learnMore" + i18n-values="href:instantLearnMoreLink"></a> + </div> + <div class="action-area"> + <div class="button-strip"> + <button id="instantConfirmCancel" i18n-content="cancel" + class="cancel-button"></button> + <button id="instantConfirmOk" i18n-content="ok"></button> + </div> + </div> +</div> diff --git a/chrome/browser/resources/options2/instant_confirm_overlay.js b/chrome/browser/resources/options2/instant_confirm_overlay.js new file mode 100644 index 0000000..01a9ee5 --- /dev/null +++ b/chrome/browser/resources/options2/instant_confirm_overlay.js @@ -0,0 +1,39 @@ +// Copyright (c) 2011 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; + + function InstantConfirmOverlay() { + OptionsPage.call(this, 'instantConfirm', + templateData.instantConfirmTitle, + 'instantConfirmOverlay'); + }; + + cr.addSingletonGetter(InstantConfirmOverlay); + + InstantConfirmOverlay.prototype = { + // Inherit from OptionsPage. + __proto__: OptionsPage.prototype, + + initializePage: function() { + OptionsPage.prototype.initializePage.call(this); + + $('instantConfirmCancel').onclick = function() { + OptionsPage.closeOverlay(); + $('instantEnabledCheckbox').checked = false; + }; + + $('instantConfirmOk').onclick = function() { + OptionsPage.closeOverlay(); + chrome.send('enableInstant'); + }; + }, + }; + + // Export + return { + InstantConfirmOverlay: InstantConfirmOverlay + }; +}); diff --git a/chrome/browser/resources/options2/intents_list.js b/chrome/browser/resources/options2/intents_list.js new file mode 100644 index 0000000..e2dc21e --- /dev/null +++ b/chrome/browser/resources/options2/intents_list.js @@ -0,0 +1,707 @@ +// Copyright (c) 2011 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. + +// TODO(gbillock): refactor this together with CookiesList once we have +// a better sense from UX design what it'll look like and so what'll be shared. +cr.define('options', function() { + const DeletableItemList = options.DeletableItemList; + const DeletableItem = options.DeletableItem; + const ArrayDataModel = cr.ui.ArrayDataModel; + const ListSingleSelectionModel = cr.ui.ListSingleSelectionModel; + const localStrings = new LocalStrings(); + + /** + * Returns the item's height, like offsetHeight but such that it works better + * when the page is zoomed. See the similar calculation in @{code cr.ui.List}. + * This version also accounts for the animation done in this file. + * @param {Element} item The item to get the height of. + * @return {number} The height of the item, calculated with zooming in mind. + */ + function getItemHeight(item) { + var height = item.style.height; + // Use the fixed animation target height if set, in case the element is + // currently being animated and we'd get an intermediate height below. + if (height && height.substr(-2) == 'px') + return parseInt(height.substr(0, height.length - 2)); + return item.getBoundingClientRect().height; + } + + // Map of parent pathIDs to node objects. + var parentLookup = {}; + + // Pending requests for child information. + var lookupRequests = {}; + + /** + * Creates a new list item for intent service data. Note that these are + * created and destroyed lazily as they scroll into and out of view, + * so they must be stateless. We cache the expanded item in + * @{code IntentsList} though, so it can keep state. + * (Mostly just which item is selected.) + * + * @param {Object} origin Data used to create an intents list item. + * @param {IntentsList} list The list that will contain this item. + * @constructor + * @extends {DeletableItem} + */ + function IntentsListItem(origin, list) { + var listItem = new DeletableItem(null); + listItem.__proto__ = IntentsListItem.prototype; + + listItem.origin = origin; + listItem.list = list; + listItem.decorate(); + + // This hooks up updateOrigin() to the list item, makes the top-level + // tree nodes (i.e., origins) register their IDs in parentLookup, and + // causes them to request their children if they have none. Note that we + // have special logic in the setter for the parent property to make sure + // that we can still garbage collect list items when they scroll out of + // view, even though it appears that we keep a direct reference. + if (origin) { + origin.parent = listItem; + origin.updateOrigin(); + } + + return listItem; + } + + IntentsListItem.prototype = { + __proto__: DeletableItem.prototype, + + /** @inheritDoc */ + decorate: function() { + this.siteChild = this.ownerDocument.createElement('div'); + this.siteChild.className = 'intents-site'; + this.dataChild = this.ownerDocument.createElement('div'); + this.dataChild.className = 'intents-data'; + this.itemsChild = this.ownerDocument.createElement('div'); + this.itemsChild.className = 'intents-items'; + this.infoChild = this.ownerDocument.createElement('div'); + this.infoChild.className = 'intents-details'; + this.infoChild.hidden = true; + var remove = this.ownerDocument.createElement('button'); + remove.textContent = localStrings.getString('removeIntent'); + remove.onclick = this.removeIntent_.bind(this); + this.infoChild.appendChild(remove); + var content = this.contentElement; + content.appendChild(this.siteChild); + content.appendChild(this.dataChild); + content.appendChild(this.itemsChild); + this.itemsChild.appendChild(this.infoChild); + if (this.origin && this.origin.data) { + this.siteChild.textContent = this.origin.data.site; + this.siteChild.setAttribute('title', this.origin.data.site); + } + this.itemList_ = []; + }, + + /** @type {boolean} */ + get expanded() { + return this.expanded_; + }, + set expanded(expanded) { + if (this.expanded_ == expanded) + return; + this.expanded_ = expanded; + if (expanded) { + var oldExpanded = this.list.expandedItem; + this.list.expandedItem = this; + this.updateItems_(); + if (oldExpanded) + oldExpanded.expanded = false; + this.classList.add('show-items'); + this.dataChild.hidden = true; + } else { + if (this.list.expandedItem == this) { + this.list.expandedItem = null; + } + this.style.height = ''; + this.itemsChild.style.height = ''; + this.classList.remove('show-items'); + this.dataChild.hidden = false; + } + }, + + /** + * The callback for the "remove" button shown when an item is selected. + * Requests that the currently selected intent service be removed. + * @private + */ + removeIntent_: function() { + if (this.selectedIndex_ >= 0) { + var item = this.itemList_[this.selectedIndex_]; + if (item && item.node) + chrome.send('removeIntent', [item.node.pathId]); + } + }, + + /** + * Disable animation within this intents list item, in preparation for + * making changes that will need to be animated. Makes it possible to + * measure the contents without displaying them, to set animation targets. + * @private + */ + disableAnimation_: function() { + this.itemsHeight_ = getItemHeight(this.itemsChild); + this.classList.add('measure-items'); + }, + + /** + * Enable animation after changing the contents of this intents list item. + * See @{code disableAnimation_}. + * @private + */ + enableAnimation_: function() { + if (!this.classList.contains('measure-items')) + this.disableAnimation_(); + this.itemsChild.style.height = ''; + // This will force relayout in order to calculate the new heights. + var itemsHeight = getItemHeight(this.itemsChild); + var fixedHeight = getItemHeight(this) + itemsHeight - this.itemsHeight_; + this.itemsChild.style.height = this.itemsHeight_ + 'px'; + // Force relayout before enabling animation, so that if we have + // changed things since the last layout, they will not be animated + // during subsequent layouts. + this.itemsChild.offsetHeight; + this.classList.remove('measure-items'); + this.itemsChild.style.height = itemsHeight + 'px'; + this.style.height = fixedHeight + 'px'; + }, + + /** + * Updates the origin summary to reflect changes in its items. + * Both IntentsListItem and IntentsTreeNode implement this API. + * This implementation scans the descendants to update the text. + */ + updateOrigin: function() { + var text = ''; + for (var i = 0; i < this.origin.children.length; ++i) { + if (text.length > 0) + text += ', ' + this.origin.children[i].data.action; + else + text = this.origin.children[i].data.action; + } + this.dataChild.textContent = text; + + if (this.expanded) + this.updateItems_(); + }, + + /** + * Updates the items section to reflect changes, animating to the new state. + * Removes existing contents and calls @{code IntentsTreeNode.createItems}. + * @private + */ + updateItems_: function() { + this.disableAnimation_(); + this.itemsChild.textContent = ''; + this.infoChild.hidden = true; + this.selectedIndex_ = -1; + this.itemList_ = []; + if (this.origin) + this.origin.createItems(this); + this.itemsChild.appendChild(this.infoChild); + this.enableAnimation_(); + }, + + /** + * Append a new intents node "bubble" to this list item. + * @param {IntentsTreeNode} node The intents node to add a bubble for. + * @param {Element} div The DOM element for the bubble itself. + * @return {number} The index the bubble was added at. + */ + appendItem: function(node, div) { + this.itemList_.push({node: node, div: div}); + this.itemsChild.appendChild(div); + return this.itemList_.length - 1; + }, + + /** + * The currently selected intents node ("intents bubble") index. + * @type {number} + * @private + */ + selectedIndex_: -1, + + /** + * Get the currently selected intents node ("intents bubble") index. + * @type {number} + */ + get selectedIndex() { + return this.selectedIndex_; + }, + + /** + * Set the currently selected intents node ("intents bubble") index to + * @{code itemIndex}, unselecting any previously selected node first. + * @param {number} itemIndex The index to set as the selected index. + * TODO: KILL THIS + */ + set selectedIndex(itemIndex) { + // Get the list index up front before we change anything. + var index = this.list.getIndexOfListItem(this); + // Unselect any previously selected item. + if (this.selectedIndex_ >= 0) { + var item = this.itemList_[this.selectedIndex_]; + if (item && item.div) + item.div.removeAttribute('selected'); + } + // Special case: decrementing -1 wraps around to the end of the list. + if (itemIndex == -2) + itemIndex = this.itemList_.length - 1; + // Check if we're going out of bounds and hide the item details. + if (itemIndex < 0 || itemIndex >= this.itemList_.length) { + this.selectedIndex_ = -1; + this.disableAnimation_(); + this.infoChild.hidden = true; + this.enableAnimation_(); + return; + } + // Set the new selected item and show the item details for it. + this.selectedIndex_ = itemIndex; + this.itemList_[itemIndex].div.setAttribute('selected', ''); + this.disableAnimation_(); + this.infoChild.hidden = false; + this.enableAnimation_(); + // If we're near the bottom of the list this may cause the list item to go + // beyond the end of the visible area. Fix it after the animation is done. + var list = this.list; + window.setTimeout(function() { list.scrollIndexIntoView(index); }, 150); + }, + }; + + /** + * {@code IntentsTreeNode}s mirror the structure of the intents tree lazily, + * and contain all the actual data used to generate the + * {@code IntentsListItem}s. + * @param {Object} data The data object for this node. + * @constructor + */ + function IntentsTreeNode(data) { + this.data = data; + this.children = []; + } + + IntentsTreeNode.prototype = { + /** + * Insert an intents tree node at the given index. + * Both IntentsList and IntentsTreeNode implement this API. + * @param {Object} data The data object for the node to add. + * @param {number} index The index at which to insert the node. + */ + insertAt: function(data, index) { + var child = new IntentsTreeNode(data); + this.children.splice(index, 0, child); + child.parent = this; + this.updateOrigin(); + }, + + /** + * Remove an intents tree node from the given index. + * Both IntentsList and IntentsTreeNode implement this API. + * @param {number} index The index of the tree node to remove. + */ + remove: function(index) { + if (index < this.children.length) { + this.children.splice(index, 1); + this.updateOrigin(); + } + }, + + /** + * Clears all children. + * Both IntentsList and IntentsTreeNode implement this API. + * It is used by IntentsList.loadChildren(). + */ + clear: function() { + // We might leave some garbage in parentLookup for removed children. + // But that should be OK because parentLookup is cleared when we + // reload the tree. + this.children = []; + this.updateOrigin(); + }, + + /** + * The counter used by startBatchUpdates() and endBatchUpdates(). + * @type {number} + */ + batchCount_: 0, + + /** + * See cr.ui.List.startBatchUpdates(). + * Both IntentsList (via List) and IntentsTreeNode implement this API. + */ + startBatchUpdates: function() { + this.batchCount_++; + }, + + /** + * See cr.ui.List.endBatchUpdates(). + * Both IntentsList (via List) and IntentsTreeNode implement this API. + */ + endBatchUpdates: function() { + if (!--this.batchCount_) + this.updateOrigin(); + }, + + /** + * Requests updating the origin summary to reflect changes in this item. + * Both IntentsListItem and IntentsTreeNode implement this API. + */ + updateOrigin: function() { + if (!this.batchCount_ && this.parent) + this.parent.updateOrigin(); + }, + + /** + * Create the intents services rows for this node. + * Append the rows to @{code item}. + * @param {IntentsListItem} item The intents list item to create items in. + */ + createItems: function(item) { + if (this.children.length > 0) { + for (var i = 0; i < this.children.length; ++i) + this.children[i].createItems(item); + } else if (this.data && !this.data.hasChildren) { + var div = item.ownerDocument.createElement('div'); + div.className = 'intents-item'; + // Help out screen readers and such: this is a clickable thing. + div.setAttribute('role', 'button'); + + var divAction = item.ownerDocument.createElement('div'); + divAction.className = 'intents-item-action'; + divAction.textContent = this.data.action; + div.appendChild(divAction); + + var divTypes = item.ownerDocument.createElement('div'); + divTypes.className = 'intents-item-types'; + var text = ""; + for (var i = 0; i < this.data.types.length; ++i) { + if (text != "") + text += ", "; + text += this.data.types[i]; + } + divTypes.textContent = text; + div.appendChild(divTypes); + + var divUrl = item.ownerDocument.createElement('div'); + divUrl.className = 'intents-item-url'; + divUrl.textContent = this.data.url; + div.appendChild(divUrl); + + var index = item.appendItem(this, div); + div.onclick = function() { + if (item.selectedIndex == index) + item.selectedIndex = -1; + else + item.selectedIndex = index; + }; + } + }, + + /** + * The parent of this intents tree node. + * @type {?IntentsTreeNode|IntentsListItem} + */ + get parent(parent) { + // See below for an explanation of this special case. + if (typeof this.parent_ == 'number') + return this.list_.getListItemByIndex(this.parent_); + return this.parent_; + }, + set parent(parent) { + if (parent == this.parent) + return; + + if (parent instanceof IntentsListItem) { + // If the parent is to be a IntentsListItem, then we keep the reference + // to it by its containing list and list index, rather than directly. + // This allows the list items to be garbage collected when they scroll + // out of view (except the expanded item, which we cache). This is + // transparent except in the setter and getter, where we handle it. + this.parent_ = parent.listIndex; + this.list_ = parent.list; + parent.addEventListener('listIndexChange', + this.parentIndexChanged_.bind(this)); + } else { + this.parent_ = parent; + } + + + if (parent) + parentLookup[this.pathId] = this; + else + delete parentLookup[this.pathId]; + + if (this.data && this.data.hasChildren && + !this.children.length && !lookupRequests[this.pathId]) { + lookupRequests[this.pathId] = true; + chrome.send('loadIntents', [this.pathId]); + } + }, + + /** + * Called when the parent is a IntentsListItem whose index has changed. + * See the code above that avoids keeping a direct reference to + * IntentsListItem parents, to allow them to be garbage collected. + * @private + */ + parentIndexChanged_: function(event) { + if (typeof this.parent_ == 'number') { + this.parent_ = event.newValue; + // We set a timeout to update the origin, rather than doing it right + // away, because this callback may occur while the list items are + // being repopulated following a scroll event. Calling updateOrigin() + // immediately could trigger relayout that would reset the scroll + // position within the list, among other things. + window.setTimeout(this.updateOrigin.bind(this), 0); + } + }, + + /** + * The intents tree path id. + * @type {string} + */ + get pathId() { + var parent = this.parent; + if (parent && parent instanceof IntentsTreeNode) + return parent.pathId + ',' + this.data.action; + return this.data.site; + }, + }; + + /** + * Creates a new intents list. + * @param {Object=} opt_propertyBag Optional properties. + * @constructor + * @extends {DeletableItemList} + */ + var IntentsList = cr.ui.define('list'); + + IntentsList.prototype = { + __proto__: DeletableItemList.prototype, + + /** @inheritDoc */ + decorate: function() { + DeletableItemList.prototype.decorate.call(this); + this.classList.add('intents-list'); + this.data_ = []; + this.dataModel = new ArrayDataModel(this.data_); + this.addEventListener('keydown', this.handleKeyLeftRight_.bind(this)); + var sm = new ListSingleSelectionModel(); + sm.addEventListener('change', this.cookieSelectionChange_.bind(this)); + sm.addEventListener('leadIndexChange', this.cookieLeadChange_.bind(this)); + this.selectionModel = sm; + this.fixedHeight = false; + }, + + /** + * Handles key down events and looks for left and right arrows, then + * dispatches to the currently expanded item, if any. + * @param {Event} e The keydown event. + * @private + */ + handleKeyLeftRight_: function(e) { + var id = e.keyIdentifier; + if ((id == 'Left' || id == 'Right') && this.expandedItem) { + var cs = this.ownerDocument.defaultView.getComputedStyle(this); + var rtl = cs.direction == 'rtl'; + if ((!rtl && id == 'Left') || (rtl && id == 'Right')) + this.expandedItem.selectedIndex--; + else + this.expandedItem.selectedIndex++; + this.scrollIndexIntoView(this.expandedItem.listIndex); + // Prevent the page itself from scrolling. + e.preventDefault(); + } + }, + + /** + * Called on selection model selection changes. + * @param {Event} ce The selection change event. + * @private + */ + cookieSelectionChange_: function(ce) { + ce.changes.forEach(function(change) { + var listItem = this.getListItemByIndex(change.index); + if (listItem) { + if (!change.selected) { + // We set a timeout here, rather than setting the item unexpanded + // immediately, so that if another item gets set expanded right + // away, it will be expanded before this item is unexpanded. It + // will notice that, and unexpand this item in sync with its own + // expansion. Later, this callback will end up having no effect. + window.setTimeout(function() { + if (!listItem.selected || !listItem.lead) + listItem.expanded = false; + }, 0); + } else if (listItem.lead) { + listItem.expanded = true; + } + } + }, this); + }, + + /** + * Called on selection model lead changes. + * @param {Event} pe The lead change event. + * @private + */ + cookieLeadChange_: function(pe) { + if (pe.oldValue != -1) { + var listItem = this.getListItemByIndex(pe.oldValue); + if (listItem) { + // See cookieSelectionChange_ above for why we use a timeout here. + window.setTimeout(function() { + if (!listItem.lead || !listItem.selected) + listItem.expanded = false; + }, 0); + } + } + if (pe.newValue != -1) { + var listItem = this.getListItemByIndex(pe.newValue); + if (listItem && listItem.selected) + listItem.expanded = true; + } + }, + + /** + * The currently expanded item. Used by IntentsListItem above. + * @type {?IntentsListItem} + */ + expandedItem: null, + + // from cr.ui.List + /** @inheritDoc */ + createItem: function(data) { + // We use the cached expanded item in order to allow it to maintain some + // state (like its fixed height, and which bubble is selected). + if (this.expandedItem && this.expandedItem.origin == data) + return this.expandedItem; + return new IntentsListItem(data, this); + }, + + // from options.DeletableItemList + /** @inheritDoc */ + deleteItemAtIndex: function(index) { + var item = this.data_[index]; + if (item) { + var pathId = item.pathId; + if (pathId) + chrome.send('removeIntent', [pathId]); + } + }, + + /** + * Insert an intents tree node at the given index. + * Both IntentsList and IntentsTreeNode implement this API. + * @param {Object} data The data object for the node to add. + * @param {number} index The index at which to insert the node. + */ + insertAt: function(data, index) { + this.dataModel.splice(index, 0, new IntentsTreeNode(data)); + }, + + /** + * Remove an intents tree node from the given index. + * Both IntentsList and IntentsTreeNode implement this API. + * @param {number} index The index of the tree node to remove. + */ + remove: function(index) { + if (index < this.data_.length) + this.dataModel.splice(index, 1); + }, + + /** + * Clears the list. + * Both IntentsList and IntentsTreeNode implement this API. + * It is used by IntentsList.loadChildren(). + */ + clear: function() { + parentLookup = {}; + this.data_ = []; + this.dataModel = new ArrayDataModel(this.data_); + this.redraw(); + }, + + /** + * Add tree nodes by given parent. + * Note: this method will be O(n^2) in the general case. Use it only to + * populate an empty parent or to insert single nodes to avoid this. + * @param {Object} parent The parent node. + * @param {number} start Start index of where to insert nodes. + * @param {Array} nodesData Nodes data array. + * @private + */ + addByParent_: function(parent, start, nodesData) { + if (!parent) + return; + + parent.startBatchUpdates(); + for (var i = 0; i < nodesData.length; ++i) + parent.insertAt(nodesData[i], start + i); + parent.endBatchUpdates(); + + cr.dispatchSimpleEvent(this, 'change'); + }, + + /** + * Add tree nodes by parent id. + * This is used by intents_view.js. + * Note: this method will be O(n^2) in the general case. Use it only to + * populate an empty parent or to insert single nodes to avoid this. + * @param {string} parentId Id of the parent node. + * @param {number} start Start index of where to insert nodes. + * @param {Array} nodesData Nodes data array. + */ + addByParentId: function(parentId, start, nodesData) { + var parent = parentId ? parentLookup[parentId] : this; + this.addByParent_(parent, start, nodesData); + }, + + /** + * Removes tree nodes by parent id. + * This is used by intents_view.js. + * @param {string} parentId Id of the parent node. + * @param {number} start Start index of nodes to remove. + * @param {number} count Number of nodes to remove. + */ + removeByParentId: function(parentId, start, count) { + var parent = parentId ? parentLookup[parentId] : this; + if (!parent) + return; + + parent.startBatchUpdates(); + while (count-- > 0) + parent.remove(start); + parent.endBatchUpdates(); + + cr.dispatchSimpleEvent(this, 'change'); + }, + + /** + * Loads the immediate children of given parent node. + * This is used by intents_view.js. + * @param {string} parentId Id of the parent node. + * @param {Array} children The immediate children of parent node. + */ + loadChildren: function(parentId, children) { + if (parentId) + delete lookupRequests[parentId]; + var parent = parentId ? parentLookup[parentId] : this; + if (!parent) + return; + + parent.startBatchUpdates(); + parent.clear(); + this.addByParent_(parent, 0, children); + parent.endBatchUpdates(); + }, + }; + + return { + IntentsList: IntentsList + }; +}); diff --git a/chrome/browser/resources/options2/intents_view.css b/chrome/browser/resources/options2/intents_view.css new file mode 100644 index 0000000..84c274d --- /dev/null +++ b/chrome/browser/resources/options2/intents_view.css @@ -0,0 +1,181 @@ +/* +Copyright (c) 2011 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. +*/ + +/* Styles for the intents list elements in intents_view.html. */ + +#intents-column-headers { + position: relative; + width: 100%; +} + +#intents-column-headers h3 { + font-size: 105%; + font-weight: bold; + margin: 10px 0; +} + +/* Notice the width and padding for these columns match up with those below. */ +#intents-site-column { + display: inline-block; + font-weight: bold; + width: 11em; +} + +#intents-data-column { + -webkit-padding-start: 7px; + display: inline-block; + font-weight: bold; +} + +#intents-list { + border: 1px solid #d9d9d9; + margin: 0; +} + +/* Enable animating the height of items. */ +list.intents-list .deletable-item { + -webkit-transition: height .15s ease-in-out; +} + +/* Disable webkit-box display. */ +list.intents-list .deletable-item > :first-child { + display: block; +} + +/* Force the X for deleting an origin to stay at the top. */ +list.intents-list > .deletable-item > .close-button { + position: absolute; + right: 2px; + top: 8px; +} + +html[dir=rtl] list.intents-list > .deletable-item > .close-button { + left: 2px; + right: auto; +} + +/* Styles for the site (aka origin) and its summary. */ + +.intents-site { + /* Notice that the width, margin, and padding match up with those above. */ + -webkit-margin-end: 2px; + -webkit-padding-start: 5px; + display: inline-block; + overflow: hidden; + text-overflow: ellipsis; + width: 11em; +} + +list.intents-list > .deletable-item[selected] .intents-site { + -webkit-user-select: text; +} + +.intents-data { + display: inline-block; +} + +list.intents-list > .deletable-item[selected] .intents-data { + -webkit-user-select: text; +} + +.intents-items { + /* Notice that the margin and padding match up with those above. */ + -webkit-margin-start: 11em; + -webkit-padding-start: 7px; + -webkit-transition: .15s ease-in-out; + display: table; + height: 0; + opacity: 0; + /* Make the intents items wrap correctly. */ + white-space: normal; +} + +.measure-items .intents-items { + -webkit-transition: none; + height: auto; + visibility: hidden; +} + +.show-items .intents-items { + opacity: 1; +} + +.intents-items .intents-item { + -webkit-box-orient: horizontal; + -webkit-box-pack: start; + background: #e0e9f5; + border: 1px solid #8392ae; + display: table-row; + font-size: 85%; + height: auto; + margin: 2px 4px 2px 0; + overflow: hidden; + padding: 0 3px; + text-align: center; + text-overflow: ellipsis; +} + +.intents-item .intents-item-action { + display: table-cell; + padding: 2px 5px; +} + +.intents-item .intents-item-types { + display: table-cell; + padding: 2px 5px; + overflow: hidden; +} + +.intents-item .intents-item-url { + display: table-cell; + padding: 2px 5px; + overflow: hidden; + text-overflow: ellipsis; +} + +.intents-items .intents-item:hover { + background: #eef3f9; + border-color: #647187; +} + +.intents-items .intents-item[selected] { + background: #f5f8f8; + border-color: #b2b2b2; +} + +.intents-items .intents-item[selected]:hover { + background: #f5f8f8; + border-color: #647187; +} + +/* Styles for the intents details box. */ + +.intents-details { + background: #f5f8f8; + border-radius: 5px; + border: 1px solid #b2b2b2; + margin-top: 2px; + padding: 5px; +} + +list.intents-list > .deletable-item[selected] .intents-details { + -webkit-user-select: text; +} + +.intents-details-table { + table-layout: fixed; + width: 100%; +} + +.intents-details-label { + vertical-align: top; + white-space: pre; + width: 10em; +} + +.intents-details-value { + word-wrap: break-word; +} diff --git a/chrome/browser/resources/options2/intents_view.html b/chrome/browser/resources/options2/intents_view.html new file mode 100644 index 0000000..6803c23 --- /dev/null +++ b/chrome/browser/resources/options2/intents_view.html @@ -0,0 +1,12 @@ +<div id="intents-view-page" class="page" hidden> + <h1 i18n-content="intentsViewPage"></h1> + <div id="intents-column-headers"> + <div id="intents-site-column"> + <h3 i18n-content="intentsDomain"></h3> + </div> + <div id="intents-data-column"> + <h3 i18n-content="intentsServiceData"></h3> + </div> + </div> + <list id="intents-list"></list> +</div> diff --git a/chrome/browser/resources/options2/intents_view.js b/chrome/browser/resources/options2/intents_view.js new file mode 100644 index 0000000..afc7d68 --- /dev/null +++ b/chrome/browser/resources/options2/intents_view.js @@ -0,0 +1,83 @@ +// Copyright (c) 2011 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; + + ///////////////////////////////////////////////////////////////////////////// + // IntentsView class: + + /** + * Encapsulated handling of the Intents data page. + * @constructor + */ + function IntentsView(model) { + OptionsPage.call(this, 'intents', + templateData.intentsViewPageTabTitle, + 'intents-view-page'); + } + + cr.addSingletonGetter(IntentsView); + + IntentsView.prototype = { + __proto__: OptionsPage.prototype, + + initializePage: function() { + OptionsPage.prototype.initializePage.call(this); + + var intentsList = $('intents-list'); + options.IntentsList.decorate(intentsList); + window.addEventListener('resize', this.handleResize_.bind(this)); + + this.addEventListener('visibleChange', this.handleVisibleChange_); + }, + + initialized_: false, + + /** + * Handler for OptionsPage's visible property change event. + * @param {Event} e Property change event. + * @private + */ + handleVisibleChange_: function(e) { + if (!this.visible) + return; + + // Resize the intents list whenever the options page becomes visible. + this.handleResize_(null); + if (!this.initialized_) { + this.initialized_ = true; + chrome.send('loadIntents'); + } else { + $('intents-list').redraw(); + } + }, + + /** + * Handler for when the window changes size. Resizes the intents list to + * match the window height. + * @param {?Event} e Window resize event, or null if called directly. + * @private + */ + handleResize_: function(e) { + if (!this.visible) + return; + var intentsList = $('intents-list'); + // 25 pixels from the window bottom seems like a visually pleasing amount. + var height = window.innerHeight - intentsList.offsetTop - 25; + intentsList.style.height = height + 'px'; + }, + }; + + // IntentsViewHandler callbacks. + IntentsView.loadChildren = function(args) { + $('intents-list').loadChildren(args[0], args[1]); + }; + + // Export + return { + IntentsView: IntentsView + }; + +}); diff --git a/chrome/browser/resources/options2/language_add_language_overlay.html b/chrome/browser/resources/options2/language_add_language_overlay.html new file mode 100644 index 0000000..bca2e75 --- /dev/null +++ b/chrome/browser/resources/options2/language_add_language_overlay.html @@ -0,0 +1,28 @@ +<div id="add-language-overlay-page" class="page" hidden> +<!-- +Buttons are too small for touch in the touchui builds. +Use drop down box for touchui builds instead. +--> +<if expr="pp_ifdef('chromeos')"> + <ul id="add-language-overlay-language-list"> + </ul> + <button id="add-language-overlay-cancel-button" + i18n-content="cancel"></button> +</if> +<if expr="not pp_ifdef('chromeos')"> + <h1 i18n-content="add_language_title"></h1> + <div class="content-area"> + <label> + <span i18n-content="add_language_select_label"></span> + <select id="add-language-overlay-language-list"></select> + </label> + </div> + <div class="action-area"> + <div class="button-strip"> + <button id="add-language-overlay-cancel-button" i18n-content="cancel"> + </button> + <button id="add-language-overlay-ok-button" i18n-content="ok"></button> + </div> + </div> +</if> +</div> diff --git a/chrome/browser/resources/options2/language_add_language_overlay.js b/chrome/browser/resources/options2/language_add_language_overlay.js new file mode 100644 index 0000000..9fa856d --- /dev/null +++ b/chrome/browser/resources/options2/language_add_language_overlay.js @@ -0,0 +1,73 @@ +// Copyright (c) 2011 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. + +/////////////////////////////////////////////////////////////////////////////// +// AddLanguageOverlay class: + +cr.define('options', function() { + const OptionsPage = options.OptionsPage; + + /** + * Encapsulated handling of ChromeOS add language overlay page. + * @constructor + */ + function AddLanguageOverlay() { + OptionsPage.call(this, 'addLanguage', + localStrings.getString('add_button'), + 'add-language-overlay-page'); + } + + cr.addSingletonGetter(AddLanguageOverlay); + + AddLanguageOverlay.prototype = { + // Inherit AddLanguageOverlay from OptionsPage. + __proto__: OptionsPage.prototype, + + /** + * Initializes AddLanguageOverlay page. + * Calls base class implementation to starts preference initialization. + */ + initializePage: function() { + // Call base class implementation to starts preference initialization. + OptionsPage.prototype.initializePage.call(this); + + // Set up the cancel button. + $('add-language-overlay-cancel-button').onclick = function(e) { + OptionsPage.closeOverlay(); + }; + + // Create the language list with which users can add a language. + var addLanguageList = $('add-language-overlay-language-list'); + var languageListData = templateData.languageList; + for (var i = 0; i < languageListData.length; i++) { + var language = languageListData[i]; + var displayText = language.displayName; + // If the native name is different, add it. + if (language.displayName != language.nativeDisplayName) { + displayText += ' - ' + language.nativeDisplayName; + } + + if (cr.isChromeOS) { + var button = document.createElement('button'); + button.className = 'link-button'; + button.textContent = displayText; + button.languageCode = language.code; + var li = document.createElement('li'); + li.languageCode = language.code; + li.appendChild(button); + addLanguageList.appendChild(li); + } else { + var option = document.createElement('option'); + option.value = language.code; + option.textContent = displayText; + addLanguageList.appendChild(option); + } + } + }, + }; + + return { + AddLanguageOverlay: AddLanguageOverlay + }; +}); diff --git a/chrome/browser/resources/options2/language_list.js b/chrome/browser/resources/options2/language_list.js new file mode 100644 index 0000000..3cbcb75 --- /dev/null +++ b/chrome/browser/resources/options2/language_list.js @@ -0,0 +1,487 @@ +// Copyright (c) 2011 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() { + const ArrayDataModel = cr.ui.ArrayDataModel; + const DeletableItem = options.DeletableItem; + const DeletableItemList = options.DeletableItemList; + const List = cr.ui.List; + const ListItem = cr.ui.ListItem; + const ListSingleSelectionModel = cr.ui.ListSingleSelectionModel; + + /** + * Creates a new Language list item. + * @param {String} languageCode the languageCode. + * @constructor + * @extends {DeletableItem.ListItem} + */ + function LanguageListItem(languageCode) { + var el = cr.doc.createElement('li'); + el.__proto__ = LanguageListItem.prototype; + el.languageCode_ = languageCode; + el.decorate(); + return el; + }; + + LanguageListItem.prototype = { + __proto__: DeletableItem.prototype, + + /** + * The language code of this language. + * @type {String} + * @private + */ + languageCode_: null, + + /** @inheritDoc */ + decorate: function() { + DeletableItem.prototype.decorate.call(this); + + var languageCode = this.languageCode_; + var languageOptions = options.LanguageOptions.getInstance(); + this.deletable = languageOptions.languageIsDeletable(languageCode); + this.languageCode = languageCode; + this.languageName = cr.doc.createElement('div'); + this.languageName.className = 'language-name'; + this.languageName.textContent = + LanguageList.getDisplayNameFromLanguageCode(languageCode); + this.contentElement.appendChild(this.languageName); + this.title = + LanguageList.getNativeDisplayNameFromLanguageCode(languageCode); + this.draggable = true; + }, + }; + + /** + * Creates a new language list. + * @param {Object=} opt_propertyBag Optional properties. + * @constructor + * @extends {cr.ui.List} + */ + var LanguageList = cr.ui.define('list'); + + /** + * Gets display name from the given language code. + * @param {string} languageCode Language code (ex. "fr"). + */ + LanguageList.getDisplayNameFromLanguageCode = function(languageCode) { + // Build the language code to display name dictionary at first time. + if (!this.languageCodeToDisplayName_) { + this.languageCodeToDisplayName_ = {}; + var languageList = templateData.languageList; + for (var i = 0; i < languageList.length; i++) { + var language = languageList[i]; + this.languageCodeToDisplayName_[language.code] = language.displayName; + } + } + + return this.languageCodeToDisplayName_[languageCode]; + } + + /** + * Gets native display name from the given language code. + * @param {string} languageCode Language code (ex. "fr"). + */ + LanguageList.getNativeDisplayNameFromLanguageCode = function(languageCode) { + // Build the language code to display name dictionary at first time. + if (!this.languageCodeToNativeDisplayName_) { + this.languageCodeToNativeDisplayName_ = {}; + var languageList = templateData.languageList; + for (var i = 0; i < languageList.length; i++) { + var language = languageList[i]; + this.languageCodeToNativeDisplayName_[language.code] = + language.nativeDisplayName; + } + } + + return this.languageCodeToNativeDisplayName_[languageCode]; + } + + /** + * Returns true if the given language code is valid. + * @param {string} languageCode Language code (ex. "fr"). + */ + LanguageList.isValidLanguageCode = function(languageCode) { + // Having the display name for the language code means that the + // language code is valid. + if (LanguageList.getDisplayNameFromLanguageCode(languageCode)) { + return true; + } + return false; + } + + LanguageList.prototype = { + __proto__: DeletableItemList.prototype, + + // The list item being dragged. + draggedItem: null, + // The drop position information: "below" or "above". + dropPos: null, + // The preference is a CSV string that describes preferred languages + // in Chrome OS. The language list is used for showing the language + // list in "Language and Input" options page. + preferredLanguagesPref: 'settings.language.preferred_languages', + // The preference is a CSV string that describes accept languages used + // for content negotiation. To be more precise, the list will be used + // in "Accept-Language" header in HTTP requests. + acceptLanguagesPref: 'intl.accept_languages', + + /** @inheritDoc */ + decorate: function() { + DeletableItemList.prototype.decorate.call(this); + this.selectionModel = new ListSingleSelectionModel; + + // HACK(arv): http://crbug.com/40902 + window.addEventListener('resize', this.redraw.bind(this)); + + // Listen to pref change. + if (cr.isChromeOS) { + Preferences.getInstance().addEventListener(this.preferredLanguagesPref, + this.handlePreferredLanguagesPrefChange_.bind(this)); + } else { + Preferences.getInstance().addEventListener(this.acceptLanguagesPref, + this.handleAcceptLanguagesPrefChange_.bind(this)); + } + + // Listen to drag and drop events. + this.addEventListener('dragstart', this.handleDragStart_.bind(this)); + this.addEventListener('dragenter', this.handleDragEnter_.bind(this)); + this.addEventListener('dragover', this.handleDragOver_.bind(this)); + this.addEventListener('drop', this.handleDrop_.bind(this)); + this.addEventListener('dragleave', this.handleDragLeave_.bind(this)); + }, + + createItem: function(languageCode) { + return new LanguageListItem(languageCode); + }, + + /* + * For each item, determines whether it's deletable. + */ + updateDeletable: function() { + var items = this.items; + for (var i = 0; i < items.length; ++i) { + var item = items[i]; + var languageCode = item.languageCode; + var languageOptions = options.LanguageOptions.getInstance(); + item.deletable = languageOptions.languageIsDeletable(languageCode); + } + }, + + /* + * Adds a language to the language list. + * @param {string} languageCode language code (ex. "fr"). + */ + addLanguage: function(languageCode) { + // It shouldn't happen but ignore the language code if it's + // null/undefined, or already present. + if (!languageCode || this.dataModel.indexOf(languageCode) >= 0) { + return; + } + this.dataModel.push(languageCode); + // Select the last item, which is the language added. + this.selectionModel.selectedIndex = this.dataModel.length - 1; + + this.savePreference_(); + }, + + /* + * Gets the language codes of the currently listed languages. + */ + getLanguageCodes: function() { + return this.dataModel.slice(); + }, + + /* + * Gets the language code of the selected language. + */ + getSelectedLanguageCode: function() { + return this.selectedItem; + }, + + /* + * Selects the language by the given language code. + * @returns {boolean} True if the operation is successful. + */ + selectLanguageByCode: function(languageCode) { + var index = this.dataModel.indexOf(languageCode); + if (index >= 0) { + this.selectionModel.selectedIndex = index; + return true; + } + return false; + }, + + /** @inheritDoc */ + deleteItemAtIndex: function(index) { + if (index >= 0) { + this.dataModel.splice(index, 1); + // Once the selected item is removed, there will be no selected item. + // Select the item pointed by the lead index. + index = this.selectionModel.leadIndex; + this.savePreference_(); + } + return index; + }, + + /* + * Computes the target item of drop event. + * @param {Event} e The drop or dragover event. + * @private + */ + getTargetFromDropEvent_ : function(e) { + var target = e.target; + // e.target may be an inner element of the list item + while (target != null && !(target instanceof ListItem)) { + target = target.parentNode; + } + return target; + }, + + /* + * Handles the dragstart event. + * @param {Event} e The dragstart event. + * @private + */ + handleDragStart_: function(e) { + var target = e.target; + // ListItem should be the only draggable element type in the page, + // but just in case. + if (target instanceof ListItem) { + this.draggedItem = target; + e.dataTransfer.effectAllowed = 'move'; + // We need to put some kind of data in the drag or it will be + // ignored. Use the display name in case the user drags to a text + // field or the desktop. + e.dataTransfer.setData('text/plain', target.title); + } + }, + + /* + * Handles the dragenter event. + * @param {Event} e The dragenter event. + * @private + */ + handleDragEnter_: function(e) { + e.preventDefault(); + }, + + /* + * Handles the dragover event. + * @param {Event} e The dragover event. + * @private + */ + handleDragOver_: function(e) { + var dropTarget = this.getTargetFromDropEvent_(e); + // Determines whether the drop target is to accept the drop. + // The drop is only successful on another ListItem. + if (!(dropTarget instanceof ListItem) || + dropTarget == this.draggedItem) { + this.hideDropMarker_(); + return; + } + // Compute the drop postion. Should we move the dragged item to + // below or above the drop target? + var rect = dropTarget.getBoundingClientRect(); + var dy = e.clientY - rect.top; + var yRatio = dy / rect.height; + var dropPos = yRatio <= .5 ? 'above' : 'below'; + this.dropPos = dropPos; + this.showDropMarker_(dropTarget, dropPos); + e.preventDefault(); + }, + + /* + * Handles the drop event. + * @param {Event} e The drop event. + * @private + */ + handleDrop_: function(e) { + var dropTarget = this.getTargetFromDropEvent_(e); + this.hideDropMarker_(); + + // Delete the language from the original position. + var languageCode = this.draggedItem.languageCode; + var originalIndex = this.dataModel.indexOf(languageCode); + this.dataModel.splice(originalIndex, 1); + // Insert the language to the new position. + var newIndex = this.dataModel.indexOf(dropTarget.languageCode); + if (this.dropPos == 'below') + newIndex += 1; + this.dataModel.splice(newIndex, 0, languageCode); + // The cursor should move to the moved item. + this.selectionModel.selectedIndex = newIndex; + // Save the preference. + this.savePreference_(); + }, + + /* + * Handles the dragleave event. + * @param {Event} e The dragleave event + * @private + */ + handleDragLeave_ : function(e) { + this.hideDropMarker_(); + }, + + /* + * Shows and positions the marker to indicate the drop target. + * @param {HTMLElement} target The current target list item of drop + * @param {string} pos 'below' or 'above' + * @private + */ + showDropMarker_ : function(target, pos) { + window.clearTimeout(this.hideDropMarkerTimer_); + var marker = $('language-options-list-dropmarker'); + var rect = target.getBoundingClientRect(); + var markerHeight = 8; + if (pos == 'above') { + marker.style.top = (rect.top - markerHeight/2) + 'px'; + } else { + marker.style.top = (rect.bottom - markerHeight/2) + 'px'; + } + marker.style.width = rect.width + 'px'; + marker.style.left = rect.left + 'px'; + marker.style.display = 'block'; + }, + + /* + * Hides the drop marker. + * @private + */ + hideDropMarker_ : function() { + // Hide the marker in a timeout to reduce flickering as we move between + // valid drop targets. + window.clearTimeout(this.hideDropMarkerTimer_); + this.hideDropMarkerTimer_ = window.setTimeout(function() { + $('language-options-list-dropmarker').style.display = ''; + }, 100); + }, + + /** + * Handles preferred languages pref change. + * @param {Event} e The change event object. + * @private + */ + handlePreferredLanguagesPrefChange_: function(e) { + var languageCodesInCsv = e.value.value; + var languageCodes = languageCodesInCsv.split(','); + + // Add the UI language to the initial list of languages. This is to avoid + // a bug where the UI language would be removed from the preferred + // language list by sync on first login. + // See: crosbug.com/14283 + languageCodes.push(navigator.language); + languageCodes = this.filterBadLanguageCodes_(languageCodes); + this.load_(languageCodes); + }, + + /** + * Handles accept languages pref change. + * @param {Event} e The change event object. + * @private + */ + handleAcceptLanguagesPrefChange_: function(e) { + var languageCodesInCsv = e.value.value; + var languageCodes = this.filterBadLanguageCodes_( + languageCodesInCsv.split(',')); + this.load_(languageCodes); + }, + + /** + * Loads given language list. + * @param {Array} languageCodes List of language codes. + * @private + */ + load_: function(languageCodes) { + // Preserve the original selected index. See comments below. + var originalSelectedIndex = (this.selectionModel ? + this.selectionModel.selectedIndex : -1); + this.dataModel = new ArrayDataModel(languageCodes); + if (originalSelectedIndex >= 0 && + originalSelectedIndex < this.dataModel.length) { + // Restore the original selected index if the selected index is + // valid after the data model is loaded. This is neeeded to keep + // the selected language after the languge is added or removed. + this.selectionModel.selectedIndex = originalSelectedIndex; + // The lead index should be updated too. + this.selectionModel.leadIndex = originalSelectedIndex; + } else if (this.dataModel.length > 0){ + // Otherwise, select the first item if it's not empty. + // Note that ListSingleSelectionModel won't select an item + // automatically, hence we manually select the first item here. + this.selectionModel.selectedIndex = 0; + } + }, + + /** + * Saves the preference. + */ + savePreference_: function() { + // Encode the language codes into a CSV string. + if (cr.isChromeOS) + Preferences.setStringPref(this.preferredLanguagesPref, + this.dataModel.slice().join(',')); + // Save the same language list as accept languages preference as + // well, but we need to expand the language list, to make it more + // acceptable. For instance, some web sites don't understand 'en-US' + // but 'en'. See crosbug.com/9884. + var acceptLanguages = this.expandLanguageCodes(this.dataModel.slice()); + Preferences.setStringPref(this.acceptLanguagesPref, + acceptLanguages.join(',')); + cr.dispatchSimpleEvent(this, 'save'); + }, + + /** + * Expands language codes to make these more suitable for Accept-Language. + * Example: ['en-US', 'ja', 'en-CA'] => ['en-US', 'en', 'ja', 'en-CA']. + * 'en' won't appear twice as this function eliminates duplicates. + * @param {Array} languageCodes List of language codes. + * @private + */ + expandLanguageCodes: function(languageCodes) { + var expandedLanguageCodes = []; + var seen = {}; // Used to eliminiate duplicates. + for (var i = 0; i < languageCodes.length; i++) { + var languageCode = languageCodes[i]; + if (!(languageCode in seen)) { + expandedLanguageCodes.push(languageCode); + seen[languageCode] = true; + } + var parts = languageCode.split('-'); + if (!(parts[0] in seen)) { + expandedLanguageCodes.push(parts[0]); + seen[parts[0]] = true; + } + } + return expandedLanguageCodes; + }, + + /** + * Filters bad language codes in case bad language codes are + * stored in the preference. Removes duplicates as well. + * @param {Array} languageCodes List of language codes. + * @private + */ + filterBadLanguageCodes_: function(languageCodes) { + var filteredLanguageCodes = []; + var seen = {}; + for (var i = 0; i < languageCodes.length; i++) { + // Check if the the language code is valid, and not + // duplicate. Otherwise, skip it. + if (LanguageList.isValidLanguageCode(languageCodes[i]) && + !(languageCodes[i] in seen)) { + filteredLanguageCodes.push(languageCodes[i]); + seen[languageCodes[i]] = true; + } + } + return filteredLanguageCodes; + }, + }; + + return { + LanguageList: LanguageList, + LanguageListItem: LanguageListItem + }; +}); diff --git a/chrome/browser/resources/options2/language_options.css b/chrome/browser/resources/options2/language_options.css new file mode 100644 index 0000000..55b7aa8 --- /dev/null +++ b/chrome/browser/resources/options2/language_options.css @@ -0,0 +1,239 @@ +.language-options { + display: -webkit-box; + margin: 10px 0; +} + +.language-options-lower-left button, +.language-options-right button { + min-width: 70px; +} + +.language-options h3 { + -webkit-margin-start: 12px; + font-size: 100%; + font-weight: bold; + margin-top: 12px; +} + +.language-options-contents { + -webkit-padding-start: 12px; + -webkit-padding-end: 12px; + padding-bottom: 10px; +} + +.language-options-header, .language-options-footer { + margin: 10px 0; +} + +.language-options-left, .language-options-right { + border: 1px solid #cccccc; + vertical-align: top; + padding: 0; + height: 400px; +} + +.language-options-left { + -webkit-box-orient: vertical; + display: -webkit-box; + background-color: #ebeff9; + width: 300px; +} + +/* On OS X we use the native OS spellchecker, so don't display the dictionary */ +/* pane. */ +html[os=mac] .language-options-left { + background-color: white; +} + +html[os=mac] .language-options-right { + visibility: hidden; +} + +.language-options-lower-left { + -webkit-box-flex: 0; + -webkit-padding-start: 12px; + padding-bottom: 10px; +} + +.language-options-right { + /* To share the center line with the left pane. */ + -webkit-margin-start: -1px; + width: 360px; +} + +.language-options-notification { + display: none; + background-color: #fff29e; + border-top: 1px solid #ccc; + border-bottom: 1px solid #ccc; + padding: 12px 30px 12px 12px; +} + +#language-options-input-method-list button { + display: block; + -webkit-margin-start: 20px; +} + +#language-options-ui-language-button { + width: 95%; + -webkit-margin-start: 10px; +} + +#language-options-spell-check-language-button { + width: 95%; + -webkit-margin-start: 10px; +} + +#language-options-input-method-list label { + margin: 4px 0; +} + +#language-options-list { + -webkit-box-flex: 1; + outline: none; + padding: 1px 0 0; + width: 100%; +} + +#language-options-list .language-name { + -webkit-box-flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +#language-options-list li { + -webkit-padding-start: 12px; + padding-top: 2px; + padding-bottom: 2px; +} + +#language-options-list-dropmarker { + background-color: hsl(214, 91%, 65%); + background-clip: padding-box; + border: 3px solid hsl(214, 91%, 65%); + border-bottom-color: transparent; + border-top-color: transparent; + border-radius: 0; + box-sizing: border-box; + display: none; + height: 8px; + overflow: hidden; + pointer-events: none; + position: fixed; + z-index: 10; +} + +#language-options-ui-restart-button { + margin-top: 12px; +} + +/* + * In ChromeOS we present the language choices as a big page of links. + */ + +html[os=chromeos] #add-language-overlay-language-list { + -webkit-column-count: 2; + -webkit-column-gap: 20px; +} + +html[os=chromeos] #add-language-overlay-cancel-button { + /* Place the button in the center. */ + display: block; + margin: auto; + margin-top: 15px; +} + +html[os=chromeos] #add-language-overlay-page { + width: 800px; + padding: 20px; +} + +html[os=chromeos] #add-language-overlay-page button.link-button { + padding: 0; + text-align: left; +} + +html[os=chromeos] #add-language-overlay-page ul { + padding: 0; + margin: 0; +} + +/* TODO(kochi): This is temporary copy from new_tab.css */ +/* Notification */ + +#notification { + position: relative; + background-color: hsl(52, 100%, 80%); + border: 1px solid rgb(211, 211, 211); + border-radius: 6px; + padding: 7px 15px; + white-space: nowrap; + display: table; + /* Set the height and margin so that the element does not use any vertical + space */ + height: 16px; + margin: -44px auto 12px auto; + font-weight: bold; + opacity: 0; + pointer-events: none; + -webkit-transition: opacity 150ms; + z-index: 1; + color: black; +} + +#notification > * { + display: table-cell; + max-width: 500px; + overflow: hidden; + text-overflow: ellipsis; +} + +#notification.show { + opacity: 1; + pointer-events: all; + -webkit-transition: opacity 1s; +} + +#notification .link { + cursor: pointer; + text-decoration: underline; + -webkit-appearance: none; + border: 0; + background: none; + color: rgba(0, 102, 204, 0.3); + -webkit-padding-start: 20px; +} + +#notification .link-color { + color: rgb(0, 102, 204); +} + +#chewing-max-chi-symbol-len { + width: 100px; + height: 30%; +} + +#add-language-overlay-page .content-area { + padding-bottom: 10px; +} + +.text-button, +.text-button:active, +.text-button:focus, +.text-button:hover { + -webkit-box-shadow: none; + background: transparent none; + border-color: transparent; + color: #000; +} + +button[disabled].text-button, +button[disabled].text-button:active, +button[disabled].text-button:focus, +button[disabled].text-button:hover { + -webkit-box-shadow: none; + background: transparent none; + border-color: transparent; + color: #AAA; +} diff --git a/chrome/browser/resources/options2/language_options.html b/chrome/browser/resources/options2/language_options.html new file mode 100644 index 0000000..46373c1 --- /dev/null +++ b/chrome/browser/resources/options2/language_options.html @@ -0,0 +1,78 @@ +<div id="languagePage" class="page" hidden> + <h1 i18n-content="languagePage"></h1> + <div id="notification"> + <span> </span> + <span class="link"><span class="link-color"></span></span> + </div> + <div class="language-options-header"> + <div i18n-content="add_language_instructions"></div> +<if expr="pp_ifdef('chromeos')"> + <div i18n-content="input_method_instructions"></div> +</if> + </div> + <div class="language-options"> + <div class="language-options-left"> + <h3 i18n-content="languages"></h3> + <list id="language-options-list"></list> + <div class="language-options-lower-left"> + <button id="language-options-add-button" + i18n-content="add_button"></button> + </div> + <div id="language-options-list-dropmarker"></div> + </div> + <div class="language-options-right"> + <h3 id="language-options-language-name"></h3> +<if expr="os == 'win32' or pp_ifdef('chromeos')"> + <div class="language-options-contents"> + <button id="language-options-ui-language-button"></button> + </div> +</if> + <div class="language-options-contents"> + <button id="language-options-spell-check-language-button"></button> + </div> + <div id="language-options-ui-notification-bar" + class="language-options-notification"> + <div i18n-content="restart_required"></div> +<if expr="pp_ifdef('chromeos')"> + <button id="language-options-ui-restart-button" + i18n-content="restart_button"></button> +</if> + </div> +<if expr="pp_ifdef('chromeos')"> + <h3 i18n-content="input_method"></h3> + <div id="language-options-input-method-list" + class="language-options-contents"> + </div> +</if> +<if expr="pp_ifdef('chromeos') and pp_ifdef('use_virtual_keyboard')"> + <div class="language-options-contents"> + <button id="language-options-virtual-keyboard" + i18n-content="virtual_keyboard_button"></button> + </div> +</if> + </div> + </div> + <div class="language-options-footer"> +<if expr="pp_ifdef('chromeos')"> + <div i18n-content="switch_input_methods_hint"></div> + <div i18n-content="select_previous_input_method_hint"></div> +</if> +<if expr="not pp_ifdef('chromeos') and not is_macosx"> + <div id="spell-check-option" class="checkbox"> + <label> + <input id="enable-spell-check" pref="browser.enable_spellchecking" + metric="Options_SpellCheck" type="checkbox"> + <span i18n-content="enable_spell_check"></span> + </label> + </div> + <div id="auto-spell-correction-option" class="checkbox" hidden> + <label> + <input id="enable-auto-spell-correction" + pref="browser.enable_autospellcorrect" + metric="Options_AutoSpellCorrection" type="checkbox"> + <span i18n-content="enable_auto_spell_correction"></span> + </label> + </div> +</if> + </div> +</div> diff --git a/chrome/browser/resources/options2/language_options.js b/chrome/browser/resources/options2/language_options.js new file mode 100644 index 0000000..47674ed --- /dev/null +++ b/chrome/browser/resources/options2/language_options.js @@ -0,0 +1,815 @@ +// Copyright (c) 2011 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. + +// TODO(kochi): Generalize the notification as a component and put it +// in js/cr/ui/notification.js . + +cr.define('options', function() { + const OptionsPage = options.OptionsPage; + const LanguageList = options.LanguageList; + + // Some input methods like Chinese Pinyin have config pages. + // This is the map of the input method names to their config page names. + const INPUT_METHOD_ID_TO_CONFIG_PAGE_NAME = { + 'mozc': 'languageMozc', + 'mozc-chewing': 'languageChewing', + 'mozc-dv': 'languageMozc', + 'mozc-hangul': 'languageHangul', + 'mozc-jp': 'languageMozc', + 'pinyin': 'languagePinyin', + 'pinyin-dv': 'languagePinyin', + }; + + ///////////////////////////////////////////////////////////////////////////// + // LanguageOptions class: + + /** + * Encapsulated handling of ChromeOS language options page. + * @constructor + */ + function LanguageOptions(model) { + OptionsPage.call(this, 'languages', templateData.languagePageTabTitle, + 'languagePage'); + } + + cr.addSingletonGetter(LanguageOptions); + + // Inherit LanguageOptions from OptionsPage. + LanguageOptions.prototype = { + __proto__: OptionsPage.prototype, + + /** + * Initializes LanguageOptions page. + * Calls base class implementation to starts preference initialization. + */ + initializePage: function() { + OptionsPage.prototype.initializePage.call(this); + + var languageOptionsList = $('language-options-list'); + LanguageList.decorate(languageOptionsList); + + languageOptionsList.addEventListener('change', + this.handleLanguageOptionsListChange_.bind(this)); + languageOptionsList.addEventListener('save', + this.handleLanguageOptionsListSave_.bind(this)); + + this.addEventListener('visibleChange', + this.handleVisibleChange_.bind(this)); + + if (cr.isChromeOS) { + this.initializeInputMethodList_(); + this.initializeLanguageCodeToInputMethodIdsMap_(); + } + Preferences.getInstance().addEventListener(this.spellCheckDictionaryPref, + this.handleSpellCheckDictionaryPrefChange_.bind(this)); + + // Set up add button. + $('language-options-add-button').onclick = function(e) { + // Add the language without showing the overlay if it's specified in + // the URL hash (ex. lang_add=ja). Used for automated testing. + var match = document.location.hash.match(/\blang_add=([\w-]+)/); + if (match) { + var addLanguageCode = match[1]; + $('language-options-list').addLanguage(addLanguageCode); + } else { + OptionsPage.navigateToPage('addLanguage'); + } + }; + + if (cr.isChromeOS) { + // Listen to user clicks on the add language list. + var addLanguageList = $('add-language-overlay-language-list'); + addLanguageList.addEventListener('click', + this.handleAddLanguageListClick_.bind(this)); + } else { + // Listen to add language dialog ok button. + var addLanguageOkButton = $('add-language-overlay-ok-button'); + addLanguageOkButton.addEventListener('click', + this.handleAddLanguageOkButtonClick_.bind(this)); + + // Show experimental features if enabled. + if (templateData.experimentalSpellCheckFeatures == 'true') + $('auto-spell-correction-option').hidden = false; + + // Handle spell check enable/disable. + if (!cr.isMac) { + Preferences.getInstance().addEventListener( + this.enableSpellCheckPref, + this.updateEnableSpellCheck_.bind(this)); + } + } + + // Listen to user clicks on the "Change touch keyboard settings..." + // button (if it exists). + var virtualKeyboardButton = $('language-options-virtual-keyboard'); + if (virtualKeyboardButton) { + // TODO(yusukes): would be better to hide the button if no virtual + // keyboard is registered. + virtualKeyboardButton.onclick = function(e) { + OptionsPage.navigateToPage('virtualKeyboards'); + }; + } + }, + + // The preference is a boolean that enables/disables spell checking. + enableSpellCheckPref: 'browser.enable_spellchecking', + // The preference is a CSV string that describes preload engines + // (i.e. active input methods). + preloadEnginesPref: 'settings.language.preload_engines', + // The list of preload engines, like ['mozc', 'pinyin']. + preloadEngines_: [], + // The preference is a string that describes the spell check + // dictionary language, like "en-US". + spellCheckDictionaryPref: 'spellcheck.dictionary', + spellCheckDictionary_: "", + // The map of language code to input method IDs, like: + // {'ja': ['mozc', 'mozc-jp'], 'zh-CN': ['pinyin'], ...} + languageCodeToInputMethodIdsMap_: {}, + + /** + * Initializes the input method list. + */ + initializeInputMethodList_: function() { + var inputMethodList = $('language-options-input-method-list'); + var inputMethodListData = templateData.inputMethodList; + + // Add all input methods, but make all of them invisible here. We'll + // change the visibility in handleLanguageOptionsListChange_() based + // on the selected language. Note that we only have less than 100 + // input methods, so creating DOM nodes at once here should be ok. + for (var i = 0; i < inputMethodListData.length; i++) { + var inputMethod = inputMethodListData[i]; + var input = document.createElement('input'); + input.type = 'checkbox'; + input.inputMethodId = inputMethod.id; + // Listen to user clicks. + input.addEventListener('click', + this.handleCheckboxClick_.bind(this)); + var label = document.createElement('label'); + label.appendChild(input); + // Adding a space between the checkbox and the text. This is a bit + // dirty, but we rely on a space character for all other checkboxes. + label.appendChild(document.createTextNode( + ' ' + inputMethod.displayName)); + label.style.display = 'none'; + label.languageCodeSet = inputMethod.languageCodeSet; + // Add the configure button if the config page is present for this + // input method. + if (inputMethod.id in INPUT_METHOD_ID_TO_CONFIG_PAGE_NAME) { + var pageName = INPUT_METHOD_ID_TO_CONFIG_PAGE_NAME[inputMethod.id]; + var button = this.createConfigureInputMethodButton_(inputMethod.id, + pageName); + label.appendChild(button); + } + + inputMethodList.appendChild(label); + } + // Listen to pref change once the input method list is initialized. + Preferences.getInstance().addEventListener(this.preloadEnginesPref, + this.handlePreloadEnginesPrefChange_.bind(this)); + }, + + /** + * Creates a configure button for the given input method ID. + * @param {string} inputMethodId Input method ID (ex. "pinyin"). + * @param {string} pageName Name of the config page (ex. "languagePinyin"). + * @private + */ + createConfigureInputMethodButton_: function(inputMethodId, pageName) { + var button = document.createElement('button'); + button.textContent = localStrings.getString('configure'); + button.onclick = function(e) { + // Prevent the default action (i.e. changing the checked property + // of the checkbox). The button click here should not be handled + // as checkbox click. + e.preventDefault(); + chrome.send('inputMethodOptionsOpen', [inputMethodId]); + OptionsPage.navigateToPage(pageName); + } + return button; + }, + + /** + * Handles OptionsPage's visible property change event. + * @param {Event} e Property change event. + * @private + */ + handleVisibleChange_: function(e) { + if (this.visible) { + $('language-options-list').redraw(); + chrome.send('languageOptionsOpen'); + } + }, + + /** + * Handles languageOptionsList's change event. + * @param {Event} e Change event. + * @private + */ + handleLanguageOptionsListChange_: function(e) { + var languageOptionsList = $('language-options-list'); + var languageCode = languageOptionsList.getSelectedLanguageCode(); + // Select the language if it's specified in the URL hash (ex. lang=ja). + // Used for automated testing. + var match = document.location.hash.match(/\blang=([\w-]+)/); + if (match) { + var specifiedLanguageCode = match[1]; + if (languageOptionsList.selectLanguageByCode(specifiedLanguageCode)) { + languageCode = specifiedLanguageCode; + } + } + this.updateSelectedLanguageName_(languageCode); + if (cr.isWindows || cr.isChromeOS) + this.updateUiLanguageButton_(languageCode); + if (!cr.isMac) + this.updateSpellCheckLanguageButton_(languageCode); + if (cr.isChromeOS) + this.updateInputMethodList_(languageCode); + this.updateLanguageListInAddLanguageOverlay_(); + }, + + /** + * Handles languageOptionsList's save event. + * @param {Event} e Save event. + * @private + */ + handleLanguageOptionsListSave_: function(e) { + if (cr.isChromeOS) { + // Sort the preload engines per the saved languages before save. + this.preloadEngines_ = this.sortPreloadEngines_(this.preloadEngines_); + this.savePreloadEnginesPref_(); + } + }, + + /** + * Sorts preloadEngines_ by languageOptionsList's order. + * @param {Array} preloadEngines List of preload engines. + * @return {Array} Returns sorted preloadEngines. + * @private + */ + sortPreloadEngines_: function(preloadEngines) { + // For instance, suppose we have two languages and associated input + // methods: + // + // - Korean: hangul + // - Chinese: pinyin + // + // The preloadEngines preference should look like "hangul,pinyin". + // If the user reverse the order, the preference should be reorderd + // to "pinyin,hangul". + var languageOptionsList = $('language-options-list'); + var languageCodes = languageOptionsList.getLanguageCodes(); + + // Convert the list into a dictonary for simpler lookup. + var preloadEngineSet = {}; + for (var i = 0; i < preloadEngines.length; i++) { + preloadEngineSet[preloadEngines[i]] = true; + } + + // Create the new preload engine list per the language codes. + var newPreloadEngines = []; + for (var i = 0; i < languageCodes.length; i++) { + var languageCode = languageCodes[i]; + var inputMethodIds = this.languageCodeToInputMethodIdsMap_[ + languageCode]; + // Check if we have active input methods associated with the language. + for (var j = 0; j < inputMethodIds.length; j++) { + var inputMethodId = inputMethodIds[j]; + if (inputMethodId in preloadEngineSet) { + // If we have, add it to the new engine list. + newPreloadEngines.push(inputMethodId); + // And delete it from the set. This is necessary as one input + // method can be associated with more than one language thus + // we should avoid having duplicates in the new list. + delete preloadEngineSet[inputMethodId]; + } + } + } + + return newPreloadEngines; + }, + + /** + * Initializes the map of language code to input method IDs. + * @private + */ + initializeLanguageCodeToInputMethodIdsMap_: function() { + var inputMethodList = templateData.inputMethodList; + for (var i = 0; i < inputMethodList.length; i++) { + var inputMethod = inputMethodList[i]; + for (var languageCode in inputMethod.languageCodeSet) { + if (languageCode in this.languageCodeToInputMethodIdsMap_) { + this.languageCodeToInputMethodIdsMap_[languageCode].push( + inputMethod.id); + } else { + this.languageCodeToInputMethodIdsMap_[languageCode] = + [inputMethod.id]; + } + } + } + }, + + /** + * Updates the currently selected language name. + * @param {string} languageCode Language code (ex. "fr"). + * @private + */ + updateSelectedLanguageName_: function(languageCode) { + var languageDisplayName = LanguageList.getDisplayNameFromLanguageCode( + languageCode); + var languageNativeDisplayName = + LanguageList.getNativeDisplayNameFromLanguageCode(languageCode); + // If the native name is different, add it. + if (languageDisplayName != languageNativeDisplayName) { + languageDisplayName += ' - ' + languageNativeDisplayName; + } + // Update the currently selected language name. + var languageName = $('language-options-language-name'); + if (languageDisplayName) { + languageName.hidden = false; + languageName.textContent = languageDisplayName; + } else { + languageName.hidden = true; + } + }, + + /** + * Updates the UI language button. + * @param {string} languageCode Language code (ex. "fr"). + * @private + */ + updateUiLanguageButton_: function(languageCode) { + var uiLanguageButton = $('language-options-ui-language-button'); + // Check if the language code matches the current UI language. + if (languageCode == templateData.currentUiLanguageCode) { + // If it matches, the button just says that the UI language is + // currently in use. + uiLanguageButton.textContent = + localStrings.getString('is_displayed_in_this_language'); + // Make it look like a text label. + uiLanguageButton.className = 'text-button'; + // Remove the event listner. + uiLanguageButton.onclick = undefined; + } else if (languageCode in templateData.uiLanguageCodeSet) { + // If the language is supported as UI language, users can click on + // the button to change the UI language. + if (cr.commandLine && cr.commandLine.options['--bwsi']) { + // In the guest mode for ChromeOS, changing UI language does not make + // sense because it does not take effect after browser restart. + uiLanguageButton.hidden = true; + } else { + uiLanguageButton.textContent = + localStrings.getString('display_in_this_language'); + uiLanguageButton.className = ''; + // Send the change request to Chrome. + uiLanguageButton.onclick = function(e) { + chrome.send('uiLanguageChange', [languageCode]); + } + } + if (cr.isChromeOS) { + $('language-options-ui-restart-button').onclick = function(e) { + chrome.send('uiLanguageRestart'); + } + } + } else { + // If the language is not supported as UI language, the button + // just says that Chromium OS cannot be displayed in this language. + uiLanguageButton.textContent = + localStrings.getString('cannot_be_displayed_in_this_language'); + uiLanguageButton.className = 'text-button'; + uiLanguageButton.onclick = undefined; + } + uiLanguageButton.style.display = 'block'; + $('language-options-ui-notification-bar').style.display = 'none'; + }, + + /** + * Updates the spell check language button. + * @param {string} languageCode Language code (ex. "fr"). + * @private + */ + updateSpellCheckLanguageButton_: function(languageCode) { + var display = 'block'; + var spellCheckLanguageButton = $( + 'language-options-spell-check-language-button'); + // Check if the language code matches the current spell check language. + if (languageCode == this.spellCheckDictionary_) { + // If it matches, the button just says that the spell check language is + // currently in use. + spellCheckLanguageButton.textContent = + localStrings.getString('is_used_for_spell_checking'); + // Make it look like a text label. + spellCheckLanguageButton.className = 'text-button'; + // Remove the event listner. + spellCheckLanguageButton.onclick = undefined; + } else if (languageCode in templateData.spellCheckLanguageCodeSet) { + // If the language is supported as spell check language, users can + // click on the button to change the spell check language. + spellCheckLanguageButton.textContent = + localStrings.getString('use_this_for_spell_checking'); + spellCheckLanguageButton.className = ''; + spellCheckLanguageButton.languageCode = languageCode; + // Add an event listner to the click event. + spellCheckLanguageButton.addEventListener('click', + this.handleSpellCheckLanguageButtonClick_.bind(this)); + } else if (!languageCode) { + display = 'none'; + } else { + // If the language is not supported as spell check language, the + // button just says that this language cannot be used for spell + // checking. + spellCheckLanguageButton.textContent = + localStrings.getString('cannot_be_used_for_spell_checking'); + spellCheckLanguageButton.className = 'text-button'; + spellCheckLanguageButton.onclick = undefined; + } + spellCheckLanguageButton.style.display = display; + $('language-options-ui-notification-bar').style.display = 'none'; + }, + + /** + * Updates the input method list. + * @param {string} languageCode Language code (ex. "fr"). + * @private + */ + updateInputMethodList_: function(languageCode) { + // Give one of the checkboxes or buttons focus, if it's specified in the + // URL hash (ex. focus=mozc). Used for automated testing. + var focusInputMethodId = -1; + var match = document.location.hash.match(/\bfocus=([\w:-]+)\b/); + if (match) { + focusInputMethodId = match[1]; + } + // Change the visibility of the input method list. Input methods that + // matches |languageCode| will become visible. + var inputMethodList = $('language-options-input-method-list'); + var labels = inputMethodList.querySelectorAll('label'); + for (var i = 0; i < labels.length; i++) { + var label = labels[i]; + if (languageCode in label.languageCodeSet) { + label.style.display = 'block'; + var input = label.childNodes[0]; + // Give it focus if the ID matches. + if (input.inputMethodId == focusInputMethodId) { + input.focus(); + } + } else { + label.style.display = 'none'; + } + } + + if (focusInputMethodId == 'add') { + $('language-options-add-button').focus(); + } + }, + + /** + * Updates the language list in the add language overlay. + * @param {string} languageCode Language code (ex. "fr"). + * @private + */ + updateLanguageListInAddLanguageOverlay_: function(languageCode) { + // Change the visibility of the language list in the add language + // overlay. Languages that are already active will become invisible, + // so that users don't add the same language twice. + var languageOptionsList = $('language-options-list'); + var languageCodes = languageOptionsList.getLanguageCodes(); + var languageCodeSet = {}; + for (var i = 0; i < languageCodes.length; i++) { + languageCodeSet[languageCodes[i]] = true; + } + var addLanguageList = $('add-language-overlay-language-list'); + var lis = addLanguageList.querySelectorAll('li'); + for (var i = 0; i < lis.length; i++) { + // The first child button knows the language code. + var button = lis[i].childNodes[0]; + if (button.languageCode in languageCodeSet) { + lis[i].style.display = 'none'; + } else { + lis[i].style.display = 'block'; + } + } + }, + + /** + * Handles preloadEnginesPref change. + * @param {Event} e Change event. + * @private + */ + handlePreloadEnginesPrefChange_: function(e) { + var value = e.value.value; + this.preloadEngines_ = this.filterBadPreloadEngines_(value.split(',')); + this.updateCheckboxesFromPreloadEngines_(); + $('language-options-list').updateDeletable(); + }, + + /** + * Handles input method checkbox's click event. + * @param {Event} e Click event. + * @private + */ + handleCheckboxClick_ : function(e) { + var checkbox = e.target; + if (this.preloadEngines_.length == 1 && !checkbox.checked) { + // Don't allow disabling the last input method. + this.showNotification_( + localStrings.getString('please_add_another_input_method'), + localStrings.getString('ok_button')); + checkbox.checked = true; + return; + } + if (checkbox.checked) { + chrome.send('inputMethodEnable', [checkbox.inputMethodId]); + } else { + chrome.send('inputMethodDisable', [checkbox.inputMethodId]); + } + this.updatePreloadEnginesFromCheckboxes_(); + this.preloadEngines_ = this.sortPreloadEngines_(this.preloadEngines_); + this.savePreloadEnginesPref_(); + }, + + /** + * Handles add language list's click event. + * @param {Event} e Click event. + */ + handleAddLanguageListClick_ : function(e) { + var languageOptionsList = $('language-options-list'); + var languageCode = e.target.languageCode; + // languageCode can be undefined, if click was made on some random + // place in the overlay, rather than a button. Ignore it. + if (!languageCode) { + return; + } + languageOptionsList.addLanguage(languageCode); + var inputMethodIds = this.languageCodeToInputMethodIdsMap_[languageCode]; + // Enable the first input method for the language added. + if (inputMethodIds && inputMethodIds[0] && + // Don't add the input method it's already present. This can + // happen if the same input method is shared among multiple + // languages (ex. English US keyboard is used for English US and + // Filipino). + this.preloadEngines_.indexOf(inputMethodIds[0]) == -1) { + this.preloadEngines_.push(inputMethodIds[0]); + this.updateCheckboxesFromPreloadEngines_(); + this.savePreloadEnginesPref_(); + } + OptionsPage.closeOverlay(); + }, + + /** + * Handles add language dialog ok button. + */ + handleAddLanguageOkButtonClick_ : function() { + var languagesSelect = $('add-language-overlay-language-list'); + var selectedIndex = languagesSelect.selectedIndex; + if (selectedIndex >= 0) { + var selection = languagesSelect.options[selectedIndex]; + $('language-options-list').addLanguage(String(selection.value)); + OptionsPage.closeOverlay(); + } + }, + + /** + * Checks if languageCode is deletable or not. + * @param {String} languageCode the languageCode to check for deletability. + */ + languageIsDeletable: function(languageCode) { + // Don't allow removing the language if it's as UI language. + if (languageCode == templateData.currentUiLanguageCode) + return false; + return (!cr.isChromeOS || + this.canDeleteLanguage_(languageCode)); + }, + + /** + * Handles browse.enable_spellchecking change. + * @param {Event} e Change event. + * @private + */ + updateEnableSpellCheck_: function() { + var value = !$('enable-spell-check').checked; + + $('language-options-spell-check-language-button').disabled = value; + }, + + /** + * Handles spellCheckDictionaryPref change. + * @param {Event} e Change event. + * @private + */ + handleSpellCheckDictionaryPrefChange_: function(e) { + var languageCode = e.value.value + this.spellCheckDictionary_ = languageCode; + var languageOptionsList = $('language-options-list'); + var selectedLanguageCode = languageOptionsList.getSelectedLanguageCode(); + this.updateSpellCheckLanguageButton_(selectedLanguageCode); + }, + + /** + * Handles spellCheckLanguageButton click. + * @param {Event} e Click event. + * @private + */ + handleSpellCheckLanguageButtonClick_: function(e) { + var languageCode = e.target.languageCode; + // Save the preference. + Preferences.setStringPref(this.spellCheckDictionaryPref, + languageCode); + chrome.send('spellCheckLanguageChange', [languageCode]); + }, + + /** + * Checks whether it's possible to remove the language specified by + * languageCode and returns true if possible. This function returns false + * if the removal causes the number of preload engines to be zero. + * + * @param {string} languageCode Language code (ex. "fr"). + * @return {boolean} Returns true on success. + * @private + */ + canDeleteLanguage_: function(languageCode) { + // First create the set of engines to be removed from input methods + // associated with the language code. + var enginesToBeRemovedSet = {}; + var inputMethodIds = this.languageCodeToInputMethodIdsMap_[languageCode]; + for (var i = 0; i < inputMethodIds.length; i++) { + enginesToBeRemovedSet[inputMethodIds[i]] = true; + } + + // Then eliminate engines that are also used for other active languages. + // For instance, if "xkb:us::eng" is used for both English and Filipino. + var languageCodes = $('language-options-list').getLanguageCodes(); + for (var i = 0; i < languageCodes.length; i++) { + // Skip the target language code. + if (languageCodes[i] == languageCode) { + continue; + } + // Check if input methods used in this language are included in + // enginesToBeRemovedSet. If so, eliminate these from the set, so + // we don't remove this time. + var inputMethodIdsForAnotherLanguage = + this.languageCodeToInputMethodIdsMap_[languageCodes[i]]; + for (var j = 0; j < inputMethodIdsForAnotherLanguage.length; j++) { + var inputMethodId = inputMethodIdsForAnotherLanguage[j]; + if (inputMethodId in enginesToBeRemovedSet) { + delete enginesToBeRemovedSet[inputMethodId]; + } + } + } + + // Update the preload engine list with the to-be-removed set. + var newPreloadEngines = []; + for (var i = 0; i < this.preloadEngines_.length; i++) { + if (!(this.preloadEngines_[i] in enginesToBeRemovedSet)) { + newPreloadEngines.push(this.preloadEngines_[i]); + } + } + // Don't allow this operation if it causes the number of preload + // engines to be zero. + return (newPreloadEngines.length > 0); + }, + + /** + * Saves the preload engines preference. + * @private + */ + savePreloadEnginesPref_: function() { + Preferences.setStringPref(this.preloadEnginesPref, + this.preloadEngines_.join(',')); + }, + + /** + * Updates the checkboxes in the input method list from the preload + * engines preference. + * @private + */ + updateCheckboxesFromPreloadEngines_: function() { + // Convert the list into a dictonary for simpler lookup. + var dictionary = {}; + for (var i = 0; i < this.preloadEngines_.length; i++) { + dictionary[this.preloadEngines_[i]] = true; + } + + var inputMethodList = $('language-options-input-method-list'); + var checkboxes = inputMethodList.querySelectorAll('input'); + for (var i = 0; i < checkboxes.length; i++) { + checkboxes[i].checked = (checkboxes[i].inputMethodId in dictionary); + } + }, + + /** + * Updates the preload engines preference from the checkboxes in the + * input method list. + * @private + */ + updatePreloadEnginesFromCheckboxes_: function() { + this.preloadEngines_ = []; + var inputMethodList = $('language-options-input-method-list'); + var checkboxes = inputMethodList.querySelectorAll('input'); + for (var i = 0; i < checkboxes.length; i++) { + if (checkboxes[i].checked) { + this.preloadEngines_.push(checkboxes[i].inputMethodId); + } + } + var languageOptionsList = $('language-options-list'); + languageOptionsList.updateDeletable(); + }, + + /** + * Filters bad preload engines in case bad preload engines are + * stored in the preference. Removes duplicates as well. + * @param {Array} preloadEngines List of preload engines. + * @private + */ + filterBadPreloadEngines_: function(preloadEngines) { + // Convert the list into a dictonary for simpler lookup. + var dictionary = {}; + for (var i = 0; i < templateData.inputMethodList.length; i++) { + dictionary[templateData.inputMethodList[i].id] = true; + } + + var filteredPreloadEngines = []; + var seen = {}; + for (var i = 0; i < preloadEngines.length; i++) { + // Check if the preload engine is present in the + // dictionary, and not duplicate. Otherwise, skip it. + if (preloadEngines[i] in dictionary && !(preloadEngines[i] in seen)) { + filteredPreloadEngines.push(preloadEngines[i]); + seen[preloadEngines[i]] = true; + } + } + return filteredPreloadEngines; + }, + + // TODO(kochi): This is an adapted copy from new_tab.js. + // If this will go as final UI, refactor this to share the component with + // new new tab page. + /** + * Shows notification + * @private + */ + notificationTimeout_: null, + showNotification_ : function(text, actionText, opt_delay) { + var notificationElement = $('notification'); + var actionLink = notificationElement.querySelector('.link-color'); + var delay = opt_delay || 10000; + + function show() { + window.clearTimeout(this.notificationTimeout_); + notificationElement.classList.add('show'); + document.body.classList.add('notification-shown'); + } + + function hide() { + window.clearTimeout(this.notificationTimeout_); + notificationElement.classList.remove('show'); + document.body.classList.remove('notification-shown'); + // Prevent tabbing to the hidden link. + actionLink.tabIndex = -1; + // Setting tabIndex to -1 only prevents future tabbing to it. If, + // however, the user switches window or a tab and then moves back to + // this tab the element may gain focus. We therefore make sure that we + // blur the element so that the element focus is not restored when + // coming back to this window. + actionLink.blur(); + } + + function delayedHide() { + this.notificationTimeout_ = window.setTimeout(hide, delay); + } + + notificationElement.firstElementChild.textContent = text; + actionLink.textContent = actionText; + + actionLink.onclick = hide; + actionLink.onkeydown = function(e) { + if (e.keyIdentifier == 'Enter') { + hide(); + } + }; + notificationElement.onmouseover = show; + notificationElement.onmouseout = delayedHide; + actionLink.onfocus = show; + actionLink.onblur = delayedHide; + // Enable tabbing to the link now that it is shown. + actionLink.tabIndex = 0; + + show(); + delayedHide(); + } + }; + + /** + * Chrome callback for when the UI language preference is saved. + */ + LanguageOptions.uiLanguageSaved = function() { + $('language-options-ui-language-button').style.display = 'none'; + $('language-options-ui-notification-bar').style.display = 'block'; + }; + + // Export + return { + LanguageOptions: LanguageOptions + }; +}); diff --git a/chrome/browser/resources/options2/manage_profile_overlay.css b/chrome/browser/resources/options2/manage_profile_overlay.css new file mode 100644 index 0000000..aa17290 --- /dev/null +++ b/chrome/browser/resources/options2/manage_profile_overlay.css @@ -0,0 +1,88 @@ +/* Copyright (c) 2011 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. + */ + +#manage-profile-overlay { + width: 500px; +} + +.profile-icon-grid-item { + border: none !important; + height: 31px; + margin: 4px 6px; + padding: 4px; + width: 38px; +} + +.profile-icon { + height: 31px; + width: 38px; +} + +#manage-profile-content > :first-child { + margin-bottom: 5px; +} + +#manage-profile-content > :last-child { + margin-top: 5px; +} + +#manage-profile-content > :not(:first-child):not(:last-child) { + margin-top: 5px; + margin-bottom: 5px; +} + +#manage-profile-name-div { + -webkit-box-align: baseline; + -webkit-box-orient: horizontal; + display: -webkit-box; +} + +#manage-profile-name-label { + -webkit-margin-end: 20px; +} + +#manage-profile-name { + -webkit-box-flex: 1; + display: block; +} + +#manage-profile-name:invalid { + background-color: pink; +} + +#manage-profile-error-bubble { + -webkit-transition: max-height 200ms, padding 200ms; + background-color: #eeb939; + border-radius: 4px; + font-weight: bold; + margin-left: auto; + margin-right: auto; + max-height: 50px; + overflow: hidden; + padding: 1px 10px; + text-align: center; + width: 80%; +} + +#manage-profile-error-bubble[hidden] { + display: block !important; + max-height: 0; + padding: 0 10px; +} + +#manage-profile-icon-grid { + background-color: rgba(255, 255, 255, 0.75); + border: 1px solid rgba(0, 0, 0, 0.3); + padding: 2px; +} + +#delete-profile-message { + background-repeat: no-repeat; + -webkit-padding-start: 48px; +} + +html[dir='rtl'] #delete-profile-message { + background-position: right; +}
\ No newline at end of file diff --git a/chrome/browser/resources/options2/manage_profile_overlay.html b/chrome/browser/resources/options2/manage_profile_overlay.html new file mode 100644 index 0000000..afc4801 --- /dev/null +++ b/chrome/browser/resources/options2/manage_profile_overlay.html @@ -0,0 +1,38 @@ +<div id="manage-profile-overlay" class="page" hidden> + <!-- Dialog for managing profiles. --> + <div id="manage-profile-overlay-manage" hidden> + <h1 i18n-content="manageProfilesTitle"></h1> + <div id="manage-profile-content" class="content-area"> + <div id="manage-profile-name-div"> + <span id="manage-profile-name-label" + i18n-content="manageProfilesNameLabel"></span> + <input id="manage-profile-name" type="text" required> + </div> + <div id="manage-profile-error-bubble" hidden> + </div> + <div id="manage-profile-icon-label" + i18n-content="manageProfilesIconLabel"></div> + <grid id="manage-profile-icon-grid"></grid> + </div> + <div class="action-area"> + <div class="button-strip"> + <button id="manage-profile-cancel" i18n-content="cancel"></button> + <button id="manage-profile-ok" i18n-content="ok"></button> + </div> + </div> + </div> + <!-- Dialog for deleting profiles. --> + <div id="manage-profile-overlay-delete" hidden> + <h1 i18n-content="deleteProfileTitle"></h1> + <div class="content-area"> + <div id="delete-profile-message"></div> + </div> + <div class="action-area"> + <div class="button-strip"> + <button id="delete-profile-cancel" i18n-content="cancel"></button> + <button id="delete-profile-ok" + i18n-content="deleteProfileOK"></button> + </div> + </div> + </div> +</div> diff --git a/chrome/browser/resources/options2/manage_profile_overlay.js b/chrome/browser/resources/options2/manage_profile_overlay.js new file mode 100644 index 0000000..18cccf2 --- /dev/null +++ b/chrome/browser/resources/options2/manage_profile_overlay.js @@ -0,0 +1,265 @@ +// Copyright (c) 2011 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; + + const localStrings = new LocalStrings(); + + /** + * ManageProfileOverlay class + * Encapsulated handling of the 'Manage profile...' overlay page. + * @constructor + * @class + */ + function ManageProfileOverlay() { + OptionsPage.call(this, + 'manageProfile', + templateData.manageProfileOverlayTabTitle, + 'manage-profile-overlay'); + }; + + cr.addSingletonGetter(ManageProfileOverlay); + + ManageProfileOverlay.prototype = { + // Inherit from OptionsPage. + __proto__: OptionsPage.prototype, + + // Info about the currently managed/deleted profile. + profileInfo_: null, + + // An object containing all known profile names. + profileNames_: {}, + + // The currently selected icon in the icon grid. + iconGridSelectedURL_: null, + + /** + * Initialize the page. + */ + initializePage: function() { + // Call base class implementation to start preference initialization. + OptionsPage.prototype.initializePage.call(this); + + var self = this; + var iconGrid = $('manage-profile-icon-grid'); + options.ProfilesIconGrid.decorate(iconGrid); + iconGrid.addEventListener('change', function(e) { + self.onIconGridSelectionChanged_(); + }); + + $('manage-profile-name').oninput = this.onNameChanged_.bind(this); + $('manage-profile-cancel').onclick = + $('delete-profile-cancel').onclick = function(event) { + OptionsPage.closeOverlay(); + }; + $('manage-profile-ok').onclick = function(event) { + OptionsPage.closeOverlay(); + self.submitManageChanges_(); + }; + $('delete-profile-ok').onclick = function(event) { + OptionsPage.closeOverlay(); + chrome.send('deleteProfile', [self.profileInfo_.filePath]); + }; + }, + + /** @inheritDoc */ + didShowPage: function() { + chrome.send('requestDefaultProfileIcons'); + + // Use the hash to specify the profile index. + var hash = location.hash; + if (hash) { + $('manage-profile-overlay-manage').hidden = false; + $('manage-profile-overlay-delete').hidden = true; + ManageProfileOverlay.getInstance().hideErrorBubble_(); + + chrome.send('requestProfileInfo', [parseInt(hash.slice(1), 10)]); + } + + $('manage-profile-name').focus(); + }, + + /** + * Set the profile info used in the dialog. + * @param {Object} profileInfo An object of the form: + * profileInfo = { + * name: "Profile Name", + * iconURL: "chrome://path/to/icon/image", + * filePath: "/path/to/profile/data/on/disk" + * isCurrentProfile: false, + * }; + * @private + */ + setProfileInfo_: function(profileInfo) { + this.iconGridSelectedURL_ = profileInfo.iconURL; + this.profileInfo_ = profileInfo; + $('manage-profile-name').value = profileInfo.name; + $('manage-profile-icon-grid').selectedItem = profileInfo.iconURL; + }, + + /** + * Sets the name of the currently edited profile. + * @private + */ + setProfileName_: function(name) { + if (this.profileInfo_) + this.profileInfo_.name = name; + $('manage-profile-name').value = name; + }, + + /** + * Set an array of default icon URLs. These will be added to the grid that + * the user will use to choose their profile icon. + * @param {Array.<string>} iconURLs An array of icon URLs. + * @private + */ + receiveDefaultProfileIcons_: function(iconURLs) { + $('manage-profile-icon-grid').dataModel = new ArrayDataModel(iconURLs); + + // Changing the dataModel resets the selectedItem. Re-select it, if there + // is one. + if (this.profileInfo_) + $('manage-profile-icon-grid').selectedItem = this.profileInfo_.iconURL; + + var grid = $('manage-profile-icon-grid'); + // Recalculate the measured item size. + grid.measured_ = null; + grid.columns = 0; + grid.redraw(); + }, + + /** + * Set a dictionary of all profile names. These are used to prevent the + * user from naming two profiles the same. + * @param {Object} profileNames A dictionary of profile names. + * @private + */ + receiveProfileNames_: function(profileNames) { + this.profileNames_ = profileNames; + }, + + /** + * Display the error bubble, with |errorText| in the bubble. + * @param {string} errorText The localized string id to display as an error. + * @private + */ + showErrorBubble_: function(errorText) { + var nameErrorEl = $('manage-profile-error-bubble'); + nameErrorEl.hidden = false; + nameErrorEl.textContent = localStrings.getString(errorText); + + $('manage-profile-ok').disabled = true; + }, + + /** + * Hide the error bubble. + * @private + */ + hideErrorBubble_: function() { + $('manage-profile-error-bubble').hidden = true; + $('manage-profile-ok').disabled = false; + }, + + /** + * oninput callback for <input> field. + * @param event The event object + * @private + */ + onNameChanged_: function(event) { + var newName = event.target.value; + var oldName = this.profileInfo_.name; + + if (newName == oldName) { + this.hideErrorBubble_(); + } else if (this.profileNames_[newName] != undefined) { + this.showErrorBubble_('manageProfilesDuplicateNameError'); + } else { + this.hideErrorBubble_(); + + var nameIsValid = $('manage-profile-name').validity.valid; + $('manage-profile-ok').disabled = !nameIsValid; + } + }, + + /** + * Called when the user clicks "OK". Saves the newly changed profile info. + * @private + */ + submitManageChanges_: function() { + var name = $('manage-profile-name').value; + var iconURL = $('manage-profile-icon-grid').selectedItem; + chrome.send('setProfileNameAndIcon', + [this.profileInfo_.filePath, name, iconURL]); + }, + + /** + * Called when the selected icon in the icon grid changes. + * @private + */ + onIconGridSelectionChanged_: function() { + var iconURL = $('manage-profile-icon-grid').selectedItem; + if (!iconURL || iconURL == this.iconGridSelectedURL_) + return; + this.iconGridSelectedURL_ = iconURL; + chrome.send('profileIconSelectionChanged', + [this.profileInfo_.filePath, iconURL]); + }, + + /** + * Display the "Manage Profile" dialog. + * @param {Object} profileInfo The profile object of the profile to manage. + * @private + */ + showManageDialog_: function(profileInfo) { + ManageProfileOverlay.setProfileInfo(profileInfo); + $('manage-profile-overlay-manage').hidden = false; + $('manage-profile-overlay-delete').hidden = true; + ManageProfileOverlay.getInstance().hideErrorBubble_(); + + // Intentionally don't show the URL in the location bar as we don't want + // people trying to navigate here by hand. + OptionsPage.showPageByName('manageProfile', false); + }, + + /** + * Display the "Delete Profile" dialog. + * @param {Object} profileInfo The profile object of the profile to delete. + * @private + */ + showDeleteDialog_: function(profileInfo) { + ManageProfileOverlay.setProfileInfo(profileInfo); + $('manage-profile-overlay-manage').hidden = true; + $('manage-profile-overlay-delete').hidden = false; + $('delete-profile-message').textContent = + localStrings.getStringF('deleteProfileMessage', profileInfo.name); + $('delete-profile-message').style.backgroundImage = 'url("' + + profileInfo.iconURL + '")'; + + // Intentionally don't show the URL in the location bar as we don't want + // people trying to navigate here by hand. + OptionsPage.showPageByName('manageProfile', false); + }, + }; + + // Forward public APIs to private implementations. + [ + 'receiveDefaultProfileIcons', + 'receiveProfileNames', + 'setProfileInfo', + 'setProfileName', + 'showManageDialog', + 'showDeleteDialog', + ].forEach(function(name) { + ManageProfileOverlay[name] = function(value) { + ManageProfileOverlay.getInstance()[name + '_'](value); + }; + }); + + // Export + return { + ManageProfileOverlay: ManageProfileOverlay + }; +}); diff --git a/chrome/browser/resources/options2/options.html b/chrome/browser/resources/options2/options.html new file mode 100644 index 0000000..5751729 --- /dev/null +++ b/chrome/browser/resources/options2/options.html @@ -0,0 +1,188 @@ +<!DOCTYPE HTML> +<html id="t" i18n-values="dir:textdirection"> +<head> +<meta charset="utf-8"> +<!-- Set the title to that of the default page so that the title doesn't flash + on load (for the most common case). --> +<title i18n-content="browserPageTabTitle"></title> + +<link rel="icon" href="../../../app/theme/settings_favicon.png"> +<link rel="stylesheet" href="chrome://resources/css/button.css"> +<link rel="stylesheet" href="chrome://resources/css/checkbox.css"> +<link rel="stylesheet" href="chrome://resources/css/list.css"> +<link rel="stylesheet" href="chrome://resources/css/chrome_shared.css"> +<link rel="stylesheet" href="chrome://resources/css/select.css"> +<link rel="stylesheet" href="chrome://resources/css/spinner.css"> +<link rel="stylesheet" href="chrome://resources/css/throbber.css"> +<link rel="stylesheet" href="chrome://resources/css/tree.css"> +<link rel="stylesheet" href="options_page.css"> +<link rel="stylesheet" href="advanced_options.css"> +<link rel="stylesheet" href="alert_overlay.css"> +<link rel="stylesheet" href="autofill_options.css"> +<link rel="stylesheet" href="autofill_overlay.css"> +<link rel="stylesheet" href="browser_options_page.css"> +<link rel="stylesheet" href="clear_browser_data_overlay.css"> +<link rel="stylesheet" href="content_settings.css"> +<link rel="stylesheet" href="cookies_view.css"> +<link rel="stylesheet" href="extension_settings.css"> +<link rel="stylesheet" href="font_settings.css"> +<if expr="pp_ifdef('enable_register_protocol_handler')"> + <link rel="stylesheet" href="handler_options.css"> +</if> +<link rel="stylesheet" href="import_data_overlay.css"> +<if expr="pp_ifdef('enable_web_intents')"> + <link rel="stylesheet" href="intents_view.css"> +</if> +<link rel="stylesheet" href="language_options.css"> +<link rel="stylesheet" href="manage_profile_overlay.css"> +<link rel="stylesheet" href="pack_extension_overlay.css"> +<link rel="stylesheet" href="password_manager.css"> +<link rel="stylesheet" href="password_manager_list.css"> +<link rel="stylesheet" href="personal_options.css"> +<link rel="stylesheet" href="search_engine_manager.css"> +<link rel="stylesheet" href="search_page.css"> +<link rel="stylesheet" href="subpages_tab_controls.css"> +<link rel="stylesheet" href="../sync_setup_overlay.css"> +<if expr="pp_ifdef('chromeos')"> + <link rel="stylesheet" href="about_page.css"> + <link rel="stylesheet" href="chromeos/accounts_options_page.css"> + <link rel="stylesheet" href="chromeos/change_picture_options.css"> + <link rel="stylesheet" href="chromeos/internet_options_page.css"> + <link rel="stylesheet" href="chromeos/proxy.css"> + <link rel="stylesheet" href="chromeos/system_options_page.css"> +</if> +<if expr="pp_ifdef('chromeos') and pp_ifdef('use_virtual_keyboard')"> + <link rel="stylesheet" href="chromeos/virtual_keyboard.css"> +</if> + +<if expr="not pp_ifdef('win32') and not pp_ifdef('darwin')"> + <link rel="stylesheet" href="certificate_manager.css"> + <link rel="stylesheet" href="certificate_tree.css"> +</if> + +<script src="chrome://resources/css/tree.css.js"></script> + +<script src="chrome://resources/js/cr.js"></script> +<script src="chrome://resources/js/cr/command_line.js"></script> +<script src="chrome://resources/js/cr/event_target.js"></script> +<script src="chrome://resources/js/cr/ui.js"></script> +<script src="chrome://resources/js/cr/ui/array_data_model.js"></script> +<script src="chrome://resources/js/cr/ui/list_selection_model.js"></script> +<script src="chrome://resources/js/cr/ui/list_selection_controller.js"></script> +<script src="chrome://resources/js/cr/ui/list_single_selection_model.js"></script> +<script src="chrome://resources/js/cr/ui/list_item.js"></script> +<script src="chrome://resources/js/cr/ui/list.js"></script> +<script src="chrome://resources/js/cr/ui/grid.js"></script> +<script src="chrome://resources/js/cr/ui/position_util.js"></script> +<script src="chrome://resources/js/cr/ui/repeating_button.js"></script> +<script src="chrome://resources/js/cr/ui/tree.js"></script> +<script src="chrome://resources/js/local_strings.js"></script> +<script src="chrome://resources/js/util.js"></script> +<script src="chrome://settings-frame/options_bundle.js"></script> +</head> +<body i18n-values=".style.fontFamily:fontfamily;"> +<div id="overlay" class="overlay transparent" hidden> + <include src="alert_overlay.html"> + <include src="autofill_edit_address_overlay.html"> + <include src="autofill_edit_creditcard_overlay.html"> + <include src="clear_browser_data_overlay.html"> + <include src="import_data_overlay.html"> + <include src="instant_confirm_overlay.html"> + <include src="language_add_language_overlay.html"> + <include src="manage_profile_overlay.html"> + <include src="pack_extension_overlay.html"> + <include src="../sync_setup_overlay.html"> + <if expr="pp_ifdef('chromeos')"> + <include + src="chromeos/language_customize_modifier_keys_overlay.html"> + <include src="chromeos/internet_detail.html"> + </if> + <if expr="not pp_ifdef('win32') and not pp_ifdef('darwin')"> + <include src="certificate_restore_overlay.html"> + <include src="certificate_backup_overlay.html"> + <include src="certificate_edit_ca_trust_overlay.html"> + <include src="certificate_import_error_overlay.html"> + </if> +</div> +<div id="main-content"> + <div id="navbar-container"> + <h1 id="navbar-content-title" i18n-content="title"></h1> + <ul id="navbar"> + </ul> + </div> + <div id="mainview"> + <div id="managed-prefs-banner" class="managed-prefs-banner" hidden> + <span id="managed-prefs-icon" class="managed-prefs-icon"></span> + <span id="managed-prefs-text" class="managed-prefs-text"></span> + </div> + <div id="subpage-backdrop" hidden></div> + <div id="mainview-content"> + <div id="page-container"> + <!-- Please keep the main pages in desired order of display. This will + allow search results to display in the desired order. --> + <include src="search_page.html"> + <include src="browser_options.html"> + <include src="personal_options.html"> + <if expr="pp_ifdef('chromeos')"> + <include src="chromeos/system_options.html"> + <include src="chromeos/internet_options.html"> + <include src="chromeos/accounts_options.html"> + </if> + <include src="advanced_options.html"> + <include src="extension_settings.html"> + </div> + <div id="subpage-sheet-container-1" + class="subpage-sheet-container transparent" hidden> + <div id="subpage-sheet-1" class="subpage-sheet"> + <button class="raw-button close-subpage custom-appearance"></button> + <div class="subpage-sheet-contents"> + <if expr="pp_ifdef('chromeos')"> + <include src="about_page.html"> + <include src="chromeos/change_picture_options.html"> + <include src="chromeos/proxy.html"> + </if> + <if expr="not pp_ifdef('win32') and not pp_ifdef('darwin')"> + <include src="certificate_manager.html"> + </if> + <include src="autofill_options.html"> + <include src="content_settings.html"> + <include src="font_settings.html"> + <include src="language_options.html"> + <include src="password_manager.html"> + <include src="search_engine_manager.html"> + </div> + </div> + </div> + <div id="subpage-sheet-container-2" + class="subpage-sheet-container transparent" hidden> + <div id="subpage-sheet-2" class="subpage-sheet"> + <button class="raw-button close-subpage custom-appearance"></button> + <div class="subpage-sheet-contents"> + <if expr="pp_ifdef('chromeos')"> + <include src="chromeos/language_chewing_options.html"> + <include src="chromeos/language_hangul_options.html"> + <include src="chromeos/language_mozc_options.html"> + <include src="chromeos/language_pinyin_options.html"> + </if> + <if expr="pp_ifdef('chromeos') and pp_ifdef('use_virtual_keyboard')"> + <include src="chromeos/virtual_keyboard.html"> + </if> + <include src="cookies_view.html"> + <if expr="pp_ifdef('enable_register_protocol_handler')"> + <include src="handler_options.html"> + </if> + <if expr="pp_ifdef('enable_web_intents')"> + <include src="intents_view.html"> + </if> + <include src="content_settings_exceptions_area.html"> + </div> + </div> + </div> + </div> + </div> +</div> +<script src="chrome://settings-frame/strings.js"></script> +<script src="chrome://resources/js/i18n_template.js"></script> +<script src="chrome://resources/js/i18n_process.js"></script> +</body> +</html> diff --git a/chrome/browser/resources/options2/options.js b/chrome/browser/resources/options2/options.js new file mode 100644 index 0000000..5a744f3 --- /dev/null +++ b/chrome/browser/resources/options2/options.js @@ -0,0 +1,247 @@ +// Copyright (c) 2011 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. + +var AddLanguageOverlay = options.AddLanguageOverlay; +var AdvancedOptions = options.AdvancedOptions; +var AlertOverlay = options.AlertOverlay; +var AutofillEditAddressOverlay = options.AutofillEditAddressOverlay; +var AutofillEditCreditCardOverlay = options.AutofillEditCreditCardOverlay; +var AutofillOptions = options.AutofillOptions; +var BrowserOptions = options.BrowserOptions; +var ClearBrowserDataOverlay = options.ClearBrowserDataOverlay; +var ContentSettings = options.ContentSettings; +var ContentSettingsExceptionsArea = + options.contentSettings.ContentSettingsExceptionsArea; +var CookiesView = options.CookiesView; +var ExtensionSettings = options.ExtensionSettings; +var FontSettings = options.FontSettings; +var HandlerOptions = options.HandlerOptions; +var ImportDataOverlay = options.ImportDataOverlay; +var IntentsView = options.IntentsView; +var InstantConfirmOverlay = options.InstantConfirmOverlay; +var LanguageOptions = options.LanguageOptions; +var OptionsPage = options.OptionsPage; +var PackExtensionOverlay = options.PackExtensionOverlay; +var PasswordManager = options.PasswordManager; +var PersonalOptions = options.PersonalOptions; +var Preferences = options.Preferences; +var ManageProfileOverlay = options.ManageProfileOverlay; +var ProxyOptions = options.ProxyOptions; +var SearchEngineManager = options.SearchEngineManager; +var SearchPage = options.SearchPage; +var SyncSetupOverlay = options.SyncSetupOverlay; +var VirtualKeyboardManager = options.VirtualKeyboardManager; + +/** + * DOMContentLoaded handler, sets up the page. + */ +function load() { + // Decorate the existing elements in the document. + cr.ui.decorate('input[pref][type=checkbox]', options.PrefCheckbox); + cr.ui.decorate('input[pref][type=number]', options.PrefNumber); + cr.ui.decorate('input[pref][type=radio]', options.PrefRadio); + cr.ui.decorate('input[pref][type=range]', options.PrefRange); + cr.ui.decorate('select[pref]', options.PrefSelect); + cr.ui.decorate('input[pref][type=text]', options.PrefTextField); + cr.ui.decorate('input[pref][type=url]', options.PrefTextField); + cr.ui.decorate('button[pref]', options.PrefButton); + cr.ui.decorate('#content-settings-page input[type=radio]:not(.handler-radio)', + options.ContentSettingsRadio); + cr.ui.decorate('#content-settings-page input[type=radio].handler-radio', + options.HandlersEnabledRadio); + cr.ui.decorate('span.controlled-setting-indicator', + options.ControlledSettingIndicator); + + var menuOffPattern = /(^\?|&)menu=off($|&)/; + var menuDisabled = menuOffPattern.test(window.location.search); + // document.documentElement.setAttribute('hide-menu', menuDisabled); + // We can't use an attribute on the html element because of webkit bug + // 12519. Instead, we add a class. + if (menuDisabled) + document.documentElement.classList.add('hide-menu'); + + localStrings = new LocalStrings(); + + OptionsPage.register(SearchPage.getInstance()); + + OptionsPage.register(BrowserOptions.getInstance()); + OptionsPage.registerSubPage(SearchEngineManager.getInstance(), + BrowserOptions.getInstance(), + [$('defaultSearchManageEnginesButton')]); + OptionsPage.register(PersonalOptions.getInstance()); + OptionsPage.registerSubPage(AutofillOptions.getInstance(), + PersonalOptions.getInstance(), + [$('autofill-settings')]); + OptionsPage.registerSubPage(PasswordManager.getInstance(), + PersonalOptions.getInstance(), + [$('manage-passwords')]); + if (cr.isChromeOS) { + OptionsPage.register(SystemOptions.getInstance()); + OptionsPage.registerSubPage(AboutPage.getInstance(), + SystemOptions.getInstance()); + OptionsPage.registerSubPage(LanguageOptions.getInstance(), + SystemOptions.getInstance(), + [$('language-button')]); + OptionsPage.registerSubPage( + new OptionsPage('languageChewing', + templateData.languageChewingPageTabTitle, + 'languageChewingPage'), + LanguageOptions.getInstance()); + OptionsPage.registerSubPage( + new OptionsPage('languageHangul', + templateData.languageHangulPageTabTitle, + 'languageHangulPage'), + LanguageOptions.getInstance()); + OptionsPage.registerSubPage( + new OptionsPage('languageMozc', + templateData.languageMozcPageTabTitle, + 'languageMozcPage'), + LanguageOptions.getInstance()); + OptionsPage.registerSubPage( + new OptionsPage('languagePinyin', + templateData.languagePinyinPageTabTitle, + 'languagePinyinPage'), + LanguageOptions.getInstance()); + // Only use the VirtualKeyboardManager if the keyboard DOM elements (which + // it will assume exists) are present (i.e. if we were built with + // USE_VIRTUAL_KEYBOARD). + if ($('language-options-virtual-keyboard')) { + OptionsPage.registerSubPage(VirtualKeyboardManager.getInstance(), + LanguageOptions.getInstance()); + } + OptionsPage.register(InternetOptions.getInstance()); + } + OptionsPage.register(AdvancedOptions.getInstance()); + OptionsPage.registerSubPage(ContentSettings.getInstance(), + AdvancedOptions.getInstance(), + [$('privacyContentSettingsButton')]); + OptionsPage.registerSubPage(ContentSettingsExceptionsArea.getInstance(), + ContentSettings.getInstance()); + OptionsPage.registerSubPage(CookiesView.getInstance(), + ContentSettings.getInstance(), + [$('privacyContentSettingsButton'), + $('show-cookies-button')]); + // If HandlerOptions is null it means it got compiled out. + if (HandlerOptions) { + OptionsPage.registerSubPage(HandlerOptions.getInstance(), + ContentSettings.getInstance(), + [$('manage-handlers-button')]); + } + if (IntentsView && $('manage-intents-button')) { + OptionsPage.registerSubPage(IntentsView.getInstance(), + ContentSettings.getInstance(), + [$('manage-intents-button')]); + } + OptionsPage.registerSubPage(FontSettings.getInstance(), + AdvancedOptions.getInstance(), + [$('fontSettingsCustomizeFontsButton')]); + if (!cr.isChromeOS) { + OptionsPage.registerSubPage(LanguageOptions.getInstance(), + AdvancedOptions.getInstance(), + [$('language-button')]); + } + if (!cr.isWindows && !cr.isMac) { + OptionsPage.registerSubPage(CertificateManager.getInstance(), + AdvancedOptions.getInstance(), + [$('certificatesManageButton')]); + OptionsPage.registerOverlay(CertificateRestoreOverlay.getInstance(), + CertificateManager.getInstance()); + OptionsPage.registerOverlay(CertificateBackupOverlay.getInstance(), + CertificateManager.getInstance()); + OptionsPage.registerOverlay(CertificateEditCaTrustOverlay.getInstance(), + CertificateManager.getInstance()); + OptionsPage.registerOverlay(CertificateImportErrorOverlay.getInstance(), + CertificateManager.getInstance()); + } + OptionsPage.registerOverlay(AddLanguageOverlay.getInstance(), + LanguageOptions.getInstance()); + OptionsPage.registerOverlay(AlertOverlay.getInstance()); + OptionsPage.registerOverlay(AutofillEditAddressOverlay.getInstance(), + AutofillOptions.getInstance()); + OptionsPage.registerOverlay(AutofillEditCreditCardOverlay.getInstance(), + AutofillOptions.getInstance()); + OptionsPage.registerOverlay(ClearBrowserDataOverlay.getInstance(), + AdvancedOptions.getInstance(), + [$('privacyClearDataButton')]); + OptionsPage.registerOverlay(ImportDataOverlay.getInstance(), + PersonalOptions.getInstance()); + OptionsPage.registerOverlay(InstantConfirmOverlay.getInstance(), + BrowserOptions.getInstance()); + OptionsPage.registerOverlay(SyncSetupOverlay.getInstance(), + PersonalOptions.getInstance()); + OptionsPage.registerOverlay(ManageProfileOverlay.getInstance(), + PersonalOptions.getInstance()); + + OptionsPage.register(ExtensionSettings.getInstance()); + OptionsPage.registerOverlay(PackExtensionOverlay.getInstance(), + ExtensionSettings.getInstance()); + + if (cr.isChromeOS) { + OptionsPage.register(AccountsOptions.getInstance()); + OptionsPage.registerSubPage(ProxyOptions.getInstance(), + InternetOptions.getInstance()); + OptionsPage.registerSubPage(ChangePictureOptions.getInstance(), + PersonalOptions.getInstance(), + [$('change-picture-button')]); + OptionsPage.registerOverlay(DetailsInternetPage.getInstance(), + InternetOptions.getInstance()); + + var languageModifierKeysOverlay = new OptionsPage( + 'languageCustomizeModifierKeysOverlay', + localStrings.getString('languageCustomizeModifierKeysOverlay'), + 'languageCustomizeModifierKeysOverlay') + $('languageCustomizeModifierKeysOverleyDismissButton').onclick = + function() { + OptionsPage.closeOverlay(); + }; + OptionsPage.registerOverlay(languageModifierKeysOverlay, + SystemOptions.getInstance(), + [$('modifier-keys-button')]); + } + + Preferences.getInstance().initialize(); + OptionsPage.initialize(); + + var path = document.location.pathname; + + if (path.length > 1) { + // Skip starting slash and remove trailing slash (if any). + var pageName = path.slice(1).replace(/\/$/, ''); + // Proxy page is now per network and only reachable from internet details. + if (pageName != 'proxy') { + // Show page, but don't update history (there's already an entry for it). + OptionsPage.showPageByName(pageName, false); + } + } else { + OptionsPage.showDefaultPage(); + } + + var subpagesNavTabs = document.querySelectorAll('.subpages-nav-tabs'); + for(var i = 0; i < subpagesNavTabs.length; i++) { + subpagesNavTabs[i].onclick = function(event) { + OptionsPage.showTab(event.srcElement); + } + } + + // Allow platform specific CSS rules. + cr.enablePlatformSpecificCSSRules(); + + if (navigator.plugins['Shockwave Flash']) + document.documentElement.setAttribute('hasFlashPlugin', ''); + + // Clicking on the Settings title brings up the 'Basics' page. + $('navbar-content-title').onclick = function() { + OptionsPage.navigateToPage(BrowserOptions.getInstance().name); + }; +} + +document.addEventListener('DOMContentLoaded', load); + +window.onpopstate = function(e) { + options.OptionsPage.setState(e.state); +}; + +window.onbeforeunload = function() { + options.OptionsPage.willClose(); +}; diff --git a/chrome/browser/resources/options2/options_bundle.js b/chrome/browser/resources/options2/options_bundle.js new file mode 100644 index 0000000..f6e9939 --- /dev/null +++ b/chrome/browser/resources/options2/options_bundle.js @@ -0,0 +1,94 @@ +// Copyright (c) 2011 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. +// +// This file exists to aggregate all of the javascript used by the +// settings page into a single file which will be flattened and served +// as a single resource. +<include src="preferences.js"></include> +<include src="pref_ui.js"></include> +<include src="deletable_item_list.js"></include> +<include src="inline_editable_list.js"></include> +<include src="controlled_setting.js"></include> +<include src="options_page.js"></include> +<if expr="pp_ifdef('chromeos')"> + <include src="about_page.js"></include> + <include src="../chromeos/user_images_grid.js"></include> + <include src="chromeos/cellular_plan_element.js"></include> + <include src="chromeos/change_picture_options.js"></include> + <include src="chromeos/internet_detail_ip_config_list.js"></include> + <include src="chromeos/internet_network_element.js"></include> + <include src="chromeos/internet_options.js"></include> + <include src="chromeos/internet_detail.js"></include> + <include src="chromeos/system_options.js"></include> + <include src="chromeos/bluetooth_list_element.js"></include> + <include src="chromeos/accounts_options.js"></include> + <include src="chromeos/proxy_options.js"></include> + <include src="chromeos/proxy_rules_list.js"></include> + <include src="chromeos/accounts_user_list.js"></include> + <include src="chromeos/accounts_user_name_edit.js"></include> + <include src="chromeos/virtual_keyboard.js"></include> + <include src="chromeos/virtual_keyboard_list.js"></include> + var AboutPage = options.AboutPage; + var AccountsOptions = options.AccountsOptions; + var ChangePictureOptions = options.ChangePictureOptions; + var InternetOptions = options.InternetOptions; + var DetailsInternetPage = options.internet.DetailsInternetPage; + var SystemOptions = options.SystemOptions; +</if> +<if expr="not pp_ifdef('win32') and not pp_ifdef('darwin')"> + <include src="certificate_tree.js"></include> + <include src="certificate_manager.js"></include> + <include src="certificate_restore_overlay.js"></include> + <include src="certificate_backup_overlay.js"></include> + <include src="certificate_edit_ca_trust_overlay.js"></include> + <include src="certificate_import_error_overlay.js"></include> + var CertificateManager = options.CertificateManager; + var CertificateRestoreOverlay = options.CertificateRestoreOverlay; + var CertificateBackupOverlay = options.CertificateBackupOverlay; + var CertificateEditCaTrustOverlay = options.CertificateEditCaTrustOverlay; + var CertificateImportErrorOverlay = options.CertificateImportErrorOverlay; +</if> +<include src="advanced_options.js"></include> +<include src="alert_overlay.js"></include> +<include src="autocomplete_list.js"></include> +<include src="autofill_edit_address_overlay.js"></include> +<include src="autofill_edit_creditcard_overlay.js"></include> +<include src="autofill_options_list.js"></include> +<include src="autofill_options.js"></include> +<include src="browser_options.js"></include> +<include src="browser_options_startup_page_list.js"></include> +<include src="clear_browser_data_overlay.js"></include> +<include src="content_settings.js"></include> +<include src="content_settings_exceptions_area.js"></include> +<include src="content_settings_ui.js"></include> +<include src="cookies_list.js"></include> +<include src="cookies_view.js"></include> +<include src="extension_list.js"></include> +<include src="extension_settings.js"></include> +<include src="font_settings.js"></include> +<if expr="pp_ifdef('enable_register_protocol_handler')"> + <include src="handler_options.js"></script> + <include src="handler_options_list.js"></script> +</if> +<include src="import_data_overlay.js"></include> +<include src="instant_confirm_overlay.js"></include> +<if expr="pp_ifdef('enable_web_intents')"> + <include src="intents_list.js"></include> + <include src="intents_view.js"></include> +</if> +<include src="language_add_language_overlay.js"></include> +<include src="language_list.js"></include> +<include src="language_options.js"></include> +<include src="manage_profile_overlay.js"></include> +<include src="pack_extension_overlay.js"></include> +<include src="password_manager.js"></include> +<include src="password_manager_list.js"></include> +<include src="personal_options.js"></include> +<include src="personal_options_profile_list.js"></include> +<include src="profiles_icon_grid.js"></include> +<include src="search_engine_manager.js"></include> +<include src="search_engine_manager_engine_list.js"></include> +<include src="search_page.js"></include> +<include src="../sync_setup_overlay.js"></include> +<include src="options.js"></include> diff --git a/chrome/browser/resources/options2/options_page.css b/chrome/browser/resources/options2/options_page.css new file mode 100644 index 0000000..ff45d67 --- /dev/null +++ b/chrome/browser/resources/options2/options_page.css @@ -0,0 +1,736 @@ +.hbox { + display: -webkit-box; + -webkit-box-orient: horizontal; +} + +.vbox { + display: -webkit-box; + -webkit-box-orient: vertical; +} + +.stretch { + -webkit-box-flex: 1; +} + +.frozen, +.subpage-sheet-container.frozen { + position: fixed; +} + +#search-field { + font-size: inherit; + margin: 0; +} + +/* + * Add padding to increase the touchable area of search box. Use original font + * size to avoid the width of search box exceeding the width of navbar. + */ +html[touch-optimized] #search-field { + font-size: 13px; + padding: 5px; +} +html[touch-optimized] #search-field::-webkit-search-cancel-button { + -webkit-transform: scale(1.5); +} + +/* + * For touch-optimized UI, make the radio/checkbox input boxes in + * options/preference pages easier to touch. + * TODO(rbyers): We need to solve this more generally for all web pages + * (crbug.com/99981), and perhaps temporarily for all WebUI (crbug.com/102482). + */ +html[touch-optimized] div.radio > label > span, +html[touch-optimized] div.checkbox > label > span { + -webkit-padding-start: 5px; +} + +html[touch-optimized] label > input[type=checkbox], +html[touch-optimized] label > input[type=radio] { + -webkit-transform: scale(1.4); +} + +/* + * Override the font-size rule in shared_options.css file. + * 16 px font-size proved to be more touch friendly. It increases the touchable + * area for buttons and input boxes. + */ +html[touch-optimized] body { + font-size: 16px; +} + +.overlay { + -webkit-box-align: center; + -webkit-box-orient: vertical; + -webkit-box-pack: center; + -webkit-transition: 250ms opacity; + background: -webkit-radial-gradient(rgba(127, 127, 127, 0.5), + rgba(127, 127, 127, 0.5) 35%, + rgba(0, 0, 0, 0.7)); + bottom: 0; + display: -webkit-box; + left: 0; + overflow: auto; + padding: 20px; + padding-bottom: 130px; + position: fixed; + right: 0; + top: 0; + z-index: 10; +} + +.raw-button, +.raw-button:hover, +.raw-button:active { + -webkit-box-shadow: none; + background-color: transparent; + background-repeat: no-repeat; + border: none; + min-width: 0; + padding: 1px 6px; +} + +.close-subpage { + background-image: url('chrome://theme/IDR_CLOSE_BAR'); + height: 16px; + min-width: 0; + position: relative; + top: 16px; + width: 16px; +} + +/* In TOUCH_UI builds, the IDR_CLOSE_BAR resource is double-size. */ +<if expr="pp_ifdef('touchui')"> +.close-subpage { + height: 32px; + width: 32px; +} +</if> + +.close-subpage:hover { + background-image: url('chrome://theme/IDR_CLOSE_BAR_H'); +} + +.close-subpage:active { + background-image: url('chrome://theme/IDR_CLOSE_BAR_P'); +} + +html[dir='ltr'] .close-subpage { + float: right; + right: 20px; +} + +html[dir='rtl'] .close-subpage { + float: left; + left: 20px; +} + +html.hide-menu .close-subpage { + display: none +} + +.content-area { + padding: 10px 15px 5px 15px; +} + +.action-area { + -webkit-box-align: center; + -webkit-box-orient: horizontal; + -webkit-box-pack: end; + border-top: 1px solid rgba(188, 193, 208, .5); + display: -webkit-box; + padding: 12px; +} + +html[dir='rtl'] .action-area { + left: 0; +} + +.action-area-right { + display: -webkit-box; +} + +.button-strip { + -webkit-box-orient: horizontal; + display: -webkit-box; +} + +.button-strip > button { + -webkit-margin-start: 10px; + display: block; +} + +.bottom-strip { + padding: 12px; + position: absolute; + right: 0px; + bottom: 0px; + border-top: none; +} + +.overlay .page { + -webkit-box-shadow: 0px 5px 80px #505050; + background: white; + border: 1px solid rgb(188, 193, 208); + border-radius: 2px; + min-width: 400px; + padding: 0; + position: relative; +} + +#subpage-backdrop { + -webkit-transition: 250ms opacity; + background-color: rgba(233, 238, 242, .5); + height: 100%; + left: 216px; + right: 216px; + position: fixed; + top: 0; + width: 100%; +} + +.subpage-sheet-container { + -webkit-transition: 250ms opacity, 100ms padding-left, 100ms padding-right; + box-sizing: border-box; + min-height: 100%; + position: absolute; + /* We set both left and right for the sake of RTL. */ + left: 0; + right: 0; + top: 0; + width: 100%; +} + +#subpage-sheet-container-1 { + -webkit-padding-start: 40px; + z-index: 5; +} + +#subpage-sheet-container-2 { + -webkit-padding-start: 80px; + z-index: 10; +} + +.subpage-sheet { + -webkit-box-shadow: #666 0px 2px 5px; + background-color: white; + border-left: 1px solid #b8b8b8; + box-sizing: border-box; + min-height: 100%; + width: 100%; + min-width: 651px; +} + +.subpage-sheet-contents { + box-sizing: border-box; + padding: 0px 20px 20px 20px; + width: 650px; +} + +.managed-prefs-banner { + background: -webkit-linear-gradient(#fff2b7, #fae691 97%, #878787); + height: 31px; + width: 100%; + margin: 0; + padding: 0; + position: relative; + vertical-align: middle; + z-index: 11; +} + +.managed-prefs-banner.clickable:active { + background: -webkit-linear-gradient(#878787, #fae691 3%, #fff2b7); +} + +.managed-prefs-icon { + background-image: url("chrome://theme/IDR_WARNING"); + background-repeat: no-repeat; + background-position:center; + display: inline-block; + padding: 5px; + height: 21px; + vertical-align: middle; + width: 24px; +} + +.managed-prefs-text { + vertical-align: middle; +} + +.subpage-sheet .page h1 { + margin-bottom: 10px; +} + +.overlay .page h1 { + background: -webkit-linear-gradient(white, #F8F8F8); + border-bottom: 1px solid rgba(188, 193, 208, .5); + font-size: 105%; + font-weight: bold; + padding: 10px 15px 8px 15px; +} + +.page list { + /* Min height is a multiple of the list item height (32) */ + min-height: 192px; +} + +/** + * TODO(kevers): Standardize formatting of sections to use display tables. + * For now, we require separate specialized rules for sections that are + * formatted as table rows. + */ +section { + -webkit-box-orient: horizontal; + border-bottom: 1px solid #eeeeee; + display: -webkit-box; + margin-top: 17px; + padding-bottom: 20px; +} + +div.page section:last-child { + border-bottom: none; +} + +h3 { + font-size: 105%; + font-weight: bold; + margin: 20px 0 10px 0; +} + +section > h3 { + margin: 0; + vertical-align: middle; + width: 130px; + -webkit-padding-end: 10px; +} + +section > div:only-of-type { + -webkit-box-flex: 1; +} + +/* Don't allow edge margin on the first/last child of a section. */ +section > h3 + * > *:last-child { + margin-bottom: 0; +} +section > h3 + * > *:first-child { + margin-top: 0; +} + +.option { + margin-top: 0; +} + +/* [hidden] does display:none, but its priority is too low in some cases. */ +[hidden] { + display: none !important; +} + +.transparent { + opacity: 0; +} + +.touch-slider { + -webkit-appearance: slider-horizontal; +} + + +.settings-list, +.settings-list-empty { + border: 1px solid #d9d9d9; + border-radius: 2px; +} + +.settings-list-empty { + background-color: #f4f4f4; + box-sizing: border-box; + min-height: 125px; + padding-left: 20px; + padding-top: 20px; +} + +list > * { + -webkit-box-align: center; + -webkit-transition: 150ms background-color; + box-sizing: border-box; + border-radius: 0; + display: -webkit-box; + height: 32px; + border: none; + margin: 0; +} + +list:not([disabled]) > :hover { + background-color: #e4ecf7; +} + +/* TODO(stuartmorgan): Once this becomes the list style for other WebUI pages + * these rules can be simplified (since they wont need to override other rules). + */ + +list:not([hasElementFocus]) > [selected], +list:not([hasElementFocus]) > [lead][selected] { + background-color: #d0d0d0; + background-image: none; +} + +list[hasElementFocus] > [selected], +list[hasElementFocus] > [lead][selected], +list:not([hasElementFocus]) > [selected]:hover, +list:not([hasElementFocus]) > [selected][lead]:hover { + background-color: #bbcee9; + background-image: none; +} + +list[hasElementFocus] > [lead], +list[hasElementFocus] > [lead][selected] { + border-top: 1px solid #7892b4; + border-bottom: 1px solid #7892b4; +} + +list[hasElementFocus] > [lead]:nth-child(2), +list[hasElementFocus] > [lead][selected]:nth-child(2) { + border-top: 1px solid transparent; +} + +list[hasElementFocus] > [lead]:nth-last-child(2), +list[hasElementFocus] > [lead][selected]:nth-last-child(2) { + border-bottom: 1px solid transparent; +} + +list[disabled] > [lead][selected], +list[disabled]:focus > [lead][selected] { + border: none; +} + +list[disabled] { + opacity: 0.6; +} + +list > .heading { + color: #666666; +} + +list > .heading:hover { + background-color: transparent; + border-color: transparent; +} + +list .deletable-item { + -webkit-box-align: center; +} + +list .deletable-item > :first-child { + -webkit-box-align: center; + -webkit-box-flex: 1; + -webkit-padding-end: 5px; + display: -webkit-box; +} + +list .close-button { + -webkit-transition: 150ms opacity; + background-color: transparent; + /* TODO(stuartmorgan): Replace with real images once they are available. */ + background-image: url("../../../app/theme/close_bar.png"); + border: none; + display: block; + height: 16px; + opacity: 1; + width: 16px; +} + +list > *:not(:hover):not([lead]) .close-button, +list > *:not(:hover):not([selected]) .close-button, +list:not([hasElementFocus]) > *:not(:hover) .close-button, +list[disabled] .close-button, +list .close-button[disabled] { + opacity: 0; + pointer-events: none; +} + +list .close-button:hover { + background-image: url("../../../app/theme/close_bar_h.png"); +} + +list .close-button:active { + background-image: url("../../../app/theme/close_bar_p.png"); +} + +list .static-text { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +list[inlineeditable] input { + box-sizing: border-box; + margin: 0; + width: 100%; +} + +list > :not([editing]) [displaymode="edit"] { + display: none; +} + +list > [editing] [displaymode="static"] { + display: none; +} + +list > [editing] input:invalid { + /* TODO(stuartmorgan): Replace with validity badge */ + background-color: pink; +} + +.option-name { + padding-right: 5px; +} + +html[dir=rtl].option-name { + padding-left: 5px; +} + +.favicon-cell { + -webkit-padding-start: 20px; + background-position: left; + background-repeat: no-repeat; +} + +input[type="url"].favicon-cell { + -webkit-padding-start: 22px; + background-position-x: 4px; +} + +/* TODO(jhawkins): Use something better than 99.3% when CSS3 background + * positioning is available. + */ +html[dir=rtl] input.favicon-cell { + background-position-x: 99.3%; +} + +list .favicon-cell { + -webkit-margin-start: 7px; + -webkit-padding-start: 26px; + display: block; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; +} + +html[dir=rtl] list .favicon-cell { + background-position: right; +} + +html[enable-background-mode=false] #background-mode-section { + display: none; +} + +/* UI Controls */ + +/* LIST */ +html:not([os=mac]) list[hasElementFocus] { + outline: 1px solid rgba(0, 128, 256, 0.5); + outline-offset: -2px; +} + +/* This matches the native list outline on Mac */ +html[os=mac] list[hasElementFocus] { + outline-color: #759ad9; + outline-offset: -1px; + outline-style: auto; + outline-width: 5px; +} + +.suboption { + -webkit-margin-start: 23px; +} + +.informational-text { + color: grey; +} + +#main-content list.autocomplete-suggestions { + background-color: white; + border: 1px solid #aaa; + border-radius: 2px; + min-height: 0; + opacity: 0.9; + position: fixed; + z-index: 3; +} + +list.autocomplete-suggestions > div { + height: auto; +} + +list.autocomplete-suggestions:not([hasElementFocus]) > [selected], +list.autocomplete-suggestions:not([hasElementFocus]) > [lead][selected] { + background-color: #bbcee9; +} + +html:not([hasFlashPlugin]) .flash-plugin-area, +/* If the Flash plug-in supports the NPP_ClearSiteData API, we don't need to + * show the link to the Flash storage settings manager: + */ +html[flashPluginSupportsClearSiteData] .flash-plugin-area, +html:not([flashPluginSupportsClearSiteData]) .clear-plugin-lso-data-enabled, +html[flashPluginSupportsClearSiteData] .clear-plugin-lso-data-disabled { + display: none; +} + + +/* Display a collection of sections as a table in order to display nicely + * in multiple locales. + */ +.displaytable { + display: table; + width: 100%; +} + +.displaytable > section { + display: table-row; +} + +/* right table column containing settable options */ +.displaytable > section > h3 + div, +.displaytable > section > h3 + table { + padding-bottom: 20px; +} + +/* Setting the padding on the header so the alignment doesn't depend on the + * contents of the right table column. */ +.displaytable > section > h3 { + padding-top: 17px; +} + +.displaytable > section > * { + display: table-cell; + vertical-align: baseline; + border-bottom: 1px solid #eeeeee; +} + +/* do not display a border after the last section in the table */ +.displaytable:not([searching='true']) > section:last-child > * { + border-bottom: none; +} + +/* Controlled setting indicator and bubble. */ +.controlled-setting-indicator { + display: inline-block; + /* Establish a containing block for absolutely positioning the bubble. */ + position: relative; + vertical-align: text-bottom; +} + +.controlled-setting-indicator[controlled-by] summary { + background-size: contain; + height: 16px; + width: 16px; +} + +.controlled-setting-indicator summary::-webkit-details-marker { + display: none; +} + +.controlled-setting-indicator[controlled-by='policy'] summary { + background-image: + url('chrome://theme/IDR_CONTROLLED_SETTING_MANDATORY_GRAY'); +} + +.controlled-setting-indicator[controlled-by='policy'] summary:hover { + background-image: + url('chrome://theme/IDR_CONTROLLED_SETTING_MANDATORY'); +} + +.controlled-setting-indicator[controlled-by='extension'] summary { + background-image: + url('chrome://theme/IDR_CONTROLLED_SETTING_EXTENSION_GRAY'); +} + +.controlled-setting-indicator[controlled-by='extension'] summary:hover { + background-image: + url('chrome://theme/IDR_CONTROLLED_SETTING_EXTENSION'); +} + +.controlled-setting-indicator[controlled-by='recommended'] summary { + background-image: + url('chrome://theme/IDR_CONTROLLED_SETTING_RECOMMENDED_GRAY'); +} + +.controlled-setting-indicator[controlled-by='recommended'] summary:hover { + background-image: + url('chrome://theme/IDR_CONTROLLED_SETTING_RECOMMENDED'); +} + +.controlled-setting-bubble { + -webkit-margin-start: -20px; + background-color: white; + border-radius: 4px; + border: 1px solid #ccc; + box-shadow: 0 2px 2px #ddd; + margin-top: 10px; + padding: 10px; + position: absolute; + top: 50%; + z-index: 10; +} + +html[dir='ltr'] .controlled-setting-bubble { + left: 50%; +} + +html[dir='rtl'] .controlled-setting-bubble { + right: 50%; +} + +.controlled-setting-bubble::before { + -webkit-margin-start: 4px; + border-color: #ccc transparent; + border-style: solid; + border-width: 0 5px 5px; + content: ''; + position: absolute; + top: -5px; +} + +.controlled-setting-bubble::after { + -webkit-margin-start: 5px; + border-color: white transparent; + border-style: solid; + border-width: 0 4px 4px; + content: ''; + position: absolute; + top: -4px; +} + +.controlled-setting-bubble-text { + -webkit-padding-start: 30px; + background-repeat: no-repeat; + margin: 0; + min-height: 32px; + min-width: 200px; +} + +.controlled-setting-indicator[controlled-by='policy'] + .controlled-setting-bubble-text { + background-image: + url('chrome://theme/IDR_CONTROLLED_SETTING_MANDATORY_LARGE'); +} + +.controlled-setting-indicator[controlled-by='extension'] + .controlled-setting-bubble-text { + background-image: + url('chrome://theme/IDR_CONTROLLED_SETTING_EXTENSION_LARGE'); +} + +.controlled-setting-indicator[controlled-by='recommended'] + .controlled-setting-bubble-text { + background-image: + url('chrome://theme/IDR_CONTROLLED_SETTING_RECOMMENDED_LARGE'); +} + +html[dir='rtl'] .controlled-setting-bubble-text { + background-position: right top; +} + +.controlled-setting-bubble-action { + padding: 0 !important; +} diff --git a/chrome/browser/resources/options2/options_page.js b/chrome/browser/resources/options2/options_page.js new file mode 100644 index 0000000..0599e0e --- /dev/null +++ b/chrome/browser/resources/options2/options_page.js @@ -0,0 +1,1076 @@ +// Copyright (c) 2011 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() { + ///////////////////////////////////////////////////////////////////////////// + // OptionsPage class: + + /** + * Base class for options page. + * @constructor + * @param {string} name Options page name, also defines id of the div element + * containing the options view and the name of options page navigation bar + * item as name+'PageNav'. + * @param {string} title Options page title, used for navigation bar + * @extends {EventTarget} + */ + function OptionsPage(name, title, pageDivName) { + this.name = name; + this.title = title; + this.pageDivName = pageDivName; + this.pageDiv = $(this.pageDivName); + this.tab = null; + } + + const SUBPAGE_SHEET_COUNT = 2; + + /** + * Main level option pages. Maps lower-case page names to the respective page + * object. + * @protected + */ + OptionsPage.registeredPages = {}; + + /** + * Pages which are meant to behave like modal dialogs. Maps lower-case overlay + * names to the respective overlay object. + * @protected + */ + OptionsPage.registeredOverlayPages = {}; + + /** + * Whether or not |initialize| has been called. + * @private + */ + OptionsPage.initialized_ = false; + + /** + * Gets the default page (to be shown on initial load). + */ + OptionsPage.getDefaultPage = function() { + return BrowserOptions.getInstance(); + }; + + /** + * Shows the default page. + */ + OptionsPage.showDefaultPage = function() { + this.navigateToPage(this.getDefaultPage().name); + }; + + /** + * "Navigates" to a page, meaning that the page will be shown and the + * appropriate entry is placed in the history. + * @param {string} pageName Page name. + */ + OptionsPage.navigateToPage = function(pageName) { + this.showPageByName(pageName, true); + }; + + /** + * Shows a registered page. This handles both top-level pages and sub-pages. + * @param {string} pageName Page name. + * @param {boolean} updateHistory True if we should update the history after + * showing the page. + * @private + */ + OptionsPage.showPageByName = function(pageName, updateHistory) { + // Find the currently visible root-level page. + var rootPage = null; + for (var name in this.registeredPages) { + var page = this.registeredPages[name]; + if (page.visible && !page.parentPage) { + rootPage = page; + break; + } + } + + // Find the target page. + var targetPage = this.registeredPages[pageName.toLowerCase()]; + if (!targetPage || !targetPage.canShowPage()) { + // If it's not a page, try it as an overlay. + if (!targetPage && this.showOverlay_(pageName, rootPage)) { + if (updateHistory) + this.updateHistoryState_(); + return; + } else { + targetPage = this.getDefaultPage(); + } + } + + pageName = targetPage.name.toLowerCase(); + var targetPageWasVisible = targetPage.visible; + + // Determine if the root page is 'sticky', meaning that it + // shouldn't change when showing a sub-page. This can happen for special + // pages like Search. + var isRootPageLocked = + rootPage && rootPage.sticky && targetPage.parentPage; + + // Notify pages if they will be hidden. + for (var name in this.registeredPages) { + var page = this.registeredPages[name]; + if (!page.parentPage && isRootPageLocked) + continue; + if (page.willHidePage && name != pageName && + !page.isAncestorOfPage(targetPage)) + page.willHidePage(); + } + + // Update visibilities to show only the hierarchy of the target page. + for (var name in this.registeredPages) { + var page = this.registeredPages[name]; + if (!page.parentPage && isRootPageLocked) + continue; + page.visible = name == pageName || + (!document.documentElement.classList.contains('hide-menu') && + page.isAncestorOfPage(targetPage)); + } + + // Update the history and current location. + if (updateHistory) + this.updateHistoryState_(); + + // Always update the page title. + document.title = targetPage.title; + + // Notify pages if they were shown. + for (var name in this.registeredPages) { + var page = this.registeredPages[name]; + if (!page.parentPage && isRootPageLocked) + continue; + if (!targetPageWasVisible && page.didShowPage && (name == pageName || + page.isAncestorOfPage(targetPage))) + page.didShowPage(); + } + }; + + /** + * Updates the visibility and stacking order of the subpage backdrop + * according to which subpage is topmost and visible. + * @private + */ + OptionsPage.updateSubpageBackdrop_ = function () { + var topmostPage = this.getTopmostVisibleNonOverlayPage_(); + var nestingLevel = topmostPage ? topmostPage.nestingLevel : 0; + + var subpageBackdrop = $('subpage-backdrop'); + if (nestingLevel > 0) { + var container = $('subpage-sheet-container-' + nestingLevel); + subpageBackdrop.style.zIndex = + parseInt(window.getComputedStyle(container).zIndex) - 1; + subpageBackdrop.hidden = false; + } else { + subpageBackdrop.hidden = true; + } + }; + + /** + * Scrolls the page to the correct position (the top when opening a subpage, + * or the old scroll position a previously hidden subpage becomes visible). + * @private + */ + OptionsPage.updateScrollPosition_ = function () { + var topmostPage = this.getTopmostVisibleNonOverlayPage_(); + var nestingLevel = topmostPage ? topmostPage.nestingLevel : 0; + + var container = (nestingLevel > 0) ? + $('subpage-sheet-container-' + nestingLevel) : $('page-container'); + + var scrollTop = container.oldScrollTop || 0; + container.oldScrollTop = undefined; + window.scroll(document.body.scrollLeft, scrollTop); + }; + + /** + * Pushes the current page onto the history stack, overriding the last page + * if it is the generic chrome://settings/. + * @private + */ + OptionsPage.updateHistoryState_ = function() { + var page = this.getTopmostVisiblePage(); + var path = location.pathname; + if (path) + path = path.slice(1).replace(/\/$/, ''); // Remove trailing slash. + // The page is already in history (the user may have clicked the same link + // twice). Do nothing. + if (path == page.name) + return; + + // If there is no path, the current location is chrome://settings/. + // Override this with the new page. + var historyFunction = path ? window.history.pushState : + window.history.replaceState; + historyFunction.call(window.history, + {pageName: page.name}, + page.title, + '/' + page.name); + // Update tab title. + document.title = page.title; + }; + + /** + * Shows a registered Overlay page. Does not update history. + * @param {string} overlayName Page name. + * @param {OptionPage} rootPage The currently visible root-level page. + * @return {boolean} whether we showed an overlay. + */ + OptionsPage.showOverlay_ = function(overlayName, rootPage) { + var overlay = this.registeredOverlayPages[overlayName.toLowerCase()]; + if (!overlay || !overlay.canShowPage()) + return false; + + if ((!rootPage || !rootPage.sticky) && overlay.parentPage) + this.showPageByName(overlay.parentPage.name, false); + + if (!overlay.visible) { + overlay.visible = true; + if (overlay.didShowPage) overlay.didShowPage(); + } + + return true; + }; + + /** + * Returns whether or not an overlay is visible. + * @return {boolean} True if an overlay is visible. + * @private + */ + OptionsPage.isOverlayVisible_ = function() { + return this.getVisibleOverlay_() != null; + }; + + /** + * Returns the currently visible overlay, or null if no page is visible. + * @return {OptionPage} The visible overlay. + */ + OptionsPage.getVisibleOverlay_ = function() { + for (var name in this.registeredOverlayPages) { + var page = this.registeredOverlayPages[name]; + if (page.visible) + return page; + } + return null; + }; + + /** + * Closes the visible overlay. Updates the history state after closing the + * overlay. + */ + OptionsPage.closeOverlay = function() { + var overlay = this.getVisibleOverlay_(); + if (!overlay) + return; + + overlay.visible = false; + if (overlay.didClosePage) overlay.didClosePage(); + this.updateHistoryState_(); + }; + + /** + * Hides the visible overlay. Does not affect the history state. + * @private + */ + OptionsPage.hideOverlay_ = function() { + var overlay = this.getVisibleOverlay_(); + if (overlay) + overlay.visible = false; + }; + + /** + * Returns the topmost visible page (overlays excluded). + * @return {OptionPage} The topmost visible page aside any overlay. + * @private + */ + OptionsPage.getTopmostVisibleNonOverlayPage_ = function() { + var topPage = null; + for (var name in this.registeredPages) { + var page = this.registeredPages[name]; + if (page.visible && + (!topPage || page.nestingLevel > topPage.nestingLevel)) + topPage = page; + } + + return topPage; + }; + + /** + * Returns the topmost visible page, or null if no page is visible. + * @return {OptionPage} The topmost visible page. + */ + OptionsPage.getTopmostVisiblePage = function() { + // Check overlays first since they're top-most if visible. + return this.getVisibleOverlay_() || this.getTopmostVisibleNonOverlayPage_(); + }; + + /** + * Closes the topmost open subpage, if any. + * @private + */ + OptionsPage.closeTopSubPage_ = function() { + var topPage = this.getTopmostVisiblePage(); + if (topPage && !topPage.isOverlay && topPage.parentPage) { + if (topPage.willHidePage) + topPage.willHidePage(); + topPage.visible = false; + } + + this.updateHistoryState_(); + }; + + /** + * Closes all subpages below the given level. + * @param {number} level The nesting level to close below. + */ + OptionsPage.closeSubPagesToLevel = function(level) { + var topPage = this.getTopmostVisiblePage(); + while (topPage && topPage.nestingLevel > level) { + if (topPage.willHidePage) + topPage.willHidePage(); + topPage.visible = false; + topPage = topPage.parentPage; + } + + this.updateHistoryState_(); + }; + + /** + * Updates managed banner visibility state based on the topmost page. + */ + OptionsPage.updateManagedBannerVisibility = function() { + var topPage = this.getTopmostVisiblePage(); + if (topPage) + topPage.updateManagedBannerVisibility(); + }; + + /** + * Shows the tab contents for the given navigation tab. + * @param {!Element} tab The tab that the user clicked. + */ + OptionsPage.showTab = function(tab) { + // Search parents until we find a tab, or the nav bar itself. This allows + // tabs to have child nodes, e.g. labels in separately-styled spans. + while (tab && !tab.classList.contains('subpages-nav-tabs') && + !tab.classList.contains('tab')) { + tab = tab.parentNode; + } + if (!tab || !tab.classList.contains('tab')) + return; + + // Find tab bar of the tab. + var tabBar = tab; + while (tabBar && !tabBar.classList.contains('subpages-nav-tabs')) { + tabBar = tabBar.parentNode; + } + if (!tabBar) + return; + + if (tabBar.activeNavTab != null) { + tabBar.activeNavTab.classList.remove('active-tab'); + $(tabBar.activeNavTab.getAttribute('tab-contents')).classList. + remove('active-tab-contents'); + } + + tab.classList.add('active-tab'); + $(tab.getAttribute('tab-contents')).classList.add('active-tab-contents'); + tabBar.activeNavTab = tab; + }; + + /** + * Registers new options page. + * @param {OptionsPage} page Page to register. + */ + OptionsPage.register = function(page) { + this.registeredPages[page.name.toLowerCase()] = page; + // Create and add new page <li> element to navbar. + var pageNav = document.createElement('li'); + pageNav.id = page.name + 'PageNav'; + pageNav.className = 'navbar-item'; + pageNav.setAttribute('pageName', page.name); + pageNav.setAttribute('role', 'tab'); + pageNav.textContent = page.pageDiv.querySelector('h1').textContent; + pageNav.tabIndex = -1; + pageNav.onclick = function(event) { + OptionsPage.navigateToPage(this.getAttribute('pageName')); + }; + pageNav.onkeydown = function(event) { + if ((event.keyCode == 37 || event.keyCode==38) && + this.previousSibling && this.previousSibling.onkeydown) { + // Left and up arrow moves back one tab. + OptionsPage.navigateToPage( + this.previousSibling.getAttribute('pageName')); + this.previousSibling.focus(); + } else if ((event.keyCode == 39 || event.keyCode == 40) && + this.nextSibling) { + // Right and down arrows move forward one tab. + OptionsPage.navigateToPage(this.nextSibling.getAttribute('pageName')); + this.nextSibling.focus(); + } + }; + pageNav.onkeypress = function(event) { + // Enter or space + if (event.keyCode == 13 || event.keyCode == 32) { + OptionsPage.navigateToPage(this.getAttribute('pageName')); + } + }; + var navbar = $('navbar'); + navbar.appendChild(pageNav); + page.tab = pageNav; + page.initializePage(); + }; + + /** + * Find an enclosing section for an element if it exists. + * @param {Element} element Element to search. + * @return {OptionPage} The section element, or null. + * @private + */ + OptionsPage.findSectionForNode_ = function(node) { + while (node = node.parentNode) { + if (node.nodeName == 'SECTION') + return node; + } + return null; + }; + + /** + * Registers a new Sub-page. + * @param {OptionsPage} subPage Sub-page to register. + * @param {OptionsPage} parentPage Associated parent page for this page. + * @param {Array} associatedControls Array of control elements that lead to + * this sub-page. The first item is typically a button in a root-level + * page. There may be additional buttons for nested sub-pages. + */ + OptionsPage.registerSubPage = function(subPage, + parentPage, + associatedControls) { + this.registeredPages[subPage.name.toLowerCase()] = subPage; + subPage.parentPage = parentPage; + if (associatedControls) { + subPage.associatedControls = associatedControls; + if (associatedControls.length) { + subPage.associatedSection = + this.findSectionForNode_(associatedControls[0]); + } + } + subPage.tab = undefined; + subPage.initializePage(); + }; + + /** + * Registers a new Overlay page. + * @param {OptionsPage} overlay Overlay to register. + * @param {OptionsPage} parentPage Associated parent page for this overlay. + * @param {Array} associatedControls Array of control elements associated with + * this page. + */ + OptionsPage.registerOverlay = function(overlay, + parentPage, + associatedControls) { + this.registeredOverlayPages[overlay.name.toLowerCase()] = overlay; + overlay.parentPage = parentPage; + if (associatedControls) { + overlay.associatedControls = associatedControls; + if (associatedControls.length) { + overlay.associatedSection = + this.findSectionForNode_(associatedControls[0]); + } + } + + // Reverse the button strip for views. See the documentation of + // reverseButtonStrip_() for an explanation of why this is necessary. + if (cr.isViews) + this.reverseButtonStrip_(overlay); + + overlay.tab = undefined; + overlay.isOverlay = true; + overlay.initializePage(); + }; + + /** + * Reverses the child elements of a button strip. This is necessary because + * WebKit does not alter the tab order for elements that are visually reversed + * using -webkit-box-direction: reverse, and the button order is reversed for + * views. See https://bugs.webkit.org/show_bug.cgi?id=62664 for more + * information. + * @param {Object} overlay The overlay containing the button strip to reverse. + * @private + */ + OptionsPage.reverseButtonStrip_ = function(overlay) { + var buttonStrips = overlay.pageDiv.querySelectorAll('.button-strip'); + + // Reverse all button-strips in the overlay. + for (var j = 0; j < buttonStrips.length; j++) { + var buttonStrip = buttonStrips[j]; + + var childNodes = buttonStrip.childNodes; + for (var i = childNodes.length - 1; i >= 0; i--) + buttonStrip.appendChild(childNodes[i]); + } + }; + + /** + * Callback for window.onpopstate. + * @param {Object} data State data pushed into history. + */ + OptionsPage.setState = function(data) { + if (data && data.pageName) { + // It's possible an overlay may be the last top-level page shown. + if (this.isOverlayVisible_() && + !this.registeredOverlayPages[data.pageName.toLowerCase()]) { + this.hideOverlay_(); + } + + this.showPageByName(data.pageName, false); + } + }; + + /** + * Callback for window.onbeforeunload. Used to notify overlays that they will + * be closed. + */ + OptionsPage.willClose = function() { + var overlay = this.getVisibleOverlay_(); + if (overlay && overlay.didClosePage) + overlay.didClosePage(); + }; + + /** + * Freezes/unfreezes the scroll position of given level's page container. + * @param {boolean} freeze Whether the page should be frozen. + * @param {number} level The level to freeze/unfreeze. + * @private + */ + OptionsPage.setPageFrozenAtLevel_ = function(freeze, level) { + var container = level == 0 ? $('page-container') + : $('subpage-sheet-container-' + level); + + if (container.classList.contains('frozen') == freeze) + return; + + if (freeze) { + // Lock the width, since auto width computation may change. + container.style.width = window.getComputedStyle(container).width; + container.oldScrollTop = document.body.scrollTop; + container.classList.add('frozen'); + var verticalPosition = + container.getBoundingClientRect().top - container.oldScrollTop; + container.style.top = verticalPosition + 'px'; + this.updateFrozenElementHorizontalPosition_(container); + } else { + container.classList.remove('frozen'); + container.style.top = ''; + container.style.left = ''; + container.style.right = ''; + container.style.width = ''; + } + }; + + /** + * Freezes/unfreezes the scroll position of visible pages based on the current + * page stack. + */ + OptionsPage.updatePageFreezeStates = function() { + var topPage = OptionsPage.getTopmostVisiblePage(); + if (!topPage) + return; + var nestingLevel = topPage.isOverlay ? 100 : topPage.nestingLevel; + for (var i = 0; i <= SUBPAGE_SHEET_COUNT; i++) { + this.setPageFrozenAtLevel_(i < nestingLevel, i); + } + }; + + /** + * Initializes the complete options page. This will cause all C++ handlers to + * be invoked to do final setup. + */ + OptionsPage.initialize = function() { + chrome.send('coreOptionsInitialize'); + this.initialized_ = true; + + document.addEventListener('scroll', this.handleScroll_.bind(this)); + window.addEventListener('resize', this.handleResize_.bind(this)); + + if (!document.documentElement.classList.contains('hide-menu')) { + // Close subpages if the user clicks on the html body. Listen in the + // capturing phase so that we can stop the click from doing anything. + document.body.addEventListener('click', + this.bodyMouseEventHandler_.bind(this), + true); + // We also need to cancel mousedowns on non-subpage content. + document.body.addEventListener('mousedown', + this.bodyMouseEventHandler_.bind(this), + true); + + var self = this; + // Hook up the close buttons. + subpageCloseButtons = document.querySelectorAll('.close-subpage'); + for (var i = 0; i < subpageCloseButtons.length; i++) { + subpageCloseButtons[i].onclick = function() { + self.closeTopSubPage_(); + }; + }; + + // Install handler for key presses. + document.addEventListener('keydown', + this.keyDownEventHandler_.bind(this)); + + document.addEventListener('focus', this.manageFocusChange_.bind(this), + true); + } + + // Calculate and store the horizontal locations of elements that may be + // frozen later. + var sidebarWidth = + parseInt(window.getComputedStyle($('mainview')).webkitPaddingStart, 10); + $('page-container').horizontalOffset = sidebarWidth + + parseInt(window.getComputedStyle( + $('mainview-content')).webkitPaddingStart, 10); + for (var level = 1; level <= SUBPAGE_SHEET_COUNT; level++) { + var containerId = 'subpage-sheet-container-' + level; + $(containerId).horizontalOffset = sidebarWidth; + } + $('subpage-backdrop').horizontalOffset = sidebarWidth; + // Trigger the resize handler manually to set the initial state. + this.handleResize_(null); + }; + + /** + * Does a bounds check for the element on the given x, y client coordinates. + * @param {Element} e The DOM element. + * @param {number} x The client X to check. + * @param {number} y The client Y to check. + * @return {boolean} True if the point falls within the element's bounds. + * @private + */ + OptionsPage.elementContainsPoint_ = function(e, x, y) { + var clientRect = e.getBoundingClientRect(); + return x >= clientRect.left && x <= clientRect.right && + y >= clientRect.top && y <= clientRect.bottom; + }; + + /** + * Called when focus changes; ensures that focus doesn't move outside + * the topmost subpage/overlay. + * @param {Event} e The focus change event. + * @private + */ + OptionsPage.manageFocusChange_ = function(e) { + var focusableItemsRoot; + var topPage = this.getTopmostVisiblePage(); + if (!topPage) + return; + + if (topPage.isOverlay) { + // If an overlay is visible, that defines the tab loop. + focusableItemsRoot = topPage.pageDiv; + } else { + // If a subpage is visible, use its parent as the tab loop constraint. + // (The parent is used because it contains the close button.) + if (topPage.nestingLevel > 0) + focusableItemsRoot = topPage.pageDiv.parentNode; + } + + if (focusableItemsRoot && !focusableItemsRoot.contains(e.target)) + topPage.focusFirstElement(); + }; + + /** + * Called when the page is scrolled; moves elements that are position:fixed + * but should only behave as if they are fixed for vertical scrolling. + * @param {Event} e The scroll event. + * @private + */ + OptionsPage.handleScroll_ = function(e) { + var scrollHorizontalOffset = document.body.scrollLeft; + // position:fixed doesn't seem to work for horizontal scrolling in RTL mode, + // so only adjust in LTR mode (where scroll values will be positive). + if (scrollHorizontalOffset >= 0) { + $('navbar-container').style.left = -scrollHorizontalOffset + 'px'; + var subpageBackdrop = $('subpage-backdrop'); + subpageBackdrop.style.left = subpageBackdrop.horizontalOffset - + scrollHorizontalOffset + 'px'; + this.updateAllFrozenElementPositions_(); + } + }; + + /** + * Updates all frozen pages to match the horizontal scroll position. + * @private + */ + OptionsPage.updateAllFrozenElementPositions_ = function() { + var frozenElements = document.querySelectorAll('.frozen'); + for (var i = 0; i < frozenElements.length; i++) { + this.updateFrozenElementHorizontalPosition_(frozenElements[i]); + } + }; + + /** + * Updates the given frozen element to match the horizontal scroll position. + * @param {HTMLElement} e The frozen element to update + * @private + */ + OptionsPage.updateFrozenElementHorizontalPosition_ = function(e) { + if (document.documentElement.dir == 'rtl') + e.style.right = e.horizontalOffset + 'px'; + else + e.style.left = e.horizontalOffset - document.body.scrollLeft + 'px'; + }; + + /** + * Called when the page is resized; adjusts the size of elements that depend + * on the veiwport. + * @param {Event} e The resize event. + * @private + */ + OptionsPage.handleResize_ = function(e) { + // Set an explicit height equal to the viewport on all the subpage + // containers shorter than the viewport. This is used instead of + // min-height: 100% so that there is an explicit height for the subpages' + // min-height: 100%. + var viewportHeight = document.documentElement.clientHeight; + var subpageContainers = + document.querySelectorAll('.subpage-sheet-container'); + for (var i = 0; i < subpageContainers.length; i++) { + if (subpageContainers[i].scrollHeight > viewportHeight) + subpageContainers[i].style.removeProperty('height'); + else + subpageContainers[i].style.height = viewportHeight + 'px'; + } + }; + + /** + * A function to handle mouse events (mousedown or click) on the html body by + * closing subpages and/or stopping event propagation. + * @return {Event} a mousedown or click event. + * @private + */ + OptionsPage.bodyMouseEventHandler_ = function(event) { + // Do nothing if a subpage isn't showing. + var topPage = this.getTopmostVisiblePage(); + if (!topPage || topPage.isOverlay || !topPage.parentPage) + return; + + // Don't close subpages if a user is clicking in a select element. + // This is necessary because WebKit sends click events with strange + // coordinates when a user selects a new entry in a select element. + // See: http://crbug.com/87199 + if (event.srcElement.nodeName == 'SELECT') + return; + + // Do nothing if the client coordinates are not within the source element. + // This occurs if the user toggles a checkbox by pressing spacebar. + // This is a workaround to prevent keyboard events from closing the window. + // See: crosbug.com/15678 + if (event.clientX == -document.body.scrollLeft && + event.clientY == -document.body.scrollTop) { + return; + } + + // Don't interfere with navbar clicks. + if ($('navbar').contains(event.target)) + return; + + // Figure out which page the click happened in. + for (var level = topPage.nestingLevel; level >= 0; level--) { + var clickIsWithinLevel = level == 0 ? true : + OptionsPage.elementContainsPoint_( + $('subpage-sheet-' + level), event.clientX, event.clientY); + + if (!clickIsWithinLevel) + continue; + + // Event was within the topmost page; do nothing. + if (topPage.nestingLevel == level) + return; + + // Block propgation of both clicks and mousedowns, but only close subpages + // on click. + if (event.type == 'click') + this.closeSubPagesToLevel(level); + event.stopPropagation(); + event.preventDefault(); + return; + } + }; + + /** + * A function to handle key press events. + * @return {Event} a keydown event. + * @private + */ + OptionsPage.keyDownEventHandler_ = function(event) { + // Close the top overlay or sub-page on esc. + if (event.keyCode == 27) { // Esc + if (this.isOverlayVisible_()) + this.closeOverlay(); + else + this.closeTopSubPage_(); + } + }; + + OptionsPage.setClearPluginLSODataEnabled = function(enabled) { + if (enabled) { + document.documentElement.setAttribute( + 'flashPluginSupportsClearSiteData', ''); + } else { + document.documentElement.removeAttribute( + 'flashPluginSupportsClearSiteData'); + } + }; + + /** + * Re-initializes the C++ handlers if necessary. This is called if the + * handlers are torn down and recreated but the DOM may not have been (in + * which case |initialize| won't be called again). If |initialize| hasn't been + * called, this does nothing (since it will be later, once the DOM has + * finished loading). + */ + OptionsPage.reinitializeCore = function() { + if (this.initialized_) + chrome.send('coreOptionsInitialize'); + } + + OptionsPage.prototype = { + __proto__: cr.EventTarget.prototype, + + /** + * The parent page of this option page, or null for top-level pages. + * @type {OptionsPage} + */ + parentPage: null, + + /** + * The section on the parent page that is associated with this page. + * Can be null. + * @type {Element} + */ + associatedSection: null, + + /** + * An array of controls that are associated with this page. The first + * control should be located on a top-level page. + * @type {OptionsPage} + */ + associatedControls: null, + + /** + * Initializes page content. + */ + initializePage: function() {}, + + /** + * Updates managed banner visibility state. This function iterates over + * all input fields of a window and if any of these is marked as managed + * it triggers the managed banner to be visible. The banner can be enforced + * being on through the managed flag of this class but it can not be forced + * being off if managed items exist. + */ + updateManagedBannerVisibility: function() { + var bannerDiv = $('managed-prefs-banner'); + + var controlledByPolicy = false; + var controlledByExtension = false; + var inputElements = this.pageDiv.querySelectorAll('input[controlled-by]'); + for (var i = 0, len = inputElements.length; i < len; i++) { + if (inputElements[i].controlledBy == 'policy') + controlledByPolicy = true; + else if (inputElements[i].controlledBy == 'extension') + controlledByExtension = true; + } + if (!controlledByPolicy && !controlledByExtension) { + bannerDiv.hidden = true; + } else { + bannerDiv.hidden = false; + var height = window.getComputedStyle(bannerDiv).height; + if (controlledByPolicy && !controlledByExtension) { + $('managed-prefs-text').textContent = + templateData.policyManagedPrefsBannerText; + } else if (!controlledByPolicy && controlledByExtension) { + $('managed-prefs-text').textContent = + templateData.extensionManagedPrefsBannerText; + } else if (controlledByPolicy && controlledByExtension) { + $('managed-prefs-text').textContent = + templateData.policyAndExtensionManagedPrefsBannerText; + } + } + }, + + /** + * Gets page visibility state. + */ + get visible() { + return !this.pageDiv.hidden; + }, + + /** + * Sets page visibility. + */ + set visible(visible) { + if ((this.visible && visible) || (!this.visible && !visible)) + return; + + this.setContainerVisibility_(visible); + if (visible) { + this.pageDiv.hidden = false; + + if (this.tab) { + this.tab.classList.add('navbar-item-selected'); + this.tab.setAttribute('aria-selected', 'true'); + this.tab.tabIndex = 0; + } + } else { + this.pageDiv.hidden = true; + + if (this.tab) { + this.tab.classList.remove('navbar-item-selected'); + this.tab.setAttribute('aria-selected', 'false'); + this.tab.tabIndex = -1; + } + } + + OptionsPage.updatePageFreezeStates(); + + // The managed prefs banner is global, so after any visibility change + // update it based on the topmost page, not necessarily this page + // (e.g., if an ancestor is made visible after a child). + OptionsPage.updateManagedBannerVisibility(); + + // A subpage was shown or hidden. + if (!this.isOverlay && this.nestingLevel > 0) { + OptionsPage.updateSubpageBackdrop_(); + OptionsPage.updateScrollPosition_(); + } + + cr.dispatchPropertyChange(this, 'visible', visible, !visible); + }, + + /** + * Shows or hides this page's container. + * @param {boolean} visible Whether the container should be visible or not. + * @private + */ + setContainerVisibility_: function(visible) { + var container = null; + if (this.isOverlay) { + container = $('overlay'); + } else { + var nestingLevel = this.nestingLevel; + if (nestingLevel > 0) + container = $('subpage-sheet-container-' + nestingLevel); + } + var isSubpage = !this.isOverlay; + + if (!container) + return; + + if (container.hidden != visible) { + if (visible) { + // If the container is set hidden and then immediately set visible + // again, the fadeCompleted_ callback would cause it to be erroneously + // hidden again. Removing the transparent tag avoids that. + container.classList.remove('transparent'); + } + return; + } + + if (visible) { + container.hidden = false; + if (isSubpage) { + var computedStyle = window.getComputedStyle(container); + container.style.WebkitPaddingStart = + parseInt(computedStyle.WebkitPaddingStart, 10) + 100 + 'px'; + } + // Separate animating changes from the removal of display:none. + window.setTimeout(function() { + container.classList.remove('transparent'); + if (isSubpage) + container.style.WebkitPaddingStart = ''; + }); + } else { + var self = this; + container.addEventListener('webkitTransitionEnd', function f(e) { + if (e.propertyName != 'opacity') + return; + container.removeEventListener('webkitTransitionEnd', f); + self.fadeCompleted_(container); + }); + container.classList.add('transparent'); + } + }, + + /** + * Called when a container opacity transition finishes. + * @param {HTMLElement} container The container element. + * @private + */ + fadeCompleted_: function(container) { + if (container.classList.contains('transparent')) + container.hidden = true; + }, + + /** + * Focuses the first control on the page. + */ + focusFirstElement: function() { + // Sets focus on the first interactive element in the page. + var focusElement = + this.pageDiv.querySelector('button, input, list, select'); + if (focusElement) + focusElement.focus(); + }, + + /** + * The nesting level of this page. + * @type {number} The nesting level of this page (0 for top-level page) + */ + get nestingLevel() { + var level = 0; + var parent = this.parentPage; + while (parent) { + level++; + parent = parent.parentPage; + } + return level; + }, + + /** + * Whether the page is considered 'sticky', such that it will + * remain a top-level page even if sub-pages change. + * @type {boolean} True if this page is sticky. + */ + get sticky() { + return false; + }, + + /** + * Checks whether this page is an ancestor of the given page in terms of + * subpage nesting. + * @param {OptionsPage} page + * @return {boolean} True if this page is nested under |page| + */ + isAncestorOfPage: function(page) { + var parent = page.parentPage; + while (parent) { + if (parent == this) + return true; + parent = parent.parentPage; + } + return false; + }, + + /** + * Whether it should be possible to show the page. + * @return {boolean} True if the page should be shown + */ + canShowPage: function() { + return true; + }, + }; + + // Export + return { + OptionsPage: OptionsPage + }; +}); diff --git a/chrome/browser/resources/options2/pack_extension_overlay.css b/chrome/browser/resources/options2/pack_extension_overlay.css new file mode 100644 index 0000000..169750b --- /dev/null +++ b/chrome/browser/resources/options2/pack_extension_overlay.css @@ -0,0 +1,18 @@ +/* +Copyright (c) 2011 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. +*/ + +.packExtensionHeading { + width: 520px; + padding-bottom: 5px; +} + +.packExtensionTextBoxes { + text-align: right; +} + +.packExtensionTextArea { + width: 260px; +} diff --git a/chrome/browser/resources/options2/pack_extension_overlay.html b/chrome/browser/resources/options2/pack_extension_overlay.html new file mode 100644 index 0000000..bd77789 --- /dev/null +++ b/chrome/browser/resources/options2/pack_extension_overlay.html @@ -0,0 +1,28 @@ +<div id="packExtensionOverlay" class="page" hidden> + <h1 i18n-content="packExtensionOverlay"></h1> + <div id="cbdContentArea" class="content-area"> + <div class="packExtensionHeading" i18n-content="packExtensionHeading"></div> + <div class="packExtensionTextBoxes"> + <label i18n-content="packExtensionRootDir"></label> + <input class="packExtensionTextArea" id="extensionRootDir" type="text" /> + <button id="browseExtensionDir" + i18n-content="packExtensionBrowseButton"></button> + </div> + <div class="packExtensionTextBoxes"> + <label i18n-content="packExtensionPrivateKey"></label> + <input class="packExtensionTextArea" + id="extensionPrivateKey" type="text" /> + <button id="browsePrivateKey" + i18n-content="packExtensionBrowseButton"></button> + </div> + </div> + <div class="action-area"> + <div class="action-area-right"> + <div class="button-strip"> + <button id="packExtensionDismiss" i18n-content="cancel"></button> + <button id="packExtensionCommit" + i18n-content="packExtensionCommit"></button> + </div> + </div> + </div> +</div>
\ No newline at end of file diff --git a/chrome/browser/resources/options2/pack_extension_overlay.js b/chrome/browser/resources/options2/pack_extension_overlay.js new file mode 100644 index 0000000..4154dcd --- /dev/null +++ b/chrome/browser/resources/options2/pack_extension_overlay.js @@ -0,0 +1,90 @@ +// Copyright (c) 2011 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() { + const OptionsPage = options.OptionsPage; + + /** + * PackExtensionOverlay class + * Encapsulated handling of the 'Pack Extension' overlay page. + * @constructor + */ + function PackExtensionOverlay() { + OptionsPage.call(this, 'packExtensionOverlay', + templateData.packExtensionOverlayTabTitle, + 'packExtensionOverlay'); + } + + cr.addSingletonGetter(PackExtensionOverlay); + + PackExtensionOverlay.prototype = { + // Inherit PackExtensionOverlay from OptionsPage. + __proto__: OptionsPage.prototype, + + /** + * Initialize the page. + */ + initializePage: function() { + // Call base class implementation to starts preference initialization. + OptionsPage.prototype.initializePage.call(this); + + $('packExtensionDismiss').onclick = function(event) { + OptionsPage.closeOverlay(); + }; + $('packExtensionCommit').onclick = function(event) { + var extensionPath = $('extensionRootDir').value; + var privateKeyPath = $('extensionPrivateKey').value; + chrome.send('pack', [extensionPath, privateKeyPath]); + }; + $('browseExtensionDir').addEventListener('click', + this.handleBrowseExtensionDir_.bind(this)); + $('browsePrivateKey').addEventListener('click', + this.handleBrowsePrivateKey_.bind(this)); + }, + + /** + * Utility function which asks the C++ to show a platform-specific file + * select dialog, and fire |callback| with the |filePath| that resulted. + * |selectType| can be either 'file' or 'folder'. |operation| can be 'load', + * 'packRoot', or 'pem' which are signals to the C++ to do some + * operation-specific configuration. + * @private + */ + showFileDialog_: function(selectType, operation, callback) { + handleFilePathSelected = function(filePath) { + callback(filePath); + handleFilePathSelected = function() {}; + }; + + chrome.send('extensionSettingsSelectFilePath', [selectType, operation]); + }, + + /** + * Handles the showing of the extension directory browser. + * @param {Event} e Change event. + * @private + */ + handleBrowseExtensionDir_: function(e) { + this.showFileDialog_('folder', 'load', function(filePath) { + $('extensionRootDir').value = filePath; + }); + }, + + /** + * Handles the showing of the extension private key file. + * @param {Event} e Change event. + * @private + */ + handleBrowsePrivateKey_: function(e) { + this.showFileDialog_('file', 'load', function(filePath) { + $('extensionPrivateKey').value = filePath; + }); + }, + }; + + // Export + return { + PackExtensionOverlay: PackExtensionOverlay + }; +}); diff --git a/chrome/browser/resources/options2/password_manager.css b/chrome/browser/resources/options2/password_manager.css new file mode 100644 index 0000000..76ec562 --- /dev/null +++ b/chrome/browser/resources/options2/password_manager.css @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2011 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. + */ + +#password-search-column { + bottom: 10px; + position: absolute; + right: 0; +} + +html[dir=rtl] #password-search-column { + left: 0; + right: auto; +} + +#password-list-headers { + position: relative; + width: 100%; +} + +#password-list-headers h3 { + font-size: 105%; + font-weight: bold; + margin: 10px 0; +} + +#passwords-title { + display: inline-block; +} diff --git a/chrome/browser/resources/options2/password_manager.html b/chrome/browser/resources/options2/password_manager.html new file mode 100644 index 0000000..333042f --- /dev/null +++ b/chrome/browser/resources/options2/password_manager.html @@ -0,0 +1,28 @@ +<div id="password-manager" class="page" hidden> + <h1 i18n-content="passwordsPage"></h1> + <div id="password-list-headers"> + <div id="passwords-title"> + <h3 i18n-content="savedPasswordsTitle"></h3> + </div> + <div id="password-search-column"> + <input id="password-search-box" type="search" + i18n-values="placeholder:passwordSearchPlaceholder" incremental + results="10" autosave="org.chromium.options.passwords.search"> + </div> + </div> + <list id="saved-passwords-list" class="settings-list"></list> + <div id="saved-passwords-list-empty-placeholder" + class="settings-list-empty" hidden> + <span i18n-content="passwordsNoPasswordsDescription"></span> + <a target="_blank" i18n-content="learnMore" + i18n-values="href:passwordManagerLearnMoreURL"></a> + </div> + <h3 i18n-content="passwordExceptionsTitle"></h3> + <list id="password-exceptions-list" class="settings-list"></list> + <div id="password-exceptions-list-empty-placeholder" hidden + class="settings-list-empty"> + <span i18n-content="passwordsNoExceptionsDescription"></span> + <a target="_blank" i18n-content="learnMore" + i18n-values="href:passwordManagerLearnMoreURL"></a> + </div> +</div> diff --git a/chrome/browser/resources/options2/password_manager.js b/chrome/browser/resources/options2/password_manager.js new file mode 100644 index 0000000..273d2c0 --- /dev/null +++ b/chrome/browser/resources/options2/password_manager.js @@ -0,0 +1,228 @@ +// Copyright (c) 2011 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() { + const OptionsPage = options.OptionsPage; + const ArrayDataModel = cr.ui.ArrayDataModel; + + ///////////////////////////////////////////////////////////////////////////// + // PasswordManager class: + + /** + * Encapsulated handling of password and exceptions page. + * @constructor + */ + function PasswordManager() { + this.activeNavTab = null; + OptionsPage.call(this, + 'passwords', + templateData.passwordsPageTabTitle, + 'password-manager'); + } + + cr.addSingletonGetter(PasswordManager); + + PasswordManager.prototype = { + __proto__: OptionsPage.prototype, + + /** + * The saved passwords list. + * @type {DeletableItemList} + * @private + */ + savedPasswordsList_: null, + + /** + * The password exceptions list. + * @type {DeletableItemList} + * @private + */ + passwordExceptionsList_: null, + + /** + * The timer id of the timer set on search query change events. + * @type {number} + * @private + */ + queryDelayTimerId_: 0, + + /** + * The most recent search query, or null if the query is empty. + * @type {?string} + * @private + */ + lastQuery_: null, + + /** @inheritDoc */ + initializePage: function() { + OptionsPage.prototype.initializePage.call(this); + + $('password-search-box').addEventListener('search', + this.handleSearchQueryChange_.bind(this)); + + this.createSavedPasswordsList_(); + this.createPasswordExceptionsList_(); + }, + + /** @inheritDoc */ + canShowPage: function() { + return !PersonalOptions.disablePasswordManagement(); + }, + + /** @inheritDoc */ + didShowPage: function() { + // Updating the password lists may cause a blocking platform dialog pop up + // (Mac, Linux), so we delay this operation until the page is shown. + chrome.send('updatePasswordLists'); + $('password-search-box').focus(); + }, + + /** + * Creates, decorates and initializes the saved passwords list. + * @private + */ + createSavedPasswordsList_: function() { + this.savedPasswordsList_ = $('saved-passwords-list'); + options.passwordManager.PasswordsList.decorate(this.savedPasswordsList_); + this.savedPasswordsList_.autoExpands = true; + }, + + /** + * Creates, decorates and initializes the password exceptions list. + * @private + */ + createPasswordExceptionsList_: function() { + this.passwordExceptionsList_ = $('password-exceptions-list'); + options.passwordManager.PasswordExceptionsList.decorate( + this.passwordExceptionsList_); + this.passwordExceptionsList_.autoExpands = true; + }, + + /** + * Handles search query changes. + * @param {!Event} e The event object. + * @private + */ + handleSearchQueryChange_: function(e) { + if (this.queryDelayTimerId_) + window.clearTimeout(this.queryDelayTimerId_); + + // Searching cookies uses a timeout of 500ms. We use a shorter timeout + // because there are probably fewer passwords and we want the UI to be + // snappier since users will expect that it's "less work." + this.queryDelayTimerId_ = window.setTimeout( + this.searchPasswords_.bind(this), 250); + }, + + /** + * Search passwords using text in |password-search-box|. + * @private + */ + searchPasswords_: function() { + this.queryDelayTimerId_ = 0; + var filter = $('password-search-box').value; + filter = (filter == '') ? null : filter; + if (this.lastQuery_ != filter) { + this.lastQuery_ = filter; + // Searching for passwords has the side effect of requerying the + // underlying password store. This is done intentionally, as on OS X and + // Linux they can change from outside and we won't be notified of it. + chrome.send('updatePasswordLists'); + } + }, + + /** + * Updates the visibility of the list and empty list placeholder. + * @param {!List} list The list to toggle visilibility for. + */ + updateListVisibility_: function(list) { + var empty = list.dataModel.length == 0; + var listPlaceHolderID = list.id + '-empty-placeholder'; + list.hidden = empty; + $(listPlaceHolderID).hidden = !empty; + }, + + /** + * Updates the data model for the saved passwords list with the values from + * |entries|. + * @param {Array} entries The list of saved password data. + */ + setSavedPasswordsList_: function(entries) { + if (this.lastQuery_) { + // Implement password searching here in javascript, rather than in C++. + // The number of saved passwords shouldn't be too big for us to handle. + var query = this.lastQuery_; + var filter = function(entry, index, list) { + // Search both URL and username. + if (entry[0].indexOf(query) >= 0 || entry[1].indexOf(query) >= 0) { + // Keep the original index so we can delete correctly. See also + // deleteItemAtIndex() in password_manager_list.js that uses this. + entry[3] = index; + return true; + } + return false; + }; + entries = entries.filter(filter); + } + this.savedPasswordsList_.dataModel = new ArrayDataModel(entries); + this.updateListVisibility_(this.savedPasswordsList_); + }, + + /** + * Updates the data model for the password exceptions list with the values + * from |entries|. + * @param {Array} entries The list of password exception data. + */ + setPasswordExceptionsList_: function(entries) { + this.passwordExceptionsList_.dataModel = new ArrayDataModel(entries); + this.updateListVisibility_(this.passwordExceptionsList_); + }, + }; + + /** + * Call to remove a saved password. + * @param rowIndex indicating the row to remove. + */ + PasswordManager.removeSavedPassword = function(rowIndex) { + chrome.send('removeSavedPassword', [String(rowIndex)]); + }; + + /** + * Call to remove a password exception. + * @param rowIndex indicating the row to remove. + */ + PasswordManager.removePasswordException = function(rowIndex) { + chrome.send('removePasswordException', [String(rowIndex)]); + }; + + /** + * Call to remove all saved passwords. + * @param tab contentType of the tab currently on. + */ + PasswordManager.removeAllPasswords = function() { + chrome.send('removeAllSavedPasswords'); + }; + + /** + * Call to remove all saved passwords. + * @param tab contentType of the tab currently on. + */ + PasswordManager.removeAllPasswordExceptions = function() { + chrome.send('removeAllPasswordExceptions'); + }; + + PasswordManager.setSavedPasswordsList = function(entries) { + PasswordManager.getInstance().setSavedPasswordsList_(entries); + }; + + PasswordManager.setPasswordExceptionsList = function(entries) { + PasswordManager.getInstance().setPasswordExceptionsList_(entries); + }; + + // Export + return { + PasswordManager: PasswordManager + }; + +}); diff --git a/chrome/browser/resources/options2/password_manager_list.css b/chrome/browser/resources/options2/password_manager_list.css new file mode 100644 index 0000000..576ecfd --- /dev/null +++ b/chrome/browser/resources/options2/password_manager_list.css @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2011 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. + */ + +button.password-button { + -webkit-transition: opacity .15s; + background: #8aaaed; + color: #fff; + display: inline; + font-size: 90%; + font-weight: bold; + height: 18px; + opacity: 0.3; + padding: 0 2px; + position: absolute; + top: 5px; +} + +button.password-button:hover { + -webkit-transition: opacity .15s; + opacity: 1; +} + +html[dir='ltr'] button.password-button { + right: 2px; +} + +html[dir='rtl'] button.password-button { + left: 2px; +} + +input[type="password"].inactive-password { + background: transparent; + border: none; +} + +#saved-passwords-list .url { + box-sizing: border-box; + width: 40%; +} + +#saved-passwords-list .name { + -webkit-box-flex: 1; + width: 20%; +} + +#saved-passwords-list .password { + -webkit-box-flex: 1; + position: relative; +} + +#saved-passwords-list .password input[type="password"], +#saved-passwords-list .password input[type="text"] { + box-sizing: border-box; + width: 100%; +} + +#password-exceptions-list .url { + -webkit-box-flex: 1; +} + +#saved-passwords-list .url, +#saved-passwords-list .name, +#password-exceptions-list .url { + overflow: hidden; + text-overflow: ellipsis; +} diff --git a/chrome/browser/resources/options2/password_manager_list.js b/chrome/browser/resources/options2/password_manager_list.js new file mode 100644 index 0000000..c3b822a --- /dev/null +++ b/chrome/browser/resources/options2/password_manager_list.js @@ -0,0 +1,283 @@ +// Copyright (c) 2011 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.passwordManager', function() { + const ArrayDataModel = cr.ui.ArrayDataModel; + const DeletableItemList = options.DeletableItemList; + const DeletableItem = options.DeletableItem; + const List = cr.ui.List; + + /** + * Creates a new passwords list item. + * @param {Array} entry An array of the form [url, username, password]. When + * the list has been filtered, a fourth element [index] may be present. + * @constructor + * @extends {cr.ui.ListItem} + */ + function PasswordListItem(entry, showPasswords) { + var el = cr.doc.createElement('div'); + el.dataItem = entry; + el.__proto__ = PasswordListItem.prototype; + el.decorate(showPasswords); + + return el; + } + + PasswordListItem.prototype = { + __proto__: DeletableItem.prototype, + + /** @inheritDoc */ + decorate: function(showPasswords) { + DeletableItem.prototype.decorate.call(this); + + // The URL of the site. + var urlLabel = this.ownerDocument.createElement('div'); + urlLabel.classList.add('favicon-cell'); + urlLabel.classList.add('weakrtl'); + urlLabel.classList.add('url'); + urlLabel.setAttribute('title', this.url); + urlLabel.textContent = this.url; + urlLabel.style.backgroundImage = url('chrome://favicon/' + this.url); + this.contentElement.appendChild(urlLabel); + + // The stored username. + var usernameLabel = this.ownerDocument.createElement('div'); + usernameLabel.className = 'name'; + usernameLabel.textContent = this.username; + this.contentElement.appendChild(usernameLabel); + + // The stored password. + var passwordInputDiv = this.ownerDocument.createElement('div'); + passwordInputDiv.className = 'password'; + + // The password input field. + var passwordInput = this.ownerDocument.createElement('input'); + passwordInput.type = 'password'; + passwordInput.className = 'inactive-password'; + passwordInput.readOnly = true; + passwordInput.value = showPasswords ? this.password : '********'; + passwordInputDiv.appendChild(passwordInput); + + // The show/hide button. + if (showPasswords) { + var button = this.ownerDocument.createElement('button'); + button.hidden = true; + button.classList.add('password-button'); + button.textContent = localStrings.getString('passwordShowButton'); + button.addEventListener('click', this.onClick_, true); + passwordInputDiv.appendChild(button); + } + + this.contentElement.appendChild(passwordInputDiv); + }, + + /** @inheritDoc */ + selectionChanged: function() { + var passwordInput = this.querySelector('input[type=password]'); + var textInput = this.querySelector('input[type=text]'); + var input = passwordInput || textInput; + var button = input.nextSibling; + // |button| doesn't exist when passwords can't be shown. + if (!button) + return; + if (this.selected) { + input.classList.remove('inactive-password'); + button.hidden = false; + } else { + input.classList.add('inactive-password'); + button.hidden = true; + } + }, + + /** + * On-click event handler. Swaps the type of the input field from password + * to text and back. + * @private + */ + onClick_: function(event) { + // The password is the input element previous to the button. + var button = event.currentTarget; + var passwordInput = button.previousSibling; + if (passwordInput.type == 'password') { + passwordInput.type = 'text'; + button.textContent = localStrings.getString('passwordHideButton'); + } else { + passwordInput.type = 'password'; + button.textContent = localStrings.getString('passwordShowButton'); + } + }, + + /** + * Get and set the URL for the entry. + * @type {string} + */ + get url() { + return this.dataItem[0]; + }, + set url(url) { + this.dataItem[0] = url; + }, + + /** + * Get and set the username for the entry. + * @type {string} + */ + get username() { + return this.dataItem[1]; + }, + set username(username) { + this.dataItem[1] = username; + }, + + /** + * Get and set the password for the entry. + * @type {string} + */ + get password() { + return this.dataItem[2]; + }, + set password(password) { + this.dataItem[2] = password; + }, + }; + + /** + * Creates a new PasswordExceptions list item. + * @param {Array} entry A pair of the form [url, username]. + * @constructor + * @extends {Deletable.ListItem} + */ + function PasswordExceptionsListItem(entry) { + var el = cr.doc.createElement('div'); + el.dataItem = entry; + el.__proto__ = PasswordExceptionsListItem.prototype; + el.decorate(); + + return el; + } + + PasswordExceptionsListItem.prototype = { + __proto__: DeletableItem.prototype, + + /** + * Call when an element is decorated as a list item. + */ + decorate: function() { + DeletableItem.prototype.decorate.call(this); + + // The URL of the site. + var urlLabel = this.ownerDocument.createElement('div'); + urlLabel.className = 'url'; + urlLabel.classList.add('favicon-cell'); + urlLabel.classList.add('weakrtl'); + urlLabel.textContent = this.url; + urlLabel.style.backgroundImage = url('chrome://favicon/' + this.url); + this.contentElement.appendChild(urlLabel); + }, + + /** + * Get the url for the entry. + * @type {string} + */ + get url() { + return this.dataItem; + }, + set url(url) { + this.dataItem = url; + }, + }; + + /** + * Create a new passwords list. + * @constructor + * @extends {cr.ui.List} + */ + var PasswordsList = cr.ui.define('list'); + + PasswordsList.prototype = { + __proto__: DeletableItemList.prototype, + + /** + * Whether passwords can be revealed or not. + * @type {boolean} + * @private + */ + showPasswords_: true, + + /** @inheritDoc */ + decorate: function() { + DeletableItemList.prototype.decorate.call(this); + Preferences.getInstance().addEventListener( + "profile.password_manager_allow_show_passwords", + this.onPreferenceChanged_.bind(this)); + }, + + /** + * Listener for changes on the preference. + * @param {Event} event The preference update event. + * @private + */ + onPreferenceChanged_: function(event) { + this.showPasswords_ = event.value.value; + this.redraw(); + }, + + /** @inheritDoc */ + createItem: function(entry) { + return new PasswordListItem(entry, this.showPasswords_); + }, + + /** @inheritDoc */ + deleteItemAtIndex: function(index) { + var item = this.dataModel.item(index); + if (item && item.length > 3) { + // The fourth element, if present, is the original index to delete. + index = item[3]; + } + PasswordManager.removeSavedPassword(index); + }, + + /** + * The length of the list. + */ + get length() { + return this.dataModel.length; + }, + }; + + /** + * Create a new passwords list. + * @constructor + * @extends {cr.ui.List} + */ + var PasswordExceptionsList = cr.ui.define('list'); + + PasswordExceptionsList.prototype = { + __proto__: DeletableItemList.prototype, + + /** @inheritDoc */ + createItem: function(entry) { + return new PasswordExceptionsListItem(entry); + }, + + /** @inheritDoc */ + deleteItemAtIndex: function(index) { + PasswordManager.removePasswordException(index); + }, + + /** + * The length of the list. + */ + get length() { + return this.dataModel.length; + }, + }; + + return { + PasswordListItem: PasswordListItem, + PasswordExceptionsListItem: PasswordExceptionsListItem, + PasswordsList: PasswordsList, + PasswordExceptionsList: PasswordExceptionsList, + }; +}); diff --git a/chrome/browser/resources/options2/personal_options.css b/chrome/browser/resources/options2/personal_options.css new file mode 100644 index 0000000..ee18893 --- /dev/null +++ b/chrome/browser/resources/options2/personal_options.css @@ -0,0 +1,66 @@ +#account-picture-wrapper { + border-radius: 4px; + border: 1px solid rgba(0, 0, 0, 0.3); + display: inline-block; + margin: 5px 10px 5px 2px; + padding: 3px; +} + +#account-picture { + width: 70px; + height: 70px; + vertical-align: middle; +} + +#sync-buttons, #profiles-buttons { + margin-top: 10px; +} + +#start-stop-sync { + margin-left: 0; + margin-right: 5px; +} + +#profiles-list { + min-height: 0; + margin-bottom: 10px; +} + +#profiles-list > * { + height: 40px; +} + +.profile-img { + height: 31px; + padding: 3px; + vertical-align: middle; + width: 38px; +} + +.profile-item-current { + font-weight: bold; +} + +#themes-gallery-div { + margin: 10px 0; +} + +.sync-error { + background: #FFDBDB; + border: 1px solid #ce4c4c; + border-radius: 2px; + padding: 10px; +} + +.sync-error .link-button { + margin: 0 1ex; + padding: 0; +} + +#enable-auto-login-checkbox { + margin-top: 10px; +} + +#mac-passwords-warning { + margin-top: 10px; +} diff --git a/chrome/browser/resources/options2/personal_options.html b/chrome/browser/resources/options2/personal_options.html new file mode 100644 index 0000000..620ec06 --- /dev/null +++ b/chrome/browser/resources/options2/personal_options.html @@ -0,0 +1,162 @@ +<div id="personal-page" class="page" hidden> + <h1 i18n-content="personalPage"></h1> + <div class="displaytable"> +<if expr="pp_ifdef('chromeos')"> + <section> + <h3 i18n-content="account"></h3> + <div> + <span id="account-picture-wrapper"> + <img id="account-picture" + src="chrome://theme/IDR_PROFILE_PICTURE_LOADING"> + </span> + <label> + <input id="enable-screen-lock" type="checkbox" + pref="settings.enable_screen_lock"> + <span i18n-content="enableScreenlock"></span> + </label> + <br> + <button id="change-picture-button" i18n-content="changePicture"> + </button> + </div> + </section> +</if> + <section id="sync-section"> + <h3 i18n-content="syncSection"></h3> + <div> + <div id="sync-overview" hidden> + <span i18n-content="syncOverview"></span> + <a i18n-values="href:syncLearnMoreURL" i18n-content="learnMore"></a> + </div> + <div id="sync-status" hidden> + <span id="sync-status-text"></span> + <button id="sync-action-link" class="link-button"></button> + </div> + <div id="sync-buttons"> + <button id="start-stop-sync" hidden></button> + <button id="customize-sync" i18n-content="customizeSync" hidden> + </button> + <div id="enable-auto-login-checkbox" class="checkbox" hidden> + <label> + <input id="enable-auto-login" pref="autologin.enabled" + metric="Options_Autologin" type="checkbox"> + <span i18n-content="autologinEnabled"></span> + </label> + </div> + </div> + </div> + </section> + <section id="profiles-section" hidden> + <h3 i18n-content="profiles"></h3> + <div> + <list id="profiles-list" class="settings-list" hidden></list> + <div id="profiles-single-message" i18n-content="profilesSingleUser"> + </div> + <div id="profiles-buttons"> + <button id="profiles-create" i18n-content="profilesCreate"></button> + <button id="profiles-manage" i18n-content="profilesManage" disabled> + </button> + <button id="profiles-delete" i18n-content="profilesDelete"> + </button> + </div> + </div> + </section> + <section> + <h3 i18n-content="passwords"></h3> + <div> + <div class="radio"> + <label> + <input id="passwords-offersave" type="radio" name="passwords_radio" + value="true" pref="profile.password_manager_enabled" + metric="Options_PasswordManager"> + <span i18n-content="passwordsAskToSave"></span> + </label> + </div> + <div class="radio"> + <label> + <input id="passwords-neversave" type="radio" name="passwords_radio" + value="false" pref="profile.password_manager_enabled" + metric="Options_PasswordManager"> + <span i18n-content="passwordsNeverSave"></span> + </label> + </div> + <div><button id="manage-passwords" i18n-content="manage_passwords" + pref="profile.password_manager_enabled"></button></div> + <div id="mac-passwords-warning" i18n-content="macPasswordsWarning" + hidden></div> + <!-- This makes the managed-banner appear when the "pref" is + configured by the IT administrator. --> + <input name="password_allow_show_hidden" type="text" + pref="profile.password_manager_allow_show_passwords" hidden> + </div> + </section> + <section id="autofill-section"> + <h3 i18n-content="autofill"></h3> + <div> + <div class="checkbox"> + <label> + <input id="autofill-enabled" pref="autofill.enabled" + metric="Options_FormAutofill" type="checkbox"> + <span i18n-content="autofillEnabled"></span> + </label> + </div> + <button id="autofill-settings" pref="autofill.enabled" + i18n-content="manageAutofillSettings"></button> + </div> + </section> +<if expr="not pp_ifdef('chromeos')"> + <section> + <h3 i18n-content="browsingData"></h3> + <div> + <button id="import-data" i18n-content="importData"></button> + </div> + </section> +</if> +<if expr="not pp_ifdef('toolkit_views') and is_posix and not is_macosx"> + <section> + <h3 i18n-content="appearance"></h3> + <div> + <div> + <button id="themes-GTK-button" + i18n-content="themesGTKButton"></button> + <button id="themes-reset" + i18n-content="themesSetClassic"></button> + </div> + <div id="themes-gallery-div"> + <a id="themes-gallery" i18n-content="themesGallery" + i18n-values="href:themesGalleryURL" target="_blank"></a> + </div> + <div class="radio"> + <label> + <input name="decorations_radio" + pref="browser.custom_chrome_frame" + type="radio" value="false" metric="Options_CustomFrame"> + <span i18n-content="showWindowDecorations"></span> + </label> + </div> + <div class="radio"> + <label> + <input name="decorations_radio" + pref="browser.custom_chrome_frame" + type="radio" value="true" metric="Options_CustomFrame"> + <span i18n-content="hideWindowDecorations"></span> + </label> + </div> + </div> + </section> +</if> +<if expr="pp_ifdef('toolkit_views') or os == 'win32' or os == 'darwin'"> + <section> + <h3 i18n-content="themes"></h3> + <div> + <div> + <button id="themes-reset" i18n-content="themesReset"></button> + </div> + <div id="themes-gallery-div"> + <a id="themes-gallery" i18n-content="themesGallery" + i18n-values="href:themesGalleryURL" target="_blank"></a> + </div> + </div> + </section> +</if> + </div> +</div> diff --git a/chrome/browser/resources/options2/personal_options.js b/chrome/browser/resources/options2/personal_options.js new file mode 100644 index 0000000..78d40dc --- /dev/null +++ b/chrome/browser/resources/options2/personal_options.js @@ -0,0 +1,372 @@ +// Copyright (c) 2011 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; + + /** + * Encapsulated handling of personal options page. + * @constructor + */ + function PersonalOptions() { + OptionsPage.call(this, 'personal', + templateData.personalPageTabTitle, + 'personal-page'); + if (cr.isChromeOS) { + // Username (canonical email) of the currently logged in user or + // |kGuestUser| if a guest session is active. + this.username_ = localStrings.getString('username'); + } + } + + cr.addSingletonGetter(PersonalOptions); + + PersonalOptions.prototype = { + // Inherit PersonalOptions from OptionsPage. + __proto__: options.OptionsPage.prototype, + + // State variables. + syncEnabled: false, + syncSetupCompleted: false, + + // Initialize PersonalOptions page. + initializePage: function() { + // Call base class implementation to start preference initialization. + OptionsPage.prototype.initializePage.call(this); + + var self = this; + + // Sync. + $('sync-action-link').onclick = function(event) { + SyncSetupOverlay.showErrorUI(); + }; + $('start-stop-sync').onclick = function(event) { + if (self.syncSetupCompleted) + SyncSetupOverlay.showStopSyncingUI(); + else + SyncSetupOverlay.showSetupUI(); + }; + $('customize-sync').onclick = function(event) { + SyncSetupOverlay.showSetupUI(); + }; + + // Profiles. + var profilesList = $('profiles-list'); + options.personal_options.ProfileList.decorate(profilesList); + profilesList.autoExpands = true; + + profilesList.onchange = self.setProfileViewButtonsStatus_; + $('profiles-create').onclick = function(event) { + chrome.send('createProfile'); + }; + $('profiles-manage').onclick = function(event) { + var selectedProfile = self.getSelectedProfileItem_(); + if (selectedProfile) + ManageProfileOverlay.showManageDialog(selectedProfile); + }; + $('profiles-delete').onclick = function(event) { + var selectedProfile = self.getSelectedProfileItem_(); + if (selectedProfile) + ManageProfileOverlay.showDeleteDialog(selectedProfile); + }; + + // Passwords. + $('manage-passwords').onclick = function(event) { + OptionsPage.navigateToPage('passwords'); + OptionsPage.showTab($('passwords-nav-tab')); + chrome.send('coreOptionsUserMetricsAction', + ['Options_ShowPasswordManager']); + }; + + // Autofill. + $('autofill-settings').onclick = function(event) { + OptionsPage.navigateToPage('autofill'); + chrome.send('coreOptionsUserMetricsAction', + ['Options_ShowAutofillSettings']); + }; + if (cr.isChromeOS && cr.commandLine && cr.commandLine.options['--bwsi']) { + // Hide Autofill options for the guest user. + $('autofill-section').hidden = true; + } + + // Appearance. + $('themes-reset').onclick = function(event) { + chrome.send('themesReset'); + }; + + if (!cr.isChromeOS) { + $('import-data').onclick = function(event) { + // Make sure that any previous import success message is hidden, and + // we're showing the UI to import further data. + $('import-data-configure').hidden = false; + $('import-data-success').hidden = true; + OptionsPage.navigateToPage('importData'); + chrome.send('coreOptionsUserMetricsAction', ['Import_ShowDlg']); + }; + + if ($('themes-GTK-button')) { + $('themes-GTK-button').onclick = function(event) { + chrome.send('themesSetGTK'); + }; + } + } else { + $('change-picture-button').onclick = function(event) { + OptionsPage.navigateToPage('changePicture'); + }; + this.updateAccountPicture_(); + + if (cr.commandLine && cr.commandLine.options['--bwsi']) { + // Disable the screen lock checkbox and change-picture-button in + // guest mode. + $('enable-screen-lock').disabled = true; + $('change-picture-button').disabled = true; + } + } + + if (PersonalOptions.disablePasswordManagement()) { + // Disable the Password Manager in guest mode. + $('passwords-offersave').disabled = true; + $('passwords-neversave').disabled = true; + $('passwords-offersave').value = false; + $('passwords-neversave').value = true; + $('manage-passwords').disabled = true; + } + + $('mac-passwords-warning').hidden = + !(localStrings.getString('macPasswordsWarning')); + + if (PersonalOptions.disableAutofillManagement()) { + $('autofill-settings').disabled = true; + + // Disable and turn off autofill. + var autofillEnabled = $('autofill-enabled'); + autofillEnabled.disabled = true; + autofillEnabled.checked = false; + cr.dispatchSimpleEvent(autofillEnabled, 'change'); + } + }, + + setSyncEnabled_: function(enabled) { + this.syncEnabled = enabled; + }, + + setAutoLoginVisible_ : function(visible) { + $('enable-auto-login-checkbox').hidden = !visible; + }, + + setSyncSetupCompleted_: function(completed) { + this.syncSetupCompleted = completed; + $('customize-sync').hidden = !completed; + }, + + setSyncStatus_: function(status) { + var statusSet = status != ''; + $('sync-overview').hidden = statusSet; + $('sync-status').hidden = !statusSet; + $('sync-status-text').innerHTML = status; + }, + + setSyncStatusErrorVisible_: function(visible) { + visible ? $('sync-status').classList.add('sync-error') : + $('sync-status').classList.remove('sync-error'); + }, + + setCustomizeSyncButtonEnabled_: function(enabled) { + $('customize-sync').disabled = !enabled; + }, + + setSyncActionLinkEnabled_: function(enabled) { + $('sync-action-link').disabled = !enabled; + }, + + setSyncActionLinkLabel_: function(status) { + $('sync-action-link').textContent = status; + + // link-button does is not zero-area when the contents of the button are + // empty, so explicitly hide the element. + $('sync-action-link').hidden = !status.length; + }, + + /** + * Display or hide the profiles section of the page. This is used for + * multi-profile settings. + * @param {boolean} visible True to show the section. + * @private + */ + setProfilesSectionVisible_: function(visible) { + $('profiles-section').hidden = !visible; + }, + + /** + * 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. + */ + setProfileViewButtonsStatus_: function() { + var profilesList = $('profiles-list'); + var selectedProfile = profilesList.selectedItem; + var hasSelection = selectedProfile != null; + var hasSingleProfile = profilesList.dataModel.length == 1; + $('profiles-manage').disabled = !hasSelection || + !selectedProfile.isCurrentProfile; + $('profiles-delete').disabled = !hasSelection && !hasSingleProfile; + }, + + /** + * Display the correct dialog layout, depending on how many profiles are + * available. + * @param {number} numProfiles The number of profiles to display. + */ + setProfileViewSingle_: function(numProfiles) { + var hasSingleProfile = numProfiles == 1; + $('profiles-list').hidden = hasSingleProfile; + $('profiles-single-message').hidden = !hasSingleProfile; + $('profiles-manage').hidden = hasSingleProfile; + $('profiles-delete').textContent = hasSingleProfile ? + templateData.profilesDeleteSingle : + templateData.profilesDelete; + }, + + /** + * Adds all |profiles| to the list. + * @param {Array.<Object>} 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 + * }; + */ + 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); + this.setProfileViewButtonsStatus_(); + }, + + setStartStopButtonVisible_: function(visible) { + $('start-stop-sync').hidden = !visible; + }, + + setStartStopButtonEnabled_: function(enabled) { + $('start-stop-sync').disabled = !enabled; + }, + + setStartStopButtonLabel_: function(label) { + $('start-stop-sync').textContent = label; + }, + + setGtkThemeButtonEnabled_: function(enabled) { + if (!cr.isChromeOS && navigator.platform.match(/linux|BSD/i)) { + $('themes-GTK-button').disabled = !enabled; + } + }, + + setThemesResetButtonEnabled_: function(enabled) { + $('themes-reset').disabled = !enabled; + }, + + hideSyncSection_: function() { + $('sync-section').hidden = true; + }, + + /** + * Get the start/stop sync button DOM element. + * @return {DOMElement} The start/stop sync button. + * @private + */ + getStartStopSyncButton_: function() { + return $('start-stop-sync'); + }, + + /** + * (Re)loads IMG element with current user account picture. + */ + updateAccountPicture_: function() { + $('account-picture').src = + 'chrome://userimage/' + this.username_ + + '?id=' + (new Date()).getTime(); + }, + }; + + /** + * Returns whether the user should be able to manage (view and edit) their + * stored passwords. Password management is disabled in guest mode. + * @return {boolean} True if password management should be disabled. + */ + PersonalOptions.disablePasswordManagement = function() { + return cr.commandLine && cr.commandLine.options['--bwsi']; + }; + + /** + * Returns whether the user should be able to manage autofill settings. + * @return {boolean} True if password management should be disabled. + */ + PersonalOptions.disableAutofillManagement = function() { + return cr.commandLine && cr.commandLine.options['--bwsi']; + }; + + if (cr.isChromeOS) { + /** + * Returns username (canonical email) of the user logged in (ChromeOS only). + * @return {string} user email. + */ + PersonalOptions.getLoggedInUsername = function() { + return PersonalOptions.getInstance().username_; + }; + } + + // Forward public APIs to private implementations. + [ + 'getStartStopSyncButton', + 'hideSyncSection', + 'setAutoLoginVisible', + 'setCustomizeSyncButtonEnabled', + 'setGtkThemeButtonEnabled', + 'setProfilesInfo', + 'setProfilesSectionVisible', + 'setStartStopButtonEnabled', + 'setStartStopButtonLabel', + 'setStartStopButtonVisible', + 'setSyncActionLinkEnabled', + 'setSyncActionLinkLabel', + 'setSyncEnabled', + 'setSyncSetupCompleted', + 'setSyncStatus', + 'setSyncStatusErrorVisible', + 'setThemesResetButtonEnabled', + 'updateAccountPicture', + ].forEach(function(name) { + PersonalOptions[name] = function(value) { + return PersonalOptions.getInstance()[name + '_'](value); + }; + }); + + // Export + return { + PersonalOptions: PersonalOptions + }; + +}); diff --git a/chrome/browser/resources/options2/personal_options_profile_list.js b/chrome/browser/resources/options2/personal_options_profile_list.js new file mode 100644 index 0000000..64436a3 --- /dev/null +++ b/chrome/browser/resources/options2/personal_options_profile_list.js @@ -0,0 +1,105 @@ +// Copyright (c) 2011 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.personal_options', function() { + const DeletableItem = options.DeletableItem; + const DeletableItemList = options.DeletableItemList; + const ListSingleSelectionModel = cr.ui.ListSingleSelectionModel; + + var localStrings = new LocalStrings(); + + /** + * Creates a new profile list item. + * @param {Object} profileInfo The profile this item respresents. + * @constructor + * @extends {cr.ui.DeletableItem} + */ + function ProfileListItem(profileInfo) { + var el = cr.doc.createElement('div'); + el.profileInfo_ = profileInfo; + ProfileListItem.decorate(el); + return el; + } + + /** + * Decorates an element as a profile list item. + * @param {!HTMLElement} el The element to decorate. + */ + ProfileListItem.decorate = function(el) { + el.__proto__ = ProfileListItem.prototype; + el.decorate(); + }; + + ProfileListItem.prototype = { + __proto__: DeletableItem.prototype, + + /** + * Get the filepath for this profile list item. + * @return the file path of this item. + */ + get profilePath() { + return this.profileInfo_.filePath; + }, + + /** @inheritDoc */ + decorate: function() { + DeletableItem.prototype.decorate.call(this); + + var profileInfo = this.profileInfo_; + + var iconEl = this.ownerDocument.createElement('img'); + iconEl.className = 'profile-img'; + iconEl.src = profileInfo.iconURL; + this.contentElement.appendChild(iconEl); + + var nameEl = this.ownerDocument.createElement('div'); + if (profileInfo.isCurrentProfile) + nameEl.classList.add('profile-item-current'); + this.contentElement.appendChild(nameEl); + + var displayName = profileInfo.name; + if (profileInfo.isCurrentProfile) + displayName = localStrings.getStringF( + 'profilesListItemCurrent', + profileInfo.name) + nameEl.textContent = displayName; + }, + }; + + var ProfileList = cr.ui.define('list'); + + ProfileList.prototype = { + __proto__: DeletableItemList.prototype, + + /** @inheritDoc */ + decorate: function() { + DeletableItemList.prototype.decorate.call(this); + this.selectionModel = new ListSingleSelectionModel(); + }, + + /** @inheritDoc */ + createItem: function(pageInfo) { + var item = new ProfileListItem(pageInfo); + return item; + }, + + /** @inheritDoc */ + deleteItemAtIndex: function(index) { + ManageProfileOverlay.showDeleteDialog(this.dataModel.item(index)); + }, + + /** @inheritDoc */ + activateItemAtIndex: function(index) { + // Don't allow the user to edit a profile that is not current. + var profileInfo = this.dataModel.item(index); + if (profileInfo.isCurrentProfile) + ManageProfileOverlay.showManageDialog(profileInfo); + }, + }; + + return { + ProfileList: ProfileList + }; +}); + diff --git a/chrome/browser/resources/options2/pref_ui.js b/chrome/browser/resources/options2/pref_ui.js new file mode 100644 index 0000000..0468f04 --- /dev/null +++ b/chrome/browser/resources/options2/pref_ui.js @@ -0,0 +1,723 @@ +// Copyright (c) 2011 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 Preferences = options.Preferences; + + /** + * Allows an element to be disabled for several reasons. + * The element is disabled if at least one reason is true, and the reasons + * can be set separately. + * @private + * @param {!HTMLElement} el The element to update. + * @param {string} reason The reason for disabling the element. + * @param {boolean} disabled Whether the element should be disabled or enabled + * for the given |reason|. + */ + function updateDisabledState_(el, reason, disabled) { + if (!el.disabledReasons) + el.disabledReasons = {}; + if (el.disabled && (Object.keys(el.disabledReasons).length == 0)) { + // The element has been previously disabled without a reason, so we add + // one to keep it disabled. + el.disabledReasons['other'] = true; + } + if (!el.disabled) { + // If the element is not disabled, there should be no reason, except for + // 'other'. + delete el.disabledReasons['other']; + if (Object.keys(el.disabledReasons).length > 0) + console.error("Element is not disabled but should be"); + } + if (disabled) { + el.disabledReasons[reason] = true; + } else { + delete el.disabledReasons[reason]; + } + el.disabled = Object.keys(el.disabledReasons).length > 0; + } + + /** + * Helper function to update element's state from pref change event. + * @private + * @param {!HTMLElement} el The element to update. + * @param {!Event} event The pref change event. + */ + function updateElementState_(el, event) { + el.controlledBy = null; + + if (!event.value) + return; + + updateDisabledState_(el, 'notUserModifiable', event.value.disabled); + + el.controlledBy = event.value['controlledBy']; + + OptionsPage.updateManagedBannerVisibility(); + } + + ///////////////////////////////////////////////////////////////////////////// + // PrefCheckbox class: + // TODO(jhawkins): Refactor all this copy-pasted code! + + // Define a constructor that uses an input element as its underlying element. + var PrefCheckbox = cr.ui.define('input'); + + PrefCheckbox.prototype = { + // Set up the prototype chain + __proto__: HTMLInputElement.prototype, + + /** + * Initialization function for the cr.ui framework. + */ + decorate: function() { + this.type = 'checkbox'; + var self = this; + + self.initializeValueType(self.getAttribute('value-type')); + + // Listen to pref changes. + Preferences.getInstance().addEventListener( + this.pref, + function(event) { + var value = event.value && event.value['value'] != undefined ? + event.value['value'] : event.value; + + // Invert pref value if inverted_pref == true. + if (self.inverted_pref) + self.checked = !Boolean(value); + else + self.checked = Boolean(value); + + updateElementState_(self, event); + }); + + // Listen to user events. + this.addEventListener( + 'change', + function(e) { + if (self.customChangeHandler(e)) + return; + var value = self.inverted_pref ? !self.checked : self.checked; + switch(self.valueType) { + case 'number': + Preferences.setIntegerPref(self.pref, + Number(value), self.metric); + break; + case 'boolean': + Preferences.setBooleanPref(self.pref, + value, self.metric); + break; + } + }); + }, + + /** + * Sets up options in checkbox element. + * @param {String} valueType The preference type for this checkbox. + */ + initializeValueType: function(valueType) { + this.valueType = valueType || 'boolean'; + }, + + /** + * See |updateDisabledState_| above. + */ + setDisabled: function(reason, disabled) { + updateDisabledState_(this, reason, disabled); + }, + + /** + * This method is called first while processing an onchange event. If it + * returns false, regular onchange processing continues (setting the + * associated pref, etc). If it returns true, the rest of the onchange is + * not performed. I.e., this works like stopPropagation or cancelBubble. + * @param {Event} event Change event. + */ + customChangeHandler: function(event) { + return false; + }, + }; + + /** + * The preference name. + * @type {string} + */ + cr.defineProperty(PrefCheckbox, 'pref', cr.PropertyKind.ATTR); + + /** + * Whether the preference is controlled by something else than the user's + * settings (either 'policy' or 'extension'). + * @type {string} + */ + cr.defineProperty(PrefCheckbox, 'controlledBy', cr.PropertyKind.ATTR); + + /** + * The user metric string. + * @type {string} + */ + cr.defineProperty(PrefCheckbox, 'metric', cr.PropertyKind.ATTR); + + /** + * Whether to use inverted pref value. + * @type {boolean} + */ + cr.defineProperty(PrefCheckbox, 'inverted_pref', cr.PropertyKind.BOOL_ATTR); + + ///////////////////////////////////////////////////////////////////////////// + // PrefRadio class: + + //Define a constructor that uses an input element as its underlying element. + var PrefRadio = cr.ui.define('input'); + + PrefRadio.prototype = { + // Set up the prototype chain + __proto__: HTMLInputElement.prototype, + + /** + * Initialization function for the cr.ui framework. + */ + decorate: function() { + this.type = 'radio'; + var self = this; + + // Listen to pref changes. + Preferences.getInstance().addEventListener(this.pref, + function(event) { + var value = event.value && event.value['value'] != undefined ? + event.value['value'] : event.value; + self.checked = String(value) == self.value; + + updateElementState_(self, event); + }); + + // Listen to user events. + this.addEventListener('change', + function(e) { + if(self.value == 'true' || self.value == 'false') { + Preferences.setBooleanPref(self.pref, + self.value == 'true', self.metric); + } else { + Preferences.setIntegerPref(self.pref, + parseInt(self.value, 10), self.metric); + } + }); + }, + + /** + * See |updateDisabledState_| above. + */ + setDisabled: function(reason, disabled) { + updateDisabledState_(this, reason, disabled); + }, + }; + + /** + * The preference name. + * @type {string} + */ + cr.defineProperty(PrefRadio, 'pref', cr.PropertyKind.ATTR); + + /** + * Whether the preference is controlled by something else than the user's + * settings (either 'policy' or 'extension'). + * @type {string} + */ + cr.defineProperty(PrefRadio, 'controlledBy', cr.PropertyKind.ATTR); + + /** + * The user metric string. + * @type {string} + */ + cr.defineProperty(PrefRadio, 'metric', cr.PropertyKind.ATTR); + + ///////////////////////////////////////////////////////////////////////////// + // PrefNumeric class: + + // Define a constructor that uses an input element as its underlying element. + var PrefNumeric = function() {}; + PrefNumeric.prototype = { + // Set up the prototype chain + __proto__: HTMLInputElement.prototype, + + /** + * Initialization function for the cr.ui framework. + */ + decorate: function() { + var self = this; + + // Listen to pref changes. + Preferences.getInstance().addEventListener(this.pref, + function(event) { + self.value = event.value && event.value['value'] != undefined ? + event.value['value'] : event.value; + + updateElementState_(self, event); + }); + + // Listen to user events. + this.addEventListener('change', + function(e) { + if (this.validity.valid) { + Preferences.setIntegerPref(self.pref, self.value, self.metric); + } + }); + }, + + /** + * See |updateDisabledState_| above. + */ + setDisabled: function(reason, disabled) { + updateDisabledState_(this, reason, disabled); + }, + }; + + /** + * The preference name. + * @type {string} + */ + cr.defineProperty(PrefNumeric, 'pref', cr.PropertyKind.ATTR); + + /** + * Whether the preference is controlled by something else than the user's + * settings (either 'policy' or 'extension'). + * @type {string} + */ + cr.defineProperty(PrefNumeric, 'controlledBy', cr.PropertyKind.ATTR); + + /** + * The user metric string. + * @type {string} + */ + cr.defineProperty(PrefNumeric, 'metric', cr.PropertyKind.ATTR); + + ///////////////////////////////////////////////////////////////////////////// + // PrefNumber class: + + // Define a constructor that uses an input element as its underlying element. + var PrefNumber = cr.ui.define('input'); + + PrefNumber.prototype = { + // Set up the prototype chain + __proto__: PrefNumeric.prototype, + + /** + * Initialization function for the cr.ui framework. + */ + decorate: function() { + this.type = 'number'; + PrefNumeric.prototype.decorate.call(this); + + // Listen to user events. + this.addEventListener('input', + function(e) { + if (this.validity.valid) { + Preferences.setIntegerPref(self.pref, self.value, self.metric); + } + }); + }, + + /** + * See |updateDisabledState_| above. + */ + setDisabled: function(reason, disabled) { + updateDisabledState_(this, reason, disabled); + }, + }; + + ///////////////////////////////////////////////////////////////////////////// + // PrefRange class: + + // Define a constructor that uses an input element as its underlying element. + var PrefRange = cr.ui.define('input'); + + PrefRange.prototype = { + // Set up the prototype chain + __proto__: HTMLInputElement.prototype, + + /** + * The map from input range value to the corresponding preference value. + */ + valueMap: undefined, + + /** + * If true, the associated pref will be modified on each onchange event; + * otherwise, the pref will only be modified on the onmouseup event after + * the drag. + */ + continuous: true, + + /** + * Initialization function for the cr.ui framework. + */ + decorate: function() { + this.type = 'range'; + + // Update the UI when the pref changes. + Preferences.getInstance().addEventListener( + this.pref, this.onPrefChange_.bind(this)); + + // Listen to user events. + // TODO(jhawkins): Add onmousewheel handling once the associated WK bug is + // fixed. + // https://bugs.webkit.org/show_bug.cgi?id=52256 + this.onchange = this.onChange_.bind(this); + this.onkeyup = this.onmouseup = this.onInputUp_.bind(this); + }, + + /** + * Event listener that updates the UI when the underlying pref changes. + * @param {Event} event The event that details the pref change. + * @private + */ + onPrefChange_: function(event) { + var value = event.value && event.value['value'] != undefined ? + event.value['value'] : event.value; + if (value != undefined) + this.value = this.valueMap ? this.valueMap.indexOf(value) : value; + }, + + /** + * onchange handler that sets the pref when the user changes the value of + * the input element. + * @private + */ + onChange_: function(event) { + if (this.continuous) + this.setRangePref_(); + + if (this.notifyChange) + this.notifyChange(this, this.mapValueToRange_(this.value)); + }, + + /** + * Sets the integer value of |pref| to the value of this element. + * @private + */ + setRangePref_: function() { + Preferences.setIntegerPref( + this.pref, this.mapValueToRange_(this.value), this.metric); + + if (this.notifyPrefChange) + this.notifyPrefChange(this, this.mapValueToRange_(this.value)); + }, + + /** + * onkeyup/onmouseup handler that modifies the pref if |continuous| is + * false. + * @private + */ + onInputUp_: function(event) { + if (!this.continuous) + this.setRangePref_(); + }, + + /** + * Maps the value of this element into the range provided by the client, + * represented by |valueMap|. + * @param {number} value The value to map. + * @private + */ + mapValueToRange_: function(value) { + return this.valueMap ? this.valueMap[value] : value; + }, + + /** + * Called when the client has specified non-continuous mode and the value of + * the range control changes. + * @param {Element} el This element. + * @param {number} value The value of this element. + */ + notifyChange: function(el, value) { + }, + + /** + * See |updateDisabledState_| above. + */ + setDisabled: function(reason, disabled) { + updateDisabledState_(this, reason, disabled); + }, + }; + + /** + * The preference name. + * @type {string} + */ + cr.defineProperty(PrefRange, 'pref', cr.PropertyKind.ATTR); + + /** + * Whether the preference is controlled by something else than the user's + * settings (either 'policy' or 'extension'). + * @type {string} + */ + cr.defineProperty(PrefRange, 'controlledBy', cr.PropertyKind.ATTR); + + /** + * The user metric string. + * @type {string} + */ + cr.defineProperty(PrefRange, 'metric', cr.PropertyKind.ATTR); + + ///////////////////////////////////////////////////////////////////////////// + // PrefSelect class: + + // Define a constructor that uses a select element as its underlying element. + var PrefSelect = cr.ui.define('select'); + + PrefSelect.prototype = { + // Set up the prototype chain + __proto__: HTMLSelectElement.prototype, + + /** + * Initialization function for the cr.ui framework. + */ + decorate: function() { + var self = this; + + // Listen to pref changes. + Preferences.getInstance().addEventListener(this.pref, + function(event) { + var value = event.value && event.value['value'] != undefined ? + event.value['value'] : event.value; + + // Make sure |value| is a string, because the value is stored as a + // string in the HTMLOptionElement. + value = value.toString(); + + updateElementState_(self, event); + + var found = false; + for (var i = 0; i < self.options.length; i++) { + if (self.options[i].value == value) { + self.selectedIndex = i; + found = true; + } + } + + // Item not found, select first item. + if (!found) + self.selectedIndex = 0; + + if (self.onchange != undefined) + self.onchange(event); + }); + + // Listen to user events. + this.addEventListener('change', + function(e) { + if (!self.dataType) { + console.error('undefined data type for <select> pref'); + return; + } + + switch(self.dataType) { + case 'number': + Preferences.setIntegerPref(self.pref, + self.options[self.selectedIndex].value, self.metric); + break; + case 'double': + Preferences.setDoublePref(self.pref, + self.options[self.selectedIndex].value, self.metric); + break; + case 'boolean': + var option = self.options[self.selectedIndex]; + var value = (option.value == 'true') ? true : false; + Preferences.setBooleanPref(self.pref, value, self.metric); + break; + case 'string': + Preferences.setStringPref(self.pref, + self.options[self.selectedIndex].value, self.metric); + break; + default: + console.error('unknown data type for <select> pref: ' + + self.dataType); + } + }); + }, + + /** + * See |updateDisabledState_| above. + */ + setDisabled: function(reason, disabled) { + updateDisabledState_(this, reason, disabled); + }, + }; + + /** + * The preference name. + * @type {string} + */ + cr.defineProperty(PrefSelect, 'pref', cr.PropertyKind.ATTR); + + /** + * Whether the preference is controlled by something else than the user's + * settings (either 'policy' or 'extension'). + * @type {string} + */ + cr.defineProperty(PrefSelect, 'controlledBy', cr.PropertyKind.ATTR); + + /** + * The user metric string. + * @type {string} + */ + cr.defineProperty(PrefSelect, 'metric', cr.PropertyKind.ATTR); + + /** + * The data type for the preference options. + * @type {string} + */ + cr.defineProperty(PrefSelect, 'dataType', cr.PropertyKind.ATTR); + + ///////////////////////////////////////////////////////////////////////////// + // PrefTextField class: + + // Define a constructor that uses an input element as its underlying element. + var PrefTextField = cr.ui.define('input'); + + PrefTextField.prototype = { + // Set up the prototype chain + __proto__: HTMLInputElement.prototype, + + /** + * Initialization function for the cr.ui framework. + */ + decorate: function() { + var self = this; + + // Listen to pref changes. + Preferences.getInstance().addEventListener(this.pref, + function(event) { + self.value = event.value && event.value['value'] != undefined ? + event.value['value'] : event.value; + + updateElementState_(self, event); + }); + + // Listen to user events. + this.addEventListener('change', + function(e) { + switch(self.dataType) { + case 'number': + Preferences.setIntegerPref(self.pref, self.value, self.metric); + break; + case 'double': + Preferences.setDoublePref(self.pref, self.value, self.metric); + break; + case 'url': + Preferences.setURLPref(self.pref, self.value, self.metric); + break; + default: + Preferences.setStringPref(self.pref, self.value, self.metric); + break; + } + }); + + window.addEventListener('unload', + function() { + if (document.activeElement == self) + self.blur(); + }); + }, + + /** + * See |updateDisabledState_| above. + */ + setDisabled: function(reason, disabled) { + updateDisabledState_(this, reason, disabled); + }, + }; + + /** + * The preference name. + * @type {string} + */ + cr.defineProperty(PrefTextField, 'pref', cr.PropertyKind.ATTR); + + /** + * Whether the preference is controlled by something else than the user's + * settings (either 'policy' or 'extension'). + * @type {string} + */ + cr.defineProperty(PrefTextField, 'controlledBy', cr.PropertyKind.ATTR); + + /** + * The user metric string. + * @type {string} + */ + cr.defineProperty(PrefTextField, 'metric', cr.PropertyKind.ATTR); + + /** + * The data type for the preference options. + * @type {string} + */ + cr.defineProperty(PrefTextField, 'dataType', cr.PropertyKind.ATTR); + + ///////////////////////////////////////////////////////////////////////////// + // PrefButton class: + + // Define a constructor that uses a button element as its underlying element. + var PrefButton = cr.ui.define('button'); + + PrefButton.prototype = { + // Set up the prototype chain + __proto__: HTMLButtonElement.prototype, + + /** + * Initialization function for the cr.ui framework. + */ + decorate: function() { + var self = this; + + // Listen to pref changes. This element behaves like a normal button and + // doesn't affect the underlying preference; it just becomes disabled + // when the preference is managed, and its value is false. + // This is useful for buttons that should be disabled when the underlying + // boolean preference is set to false by a policy or extension. + Preferences.getInstance().addEventListener(this.pref, + function(event) { + var e = { + value: { + 'disabled': event.value['disabled'] && !event.value['value'], + 'controlledBy': event.value['controlledBy'] + } + }; + updateElementState_(self, e); + }); + }, + + /** + * See |updateDisabledState_| above. + */ + setDisabled: function(reason, disabled) { + updateDisabledState_(this, reason, disabled); + }, + }; + + /** + * The preference name. + * @type {string} + */ + cr.defineProperty(PrefButton, 'pref', cr.PropertyKind.ATTR); + + /** + * Whether the preference is controlled by something else than the user's + * settings (either 'policy' or 'extension'). + * @type {string} + */ + cr.defineProperty(PrefButton, 'controlledBy', cr.PropertyKind.ATTR); + + // Export + return { + PrefCheckbox: PrefCheckbox, + PrefNumber: PrefNumber, + PrefNumeric: PrefNumeric, + PrefRadio: PrefRadio, + PrefRange: PrefRange, + PrefSelect: PrefSelect, + PrefTextField: PrefTextField, + PrefButton: PrefButton + }; + +}); diff --git a/chrome/browser/resources/options2/preferences.js b/chrome/browser/resources/options2/preferences.js new file mode 100644 index 0000000..807e45e --- /dev/null +++ b/chrome/browser/resources/options2/preferences.js @@ -0,0 +1,185 @@ +// Copyright (c) 2011 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() { + + ///////////////////////////////////////////////////////////////////////////// + // Preferences class: + + /** + * Preferences class manages access to Chrome profile preferences. + * @constructor + */ + function Preferences() { + } + + cr.addSingletonGetter(Preferences); + + /** + * Sets value of a boolean preference. + * and signals its changed value. + * @param {string} name Preference name. + * @param {boolean} value New preference value. + * @param {string} metric User metrics identifier. + */ + Preferences.setBooleanPref = function(name, value, metric) { + var argumentList = [name, Boolean(value)]; + if (metric != undefined) argumentList.push(metric); + chrome.send('setBooleanPref', argumentList); + }; + + /** + * Sets value of an integer preference. + * and signals its changed value. + * @param {string} name Preference name. + * @param {number} value New preference value. + * @param {string} metric User metrics identifier. + */ + Preferences.setIntegerPref = function(name, value, metric) { + var argumentList = [name, Number(value)]; + if (metric != undefined) argumentList.push(metric); + chrome.send('setIntegerPref', argumentList); + }; + + /** + * Sets value of a double-valued preference. + * and signals its changed value. + * @param {string} name Preference name. + * @param {number} value New preference value. + * @param {string} metric User metrics identifier. + */ + Preferences.setDoublePref = function(name, value, metric) { + var argumentList = [name, Number(value)]; + if (metric != undefined) argumentList.push(metric); + chrome.send('setDoublePref', argumentList); + }; + + /** + * Sets value of a string preference. + * and signals its changed value. + * @param {string} name Preference name. + * @param {string} value New preference value. + * @param {string} metric User metrics identifier. + */ + Preferences.setStringPref = function(name, value, metric) { + var argumentList = [name, String(value)]; + if (metric != undefined) argumentList.push(metric); + chrome.send('setStringPref', argumentList); + }; + + /** + * Sets value of a string preference that represents a URL + * and signals its changed value. The value will be fixed to be a valid URL. + * @param {string} name Preference name. + * @param {string} value New preference value. + * @param {string} metric User metrics identifier. + */ + Preferences.setURLPref = function(name, value, metric) { + var argumentList = [name, String(value)]; + if (metric != undefined) argumentList.push(metric); + chrome.send('setURLPref', argumentList); + }; + + /** + * Sets value of a JSON list preference. + * and signals its changed value. + * @param {string} name Preference name. + * @param {Array} value New preference value. + * @param {string} metric User metrics identifier. + */ + Preferences.setListPref = function(name, value, metric) { + var argumentList = [name, JSON.stringify(value)]; + if (metric != undefined) argumentList.push(metric); + chrome.send('setListPref', argumentList); + }; + + /** + * Clears value of a JSON preference. + * @param {string} name Preference name. + * @param {string} metric User metrics identifier. + */ + Preferences.clearPref = function(name, metric) { + var argumentList = [name]; + if (metric != undefined) argumentList.push(metric); + chrome.send('clearPref', argumentList); + }; + + Preferences.prototype = { + __proto__: cr.EventTarget.prototype, + + // Map of registered preferences. + registeredPreferences_: {}, + + /** + * Adds an event listener to the target. + * @param {string} type The name of the event. + * @param {!Function|{handleEvent:Function}} handler The handler for the + * event. This is called when the event is dispatched. + */ + addEventListener: function(type, handler) { + cr.EventTarget.prototype.addEventListener.call(this, type, handler); + this.registeredPreferences_[type] = true; + }, + + /** + * Initializes preference reading and change notifications. + */ + initialize: function() { + var params1 = ['Preferences.prefsFetchedCallback']; + var params2 = ['Preferences.prefsChangedCallback']; + for (var prefName in this.registeredPreferences_) { + params1.push(prefName); + params2.push(prefName); + } + chrome.send('fetchPrefs', params1); + chrome.send('observePrefs', params2); + }, + + /** + * Helper function for flattening of dictionary passed via fetchPrefs + * callback. + * @param {string} prefix Preference name prefix. + * @param {object} dict Map with preference values. + */ + flattenMapAndDispatchEvent_: function(prefix, dict) { + for (var prefName in dict) { + if (typeof dict[prefName] == 'object' && + !this.registeredPreferences_[prefix + prefName]) { + this.flattenMapAndDispatchEvent_(prefix + prefName + '.', + dict[prefName]); + } else { + var event = new cr.Event(prefix + prefName); + event.value = dict[prefName]; + this.dispatchEvent(event); + } + } + } + }; + + /** + * Callback for fetchPrefs method. + * @param {object} dict Map of fetched property values. + */ + Preferences.prefsFetchedCallback = function(dict) { + Preferences.getInstance().flattenMapAndDispatchEvent_('', dict); + }; + + /** + * Callback for observePrefs method. + * @param {array} notification An array defining changed preference values. + * notification[0] contains name of the change preference while its new value + * is stored in notification[1]. + */ + Preferences.prefsChangedCallback = function(notification) { + var event = new cr.Event(notification[0]); + event.value = notification[1]; + Preferences.getInstance().dispatchEvent(event); + }; + + // Export + return { + Preferences: Preferences + }; + +}); diff --git a/chrome/browser/resources/options2/profiles_icon_grid.js b/chrome/browser/resources/options2/profiles_icon_grid.js new file mode 100644 index 0000000..cdd9529 --- /dev/null +++ b/chrome/browser/resources/options2/profiles_icon_grid.js @@ -0,0 +1,68 @@ +// Copyright (c) 2011 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() { + const ListItem = cr.ui.ListItem; + const Grid = cr.ui.Grid; + const ListSingleSelectionModel = cr.ui.ListSingleSelectionModel; + + /** + * Creates a new profile icon grid item. + * @param {Object} iconURL The profile icon URL. + * @constructor + * @extends {cr.ui.GridItem} + */ + function ProfilesIconGridItem(iconURL) { + var el = cr.doc.createElement('span'); + el.iconURL_ = iconURL; + ProfilesIconGridItem.decorate(el); + return el; + } + + /** + * Decorates an element as a profile grid item. + * @param {!HTMLElement} el The element to decorate. + */ + ProfilesIconGridItem.decorate = function(el) { + el.__proto__ = ProfilesIconGridItem.prototype; + el.decorate(); + }; + + ProfilesIconGridItem.prototype = { + __proto__: ListItem.prototype, + + /** @inheritDoc */ + decorate: function() { + ListItem.prototype.decorate.call(this); + var imageEl = cr.doc.createElement('img'); + imageEl.className = 'profile-icon'; + imageEl.src = this.iconURL_; + this.appendChild(imageEl); + + this.className = 'profile-icon-grid-item'; + }, + }; + + var ProfilesIconGrid = cr.ui.define('grid'); + + ProfilesIconGrid.prototype = { + __proto__: Grid.prototype, + + /** @inheritDoc */ + decorate: function() { + Grid.prototype.decorate.call(this); + this.selectionModel = new ListSingleSelectionModel(); + }, + + /** @inheritDoc */ + createItem: function(iconURL) { + return new ProfilesIconGridItem(iconURL); + }, + }; + + return { + ProfilesIconGrid: ProfilesIconGrid + }; +}); + diff --git a/chrome/browser/resources/options2/search_engine_manager.css b/chrome/browser/resources/options2/search_engine_manager.css new file mode 100644 index 0000000..50c2db2 --- /dev/null +++ b/chrome/browser/resources/options2/search_engine_manager.css @@ -0,0 +1,64 @@ +.search-engine-list > div { + display: -webkit-box; +} + +.search-engine-list .favicon { + padding: 1px 7px 0px 7px; + height: 16px; +} + +.search-engine-list .name-column { + -webkit-box-align: center; + -webkit-padding-end: 1ex; + box-sizing: border-box; + display: -webkit-box; + width: 37%; +} + +.search-engine-list .name-column :last-child { + -webkit-box-flex: 1; +} + +.search-engine-list .keyword-column { + -webkit-padding-end: 1ex; + box-sizing: border-box; + width: 26%; +} + +.search-engine-list .url-column { + box-sizing: border-box; + width: 37%; +} + +.search-engine-list .keyword-column, +.search-engine-list .url-column { + color: #666666; +} + +.search-engine-list .default .name-column, +.search-engine-list .default .keyword-column { + font-weight: bold; +} + +/* For temporary Make Default button */ +.search-engine-list .url-column { + display: -webkit-box; + -webkit-box-align: center; +} + +.search-engine-list .url-column :first-child { + -webkit-box-flex: 1; +} + +.search-engine-list .url-column button { + -webkit-margin-start: 3px; + background: #8aaaed; + color: #fff; + margin-top: 0; +} + +.search-engine-list > :not(:hover):not([editing]) .url-column button { + display: none; +} + +/* End temporary Make Default button styling */ diff --git a/chrome/browser/resources/options2/search_engine_manager.html b/chrome/browser/resources/options2/search_engine_manager.html new file mode 100644 index 0000000..cc00a73 --- /dev/null +++ b/chrome/browser/resources/options2/search_engine_manager.html @@ -0,0 +1,19 @@ +<div id="search-engine-manager-page" class="page" hidden> + <h1 i18n-content="searchEngineManagerPage"></h1> + <h3 i18n-content="defaultSearchEngineListTitle"></h3> + <list id="default-search-engine-list" + class="search-engine-list settings-list"></list> + <h3 i18n-content="otherSearchEngineListTitle"></h3> + <list id="other-search-engine-list" + class="search-engine-list settings-list"></list> + <div id="extension-keyword-div" hidden> + <h3 id="extension-keyword-list-title" + i18n-content="extensionKeywordsListTitle"></h3> + <list id="extension-keyword-list" + class="search-engine-list settings-list"></list> + <div id="manage-extension-link"> + <a href="chrome://settings/extensions/" + i18n-content="manageExtensionsLinkText"></a> + </div> + </div> +</div> diff --git a/chrome/browser/resources/options2/search_engine_manager.js b/chrome/browser/resources/options2/search_engine_manager.js new file mode 100644 index 0000000..57826d8 --- /dev/null +++ b/chrome/browser/resources/options2/search_engine_manager.js @@ -0,0 +1,125 @@ +// Copyright (c) 2011 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() { + const OptionsPage = options.OptionsPage; + const ArrayDataModel = cr.ui.ArrayDataModel; + + /** + * Encapsulated handling of search engine management page. + * @constructor + */ + function SearchEngineManager() { + this.activeNavTab = null; + OptionsPage.call(this, 'searchEngines', + templateData.searchEngineManagerPageTabTitle, + 'search-engine-manager-page'); + } + + cr.addSingletonGetter(SearchEngineManager); + + SearchEngineManager.prototype = { + __proto__: OptionsPage.prototype, + + /** + * List for default search engine options. + * @private + */ + defaultsList_: null, + + /** + * List for other search engine options. + * @private + */ + othersList_: null, + + /** + * List for extension keywords. + * @private + extensionList_ : null, + + /** inheritDoc */ + initializePage: function() { + OptionsPage.prototype.initializePage.call(this); + + this.defaultsList_ = $('default-search-engine-list'); + this.setUpList_(this.defaultsList_); + + this.othersList_ = $('other-search-engine-list'); + this.setUpList_(this.othersList_); + + this.extensionList_ = $('extension-keyword-list'); + this.setUpList_(this.extensionList_); + }, + + /** + * Sets up the given list as a search engine list + * @param {List} list The list to set up. + * @private + */ + setUpList_: function(list) { + options.search_engines.SearchEngineList.decorate(list); + list.autoExpands = true; + }, + + /** + * Updates the search engine list with the given entries. + * @private + * @param {Array} defaultEngines List of possible default search engines. + * @param {Array} otherEngines List of other search engines. + * @param {Array} keywords List of keywords from extensions. + */ + updateSearchEngineList_: function(defaultEngines, otherEngines, keywords) { + this.defaultsList_.dataModel = new ArrayDataModel(defaultEngines); + + otherEngines = otherEngines.map(function(x) { + return [x, x['name'].toLocaleLowerCase()]; + }).sort(function(a,b){ + return a[1].localeCompare(b[1]); + }).map(function(x){ + return x[0]; + }); + + var othersModel = new ArrayDataModel(otherEngines); + // Add a "new engine" row. + othersModel.push({ + 'modelIndex': '-1', + 'canBeEdited': true + }); + this.othersList_.dataModel = othersModel; + + if (keywords.length > 0) { + $('extension-keyword-div').hidden = false; + var extensionsModel = new ArrayDataModel(keywords); + this.extensionList_.dataModel = extensionsModel; + } else { + $('extension-keyword-div').hidden = true; + } + }, + }; + + SearchEngineManager.updateSearchEngineList = function(defaultEngines, + otherEngines, + keywords) { + SearchEngineManager.getInstance().updateSearchEngineList_(defaultEngines, + otherEngines, + keywords); + }; + + SearchEngineManager.validityCheckCallback = function(validity, modelIndex) { + // Forward to both lists; the one without a matching modelIndex will ignore + // it. + SearchEngineManager.getInstance().defaultsList_.validationComplete( + validity, modelIndex); + SearchEngineManager.getInstance().othersList_.validationComplete( + validity, modelIndex); + }; + + // Export + return { + SearchEngineManager: SearchEngineManager + }; + +}); + diff --git a/chrome/browser/resources/options2/search_engine_manager_engine_list.js b/chrome/browser/resources/options2/search_engine_manager_engine_list.js new file mode 100644 index 0000000..87ee2f5 --- /dev/null +++ b/chrome/browser/resources/options2/search_engine_manager_engine_list.js @@ -0,0 +1,316 @@ +// Copyright (c) 2011 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.search_engines', function() { + const InlineEditableItemList = options.InlineEditableItemList; + const InlineEditableItem = options.InlineEditableItem; + const ListSelectionController = cr.ui.ListSelectionController; + + /** + * Creates a new search engine list item. + * @param {Object} searchEnigne The search engine this represents. + * @constructor + * @extends {cr.ui.ListItem} + */ + function SearchEngineListItem(searchEngine) { + var el = cr.doc.createElement('div'); + el.searchEngine_ = searchEngine; + SearchEngineListItem.decorate(el); + return el; + } + + /** + * Decorates an element as a search engine list item. + * @param {!HTMLElement} el The element to decorate. + */ + SearchEngineListItem.decorate = function(el) { + el.__proto__ = SearchEngineListItem.prototype; + el.decorate(); + }; + + SearchEngineListItem.prototype = { + __proto__: InlineEditableItem.prototype, + + /** + * Input field for editing the engine name. + * @type {HTMLElement} + * @private + */ + nameField_: null, + + /** + * Input field for editing the engine keyword. + * @type {HTMLElement} + * @private + */ + keywordField_: null, + + /** + * Input field for editing the engine url. + * @type {HTMLElement} + * @private + */ + urlField_: null, + + /** + * Whether or not an input validation request is currently outstanding. + * @type {boolean} + * @private + */ + waitingForValidation_: false, + + /** + * Whether or not the current set of input is known to be valid. + * @type {boolean} + * @private + */ + currentlyValid_: false, + + /** @inheritDoc */ + decorate: function() { + InlineEditableItem.prototype.decorate.call(this); + + var engine = this.searchEngine_; + + if (engine['modelIndex'] == '-1') { + this.isPlaceholder = true; + engine['name'] = ''; + engine['keyword'] = ''; + engine['url'] = ''; + } + + this.currentlyValid_ = !this.isPlaceholder; + + if (engine['default']) + this.classList.add('default'); + + this.deletable = engine['canBeRemoved']; + + // Construct the name column. + var nameColEl = this.ownerDocument.createElement('div'); + nameColEl.className = 'name-column'; + nameColEl.classList.add('weakrtl'); + this.contentElement.appendChild(nameColEl); + + // Add the favicon. + var faviconDivEl = this.ownerDocument.createElement('div'); + faviconDivEl.className = 'favicon'; + var imgEl = this.ownerDocument.createElement('img'); + imgEl.src = 'chrome://favicon/iconurl/' + engine['iconURL']; + faviconDivEl.appendChild(imgEl); + nameColEl.appendChild(faviconDivEl); + + var nameEl = this.createEditableTextCell(engine['displayName']); + nameEl.classList.add('weakrtl'); + nameColEl.appendChild(nameEl); + + // Then the keyword column. + var keywordEl = this.createEditableTextCell(engine['keyword']); + keywordEl.className = 'keyword-column'; + keywordEl.classList.add('weakrtl'); + this.contentElement.appendChild(keywordEl); + + // And the URL column. + var urlEl = this.createEditableTextCell(engine['url']); + var urlWithButtonEl = this.ownerDocument.createElement('div'); + urlWithButtonEl.appendChild(urlEl); + urlWithButtonEl.className = 'url-column'; + urlWithButtonEl.classList.add('weakrtl'); + this.contentElement.appendChild(urlWithButtonEl); + // Add the Make Default button. Temporary until drag-and-drop re-ordering + // is implemented. When this is removed, remove the extra div above. + if (engine['canBeDefault']) { + var makeDefaultButtonEl = this.ownerDocument.createElement('button'); + makeDefaultButtonEl.className = 'raw-button custom-appearance'; + makeDefaultButtonEl.textContent = + templateData.makeDefaultSearchEngineButton; + makeDefaultButtonEl.onclick = function(e) { + chrome.send('managerSetDefaultSearchEngine', [engine['modelIndex']]); + }; + // Don't select the row when clicking the button. + makeDefaultButtonEl.onmousedown = function(e) { + e.stopPropagation(); + }; + urlWithButtonEl.appendChild(makeDefaultButtonEl); + } + + // Do final adjustment to the input fields. + this.nameField_ = nameEl.querySelector('input'); + // The editable field uses the raw name, not the display name. + this.nameField_.value = engine['name']; + this.keywordField_ = keywordEl.querySelector('input'); + this.urlField_ = urlEl.querySelector('input'); + + if (engine['urlLocked']) + this.urlField_.disabled = true; + + if (this.isPlaceholder) { + this.nameField_.placeholder = + localStrings.getString('searchEngineTableNamePlaceholder'); + this.keywordField_.placeholder = + localStrings.getString('searchEngineTableKeywordPlaceholder'); + this.urlField_.placeholder = + localStrings.getString('searchEngineTableURLPlaceholder'); + } + + var fields = [ this.nameField_, this.keywordField_, this.urlField_ ]; + for (var i = 0; i < fields.length; i++) { + fields[i].oninput = this.startFieldValidation_.bind(this); + } + + // Listen for edit events. + if (engine['canBeEdited']) { + this.addEventListener('edit', this.onEditStarted_.bind(this)); + this.addEventListener('canceledit', this.onEditCancelled_.bind(this)); + this.addEventListener('commitedit', this.onEditCommitted_.bind(this)); + } else { + this.editable = false; + } + }, + + /** @inheritDoc */ + get currentInputIsValid() { + return !this.waitingForValidation_ && this.currentlyValid_; + }, + + /** @inheritDoc */ + get hasBeenEdited() { + var engine = this.searchEngine_; + return this.nameField_.value != engine['name'] || + this.keywordField_.value != engine['keyword'] || + this.urlField_.value != engine['url']; + }, + + /** + * Called when entering edit mode; starts an edit session in the model. + * @param {Event} e The edit event. + * @private + */ + onEditStarted_: function(e) { + var editIndex = this.searchEngine_['modelIndex']; + chrome.send('editSearchEngine', [String(editIndex)]); + this.startFieldValidation_(); + }, + + /** + * Called when committing an edit; updates the model. + * @param {Event} e The end event. + * @private + */ + onEditCommitted_: function(e) { + chrome.send('searchEngineEditCompleted', this.getInputFieldValues_()); + }, + + /** + * Called when cancelling an edit; informs the model and resets the control + * states. + * @param {Event} e The cancel event. + * @private + */ + onEditCancelled_: function() { + chrome.send('searchEngineEditCancelled'); + + // The name field has been automatically set to match the display name, + // but it should use the raw name instead. + this.nameField_.value = this.searchEngine_['name']; + this.currentlyValid_ = !this.isPlaceholder; + }, + + /** + * Returns the input field values as an array suitable for passing to + * chrome.send. The order of the array is important. + * @private + * @return {array} The current input field values. + */ + getInputFieldValues_: function() { + return [ this.nameField_.value, + this.keywordField_.value, + this.urlField_.value ]; + }, + + /** + * Begins the process of asynchronously validing the input fields. + * @private + */ + startFieldValidation_: function() { + this.waitingForValidation_ = true; + var args = this.getInputFieldValues_(); + args.push(this.searchEngine_['modelIndex']); + chrome.send('checkSearchEngineInfoValidity', args); + }, + + /** + * Callback for the completion of an input validition check. + * @param {Object} validity A dictionary of validitation results. + */ + validationComplete: function(validity) { + this.waitingForValidation_ = false; + // TODO(stuartmorgan): Implement the full validation UI with + // checkmark/exclamation mark icons and tooltips showing the errors. + if (validity['name']) { + this.nameField_.setCustomValidity(''); + } else { + this.nameField_.setCustomValidity( + templateData.editSearchEngineInvalidTitleToolTip); + } + + if (validity['keyword']) { + this.keywordField_.setCustomValidity(''); + } else { + this.keywordField_.setCustomValidity( + templateData.editSearchEngineInvalidKeywordToolTip); + } + + if (validity['url']) { + this.urlField_.setCustomValidity(''); + } else { + this.urlField_.setCustomValidity( + templateData.editSearchEngineInvalidURLToolTip); + } + + this.currentlyValid_ = validity['name'] && validity['keyword'] && + validity['url']; + }, + }; + + var SearchEngineList = cr.ui.define('list'); + + SearchEngineList.prototype = { + __proto__: InlineEditableItemList.prototype, + + /** @inheritDoc */ + createItem: function(searchEngine) { + return new SearchEngineListItem(searchEngine); + }, + + /** @inheritDoc */ + deleteItemAtIndex: function(index) { + var modelIndex = this.dataModel.item(index)['modelIndex'] + chrome.send('removeSearchEngine', [String(modelIndex)]); + }, + + /** + * Passes the results of an input validation check to the requesting row + * if it's still being edited. + * @param {number} modelIndex The model index of the item that was checked. + * @param {Object} validity A dictionary of validitation results. + */ + validationComplete: function(validity, modelIndex) { + // If it's not still being edited, it no longer matters. + var currentSelection = this.selectedItem; + if (!currentSelection) + return; + var listItem = this.getListItem(currentSelection); + if (listItem.editing && currentSelection['modelIndex'] == modelIndex) + listItem.validationComplete(validity); + }, + }; + + // Export + return { + SearchEngineList: SearchEngineList + }; + +}); + diff --git a/chrome/browser/resources/options2/search_page.css b/chrome/browser/resources/options2/search_page.css new file mode 100644 index 0000000..942dd98 --- /dev/null +++ b/chrome/browser/resources/options2/search_page.css @@ -0,0 +1,36 @@ +.search-hidden { + display: none !important; +} + +.search-highlighted { + background-color: rgba(255, 240, 120, 0.9); +} + +.search-bubble { + -webkit-box-shadow: 0 2px 2px #888; + background-color: rgba(255, 240, 120, 0.8); + border-radius: 6px; + box-shadow: 0 2px 2px #888; + left: 0; + margin: 12px 0 0; + padding: 4px 10px; + pointer-events: none; + position: absolute; + text-align: center; + top: -1000px; /* minor hack: position off-screen by default */ + width: 100px; +} + +.search-bubble:after { + border-color: rgba(255, 240, 120, 0.9) transparent; + border-style: solid; + border-width: 0 10px 10px; + content: ""; + left: 50px; + position: absolute; + top: -10px; +} + +.search-bubble-wrapper { + position: relative; +} diff --git a/chrome/browser/resources/options2/search_page.html b/chrome/browser/resources/options2/search_page.html new file mode 100644 index 0000000..5379ca5f --- /dev/null +++ b/chrome/browser/resources/options2/search_page.html @@ -0,0 +1,10 @@ +<div id="searchPage" class="page" hidden> + <h1 i18n-content="searchPage"></h1> + <div id="searchPageNoMatches"> + <p i18n-content="searchPageNoMatches"></p> + <p><span i18n-content="searchPageHelpLabel"></span> + <a target="_blank" i18n-content="searchPageHelpTitle" + i18n-values="href:searchPageHelpURL"></a> + </p> + </div> +</div> diff --git a/chrome/browser/resources/options2/search_page.js b/chrome/browser/resources/options2/search_page.js new file mode 100644 index 0000000..8f0ca9f --- /dev/null +++ b/chrome/browser/resources/options2/search_page.js @@ -0,0 +1,598 @@ +// Copyright (c) 2011 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() { + const OptionsPage = options.OptionsPage; + + /** + * Encapsulated handling of a search bubble. + * @constructor + */ + function SearchBubble(text) { + var el = cr.doc.createElement('div'); + SearchBubble.decorate(el); + el.textContent = text; + return el; + } + + SearchBubble.decorate = function(el) { + el.__proto__ = SearchBubble.prototype; + el.decorate(); + }; + + SearchBubble.prototype = { + __proto__: HTMLDivElement.prototype, + + decorate: function() { + this.className = 'search-bubble'; + + // We create a timer to periodically update the position of the bubbles. + // While this isn't all that desirable, it's the only sure-fire way of + // making sure the bubbles stay in the correct location as sections + // may dynamically change size at any time. + var self = this; + this.intervalId = setInterval(this.updatePosition.bind(this), 250); + }, + + /** + * Attach the bubble to the element. + */ + attachTo: function(element) { + var parent = element.parentElement; + if (!parent) + return; + if (parent.tagName == 'TD') { + // To make absolute positioning work inside a table cell we need + // to wrap the bubble div into another div with position:relative. + // This only works properly if the element is the first child of the + // table cell which is true for all options pages. + this.wrapper = cr.doc.createElement('div'); + this.wrapper.className = 'search-bubble-wrapper'; + this.wrapper.appendChild(this); + parent.insertBefore(this.wrapper, element); + } else { + parent.insertBefore(this, element); + } + }, + + /** + * Clear the interval timer and remove the element from the page. + */ + dispose: function() { + clearInterval(this.intervalId); + + var child = this.wrapper || this; + var parent = child.parentNode; + if (parent) + parent.removeChild(child); + }, + + /** + * Update the position of the bubble. Called at creation time and then + * periodically while the bubble remains visible. + */ + updatePosition: function() { + // This bubble is 'owned' by the next sibling. + var owner = (this.wrapper || this).nextSibling; + + // If there isn't an offset parent, we have nothing to do. + if (!owner.offsetParent) + return; + + // Position the bubble below the location of the owner. + var left = owner.offsetLeft + owner.offsetWidth / 2 - + this.offsetWidth / 2; + var top = owner.offsetTop + owner.offsetHeight; + + // Update the position in the CSS. Cache the last values for + // best performance. + if (left != this.lastLeft) { + this.style.left = left + 'px'; + this.lastLeft = left; + } + if (top != this.lastTop) { + this.style.top = top + 'px'; + this.lastTop = top; + } + } + } + + /** + * Encapsulated handling of the search page. + * @constructor + */ + function SearchPage() { + OptionsPage.call(this, 'search', templateData.searchPageTabTitle, + 'searchPage'); + } + + cr.addSingletonGetter(SearchPage); + + SearchPage.prototype = { + // Inherit SearchPage from OptionsPage. + __proto__: OptionsPage.prototype, + + /** + * A boolean to prevent recursion. Used by setSearchText_(). + * @type {Boolean} + * @private + */ + insideSetSearchText_: false, + + /** + * Initialize the page. + */ + initializePage: function() { + // Call base class implementation to start preference initialization. + OptionsPage.prototype.initializePage.call(this); + + var self = this; + + // Create a search field element. + var searchField = document.createElement('input'); + searchField.id = 'search-field'; + searchField.type = 'search'; + searchField.incremental = true; + searchField.placeholder = localStrings.getString('searchPlaceholder'); + searchField.setAttribute('aria-label', searchField.placeholder); + this.searchField = searchField; + + // Replace the contents of the navigation tab with the search field. + self.tab.textContent = ''; + self.tab.appendChild(searchField); + self.tab.onclick = self.tab.onkeydown = self.tab.onkeypress = undefined; + self.tab.tabIndex = -1; + self.tab.setAttribute('role', ''); + + // Don't allow the focus on the search navbar. http://crbug.com/77989 + self.tab.onfocus = self.tab.blur; + + // Handle search events. (No need to throttle, WebKit's search field + // will do that automatically.) + searchField.onsearch = function(e) { + self.setSearchText_(this.value); + }; + + // We update the history stack every time the search field blurs. This way + // we get a history entry for each search, roughly, but not each letter + // typed. + searchField.onblur = function(e) { + var query = SearchPage.canonicalizeQuery(searchField.value); + if (!query) + return; + + // Don't push the same page onto the history stack more than once (if + // the user clicks in the search field and away several times). + var currentHash = location.hash; + var newHash = '#' + escape(query); + if (currentHash == newHash) + return; + + // If there is no hash on the current URL, the history entry has no + // search query. Replace the history entry with no search with an entry + // that does have a search. Otherwise, add it onto the history stack. + var historyFunction = currentHash ? window.history.pushState : + window.history.replaceState; + historyFunction.call( + window.history, + {pageName: self.name}, + self.title, + '/' + self.name + newHash); + }; + + // Install handler for key presses. + document.addEventListener('keydown', + this.keyDownEventHandler_.bind(this)); + + // Focus the search field by default. + searchField.focus(); + }, + + /** + * @inheritDoc + */ + get sticky() { + return true; + }, + + /** + * Called after this page has shown. + */ + didShowPage: function() { + // This method is called by the Options page after all pages have + // had their visibilty attribute set. At this point we can perform the + // search specific DOM manipulation. + this.setSearchActive_(true); + }, + + /** + * Called before this page will be hidden. + */ + willHidePage: function() { + // This method is called by the Options page before all pages have + // their visibilty attribute set. Before that happens, we need to + // undo the search specific DOM manipulation that was performed in + // didShowPage. + this.setSearchActive_(false); + }, + + /** + * Update the UI to reflect whether we are in a search state. + * @param {boolean} active True if we are on the search page. + * @private + */ + setSearchActive_: function(active) { + // It's fine to exit if search wasn't active and we're not going to + // activate it now. + if (!this.searchActive_ && !active) + return; + + this.searchActive_ = active; + + if (active) { + var hash = location.hash; + if (hash) + this.searchField.value = unescape(hash.slice(1)); + } else { + // Just wipe out any active search text since it's no longer relevant. + this.searchField.value = ''; + } + + var pagesToSearch = this.getSearchablePages_(); + for (var key in pagesToSearch) { + var page = pagesToSearch[key]; + + if (!active) + page.visible = false; + + // Update the visible state of all top-level elements that are not + // sections (ie titles, button strips). We do this before changing + // the page visibility to avoid excessive re-draw. + for (var i = 0, childDiv; childDiv = page.pageDiv.children[i]; i++) { + if (childDiv.classList.contains('displaytable')) { + childDiv.setAttribute('searching', active ? 'true' : 'false'); + for (var j = 0, subDiv; subDiv = childDiv.children[j]; j++) { + if (active) { + if (subDiv.tagName != 'SECTION') + subDiv.classList.add('search-hidden'); + } else { + subDiv.classList.remove('search-hidden'); + } + } + } else { + if (active) + childDiv.classList.add('search-hidden'); + else + childDiv.classList.remove('search-hidden'); + } + } + + if (active) { + // When search is active, remove the 'hidden' tag. This tag may have + // been added by the OptionsPage. + page.pageDiv.hidden = false; + } + } + + if (active) { + this.setSearchText_(this.searchField.value); + } else { + // After hiding all page content, remove any search results. + this.unhighlightMatches_(); + this.removeSearchBubbles_(); + } + }, + + /** + * Set the current search criteria. + * @param {string} text Search text. + * @private + */ + setSearchText_: function(text) { + // Prevent recursive execution of this method. + if (this.insideSetSearchText_) return; + this.insideSetSearchText_ = true; + + // Cleanup the search query string. + text = SearchPage.canonicalizeQuery(text); + + // Notify listeners about the new search query, some pages may wish to + // show/hide elements based on the query. + var event = new cr.Event('searchChanged'); + event.searchText = text; + this.dispatchEvent(event); + + // Toggle the search page if necessary. + if (text.length) { + if (!this.searchActive_) + OptionsPage.navigateToPage(this.name); + } else { + if (this.searchActive_) + OptionsPage.showDefaultPage(); + + this.insideSetSearchText_ = false; + return; + } + + var foundMatches = false; + var bubbleControls = []; + + // Remove any prior search results. + this.unhighlightMatches_(); + this.removeSearchBubbles_(); + + // Generate search text by applying lowercase and escaping any characters + // that would be problematic for regular expressions. + var searchText = + text.toLowerCase().replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&'); + + // Generate a regular expression and replace string for hilighting + // search terms. + var regEx = new RegExp('(' + searchText + ')', 'ig'); + var replaceString = '<span class="search-highlighted">$1</span>'; + + // Initialize all sections. If the search string matches a title page, + // show sections for that page. + var page, pageMatch, childDiv, length; + var pagesToSearch = this.getSearchablePages_(); + for (var key in pagesToSearch) { + page = pagesToSearch[key]; + pageMatch = false; + if (searchText.length) { + pageMatch = this.performReplace_(regEx, replaceString, page.tab); + } + if (pageMatch) + foundMatches = true; + var elements = page.pageDiv.querySelectorAll('.displaytable > section'); + for (var i = 0, node; node = elements[i]; i++) { + if (pageMatch) + node.classList.remove('search-hidden'); + else + node.classList.add('search-hidden'); + } + } + + if (searchText.length) { + // Search all top-level sections for anchored string matches. + for (var key in pagesToSearch) { + page = pagesToSearch[key]; + var elements = + page.pageDiv.querySelectorAll('.displaytable > section'); + for (var i = 0, node; node = elements[i]; i++) { + if (this.performReplace_(regEx, replaceString, node)) { + node.classList.remove('search-hidden'); + foundMatches = true; + } + } + } + + // Search all sub-pages, generating an array of top-level sections that + // we need to make visible. + var subPagesToSearch = this.getSearchableSubPages_(); + var control, node; + for (var key in subPagesToSearch) { + page = subPagesToSearch[key]; + if (this.performReplace_(regEx, replaceString, page.pageDiv)) { + // Reveal the section for this search result. + section = page.associatedSection; + if (section) + section.classList.remove('search-hidden'); + + // Identify any controls that should have bubbles. + var controls = page.associatedControls; + if (controls) { + length = controls.length; + for (var i = 0; i < length; i++) + bubbleControls.push(controls[i]); + } + + foundMatches = true; + } + } + } + + // Configure elements on the search results page based on search results. + if (foundMatches) + $('searchPageNoMatches').classList.add('search-hidden'); + else + $('searchPageNoMatches').classList.remove('search-hidden'); + + // Create search balloons for sub-page results. + length = bubbleControls.length; + for (var i = 0; i < length; i++) + this.createSearchBubble_(bubbleControls[i], text); + + // Cleanup the recursion-prevention variable. + this.insideSetSearchText_ = false; + }, + + /** + * Performs a string replacement based on a regex and replace string. + * @param {RegEx} regex A regular expression for finding search matches. + * @param {String} replace A string to apply the replace operation. + * @param {Element} element An HTML container element. + * @returns {Boolean} true if the element was changed. + * @private + */ + performReplace_: function(regex, replace, element) { + var found = false; + var div, child, tmp; + + // Walk the tree, searching each TEXT node. + var walker = document.createTreeWalker(element, + NodeFilter.SHOW_TEXT, + null, + false); + var node = walker.nextNode(); + while (node) { + // Perform a search and replace on the text node value. + var newValue = node.nodeValue.replace(regex, replace); + if (newValue != node.nodeValue) { + // The text node has changed so that means we found at least one + // match. + found = true; + + // Create a temporary div element and set the innerHTML to the new + // value. + div = document.createElement('div'); + div.innerHTML = newValue; + + // Insert all the child nodes of the temporary div element into the + // document, before the original node. + child = div.firstChild; + while (child = div.firstChild) { + node.parentNode.insertBefore(child, node); + }; + + // Delete the old text node and advance the walker to the next + // node. + tmp = node; + node = walker.nextNode(); + tmp.parentNode.removeChild(tmp); + } else { + node = walker.nextNode(); + } + } + + return found; + }, + + /** + * Removes all search highlight tags from the document. + * @private + */ + unhighlightMatches_: function() { + // Find all search highlight elements. + var elements = document.querySelectorAll('.search-highlighted'); + + // For each element, remove the highlighting. + var parent, i; + for (var i = 0, node; node = elements[i]; i++) { + parent = node.parentNode; + + // Replace the highlight element with the first child (the text node). + parent.replaceChild(node.firstChild, node); + + // Normalize the parent so that multiple text nodes will be combined. + parent.normalize(); + } + }, + + /** + * Creates a search result bubble attached to an element. + * @param {Element} element An HTML element, usually a button. + * @param {string} text A string to show in the bubble. + * @private + */ + createSearchBubble_: function(element, text) { + // avoid appending multiple bubbles to a button. + var sibling = element.previousElementSibling; + if (sibling && (sibling.classList.contains('search-bubble') || + sibling.classList.contains('search-bubble-wrapper'))) + return; + + var parent = element.parentElement; + if (parent) { + var bubble = new SearchBubble(text); + bubble.attachTo(element); + bubble.updatePosition(); + } + }, + + /** + * Removes all search match bubbles. + * @private + */ + removeSearchBubbles_: function() { + var elements = document.querySelectorAll('.search-bubble'); + var length = elements.length; + for (var i = 0; i < length; i++) + elements[i].dispose(); + }, + + /** + * Builds a list of top-level pages to search. Omits the search page and + * all sub-pages. + * @returns {Array} An array of pages to search. + * @private + */ + getSearchablePages_: function() { + var name, page, pages = []; + for (name in OptionsPage.registeredPages) { + if (name != this.name) { + page = OptionsPage.registeredPages[name]; + if (!page.parentPage) + pages.push(page); + } + } + return pages; + }, + + /** + * Builds a list of sub-pages (and overlay pages) to search. Ignore pages + * that have no associated controls. + * @returns {Array} An array of pages to search. + * @private + */ + getSearchableSubPages_: function() { + var name, pageInfo, page, pages = []; + for (name in OptionsPage.registeredPages) { + page = OptionsPage.registeredPages[name]; + if (page.parentPage && page.associatedSection) + pages.push(page); + } + for (name in OptionsPage.registeredOverlayPages) { + page = OptionsPage.registeredOverlayPages[name]; + if (page.associatedSection && page.pageDiv != undefined) + pages.push(page); + } + return pages; + }, + + /** + * A function to handle key press events. + * @return {Event} a keydown event. + * @private + */ + keyDownEventHandler_: function(event) { + const ESCAPE_KEY_CODE = 27; + const FORWARD_SLASH_KEY_CODE = 191; + + switch(event.keyCode) { + case ESCAPE_KEY_CODE: + if (event.target == this.searchField) { + this.setSearchText_(''); + this.searchField.blur(); + event.stopPropagation(); + event.preventDefault(); + } + break; + case FORWARD_SLASH_KEY_CODE: + if (!/INPUT|SELECT|BUTTON|TEXTAREA/.test(event.target.tagName) && + !event.ctrlKey && !event.altKey) { + this.searchField.focus(); + event.stopPropagation(); + event.preventDefault(); + } + break; + } + }, + }; + + /** + * Standardizes a user-entered text query by removing extra whitespace. + * @param {string} The user-entered text. + * @return {string} The trimmed query. + */ + SearchPage.canonicalizeQuery = function(text) { + // Trim beginning and ending whitespace. + return text.replace(/^\s+|\s+$/g, ''); + }; + + // Export + return { + SearchPage: SearchPage + }; + +}); diff --git a/chrome/browser/resources/options2/subpages_tab_controls.css b/chrome/browser/resources/options2/subpages_tab_controls.css new file mode 100644 index 0000000..774520c --- /dev/null +++ b/chrome/browser/resources/options2/subpages_tab_controls.css @@ -0,0 +1,66 @@ +/* +Copyright (c) 2010 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. +*/ + +.subpages-nav-tabs .tab { + position: relative; + padding: 4px 8px; +} + +.subpages-nav-tabs .active-tab { + position: relative; + background: white; + border: 1px solid #A0A0A0; /* light gray */ + border-bottom: 2px solid white; + border-top-left-radius: 3px; + border-top-right-radius: 3px; +} + +/* To avoid tabs changing size when they are clicked and their labels become + * bold, we actually put two labels inside each tab: an inactive label and an + * active label. Only one is visible at a time, but the bold label is used to + * size the tab even when it's not visible. This keeps the tab size constant. + */ +.subpages-nav-tabs .active-tab-label { + font-weight: bold; +} + +.subpages-nav-tabs .tab-label { + position: absolute; + top: 5px; + left: 9px; +} + +html[dir=rtl] .subpages-nav-tabs .tab-label { + right: 9px; +} + +.subpages-nav-tabs .active-tab-label, +.subpages-nav-tabs .active-tab .tab-label { + visibility: hidden; +} + +/* .tab is not removed when .active-tab is added, so we must + * override the hidden visibility above in the active tab case. + */ +.subpages-nav-tabs .active-tab .active-tab-label { + visibility: visible; +} + +.subpages-nav-tabs { + padding: 4px; + border-bottom: 1px solid #A0A0A0; /* light gray */ + background: -webkit-linear-gradient(white, #F3F3F3); /* very light gray */ + margin-bottom: 15px; +} + +.subpages-tab-contents { + display: none; + -webkit-padding-start: 10px; +} + +.active-tab-contents { + display: block; +} diff --git a/chrome/browser/resources/options2_resources.grd b/chrome/browser/resources/options2_resources.grd new file mode 100644 index 0000000..f094fda --- /dev/null +++ b/chrome/browser/resources/options2_resources.grd @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<grit latest_public_release="0" current_release="1"> + <outputs> + <output filename="grit/options2_resources.h" type="rc_header"> + <emit emit_type='prepend'></emit> + </output> + <output filename="options2_resources.pak" type="data_package" /> + </outputs> + <release seq="1"> + <includes> + <include name="IDR_OPTIONS2_BUNDLE_JS" file="options2/options_bundle.js" flattenhtml="true" type="BINDATA" /> + <include name="IDR_OPTIONS2_HTML" file="options2/options.html" flattenhtml="true" allowexternalscript="true" type="BINDATA" /> + </includes> + </release> +</grit> diff --git a/chrome/browser/resources/uber/uber.html b/chrome/browser/resources/uber/uber.html index 61469f8..d8f3bfd 100644 --- a/chrome/browser/resources/uber/uber.html +++ b/chrome/browser/resources/uber/uber.html @@ -13,7 +13,7 @@ <body> -<iframe src="chrome://settings/"></iframe> +<iframe src="chrome://settings-frame/"></iframe> <iframe src="chrome://extensions-frame/"></iframe> </body> diff --git a/chrome/browser/ui/browser_navigator.cc b/chrome/browser/ui/browser_navigator.cc index ab73fbc..234f2e9 100644 --- a/chrome/browser/ui/browser_navigator.cc +++ b/chrome/browser/ui/browser_navigator.cc @@ -641,6 +641,7 @@ bool IsURLAllowedInIncognito(const GURL& url) { return !(url.scheme() == chrome::kChromeUIScheme && (url.host() == chrome::kChromeUISettingsHost || + url.host() == chrome::kChromeUISettingsFrameHost || url.host() == chrome::kChromeUIExtensionsHost || url.host() == chrome::kChromeUIBookmarksHost || url.host() == chrome::kChromeUISyncPromoHost)); diff --git a/chrome/browser/ui/webui/chrome_web_ui_factory.cc b/chrome/browser/ui/webui/chrome_web_ui_factory.cc index d7000e1..eabb387 100644 --- a/chrome/browser/ui/webui/chrome_web_ui_factory.cc +++ b/chrome/browser/ui/webui/chrome_web_ui_factory.cc @@ -33,6 +33,7 @@ #include "chrome/browser/ui/webui/net_internals_ui.h" #include "chrome/browser/ui/webui/ntp/new_tab_ui.h" #include "chrome/browser/ui/webui/options/options_ui.h" +#include "chrome/browser/ui/webui/options2/options_ui2.h" #include "chrome/browser/ui/webui/plugins_ui.h" #include "chrome/browser/ui/webui/policy_ui.h" #include "chrome/browser/ui/webui/print_preview_ui.h" @@ -214,6 +215,8 @@ WebUIFactoryFunction GetWebUIFactoryFunction(TabContents* tab_contents, return &NewWebUI<SessionsUI>; if (url.host() == chrome::kChromeUISettingsHost) return &NewWebUI<OptionsUI>; + if (url.host() == chrome::kChromeUISettingsFrameHost) + return &NewWebUI<Options2UI>; if (url.host() == chrome::kChromeUISyncInternalsHost) return &NewWebUI<SyncInternalsUI>; if (url.host() == chrome::kChromeUITaskManagerHost) @@ -449,6 +452,9 @@ RefCountedMemory* ChromeWebUIFactory::GetFaviconResourceBytes( if (page_url.host() == chrome::kChromeUISettingsHost) return OptionsUI::GetFaviconResourceBytes(); + if (page_url.host() == chrome::kChromeUISettingsFrameHost) + return Options2UI::GetFaviconResourceBytes(); + if (page_url.host() == chrome::kChromeUIPluginsHost) return PluginsUI::GetFaviconResourceBytes(); diff --git a/chrome/browser/ui/webui/options/chromeos/bluetooth_options_handler.cc b/chrome/browser/ui/webui/options/chromeos/bluetooth_options_handler.cc index 5dfe692..0e194a2 100644 --- a/chrome/browser/ui/webui/options/chromeos/bluetooth_options_handler.cc +++ b/chrome/browser/ui/webui/options/chromeos/bluetooth_options_handler.cc @@ -19,7 +19,7 @@ namespace { -// |UpdateDeviceCallback| takes a variable length list as an arugment. The +// |UpdateDeviceCallback| takes a variable length list as an argument. The // value stored in each list element is indicated by the following constants. const int kUpdateDeviceAddressIndex = 0; const int kUpdateDeviceCommandIndex = 1; diff --git a/chrome/browser/ui/webui/options/options_ui.h b/chrome/browser/ui/webui/options/options_ui.h index 184af36..16237ff 100644 --- a/chrome/browser/ui/webui/options/options_ui.h +++ b/chrome/browser/ui/webui/options/options_ui.h @@ -72,6 +72,9 @@ class OptionsPageUIHandler : public WebUIMessageHandler, class OptionsPageUIHandlerHost { public: virtual void InitializeHandlers() = 0; + + protected: + virtual ~OptionsPageUIHandlerHost() {} }; // The WebUI for chrome:settings. diff --git a/chrome/browser/ui/webui/options2/OWNERS b/chrome/browser/ui/webui/options2/OWNERS new file mode 100644 index 0000000..67257e6 --- /dev/null +++ b/chrome/browser/ui/webui/options2/OWNERS @@ -0,0 +1,4 @@ +csilv@chromium.org +estade@chromium.org +jhawkins@chromium.org +stuartmorgan@chromium.org diff --git a/chrome/browser/ui/webui/options2/advanced_options_browsertest.js b/chrome/browser/ui/webui/options2/advanced_options_browsertest.js new file mode 100644 index 0000000..a929bbb --- /dev/null +++ b/chrome/browser/ui/webui/options2/advanced_options_browsertest.js @@ -0,0 +1,63 @@ +// Copyright (c) 2011 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. + +/** + * TestFixture for advanced options WebUI testing. + * @extends {testing.Test} + * @constructor + **/ +function AdvancedOptionsWebUITest() {} + +AdvancedOptionsWebUITest.prototype = { + __proto__: testing.Test.prototype, + + /** + * Browse to advanced options. + **/ + browsePreload: 'chrome://settings/advanced', + + /** + * Register a mock handler. + * @type {Function} + * @override + */ + preLoad: function() { + this.makeAndRegisterMockHandler(['defaultZoomFactorAction']); + }, +}; + +/** + * The expected minimum length of the |defaultZoomFactor| element. + * @type {number} + * @const + */ +var defaultZoomFactorMinimumLength = 10; + +/** + * Test opening the advanced options has correct location. + */ +TEST_F('AdvancedOptionsWebUITest', 'testOpenAdvancedOptions', function() { + assertEquals(this.browsePreload, document.location.href); +}); + +/** + * Test the default zoom factor select element. + */ +TEST_F('AdvancedOptionsWebUITest', 'testDefaultZoomFactor', function() { + // Verify that the zoom factor element exists. + var defaultZoomFactor = $('defaultZoomFactor'); + assertNotEquals(defaultZoomFactor, null); + + // Verify that the zoom factor element has a reasonable number of choices. + expectGE(defaultZoomFactor.options.length, defaultZoomFactorMinimumLength); + + // Simulate a change event, selecting the highest zoom value. Verify that + // the javascript handler was invoked once. + this.mockHandler.expects(once()).defaultZoomFactorAction(NOT_NULL). + will(callFunction(function() { })); + defaultZoomFactor.selectedIndex = defaultZoomFactor.options.length - 1; + var event = { target: defaultZoomFactor }; + if (defaultZoomFactor.onchange) defaultZoomFactor.onchange(event); +}); + diff --git a/chrome/browser/ui/webui/options2/advanced_options_handler.cc b/chrome/browser/ui/webui/options2/advanced_options_handler.cc new file mode 100644 index 0000000..fbb8aed --- /dev/null +++ b/chrome/browser/ui/webui/options2/advanced_options_handler.cc @@ -0,0 +1,643 @@ +// Copyright (c) 2011 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. + +#include "chrome/browser/ui/webui/options2/advanced_options_handler.h" + +#include <string> + +#include "base/basictypes.h" +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/command_line.h" +#include "base/utf_string_conversions.h" +#include "base/values.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/chrome_page_zoom.h" +#include "chrome/browser/download/download_prefs.h" +#include "chrome/browser/google/google_util.h" +#include "chrome/browser/prefs/pref_service.h" +#include "chrome/browser/printing/cloud_print/cloud_print_proxy_service.h" +#include "chrome/browser/printing/cloud_print/cloud_print_proxy_service_factory.h" +#include "chrome/browser/printing/cloud_print/cloud_print_setup_flow.h" +#include "chrome/browser/printing/cloud_print/cloud_print_url.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/service/service_process_control.h" +#include "chrome/browser/ui/options/options_util.h" +#include "chrome/common/chrome_notification_types.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/common/pref_names.h" +#include "chrome/common/url_constants.h" +#include "content/browser/download/download_manager.h" +#include "content/browser/tab_contents/tab_contents.h" +#include "content/browser/tab_contents/tab_contents_view.h" +#include "content/public/browser/notification_details.h" +#include "content/public/browser/notification_types.h" +#include "content/public/browser/user_metrics.h" +#include "content/public/common/page_zoom.h" +#include "grit/chromium_strings.h" +#include "grit/generated_resources.h" +#include "grit/locale_settings.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebView.h" +#include "ui/base/l10n/l10n_util.h" + +#if !defined(OS_CHROMEOS) +#include "chrome/browser/printing/cloud_print/cloud_print_setup_handler.h" +#include "chrome/browser/ui/webui/options2/advanced_options_utils.h" +#endif + +using content::UserMetricsAction; + +AdvancedOptionsHandler::AdvancedOptionsHandler() { + +#if(!defined(GOOGLE_CHROME_BUILD) && defined(OS_WIN)) + // On Windows, we need the PDF plugin which is only guaranteed to exist on + // Google Chrome builds. Use a command-line switch for Windows non-Google + // Chrome builds. + cloud_print_proxy_ui_enabled_ = CommandLine::ForCurrentProcess()->HasSwitch( + switches::kEnableCloudPrintProxy); +#elif(!defined(OS_CHROMEOS)) + // Always enabled for Mac, Linux and Google Chrome Windows builds. + // Never enabled for Chrome OS, we don't even need to indicate it. + cloud_print_proxy_ui_enabled_ = true; +#endif +} + +AdvancedOptionsHandler::~AdvancedOptionsHandler() { + // There may be pending file dialogs, we need to tell them that we've gone + // away so they don't try and call back to us. + if (select_folder_dialog_.get()) + select_folder_dialog_->ListenerDestroyed(); +} + +void AdvancedOptionsHandler::GetLocalizedValues( + DictionaryValue* localized_strings) { + DCHECK(localized_strings); + + static OptionsStringResource resources[] = { + { "downloadLocationGroupName", + IDS_OPTIONS_DOWNLOADLOCATION_GROUP_NAME }, + { "downloadLocationChangeButton", + IDS_OPTIONS_DOWNLOADLOCATION_CHANGE_BUTTON }, + { "downloadLocationBrowseTitle", + IDS_OPTIONS_DOWNLOADLOCATION_BROWSE_TITLE }, + { "downloadLocationBrowseWindowTitle", + IDS_OPTIONS_DOWNLOADLOCATION_BROWSE_WINDOW_TITLE }, + { "downloadLocationAskForSaveLocation", + IDS_OPTIONS_DOWNLOADLOCATION_ASKFORSAVELOCATION }, + { "autoOpenFileTypesInfo", + IDS_OPTIONS_OPEN_FILE_TYPES_AUTOMATICALLY }, + { "autoOpenFileTypesResetToDefault", + IDS_OPTIONS_AUTOOPENFILETYPES_RESETTODEFAULT }, + { "translateEnableTranslate", + IDS_OPTIONS_TRANSLATE_ENABLE_TRANSLATE }, + { "certificatesManageButton", + IDS_OPTIONS_CERTIFICATES_MANAGE_BUTTON }, + { "proxiesLabel", + IDS_OPTIONS_PROXIES_LABEL }, +#if !defined(OS_CHROMEOS) + { "proxiesConfigureButton", + IDS_OPTIONS_PROXIES_CONFIGURE_BUTTON }, +#endif + { "safeBrowsingEnableProtection", + IDS_OPTIONS_SAFEBROWSING_ENABLEPROTECTION }, + { "sslGroupDescription", + IDS_OPTIONS_SSL_GROUP_DESCRIPTION }, + { "sslCheckRevocation", + IDS_OPTIONS_SSL_CHECKREVOCATION }, + { "networkPredictionEnabledDescription", + IDS_NETWORK_PREDICTION_ENABLED_DESCRIPTION }, + { "privacyContentSettingsButton", + IDS_OPTIONS_PRIVACY_CONTENT_SETTINGS_BUTTON }, + { "privacyClearDataButton", + IDS_OPTIONS_PRIVACY_CLEAR_DATA_BUTTON }, + { "linkDoctorPref", + IDS_OPTIONS_LINKDOCTOR_PREF }, + { "suggestPref", + IDS_OPTIONS_SUGGEST_PREF }, + { "tabsToLinksPref", + IDS_OPTIONS_TABS_TO_LINKS_PREF }, + { "fontSettingsInfo", + IDS_OPTIONS_FONTSETTINGS_INFO }, + { "defaultZoomFactorLabel", + IDS_OPTIONS_DEFAULT_ZOOM_LEVEL_LABEL }, + { "defaultFontSizeLabel", + IDS_OPTIONS_DEFAULT_FONT_SIZE_LABEL }, + { "fontSizeLabelVerySmall", + IDS_OPTIONS_FONT_SIZE_LABEL_VERY_SMALL }, + { "fontSizeLabelSmall", + IDS_OPTIONS_FONT_SIZE_LABEL_SMALL }, + { "fontSizeLabelMedium", + IDS_OPTIONS_FONT_SIZE_LABEL_MEDIUM }, + { "fontSizeLabelLarge", + IDS_OPTIONS_FONT_SIZE_LABEL_LARGE }, + { "fontSizeLabelVeryLarge", + IDS_OPTIONS_FONT_SIZE_LABEL_VERY_LARGE }, + { "fontSizeLabelCustom", + IDS_OPTIONS_FONT_SIZE_LABEL_CUSTOM }, + { "fontSettingsCustomizeFontsButton", + IDS_OPTIONS_FONTSETTINGS_CUSTOMIZE_FONTS_BUTTON }, + { "languageAndSpellCheckSettingsButton", + IDS_OPTIONS_LANGUAGE_AND_SPELLCHECK_BUTTON }, + { "advancedSectionTitlePrivacy", + IDS_OPTIONS_ADVANCED_SECTION_TITLE_PRIVACY }, + { "advancedSectionTitleContent", + IDS_OPTIONS_ADVANCED_SECTION_TITLE_CONTENT }, + { "advancedSectionTitleSecurity", + IDS_OPTIONS_ADVANCED_SECTION_TITLE_SECURITY }, + { "advancedSectionTitleNetwork", + IDS_OPTIONS_ADVANCED_SECTION_TITLE_NETWORK }, + { "advancedSectionTitleTranslate", + IDS_OPTIONS_ADVANCED_SECTION_TITLE_TRANSLATE }, + { "translateEnableTranslate", + IDS_OPTIONS_TRANSLATE_ENABLE_TRANSLATE }, + { "enableLogging", + IDS_OPTIONS_ENABLE_LOGGING }, + { "improveBrowsingExperience", + IDS_OPTIONS_IMPROVE_BROWSING_EXPERIENCE }, + { "disableWebServices", + IDS_OPTIONS_DISABLE_WEB_SERVICES }, +#if defined(OS_CHROMEOS) + { "cloudPrintChromeosOptionLabel", + IDS_CLOUD_PRINT_CHROMEOS_OPTION_LABEL }, + { "cloudPrintChromeosOptionButton", + IDS_CLOUD_PRINT_CHROMEOS_OPTION_BUTTON }, +#endif + { "cloudPrintOptionsStaticLabel", + IDS_CLOUD_PRINT_SETUP_DIALOG_TITLE }, + { "cloudPrintProxyEnabledManageButton", + IDS_OPTIONS_CLOUD_PRINT_PROXY_ENABLED_MANAGE_BUTTON }, + { "advancedSectionTitleCloudPrint", + IDS_OPTIONS_ADVANCED_SECTION_TITLE_CLOUD_PRINT }, +#if !defined(OS_CHROMEOS) + { "cloudPrintProxyDisabledLabel", + IDS_OPTIONS_CLOUD_PRINT_PROXY_DISABLED_LABEL }, + { "cloudPrintProxyDisabledButton", + IDS_OPTIONS_CLOUD_PRINT_PROXY_DISABLED_BUTTON }, + { "cloudPrintProxyEnabledButton", + IDS_OPTIONS_CLOUD_PRINT_PROXY_ENABLED_BUTTON }, + { "cloudPrintProxyEnablingButton", + IDS_OPTIONS_CLOUD_PRINT_PROXY_ENABLING_BUTTON }, +#endif +#if !defined(OS_MACOSX) && !defined(OS_CHROMEOS) + { "advancedSectionTitleBackground", + IDS_OPTIONS_ADVANCED_SECTION_TITLE_BACKGROUND }, + { "backgroundModeCheckbox", + IDS_OPTIONS_BACKGROUND_ENABLE_BACKGROUND_MODE }, +#endif + }; + + RegisterStrings(localized_strings, resources, arraysize(resources)); + RegisterTitle(localized_strings, "advancedPage", + IDS_OPTIONS_ADVANCED_TAB_LABEL); + + localized_strings->SetString("privacyLearnMoreURL", + google_util::AppendGoogleLocaleParam( + GURL(chrome::kPrivacyLearnMoreURL)).spec()); + +#if defined(OS_CHROMEOS) + localized_strings->SetString("cloudPrintLearnMoreURL", + google_util::AppendGoogleLocaleParam( + GURL(chrome::kCloudPrintLearnMoreURL)).spec()); +#endif +} + +void AdvancedOptionsHandler::Initialize() { + DCHECK(web_ui_); + SetupMetricsReportingCheckbox(); + SetupMetricsReportingSettingVisibility(); + SetupFontSizeSelector(); + SetupPageZoomSelector(); + SetupAutoOpenFileTypesDisabledAttribute(); + SetupProxySettingsSection(); + SetupSSLConfigSettings(); +#if !defined(OS_CHROMEOS) + if (cloud_print_proxy_ui_enabled_) { + SetupCloudPrintProxySection(); + RefreshCloudPrintStatusFromService(); + } else { + RemoveCloudPrintProxySection(); + } +#endif +#if !defined(OS_MACOSX) && !defined(OS_CHROMEOS) + SetupBackgroundModeSettings(); +#endif + +} + +WebUIMessageHandler* AdvancedOptionsHandler::Attach(WebUI* web_ui) { + // Call through to superclass. + WebUIMessageHandler* handler = OptionsPage2UIHandler::Attach(web_ui); + + // Register for preferences that we need to observe manually. These have + // special behaviors that aren't handled by the standard prefs UI. + DCHECK(web_ui_); + PrefService* prefs = Profile::FromWebUI(web_ui_)->GetPrefs(); +#if !defined(OS_CHROMEOS) + enable_metrics_recording_.Init(prefs::kMetricsReportingEnabled, + g_browser_process->local_state(), this); + cloud_print_proxy_email_.Init(prefs::kCloudPrintEmail, prefs, this); + cloud_print_proxy_enabled_.Init(prefs::kCloudPrintProxyEnabled, prefs, this); +#endif + + rev_checking_enabled_.Init(prefs::kCertRevocationCheckingEnabled, + g_browser_process->local_state(), this); + +#if !defined(OS_MACOSX) && !defined(OS_CHROMEOS) + background_mode_enabled_.Init(prefs::kBackgroundModeEnabled, + g_browser_process->local_state(), + this); +#endif + + auto_open_files_.Init(prefs::kDownloadExtensionsToOpen, prefs, this); + default_font_size_.Init(prefs::kWebKitDefaultFontSize, prefs, this); + default_zoom_level_.Init(prefs::kDefaultZoomLevel, prefs, this); +#if !defined(OS_CHROMEOS) + proxy_prefs_.reset( + PrefSetObserver::CreateProxyPrefSetObserver(prefs, this)); +#endif // !defined(OS_CHROMEOS) + + // Return result from the superclass. + return handler; +} + +void AdvancedOptionsHandler::RegisterMessages() { + // Setup handlers specific to this panel. + DCHECK(web_ui_); + web_ui_->RegisterMessageCallback("selectDownloadLocation", + base::Bind(&AdvancedOptionsHandler::HandleSelectDownloadLocation, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("autoOpenFileTypesAction", + base::Bind(&AdvancedOptionsHandler::HandleAutoOpenButton, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("defaultFontSizeAction", + base::Bind(&AdvancedOptionsHandler::HandleDefaultFontSize, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("defaultZoomFactorAction", + base::Bind(&AdvancedOptionsHandler::HandleDefaultZoomFactor, + base::Unretained(this))); +#if !defined(OS_CHROMEOS) + web_ui_->RegisterMessageCallback("metricsReportingCheckboxAction", + base::Bind(&AdvancedOptionsHandler::HandleMetricsReportingCheckbox, + base::Unretained(this))); +#endif +#if !defined(USE_NSS) && !defined(USE_OPENSSL) + web_ui_->RegisterMessageCallback("showManageSSLCertificates", + base::Bind(&AdvancedOptionsHandler::ShowManageSSLCertificates, + base::Unretained(this))); +#endif + web_ui_->RegisterMessageCallback("showCloudPrintManagePage", + base::Bind(&AdvancedOptionsHandler::ShowCloudPrintManagePage, + base::Unretained(this))); +#if !defined(OS_CHROMEOS) + if (cloud_print_proxy_ui_enabled_) { + web_ui_->RegisterMessageCallback("showCloudPrintSetupDialog", + base::Bind(&AdvancedOptionsHandler::ShowCloudPrintSetupDialog, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("disableCloudPrintProxy", + base::Bind(&AdvancedOptionsHandler::HandleDisableCloudPrintProxy, + base::Unretained(this))); + } + web_ui_->RegisterMessageCallback("showNetworkProxySettings", + base::Bind(&AdvancedOptionsHandler::ShowNetworkProxySettings, + base::Unretained(this))); +#endif + web_ui_->RegisterMessageCallback("checkRevocationCheckboxAction", + base::Bind(&AdvancedOptionsHandler::HandleCheckRevocationCheckbox, + base::Unretained(this))); +#if !defined(OS_MACOSX) && !defined(OS_CHROMEOS) + web_ui_->RegisterMessageCallback("backgroundModeAction", + base::Bind(&AdvancedOptionsHandler::HandleBackgroundModeCheckbox, + base::Unretained(this))); +#endif +} + +void AdvancedOptionsHandler::Observe( + int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) { + if (type == chrome::NOTIFICATION_PREF_CHANGED) { + std::string* pref_name = content::Details<std::string>(details).ptr(); + if (*pref_name == prefs::kDownloadExtensionsToOpen) { + SetupAutoOpenFileTypesDisabledAttribute(); +#if !defined(OS_CHROMEOS) + } else if (proxy_prefs_->IsObserved(*pref_name)) { + SetupProxySettingsSection(); +#endif // !defined(OS_CHROMEOS) + } else if ((*pref_name == prefs::kCloudPrintEmail) || + (*pref_name == prefs::kCloudPrintProxyEnabled)) { +#if !defined(OS_CHROMEOS) + if (cloud_print_proxy_ui_enabled_) + SetupCloudPrintProxySection(); +#endif + } else if (*pref_name == prefs::kWebKitDefaultFontSize) { + SetupFontSizeSelector(); + } else if (*pref_name == prefs::kDefaultZoomLevel) { + SetupPageZoomSelector(); +#if !defined(OS_MACOSX) && !defined(OS_CHROMEOS) + } else if (*pref_name == prefs::kBackgroundModeEnabled) { + SetupBackgroundModeSettings(); +#endif + } + } +} + +void AdvancedOptionsHandler::HandleSelectDownloadLocation( + const ListValue* args) { + PrefService* pref_service = Profile::FromWebUI(web_ui_)->GetPrefs(); + select_folder_dialog_ = SelectFileDialog::Create(this); + select_folder_dialog_->SelectFile( + SelectFileDialog::SELECT_FOLDER, + l10n_util::GetStringUTF16(IDS_OPTIONS_DOWNLOADLOCATION_BROWSE_TITLE), + pref_service->GetFilePath(prefs::kDownloadDefaultDirectory), + NULL, 0, FILE_PATH_LITERAL(""), web_ui_->tab_contents(), + web_ui_->tab_contents()->view()->GetTopLevelNativeWindow(), NULL); +} + +void AdvancedOptionsHandler::FileSelected(const FilePath& path, int index, + void* params) { + content::RecordAction(UserMetricsAction("Options_SetDownloadDirectory")); + PrefService* pref_service = Profile::FromWebUI(web_ui_)->GetPrefs(); + pref_service->SetFilePath(prefs::kDownloadDefaultDirectory, path); +} + +void AdvancedOptionsHandler::OnCloudPrintSetupClosed() { +#if !defined(OS_CHROMEOS) + if (cloud_print_proxy_ui_enabled_) + SetupCloudPrintProxySection(); +#endif +} + +void AdvancedOptionsHandler::HandleAutoOpenButton(const ListValue* args) { + content::RecordAction(UserMetricsAction("Options_ResetAutoOpenFiles")); + DownloadManager* manager = + web_ui_->tab_contents()->browser_context()->GetDownloadManager(); + if (manager) + DownloadPrefs::FromDownloadManager(manager)->ResetAutoOpen(); +} + +void AdvancedOptionsHandler::HandleMetricsReportingCheckbox( + const ListValue* args) { +#if defined(GOOGLE_CHROME_BUILD) && !defined(OS_CHROMEOS) + std::string checked_str = UTF16ToUTF8(ExtractStringValue(args)); + bool enabled = checked_str == "true"; + content::RecordAction( + enabled ? + UserMetricsAction("Options_MetricsReportingCheckbox_Enable") : + UserMetricsAction("Options_MetricsReportingCheckbox_Disable")); + bool is_enabled = OptionsUtil::ResolveMetricsReportingEnabled(enabled); + enable_metrics_recording_.SetValue(is_enabled); + SetupMetricsReportingCheckbox(); +#endif +} + +void AdvancedOptionsHandler::HandleDefaultFontSize(const ListValue* args) { + int font_size; + if (ExtractIntegerValue(args, &font_size)) { + if (font_size > 0) { + default_font_size_.SetValue(font_size); + SetupFontSizeSelector(); + } + } +} + +void AdvancedOptionsHandler::HandleDefaultZoomFactor(const ListValue* args) { + double zoom_factor; + if (ExtractDoubleValue(args, &zoom_factor)) { + default_zoom_level_.SetValue( + WebKit::WebView::zoomFactorToZoomLevel(zoom_factor)); + } +} + +void AdvancedOptionsHandler::HandleCheckRevocationCheckbox( + const ListValue* args) { + std::string checked_str = UTF16ToUTF8(ExtractStringValue(args)); + bool enabled = checked_str == "true"; + content::RecordAction( + enabled ? + UserMetricsAction("Options_CheckCertRevocation_Enable") : + UserMetricsAction("Options_CheckCertRevocation_Disable")); + rev_checking_enabled_.SetValue(enabled); +} + +#if !defined(OS_MACOSX) && !defined(OS_CHROMEOS) +void AdvancedOptionsHandler::HandleBackgroundModeCheckbox( + const ListValue* args) { + std::string checked_str = UTF16ToUTF8(ExtractStringValue(args)); + bool enabled = checked_str == "true"; + content::RecordAction(enabled ? + UserMetricsAction("Options_BackgroundMode_Enable") : + UserMetricsAction("Options_BackgroundMode_Disable")); + background_mode_enabled_.SetValue(enabled); +} + +void AdvancedOptionsHandler::SetupBackgroundModeSettings() { + base::FundamentalValue checked(background_mode_enabled_.GetValue()); + web_ui_->CallJavascriptFunction( + "options.AdvancedOptions.SetBackgroundModeCheckboxState", checked); +} +#endif + +#if !defined(OS_CHROMEOS) +void AdvancedOptionsHandler::ShowNetworkProxySettings(const ListValue* args) { + content::RecordAction(UserMetricsAction("Options_ShowProxySettings")); + AdvancedOptionsUtilities::ShowNetworkProxySettings(web_ui_->tab_contents()); +} +#endif + +#if !defined(USE_NSS) && !defined(USE_OPENSSL) +void AdvancedOptionsHandler::ShowManageSSLCertificates(const ListValue* args) { + content::RecordAction(UserMetricsAction("Options_ManageSSLCertificates")); + AdvancedOptionsUtilities::ShowManageSSLCertificates(web_ui_->tab_contents()); +} +#endif + +void AdvancedOptionsHandler::ShowCloudPrintManagePage(const ListValue* args) { + content::RecordAction(UserMetricsAction("Options_ManageCloudPrinters")); + // Open a new tab in the current window for the management page. + Profile* profile = Profile::FromWebUI(web_ui_); + web_ui_->tab_contents()->OpenURL( + CloudPrintURL(profile).GetCloudPrintServiceManageURL(), + GURL(), NEW_FOREGROUND_TAB, content::PAGE_TRANSITION_LINK); +} + +#if !defined(OS_CHROMEOS) +void AdvancedOptionsHandler::ShowCloudPrintSetupDialog(const ListValue* args) { + content::RecordAction(UserMetricsAction("Options_EnableCloudPrintProxy")); + // Open the connector enable page in the current tab. + Profile* profile = Profile::FromWebUI(web_ui_); + web_ui_->tab_contents()->OpenURL( + CloudPrintURL(profile).GetCloudPrintServiceEnableURL( + CloudPrintProxyServiceFactory::GetForProfile(profile)->proxy_id()), + GURL(), CURRENT_TAB, content::PAGE_TRANSITION_LINK); +} + +void AdvancedOptionsHandler::HandleDisableCloudPrintProxy( + const ListValue* args) { + content::RecordAction( + UserMetricsAction("Options_DisableCloudPrintProxy")); + CloudPrintProxyServiceFactory::GetForProfile(Profile::FromWebUI(web_ui_))-> + DisableForUser(); +} + +void AdvancedOptionsHandler::RefreshCloudPrintStatusFromService() { + if (cloud_print_proxy_ui_enabled_) + CloudPrintProxyServiceFactory::GetForProfile(Profile::FromWebUI(web_ui_))-> + RefreshStatusFromService(); +} + +void AdvancedOptionsHandler::SetupCloudPrintProxySection() { + Profile* profile = Profile::FromWebUI(web_ui_); + if (!CloudPrintProxyServiceFactory::GetForProfile(profile)) { + cloud_print_proxy_ui_enabled_ = false; + RemoveCloudPrintProxySection(); + return; + } + + bool cloud_print_proxy_allowed = + !cloud_print_proxy_enabled_.IsManaged() || + cloud_print_proxy_enabled_.GetValue(); + base::FundamentalValue allowed(cloud_print_proxy_allowed); + + std::string email; + if (profile->GetPrefs()->HasPrefPath(prefs::kCloudPrintEmail) && + cloud_print_proxy_allowed) { + email = profile->GetPrefs()->GetString(prefs::kCloudPrintEmail); + } + base::FundamentalValue disabled(email.empty()); + + string16 label_str; + if (email.empty()) { + label_str = l10n_util::GetStringUTF16( + IDS_OPTIONS_CLOUD_PRINT_PROXY_DISABLED_LABEL); + } else { + label_str = l10n_util::GetStringFUTF16( + IDS_OPTIONS_CLOUD_PRINT_PROXY_ENABLED_LABEL, UTF8ToUTF16(email)); + } + StringValue label(label_str); + + web_ui_->CallJavascriptFunction( + "options.AdvancedOptions.SetupCloudPrintProxySection", + disabled, label, allowed); +} + +void AdvancedOptionsHandler::RemoveCloudPrintProxySection() { + web_ui_->CallJavascriptFunction( + "options.AdvancedOptions.RemoveCloudPrintProxySection"); +} + +#endif + +void AdvancedOptionsHandler::SetupMetricsReportingCheckbox() { +#if defined(GOOGLE_CHROME_BUILD) && !defined(OS_CHROMEOS) + base::FundamentalValue checked(enable_metrics_recording_.GetValue()); + base::FundamentalValue disabled(enable_metrics_recording_.IsManaged()); + web_ui_->CallJavascriptFunction( + "options.AdvancedOptions.SetMetricsReportingCheckboxState", checked, + disabled); +#endif +} + +void AdvancedOptionsHandler::SetupMetricsReportingSettingVisibility() { +#if defined(GOOGLE_CHROME_BUILD) && defined(OS_CHROMEOS) + // Don't show the reporting setting if we are in the guest mode. + if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kGuestSession)) { + base::FundamentalValue visible(false); + web_ui_->CallJavascriptFunction( + "options.AdvancedOptions.SetMetricsReportingSettingVisibility", + visible); + } +#endif +} + +void AdvancedOptionsHandler::SetupFontSizeSelector() { + // We're only interested in integer values, so convert to int. + base::FundamentalValue font_size(default_font_size_.GetValue()); + web_ui_->CallJavascriptFunction( + "options.AdvancedOptions.SetFontSize", font_size); +} + +void AdvancedOptionsHandler::SetupPageZoomSelector() { + PrefService* pref_service = Profile::FromWebUI(web_ui_)->GetPrefs(); + double default_zoom_level = pref_service->GetDouble(prefs::kDefaultZoomLevel); + double default_zoom_factor = + WebKit::WebView::zoomLevelToZoomFactor(default_zoom_level); + + // Generate a vector of zoom factors from an array of known presets along with + // the default factor added if necessary. + std::vector<double> zoom_factors = + chrome_page_zoom::PresetZoomFactors(default_zoom_factor); + + // Iterate through the zoom factors and and build the contents of the + // selector that will be sent to the javascript handler. + // Each item in the list has the following parameters: + // 1. Title (string). + // 2. Value (double). + // 3. Is selected? (bool). + ListValue zoom_factors_value; + for (std::vector<double>::const_iterator i = zoom_factors.begin(); + i != zoom_factors.end(); ++i) { + ListValue* option = new ListValue(); + double factor = *i; + int percent = static_cast<int>(factor * 100 + 0.5); + option->Append(Value::CreateStringValue( + l10n_util::GetStringFUTF16Int(IDS_ZOOM_PERCENT, percent))); + option->Append(Value::CreateDoubleValue(factor)); + bool selected = content::ZoomValuesEqual(factor, default_zoom_factor); + option->Append(Value::CreateBooleanValue(selected)); + zoom_factors_value.Append(option); + } + + web_ui_->CallJavascriptFunction( + "options.AdvancedOptions.SetupPageZoomSelector", zoom_factors_value); +} + +void AdvancedOptionsHandler::SetupAutoOpenFileTypesDisabledAttribute() { + // Set the enabled state for the AutoOpenFileTypesResetToDefault button. + // We enable the button if the user has any auto-open file types registered. + DownloadManager* manager = + web_ui_->tab_contents()->browser_context()->GetDownloadManager(); + bool disabled = !(manager && + DownloadPrefs::FromDownloadManager(manager)->IsAutoOpenUsed()); + base::FundamentalValue value(disabled); + web_ui_->CallJavascriptFunction( + "options.AdvancedOptions.SetAutoOpenFileTypesDisabledAttribute", value); +} + +void AdvancedOptionsHandler::SetupProxySettingsSection() { +#if !defined(OS_CHROMEOS) + // Disable the button if proxy settings are managed by a sysadmin or + // overridden by an extension. + PrefService* pref_service = Profile::FromWebUI(web_ui_)->GetPrefs(); + const PrefService::Preference* proxy_config = + pref_service->FindPreference(prefs::kProxy); + bool is_extension_controlled = (proxy_config && + proxy_config->IsExtensionControlled()); + + base::FundamentalValue disabled(proxy_prefs_->IsManaged() || + is_extension_controlled); + + // Get the appropriate info string to describe the button. + string16 label_str; + if (is_extension_controlled) { + label_str = l10n_util::GetStringUTF16(IDS_OPTIONS_EXTENSION_PROXIES_LABEL); + } else { + label_str = l10n_util::GetStringFUTF16(IDS_OPTIONS_SYSTEM_PROXIES_LABEL, + l10n_util::GetStringUTF16(IDS_PRODUCT_NAME)); + } + StringValue label(label_str); + + web_ui_->CallJavascriptFunction( + "options.AdvancedOptions.SetupProxySettingsSection", disabled, label); +#endif // !defined(OS_CHROMEOS) +} + +void AdvancedOptionsHandler::SetupSSLConfigSettings() { + { + base::FundamentalValue checked(rev_checking_enabled_.GetValue()); + base::FundamentalValue disabled(rev_checking_enabled_.IsManaged()); + web_ui_->CallJavascriptFunction( + "options.AdvancedOptions.SetCheckRevocationCheckboxState", checked, + disabled); + } +} diff --git a/chrome/browser/ui/webui/options2/advanced_options_handler.h b/chrome/browser/ui/webui/options2/advanced_options_handler.h new file mode 100644 index 0000000..3e404fa --- /dev/null +++ b/chrome/browser/ui/webui/options2/advanced_options_handler.h @@ -0,0 +1,182 @@ +// Copyright (c) 2011 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. + +#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS2_ADVANCED_OPTIONS_HANDLER_H_ +#define CHROME_BROWSER_UI_WEBUI_OPTIONS2_ADVANCED_OPTIONS_HANDLER_H_ +#pragma once + +#include "chrome/browser/prefs/pref_member.h" +#include "chrome/browser/printing/cloud_print/cloud_print_setup_handler.h" +#include "chrome/browser/ui/select_file_dialog.h" +#include "chrome/browser/ui/webui/options2/options_ui2.h" + +#if !defined(OS_CHROMEOS) +#include "chrome/browser/prefs/pref_set_observer.h" +#endif // !defined(OS_CHROMEOS) + +class CloudPrintSetupHandler; + +// Chrome advanced options page UI handler. +class AdvancedOptionsHandler + : public OptionsPage2UIHandler, + public SelectFileDialog::Listener, + public CloudPrintSetupHandlerDelegate { + public: + AdvancedOptionsHandler(); + virtual ~AdvancedOptionsHandler(); + + // OptionsPage2UIHandler implementation. + virtual void GetLocalizedValues(DictionaryValue* localized_strings) OVERRIDE; + virtual void Initialize() OVERRIDE; + + // WebUIMessageHandler implementation. + virtual WebUIMessageHandler* Attach(WebUI* web_ui) OVERRIDE; + virtual void RegisterMessages() OVERRIDE; + + // content::NotificationObserver implementation. + virtual void Observe(int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) OVERRIDE; + + // SelectFileDialog::Listener implementation + virtual void FileSelected(const FilePath& path, + int index, + void* params) OVERRIDE; + + // CloudPrintSetupHandler::Delegate implementation. + virtual void OnCloudPrintSetupClosed() OVERRIDE; + + private: + // Callback for the "selectDownloadLocation" message. This will prompt the + // user for a destination folder using platform-specific APIs. + void HandleSelectDownloadLocation(const ListValue* args); + + // Callback for the "autoOpenFileTypesResetToDefault" message. This will + // remove all auto-open file-type settings. + void HandleAutoOpenButton(const ListValue* args); + + // Callback for the "metricsReportingCheckboxAction" message. This is called + // if the user toggles the metrics reporting checkbox. + void HandleMetricsReportingCheckbox(const ListValue* args); + + // Callback for the "defaultFontSizeAction" message. This is called if the + // user changes the default font size. |args| is an array that contains + // one item, the font size as a numeric value. + void HandleDefaultFontSize(const ListValue* args); + + // Callback for the "defaultZoomFactorAction" message. This is called if the + // user changes the default zoom factor. |args| is an array that contains + // one item, the zoom factor as a numeric value. + void HandleDefaultZoomFactor(const ListValue* args); + + // Callback for the "Check for server certificate revocation" checkbox. This + // is called if the user toggles the "Check for server certificate revocation" + // checkbox. + void HandleCheckRevocationCheckbox(const ListValue* args); + + // Callback for the "Use SSL 3.0" checkbox. This is called if the user toggles + // the "Use SSL 3.0" checkbox. + void HandleUseSSL3Checkbox(const ListValue* args); + + // Callback for the "Use TLS 1.0" checkbox. This is called if the user toggles + // the "Use TLS 1.0" checkbox. + void HandleUseTLS1Checkbox(const ListValue* args); + +#if !defined(OS_CHROMEOS) + // Callback for the "showNetworkProxySettings" message. This will invoke + // an appropriate dialog for configuring proxy settings. + void ShowNetworkProxySettings(const ListValue* args); +#endif + +#if !defined(USE_NSS) + // Callback for the "showManageSSLCertificates" message. This will invoke + // an appropriate certificate management action based on the platform. + void ShowManageSSLCertificates(const ListValue* args); +#endif + + // Callback for the Cloud Print manage button. This will open a new + // tab pointed at the management URL. + void ShowCloudPrintManagePage(const ListValue* args); + +#if !defined(OS_CHROMEOS) + // Callback for the Sign in to Cloud Print button. This will start + // the authentication process. + void ShowCloudPrintSetupDialog(const ListValue* args); + + // Callback for the Disable Cloud Print button. This will sign out + // of cloud print. + void HandleDisableCloudPrintProxy(const ListValue* args); + + // Pings the service to send us it's current notion of the enabled state. + void RefreshCloudPrintStatusFromService(); + + // Setup the enabled or disabled state of the cloud print proxy + // management UI. + void SetupCloudPrintProxySection(); + + // Remove cloud print proxy section if cloud print proxy management UI is + // disabled. + void RemoveCloudPrintProxySection(); + +#endif + +#if !defined(OS_MACOSX) && !defined(OS_CHROMEOS) + // Sets up the checked state for the "Continue running background apps..." + // checkbox. + void SetupBackgroundModeSettings(); + + // Callback for the "Continue running background apps..." checkbox. + void HandleBackgroundModeCheckbox(const ListValue* args); +#endif + + // Setup the checked state for the metrics reporting checkbox. + void SetupMetricsReportingCheckbox(); + + // Setup the visibility for the metrics reporting setting. + void SetupMetricsReportingSettingVisibility(); + + // Setup the font size selector control. + void SetupFontSizeSelector(); + + // Setup the page zoom selector control. + void SetupPageZoomSelector(); + + // Setup the enabled state of the reset button. + void SetupAutoOpenFileTypesDisabledAttribute(); + + // Setup the proxy settings section UI. + void SetupProxySettingsSection(); + + // Setup the checked state for SSL related checkboxes. + void SetupSSLConfigSettings(); + + scoped_refptr<SelectFileDialog> select_folder_dialog_; + +#if !defined(OS_CHROMEOS) + BooleanPrefMember enable_metrics_recording_; + StringPrefMember cloud_print_proxy_email_; + BooleanPrefMember cloud_print_proxy_enabled_; + bool cloud_print_proxy_ui_enabled_; + scoped_ptr<CloudPrintSetupHandler> cloud_print_setup_handler_; +#endif + + // SSLConfigService prefs. + BooleanPrefMember rev_checking_enabled_; + +#if !defined(OS_MACOSX) && !defined(OS_CHROMEOS) + BooleanPrefMember background_mode_enabled_; +#endif + + StringPrefMember auto_open_files_; + IntegerPrefMember default_font_size_; + DoublePrefMember default_zoom_level_; + +#if !defined(OS_CHROMEOS) + scoped_ptr<PrefSetObserver> proxy_prefs_; +#endif // !defined(OS_CHROMEOS) + + DISALLOW_COPY_AND_ASSIGN(AdvancedOptionsHandler); +}; + +#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS2_ADVANCED_OPTIONS_HANDLER_H_ diff --git a/chrome/browser/ui/webui/options2/advanced_options_utils.h b/chrome/browser/ui/webui/options2/advanced_options_utils.h new file mode 100644 index 0000000..edaacf5 --- /dev/null +++ b/chrome/browser/ui/webui/options2/advanced_options_utils.h @@ -0,0 +1,25 @@ +// Copyright (c) 2011 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. + +#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS2_ADVANCED_OPTIONS_UTILS_H_ +#define CHROME_BROWSER_UI_WEBUI_OPTIONS2_ADVANCED_OPTIONS_UTILS_H_ + +#include "base/basictypes.h" + +class TabContents; + +// Chrome advanced options utility methods. +class AdvancedOptionsUtilities { + public: + // Invoke UI for network proxy settings. + static void ShowNetworkProxySettings(TabContents*); + + // Invoke UI for SSL certificates. + static void ShowManageSSLCertificates(TabContents*); + + private: + DISALLOW_IMPLICIT_CONSTRUCTORS(AdvancedOptionsUtilities); +}; + +#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS2_ADVANCED_OPTIONS_UTILS_H_ diff --git a/chrome/browser/ui/webui/options2/advanced_options_utils_mac.mm b/chrome/browser/ui/webui/options2/advanced_options_utils_mac.mm new file mode 100644 index 0000000..c1e5b52 --- /dev/null +++ b/chrome/browser/ui/webui/options2/advanced_options_utils_mac.mm @@ -0,0 +1,41 @@ +// Copyright (c) 2011 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. + +#import <Cocoa/Cocoa.h> + +#include "chrome/browser/ui/webui/options2/advanced_options_utils.h" + +#include "base/logging.h" +#include "base/mac/scoped_aedesc.h" + +void AdvancedOptionsUtilities::ShowNetworkProxySettings( + TabContents* tab_contents) { + NSArray* itemsToOpen = [NSArray arrayWithObject:[NSURL fileURLWithPath: + @"/System/Library/PreferencePanes/Network.prefPane"]]; + + const char* proxyPrefCommand = "Proxies"; + base::mac::ScopedAEDesc<> openParams; + OSStatus status = AECreateDesc('ptru', + proxyPrefCommand, + strlen(proxyPrefCommand), + openParams.OutPointer()); + LOG_IF(ERROR, status != noErr) << "Failed to create open params: " << status; + + LSLaunchURLSpec launchSpec = { 0 }; + launchSpec.itemURLs = (CFArrayRef)itemsToOpen; + launchSpec.passThruParams = openParams; + launchSpec.launchFlags = kLSLaunchAsync | kLSLaunchDontAddToRecents; + LSOpenFromURLSpec(&launchSpec, NULL); +} + +void AdvancedOptionsUtilities::ShowManageSSLCertificates( + TabContents* tab_contents) { + NSString* const kKeychainBundleId = @"com.apple.keychainaccess"; + [[NSWorkspace sharedWorkspace] + launchAppWithBundleIdentifier:kKeychainBundleId + options:0L + additionalEventParamDescriptor:nil + launchIdentifier:nil]; +} + diff --git a/chrome/browser/ui/webui/options2/advanced_options_utils_win.cc b/chrome/browser/ui/webui/options2/advanced_options_utils_win.cc new file mode 100644 index 0000000..5942ff9 --- /dev/null +++ b/chrome/browser/ui/webui/options2/advanced_options_utils_win.cc @@ -0,0 +1,68 @@ +// Copyright (c) 2011 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. + +#include "chrome/browser/ui/webui/options2/advanced_options_utils.h" + +#include <windows.h> +#include <cryptuiapi.h> +#pragma comment(lib, "cryptui.lib") +#include <shellapi.h> + +#include "base/bind.h" +#include "base/file_util.h" +#include "base/path_service.h" +#include "base/threading/thread.h" +#include "chrome/browser/browser_process.h" +#include "content/browser/tab_contents/tab_contents.h" +#include "content/browser/tab_contents/tab_contents_view.h" +#include "content/public/browser/browser_thread.h" + +using content::BrowserThread; + +// Callback that opens the Internet Options control panel dialog with the +// Connections tab selected. +void OpenConnectionDialogCallback() { + // Using rundll32 seems better than LaunchConnectionDialog which causes a + // new dialog to be made for each call. rundll32 uses the same global + // dialog and it seems to share with the shortcut in control panel. + FilePath rundll32; + PathService::Get(base::DIR_SYSTEM, &rundll32); + rundll32 = rundll32.AppendASCII("rundll32.exe"); + + FilePath shell32dll; + PathService::Get(base::DIR_SYSTEM, &shell32dll); + shell32dll = shell32dll.AppendASCII("shell32.dll"); + + FilePath inetcpl; + PathService::Get(base::DIR_SYSTEM, &inetcpl); + inetcpl = inetcpl.AppendASCII("inetcpl.cpl,,4"); + + std::wstring args(shell32dll.value()); + args.append(L",Control_RunDLL "); + args.append(inetcpl.value()); + + ShellExecute(NULL, L"open", rundll32.value().c_str(), args.c_str(), NULL, + SW_SHOWNORMAL); +} + +void AdvancedOptionsUtilities::ShowNetworkProxySettings( + TabContents* tab_contents) { + DCHECK(BrowserThread::IsMessageLoopValid(BrowserThread::FILE)); + BrowserThread::PostTask(BrowserThread::FILE, + FROM_HERE, + base::Bind(&OpenConnectionDialogCallback)); +} + +void AdvancedOptionsUtilities::ShowManageSSLCertificates( + TabContents* tab_contents) { + CRYPTUI_CERT_MGR_STRUCT cert_mgr = { 0 }; + cert_mgr.dwSize = sizeof(CRYPTUI_CERT_MGR_STRUCT); + cert_mgr.hwndParent = +#if defined(USE_AURA) + NULL; +#else + tab_contents->view()->GetTopLevelNativeWindow(); +#endif + ::CryptUIDlgCertMgr(&cert_mgr); +} diff --git a/chrome/browser/ui/webui/options2/advanced_options_utils_x11.cc b/chrome/browser/ui/webui/options2/advanced_options_utils_x11.cc new file mode 100644 index 0000000..20579b3 --- /dev/null +++ b/chrome/browser/ui/webui/options2/advanced_options_utils_x11.cc @@ -0,0 +1,140 @@ +// Copyright (c) 2011 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. + +#if !defined(OS_CHROMEOS) + +#include "chrome/browser/ui/webui/options2/advanced_options_utils.h" + +#include "base/bind.h" +#include "base/environment.h" +#include "base/file_path.h" +#include "base/file_util.h" +#include "base/nix/xdg_util.h" +#include "base/process_util.h" +#include "base/string_util.h" +#include "content/browser/tab_contents/tab_contents.h" +#include "content/public/browser/browser_thread.h" + +using content::BrowserThread; + +// Command used to configure GNOME 2 proxy settings. +const char* kGNOME2ProxyConfigCommand[] = {"gnome-network-properties", NULL}; +// In GNOME 3, we might need to run gnome-control-center instead. We try this +// only after gnome-network-properties is not found, because older GNOME also +// has this but it doesn't do the same thing. See below where we use it. +const char* kGNOME3ProxyConfigCommand[] = {"gnome-control-center", "network", + NULL}; +// KDE3 and KDE4 are only slightly different, but incompatible. Go figure. +const char* kKDE3ProxyConfigCommand[] = {"kcmshell", "proxy", NULL}; +const char* kKDE4ProxyConfigCommand[] = {"kcmshell4", "proxy", NULL}; + +// The URL for Linux proxy configuration help when not running under a +// supported desktop environment. +const char kLinuxProxyConfigUrl[] = "about:linux-proxy-config"; + +namespace { + +// Show the proxy config URL in the given tab. +void ShowLinuxProxyConfigUrl(TabContents* tab_contents) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + scoped_ptr<base::Environment> env(base::Environment::Create()); + const char* name = base::nix::GetDesktopEnvironmentName(env.get()); + if (name) + LOG(ERROR) << "Could not find " << name << " network settings in $PATH"; + tab_contents->OpenURL(GURL(kLinuxProxyConfigUrl), GURL(), + NEW_FOREGROUND_TAB, content::PAGE_TRANSITION_LINK); +} + +// Start the given proxy configuration utility. +bool StartProxyConfigUtil(TabContents* tab_contents, const char* command[]) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); + // base::LaunchProcess() returns true ("success") if the fork() + // succeeds, but not necessarily the exec(). We'd like to be able to + // use StartProxyConfigUtil() to search possible options and stop on + // success, so we search $PATH first to predict whether the exec is + // expected to succeed. + // TODO(mdm): this is a useful check, and is very similar to some + // code in proxy_config_service_linux.cc. It should probably be in + // base:: somewhere. + scoped_ptr<base::Environment> env(base::Environment::Create()); + std::string path; + if (!env->GetVar("PATH", &path)) { + LOG(ERROR) << "No $PATH variable. Assuming no " << command[0] << "."; + return false; + } + std::vector<std::string> paths; + Tokenize(path, ":", &paths); + bool found = false; + for (size_t i = 0; i < paths.size(); ++i) { + FilePath file(paths[i]); + if (file_util::PathExists(file.Append(command[0]))) { + found = true; + break; + } + } + if (!found) + return false; + std::vector<std::string> argv; + for (size_t i = 0; command[i]; ++i) + argv.push_back(command[i]); + base::ProcessHandle handle; + if (!base::LaunchProcess(argv, base::LaunchOptions(), &handle)) { + LOG(ERROR) << "StartProxyConfigUtil failed to start " << command[0]; + return false; + } + base::EnsureProcessGetsReaped(handle); + return true; +} + +// Detect, and if possible, start the appropriate proxy config utility. On +// failure to do so, show the Linux proxy config URL in a new tab instead. +void DetectAndStartProxyConfigUtil(TabContents* tab_contents) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); + scoped_ptr<base::Environment> env(base::Environment::Create()); + + bool launched = false; + switch (base::nix::GetDesktopEnvironment(env.get())) { + case base::nix::DESKTOP_ENVIRONMENT_GNOME: { + launched = StartProxyConfigUtil(tab_contents, kGNOME2ProxyConfigCommand); + if (!launched) { + // We try this second, even though it's the newer way, because this + // command existed in older versions of GNOME, but it didn't do the + // same thing. The older command is gone though, so this should do + // the right thing. (Also some distributions have blurred the lines + // between GNOME 2 and 3, so we can't necessarily detect what the + // right thing is based on indications of which version we have.) + launched = StartProxyConfigUtil(tab_contents, + kGNOME3ProxyConfigCommand); + } + break; + } + + case base::nix::DESKTOP_ENVIRONMENT_KDE3: + launched = StartProxyConfigUtil(tab_contents, kKDE3ProxyConfigCommand); + break; + + case base::nix::DESKTOP_ENVIRONMENT_KDE4: + launched = StartProxyConfigUtil(tab_contents, kKDE4ProxyConfigCommand); + break; + + case base::nix::DESKTOP_ENVIRONMENT_XFCE: + case base::nix::DESKTOP_ENVIRONMENT_OTHER: + break; + } + + if (launched) + return; + BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, + base::Bind(&ShowLinuxProxyConfigUrl, tab_contents)); +} + +} // anonymous namespace + +void AdvancedOptionsUtilities::ShowNetworkProxySettings( + TabContents* tab_contents) { + BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, + base::Bind(&DetectAndStartProxyConfigUtil, tab_contents)); +} + +#endif // !defined(OS_CHROMEOS) diff --git a/chrome/browser/ui/webui/options2/autofill_options_browsertest.js b/chrome/browser/ui/webui/options2/autofill_options_browsertest.js new file mode 100644 index 0000000..d9a5c9b --- /dev/null +++ b/chrome/browser/ui/webui/options2/autofill_options_browsertest.js @@ -0,0 +1,24 @@ +// Copyright (c) 2011 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. + +/** + * TestFixture for autofill options WebUI testing. + * @extends {testing.Test} + * @constructor + **/ +function AutofillOptionsWebUITest() {} + +AutofillOptionsWebUITest.prototype = { + __proto__: testing.Test.prototype, + + /** + * Browse to autofill options. + **/ + browsePreload: 'chrome://settings/autofill', +}; + +// Test opening the autofill options has correct location. +TEST_F('AutofillOptionsWebUITest', 'testOpenAutofillOptions', function() { + assertEquals(this.browsePreload, document.location.href); +}); diff --git a/chrome/browser/ui/webui/options2/autofill_options_handler.cc b/chrome/browser/ui/webui/options2/autofill_options_handler.cc new file mode 100644 index 0000000..f751e635 --- /dev/null +++ b/chrome/browser/ui/webui/options2/autofill_options_handler.cc @@ -0,0 +1,614 @@ +// Copyright (c) 2011 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. + +#include "chrome/browser/ui/webui/options2/autofill_options_handler.h" + +#include <vector> + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/logging.h" +#include "base/string16.h" +#include "base/string_number_conversions.h" +#include "base/utf_string_conversions.h" +#include "base/values.h" +#include "chrome/browser/autofill/autofill_country.h" +#include "chrome/browser/autofill/autofill_profile.h" +#include "chrome/browser/autofill/credit_card.h" +#include "chrome/browser/autofill/personal_data_manager.h" +#include "chrome/browser/autofill/personal_data_manager_factory.h" +#include "chrome/browser/autofill/phone_number_i18n.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/ui/webui/web_ui_util.h" +#include "chrome/common/guid.h" +#include "grit/generated_resources.h" +#include "grit/webkit_resources.h" +#include "ui/base/l10n/l10n_util.h" + +namespace { + +// Converts a credit card type to the appropriate resource ID of the CC icon. +int CreditCardTypeToResourceID(const std::string& type) { + if (type == kAmericanExpressCard) + return IDR_AUTOFILL_CC_AMEX; + else if (type == kDinersCard) + return IDR_AUTOFILL_CC_DINERS; + else if (type == kDiscoverCard) + return IDR_AUTOFILL_CC_DISCOVER; + else if (type == kGenericCard) + return IDR_AUTOFILL_CC_GENERIC; + else if (type == kJCBCard) + return IDR_AUTOFILL_CC_JCB; + else if (type == kMasterCard) + return IDR_AUTOFILL_CC_MASTERCARD; + else if (type == kSoloCard) + return IDR_AUTOFILL_CC_SOLO; + else if (type == kVisaCard) + return IDR_AUTOFILL_CC_VISA; + + NOTREACHED(); + return 0; +} + +// Converts a credit card type to the appropriate localized card type. +string16 LocalizedCreditCardType(const std::string& type) { + if (type == kAmericanExpressCard) + return l10n_util::GetStringUTF16(IDS_AUTOFILL_CC_AMEX); + else if (type == kDinersCard) + return l10n_util::GetStringUTF16(IDS_AUTOFILL_CC_DINERS); + else if (type == kDiscoverCard) + return l10n_util::GetStringUTF16(IDS_AUTOFILL_CC_DISCOVER); + else if (type == kGenericCard) + return l10n_util::GetStringUTF16(IDS_AUTOFILL_CC_GENERIC); + else if (type == kJCBCard) + return l10n_util::GetStringUTF16(IDS_AUTOFILL_CC_JCB); + else if (type == kMasterCard) + return l10n_util::GetStringUTF16(IDS_AUTOFILL_CC_MASTERCARD); + else if (type == kSoloCard) + return l10n_util::GetStringUTF16(IDS_AUTOFILL_CC_SOLO); + else if (type == kVisaCard) + return l10n_util::GetStringUTF16(IDS_AUTOFILL_CC_VISA); + + NOTREACHED(); + return string16(); +} + +// Returns a dictionary that maps country codes to data for the country. +DictionaryValue* GetCountryData() { + std::string app_locale = AutofillCountry::ApplicationLocale(); + std::vector<std::string> country_codes; + AutofillCountry::GetAvailableCountries(&country_codes); + + DictionaryValue* country_data = new DictionaryValue(); + for (size_t i = 0; i < country_codes.size(); ++i) { + const AutofillCountry country(country_codes[i], app_locale); + + DictionaryValue* details = new DictionaryValue(); + details->SetString("name", country.name()); + details->SetString("postalCodeLabel", country.postal_code_label()); + details->SetString("stateLabel", country.state_label()); + + country_data->Set(country.country_code(), details); + } + + return country_data; +} + +// Get the multi-valued element for |type| and return it in |ListValue| form. +void GetValueList(const AutofillProfile& profile, + AutofillFieldType type, + scoped_ptr<ListValue>* list) { + list->reset(new ListValue); + + std::vector<string16> values; + profile.GetMultiInfo(type, &values); + + // |GetMultiInfo()| always returns at least one, potentially empty, item. + if (values.size() == 1 && values.front().empty()) + return; + + for (size_t i = 0; i < values.size(); ++i) { + (*list)->Set(i, Value::CreateStringValue(values[i])); + } +} + +// Set the multi-valued element for |type| from input |list| values. +void SetValueList(const ListValue* list, + AutofillFieldType type, + AutofillProfile* profile) { + std::vector<string16> values(list->GetSize()); + for (size_t i = 0; i < list->GetSize(); ++i) { + string16 value; + if (list->GetString(i, &value)) + values[i] = value; + } + profile->SetMultiInfo(type, values); +} + +// Get the multi-valued element for |type| and return it in |ListValue| form. +void GetNameList(const AutofillProfile& profile, + scoped_ptr<ListValue>* names) { + names->reset(new ListValue); + + std::vector<string16> first_names; + std::vector<string16> middle_names; + std::vector<string16> last_names; + profile.GetMultiInfo(NAME_FIRST, &first_names); + profile.GetMultiInfo(NAME_MIDDLE, &middle_names); + profile.GetMultiInfo(NAME_LAST, &last_names); + DCHECK_EQ(first_names.size(), middle_names.size()); + DCHECK_EQ(first_names.size(), last_names.size()); + + // |GetMultiInfo()| always returns at least one, potentially empty, item. + if (first_names.size() == 1 && first_names.front().empty() && + middle_names.front().empty() && last_names.front().empty()) { + return; + } + + for (size_t i = 0; i < first_names.size(); ++i) { + ListValue* name = new ListValue; // owned by |list| + name->Set(0, Value::CreateStringValue(first_names[i])); + name->Set(1, Value::CreateStringValue(middle_names[i])); + name->Set(2, Value::CreateStringValue(last_names[i])); + (*names)->Set(i, name); + } +} + +// Set the multi-valued element for |type| from input |list| values. +void SetNameList(const ListValue* names, + AutofillProfile* profile) { + const size_t size = names->GetSize(); + std::vector<string16> first_names(size); + std::vector<string16> middle_names(size); + std::vector<string16> last_names(size); + + for (size_t i = 0; i < size; ++i) { + ListValue* name; + bool success = names->GetList(i, &name); + DCHECK(success); + + string16 first_name; + success = name->GetString(0, &first_name); + DCHECK(success); + first_names[i] = first_name; + + string16 middle_name; + success = name->GetString(1, &middle_name); + DCHECK(success); + middle_names[i] = middle_name; + + string16 last_name; + success = name->GetString(2, &last_name); + DCHECK(success); + last_names[i] = last_name; + } + + profile->SetMultiInfo(NAME_FIRST, first_names); + profile->SetMultiInfo(NAME_MIDDLE, middle_names); + profile->SetMultiInfo(NAME_LAST, last_names); +} + +// Pulls the phone number |index|, |phone_number_list|, and |country_code| from +// the |args| input. +void ExtractPhoneNumberInformation(const ListValue* args, + size_t* index, + ListValue** phone_number_list, + std::string* country_code) { + // Retrieve index as a |double|, as that is how it comes across from + // JavaScript. + double number = 0.0; + if (!args->GetDouble(0, &number)) { + NOTREACHED(); + return; + } + *index = number; + + if (!args->GetList(1, phone_number_list)) { + NOTREACHED(); + return; + } + + if (!args->GetString(2, country_code)) { + NOTREACHED(); + return; + } +} + +// Searches the |list| for the value at |index|. If this value is present +// in any of the rest of the list, then the item (at |index|) is removed. +// The comparison of phone number values is done on normalized versions of the +// phone number values. +void RemoveDuplicatePhoneNumberAtIndex(size_t index, + const std::string& country_code, + ListValue* list) { + string16 new_value; + if (!list->GetString(index, &new_value)) { + NOTREACHED() << "List should have a value at index " << index; + return; + } + + bool is_duplicate = false; + for (size_t i = 0; i < list->GetSize() && !is_duplicate; ++i) { + if (i == index) + continue; + + string16 existing_value; + if (!list->GetString(i, &existing_value)) { + NOTREACHED() << "List should have a value at index " << i; + continue; + } + is_duplicate = autofill_i18n::PhoneNumbersMatch(new_value, + existing_value, + country_code); + } + + if (is_duplicate) + list->Remove(index, NULL); +} + +void ValidatePhoneArguments(const ListValue* args, ListValue** list) { + size_t index = 0; + std::string country_code; + ExtractPhoneNumberInformation(args, &index, list, &country_code); + + RemoveDuplicatePhoneNumberAtIndex(index, country_code, *list); +} + +} // namespace + +AutofillOptionsHandler::AutofillOptionsHandler() + : personal_data_(NULL) { +} + +AutofillOptionsHandler::~AutofillOptionsHandler() { + if (personal_data_) + personal_data_->RemoveObserver(this); +} + +///////////////////////////////////////////////////////////////////////////// +// OptionsPageUIHandler implementation: +void AutofillOptionsHandler::GetLocalizedValues( + DictionaryValue* localized_strings) { + DCHECK(localized_strings); + + static OptionsStringResource resources[] = { + { "autofillAddresses", IDS_AUTOFILL_ADDRESSES_GROUP_NAME }, + { "autofillCreditCards", IDS_AUTOFILL_CREDITCARDS_GROUP_NAME }, + { "autofillAddAddress", IDS_AUTOFILL_ADD_ADDRESS_BUTTON }, + { "autofillAddCreditCard", IDS_AUTOFILL_ADD_CREDITCARD_BUTTON }, + { "autofillEditProfileButton", IDS_AUTOFILL_EDIT_PROFILE_BUTTON }, + { "helpButton", IDS_AUTOFILL_HELP_LABEL }, + { "addAddressTitle", IDS_AUTOFILL_ADD_ADDRESS_CAPTION }, + { "editAddressTitle", IDS_AUTOFILL_EDIT_ADDRESS_CAPTION }, + { "addCreditCardTitle", IDS_AUTOFILL_ADD_CREDITCARD_CAPTION }, + { "editCreditCardTitle", IDS_AUTOFILL_EDIT_CREDITCARD_CAPTION }, +#if defined(OS_MACOSX) + { "auxiliaryProfilesEnabled", IDS_AUTOFILL_USE_MAC_ADDRESS_BOOK }, +#endif // defined(OS_MACOSX) + }; + + RegisterStrings(localized_strings, resources, arraysize(resources)); + RegisterTitle(localized_strings, "autofillOptionsPage", + IDS_AUTOFILL_OPTIONS_TITLE); + + SetAddressOverlayStrings(localized_strings); + SetCreditCardOverlayStrings(localized_strings); +} + +void AutofillOptionsHandler::Initialize() { + personal_data_ = PersonalDataManagerFactory::GetForProfile( + Profile::FromWebUI(web_ui_)); + // personal_data_ is NULL in guest mode on Chrome OS. + if (personal_data_) { + personal_data_->SetObserver(this); + LoadAutofillData(); + } +} + +void AutofillOptionsHandler::RegisterMessages() { + web_ui_->RegisterMessageCallback( + "removeAddress", + base::Bind(&AutofillOptionsHandler::RemoveAddress, + base::Unretained(this))); + web_ui_->RegisterMessageCallback( + "removeCreditCard", + base::Bind(&AutofillOptionsHandler::RemoveCreditCard, + base::Unretained(this))); + web_ui_->RegisterMessageCallback( + "loadAddressEditor", + base::Bind(&AutofillOptionsHandler::LoadAddressEditor, + base::Unretained(this))); + web_ui_->RegisterMessageCallback( + "loadCreditCardEditor", + base::Bind(&AutofillOptionsHandler::LoadCreditCardEditor, + base::Unretained(this))); + web_ui_->RegisterMessageCallback( + "setAddress", + base::Bind(&AutofillOptionsHandler::SetAddress, base::Unretained(this))); + web_ui_->RegisterMessageCallback( + "setCreditCard", + base::Bind(&AutofillOptionsHandler::SetCreditCard, + base::Unretained(this))); + web_ui_->RegisterMessageCallback( + "validatePhoneNumbers", + base::Bind(&AutofillOptionsHandler::ValidatePhoneNumbers, + base::Unretained(this))); +} + +///////////////////////////////////////////////////////////////////////////// +// PersonalDataManagerObserver implementation: +void AutofillOptionsHandler::OnPersonalDataChanged() { + LoadAutofillData(); +} + +void AutofillOptionsHandler::SetAddressOverlayStrings( + DictionaryValue* localized_strings) { + localized_strings->SetString("autofillEditAddressTitle", + l10n_util::GetStringUTF16(IDS_AUTOFILL_EDIT_ADDRESS_CAPTION)); + localized_strings->SetString("autofillFirstNameLabel", + l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_FIRST_NAME)); + localized_strings->SetString("autofillMiddleNameLabel", + l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_MIDDLE_NAME)); + localized_strings->SetString("autofillLastNameLabel", + l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_LAST_NAME)); + localized_strings->SetString("autofillCompanyNameLabel", + l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_COMPANY_NAME)); + localized_strings->SetString("autofillAddrLine1Label", + l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_ADDRESS_LINE_1)); + localized_strings->SetString("autofillAddrLine2Label", + l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_ADDRESS_LINE_2)); + localized_strings->SetString("autofillCityLabel", + l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_CITY)); + localized_strings->SetString("autofillCountryLabel", + l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_COUNTRY)); + localized_strings->SetString("autofillPhoneLabel", + l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_PHONE)); + localized_strings->SetString("autofillEmailLabel", + l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_EMAIL)); + localized_strings->SetString("autofillAddFirstNamePlaceholder", + l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_ADD_FIRST_NAME)); + localized_strings->SetString("autofillAddMiddleNamePlaceholder", + l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_ADD_MIDDLE_NAME)); + localized_strings->SetString("autofillAddLastNamePlaceholder", + l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_ADD_LAST_NAME)); + localized_strings->SetString("autofillAddPhonePlaceholder", + l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_ADD_PHONE)); + localized_strings->SetString("autofillAddEmailPlaceholder", + l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_ADD_EMAIL)); + + std::string app_locale = AutofillCountry::ApplicationLocale(); + std::string default_country_code = + AutofillCountry::CountryCodeForLocale(app_locale); + localized_strings->SetString("defaultCountryCode", default_country_code); + localized_strings->Set("autofillCountryData", GetCountryData()); +} + +void AutofillOptionsHandler::SetCreditCardOverlayStrings( + DictionaryValue* localized_strings) { + localized_strings->SetString("autofillEditCreditCardTitle", + l10n_util::GetStringUTF16(IDS_AUTOFILL_EDIT_CREDITCARD_CAPTION)); + localized_strings->SetString("nameOnCardLabel", + l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_NAME_ON_CARD)); + localized_strings->SetString("creditCardNumberLabel", + l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_CREDIT_CARD_NUMBER)); + localized_strings->SetString("creditCardExpirationDateLabel", + l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_EXPIRATION_DATE)); +} + +void AutofillOptionsHandler::LoadAutofillData() { + if (!personal_data_->IsDataLoaded()) + return; + + ListValue addresses; + for (std::vector<AutofillProfile*>::const_iterator i = + personal_data_->web_profiles().begin(); + i != personal_data_->web_profiles().end(); ++i) { + ListValue* entry = new ListValue(); + entry->Append(new StringValue((*i)->guid())); + entry->Append(new StringValue((*i)->Label())); + addresses.Append(entry); + } + + web_ui_->CallJavascriptFunction("AutofillOptions.setAddressList", addresses); + + ListValue credit_cards; + for (std::vector<CreditCard*>::const_iterator i = + personal_data_->credit_cards().begin(); + i != personal_data_->credit_cards().end(); ++i) { + ListValue* entry = new ListValue(); + entry->Append(new StringValue((*i)->guid())); + entry->Append(new StringValue((*i)->Label())); + int res = CreditCardTypeToResourceID((*i)->type()); + entry->Append( + new StringValue(web_ui_util::GetImageDataUrlFromResource(res))); + entry->Append(new StringValue(LocalizedCreditCardType((*i)->type()))); + credit_cards.Append(entry); + } + + web_ui_->CallJavascriptFunction("AutofillOptions.setCreditCardList", + credit_cards); +} + +void AutofillOptionsHandler::RemoveAddress(const ListValue* args) { + DCHECK(personal_data_->IsDataLoaded()); + + std::string guid; + if (!args->GetString(0, &guid)) { + NOTREACHED(); + return; + } + + personal_data_->RemoveProfile(guid); +} + +void AutofillOptionsHandler::RemoveCreditCard(const ListValue* args) { + DCHECK(personal_data_->IsDataLoaded()); + + std::string guid; + if (!args->GetString(0, &guid)) { + NOTREACHED(); + return; + } + + personal_data_->RemoveCreditCard(guid); +} + +void AutofillOptionsHandler::LoadAddressEditor(const ListValue* args) { + DCHECK(personal_data_->IsDataLoaded()); + + std::string guid; + if (!args->GetString(0, &guid)) { + NOTREACHED(); + return; + } + + AutofillProfile* profile = personal_data_->GetProfileByGUID(guid); + if (!profile) { + // There is a race where a user can click once on the close button and + // quickly click again on the list item before the item is removed (since + // the list is not updated until the model tells the list an item has been + // removed). This will activate the editor for a profile that has been + // removed. Do nothing in that case. + return; + } + + DictionaryValue address; + address.SetString("guid", profile->guid()); + scoped_ptr<ListValue> list; + GetNameList(*profile, &list); + address.Set("fullName", list.release()); + address.SetString("companyName", profile->GetInfo(COMPANY_NAME)); + address.SetString("addrLine1", profile->GetInfo(ADDRESS_HOME_LINE1)); + address.SetString("addrLine2", profile->GetInfo(ADDRESS_HOME_LINE2)); + address.SetString("city", profile->GetInfo(ADDRESS_HOME_CITY)); + address.SetString("state", profile->GetInfo(ADDRESS_HOME_STATE)); + address.SetString("postalCode", profile->GetInfo(ADDRESS_HOME_ZIP)); + address.SetString("country", profile->CountryCode()); + GetValueList(*profile, PHONE_HOME_WHOLE_NUMBER, &list); + address.Set("phone", list.release()); + GetValueList(*profile, EMAIL_ADDRESS, &list); + address.Set("email", list.release()); + + web_ui_->CallJavascriptFunction("AutofillOptions.editAddress", address); +} + +void AutofillOptionsHandler::LoadCreditCardEditor(const ListValue* args) { + DCHECK(personal_data_->IsDataLoaded()); + + std::string guid; + if (!args->GetString(0, &guid)) { + NOTREACHED(); + return; + } + + CreditCard* credit_card = personal_data_->GetCreditCardByGUID(guid); + if (!credit_card) { + // There is a race where a user can click once on the close button and + // quickly click again on the list item before the item is removed (since + // the list is not updated until the model tells the list an item has been + // removed). This will activate the editor for a profile that has been + // removed. Do nothing in that case. + return; + } + + DictionaryValue credit_card_data; + credit_card_data.SetString("guid", credit_card->guid()); + credit_card_data.SetString("nameOnCard", + credit_card->GetInfo(CREDIT_CARD_NAME)); + credit_card_data.SetString("creditCardNumber", + credit_card->GetInfo(CREDIT_CARD_NUMBER)); + credit_card_data.SetString("expirationMonth", + credit_card->GetInfo(CREDIT_CARD_EXP_MONTH)); + credit_card_data.SetString( + "expirationYear", + credit_card->GetInfo(CREDIT_CARD_EXP_4_DIGIT_YEAR)); + + web_ui_->CallJavascriptFunction("AutofillOptions.editCreditCard", + credit_card_data); +} + +void AutofillOptionsHandler::SetAddress(const ListValue* args) { + if (!personal_data_->IsDataLoaded()) + return; + + std::string guid; + if (!args->GetString(0, &guid)) { + NOTREACHED(); + return; + } + + AutofillProfile profile(guid); + + std::string country_code; + string16 value; + ListValue* list_value; + if (args->GetList(1, &list_value)) + SetNameList(list_value, &profile); + if (args->GetString(2, &value)) + profile.SetInfo(COMPANY_NAME, value); + if (args->GetString(3, &value)) + profile.SetInfo(ADDRESS_HOME_LINE1, value); + if (args->GetString(4, &value)) + profile.SetInfo(ADDRESS_HOME_LINE2, value); + if (args->GetString(5, &value)) + profile.SetInfo(ADDRESS_HOME_CITY, value); + if (args->GetString(6, &value)) + profile.SetInfo(ADDRESS_HOME_STATE, value); + if (args->GetString(7, &value)) + profile.SetInfo(ADDRESS_HOME_ZIP, value); + if (args->GetString(8, &country_code)) + profile.SetCountryCode(country_code); + if (args->GetList(9, &list_value)) + SetValueList(list_value, PHONE_HOME_WHOLE_NUMBER, &profile); + if (args->GetList(10, &list_value)) + SetValueList(list_value, EMAIL_ADDRESS, &profile); + + if (!guid::IsValidGUID(profile.guid())) { + profile.set_guid(guid::GenerateGUID()); + personal_data_->AddProfile(profile); + } else { + personal_data_->UpdateProfile(profile); + } +} + +void AutofillOptionsHandler::SetCreditCard(const ListValue* args) { + if (!personal_data_->IsDataLoaded()) + return; + + std::string guid; + if (!args->GetString(0, &guid)) { + NOTREACHED(); + return; + } + + CreditCard credit_card(guid); + + string16 value; + if (args->GetString(1, &value)) + credit_card.SetInfo(CREDIT_CARD_NAME, value); + if (args->GetString(2, &value)) + credit_card.SetInfo(CREDIT_CARD_NUMBER, value); + if (args->GetString(3, &value)) + credit_card.SetInfo(CREDIT_CARD_EXP_MONTH, value); + if (args->GetString(4, &value)) + credit_card.SetInfo(CREDIT_CARD_EXP_4_DIGIT_YEAR, value); + + if (!guid::IsValidGUID(credit_card.guid())) { + credit_card.set_guid(guid::GenerateGUID()); + personal_data_->AddCreditCard(credit_card); + } else { + personal_data_->UpdateCreditCard(credit_card); + } +} + +void AutofillOptionsHandler::ValidatePhoneNumbers(const ListValue* args) { + if (!personal_data_->IsDataLoaded()) + return; + + ListValue* list_value = NULL; + ValidatePhoneArguments(args, &list_value); + + web_ui_->CallJavascriptFunction( + "AutofillEditAddressOverlay.setValidatedPhoneNumbers", *list_value); +} diff --git a/chrome/browser/ui/webui/options2/autofill_options_handler.h b/chrome/browser/ui/webui/options2/autofill_options_handler.h new file mode 100644 index 0000000..4eeb90a --- /dev/null +++ b/chrome/browser/ui/webui/options2/autofill_options_handler.h @@ -0,0 +1,89 @@ +// Copyright (c) 2011 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. + +#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS2_AUTOFILL_OPTIONS_HANDLER_H_ +#define CHROME_BROWSER_UI_WEBUI_OPTIONS2_AUTOFILL_OPTIONS_HANDLER_H_ + +#include <string> + +#include "base/compiler_specific.h" +#include "chrome/browser/autofill/personal_data_manager_observer.h" +#include "chrome/browser/ui/webui/options2/options_ui2.h" + +class PersonalDataManager; + +namespace base { +class DictionaryValue; +class ListValue; +} + +class AutofillOptionsHandler : public OptionsPage2UIHandler, + public PersonalDataManagerObserver { + public: + AutofillOptionsHandler(); + virtual ~AutofillOptionsHandler(); + + // OptionsPage2UIHandler implementation. + virtual void GetLocalizedValues( + base::DictionaryValue* localized_strings) OVERRIDE; + virtual void Initialize() OVERRIDE; + virtual void RegisterMessages() OVERRIDE; + + // PersonalDataManagerObserver implementation. + virtual void OnPersonalDataChanged() OVERRIDE; + + private: + // Loads the strings for the address and credit card overlays. + void SetAddressOverlayStrings(base::DictionaryValue* localized_strings); + void SetCreditCardOverlayStrings(base::DictionaryValue* localized_strings); + + // Loads Autofill addresses and credit cards using the PersonalDataManager. + void LoadAutofillData(); + + // Removes an address from the PersonalDataManager. + // |args| - A string, the GUID of the address to remove. + void RemoveAddress(const base::ListValue* args); + + // Removes a credit card from the PersonalDataManager. + // |args| - A string, the GUID of the credit card to remove. + void RemoveCreditCard(const base::ListValue* args); + + // Requests profile data for a specific address. Calls into WebUI with the + // loaded profile data to open the address editor. + // |args| - A string, the GUID of the address to load. + void LoadAddressEditor(const base::ListValue* args); + + // Requests profile data for a specific credit card. Calls into WebUI with the + // loaded profile data to open the credit card editor. + // |args| - A string, the GUID of the credit card to load. + void LoadCreditCardEditor(const base::ListValue* args); + + // Adds or updates an address, depending on the GUID of the profile. If the + // GUID is empty, a new address is added to the WebDatabase; otherwise, the + // address with the matching GUID is updated. Called from WebUI. + // |args| - an array containing the GUID of the address followed by the + // address data. + void SetAddress(const base::ListValue* args); + + // Adds or updates a credit card, depending on the GUID of the profile. If the + // GUID is empty, a new credit card is added to the WebDatabase; otherwise, + // the credit card with the matching GUID is updated. Called from WebUI. + // |args| - an array containing the GUID of the credit card followed by the + // credit card data. + void SetCreditCard(const base::ListValue* args); + + // Validates a list of phone numbers. The resulting validated list of + // numbers is then sent back to the WebUI. + // |args| - an array containing the index of the modified or added number, the + // array of numbers, and the country code string set on the profile. + void ValidatePhoneNumbers(const base::ListValue* args); + + // The personal data manager, used to load Autofill profiles and credit cards. + // Unowned pointer, may not be NULL. + PersonalDataManager* personal_data_; + + DISALLOW_COPY_AND_ASSIGN(AutofillOptionsHandler); +}; + +#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS2_AUTOFILL_OPTIONS_HANDLER_H_ diff --git a/chrome/browser/ui/webui/options2/browser_options_browsertest.js b/chrome/browser/ui/webui/options2/browser_options_browsertest.js new file mode 100644 index 0000000..e983b27 --- /dev/null +++ b/chrome/browser/ui/webui/options2/browser_options_browsertest.js @@ -0,0 +1,24 @@ +// Copyright (c) 2011 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. + +/** + * TestFixture for browser options WebUI testing. + * @extends {testing.Test} + * @constructor + **/ +function BrowserOptionsWebUITest() {} + +BrowserOptionsWebUITest.prototype = { + __proto__: testing.Test.prototype, + + /** + * Browse to browser options. + **/ + browsePreload: 'chrome://settings/browser', +}; + +// Test opening the browser options has correct location. +TEST_F('BrowserOptionsWebUITest', 'testOpenBrowserOptions', function() { + assertEquals(this.browsePreload, document.location.href); +}); diff --git a/chrome/browser/ui/webui/options2/browser_options_handler.cc b/chrome/browser/ui/webui/options2/browser_options_handler.cc new file mode 100644 index 0000000..205512c --- /dev/null +++ b/chrome/browser/ui/webui/options2/browser_options_handler.cc @@ -0,0 +1,496 @@ +// Copyright (c) 2011 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. + +#include "chrome/browser/ui/webui/options2/browser_options_handler.h" + +#include "base/basictypes.h" +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/memory/singleton.h" +#include "base/string_number_conversions.h" +#include "base/utf_string_conversions.h" +#include "base/values.h" +#include "chrome/browser/autocomplete/autocomplete.h" +#include "chrome/browser/autocomplete/autocomplete_match.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/custom_home_pages_table_model.h" +#include "chrome/browser/instant/instant_confirm_dialog.h" +#include "chrome/browser/instant/instant_controller.h" +#include "chrome/browser/instant/instant_field_trial.h" +#include "chrome/browser/net/url_fixer_upper.h" +#include "chrome/browser/prefs/pref_service.h" +#include "chrome/browser/prefs/session_startup_pref.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/search_engines/template_url.h" +#include "chrome/browser/search_engines/template_url_service.h" +#include "chrome/browser/search_engines/template_url_service_factory.h" +#include "chrome/browser/ui/webui/favicon_source.h" +#include "chrome/common/chrome_notification_types.h" +#include "chrome/common/pref_names.h" +#include "chrome/common/url_constants.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/notification_details.h" +#include "content/public/browser/notification_source.h" +#include "content/public/browser/user_metrics.h" +#include "grit/chromium_strings.h" +#include "grit/generated_resources.h" +#include "ui/base/l10n/l10n_util.h" + +using content::UserMetricsAction; + +BrowserOptionsHandler::BrowserOptionsHandler() + : template_url_service_(NULL), startup_custom_pages_table_model_(NULL) { +#if !defined(OS_MACOSX) + default_browser_worker_ = new ShellIntegration::DefaultBrowserWorker(this); +#endif +} + +BrowserOptionsHandler::~BrowserOptionsHandler() { + if (default_browser_worker_.get()) + default_browser_worker_->ObserverDestroyed(); + if (template_url_service_) + template_url_service_->RemoveObserver(this); +} + +void BrowserOptionsHandler::GetLocalizedValues( + DictionaryValue* localized_strings) { + DCHECK(localized_strings); + + static OptionsStringResource resources[] = { + { "startupGroupName", IDS_OPTIONS_STARTUP_GROUP_NAME }, + { "startupShowDefaultAndNewTab", + IDS_OPTIONS_STARTUP_SHOW_DEFAULT_AND_NEWTAB}, + { "startupShowLastSession", IDS_OPTIONS_STARTUP_SHOW_LAST_SESSION }, + { "startupShowPages", IDS_OPTIONS_STARTUP_SHOW_PAGES }, + { "startupAddLabel", IDS_OPTIONS_STARTUP_ADD_LABEL }, + { "startupUseCurrent", IDS_OPTIONS_STARTUP_USE_CURRENT }, + { "homepageGroupName", IDS_OPTIONS_HOMEPAGE_GROUP_NAME }, + { "homepageUseNewTab", IDS_OPTIONS_HOMEPAGE_USE_NEWTAB }, + { "homepageUseURL", IDS_OPTIONS_HOMEPAGE_USE_URL }, + { "toolbarGroupName", IDS_OPTIONS_TOOLBAR_GROUP_NAME }, + { "toolbarShowHomeButton", IDS_OPTIONS_TOOLBAR_SHOW_HOME_BUTTON }, + { "toolbarShowBookmarksBar", IDS_OPTIONS_TOOLBAR_SHOW_BOOKMARKS_BAR }, + { "defaultSearchGroupName", IDS_OPTIONS_DEFAULTSEARCH_GROUP_NAME }, + { "defaultSearchManageEngines", IDS_OPTIONS_DEFAULTSEARCH_MANAGE_ENGINES }, + { "instantName", IDS_INSTANT_PREF }, + { "instantWarningText", IDS_INSTANT_PREF_WARNING }, + { "instantConfirmTitle", IDS_INSTANT_OPT_IN_TITLE }, + { "instantConfirmMessage", IDS_INSTANT_OPT_IN_MESSAGE }, + { "defaultBrowserGroupName", IDS_OPTIONS_DEFAULTBROWSER_GROUP_NAME }, + }; + + RegisterStrings(localized_strings, resources, arraysize(resources)); + RegisterTitle(localized_strings, "browserPage", + IDS_OPTIONS_GENERAL_TAB_LABEL); + + localized_strings->SetString("instantLearnMoreLink", + ASCIIToUTF16(browser::InstantLearnMoreURL().spec())); + localized_strings->SetString("defaultBrowserUnknown", + l10n_util::GetStringFUTF16(IDS_OPTIONS_DEFAULTBROWSER_UNKNOWN, + l10n_util::GetStringUTF16(IDS_PRODUCT_NAME))); + localized_strings->SetString("defaultBrowserUseAsDefault", + l10n_util::GetStringFUTF16(IDS_OPTIONS_DEFAULTBROWSER_USEASDEFAULT, + l10n_util::GetStringUTF16(IDS_PRODUCT_NAME))); +} + +void BrowserOptionsHandler::RegisterMessages() { + web_ui_->RegisterMessageCallback("becomeDefaultBrowser", + base::Bind(&BrowserOptionsHandler::BecomeDefaultBrowser, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("setDefaultSearchEngine", + base::Bind(&BrowserOptionsHandler::SetDefaultSearchEngine, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("removeStartupPages", + base::Bind(&BrowserOptionsHandler::RemoveStartupPages, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("addStartupPage", + base::Bind(&BrowserOptionsHandler::AddStartupPage, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("editStartupPage", + base::Bind(&BrowserOptionsHandler::EditStartupPage, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("setStartupPagesToCurrentPages", + base::Bind(&BrowserOptionsHandler::SetStartupPagesToCurrentPages, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("dragDropStartupPage", + base::Bind(&BrowserOptionsHandler::DragDropStartupPage, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("requestAutocompleteSuggestions", + base::Bind(&BrowserOptionsHandler::RequestAutocompleteSuggestions, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("enableInstant", + base::Bind(&BrowserOptionsHandler::EnableInstant, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("disableInstant", + base::Bind(&BrowserOptionsHandler::DisableInstant, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("getInstantFieldTrialStatus", + base::Bind(&BrowserOptionsHandler::GetInstantFieldTrialStatus, + base::Unretained(this))); +} + +void BrowserOptionsHandler::Initialize() { + Profile* profile = Profile::FromWebUI(web_ui_); + + // Create our favicon data source. + profile->GetChromeURLDataManager()->AddDataSource( + new FaviconSource(profile, FaviconSource::FAVICON)); + + homepage_.Init(prefs::kHomePage, profile->GetPrefs(), NULL); + default_browser_policy_.Init(prefs::kDefaultBrowserSettingEnabled, + g_browser_process->local_state(), + this); + UpdateDefaultBrowserState(); + + startup_custom_pages_table_model_.reset( + new CustomHomePagesTableModel(profile)); + startup_custom_pages_table_model_->SetObserver(this); + UpdateStartupPages(); + + pref_change_registrar_.Init(profile->GetPrefs()); + pref_change_registrar_.Add(prefs::kURLsToRestoreOnStartup, this); + + UpdateSearchEngines(); + + autocomplete_controller_.reset(new AutocompleteController(profile, this)); +} + +void BrowserOptionsHandler::UpdateDefaultBrowserState() { + // Check for side-by-side first. + if (!ShellIntegration::CanSetAsDefaultBrowser()) { + SetDefaultBrowserUIString(IDS_OPTIONS_DEFAULTBROWSER_SXS); + return; + } + +#if defined(OS_MACOSX) + ShellIntegration::DefaultWebClientState state = + ShellIntegration::IsDefaultBrowser(); + int status_string_id; + if (state == ShellIntegration::IS_DEFAULT_WEB_CLIENT) + status_string_id = IDS_OPTIONS_DEFAULTBROWSER_DEFAULT; + else if (state == ShellIntegration::NOT_DEFAULT_WEB_CLIENT) + status_string_id = IDS_OPTIONS_DEFAULTBROWSER_NOTDEFAULT; + else + status_string_id = IDS_OPTIONS_DEFAULTBROWSER_UNKNOWN; + + SetDefaultBrowserUIString(status_string_id); +#else + default_browser_worker_->StartCheckIsDefault(); +#endif +} + +void BrowserOptionsHandler::BecomeDefaultBrowser(const ListValue* args) { + // If the default browser setting is managed then we should not be able to + // call this function. + if (default_browser_policy_.IsManaged()) + return; + + content::RecordAction(UserMetricsAction("Options_SetAsDefaultBrowser")); +#if defined(OS_MACOSX) + if (ShellIntegration::SetAsDefaultBrowser()) + UpdateDefaultBrowserState(); +#else + default_browser_worker_->StartSetAsDefault(); + // Callback takes care of updating UI. +#endif + + // If the user attempted to make Chrome the default browser, then he/she + // arguably wants to be notified when that changes. + PrefService* prefs = Profile::FromWebUI(web_ui_)->GetPrefs(); + prefs->SetBoolean(prefs::kCheckDefaultBrowser, true); +} + +int BrowserOptionsHandler::StatusStringIdForState( + ShellIntegration::DefaultWebClientState state) { + if (state == ShellIntegration::IS_DEFAULT_WEB_CLIENT) + return IDS_OPTIONS_DEFAULTBROWSER_DEFAULT; + if (state == ShellIntegration::NOT_DEFAULT_WEB_CLIENT) + return IDS_OPTIONS_DEFAULTBROWSER_NOTDEFAULT; + return IDS_OPTIONS_DEFAULTBROWSER_UNKNOWN; +} + +void BrowserOptionsHandler::SetDefaultWebClientUIState( + ShellIntegration::DefaultWebClientUIState state) { + int status_string_id; + if (state == ShellIntegration::STATE_IS_DEFAULT) + status_string_id = IDS_OPTIONS_DEFAULTBROWSER_DEFAULT; + else if (state == ShellIntegration::STATE_NOT_DEFAULT) + status_string_id = IDS_OPTIONS_DEFAULTBROWSER_NOTDEFAULT; + else if (state == ShellIntegration::STATE_UNKNOWN) + status_string_id = IDS_OPTIONS_DEFAULTBROWSER_UNKNOWN; + else + return; // Still processing. + + SetDefaultBrowserUIString(status_string_id); +} + +void BrowserOptionsHandler::SetDefaultBrowserUIString(int status_string_id) { + scoped_ptr<Value> status_string(Value::CreateStringValue( + l10n_util::GetStringFUTF16(status_string_id, + l10n_util::GetStringUTF16(IDS_PRODUCT_NAME)))); + + scoped_ptr<Value> is_default(Value::CreateBooleanValue( + status_string_id == IDS_OPTIONS_DEFAULTBROWSER_DEFAULT)); + + scoped_ptr<Value> can_be_default(Value::CreateBooleanValue( + !default_browser_policy_.IsManaged() && + (status_string_id == IDS_OPTIONS_DEFAULTBROWSER_DEFAULT || + status_string_id == IDS_OPTIONS_DEFAULTBROWSER_NOTDEFAULT))); + + web_ui_->CallJavascriptFunction("BrowserOptions.updateDefaultBrowserState", + *status_string, *is_default, *can_be_default); +} + +void BrowserOptionsHandler::OnTemplateURLServiceChanged() { + if (!template_url_service_ || !template_url_service_->loaded()) + return; + + const TemplateURL* default_url = + template_url_service_->GetDefaultSearchProvider(); + + int default_index = 0; + ListValue search_engines; + std::vector<const TemplateURL*> model_urls = + template_url_service_->GetTemplateURLs(); + for (size_t i = 0; i < model_urls.size(); ++i) { + if (!model_urls[i]->ShowInDefaultList()) + continue; + + DictionaryValue* entry = new DictionaryValue(); + entry->SetString("name", model_urls[i]->short_name()); + entry->SetInteger("index", i); + search_engines.Append(entry); + if (model_urls[i] == default_url) + default_index = i; + } + + scoped_ptr<Value> default_value(Value::CreateIntegerValue(default_index)); + scoped_ptr<Value> default_managed(Value::CreateBooleanValue( + template_url_service_->is_default_search_managed())); + + web_ui_->CallJavascriptFunction("BrowserOptions.updateSearchEngines", + search_engines, *default_value, + *default_managed); +} + +void BrowserOptionsHandler::SetDefaultSearchEngine(const ListValue* args) { + int selected_index = -1; + if (!ExtractIntegerValue(args, &selected_index)) { + NOTREACHED(); + return; + } + + std::vector<const TemplateURL*> model_urls = + template_url_service_->GetTemplateURLs(); + if (selected_index >= 0 && + selected_index < static_cast<int>(model_urls.size())) + template_url_service_->SetDefaultSearchProvider(model_urls[selected_index]); + + content::RecordAction(UserMetricsAction("Options_SearchEngineChanged")); +} + +void BrowserOptionsHandler::UpdateSearchEngines() { + template_url_service_ = + TemplateURLServiceFactory::GetForProfile(Profile::FromWebUI(web_ui_)); + if (template_url_service_) { + template_url_service_->Load(); + template_url_service_->AddObserver(this); + OnTemplateURLServiceChanged(); + } +} + +void BrowserOptionsHandler::UpdateStartupPages() { + Profile* profile = Profile::FromWebUI(web_ui_); + const SessionStartupPref startup_pref = + SessionStartupPref::GetStartupPref(profile->GetPrefs()); + startup_custom_pages_table_model_->SetURLs(startup_pref.urls); +} + +void BrowserOptionsHandler::OnModelChanged() { + ListValue startup_pages; + int page_count = startup_custom_pages_table_model_->RowCount(); + std::vector<GURL> urls = startup_custom_pages_table_model_->GetURLs(); + for (int i = 0; i < page_count; ++i) { + DictionaryValue* entry = new DictionaryValue(); + entry->SetString("title", startup_custom_pages_table_model_->GetText(i, 0)); + entry->SetString("url", urls[i].spec()); + entry->SetString("tooltip", + startup_custom_pages_table_model_->GetTooltip(i)); + entry->SetString("modelIndex", base::IntToString(i)); + startup_pages.Append(entry); + } + + web_ui_->CallJavascriptFunction("BrowserOptions.updateStartupPages", + startup_pages); +} + +void BrowserOptionsHandler::OnItemsChanged(int start, int length) { + OnModelChanged(); +} + +void BrowserOptionsHandler::OnItemsAdded(int start, int length) { + OnModelChanged(); +} + +void BrowserOptionsHandler::OnItemsRemoved(int start, int length) { + OnModelChanged(); +} + +void BrowserOptionsHandler::Observe( + int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) { + if (type == chrome::NOTIFICATION_PREF_CHANGED) { + std::string* pref = content::Details<std::string>(details).ptr(); + if (*pref == prefs::kDefaultBrowserSettingEnabled) { + UpdateDefaultBrowserState(); + } else if (*pref == prefs::kURLsToRestoreOnStartup) { + UpdateStartupPages(); + } else { + NOTREACHED(); + } + } else { + NOTREACHED(); + } +} + +void BrowserOptionsHandler::SetStartupPagesToCurrentPages( + const ListValue* args) { + startup_custom_pages_table_model_->SetToCurrentlyOpenPages(); + SaveStartupPagesPref(); +} + +void BrowserOptionsHandler::RemoveStartupPages(const ListValue* args) { + for (int i = args->GetSize() - 1; i >= 0; --i) { + std::string string_value; + CHECK(args->GetString(i, &string_value)); + + int selected_index; + base::StringToInt(string_value, &selected_index); + if (selected_index < 0 || + selected_index >= startup_custom_pages_table_model_->RowCount()) { + NOTREACHED(); + return; + } + startup_custom_pages_table_model_->Remove(selected_index); + } + + SaveStartupPagesPref(); +} + +void BrowserOptionsHandler::AddStartupPage(const ListValue* args) { + std::string url_string; + CHECK_EQ(args->GetSize(), 1U); + CHECK(args->GetString(0, &url_string)); + + GURL url = URLFixerUpper::FixupURL(url_string, std::string()); + if (!url.is_valid()) + return; + int index = startup_custom_pages_table_model_->RowCount(); + startup_custom_pages_table_model_->Add(index, url); + SaveStartupPagesPref(); +} + +void BrowserOptionsHandler::EditStartupPage(const ListValue* args) { + std::string url_string; + std::string index_string; + int index; + CHECK_EQ(args->GetSize(), 2U); + CHECK(args->GetString(0, &index_string)); + CHECK(base::StringToInt(index_string, &index)); + CHECK(args->GetString(1, &url_string)); + + if (index < 0 || index > startup_custom_pages_table_model_->RowCount()) { + NOTREACHED(); + return; + } + + std::vector<GURL> urls = startup_custom_pages_table_model_->GetURLs(); + urls[index] = URLFixerUpper::FixupURL(url_string, std::string()); + startup_custom_pages_table_model_->SetURLs(urls); + SaveStartupPagesPref(); +} + +void BrowserOptionsHandler::DragDropStartupPage(const ListValue* args) { + CHECK_EQ(args->GetSize(), 2U); + + std::string value; + int to_index; + + CHECK(args->GetString(0, &value)); + base::StringToInt(value, &to_index); + + ListValue* selected; + CHECK(args->GetList(1, &selected)); + + std::vector<int> index_list; + for (size_t i = 0; i < selected->GetSize(); ++i) { + int index; + CHECK(selected->GetString(i, &value)); + base::StringToInt(value, &index); + index_list.push_back(index); + } + + startup_custom_pages_table_model_->MoveURLs(to_index, index_list); + SaveStartupPagesPref(); +} + +void BrowserOptionsHandler::SaveStartupPagesPref() { + PrefService* prefs = Profile::FromWebUI(web_ui_)->GetPrefs(); + + SessionStartupPref pref = SessionStartupPref::GetStartupPref(prefs); + pref.urls = startup_custom_pages_table_model_->GetURLs(); + + SessionStartupPref::SetStartupPref(prefs, pref); +} + +void BrowserOptionsHandler::RequestAutocompleteSuggestions( + const ListValue* args) { + string16 input; + CHECK_EQ(args->GetSize(), 1U); + CHECK(args->GetString(0, &input)); + + autocomplete_controller_->Start(input, string16(), true, false, false, + AutocompleteInput::ALL_MATCHES); +} + +void BrowserOptionsHandler::EnableInstant(const ListValue* args) { + InstantController::Enable(Profile::FromWebUI(web_ui_)); +} + +void BrowserOptionsHandler::DisableInstant(const ListValue* args) { + InstantController::Disable(Profile::FromWebUI(web_ui_)); +} + +void BrowserOptionsHandler::GetInstantFieldTrialStatus(const ListValue* args) { + Profile* profile = Profile::FromWebUI(web_ui_); + base::FundamentalValue enabled( + InstantFieldTrial::IsInstantExperiment(profile) && + !InstantFieldTrial::IsHiddenExperiment(profile)); + web_ui_->CallJavascriptFunction("BrowserOptions.setInstantFieldTrialStatus", + enabled); +} + +void BrowserOptionsHandler::OnResultChanged(bool default_match_changed) { + const AutocompleteResult& result = autocomplete_controller_->result(); + ListValue suggestions; + for (size_t i = 0; i < result.size(); ++i) { + const AutocompleteMatch& match = result.match_at(i); + AutocompleteMatch::Type type = match.type; + if (type != AutocompleteMatch::HISTORY_URL && + type != AutocompleteMatch::HISTORY_TITLE && + type != AutocompleteMatch::HISTORY_BODY && + type != AutocompleteMatch::HISTORY_KEYWORD && + type != AutocompleteMatch::NAVSUGGEST) + continue; + DictionaryValue* entry = new DictionaryValue(); + entry->SetString("title", match.description); + entry->SetString("displayURL", match.contents); + entry->SetString("url", match.destination_url.spec()); + suggestions.Append(entry); + } + + web_ui_->CallJavascriptFunction( + "BrowserOptions.updateAutocompleteSuggestions", suggestions); +} diff --git a/chrome/browser/ui/webui/options2/browser_options_handler.h b/chrome/browser/ui/webui/options2/browser_options_handler.h new file mode 100644 index 0000000..894620a --- /dev/null +++ b/chrome/browser/ui/webui/options2/browser_options_handler.h @@ -0,0 +1,136 @@ +// Copyright (c) 2011 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. + +#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS2_BROWSER_OPTIONS_HANDLER_H_ +#define CHROME_BROWSER_UI_WEBUI_OPTIONS2_BROWSER_OPTIONS_HANDLER_H_ +#pragma once + +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "chrome/browser/autocomplete/autocomplete_controller_delegate.h" +#include "chrome/browser/prefs/pref_change_registrar.h" +#include "chrome/browser/prefs/pref_member.h" +#include "chrome/browser/search_engines/template_url_service_observer.h" +#include "chrome/browser/shell_integration.h" +#include "chrome/browser/ui/webui/options2/options_ui2.h" +#include "ui/base/models/table_model_observer.h" + +class AutocompleteController; +class CustomHomePagesTableModel; +class TemplateURLService; + +// Chrome browser options page UI handler. +class BrowserOptionsHandler : public OptionsPage2UIHandler, + public AutocompleteControllerDelegate, + public ShellIntegration::DefaultWebClientObserver, + public TemplateURLServiceObserver, + public ui::TableModelObserver { + public: + BrowserOptionsHandler(); + virtual ~BrowserOptionsHandler(); + + virtual void Initialize() OVERRIDE; + + // OptionsPage2UIHandler implementation. + virtual void GetLocalizedValues(DictionaryValue* localized_strings) OVERRIDE; + virtual void RegisterMessages() OVERRIDE; + + // AutocompleteControllerDelegate implementation. + virtual void OnResultChanged(bool default_match_changed) OVERRIDE; + + // ShellIntegration::DefaultWebClientObserver implementation. + virtual void SetDefaultWebClientUIState( + ShellIntegration::DefaultWebClientUIState state) OVERRIDE; + + // TemplateURLServiceObserver implementation. + virtual void OnTemplateURLServiceChanged() OVERRIDE; + + // ui::TableModelObserver implementation. + virtual void OnModelChanged() OVERRIDE; + virtual void OnItemsChanged(int start, int length) OVERRIDE; + virtual void OnItemsAdded(int start, int length) OVERRIDE; + virtual void OnItemsRemoved(int start, int length) OVERRIDE; + + private: + // content::NotificationObserver implementation. + virtual void Observe(int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) OVERRIDE; + + // Makes this the default browser. Called from WebUI. + void BecomeDefaultBrowser(const ListValue* args); + + // Sets the search engine at the given index to be default. Called from WebUI. + void SetDefaultSearchEngine(const ListValue* args); + + // Removes the startup page at the given indexes. Called from WebUI. + void RemoveStartupPages(const ListValue* args); + + // Adds a startup page with the given URL after the given index. + // Called from WebUI. + void AddStartupPage(const ListValue* args); + + // Changes the startup page at the given index to the given URL. + // Called from WebUI. + void EditStartupPage(const ListValue* args); + + // Sets the startup page set to the current pages. Called from WebUI. + void SetStartupPagesToCurrentPages(const ListValue* args); + + // Writes the current set of startup pages to prefs. Called from WebUI. + void DragDropStartupPage(const ListValue* args); + + // Gets autocomplete suggestions asychronously for the given string. + // Called from WebUI. + void RequestAutocompleteSuggestions(const ListValue* args); + + // Enables/disables Instant. + void EnableInstant(const ListValue* args); + void DisableInstant(const ListValue* args); + + // Called to request information about the Instant field trial. + void GetInstantFieldTrialStatus(const ListValue* args); + + // Returns the string ID for the given default browser state. + int StatusStringIdForState(ShellIntegration::DefaultWebClientState state); + + // Gets the current default browser state, and asynchronously reports it to + // the WebUI page. + void UpdateDefaultBrowserState(); + + // Updates the UI with the given state for the default browser. + void SetDefaultBrowserUIString(int status_string_id); + + // Loads the current set of custom startup pages and reports it to the WebUI. + void UpdateStartupPages(); + + // Loads the possible default search engine list and reports it to the WebUI. + void UpdateSearchEngines(); + + // Writes the current set of startup pages to prefs. + void SaveStartupPagesPref(); + + scoped_refptr<ShellIntegration::DefaultBrowserWorker> + default_browser_worker_; + + StringPrefMember homepage_; + BooleanPrefMember default_browser_policy_; + + // Used to observe updates to the preference of the list of URLs to load + // on startup, which can be updated via sync. + PrefChangeRegistrar pref_change_registrar_; + + TemplateURLService* template_url_service_; // Weak. + + // TODO(stuartmorgan): Once there are no other clients of + // CustomHomePagesTableModel, consider changing it to something more like + // TemplateURLService. + scoped_ptr<CustomHomePagesTableModel> startup_custom_pages_table_model_; + + scoped_ptr<AutocompleteController> autocomplete_controller_; + + DISALLOW_COPY_AND_ASSIGN(BrowserOptionsHandler); +}; + +#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS2_BROWSER_OPTIONS_HANDLER_H_ diff --git a/chrome/browser/ui/webui/options2/certificate_manager_browsertest.js b/chrome/browser/ui/webui/options2/certificate_manager_browsertest.js new file mode 100644 index 0000000..fdd2373 --- /dev/null +++ b/chrome/browser/ui/webui/options2/certificate_manager_browsertest.js @@ -0,0 +1,33 @@ +// Copyright (c) 2011 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. + +/** + * TestFixture for certificate manager WebUI testing. + * @extends {testing.Test} + * @constructor + **/ +function CertificateManagerWebUITest() {} + +CertificateManagerWebUITest.prototype = { + __proto__: testing.Test.prototype, + + /** + * Browse to the certificate manager. + **/ + browsePreload: 'chrome://settings/certificates', +}; + +// Mac and Windows go to native certificate manager. +GEN('#if defined(OS_MACOSX) || defined(OS_WIN)'); +GEN('#define MAYBE_testOpenCertificateManager ' + + 'DISABLED_testOpenCertificateManager'); +GEN('#else'); +GEN('#define MAYBE_testOpenCertificateManager ' + + 'testOpenCertificateManager'); +GEN('#endif // defined(OS_MACOSX) || defined(OS_WIN)'); +// Test opening the certificate manager has correct location. +TEST_F('CertificateManagerWebUITest', + 'MAYBE_testOpenCertificateManager', function() { + assertEquals(this.browsePreload, document.location.href); + }); diff --git a/chrome/browser/ui/webui/options2/certificate_manager_handler.cc b/chrome/browser/ui/webui/options2/certificate_manager_handler.cc new file mode 100644 index 0000000..00d52f6 --- /dev/null +++ b/chrome/browser/ui/webui/options2/certificate_manager_handler.cc @@ -0,0 +1,1046 @@ +// Copyright (c) 2011 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. + +#include "chrome/browser/ui/webui/options2/certificate_manager_handler.h" + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/file_util.h" // for FileAccessProvider +#include "base/memory/scoped_vector.h" +#include "base/safe_strerror_posix.h" +#include "base/string_number_conversions.h" +#include "base/values.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/certificate_viewer.h" +#include "chrome/browser/ui/certificate_dialogs.h" +#include "chrome/browser/ui/crypto_module_password_dialog.h" +#include "content/browser/tab_contents/tab_contents.h" +#include "content/browser/tab_contents/tab_contents_view.h" +#include "content/public/browser/browser_thread.h" // for FileAccessProvider +#include "grit/generated_resources.h" +#include "net/base/crypto_module.h" +#include "net/base/x509_certificate.h" +#include "ui/base/l10n/l10n_util.h" +#include "ui/base/l10n/l10n_util_collator.h" + +#if defined(OS_CHROMEOS) +#include "chrome/browser/chromeos/cros/cros_library.h" +#include "chrome/browser/chromeos/cros/cryptohome_library.h" +#endif + +using content::BrowserThread; + +namespace { + +static const char kKeyId[] = "id"; +static const char kSubNodesId[] = "subnodes"; +static const char kNameId[] = "name"; +static const char kReadOnlyId[] = "readonly"; +static const char kUntrustedId[] = "untrusted"; +static const char kSecurityDeviceId[] = "device"; +static const char kErrorId[] = "error"; + +// Enumeration of different callers of SelectFile. (Start counting at 1 so +// if SelectFile is accidentally called with params=NULL it won't match any.) +enum { + EXPORT_PERSONAL_FILE_SELECTED = 1, + IMPORT_PERSONAL_FILE_SELECTED, + IMPORT_SERVER_FILE_SELECTED, + IMPORT_CA_FILE_SELECTED, +}; + +// TODO(mattm): These are duplicated from cookies_view_handler.cc +// Encodes a pointer value into a hex string. +std::string PointerToHexString(const void* pointer) { + return base::HexEncode(&pointer, sizeof(pointer)); +} + +// Decodes a pointer from a hex string. +void* HexStringToPointer(const std::string& str) { + std::vector<uint8> buffer; + if (!base::HexStringToBytes(str, &buffer) || + buffer.size() != sizeof(void*)) { + return NULL; + } + + return *reinterpret_cast<void**>(&buffer[0]); +} + +std::string OrgNameToId(const std::string& org) { + return "org-" + org; +} + +std::string CertToId(const net::X509Certificate& cert) { + return "cert-" + PointerToHexString(&cert); +} + +net::X509Certificate* IdToCert(const std::string& id) { + if (!StartsWithASCII(id, "cert-", true)) + return NULL; + return reinterpret_cast<net::X509Certificate*>( + HexStringToPointer(id.substr(5))); +} + +net::X509Certificate* CallbackArgsToCert(const ListValue* args) { + std::string node_id; + if (!args->GetString(0, &node_id)){ + return NULL; + } + net::X509Certificate* cert = IdToCert(node_id); + if (!cert) { + NOTREACHED(); + return NULL; + } + return cert; +} + +bool CallbackArgsToBool(const ListValue* args, int index, bool* result) { + std::string string_value; + if (!args->GetString(index, &string_value)) + return false; + + *result = string_value[0] == 't'; + return true; +} + +struct DictionaryIdComparator { + explicit DictionaryIdComparator(icu::Collator* collator) + : collator_(collator) { + } + + bool operator()(const Value* a, + const Value* b) const { + DCHECK(a->GetType() == Value::TYPE_DICTIONARY); + DCHECK(b->GetType() == Value::TYPE_DICTIONARY); + const DictionaryValue* a_dict = reinterpret_cast<const DictionaryValue*>(a); + const DictionaryValue* b_dict = reinterpret_cast<const DictionaryValue*>(b); + string16 a_str; + string16 b_str; + a_dict->GetString(kNameId, &a_str); + b_dict->GetString(kNameId, &b_str); + if (collator_ == NULL) + return a_str < b_str; + return l10n_util::CompareString16WithCollator( + collator_, a_str, b_str) == UCOL_LESS; + } + + icu::Collator* collator_; +}; + +std::string NetErrorToString(int net_error) { + switch (net_error) { + // TODO(mattm): handle more cases. + case net::ERR_IMPORT_CA_CERT_NOT_CA: + return l10n_util::GetStringUTF8(IDS_CERT_MANAGER_ERROR_NOT_CA); + default: + return l10n_util::GetStringUTF8(IDS_CERT_MANAGER_UNKNOWN_ERROR); + } +} + +} // namespace + +/////////////////////////////////////////////////////////////////////////////// +// FileAccessProvider + +// TODO(mattm): Move to some shared location? +class FileAccessProvider + : public base::RefCountedThreadSafe<FileAccessProvider>, + public CancelableRequestProvider { + public: + // Reports 0 on success or errno on failure, and the data of the file upon + // success. + // TODO(mattm): don't pass std::string by value.. could use RefCountedBytes + // but it's a vector. Maybe do the derive from CancelableRequest thing + // described in cancelable_request.h? + typedef Callback2<int, std::string>::Type ReadCallback; + + // Reports 0 on success or errno on failure, and the number of bytes written, + // on success. + typedef Callback2<int, int>::Type WriteCallback; + + Handle StartRead(const FilePath& path, + CancelableRequestConsumerBase* consumer, + ReadCallback* callback); + Handle StartWrite(const FilePath& path, + const std::string& data, + CancelableRequestConsumerBase* consumer, + WriteCallback* callback); + + private: + void DoRead(scoped_refptr<CancelableRequest<ReadCallback> > request, + FilePath path); + void DoWrite(scoped_refptr<CancelableRequest<WriteCallback> > request, + FilePath path, + std::string data); +}; + +CancelableRequestProvider::Handle FileAccessProvider::StartRead( + const FilePath& path, + CancelableRequestConsumerBase* consumer, + FileAccessProvider::ReadCallback* callback) { + scoped_refptr<CancelableRequest<ReadCallback> > request( + new CancelableRequest<ReadCallback>(callback)); + AddRequest(request, consumer); + + // Send the parameters and the request to the file thread. + BrowserThread::PostTask( + BrowserThread::FILE, FROM_HERE, + base::Bind(&FileAccessProvider::DoRead, this, request, path)); + + // The handle will have been set by AddRequest. + return request->handle(); +} + +CancelableRequestProvider::Handle FileAccessProvider::StartWrite( + const FilePath& path, + const std::string& data, + CancelableRequestConsumerBase* consumer, + WriteCallback* callback) { + scoped_refptr<CancelableRequest<WriteCallback> > request( + new CancelableRequest<WriteCallback>(callback)); + AddRequest(request, consumer); + + // Send the parameters and the request to the file thWrite. + BrowserThread::PostTask( + BrowserThread::FILE, FROM_HERE, + base::Bind(&FileAccessProvider::DoWrite, this, request, path, data)); + + // The handle will have been set by AddRequest. + return request->handle(); +} + +void FileAccessProvider::DoRead( + scoped_refptr<CancelableRequest<ReadCallback> > request, + FilePath path) { + if (request->canceled()) + return; + + std::string data; + VLOG(1) << "DoRead starting read"; + bool success = file_util::ReadFileToString(path, &data); + int saved_errno = success ? 0 : errno; + VLOG(1) << "DoRead done read: " << success << " " << data.size(); + request->ForwardResult(ReadCallback::TupleType(saved_errno, data)); +} + +void FileAccessProvider::DoWrite( + scoped_refptr<CancelableRequest<WriteCallback> > request, + FilePath path, + std::string data) { + VLOG(1) << "DoWrite starting write"; + int bytes_written = file_util::WriteFile(path, data.data(), data.size()); + int saved_errno = bytes_written >= 0 ? 0 : errno; + VLOG(1) << "DoWrite done write " << bytes_written; + + if (request->canceled()) + return; + + request->ForwardResult(WriteCallback::TupleType(saved_errno, bytes_written)); +} + +/////////////////////////////////////////////////////////////////////////////// +// CertificateManagerHandler + +CertificateManagerHandler::CertificateManagerHandler() + : file_access_provider_(new FileAccessProvider) { + certificate_manager_model_.reset(new CertificateManagerModel(this)); +} + +CertificateManagerHandler::~CertificateManagerHandler() { +} + +void CertificateManagerHandler::GetLocalizedValues( + DictionaryValue* localized_strings) { + DCHECK(localized_strings); + + RegisterTitle(localized_strings, "certificateManagerPage", + IDS_CERTIFICATE_MANAGER_TITLE); + + // Tabs. + localized_strings->SetString("personalCertsTabTitle", + l10n_util::GetStringUTF16(IDS_CERT_MANAGER_PERSONAL_CERTS_TAB_LABEL)); + localized_strings->SetString("serverCertsTabTitle", + l10n_util::GetStringUTF16(IDS_CERT_MANAGER_SERVER_CERTS_TAB_LABEL)); + localized_strings->SetString("caCertsTabTitle", + l10n_util::GetStringUTF16(IDS_CERT_MANAGER_CERT_AUTHORITIES_TAB_LABEL)); + localized_strings->SetString("unknownCertsTabTitle", + l10n_util::GetStringUTF16(IDS_CERT_MANAGER_UNKNOWN_TAB_LABEL)); + + // Tab descriptions. + localized_strings->SetString("personalCertsTabDescription", + l10n_util::GetStringUTF16(IDS_CERT_MANAGER_USER_TREE_DESCRIPTION)); + localized_strings->SetString("serverCertsTabDescription", + l10n_util::GetStringUTF16(IDS_CERT_MANAGER_SERVER_TREE_DESCRIPTION)); + localized_strings->SetString("caCertsTabDescription", + l10n_util::GetStringUTF16(IDS_CERT_MANAGER_AUTHORITIES_TREE_DESCRIPTION)); + localized_strings->SetString("unknownCertsTabDescription", + l10n_util::GetStringUTF16(IDS_CERT_MANAGER_UNKNOWN_TREE_DESCRIPTION)); + + // Tree columns. + localized_strings->SetString("certNameColumn", + l10n_util::GetStringUTF16(IDS_CERT_MANAGER_NAME_COLUMN_LABEL)); + localized_strings->SetString("certDeviceColumn", + l10n_util::GetStringUTF16(IDS_CERT_MANAGER_DEVICE_COLUMN_LABEL)); + localized_strings->SetString("certSerialColumn", + l10n_util::GetStringUTF16(IDS_CERT_MANAGER_SERIAL_NUMBER_COLUMN_LABEL)); + localized_strings->SetString("certExpiresColumn", + l10n_util::GetStringUTF16(IDS_CERT_MANAGER_EXPIRES_COLUMN_LABEL)); + + // Buttons. + localized_strings->SetString("view_certificate", + l10n_util::GetStringUTF16(IDS_CERT_MANAGER_VIEW_CERT_BUTTON)); + localized_strings->SetString("import_certificate", + l10n_util::GetStringUTF16(IDS_CERT_MANAGER_IMPORT_BUTTON)); + localized_strings->SetString("export_certificate", + l10n_util::GetStringUTF16(IDS_CERT_MANAGER_EXPORT_BUTTON)); + localized_strings->SetString("export_all_certificates", + l10n_util::GetStringUTF16(IDS_CERT_MANAGER_EXPORT_ALL_BUTTON)); + localized_strings->SetString("edit_certificate", + l10n_util::GetStringUTF16(IDS_CERT_MANAGER_EDIT_BUTTON)); + localized_strings->SetString("delete_certificate", + l10n_util::GetStringUTF16(IDS_CERT_MANAGER_DELETE_BUTTON)); + + // Certificate Delete overlay strings. + localized_strings->SetString("personalCertsTabDeleteConfirm", + l10n_util::GetStringUTF16(IDS_CERT_MANAGER_DELETE_USER_FORMAT)); + localized_strings->SetString("personalCertsTabDeleteImpact", + l10n_util::GetStringUTF16(IDS_CERT_MANAGER_DELETE_USER_DESCRIPTION)); + localized_strings->SetString("serverCertsTabDeleteConfirm", + l10n_util::GetStringUTF16(IDS_CERT_MANAGER_DELETE_SERVER_FORMAT)); + localized_strings->SetString("serverCertsTabDeleteImpact", + l10n_util::GetStringUTF16(IDS_CERT_MANAGER_DELETE_SERVER_DESCRIPTION)); + localized_strings->SetString("caCertsTabDeleteConfirm", + l10n_util::GetStringUTF16(IDS_CERT_MANAGER_DELETE_CA_FORMAT)); + localized_strings->SetString("caCertsTabDeleteImpact", + l10n_util::GetStringUTF16(IDS_CERT_MANAGER_DELETE_CA_DESCRIPTION)); + localized_strings->SetString("unknownCertsTabDeleteConfirm", + l10n_util::GetStringUTF16(IDS_CERT_MANAGER_DELETE_UNKNOWN_FORMAT)); + localized_strings->SetString("unknownCertsTabDeleteImpact", ""); + + // Certificate Restore overlay strings. + localized_strings->SetString("certificateRestorePasswordDescription", + l10n_util::GetStringUTF16(IDS_CERT_MANAGER_RESTORE_PASSWORD_DESC)); + localized_strings->SetString("certificatePasswordLabel", + l10n_util::GetStringUTF16(IDS_CERT_MANAGER_PASSWORD_LABEL)); + + // Personal Certificate Export overlay strings. + localized_strings->SetString("certificateExportPasswordDescription", + l10n_util::GetStringUTF16(IDS_CERT_MANAGER_EXPORT_PASSWORD_DESC)); + localized_strings->SetString("certificateExportPasswordHelp", + l10n_util::GetStringUTF16(IDS_CERT_MANAGER_EXPORT_PASSWORD_HELP)); + localized_strings->SetString("certificateConfirmPasswordLabel", + l10n_util::GetStringUTF16(IDS_CERT_MANAGER_CONFIRM_PASSWORD_LABEL)); + + // Edit CA Trust & Import CA overlay strings. + localized_strings->SetString("certificateEditTrustLabel", + l10n_util::GetStringUTF16(IDS_CERT_MANAGER_EDIT_TRUST_LABEL)); + localized_strings->SetString("certificateEditCaTrustDescriptionFormat", + l10n_util::GetStringUTF16( + IDS_CERT_MANAGER_EDIT_CA_TRUST_DESCRIPTION_FORMAT)); + localized_strings->SetString("certificateImportCaDescriptionFormat", + l10n_util::GetStringUTF16( + IDS_CERT_MANAGER_IMPORT_CA_DESCRIPTION_FORMAT)); + localized_strings->SetString("certificateCaTrustSSLLabel", + l10n_util::GetStringUTF16(IDS_CERT_MANAGER_EDIT_CA_TRUST_SSL_LABEL)); + localized_strings->SetString("certificateCaTrustEmailLabel", + l10n_util::GetStringUTF16(IDS_CERT_MANAGER_EDIT_CA_TRUST_EMAIL_LABEL)); + localized_strings->SetString("certificateCaTrustObjSignLabel", + l10n_util::GetStringUTF16(IDS_CERT_MANAGER_EDIT_CA_TRUST_OBJSIGN_LABEL)); + localized_strings->SetString("certificateImportErrorFormat", + l10n_util::GetStringUTF16(IDS_CERT_MANAGER_IMPORT_ERROR_FORMAT)); + + // Badges next to certificates + localized_strings->SetString("badgeCertUntrusted", + l10n_util::GetStringUTF16(IDS_CERT_MANAGER_UNTRUSTED)); + +#if defined(OS_CHROMEOS) + localized_strings->SetString("importAndBindCertificate", + l10n_util::GetStringUTF16(IDS_CERT_MANAGER_IMPORT_AND_BIND_BUTTON)); + localized_strings->SetString("hardwareBackedKeyFormat", + l10n_util::GetStringUTF16(IDS_CERT_MANAGER_HARDWARE_BACKED_KEY_FORMAT)); + localized_strings->SetString("chromeOSDeviceName", + l10n_util::GetStringUTF16(IDS_CERT_MANAGER_HARDWARE_BACKED)); +#endif // defined(OS_CHROMEOS) +} + +void CertificateManagerHandler::RegisterMessages() { + web_ui_->RegisterMessageCallback( + "viewCertificate", + base::Bind(&CertificateManagerHandler::View, base::Unretained(this))); + + web_ui_->RegisterMessageCallback( + "getCaCertificateTrust", + base::Bind(&CertificateManagerHandler::GetCATrust, + base::Unretained(this))); + web_ui_->RegisterMessageCallback( + "editCaCertificateTrust", + base::Bind(&CertificateManagerHandler::EditCATrust, + base::Unretained(this))); + + web_ui_->RegisterMessageCallback( + "editServerCertificate", + base::Bind(&CertificateManagerHandler::EditServer, + base::Unretained(this))); + + web_ui_->RegisterMessageCallback( + "cancelImportExportCertificate", + base::Bind(&CertificateManagerHandler::CancelImportExportProcess, + base::Unretained(this))); + + web_ui_->RegisterMessageCallback( + "exportPersonalCertificate", + base::Bind(&CertificateManagerHandler::ExportPersonal, + base::Unretained(this))); + web_ui_->RegisterMessageCallback( + "exportAllPersonalCertificates", + base::Bind(&CertificateManagerHandler::ExportAllPersonal, + base::Unretained(this))); + web_ui_->RegisterMessageCallback( + "exportPersonalCertificatePasswordSelected", + base::Bind(&CertificateManagerHandler::ExportPersonalPasswordSelected, + base::Unretained(this))); + + web_ui_->RegisterMessageCallback( + "importPersonalCertificate", + base::Bind(&CertificateManagerHandler::StartImportPersonal, + base::Unretained(this))); + web_ui_->RegisterMessageCallback( + "importPersonalCertificatePasswordSelected", + base::Bind(&CertificateManagerHandler::ImportPersonalPasswordSelected, + base::Unretained(this))); + + web_ui_->RegisterMessageCallback( + "importCaCertificate", + base::Bind(&CertificateManagerHandler::ImportCA, + base::Unretained(this))); + web_ui_->RegisterMessageCallback( + "importCaCertificateTrustSelected", + base::Bind(&CertificateManagerHandler::ImportCATrustSelected, + base::Unretained(this))); + + web_ui_->RegisterMessageCallback( + "importServerCertificate", + base::Bind(&CertificateManagerHandler::ImportServer, + base::Unretained(this))); + + web_ui_->RegisterMessageCallback( + "exportCertificate", + base::Bind(&CertificateManagerHandler::Export, + base::Unretained(this))); + + web_ui_->RegisterMessageCallback( + "deleteCertificate", + base::Bind(&CertificateManagerHandler::Delete, + base::Unretained(this))); + + web_ui_->RegisterMessageCallback( + "populateCertificateManager", + base::Bind(&CertificateManagerHandler::Populate, + base::Unretained(this))); + +#if defined(OS_CHROMEOS) + web_ui_->RegisterMessageCallback( + "checkTpmTokenReady", + base::Bind(&CertificateManagerHandler::CheckTpmTokenReady, + base::Unretained(this))); +#endif +} + +void CertificateManagerHandler::CertificatesRefreshed() { + PopulateTree("personalCertsTab", net::USER_CERT); + PopulateTree("serverCertsTab", net::SERVER_CERT); + PopulateTree("caCertsTab", net::CA_CERT); + PopulateTree("otherCertsTab", net::UNKNOWN_CERT); + VLOG(1) << "populating finished"; +} + +void CertificateManagerHandler::FileSelected(const FilePath& path, int index, + void* params) { + switch (reinterpret_cast<intptr_t>(params)) { + case EXPORT_PERSONAL_FILE_SELECTED: + ExportPersonalFileSelected(path); + break; + case IMPORT_PERSONAL_FILE_SELECTED: + ImportPersonalFileSelected(path); + break; + case IMPORT_SERVER_FILE_SELECTED: + ImportServerFileSelected(path); + break; + case IMPORT_CA_FILE_SELECTED: + ImportCAFileSelected(path); + break; + default: + NOTREACHED(); + } +} + +void CertificateManagerHandler::FileSelectionCanceled(void* params) { + switch (reinterpret_cast<intptr_t>(params)) { + case EXPORT_PERSONAL_FILE_SELECTED: + case IMPORT_PERSONAL_FILE_SELECTED: + case IMPORT_SERVER_FILE_SELECTED: + case IMPORT_CA_FILE_SELECTED: + ImportExportCleanup(); + break; + default: + NOTREACHED(); + } +} + +void CertificateManagerHandler::View(const ListValue* args) { + net::X509Certificate* cert = CallbackArgsToCert(args); + if (!cert) + return; + ShowCertificateViewer(GetParentWindow(), cert); +} + +void CertificateManagerHandler::GetCATrust(const ListValue* args) { + net::X509Certificate* cert = CallbackArgsToCert(args); + if (!cert) { + web_ui_->CallJavascriptFunction("CertificateEditCaTrustOverlay.dismiss"); + return; + } + + net::CertDatabase::TrustBits trust_bits = + certificate_manager_model_->cert_db().GetCertTrust(cert, net::CA_CERT); + base::FundamentalValue ssl_value( + static_cast<bool>(trust_bits & net::CertDatabase::TRUSTED_SSL)); + base::FundamentalValue email_value( + static_cast<bool>(trust_bits & net::CertDatabase::TRUSTED_EMAIL)); + base::FundamentalValue obj_sign_value( + static_cast<bool>(trust_bits & net::CertDatabase::TRUSTED_OBJ_SIGN)); + web_ui_->CallJavascriptFunction( + "CertificateEditCaTrustOverlay.populateTrust", + ssl_value, email_value, obj_sign_value); +} + +void CertificateManagerHandler::EditCATrust(const ListValue* args) { + net::X509Certificate* cert = CallbackArgsToCert(args); + bool fail = !cert; + bool trust_ssl = false; + bool trust_email = false; + bool trust_obj_sign = false; + fail |= !CallbackArgsToBool(args, 1, &trust_ssl); + fail |= !CallbackArgsToBool(args, 2, &trust_email); + fail |= !CallbackArgsToBool(args, 3, &trust_obj_sign); + if (fail) { + LOG(ERROR) << "EditCATrust args fail"; + web_ui_->CallJavascriptFunction("CertificateEditCaTrustOverlay.dismiss"); + return; + } + + bool result = certificate_manager_model_->SetCertTrust( + cert, + net::CA_CERT, + trust_ssl * net::CertDatabase::TRUSTED_SSL + + trust_email * net::CertDatabase::TRUSTED_EMAIL + + trust_obj_sign * net::CertDatabase::TRUSTED_OBJ_SIGN); + web_ui_->CallJavascriptFunction("CertificateEditCaTrustOverlay.dismiss"); + if (!result) { + // TODO(mattm): better error messages? + ShowError( + l10n_util::GetStringUTF8(IDS_CERT_MANAGER_SET_TRUST_ERROR_TITLE), + l10n_util::GetStringUTF8(IDS_CERT_MANAGER_UNKNOWN_ERROR)); + } +} + +void CertificateManagerHandler::EditServer(const ListValue* args) { + NOTIMPLEMENTED(); +} + +void CertificateManagerHandler::ExportPersonal(const ListValue* args) { + net::X509Certificate* cert = CallbackArgsToCert(args); + if (!cert) + return; + + selected_cert_list_.push_back(cert); + + SelectFileDialog::FileTypeInfo file_type_info; + file_type_info.extensions.resize(1); + file_type_info.extensions[0].push_back(FILE_PATH_LITERAL("p12")); + file_type_info.extension_description_overrides.push_back( + l10n_util::GetStringUTF16(IDS_CERT_MANAGER_PKCS12_FILES)); + file_type_info.include_all_files = true; + select_file_dialog_ = SelectFileDialog::Create(this); + select_file_dialog_->SelectFile( + SelectFileDialog::SELECT_SAVEAS_FILE, string16(), + FilePath(), &file_type_info, 1, FILE_PATH_LITERAL("p12"), + web_ui_->tab_contents(), GetParentWindow(), + reinterpret_cast<void*>(EXPORT_PERSONAL_FILE_SELECTED)); +} + +void CertificateManagerHandler::ExportAllPersonal(const ListValue* args) { + NOTIMPLEMENTED(); +} + +void CertificateManagerHandler::ExportPersonalFileSelected( + const FilePath& path) { + file_path_ = path; + web_ui_->CallJavascriptFunction( + "CertificateManager.exportPersonalAskPassword"); +} + +void CertificateManagerHandler::ExportPersonalPasswordSelected( + const ListValue* args) { + if (!args->GetString(0, &password_)){ + web_ui_->CallJavascriptFunction("CertificateRestoreOverlay.dismiss"); + ImportExportCleanup(); + return; + } + + // Currently, we don't support exporting more than one at a time. If we do, + // this would need to either change this to use UnlockSlotsIfNecessary or + // change UnlockCertSlotIfNecessary to take a CertificateList. + DCHECK_EQ(selected_cert_list_.size(), 1U); + + // TODO(mattm): do something smarter about non-extractable keys + browser::UnlockCertSlotIfNecessary( + selected_cert_list_[0].get(), + browser::kCryptoModulePasswordCertExport, + "", // unused. + base::Bind(&CertificateManagerHandler::ExportPersonalSlotsUnlocked, + base::Unretained(this))); +} + +void CertificateManagerHandler::ExportPersonalSlotsUnlocked() { + std::string output; + int num_exported = certificate_manager_model_->cert_db().ExportToPKCS12( + selected_cert_list_, + password_, + &output); + if (!num_exported) { + web_ui_->CallJavascriptFunction("CertificateRestoreOverlay.dismiss"); + ShowError( + l10n_util::GetStringUTF8(IDS_CERT_MANAGER_PKCS12_EXPORT_ERROR_TITLE), + l10n_util::GetStringUTF8(IDS_CERT_MANAGER_UNKNOWN_ERROR)); + ImportExportCleanup(); + return; + } + file_access_provider_->StartWrite( + file_path_, + output, + &consumer_, + NewCallback(this, &CertificateManagerHandler::ExportPersonalFileWritten)); +} + +void CertificateManagerHandler::ExportPersonalFileWritten(int write_errno, + int bytes_written) { + web_ui_->CallJavascriptFunction("CertificateRestoreOverlay.dismiss"); + ImportExportCleanup(); + if (write_errno) { + ShowError( + l10n_util::GetStringUTF8(IDS_CERT_MANAGER_PKCS12_EXPORT_ERROR_TITLE), + l10n_util::GetStringFUTF8(IDS_CERT_MANAGER_WRITE_ERROR_FORMAT, + UTF8ToUTF16(safe_strerror(write_errno)))); + } +} + +void CertificateManagerHandler::StartImportPersonal(const ListValue* args) { + SelectFileDialog::FileTypeInfo file_type_info; + if (!args->GetBoolean(0, &use_hardware_backed_)){ + // Unable to retrieve the hardware backed attribute from the args, + // so bail. + web_ui_->CallJavascriptFunction("CertificateRestoreOverlay.dismiss"); + ImportExportCleanup(); + return; + } + file_type_info.extensions.resize(1); + file_type_info.extensions[0].push_back(FILE_PATH_LITERAL("p12")); + file_type_info.extension_description_overrides.push_back( + l10n_util::GetStringUTF16(IDS_CERT_MANAGER_PKCS12_FILES)); + file_type_info.include_all_files = true; + select_file_dialog_ = SelectFileDialog::Create(this); + select_file_dialog_->SelectFile( + SelectFileDialog::SELECT_OPEN_FILE, string16(), + FilePath(), &file_type_info, 1, FILE_PATH_LITERAL("p12"), + web_ui_->tab_contents(), GetParentWindow(), + reinterpret_cast<void*>(IMPORT_PERSONAL_FILE_SELECTED)); +} + +void CertificateManagerHandler::ImportPersonalFileSelected( + const FilePath& path) { + file_path_ = path; + web_ui_->CallJavascriptFunction( + "CertificateManager.importPersonalAskPassword"); +} + +void CertificateManagerHandler::ImportPersonalPasswordSelected( + const ListValue* args) { + if (!args->GetString(0, &password_)){ + web_ui_->CallJavascriptFunction("CertificateRestoreOverlay.dismiss"); + ImportExportCleanup(); + return; + } + file_access_provider_->StartRead( + file_path_, + &consumer_, + NewCallback(this, &CertificateManagerHandler::ImportPersonalFileRead)); +} + +void CertificateManagerHandler::ImportPersonalFileRead( + int read_errno, std::string data) { + if (read_errno) { + ImportExportCleanup(); + web_ui_->CallJavascriptFunction("CertificateRestoreOverlay.dismiss"); + ShowError( + l10n_util::GetStringUTF8(IDS_CERT_MANAGER_PKCS12_IMPORT_ERROR_TITLE), + l10n_util::GetStringFUTF8(IDS_CERT_MANAGER_READ_ERROR_FORMAT, + UTF8ToUTF16(safe_strerror(read_errno)))); + return; + } + + file_data_ = data; + + if (use_hardware_backed_) { + module_ = certificate_manager_model_->cert_db().GetPrivateModule(); + } else { + module_ = certificate_manager_model_->cert_db().GetPublicModule(); + } + + net::CryptoModuleList modules; + modules.push_back(module_); + browser::UnlockSlotsIfNecessary( + modules, + browser::kCryptoModulePasswordCertImport, + "", // unused. + base::Bind(&CertificateManagerHandler::ImportPersonalSlotUnlocked, + base::Unretained(this))); +} + +void CertificateManagerHandler::ImportPersonalSlotUnlocked() { + // Determine if the private key should be unextractable after the import. + // We do this by checking the value of |use_hardware_backed_| which is set + // to true if importing into a hardware module. Currently, this only happens + // for Chrome OS when the "Import and Bind" option is chosen. + bool is_extractable = !use_hardware_backed_; + int result = certificate_manager_model_->ImportFromPKCS12( + module_, file_data_, password_, is_extractable); + ImportExportCleanup(); + web_ui_->CallJavascriptFunction("CertificateRestoreOverlay.dismiss"); + int string_id; + switch (result) { + case net::OK: + return; + case net::ERR_PKCS12_IMPORT_BAD_PASSWORD: + // TODO(mattm): if the error was a bad password, we should reshow the + // password dialog after the user dismisses the error dialog. + string_id = IDS_CERT_MANAGER_BAD_PASSWORD; + break; + case net::ERR_PKCS12_IMPORT_INVALID_MAC: + string_id = IDS_CERT_MANAGER_PKCS12_IMPORT_INVALID_MAC; + break; + case net::ERR_PKCS12_IMPORT_INVALID_FILE: + string_id = IDS_CERT_MANAGER_PKCS12_IMPORT_INVALID_FILE; + break; + case net::ERR_PKCS12_IMPORT_UNSUPPORTED: + string_id = IDS_CERT_MANAGER_PKCS12_IMPORT_UNSUPPORTED; + break; + default: + string_id = IDS_CERT_MANAGER_UNKNOWN_ERROR; + break; + } + ShowError( + l10n_util::GetStringUTF8(IDS_CERT_MANAGER_PKCS12_IMPORT_ERROR_TITLE), + l10n_util::GetStringUTF8(string_id)); +} + +void CertificateManagerHandler::CancelImportExportProcess( + const ListValue* args) { + ImportExportCleanup(); +} + +void CertificateManagerHandler::ImportExportCleanup() { + file_path_.clear(); + password_.clear(); + file_data_.clear(); + use_hardware_backed_ = false; + selected_cert_list_.clear(); + module_ = NULL; + + // There may be pending file dialogs, we need to tell them that we've gone + // away so they don't try and call back to us. + if (select_file_dialog_.get()) + select_file_dialog_->ListenerDestroyed(); + select_file_dialog_ = NULL; +} + +void CertificateManagerHandler::ImportServer(const ListValue* args) { + select_file_dialog_ = SelectFileDialog::Create(this); + ShowCertSelectFileDialog( + select_file_dialog_.get(), + SelectFileDialog::SELECT_OPEN_FILE, + FilePath(), + web_ui_->tab_contents(), + GetParentWindow(), + reinterpret_cast<void*>(IMPORT_SERVER_FILE_SELECTED)); +} + +void CertificateManagerHandler::ImportServerFileSelected(const FilePath& path) { + file_path_ = path; + file_access_provider_->StartRead( + file_path_, + &consumer_, + NewCallback(this, &CertificateManagerHandler::ImportServerFileRead)); +} + +void CertificateManagerHandler::ImportServerFileRead(int read_errno, + std::string data) { + if (read_errno) { + ImportExportCleanup(); + ShowError( + l10n_util::GetStringUTF8(IDS_CERT_MANAGER_SERVER_IMPORT_ERROR_TITLE), + l10n_util::GetStringFUTF8(IDS_CERT_MANAGER_READ_ERROR_FORMAT, + UTF8ToUTF16(safe_strerror(read_errno)))); + return; + } + + selected_cert_list_ = net::X509Certificate::CreateCertificateListFromBytes( + data.data(), data.size(), net::X509Certificate::FORMAT_AUTO); + if (selected_cert_list_.empty()) { + ImportExportCleanup(); + ShowError( + l10n_util::GetStringUTF8(IDS_CERT_MANAGER_SERVER_IMPORT_ERROR_TITLE), + l10n_util::GetStringUTF8(IDS_CERT_MANAGER_CERT_PARSE_ERROR)); + return; + } + + net::CertDatabase::ImportCertFailureList not_imported; + bool result = certificate_manager_model_->ImportServerCert( + selected_cert_list_, + ¬_imported); + if (!result) { + ShowError( + l10n_util::GetStringUTF8(IDS_CERT_MANAGER_SERVER_IMPORT_ERROR_TITLE), + l10n_util::GetStringUTF8(IDS_CERT_MANAGER_UNKNOWN_ERROR)); + } else if (!not_imported.empty()) { + ShowImportErrors( + l10n_util::GetStringUTF8(IDS_CERT_MANAGER_SERVER_IMPORT_ERROR_TITLE), + not_imported); + } + ImportExportCleanup(); +} + +void CertificateManagerHandler::ImportCA(const ListValue* args) { + select_file_dialog_ = SelectFileDialog::Create(this); + ShowCertSelectFileDialog(select_file_dialog_.get(), + SelectFileDialog::SELECT_OPEN_FILE, + FilePath(), + web_ui_->tab_contents(), + GetParentWindow(), + reinterpret_cast<void*>(IMPORT_CA_FILE_SELECTED)); +} + +void CertificateManagerHandler::ImportCAFileSelected(const FilePath& path) { + file_path_ = path; + file_access_provider_->StartRead( + file_path_, + &consumer_, + NewCallback(this, &CertificateManagerHandler::ImportCAFileRead)); +} + +void CertificateManagerHandler::ImportCAFileRead(int read_errno, + std::string data) { + if (read_errno) { + ImportExportCleanup(); + ShowError( + l10n_util::GetStringUTF8(IDS_CERT_MANAGER_CA_IMPORT_ERROR_TITLE), + l10n_util::GetStringFUTF8(IDS_CERT_MANAGER_READ_ERROR_FORMAT, + UTF8ToUTF16(safe_strerror(read_errno)))); + return; + } + + selected_cert_list_ = net::X509Certificate::CreateCertificateListFromBytes( + data.data(), data.size(), net::X509Certificate::FORMAT_AUTO); + if (selected_cert_list_.empty()) { + ImportExportCleanup(); + ShowError( + l10n_util::GetStringUTF8(IDS_CERT_MANAGER_CA_IMPORT_ERROR_TITLE), + l10n_util::GetStringUTF8(IDS_CERT_MANAGER_CERT_PARSE_ERROR)); + return; + } + + scoped_refptr<net::X509Certificate> root_cert = + certificate_manager_model_->cert_db().FindRootInList(selected_cert_list_); + + // TODO(mattm): check here if root_cert is not a CA cert and show error. + + StringValue cert_name(root_cert->subject().GetDisplayName()); + web_ui_->CallJavascriptFunction("CertificateEditCaTrustOverlay.showImport", + cert_name); +} + +void CertificateManagerHandler::ImportCATrustSelected(const ListValue* args) { + bool fail = false; + bool trust_ssl = false; + bool trust_email = false; + bool trust_obj_sign = false; + fail |= !CallbackArgsToBool(args, 0, &trust_ssl); + fail |= !CallbackArgsToBool(args, 1, &trust_email); + fail |= !CallbackArgsToBool(args, 2, &trust_obj_sign); + if (fail) { + LOG(ERROR) << "ImportCATrustSelected args fail"; + ImportExportCleanup(); + web_ui_->CallJavascriptFunction("CertificateEditCaTrustOverlay.dismiss"); + return; + } + + net::CertDatabase::ImportCertFailureList not_imported; + bool result = certificate_manager_model_->ImportCACerts( + selected_cert_list_, + trust_ssl * net::CertDatabase::TRUSTED_SSL + + trust_email * net::CertDatabase::TRUSTED_EMAIL + + trust_obj_sign * net::CertDatabase::TRUSTED_OBJ_SIGN, + ¬_imported); + web_ui_->CallJavascriptFunction("CertificateEditCaTrustOverlay.dismiss"); + if (!result) { + ShowError( + l10n_util::GetStringUTF8(IDS_CERT_MANAGER_CA_IMPORT_ERROR_TITLE), + l10n_util::GetStringUTF8(IDS_CERT_MANAGER_UNKNOWN_ERROR)); + } else if (!not_imported.empty()) { + ShowImportErrors( + l10n_util::GetStringUTF8(IDS_CERT_MANAGER_CA_IMPORT_ERROR_TITLE), + not_imported); + } + ImportExportCleanup(); +} + +void CertificateManagerHandler::Export(const ListValue* args) { + net::X509Certificate* cert = CallbackArgsToCert(args); + if (!cert) + return; + ShowCertExportDialog(web_ui_->tab_contents(), GetParentWindow(), + cert->os_cert_handle()); +} + +void CertificateManagerHandler::Delete(const ListValue* args) { + net::X509Certificate* cert = CallbackArgsToCert(args); + if (!cert) + return; + bool result = certificate_manager_model_->Delete(cert); + if (!result) { + // TODO(mattm): better error messages? + ShowError( + l10n_util::GetStringUTF8(IDS_CERT_MANAGER_DELETE_CERT_ERROR_TITLE), + l10n_util::GetStringUTF8(IDS_CERT_MANAGER_UNKNOWN_ERROR)); + } +} + +void CertificateManagerHandler::Populate(const ListValue* args) { + certificate_manager_model_->Refresh(); +} + +void CertificateManagerHandler::PopulateTree(const std::string& tab_name, + net::CertType type) { + const std::string tree_name = tab_name + "-tree"; + + scoped_ptr<icu::Collator> collator; + UErrorCode error = U_ZERO_ERROR; + collator.reset( + icu::Collator::createInstance( + icu::Locale(g_browser_process->GetApplicationLocale().c_str()), + error)); + if (U_FAILURE(error)) + collator.reset(NULL); + DictionaryIdComparator comparator(collator.get()); + CertificateManagerModel::OrgGroupingMap map; + + certificate_manager_model_->FilterAndBuildOrgGroupingMap(type, &map); + + { + ListValue* nodes = new ListValue; + for (CertificateManagerModel::OrgGroupingMap::iterator i = map.begin(); + i != map.end(); ++i) { + // Populate first level (org name). + DictionaryValue* dict = new DictionaryValue; + dict->SetString(kKeyId, OrgNameToId(i->first)); + dict->SetString(kNameId, i->first); + + // Populate second level (certs). + ListValue* subnodes = new ListValue; + for (net::CertificateList::const_iterator org_cert_it = i->second.begin(); + org_cert_it != i->second.end(); ++org_cert_it) { + DictionaryValue* cert_dict = new DictionaryValue; + net::X509Certificate* cert = org_cert_it->get(); + cert_dict->SetString(kKeyId, CertToId(*cert)); + cert_dict->SetString(kNameId, certificate_manager_model_->GetColumnText( + *cert, CertificateManagerModel::COL_SUBJECT_NAME)); + cert_dict->SetBoolean( + kReadOnlyId, + certificate_manager_model_->cert_db().IsReadOnly(cert)); + cert_dict->SetBoolean( + kUntrustedId, + certificate_manager_model_->cert_db().IsUntrusted(cert)); + // TODO(mattm): Other columns. + subnodes->Append(cert_dict); + } + std::sort(subnodes->begin(), subnodes->end(), comparator); + + dict->Set(kSubNodesId, subnodes); + nodes->Append(dict); + } + std::sort(nodes->begin(), nodes->end(), comparator); + + ListValue args; + args.Append(Value::CreateStringValue(tree_name)); + args.Append(nodes); + web_ui_->CallJavascriptFunction("CertificateManager.onPopulateTree", args); + } +} + +void CertificateManagerHandler::ShowError(const std::string& title, + const std::string& error) const { + ScopedVector<const Value> args; + args.push_back(Value::CreateStringValue(title)); + args.push_back(Value::CreateStringValue(error)); + args.push_back(Value::CreateStringValue(l10n_util::GetStringUTF8(IDS_OK))); + args.push_back(Value::CreateNullValue()); // cancelTitle + args.push_back(Value::CreateNullValue()); // okCallback + args.push_back(Value::CreateNullValue()); // cancelCallback + web_ui_->CallJavascriptFunction("AlertOverlay.show", args.get()); +} + +void CertificateManagerHandler::ShowImportErrors( + const std::string& title, + const net::CertDatabase::ImportCertFailureList& not_imported) const { + std::string error; + if (selected_cert_list_.size() == 1) + error = l10n_util::GetStringUTF8( + IDS_CERT_MANAGER_IMPORT_SINGLE_NOT_IMPORTED); + else if (not_imported.size() == selected_cert_list_.size()) + error = l10n_util::GetStringUTF8(IDS_CERT_MANAGER_IMPORT_ALL_NOT_IMPORTED); + else + error = l10n_util::GetStringUTF8(IDS_CERT_MANAGER_IMPORT_SOME_NOT_IMPORTED); + + ListValue cert_error_list; + for (size_t i = 0; i < not_imported.size(); ++i) { + const net::CertDatabase::ImportCertFailure& failure = not_imported[i]; + DictionaryValue* dict = new DictionaryValue; + dict->SetString(kNameId, failure.certificate->subject().GetDisplayName()); + dict->SetString(kErrorId, NetErrorToString(failure.net_error)); + cert_error_list.Append(dict); + } + + StringValue title_value(title); + StringValue error_value(error); + web_ui_->CallJavascriptFunction("CertificateImportErrorOverlay.show", + title_value, + error_value, + cert_error_list); +} + +#if defined(OS_CHROMEOS) +void CertificateManagerHandler::CheckTpmTokenReady(const ListValue* args) { + chromeos::CryptohomeLibrary* cryptohome = + chromeos::CrosLibrary::Get()->GetCryptohomeLibrary(); + + // TODO(xiyuan): Use async way when underlying supports it. + base::FundamentalValue ready(cryptohome->Pkcs11IsTpmTokenReady()); + web_ui_->CallJavascriptFunction("CertificateManager.onCheckTpmTokenReady", + ready); +} +#endif + +gfx::NativeWindow CertificateManagerHandler::GetParentWindow() const { + return web_ui_->tab_contents()->view()->GetTopLevelNativeWindow(); +} diff --git a/chrome/browser/ui/webui/options2/certificate_manager_handler.h b/chrome/browser/ui/webui/options2/certificate_manager_handler.h new file mode 100644 index 0000000..9844a19 --- /dev/null +++ b/chrome/browser/ui/webui/options2/certificate_manager_handler.h @@ -0,0 +1,171 @@ +// Copyright (c) 2011 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. + +#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS2_CERTIFICATE_MANAGER_HANDLER_H_ +#define CHROME_BROWSER_UI_WEBUI_OPTIONS2_CERTIFICATE_MANAGER_HANDLER_H_ +#pragma once + +#include <string> + +#include "base/compiler_specific.h" +#include "base/memory/scoped_ptr.h" +#include "chrome/browser/certificate_manager_model.h" +#include "chrome/browser/ui/select_file_dialog.h" +#include "chrome/browser/ui/webui/options2/options_ui2.h" +#include "content/browser/cancelable_request.h" +#include "net/base/cert_database.h" +#include "ui/gfx/native_widget_types.h" + +class FileAccessProvider; + +class CertificateManagerHandler : public OptionsPage2UIHandler, + public CertificateManagerModel::Observer, + public SelectFileDialog::Listener { + public: + CertificateManagerHandler(); + virtual ~CertificateManagerHandler(); + + // OptionsPage2UIHandler implementation. + virtual void GetLocalizedValues( + base::DictionaryValue* localized_strings) OVERRIDE; + virtual void RegisterMessages() OVERRIDE; + + // CertificateManagerModel::Observer implementation. + virtual void CertificatesRefreshed() OVERRIDE; + + // SelectFileDialog::Listener implementation. + virtual void FileSelected(const FilePath& path, + int index, + void* params) OVERRIDE; + virtual void FileSelectionCanceled(void* params) OVERRIDE; + + private: + // View certificate. + void View(const base::ListValue* args); + + // Edit server certificate trust values. + void EditServer(const base::ListValue* args); + + // Edit certificate authority trust values. The sequence goes like: + // 1. user clicks edit button -> CertificateEditCaTrustOverlay.show -> + // GetCATrust -> CertificateEditCaTrustOverlay.populateTrust + // 2. user clicks ok -> EditCATrust -> CertificateEditCaTrustOverlay.dismiss + void GetCATrust(const base::ListValue* args); + void EditCATrust(const base::ListValue* args); + + // Cleanup state stored during import or export process. + void CancelImportExportProcess(const base::ListValue* args); + void ImportExportCleanup(); + + // Export to PKCS #12 file. The sequence goes like: + // 1a. user click on export button -> ExportPersonal -> launches file + // selector + // 1b. user click on export all button -> ExportAllPersonal -> launches file + // selector + // 2. user selects file -> ExportPersonalFileSelected -> launches password + // dialog + // 3. user enters password -> ExportPersonalPasswordSelected -> unlock slots + // 4. slots unlocked -> ExportPersonalSlotsUnlocked -> exports to memory + // buffer -> starts async write operation + // 5. write finishes (or fails) -> ExportPersonalFileWritten + void ExportPersonal(const base::ListValue* args); + void ExportAllPersonal(const base::ListValue* args); + void ExportPersonalFileSelected(const FilePath& path); + void ExportPersonalPasswordSelected(const base::ListValue* args); + void ExportPersonalSlotsUnlocked(); + void ExportPersonalFileWritten(int write_errno, int bytes_written); + + // Import from PKCS #12 file. The sequence goes like: + // 1. user click on import button -> StartImportPersonal -> launches file + // selector + // 2. user selects file -> ImportPersonalFileSelected -> launches password + // dialog + // 3. user enters password -> ImportPersonalPasswordSelected -> starts async + // read operation + // 4. read operation completes -> ImportPersonalFileRead -> unlock slot + // 5. slot unlocked -> ImportPersonalSlotUnlocked attempts to + // import with previously entered password + // 6a. if import succeeds -> ImportExportCleanup + // 6b. if import fails -> show error, ImportExportCleanup + // TODO(mattm): allow retrying with different password + void StartImportPersonal(const base::ListValue* args); + void ImportPersonalFileSelected(const FilePath& path); + void ImportPersonalPasswordSelected(const base::ListValue* args); + void ImportPersonalFileRead(int read_errno, std::string data); + void ImportPersonalSlotUnlocked(); + + // Import Server certificates from file. Sequence goes like: + // 1. user clicks on import button -> ImportServer -> launches file selector + // 2. user selects file -> ImportServerFileSelected -> starts async read + // 3. read completes -> ImportServerFileRead -> parse certs -> attempt import + // 4a. if import succeeds -> ImportExportCleanup + // 4b. if import fails -> show error, ImportExportCleanup + void ImportServer(const base::ListValue* args); + void ImportServerFileSelected(const FilePath& path); + void ImportServerFileRead(int read_errno, std::string data); + + // Import Certificate Authorities from file. Sequence goes like: + // 1. user clicks on import button -> ImportCA -> launches file selector + // 2. user selects file -> ImportCAFileSelected -> starts async read + // 3. read completes -> ImportCAFileRead -> parse certs -> + // CertificateEditCaTrustOverlay.showImport + // 4. user clicks ok -> ImportCATrustSelected -> attempt import + // 5a. if import succeeds -> ImportExportCleanup + // 5b. if import fails -> show error, ImportExportCleanup + void ImportCA(const base::ListValue* args); + void ImportCAFileSelected(const FilePath& path); + void ImportCAFileRead(int read_errno, std::string data); + void ImportCATrustSelected(const base::ListValue* args); + + // Export a certificate. + void Export(const base::ListValue* args); + + // Delete certificate and private key (if any). + void Delete(const base::ListValue* args); + + // Populate the trees in all the tabs. + void Populate(const base::ListValue* args); + + // Populate the given tab's tree. + void PopulateTree(const std::string& tab_name, net::CertType type); + + // Display a WebUI error message box. + void ShowError(const std::string& title, const std::string& error) const; + + // Display a WebUI error message box for import failures. + // Depends on |selected_cert_list_| being set to the imports that we + // attempted to import. + void ShowImportErrors( + const std::string& title, + const net::CertDatabase::ImportCertFailureList& not_imported) const; + +#if defined(OS_CHROMEOS) + // Check whether Tpm token is ready and notifiy JS side. + void CheckTpmTokenReady(const base::ListValue* args); +#endif + + gfx::NativeWindow GetParentWindow() const; + + // The Certificates Manager model + scoped_ptr<CertificateManagerModel> certificate_manager_model_; + + // For multi-step import or export processes, we need to store the path, + // password, etc the user chose while we wait for them to enter a password, + // wait for file to be read, etc. + FilePath file_path_; + string16 password_; + bool use_hardware_backed_; + std::string file_data_; + net::CertificateList selected_cert_list_; + scoped_refptr<SelectFileDialog> select_file_dialog_; + scoped_refptr<net::CryptoModule> module_; + + // Used in reading and writing certificate files. + CancelableRequestConsumer consumer_; + scoped_refptr<FileAccessProvider> file_access_provider_; + + DISALLOW_COPY_AND_ASSIGN(CertificateManagerHandler); +}; + +#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS2_CERTIFICATE_MANAGER_HANDLER_H_ diff --git a/chrome/browser/ui/webui/options2/chromeos/about_page_handler.cc b/chrome/browser/ui/webui/options2/chromeos/about_page_handler.cc new file mode 100644 index 0000000..31d960e --- /dev/null +++ b/chrome/browser/ui/webui/options2/chromeos/about_page_handler.cc @@ -0,0 +1,419 @@ +// Copyright (c) 2011 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. + +#include "chrome/browser/ui/webui/options2/chromeos/about_page_handler.h" + +#include <vector> + +#include "base/basictypes.h" +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/command_line.h" +#include "base/i18n/time_formatting.h" +#include "base/string16.h" +#include "base/string_number_conversions.h" +#include "base/time.h" +#include "base/utf_string_conversions.h" +#include "base/values.h" +#include "chrome/browser/chromeos/dbus/dbus_thread_manager.h" +#include "chrome/browser/chromeos/dbus/power_manager_client.h" +#include "chrome/browser/chromeos/dbus/update_engine_client.h" +#include "chrome/browser/chromeos/login/user_manager.h" +#include "chrome/browser/chromeos/login/wizard_controller.h" +#include "chrome/browser/google/google_util.h" +#include "chrome/common/chrome_version_info.h" +#include "chrome/common/url_constants.h" +#include "content/public/common/content_client.h" +#include "googleurl/src/gurl.h" +#include "grit/chromium_strings.h" +#include "grit/generated_resources.h" +#include "grit/locale_settings.h" +#include "grit/theme_resources.h" +#include "ui/base/l10n/l10n_util.h" +#include "ui/base/resource/resource_bundle.h" +#include "v8/include/v8.h" +#include "webkit/glue/user_agent.h" +#include "webkit/glue/webkit_glue.h" +#include "webkit/glue/user_agent.h" + +namespace { + +// These are used as placeholder text around the links in the text in the +// license. +const char kBeginLink[] = "BEGIN_LINK"; +const char kEndLink[] = "END_LINK"; +const char kBeginLinkChr[] = "BEGIN_LINK_CHR"; +const char kBeginLinkOss[] = "BEGIN_LINK_OSS"; +const char kEndLinkChr[] = "END_LINK_CHR"; +const char kEndLinkOss[] = "END_LINK_OSS"; +const char kBeginLinkCrosOss[] = "BEGIN_LINK_CROS_OSS"; +const char kEndLinkCrosOss[] = "END_LINK_CROS_OSS"; + +// Returns a substring [start, end) from |text|. +std::string StringSubRange(const std::string& text, size_t start, + size_t end) { + DCHECK(end > start); + return text.substr(start, end - start); +} + +} // namespace + +namespace chromeos { + +class AboutPageHandler::UpdateObserver + : public UpdateEngineClient::Observer { + public: + explicit UpdateObserver(AboutPageHandler* handler) : page_handler_(handler) {} + virtual ~UpdateObserver() {} + + AboutPageHandler* page_handler() const { return page_handler_; } + + private: + virtual void UpdateStatusChanged( + const UpdateEngineClient::Status& status) OVERRIDE { + page_handler_->UpdateStatus(status); + } + + AboutPageHandler* page_handler_; + + DISALLOW_COPY_AND_ASSIGN(UpdateObserver); +}; + +AboutPageHandler::AboutPageHandler() + : progress_(-1), + sticky_(false), + started_(false) +{} + +AboutPageHandler::~AboutPageHandler() { + if (update_observer_.get()) { + DBusThreadManager::Get()->GetUpdateEngineClient()-> + RemoveObserver(update_observer_.get()); + } +} + +void AboutPageHandler::GetLocalizedValues(DictionaryValue* localized_strings) { + DCHECK(localized_strings); + + static OptionsStringResource resources[] = { + { "firmware", IDS_ABOUT_PAGE_FIRMWARE }, + { "product", IDS_PRODUCT_OS_NAME }, + { "os", IDS_PRODUCT_OS_NAME }, + { "platform", IDS_PLATFORM_LABEL }, + { "loading", IDS_ABOUT_PAGE_LOADING }, + { "check_now", IDS_ABOUT_PAGE_CHECK_NOW }, + { "update_status", IDS_UPGRADE_CHECK_STARTED }, + { "restart_now", IDS_RELAUNCH_AND_UPDATE }, + { "browser", IDS_PRODUCT_NAME }, + { "more_info", IDS_ABOUT_PAGE_MORE_INFO }, + { "copyright", IDS_ABOUT_VERSION_COPYRIGHT }, + { "channel", IDS_ABOUT_PAGE_CHANNEL }, + { "stable", IDS_ABOUT_PAGE_CHANNEL_STABLE }, + { "beta", IDS_ABOUT_PAGE_CHANNEL_BETA }, + { "dev", IDS_ABOUT_PAGE_CHANNEL_DEVELOPMENT }, + { "canary", IDS_ABOUT_PAGE_CHANNEL_CANARY }, + { "channel_warning_header", IDS_ABOUT_PAGE_CHANNEL_WARNING_HEADER }, + { "channel_warning_text", IDS_ABOUT_PAGE_CHANNEL_WARNING_TEXT }, + { "user_agent", IDS_ABOUT_VERSION_USER_AGENT }, + { "command_line", IDS_ABOUT_VERSION_COMMAND_LINE }, + }; + + RegisterStrings(localized_strings, resources, arraysize(resources)); + RegisterTitle(localized_strings, "aboutPage", IDS_ABOUT_TAB_TITLE); + + // browser version + + chrome::VersionInfo version_info; + DCHECK(version_info.is_valid()); + + std::string browser_version = version_info.Version(); + std::string version_modifier = + chrome::VersionInfo::GetVersionStringModifier(); + if (!version_modifier.empty()) + browser_version += " " + version_modifier; + +#if !defined(GOOGLE_CHROME_BUILD) + browser_version += " ("; + browser_version += version_info.LastChange(); + browser_version += ")"; +#endif + + localized_strings->SetString("browser_version", browser_version); + + // license + + std::string text = l10n_util::GetStringUTF8(IDS_ABOUT_VERSION_LICENSE); + + bool chromium_url_appears_first = + text.find(kBeginLinkChr) < text.find(kBeginLinkOss); + + size_t link1 = text.find(kBeginLink); + DCHECK(link1 != std::string::npos); + size_t link1_end = text.find(kEndLink, link1); + DCHECK(link1_end != std::string::npos); + size_t link2 = text.find(kBeginLink, link1_end); + DCHECK(link2 != std::string::npos); + size_t link2_end = text.find(kEndLink, link2); + DCHECK(link2_end != std::string::npos); + + localized_strings->SetString("license_content_0", text.substr(0, link1)); + localized_strings->SetString("license_content_1", + StringSubRange(text, link1_end + strlen(kEndLinkOss), link2)); + localized_strings->SetString("license_content_2", + text.substr(link2_end + strlen(kEndLinkOss))); + + // The Chromium link within the main text of the dialog. + localized_strings->SetString(chromium_url_appears_first ? + "license_link_content_0" : "license_link_content_1", + StringSubRange(text, + text.find(kBeginLinkChr) + strlen(kBeginLinkChr), + text.find(kEndLinkChr))); + GURL url = google_util::AppendGoogleLocaleParam( + GURL(chrome::kChromiumProjectURL)); + localized_strings->SetString(chromium_url_appears_first ? + "license_link_0" : "license_link_1", url.spec()); + + // The Open Source link within the main text of the dialog. + localized_strings->SetString(chromium_url_appears_first ? + "license_link_content_1" : "license_link_content_0", + StringSubRange(text, + text.find(kBeginLinkOss) + strlen(kBeginLinkOss), + text.find(kEndLinkOss))); + localized_strings->SetString(chromium_url_appears_first ? + "license_link_1" : "license_link_0", chrome::kChromeUICreditsURL); + + std::string cros_text = + l10n_util::GetStringUTF8(IDS_ABOUT_CROS_VERSION_LICENSE); + + size_t cros_link = cros_text.find(kBeginLinkCrosOss); + DCHECK(cros_link != std::string::npos); + size_t cros_link_end = cros_text.find(kEndLinkCrosOss, cros_link); + DCHECK(cros_link_end != std::string::npos); + + localized_strings->SetString("cros_license_content_0", + cros_text.substr(0, cros_link)); + localized_strings->SetString("cros_license_content_1", + cros_text.substr(cros_link_end + strlen(kEndLinkCrosOss))); + localized_strings->SetString("cros_license_link_content_0", + StringSubRange(cros_text, cros_link + strlen(kBeginLinkCrosOss), + cros_link_end)); + localized_strings->SetString("cros_license_link_0", + chrome::kChromeUIOSCreditsURL); + + // webkit + + localized_strings->SetString("webkit_version", + webkit_glue::GetWebKitVersion()); + + // javascript + + localized_strings->SetString("js_engine", "V8"); + localized_strings->SetString("js_engine_version", v8::V8::GetVersion()); + + // user agent + + localized_strings->SetString("user_agent_info", + content::GetUserAgent(GURL())); + + // command line + +#if defined(OS_WIN) + localized_strings->SetString("command_line_info", + WideToUTF16(CommandLine::ForCurrentProcess()->GetCommandLineString())); +#elif defined(OS_POSIX) + // TODO(viettrungluu): something horrible might happen if there are non-UTF-8 + // arguments (since |SetString()| requires Unicode). + std::string command_line = ""; + typedef std::vector<std::string> ArgvList; + const ArgvList& argv = CommandLine::ForCurrentProcess()->argv(); + for (ArgvList::const_iterator iter = argv.begin(); iter != argv.end(); iter++) + command_line += " " + *iter; + localized_strings->SetString("command_line_info", command_line); +#endif +} + +void AboutPageHandler::RegisterMessages() { + web_ui_->RegisterMessageCallback("PageReady", + base::Bind(&AboutPageHandler::PageReady, base::Unretained(this))); + web_ui_->RegisterMessageCallback("SetReleaseTrack", + base::Bind(&AboutPageHandler::SetReleaseTrack, base::Unretained(this))); + + web_ui_->RegisterMessageCallback("CheckNow", + base::Bind(&AboutPageHandler::CheckNow, base::Unretained(this))); + web_ui_->RegisterMessageCallback("RestartNow", + base::Bind(&AboutPageHandler::RestartNow, base::Unretained(this))); +} + +void AboutPageHandler::PageReady(const ListValue* args) { + // Version information is loaded from a callback + loader_.GetVersion(&consumer_, + base::Bind(&AboutPageHandler::OnOSVersion, + base::Unretained(this)), + VersionLoader::VERSION_FULL); + loader_.GetFirmware(&consumer_, + base::Bind(&AboutPageHandler::OnOSFirmware, + base::Unretained(this))); + + UpdateEngineClient* update_engine_client = + DBusThreadManager::Get()->GetUpdateEngineClient(); + + update_observer_.reset(new UpdateObserver(this)); + update_engine_client->AddObserver(update_observer_.get()); + + // Update the WebUI page with the current status. See comments below. + UpdateStatus(update_engine_client->GetLastStatus()); + + // Initiate update check. UpdateStatus() below will be called when we + // get update status via update_observer_. If the update has been + // already complete, update_observer_ won't receive a notification. + // This is why we manually update the WebUI page above. + CheckNow(NULL); + + // Request the channel information. Use the observer to track the about + // page handler and ensure it does not get deleted before the callback. + update_engine_client->GetReleaseTrack( + base::Bind(UpdateSelectedChannel, update_observer_.get())); +} + +void AboutPageHandler::SetReleaseTrack(const ListValue* args) { + if (!UserManager::Get()->current_user_is_owner()) { + LOG(WARNING) << "Non-owner tried to change release track."; + return; + } + const std::string channel = UTF16ToUTF8(ExtractStringValue(args)); + DBusThreadManager::Get()->GetUpdateEngineClient()->SetReleaseTrack(channel); +} + +void AboutPageHandler::CheckNow(const ListValue* args) { + // Make sure that libcros is loaded and OOBE is complete. + if (!WizardController::default_controller() || + WizardController::IsDeviceRegistered()) { + DBusThreadManager::Get()->GetUpdateEngineClient()-> + RequestUpdateCheck(UpdateEngineClient::EmptyUpdateCheckCallback()); + } +} + +void AboutPageHandler::RestartNow(const ListValue* args) { + DBusThreadManager::Get()->GetPowerManagerClient()->RequestRestart(); +} + +void AboutPageHandler::UpdateStatus( + const UpdateEngineClient::Status& status) { + string16 message; + std::string image = "up-to-date"; + bool enabled = false; + + switch (status.status) { + case UpdateEngineClient::UPDATE_STATUS_IDLE: + if (!sticky_) { + message = l10n_util::GetStringFUTF16(IDS_UPGRADE_ALREADY_UP_TO_DATE, + l10n_util::GetStringUTF16(IDS_PRODUCT_OS_NAME)); + enabled = true; + } + break; + case UpdateEngineClient::UPDATE_STATUS_CHECKING_FOR_UPDATE: + message = l10n_util::GetStringUTF16(IDS_UPGRADE_CHECK_STARTED); + sticky_ = false; + break; + case UpdateEngineClient::UPDATE_STATUS_UPDATE_AVAILABLE: + message = l10n_util::GetStringUTF16(IDS_UPDATE_AVAILABLE); + started_ = true; + break; + case UpdateEngineClient::UPDATE_STATUS_DOWNLOADING: + { + int progress = static_cast<int>(status.download_progress * 100.0); + if (progress != progress_) { + progress_ = progress; + message = l10n_util::GetStringFUTF16Int(IDS_UPDATE_DOWNLOADING, + progress_); + } + started_ = true; + } + break; + case UpdateEngineClient::UPDATE_STATUS_VERIFYING: + message = l10n_util::GetStringUTF16(IDS_UPDATE_VERIFYING); + started_ = true; + break; + case UpdateEngineClient::UPDATE_STATUS_FINALIZING: + message = l10n_util::GetStringUTF16(IDS_UPDATE_FINALIZING); + started_ = true; + break; + case UpdateEngineClient::UPDATE_STATUS_UPDATED_NEED_REBOOT: + message = l10n_util::GetStringUTF16(IDS_UPDATE_COMPLETED); + image = "available"; + sticky_ = true; + break; + default: + // case UpdateEngineClient::UPDATE_STATUS_ERROR: + // case UpdateEngineClient::UPDATE_STATUS_REPORTING_ERROR_EVENT: + + // The error is only displayed if we were able to determine an + // update was available. + if (started_) { + message = l10n_util::GetStringUTF16(IDS_UPDATE_ERROR); + image = "fail"; + enabled = true; + sticky_ = true; + started_ = false; + } + break; + } + if (message.size()) { + scoped_ptr<Value> update_message(Value::CreateStringValue(message)); + // "Checking for update..." needs to be shown for a while, so users + // can read it, hence insert delay for this. + scoped_ptr<Value> insert_delay(Value::CreateBooleanValue( + status.status == + UpdateEngineClient::UPDATE_STATUS_CHECKING_FOR_UPDATE)); + web_ui_->CallJavascriptFunction("AboutPage.updateStatusCallback", + *update_message, *insert_delay); + + scoped_ptr<Value> enabled_value(Value::CreateBooleanValue(enabled)); + web_ui_->CallJavascriptFunction("AboutPage.updateEnableCallback", + *enabled_value); + + scoped_ptr<Value> image_string(Value::CreateStringValue(image)); + web_ui_->CallJavascriptFunction("AboutPage.setUpdateImage", + *image_string); + } + // We'll change the "Check For Update" button to "Restart" button. + if (status.status == UpdateEngineClient::UPDATE_STATUS_UPDATED_NEED_REBOOT) { + web_ui_->CallJavascriptFunction("AboutPage.changeToRestartButton"); + } +} + +void AboutPageHandler::OnOSVersion(VersionLoader::Handle handle, + std::string version) { + if (version.size()) { + scoped_ptr<Value> version_string(Value::CreateStringValue(version)); + web_ui_->CallJavascriptFunction("AboutPage.updateOSVersionCallback", + *version_string); + } +} + +void AboutPageHandler::OnOSFirmware(VersionLoader::Handle handle, + std::string firmware) { + if (firmware.size()) { + scoped_ptr<Value> firmware_string(Value::CreateStringValue(firmware)); + web_ui_->CallJavascriptFunction("AboutPage.updateOSFirmwareCallback", + *firmware_string); + } +} + +// Callback from UpdateEngine with channel information. +// static +void AboutPageHandler::UpdateSelectedChannel(UpdateObserver* observer, + const std::string& channel) { + if (DBusThreadManager::Get()->GetUpdateEngineClient() + ->HasObserver(observer)) { + // If UpdateEngineClient still has the observer, then the page handler + // is valid. + AboutPageHandler* handler = observer->page_handler(); + scoped_ptr<Value> channel_string(Value::CreateStringValue(channel)); + handler->web_ui_->CallJavascriptFunction( + "AboutPage.updateSelectedOptionCallback", *channel_string); + } +} + +} // namespace chromeos diff --git a/chrome/browser/ui/webui/options2/chromeos/about_page_handler.h b/chrome/browser/ui/webui/options2/chromeos/about_page_handler.h new file mode 100644 index 0000000..cfd78b0 --- /dev/null +++ b/chrome/browser/ui/webui/options2/chromeos/about_page_handler.h @@ -0,0 +1,74 @@ +// Copyright (c) 2011 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. + +#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_ABOUT_PAGE_HANDLER_H_ +#define CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_ABOUT_PAGE_HANDLER_H_ + +#include <string> + +#include "base/compiler_specific.h" +#include "chrome/browser/ui/webui/options2/options_ui2.h" +#include "chrome/browser/chromeos/dbus/update_engine_client.h" +#include "chrome/browser/chromeos/version_loader.h" + +namespace chromeos { + +// ChromeOS about page UI handler. +class AboutPageHandler : public OptionsPage2UIHandler { + + public: + AboutPageHandler(); + virtual ~AboutPageHandler(); + + // OptionsPage2UIHandler implementation. + virtual void GetLocalizedValues( + base::DictionaryValue* localized_strings) OVERRIDE; + virtual void RegisterMessages() OVERRIDE; + + private: + class UpdateObserver; + + // The function is called from JavaScript when the about page is ready. + void PageReady(const base::ListValue* args); + + // The function is called from JavaScript to set the release track like + // "beta-channel" and "dev-channel". + void SetReleaseTrack(const base::ListValue* args); + + // Initiates update check. + void CheckNow(const base::ListValue* args); + + // Restarts the system. + void RestartNow(const base::ListValue* args); + + // Callback from VersionLoader giving the version. + void OnOSVersion(VersionLoader::Handle handle, + std::string version); + void OnOSFirmware(VersionLoader::Handle handle, + std::string firmware); + void UpdateStatus(const UpdateEngineClient::Status& status); + + // UpdateEngine Callback handler. + static void UpdateSelectedChannel(UpdateObserver* observer, + const std::string& channel); + + // Handles asynchronously loading the version. + VersionLoader loader_; + + // Used to request the version. + CancelableRequestConsumer consumer_; + + // Update Observer + scoped_ptr<UpdateObserver> update_observer_; + + int progress_; + bool sticky_; + bool started_; + + DISALLOW_COPY_AND_ASSIGN(AboutPageHandler); +}; + +} // namespace chromeos + +#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_ABOUT_PAGE_HANDLER_H_ diff --git a/chrome/browser/ui/webui/options2/chromeos/accounts_options_handler.cc b/chrome/browser/ui/webui/options2/chromeos/accounts_options_handler.cc new file mode 100644 index 0000000..396dcec --- /dev/null +++ b/chrome/browser/ui/webui/options2/chromeos/accounts_options_handler.cc @@ -0,0 +1,132 @@ +// Copyright (c) 2011 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. + +#include "chrome/browser/ui/webui/options2/chromeos/accounts_options_handler.h" + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/json/json_reader.h" +#include "base/memory/scoped_ptr.h" +#include "base/utf_string_conversions.h" +#include "base/values.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/chromeos/cros_settings.h" +#include "chrome/browser/chromeos/cros_settings_names.h" +#include "chrome/browser/chromeos/login/authenticator.h" +#include "chrome/browser/chromeos/login/user_manager.h" +#include "chrome/browser/prefs/pref_service.h" +#include "chrome/browser/policy/browser_policy_connector.h" +#include "grit/generated_resources.h" +#include "ui/base/l10n/l10n_util.h" + +namespace chromeos { + +namespace { + +// Adds specified user to the whitelist. Returns false if that user is already +// in the whitelist. +bool WhitelistUser(const std::string& username) { + CrosSettings* cros_settings = CrosSettings::Get(); + if (cros_settings->FindEmailInList(kAccountsPrefUsers, username)) + return false; + base::StringValue username_value(username); + cros_settings->AppendToList(kAccountsPrefUsers, &username_value); + return true; +} + +} // namespace + +AccountsOptionsHandler::AccountsOptionsHandler() { +} + +AccountsOptionsHandler::~AccountsOptionsHandler() { +} + +void AccountsOptionsHandler::RegisterMessages() { + DCHECK(web_ui_); + web_ui_->RegisterMessageCallback("whitelistUser", + base::Bind(&AccountsOptionsHandler::HandleWhitelistUser, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("unwhitelistUser", + base::Bind(&AccountsOptionsHandler::HandleUnwhitelistUser, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("whitelistExistingUsers", + base::Bind(&AccountsOptionsHandler::HandleWhitelistExistingUsers, + base::Unretained(this))); +} + +void AccountsOptionsHandler::GetLocalizedValues( + base::DictionaryValue* localized_strings) { + DCHECK(localized_strings); + + RegisterTitle(localized_strings, "accountsPage", + IDS_OPTIONS_ACCOUNTS_TAB_LABEL); + + localized_strings->SetString("allow_BWSI", l10n_util::GetStringUTF16( + IDS_OPTIONS_ACCOUNTS_ALLOW_BWSI_DESCRIPTION)); + localized_strings->SetString("use_whitelist",l10n_util::GetStringUTF16( + IDS_OPTIONS_ACCOUNTS_USE_WHITELIST_DESCRIPTION)); + localized_strings->SetString("show_user_on_signin",l10n_util::GetStringUTF16( + IDS_OPTIONS_ACCOUNTS_SHOW_USER_NAMES_ON_SINGIN_DESCRIPTION)); + localized_strings->SetString("username_edit_hint",l10n_util::GetStringUTF16( + IDS_OPTIONS_ACCOUNTS_USERNAME_EDIT_HINT)); + localized_strings->SetString("username_format",l10n_util::GetStringUTF16( + IDS_OPTIONS_ACCOUNTS_USERNAME_FORMAT)); + localized_strings->SetString("add_users",l10n_util::GetStringUTF16( + IDS_OPTIONS_ACCOUNTS_ADD_USERS)); + localized_strings->SetString("owner_only", l10n_util::GetStringUTF16( + IDS_OPTIONS_ACCOUNTS_OWNER_ONLY)); + + std::string owner_email; + CrosSettings::Get()->GetString(kDeviceOwner, &owner_email); + // Translate owner's email to the display email. + std::string display_email = + UserManager::Get()->GetUserDisplayEmail(owner_email); + localized_strings->SetString("owner_user_id", UTF8ToUTF16(display_email)); + + localized_strings->SetString("current_user_is_owner", + UserManager::Get()->current_user_is_owner() ? + ASCIIToUTF16("true") : ASCIIToUTF16("false")); + localized_strings->SetString("logged_in_as_guest", + UserManager::Get()->IsLoggedInAsGuest() ? + ASCIIToUTF16("true") : ASCIIToUTF16("false")); + localized_strings->SetString("whitelist_is_managed", + g_browser_process->browser_policy_connector()->IsEnterpriseManaged() ? + ASCIIToUTF16("true") : ASCIIToUTF16("false")); +} + +void AccountsOptionsHandler::HandleWhitelistUser(const base::ListValue* args) { + std::string typed_email; + std::string name; + if (!args->GetString(0, &typed_email) || + !args->GetString(1, &name)) { + return; + } + + WhitelistUser(Authenticator::Canonicalize(typed_email)); +} + +void AccountsOptionsHandler::HandleUnwhitelistUser( + const base::ListValue* args) { + std::string email; + if (!args->GetString(0, &email)) { + return; + } + + base::StringValue canonical_email(Authenticator::Canonicalize(email)); + CrosSettings::Get()->RemoveFromList(kAccountsPrefUsers, &canonical_email); + UserManager::Get()->RemoveUser(email, NULL); +} + +void AccountsOptionsHandler::HandleWhitelistExistingUsers( + const base::ListValue* args) { + DCHECK(args && args->empty()); + + const UserList& users = UserManager::Get()->GetUsers(); + for (UserList::const_iterator it = users.begin(); it < users.end(); ++it) { + WhitelistUser((*it)->email()); + } +} + +} // namespace chromeos diff --git a/chrome/browser/ui/webui/options2/chromeos/accounts_options_handler.h b/chrome/browser/ui/webui/options2/chromeos/accounts_options_handler.h new file mode 100644 index 0000000..f16c5ba --- /dev/null +++ b/chrome/browser/ui/webui/options2/chromeos/accounts_options_handler.h @@ -0,0 +1,40 @@ +// Copyright (c) 2011 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. + +#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_ACCOUNTS_OPTIONS_HANDLER_H_ +#define CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_ACCOUNTS_OPTIONS_HANDLER_H_ +#pragma once + +#include "base/compiler_specific.h" +#include "chrome/browser/ui/webui/options2/options_ui2.h" + +namespace chromeos { + +// ChromeOS accounts options page handler. +class AccountsOptionsHandler : public OptionsPage2UIHandler { + public: + AccountsOptionsHandler(); + virtual ~AccountsOptionsHandler(); + + // WebUIMessageHandler implementation. + virtual void RegisterMessages() OVERRIDE; + + // OptionsPage2UIHandler implementation. + virtual void GetLocalizedValues( + base::DictionaryValue* localized_strings) OVERRIDE; + + private: + // Javascript callbacks to whitelist/unwhitelist user. + void HandleWhitelistUser(const base::ListValue* args); + void HandleUnwhitelistUser(const base::ListValue* args); + + // Javascript callback to auto add existing users to white list. + void HandleWhitelistExistingUsers(const base::ListValue* args); + + DISALLOW_COPY_AND_ASSIGN(AccountsOptionsHandler); +}; + +} // namespace chromeos + +#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_ACCOUNTS_OPTIONS_HANDLER_H_ diff --git a/chrome/browser/ui/webui/options2/chromeos/bluetooth_options_handler.cc b/chrome/browser/ui/webui/options2/chromeos/bluetooth_options_handler.cc new file mode 100644 index 0000000..f8f6a28 --- /dev/null +++ b/chrome/browser/ui/webui/options2/chromeos/bluetooth_options_handler.cc @@ -0,0 +1,411 @@ +// Copyright (c) 2011 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. + +#include "chrome/browser/ui/webui/options2/chromeos/bluetooth_options_handler.h" + +#include "base/bind.h" +#include "base/command_line.h" +#include "base/utf_string_conversions.h" +#include "base/values.h" +#include "chrome/browser/chromeos/bluetooth/bluetooth_device.h" +#include "chrome/browser/chromeos/system/runtime_environment.h" +#include "chrome/browser/ui/webui/options2/chromeos/system_settings_provider.h" +#include "chrome/common/chrome_switches.h" +#include "grit/chromium_strings.h" +#include "grit/generated_resources.h" +#include "third_party/cros_system_api/dbus/service_constants.h" +#include "ui/base/l10n/l10n_util.h" + +namespace { + +// |UpdateDeviceCallback| takes a variable length list as an argument. The +// value stored in each list element is indicated by the following constants. +const int kUpdateDeviceAddressIndex = 0; +const int kUpdateDeviceCommandIndex = 1; +const int kUpdateDevicePasskeyIndex = 2; + +} // namespace + +namespace chromeos { + +BluetoothOptionsHandler::BluetoothOptionsHandler() { +} + +BluetoothOptionsHandler::~BluetoothOptionsHandler() { + if (!CommandLine::ForCurrentProcess() + ->HasSwitch(switches::kEnableBluetooth)) { + return; + } + + chromeos::BluetoothManager* bluetooth_manager = + chromeos::BluetoothManager::GetInstance(); + DCHECK(bluetooth_manager); + + chromeos::BluetoothAdapter* default_adapter = + bluetooth_manager->DefaultAdapter(); + + if (default_adapter != NULL) { + default_adapter->RemoveObserver(this); + } + + bluetooth_manager->RemoveObserver(this); +} + +void BluetoothOptionsHandler::GetLocalizedValues( + DictionaryValue* localized_strings) { + DCHECK(localized_strings); + + localized_strings->SetString("bluetooth", + l10n_util::GetStringUTF16(IDS_OPTIONS_SETTINGS_SECTION_TITLE_BLUETOOTH)); + localized_strings->SetString("disableBluetooth", + l10n_util::GetStringUTF16(IDS_OPTIONS_SETTINGS_BLUETOOTH_DISABLE)); + localized_strings->SetString("enableBluetooth", + l10n_util::GetStringUTF16(IDS_OPTIONS_SETTINGS_BLUETOOTH_ENABLE)); + localized_strings->SetString("noBluetoothDevicesFound", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_NO_BLUETOOTH_DEVICES_FOUND)); + localized_strings->SetString("findBluetoothDevices", + l10n_util::GetStringUTF16(IDS_OPTIONS_SETTINGS_FIND_BLUETOOTH_DEVICES)); + localized_strings->SetString("bluetoothScanning", + l10n_util::GetStringUTF16(IDS_OPTIONS_SETTINGS_BLUETOOTH_SCANNING)); + localized_strings->SetString("bluetoothDeviceConnected", + l10n_util::GetStringUTF16(IDS_OPTIONS_SETTINGS_BLUETOOTH_CONNECTED)); + localized_strings->SetString("bluetoothDeviceConnecting", + l10n_util::GetStringUTF16(IDS_OPTIONS_SETTINGS_BLUETOOTH_CONNECTING)); + localized_strings->SetString("bluetoothDeviceNotPaired", + l10n_util::GetStringUTF16(IDS_OPTIONS_SETTINGS_BLUETOOTH_NOT_PAIRED)); + localized_strings->SetString("bluetoothDevicePaired", + l10n_util::GetStringUTF16(IDS_OPTIONS_SETTINGS_BLUETOOTH_PAIRED)); + localized_strings->SetString("bluetoothDeviceFailedPairing", + l10n_util::GetStringUTF16(IDS_OPTIONS_SETTINGS_BLUETOOTH_FAILED_PAIRING)); + localized_strings->SetString("bluetoothConnectDevice", + l10n_util::GetStringUTF16(IDS_OPTIONS_SETTINGS_BLUETOOTH_CONNECT)); + localized_strings->SetString("bluetoothDisconnectDevice", + l10n_util::GetStringUTF16(IDS_OPTIONS_SETTINGS_BLUETOOTH_DISCONNECT)); + localized_strings->SetString("bluetoothForgetDevice", + l10n_util::GetStringUTF16(IDS_OPTIONS_SETTINGS_BLUETOOTH_FORGET)); + localized_strings->SetString("bluetoothCancel", + l10n_util::GetStringUTF16(IDS_OPTIONS_SETTINGS_BLUETOOTH_CANCEL)); + localized_strings->SetString("bluetoothAcceptPasskey", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_BLUETOOTH_ACCEPT_PASSKEY)); + localized_strings->SetString("bluetoothRejectPasskey", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_BLUETOOTH_REJECT_PASSKEY)); + localized_strings->SetString("bluetoothConfirmPasskey", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_BLUETOOTH_CONFIRM_PASSKEY_REQUEST)); + localized_strings->SetString("bluetoothEnterPasskey", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_BLUETOOTH_ENTER_PASSKEY_REQUEST)); + localized_strings->SetString("bluetoothRemotePasskey", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_BLUETOOTH_REMOTE_PASSKEY_REQUEST)); + localized_strings->SetString("bluetoothFailedPairingInstructions", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_BLUETOOTH_FAILED_PAIRING_INSTRUCTIONS)); +} + +void BluetoothOptionsHandler::Initialize() { + DCHECK(web_ui_); + // Bluetooth support is a work in progress. Supress the feature unless + // explicitly enabled via a command line flag. + if (!CommandLine::ForCurrentProcess() + ->HasSwitch(switches::kEnableBluetooth)) { + return; + } + + web_ui_->CallJavascriptFunction( + "options.SystemOptions.showBluetoothSettings"); + + // TODO(kevers): Determine whether bluetooth adapter is powered. + bool bluetooth_on = false; + base::FundamentalValue checked(bluetooth_on); + web_ui_->CallJavascriptFunction( + "options.SystemOptions.setBluetoothState", checked); + + chromeos::BluetoothManager* bluetooth_manager = + chromeos::BluetoothManager::GetInstance(); + DCHECK(bluetooth_manager); + bluetooth_manager->AddObserver(this); + + chromeos::BluetoothAdapter* default_adapter = + bluetooth_manager->DefaultAdapter(); + DefaultAdapterChanged(default_adapter); +} + +void BluetoothOptionsHandler::RegisterMessages() { + DCHECK(web_ui_); + web_ui_->RegisterMessageCallback("bluetoothEnableChange", + base::Bind(&BluetoothOptionsHandler::EnableChangeCallback, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("findBluetoothDevices", + base::Bind(&BluetoothOptionsHandler::FindDevicesCallback, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("updateBluetoothDevice", + base::Bind(&BluetoothOptionsHandler::UpdateDeviceCallback, + base::Unretained(this))); +} + +void BluetoothOptionsHandler::EnableChangeCallback( + const ListValue* args) { + bool bluetooth_enabled; + args->GetBoolean(0, &bluetooth_enabled); + // TODO(kevers): Call Bluetooth API to enable or disable. + base::FundamentalValue checked(bluetooth_enabled); + web_ui_->CallJavascriptFunction( + "options.SystemOptions.setBluetoothState", checked); +} + +void BluetoothOptionsHandler::FindDevicesCallback( + const ListValue* args) { + // We only initiate a scan if we're running on Chrome OS. Otherwise, we + // generate a fake device list. + if (!chromeos::system::runtime_environment::IsRunningOnChromeOS()) { + GenerateFakeDeviceList(); + return; + } + + chromeos::BluetoothManager* bluetooth_manager = + chromeos::BluetoothManager::GetInstance(); + DCHECK(bluetooth_manager); + + chromeos::BluetoothAdapter* default_adapter = + bluetooth_manager->DefaultAdapter(); + + ValidateDefaultAdapter(default_adapter); + + if (default_adapter == NULL) { + VLOG(1) << "FindDevicesCallback: no default adapter"; + return; + } + + default_adapter->StartDiscovery(); +} + +void BluetoothOptionsHandler::UpdateDeviceCallback( + const ListValue* args) { + // TODO(kevers): Trigger connect/disconnect. + int size = args->GetSize(); + std::string address; + std::string command; + args->GetString(kUpdateDeviceAddressIndex, &address); + args->GetString(kUpdateDeviceCommandIndex, &command); + if (size > kUpdateDevicePasskeyIndex) { + // Passkey confirmation as part of the pairing process. + std::string passkey; + args->GetString(kUpdateDevicePasskeyIndex, &passkey); + DVLOG(1) << "UpdateDeviceCallback: " << address << ": " << command + << " [" << passkey << "]"; + } else { + // Initiating a device connection or disconnecting + DVLOG(1) << "UpdateDeviceCallback: " << address << ": " << command; + } +} + +void BluetoothOptionsHandler::SendDeviceNotification( + chromeos::BluetoothDevice* device, + base::DictionaryValue* params) { + // Retrieve properties of the bluetooth device. The properties names are + // in title case. Convert to camel case in accordance with our Javascript + // naming convention. + const DictionaryValue& properties = device->AsDictionary(); + base::DictionaryValue js_properties; + for (DictionaryValue::key_iterator it = properties.begin_keys(); + it != properties.end_keys(); ++it) { + base::Value* child = NULL; + properties.GetWithoutPathExpansion(*it, &child); + if (child) { + std::string js_key = *it; + js_key[0] = tolower(js_key[0]); + js_properties.SetWithoutPathExpansion(js_key, child->DeepCopy()); + } + } + if (params) { + js_properties.MergeDictionary(params); + } + web_ui_->CallJavascriptFunction( + "options.SystemOptions.addBluetoothDevice", + js_properties); +} + +void BluetoothOptionsHandler::RequestConfirmation( + chromeos::BluetoothDevice* device, + int passkey) { + DictionaryValue params; + params.SetString("pairing", "bluetoothConfirmPasskey"); + params.SetInteger("passkey", passkey); + SendDeviceNotification(device, ¶ms); +} + +void BluetoothOptionsHandler::DisplayPasskey( + chromeos::BluetoothDevice* device, + int passkey, + int entered) { + DictionaryValue params; + params.SetString("pairing", "bluetoothRemotePasskey"); + params.SetInteger("passkey", passkey); + params.SetInteger("entered", entered); + SendDeviceNotification(device, ¶ms); +} + +void BluetoothOptionsHandler::RequestPasskey( + chromeos::BluetoothDevice* device) { + DictionaryValue params; + params.SetString("pairing", "bluetoothEnterPasskey"); + SendDeviceNotification(device, ¶ms); +} + +void BluetoothOptionsHandler::ValidatePasskeyCallback( + const base::ListValue* args) { + // TODO(kevers): Implement me. +} + +void BluetoothOptionsHandler::DefaultAdapterChanged( + chromeos::BluetoothAdapter* adapter) { + std::string old_default_adapter_id = default_adapter_id_; + + if (adapter == NULL) { + default_adapter_id_.clear(); + VLOG(2) << "DefaultAdapterChanged: no default bluetooth adapter"; + } else { + default_adapter_id_ = adapter->Id(); + VLOG(2) << "DefaultAdapterChanged: " << default_adapter_id_; + } + + if (default_adapter_id_ == old_default_adapter_id) { + return; + } + + if (adapter != NULL) { + adapter->AddObserver(this); + } + + // TODO(vlaviano): Respond to adapter change. +} + +void BluetoothOptionsHandler::DiscoveryStarted(const std::string& adapter_id) { + VLOG(2) << "Discovery started on " << adapter_id; +} + +void BluetoothOptionsHandler::DiscoveryEnded(const std::string& adapter_id) { + VLOG(2) << "Discovery ended on " << adapter_id; + web_ui_->CallJavascriptFunction( + "options.SystemOptions.notifyBluetoothSearchComplete"); + + // Stop the discovery session. + // TODO(vlaviano): We may want to expose DeviceDisappeared, remove the + // "Find devices" button, and let the discovery session continue throughout + // the time that the page is visible rather than just doing a single discovery + // cycle in response to a button click. + chromeos::BluetoothManager* bluetooth_manager = + chromeos::BluetoothManager::GetInstance(); + DCHECK(bluetooth_manager); + + chromeos::BluetoothAdapter* default_adapter = + bluetooth_manager->DefaultAdapter(); + + ValidateDefaultAdapter(default_adapter); + + if (default_adapter == NULL) { + VLOG(1) << "DiscoveryEnded: no default adapter"; + return; + } + + default_adapter->StopDiscovery(); +} + +void BluetoothOptionsHandler::DeviceFound(const std::string& adapter_id, + chromeos::BluetoothDevice* device) { + VLOG(2) << "Device found on " << adapter_id; + DCHECK(device); + SendDeviceNotification(device, NULL); +} + +void BluetoothOptionsHandler::ValidateDefaultAdapter( + chromeos::BluetoothAdapter* adapter) { + if ((adapter == NULL && !default_adapter_id_.empty()) || + (adapter != NULL && default_adapter_id_ != adapter->Id())) { + VLOG(1) << "unexpected default adapter change from \"" + << default_adapter_id_ << "\" to \"" << adapter->Id() << "\""; + DefaultAdapterChanged(adapter); + } +} + +void BluetoothOptionsHandler::GenerateFakeDeviceList() { + GenerateFakeDevice( + "Fake Wireless Keyboard", + "01-02-03-04-05-06", + "input-keyboard", + true, + true, + ""); + GenerateFakeDevice( + "Fake Wireless Mouse", + "02-03-04-05-06-01", + "input-mouse", + true, + false, + ""); + GenerateFakeDevice( + "Fake Wireless Headset", + "03-04-05-06-01-02", + "headset", + false, + false, + ""); + GenerateFakeDevice( + "Fake Connecting Keyboard", + "04-05-06-01-02-03", + "input-keyboard", + false, + false, + "bluetoothRemotePasskey"); + GenerateFakeDevice( + "Fake Connecting Phone", + "05-06-01-02-03-04", + "phone", + false, + false, + "bluetoothConfirmPasskey"); + GenerateFakeDevice( + "Fake Connecting Headset", + "06-01-02-03-04-05", + "headset", + false, + false, + "bluetoothEnterPasskey"); + web_ui_->CallJavascriptFunction( + "options.SystemOptions.notifyBluetoothSearchComplete"); +} + +void BluetoothOptionsHandler::GenerateFakeDevice( + const std::string& name, + const std::string& address, + const std::string& icon, + bool paired, + bool connected, + const std::string& pairing) { + DictionaryValue properties; + properties.SetString(bluetooth_device::kNameProperty, name); + properties.SetString(bluetooth_device::kAddressProperty, address); + properties.SetString(bluetooth_device::kIconProperty, icon); + properties.SetBoolean(bluetooth_device::kPairedProperty, paired); + properties.SetBoolean(bluetooth_device::kConnectedProperty, connected); + properties.SetInteger(bluetooth_device::kClassProperty, 0); + chromeos::BluetoothDevice* device = + chromeos::BluetoothDevice::Create(properties); + DeviceFound("FakeAdapter", device); + if (pairing.compare("bluetoothRemotePasskey") == 0) { + DisplayPasskey(device, 12345, 2); + } else if (pairing.compare("bluetoothConfirmPasskey") == 0) { + RequestConfirmation(device, 12345); + } else if (pairing.compare("bluetoothEnterPasskey") == 0) { + RequestPasskey(device); + } + delete device; +} + +} // namespace chromeos + diff --git a/chrome/browser/ui/webui/options2/chromeos/bluetooth_options_handler.h b/chrome/browser/ui/webui/options2/chromeos/bluetooth_options_handler.h new file mode 100644 index 0000000..f719734 --- /dev/null +++ b/chrome/browser/ui/webui/options2/chromeos/bluetooth_options_handler.h @@ -0,0 +1,138 @@ +// Copyright (c) 2011 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. + +#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_BLUETOOTH_OPTIONS_HANDLER_H_ +#define CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_BLUETOOTH_OPTIONS_HANDLER_H_ +#pragma once + +#include <string> + +#include "base/compiler_specific.h" +#include "chrome/browser/chromeos/bluetooth/bluetooth_adapter.h" +#include "chrome/browser/chromeos/bluetooth/bluetooth_manager.h" +#include "chrome/browser/ui/webui/options2/options_ui2.h" + +namespace base { +class DictionaryValue; +} + +namespace chromeos { + +// Handler for Bluetooth options on the system options page. +class BluetoothOptionsHandler : public OptionsPage2UIHandler, + public chromeos::BluetoothManager::Observer, + public chromeos::BluetoothAdapter::Observer { + public: + BluetoothOptionsHandler(); + virtual ~BluetoothOptionsHandler(); + + // OptionsPage2UIHandler implementation. + virtual void GetLocalizedValues( + base::DictionaryValue* localized_strings) OVERRIDE; + virtual void Initialize() OVERRIDE; + virtual void RegisterMessages() OVERRIDE; + + // Called when the 'Enable bluetooth' checkbox value is changed. + // |args| will contain the checkbox checked state as a string + // ("true" or "false"). + void EnableChangeCallback(const base::ListValue* args); + + // Called when the 'Find Devices' button is pressed from the Bluetooth + // ssettings. + // |args| will be an empty list. + void FindDevicesCallback(const base::ListValue* args); + + // Called when the user requests to connect to or disconnect from a Bluetooth + // device. + // |args| will be a list containing two or three arguments, the first argument + // is the device ID and the second is the requested action. If a third + // argument is present, it is the passkey for pairing confirmation. + void UpdateDeviceCallback(const base::ListValue* args); + + // Sends a notification to the Web UI of the status of a Bluetooth device. + // |device| is the Bluetooth device. + // |params| is an optional set of parameters. + void SendDeviceNotification(chromeos::BluetoothDevice* device, + base::DictionaryValue* params); + + // Displays a passkey for a device, requesting user confirmation that the + // key matches an expected value (value displayed on a smartphone for + // example). + // |device| is the Bluetooth device being paired. + // |passkey| is the passkey to display for confirmation. + void RequestConfirmation(chromeos::BluetoothDevice* device, + int passkey); + + // Displays a passkey for a device, which is being typed remotely. During + // the pairing process, this method may be called repeatedly to track the + // number of characters entered. This method is commonly used for pairing + // keyboards. + // |device| is the Bluetooth device being paired. + // |passkey| is the required passkey. + // |entered| is the number of characters that have already been entered on + // the remote device. + void DisplayPasskey(chromeos::BluetoothDevice* device, + int passkey, + int entered); + + // Displays a blank field for entering a passkey. The passkey may be + // a set value specified by the manufacturer of the Bluetooth device, or + // on a remote display. The validation is asychronous, and a call is made + // to |ValidatePasskeyCallback| when the passkey entry is complete. + // |device| is the Bluetooth device being paired. + void RequestPasskey(chromeos::BluetoothDevice* device); + + // Callback to validate a user entered passkey. + // |args| is a list containing the device address and entered passkey. + void ValidatePasskeyCallback(const base::ListValue* args); + + // chromeos::BluetoothManager::Observer override. + virtual void DefaultAdapterChanged( + chromeos::BluetoothAdapter* adapter) OVERRIDE; + + // chromeos::BluetoothAdapter::Observer override. + virtual void DiscoveryStarted(const std::string& adapter_id) OVERRIDE; + + // chromeos::BluetoothAdapter::Observer override. + virtual void DiscoveryEnded(const std::string& adapter_id) OVERRIDE; + + // chromeos::BluetoothAdapter::Observer override. + virtual void DeviceFound(const std::string& adapter_id, + chromeos::BluetoothDevice* device) OVERRIDE; + + private: + // Compares |adapter| with our cached default adapter ID and calls + // DefaultAdapterChanged if there has been an unexpected change. + void ValidateDefaultAdapter(chromeos::BluetoothAdapter* adapter); + + // Simulates extracting a list of available bluetooth devices. + // Called when emulating ChromeOS from a desktop environment. + void GenerateFakeDeviceList(); + + // Simulates the discovery or pairing of a Bluetooth device. Used when + // emulating ChromeOS from a desktop environment. + // |name| is the display name for the device. + // |address| is the unique Mac address of the device. + // |icon| is the base name of the icon to use for the device and corresponds + // to the general device category (e.g. mouse or keyboard). + // |paired| indicates if the device is paired. + // |connected| indicates if the device is connected. + // |pairing| indicates the type of pairing operation. + void GenerateFakeDevice(const std::string& name, + const std::string& address, + const std::string& icon, + bool paired, + bool connected, + const std::string& pairing); + + // The id of the current default bluetooth adapter. + // The empty string represents "none". + std::string default_adapter_id_; + + DISALLOW_COPY_AND_ASSIGN(BluetoothOptionsHandler); +}; + +} // namespace chromeos + +#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_BLUETOOTH_OPTIONS_HANDLER_H_ diff --git a/chrome/browser/ui/webui/options2/chromeos/change_picture_options_handler.cc b/chrome/browser/ui/webui/options2/chromeos/change_picture_options_handler.cc new file mode 100644 index 0000000..c3ac974 --- /dev/null +++ b/chrome/browser/ui/webui/options2/chromeos/change_picture_options_handler.cc @@ -0,0 +1,344 @@ +// Copyright (c) 2011 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. + +#include "chrome/browser/ui/webui/options2/chromeos/change_picture_options_handler.h" + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/metrics/histogram.h" +#include "base/path_service.h" +#include "base/string_util.h" +#include "base/values.h" +#include "chrome/browser/chromeos/login/camera_detector.h" +#include "chrome/browser/chromeos/login/default_user_images.h" +#include "chrome/browser/chromeos/login/user_manager.h" +#include "chrome/browser/chromeos/options/take_photo_dialog.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/ui/browser_list.h" +#include "chrome/browser/ui/browser_window.h" +#include "chrome/browser/ui/dialog_style.h" +#include "chrome/browser/ui/views/window.h" +#include "chrome/browser/ui/webui/web_ui_util.h" +#include "chrome/common/chrome_notification_types.h" +#include "chrome/common/chrome_paths.h" +#include "chrome/common/url_constants.h" +#include "content/public/browser/notification_service.h" +#include "content/public/common/url_constants.h" +#include "grit/generated_resources.h" +#include "grit/theme_resources.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "ui/base/l10n/l10n_util.h" +#include "ui/base/resource/resource_bundle.h" +#include "ui/views/widget/widget.h" + +namespace chromeos { + +namespace { + +// Returns info about extensions for files we support as user images. +SelectFileDialog::FileTypeInfo GetUserImageFileTypeInfo() { + SelectFileDialog::FileTypeInfo file_type_info; + file_type_info.extensions.resize(5); + + file_type_info.extensions[0].push_back(FILE_PATH_LITERAL("bmp")); + + file_type_info.extensions[1].push_back(FILE_PATH_LITERAL("gif")); + + file_type_info.extensions[2].push_back(FILE_PATH_LITERAL("jpg")); + file_type_info.extensions[2].push_back(FILE_PATH_LITERAL("jpeg")); + + file_type_info.extensions[3].push_back(FILE_PATH_LITERAL("png")); + + file_type_info.extensions[4].push_back(FILE_PATH_LITERAL("tif")); + file_type_info.extensions[4].push_back(FILE_PATH_LITERAL("tiff")); + + return file_type_info; +} + +} // namespace + +ChangePictureOptionsHandler::ChangePictureOptionsHandler() + : previous_image_data_url_(chrome::kAboutBlankURL), + previous_image_index_(User::kInvalidImageIndex), + ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)) { + registrar_.Add(this, chrome::NOTIFICATION_PROFILE_IMAGE_UPDATED, + content::NotificationService::AllSources()); + registrar_.Add(this, chrome::NOTIFICATION_PROFILE_IMAGE_UPDATE_FAILED, + content::NotificationService::AllSources()); +} + +ChangePictureOptionsHandler::~ChangePictureOptionsHandler() { + if (select_file_dialog_.get()) + select_file_dialog_->ListenerDestroyed(); +} + +void ChangePictureOptionsHandler::GetLocalizedValues( + DictionaryValue* localized_strings) { + DCHECK(localized_strings); + localized_strings->SetString("changePicturePage", + l10n_util::GetStringUTF16(IDS_OPTIONS_CHANGE_PICTURE_DIALOG_TITLE)); + localized_strings->SetString("changePicturePageDescription", + l10n_util::GetStringUTF16(IDS_OPTIONS_CHANGE_PICTURE_DIALOG_TEXT)); + localized_strings->SetString("takePhoto", + l10n_util::GetStringUTF16(IDS_OPTIONS_CHANGE_PICTURE_TAKE_PHOTO)); + localized_strings->SetString("chooseFile", + l10n_util::GetStringUTF16(IDS_OPTIONS_CHANGE_PICTURE_CHOOSE_FILE)); + localized_strings->SetString("profilePhoto", + l10n_util::GetStringUTF16(IDS_OPTIONS_CHANGE_PICTURE_PROFILE_PHOTO)); + localized_strings->SetString("profilePhotoLoading", + l10n_util::GetStringUTF16( + IDS_OPTIONS_CHANGE_PICTURE_PROFILE_LOADING_PHOTO)); +} + +void ChangePictureOptionsHandler::RegisterMessages() { + DCHECK(web_ui_); + web_ui_->RegisterMessageCallback("chooseFile", + base::Bind(&ChangePictureOptionsHandler::HandleChooseFile, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("takePhoto", + base::Bind(&ChangePictureOptionsHandler::HandleTakePhoto, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("onChangePicturePageShown", + base::Bind(&ChangePictureOptionsHandler::HandlePageShown, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("onChangePicturePageInitialized", + base::Bind(&ChangePictureOptionsHandler::HandlePageInitialized, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("selectImage", + base::Bind(&ChangePictureOptionsHandler::HandleSelectImage, + base::Unretained(this))); +} + +void ChangePictureOptionsHandler::SendDefaultImages() { + ListValue image_urls; + for (int i = 0; i < kDefaultImagesCount; ++i) { + image_urls.Append(new StringValue(GetDefaultImageUrl(i))); + } + web_ui_->CallJavascriptFunction("ChangePictureOptions.setDefaultImages", + image_urls); +} + +void ChangePictureOptionsHandler::HandleChooseFile(const ListValue* args) { + DCHECK(args && args->empty()); + if (!select_file_dialog_.get()) + select_file_dialog_ = SelectFileDialog::Create(this); + + FilePath downloads_path; + if (!PathService::Get(chrome::DIR_DEFAULT_DOWNLOADS, &downloads_path)) { + NOTREACHED(); + return; + } + + // Static so we initialize it only once. + CR_DEFINE_STATIC_LOCAL(SelectFileDialog::FileTypeInfo, file_type_info, + (GetUserImageFileTypeInfo())); + + select_file_dialog_->SelectFile( + SelectFileDialog::SELECT_OPEN_FILE, + l10n_util::GetStringUTF16(IDS_DOWNLOAD_TITLE), + downloads_path, + &file_type_info, + 0, + FILE_PATH_LITERAL(""), + web_ui_->tab_contents(), + GetBrowserWindow(), + NULL); +} + +void ChangePictureOptionsHandler::HandleTakePhoto(const ListValue* args) { + DCHECK(args && args->empty()); + views::Widget* window = browser::CreateViewsWindow( + GetBrowserWindow(), + new TakePhotoDialog(this), + STYLE_GENERIC); + window->SetAlwaysOnTop(true); + window->Show(); +} + +void ChangePictureOptionsHandler::HandlePageInitialized( + const base::ListValue* args) { + DCHECK(args && args->empty()); + // If no camera presence check has been performed in this session, + // start one now. + if (CameraDetector::camera_presence() == + CameraDetector::kCameraPresenceUnknown) { + CheckCameraPresence(); + } + + // While the check is in progress, use previous camera presence state and + // presume it is present if no check has been performed yet. + SetCameraPresent(CameraDetector::camera_presence() != + CameraDetector::kCameraAbsent); + + SendDefaultImages(); +} + +void ChangePictureOptionsHandler::HandlePageShown(const base::ListValue* args) { + DCHECK(args && args->empty()); + // TODO(ivankr): If user opens settings and goes to Change Picture page right + // after the check started |HandlePageInitialized| has been completed, + // |CheckCameraPresence| will be called twice, should be throttled. + CheckCameraPresence(); + SendSelectedImage(); + UpdateProfileImage(); +} + +void ChangePictureOptionsHandler::SendSelectedImage() { + const User& user = UserManager::Get()->logged_in_user(); + DCHECK(!user.email().empty()); + + previous_image_index_ = user.image_index(); + switch (previous_image_index_) { + case User::kExternalImageIndex: { + // User has image from camera/file, record it and add to the image list. + previous_image_ = user.image(); + previous_image_data_url_ = web_ui_util::GetImageDataUrl(previous_image_); + web_ui_->CallJavascriptFunction("ChangePictureOptions.setOldImage"); + break; + } + case User::kProfileImageIndex: { + // User has his/her Profile image as the current image. + SendProfileImage(user.image(), true); + break; + } + default: { + DCHECK(previous_image_index_ >= 0 && + previous_image_index_ < kDefaultImagesCount); + // User has image from the set of default images. + base::StringValue image_url(GetDefaultImageUrl(previous_image_index_)); + web_ui_->CallJavascriptFunction("ChangePictureOptions.setSelectedImage", + image_url); + } + } +} + +void ChangePictureOptionsHandler::SendProfileImage(const SkBitmap& image, + bool should_select) { + base::StringValue data_url(web_ui_util::GetImageDataUrl(image)); + base::FundamentalValue select(should_select); + web_ui_->CallJavascriptFunction("ChangePictureOptions.setProfileImage", + data_url, select); +} + +void ChangePictureOptionsHandler::UpdateProfileImage() { + UserManager* user_manager = UserManager::Get(); + + // If we have a downloaded profile image and haven't sent it in + // |SendSelectedImage|, send it now (without selecting). + if (previous_image_index_ != User::kProfileImageIndex && + !user_manager->downloaded_profile_image().empty()) + SendProfileImage(user_manager->downloaded_profile_image(), false); + + user_manager->DownloadProfileImage(); +} + +void ChangePictureOptionsHandler::HandleSelectImage(const ListValue* args) { + std::string image_url; + if (!args || + args->GetSize() != 1 || + !args->GetString(0, &image_url)) { + NOTREACHED(); + return; + } + DCHECK(!image_url.empty()); + + UserManager* user_manager = UserManager::Get(); + const User& user = user_manager->logged_in_user(); + int image_index = User::kInvalidImageIndex; + + if (StartsWithASCII(image_url, chrome::kChromeUIUserImageURL, false)) { + // Image from file/camera uses kChromeUIUserImageURL as URL while + // current profile image always has a full data URL. + // This way transition from (current profile image) to + // (profile image, current image from file) is easier. + + DCHECK(!previous_image_.empty()); + user_manager->SaveUserImage(user.email(), previous_image_); + + UMA_HISTOGRAM_ENUMERATION("UserImage.ChangeChoice", + kHistogramImageOld, + kHistogramImagesCount); + VLOG(1) << "Selected old user image"; + } else if (IsDefaultImageUrl(image_url, &image_index)) { + // One of the default user images. + user_manager->SaveUserDefaultImageIndex(user.email(), image_index); + + UMA_HISTOGRAM_ENUMERATION("UserImage.ChangeChoice", + image_index, + kHistogramImagesCount); + VLOG(1) << "Selected default user image: " << image_index; + } else { + // Profile image selected. Could be previous (old) user image. + user_manager->SaveUserImageFromProfileImage(user.email()); + + if (previous_image_index_ == User::kProfileImageIndex) { + UMA_HISTOGRAM_ENUMERATION("UserImage.ChangeChoice", + kHistogramImageOld, + kHistogramImagesCount); + VLOG(1) << "Selected old (profile) user image"; + } else { + UMA_HISTOGRAM_ENUMERATION("UserImage.ChangeChoice", + kHistogramImageFromProfile, + kHistogramImagesCount); + VLOG(1) << "Selected profile image"; + } + } +} + +void ChangePictureOptionsHandler::FileSelected(const FilePath& path, + int index, + void* params) { + UserManager* user_manager = UserManager::Get(); + user_manager->SaveUserImageFromFile(user_manager->logged_in_user().email(), + path); + UMA_HISTOGRAM_ENUMERATION("UserImage.ChangeChoice", + kHistogramImageFromFile, + kHistogramImagesCount); +} + +void ChangePictureOptionsHandler::OnPhotoAccepted(const SkBitmap& photo) { + UserManager* user_manager = UserManager::Get(); + user_manager->SaveUserImage(user_manager->logged_in_user().email(), photo); + UMA_HISTOGRAM_ENUMERATION("UserImage.ChangeChoice", + kHistogramImageFromCamera, + kHistogramImagesCount); +} + +void ChangePictureOptionsHandler::CheckCameraPresence() { + CameraDetector::StartPresenceCheck( + base::Bind(&ChangePictureOptionsHandler::OnCameraPresenceCheckDone, + weak_factory_.GetWeakPtr())); +} + +void ChangePictureOptionsHandler::SetCameraPresent(bool present) { + base::FundamentalValue present_value(present); + web_ui_->CallJavascriptFunction("ChangePictureOptions.setCameraPresent", + present_value); +} + +void ChangePictureOptionsHandler::OnCameraPresenceCheckDone() { + SetCameraPresent(CameraDetector::camera_presence() == + CameraDetector::kCameraPresent); +} + +void ChangePictureOptionsHandler::Observe( + int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) { + OptionsPage2UIHandler::Observe(type, source, details); + if (type == chrome::NOTIFICATION_PROFILE_IMAGE_UPDATED) { + // User profile image has been updated. + SendProfileImage(*content::Details<const SkBitmap>(details).ptr(), false); + } +} + +gfx::NativeWindow ChangePictureOptionsHandler::GetBrowserWindow() const { + Browser* browser = + BrowserList::FindBrowserWithProfile(Profile::FromWebUI(web_ui_)); + if (!browser) + return NULL; + return browser->window()->GetNativeHandle(); +} + +} // namespace chromeos diff --git a/chrome/browser/ui/webui/options2/chromeos/change_picture_options_handler.h b/chrome/browser/ui/webui/options2/chromeos/change_picture_options_handler.h new file mode 100644 index 0000000..8db67f4 --- /dev/null +++ b/chrome/browser/ui/webui/options2/chromeos/change_picture_options_handler.h @@ -0,0 +1,113 @@ +// Copyright (c) 2011 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. + +#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_CHANGE_PICTURE_OPTIONS_HANDLER_H_ +#define CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_CHANGE_PICTURE_OPTIONS_HANDLER_H_ + +#include "base/memory/weak_ptr.h" +#include "chrome/browser/chromeos/options/take_photo_dialog.h" +#include "chrome/browser/ui/select_file_dialog.h" +#include "chrome/browser/ui/webui/options2/options_ui2.h" +#include "content/public/browser/notification_registrar.h" +#include "ui/gfx/native_widget_types.h" + +namespace base { +class DictionaryValue; +class ListValue; +} + +namespace chromeos { + +// ChromeOS user image options page UI handler. +class ChangePictureOptionsHandler : public OptionsPage2UIHandler, + public SelectFileDialog::Listener, + public TakePhotoDialog::Delegate { + public: + ChangePictureOptionsHandler(); + virtual ~ChangePictureOptionsHandler(); + + // OptionsPage2UIHandler implementation. + virtual void GetLocalizedValues( + base::DictionaryValue* localized_strings) OVERRIDE; + + // WebUIMessageHandler implementation. + virtual void RegisterMessages() OVERRIDE; + + private: + // Sends list of available default images to the page. + void SendDefaultImages(); + + // Sends current selection to the page. + void SendSelectedImage(); + + // Sends the profile image to the page. If |should_select| is true then + // the profile image element is selected. + void SendProfileImage(const SkBitmap& image, bool should_select); + + // Starts profile image update and shows the last downloaded profile image, + // if any, on the page. Shouldn't be called before |SendProfileImage|. + void UpdateProfileImage(); + + // Starts camera presence check. + void CheckCameraPresence(); + + // Updates UI with camera presence state. + void SetCameraPresent(bool present); + + // Opens a file selection dialog to choose user image from file. + void HandleChooseFile(const base::ListValue* args); + + // Opens the camera capture dialog. + void HandleTakePhoto(const base::ListValue* args); + + // Gets the list of available user images and sends it to the page. + void HandleGetAvailableImages(const base::ListValue* args); + + // Handles page initialized event. + void HandlePageInitialized(const base::ListValue* args); + + // Handles page shown event. + void HandlePageShown(const base::ListValue* args); + + // Selects one of the available images as user's. + void HandleSelectImage(const base::ListValue* args); + + // SelectFileDialog::Delegate implementation. + virtual void FileSelected( + const FilePath& path, + int index, void* params) OVERRIDE; + + // TakePhotoDialog::Delegate implementation. + virtual void OnPhotoAccepted(const SkBitmap& photo) OVERRIDE; + + // content::NotificationObserver implementation. + virtual void Observe(int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) OVERRIDE; + + // Called when the camera presence check has been completed. + void OnCameraPresenceCheckDone(); + + // Returns handle to browser window or NULL if it can't be found. + gfx::NativeWindow GetBrowserWindow() const; + + scoped_refptr<SelectFileDialog> select_file_dialog_; + + // Previous user image from camera/file and its data URL. + SkBitmap previous_image_; + std::string previous_image_data_url_; + + // Index of the previous user image. + int previous_image_index_; + + content::NotificationRegistrar registrar_; + + base::WeakPtrFactory<ChangePictureOptionsHandler> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(ChangePictureOptionsHandler); +}; + +} // namespace chromeos + +#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_CHANGE_PICTURE_OPTIONS_HANDLER_H_ diff --git a/chrome/browser/ui/webui/options2/chromeos/core_chromeos_options_handler.cc b/chrome/browser/ui/webui/options2/chromeos/core_chromeos_options_handler.cc new file mode 100644 index 0000000..91fbc226 --- /dev/null +++ b/chrome/browser/ui/webui/options2/chromeos/core_chromeos_options_handler.cc @@ -0,0 +1,268 @@ +// Copyright (c) 2011 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. + +#include "chrome/browser/ui/webui/options2/chromeos/core_chromeos_options_handler.h" + +#include <string> + +#include "base/bind.h" +#include "base/string_number_conversions.h" +#include "base/string_util.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/chromeos/cros_settings.h" +#include "chrome/browser/chromeos/login/user_manager.h" +#include "chrome/browser/chromeos/proxy_config_service_impl.h" +#include "chrome/browser/chromeos/proxy_cros_settings_parser.h" +#include "chrome/browser/policy/browser_policy_connector.h" +#include "chrome/browser/prefs/pref_set_observer.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/ui/webui/options2/chromeos/accounts_options_handler.h" +#include "chrome/common/chrome_notification_types.h" +#include "chrome/common/pref_names.h" +#include "content/public/browser/notification_details.h" +#include "content/public/browser/notification_service.h" +#include "content/public/browser/notification_source.h" +#include "content/public/browser/user_metrics.h" + +namespace chromeos { + +namespace { + +// Create a settings value with "managed" and "disabled" property. +// "managed" property is true if the setting is managed by administrator. +// "disabled" property is true if the UI for the setting should be disabled. +base::Value* CreateSettingsValue(base::Value *value, + bool managed, + bool disabled) { + DictionaryValue* dict = new DictionaryValue; + dict->Set("value", value); + dict->Set("managed", base::Value::CreateBooleanValue(managed)); + dict->Set("disabled", base::Value::CreateBooleanValue(disabled)); + return dict; +} + +// Returns true if |username| is the logged-in owner. +bool IsLoggedInOwner(const std::string& username) { + UserManager* user_manager = UserManager::Get(); + return user_manager->current_user_is_owner() && + user_manager->logged_in_user().email() == username; +} + +// Creates a user info dictionary to be stored in the |ListValue| that is +// passed to Javascript for the |kAccountsPrefUsers| preference. +base::DictionaryValue* CreateUserInfo(const std::string& username, + const std::string& display_email, + const std::string& display_name) { + base::DictionaryValue* user_dict = new DictionaryValue; + user_dict->SetString("username", username); + user_dict->SetString("name", display_email); + user_dict->SetString("email", display_name); + user_dict->SetBoolean("owner", IsLoggedInOwner(username)); + return user_dict; +} + +// This function decorates the bare list of emails with some more information +// needed by the UI to properly display the Accounts page. +base::Value* CreateUsersWhitelist(const base::Value *pref_value) { + const base::ListValue* list_value = + static_cast<const base::ListValue*>(pref_value); + base::ListValue* user_list = new base::ListValue(); + UserManager* user_manager = UserManager::Get(); + + for (base::ListValue::const_iterator i = list_value->begin(); + i != list_value->end(); ++i) { + std::string email; + if ((*i)->GetAsString(&email)) { + // Translate email to the display email. + std::string display_email = user_manager->GetUserDisplayEmail(email); + // TODO(ivankr): fetch display name for existing users. + user_list->Append(CreateUserInfo(email, display_email, std::string())); + } + } + return user_list; +} + +} // namespace + +CoreChromeOSOptionsHandler::CoreChromeOSOptionsHandler() + : handling_change_(false), + pointer_factory_(this) { +} + +CoreChromeOSOptionsHandler::~CoreChromeOSOptionsHandler() { + PrefProxyConfigTracker* proxy_tracker = + Profile::FromWebUI(web_ui_)->GetProxyConfigTracker(); + proxy_tracker->RemoveNotificationCallback( + base::Bind(&CoreChromeOSOptionsHandler::NotifyProxyPrefsChanged, + pointer_factory_.GetWeakPtr())); +} + +void CoreChromeOSOptionsHandler::Initialize() { + proxy_prefs_.reset(PrefSetObserver::CreateProxyPrefSetObserver( + Profile::FromWebUI(web_ui_)->GetPrefs(), this)); + // Observe the chromeos::ProxyConfigServiceImpl for changes from the UI. + PrefProxyConfigTracker* proxy_tracker = + Profile::FromWebUI(web_ui_)->GetProxyConfigTracker(); + proxy_tracker->AddNotificationCallback( + base::Bind(&CoreChromeOSOptionsHandler::NotifyProxyPrefsChanged, + pointer_factory_.GetWeakPtr())); +} + +base::Value* CoreChromeOSOptionsHandler::FetchPref( + const std::string& pref_name) { + if (proxy_cros_settings_parser::IsProxyPref(pref_name)) { + base::Value *value = NULL; + proxy_cros_settings_parser::GetProxyPrefValue(Profile::FromWebUI(web_ui_), + pref_name, &value); + if (!value) + return base::Value::CreateNullValue(); + + return value; + } + if (!CrosSettings::IsCrosSettings(pref_name)) { + // Specially handle kUseSharedProxies because kProxy controls it to + // determine if it's managed by policy/extension. + if (pref_name == prefs::kUseSharedProxies) { + PrefService* pref_service = Profile::FromWebUI(web_ui_)->GetPrefs(); + const PrefService::Preference* pref = + pref_service->FindPreference(prefs::kUseSharedProxies); + if (!pref) + return base::Value::CreateNullValue(); + const PrefService::Preference* controlling_pref = + pref_service->FindPreference(prefs::kProxy); + return CreateValueForPref(pref, controlling_pref); + } + return ::CoreOptionsHandler::FetchPref(pref_name); + } + + const base::Value* pref_value = CrosSettings::Get()->GetPref(pref_name); + if (!pref_value) + return base::Value::CreateNullValue(); + + // Lists don't get the standard pref decoration. + if (pref_value->GetType() == base::Value::TYPE_LIST) { + if (pref_name == kAccountsPrefUsers) + return CreateUsersWhitelist(pref_value); + // Return a copy because the UI will take ownership of this object. + return pref_value->DeepCopy(); + } + // All other prefs are decorated the same way. + return CreateSettingsValue( + pref_value->DeepCopy(), // The copy will be owned by the dictionary. + g_browser_process->browser_policy_connector()->IsEnterpriseManaged(), + !UserManager::Get()->current_user_is_owner()); +} + +void CoreChromeOSOptionsHandler::ObservePref(const std::string& pref_name) { + if (proxy_cros_settings_parser::IsProxyPref(pref_name)) { + // We observe those all the time. + return; + } + if (!CrosSettings::IsCrosSettings(pref_name)) + return ::CoreOptionsHandler::ObservePref(pref_name); + CrosSettings::Get()->AddSettingsObserver(pref_name.c_str(), this); +} + +void CoreChromeOSOptionsHandler::SetPref(const std::string& pref_name, + const base::Value* value, + const std::string& metric) { + if (proxy_cros_settings_parser::IsProxyPref(pref_name)) { + proxy_cros_settings_parser::SetProxyPrefValue(Profile::FromWebUI(web_ui_), + pref_name, value); + ProcessUserMetric(value, metric); + return; + } + if (!CrosSettings::IsCrosSettings(pref_name)) + return ::CoreOptionsHandler::SetPref(pref_name, value, metric); + handling_change_ = true; + CrosSettings::Get()->Set(pref_name, *value); + handling_change_ = false; + + ProcessUserMetric(value, metric); +} + +void CoreChromeOSOptionsHandler::StopObservingPref(const std::string& path) { + if (proxy_cros_settings_parser::IsProxyPref(path)) + return; // We unregister those in the destructor. + // Unregister this instance from observing prefs of chrome os settings. + if (CrosSettings::IsCrosSettings(path)) + CrosSettings::Get()->RemoveSettingsObserver(path.c_str(), this); + else // Call base class to handle regular preferences. + ::CoreOptionsHandler::StopObservingPref(path); +} + +void CoreChromeOSOptionsHandler::Observe( + int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) { + // Ignore the notification if this instance had caused it. + if (handling_change_) + return; + if (type == chrome::NOTIFICATION_SYSTEM_SETTING_CHANGED) { + NotifySettingsChanged(content::Details<std::string>(details).ptr()); + return; + } + // Special handling for preferences kUseSharedProxies and kProxy, the latter + // controls the former and decides if it's managed by policy/extension. + if (type == chrome::NOTIFICATION_PREF_CHANGED) { + const PrefService* pref_service = Profile::FromWebUI(web_ui_)->GetPrefs(); + std::string* pref_name = content::Details<std::string>(details).ptr(); + if (content::Source<PrefService>(source).ptr() == pref_service && + (proxy_prefs_->IsObserved(*pref_name) || + *pref_name == prefs::kUseSharedProxies)) { + NotifyPrefChanged(prefs::kUseSharedProxies, prefs::kProxy); + return; + } + } + ::CoreOptionsHandler::Observe(type, source, details); +} + +void CoreChromeOSOptionsHandler::NotifySettingsChanged( + const std::string* setting_name) { + DCHECK(web_ui_); + DCHECK(CrosSettings::Get()->IsCrosSettings(*setting_name)); + const base::Value* value = FetchPref(*setting_name); + if (!value) { + NOTREACHED(); + return; + } + std::pair<PreferenceCallbackMap::const_iterator, + PreferenceCallbackMap::const_iterator> range = + pref_callback_map_.equal_range(*setting_name); + for (PreferenceCallbackMap::const_iterator iter = range.first; + iter != range.second; ++iter) { + const std::wstring& callback_function = iter->second; + ListValue result_value; + result_value.Append(base::Value::CreateStringValue(setting_name->c_str())); + result_value.Append(value->DeepCopy()); + web_ui_->CallJavascriptFunction(WideToASCII(callback_function), + result_value); + } + if (value) + delete value; +} + +void CoreChromeOSOptionsHandler::NotifyProxyPrefsChanged() { + DCHECK(web_ui_); + for (size_t i = 0; i < kProxySettingsCount; ++i) { + base::Value* value = NULL; + proxy_cros_settings_parser::GetProxyPrefValue( + Profile::FromWebUI(web_ui_), kProxySettings[i], &value); + DCHECK(value); + PreferenceCallbackMap::const_iterator iter = + pref_callback_map_.find(kProxySettings[i]); + for (; iter != pref_callback_map_.end(); ++iter) { + const std::wstring& callback_function = iter->second; + ListValue result_value; + result_value.Append(base::Value::CreateStringValue(kProxySettings[i])); + result_value.Append(value->DeepCopy()); + web_ui_->CallJavascriptFunction(WideToASCII(callback_function), + result_value); + } + if (value) + delete value; + } +} + +} // namespace chromeos diff --git a/chrome/browser/ui/webui/options2/chromeos/core_chromeos_options_handler.h b/chrome/browser/ui/webui/options2/chromeos/core_chromeos_options_handler.h new file mode 100644 index 0000000..48aa029 --- /dev/null +++ b/chrome/browser/ui/webui/options2/chromeos/core_chromeos_options_handler.h @@ -0,0 +1,53 @@ +// Copyright (c) 2011 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. + +#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_CORE_CHROMEOS_OPTIONS_HANDLER_H_ +#define CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_CORE_CHROMEOS_OPTIONS_HANDLER_H_ +#pragma once + +#include "base/compiler_specific.h" +#include "base/memory/weak_ptr.h" +#include "chrome/browser/ui/webui/options2/core_options_handler.h" + +class PrefSetObserver; + +namespace chromeos { + +// CoreChromeOSOptionsHandler handles ChromeOS settings. +class CoreChromeOSOptionsHandler : public CoreOptionsHandler { + public: + CoreChromeOSOptionsHandler(); + virtual ~CoreChromeOSOptionsHandler(); + + protected: + // ::CoreOptionsHandler overrides + virtual void Initialize() OVERRIDE; + virtual base::Value* FetchPref(const std::string& pref_name) OVERRIDE; + virtual void ObservePref(const std::string& pref_name) OVERRIDE; + virtual void SetPref(const std::string& pref_name, + const base::Value* value, + const std::string& metric) OVERRIDE; + virtual void StopObservingPref(const std::string& path) OVERRIDE; + + // content::NotificationObserver implementation. + virtual void Observe(int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) OVERRIDE; + + private: + // Notifies registered JS callbacks on ChromeOS setting change. + void NotifySettingsChanged(const std::string* setting_name); + void NotifyProxyPrefsChanged(); + + // Keeps the track of change caused by the handler to make sure + // it does not signal itself again. + bool handling_change_; + + scoped_ptr<PrefSetObserver> proxy_prefs_; + base::WeakPtrFactory<CoreChromeOSOptionsHandler> pointer_factory_; +}; + +} // namespace chromeos + +#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_CORE_CHROMEOS_OPTIONS_HANDLER_H_ diff --git a/chrome/browser/ui/webui/options2/chromeos/cros_language_options_handler.cc b/chrome/browser/ui/webui/options2/chromeos/cros_language_options_handler.cc new file mode 100644 index 0000000..6fc2dce --- /dev/null +++ b/chrome/browser/ui/webui/options2/chromeos/cros_language_options_handler.cc @@ -0,0 +1,242 @@ +// Copyright (c) 2011 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. + +#include "chrome/browser/ui/webui/options2/chromeos/cros_language_options_handler.h" + +#include <map> +#include <set> +#include <vector> + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/stringprintf.h" +#include "base/utf_string_conversions.h" +#include "base/values.h" +#include "chrome/app/chrome_command_ids.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/chromeos/input_method/input_method_manager.h" +#include "chrome/browser/chromeos/input_method/input_method_util.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/ui/browser.h" +#include "content/browser/tab_contents/tab_contents.h" +#include "content/public/browser/user_metrics.h" +#include "grit/chromium_strings.h" +#include "grit/generated_resources.h" +#include "ui/base/l10n/l10n_util.h" + +namespace chromeos { + +CrosLanguageOptionsHandler::CrosLanguageOptionsHandler() { +} + +CrosLanguageOptionsHandler::~CrosLanguageOptionsHandler() { +} + +void CrosLanguageOptionsHandler::GetLocalizedValues( + DictionaryValue* localized_strings) { + LanguageOptionsHandlerCommon::GetLocalizedValues(localized_strings); + + RegisterTitle(localized_strings, "languagePage", + IDS_OPTIONS_SETTINGS_LANGUAGES_AND_INPUT_DIALOG_TITLE); + localized_strings->SetString("ok_button", l10n_util::GetStringUTF16(IDS_OK)); + localized_strings->SetString("configure", + l10n_util::GetStringUTF16(IDS_OPTIONS_SETTINGS_LANGUAGES_CONFIGURE)); + localized_strings->SetString("input_method", + l10n_util::GetStringUTF16(IDS_OPTIONS_SETTINGS_LANGUAGES_INPUT_METHOD)); + localized_strings->SetString("please_add_another_input_method", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_LANGUAGES_PLEASE_ADD_ANOTHER_INPUT_METHOD)); + localized_strings->SetString("input_method_instructions", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_LANGUAGES_INPUT_METHOD_INSTRUCTIONS)); + localized_strings->SetString("switch_input_methods_hint", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_LANGUAGES_SWITCH_INPUT_METHODS_HINT)); + localized_strings->SetString("select_previous_input_method_hint", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_LANGUAGES_SELECT_PREVIOUS_INPUT_METHOD_HINT)); + localized_strings->SetString("restart_button", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_LANGUAGES_SIGN_OUT_BUTTON)); + localized_strings->SetString("virtual_keyboard_button", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_LANGUAGES_VIRTUAL_KEYBOARD_BUTTON)); + + input_method::InputMethodManager* manager = + input_method::InputMethodManager::GetInstance(); + // GetSupportedInputMethods() never return NULL. + scoped_ptr<input_method::InputMethodDescriptors> descriptors( + manager->GetSupportedInputMethods()); + localized_strings->Set("languageList", GetLanguageList(*descriptors)); + localized_strings->Set("inputMethodList", GetInputMethodList(*descriptors)); +} + +void CrosLanguageOptionsHandler::RegisterMessages() { + LanguageOptionsHandlerCommon::RegisterMessages(); + + web_ui_->RegisterMessageCallback("inputMethodDisable", + base::Bind(&CrosLanguageOptionsHandler::InputMethodDisableCallback, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("inputMethodEnable", + base::Bind(&CrosLanguageOptionsHandler::InputMethodEnableCallback, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("inputMethodOptionsOpen", + base::Bind(&CrosLanguageOptionsHandler::InputMethodOptionsOpenCallback, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("uiLanguageRestart", + base::Bind(&CrosLanguageOptionsHandler::RestartCallback, + base::Unretained(this))); +} + +ListValue* CrosLanguageOptionsHandler::GetInputMethodList( + const input_method::InputMethodDescriptors& descriptors) { + input_method::InputMethodManager* manager = + input_method::InputMethodManager::GetInstance(); + + ListValue* input_method_list = new ListValue(); + + for (size_t i = 0; i < descriptors.size(); ++i) { + const input_method::InputMethodDescriptor& descriptor = + descriptors[i]; + const std::string language_code = + manager->GetInputMethodUtil()->GetLanguageCodeFromDescriptor( + descriptor); + const std::string display_name = + manager->GetInputMethodUtil()->GetInputMethodDisplayNameFromId( + descriptor.id()); + + DictionaryValue* dictionary = new DictionaryValue(); + dictionary->SetString("id", descriptor.id()); + dictionary->SetString("displayName", display_name); + + // One input method can be associated with multiple languages, hence + // we use a dictionary here. + DictionaryValue* language_codes = new DictionaryValue(); + language_codes->SetBoolean(language_code, true); + // Check kExtraLanguages to see if there are languages associated with + // this input method. If these are present, add these. + for (size_t j = 0; j < input_method::kExtraLanguagesLength; ++j) { + const std::string extra_input_method_id = + input_method::kExtraLanguages[j].input_method_id; + const std::string extra_language_code = + input_method::kExtraLanguages[j].language_code; + if (extra_input_method_id == descriptor.id()) { + language_codes->SetBoolean(extra_language_code, true); + } + } + dictionary->Set("languageCodeSet", language_codes); + + input_method_list->Append(dictionary); + } + + return input_method_list; +} + +ListValue* CrosLanguageOptionsHandler::GetLanguageList( + const input_method::InputMethodDescriptors& descriptors) { + input_method::InputMethodManager* manager = + input_method::InputMethodManager::GetInstance(); + + std::set<std::string> language_codes; + // Collect the language codes from the supported input methods. + for (size_t i = 0; i < descriptors.size(); ++i) { + const input_method::InputMethodDescriptor& descriptor = descriptors[i]; + const std::string language_code = + manager->GetInputMethodUtil()->GetLanguageCodeFromDescriptor( + descriptor); + language_codes.insert(language_code); + } + // Collect the language codes from kExtraLanguages. + for (size_t i = 0; i < input_method::kExtraLanguagesLength; ++i) { + const char* language_code = + input_method::kExtraLanguages[i].language_code; + language_codes.insert(language_code); + } + + // Map of display name -> {language code, native_display_name}. + // In theory, we should be able to create a map that is sorted by + // display names using ICU comparator, but doing it is hard, thus we'll + // use an auxiliary vector to achieve the same result. + typedef std::pair<std::string, string16> LanguagePair; + typedef std::map<string16, LanguagePair> LanguageMap; + LanguageMap language_map; + // The auxiliary vector mentioned above. + std::vector<string16> display_names; + + // Build the list of display names, and build the language map. + for (std::set<std::string>::const_iterator iter = language_codes.begin(); + iter != language_codes.end(); ++iter) { + const string16 display_name = + input_method::InputMethodUtil::GetLanguageDisplayNameFromCode(*iter); + const string16 native_display_name = + input_method::InputMethodUtil::GetLanguageNativeDisplayNameFromCode( + *iter); + display_names.push_back(display_name); + language_map[display_name] = + std::make_pair(*iter, native_display_name); + } + DCHECK_EQ(display_names.size(), language_map.size()); + + // Sort display names using locale specific sorter. + l10n_util::SortStrings16(g_browser_process->GetApplicationLocale(), + &display_names); + + // Build the language list from the language map. + ListValue* language_list = new ListValue(); + for (size_t i = 0; i < display_names.size(); ++i) { + const LanguagePair& pair = language_map[display_names[i]]; + DictionaryValue* dictionary = new DictionaryValue(); + dictionary->SetString("code", pair.first); + dictionary->SetString("displayName", display_names[i]); + dictionary->SetString("nativeDisplayName", pair.second); + language_list->Append(dictionary); + } + + return language_list; +} + +string16 CrosLanguageOptionsHandler::GetProductName() { + return l10n_util::GetStringUTF16(IDS_PRODUCT_OS_NAME); +} + +void CrosLanguageOptionsHandler::SetApplicationLocale( + const std::string& language_code) { + Profile::FromWebUI(web_ui_)->ChangeAppLocale( + language_code, Profile::APP_LOCALE_CHANGED_VIA_SETTINGS); +} + +void CrosLanguageOptionsHandler::RestartCallback(const ListValue* args) { + UserMetrics::RecordAction(UserMetricsAction("LanguageOptions_SignOut")); + + Browser* browser = Browser::GetBrowserForController( + &web_ui_->tab_contents()->controller(), NULL); + if (browser) + browser->ExecuteCommand(IDC_EXIT); +} + +void CrosLanguageOptionsHandler::InputMethodDisableCallback( + const ListValue* args) { + const std::string input_method_id = UTF16ToASCII(ExtractStringValue(args)); + const std::string action = base::StringPrintf( + "LanguageOptions_DisableInputMethod_%s", input_method_id.c_str()); + UserMetrics::RecordComputedAction(action); +} + +void CrosLanguageOptionsHandler::InputMethodEnableCallback( + const ListValue* args) { + const std::string input_method_id = UTF16ToASCII(ExtractStringValue(args)); + const std::string action = base::StringPrintf( + "LanguageOptions_EnableInputMethod_%s", input_method_id.c_str()); + UserMetrics::RecordComputedAction(action); +} + +void CrosLanguageOptionsHandler::InputMethodOptionsOpenCallback( + const ListValue* args) { + const std::string input_method_id = UTF16ToASCII(ExtractStringValue(args)); + const std::string action = base::StringPrintf( + "InputMethodOptions_Open_%s", input_method_id.c_str()); + UserMetrics::RecordComputedAction(action); +} + +} // namespace chromeos diff --git a/chrome/browser/ui/webui/options2/chromeos/cros_language_options_handler.h b/chrome/browser/ui/webui/options2/chromeos/cros_language_options_handler.h new file mode 100644 index 0000000..aa0464b --- /dev/null +++ b/chrome/browser/ui/webui/options2/chromeos/cros_language_options_handler.h @@ -0,0 +1,73 @@ +// Copyright (c) 2011 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. + +#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_CROS_LANGUAGE_OPTIONS_HANDLER_H_ +#define CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_CROS_LANGUAGE_OPTIONS_HANDLER_H_ +#pragma once + +#include "base/compiler_specific.h" +#include "chrome/browser/chromeos/input_method/ibus_controller.h" +#include "chrome/browser/ui/webui/options2/language_options_handler.h" + +namespace chromeos { + +// Language options page UI handler for Chrome OS. For non-Chrome OS, +// see LanguageOptionsHnadler. +class CrosLanguageOptionsHandler : public LanguageOptionsHandlerCommon { + public: + CrosLanguageOptionsHandler(); + virtual ~CrosLanguageOptionsHandler(); + + // OptionsPageUIHandler implementation. + virtual void GetLocalizedValues( + base::DictionaryValue* localized_strings) OVERRIDE; + + // DOMMessageHandler implementation. + virtual void RegisterMessages() OVERRIDE; + + // The following static methods are public for ease of testing. + + // Gets the list of input methods from the given input descriptors. + // The return value will look like: + // [{'id': 'pinyin', 'displayName': 'Pinyin', + // 'languageCodeSet': {'zh-CW': true}}, ...] + // + // Note that true in languageCodeSet does not mean anything. We just use + // the dictionary as a set. + static base::ListValue* GetInputMethodList( + const input_method::InputMethodDescriptors& descriptors); + + // Gets the list of languages from the given input descriptors. + // The return value will look like: + // [{'code': 'fi', 'displayName': 'Finnish', 'nativeDisplayName': 'suomi'}, + // ...] + static base::ListValue* GetLanguageList( + const input_method::InputMethodDescriptors& descriptors); + + private: + // LanguageOptionsHandlerCommon implementation. + virtual string16 GetProductName() OVERRIDE; + virtual void SetApplicationLocale(const std::string& language_code) OVERRIDE; + + // Called when the sign-out button is clicked. + void RestartCallback(const base::ListValue* args); + + // Called when the input method is disabled. + // |args| will contain the input method ID as string (ex. "mozc"). + void InputMethodDisableCallback(const base::ListValue* args); + + // Called when the input method is enabled. + // |args| will contain the input method ID as string (ex. "mozc"). + void InputMethodEnableCallback(const base::ListValue* args); + + // Called when the input method options page is opened. + // |args| will contain the input method ID as string (ex. "mozc"). + void InputMethodOptionsOpenCallback(const base::ListValue* args); + + DISALLOW_COPY_AND_ASSIGN(CrosLanguageOptionsHandler); +}; + +} // namespace chromeos + +#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_CROS_LANGUAGE_OPTIONS_HANDLER_H_ diff --git a/chrome/browser/ui/webui/options2/chromeos/guest_mode_options_ui_uitest.cc b/chrome/browser/ui/webui/options2/chromeos/guest_mode_options_ui_uitest.cc new file mode 100644 index 0000000..e001d63 --- /dev/null +++ b/chrome/browser/ui/webui/options2/chromeos/guest_mode_options_ui_uitest.cc @@ -0,0 +1,43 @@ +// Copyright (c) 2011 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. + +#include "chrome/browser/ui/webui/options2/options_ui_uitest.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/test/automation/automation_proxy.h" +#include "chrome/test/automation/browser_proxy.h" +#include "chrome/test/automation/tab_proxy.h" +#include "chrome/test/ui/ui_test.h" + +namespace { + +// Same as OptionsUITest but launches with Guest mode command line switches. +class GuestModeOptionsUITest : public OptionsUITest { + public: + GuestModeOptionsUITest() : OptionsUITest() { + launch_arguments_.AppendSwitch(switches::kGuestSession); + launch_arguments_.AppendSwitch(switches::kIncognito); + } +}; + +// See bug 104393. +#if defined(USE_AURA) +#define MAYBE_LoadOptionsByURL FAILS_LoadOptionsByURL +#else +#define MAYBE_LoadOptionsByURL LoadOptionsByURL +#endif + +TEST_F(GuestModeOptionsUITest, MAYBE_LoadOptionsByURL) { + scoped_refptr<BrowserProxy> browser(automation()->GetBrowserWindow(0)); + ASSERT_TRUE(browser.get()); + + scoped_refptr<TabProxy> tab = browser->GetActiveTab(); + ASSERT_TRUE(tab.get()); + + NavigateToSettings(tab); + VerifyTitle(tab); + VerifyNavbar(tab); + VerifySections(tab); +} + +} // namespace diff --git a/chrome/browser/ui/webui/options2/chromeos/internet_options_handler.cc b/chrome/browser/ui/webui/options2/chromeos/internet_options_handler.cc new file mode 100644 index 0000000..7af2d1b --- /dev/null +++ b/chrome/browser/ui/webui/options2/chromeos/internet_options_handler.cc @@ -0,0 +1,1372 @@ +// Copyright (c) 2011 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. + +#include "chrome/browser/ui/webui/options2/chromeos/internet_options_handler.h" + +#include <ctype.h> + +#include <map> +#include <string> +#include <vector> + +#include "base/base64.h" +#include "base/basictypes.h" +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/command_line.h" +#include "base/i18n/time_formatting.h" +#include "base/string16.h" +#include "base/string_number_conversions.h" +#include "base/stringprintf.h" +#include "base/time.h" +#include "base/utf_string_conversions.h" +#include "base/values.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/chromeos/choose_mobile_network_dialog.h" +#include "chrome/browser/chromeos/cros/cros_library.h" +#include "chrome/browser/chromeos/cros/network_library.h" +#include "chrome/browser/chromeos/cros_settings.h" +#include "chrome/browser/chromeos/mobile_config.h" +#include "chrome/browser/chromeos/options/network_config_view.h" +#include "chrome/browser/chromeos/proxy_config_service_impl.h" +#include "chrome/browser/chromeos/sim_dialog_delegate.h" +#include "chrome/browser/chromeos/status/network_menu_icon.h" +#include "chrome/browser/net/pref_proxy_config_tracker.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/ui/browser.h" +#include "chrome/browser/ui/browser_list.h" +#include "chrome/browser/ui/browser_window.h" +#include "chrome/browser/ui/dialog_style.h" +#include "chrome/browser/ui/views/window.h" +#include "chrome/browser/ui/webui/web_ui_util.h" +#include "chrome/common/chrome_notification_types.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/common/time_format.h" +#include "content/public/browser/notification_service.h" +#include "grit/chromium_strings.h" +#include "grit/generated_resources.h" +#include "grit/locale_settings.h" +#include "grit/theme_resources.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "ui/base/l10n/l10n_util.h" +#include "ui/base/resource/resource_bundle.h" +#include "ui/views/widget/widget.h" + +namespace { + +static const char kOtherNetworksFakePath[] = "?"; + +// Keys for the network description dictionary passed to the web ui. Make sure +// to keep the strings in sync with what the Javascript side uses. +const char kNetworkInfoKeyActivationState[] = "activation_state"; +const char kNetworkInfoKeyConnectable[] = "connectable"; +const char kNetworkInfoKeyConnected[] = "connected"; +const char kNetworkInfoKeyConnecting[] = "connecting"; +const char kNetworkInfoKeyIconURL[] = "iconURL"; +const char kNetworkInfoKeyNeedsNewPlan[] = "needs_new_plan"; +const char kNetworkInfoKeyNetworkName[] = "networkName"; +const char kNetworkInfoKeyNetworkStatus[] = "networkStatus"; +const char kNetworkInfoKeyNetworkType[] = "networkType"; +const char kNetworkInfoKeyRemembered[] = "remembered"; +const char kNetworkInfoKeyServicePath[] = "servicePath"; +const char kNetworkInfoKeyPolicyManaged[] = "policyManaged"; + +// A helper class for building network information dictionaries to be sent to +// the webui code. +class NetworkInfoDictionary { + public: + // Initializes the dictionary with default values. + NetworkInfoDictionary(); + + // Copies in service path, connect{ing|ed|able} flags and connection type from + // the provided network object. Also chooses an appropriate icon based on the + // network type. + explicit NetworkInfoDictionary(const chromeos::Network* network); + + // Initializes a remembered network entry, pulling information from the passed + // network object and the corresponding remembered network object. |network| + // may be NULL. + NetworkInfoDictionary(const chromeos::Network* network, + const chromeos::Network* remembered); + + // Setters for filling in information. + void set_service_path(const std::string& service_path) { + service_path_ = service_path; + } + void set_icon(const SkBitmap& icon) { + icon_url_ = icon.isNull() ? "" : web_ui_util::GetImageDataUrl(icon); + } + void set_name(const std::string& name) { + name_ = name; + } + void set_connecting(bool connecting) { + connecting_ = connecting; + } + void set_connected(bool connected) { + connected_ = connected; + } + void set_connectable(bool connectable) { + connectable_ = connectable; + } + void set_connection_type(chromeos::ConnectionType connection_type) { + connection_type_ = connection_type; + } + void set_remembered(bool remembered) { + remembered_ = remembered; + } + void set_shared(bool shared) { + shared_ = shared; + } + void set_activation_state(chromeos::ActivationState activation_state) { + activation_state_ = activation_state; + } + void set_needs_new_plan(bool needs_new_plan) { + needs_new_plan_ = needs_new_plan; + } + void set_policy_managed(bool policy_managed) { + policy_managed_ = policy_managed; + } + + // Builds the DictionaryValue representation from the previously set + // parameters. Ownership of the returned pointer is transferred to the caller. + DictionaryValue* BuildDictionary(); + + private: + // Values to be filled into the dictionary. + std::string service_path_; + std::string icon_url_; + std::string name_; + bool connecting_; + bool connected_; + bool connectable_; + chromeos::ConnectionType connection_type_; + bool remembered_; + bool shared_; + chromeos::ActivationState activation_state_; + bool needs_new_plan_; + bool policy_managed_; + + DISALLOW_COPY_AND_ASSIGN(NetworkInfoDictionary); +}; + +NetworkInfoDictionary::NetworkInfoDictionary() { + set_connecting(false); + set_connected(false); + set_connectable(false); + set_remembered(false); + set_shared(false); + set_activation_state(chromeos::ACTIVATION_STATE_UNKNOWN); + set_needs_new_plan(false); + set_policy_managed(false); +} + +NetworkInfoDictionary::NetworkInfoDictionary(const chromeos::Network* network) { + set_service_path(network->service_path()); + set_icon(chromeos::NetworkMenuIcon::GetBitmap(network)); + set_name(network->name()); + set_connecting(network->connecting()); + set_connected(network->connected()); + set_connectable(network->connectable()); + set_connection_type(network->type()); + set_remembered(false); + set_shared(false); + set_needs_new_plan(false); + set_policy_managed(chromeos::NetworkUIData::IsManaged(network)); +} + +NetworkInfoDictionary::NetworkInfoDictionary( + const chromeos::Network* network, + const chromeos::Network* remembered) { + set_service_path(remembered->service_path()); + set_icon( + chromeos::NetworkMenuIcon::GetBitmap(network ? network : remembered)); + set_name(remembered->name()); + set_connecting(network ? network->connecting() : false); + set_connected(network ? network->connected() : false); + set_connectable(true); + set_connection_type(remembered->type()); + set_remembered(true); + set_shared(remembered->profile_type() == chromeos::PROFILE_SHARED); + set_needs_new_plan(false); + set_policy_managed( + network ? chromeos::NetworkUIData::IsManaged(network) : false); +} + +DictionaryValue* NetworkInfoDictionary::BuildDictionary() { + std::string status; + + if (remembered_) { + if (shared_) + status = l10n_util::GetStringUTF8(IDS_OPTIONS_SETTINGS_SHARED_NETWORK); + } else { + // 802.1X networks can be connected but not have saved credentials, and + // hence be "not configured". Give preference to the "connected" and + // "connecting" states. http://crosbug.com/14459 + int connection_state = IDS_STATUSBAR_NETWORK_DEVICE_DISCONNECTED; + if (connected_) + connection_state = IDS_STATUSBAR_NETWORK_DEVICE_CONNECTED; + else if (connecting_) + connection_state = IDS_STATUSBAR_NETWORK_DEVICE_CONNECTING; + else if (!connectable_) + connection_state = IDS_STATUSBAR_NETWORK_DEVICE_NOT_CONFIGURED; + status = l10n_util::GetStringUTF8(connection_state); + if (connection_type_ == chromeos::TYPE_CELLULAR) { + if (needs_new_plan_) { + status = l10n_util::GetStringUTF8(IDS_OPTIONS_SETTINGS_NO_PLAN_LABEL); + } else if (activation_state_ != chromeos::ACTIVATION_STATE_ACTIVATED) { + status.append(" / "); + status.append(chromeos::CellularNetwork::ActivationStateToString( + activation_state_)); + } + } + } + + scoped_ptr<DictionaryValue> network_info(new DictionaryValue()); + network_info->SetInteger(kNetworkInfoKeyActivationState, + static_cast<int>(activation_state_)); + network_info->SetBoolean(kNetworkInfoKeyConnectable, connectable_); + network_info->SetBoolean(kNetworkInfoKeyConnected, connected_); + network_info->SetBoolean(kNetworkInfoKeyConnecting, connecting_); + network_info->SetString(kNetworkInfoKeyIconURL, icon_url_); + network_info->SetBoolean(kNetworkInfoKeyNeedsNewPlan, needs_new_plan_); + network_info->SetString(kNetworkInfoKeyNetworkName, name_); + network_info->SetString(kNetworkInfoKeyNetworkStatus, status); + network_info->SetInteger(kNetworkInfoKeyNetworkType, + static_cast<int>(connection_type_)); + network_info->SetBoolean(kNetworkInfoKeyRemembered, remembered_); + network_info->SetString(kNetworkInfoKeyServicePath, service_path_); + network_info->SetBoolean(kNetworkInfoKeyPolicyManaged, policy_managed_); + + return network_info.release(); +} + +} // namespace + +InternetOptionsHandler::InternetOptionsHandler() { + registrar_.Add(this, chrome::NOTIFICATION_REQUIRE_PIN_SETTING_CHANGE_ENDED, + content::NotificationService::AllSources()); + registrar_.Add(this, chrome::NOTIFICATION_ENTER_PIN_ENDED, + content::NotificationService::AllSources()); + cros_ = chromeos::CrosLibrary::Get()->GetNetworkLibrary(); + if (cros_) { + cros_->AddNetworkManagerObserver(this); + cros_->AddCellularDataPlanObserver(this); + MonitorNetworks(); + } +} + +InternetOptionsHandler::~InternetOptionsHandler() { + if (cros_) { + cros_->RemoveNetworkManagerObserver(this); + cros_->RemoveCellularDataPlanObserver(this); + cros_->RemoveObserverForAllNetworks(this); + } +} + +void InternetOptionsHandler::GetLocalizedValues( + DictionaryValue* localized_strings) { + DCHECK(localized_strings); + + RegisterTitle(localized_strings, "internetPage", + IDS_OPTIONS_INTERNET_TAB_LABEL); + + localized_strings->SetString("wired_title", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_SECTION_TITLE_WIRED_NETWORK)); + localized_strings->SetString("wireless_title", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_SECTION_TITLE_WIRELESS_NETWORK)); + localized_strings->SetString("vpn_title", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_SECTION_TITLE_VIRTUAL_NETWORK)); + localized_strings->SetString("remembered_title", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_SECTION_TITLE_REMEMBERED_NETWORK)); + + localized_strings->SetString("connect_button", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_CONNECT)); + localized_strings->SetString("disconnect_button", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_DISCONNECT)); + localized_strings->SetString("options_button", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_OPTIONS)); + localized_strings->SetString("forget_button", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_FORGET)); + localized_strings->SetString("activate_button", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_ACTIVATE)); + localized_strings->SetString("buyplan_button", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_BUY_PLAN)); + + localized_strings->SetString("changeProxyButton", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_INTERNET_OPTIONS_CHANGE_PROXY_BUTTON)); + + localized_strings->SetString("managedNetwork", + l10n_util::GetStringUTF16(IDS_OPTIONS_SETTINGS_MANAGED_NETWORK)); + + localized_strings->SetString("wifiNetworkTabLabel", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_INTERNET_TAB_WIFI)); + localized_strings->SetString("vpnTabLabel", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_INTERNET_TAB_VPN)); + localized_strings->SetString("cellularPlanTabLabel", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_INTERNET_TAB_PLAN)); + localized_strings->SetString("cellularConnTabLabel", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_INTERNET_TAB_CONNECTION)); + localized_strings->SetString("cellularDeviceTabLabel", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_INTERNET_TAB_DEVICE)); + localized_strings->SetString("networkTabLabel", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_INTERNET_TAB_NETWORK)); + localized_strings->SetString("securityTabLabel", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_INTERNET_TAB_SECURITY)); + + localized_strings->SetString("useDHCP", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_INTERNET_OPTIONS_USE_DHCP)); + localized_strings->SetString("useStaticIP", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_INTERNET_OPTIONS_USE_STATIC_IP)); + localized_strings->SetString("connectionState", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_INTERNET_CONNECTION_STATE)); + localized_strings->SetString("inetAddress", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_INTERNET_OPTIONS_ADDRESS)); + localized_strings->SetString("inetSubnetAddress", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_INTERNET_OPTIONS_SUBNETMASK)); + localized_strings->SetString("inetGateway", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_INTERNET_OPTIONS_GATEWAY)); + localized_strings->SetString("inetDns", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_INTERNET_OPTIONS_DNSSERVER)); + localized_strings->SetString("hardwareAddress", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_INTERNET_OPTIONS_HARDWARE_ADDRESS)); + + // Wifi Tab. + localized_strings->SetString("accessLockedMsg", + l10n_util::GetStringUTF16( + IDS_STATUSBAR_NETWORK_LOCKED)); + localized_strings->SetString("inetSsid", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_INTERNET_OPTIONS_NETWORK_ID)); + localized_strings->SetString("inetPassProtected", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_INTERNET_OPTIONS_NET_PROTECTED)); + localized_strings->SetString("inetNetworkShared", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_INTERNET_OPTIONS_NETWORK_SHARED)); + localized_strings->SetString("inetPreferredNetwork", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_INTERNET_OPTIONS_PREFER_NETWORK)); + localized_strings->SetString("inetAutoConnectNetwork", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_INTERNET_OPTIONS_AUTO_CONNECT)); + localized_strings->SetString("inetLogin", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_INTERNET_OPTIONS_LOGIN)); + localized_strings->SetString("inetShowPass", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_INTERNET_OPTIONS_SHOWPASSWORD)); + localized_strings->SetString("inetPassPrompt", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_INTERNET_OPTIONS_PASSWORD)); + localized_strings->SetString("inetSsidPrompt", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_INTERNET_OPTIONS_SSID)); + localized_strings->SetString("inetStatus", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_INTERNET_OPTIONS_STATUS_TITLE)); + localized_strings->SetString("inetConnect", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_INTERNET_OPTIONS_CONNECT_TITLE)); + + // VPN Tab. + localized_strings->SetString("inetServiceName", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_INTERNET_OPTIONS_VPN_SERVICE_NAME)); + localized_strings->SetString("inetServerHostname", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_INTERNET_OPTIONS_VPN_SERVER_HOSTNAME)); + localized_strings->SetString("inetProviderType", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_INTERNET_OPTIONS_VPN_PROVIDER_TYPE)); + localized_strings->SetString("inetUsername", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_INTERNET_OPTIONS_VPN_USERNAME)); + + // Cellular Tab. + localized_strings->SetString("serviceName", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_INTERNET_CELLULAR_SERVICE_NAME)); + localized_strings->SetString("networkTechnology", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_INTERNET_CELLULAR_NETWORK_TECHNOLOGY)); + localized_strings->SetString("operatorName", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_INTERNET_CELLULAR_OPERATOR)); + localized_strings->SetString("operatorCode", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_INTERNET_CELLULAR_OPERATOR_CODE)); + localized_strings->SetString("activationState", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_INTERNET_CELLULAR_ACTIVATION_STATE)); + localized_strings->SetString("roamingState", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_INTERNET_CELLULAR_ROAMING_STATE)); + localized_strings->SetString("restrictedPool", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_INTERNET_CELLULAR_RESTRICTED_POOL)); + localized_strings->SetString("errorState", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_INTERNET_CELLULAR_ERROR_STATE)); + localized_strings->SetString("manufacturer", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_INTERNET_CELLULAR_MANUFACTURER)); + localized_strings->SetString("modelId", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_INTERNET_CELLULAR_MODEL_ID)); + localized_strings->SetString("firmwareRevision", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_INTERNET_CELLULAR_FIRMWARE_REVISION)); + localized_strings->SetString("hardwareRevision", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_INTERNET_CELLULAR_HARDWARE_REVISION)); + localized_strings->SetString("prlVersion", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_INTERNET_CELLULAR_PRL_VERSION)); + localized_strings->SetString("cellularApnLabel", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_INTERNET_CELLULAR_APN)); + localized_strings->SetString("cellularApnOther", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_INTERNET_CELLULAR_APN_OTHER)); + localized_strings->SetString("cellularApnUsername", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_INTERNET_CELLULAR_APN_USERNAME)); + localized_strings->SetString("cellularApnPassword", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_INTERNET_CELLULAR_APN_PASSWORD)); + localized_strings->SetString("cellularApnUseDefault", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_INTERNET_CELLULAR_APN_CLEAR)); + localized_strings->SetString("cellularApnSet", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_INTERNET_CELLULAR_APN_SET)); + localized_strings->SetString("cellularApnCancel", + l10n_util::GetStringUTF16( + IDS_CANCEL)); + + localized_strings->SetString("accessSecurityTabLink", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_INTERNET_CELLULAR_ACCESS_SECURITY_TAB)); + localized_strings->SetString("lockSimCard", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_INTERNET_CELLULAR_LOCK_SIM_CARD)); + localized_strings->SetString("changePinButton", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_INTERNET_CELLULAR_CHANGE_PIN_BUTTON)); + + localized_strings->SetString("planName", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_INTERNET_CELL_PLAN_NAME)); + localized_strings->SetString("planLoading", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_INTERNET_OPTIONS_LOADING_PLAN)); + localized_strings->SetString("noPlansFound", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_INTERNET_OPTIONS_NO_PLANS_FOUND)); + localized_strings->SetString("purchaseMore", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_INTERNET_OPTIONS_PURCHASE_MORE)); + localized_strings->SetString("dataRemaining", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_INTERNET_OPTIONS_DATA_REMAINING)); + localized_strings->SetString("planExpires", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_INTERNET_OPTIONS_EXPIRES)); + localized_strings->SetString("showPlanNotifications", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_INTERNET_OPTIONS_SHOW_MOBILE_NOTIFICATION)); + localized_strings->SetString("autoconnectCellular", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_INTERNET_OPTIONS_AUTO_CONNECT)); + localized_strings->SetString("customerSupport", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_INTERNET_OPTIONS_CUSTOMER_SUPPORT)); + + localized_strings->SetString("enableWifi", + l10n_util::GetStringFUTF16( + IDS_STATUSBAR_NETWORK_DEVICE_ENABLE, + l10n_util::GetStringUTF16(IDS_STATUSBAR_NETWORK_DEVICE_WIFI))); + localized_strings->SetString("disableWifi", + l10n_util::GetStringFUTF16( + IDS_STATUSBAR_NETWORK_DEVICE_DISABLE, + l10n_util::GetStringUTF16(IDS_STATUSBAR_NETWORK_DEVICE_WIFI))); + localized_strings->SetString("enableCellular", + l10n_util::GetStringFUTF16( + IDS_STATUSBAR_NETWORK_DEVICE_ENABLE, + l10n_util::GetStringUTF16(IDS_STATUSBAR_NETWORK_DEVICE_CELLULAR))); + localized_strings->SetString("disableCellular", + l10n_util::GetStringFUTF16( + IDS_STATUSBAR_NETWORK_DEVICE_DISABLE, + l10n_util::GetStringUTF16(IDS_STATUSBAR_NETWORK_DEVICE_CELLULAR))); + localized_strings->SetString("useSharedProxies", + l10n_util::GetStringUTF16(IDS_OPTIONS_SETTINGS_USE_SHARED_PROXIES)); + localized_strings->SetString("enableDataRoaming", + l10n_util::GetStringUTF16(IDS_OPTIONS_SETTINGS_ENABLE_DATA_ROAMING)); + localized_strings->SetString("generalNetworkingTitle", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_INTERNET_CONTROL_TITLE)); + localized_strings->SetString("detailsInternetDismiss", + l10n_util::GetStringUTF16(IDS_CLOSE)); + localized_strings->SetString("ownerOnly", l10n_util::GetStringUTF16( + IDS_OPTIONS_ACCOUNTS_OWNER_ONLY)); + std::string owner; + chromeos::CrosSettings::Get()->GetString(chromeos::kDeviceOwner, &owner); + localized_strings->SetString("ownerUserId", UTF8ToUTF16(owner)); + + FillNetworkInfo(localized_strings); +} + +void InternetOptionsHandler::Initialize() { + cros_->RequestNetworkScan(); +} + +void InternetOptionsHandler::RegisterMessages() { + // Setup handlers specific to this panel. + DCHECK(web_ui_); + web_ui_->RegisterMessageCallback("buttonClickCallback", + base::Bind(&InternetOptionsHandler::ButtonClickCallback, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("refreshCellularPlan", + base::Bind(&InternetOptionsHandler::RefreshCellularPlanCallback, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("setPreferNetwork", + base::Bind(&InternetOptionsHandler::SetPreferNetworkCallback, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("setAutoConnect", + base::Bind(&InternetOptionsHandler::SetAutoConnectCallback, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("setIPConfig", + base::Bind(&InternetOptionsHandler::SetIPConfigCallback, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("enableWifi", + base::Bind(&InternetOptionsHandler::EnableWifiCallback, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("disableWifi", + base::Bind(&InternetOptionsHandler::DisableWifiCallback, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("enableCellular", + base::Bind(&InternetOptionsHandler::EnableCellularCallback, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("disableCellular", + base::Bind(&InternetOptionsHandler::DisableCellularCallback, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("buyDataPlan", + base::Bind(&InternetOptionsHandler::BuyDataPlanCallback, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("showMorePlanInfo", + base::Bind(&InternetOptionsHandler::BuyDataPlanCallback, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("setApn", + base::Bind(&InternetOptionsHandler::SetApnCallback, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("setSimCardLock", + base::Bind(&InternetOptionsHandler::SetSimCardLockCallback, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("changePin", + base::Bind(&InternetOptionsHandler::ChangePinCallback, + base::Unretained(this))); +} + +void InternetOptionsHandler::EnableWifiCallback(const ListValue* args) { + cros_->EnableWifiNetworkDevice(true); +} + +void InternetOptionsHandler::DisableWifiCallback(const ListValue* args) { + cros_->EnableWifiNetworkDevice(false); +} + +void InternetOptionsHandler::EnableCellularCallback(const ListValue* args) { + const chromeos::NetworkDevice* cellular = cros_->FindCellularDevice(); + if (!cellular) { + LOG(ERROR) << "Didn't find cellular device, it should have been available."; + cros_->EnableCellularNetworkDevice(true); + } else if (cellular->sim_lock_state() == chromeos::SIM_UNLOCKED || + cellular->sim_lock_state() == chromeos::SIM_UNKNOWN) { + cros_->EnableCellularNetworkDevice(true); + } else { + chromeos::SimDialogDelegate::ShowDialog(GetNativeWindow(), + chromeos::SimDialogDelegate::SIM_DIALOG_UNLOCK); + } +} + +void InternetOptionsHandler::DisableCellularCallback(const ListValue* args) { + cros_->EnableCellularNetworkDevice(false); +} + +void InternetOptionsHandler::BuyDataPlanCallback(const ListValue* args) { + if (!web_ui_) + return; + Browser* browser = BrowserList::FindBrowserWithFeature( + Profile::FromWebUI(web_ui_), Browser::FEATURE_TABSTRIP); + if (browser) + browser->OpenMobilePlanTabAndActivate(); +} + +void InternetOptionsHandler::SetApnCallback(const ListValue* args) { + std::string service_path; + std::string apn; + std::string username; + std::string password; + if (args->GetSize() != 4 || + !args->GetString(0, &service_path) || + !args->GetString(1, &apn) || + !args->GetString(2, &username) || + !args->GetString(3, &password)) { + NOTREACHED(); + return; + } + + chromeos::CellularNetwork* network = + cros_->FindCellularNetworkByPath(service_path); + if (network) { + network->SetApn(chromeos::CellularApn( + apn, network->apn().network_id, username, password)); + } +} + +void InternetOptionsHandler::SetSimCardLockCallback(const ListValue* args) { + bool require_pin_new_value; + if (!args->GetBoolean(0, &require_pin_new_value)) { + NOTREACHED(); + return; + } + // 1. Bring up SIM unlock dialog, pass new RequirePin setting in URL. + // 2. Dialog will ask for current PIN in any case. + // 3. If card is locked it will first call PIN unlock operation + // 4. Then it will call Set RequirePin, passing the same PIN. + // 5. We'll get notified by REQUIRE_PIN_SETTING_CHANGE_ENDED notification. + chromeos::SimDialogDelegate::SimDialogMode mode; + if (require_pin_new_value) + mode = chromeos::SimDialogDelegate::SIM_DIALOG_SET_LOCK_ON; + else + mode = chromeos::SimDialogDelegate::SIM_DIALOG_SET_LOCK_OFF; + chromeos::SimDialogDelegate::ShowDialog(GetNativeWindow(), mode); +} + +void InternetOptionsHandler::ChangePinCallback(const ListValue* args) { + chromeos::SimDialogDelegate::ShowDialog(GetNativeWindow(), + chromeos::SimDialogDelegate::SIM_DIALOG_CHANGE_PIN); +} + +void InternetOptionsHandler::RefreshNetworkData() { + DictionaryValue dictionary; + FillNetworkInfo(&dictionary); + web_ui_->CallJavascriptFunction( + "options.InternetOptions.refreshNetworkData", dictionary); +} + +void InternetOptionsHandler::OnNetworkManagerChanged( + chromeos::NetworkLibrary* cros) { + if (!web_ui_) + return; + MonitorNetworks(); + RefreshNetworkData(); +} + +void InternetOptionsHandler::OnNetworkChanged( + chromeos::NetworkLibrary* cros, + const chromeos::Network* network) { + if (web_ui_) + RefreshNetworkData(); +} + +// Monitor wireless networks for changes. It is only necessary +// to set up individual observers for the cellular networks +// (if any) and for the connected Wi-Fi network (if any). The +// only change we are interested in for Wi-Fi networks is signal +// strength. For non-connected Wi-Fi networks, all information is +// reported via scan results, which trigger network manager +// updates. Only the connected Wi-Fi network has changes reported +// via service property updates. +void InternetOptionsHandler::MonitorNetworks() { + cros_->RemoveObserverForAllNetworks(this); + const chromeos::WifiNetwork* wifi_network = cros_->wifi_network(); + if (wifi_network) + cros_->AddNetworkObserver(wifi_network->service_path(), this); + // Always monitor the cellular networks, if any, so that changes + // in network technology, roaming status, and signal strength + // will be shown. + const chromeos::CellularNetworkVector& cell_networks = + cros_->cellular_networks(); + for (size_t i = 0; i < cell_networks.size(); ++i) { + chromeos::CellularNetwork* cell_network = cell_networks[i]; + cros_->AddNetworkObserver(cell_network->service_path(), this); + } + const chromeos::VirtualNetwork* virtual_network = cros_->virtual_network(); + if (virtual_network) + cros_->AddNetworkObserver(virtual_network->service_path(), this); +} + +void InternetOptionsHandler::OnCellularDataPlanChanged( + chromeos::NetworkLibrary* cros) { + if (!web_ui_) + return; + const chromeos::CellularNetwork* cellular = cros_->cellular_network(); + if (!cellular) + return; + const chromeos::CellularDataPlanVector* plans = + cros_->GetDataPlans(cellular->service_path()); + DictionaryValue connection_plans; + ListValue* plan_list = new ListValue(); + if (plans) { + for (chromeos::CellularDataPlanVector::const_iterator iter = plans->begin(); + iter != plans->end(); ++iter) { + plan_list->Append(CellularDataPlanToDictionary(*iter)); + } + } + connection_plans.SetString("servicePath", cellular->service_path()); + connection_plans.SetBoolean("needsPlan", cellular->needs_new_plan()); + connection_plans.SetBoolean("activated", + cellular->activation_state() == chromeos::ACTIVATION_STATE_ACTIVATED); + connection_plans.Set("plans", plan_list); + SetActivationButtonVisibility(cellular, &connection_plans); + web_ui_->CallJavascriptFunction( + "options.InternetOptions.updateCellularPlans", connection_plans); +} + + +void InternetOptionsHandler::Observe( + int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) { + OptionsPage2UIHandler::Observe(type, source, details); + if (type == chrome::NOTIFICATION_REQUIRE_PIN_SETTING_CHANGE_ENDED) { + base::FundamentalValue require_pin(*content::Details<bool>(details).ptr()); + web_ui_->CallJavascriptFunction( + "options.InternetOptions.updateSecurityTab", require_pin); + } else if (type == chrome::NOTIFICATION_ENTER_PIN_ENDED) { + // We make an assumption (which is valid for now) that the SIM + // unlock dialog is put up only when the user is trying to enable + // mobile data. + bool cancelled = *content::Details<bool>(details).ptr(); + if (cancelled) { + base::DictionaryValue dictionary; + FillNetworkInfo(&dictionary); + web_ui_->CallJavascriptFunction( + "options.InternetOptions.setupAttributes", dictionary); + } + // The case in which the correct PIN was entered and the SIM is + // now unlocked is handled in NetworkMenuButton. + } +} + +DictionaryValue* InternetOptionsHandler::CellularDataPlanToDictionary( + const chromeos::CellularDataPlan* plan) { + DictionaryValue* plan_dict = new DictionaryValue(); + plan_dict->SetInteger("planType", plan->plan_type); + plan_dict->SetString("name", plan->plan_name); + plan_dict->SetString("planSummary", plan->GetPlanDesciption()); + plan_dict->SetString("dataRemaining", plan->GetDataRemainingDesciption()); + plan_dict->SetString("planExpires", plan->GetPlanExpiration()); + plan_dict->SetString("warning", plan->GetRemainingWarning()); + return plan_dict; +} + +void InternetOptionsHandler::SetPreferNetworkCallback(const ListValue* args) { + std::string service_path; + std::string prefer_network_str; + + if (args->GetSize() < 2 || + !args->GetString(0, &service_path) || + !args->GetString(1, &prefer_network_str)) { + NOTREACHED(); + return; + } + + chromeos::Network* network = cros_->FindNetworkByPath(service_path); + if (!network) + return; + + bool prefer_network = prefer_network_str == "true"; + if (prefer_network != network->preferred()) + network->SetPreferred(prefer_network); +} + +void InternetOptionsHandler::SetAutoConnectCallback(const ListValue* args) { + std::string service_path; + std::string auto_connect_str; + + if (args->GetSize() < 2 || + !args->GetString(0, &service_path) || + !args->GetString(1, &auto_connect_str)) { + NOTREACHED(); + return; + } + + chromeos::Network* network = cros_->FindNetworkByPath(service_path); + if (!network) + return; + + bool auto_connect = auto_connect_str == "true"; + if (auto_connect != network->auto_connect()) + network->SetAutoConnect(auto_connect); +} + +void InternetOptionsHandler::SetIPConfigCallback(const ListValue* args) { + std::string service_path; + std::string dhcp_str; + std::string address; + std::string netmask; + std::string gateway; + std::string name_servers; + + if (args->GetSize() < 6 || + !args->GetString(0, &service_path) || + !args->GetString(1, &dhcp_str) || + !args->GetString(2, &address) || + !args->GetString(3, &netmask) || + !args->GetString(4, &gateway) || + !args->GetString(5, &name_servers)) { + NOTREACHED(); + return; + } + + chromeos::Network* network = cros_->FindNetworkByPath(service_path); + if (!network) + return; + + cros_->SetIPConfig(chromeos::NetworkIPConfig(network->device_path(), + dhcp_str == "true" ? chromeos::IPCONFIG_TYPE_DHCP : + chromeos::IPCONFIG_TYPE_IPV4, + address, netmask, gateway, name_servers)); +} + +void InternetOptionsHandler::PopulateDictionaryDetails( + const chromeos::Network* network) { + DCHECK(network); + + if (web_ui_) { + Profile::FromWebUI(web_ui_)->GetProxyConfigTracker()->UISetCurrentNetwork( + network->service_path()); + } + + DictionaryValue dictionary; + std::string hardware_address; + chromeos::NetworkIPConfigVector ipconfigs = cros_->GetIPConfigs( + network->device_path(), &hardware_address, + chromeos::NetworkLibrary::FORMAT_COLON_SEPARATED_HEX); + if (!hardware_address.empty()) + dictionary.SetString("hardwareAddress", hardware_address); + + scoped_ptr<DictionaryValue> ipconfig_dhcp; + scoped_ptr<DictionaryValue> ipconfig_static; + for (chromeos::NetworkIPConfigVector::const_iterator it = ipconfigs.begin(); + it != ipconfigs.end(); ++it) { + const chromeos::NetworkIPConfig& ipconfig = *it; + scoped_ptr<DictionaryValue> ipconfig_dict(new DictionaryValue()); + ipconfig_dict->SetString("address", ipconfig.address); + ipconfig_dict->SetString("subnetAddress", ipconfig.netmask); + ipconfig_dict->SetString("gateway", ipconfig.gateway); + ipconfig_dict->SetString("dns", ipconfig.name_servers); + if (ipconfig.type == chromeos::IPCONFIG_TYPE_DHCP) + ipconfig_dhcp.reset(ipconfig_dict.release()); + else if (ipconfig.type == chromeos::IPCONFIG_TYPE_IPV4) + ipconfig_static.reset(ipconfig_dict.release()); + } + + chromeos::NetworkPropertyUIData ipconfig_dhcp_ui_data(network, NULL); + SetValueDictionary(&dictionary, "ipconfigDHCP", ipconfig_dhcp.release(), + ipconfig_dhcp_ui_data); + chromeos::NetworkPropertyUIData ipconfig_static_ui_data(network, NULL); + SetValueDictionary(&dictionary, "ipconfigStatic", ipconfig_static.release(), + ipconfig_static_ui_data); + + chromeos::ConnectionType type = network->type(); + dictionary.SetInteger("type", type); + dictionary.SetString("servicePath", network->service_path()); + dictionary.SetBoolean("connecting", network->connecting()); + dictionary.SetBoolean("connected", network->connected()); + dictionary.SetString("connectionState", network->GetStateString()); + + // Only show proxy for remembered networks. + chromeos::NetworkProfileType network_profile = network->profile_type(); + dictionary.SetBoolean("showProxy", network_profile != chromeos::PROFILE_NONE); + + // Hide the dhcp/static radio if not ethernet or wifi (or if not enabled) + bool staticIPConfig = CommandLine::ForCurrentProcess()->HasSwitch( + switches::kEnableStaticIPConfig); + dictionary.SetBoolean("showStaticIPConfig", staticIPConfig && + (type == chromeos::TYPE_WIFI || type == chromeos::TYPE_ETHERNET)); + + chromeos::NetworkPropertyUIData preferred_ui_data( + network, chromeos::NetworkUIData::kPropertyPreferred); + if (network_profile == chromeos::PROFILE_USER) { + dictionary.SetBoolean("showPreferred", true); + SetValueDictionary(&dictionary, "preferred", + Value::CreateBooleanValue(network->preferred()), + preferred_ui_data); + } else { + dictionary.SetBoolean("showPreferred", false); + SetValueDictionary(&dictionary, "preferred", + Value::CreateBooleanValue(network->preferred()), + preferred_ui_data); + } + chromeos::NetworkPropertyUIData auto_connect_ui_data( + network, chromeos::NetworkUIData::kPropertyAutoConnect); + SetValueDictionary(&dictionary, "autoConnect", + Value::CreateBooleanValue(network->auto_connect()), + auto_connect_ui_data); + + if (type == chromeos::TYPE_WIFI) { + dictionary.SetBoolean("deviceConnected", cros_->wifi_connected()); + const chromeos::WifiNetwork* wifi = + cros_->FindWifiNetworkByPath(network->service_path()); + if (!wifi) { + LOG(WARNING) << "Cannot find network " << network->service_path(); + } else { + PopulateWifiDetails(wifi, &dictionary); + } + } else if (type == chromeos::TYPE_CELLULAR) { + dictionary.SetBoolean("deviceConnected", cros_->cellular_connected()); + const chromeos::CellularNetwork* cellular = + cros_->FindCellularNetworkByPath(network->service_path()); + if (!cellular) { + LOG(WARNING) << "Cannot find network " << network->service_path(); + } else { + PopulateCellularDetails(cellular, &dictionary); + } + } else if (type == chromeos::TYPE_VPN) { + dictionary.SetBoolean("deviceConnected", + cros_->virtual_network_connected()); + const chromeos::VirtualNetwork* vpn = + cros_->FindVirtualNetworkByPath(network->service_path()); + if (!vpn) { + LOG(WARNING) << "Cannot find network " << network->service_path(); + } else { + PopulateVPNDetails(vpn, &dictionary); + } + } else if (type == chromeos::TYPE_ETHERNET) { + dictionary.SetBoolean("deviceConnected", cros_->ethernet_connected()); + } + + web_ui_->CallJavascriptFunction( + "options.InternetOptions.showDetailedInfo", dictionary); +} + +void InternetOptionsHandler::PopulateWifiDetails( + const chromeos::WifiNetwork* wifi, + DictionaryValue* dictionary) { + dictionary->SetString("ssid", wifi->name()); + bool remembered = (wifi->profile_type() != chromeos::PROFILE_NONE); + dictionary->SetBoolean("remembered", remembered); + dictionary->SetBoolean("encrypted", wifi->encrypted()); + bool shared = wifi->profile_type() == chromeos::PROFILE_SHARED; + dictionary->SetBoolean("shared", shared); +} + +DictionaryValue* InternetOptionsHandler::CreateDictionaryFromCellularApn( + const chromeos::CellularApn& apn) { + DictionaryValue* dictionary = new DictionaryValue(); + dictionary->SetString("apn", apn.apn); + dictionary->SetString("networkId", apn.network_id); + dictionary->SetString("username", apn.username); + dictionary->SetString("password", apn.password); + dictionary->SetString("name", apn.name); + dictionary->SetString("localizedName", apn.localized_name); + dictionary->SetString("language", apn.language); + return dictionary; +} + +void InternetOptionsHandler::PopulateCellularDetails( + const chromeos::CellularNetwork* cellular, + DictionaryValue* dictionary) { + // Cellular network / connection settings. + dictionary->SetString("serviceName", cellular->name()); + dictionary->SetString("networkTechnology", + cellular->GetNetworkTechnologyString()); + dictionary->SetString("operatorName", cellular->operator_name()); + dictionary->SetString("operatorCode", cellular->operator_code()); + dictionary->SetString("activationState", + cellular->GetActivationStateString()); + dictionary->SetString("roamingState", + cellular->GetRoamingStateString()); + dictionary->SetString("restrictedPool", + cellular->restricted_pool() ? + l10n_util::GetStringUTF8( + IDS_CONFIRM_MESSAGEBOX_YES_BUTTON_LABEL) : + l10n_util::GetStringUTF8( + IDS_CONFIRM_MESSAGEBOX_NO_BUTTON_LABEL)); + dictionary->SetString("errorState", cellular->GetErrorString()); + dictionary->SetString("supportUrl", cellular->payment_url()); + dictionary->SetBoolean("needsPlan", cellular->needs_new_plan()); + + dictionary->Set("apn", CreateDictionaryFromCellularApn(cellular->apn())); + dictionary->Set("lastGoodApn", + CreateDictionaryFromCellularApn(cellular->last_good_apn())); + + // Device settings. + const chromeos::NetworkDevice* device = + cros_->FindNetworkDeviceByPath(cellular->device_path()); + if (device) { + chromeos::NetworkPropertyUIData cellular_propety_ui_data(cellular, NULL); + dictionary->SetString("manufacturer", device->manufacturer()); + dictionary->SetString("modelId", device->model_id()); + dictionary->SetString("firmwareRevision", device->firmware_revision()); + dictionary->SetString("hardwareRevision", device->hardware_revision()); + dictionary->SetString("prlVersion", + base::StringPrintf("%u", device->prl_version())); + dictionary->SetString("meid", device->meid()); + dictionary->SetString("imei", device->imei()); + dictionary->SetString("mdn", device->mdn()); + dictionary->SetString("imsi", device->imsi()); + dictionary->SetString("esn", device->esn()); + dictionary->SetString("min", device->min()); + dictionary->SetBoolean("gsm", + device->technology_family() == chromeos::TECHNOLOGY_FAMILY_GSM); + SetValueDictionary( + dictionary, "simCardLockEnabled", + Value::CreateBooleanValue( + device->sim_pin_required() == chromeos::SIM_PIN_REQUIRED), + cellular_propety_ui_data); + + chromeos::MobileConfig* config = chromeos::MobileConfig::GetInstance(); + if (config->IsReady()) { + std::string carrier_id = cros_->GetCellularHomeCarrierId(); + const chromeos::MobileConfig::Carrier* carrier = + config->GetCarrier(carrier_id); + if (carrier && !carrier->top_up_url().empty()) + dictionary->SetString("carrierUrl", carrier->top_up_url()); + } + + const chromeos::CellularApnList& apn_list = device->provider_apn_list(); + ListValue* apn_list_value = new ListValue(); + for (chromeos::CellularApnList::const_iterator it = apn_list.begin(); + it != apn_list.end(); ++it) { + apn_list_value->Append(CreateDictionaryFromCellularApn(*it)); + } + SetValueDictionary(dictionary, "providerApnList", apn_list_value, + cellular_propety_ui_data); + } + + SetActivationButtonVisibility(cellular, dictionary); +} + +void InternetOptionsHandler::PopulateVPNDetails( + const chromeos::VirtualNetwork* vpn, + DictionaryValue* dictionary) { + dictionary->SetString("service_name", vpn->name()); + bool remembered = (vpn->profile_type() != chromeos::PROFILE_NONE); + dictionary->SetBoolean("remembered", remembered); + dictionary->SetString("server_hostname", vpn->server_hostname()); + dictionary->SetString("provider_type", vpn->GetProviderTypeString()); + dictionary->SetString("username", vpn->username()); +} + +void InternetOptionsHandler::SetActivationButtonVisibility( + const chromeos::CellularNetwork* cellular, + DictionaryValue* dictionary) { + if (cellular->needs_new_plan()) { + dictionary->SetBoolean("showBuyButton", true); + } else if (cellular->activation_state() != + chromeos::ACTIVATION_STATE_ACTIVATING && + cellular->activation_state() != + chromeos::ACTIVATION_STATE_ACTIVATED) { + dictionary->SetBoolean("showActivateButton", true); + } +} + +void InternetOptionsHandler::CreateModalPopup(views::WidgetDelegate* view) { + views::Widget* window = browser::CreateViewsWindow(GetNativeWindow(), + view, + STYLE_GENERIC); + window->SetAlwaysOnTop(true); + window->Show(); +} + +gfx::NativeWindow InternetOptionsHandler::GetNativeWindow() const { + // TODO(beng): This is an improper direct dependency on Browser. Route this + // through some sort of delegate. + Browser* browser = + BrowserList::FindBrowserWithProfile(Profile::FromWebUI(web_ui_)); + return browser->window()->GetNativeHandle(); +} + +void InternetOptionsHandler::ButtonClickCallback(const ListValue* args) { + std::string str_type; + std::string service_path; + std::string command; + if (args->GetSize() != 3 || + !args->GetString(0, &str_type) || + !args->GetString(1, &service_path) || + !args->GetString(2, &command)) { + NOTREACHED(); + return; + } + + int type = atoi(str_type.c_str()); + if (type == chromeos::TYPE_ETHERNET) { + const chromeos::EthernetNetwork* ether = cros_->ethernet_network(); + if (ether) + PopulateDictionaryDetails(ether); + } else if (type == chromeos::TYPE_WIFI) { + HandleWifiButtonClick(service_path, command); + } else if (type == chromeos::TYPE_CELLULAR) { + HandleCellularButtonClick(service_path, command); + } else if (type == chromeos::TYPE_VPN) { + HandleVPNButtonClick(service_path, command); + } else { + NOTREACHED(); + } +} + +void InternetOptionsHandler::HandleWifiButtonClick( + const std::string& service_path, + const std::string& command) { + chromeos::WifiNetwork* wifi = NULL; + if (command == "forget") { + cros_->ForgetNetwork(service_path); + } else if (service_path == kOtherNetworksFakePath) { + // Other wifi networks. + CreateModalPopup(new chromeos::NetworkConfigView(chromeos::TYPE_WIFI)); + } else if ((wifi = cros_->FindWifiNetworkByPath(service_path))) { + if (command == "connect") { + // Connect to wifi here. Open password page if appropriate. + if (wifi->IsPassphraseRequired()) { + CreateModalPopup(new chromeos::NetworkConfigView(wifi)); + } else { + cros_->ConnectToWifiNetwork(wifi); + } + } else if (command == "disconnect") { + cros_->DisconnectFromNetwork(wifi); + } else if (command == "options") { + PopulateDictionaryDetails(wifi); + } + } +} + +void InternetOptionsHandler::HandleCellularButtonClick( + const std::string& service_path, + const std::string& command) { + chromeos::CellularNetwork* cellular = NULL; + if (service_path == kOtherNetworksFakePath) { + chromeos::ChooseMobileNetworkDialog::ShowDialog(GetNativeWindow()); + } else if ((cellular = cros_->FindCellularNetworkByPath(service_path))) { + if (command == "connect") { + cros_->ConnectToCellularNetwork(cellular); + } else if (command == "disconnect") { + cros_->DisconnectFromNetwork(cellular); + } else if (command == "activate") { + Browser* browser = BrowserList::GetLastActive(); + if (browser) + browser->OpenMobilePlanTabAndActivate(); + } else if (command == "options") { + PopulateDictionaryDetails(cellular); + } + } +} + +void InternetOptionsHandler::HandleVPNButtonClick( + const std::string& service_path, + const std::string& command) { + chromeos::VirtualNetwork* network = NULL; + if (command == "forget") { + cros_->ForgetNetwork(service_path); + } else if (service_path == kOtherNetworksFakePath) { + // TODO(altimofeev): verify if service_path in condition is correct. + // Other VPN networks. + CreateModalPopup(new chromeos::NetworkConfigView(chromeos::TYPE_VPN)); + } else if ((network = cros_->FindVirtualNetworkByPath(service_path))) { + if (command == "connect") { + // Connect to VPN here. Open password page if appropriate. + if (network->NeedMoreInfoToConnect()) { + CreateModalPopup(new chromeos::NetworkConfigView(network)); + } else { + cros_->ConnectToVirtualNetwork(network); + } + } else if (command == "disconnect") { + cros_->DisconnectFromNetwork(network); + } else if (command == "options") { + PopulateDictionaryDetails(network); + } + } +} + +void InternetOptionsHandler::RefreshCellularPlanCallback( + const ListValue* args) { + std::string service_path; + if (args->GetSize() != 1 || + !args->GetString(0, &service_path)) { + NOTREACHED(); + return; + } + const chromeos::CellularNetwork* cellular = + cros_->FindCellularNetworkByPath(service_path); + if (cellular) + cellular->RefreshDataPlansIfNeeded(); +} + +ListValue* InternetOptionsHandler::GetWiredList() { + ListValue* list = new ListValue(); + + // If ethernet is not enabled, then don't add anything. + if (cros_->ethernet_enabled()) { + const chromeos::EthernetNetwork* ethernet_network = + cros_->ethernet_network(); + if (ethernet_network) { + NetworkInfoDictionary network_dict(ethernet_network); + network_dict.set_name( + l10n_util::GetStringUTF8(IDS_STATUSBAR_NETWORK_DEVICE_ETHERNET)), + list->Append(network_dict.BuildDictionary()); + } + } + return list; +} + +ListValue* InternetOptionsHandler::GetWirelessList() { + ListValue* list = new ListValue(); + + const chromeos::WifiNetworkVector& wifi_networks = cros_->wifi_networks(); + for (chromeos::WifiNetworkVector::const_iterator it = + wifi_networks.begin(); it != wifi_networks.end(); ++it) { + NetworkInfoDictionary network_dict(*it); + network_dict.set_connectable(cros_->CanConnectToNetwork(*it)); + list->Append(network_dict.BuildDictionary()); + } + + // Add "Other WiFi network..." if wifi is enabled. + if (cros_->wifi_enabled()) { + NetworkInfoDictionary network_dict; + network_dict.set_service_path(kOtherNetworksFakePath); + network_dict.set_icon( + chromeos::NetworkMenuIcon::GetConnectedBitmap( + chromeos::NetworkMenuIcon::ARCS)); + network_dict.set_name( + l10n_util::GetStringUTF8(IDS_OPTIONS_SETTINGS_OTHER_WIFI_NETWORKS)); + network_dict.set_connectable(true); + network_dict.set_connection_type(chromeos::TYPE_WIFI); + list->Append(network_dict.BuildDictionary()); + } + + const chromeos::CellularNetworkVector cellular_networks = + cros_->cellular_networks(); + for (chromeos::CellularNetworkVector::const_iterator it = + cellular_networks.begin(); it != cellular_networks.end(); ++it) { + NetworkInfoDictionary network_dict(*it); + network_dict.set_connectable(cros_->CanConnectToNetwork(*it)); + network_dict.set_activation_state((*it)->activation_state()); + network_dict.set_needs_new_plan( + (*it)->SupportsDataPlan() && (*it)->restricted_pool()); + list->Append(network_dict.BuildDictionary()); + } + + const chromeos::NetworkDevice* cellular_device = cros_->FindCellularDevice(); + if (cellular_device && cellular_device->support_network_scan() && + cros_->cellular_enabled()) { + NetworkInfoDictionary network_dict; + network_dict.set_service_path(kOtherNetworksFakePath); + network_dict.set_icon( + chromeos::NetworkMenuIcon::GetDisconnectedBitmap( + chromeos::NetworkMenuIcon::BARS)); + network_dict.set_name( + l10n_util::GetStringUTF8(IDS_OPTIONS_SETTINGS_OTHER_CELLULAR_NETWORKS)); + network_dict.set_connectable(true); + network_dict.set_connection_type(chromeos::TYPE_CELLULAR); + network_dict.set_activation_state(chromeos::ACTIVATION_STATE_ACTIVATED); + list->Append(network_dict.BuildDictionary()); + } + + return list; +} + +ListValue* InternetOptionsHandler::GetVPNList() { + ListValue* list = new ListValue(); + + const chromeos::VirtualNetworkVector& virtual_networks = + cros_->virtual_networks(); + for (chromeos::VirtualNetworkVector::const_iterator it = + virtual_networks.begin(); it != virtual_networks.end(); ++it) { + NetworkInfoDictionary network_dict(*it); + network_dict.set_connectable(cros_->CanConnectToNetwork(*it)); + list->Append(network_dict.BuildDictionary()); + } + + return list; +} + +ListValue* InternetOptionsHandler::GetRememberedList() { + ListValue* list = new ListValue(); + + for (chromeos::WifiNetworkVector::const_iterator rit = + cros_->remembered_wifi_networks().begin(); + rit != cros_->remembered_wifi_networks().end(); ++rit) { + chromeos::WifiNetwork* remembered = *rit; + chromeos::WifiNetwork* wifi = static_cast<chromeos::WifiNetwork*>( + cros_->FindNetworkByUniqueId(remembered->unique_id())); + + NetworkInfoDictionary network_dict(wifi, remembered); + list->Append(network_dict.BuildDictionary()); + } + + for (chromeos::VirtualNetworkVector::const_iterator rit = + cros_->remembered_virtual_networks().begin(); + rit != cros_->remembered_virtual_networks().end(); ++rit) { + chromeos::VirtualNetwork* remembered = *rit; + chromeos::VirtualNetwork* vpn = static_cast<chromeos::VirtualNetwork*>( + cros_->FindNetworkByUniqueId(remembered->unique_id())); + + NetworkInfoDictionary network_dict(vpn, remembered); + list->Append(network_dict.BuildDictionary()); + } + + return list; +} + +void InternetOptionsHandler::FillNetworkInfo(DictionaryValue* dictionary) { + dictionary->SetBoolean("accessLocked", cros_->IsLocked()); + dictionary->Set("wiredList", GetWiredList()); + dictionary->Set("wirelessList", GetWirelessList()); + dictionary->Set("vpnList", GetVPNList()); + dictionary->Set("rememberedList", GetRememberedList()); + dictionary->SetBoolean("wifiAvailable", cros_->wifi_available()); + dictionary->SetBoolean("wifiBusy", cros_->wifi_busy()); + dictionary->SetBoolean("wifiEnabled", cros_->wifi_enabled()); + dictionary->SetBoolean("cellularAvailable", cros_->cellular_available()); + dictionary->SetBoolean("cellularBusy", cros_->cellular_busy()); + dictionary->SetBoolean("cellularEnabled", cros_->cellular_enabled()); +} + +void InternetOptionsHandler::SetValueDictionary( + DictionaryValue* settings, + const char* key, + base::Value* value, + const chromeos::NetworkPropertyUIData& ui_data) { + DictionaryValue* value_dict = new DictionaryValue(); + // DictionaryValue::Set() takes ownership of |value|. + if (value) + value_dict->Set("value", value); + const base::Value* default_value = ui_data.default_value(); + if (default_value) + value_dict->Set("default", default_value->DeepCopy()); + if (ui_data.managed()) + value_dict->SetString("controlledBy", "policy"); + else if (ui_data.recommended()) + value_dict->SetString("controlledBy", "recommended"); + settings->Set(key, value_dict); +} diff --git a/chrome/browser/ui/webui/options2/chromeos/internet_options_handler.h b/chrome/browser/ui/webui/options2/chromeos/internet_options_handler.h new file mode 100644 index 0000000..568f097 --- /dev/null +++ b/chrome/browser/ui/webui/options2/chromeos/internet_options_handler.h @@ -0,0 +1,148 @@ +// Copyright (c) 2011 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. + +#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_INTERNET_OPTIONS_HANDLER_H_ +#define CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_INTERNET_OPTIONS_HANDLER_H_ + +#include <string> + +#include "base/compiler_specific.h" +#include "chrome/browser/chromeos/cros/network_library.h" +#include "chrome/browser/chromeos/cros/network_ui_data.h" +#include "chrome/browser/ui/webui/options2/options_ui2.h" +#include "content/public/browser/notification_registrar.h" +#include "ui/gfx/native_widget_types.h" + +class SkBitmap; +namespace views { +class WidgetDelegate; +} + +// ChromeOS internet options page UI handler. +class InternetOptionsHandler + : public OptionsPage2UIHandler, + public chromeos::NetworkLibrary::NetworkManagerObserver, + public chromeos::NetworkLibrary::NetworkObserver, + public chromeos::NetworkLibrary::CellularDataPlanObserver { + public: + InternetOptionsHandler(); + virtual ~InternetOptionsHandler(); + + // OptionsPage2UIHandler implementation. + virtual void GetLocalizedValues( + base::DictionaryValue* localized_strings) OVERRIDE; + virtual void Initialize() OVERRIDE; + + // WebUIMessageHandler implementation. + virtual void RegisterMessages() OVERRIDE; + + // NetworkLibrary::NetworkManagerObserver implementation. + virtual void OnNetworkManagerChanged( + chromeos::NetworkLibrary* network_lib) OVERRIDE; + // NetworkLibrary::NetworkObserver implementation. + virtual void OnNetworkChanged(chromeos::NetworkLibrary* network_lib, + const chromeos::Network* network) OVERRIDE; + // NetworkLibrary::CellularDataPlanObserver implementation. + virtual void OnCellularDataPlanChanged( + chromeos::NetworkLibrary* network_lib) OVERRIDE; + + // content::NotificationObserver implementation. + virtual void Observe(int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) OVERRIDE; + + private: + // Opens a modal popup dialog. + void CreateModalPopup(views::WidgetDelegate* view); + gfx::NativeWindow GetNativeWindow() const; + + // Passes data needed to show details overlay for network. + // |args| will be [ network_type, service_path, command ] + // And command is one of 'options', 'connect', disconnect', 'activate' or + // 'forget' + // Handle{Wifi,Cellular}ButtonClick handles button click on a wireless + // network item and a cellular network item respectively. + void ButtonClickCallback(const base::ListValue* args); + void HandleWifiButtonClick(const std::string& service_path, + const std::string& command); + void HandleCellularButtonClick(const std::string& service_path, + const std::string& command); + void HandleVPNButtonClick(const std::string& service_path, + const std::string& command); + + // Initiates cellular plan data refresh. The results from libcros will be + // passed through CellularDataPlanChanged() callback method. + // |args| will be [ service_path ] + void RefreshCellularPlanCallback(const base::ListValue* args); + void SetActivationButtonVisibility( + const chromeos::CellularNetwork* cellular, + base::DictionaryValue* dictionary); + + void SetPreferNetworkCallback(const base::ListValue* args); + void SetAutoConnectCallback(const base::ListValue* args); + void SetSharedCallback(const base::ListValue* args); + void SetIPConfigCallback(const base::ListValue* args); + void EnableWifiCallback(const base::ListValue* args); + void DisableWifiCallback(const base::ListValue* args); + void EnableCellularCallback(const base::ListValue* args); + void DisableCellularCallback(const base::ListValue* args); + void BuyDataPlanCallback(const base::ListValue* args); + void SetApnCallback(const base::ListValue* args); + void SetSimCardLockCallback(const base::ListValue* args); + void ChangePinCallback(const base::ListValue* args); + void ShareNetworkCallback(const base::ListValue* args); + + // Populates the ui with the details of the given device path. This forces + // an overlay to be displayed in the UI. + void PopulateDictionaryDetails(const chromeos::Network* network); + void PopulateWifiDetails(const chromeos::WifiNetwork* wifi, + base::DictionaryValue* dictionary); + void PopulateCellularDetails(const chromeos::CellularNetwork* cellular, + base::DictionaryValue* dictionary); + void PopulateVPNDetails(const chromeos::VirtualNetwork* vpn, + base::DictionaryValue* dictionary); + + // Converts CellularDataPlan structure into dictionary for JS. Formats plan + // settings into human readable texts. + base::DictionaryValue* CellularDataPlanToDictionary( + const chromeos::CellularDataPlan* plan); + + // Converts CellularApn stuct into dictionary for JS. + base::DictionaryValue* CreateDictionaryFromCellularApn( + const chromeos::CellularApn& apn); + + // Creates the map of wired networks. + base::ListValue* GetWiredList(); + // Creates the map of wireless networks. + base::ListValue* GetWirelessList(); + // Creates the map of virtual networks. + base::ListValue* GetVPNList(); + // Creates the map of remembered networks. + base::ListValue* GetRememberedList(); + // Fills network information into JS dictionary for displaying network lists. + void FillNetworkInfo(base::DictionaryValue* dictionary); + // Refreshes the display of network information. + void RefreshNetworkData(); + // Adds observers for wireless networks, if any, so that we can dynamically + // display the correct icon for that network's signal strength and, in the + // case of cellular networks, network technology and roaming status. + void MonitorNetworks(); + + // Stores a dictionary under |key| in |settings| that is suitable to be sent + // to the webui that contains the actual value of a setting and whether it's + // controlled by policy. Takes ownership of |value|. + void SetValueDictionary(DictionaryValue* settings, + const char* key, + base::Value* value, + const chromeos::NetworkPropertyUIData& ui_data); + + // Convenience pointer to netwrok library (will not change). + chromeos::NetworkLibrary* cros_; + + content::NotificationRegistrar registrar_; + + DISALLOW_COPY_AND_ASSIGN(InternetOptionsHandler); +}; + +#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_INTERNET_OPTIONS_HANDLER_H_ diff --git a/chrome/browser/ui/webui/options2/chromeos/language_chewing_handler.cc b/chrome/browser/ui/webui/options2/chromeos/language_chewing_handler.cc new file mode 100644 index 0000000..c3c3096 --- /dev/null +++ b/chrome/browser/ui/webui/options2/chromeos/language_chewing_handler.cc @@ -0,0 +1,103 @@ +// Copyright (c) 2011 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. + +#include "chrome/browser/ui/webui/options2/chromeos/language_chewing_handler.h" + +#include <limits> + +#include "base/logging.h" +#include "base/string_number_conversions.h" +#include "base/utf_string_conversions.h" +#include "base/values.h" +#include "chrome/browser/chromeos/language_preferences.h" +#include "chrome/browser/ui/webui/options2/chromeos/language_options_util.h" +#include "grit/generated_resources.h" +#include "ui/base/l10n/l10n_util.h" + +namespace { +const char kI18nPrefix[] = "Chewing_"; +} // namespace + +namespace chromeos { + +LanguageChewingHandler::LanguageChewingHandler() { +} + +LanguageChewingHandler::~LanguageChewingHandler() { +} + +void LanguageChewingHandler::GetLocalizedValues( + DictionaryValue* localized_strings) { + DCHECK(localized_strings); + + RegisterTitle(localized_strings, "languageChewingPage", + IDS_OPTIONS_SETTINGS_LANGUAGES_CHEWING_SETTINGS_TITLE); + + for (size_t i = 0; i < language_prefs::kNumChewingBooleanPrefs; ++i) { + localized_strings->SetString( + GetI18nContentValue(language_prefs::kChewingBooleanPrefs[i], + kI18nPrefix), + l10n_util::GetStringUTF16( + language_prefs::kChewingBooleanPrefs[i].message_id)); + } + + // For maximum Chinese characters in pre-edit buffer, we use slider UI. + { + const language_prefs::LanguageIntegerRangePreference& preference = + language_prefs::kChewingIntegerPrefs[ + language_prefs::kChewingMaxChiSymbolLenIndex]; + localized_strings->SetString( + GetI18nContentValue(preference, kI18nPrefix), + l10n_util::GetStringUTF16(preference.message_id)); + localized_strings->SetString( + GetTemplateDataMinName(preference, kI18nPrefix), + base::IntToString(preference.min_pref_value)); + localized_strings->SetString( + GetTemplateDataMaxName(preference, kI18nPrefix), + base::IntToString(preference.max_pref_value)); + } + + // For number of candidates per page, we use select-option UI. + { + const language_prefs::LanguageIntegerRangePreference& preference = + language_prefs::kChewingIntegerPrefs[ + language_prefs::kChewingCandPerPageIndex]; + localized_strings->SetString( + GetI18nContentValue(preference, kI18nPrefix), + l10n_util::GetStringUTF16(preference.message_id)); + ListValue* list_value = new ListValue(); + for (int i = preference.min_pref_value; i <= preference.max_pref_value; + ++i) { + ListValue* option = new ListValue(); + option->Append(CreateValue(i)); + option->Append(CreateValue(i)); + list_value->Append(option); + } + localized_strings->Set(GetTemplateDataPropertyName(preference, kI18nPrefix), + list_value); + } + + for (size_t i = 0; i < language_prefs::kNumChewingMultipleChoicePrefs; + ++i) { + const language_prefs::LanguageMultipleChoicePreference<const char*>& + preference = language_prefs::kChewingMultipleChoicePrefs[i]; + localized_strings->SetString( + GetI18nContentValue(preference, kI18nPrefix), + l10n_util::GetStringUTF16(preference.label_message_id)); + localized_strings->Set( + GetTemplateDataPropertyName(preference, kI18nPrefix), + CreateMultipleChoiceList(preference)); + } + + localized_strings->SetString( + GetI18nContentValue(language_prefs::kChewingHsuSelKeyType, kI18nPrefix), + l10n_util::GetStringUTF16( + language_prefs::kChewingHsuSelKeyType.label_message_id)); + localized_strings->Set( + GetTemplateDataPropertyName(language_prefs::kChewingHsuSelKeyType, + kI18nPrefix), + CreateMultipleChoiceList(language_prefs::kChewingHsuSelKeyType)); +} + +} // namespace chromeos diff --git a/chrome/browser/ui/webui/options2/chromeos/language_chewing_handler.h b/chrome/browser/ui/webui/options2/chromeos/language_chewing_handler.h new file mode 100644 index 0000000..5a37249 --- /dev/null +++ b/chrome/browser/ui/webui/options2/chromeos/language_chewing_handler.h @@ -0,0 +1,35 @@ +// Copyright (c) 2011 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. + +#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_LANGUAGE_CHEWING_HANDLER_H_ +#define CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_LANGUAGE_CHEWING_HANDLER_H_ +#pragma once + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "chrome/browser/ui/webui/options2/options_ui2.h" + +namespace base { +class DictionaryValue; +} + +namespace chromeos { + +// Chewing options page UI handler. +class LanguageChewingHandler : public OptionsPage2UIHandler { + public: + LanguageChewingHandler(); + virtual ~LanguageChewingHandler(); + + // OptionsPage2UIHandler implementation. + virtual void GetLocalizedValues( + base::DictionaryValue* localized_strings) OVERRIDE; + + private: + DISALLOW_COPY_AND_ASSIGN(LanguageChewingHandler); +}; + +} // namespace chromeos + +#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_LANGUAGE_CHEWING_HANDLER_H_ diff --git a/chrome/browser/ui/webui/options2/chromeos/language_customize_modifier_keys_handler.cc b/chrome/browser/ui/webui/options2/chromeos/language_customize_modifier_keys_handler.cc new file mode 100644 index 0000000..d95b2ace --- /dev/null +++ b/chrome/browser/ui/webui/options2/chromeos/language_customize_modifier_keys_handler.cc @@ -0,0 +1,79 @@ +// Copyright (c) 2011 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. + +#include "chrome/browser/ui/webui/options2/chromeos/language_customize_modifier_keys_handler.h" + +#include "base/values.h" +#include "chrome/browser/chromeos/input_method/xkeyboard.h" +#include "grit/generated_resources.h" +#include "ui/base/l10n/l10n_util.h" + +namespace { +const struct ModifierKeysSelectItem { + int message_id; + chromeos::input_method::ModifierKey value; +} kModifierKeysSelectItems[] = { + { IDS_OPTIONS_SETTINGS_LANGUAGES_XKB_KEY_SEARCH, + chromeos::input_method::kSearchKey }, + { IDS_OPTIONS_SETTINGS_LANGUAGES_XKB_KEY_LEFT_CTRL, + chromeos::input_method::kLeftControlKey }, + { IDS_OPTIONS_SETTINGS_LANGUAGES_XKB_KEY_LEFT_ALT, + chromeos::input_method::kLeftAltKey }, + { IDS_OPTIONS_SETTINGS_LANGUAGES_XKB_KEY_VOID, + chromeos::input_method::kVoidKey }, + { IDS_OPTIONS_SETTINGS_LANGUAGES_XKB_KEY_CAPS_LOCK, + chromeos::input_method::kCapsLockKey }, +}; + +const char* kDataValuesNames[] = { + "xkbRemapSearchKeyToValue", + "xkbRemapControlKeyToValue", + "xkbRemapAltKeyToValue", +}; +} // namespace + +namespace chromeos { + +LanguageCustomizeModifierKeysHandler::LanguageCustomizeModifierKeysHandler() { +} + +LanguageCustomizeModifierKeysHandler::~LanguageCustomizeModifierKeysHandler() { +} + +void LanguageCustomizeModifierKeysHandler::GetLocalizedValues( + DictionaryValue* localized_strings) { + DCHECK(localized_strings); + + localized_strings->SetString("xkbRemapSearchKeyToContent", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_LANGUAGES_XKB_KEY_SEARCH_LABEL)); + localized_strings->SetString("xkbRemapControlKeyToContent", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_LANGUAGES_XKB_KEY_LEFT_CTRL_LABEL)); + localized_strings->SetString("xkbRemapAltKeyToContent", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_LANGUAGES_XKB_KEY_LEFT_ALT_LABEL)); + + for (size_t i = 0; i < arraysize(kDataValuesNames); ++i) { + ListValue* list_value = new ListValue(); + for (size_t j = 0; j < arraysize(kModifierKeysSelectItems); ++j) { + const input_method::ModifierKey value = + kModifierKeysSelectItems[j].value; + const int message_id = kModifierKeysSelectItems[j].message_id; + // Only the seach key can be remapped to the caps lock key. + if (kDataValuesNames[i] != std::string("xkbRemapSearchKeyToValue") && + value == input_method::kCapsLockKey) { + continue; + } + ListValue* option = new ListValue(); + option->Append(Value::CreateIntegerValue(value)); + option->Append(Value::CreateStringValue(l10n_util::GetStringUTF16( + message_id))); + list_value->Append(option); + } + localized_strings->Set(kDataValuesNames[i], list_value); + } +} + +} // namespace chromeos diff --git a/chrome/browser/ui/webui/options2/chromeos/language_customize_modifier_keys_handler.h b/chrome/browser/ui/webui/options2/chromeos/language_customize_modifier_keys_handler.h new file mode 100644 index 0000000..67ea8bd --- /dev/null +++ b/chrome/browser/ui/webui/options2/chromeos/language_customize_modifier_keys_handler.h @@ -0,0 +1,30 @@ +// Copyright (c) 2011 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. + +#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_LANGUAGE_CUSTOMIZE_MODIFIER_KEYS_HANDLER_H_ // NOLINT +#define CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_LANGUAGE_CUSTOMIZE_MODIFIER_KEYS_HANDLER_H_ // NOLINT +#pragma once + +#include "base/compiler_specific.h" +#include "chrome/browser/prefs/pref_member.h" +#include "chrome/browser/ui/webui/options2/options_ui2.h" + +namespace chromeos { + +// Customize modifier keys overlay page UI handler. +class LanguageCustomizeModifierKeysHandler : public OptionsPage2UIHandler { + public: + LanguageCustomizeModifierKeysHandler(); + virtual ~LanguageCustomizeModifierKeysHandler(); + + // OptionsPage2UIHandler implementation. + virtual void GetLocalizedValues(DictionaryValue* localized_strings) OVERRIDE; + + private: + DISALLOW_COPY_AND_ASSIGN(LanguageCustomizeModifierKeysHandler); +}; + +} // namespace chromeos + +#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_LANGUAGE_CUSTOMIZE_MODIFIER_KEYS_HANDLER_H_ // NOLINT diff --git a/chrome/browser/ui/webui/options2/chromeos/language_hangul_handler.cc b/chrome/browser/ui/webui/options2/chromeos/language_hangul_handler.cc new file mode 100644 index 0000000..5362ccf --- /dev/null +++ b/chrome/browser/ui/webui/options2/chromeos/language_hangul_handler.cc @@ -0,0 +1,47 @@ +// Copyright (c) 2011 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. + +#include "chrome/browser/ui/webui/options2/chromeos/language_hangul_handler.h" + +#include "base/utf_string_conversions.h" +#include "base/values.h" +#include "chrome/browser/chromeos/language_preferences.h" +#include "grit/generated_resources.h" +#include "ui/base/l10n/l10n_util.h" + +namespace chromeos { + +LanguageHangulHandler::LanguageHangulHandler() { +} + +LanguageHangulHandler::~LanguageHangulHandler() { +} + +void LanguageHangulHandler::GetLocalizedValues( + DictionaryValue* localized_strings) { + DCHECK(localized_strings); + + RegisterTitle(localized_strings, "languageHangulPage", + IDS_OPTIONS_SETTINGS_LANGUAGES_HANGUL_SETTINGS_TITLE); + + localized_strings->SetString("hangul_keyboard_layout", + l10n_util::GetStringUTF16(IDS_OPTIONS_SETTINGS_KEYBOARD_LAYOUT_TEXT)); + + localized_strings->Set("HangulkeyboardLayoutList", GetKeyboardLayoutList()); +} + +ListValue* LanguageHangulHandler::GetKeyboardLayoutList() { + ListValue* keyboard_layout_list = new ListValue(); + for (size_t i = 0; i < language_prefs::kNumHangulKeyboardNameIDPairs; ++i) { + ListValue* option = new ListValue(); + option->Append(Value::CreateStringValue( + language_prefs::kHangulKeyboardNameIDPairs[i].keyboard_id)); + option->Append(Value::CreateStringValue(l10n_util::GetStringUTF16( + language_prefs::kHangulKeyboardNameIDPairs[i].message_id))); + keyboard_layout_list->Append(option); + } + return keyboard_layout_list; +} + +} // namespace chromeos diff --git a/chrome/browser/ui/webui/options2/chromeos/language_hangul_handler.h b/chrome/browser/ui/webui/options2/chromeos/language_hangul_handler.h new file mode 100644 index 0000000..8b1c9de --- /dev/null +++ b/chrome/browser/ui/webui/options2/chromeos/language_hangul_handler.h @@ -0,0 +1,38 @@ +// Copyright (c) 2011 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. + +#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_LANGUAGE_HANGUL_HANDLER_H_ +#define CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_LANGUAGE_HANGUL_HANDLER_H_ +#pragma once + +#include "base/compiler_specific.h" +#include "chrome/browser/ui/webui/options2/options_ui2.h" + +namespace base { +class DictionaryValue; +class ListValue; +} + +namespace chromeos { + +// Hangul options page UI handler. +class LanguageHangulHandler : public OptionsPage2UIHandler { + public: + LanguageHangulHandler(); + virtual ~LanguageHangulHandler(); + + // OptionsPage2UIHandler implementation. + virtual void GetLocalizedValues( + base::DictionaryValue* localized_strings) OVERRIDE; + + private: + // Returns the list of hangul keyboards. + static base::ListValue* GetKeyboardLayoutList(); + + DISALLOW_COPY_AND_ASSIGN(LanguageHangulHandler); +}; + +} // namespace chromeos + +#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_LANGUAGE_HANGUL_HANDLER_H_ diff --git a/chrome/browser/ui/webui/options2/chromeos/language_mozc_handler.cc b/chrome/browser/ui/webui/options2/chromeos/language_mozc_handler.cc new file mode 100644 index 0000000..ce8afe9 --- /dev/null +++ b/chrome/browser/ui/webui/options2/chromeos/language_mozc_handler.cc @@ -0,0 +1,70 @@ +// Copyright (c) 2011 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. + +#include "chrome/browser/ui/webui/options2/chromeos/language_mozc_handler.h" + +#include "base/string_number_conversions.h" +#include "base/utf_string_conversions.h" +#include "base/values.h" +#include "chrome/browser/chromeos/language_preferences.h" +#include "chrome/browser/ui/webui/options2/chromeos/language_options_util.h" +#include "grit/generated_resources.h" +#include "ui/base/l10n/l10n_util.h" + +namespace { +const char kI18nPrefix[] = "mozc_"; +} // namespace + +namespace chromeos { + +LanguageMozcHandler::LanguageMozcHandler() { +} + +LanguageMozcHandler::~LanguageMozcHandler() { +} + +void LanguageMozcHandler::GetLocalizedValues( + DictionaryValue* localized_strings) { + DCHECK(localized_strings); + + RegisterTitle(localized_strings, "languageMozcPage", + IDS_OPTIONS_SETTINGS_LANGUAGES_MOZC_SETTINGS_TITLE); + + for (size_t i = 0; i < language_prefs::kNumMozcBooleanPrefs; ++i) { + localized_strings->SetString( + GetI18nContentValue(language_prefs::kMozcBooleanPrefs[i], kI18nPrefix), + l10n_util::GetStringUTF16( + language_prefs::kMozcBooleanPrefs[i].message_id)); + } + + for (size_t i = 0; i < language_prefs::kNumMozcMultipleChoicePrefs; ++i) { + const language_prefs::LanguageMultipleChoicePreference<const char*>& + preference = language_prefs::kMozcMultipleChoicePrefs[i]; + localized_strings->SetString( + GetI18nContentValue(preference, kI18nPrefix), + l10n_util::GetStringUTF16(preference.label_message_id)); + localized_strings->Set(GetTemplateDataPropertyName(preference, kI18nPrefix), + CreateMultipleChoiceList(preference)); + } + + for (size_t i = 0; i < language_prefs::kNumMozcIntegerPrefs; ++i) { + const language_prefs::LanguageIntegerRangePreference& preference = + language_prefs::kMozcIntegerPrefs[i]; + localized_strings->SetString( + GetI18nContentValue(preference, kI18nPrefix), + l10n_util::GetStringUTF16(preference.message_id)); + ListValue* list_value = new ListValue(); + for (int j = preference.min_pref_value; j <= preference.max_pref_value; + ++j) { + ListValue* option = new ListValue(); + option->Append(CreateValue(j)); + option->Append(CreateValue(j)); + list_value->Append(option); + } + localized_strings->Set(GetTemplateDataPropertyName(preference, kI18nPrefix), + list_value); + } +} + +} // namespace chromeos diff --git a/chrome/browser/ui/webui/options2/chromeos/language_mozc_handler.h b/chrome/browser/ui/webui/options2/chromeos/language_mozc_handler.h new file mode 100644 index 0000000..df1410e --- /dev/null +++ b/chrome/browser/ui/webui/options2/chromeos/language_mozc_handler.h @@ -0,0 +1,34 @@ +// Copyright (c) 2011 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. + +#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_LANGUAGE_MOZC_HANDLER_H_ +#define CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_LANGUAGE_MOZC_HANDLER_H_ +#pragma once + +#include "base/compiler_specific.h" +#include "chrome/browser/ui/webui/options2/options_ui2.h" + +namespace base { +class DictionaryValue; +} + +namespace chromeos { + +// Mozc options page UI handler. +class LanguageMozcHandler : public OptionsPage2UIHandler { + public: + LanguageMozcHandler(); + virtual ~LanguageMozcHandler(); + + // OptionsPage2UIHandler implementation. + virtual void GetLocalizedValues( + base::DictionaryValue* localized_strings) OVERRIDE; + + private: + DISALLOW_COPY_AND_ASSIGN(LanguageMozcHandler); +}; + +} // namespace chromeos + +#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_LANGUAGE_MOZC_HANDLER_H_ diff --git a/chrome/browser/ui/webui/options2/chromeos/language_options_util.cc b/chrome/browser/ui/webui/options2/chromeos/language_options_util.cc new file mode 100644 index 0000000..6108070 --- /dev/null +++ b/chrome/browser/ui/webui/options2/chromeos/language_options_util.cc @@ -0,0 +1,18 @@ +// Copyright (c) 2011 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. + +#include "chrome/browser/ui/webui/options2/chromeos/language_options_util.h" + +namespace chromeos { + +// See comments in .h. +Value* CreateValue(const char* in_value) { + return Value::CreateStringValue(in_value); +} + +Value* CreateValue(int in_value) { + return Value::CreateIntegerValue(in_value); +} + +} // namespace chromeos diff --git a/chrome/browser/ui/webui/options2/chromeos/language_options_util.h b/chrome/browser/ui/webui/options2/chromeos/language_options_util.h new file mode 100644 index 0000000..70f7307 --- /dev/null +++ b/chrome/browser/ui/webui/options2/chromeos/language_options_util.h @@ -0,0 +1,83 @@ +// Copyright (c) 2011 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. + +#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_LANGUAGE_OPTIONS_UTIL_H_ +#define CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_LANGUAGE_OPTIONS_UTIL_H_ +#pragma once + +#include <string> + +#include "base/utf_string_conversions.h" +#include "base/values.h" +#include "chrome/browser/chromeos/language_preferences.h" +#include "ui/base/l10n/l10n_util.h" + +namespace base { +class ListValue; +} + +namespace chromeos { + +// Returns an i18n-content value corresponding to |preference|. +template <typename T> +std::string GetI18nContentValue(const T& preference, const char* prefix) { + return std::string(prefix) + preference.ibus_config_name; +} + +// Returns a property name of templateData corresponding to |preference|. +template <typename T> +std::string GetTemplateDataPropertyName(const T& preference, + const char* prefix) { + return std::string(prefix) + preference.ibus_config_name + "Value"; +} + +// Returns an property name of templateData corresponding the value of the min +// attribute. +template <typename T> +std::string GetTemplateDataMinName(const T& preference, const char* prefix) { + return std::string(prefix) + preference.ibus_config_name + "Min"; +} + +// Returns an property name of templateData corresponding the value of the max +// attribute. +template <typename T> +std::string GetTemplateDataMaxName(const T& preference, const char* prefix) { + return std::string(prefix) + preference.ibus_config_name + "Max"; +} + +// Creates a Value object from the given value. Here we use function +// overloading to handle string and integer preferences in +// CreateMultipleChoiceList. +Value* CreateValue(const char* in_value); +Value* CreateValue(int in_value); + +// Creates a multiple choice list from the given preference. +template <typename T> +base::ListValue* CreateMultipleChoiceList( + const language_prefs::LanguageMultipleChoicePreference<T>& preference) { + int list_length = 0; + for (size_t i = 0; + i < language_prefs::LanguageMultipleChoicePreference<T>::kMaxItems; + ++i) { + if (preference.values_and_ids[i].item_message_id == 0) + break; + ++list_length; + } + DCHECK_GT(list_length, 0); + + base::ListValue* list_value = new base::ListValue(); + for (int i = 0; i < list_length; ++i) { + base::ListValue* option = new base::ListValue(); + option->Append(CreateValue( + preference.values_and_ids[i].ibus_config_value)); + option->Append(base::Value::CreateStringValue(l10n_util::GetStringUTF16( + preference.values_and_ids[i].item_message_id))); + list_value->Append(option); + } + return list_value; +} + +} // namespace chromeos + +#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_LANGUAGE_OPTIONS_UTIL_H_ diff --git a/chrome/browser/ui/webui/options2/chromeos/language_pinyin_handler.cc b/chrome/browser/ui/webui/options2/chromeos/language_pinyin_handler.cc new file mode 100644 index 0000000..8e2c19c --- /dev/null +++ b/chrome/browser/ui/webui/options2/chromeos/language_pinyin_handler.cc @@ -0,0 +1,67 @@ +// Copyright (c) 2011 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. + +#include "chrome/browser/ui/webui/options2/chromeos/language_pinyin_handler.h" + +#include "base/values.h" +#include "chrome/browser/chromeos/language_preferences.h" +#include "chrome/browser/ui/webui/options2/chromeos/language_options_util.h" +#include "grit/generated_resources.h" +#include "ui/base/l10n/l10n_util.h" + +namespace { +const char kI18nPrefix[] = "Pinyin"; +} // namespace + +namespace chromeos { + +LanguagePinyinHandler::LanguagePinyinHandler() { +} + +LanguagePinyinHandler::~LanguagePinyinHandler() { +} + +void LanguagePinyinHandler::GetLocalizedValues( + DictionaryValue* localized_strings) { + DCHECK(localized_strings); + + RegisterTitle(localized_strings, "languagePinyinPage", + IDS_OPTIONS_SETTINGS_LANGUAGES_PINYIN_SETTINGS_TITLE); + + for (size_t i = 0; i < language_prefs::kNumPinyinBooleanPrefs; ++i) { + localized_strings->SetString( + GetI18nContentValue(language_prefs::kPinyinBooleanPrefs[i], + kI18nPrefix), + l10n_util::GetStringUTF16( + language_prefs::kPinyinBooleanPrefs[i].message_id)); + } + + localized_strings->SetString( + GetI18nContentValue(language_prefs::kPinyinDoublePinyinSchema, + kI18nPrefix), + l10n_util::GetStringUTF16( + language_prefs::kPinyinDoublePinyinSchema.label_message_id)); + ListValue* list_value = new ListValue(); + for (size_t i = 0; + i < language_prefs::LanguageMultipleChoicePreference<int>::kMaxItems; + ++i) { + if (language_prefs::kPinyinDoublePinyinSchema.values_and_ids[i]. + item_message_id == 0) + break; + ListValue* option = new ListValue(); + option->Append(Value::CreateIntegerValue( + language_prefs::kPinyinDoublePinyinSchema.values_and_ids[i]. + ibus_config_value)); + option->Append(Value::CreateStringValue(l10n_util::GetStringUTF16( + language_prefs::kPinyinDoublePinyinSchema.values_and_ids[i]. + item_message_id))); + list_value->Append(option); + } + localized_strings->Set( + GetTemplateDataPropertyName(language_prefs::kPinyinDoublePinyinSchema, + kI18nPrefix), + list_value); +} + +} // namespace chromeos diff --git a/chrome/browser/ui/webui/options2/chromeos/language_pinyin_handler.h b/chrome/browser/ui/webui/options2/chromeos/language_pinyin_handler.h new file mode 100644 index 0000000..7e0d922 --- /dev/null +++ b/chrome/browser/ui/webui/options2/chromeos/language_pinyin_handler.h @@ -0,0 +1,34 @@ +// Copyright (c) 2011 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. + +#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_LANGUAGE_PINYIN_HANDLER_H_ +#define CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_LANGUAGE_PINYIN_HANDLER_H_ +#pragma once + +#include "base/compiler_specific.h" +#include "chrome/browser/ui/webui/options2/options_ui2.h" + +namespace base { +class DictionaryValue; +} + +namespace chromeos { + +// Pinyin options page UI handler. +class LanguagePinyinHandler : public OptionsPage2UIHandler { + public: + LanguagePinyinHandler(); + virtual ~LanguagePinyinHandler(); + + // OptionsPage2UIHandler implementation. + virtual void GetLocalizedValues( + base::DictionaryValue* localized_strings) OVERRIDE; + + private: + DISALLOW_COPY_AND_ASSIGN(LanguagePinyinHandler); +}; + +} // namespace chromeos + +#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_LANGUAGE_PINYIN_HANDLER_H_ diff --git a/chrome/browser/ui/webui/options2/chromeos/proxy_handler.cc b/chrome/browser/ui/webui/options2/chromeos/proxy_handler.cc new file mode 100644 index 0000000..773fe51 --- /dev/null +++ b/chrome/browser/ui/webui/options2/chromeos/proxy_handler.cc @@ -0,0 +1,86 @@ +// Copyright (c) 2011 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. + +#include "chrome/browser/ui/webui/options2/chromeos/proxy_handler.h" + +#include "base/basictypes.h" +#include "base/callback.h" +#include "base/stl_util.h" +#include "base/time.h" +#include "base/utf_string_conversions.h" +#include "base/values.h" +#include "grit/chromium_strings.h" +#include "grit/generated_resources.h" +#include "grit/locale_settings.h" +#include "grit/theme_resources.h" +#include "ui/base/l10n/l10n_util.h" +#include "ui/base/resource/resource_bundle.h" + +namespace chromeos { + +ProxyHandler::ProxyHandler() { +} + +ProxyHandler::~ProxyHandler() { +} + +void ProxyHandler::GetLocalizedValues( + DictionaryValue* localized_strings) { + DCHECK(localized_strings); + // Proxy page - ChromeOS + localized_strings->SetString("proxyPage", + l10n_util::GetStringUTF16(IDS_OPTIONS_PROXY_TAB_LABEL)); + localized_strings->SetString("proxyPageTitleFormat", + l10n_util::GetStringUTF16(IDS_PROXY_PAGE_TITLE_FORMAT)); + localized_strings->SetString("proxy_config_title", + l10n_util::GetStringUTF16(IDS_PROXY_CONFIG_TITLE)); + localized_strings->SetString("proxyDirectInternetConnection", + l10n_util::GetStringUTF16(IDS_PROXY_DIRECT_CONNECTION)); + + localized_strings->SetString("proxyManual", + l10n_util::GetStringUTF16(IDS_PROXY_MANUAL_CONFIG)); + localized_strings->SetString("sameProxyProtocols", + l10n_util::GetStringUTF16(IDS_PROXY_SAME_FORALL)); + + localized_strings->SetString("httpProxy", + l10n_util::GetStringUTF16(IDS_PROXY_HTTP_PROXY)); + localized_strings->SetString("secureHttpProxy", + l10n_util::GetStringUTF16(IDS_PROXY_HTTP_SECURE_HTTP_PROXY)); + localized_strings->SetString("ftpProxy", + l10n_util::GetStringUTF16(IDS_PROXY_FTP_PROXY)); + localized_strings->SetString("socksHost", + l10n_util::GetStringUTF16(IDS_PROXY_SOCKS_HOST)); + localized_strings->SetString("proxyAutomatic", + l10n_util::GetStringUTF16(IDS_PROXY_AUTOMATIC)); + localized_strings->SetString("proxyConfigUrl", + l10n_util::GetStringUTF16(IDS_PROXY_CONFIG_URL)); + localized_strings->SetString("advanced_proxy_config", + l10n_util::GetStringUTF16(IDS_PROXY_ADVANCED_CONFIG)); + localized_strings->SetString("addHost", + l10n_util::GetStringUTF16(IDS_PROXY_ADD_HOST)); + localized_strings->SetString("removeHost", + l10n_util::GetStringUTF16(IDS_PROXY_REMOVE_HOST)); + localized_strings->SetString("proxyPort", + l10n_util::GetStringUTF16(IDS_PROXY_PORT)); + localized_strings->SetString("proxyBypass", + l10n_util::GetStringUTF16(IDS_PROXY_BYPASS)); + localized_strings->SetString("policyManagedPrefsBannerText", + l10n_util::GetStringUTF16(IDS_OPTIONS_POLICY_MANAGED_PREFS)); + localized_strings->SetString("extensionManagedPrefsBannerText", + l10n_util::GetStringUTF16(IDS_OPTIONS_EXTENSION_MANAGED_PREFS)); + localized_strings->SetString("unmodifiablePrefsBannerText", + l10n_util::GetStringUTF16(IDS_OPTIONS_UNMODIFIABLE_PREFS)); + localized_strings->SetString("enableSharedProxiesBannerText", + l10n_util::GetStringFUTF16( + IDS_OPTIONS_SETTINGS_INTERNET_OPTIONS_ENABLE_SHARED_PROXIES_HINT, + l10n_util::GetStringUTF16(IDS_OPTIONS_SETTINGS_USE_SHARED_PROXIES))); +} + +void ProxyHandler::SetNetworkName(const std::string& name) { + StringValue network(name); + web_ui_->CallJavascriptFunction("options.ProxyOptions.setNetworkName", + network); +} + +} // namespace chromeos diff --git a/chrome/browser/ui/webui/options2/chromeos/proxy_handler.h b/chrome/browser/ui/webui/options2/chromeos/proxy_handler.h new file mode 100644 index 0000000..d170786 --- /dev/null +++ b/chrome/browser/ui/webui/options2/chromeos/proxy_handler.h @@ -0,0 +1,33 @@ +// Copyright (c) 2011 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. + +#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_PROXY_HANDLER_H_ +#define CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_PROXY_HANDLER_H_ + +#include "base/compiler_specific.h" +#include "chrome/browser/ui/webui/options2/options_ui2.h" + +namespace chromeos { + +// ChromeOS proxy options page UI handler. +class ProxyHandler : public OptionsPage2UIHandler { + public: + explicit ProxyHandler(); + virtual ~ProxyHandler(); + + // OptionsPage2UIHandler implementation. + virtual void GetLocalizedValues( + base::DictionaryValue* localized_strings) OVERRIDE; + + // Set network name for proxy page title. + void SetNetworkName(const std::string& name); + + private: + + DISALLOW_COPY_AND_ASSIGN(ProxyHandler); +}; + +} // namespace chromeos + +#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_PROXY_HANDLER_H_ diff --git a/chrome/browser/ui/webui/options2/chromeos/stats_options_handler.cc b/chrome/browser/ui/webui/options2/chromeos/stats_options_handler.cc new file mode 100644 index 0000000..4fc40d9 --- /dev/null +++ b/chrome/browser/ui/webui/options2/chromeos/stats_options_handler.cc @@ -0,0 +1,45 @@ +// Copyright (c) 2011 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. + +#include "chrome/browser/ui/webui/options2/chromeos/stats_options_handler.h" + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/utf_string_conversions.h" +#include "base/values.h" +#include "content/public/browser/user_metrics.h" + +namespace chromeos { + +StatsOptionsHandler::StatsOptionsHandler() { +} + +// OptionsPageUIHandler implementation. +void StatsOptionsHandler::GetLocalizedValues( + DictionaryValue* localized_strings) { +} + +void StatsOptionsHandler::Initialize() { +} + +// WebUIMessageHandler implementation. +void StatsOptionsHandler::RegisterMessages() { + web_ui_->RegisterMessageCallback("metricsReportingCheckboxAction", + base::Bind(&StatsOptionsHandler::HandleMetricsReportingCheckbox, + base::Unretained(this))); +} + +void StatsOptionsHandler::HandleMetricsReportingCheckbox( + const ListValue* args) { +#if defined(GOOGLE_CHROME_BUILD) + const std::string checked_str = UTF16ToUTF8(ExtractStringValue(args)); + const bool enabled = (checked_str == "true"); + UserMetrics::RecordAction( + enabled ? + UserMetricsAction("Options_MetricsReportingCheckbox_Enable") : + UserMetricsAction("Options_MetricsReportingCheckbox_Disable")); +#endif +} + +} // namespace chromeos diff --git a/chrome/browser/ui/webui/options2/chromeos/stats_options_handler.h b/chrome/browser/ui/webui/options2/chromeos/stats_options_handler.h new file mode 100644 index 0000000..2579cb30 --- /dev/null +++ b/chrome/browser/ui/webui/options2/chromeos/stats_options_handler.h @@ -0,0 +1,38 @@ +// Copyright (c) 2011 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. + +#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_STATS_OPTIONS_HANDLER_H_ +#define CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_STATS_OPTIONS_HANDLER_H_ +#pragma once + +#include "base/compiler_specific.h" +#include "chrome/browser/ui/webui/options2/options_ui2.h" + +namespace chromeos { + +// ChromeOS handler for "Stats/crash reporting to Google" option of the Advanced +// settings page. This handler does only ChromeOS-specific actions while default +// code is in Chrome's AdvancedOptionsHandler +// (chrome/browser/webui/advanced_options_handler.cc). +class StatsOptionsHandler : public OptionsPage2UIHandler { + public: + StatsOptionsHandler(); + + // OptionsPage2UIHandler implementation. + virtual void GetLocalizedValues( + base::DictionaryValue* localized_strings) OVERRIDE; + virtual void Initialize() OVERRIDE; + + // WebUIMessageHandler implementation. + virtual void RegisterMessages() OVERRIDE; + + private: + void HandleMetricsReportingCheckbox(const base::ListValue* args); + + DISALLOW_COPY_AND_ASSIGN(StatsOptionsHandler); +}; + +} // namespace chromeos + +#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_STATS_OPTIONS_HANDLER_H_ diff --git a/chrome/browser/ui/webui/options2/chromeos/system_options_handler.cc b/chrome/browser/ui/webui/options2/chromeos/system_options_handler.cc new file mode 100644 index 0000000..62261ee --- /dev/null +++ b/chrome/browser/ui/webui/options2/chromeos/system_options_handler.cc @@ -0,0 +1,170 @@ +// Copyright (c) 2011 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. + +#include "chrome/browser/ui/webui/options2/chromeos/system_options_handler.h" + +#include <string> + +#include "base/basictypes.h" +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/command_line.h" +#include "base/json/json_value_serializer.h" +#include "base/string_number_conversions.h" +#include "base/utf_string_conversions.h" +#include "base/values.h" +#include "content/browser/tab_contents/tab_contents.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/chromeos/accessibility/accessibility_util.h" +#include "chrome/browser/chromeos/cros_settings.h" +#include "chrome/browser/chromeos/dbus/dbus_thread_manager.h" +#include "chrome/browser/chromeos/dbus/power_manager_client.h" +#include "chrome/browser/chromeos/language_preferences.h" +#include "chrome/browser/chromeos/system/touchpad_settings.h" +#include "chrome/browser/extensions/extension_service.h" +#include "chrome/browser/prefs/pref_service.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/ui/webui/options2/chromeos/system_settings_provider.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/common/pref_names.h" +#include "chrome/common/extensions/extension.h" +#include "grit/browser_resources.h" +#include "grit/chromium_strings.h" +#include "grit/generated_resources.h" +#include "grit/locale_settings.h" +#include "grit/theme_resources.h" +#include "ui/base/l10n/l10n_util.h" +#include "ui/base/resource/resource_bundle.h" + +using content::BrowserThread; + +namespace { + +void TouchpadExistsFileThread(bool* exists) { + *exists = chromeos::system::touchpad_settings::TouchpadExists(); +} + +} + +SystemOptionsHandler::SystemOptionsHandler() { +} + +SystemOptionsHandler::~SystemOptionsHandler() { +} + +void SystemOptionsHandler::GetLocalizedValues( + DictionaryValue* localized_strings) { + DCHECK(localized_strings); + + RegisterTitle(localized_strings, "systemPage", IDS_OPTIONS_SYSTEM_TAB_LABEL); + localized_strings->SetString("datetimeTitle", + l10n_util::GetStringUTF16(IDS_OPTIONS_SETTINGS_SECTION_TITLE_DATETIME)); + localized_strings->SetString("timezone", + l10n_util::GetStringUTF16(IDS_OPTIONS_SETTINGS_TIMEZONE_DESCRIPTION)); + localized_strings->SetString("use24HourClock", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_USE_24HOUR_CLOCK_DESCRIPTION)); + + localized_strings->SetString("screen", + l10n_util::GetStringUTF16(IDS_OPTIONS_SETTINGS_SECTION_TITLE_SCREEN)); + localized_strings->SetString("brightness", + l10n_util::GetStringUTF16(IDS_OPTIONS_SETTINGS_BRIGHTNESS_DESCRIPTION)); + localized_strings->SetString("brightnessDecrease", + l10n_util::GetStringUTF16(IDS_OPTIONS_SETTINGS_BRIGHTNESS_DECREASE)); + localized_strings->SetString("brightnessIncrease", + l10n_util::GetStringUTF16(IDS_OPTIONS_SETTINGS_BRIGHTNESS_INCREASE)); + + localized_strings->SetString("touchpad", + l10n_util::GetStringUTF16(IDS_OPTIONS_SETTINGS_SECTION_TITLE_TOUCHPAD)); + localized_strings->SetString("enableTapToClick", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_TAP_TO_CLICK_ENABLED_DESCRIPTION)); + localized_strings->SetString("sensitivity", + l10n_util::GetStringUTF16(IDS_OPTIONS_SETTINGS_SENSITIVITY_DESCRIPTION)); + localized_strings->SetString("sensitivityLess", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_SENSITIVITY_LESS_DESCRIPTION)); + localized_strings->SetString("sensitivityMore", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_SENSITIVITY_MORE_DESCRIPTION)); + + localized_strings->SetString("language", + l10n_util::GetStringUTF16(IDS_OPTIONS_SETTINGS_SECTION_TITLE_LANGUAGE)); + localized_strings->SetString("languageCustomize", + l10n_util::GetStringUTF16(IDS_OPTIONS_SETTINGS_LANGUAGES_CUSTOMIZE)); + localized_strings->SetString("modifierKeysCustomize", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_LANGUAGES_MODIFIER_KEYS_CUSTOMIZE)); + + localized_strings->SetString("accessibilityTitle", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_SECTION_TITLE_ACCESSIBILITY)); + localized_strings->SetString("accessibility", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_ACCESSIBILITY_DESCRIPTION)); + + // TODO(pastarmovj): replace this with a call to the CrosSettings list + // handling functionality to come. + localized_strings->Set("timezoneList", + static_cast<chromeos::SystemSettingsProvider*>( + chromeos::CrosSettings::Get()->GetProvider( + chromeos::kSystemTimezone))->GetTimezoneList()); +} + +void SystemOptionsHandler::Initialize() { + DCHECK(web_ui_); + PrefService* pref_service = g_browser_process->local_state(); + bool acc_enabled = pref_service->GetBoolean(prefs::kSpokenFeedbackEnabled); + base::FundamentalValue checked(acc_enabled); + web_ui_->CallJavascriptFunction( + "options.SystemOptions.SetAccessibilityCheckboxState", checked); + + bool* exists = new bool; + BrowserThread::PostTaskAndReply(BrowserThread::FILE, FROM_HERE, + base::Bind(&TouchpadExistsFileThread, exists), + base::Bind(&SystemOptionsHandler::TouchpadExists, AsWeakPtr(), exists)); +} + +void SystemOptionsHandler::TouchpadExists(bool* exists) { + if (*exists) + web_ui_->CallJavascriptFunction( + "options.SystemOptions.showTouchpadControls"); + delete exists; +} + +void SystemOptionsHandler::RegisterMessages() { + DCHECK(web_ui_); + web_ui_->RegisterMessageCallback("accessibilityChange", + base::Bind(&SystemOptionsHandler::AccessibilityChangeCallback, + base::Unretained(this))); + + web_ui_->RegisterMessageCallback("decreaseScreenBrightness", + base::Bind(&SystemOptionsHandler::DecreaseScreenBrightnessCallback, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("increaseScreenBrightness", + base::Bind(&SystemOptionsHandler::IncreaseScreenBrightnessCallback, + base::Unretained(this))); +} + +void SystemOptionsHandler::AccessibilityChangeCallback(const ListValue* args) { + std::string checked_str; + args->GetString(0, &checked_str); + bool accessibility_enabled = (checked_str == "true"); + + chromeos::accessibility::EnableAccessibility(accessibility_enabled, NULL); +} + +void SystemOptionsHandler::DecreaseScreenBrightnessCallback( + const ListValue* args) { + // Do not allow the options button to turn off the backlight, as that + // can make it very difficult to see the increase brightness button. + chromeos::DBusThreadManager::Get()->GetPowerManagerClient()-> + DecreaseScreenBrightness(false); +} + +void SystemOptionsHandler::IncreaseScreenBrightnessCallback( + const ListValue* args) { + chromeos::DBusThreadManager::Get()->GetPowerManagerClient()-> + IncreaseScreenBrightness(); +} diff --git a/chrome/browser/ui/webui/options2/chromeos/system_options_handler.h b/chrome/browser/ui/webui/options2/chromeos/system_options_handler.h new file mode 100644 index 0000000..e3e333b --- /dev/null +++ b/chrome/browser/ui/webui/options2/chromeos/system_options_handler.h @@ -0,0 +1,50 @@ +// Copyright (c) 2011 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. + +#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_SYSTEM_OPTIONS_HANDLER_H_ +#define CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_SYSTEM_OPTIONS_HANDLER_H_ +#pragma once + +#include "base/memory/weak_ptr.h" +#include "base/compiler_specific.h" +#include "chrome/browser/ui/webui/options2/options_ui2.h" + +namespace base { +class DictionaryValue; +} + +// ChromeOS system options page UI handler. +class SystemOptionsHandler + : public OptionsPage2UIHandler, + public base::SupportsWeakPtr<SystemOptionsHandler> { + public: + SystemOptionsHandler(); + virtual ~SystemOptionsHandler(); + + // OptionsPage2UIHandler implementation. + virtual void GetLocalizedValues( + base::DictionaryValue* localized_strings) OVERRIDE; + virtual void Initialize() OVERRIDE; + + virtual void RegisterMessages() OVERRIDE; + + // Called when the accessibility checkbox value is changed. + // |args| will contain the checkbox checked state as a string + // ("true" or "false"). + void AccessibilityChangeCallback(const base::ListValue* args); + + // Called when the System configuration screen is used to adjust + // the screen brightness. + // |args| will be an empty list. + void DecreaseScreenBrightnessCallback(const base::ListValue* args); + void IncreaseScreenBrightnessCallback(const base::ListValue* args); + + private: + // Callback for TouchpadHelper. + void TouchpadExists(bool* exists); + + DISALLOW_COPY_AND_ASSIGN(SystemOptionsHandler); +}; + +#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_SYSTEM_OPTIONS_HANDLER_H_ diff --git a/chrome/browser/ui/webui/options2/chromeos/system_settings_provider.cc b/chrome/browser/ui/webui/options2/chromeos/system_settings_provider.cc new file mode 100644 index 0000000..c951164 --- /dev/null +++ b/chrome/browser/ui/webui/options2/chromeos/system_settings_provider.cc @@ -0,0 +1,333 @@ +// Copyright (c) 2011 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. + +#include "chrome/browser/ui/webui/options2/chromeos/system_settings_provider.h" + +#include <string> + +#include "base/i18n/rtl.h" +#include "base/lazy_instance.h" +#include "base/memory/scoped_ptr.h" +#include "base/stl_util.h" +#include "base/string_util.h" +#include "base/stringprintf.h" +#include "base/synchronization/lock.h" +#include "base/time.h" +#include "base/utf_string_conversions.h" +#include "base/values.h" +#include "chrome/browser/chromeos/cros/cros_library.h" +#include "chrome/browser/chromeos/cros_settings.h" +#include "chrome/browser/chromeos/cros_settings_names.h" +#include "chrome/browser/chromeos/login/user_manager.h" +#include "grit/generated_resources.h" +#include "ui/base/l10n/l10n_util.h" +#include "unicode/calendar.h" +#include "unicode/timezone.h" +#include "unicode/ures.h" + +namespace { + +// TODO(jungshik): Using Enumerate method in ICU gives 600+ timezones. +// Even after filtering out duplicate entries with a strict identity check, +// we still have 400+ zones. Relaxing the criteria for the timezone +// identity is likely to cut down the number to < 100. Until we +// come up with a better list, we hard-code the following list as used by +// Android. +static const char* kTimeZones[] = { + "Pacific/Majuro", + "Pacific/Midway", + "Pacific/Honolulu", + "America/Anchorage", + "America/Los_Angeles", + "America/Tijuana", + "America/Denver", + "America/Phoenix", + "America/Chihuahua", + "America/Chicago", + "America/Mexico_City", + "America/Costa_Rica", + "America/Regina", + "America/New_York", + "America/Bogota", + "America/Caracas", + "America/Barbados", + "America/Manaus", + "America/Santiago", + "America/St_Johns", + "America/Sao_Paulo", + "America/Araguaina", + "America/Argentina/Buenos_Aires", + "America/Godthab", + "America/Montevideo", + "Atlantic/South_Georgia", + "Atlantic/Azores", + "Atlantic/Cape_Verde", + "Africa/Casablanca", + "Europe/London", + "Europe/Amsterdam", + "Europe/Belgrade", + "Europe/Brussels", + "Europe/Sarajevo", + "Africa/Windhoek", + "Africa/Brazzaville", + "Asia/Amman", + "Europe/Athens", + "Asia/Beirut", + "Africa/Cairo", + "Europe/Helsinki", + "Asia/Jerusalem", + "Europe/Minsk", + "Africa/Harare", + "Asia/Baghdad", + "Europe/Moscow", + "Asia/Kuwait", + "Africa/Nairobi", + "Asia/Tehran", + "Asia/Baku", + "Asia/Tbilisi", + "Asia/Yerevan", + "Asia/Dubai", + "Asia/Kabul", + "Asia/Karachi", + "Asia/Oral", + "Asia/Yekaterinburg", + "Asia/Calcutta", + "Asia/Colombo", + "Asia/Katmandu", + "Asia/Almaty", + "Asia/Rangoon", + "Asia/Krasnoyarsk", + "Asia/Bangkok", + "Asia/Shanghai", + "Asia/Hong_Kong", + "Asia/Irkutsk", + "Asia/Kuala_Lumpur", + "Australia/Perth", + "Asia/Taipei", + "Asia/Seoul", + "Asia/Tokyo", + "Asia/Yakutsk", + "Australia/Adelaide", + "Australia/Darwin", + "Australia/Brisbane", + "Australia/Hobart", + "Australia/Sydney", + "Asia/Vladivostok", + "Pacific/Guam", + "Asia/Magadan", + "Pacific/Auckland", + "Pacific/Fiji", + "Pacific/Tongatapu", +}; + +static base::LazyInstance<base::Lock, + base::LeakyLazyInstanceTraits<base::Lock> > + g_timezone_bundle_lock = LAZY_INSTANCE_INITIALIZER; + +struct UResClose { + inline void operator() (UResourceBundle* b) const { + ures_close(b); + } +}; + +string16 GetExemplarCity(const icu::TimeZone& zone) { + // TODO(jungshik): After upgrading to ICU 4.6, use U_ICUDATA_ZONE + static const char* zone_bundle_name = NULL; + + // These will be leaked at the end. + static UResourceBundle *zone_bundle = NULL; + static UResourceBundle *zone_strings = NULL; + + UErrorCode status = U_ZERO_ERROR; + { + base::AutoLock lock(g_timezone_bundle_lock.Get()); + if (zone_bundle == NULL) + zone_bundle = ures_open(zone_bundle_name, uloc_getDefault(), &status); + + if (zone_strings == NULL) + zone_strings = ures_getByKey(zone_bundle, "zone_strings", NULL, &status); + } + + icu::UnicodeString zone_id; + zone.getID(zone_id); + std::string zone_id_str; + zone_id.toUTF8String(zone_id_str); + + // resource keys for timezones use ':' in place of '/'. + ReplaceSubstringsAfterOffset(&zone_id_str, 0, "/", ":"); + scoped_ptr_malloc<UResourceBundle, UResClose> zone_item( + ures_getByKey(zone_strings, zone_id_str.c_str(), NULL, &status)); + icu::UnicodeString city; + if (!U_FAILURE(status)) { + city = icu::ures_getUnicodeStringByKey(zone_item.get(), "ec", &status); + if (U_SUCCESS(status)) + return string16(city.getBuffer(), city.length()); + } + + // Fallback case in case of failure. + ReplaceSubstringsAfterOffset(&zone_id_str, 0, ":", "/"); + // Take the last component of a timezone id (e.g. 'Baz' in 'Foo/Bar/Baz'). + // Depending on timezones, keeping all but the 1st component + // (e.g. Bar/Baz) may be better, but our current list does not have + // any timezone for which that's the case. + std::string::size_type slash_pos = zone_id_str.rfind('/'); + if (slash_pos != std::string::npos && slash_pos < zone_id_str.size()) + zone_id_str.erase(0, slash_pos + 1); + // zone id has '_' in place of ' '. + ReplaceSubstringsAfterOffset(&zone_id_str, 0, "_", " "); + return ASCIIToUTF16(zone_id_str); +} + +} // namespace anonymous + +namespace chromeos { + +SystemSettingsProvider::SystemSettingsProvider( + const NotifyObserversCallback& notify_cb) + : CrosSettingsProvider(notify_cb) { + for (size_t i = 0; i < arraysize(kTimeZones); i++) { + timezones_.push_back(icu::TimeZone::createTimeZone( + icu::UnicodeString(kTimeZones[i], -1, US_INV))); + } + system::TimezoneSettings::GetInstance()->AddObserver(this); + timezone_value_.reset(base::Value::CreateStringValue(GetKnownTimezoneID( + system::TimezoneSettings::GetInstance()->GetTimezone()))); +} + +SystemSettingsProvider::~SystemSettingsProvider() { + system::TimezoneSettings::GetInstance()->RemoveObserver(this); + STLDeleteElements(&timezones_); +} + +void SystemSettingsProvider::DoSet(const std::string& path, + const base::Value& in_value) { + // Non-guest users can change the time zone. + if (UserManager::Get()->IsLoggedInAsGuest()) + return; + + if (path == kSystemTimezone) { + string16 value; + if (!in_value.IsType(Value::TYPE_STRING) || !in_value.GetAsString(&value)) + return; + const icu::TimeZone* timezone = GetTimezone(value); + if (!timezone) + return; + system::TimezoneSettings::GetInstance()->SetTimezone(*timezone); + timezone_value_.reset( + base::Value::CreateStringValue(GetKnownTimezoneID(*timezone))); + } +} + +const base::Value* SystemSettingsProvider::Get(const std::string& path) const { + if (path == kSystemTimezone) + return timezone_value_.get(); + return NULL; +} + +// The timezone is always trusted. +bool SystemSettingsProvider::GetTrusted(const std::string& path, + const base::Closure& callback) { + return true; +} + +bool SystemSettingsProvider::HandlesSetting(const std::string& path) const { + return path == kSystemTimezone; +} + +void SystemSettingsProvider::Reload() { + // TODO(pastarmovj): We can actually cache the timezone here to make returning + // it faster. +} + +void SystemSettingsProvider::TimezoneChanged(const icu::TimeZone& timezone) { + // Fires system setting change notification. + timezone_value_.reset( + base::Value::CreateStringValue(GetKnownTimezoneID(timezone))); + NotifyObservers(kSystemTimezone); +} + +ListValue* SystemSettingsProvider::GetTimezoneList() { + ListValue* timezoneList = new ListValue(); + for (std::vector<icu::TimeZone*>::iterator iter = timezones_.begin(); + iter != timezones_.end(); ++iter) { + const icu::TimeZone* timezone = *iter; + ListValue* option = new ListValue(); + option->Append(Value::CreateStringValue(GetTimezoneID(*timezone))); + option->Append(Value::CreateStringValue(GetTimezoneName(*timezone))); + timezoneList->Append(option); + } + return timezoneList; +} + +string16 SystemSettingsProvider::GetTimezoneName( + const icu::TimeZone& timezone) { + // Instead of using the raw_offset, use the offset in effect now. + // For instance, US Pacific Time, the offset shown will be -7 in summer + // while it'll be -8 in winter. + int raw_offset, dst_offset; + UDate now = icu::Calendar::getNow(); + UErrorCode status = U_ZERO_ERROR; + timezone.getOffset(now, false, raw_offset, dst_offset, status); + DCHECK(U_SUCCESS(status)); + int offset = raw_offset + dst_offset; + // offset is in msec. + int minute_offset = std::abs(offset) / 60000; + int hour_offset = minute_offset / 60; + int min_remainder = minute_offset % 60; + // Some timezones have a non-integral hour offset. So, we need to + // use hh:mm form. + std::string offset_str = base::StringPrintf(offset >= 0 ? + "UTC+%d:%02d" : "UTC-%d:%02d", hour_offset, min_remainder); + + // TODO(jungshik): When coming up with a better list of timezones, we also + // have to come up with better 'display' names. One possibility is to list + // multiple cities (e.g. "Los Angeles, Vancouver .." in the order of + // the population of a country the city belongs to.). + // We can also think of using LONG_GENERIC or LOCATION once we upgrade + // to ICU 4.6. + // In the meantime, we use "LONG" name with "Exemplar City" to distinguish + // multiple timezones with the same "LONG" name but with different + // rules (e.g. US Mountain Time in Denver vs Phoenix). + icu::UnicodeString name; + timezone.getDisplayName(dst_offset != 0, icu::TimeZone::LONG, name); + string16 result(l10n_util::GetStringFUTF16( + IDS_OPTIONS_SETTINGS_TIMEZONE_DISPLAY_TEMPLATE, ASCIIToUTF16(offset_str), + string16(name.getBuffer(), name.length()), GetExemplarCity(timezone))); + base::i18n::AdjustStringForLocaleDirection(&result); + return result; +} + +string16 SystemSettingsProvider::GetTimezoneID( + const icu::TimeZone& timezone) { + icu::UnicodeString id; + timezone.getID(id); + return string16(id.getBuffer(), id.length()); +} + +const icu::TimeZone* SystemSettingsProvider::GetTimezone( + const string16& timezone_id) { + for (std::vector<icu::TimeZone*>::iterator iter = timezones_.begin(); + iter != timezones_.end(); ++iter) { + const icu::TimeZone* timezone = *iter; + if (GetTimezoneID(*timezone) == timezone_id) { + return timezone; + } + } + return NULL; +} + +string16 SystemSettingsProvider::GetKnownTimezoneID( + const icu::TimeZone& timezone) const { + for (std::vector<icu::TimeZone*>::const_iterator iter = timezones_.begin(); + iter != timezones_.end(); ++iter) { + const icu::TimeZone* known_timezone = *iter; + if (known_timezone->hasSameRules(timezone)) + return GetTimezoneID(*known_timezone); + } + + // Not able to find a matching timezone in our list. + return string16(); +} + +} // namespace chromeos diff --git a/chrome/browser/ui/webui/options2/chromeos/system_settings_provider.h b/chrome/browser/ui/webui/options2/chromeos/system_settings_provider.h new file mode 100644 index 0000000..33399ed --- /dev/null +++ b/chrome/browser/ui/webui/options2/chromeos/system_settings_provider.h @@ -0,0 +1,76 @@ +// Copyright (c) 2011 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. + +#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_SYSTEM_SETTINGS_PROVIDER_H_ +#define CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_SYSTEM_SETTINGS_PROVIDER_H_ + +#include <vector> + +#include "base/callback.h" +#include "base/string16.h" +#include "chrome/browser/chromeos/cros_settings_provider.h" +#include "chrome/browser/chromeos/system/timezone_settings.h" +#include "third_party/icu/public/i18n/unicode/timezone.h" + +namespace base { +class Value; +class ListValue; +class StringValue; +} + +namespace chromeos { + +class SystemSettingsProvider : public CrosSettingsProvider, + public system::TimezoneSettings::Observer { + public: + explicit SystemSettingsProvider(const NotifyObserversCallback& notify_cb); + virtual ~SystemSettingsProvider(); + + // CrosSettingsProvider overrides. + virtual const base::Value* Get(const std::string& path) const OVERRIDE; + virtual bool GetTrusted(const std::string& path, + const base::Closure& callback) OVERRIDE; + virtual bool HandlesSetting(const std::string& path) const OVERRIDE; + virtual void Reload() OVERRIDE; + + // Overridden from TimezoneSettings::Observer: + virtual void TimezoneChanged(const icu::TimeZone& timezone) OVERRIDE; + + // Creates the map of timezones used by the options page. + base::ListValue* GetTimezoneList(); + + private: + // CrosSettingsProvider overrides. + virtual void DoSet(const std::string& path, + const base::Value& in_value) OVERRIDE; + + // Gets timezone name. + static string16 GetTimezoneName(const icu::TimeZone& timezone); + + // Gets timezone ID which is also used as timezone pref value. + static string16 GetTimezoneID(const icu::TimeZone& timezone); + + // Gets timezone object from its id. + const icu::TimeZone* GetTimezone(const string16& timezone_id); + + // Gets a timezone id from a timezone in |timezones_| that has the same + // rule of given |timezone|. + // One timezone could have multiple timezones, + // e.g. + // US/Pacific == America/Los_Angeles + // We should always use the known timezone id when passing back as + // pref values. + string16 GetKnownTimezoneID(const icu::TimeZone& timezone) const; + + // Timezones. + std::vector<icu::TimeZone*> timezones_; + + scoped_ptr<base::Value> timezone_value_; + + DISALLOW_COPY_AND_ASSIGN(SystemSettingsProvider); +}; + +} // namespace chromeos + +#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_SYSTEM_SETTINGS_PROVIDER_H_ diff --git a/chrome/browser/ui/webui/options2/chromeos/user_image_source.cc b/chrome/browser/ui/webui/options2/chromeos/user_image_source.cc new file mode 100644 index 0000000..f259c03 --- /dev/null +++ b/chrome/browser/ui/webui/options2/chromeos/user_image_source.cc @@ -0,0 +1,53 @@ +// Copyright (c) 2011 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. + +#include "chrome/browser/ui/webui/options2/chromeos/user_image_source.h" + +#include "base/memory/ref_counted_memory.h" +#include "chrome/browser/chromeos/login/user_manager.h" +#include "chrome/common/url_constants.h" +#include "grit/theme_resources.h" +#include "ui/base/resource/resource_bundle.h" +#include "ui/gfx/codec/png_codec.h" + +namespace chromeos { + +std::vector<unsigned char> UserImageSource::GetUserImage( + const std::string& email) const { + std::vector<unsigned char> user_image; + const chromeos::User* user = chromeos::UserManager::Get()->FindUser(email); + if (user) { + gfx::PNGCodec::EncodeBGRASkBitmap(user->image(), false, &user_image); + return user_image; + } + gfx::PNGCodec::EncodeBGRASkBitmap( + *ResourceBundle::GetSharedInstance().GetBitmapNamed( + IDR_LOGIN_DEFAULT_USER), + false, + &user_image); + return user_image; +} + +UserImageSource::UserImageSource() + : DataSource(chrome::kChromeUIUserImageHost, MessageLoop::current()) { +} + +UserImageSource::~UserImageSource() {} + +void UserImageSource::StartDataRequest(const std::string& path, + bool is_incognito, + int request_id) { + // Strip the query param value - we only use it as a hack to ensure our + // image gets reloaded instead of being pulled from the browser cache + std::string email = path.substr(0, path.find_first_of("?")); + SendResponse(request_id, new RefCountedBytes(GetUserImage(email))); +} + +std::string UserImageSource::GetMimeType(const std::string&) const { + // We need to explicitly return a mime type, otherwise if the user tries to + // drag the image they get no extension. + return "image/png"; +} + +} // namespace chromeos diff --git a/chrome/browser/ui/webui/options2/chromeos/user_image_source.h b/chrome/browser/ui/webui/options2/chromeos/user_image_source.h new file mode 100644 index 0000000..ea38b49 --- /dev/null +++ b/chrome/browser/ui/webui/options2/chromeos/user_image_source.h @@ -0,0 +1,44 @@ +// Copyright (c) 2011 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. + +#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_USER_IMAGE_SOURCE_H_ +#define CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_USER_IMAGE_SOURCE_H_ +#pragma once + +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "chrome/browser/ui/webui/chrome_url_data_manager.h" + +namespace chromeos { + +// UserImageSource is the data source that serves user images for users that +// have it. +class UserImageSource : public ChromeURLDataManager::DataSource { + public: + UserImageSource(); + + // Called when the network layer has requested a resource underneath + // the path we registered. + virtual void StartDataRequest(const std::string& path, + bool is_incognito, + int request_id) OVERRIDE; + + virtual std::string GetMimeType(const std::string&) const OVERRIDE; + + // Returns PNG encoded image for user with specified email. + // If there's no user with such email, returns the default image. + std::vector<unsigned char> GetUserImage(const std::string& email) const; + + private: + virtual ~UserImageSource(); + + DISALLOW_COPY_AND_ASSIGN(UserImageSource); +}; + +} // namespace chromeos + +#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_USER_IMAGE_SOURCE_H_ diff --git a/chrome/browser/ui/webui/options2/chromeos/virtual_keyboard_manager_handler.cc b/chrome/browser/ui/webui/options2/chromeos/virtual_keyboard_manager_handler.cc new file mode 100644 index 0000000..22c0fe6 --- /dev/null +++ b/chrome/browser/ui/webui/options2/chromeos/virtual_keyboard_manager_handler.cc @@ -0,0 +1,237 @@ +// Copyright (c) 2011 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. + +#include "chrome/browser/ui/webui/options2/chromeos/virtual_keyboard_manager_handler.h" + +#include <map> +#include <set> +#include <string> + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/string_number_conversions.h" +#include "base/utf_string_conversions.h" +#include "base/values.h" +#include "chrome/browser/chromeos/input_method/input_method_manager.h" +#include "chrome/browser/chromeos/input_method/input_method_util.h" +#include "chrome/browser/chromeos/input_method/virtual_keyboard_selector.h" +#include "chrome/browser/chromeos/preferences.h" +#include "chrome/browser/prefs/pref_service.h" +#include "chrome/browser/prefs/scoped_user_pref_update.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/common/chrome_notification_types.h" +#include "chrome/common/pref_names.h" +#include "content/public/browser/notification_details.h" +#include "content/public/browser/notification_source.h" +#include "grit/generated_resources.h" +#include "ui/base/l10n/l10n_util.h" + +namespace ime = ::chromeos::input_method; + +namespace chromeos { + +VirtualKeyboardManagerHandler::VirtualKeyboardManagerHandler() { +} + +VirtualKeyboardManagerHandler::~VirtualKeyboardManagerHandler() { +} + +void VirtualKeyboardManagerHandler::GetLocalizedValues( + DictionaryValue* localized_strings) { + DCHECK(localized_strings); + + static const OptionsStringResource resources[] = { + { "virtualKeyboardLayoutColumnTitle", + IDS_OPTIONS_SETTINGS_LANGUAGES_VIRTUAL_KEYBOARD_LAYOUT_COLUMN_TITLE }, + { "virtualKeyboardKeyboardColumnTitle", + IDS_OPTIONS_SETTINGS_LANGUAGES_VIRTUAL_KEYBOARD_KEYBOARD_COLUMN_TITLE }, + { "defaultVirtualKeyboard", + IDS_OPTIONS_SETTINGS_LANGUAGES_DEFAULT_VIRTUAL_KEYBOARD }, + }; + RegisterStrings(localized_strings, resources, arraysize(resources)); + + RegisterTitle(localized_strings, "virtualKeyboardPage", + IDS_OPTIONS_SETTINGS_LANGUAGES_VIRTUAL_KEYBOARD_SETTINGS_TITLE); + + // Do not call GetVirtualKeyboardList() here since |web_ui_| is not ready yet. +} + +void VirtualKeyboardManagerHandler::Initialize() { +} + +void VirtualKeyboardManagerHandler::RegisterMessages() { + DCHECK(web_ui_); + // Register handler functions for chrome.send(). + web_ui_->RegisterMessageCallback("updateVirtualKeyboardList", + base::Bind(&VirtualKeyboardManagerHandler::UpdateVirtualKeyboardList, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("setVirtualKeyboardPreference", + base::Bind(&VirtualKeyboardManagerHandler::SetVirtualKeyboardPreference, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("clearVirtualKeyboardPreference", + base::Bind(&VirtualKeyboardManagerHandler::ClearVirtualKeyboardPreference, + base::Unretained(this))); +} + +ListValue* VirtualKeyboardManagerHandler::GetVirtualKeyboardList() { + DCHECK(web_ui_); + ime::InputMethodManager* input_method = + ime::InputMethodManager::GetInstance(); + + // Get a multi map from layout name (e.g. "us(dvorak)"), to virtual keyboard + // extension. + const LayoutToKeyboard& layout_to_keyboard = + input_method->GetLayoutNameToKeyboardMapping(); + const UrlToKeyboard& url_to_keyboard = + input_method->GetUrlToKeyboardMapping(); + + // Get the current pref values. + PrefService* prefs = Profile::FromWebUI(web_ui_)->GetPrefs(); + DCHECK(prefs); + const DictionaryValue* virtual_keyboard_pref = + prefs->GetDictionary(prefs::kLanguagePreferredVirtualKeyboard); + + return CreateVirtualKeyboardList( + layout_to_keyboard, url_to_keyboard, virtual_keyboard_pref); +} + +void VirtualKeyboardManagerHandler::UpdateVirtualKeyboardList( + const ListValue* args) { + scoped_ptr<Value> virtual_keyboards(GetVirtualKeyboardList()); + DCHECK(virtual_keyboards.get()); + web_ui_->CallJavascriptFunction( + "VirtualKeyboardManager.updateVirtualKeyboardList", *virtual_keyboards); +} + +void VirtualKeyboardManagerHandler::SetVirtualKeyboardPreference( + const ListValue* args) { + DCHECK(web_ui_); + std::string layout, url; + if (!args || !args->GetString(0, &layout) || !args->GetString(1, &url)) { + LOG(ERROR) << "SetVirtualKeyboardPreference: Invalid argument"; + return; + } + + // Validate args. + ime::InputMethodManager* input_method = + ime::InputMethodManager::GetInstance(); + if (!ValidateUrl(input_method->GetUrlToKeyboardMapping(), layout, url)) { + LOG(ERROR) << "SetVirtualKeyboardPreference: Invalid args: " + << "layout=" << layout << ", url=" << url; + return; + } + + PrefService* prefs = Profile::FromWebUI(web_ui_)->GetPrefs(); + DCHECK(prefs); + { + DictionaryPrefUpdate updater( + prefs, prefs::kLanguagePreferredVirtualKeyboard); + DictionaryValue* pref_value = updater.Get(); + pref_value->SetWithoutPathExpansion(layout, new StringValue(url)); + } + Preferences::UpdateVirturalKeyboardPreference(prefs); +} + +void VirtualKeyboardManagerHandler::ClearVirtualKeyboardPreference( + const ListValue* args) { + DCHECK(web_ui_); + std::string layout; + if (!args || !args->GetString(0, &layout)) { + LOG(ERROR) << "ClearVirtualKeyboardPreference: Invalid argument"; + return; + } + + // Validate |layout|. + ime::InputMethodManager* input_method = + ime::InputMethodManager::GetInstance(); + if (!input_method->GetLayoutNameToKeyboardMapping().count(layout)) { + LOG(ERROR) << "ClearVirtualKeyboardPreference: Invalid layout: " << layout; + return; + } + + PrefService* prefs = Profile::FromWebUI(web_ui_)->GetPrefs(); + DCHECK(prefs); + { + DictionaryPrefUpdate updater( + prefs, prefs::kLanguagePreferredVirtualKeyboard); + DictionaryValue* pref_value = updater.Get(); + pref_value->RemoveWithoutPathExpansion(layout, NULL); + } + Preferences::UpdateVirturalKeyboardPreference(prefs); +} + +// static +bool VirtualKeyboardManagerHandler::ValidateUrl( + const UrlToKeyboard& url_to_keyboard, + const std::string& layout, + const std::string& url) { + UrlToKeyboard::const_iterator iter = url_to_keyboard.find(GURL(url)); + if (iter == url_to_keyboard.end() || + !iter->second->supported_layouts().count(layout)) { + return false; + } + return true; +} + +// static +ListValue* VirtualKeyboardManagerHandler::CreateVirtualKeyboardList( + const LayoutToKeyboard& layout_to_keyboard, + const UrlToKeyboard& url_to_keyboard, + const DictionaryValue* virtual_keyboard_pref) { + ListValue* layout_list = new ListValue; + + // |dictionary| points to an element in the |layout_list|. One dictionary + // element is created for one layout. + DictionaryValue* dictionary = NULL; + + LayoutToKeyboard::const_iterator i; + for (i = layout_to_keyboard.begin(); i != layout_to_keyboard.end(); ++i) { + const std::string& layout_id = i->first; + + std::string string_value; + // Check the "layout" value in the current dictionary. + if (dictionary) { + dictionary->GetString("layout", &string_value); + } + + if (string_value != layout_id) { + // New layout is found. Add the layout to |layout_list|. + dictionary = new DictionaryValue; + layout_list->Append(dictionary); + + // Set layout id as well as its human readable form. + ime::InputMethodManager* manager = ime::InputMethodManager::GetInstance(); + const ime::InputMethodDescriptor* desc = + manager->GetInputMethodUtil()->GetInputMethodDescriptorFromXkbId( + layout_id); + const std::string layout_name = desc ? + manager->GetInputMethodUtil()->GetInputMethodDisplayNameFromId( + desc->id()) : layout_id; + dictionary->SetString("layout", layout_id); + dictionary->SetString("layoutName", layout_name); + + // Check if the layout is in user pref. + if (virtual_keyboard_pref && + virtual_keyboard_pref->GetString(layout_id, &string_value) && + ValidateUrl(url_to_keyboard, layout_id, string_value)) { + dictionary->SetString("preferredKeyboard", string_value); + } + dictionary->Set("supportedKeyboards", new ListValue); + } + + ListValue* supported_keyboards = NULL; + dictionary->GetList("supportedKeyboards", &supported_keyboards); + DCHECK(supported_keyboards); + + DictionaryValue* virtual_keyboard = new DictionaryValue; + virtual_keyboard->SetString("name", i->second->name()); + virtual_keyboard->SetBoolean("isSystem", i->second->is_system()); + virtual_keyboard->SetString("url", i->second->url().spec()); + supported_keyboards->Append(virtual_keyboard); + } + + return layout_list; +} + +} // namespace chromeos diff --git a/chrome/browser/ui/webui/options2/chromeos/virtual_keyboard_manager_handler.h b/chrome/browser/ui/webui/options2/chromeos/virtual_keyboard_manager_handler.h new file mode 100644 index 0000000..6e720f7 --- /dev/null +++ b/chrome/browser/ui/webui/options2/chromeos/virtual_keyboard_manager_handler.h @@ -0,0 +1,78 @@ +// Copyright (c) 2011 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. + +#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_VIRTUAL_KEYBOARD_MANAGER_HANDLER_H_ +#define CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_VIRTUAL_KEYBOARD_MANAGER_HANDLER_H_ +#pragma once + +#include <map> +#include <set> +#include <string> + +#include "chrome/browser/ui/webui/options2/options_ui2.h" +#include "googleurl/src/gurl.h" + +namespace base { +class DictionaryValue; +class ListValue; +} // namespace base + +namespace chromeos { + +namespace input_method { +class VirtualKeyboard; +} // namespace input_method; + +// A class which provides information to virtual_keyboard.js. +class VirtualKeyboardManagerHandler : public OptionsPage2UIHandler { + public: + VirtualKeyboardManagerHandler(); + virtual ~VirtualKeyboardManagerHandler(); + + // OptionsPage2UIHandler implementation. + virtual void GetLocalizedValues( + base::DictionaryValue* localized_strings) OVERRIDE; + virtual void Initialize() OVERRIDE; + virtual void RegisterMessages() OVERRIDE; + + protected: + typedef std::multimap< + std::string, const input_method::VirtualKeyboard*> LayoutToKeyboard; + typedef std::map<GURL, const input_method::VirtualKeyboard*> UrlToKeyboard; + + // Returns true if |layout_to_keyboard| contains |layout| as a key, and the + // value for |layout| contains |url|. This function is protected for + // testability. + static bool ValidateUrl(const UrlToKeyboard& url_to_keyboard, + const std::string& layout, + const std::string& url); + + // Builds a list from |layout_to_keyboard| and |virtual_keyboard_user_pref|. + // See virtual_keyboard_list.js for an example of the format the list should + // take. This function is protected for testability. + static base::ListValue* CreateVirtualKeyboardList( + const LayoutToKeyboard& layout_to_keyboard, + const UrlToKeyboard& url_to_keyboard, + const base::DictionaryValue* virtual_keyboard_user_pref); + + private: + // Reads user pref and create a list using CreateVirtualKeyboardList(). + base::ListValue* GetVirtualKeyboardList(); + + // Handles chrome.send("updateVirtualKeyboardList") JS call. + // TODO(yusukes): This function should also be called when user pref is + // updated by chrome://settings page in other tab. + void UpdateVirtualKeyboardList(const base::ListValue* args); + + // Handles chrome.send("setVirtualKeyboardPreference") JS call. + void SetVirtualKeyboardPreference(const base::ListValue* args); + // Handles chrome.send("clearVirtualKeyboardPreference") JS call. + void ClearVirtualKeyboardPreference(const base::ListValue* args); + + DISALLOW_COPY_AND_ASSIGN(VirtualKeyboardManagerHandler); +}; + +} // namespace chromeos + +#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_VIRTUAL_KEYBOARD_MANAGER_HANDLER_H_ diff --git a/chrome/browser/ui/webui/options2/chromeos/virtual_keyboard_manager_handler_unittest.cc b/chrome/browser/ui/webui/options2/chromeos/virtual_keyboard_manager_handler_unittest.cc new file mode 100644 index 0000000..5783401 --- /dev/null +++ b/chrome/browser/ui/webui/options2/chromeos/virtual_keyboard_manager_handler_unittest.cc @@ -0,0 +1,550 @@ +// Copyright (c) 2011 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. + +#include "chrome/browser/ui/webui/options2/chromeos/virtual_keyboard_manager_handler.h" + +#include <map> +#include <set> +#include <string> + +#include "base/logging.h" +#include "base/values.h" +#include "chrome/browser/chromeos/input_method/virtual_keyboard_selector.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +typedef std::multimap< + std::string, const chromeos::input_method::VirtualKeyboard*> LayoutToKeyboard; +typedef std::map< + GURL, const chromeos::input_method::VirtualKeyboard*> UrlToKeyboard; + +template <size_t L> +std::set<std::string> CreateLayoutSet(const char* (&layouts)[L]) { + return std::set<std::string>(layouts, layouts + L); +} + +} // namespace + +namespace chromeos { + +class Testee : public VirtualKeyboardManagerHandler { + public: + // Change access rights. + using VirtualKeyboardManagerHandler::ValidateUrl; + using VirtualKeyboardManagerHandler::CreateVirtualKeyboardList; +}; + +TEST(VirtualKeyboardManagerHandler, TestValidateUrl) { + static const char* layouts1[] = { "a", "b" }; + static const char* layouts2[] = { "b" }; + input_method::VirtualKeyboard virtual_keyboard_1( + GURL("http://url1/"), "name 1", CreateLayoutSet(layouts1), true); + input_method::VirtualKeyboard virtual_keyboard_2( + GURL("http://url2/"), "name 2", CreateLayoutSet(layouts2), true); + + input_method::VirtualKeyboardSelector selector; + ASSERT_TRUE(selector.AddVirtualKeyboard( + virtual_keyboard_1.url(), + virtual_keyboard_1.name(), + virtual_keyboard_1.supported_layouts(), + virtual_keyboard_1.is_system())); + ASSERT_TRUE(selector.AddVirtualKeyboard( + virtual_keyboard_2.url(), + virtual_keyboard_2.name(), + virtual_keyboard_2.supported_layouts(), + virtual_keyboard_2.is_system())); + + const UrlToKeyboard& url_to_keyboard = selector.url_to_keyboard(); + ASSERT_EQ(2U, url_to_keyboard.size()); + + EXPECT_TRUE(Testee::ValidateUrl(url_to_keyboard, "a", "http://url1/")); + EXPECT_TRUE(Testee::ValidateUrl(url_to_keyboard, "b", "http://url1/")); + EXPECT_TRUE(Testee::ValidateUrl(url_to_keyboard, "b", "http://url2/")); + + EXPECT_FALSE(Testee::ValidateUrl(url_to_keyboard, "a", "http://url3/")); + EXPECT_FALSE(Testee::ValidateUrl(url_to_keyboard, "b", "http://url3/")); + EXPECT_FALSE(Testee::ValidateUrl(url_to_keyboard, "c", "http://url1/")); + EXPECT_FALSE(Testee::ValidateUrl(url_to_keyboard, "c", "http://url2/")); +} + +TEST(VirtualKeyboardManagerHandler, TestSingleKeyboard) { + static const char* layouts[] = { "a", "b" }; + input_method::VirtualKeyboard virtual_keyboard_1( + GURL("http://url1/"), "name 1", CreateLayoutSet(layouts), true); + + input_method::VirtualKeyboardSelector selector; + ASSERT_TRUE(selector.AddVirtualKeyboard( + virtual_keyboard_1.url(), + virtual_keyboard_1.name(), + virtual_keyboard_1.supported_layouts(), + virtual_keyboard_1.is_system())); + + const LayoutToKeyboard& layout_to_keyboard = selector.layout_to_keyboard(); + ASSERT_EQ(arraysize(layouts), layout_to_keyboard.size()); + const UrlToKeyboard& url_to_keyboard = selector.url_to_keyboard(); + ASSERT_EQ(1U, url_to_keyboard.size()); + + scoped_ptr<ListValue> keyboards(Testee::CreateVirtualKeyboardList( + layout_to_keyboard, url_to_keyboard, NULL)); + ASSERT_TRUE(keyboards.get()); + ASSERT_EQ(arraysize(layouts), keyboards->GetSize()); + + DictionaryValue* dictionary_value; + std::string string_value; + ListValue* list_value; + + // Check the first element (for the layout "a"). + ASSERT_TRUE(keyboards->GetDictionary(0, &dictionary_value)); + EXPECT_TRUE(dictionary_value->GetString("layout", &string_value)); + EXPECT_EQ("a", string_value); + EXPECT_TRUE(dictionary_value->GetString("layoutName", &string_value)); + EXPECT_FALSE(dictionary_value->GetString("preferredKeyboard", &string_value)); + ASSERT_TRUE(dictionary_value->GetList("supportedKeyboards", &list_value)); + ASSERT_EQ(1U, list_value->GetSize()); + ASSERT_TRUE(list_value->GetDictionary(0, &dictionary_value)); + EXPECT_TRUE(dictionary_value->GetString("url", &string_value)); + EXPECT_EQ("http://url1/", string_value); + EXPECT_TRUE(dictionary_value->GetString("name", &string_value)); + EXPECT_EQ("name 1", string_value); + + // Check the second element (for the layout "b"). + ASSERT_TRUE(keyboards->GetDictionary(1, &dictionary_value)); + EXPECT_TRUE(dictionary_value->GetString("layout", &string_value)); + EXPECT_EQ("b", string_value); + EXPECT_TRUE(dictionary_value->GetString("layoutName", &string_value)); + EXPECT_FALSE(dictionary_value->GetString("preferredKeyboard", &string_value)); + ASSERT_TRUE(dictionary_value->GetList("supportedKeyboards", &list_value)); + ASSERT_EQ(1U, list_value->GetSize()); + ASSERT_TRUE(list_value->GetDictionary(0, &dictionary_value)); + EXPECT_TRUE(dictionary_value->GetString("url", &string_value)); + EXPECT_EQ("http://url1/", string_value); + EXPECT_TRUE(dictionary_value->GetString("name", &string_value)); + EXPECT_EQ("name 1", string_value); +} + +TEST(VirtualKeyboardManagerHandler, TestSingleKeyboardWithPref) { + static const char* layouts[] = { "a", "b" }; + input_method::VirtualKeyboard virtual_keyboard_1( + GURL("http://url1/"), "name 1", CreateLayoutSet(layouts), true); + + input_method::VirtualKeyboardSelector selector; + ASSERT_TRUE(selector.AddVirtualKeyboard( + virtual_keyboard_1.url(), + virtual_keyboard_1.name(), + virtual_keyboard_1.supported_layouts(), + virtual_keyboard_1.is_system())); + + const LayoutToKeyboard& layout_to_keyboard = selector.layout_to_keyboard(); + ASSERT_EQ(arraysize(layouts), layout_to_keyboard.size()); + const UrlToKeyboard& url_to_keyboard = selector.url_to_keyboard(); + ASSERT_EQ(1U, url_to_keyboard.size()); + + // create pref object. + scoped_ptr<DictionaryValue> pref(new DictionaryValue); + pref->SetString("b", "http://url1/"); + + scoped_ptr<ListValue> keyboards(Testee::CreateVirtualKeyboardList( + layout_to_keyboard, url_to_keyboard, pref.get())); + ASSERT_TRUE(keyboards.get()); + ASSERT_EQ(arraysize(layouts), keyboards->GetSize()); + + DictionaryValue* dictionary_value; + std::string string_value; + ListValue* list_value; + + // Check the first element (for the layout "a"). + ASSERT_TRUE(keyboards->GetDictionary(0, &dictionary_value)); + EXPECT_TRUE(dictionary_value->GetString("layout", &string_value)); + EXPECT_EQ("a", string_value); + EXPECT_TRUE(dictionary_value->GetString("layoutName", &string_value)); + EXPECT_FALSE(dictionary_value->GetString("preferredKeyboard", &string_value)); + ASSERT_TRUE(dictionary_value->GetList("supportedKeyboards", &list_value)); + ASSERT_EQ(1U, list_value->GetSize()); + ASSERT_TRUE(list_value->GetDictionary(0, &dictionary_value)); + EXPECT_TRUE(dictionary_value->GetString("url", &string_value)); + EXPECT_EQ("http://url1/", string_value); + EXPECT_TRUE(dictionary_value->GetString("name", &string_value)); + EXPECT_EQ("name 1", string_value); + + // Check the second element (for the layout "b"). + ASSERT_TRUE(keyboards->GetDictionary(1, &dictionary_value)); + EXPECT_TRUE(dictionary_value->GetString("layout", &string_value)); + EXPECT_EQ("b", string_value); + EXPECT_TRUE(dictionary_value->GetString("layoutName", &string_value)); + EXPECT_TRUE(dictionary_value->GetString("preferredKeyboard", &string_value)); + EXPECT_EQ("http://url1/", string_value); + ASSERT_TRUE(dictionary_value->GetList("supportedKeyboards", &list_value)); + ASSERT_EQ(1U, list_value->GetSize()); + ASSERT_TRUE(list_value->GetDictionary(0, &dictionary_value)); + EXPECT_TRUE(dictionary_value->GetString("url", &string_value)); + EXPECT_EQ("http://url1/", string_value); + EXPECT_TRUE(dictionary_value->GetString("name", &string_value)); + EXPECT_EQ("name 1", string_value); +} + +TEST(VirtualKeyboardManagerHandler, TestSingleKeyboardWithTwoPrefs) { + static const char* layouts[] = { "a", "b" }; + input_method::VirtualKeyboard virtual_keyboard_1( + GURL("http://url1/"), "name 1", CreateLayoutSet(layouts), true); + + input_method::VirtualKeyboardSelector selector; + ASSERT_TRUE(selector.AddVirtualKeyboard( + virtual_keyboard_1.url(), + virtual_keyboard_1.name(), + virtual_keyboard_1.supported_layouts(), + virtual_keyboard_1.is_system())); + + const LayoutToKeyboard& layout_to_keyboard = selector.layout_to_keyboard(); + ASSERT_EQ(arraysize(layouts), layout_to_keyboard.size()); + const UrlToKeyboard& url_to_keyboard = selector.url_to_keyboard(); + ASSERT_EQ(1U, url_to_keyboard.size()); + + // create pref object. + scoped_ptr<DictionaryValue> pref(new DictionaryValue); + pref->SetString("a", "http://url1/"); + pref->SetString("b", "http://url1/"); + + scoped_ptr<ListValue> keyboards(Testee::CreateVirtualKeyboardList( + layout_to_keyboard, url_to_keyboard, pref.get())); + ASSERT_TRUE(keyboards.get()); + ASSERT_EQ(arraysize(layouts), keyboards->GetSize()); + + DictionaryValue* dictionary_value; + std::string string_value; + ListValue* list_value; + + // Check the first element (for the layout "a"). + ASSERT_TRUE(keyboards->GetDictionary(0, &dictionary_value)); + EXPECT_TRUE(dictionary_value->GetString("layout", &string_value)); + EXPECT_EQ("a", string_value); + EXPECT_TRUE(dictionary_value->GetString("layoutName", &string_value)); + EXPECT_TRUE(dictionary_value->GetString("preferredKeyboard", &string_value)); + EXPECT_EQ("http://url1/", string_value); + ASSERT_TRUE(dictionary_value->GetList("supportedKeyboards", &list_value)); + ASSERT_EQ(1U, list_value->GetSize()); + ASSERT_TRUE(list_value->GetDictionary(0, &dictionary_value)); + EXPECT_TRUE(dictionary_value->GetString("url", &string_value)); + EXPECT_EQ("http://url1/", string_value); + EXPECT_TRUE(dictionary_value->GetString("name", &string_value)); + EXPECT_EQ("name 1", string_value); + + // Check the second element (for the layout "b"). + ASSERT_TRUE(keyboards->GetDictionary(1, &dictionary_value)); + EXPECT_TRUE(dictionary_value->GetString("layout", &string_value)); + EXPECT_EQ("b", string_value); + EXPECT_TRUE(dictionary_value->GetString("layoutName", &string_value)); + EXPECT_TRUE(dictionary_value->GetString("preferredKeyboard", &string_value)); + EXPECT_EQ("http://url1/", string_value); + ASSERT_TRUE(dictionary_value->GetList("supportedKeyboards", &list_value)); + ASSERT_EQ(1U, list_value->GetSize()); + ASSERT_TRUE(list_value->GetDictionary(0, &dictionary_value)); + EXPECT_TRUE(dictionary_value->GetString("url", &string_value)); + EXPECT_EQ("http://url1/", string_value); + EXPECT_TRUE(dictionary_value->GetString("name", &string_value)); + EXPECT_EQ("name 1", string_value); +} + +TEST(VirtualKeyboardManagerHandler, TestSingleKeyboardWithBadPref1) { + static const char* layouts[] = { "a", "b" }; + input_method::VirtualKeyboard virtual_keyboard_1( + GURL("http://url1/"), "name 1", CreateLayoutSet(layouts), true); + + input_method::VirtualKeyboardSelector selector; + ASSERT_TRUE(selector.AddVirtualKeyboard( + virtual_keyboard_1.url(), + virtual_keyboard_1.name(), + virtual_keyboard_1.supported_layouts(), + virtual_keyboard_1.is_system())); + + const LayoutToKeyboard& layout_to_keyboard = selector.layout_to_keyboard(); + ASSERT_EQ(arraysize(layouts), layout_to_keyboard.size()); + const UrlToKeyboard& url_to_keyboard = selector.url_to_keyboard(); + ASSERT_EQ(1U, url_to_keyboard.size()); + + // create pref object. + scoped_ptr<DictionaryValue> pref(new DictionaryValue); + pref->SetString("unknownlayout", "http://url1/"); + + scoped_ptr<ListValue> keyboards(Testee::CreateVirtualKeyboardList( + layout_to_keyboard, url_to_keyboard, pref.get())); + ASSERT_TRUE(keyboards.get()); + ASSERT_EQ(arraysize(layouts), keyboards->GetSize()); + + DictionaryValue* dictionary_value; + std::string string_value; + ListValue* list_value; + + // Check the first element (for the layout "a"). + ASSERT_TRUE(keyboards->GetDictionary(0, &dictionary_value)); + EXPECT_TRUE(dictionary_value->GetString("layout", &string_value)); + EXPECT_EQ("a", string_value); + EXPECT_TRUE(dictionary_value->GetString("layoutName", &string_value)); + EXPECT_FALSE(dictionary_value->GetString("preferredKeyboard", &string_value)); + ASSERT_TRUE(dictionary_value->GetList("supportedKeyboards", &list_value)); + ASSERT_EQ(1U, list_value->GetSize()); + ASSERT_TRUE(list_value->GetDictionary(0, &dictionary_value)); + EXPECT_TRUE(dictionary_value->GetString("url", &string_value)); + EXPECT_EQ("http://url1/", string_value); + EXPECT_TRUE(dictionary_value->GetString("name", &string_value)); + EXPECT_EQ("name 1", string_value); + + // Check the second element (for the layout "b"). + ASSERT_TRUE(keyboards->GetDictionary(1, &dictionary_value)); + EXPECT_TRUE(dictionary_value->GetString("layout", &string_value)); + EXPECT_EQ("b", string_value); + EXPECT_TRUE(dictionary_value->GetString("layoutName", &string_value)); + EXPECT_FALSE(dictionary_value->GetString("preferredKeyboard", &string_value)); + ASSERT_TRUE(dictionary_value->GetList("supportedKeyboards", &list_value)); + ASSERT_EQ(1U, list_value->GetSize()); + ASSERT_TRUE(list_value->GetDictionary(0, &dictionary_value)); + EXPECT_TRUE(dictionary_value->GetString("url", &string_value)); + EXPECT_EQ("http://url1/", string_value); + EXPECT_TRUE(dictionary_value->GetString("name", &string_value)); + EXPECT_EQ("name 1", string_value); +} + +TEST(VirtualKeyboardManagerHandler, TestSingleKeyboardWithBadPref2) { + static const char* layouts[] = { "a", "b" }; + input_method::VirtualKeyboard virtual_keyboard_1( + GURL("http://url1/"), "name 1", CreateLayoutSet(layouts), true); + + input_method::VirtualKeyboardSelector selector; + ASSERT_TRUE(selector.AddVirtualKeyboard( + virtual_keyboard_1.url(), + virtual_keyboard_1.name(), + virtual_keyboard_1.supported_layouts(), + virtual_keyboard_1.is_system())); + + const LayoutToKeyboard& layout_to_keyboard = selector.layout_to_keyboard(); + ASSERT_EQ(arraysize(layouts), layout_to_keyboard.size()); + const UrlToKeyboard& url_to_keyboard = selector.url_to_keyboard(); + ASSERT_EQ(1U, url_to_keyboard.size()); + + // create pref object. + scoped_ptr<DictionaryValue> pref(new DictionaryValue); + pref->SetString("a", "http://unknownurl/"); + + scoped_ptr<ListValue> keyboards(Testee::CreateVirtualKeyboardList( + layout_to_keyboard, url_to_keyboard, pref.get())); + ASSERT_TRUE(keyboards.get()); + ASSERT_EQ(arraysize(layouts), keyboards->GetSize()); + + DictionaryValue* dictionary_value; + std::string string_value; + ListValue* list_value; + + // Check the first element (for the layout "a"). + ASSERT_TRUE(keyboards->GetDictionary(0, &dictionary_value)); + EXPECT_TRUE(dictionary_value->GetString("layout", &string_value)); + EXPECT_EQ("a", string_value); + EXPECT_TRUE(dictionary_value->GetString("layoutName", &string_value)); + EXPECT_FALSE(dictionary_value->GetString("preferredKeyboard", &string_value)); + ASSERT_TRUE(dictionary_value->GetList("supportedKeyboards", &list_value)); + ASSERT_EQ(1U, list_value->GetSize()); + ASSERT_TRUE(list_value->GetDictionary(0, &dictionary_value)); + EXPECT_TRUE(dictionary_value->GetString("url", &string_value)); + EXPECT_EQ("http://url1/", string_value); + EXPECT_TRUE(dictionary_value->GetString("name", &string_value)); + EXPECT_EQ("name 1", string_value); + + // Check the second element (for the layout "b"). + ASSERT_TRUE(keyboards->GetDictionary(1, &dictionary_value)); + EXPECT_TRUE(dictionary_value->GetString("layout", &string_value)); + EXPECT_EQ("b", string_value); + EXPECT_TRUE(dictionary_value->GetString("layoutName", &string_value)); + EXPECT_FALSE(dictionary_value->GetString("preferredKeyboard", &string_value)); + ASSERT_TRUE(dictionary_value->GetList("supportedKeyboards", &list_value)); + ASSERT_EQ(1U, list_value->GetSize()); + ASSERT_TRUE(list_value->GetDictionary(0, &dictionary_value)); + EXPECT_TRUE(dictionary_value->GetString("url", &string_value)); + EXPECT_EQ("http://url1/", string_value); + EXPECT_TRUE(dictionary_value->GetString("name", &string_value)); + EXPECT_EQ("name 1", string_value); +} + +TEST(VirtualKeyboardManagerHandler, TestSingleKeyboardWithBadPref3) { + static const char* layout1[] = { "a" }; + static const char* layout2[] = { "b" }; + input_method::VirtualKeyboard virtual_keyboard_1( + GURL("http://url1/"), "name 1", CreateLayoutSet(layout1), true); + input_method::VirtualKeyboard virtual_keyboard_2( + GURL("http://url2/"), "name 2", CreateLayoutSet(layout2), true); + + input_method::VirtualKeyboardSelector selector; + ASSERT_TRUE(selector.AddVirtualKeyboard( + virtual_keyboard_1.url(), + virtual_keyboard_1.name(), + virtual_keyboard_1.supported_layouts(), + virtual_keyboard_1.is_system())); + ASSERT_TRUE(selector.AddVirtualKeyboard( + virtual_keyboard_2.url(), + virtual_keyboard_2.name(), + virtual_keyboard_2.supported_layouts(), + virtual_keyboard_2.is_system())); + + const LayoutToKeyboard& layout_to_keyboard = selector.layout_to_keyboard(); + ASSERT_EQ(2U, layout_to_keyboard.size()); + const UrlToKeyboard& url_to_keyboard = selector.url_to_keyboard(); + ASSERT_EQ(2U, url_to_keyboard.size()); + + // create pref object. + scoped_ptr<DictionaryValue> pref(new DictionaryValue); + pref->SetString("a", "http://url2/"); // url2 does not support "a". + + scoped_ptr<ListValue> keyboards(Testee::CreateVirtualKeyboardList( + layout_to_keyboard, url_to_keyboard, pref.get())); + ASSERT_TRUE(keyboards.get()); + ASSERT_EQ(2U, keyboards->GetSize()); + + DictionaryValue* dictionary_value; + std::string string_value; + ListValue* list_value; + + // Check the first element (for the layout "a"). + ASSERT_TRUE(keyboards->GetDictionary(0, &dictionary_value)); + EXPECT_TRUE(dictionary_value->GetString("layout", &string_value)); + EXPECT_EQ("a", string_value); + EXPECT_TRUE(dictionary_value->GetString("layoutName", &string_value)); + EXPECT_FALSE(dictionary_value->GetString("preferredKeyboard", &string_value)); + ASSERT_TRUE(dictionary_value->GetList("supportedKeyboards", &list_value)); + ASSERT_EQ(1U, list_value->GetSize()); + ASSERT_TRUE(list_value->GetDictionary(0, &dictionary_value)); + EXPECT_TRUE(dictionary_value->GetString("url", &string_value)); + EXPECT_EQ("http://url1/", string_value); + EXPECT_TRUE(dictionary_value->GetString("name", &string_value)); + EXPECT_EQ("name 1", string_value); + + // Check the second element (for the layout "b"). + ASSERT_TRUE(keyboards->GetDictionary(1, &dictionary_value)); + EXPECT_TRUE(dictionary_value->GetString("layout", &string_value)); + EXPECT_EQ("b", string_value); + EXPECT_TRUE(dictionary_value->GetString("layoutName", &string_value)); + EXPECT_FALSE(dictionary_value->GetString("preferredKeyboard", &string_value)); + ASSERT_TRUE(dictionary_value->GetList("supportedKeyboards", &list_value)); + ASSERT_EQ(1U, list_value->GetSize()); + ASSERT_TRUE(list_value->GetDictionary(0, &dictionary_value)); + EXPECT_TRUE(dictionary_value->GetString("url", &string_value)); + EXPECT_EQ("http://url2/", string_value); + EXPECT_TRUE(dictionary_value->GetString("name", &string_value)); + EXPECT_EQ("name 2", string_value); +} + +TEST(VirtualKeyboardManagerHandler, TestMultipleKeyboards) { + static const char* layouts1[] = { "a", "b" }; + static const char* layouts2[] = { "c" }; + static const char* layouts3[] = { "b", "d" }; + input_method::VirtualKeyboard virtual_keyboard_1( + GURL("http://url1/"), "name 1", CreateLayoutSet(layouts1), true); + input_method::VirtualKeyboard virtual_keyboard_2( + GURL("http://url2/"), "name 2", CreateLayoutSet(layouts2), false); + input_method::VirtualKeyboard virtual_keyboard_3( + GURL("http://url3/"), "name 3", CreateLayoutSet(layouts3), true); + + input_method::VirtualKeyboardSelector selector; + ASSERT_TRUE(selector.AddVirtualKeyboard( + virtual_keyboard_1.url(), + virtual_keyboard_1.name(), + virtual_keyboard_1.supported_layouts(), + virtual_keyboard_1.is_system())); + ASSERT_TRUE(selector.AddVirtualKeyboard( + virtual_keyboard_2.url(), + virtual_keyboard_2.name(), + virtual_keyboard_2.supported_layouts(), + virtual_keyboard_2.is_system())); + ASSERT_TRUE(selector.AddVirtualKeyboard( + virtual_keyboard_3.url(), + virtual_keyboard_3.name(), + virtual_keyboard_3.supported_layouts(), + virtual_keyboard_3.is_system())); + + const LayoutToKeyboard& layout_to_keyboard = selector.layout_to_keyboard(); + ASSERT_EQ(arraysize(layouts1) + arraysize(layouts2) + arraysize(layouts3), + layout_to_keyboard.size()); + const UrlToKeyboard& url_to_keyboard = selector.url_to_keyboard(); + ASSERT_EQ(3U, url_to_keyboard.size()); + + scoped_ptr<ListValue> keyboards(Testee::CreateVirtualKeyboardList( + layout_to_keyboard, url_to_keyboard, NULL)); + ASSERT_TRUE(keyboards.get()); + ASSERT_EQ(4U /* a, b, c, and d */, keyboards->GetSize()); + + DictionaryValue* dictionary_value; + std::string string_value; + ListValue* list_value; + bool boolean_value = false; + + // Check the first element (for the layout "a"). + ASSERT_TRUE(keyboards->GetDictionary(0, &dictionary_value)); + EXPECT_TRUE(dictionary_value->GetString("layout", &string_value)); + EXPECT_EQ("a", string_value); + EXPECT_TRUE(dictionary_value->GetString("layoutName", &string_value)); + EXPECT_FALSE(dictionary_value->GetString("preferredKeyboard", &string_value)); + ASSERT_TRUE(dictionary_value->GetList("supportedKeyboards", &list_value)); + ASSERT_EQ(1U, list_value->GetSize()); + ASSERT_TRUE(list_value->GetDictionary(0, &dictionary_value)); + EXPECT_TRUE(dictionary_value->GetString("url", &string_value)); + EXPECT_EQ("http://url1/", string_value); + EXPECT_TRUE(dictionary_value->GetBoolean("isSystem", &boolean_value)); + EXPECT_TRUE(boolean_value); + EXPECT_TRUE(dictionary_value->GetString("name", &string_value)); + EXPECT_EQ("name 1", string_value); + + // Check the second element (for the layout "b"). + ASSERT_TRUE(keyboards->GetDictionary(1, &dictionary_value)); + EXPECT_TRUE(dictionary_value->GetString("layout", &string_value)); + EXPECT_EQ("b", string_value); + EXPECT_TRUE(dictionary_value->GetString("layoutName", &string_value)); + EXPECT_FALSE(dictionary_value->GetString("preferredKeyboard", &string_value)); + ASSERT_TRUE(dictionary_value->GetList("supportedKeyboards", &list_value)); + ASSERT_EQ(2U, list_value->GetSize()); // keyboard1 and 3. + ASSERT_TRUE(list_value->GetDictionary(0, &dictionary_value)); + EXPECT_TRUE(dictionary_value->GetString("url", &string_value)); + EXPECT_EQ("http://url1/", string_value); + EXPECT_TRUE(dictionary_value->GetBoolean("isSystem", &boolean_value)); + EXPECT_TRUE(boolean_value); + EXPECT_TRUE(dictionary_value->GetString("name", &string_value)); + EXPECT_EQ("name 1", string_value); + ASSERT_TRUE(list_value->GetDictionary(1, &dictionary_value)); + EXPECT_TRUE(dictionary_value->GetString("url", &string_value)); + EXPECT_EQ("http://url3/", string_value); + EXPECT_TRUE(dictionary_value->GetBoolean("isSystem", &boolean_value)); + EXPECT_TRUE(boolean_value); + EXPECT_TRUE(dictionary_value->GetString("name", &string_value)); + EXPECT_EQ("name 3", string_value); + + // 3rd. + ASSERT_TRUE(keyboards->GetDictionary(2, &dictionary_value)); + EXPECT_TRUE(dictionary_value->GetString("layout", &string_value)); + EXPECT_EQ("c", string_value); + EXPECT_TRUE(dictionary_value->GetString("layoutName", &string_value)); + EXPECT_FALSE(dictionary_value->GetString("preferredKeyboard", &string_value)); + ASSERT_TRUE(dictionary_value->GetList("supportedKeyboards", &list_value)); + ASSERT_EQ(1U, list_value->GetSize()); + ASSERT_TRUE(list_value->GetDictionary(0, &dictionary_value)); + EXPECT_TRUE(dictionary_value->GetString("url", &string_value)); + EXPECT_EQ("http://url2/", string_value); + EXPECT_TRUE(dictionary_value->GetBoolean("isSystem", &boolean_value)); + EXPECT_FALSE(boolean_value); + EXPECT_TRUE(dictionary_value->GetString("name", &string_value)); + EXPECT_EQ("name 2", string_value); + + // 4th. + ASSERT_TRUE(keyboards->GetDictionary(3, &dictionary_value)); + EXPECT_TRUE(dictionary_value->GetString("layout", &string_value)); + EXPECT_EQ("d", string_value); + EXPECT_TRUE(dictionary_value->GetString("layoutName", &string_value)); + EXPECT_FALSE(dictionary_value->GetString("preferredKeyboard", &string_value)); + ASSERT_TRUE(dictionary_value->GetList("supportedKeyboards", &list_value)); + ASSERT_EQ(1U, list_value->GetSize()); + ASSERT_TRUE(list_value->GetDictionary(0, &dictionary_value)); + EXPECT_TRUE(dictionary_value->GetString("url", &string_value)); + EXPECT_EQ("http://url3/", string_value); + EXPECT_TRUE(dictionary_value->GetBoolean("isSystem", &boolean_value)); + EXPECT_TRUE(boolean_value); + EXPECT_TRUE(dictionary_value->GetString("name", &string_value)); + EXPECT_EQ("name 3", string_value); +} + +} // namespace chromeos diff --git a/chrome/browser/ui/webui/options2/clear_browser_data_handler.cc b/chrome/browser/ui/webui/options2/clear_browser_data_handler.cc new file mode 100644 index 0000000..a861780 --- /dev/null +++ b/chrome/browser/ui/webui/options2/clear_browser_data_handler.cc @@ -0,0 +1,143 @@ +// Copyright (c) 2011 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. + +#include "chrome/browser/ui/webui/options2/clear_browser_data_handler.h" + +#include "base/basictypes.h" +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/string16.h" +#include "base/values.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/prefs/pref_service.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/common/pref_names.h" +#include "content/public/browser/notification_details.h" +#include "grit/chromium_strings.h" +#include "grit/generated_resources.h" +#include "grit/locale_settings.h" +#include "ui/base/l10n/l10n_util.h" + +ClearBrowserDataHandler::ClearBrowserDataHandler() : remover_(NULL) { +} + +ClearBrowserDataHandler::~ClearBrowserDataHandler() { + if (remover_) + remover_->RemoveObserver(this); +} + +void ClearBrowserDataHandler::Initialize() { + clear_plugin_lso_data_enabled_.Init(prefs::kClearPluginLSODataEnabled, + Profile::FromWebUI(web_ui_)->GetPrefs(), + NULL); +} + +void ClearBrowserDataHandler::GetLocalizedValues( + DictionaryValue* localized_strings) { + DCHECK(localized_strings); + + static OptionsStringResource resources[] = { + { "clearBrowserDataLabel", IDS_CLEAR_BROWSING_DATA_LABEL }, + { "deleteBrowsingHistoryCheckbox", IDS_DEL_BROWSING_HISTORY_CHKBOX }, + { "deleteDownloadHistoryCheckbox", IDS_DEL_DOWNLOAD_HISTORY_CHKBOX }, + { "deleteCacheCheckbox", IDS_DEL_CACHE_CHKBOX }, + { "deleteCookiesCheckbox", IDS_DEL_COOKIES_CHKBOX }, + { "deleteCookiesFlashCheckbox", IDS_DEL_COOKIES_FLASH_CHKBOX }, + { "deletePasswordsCheckbox", IDS_DEL_PASSWORDS_CHKBOX }, + { "deleteFormDataCheckbox", IDS_DEL_FORM_DATA_CHKBOX }, + { "clearBrowserDataCommit", IDS_CLEAR_BROWSING_DATA_COMMIT }, + { "flashStorageSettings", IDS_FLASH_STORAGE_SETTINGS }, + { "flash_storage_url", IDS_FLASH_STORAGE_URL }, + { "clearDataDeleting", IDS_CLEAR_DATA_DELETING }, + }; + + RegisterStrings(localized_strings, resources, arraysize(resources)); + RegisterTitle(localized_strings, "clearBrowserDataOverlay", + IDS_CLEAR_BROWSING_DATA_TITLE); + + ListValue* time_list = new ListValue; + for (int i = 0; i < 5; i++) { + string16 label_string; + switch (i) { + case 0: + label_string = l10n_util::GetStringUTF16(IDS_CLEAR_DATA_HOUR); + break; + case 1: + label_string = l10n_util::GetStringUTF16(IDS_CLEAR_DATA_DAY); + break; + case 2: + label_string = l10n_util::GetStringUTF16(IDS_CLEAR_DATA_WEEK); + break; + case 3: + label_string = l10n_util::GetStringUTF16(IDS_CLEAR_DATA_4WEEKS); + break; + case 4: + label_string = l10n_util::GetStringUTF16(IDS_CLEAR_DATA_EVERYTHING); + break; + } + ListValue* option = new ListValue(); + option->Append(Value::CreateIntegerValue(i)); + option->Append(Value::CreateStringValue(label_string)); + time_list->Append(option); + } + localized_strings->Set("clearBrowserDataTimeList", time_list); +} + +void ClearBrowserDataHandler::RegisterMessages() { + // Setup handlers specific to this panel. + DCHECK(web_ui_); + web_ui_->RegisterMessageCallback("performClearBrowserData", + base::Bind(&ClearBrowserDataHandler::HandleClearBrowserData, + base::Unretained(this))); +} + +void ClearBrowserDataHandler::HandleClearBrowserData(const ListValue* value) { + Profile* profile = Profile::FromWebUI(web_ui_); + PrefService* prefs = profile->GetPrefs(); + + int remove_mask = 0; + if (prefs->GetBoolean(prefs::kDeleteBrowsingHistory)) + remove_mask |= BrowsingDataRemover::REMOVE_HISTORY; + if (prefs->GetBoolean(prefs::kDeleteDownloadHistory)) + remove_mask |= BrowsingDataRemover::REMOVE_DOWNLOADS; + if (prefs->GetBoolean(prefs::kDeleteCache)) + remove_mask |= BrowsingDataRemover::REMOVE_CACHE; + if (prefs->GetBoolean(prefs::kDeleteCookies)) { + int site_data_mask = BrowsingDataRemover::REMOVE_SITE_DATA; + // Don't try to clear LSO data if it's not supported. + if (!*clear_plugin_lso_data_enabled_) + site_data_mask &= ~BrowsingDataRemover::REMOVE_PLUGIN_DATA; + remove_mask |= site_data_mask; + } + if (prefs->GetBoolean(prefs::kDeletePasswords)) + remove_mask |= BrowsingDataRemover::REMOVE_PASSWORDS; + if (prefs->GetBoolean(prefs::kDeleteFormData)) + remove_mask |= BrowsingDataRemover::REMOVE_FORM_DATA; + + int period_selected = prefs->GetInteger(prefs::kDeleteTimePeriod); + + base::FundamentalValue state(true); + web_ui_->CallJavascriptFunction("ClearBrowserDataOverlay.setClearingState", + state); + + // If we are still observing a previous data remover, we need to stop + // observing. + if (remover_) + remover_->RemoveObserver(this); + + // BrowsingDataRemover deletes itself when done. + remover_ = new BrowsingDataRemover(profile, + static_cast<BrowsingDataRemover::TimePeriod>(period_selected), + base::Time()); + remover_->AddObserver(this); + remover_->Remove(remove_mask); +} + +void ClearBrowserDataHandler::OnBrowsingDataRemoverDone() { + // No need to remove ourselves as an observer as BrowsingDataRemover deletes + // itself after we return. + remover_ = NULL; + DCHECK(web_ui_); + web_ui_->CallJavascriptFunction("ClearBrowserDataOverlay.doneClearing"); +} diff --git a/chrome/browser/ui/webui/options2/clear_browser_data_handler.h b/chrome/browser/ui/webui/options2/clear_browser_data_handler.h new file mode 100644 index 0000000..0823158 --- /dev/null +++ b/chrome/browser/ui/webui/options2/clear_browser_data_handler.h @@ -0,0 +1,45 @@ +// Copyright (c) 2011 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. + +#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS2_CLEAR_BROWSER_DATA_HANDLER_H_ +#define CHROME_BROWSER_UI_WEBUI_OPTIONS2_CLEAR_BROWSER_DATA_HANDLER_H_ +#pragma once + +#include "chrome/browser/browsing_data_remover.h" +#include "chrome/browser/prefs/pref_member.h" +#include "chrome/browser/ui/webui/options2/options_ui2.h" + +// Clear browser data handler page UI handler. +class ClearBrowserDataHandler : public OptionsPage2UIHandler, + public BrowsingDataRemover::Observer { + public: + ClearBrowserDataHandler(); + virtual ~ClearBrowserDataHandler(); + + // OptionsPage2UIHandler implementation. + virtual void Initialize() OVERRIDE; + + virtual void GetLocalizedValues(DictionaryValue* localized_strings) OVERRIDE; + + // WebUIMessageHandler implementation. + virtual void RegisterMessages() OVERRIDE; + + private: + // Javascript callback to start clearing data. + void HandleClearBrowserData(const ListValue* value); + + // Callback from BrowsingDataRemover. Closes the dialog. + virtual void OnBrowsingDataRemoverDone() OVERRIDE; + + // If non-null it means removal is in progress. BrowsingDataRemover takes care + // of deleting itself when done. + BrowsingDataRemover* remover_; + + // Keeps track of whether clearing LSO data is supported. + BooleanPrefMember clear_plugin_lso_data_enabled_; + + DISALLOW_COPY_AND_ASSIGN(ClearBrowserDataHandler); +}; + +#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS2_CLEAR_BROWSER_DATA_HANDLER_H_ diff --git a/chrome/browser/ui/webui/options2/content_options_browsertest.js b/chrome/browser/ui/webui/options2/content_options_browsertest.js new file mode 100644 index 0000000..605660a --- /dev/null +++ b/chrome/browser/ui/webui/options2/content_options_browsertest.js @@ -0,0 +1,24 @@ +// Copyright (c) 2011 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. + +/** + * TestFixture for content options WebUI testing. + * @extends {testing.Test} + * @constructor + **/ +function ContentOptionsWebUITest() {} + +ContentOptionsWebUITest.prototype = { + __proto__: testing.Test.prototype, + + /** + * Browse to content options. + **/ + browsePreload: 'chrome://settings/content', +}; + +// Test opening the content options has correct location. +TEST_F('ContentOptionsWebUITest', 'testOpenContentOptions', function() { + assertEquals(this.browsePreload, document.location.href); +}); diff --git a/chrome/browser/ui/webui/options2/content_settings_exception_area_browsertest.js b/chrome/browser/ui/webui/options2/content_settings_exception_area_browsertest.js new file mode 100644 index 0000000..33b3fd2 --- /dev/null +++ b/chrome/browser/ui/webui/options2/content_settings_exception_area_browsertest.js @@ -0,0 +1,26 @@ +// Copyright (c) 2011 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. + +/** + * TestFixture for content settings exception area WebUI testing. + * @extends {testing.Test} + * @constructor + **/ +function ContentSettingsExceptionAreaWebUITest() {} + +ContentSettingsExceptionAreaWebUITest.prototype = { + __proto__: testing.Test.prototype, + + /** + * Browse to the content settings exception area. + **/ + browsePreload: 'chrome://settings/contentExceptions', +}; + +// Test opening the content settings exception area has correct location. +TEST_F('ContentSettingsExceptionAreaWebUITest', + 'testOpenContentSettingsExceptionArea', + function() { + assertEquals(this.browsePreload, document.location.href); + }); diff --git a/chrome/browser/ui/webui/options2/content_settings_handler.cc b/chrome/browser/ui/webui/options2/content_settings_handler.cc new file mode 100644 index 0000000..67fb223 --- /dev/null +++ b/chrome/browser/ui/webui/options2/content_settings_handler.cc @@ -0,0 +1,879 @@ +// Copyright (c) 2011 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. + +#include "chrome/browser/ui/webui/options2/content_settings_handler.h" + +#include <map> +#include <string> +#include <vector> + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/command_line.h" +#include "base/utf_string_conversions.h" +#include "base/values.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/content_settings/content_settings_details.h" +#include "chrome/browser/content_settings/content_settings_utils.h" +#include "chrome/browser/content_settings/host_content_settings_map.h" +#include "chrome/browser/custom_handlers/protocol_handler_registry.h" +#include "chrome/browser/notifications/desktop_notification_service.h" +#include "chrome/browser/notifications/desktop_notification_service_factory.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/ui/browser_list.h" +#include "chrome/common/chrome_notification_types.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/common/content_settings.h" +#include "chrome/common/content_settings_pattern.h" +#include "chrome/common/pref_names.h" +#include "chrome/common/url_constants.h" +#include "content/browser/tab_contents/tab_contents.h" +#include "content/public/browser/notification_service.h" +#include "content/public/browser/notification_source.h" +#include "content/public/browser/notification_types.h" +#include "content/public/browser/user_metrics.h" +#include "content/public/common/content_switches.h" +#include "grit/generated_resources.h" +#include "grit/locale_settings.h" +#include "ui/base/l10n/l10n_util.h" + +using content::UserMetricsAction; + +namespace { + +struct ContentSettingsTypeNameEntry { + ContentSettingsType type; + const char* name; +}; + +typedef std::map<ContentSettingsPattern, ContentSetting> OnePatternSettings; +typedef std::map<ContentSettingsPattern, OnePatternSettings> + AllPatternsSettings; + +const char* kDisplayPattern = "displayPattern"; +const char* kSetting = "setting"; +const char* kOrigin = "origin"; +const char* kSource = "source"; +const char* kEmbeddingOrigin = "embeddingOrigin"; + +const ContentSettingsTypeNameEntry kContentSettingsTypeGroupNames[] = { + {CONTENT_SETTINGS_TYPE_COOKIES, "cookies"}, + {CONTENT_SETTINGS_TYPE_IMAGES, "images"}, + {CONTENT_SETTINGS_TYPE_JAVASCRIPT, "javascript"}, + {CONTENT_SETTINGS_TYPE_PLUGINS, "plugins"}, + {CONTENT_SETTINGS_TYPE_POPUPS, "popups"}, + {CONTENT_SETTINGS_TYPE_GEOLOCATION, "location"}, + {CONTENT_SETTINGS_TYPE_NOTIFICATIONS, "notifications"}, + {CONTENT_SETTINGS_TYPE_INTENTS, "intents"}, + {CONTENT_SETTINGS_TYPE_AUTO_SELECT_CERTIFICATE, "auto-select-certificate"}, + {CONTENT_SETTINGS_TYPE_FULLSCREEN, "fullscreen"}, + {CONTENT_SETTINGS_TYPE_MOUSELOCK, "mouselock"}, +}; +COMPILE_ASSERT(arraysize(kContentSettingsTypeGroupNames) == + CONTENT_SETTINGS_NUM_TYPES, + MISSING_CONTENT_SETTINGS_TYPE); + +ContentSettingsType ContentSettingsTypeFromGroupName(const std::string& name) { + for (size_t i = 0; i < arraysize(kContentSettingsTypeGroupNames); ++i) { + if (name == kContentSettingsTypeGroupNames[i].name) + return kContentSettingsTypeGroupNames[i].type; + } + + NOTREACHED() << name << " is not a recognized content settings type."; + return CONTENT_SETTINGS_TYPE_DEFAULT; +} + +std::string ContentSettingToString(ContentSetting setting) { + switch (setting) { + case CONTENT_SETTING_ALLOW: + return "allow"; + case CONTENT_SETTING_ASK: + return "ask"; + case CONTENT_SETTING_BLOCK: + return "block"; + case CONTENT_SETTING_SESSION_ONLY: + return "session"; + case CONTENT_SETTING_DEFAULT: + return "default"; + case CONTENT_SETTING_NUM_SETTINGS: + NOTREACHED(); + } + + return ""; +} + +ContentSetting ContentSettingFromString(const std::string& name) { + if (name == "allow") + return CONTENT_SETTING_ALLOW; + if (name == "ask") + return CONTENT_SETTING_ASK; + if (name == "block") + return CONTENT_SETTING_BLOCK; + if (name == "session") + return CONTENT_SETTING_SESSION_ONLY; + + NOTREACHED() << name << " is not a recognized content setting."; + return CONTENT_SETTING_DEFAULT; +} + +std::string GeolocationExceptionToString( + const ContentSettingsPattern& origin, + const ContentSettingsPattern& embedding_origin) { + if (origin == embedding_origin) + return origin.ToString(); + + // TODO(estade): the page needs to use CSS to indent the string. + std::string indent(" "); + + if (embedding_origin == ContentSettingsPattern::Wildcard()) { + // NOTE: As long as the user cannot add/edit entries from the exceptions + // dialog, it's impossible to actually have a non-default setting for some + // origin "embedded on any other site", so this row will never appear. If + // we add the ability to add/edit exceptions, we'll need to decide when to + // display this and how "removing" it will function. + return indent + + l10n_util::GetStringUTF8(IDS_EXCEPTIONS_GEOLOCATION_EMBEDDED_ANY_OTHER); + } + + return indent + l10n_util::GetStringFUTF8( + IDS_EXCEPTIONS_GEOLOCATION_EMBEDDED_ON_HOST, + UTF8ToUTF16(embedding_origin.ToString())); +} + +// Create a DictionaryValue* that will act as a data source for a single row +// in a HostContentSettingsMap-controlled exceptions table (e.g., cookies). +// Ownership of the pointer is passed to the caller. +DictionaryValue* GetExceptionForPage( + const ContentSettingsPattern& pattern, + ContentSetting setting, + std::string provider_name) { + DictionaryValue* exception = new DictionaryValue(); + exception->SetString(kDisplayPattern, pattern.ToString()); + exception->SetString(kSetting, ContentSettingToString(setting)); + exception->SetString(kSource, provider_name); + return exception; +} + +// Create a DictionaryValue* that will act as a data source for a single row +// in the Geolocation exceptions table. Ownership of the pointer is passed to +// the caller. +DictionaryValue* GetGeolocationExceptionForPage( + const ContentSettingsPattern& origin, + const ContentSettingsPattern& embedding_origin, + ContentSetting setting) { + DictionaryValue* exception = new DictionaryValue(); + exception->SetString(kDisplayPattern, + GeolocationExceptionToString(origin, embedding_origin)); + exception->SetString(kSetting, ContentSettingToString(setting)); + exception->SetString(kOrigin, origin.ToString()); + exception->SetString(kEmbeddingOrigin, embedding_origin.ToString()); + return exception; +} + +// Create a DictionaryValue* that will act as a data source for a single row +// in the desktop notifications exceptions table. Ownership of the pointer is +// passed to the caller. +DictionaryValue* GetNotificationExceptionForPage( + const ContentSettingsPattern& pattern, + ContentSetting setting, + const std::string& provider_name) { + DictionaryValue* exception = new DictionaryValue(); + exception->SetString(kDisplayPattern, pattern.ToString()); + exception->SetString(kSetting, ContentSettingToString(setting)); + exception->SetString(kOrigin, pattern.ToString()); + exception->SetString(kSource, provider_name); + return exception; +} + +} // namespace + +ContentSettingsHandler::ContentSettingsHandler() { +} + +ContentSettingsHandler::~ContentSettingsHandler() { +} + +void ContentSettingsHandler::GetLocalizedValues( + DictionaryValue* localized_strings) { + DCHECK(localized_strings); + + static OptionsStringResource resources[] = { + { "content_exceptions", IDS_COOKIES_EXCEPTIONS_BUTTON }, + { "allowException", IDS_EXCEPTIONS_ALLOW_BUTTON }, + { "blockException", IDS_EXCEPTIONS_BLOCK_BUTTON }, + { "sessionException", IDS_EXCEPTIONS_SESSION_ONLY_BUTTON }, + { "askException", IDS_EXCEPTIONS_ASK_BUTTON }, + { "addExceptionRow", IDS_EXCEPTIONS_ADD_BUTTON }, + { "removeExceptionRow", IDS_EXCEPTIONS_REMOVE_BUTTON }, + { "editExceptionRow", IDS_EXCEPTIONS_EDIT_BUTTON }, + { "otr_exceptions_explanation", IDS_EXCEPTIONS_OTR_LABEL }, + { "examplePattern", IDS_EXCEPTIONS_PATTERN_EXAMPLE }, + { "addNewExceptionInstructions", IDS_EXCEPTIONS_ADD_NEW_INSTRUCTIONS }, + { "manage_exceptions", IDS_EXCEPTIONS_MANAGE }, + { "manage_handlers", IDS_HANDLERS_MANAGE }, + { "exceptionPatternHeader", IDS_EXCEPTIONS_PATTERN_HEADER }, + { "exceptionBehaviorHeader", IDS_EXCEPTIONS_ACTION_HEADER }, + // Cookies filter. + { "cookies_tab_label", IDS_COOKIES_TAB_LABEL }, + { "cookies_header", IDS_COOKIES_HEADER }, + { "cookies_allow", IDS_COOKIES_ALLOW_RADIO }, + { "cookies_block", IDS_COOKIES_BLOCK_RADIO }, + { "cookies_session_only", IDS_COOKIES_SESSION_ONLY_RADIO }, + { "cookies_block_3rd_party", IDS_COOKIES_BLOCK_3RDPARTY_CHKBOX }, + { "cookies_clear_when_close", IDS_COOKIES_CLEAR_WHEN_CLOSE_CHKBOX }, + { "cookies_lso_clear_when_close", IDS_COOKIES_LSO_CLEAR_WHEN_CLOSE_CHKBOX }, + { "cookies_show_cookies", IDS_COOKIES_SHOW_COOKIES_BUTTON }, + { "flash_storage_settings", IDS_FLASH_STORAGE_SETTINGS }, + { "flash_storage_url", IDS_FLASH_STORAGE_URL }, + // Image filter. + { "images_tab_label", IDS_IMAGES_TAB_LABEL }, + { "images_header", IDS_IMAGES_HEADER }, + { "images_allow", IDS_IMAGES_LOAD_RADIO }, + { "images_block", IDS_IMAGES_NOLOAD_RADIO }, + // JavaScript filter. + { "javascript_tab_label", IDS_JAVASCRIPT_TAB_LABEL }, + { "javascript_header", IDS_JAVASCRIPT_HEADER }, + { "javascript_allow", IDS_JS_ALLOW_RADIO }, + { "javascript_block", IDS_JS_DONOTALLOW_RADIO }, + // Plug-ins filter. + { "plugins_tab_label", IDS_PLUGIN_TAB_LABEL }, + { "plugins_header", IDS_PLUGIN_HEADER }, + { "plugins_ask", IDS_PLUGIN_ASK_RADIO }, + { "plugins_allow", IDS_PLUGIN_LOAD_RADIO }, + { "plugins_block", IDS_PLUGIN_NOLOAD_RADIO }, + { "disableIndividualPlugins", IDS_PLUGIN_SELECTIVE_DISABLE }, + // Pop-ups filter. + { "popups_tab_label", IDS_POPUP_TAB_LABEL }, + { "popups_header", IDS_POPUP_HEADER }, + { "popups_allow", IDS_POPUP_ALLOW_RADIO }, + { "popups_block", IDS_POPUP_BLOCK_RADIO }, + // Location filter. + { "location_tab_label", IDS_GEOLOCATION_TAB_LABEL }, + { "location_header", IDS_GEOLOCATION_HEADER }, + { "location_allow", IDS_GEOLOCATION_ALLOW_RADIO }, + { "location_ask", IDS_GEOLOCATION_ASK_RADIO }, + { "location_block", IDS_GEOLOCATION_BLOCK_RADIO }, + // Notifications filter. + { "notifications_tab_label", IDS_NOTIFICATIONS_TAB_LABEL }, + { "notifications_header", IDS_NOTIFICATIONS_HEADER }, + { "notifications_allow", IDS_NOTIFICATIONS_ALLOW_RADIO }, + { "notifications_ask", IDS_NOTIFICATIONS_ASK_RADIO }, + { "notifications_block", IDS_NOTIFICATIONS_BLOCK_RADIO }, + // Intents filter. + { "intentsTabLabel", IDS_INTENTS_TAB_LABEL }, + { "intentsAllow", IDS_INTENTS_ALLOW_RADIO }, + { "intentsAsk", IDS_INTENTS_ASK_RADIO }, + { "intentsBlock", IDS_INTENTS_BLOCK_RADIO }, + { "intents_header", IDS_INTENTS_HEADER }, + // Fullscreen filter. + { "fullscreen_tab_label", IDS_FULLSCREEN_TAB_LABEL }, + { "fullscreen_header", IDS_FULLSCREEN_HEADER }, + // Mouse Lock filter. + { "mouselock_tab_label", IDS_MOUSE_LOCK_TAB_LABEL }, + { "mouselock_header", IDS_MOUSE_LOCK_HEADER }, + { "mouselock_allow", IDS_MOUSE_LOCK_ALLOW_RADIO }, + { "mouselock_ask", IDS_MOUSE_LOCK_ASK_RADIO }, + { "mouselock_block", IDS_MOUSE_LOCK_BLOCK_RADIO }, + }; + + RegisterStrings(localized_strings, resources, arraysize(resources)); + RegisterTitle(localized_strings, "contentSettingsPage", + IDS_CONTENT_SETTINGS_TITLE); + localized_strings->SetBoolean("enable_click_to_play", + CommandLine::ForCurrentProcess()->HasSwitch( + switches::kEnableClickToPlay)); + localized_strings->SetBoolean("enable_web_intents", + CommandLine::ForCurrentProcess()->HasSwitch( + switches::kEnableWebIntents)); +} + +void ContentSettingsHandler::Initialize() { + notification_registrar_.Add( + this, chrome::NOTIFICATION_PROFILE_CREATED, + content::NotificationService::AllSources()); + notification_registrar_.Add( + this, chrome::NOTIFICATION_PROFILE_DESTROYED, + content::NotificationService::AllSources()); + + UpdateHandlersEnabledRadios(); + UpdateAllExceptionsViewsFromModel(); + notification_registrar_.Add( + this, chrome::NOTIFICATION_CONTENT_SETTINGS_CHANGED, + content::NotificationService::AllSources()); + notification_registrar_.Add( + this, chrome::NOTIFICATION_DESKTOP_NOTIFICATION_SETTINGS_CHANGED, + content::NotificationService::AllSources()); + Profile* profile = Profile::FromWebUI(web_ui_); + notification_registrar_.Add( + this, chrome::NOTIFICATION_PROTOCOL_HANDLER_REGISTRY_CHANGED, + content::Source<Profile>(profile)); + + PrefService* prefs = profile->GetPrefs(); + pref_change_registrar_.Init(prefs); + pref_change_registrar_.Add(prefs::kGeolocationContentSettings, this); +} + +void ContentSettingsHandler::Observe( + int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) { + switch (type) { + case chrome::NOTIFICATION_PROFILE_DESTROYED: { + if (content::Source<Profile>(source).ptr()->IsOffTheRecord()) { + web_ui_->CallJavascriptFunction( + "ContentSettingsExceptionsArea.OTRProfileDestroyed"); + } + break; + } + + case chrome::NOTIFICATION_PROFILE_CREATED: { + if (content::Source<Profile>(source).ptr()->IsOffTheRecord()) + UpdateAllOTRExceptionsViewsFromModel(); + break; + } + + case chrome::NOTIFICATION_CONTENT_SETTINGS_CHANGED: { + // Filter out notifications from other profiles. + HostContentSettingsMap* map = + content::Source<HostContentSettingsMap>(source).ptr(); + if (map != GetContentSettingsMap() && + map != GetOTRContentSettingsMap()) + break; + + const ContentSettingsDetails* settings_details = + content::Details<const ContentSettingsDetails>(details).ptr(); + + // TODO(estade): we pretend update_all() is always true. + if (settings_details->update_all_types()) + UpdateAllExceptionsViewsFromModel(); + else + UpdateExceptionsViewFromModel(settings_details->type()); + break; + } + + case chrome::NOTIFICATION_PREF_CHANGED: { + const std::string& pref_name = + *content::Details<std::string>(details).ptr(); + if (pref_name == prefs::kGeolocationContentSettings) + UpdateGeolocationExceptionsView(); + break; + } + + case chrome::NOTIFICATION_DESKTOP_NOTIFICATION_SETTINGS_CHANGED: { + UpdateNotificationExceptionsView(); + break; + } + + case chrome::NOTIFICATION_PROTOCOL_HANDLER_REGISTRY_CHANGED: { + UpdateHandlersEnabledRadios(); + break; + } + + default: + OptionsPage2UIHandler::Observe(type, source, details); + } +} + +void ContentSettingsHandler::UpdateSettingDefaultFromModel( + ContentSettingsType type) { + DictionaryValue filter_settings; + std::string provider_id; + filter_settings.SetString(ContentSettingsTypeToGroupName(type) + ".value", + GetSettingDefaultFromModel(type, &provider_id)); + filter_settings.SetString( + ContentSettingsTypeToGroupName(type) + ".managedBy", + provider_id); + + web_ui_->CallJavascriptFunction( + "ContentSettings.setContentFilterSettingsValue", filter_settings); +} + +std::string ContentSettingsHandler::GetSettingDefaultFromModel( + ContentSettingsType type, std::string* provider_id) { + Profile* profile = Profile::FromWebUI(web_ui_); + ContentSetting default_setting; + if (type == CONTENT_SETTINGS_TYPE_NOTIFICATIONS) { + default_setting = + DesktopNotificationServiceFactory::GetForProfile(profile)-> + GetDefaultContentSetting(provider_id); + } else { + default_setting = + profile->GetHostContentSettingsMap()-> + GetDefaultContentSetting(type, provider_id); + } + + return ContentSettingToString(default_setting); +} + +void ContentSettingsHandler::UpdateHandlersEnabledRadios() { +#if defined(ENABLE_REGISTER_PROTOCOL_HANDLER) + DCHECK(web_ui_); + base::FundamentalValue handlers_enabled( + GetProtocolHandlerRegistry()->enabled()); + + web_ui_->CallJavascriptFunction("ContentSettings.updateHandlersEnabledRadios", + handlers_enabled); +#endif // defined(ENABLE_REGISTER_PROTOCOL_HANDLER) +} + +void ContentSettingsHandler::UpdateAllExceptionsViewsFromModel() { + for (int type = CONTENT_SETTINGS_TYPE_DEFAULT + 1; + type < CONTENT_SETTINGS_NUM_TYPES; ++type) { + // The content settings type CONTENT_SETTINGS_TYPE_AUTO_SELECT_CERTIFICATE + // is supposed to be set by policy only. Hence there is no user facing UI + // for this content type and we skip it here. + if (type == CONTENT_SETTINGS_TYPE_AUTO_SELECT_CERTIFICATE) + continue; + UpdateExceptionsViewFromModel(static_cast<ContentSettingsType>(type)); + } +} + +void ContentSettingsHandler::UpdateAllOTRExceptionsViewsFromModel() { + for (int type = CONTENT_SETTINGS_TYPE_DEFAULT + 1; + type < CONTENT_SETTINGS_NUM_TYPES; ++type) { + UpdateOTRExceptionsViewFromModel(static_cast<ContentSettingsType>(type)); + } +} + +void ContentSettingsHandler::UpdateExceptionsViewFromModel( + ContentSettingsType type) { + // Skip updating intents unless it's enabled from the command line. + if (type == CONTENT_SETTINGS_TYPE_INTENTS && + !CommandLine::ForCurrentProcess()->HasSwitch( + switches::kEnableWebIntents)) + return; + + switch (type) { + case CONTENT_SETTINGS_TYPE_GEOLOCATION: + UpdateGeolocationExceptionsView(); + break; + case CONTENT_SETTINGS_TYPE_NOTIFICATIONS: + UpdateNotificationExceptionsView(); + break; + default: + UpdateExceptionsViewFromHostContentSettingsMap(type); + break; + } +} + +void ContentSettingsHandler::UpdateOTRExceptionsViewFromModel( + ContentSettingsType type) { + switch (type) { + case CONTENT_SETTINGS_TYPE_GEOLOCATION: + case CONTENT_SETTINGS_TYPE_NOTIFICATIONS: + case CONTENT_SETTINGS_TYPE_INTENTS: + case CONTENT_SETTINGS_TYPE_AUTO_SELECT_CERTIFICATE: + break; + default: + UpdateExceptionsViewFromOTRHostContentSettingsMap(type); + break; + } +} + +void ContentSettingsHandler::UpdateGeolocationExceptionsView() { + Profile* profile = Profile::FromWebUI(web_ui_); + HostContentSettingsMap* map = profile->GetHostContentSettingsMap(); + + ContentSettingsForOneType all_settings; + map->GetSettingsForOneType( + CONTENT_SETTINGS_TYPE_GEOLOCATION, + std::string(), + &all_settings); + + // Group geolocation settings by primary_pattern. + AllPatternsSettings all_patterns_settings; + for (ContentSettingsForOneType::iterator i = + all_settings.begin(); + i != all_settings.end(); + ++i) { + // Don't add default settings. + if (i->primary_pattern == ContentSettingsPattern::Wildcard() && + i->secondary_pattern == ContentSettingsPattern::Wildcard() && + i->source != "preferences") { + continue; + } + all_patterns_settings[i->primary_pattern][i->secondary_pattern] = + i->setting; + } + + ListValue exceptions; + for (AllPatternsSettings::iterator i = all_patterns_settings.begin(); + i != all_patterns_settings.end(); + ++i) { + const ContentSettingsPattern& primary_pattern = i->first; + const OnePatternSettings& one_settings = i->second; + + OnePatternSettings::const_iterator parent = + one_settings.find(primary_pattern); + + // Add the "parent" entry for the non-embedded setting. + ContentSetting parent_setting = + parent == one_settings.end() ? CONTENT_SETTING_DEFAULT : parent->second; + exceptions.Append(GetGeolocationExceptionForPage(primary_pattern, + primary_pattern, + parent_setting)); + + // Add the "children" for any embedded settings. + for (OnePatternSettings::const_iterator j = one_settings.begin(); + j != one_settings.end(); + ++j) { + // Skip the non-embedded setting which we already added above. + if (j == parent) + continue; + + exceptions.Append( + GetGeolocationExceptionForPage(primary_pattern, j->first, j->second)); + } + } + + StringValue type_string( + ContentSettingsTypeToGroupName(CONTENT_SETTINGS_TYPE_GEOLOCATION)); + web_ui_->CallJavascriptFunction("ContentSettings.setExceptions", + type_string, exceptions); + + // This is mainly here to keep this function ideologically parallel to + // UpdateExceptionsViewFromHostContentSettingsMap(). + UpdateSettingDefaultFromModel(CONTENT_SETTINGS_TYPE_GEOLOCATION); +} + +void ContentSettingsHandler::UpdateNotificationExceptionsView() { + Profile* profile = Profile::FromWebUI(web_ui_); + DesktopNotificationService* service = + DesktopNotificationServiceFactory::GetForProfile(profile); + + ContentSettingsForOneType settings; + service->GetNotificationsSettings(&settings); + + ListValue exceptions; + for (ContentSettingsForOneType::const_iterator i = + settings.begin(); + i != settings.end(); + ++i) { + // Don't add default settings. + if (i->primary_pattern == ContentSettingsPattern::Wildcard() && + i->secondary_pattern == ContentSettingsPattern::Wildcard() && + i->source != "preferences") { + continue; + } + + exceptions.Append( + GetNotificationExceptionForPage(i->primary_pattern, i->setting, + i->source)); + } + + StringValue type_string( + ContentSettingsTypeToGroupName(CONTENT_SETTINGS_TYPE_NOTIFICATIONS)); + web_ui_->CallJavascriptFunction("ContentSettings.setExceptions", + type_string, exceptions); + + // This is mainly here to keep this function ideologically parallel to + // UpdateExceptionsViewFromHostContentSettingsMap(). + UpdateSettingDefaultFromModel(CONTENT_SETTINGS_TYPE_NOTIFICATIONS); +} + +void ContentSettingsHandler::UpdateExceptionsViewFromHostContentSettingsMap( + ContentSettingsType type) { + ContentSettingsForOneType entries; + GetContentSettingsMap()->GetSettingsForOneType(type, "", &entries); + + ListValue exceptions; + for (size_t i = 0; i < entries.size(); ++i) { + // Skip default settings from extensions and policy, and the default content + // settings; all of them will affect the default setting UI. + if (entries[i].primary_pattern == ContentSettingsPattern::Wildcard() && + entries[i].secondary_pattern == ContentSettingsPattern::Wildcard() && + entries[i].source != "preference") { + continue; + } + // The content settings UI does not support secondary content settings + // pattern yet. For content settings set through the content settings UI the + // secondary pattern is by default a wildcard pattern. Hence users are not + // able to modify content settings with a secondary pattern other than the + // wildcard pattern. So only show settings that the user is able to modify. + // TODO(bauerb): Support a read-only view for those patterns. + if (entries[i].secondary_pattern == ContentSettingsPattern::Wildcard()) { + exceptions.Append( + GetExceptionForPage(entries[i].primary_pattern, entries[i].setting, + entries[i].source)); + } else { + LOG(ERROR) << "Secondary content settings patterns are not " + << "supported by the content settings UI"; + } + } + + StringValue type_string(ContentSettingsTypeToGroupName(type)); + web_ui_->CallJavascriptFunction("ContentSettings.setExceptions", type_string, + exceptions); + + UpdateExceptionsViewFromOTRHostContentSettingsMap(type); + + // TODO(koz): The default for fullscreen is always 'ask'. + // http://crbug.com/104683 + if (type == CONTENT_SETTINGS_TYPE_FULLSCREEN) + return; + + // The default may also have changed (we won't get a separate notification). + // If it hasn't changed, this call will be harmless. + UpdateSettingDefaultFromModel(type); +} + +void ContentSettingsHandler::UpdateExceptionsViewFromOTRHostContentSettingsMap( + ContentSettingsType type) { + const HostContentSettingsMap* otr_settings_map = GetOTRContentSettingsMap(); + if (!otr_settings_map) + return; + + ContentSettingsForOneType otr_entries; + otr_settings_map->GetSettingsForOneType(type, "", &otr_entries); + + ListValue otr_exceptions; + for (size_t i = 0; i < otr_entries.size(); ++i) { + // Off-the-record HostContentSettingsMap contains incognito content settings + // as well as normal content settings. Here, we use the incongnito settings + // only. + if (!otr_entries[i].incognito) + continue; + // The content settings UI does not support secondary content settings + // pattern yet. For content settings set through the content settings UI the + // secondary pattern is by default a wildcard pattern. Hence users are not + // able to modify content settings with a secondary pattern other than the + // wildcard pattern. So only show settings that the user is able to modify. + // TODO(bauerb): Support a read-only view for those patterns. + if (otr_entries[i].secondary_pattern == + ContentSettingsPattern::Wildcard()) { + otr_exceptions.Append( + GetExceptionForPage(otr_entries[i].primary_pattern, + otr_entries[i].setting, + otr_entries[i].source)); + } else { + LOG(ERROR) << "Secondary content settings patterns are not " + << "supported by the content settings UI"; + } + } + + StringValue type_string(ContentSettingsTypeToGroupName(type)); + web_ui_->CallJavascriptFunction("ContentSettings.setOTRExceptions", + type_string, otr_exceptions); +} + +void ContentSettingsHandler::RegisterMessages() { + web_ui_->RegisterMessageCallback("setContentFilter", + base::Bind(&ContentSettingsHandler::SetContentFilter, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("removeException", + base::Bind(&ContentSettingsHandler::RemoveException, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("setException", + base::Bind(&ContentSettingsHandler::SetException, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("checkExceptionPatternValidity", + base::Bind(&ContentSettingsHandler::CheckExceptionPatternValidity, + base::Unretained(this))); +} + +void ContentSettingsHandler::SetContentFilter(const ListValue* args) { + DCHECK_EQ(2U, args->GetSize()); + std::string group, setting; + if (!(args->GetString(0, &group) && + args->GetString(1, &setting))) { + NOTREACHED(); + return; + } + + ContentSetting default_setting = ContentSettingFromString(setting); + ContentSettingsType content_type = ContentSettingsTypeFromGroupName(group); + if (content_type == CONTENT_SETTINGS_TYPE_NOTIFICATIONS) { + Profile* profile = Profile::FromWebUI(web_ui_); + DesktopNotificationServiceFactory::GetForProfile(profile)-> + SetDefaultContentSetting(default_setting); + } else { + GetContentSettingsMap()-> + SetDefaultContentSetting(content_type, default_setting); + } + switch (content_type) { + case CONTENT_SETTINGS_TYPE_COOKIES: + content::RecordAction( + UserMetricsAction("Options_DefaultCookieSettingChanged")); + break; + case CONTENT_SETTINGS_TYPE_IMAGES: + content::RecordAction( + UserMetricsAction("Options_DefaultImagesSettingChanged")); + break; + case CONTENT_SETTINGS_TYPE_JAVASCRIPT: + content::RecordAction( + UserMetricsAction("Options_DefaultJavaScriptSettingChanged")); + break; + case CONTENT_SETTINGS_TYPE_PLUGINS: + content::RecordAction( + UserMetricsAction("Options_DefaultPluginsSettingChanged")); + break; + case CONTENT_SETTINGS_TYPE_POPUPS: + content::RecordAction( + UserMetricsAction("Options_DefaultPopupsSettingChanged")); + break; + case CONTENT_SETTINGS_TYPE_NOTIFICATIONS: + content::RecordAction( + UserMetricsAction("Options_DefaultNotificationsSettingChanged")); + break; + case CONTENT_SETTINGS_TYPE_GEOLOCATION: + content::RecordAction( + UserMetricsAction("Options_DefaultGeolocationSettingChanged")); + break; + case CONTENT_SETTINGS_TYPE_INTENTS: + content::RecordAction( + UserMetricsAction("Options_DefaultHandlersSettingChanged")); + break; + case CONTENT_SETTINGS_TYPE_MOUSELOCK: + content::RecordAction( + UserMetricsAction("Options_DefaultMouseLockSettingChanged")); + break; + default: + break; + } +} + +void ContentSettingsHandler::RemoveException(const ListValue* args) { + size_t arg_i = 0; + std::string type_string; + CHECK(args->GetString(arg_i++, &type_string)); + + Profile* profile = Profile::FromWebUI(web_ui_); + ContentSettingsType type = ContentSettingsTypeFromGroupName(type_string); + if (type == CONTENT_SETTINGS_TYPE_GEOLOCATION) { + std::string origin; + std::string embedding_origin; + bool rv = args->GetString(arg_i++, &origin); + DCHECK(rv); + rv = args->GetString(arg_i++, &embedding_origin); + DCHECK(rv); + + profile->GetHostContentSettingsMap()-> + SetContentSetting(ContentSettingsPattern::FromString(origin), + ContentSettingsPattern::FromString(embedding_origin), + CONTENT_SETTINGS_TYPE_GEOLOCATION, + std::string(), + CONTENT_SETTING_DEFAULT); + } else if (type == CONTENT_SETTINGS_TYPE_NOTIFICATIONS) { + std::string origin; + std::string setting; + bool rv = args->GetString(arg_i++, &origin); + DCHECK(rv); + rv = args->GetString(arg_i++, &setting); + DCHECK(rv); + ContentSetting content_setting = ContentSettingFromString(setting); + + DCHECK(content_setting == CONTENT_SETTING_ALLOW || + content_setting == CONTENT_SETTING_BLOCK); + DesktopNotificationServiceFactory::GetForProfile(profile)-> + ClearSetting(ContentSettingsPattern::FromString(origin)); + } else { + std::string mode; + bool rv = args->GetString(arg_i++, &mode); + DCHECK(rv); + + std::string pattern; + rv = args->GetString(arg_i++, &pattern); + DCHECK(rv); + + HostContentSettingsMap* settings_map = + mode == "normal" ? GetContentSettingsMap() : + GetOTRContentSettingsMap(); + // The settings map could be null if the mode was OTR but the OTR profile + // got destroyed before we received this message. + if (settings_map) { + settings_map->SetContentSetting( + ContentSettingsPattern::FromString(pattern), + ContentSettingsPattern::Wildcard(), + ContentSettingsTypeFromGroupName(type_string), + "", + CONTENT_SETTING_DEFAULT); + } + } +} + +void ContentSettingsHandler::SetException(const ListValue* args) { + size_t arg_i = 0; + std::string type_string; + CHECK(args->GetString(arg_i++, &type_string)); + std::string mode; + CHECK(args->GetString(arg_i++, &mode)); + std::string pattern; + CHECK(args->GetString(arg_i++, &pattern)); + std::string setting; + CHECK(args->GetString(arg_i++, &setting)); + + ContentSettingsType type = ContentSettingsTypeFromGroupName(type_string); + if (type == CONTENT_SETTINGS_TYPE_GEOLOCATION || + type == CONTENT_SETTINGS_TYPE_NOTIFICATIONS) { + NOTREACHED(); + return; + } + + HostContentSettingsMap* settings_map = + mode == "normal" ? GetContentSettingsMap() : + GetOTRContentSettingsMap(); + + // The settings map could be null if the mode was OTR but the OTR profile + // got destroyed before we received this message. + if (!settings_map) + return; + settings_map->SetContentSetting(ContentSettingsPattern::FromString(pattern), + ContentSettingsPattern::Wildcard(), + type, + "", + ContentSettingFromString(setting)); +} + +void ContentSettingsHandler::CheckExceptionPatternValidity( + const ListValue* args) { + size_t arg_i = 0; + Value* type; + CHECK(args->Get(arg_i++, &type)); + std::string mode_string; + CHECK(args->GetString(arg_i++, &mode_string)); + std::string pattern_string; + CHECK(args->GetString(arg_i++, &pattern_string)); + + ContentSettingsPattern pattern = + ContentSettingsPattern::FromString(pattern_string); + + scoped_ptr<Value> mode_value(Value::CreateStringValue(mode_string)); + scoped_ptr<Value> pattern_value(Value::CreateStringValue(pattern_string)); + scoped_ptr<Value> valid_value(Value::CreateBooleanValue(pattern.IsValid())); + + web_ui_->CallJavascriptFunction( + "ContentSettings.patternValidityCheckComplete", + *type, + *mode_value.get(), + *pattern_value.get(), + *valid_value.get()); +} + +// static +std::string ContentSettingsHandler::ContentSettingsTypeToGroupName( + ContentSettingsType type) { + for (size_t i = 0; i < arraysize(kContentSettingsTypeGroupNames); ++i) { + if (type == kContentSettingsTypeGroupNames[i].type) + return kContentSettingsTypeGroupNames[i].name; + } + + NOTREACHED(); + return std::string(); +} + +HostContentSettingsMap* ContentSettingsHandler::GetContentSettingsMap() { + return Profile::FromWebUI(web_ui_)->GetHostContentSettingsMap(); +} + +ProtocolHandlerRegistry* ContentSettingsHandler::GetProtocolHandlerRegistry() { + return Profile::FromWebUI(web_ui_)->GetProtocolHandlerRegistry(); +} + +HostContentSettingsMap* + ContentSettingsHandler::GetOTRContentSettingsMap() { + Profile* profile = Profile::FromWebUI(web_ui_); + if (profile->HasOffTheRecordProfile()) + return profile->GetOffTheRecordProfile()->GetHostContentSettingsMap(); + return NULL; +} diff --git a/chrome/browser/ui/webui/options2/content_settings_handler.h b/chrome/browser/ui/webui/options2/content_settings_handler.h new file mode 100644 index 0000000..665a3ef --- /dev/null +++ b/chrome/browser/ui/webui/options2/content_settings_handler.h @@ -0,0 +1,113 @@ +// Copyright (c) 2011 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. + +#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS2_CONTENT_SETTINGS_HANDLER_H_ +#define CHROME_BROWSER_UI_WEBUI_OPTIONS2_CONTENT_SETTINGS_HANDLER_H_ +#pragma once + +#include "chrome/browser/plugin_data_remover_helper.h" +#include "chrome/browser/prefs/pref_change_registrar.h" +#include "chrome/browser/ui/webui/options2/options_ui2.h" +#include "chrome/common/content_settings_types.h" +#include "content/public/browser/notification_observer.h" +#include "content/public/browser/notification_registrar.h" + +class HostContentSettingsMap; +class ProtocolHandlerRegistry; + +class ContentSettingsHandler : public OptionsPage2UIHandler { + public: + ContentSettingsHandler(); + virtual ~ContentSettingsHandler(); + + // OptionsPage2UIHandler implementation. + virtual void GetLocalizedValues(DictionaryValue* localized_strings) OVERRIDE; + + virtual void Initialize() OVERRIDE; + + virtual void RegisterMessages() OVERRIDE; + + // content::NotificationObserver implementation. + virtual void Observe(int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) OVERRIDE; + + // Gets a string identifier for the group name, for use in HTML. + static std::string ContentSettingsTypeToGroupName(ContentSettingsType type); + + private: + // Functions that call into the page ----------------------------------------- + + // Updates the page with the default settings (allow, ask, block, etc.) + void UpdateSettingDefaultFromModel(ContentSettingsType type); + + // Clobbers and rebuilds the specific content setting type exceptions table. + void UpdateExceptionsViewFromModel(ContentSettingsType type); + // Clobbers and rebuilds the specific content setting type exceptions + // OTR table. + void UpdateOTRExceptionsViewFromModel(ContentSettingsType type); + // Clobbers and rebuilds all the exceptions tables in the page (both normal + // and OTR tables). + void UpdateAllExceptionsViewsFromModel(); + // As above, but only OTR tables. + void UpdateAllOTRExceptionsViewsFromModel(); + // Clobbers and rebuilds just the geolocation exception table. + void UpdateGeolocationExceptionsView(); + // Clobbers and rebuilds just the desktop notification exception table. + void UpdateNotificationExceptionsView(); + // Clobbers and rebuilds an exception table that's managed by the host content + // settings map. + void UpdateExceptionsViewFromHostContentSettingsMap(ContentSettingsType type); + // As above, but acts on the OTR table for the content setting type. + void UpdateExceptionsViewFromOTRHostContentSettingsMap( + ContentSettingsType type); + // Updates the radio buttons for enabling / disabling handlers. + void UpdateHandlersEnabledRadios(); + + // Callbacks used by the page ------------------------------------------------ + + // Sets the default value for a specific content type. |args| includes the + // content type and a string describing the new default the user has + // chosen. + void SetContentFilter(const ListValue* args); + + // Removes the given row from the table. The first entry in |args| is the + // content type, and the rest of the arguments depend on the content type + // to be removed. + void RemoveException(const ListValue* args); + + // Changes the value of an exception. Called after the user is done editing an + // exception. + void SetException(const ListValue* args); + + // Called to decide whether a given pattern is valid, or if it should be + // rejected. Called while the user is editing an exception pattern. + void CheckExceptionPatternValidity(const ListValue* args); + + // Utility functions --------------------------------------------------------- + + // Gets the HostContentSettingsMap for the normal profile. + HostContentSettingsMap* GetContentSettingsMap(); + + // Gets the HostContentSettingsMap for the incognito profile, or NULL if there + // is no active incognito session. + HostContentSettingsMap* GetOTRContentSettingsMap(); + + // Gets the default setting in string form. If |provider_id| is not NULL, the + // id of the provider which provided the default setting is assigned to it. + std::string GetSettingDefaultFromModel(ContentSettingsType type, + std::string* provider_id); + + // Gets the ProtocolHandlerRegistry for the normal profile. + ProtocolHandlerRegistry* GetProtocolHandlerRegistry(); + + // Member variables --------------------------------------------------------- + + content::NotificationRegistrar notification_registrar_; + PrefChangeRegistrar pref_change_registrar_; + + DISALLOW_COPY_AND_ASSIGN(ContentSettingsHandler); +}; + +#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS2_CONTENT_SETTINGS_HANDLER_H_ diff --git a/chrome/browser/ui/webui/options2/cookies_view_browsertest.js b/chrome/browser/ui/webui/options2/cookies_view_browsertest.js new file mode 100644 index 0000000..43213cb --- /dev/null +++ b/chrome/browser/ui/webui/options2/cookies_view_browsertest.js @@ -0,0 +1,24 @@ +// Copyright (c) 2011 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. + +/** + * TestFixture for cookies view WebUI testing. + * @extends {testing.Test} + * @constructor + **/ +function CookiesViewWebUITest() {} + +CookiesViewWebUITest.prototype = { + __proto__: testing.Test.prototype, + + /** + * Browse to the cookies view. + **/ + browsePreload: 'chrome://settings/cookies', +}; + +// Test opening the cookies view has correct location. +TEST_F('CookiesViewWebUITest', 'testOpenCookiesView', function() { + assertEquals(this.browsePreload, document.location.href); +}); diff --git a/chrome/browser/ui/webui/options2/cookies_view_handler.cc b/chrome/browser/ui/webui/options2/cookies_view_handler.cc new file mode 100644 index 0000000..6d86eb2 --- /dev/null +++ b/chrome/browser/ui/webui/options2/cookies_view_handler.cc @@ -0,0 +1,222 @@ +// Copyright (c) 2011 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. + +#include "chrome/browser/ui/webui/options2/cookies_view_handler.h" + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/utf_string_conversions.h" +#include "base/values.h" +#include "chrome/browser/browsing_data_appcache_helper.h" +#include "chrome/browser/browsing_data_cookie_helper.h" +#include "chrome/browser/browsing_data_database_helper.h" +#include "chrome/browser/browsing_data_file_system_helper.h" +#include "chrome/browser/browsing_data_indexed_db_helper.h" +#include "chrome/browser/browsing_data_quota_helper.h" +#include "chrome/browser/browsing_data_local_storage_helper.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/ui/webui/cookies_tree_model_util.h" +#include "grit/generated_resources.h" +#include "ui/base/l10n/l10n_util.h" + +CookiesViewHandler::CookiesViewHandler() : batch_update_(false) { +} + +CookiesViewHandler::~CookiesViewHandler() { +} + +void CookiesViewHandler::GetLocalizedValues( + DictionaryValue* localized_strings) { + DCHECK(localized_strings); + + static OptionsStringResource resources[] = { + { "label_cookie_name", IDS_COOKIES_COOKIE_NAME_LABEL }, + { "label_cookie_content", IDS_COOKIES_COOKIE_CONTENT_LABEL }, + { "label_cookie_domain", IDS_COOKIES_COOKIE_DOMAIN_LABEL }, + { "label_cookie_path", IDS_COOKIES_COOKIE_PATH_LABEL }, + { "label_cookie_send_for", IDS_COOKIES_COOKIE_SENDFOR_LABEL }, + { "label_cookie_accessible_to_script", + IDS_COOKIES_COOKIE_ACCESSIBLE_TO_SCRIPT_LABEL }, + { "label_cookie_created", IDS_COOKIES_COOKIE_CREATED_LABEL }, + { "label_cookie_expires", IDS_COOKIES_COOKIE_EXPIRES_LABEL }, + { "label_webdb_desc", IDS_COOKIES_WEB_DATABASE_DESCRIPTION_LABEL }, + { "label_local_storage_size", + IDS_COOKIES_LOCAL_STORAGE_SIZE_ON_DISK_LABEL }, + { "label_local_storage_last_modified", + IDS_COOKIES_LOCAL_STORAGE_LAST_MODIFIED_LABEL }, + { "label_local_storage_origin", IDS_COOKIES_LOCAL_STORAGE_ORIGIN_LABEL }, + { "label_indexed_db_size", IDS_COOKIES_LOCAL_STORAGE_SIZE_ON_DISK_LABEL }, + { "label_indexed_db_last_modified", + IDS_COOKIES_LOCAL_STORAGE_LAST_MODIFIED_LABEL }, + { "label_indexed_db_origin", IDS_COOKIES_LOCAL_STORAGE_ORIGIN_LABEL }, + { "label_app_cache_manifest", + IDS_COOKIES_APPLICATION_CACHE_MANIFEST_LABEL }, + { "label_cookie_last_accessed", IDS_COOKIES_LAST_ACCESSED_LABEL }, + { "cookie_domain", IDS_COOKIES_DOMAIN_COLUMN_HEADER }, + { "cookie_local_data", IDS_COOKIES_DATA_COLUMN_HEADER }, + { "cookie_singular", IDS_COOKIES_SINGLE_COOKIE }, + { "cookie_plural", IDS_COOKIES_PLURAL_COOKIES }, + { "cookie_database_storage", IDS_COOKIES_DATABASE_STORAGE }, + { "cookie_indexed_db", IDS_COOKIES_INDEXED_DB }, + { "cookie_local_storage", IDS_COOKIES_LOCAL_STORAGE }, + { "cookie_app_cache", IDS_COOKIES_APPLICATION_CACHE }, + { "search_cookies", IDS_COOKIES_SEARCH_COOKIES }, + { "remove_cookie", IDS_COOKIES_REMOVE_LABEL }, + { "remove_all_cookie", IDS_COOKIES_REMOVE_ALL_LABEL }, + { "cookie_file_system", IDS_COOKIES_FILE_SYSTEM }, + { "label_file_system_origin", IDS_COOKIES_LOCAL_STORAGE_ORIGIN_LABEL }, + { "label_file_system_temporary_usage", + IDS_COOKIES_FILE_SYSTEM_TEMPORARY_USAGE_LABEL }, + { "label_file_system_persistent_usage", + IDS_COOKIES_FILE_SYSTEM_PERSISTENT_USAGE_LABEL }, + }; + + RegisterStrings(localized_strings, resources, arraysize(resources)); + RegisterTitle(localized_strings, "cookiesViewPage", + IDS_COOKIES_WEBSITE_PERMISSIONS_WINDOW_TITLE); +} + +void CookiesViewHandler::RegisterMessages() { + web_ui_->RegisterMessageCallback("updateCookieSearchResults", + base::Bind(&CookiesViewHandler::UpdateSearchResults, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("removeAllCookies", + base::Bind(&CookiesViewHandler::RemoveAll, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("removeCookie", + base::Bind(&CookiesViewHandler::Remove, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("loadCookie", + base::Bind(&CookiesViewHandler::LoadChildren, + base::Unretained(this))); +} + +void CookiesViewHandler::TreeNodesAdded(ui::TreeModel* model, + ui::TreeModelNode* parent, + int start, + int count) { + // Skip if there is a batch update in progress. + if (batch_update_) + return; + + CookieTreeNode* parent_node = cookies_tree_model_->AsNode(parent); + + ListValue* children = new ListValue; + cookies_tree_model_util::GetChildNodeList(parent_node, start, count, + children); + + ListValue args; + args.Append(parent == cookies_tree_model_->GetRoot() ? + Value::CreateNullValue() : + Value::CreateStringValue( + cookies_tree_model_util::GetTreeNodeId(parent_node))); + args.Append(Value::CreateIntegerValue(start)); + args.Append(children); + web_ui_->CallJavascriptFunction("CookiesView.onTreeItemAdded", args); +} + +void CookiesViewHandler::TreeNodesRemoved(ui::TreeModel* model, + ui::TreeModelNode* parent, + int start, + int count) { + // Skip if there is a batch update in progress. + if (batch_update_) + return; + + ListValue args; + args.Append(parent == cookies_tree_model_->GetRoot() ? + Value::CreateNullValue() : + Value::CreateStringValue(cookies_tree_model_util::GetTreeNodeId( + cookies_tree_model_->AsNode(parent)))); + args.Append(Value::CreateIntegerValue(start)); + args.Append(Value::CreateIntegerValue(count)); + web_ui_->CallJavascriptFunction("CookiesView.onTreeItemRemoved", args); +} + +void CookiesViewHandler::TreeModelBeginBatch(CookiesTreeModel* model) { + DCHECK(!batch_update_); // There should be no nested batch begin. + batch_update_ = true; +} + +void CookiesViewHandler::TreeModelEndBatch(CookiesTreeModel* model) { + DCHECK(batch_update_); + batch_update_ = false; + + SendChildren(cookies_tree_model_->GetRoot()); +} + +void CookiesViewHandler::EnsureCookiesTreeModelCreated() { + if (!cookies_tree_model_.get()) { + Profile* profile = Profile::FromWebUI(web_ui_); + cookies_tree_model_.reset(new CookiesTreeModel( + new BrowsingDataCookieHelper(profile), + new BrowsingDataDatabaseHelper(profile), + new BrowsingDataLocalStorageHelper(profile), + NULL, + new BrowsingDataAppCacheHelper(profile), + BrowsingDataIndexedDBHelper::Create(profile), + BrowsingDataFileSystemHelper::Create(profile), + BrowsingDataQuotaHelper::Create(profile), + false)); + cookies_tree_model_->AddCookiesTreeObserver(this); + } +} + +void CookiesViewHandler::UpdateSearchResults(const ListValue* args) { + std::string query; + if (!args->GetString(0, &query)) { + return; + } + + EnsureCookiesTreeModelCreated(); + + cookies_tree_model_->UpdateSearchResults(UTF8ToWide(query)); +} + +void CookiesViewHandler::RemoveAll(const ListValue* args) { + EnsureCookiesTreeModelCreated(); + cookies_tree_model_->DeleteAllStoredObjects(); +} + +void CookiesViewHandler::Remove(const ListValue* args) { + std::string node_path; + if (!args->GetString(0, &node_path)) { + return; + } + + EnsureCookiesTreeModelCreated(); + + CookieTreeNode* node = cookies_tree_model_util::GetTreeNodeFromPath( + cookies_tree_model_->GetRoot(), node_path); + if (node) + cookies_tree_model_->DeleteCookieNode(node); +} + +void CookiesViewHandler::LoadChildren(const ListValue* args) { + std::string node_path; + if (!args->GetString(0, &node_path)) { + return; + } + + EnsureCookiesTreeModelCreated(); + + CookieTreeNode* node = cookies_tree_model_util::GetTreeNodeFromPath( + cookies_tree_model_->GetRoot(), node_path); + if (node) + SendChildren(node); +} + +void CookiesViewHandler::SendChildren(CookieTreeNode* parent) { + ListValue* children = new ListValue; + cookies_tree_model_util::GetChildNodeList(parent, 0, parent->child_count(), + children); + + ListValue args; + args.Append(parent == cookies_tree_model_->GetRoot() ? + Value::CreateNullValue() : + Value::CreateStringValue(cookies_tree_model_util::GetTreeNodeId(parent))); + args.Append(children); + + web_ui_->CallJavascriptFunction("CookiesView.loadChildren", args); +} diff --git a/chrome/browser/ui/webui/options2/cookies_view_handler.h b/chrome/browser/ui/webui/options2/cookies_view_handler.h new file mode 100644 index 0000000..5167cf6 --- /dev/null +++ b/chrome/browser/ui/webui/options2/cookies_view_handler.h @@ -0,0 +1,69 @@ +// Copyright (c) 2011 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. + +#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS2_COOKIES_VIEW_HANDLER_H_ +#define CHROME_BROWSER_UI_WEBUI_OPTIONS2_COOKIES_VIEW_HANDLER_H_ +#pragma once + +#include "base/compiler_specific.h" +#include "base/memory/scoped_ptr.h" +#include "chrome/browser/cookies_tree_model.h" +#include "chrome/browser/ui/webui/options2/options_ui2.h" + +class CookiesViewHandler : public OptionsPage2UIHandler, + public CookiesTreeModel::Observer { + public: + CookiesViewHandler(); + virtual ~CookiesViewHandler(); + + // OptionsPage2UIHandler implementation. + virtual void GetLocalizedValues( + base::DictionaryValue* localized_strings) OVERRIDE; + virtual void RegisterMessages() OVERRIDE; + + // CookiesTreeModel::Observer implementation. + virtual void TreeNodesAdded(ui::TreeModel* model, + ui::TreeModelNode* parent, + int start, + int count) OVERRIDE; + virtual void TreeNodesRemoved(ui::TreeModel* model, + ui::TreeModelNode* parent, + int start, + int count) OVERRIDE; + virtual void TreeNodeChanged(ui::TreeModel* model, + ui::TreeModelNode* node) OVERRIDE {} + virtual void TreeModelBeginBatch(CookiesTreeModel* model) OVERRIDE; + virtual void TreeModelEndBatch(CookiesTreeModel* model) OVERRIDE; + + private: + // Creates the CookiesTreeModel if neccessary. + void EnsureCookiesTreeModelCreated(); + + // Updates search filter for cookies tree model. + void UpdateSearchResults(const base::ListValue* args); + + // Remove all sites data. + void RemoveAll(const base::ListValue* args); + + // Remove selected sites data. + void Remove(const base::ListValue* args); + + // Get the tree node using the tree path info in |args| and call + // SendChildren to pass back children nodes data to WebUI. + void LoadChildren(const base::ListValue* args); + + // Get children nodes data and pass it to 'CookiesView.loadChildren' to + // update the WebUI. + void SendChildren(CookieTreeNode* parent); + + // The Cookies Tree model + scoped_ptr<CookiesTreeModel> cookies_tree_model_; + + // Flag to indicate whether there is a batch update in progress. + bool batch_update_; + + DISALLOW_COPY_AND_ASSIGN(CookiesViewHandler); +}; + +#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS2_COOKIES_VIEW_HANDLER_H_ diff --git a/chrome/browser/ui/webui/options2/core_options_handler.cc b/chrome/browser/ui/webui/options2/core_options_handler.cc new file mode 100644 index 0000000..4140233 --- /dev/null +++ b/chrome/browser/ui/webui/options2/core_options_handler.cc @@ -0,0 +1,466 @@ +// Copyright (c) 2011 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. + +#include "chrome/browser/ui/webui/options2/core_options_handler.h" + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/json/json_reader.h" +#include "base/memory/scoped_ptr.h" +#include "base/string16.h" +#include "base/string_number_conversions.h" +#include "base/utf_string_conversions.h" +#include "base/values.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/google/google_util.h" +#include "chrome/browser/net/url_fixer_upper.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/common/chrome_notification_types.h" +#include "chrome/common/pref_names.h" +#include "chrome/common/url_constants.h" +#include "content/public/browser/notification_details.h" +#include "content/public/browser/notification_types.h" +#include "content/public/browser/user_metrics.h" +#include "googleurl/src/gurl.h" +#include "grit/chromium_strings.h" +#include "grit/generated_resources.h" +#include "grit/locale_settings.h" +#include "grit/theme_resources.h" +#include "ui/base/l10n/l10n_util.h" + +using content::UserMetricsAction; + +CoreOptionsHandler::CoreOptionsHandler() + : handlers_host_(NULL) { +} + +CoreOptionsHandler::~CoreOptionsHandler() {} + +void CoreOptionsHandler::Initialize() { + clear_plugin_lso_data_enabled_.Init(prefs::kClearPluginLSODataEnabled, + Profile::FromWebUI(web_ui_), + this); + UpdateClearPluginLSOData(); +} + +void CoreOptionsHandler::GetLocalizedValues( + DictionaryValue* localized_strings) { + GetStaticLocalizedValues(localized_strings); +} + +void CoreOptionsHandler::GetStaticLocalizedValues( + base::DictionaryValue* localized_strings) { + DCHECK(localized_strings); + // Main + localized_strings->SetString("title", + l10n_util::GetStringUTF16(IDS_SETTINGS_TITLE)); + + // Managed prefs + localized_strings->SetString("policyManagedPrefsBannerText", + l10n_util::GetStringUTF16(IDS_OPTIONS_POLICY_MANAGED_PREFS)); + localized_strings->SetString("extensionManagedPrefsBannerText", + l10n_util::GetStringUTF16(IDS_OPTIONS_EXTENSION_MANAGED_PREFS)); + localized_strings->SetString("policyAndExtensionManagedPrefsBannerText", + l10n_util::GetStringUTF16(IDS_OPTIONS_POLICY_EXTENSION_MANAGED_PREFS)); + + // Controlled settings bubble. + localized_strings->SetString("controlledSettingPolicy", + l10n_util::GetStringUTF16(IDS_OPTIONS_CONTROLLED_SETTING_POLICY)); + localized_strings->SetString("controlledSettingExtension", + l10n_util::GetStringUTF16(IDS_OPTIONS_CONTROLLED_SETTING_EXTENSION)); + localized_strings->SetString("controlledSettingRecommended", + l10n_util::GetStringUTF16(IDS_OPTIONS_CONTROLLED_SETTING_RECOMMENDED)); + localized_strings->SetString("controlledSettingApplyRecommendation", + l10n_util::GetStringUTF16( + IDS_OPTIONS_CONTROLLED_SETTING_APPLY_RECOMMENDATION)); + + // Search + RegisterTitle(localized_strings, "searchPage", IDS_OPTIONS_SEARCH_PAGE_TITLE); + localized_strings->SetString("searchPlaceholder", + l10n_util::GetStringUTF16(IDS_OPTIONS_SEARCH_PLACEHOLDER)); + localized_strings->SetString("searchPageNoMatches", + l10n_util::GetStringUTF16(IDS_OPTIONS_SEARCH_PAGE_NO_MATCHES)); + localized_strings->SetString("searchPageHelpLabel", + l10n_util::GetStringUTF16(IDS_OPTIONS_SEARCH_PAGE_HELP_LABEL)); + localized_strings->SetString("searchPageHelpTitle", + l10n_util::GetStringFUTF16(IDS_OPTIONS_SEARCH_PAGE_HELP_TITLE, + l10n_util::GetStringUTF16(IDS_PRODUCT_NAME))); + localized_strings->SetString("searchPageHelpURL", + google_util::AppendGoogleLocaleParam( + GURL(chrome::kChromeHelpURL)).spec()); + + // Common + localized_strings->SetString("ok", + l10n_util::GetStringUTF16(IDS_OK)); + localized_strings->SetString("cancel", + l10n_util::GetStringUTF16(IDS_CANCEL)); + localized_strings->SetString("learnMore", + l10n_util::GetStringUTF16(IDS_LEARN_MORE)); + localized_strings->SetString("close", + l10n_util::GetStringUTF16(IDS_CLOSE)); +} + +void CoreOptionsHandler::Uninitialize() { + std::string last_pref; + for (PreferenceCallbackMap::const_iterator iter = pref_callback_map_.begin(); + iter != pref_callback_map_.end(); + ++iter) { + if (last_pref != iter->first) { + StopObservingPref(iter->first); + last_pref = iter->first; + } + } +} + +WebUIMessageHandler* CoreOptionsHandler::Attach(WebUI* web_ui) { + WebUIMessageHandler* result = WebUIMessageHandler::Attach(web_ui); + DCHECK(web_ui_); + registrar_.Init(Profile::FromWebUI(web_ui_)->GetPrefs()); + return result; +} + +void CoreOptionsHandler::Observe(int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) { + if (type == chrome::NOTIFICATION_PREF_CHANGED) { + std::string* pref_name = content::Details<std::string>(details).ptr(); + if (*pref_name == prefs::kClearPluginLSODataEnabled) { + // This preference is stored in Local State, not in the user preferences. + UpdateClearPluginLSOData(); + return; + } + NotifyPrefChanged(*pref_name, std::string()); + } +} + +void CoreOptionsHandler::RegisterMessages() { + web_ui_->RegisterMessageCallback("coreOptionsInitialize", + base::Bind(&CoreOptionsHandler::HandleInitialize, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("fetchPrefs", + base::Bind(&CoreOptionsHandler::HandleFetchPrefs, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("observePrefs", + base::Bind(&CoreOptionsHandler::HandleObservePrefs, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("setBooleanPref", + base::Bind(&CoreOptionsHandler::HandleSetBooleanPref, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("setIntegerPref", + base::Bind(&CoreOptionsHandler::HandleSetIntegerPref, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("setDoublePref", + base::Bind(&CoreOptionsHandler::HandleSetDoublePref, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("setStringPref", + base::Bind(&CoreOptionsHandler::HandleSetStringPref, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("setURLPref", + base::Bind(&CoreOptionsHandler::HandleSetURLPref, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("setListPref", + base::Bind(&CoreOptionsHandler::HandleSetListPref, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("clearPref", + base::Bind(&CoreOptionsHandler::HandleClearPref, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("coreOptionsUserMetricsAction", + base::Bind(&CoreOptionsHandler::HandleUserMetricsAction, + base::Unretained(this))); +} + +void CoreOptionsHandler::HandleInitialize(const ListValue* args) { + DCHECK(handlers_host_); + handlers_host_->InitializeHandlers(); +} + +base::Value* CoreOptionsHandler::FetchPref(const std::string& pref_name) { + PrefService* pref_service = Profile::FromWebUI(web_ui_)->GetPrefs(); + + const PrefService::Preference* pref = + pref_service->FindPreference(pref_name.c_str()); + if (!pref) + return base::Value::CreateNullValue(); + + return CreateValueForPref(pref, NULL); +} + +void CoreOptionsHandler::ObservePref(const std::string& pref_name) { + registrar_.Add(pref_name.c_str(), this); +} + +void CoreOptionsHandler::SetPref(const std::string& pref_name, + const base::Value* value, + const std::string& metric) { + PrefService* pref_service = Profile::FromWebUI(web_ui_)->GetPrefs(); + + switch (value->GetType()) { + case base::Value::TYPE_BOOLEAN: + case base::Value::TYPE_INTEGER: + case base::Value::TYPE_DOUBLE: + case base::Value::TYPE_STRING: + pref_service->Set(pref_name.c_str(), *value); + break; + + default: + NOTREACHED(); + return; + } + + pref_service->ScheduleSavePersistentPrefs(); + + ProcessUserMetric(value, metric); +} + +void CoreOptionsHandler::ClearPref(const std::string& pref_name, + const std::string& metric) { + PrefService* pref_service = Profile::FromWebUI(web_ui_)->GetPrefs(); + pref_service->ClearPref(pref_name.c_str()); + pref_service->ScheduleSavePersistentPrefs(); + + if (!metric.empty()) + content::RecordComputedAction(metric); +} + +void CoreOptionsHandler::ProcessUserMetric(const base::Value* value, + const std::string& metric) { + if (metric.empty()) + return; + + std::string metric_string = metric; + if (value->IsType(base::Value::TYPE_BOOLEAN)) { + bool bool_value; + CHECK(value->GetAsBoolean(&bool_value)); + metric_string += bool_value ? "_Enable" : "_Disable"; + } + + content::RecordComputedAction(metric_string); +} + +void CoreOptionsHandler::NotifyPrefChanged( + const std::string& pref_name, + const std::string& controlling_pref_name) { + const PrefService* pref_service = Profile::FromWebUI(web_ui_)->GetPrefs(); + const PrefService::Preference* pref = + pref_service->FindPreference(pref_name.c_str()); + if (!pref) + return; + const PrefService::Preference* controlling_pref = + !controlling_pref_name.empty() ? + pref_service->FindPreference(controlling_pref_name.c_str()) : NULL; + std::pair<PreferenceCallbackMap::const_iterator, + PreferenceCallbackMap::const_iterator> range; + range = pref_callback_map_.equal_range(pref_name); + for (PreferenceCallbackMap::const_iterator iter = range.first; + iter != range.second; ++iter) { + const std::wstring& callback_function = iter->second; + ListValue result_value; + result_value.Append(base::Value::CreateStringValue(pref_name.c_str())); + result_value.Append(CreateValueForPref(pref, controlling_pref)); + web_ui_->CallJavascriptFunction(WideToASCII(callback_function), + result_value); + } +} + +DictionaryValue* CoreOptionsHandler::CreateValueForPref( + const PrefService::Preference* pref, + const PrefService::Preference* controlling_pref) { + DictionaryValue* dict = new DictionaryValue; + dict->Set("value", pref->GetValue()->DeepCopy()); + if (!controlling_pref) // No controlling pref is managing actual pref. + controlling_pref = pref; // This means pref is controlling itself. + if (controlling_pref->IsManaged()) { + dict->SetString("controlledBy", "policy"); + } else if (controlling_pref->IsExtensionControlled()) { + dict->SetString("controlledBy", "extension"); + } else if (controlling_pref->IsRecommended()) { + dict->SetString("controlledBy", "recommended"); + } + dict->SetBoolean("disabled", !controlling_pref->IsUserModifiable()); + return dict; +} + +void CoreOptionsHandler::StopObservingPref(const std::string& path) { + registrar_.Remove(path.c_str(), this); +} + +void CoreOptionsHandler::HandleFetchPrefs(const ListValue* args) { + // First param is name of callback function, so, there needs to be at least + // one more element for the actual preference identifier. + DCHECK_GE(static_cast<int>(args->GetSize()), 2); + + // Get callback JS function name. + base::Value* callback; + if (!args->Get(0, &callback) || !callback->IsType(base::Value::TYPE_STRING)) + return; + + string16 callback_function; + if (!callback->GetAsString(&callback_function)) + return; + + // Get the list of name for prefs to build the response dictionary. + DictionaryValue result_value; + base::Value* list_member; + + for (size_t i = 1; i < args->GetSize(); i++) { + if (!args->Get(i, &list_member)) + break; + + if (!list_member->IsType(base::Value::TYPE_STRING)) + continue; + + std::string pref_name; + if (!list_member->GetAsString(&pref_name)) + continue; + + result_value.Set(pref_name.c_str(), FetchPref(pref_name)); + } + web_ui_->CallJavascriptFunction(UTF16ToASCII(callback_function), + result_value); +} + +void CoreOptionsHandler::HandleObservePrefs(const ListValue* args) { + // First param is name is JS callback function name, the rest are pref + // identifiers that we are observing. + DCHECK_GE(static_cast<int>(args->GetSize()), 2); + + // Get preference change callback function name. + string16 callback_func_name; + if (!args->GetString(0, &callback_func_name)) + return; + + // Get all other parameters - pref identifiers. + for (size_t i = 1; i < args->GetSize(); i++) { + base::Value* list_member; + if (!args->Get(i, &list_member)) + break; + + // Just ignore bad pref identifiers for now. + std::string pref_name; + if (!list_member->IsType(base::Value::TYPE_STRING) || + !list_member->GetAsString(&pref_name)) + continue; + + if (pref_callback_map_.find(pref_name) == pref_callback_map_.end()) + ObservePref(pref_name); + + pref_callback_map_.insert( + PreferenceCallbackMap::value_type(pref_name, + UTF16ToWideHack(callback_func_name))); + } +} + +void CoreOptionsHandler::HandleSetBooleanPref(const ListValue* args) { + HandleSetPref(args, TYPE_BOOLEAN); +} + +void CoreOptionsHandler::HandleSetIntegerPref(const ListValue* args) { + HandleSetPref(args, TYPE_INTEGER); +} + +void CoreOptionsHandler::HandleSetDoublePref(const ListValue* args) { + HandleSetPref(args, TYPE_DOUBLE); +} + +void CoreOptionsHandler::HandleSetStringPref(const ListValue* args) { + HandleSetPref(args, TYPE_STRING); +} + +void CoreOptionsHandler::HandleSetURLPref(const ListValue* args) { + HandleSetPref(args, TYPE_URL); +} + +void CoreOptionsHandler::HandleSetListPref(const ListValue* args) { + HandleSetPref(args, TYPE_LIST); +} + +void CoreOptionsHandler::HandleSetPref(const ListValue* args, PrefType type) { + DCHECK_GT(static_cast<int>(args->GetSize()), 1); + + std::string pref_name; + if (!args->GetString(0, &pref_name)) + return; + + base::Value* value; + if (!args->Get(1, &value)) + return; + + scoped_ptr<base::Value> temp_value; + + switch (type) { + case TYPE_BOOLEAN: + CHECK_EQ(base::Value::TYPE_BOOLEAN, value->GetType()); + break; + case TYPE_INTEGER: { + // In JS all numbers are doubles. + double double_value; + CHECK(value->GetAsDouble(&double_value)); + int int_value = static_cast<int>(double_value); + temp_value.reset(base::Value::CreateIntegerValue(int_value)); + value = temp_value.get(); + break; + } + case TYPE_DOUBLE: + CHECK_EQ(base::Value::TYPE_DOUBLE, value->GetType()); + break; + case TYPE_STRING: + CHECK_EQ(base::Value::TYPE_STRING, value->GetType()); + break; + case TYPE_URL: { + std::string original; + CHECK(value->GetAsString(&original)); + GURL fixed = URLFixerUpper::FixupURL(original, std::string()); + temp_value.reset(base::Value::CreateStringValue(fixed.spec())); + value = temp_value.get(); + break; + } + case TYPE_LIST: { + // In case we have a List pref we got a JSON string. + std::string json_string; + CHECK(value->GetAsString(&json_string)); + temp_value.reset( + base::JSONReader().JsonToValue(json_string, + false, // no check_root + false)); // no trailing comma + value = temp_value.get(); + CHECK_EQ(base::Value::TYPE_LIST, value->GetType()); + break; + } + default: + NOTREACHED(); + } + + std::string metric; + if (args->GetSize() > 2 && !args->GetString(2, &metric)) + LOG(WARNING) << "Invalid metric parameter: " << pref_name; + SetPref(pref_name, value, metric); +} + +void CoreOptionsHandler::HandleClearPref(const ListValue* args) { + DCHECK_GT(static_cast<int>(args->GetSize()), 0); + + std::string pref_name; + if (!args->GetString(0, &pref_name)) + return; + + std::string metric; + if (args->GetSize() > 1) + args->GetString(1, &metric); + + ClearPref(pref_name, metric); +} + +void CoreOptionsHandler::HandleUserMetricsAction(const ListValue* args) { + std::string metric = UTF16ToUTF8(ExtractStringValue(args)); + if (!metric.empty()) + content::RecordComputedAction(metric); +} + +void CoreOptionsHandler::UpdateClearPluginLSOData() { + scoped_ptr<base::Value> enabled( + base::Value::CreateBooleanValue( + clear_plugin_lso_data_enabled_.GetValue())); + web_ui_->CallJavascriptFunction( + "OptionsPage.setClearPluginLSODataEnabled", *enabled); +} diff --git a/chrome/browser/ui/webui/options2/core_options_handler.h b/chrome/browser/ui/webui/options2/core_options_handler.h new file mode 100644 index 0000000..e1adb21 --- /dev/null +++ b/chrome/browser/ui/webui/options2/core_options_handler.h @@ -0,0 +1,151 @@ +// Copyright (c) 2011 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. + +#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS2_CORE_OPTIONS_HANDLER_H_ +#define CHROME_BROWSER_UI_WEBUI_OPTIONS2_CORE_OPTIONS_HANDLER_H_ +#pragma once + +#include <map> +#include <string> + +#include "base/values.h" +#include "chrome/browser/plugin_data_remover_helper.h" +#include "chrome/browser/prefs/pref_change_registrar.h" +#include "chrome/browser/prefs/pref_service.h" +#include "chrome/browser/ui/webui/options2/options_ui2.h" + +// Core options UI handler. +// Handles resource and JS calls common to all options sub-pages. +class CoreOptionsHandler : public OptionsPage2UIHandler { + public: + CoreOptionsHandler(); + virtual ~CoreOptionsHandler(); + + // OptionsPage2UIHandler implementation. + virtual void Initialize() OVERRIDE; + virtual void GetLocalizedValues(DictionaryValue* localized_strings) OVERRIDE; + virtual void Uninitialize() OVERRIDE; + + // content::NotificationObserver implementation. + virtual void Observe(int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) OVERRIDE; + + // WebUIMessageHandler implementation. + virtual void RegisterMessages() OVERRIDE; + virtual WebUIMessageHandler* Attach(WebUI* web_ui) OVERRIDE; + + void set_handlers_host(OptionsPage2UIHandlerHost* handlers_host) { + handlers_host_ = handlers_host; + } + + // Adds localized strings to |localized_strings|. + static void GetStaticLocalizedValues( + base::DictionaryValue* localized_strings); + + protected: + // Fetches a pref value of given |pref_name|. + // Note that caller owns the returned Value. + virtual base::Value* FetchPref(const std::string& pref_name); + + // Observes a pref of given |pref_name|. + virtual void ObservePref(const std::string& pref_name); + + // Sets a pref |value| to given |pref_name|. + virtual void SetPref(const std::string& pref_name, + const base::Value* value, + const std::string& metric); + + // Clears pref value for given |pref_name|. + void ClearPref(const std::string& pref_name, const std::string& metric); + + // Stops observing given preference identified by |path|. + virtual void StopObservingPref(const std::string& path); + + // Records a user metric action for the given value. + void ProcessUserMetric(const base::Value* value, + const std::string& metric); + + // Notifies registered JS callbacks on change in |pref_name| preference. + // |controlling_pref_name| controls if |pref_name| is managed by + // policy/extension; empty |controlling_pref_name| indicates no other pref is + // controlling |pref_name|. + void NotifyPrefChanged(const std::string& pref_name, + const std::string& controlling_pref_name); + + // Creates dictionary value for |pref|, |controlling_pref| controls if |pref| + // is managed by policy/extension; NULL indicates no other pref is controlling + // |pref|. + DictionaryValue* CreateValueForPref( + const PrefService::Preference* pref, + const PrefService::Preference* controlling_pref); + + typedef std::multimap<std::string, std::wstring> PreferenceCallbackMap; + PreferenceCallbackMap pref_callback_map_; + + private: + // Type of preference value received from the page. This doesn't map 1:1 to + // Value::Type, since a TYPE_STRING can require custom processing. + enum PrefType { + TYPE_BOOLEAN = 0, + TYPE_INTEGER, + TYPE_DOUBLE, + TYPE_STRING, + TYPE_URL, + TYPE_LIST, + }; + + // Callback for the "coreOptionsInitialize" message. This message will + // trigger the Initialize() method of all other handlers so that final + // setup can be performed before the page is shown. + void HandleInitialize(const ListValue* args); + + // Callback for the "fetchPrefs" message. This message accepts the list of + // preference names passed as the |args| parameter (ListValue). It passes + // results dictionary of preference values by calling prefsFetched() JS method + // on the page. + void HandleFetchPrefs(const ListValue* args); + + // Callback for the "observePrefs" message. This message initiates + // notification observing for given array of preference names. + void HandleObservePrefs(const ListValue* args); + + // Callbacks for the "set<type>Pref" message. This message saves the new + // preference value. |args| is an array of parameters as follows: + // item 0 - name of the preference. + // item 1 - the value of the preference in string form. + // item 2 - name of the metric identifier (optional). + void HandleSetBooleanPref(const ListValue* args); + void HandleSetIntegerPref(const ListValue* args); + void HandleSetDoublePref(const ListValue* args); + void HandleSetStringPref(const ListValue* args); + void HandleSetURLPref(const ListValue* args); + void HandleSetListPref(const ListValue* args); + + void HandleSetPref(const ListValue* args, PrefType type); + + // Callback for the "clearPref" message. This message clears a preference + // value. |args| is an array of parameters as follows: + // item 0 - name of the preference. + // item 1 - name of the metric identifier (optional). + void HandleClearPref(const ListValue* args); + + // Callback for the "coreOptionsUserMetricsAction" message. This records + // an action that should be tracked if metrics recording is enabled. |args| + // is an array that contains a single item, the name of the metric identifier. + void HandleUserMetricsAction(const ListValue* args); + + void UpdateClearPluginLSOData(); + + OptionsPage2UIHandlerHost* handlers_host_; + PrefChangeRegistrar registrar_; + + // Used for asynchronously updating the preference stating whether clearing + // LSO data is supported. + PluginDataRemoverHelper clear_plugin_lso_data_enabled_; + + DISALLOW_COPY_AND_ASSIGN(CoreOptionsHandler); +}; + +#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS2_CORE_OPTIONS_HANDLER_H_ diff --git a/chrome/browser/ui/webui/options2/extension_settings_handler.cc b/chrome/browser/ui/webui/options2/extension_settings_handler.cc new file mode 100644 index 0000000..fddf7f1 --- /dev/null +++ b/chrome/browser/ui/webui/options2/extension_settings_handler.cc @@ -0,0 +1,785 @@ +// Copyright (c) 2011 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. + +#include "chrome/browser/ui/webui/options2/extension_settings_handler.h" + +#include "base/auto_reset.h" +#include "base/base64.h" +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/command_line.h" +#include "base/file_util.h" +#include "base/string_number_conversions.h" +#include "base/utf_string_conversions.h" +#include "base/values.h" +#include "base/version.h" +#include "chrome/browser/debugger/devtools_window.h" +#include "chrome/browser/extensions/crx_installer.h" +#include "chrome/browser/extensions/extension_disabled_infobar_delegate.h" +#include "chrome/browser/extensions/extension_service.h" +#include "chrome/browser/extensions/extension_updater.h" +#include "chrome/browser/extensions/extension_warning_set.h" +#include "chrome/browser/extensions/unpacked_installer.h" +#include "chrome/browser/google/google_util.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/tab_contents/background_contents.h" +#include "chrome/browser/ui/webui/extensions/extension_icon_source.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/common/chrome_view_type.h" +#include "chrome/common/extensions/extension.h" +#include "chrome/common/pref_names.h" +#include "chrome/common/url_constants.h" +#include "content/browser/browsing_instance.h" +#include "content/browser/renderer_host/render_view_host.h" +#include "content/browser/tab_contents/tab_contents.h" +#include "content/browser/tab_contents/tab_contents_view.h" +#include "content/public/browser/notification_service.h" +#include "content/public/browser/notification_types.h" +#include "grit/browser_resources.h" +#include "grit/chromium_strings.h" +#include "grit/generated_resources.h" +#include "grit/theme_resources.h" +#include "ui/base/l10n/l10n_util.h" +#include "ui/base/resource/resource_bundle.h" + +namespace { + +bool ShouldShowExtension(const Extension* extension) { + // Don't show themes since this page's UI isn't really useful for themes. + if (extension->is_theme()) + return false; + + // Don't show component extensions because they are only extensions as an + // implementation detail of Chrome. + if (extension->location() == Extension::COMPONENT && + !CommandLine::ForCurrentProcess()->HasSwitch( + switches::kShowComponentExtensionOptions)) + return false; + + // Always show unpacked extensions and apps. + if (extension->location() == Extension::LOAD) + return true; + + // Unless they are unpacked, never show hosted apps. + if (extension->is_hosted_app()) + return false; + + return true; +} + +} // namespace + +/////////////////////////////////////////////////////////////////////////////// +// +// ExtensionSettingsHandler +// +/////////////////////////////////////////////////////////////////////////////// + +ExtensionSettingsHandler::ExtensionSettingsHandler() + : extension_service_(NULL), + ignore_notifications_(false), + deleting_rvh_(NULL), + registered_for_notifications_(false) { +} + +ExtensionSettingsHandler::~ExtensionSettingsHandler() { + // There may be pending file dialogs, we need to tell them that we've gone + // away so they don't try and call back to us. + if (load_extension_dialog_.get()) + load_extension_dialog_->ListenerDestroyed(); + + registrar_.RemoveAll(); +} + +// static +void ExtensionSettingsHandler::RegisterUserPrefs(PrefService* prefs) { + prefs->RegisterBooleanPref(prefs::kExtensionsUIDeveloperMode, + false, + PrefService::SYNCABLE_PREF); +} + +void ExtensionSettingsHandler::RegisterMessages() { + web_ui_->RegisterMessageCallback("extensionSettingsRequestExtensionsData", + base::Bind(&ExtensionSettingsHandler::HandleRequestExtensionsData, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("extensionSettingsToggleDeveloperMode", + base::Bind(&ExtensionSettingsHandler::HandleToggleDeveloperMode, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("extensionSettingsInspect", + base::Bind(&ExtensionSettingsHandler::HandleInspectMessage, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("extensionSettingsReload", + base::Bind(&ExtensionSettingsHandler::HandleReloadMessage, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("extensionSettingsEnable", + base::Bind(&ExtensionSettingsHandler::HandleEnableMessage, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("extensionSettingsEnableIncognito", + base::Bind(&ExtensionSettingsHandler::HandleEnableIncognitoMessage, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("extensionSettingsAllowFileAccess", + base::Bind(&ExtensionSettingsHandler::HandleAllowFileAccessMessage, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("extensionSettingsUninstall", + base::Bind(&ExtensionSettingsHandler::HandleUninstallMessage, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("extensionSettingsOptions", + base::Bind(&ExtensionSettingsHandler::HandleOptionsMessage, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("extensionSettingsShowButton", + base::Bind(&ExtensionSettingsHandler::HandleShowButtonMessage, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("extensionSettingsLoad", + base::Bind(&ExtensionSettingsHandler::HandleLoadMessage, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("extensionSettingsAutoupdate", + base::Bind(&ExtensionSettingsHandler::HandleAutoUpdateMessage, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("extensionSettingsSelectFilePath", + base::Bind(&ExtensionSettingsHandler::HandleSelectFilePathMessage, + base::Unretained(this))); +} + +void ExtensionSettingsHandler::HandleRequestExtensionsData( + const ListValue* args) { + DictionaryValue results; + + // Add the extensions to the results structure. + ListValue *extensions_list = new ListValue(); + + ExtensionWarningSet* warnings = extension_service_->extension_warnings(); + + const ExtensionSet* extensions = extension_service_->extensions(); + for (ExtensionSet::const_iterator extension = extensions->begin(); + extension != extensions->end(); ++extension) { + if (ShouldShowExtension(*extension)) { + extensions_list->Append(CreateExtensionDetailValue( + extension_service_, + *extension, + GetActivePagesForExtension(*extension), + warnings, + true, false)); // enabled, terminated + } + } + extensions = extension_service_->disabled_extensions(); + for (ExtensionSet::const_iterator extension = extensions->begin(); + extension != extensions->end(); ++extension) { + if (ShouldShowExtension(*extension)) { + extensions_list->Append(CreateExtensionDetailValue( + extension_service_, + *extension, + GetActivePagesForExtension(*extension), + warnings, + false, false)); // enabled, terminated + } + } + extensions = extension_service_->terminated_extensions(); + std::vector<ExtensionPage> empty_pages; + for (ExtensionSet::const_iterator extension = extensions->begin(); + extension != extensions->end(); ++extension) { + if (ShouldShowExtension(*extension)) { + extensions_list->Append(CreateExtensionDetailValue( + extension_service_, + *extension, + empty_pages, // Terminated process has no active pages. + warnings, + false, true)); // enabled, terminated + } + } + results.Set("extensions", extensions_list); + + Profile* profile = Profile::FromWebUI(web_ui_); + bool developer_mode = + profile->GetPrefs()->GetBoolean(prefs::kExtensionsUIDeveloperMode); + results.SetBoolean("developerMode", developer_mode); + + web_ui_->CallJavascriptFunction("ExtensionSettings.returnExtensionsData", + results); + + MaybeRegisterForNotifications(); +} + +void ExtensionSettingsHandler::MaybeRegisterForNotifications() { + if (registered_for_notifications_) + return; + + registered_for_notifications_ = true; + Profile* profile = Profile::FromWebUI(web_ui_); + + // Register for notifications that we need to reload the page. + registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_LOADED, + content::Source<Profile>(profile)); + registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED, + content::Source<Profile>(profile)); + registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UPDATE_DISABLED, + content::Source<Profile>(profile)); + registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_WARNING_CHANGED, + content::Source<Profile>(profile)); + registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_HOST_CREATED, + content::NotificationService::AllBrowserContextsAndSources()); + registrar_.Add(this, + content::NOTIFICATION_RENDER_VIEW_HOST_CREATED, + content::NotificationService::AllBrowserContextsAndSources()); + registrar_.Add(this, + content::NOTIFICATION_RENDER_VIEW_HOST_DELETED, + content::NotificationService::AllBrowserContextsAndSources()); + registrar_.Add(this, + chrome::NOTIFICATION_BACKGROUND_CONTENTS_NAVIGATED, + content::NotificationService::AllBrowserContextsAndSources()); + registrar_.Add(this, + chrome::NOTIFICATION_BACKGROUND_CONTENTS_DELETED, + content::NotificationService::AllBrowserContextsAndSources()); + registrar_.Add( + this, + chrome::NOTIFICATION_EXTENSION_BROWSER_ACTION_VISIBILITY_CHANGED, + content::Source<ExtensionPrefs>(profile->GetExtensionService()-> + extension_prefs())); +} + +ExtensionUninstallDialog* +ExtensionSettingsHandler::GetExtensionUninstallDialog() { + if (!extension_uninstall_dialog_.get()) { + extension_uninstall_dialog_.reset( + ExtensionUninstallDialog::Create(Profile::FromWebUI(web_ui_), this)); + } + return extension_uninstall_dialog_.get(); +} + +void ExtensionSettingsHandler::HandleToggleDeveloperMode( + const ListValue* args) { + Profile* profile = Profile::FromWebUI(web_ui_); + bool developer_mode = + profile->GetPrefs()->GetBoolean(prefs::kExtensionsUIDeveloperMode); + profile->GetPrefs()->SetBoolean( + prefs::kExtensionsUIDeveloperMode, !developer_mode); + HandleRequestExtensionsData(NULL); +} + +void ExtensionSettingsHandler::HandleInspectMessage(const ListValue* args) { + std::string render_process_id_str; + std::string render_view_id_str; + int render_process_id; + int render_view_id; + CHECK_EQ(2U, args->GetSize()); + CHECK(args->GetString(0, &render_process_id_str)); + CHECK(args->GetString(1, &render_view_id_str)); + CHECK(base::StringToInt(render_process_id_str, &render_process_id)); + CHECK(base::StringToInt(render_view_id_str, &render_view_id)); + RenderViewHost* host = RenderViewHost::FromID(render_process_id, + render_view_id); + if (!host) { + // This can happen if the host has gone away since the page was displayed. + return; + } + + DevToolsWindow::OpenDevToolsWindow(host); +} + +void ExtensionSettingsHandler::HandleReloadMessage(const ListValue* args) { + std::string extension_id = UTF16ToUTF8(ExtractStringValue(args)); + CHECK(!extension_id.empty()); + extension_service_->ReloadExtension(extension_id); +} + +void ExtensionSettingsHandler::HandleEnableMessage(const ListValue* args) { + CHECK_EQ(2U, args->GetSize()); + std::string extension_id, enable_str; + CHECK(args->GetString(0, &extension_id)); + CHECK(args->GetString(1, &enable_str)); + + const Extension* extension = + extension_service_->GetExtensionById(extension_id, true); + if (!Extension::UserMayDisable(extension->location())) { + LOG(ERROR) << "Attempt to enable an extension that is non-usermanagable was" + << "made. Extension id: " << extension->id(); + return; + } + + if (enable_str == "true") { + ExtensionPrefs* prefs = extension_service_->extension_prefs(); + if (prefs->DidExtensionEscalatePermissions(extension_id)) { + ShowExtensionDisabledDialog(extension_service_, + Profile::FromWebUI(web_ui_), extension); + } else { + extension_service_->EnableExtension(extension_id); + } + } else { + extension_service_->DisableExtension(extension_id); + } +} + +void ExtensionSettingsHandler::HandleEnableIncognitoMessage( + const ListValue* args) { + CHECK_EQ(2U, args->GetSize()); + std::string extension_id, enable_str; + CHECK(args->GetString(0, &extension_id)); + CHECK(args->GetString(1, &enable_str)); + const Extension* extension = + extension_service_->GetExtensionById(extension_id, true); + DCHECK(extension); + + // Flipping the incognito bit will generate unload/load notifications for the + // extension, but we don't want to reload the page, because a) we've already + // updated the UI to reflect the change, and b) we want the yellow warning + // text to stay until the user has left the page. + // + // TODO(aa): This creates crapiness in some cases. For example, in a main + // window, when toggling this, the browser action will flicker because it gets + // unloaded, then reloaded. It would be better to have a dedicated + // notification for this case. + // + // Bug: http://crbug.com/41384 + AutoReset<bool> auto_reset_ignore_notifications(&ignore_notifications_, true); + extension_service_->SetIsIncognitoEnabled(extension->id(), + enable_str == "true"); +} + +void ExtensionSettingsHandler::HandleAllowFileAccessMessage( + const ListValue* args) { + CHECK_EQ(2U, args->GetSize()); + std::string extension_id, allow_str; + CHECK(args->GetString(0, &extension_id)); + CHECK(args->GetString(1, &allow_str)); + const Extension* extension = + extension_service_->GetExtensionById(extension_id, true); + DCHECK(extension); + + if (!Extension::UserMayDisable(extension->location())) { + LOG(ERROR) << "Attempt to change allow file access of an extension that is " + << "non-usermanagable was made. Extension id : " + << extension->id(); + return; + } + + extension_service_->SetAllowFileAccess(extension, allow_str == "true"); +} + +void ExtensionSettingsHandler::HandleUninstallMessage(const ListValue* args) { + std::string extension_id = UTF16ToUTF8(ExtractStringValue(args)); + CHECK(!extension_id.empty()); + const Extension* extension = + extension_service_->GetExtensionById(extension_id, true); + if (!extension) + extension = extension_service_->GetTerminatedExtension(extension_id); + if (!extension) + return; + + if (!Extension::UserMayDisable(extension->location())) { + LOG(ERROR) << "Attempt to uninstall an extension that is non-usermanagable " + << "was made. Extension id : " << extension->id(); + return; + } + + if (!extension_id_prompting_.empty()) + return; // Only one prompt at a time. + + extension_id_prompting_ = extension_id; + + GetExtensionUninstallDialog()->ConfirmUninstall(extension); +} + +void ExtensionSettingsHandler::ExtensionUninstallAccepted() { + DCHECK(!extension_id_prompting_.empty()); + + bool was_terminated = false; + + // The extension can be uninstalled in another window while the UI was + // showing. Do nothing in that case. + const Extension* extension = + extension_service_->GetExtensionById(extension_id_prompting_, true); + if (!extension) { + extension = extension_service_->GetTerminatedExtension( + extension_id_prompting_); + was_terminated = true; + } + if (!extension) + return; + + extension_service_->UninstallExtension(extension_id_prompting_, + false, // External uninstall. + NULL); // Error. + extension_id_prompting_ = ""; + + // There will be no EXTENSION_UNLOADED notification for terminated + // extensions as they were already unloaded. + if (was_terminated) + HandleRequestExtensionsData(NULL); +} + +void ExtensionSettingsHandler::ExtensionUninstallCanceled() { + extension_id_prompting_ = ""; +} + +void ExtensionSettingsHandler::HandleOptionsMessage(const ListValue* args) { + const Extension* extension = GetExtension(args); + if (!extension || extension->options_url().is_empty()) + return; + Profile::FromWebUI(web_ui_)->GetExtensionProcessManager()->OpenOptionsPage( + extension, NULL); +} + +void ExtensionSettingsHandler::HandleShowButtonMessage(const ListValue* args) { + const Extension* extension = GetExtension(args); + extension_service_->SetBrowserActionVisibility(extension, true); +} + +void ExtensionSettingsHandler::HandleLoadMessage(const ListValue* args) { + FilePath::StringType string_path; + CHECK_EQ(1U, args->GetSize()) << args->GetSize(); + CHECK(args->GetString(0, &string_path)); + extensions::UnpackedInstaller::Create(extension_service_)-> + Load(FilePath(string_path)); +} + +void ExtensionSettingsHandler::ShowAlert(const std::string& message) { + ListValue arguments; + arguments.Append(Value::CreateStringValue(message)); + web_ui_->CallJavascriptFunction("alert", arguments); +} + +void ExtensionSettingsHandler::HandleAutoUpdateMessage(const ListValue* args) { + ExtensionUpdater* updater = extension_service_->updater(); + if (updater) + updater->CheckNow(); +} + +void ExtensionSettingsHandler::HandleSelectFilePathMessage( + const ListValue* args) { + std::string select_type; + std::string operation; + CHECK_EQ(2U, args->GetSize()); + CHECK(args->GetString(0, &select_type)); + CHECK(args->GetString(1, &operation)); + + SelectFileDialog::Type type = SelectFileDialog::SELECT_FOLDER; + SelectFileDialog::FileTypeInfo info; + int file_type_index = 0; + if (select_type == "file") + type = SelectFileDialog::SELECT_OPEN_FILE; + + string16 select_title; + if (operation == "load") { + select_title = l10n_util::GetStringUTF16(IDS_EXTENSION_LOAD_FROM_DIRECTORY); + } else if (operation == "packRoot") { + select_title = l10n_util::GetStringUTF16( + IDS_EXTENSION_PACK_DIALOG_SELECT_ROOT); + } else if (operation == "pem") { + select_title = l10n_util::GetStringUTF16( + IDS_EXTENSION_PACK_DIALOG_SELECT_KEY); + info.extensions.push_back(std::vector<FilePath::StringType>()); + info.extensions.front().push_back(FILE_PATH_LITERAL("pem")); + info.extension_description_overrides.push_back( + l10n_util::GetStringUTF16( + IDS_EXTENSION_PACK_DIALOG_KEY_FILE_TYPE_DESCRIPTION)); + info.include_all_files = true; + file_type_index = 1; + } else { + NOTREACHED(); + return; + } + + load_extension_dialog_ = SelectFileDialog::Create(this); + load_extension_dialog_->SelectFile(type, select_title, FilePath(), &info, + file_type_index, FILE_PATH_LITERAL(""), web_ui_->tab_contents(), + web_ui_->tab_contents()->view()->GetTopLevelNativeWindow(), NULL); +} + + +void ExtensionSettingsHandler::FileSelected(const FilePath& path, int index, + void* params) { + // Add the extensions to the results structure. + ListValue results; + results.Append(Value::CreateStringValue(path.value())); + web_ui_->CallJavascriptFunction("window.handleFilePathSelected", results); +} + +void ExtensionSettingsHandler::MultiFilesSelected( + const std::vector<FilePath>& files, void* params) { + NOTREACHED(); +} + +void ExtensionSettingsHandler::GetLocalizedValues( + DictionaryValue* localized_strings) { + DCHECK(localized_strings); + + RegisterTitle(localized_strings, "extensionSettings", + IDS_MANAGE_EXTENSIONS_SETTING_WINDOWS_TITLE); + + localized_strings->SetString("extensionSettingsVisitWebsite", + l10n_util::GetStringUTF16(IDS_EXTENSIONS_VISIT_WEBSITE)); + + localized_strings->SetString("extensionSettingsDeveloperMode", + l10n_util::GetStringUTF16(IDS_EXTENSIONS_DEVELOPER_MODE_LINK)); + localized_strings->SetString("extensionSettingsNoExtensions", + l10n_util::GetStringUTF16(IDS_EXTENSIONS_NONE_INSTALLED)); + localized_strings->SetString("extensionSettingsSuggestGallery", + l10n_util::GetStringFUTF16(IDS_EXTENSIONS_NONE_INSTALLED_SUGGEST_GALLERY, + ASCIIToUTF16("<a href='") + + ASCIIToUTF16(google_util::AppendGoogleLocaleParam( + GURL(extension_urls::GetWebstoreLaunchURL())).spec()) + + ASCIIToUTF16("'>"), + ASCIIToUTF16("</a>"))); + localized_strings->SetString("extensionSettingsGetMoreExtensions", + ASCIIToUTF16("<a href='") + + ASCIIToUTF16(google_util::AppendGoogleLocaleParam( + GURL(extension_urls::GetWebstoreLaunchURL())).spec()) + + ASCIIToUTF16("'>") + + l10n_util::GetStringUTF16(IDS_GET_MORE_EXTENSIONS) + + ASCIIToUTF16("</a>")); + localized_strings->SetString("extensionSettingsExtensionId", + l10n_util::GetStringUTF16(IDS_EXTENSIONS_ID)); + localized_strings->SetString("extensionSettingsExtensionPath", + l10n_util::GetStringUTF16(IDS_EXTENSIONS_PATH)); + localized_strings->SetString("extensionSettingsInspectViews", + l10n_util::GetStringUTF16(IDS_EXTENSIONS_INSPECT_VIEWS)); + localized_strings->SetString("viewIncognito", + l10n_util::GetStringUTF16(IDS_EXTENSIONS_VIEW_INCOGNITO)); + localized_strings->SetString("extensionSettingsEnable", + l10n_util::GetStringUTF16(IDS_EXTENSIONS_ENABLE)); + localized_strings->SetString("extensionSettingsEnabled", + l10n_util::GetStringUTF16(IDS_EXTENSIONS_ENABLED)); + localized_strings->SetString("extensionSettingsRemove", + l10n_util::GetStringUTF16(IDS_EXTENSIONS_REMOVE)); + localized_strings->SetString("extensionSettingsEnableIncognito", + l10n_util::GetStringUTF16(IDS_EXTENSIONS_ENABLE_INCOGNITO)); + localized_strings->SetString("extensionSettingsAllowFileAccess", + l10n_util::GetStringUTF16(IDS_EXTENSIONS_ALLOW_FILE_ACCESS)); + localized_strings->SetString("extensionSettingsIncognitoWarning", + l10n_util::GetStringFUTF16(IDS_EXTENSIONS_INCOGNITO_WARNING, + l10n_util::GetStringUTF16(IDS_PRODUCT_NAME))); + localized_strings->SetString("extensionSettingsReload", + l10n_util::GetStringUTF16(IDS_EXTENSIONS_RELOAD)); + localized_strings->SetString("extensionSettingsOptions", + l10n_util::GetStringUTF16(IDS_EXTENSIONS_OPTIONS)); + localized_strings->SetString("extensionSettingsPolicyControlled", + l10n_util::GetStringUTF16(IDS_EXTENSIONS_POLICY_CONTROLLED)); + localized_strings->SetString("extensionSettingsShowButton", + l10n_util::GetStringUTF16(IDS_EXTENSIONS_SHOW_BUTTON)); + localized_strings->SetString("extensionSettingsLoadUnpackedButton", + l10n_util::GetStringUTF16(IDS_EXTENSIONS_LOAD_UNPACKED_BUTTON)); + localized_strings->SetString("extensionSettingsPackButton", + l10n_util::GetStringUTF16(IDS_EXTENSIONS_PACK_BUTTON)); + localized_strings->SetString("extensionSettingsUpdateButton", + l10n_util::GetStringUTF16(IDS_EXTENSIONS_UPDATE_BUTTON)); + localized_strings->SetString("extensionSettingsCrashMessage", + l10n_util::GetStringUTF16(IDS_EXTENSIONS_CRASHED_EXTENSION)); + localized_strings->SetString("extensionSettingsInDevelopment", + l10n_util::GetStringUTF16(IDS_EXTENSIONS_IN_DEVELOPMENT)); + localized_strings->SetString("extensionSettingsWarningsTitle", + l10n_util::GetStringUTF16(IDS_EXTENSION_WARNINGS_TITLE)); + localized_strings->SetString("extensionSettingsShowDetails", + l10n_util::GetStringUTF16(IDS_EXTENSIONS_SHOW_DETAILS)); + localized_strings->SetString("extensionSettingsHideDetails", + l10n_util::GetStringUTF16(IDS_EXTENSIONS_HIDE_DETAILS)); +} + +void ExtensionSettingsHandler::Initialize() { +} + +WebUIMessageHandler* ExtensionSettingsHandler::Attach(WebUI* web_ui) { + // Call through to superclass. + WebUIMessageHandler* handler = OptionsPage2UIHandler::Attach(web_ui); + + extension_service_ = Profile::FromWebUI(web_ui_) + ->GetOriginalProfile()->GetExtensionService(); + + // Return result from the superclass. + return handler; +} + +void ExtensionSettingsHandler::Observe( + int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) { + Profile* profile = Profile::FromWebUI(web_ui_); + Profile* source_profile = NULL; + switch (type) { + // We listen for notifications that will result in the page being + // repopulated with data twice for the same event in certain cases. + // For instance, EXTENSION_LOADED & EXTENSION_HOST_CREATED because + // we don't know about the views for an extension at EXTENSION_LOADED, but + // if we only listen to EXTENSION_HOST_CREATED, we'll miss extensions + // that don't have a process at startup. + // + // Doing it this way gets everything but causes the page to be rendered + // more than we need. It doesn't seem to result in any noticeable flicker. + case content::NOTIFICATION_RENDER_VIEW_HOST_DELETED: + deleting_rvh_ = content::Source<RenderViewHost>(source).ptr(); + // Fall through. + case content::NOTIFICATION_RENDER_VIEW_HOST_CREATED: + source_profile = Profile::FromBrowserContext( + content::Source<RenderViewHost>(source)->site_instance()-> + browsing_instance()->browser_context()); + if (!profile->IsSameProfile(source_profile)) + return; + MaybeUpdateAfterNotification(); + break; + case chrome::NOTIFICATION_BACKGROUND_CONTENTS_DELETED: + deleting_rvh_ = content::Details<BackgroundContents>(details)-> + tab_contents()->render_view_host(); + // Fall through. + case chrome::NOTIFICATION_BACKGROUND_CONTENTS_NAVIGATED: + case chrome::NOTIFICATION_EXTENSION_HOST_CREATED: + source_profile = content::Source<Profile>(source).ptr(); + if (!profile->IsSameProfile(source_profile)) + return; + MaybeUpdateAfterNotification(); + break; + case chrome::NOTIFICATION_EXTENSION_LOADED: + case chrome::NOTIFICATION_EXTENSION_UNLOADED: + case chrome::NOTIFICATION_EXTENSION_UPDATE_DISABLED: + case chrome::NOTIFICATION_EXTENSION_WARNING_CHANGED: + case chrome::NOTIFICATION_EXTENSION_BROWSER_ACTION_VISIBILITY_CHANGED: + MaybeUpdateAfterNotification(); + break; + default: + NOTREACHED(); + } +} + +const Extension* ExtensionSettingsHandler::GetExtension(const ListValue* args) { + std::string extension_id = UTF16ToUTF8(ExtractStringValue(args)); + CHECK(!extension_id.empty()); + return extension_service_->GetExtensionById(extension_id, true); +} + +void ExtensionSettingsHandler::MaybeUpdateAfterNotification() { + TabContents* contents = web_ui_->tab_contents(); + if (!ignore_notifications_ && contents && contents->render_view_host()) + HandleRequestExtensionsData(NULL); + deleting_rvh_ = NULL; +} + +// Static +DictionaryValue* ExtensionSettingsHandler::CreateExtensionDetailValue( + ExtensionService* service, const Extension* extension, + const std::vector<ExtensionPage>& pages, + const ExtensionWarningSet* warnings_set, + bool enabled, bool terminated) { + DictionaryValue* extension_data = new DictionaryValue(); + GURL icon = + ExtensionIconSource::GetIconURL(extension, + Extension::EXTENSION_ICON_MEDIUM, + ExtensionIconSet::MATCH_BIGGER, + !enabled, NULL); + extension_data->SetString("id", extension->id()); + extension_data->SetString("name", extension->name()); + extension_data->SetString("description", extension->description()); + if (extension->location() == Extension::LOAD) + extension_data->SetString("path", extension->path().value()); + extension_data->SetString("version", extension->version()->GetString()); + extension_data->SetString("icon", icon.spec()); + extension_data->SetBoolean("isUnpacked", + extension->location() == Extension::LOAD); + extension_data->SetBoolean("mayDisable", + Extension::UserMayDisable(extension->location())); + extension_data->SetBoolean("enabled", enabled); + extension_data->SetBoolean("terminated", terminated); + extension_data->SetBoolean("enabledIncognito", + service ? service->IsIncognitoEnabled(extension->id()) : false); + extension_data->SetBoolean("wantsFileAccess", extension->wants_file_access()); + extension_data->SetBoolean("allowFileAccess", + service ? service->AllowFileAccess(extension) : false); + extension_data->SetBoolean("allow_reload", + extension->location() == Extension::LOAD); + extension_data->SetBoolean("is_hosted_app", extension->is_hosted_app()); + + // Determine the sort order: Extensions loaded through --load-extensions show + // up at the top. Disabled extensions show up at the bottom. + if (extension->location() == Extension::LOAD) + extension_data->SetInteger("order", 1); + else + extension_data->SetInteger("order", 2); + + if (!extension->options_url().is_empty() && enabled) + extension_data->SetString("options_url", extension->options_url().spec()); + + if (service && !service->GetBrowserActionVisibility(extension)) + extension_data->SetBoolean("enable_show_button", true); + + // Add views + ListValue* views = new ListValue; + for (std::vector<ExtensionPage>::const_iterator iter = pages.begin(); + iter != pages.end(); ++iter) { + DictionaryValue* view_value = new DictionaryValue; + if (iter->url.scheme() == chrome::kExtensionScheme) { + // No leading slash. + view_value->SetString("path", iter->url.path().substr(1)); + } else { + // For live pages, use the full URL. + view_value->SetString("path", iter->url.spec()); + } + view_value->SetInteger("renderViewId", iter->render_view_id); + view_value->SetInteger("renderProcessId", iter->render_process_id); + view_value->SetBoolean("incognito", iter->incognito); + views->Append(view_value); + } + extension_data->Set("views", views); + extension_data->SetBoolean("hasPopupAction", + extension->browser_action() || extension->page_action()); + extension_data->SetString("homepageUrl", extension->GetHomepageURL().spec()); + + // Add warnings. + ListValue* warnings_list = new ListValue; + if (warnings_set) { + std::set<ExtensionWarningSet::WarningType> warnings; + warnings_set->GetWarningsAffectingExtension(extension->id(), &warnings); + + for (std::set<ExtensionWarningSet::WarningType>::const_iterator iter = + warnings.begin(); + iter != warnings.end(); + ++iter) { + string16 warning_string(ExtensionWarningSet::GetLocalizedWarning(*iter)); + warnings_list->Append(Value::CreateStringValue(warning_string)); + } + } + extension_data->Set("warnings", warnings_list); + + return extension_data; +} + +std::vector<ExtensionPage> ExtensionSettingsHandler::GetActivePagesForExtension( + const Extension* extension) { + std::vector<ExtensionPage> result; + + // Get the extension process's active views. + ExtensionProcessManager* process_manager = + extension_service_->profile()->GetExtensionProcessManager(); + GetActivePagesForExtensionProcess( + process_manager->GetRenderViewHostsForExtension( + extension->id()), &result); + + // Repeat for the incognito process, if applicable. + if (extension_service_->profile()->HasOffTheRecordProfile() && + extension->incognito_split_mode()) { + ExtensionProcessManager* process_manager = + extension_service_->profile()->GetOffTheRecordProfile()-> + GetExtensionProcessManager(); + GetActivePagesForExtensionProcess( + process_manager->GetRenderViewHostsForExtension( + extension->id()), &result); + } + + return result; +} + +void ExtensionSettingsHandler::GetActivePagesForExtensionProcess( + const std::set<RenderViewHost*>& views, + std::vector<ExtensionPage> *result) { + for (std::set<RenderViewHost*>::const_iterator iter = views.begin(); + iter != views.end(); ++iter) { + RenderViewHost* host = *iter; + int host_type = host->delegate()->GetRenderViewType(); + if (host == deleting_rvh_ || + chrome::VIEW_TYPE_EXTENSION_POPUP == host_type || + chrome::VIEW_TYPE_EXTENSION_DIALOG == host_type) + continue; + + GURL url = host->delegate()->GetURL(); + content::RenderProcessHost* process = host->process(); + result->push_back( + ExtensionPage(url, process->GetID(), host->routing_id(), + process->GetBrowserContext()->IsOffTheRecord())); + } +} diff --git a/chrome/browser/ui/webui/options2/extension_settings_handler.h b/chrome/browser/ui/webui/options2/extension_settings_handler.h new file mode 100644 index 0000000..9ac9713 --- /dev/null +++ b/chrome/browser/ui/webui/options2/extension_settings_handler.h @@ -0,0 +1,200 @@ +// Copyright (c) 2011 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. + +#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS2_EXTENSION_SETTINGS_HANDLER_H_ +#define CHROME_BROWSER_UI_WEBUI_OPTIONS2_EXTENSION_SETTINGS_HANDLER_H_ +#pragma once + +#include <set> +#include <string> +#include <vector> + +#include "chrome/browser/extensions/extension_install_ui.h" +#include "chrome/browser/extensions/extension_uninstall_dialog.h" +#include "chrome/browser/extensions/extension_warning_set.h" +#include "chrome/browser/ui/select_file_dialog.h" +#include "chrome/browser/ui/webui/options2/options_ui2.h" +#include "chrome/browser/ui/webui/chrome_web_ui.h" +#include "chrome/common/extensions/extension_resource.h" +#include "content/public/browser/notification_observer.h" +#include "content/public/browser/notification_registrar.h" +#include "googleurl/src/gurl.h" + +class Extension; +class ExtensionService; +class FilePath; +class PrefService; +class UserScript; + +namespace base { +class DictionaryValue; +class ListValue; +} + +// Information about a page running in an extension, for example a popup bubble, +// a background page, or a tab contents. +struct ExtensionPage { + ExtensionPage(const GURL& url, int render_process_id, int render_view_id, + bool incognito) + : url(url), + render_process_id(render_process_id), + render_view_id(render_view_id), + incognito(incognito) {} + GURL url; + int render_process_id; + int render_view_id; + bool incognito; +}; + +// Extension Settings UI handler. +class ExtensionSettingsHandler : public OptionsPage2UIHandler, + public SelectFileDialog::Listener, + public ExtensionUninstallDialog::Delegate { + public: + ExtensionSettingsHandler(); + virtual ~ExtensionSettingsHandler(); + + static void RegisterUserPrefs(PrefService* prefs); + + // Extension Detail JSON Struct for page. (static for ease of testing). + // Note: |service| and |warnings| can be NULL in unit tests. + static base::DictionaryValue* CreateExtensionDetailValue( + ExtensionService* service, + const Extension* extension, + const std::vector<ExtensionPage>& pages, + const ExtensionWarningSet* warnings, + bool enabled, + bool terminated); + + // ContentScript JSON Struct for page. (static for ease of testing). + static base::DictionaryValue* CreateContentScriptDetailValue( + const UserScript& script, + const FilePath& extension_path); + + // Callback for "requestExtensionsData" message. + void HandleRequestExtensionsData(const base::ListValue* args); + + // Callback for "toggleDeveloperMode" message. + void HandleToggleDeveloperMode(const base::ListValue* args); + + // Callback for "inspect" message. + void HandleInspectMessage(const base::ListValue* args); + + // Callback for "reload" message. + void HandleReloadMessage(const base::ListValue* args); + + // Callback for "enable" message. + void HandleEnableMessage(const base::ListValue* args); + + // Callback for "enableIncognito" message. + void HandleEnableIncognitoMessage(const base::ListValue* args); + + // Callback for "allowFileAcces" message. + void HandleAllowFileAccessMessage(const base::ListValue* args); + + // Callback for "uninstall" message. + void HandleUninstallMessage(const base::ListValue* args); + + // Callback for "options" message. + void HandleOptionsMessage(const base::ListValue* args); + + // Callback for "showButton" message. + void HandleShowButtonMessage(const base::ListValue* args); + + // Callback for "load" message. + void HandleLoadMessage(const base::ListValue* args); + + // Callback for "pack" message. + void HandlePackMessage(const base::ListValue* args); + + // Callback for "autoupdate" message. + void HandleAutoUpdateMessage(const base::ListValue* args); + + // Utility for calling javascript window.alert in the page. + void ShowAlert(const std::string& message); + + // Callback for "selectFilePath" message. + void HandleSelectFilePathMessage(const base::ListValue* args); + + // Utility for callbacks that get an extension ID as the sole argument. + const Extension* GetExtension(const base::ListValue* args); + + // Forces a UI update if appropriate after a notification is received. + void MaybeUpdateAfterNotification(); + + // Register for notifications that we need to reload the page. + void MaybeRegisterForNotifications(); + + // SelectFileDialog::Listener + virtual void FileSelected(const FilePath& path, + int index, void* params) OVERRIDE; + virtual void MultiFilesSelected( + const std::vector<FilePath>& files, void* params) OVERRIDE; + virtual void FileSelectionCanceled(void* params) OVERRIDE {} + + // WebUIMessageHandler implementation. + virtual void RegisterMessages() OVERRIDE; + virtual WebUIMessageHandler* Attach(WebUI* web_ui) OVERRIDE; + + // OptionsUIHandler implementation. + virtual void GetLocalizedValues( + base::DictionaryValue* localized_strings) OVERRIDE; + virtual void Initialize() OVERRIDE; + + // content::NotificationObserver implementation. + virtual void Observe(int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) OVERRIDE; + + // ExtensionUninstallDialog::Delegate implementation, used for receiving + // notification about uninstall confirmation dialog selections. + virtual void ExtensionUninstallAccepted() OVERRIDE; + virtual void ExtensionUninstallCanceled() OVERRIDE; + + private: + // Helper that lists the current active html pages for an extension. + std::vector<ExtensionPage> GetActivePagesForExtension( + const Extension* extension); + void GetActivePagesForExtensionProcess( + const std::set<RenderViewHost*>& views, + std::vector<ExtensionPage> *result); + + // Returns the ExtensionUninstallDialog object for this class, creating it if + // needed. + ExtensionUninstallDialog* GetExtensionUninstallDialog(); + + // Our model. Outlives us since it's owned by our containing profile. + ExtensionService* extension_service_; + + // Used to pick the directory when loading an extension. + scoped_refptr<SelectFileDialog> load_extension_dialog_; + + // Used to show confirmation UI for uninstalling extensions in incognito mode. + scoped_ptr<ExtensionUninstallDialog> extension_uninstall_dialog_; + + // The id of the extension we are prompting the user about. + std::string extension_id_prompting_; + + // If true, we will ignore notifications in ::Observe(). This is needed + // to prevent reloading the page when we were the cause of the + // notification. + bool ignore_notifications_; + + // The page may be refreshed in response to a RENDER_VIEW_HOST_DELETED, + // but the iteration over RenderViewHosts will include the host because the + // notification is sent when it is in the process of being deleted (and before + // it is removed from the process). Keep a pointer to it so we can exclude + // it from the active views. + RenderViewHost* deleting_rvh_; + + // We want to register for notifications only after we've responded at least + // once to the page, otherwise we'd be calling javacsript functions on objects + // that don't exist yet when notifications come in. This variable makes sure + // we do so only once. + bool registered_for_notifications_; + + DISALLOW_COPY_AND_ASSIGN(ExtensionSettingsHandler); +}; + +#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS2_EXTENSION_SETTINGS_HANDLER_H_ diff --git a/chrome/browser/ui/webui/options2/font_settings_browsertest.js b/chrome/browser/ui/webui/options2/font_settings_browsertest.js new file mode 100644 index 0000000..9800b41 --- /dev/null +++ b/chrome/browser/ui/webui/options2/font_settings_browsertest.js @@ -0,0 +1,24 @@ +// Copyright (c) 2011 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. + +/** + * TestFixture for font settings WebUI testing. + * @extends {testing.Test} + * @constructor + **/ +function FontSettingsWebUITest() {} + +FontSettingsWebUITest.prototype = { + __proto__: testing.Test.prototype, + + /** + * Browse to the font settings page. + **/ + browsePreload: 'chrome://settings/fonts', +}; + +// Test opening font settings has correct location. +TEST_F('FontSettingsWebUITest', 'testOpenFontSettings', function() { + assertEquals(this.browsePreload, document.location.href); +}); diff --git a/chrome/browser/ui/webui/options2/font_settings_handler.cc b/chrome/browser/ui/webui/options2/font_settings_handler.cc new file mode 100644 index 0000000..2ffef10b --- /dev/null +++ b/chrome/browser/ui/webui/options2/font_settings_handler.cc @@ -0,0 +1,211 @@ +// Copyright (c) 2011 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. + +#include "chrome/browser/ui/webui/options2/font_settings_handler.h" + +#include <string> + +#include "base/basictypes.h" +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/i18n/rtl.h" +#include "base/string_number_conversions.h" +#include "base/string_util.h" +#include "base/values.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/character_encoding.h" +#include "chrome/browser/prefs/pref_service.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/ui/webui/options2/font_settings_utils.h" +#include "chrome/common/chrome_notification_types.h" +#include "chrome/common/pref_names.h" +#include "content/public/browser/notification_details.h" +#include "grit/chromium_strings.h" +#include "grit/generated_resources.h" +#include "ui/base/l10n/l10n_util.h" + +FontSettingsHandler::FontSettingsHandler() { +} + +FontSettingsHandler::~FontSettingsHandler() { +} + +void FontSettingsHandler::GetLocalizedValues( + DictionaryValue* localized_strings) { + DCHECK(localized_strings); + + static OptionsStringResource resources[] = { + { "fontSettingsStandard", + IDS_FONT_LANGUAGE_SETTING_FONT_SELECTOR_STANDARD_LABEL }, + { "fontSettingsSerif", + IDS_FONT_LANGUAGE_SETTING_FONT_SELECTOR_SERIF_LABEL }, + { "fontSettingsSansSerif", + IDS_FONT_LANGUAGE_SETTING_FONT_SELECTOR_SANS_SERIF_LABEL }, + { "fontSettingsFixedWidth", + IDS_FONT_LANGUAGE_SETTING_FONT_SELECTOR_FIXED_WIDTH_LABEL }, + { "fontSettingsMinimumSize", + IDS_FONT_LANGUAGE_SETTING_MINIMUM_FONT_SIZE_TITLE }, + { "fontSettingsEncoding", + IDS_FONT_LANGUAGE_SETTING_FONT_SUB_DIALOG_ENCODING_TITLE }, + { "fontSettingsSizeTiny", + IDS_FONT_LANGUAGE_SETTING_FONT_SIZE_TINY }, + { "fontSettingsSizeHuge", + IDS_FONT_LANGUAGE_SETTING_FONT_SIZE_HUGE }, + { "fontSettingsLoremIpsum", + IDS_FONT_LANGUAGE_SETTING_LOREM_IPSUM }, + }; + + RegisterStrings(localized_strings, resources, arraysize(resources)); + RegisterTitle(localized_strings, "fontSettingsPage", + IDS_FONT_LANGUAGE_SETTING_FONT_TAB_TITLE); + localized_strings->SetString("fontSettingsPlaceholder", + l10n_util::GetStringUTF16( + IDS_FONT_LANGUAGE_SETTING_PLACEHOLDER)); +} + +void FontSettingsHandler::Initialize() { + DCHECK(web_ui_); + SetUpStandardFontSample(); + SetUpSerifFontSample(); + SetUpSansSerifFontSample(); + SetUpFixedFontSample(); + SetUpMinimumFontSample(); +} + +WebUIMessageHandler* FontSettingsHandler::Attach(WebUI* web_ui) { + // Call through to superclass. + WebUIMessageHandler* handler = OptionsPage2UIHandler::Attach(web_ui); + + // Perform validation for saved fonts. + DCHECK(web_ui_); + PrefService* pref_service = Profile::FromWebUI(web_ui_)->GetPrefs(); + FontSettingsUtilities::ValidateSavedFonts(pref_service); + + // Register for preferences that we need to observe manually. + standard_font_.Init(prefs::kWebKitStandardFontFamily, pref_service, this); + serif_font_.Init(prefs::kWebKitSerifFontFamily, pref_service, this); + sans_serif_font_.Init(prefs::kWebKitSansSerifFontFamily, pref_service, this); + fixed_font_.Init(prefs::kWebKitFixedFontFamily, pref_service, this); + font_encoding_.Init(prefs::kDefaultCharset, pref_service, this); + default_font_size_.Init(prefs::kWebKitDefaultFontSize, pref_service, this); + default_fixed_font_size_.Init(prefs::kWebKitDefaultFixedFontSize, + pref_service, this); + minimum_font_size_.Init(prefs::kWebKitMinimumFontSize, pref_service, this); + + // Return result from the superclass. + return handler; +} + +void FontSettingsHandler::RegisterMessages() { + web_ui_->RegisterMessageCallback("fetchFontsData", + base::Bind(&FontSettingsHandler::HandleFetchFontsData, + base::Unretained(this))); +} + +void FontSettingsHandler::HandleFetchFontsData(const ListValue* args) { + content::GetFontListAsync( + base::Bind(&FontSettingsHandler::FontsListHasLoaded, + base::Unretained(this))); +} + +void FontSettingsHandler::FontsListHasLoaded( + scoped_refptr<content::FontListResult> list) { + ListValue encoding_list; + const std::vector<CharacterEncoding::EncodingInfo>* encodings; + PrefService* pref_service = Profile::FromWebUI(web_ui_)->GetPrefs(); + encodings = CharacterEncoding::GetCurrentDisplayEncodings( + g_browser_process->GetApplicationLocale(), + pref_service->GetString(prefs::kStaticEncodings), + pref_service->GetString(prefs::kRecentlySelectedEncoding)); + DCHECK(encodings); + DCHECK(!encodings->empty()); + + std::vector<CharacterEncoding::EncodingInfo>::const_iterator it; + for (it = encodings->begin(); it != encodings->end(); ++it) { + ListValue* option = new ListValue(); + if (it->encoding_id) { + int cmd_id = it->encoding_id; + std::string encoding = + CharacterEncoding::GetCanonicalEncodingNameByCommandId(cmd_id); + string16 name = it->encoding_display_name; + base::i18n::AdjustStringForLocaleDirection(&name); + option->Append(Value::CreateStringValue(encoding)); + option->Append(Value::CreateStringValue(name)); + } else { + // Add empty name/value to indicate a separator item. + option->Append(Value::CreateStringValue("")); + option->Append(Value::CreateStringValue("")); + } + encoding_list.Append(option); + } + + ListValue selected_values; + selected_values.Append(Value::CreateStringValue(standard_font_.GetValue())); + selected_values.Append(Value::CreateStringValue(serif_font_.GetValue())); + selected_values.Append(Value::CreateStringValue(sans_serif_font_.GetValue())); + selected_values.Append(Value::CreateStringValue(fixed_font_.GetValue())); + selected_values.Append(Value::CreateStringValue(font_encoding_.GetValue())); + + web_ui_->CallJavascriptFunction("FontSettings.setFontsData", + *list->list.get(), encoding_list, + selected_values); +} + +void FontSettingsHandler::Observe(int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) { + if (type == chrome::NOTIFICATION_PREF_CHANGED) { + std::string* pref_name = content::Details<std::string>(details).ptr(); + if (*pref_name == prefs::kWebKitStandardFontFamily) { + SetUpStandardFontSample(); + } else if (*pref_name == prefs::kWebKitSerifFontFamily) { + SetUpSerifFontSample(); + } else if (*pref_name == prefs::kWebKitSansSerifFontFamily) { + SetUpSansSerifFontSample(); + } else if (*pref_name == prefs::kWebKitFixedFontFamily || + *pref_name == prefs::kWebKitDefaultFixedFontSize) { + SetUpFixedFontSample(); + } else if (*pref_name == prefs::kWebKitDefaultFontSize) { + SetUpStandardFontSample(); + SetUpSerifFontSample(); + SetUpSansSerifFontSample(); + } else if (*pref_name == prefs::kWebKitMinimumFontSize) { + SetUpMinimumFontSample(); + } + } +} + +void FontSettingsHandler::SetUpStandardFontSample() { + base::StringValue font_value(standard_font_.GetValue()); + base::FundamentalValue size_value(default_font_size_.GetValue()); + web_ui_->CallJavascriptFunction( + "FontSettings.setUpStandardFontSample", font_value, size_value); +} + +void FontSettingsHandler::SetUpSerifFontSample() { + base::StringValue font_value(serif_font_.GetValue()); + base::FundamentalValue size_value(default_font_size_.GetValue()); + web_ui_->CallJavascriptFunction( + "FontSettings.setUpSerifFontSample", font_value, size_value); +} + +void FontSettingsHandler::SetUpSansSerifFontSample() { + base::StringValue font_value(sans_serif_font_.GetValue()); + base::FundamentalValue size_value(default_font_size_.GetValue()); + web_ui_->CallJavascriptFunction( + "FontSettings.setUpSansSerifFontSample", font_value, size_value); +} + +void FontSettingsHandler::SetUpFixedFontSample() { + base::StringValue font_value(fixed_font_.GetValue()); + base::FundamentalValue size_value(default_fixed_font_size_.GetValue()); + web_ui_->CallJavascriptFunction( + "FontSettings.setUpFixedFontSample", font_value, size_value); +} + +void FontSettingsHandler::SetUpMinimumFontSample() { + base::FundamentalValue size_value(minimum_font_size_.GetValue()); + web_ui_->CallJavascriptFunction("FontSettings.setUpMinimumFontSample", + size_value); +} diff --git a/chrome/browser/ui/webui/options2/font_settings_handler.h b/chrome/browser/ui/webui/options2/font_settings_handler.h new file mode 100644 index 0000000..baedd3b --- /dev/null +++ b/chrome/browser/ui/webui/options2/font_settings_handler.h @@ -0,0 +1,55 @@ +// Copyright (c) 2011 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. + +#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS2_FONT_SETTINGS_HANDLER_H_ +#define CHROME_BROWSER_UI_WEBUI_OPTIONS2_FONT_SETTINGS_HANDLER_H_ +#pragma once + +#include "chrome/browser/prefs/pref_member.h" +#include "chrome/browser/ui/webui/options2/options_ui2.h" +#include "content/browser/font_list_async.h" + +// Font settings overlay page UI handler. +class FontSettingsHandler : public OptionsPage2UIHandler { + public: + FontSettingsHandler(); + virtual ~FontSettingsHandler(); + + // OptionsPage2UIHandler implementation. + virtual void GetLocalizedValues(DictionaryValue* localized_strings) OVERRIDE; + virtual void Initialize() OVERRIDE; + + // WebUIMessageHandler implementation. + virtual WebUIMessageHandler* Attach(WebUI* web_ui) OVERRIDE; + virtual void RegisterMessages() OVERRIDE; + + // content::NotificationObserver implementation. + virtual void Observe(int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) OVERRIDE; + + private: + void HandleFetchFontsData(const ListValue* args); + + void FontsListHasLoaded(scoped_refptr<content::FontListResult> list); + + void SetUpStandardFontSample(); + void SetUpSerifFontSample(); + void SetUpSansSerifFontSample(); + void SetUpFixedFontSample(); + void SetUpMinimumFontSample(); + + StringPrefMember standard_font_; + StringPrefMember serif_font_; + StringPrefMember sans_serif_font_; + StringPrefMember fixed_font_; + StringPrefMember font_encoding_; + IntegerPrefMember default_font_size_; + IntegerPrefMember default_fixed_font_size_; + IntegerPrefMember minimum_font_size_; + + DISALLOW_COPY_AND_ASSIGN(FontSettingsHandler); +}; + +#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS2_FONT_SETTINGS_HANDLER_H_ diff --git a/chrome/browser/ui/webui/options2/font_settings_utils.h b/chrome/browser/ui/webui/options2/font_settings_utils.h new file mode 100644 index 0000000..9fb435c --- /dev/null +++ b/chrome/browser/ui/webui/options2/font_settings_utils.h @@ -0,0 +1,22 @@ +// Copyright (c) 2011 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. + +#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS2_FONT_SETTINGS_UTILS_H_ +#define CHROME_BROWSER_UI_WEBUI_OPTIONS2_FONT_SETTINGS_UTILS_H_ +#pragma once + +#include "base/basictypes.h" + +class PrefService; + +// Chrome advanced options utility methods. +class FontSettingsUtilities { + public: + static void ValidateSavedFonts(PrefService* prefs); + + private: + DISALLOW_IMPLICIT_CONSTRUCTORS(FontSettingsUtilities); +}; + +#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS2_FONT_SETTINGS_UTILS_H_ diff --git a/chrome/browser/ui/webui/options2/font_settings_utils_mac.mm b/chrome/browser/ui/webui/options2/font_settings_utils_mac.mm new file mode 100644 index 0000000..0d26d83 --- /dev/null +++ b/chrome/browser/ui/webui/options2/font_settings_utils_mac.mm @@ -0,0 +1,38 @@ +// Copyright (c) 2011 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. + +#include "chrome/browser/ui/webui/options2/font_settings_utils.h" + +#import <Cocoa/Cocoa.h> + +#include "base/mac/scoped_nsautorelease_pool.h" +#include "base/sys_string_conversions.h" +#include "base/values.h" +#include "chrome/browser/prefs/pref_service.h" +#include "chrome/common/pref_names.h" + +static void ValidateFontFamily(PrefService* prefs, + const char* family_pref_name) { + // The native font settings dialog saved fonts by the font name, rather + // than the family name. This worked for the old dialog since + // -[NSFont fontWithName:size] accepted a font or family name, but the + // behavior was technically wrong. Since we really need the family name for + // the dom-ui options window, we will fix the saved preference if necessary. + NSString *family_name = + base::SysUTF8ToNSString(prefs->GetString(family_pref_name)); + NSFont *font = [NSFont fontWithName:family_name + size:[NSFont systemFontSize]]; + if (font && + [[font familyName] caseInsensitiveCompare:family_name] != NSOrderedSame) { + std::string new_family_name = base::SysNSStringToUTF8([font familyName]); + prefs->SetString(family_pref_name, new_family_name); + } +} + +// static +void FontSettingsUtilities::ValidateSavedFonts(PrefService* prefs) { + ValidateFontFamily(prefs, prefs::kWebKitSerifFontFamily); + ValidateFontFamily(prefs, prefs::kWebKitSansSerifFontFamily); + ValidateFontFamily(prefs, prefs::kWebKitFixedFontFamily); +} diff --git a/chrome/browser/ui/webui/options2/font_settings_utils_win.cc b/chrome/browser/ui/webui/options2/font_settings_utils_win.cc new file mode 100644 index 0000000..3c5ff5e --- /dev/null +++ b/chrome/browser/ui/webui/options2/font_settings_utils_win.cc @@ -0,0 +1,11 @@ +// Copyright (c) 2011 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. + +#include "chrome/browser/ui/webui/options2/font_settings_utils.h" + +// static +void FontSettingsUtilities::ValidateSavedFonts(PrefService* prefs) { + // Nothing to do for Windows. +} + diff --git a/chrome/browser/ui/webui/options2/font_settings_utils_x11.cc b/chrome/browser/ui/webui/options2/font_settings_utils_x11.cc new file mode 100644 index 0000000..61c2c6e --- /dev/null +++ b/chrome/browser/ui/webui/options2/font_settings_utils_x11.cc @@ -0,0 +1,10 @@ +// Copyright (c) 2011 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. + +#include "chrome/browser/ui/webui/options2/font_settings_utils.h" + +// static +void FontSettingsUtilities::ValidateSavedFonts(PrefService* prefs) { + // Nothing to do for X11. +} diff --git a/chrome/browser/ui/webui/options2/handler_options_handler.cc b/chrome/browser/ui/webui/options2/handler_options_handler.cc new file mode 100644 index 0000000..4c4c30f --- /dev/null +++ b/chrome/browser/ui/webui/options2/handler_options_handler.cc @@ -0,0 +1,207 @@ +// Copyright (c) 2011 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. + +#include "chrome/browser/ui/webui/options2/handler_options_handler.h" + +#include <vector> + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/utf_string_conversions.h" +#include "base/values.h" +#include "chrome/browser/prefs/pref_service.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/common/chrome_notification_types.h" +#include "grit/generated_resources.h" +#include "ui/base/l10n/l10n_util.h" + + +HandlerOptionsHandler::HandlerOptionsHandler() { +} + +HandlerOptionsHandler::~HandlerOptionsHandler() { +} + +void HandlerOptionsHandler::GetLocalizedValues( + DictionaryValue* localized_strings) { + DCHECK(localized_strings); + + static OptionsStringResource resources[] = { + { "handlers_tab_label", IDS_HANDLERS_TAB_LABEL }, + { "handlers_allow", IDS_HANDLERS_ALLOW_RADIO }, + { "handlers_block", IDS_HANDLERS_DONOTALLOW_RADIO }, + { "handlers_type_column_header", IDS_HANDLERS_TYPE_COLUMN_HEADER }, + { "handlers_site_column_header", IDS_HANDLERS_SITE_COLUMN_HEADER }, + { "handlers_remove_link", IDS_HANDLERS_REMOVE_HANDLER_LINK }, + { "handlers_none_handler", IDS_HANDLERS_NONE_HANDLER }, + { "handlers_active_heading", IDS_HANDLERS_ACTIVE_HEADING }, + { "handlers_ignored_heading", IDS_HANDLERS_IGNORED_HEADING }, + }; + RegisterTitle(localized_strings, "handlersPage", + IDS_HANDLER_OPTIONS_WINDOW_TITLE); + RegisterStrings(localized_strings, resources, arraysize(resources)); +} + +void HandlerOptionsHandler::Initialize() { + UpdateHandlerList(); + notification_registrar_.Add( + this, chrome::NOTIFICATION_PROTOCOL_HANDLER_REGISTRY_CHANGED, + content::Source<Profile>(Profile::FromWebUI(web_ui_))); +} + +void HandlerOptionsHandler::RegisterMessages() { + DCHECK(web_ui_); + web_ui_->RegisterMessageCallback("clearDefault", + base::Bind(&HandlerOptionsHandler::ClearDefault, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("removeHandler", + base::Bind(&HandlerOptionsHandler::RemoveHandler, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("setHandlersEnabled", + base::Bind(&HandlerOptionsHandler::SetHandlersEnabled, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("setDefault", + base::Bind(&HandlerOptionsHandler::SetDefault, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("removeIgnoredHandler", + base::Bind(&HandlerOptionsHandler::RemoveIgnoredHandler, + base::Unretained(this))); +} + +ProtocolHandlerRegistry* HandlerOptionsHandler::GetProtocolHandlerRegistry() { + DCHECK(web_ui_); + return Profile::FromWebUI(web_ui_)->GetProtocolHandlerRegistry(); +} + +static void GetHandlersAsListValue( + const ProtocolHandlerRegistry::ProtocolHandlerList& handlers, + ListValue* handler_list) { + ProtocolHandlerRegistry::ProtocolHandlerList::const_iterator handler; + for (handler = handlers.begin(); handler != handlers.end(); ++handler) { + ListValue* handlerValue = new ListValue(); + handlerValue->Append(Value::CreateStringValue(handler->protocol())); + handlerValue->Append(Value::CreateStringValue(handler->url().spec())); + handlerValue->Append(Value::CreateStringValue(handler->title())); + handler_list->Append(handlerValue); + } +} + +void HandlerOptionsHandler::GetHandlersForProtocol( + const std::string& protocol, + DictionaryValue* handlers_value) { + ProtocolHandlerRegistry* registry = GetProtocolHandlerRegistry(); + handlers_value->SetString("protocol", protocol); + handlers_value->SetInteger("default_handler", + registry->GetHandlerIndex(protocol)); + + ListValue* handlers_list = new ListValue(); + GetHandlersAsListValue(registry->GetHandlersFor(protocol), handlers_list); + handlers_value->Set("handlers", handlers_list); +} + +void HandlerOptionsHandler::GetIgnoredHandlers(ListValue* handlers) { + ProtocolHandlerRegistry* registry = GetProtocolHandlerRegistry(); + ProtocolHandlerRegistry::ProtocolHandlerList ignored_handlers = + registry->GetIgnoredHandlers(); + return GetHandlersAsListValue(ignored_handlers, handlers); +} + +void HandlerOptionsHandler::UpdateHandlerList() { +#if defined(ENABLE_REGISTER_PROTOCOL_HANDLER) + ProtocolHandlerRegistry* registry = GetProtocolHandlerRegistry(); + std::vector<std::string> protocols; + registry->GetRegisteredProtocols(&protocols); + + ListValue handlers; + for (std::vector<std::string>::iterator protocol = protocols.begin(); + protocol != protocols.end(); protocol++) { + DictionaryValue* handler_value = new DictionaryValue(); + GetHandlersForProtocol(*protocol, handler_value); + handlers.Append(handler_value); + } + + scoped_ptr<ListValue> ignored_handlers(new ListValue()); + GetIgnoredHandlers(ignored_handlers.get()); + web_ui_->CallJavascriptFunction("HandlerOptions.setHandlers", handlers); + web_ui_->CallJavascriptFunction("HandlerOptions.setIgnoredHandlers", + *ignored_handlers); +#endif // defined(ENABLE_REGISTER_PROTOCOL_HANDLER) +} + +void HandlerOptionsHandler::RemoveHandler(const ListValue* args) { + ListValue* list; + if (!args->GetList(0, &list)) { + NOTREACHED(); + return; + } + + ProtocolHandler handler(ParseHandlerFromArgs(list)); + GetProtocolHandlerRegistry()->RemoveHandler(handler); + + // No need to call UpdateHandlerList() - we should receive a notification + // that the ProtocolHandlerRegistry has changed and we will update the view + // then. +} + +void HandlerOptionsHandler::RemoveIgnoredHandler(const ListValue* args) { + ListValue* list; + if (!args->GetList(0, &list)) { + NOTREACHED(); + return; + } + + ProtocolHandler handler(ParseHandlerFromArgs(list)); + GetProtocolHandlerRegistry()->RemoveIgnoredHandler(handler); +} + +void HandlerOptionsHandler::SetHandlersEnabled(const ListValue* args) { + bool enabled = true; + CHECK(args->GetBoolean(0, &enabled)); + if (enabled) + GetProtocolHandlerRegistry()->Enable(); + else + GetProtocolHandlerRegistry()->Disable(); +} + +void HandlerOptionsHandler::ClearDefault(const ListValue* args) { + Value* value; + CHECK(args->Get(0, &value)); + std::string protocol_to_clear; + CHECK(value->GetAsString(&protocol_to_clear)); + GetProtocolHandlerRegistry()->ClearDefault(protocol_to_clear); +} + +void HandlerOptionsHandler::SetDefault(const ListValue* args) { + Value* value; + CHECK(args->Get(0, &value)); + ListValue* list; + CHECK(args->GetList(0, &list)); + const ProtocolHandler& handler(ParseHandlerFromArgs(list)); + CHECK(!handler.IsEmpty()); + GetProtocolHandlerRegistry()->OnAcceptRegisterProtocolHandler(handler); +} + +ProtocolHandler HandlerOptionsHandler::ParseHandlerFromArgs( + const ListValue* args) const { + string16 protocol; + string16 url; + string16 title; + bool ok = args->GetString(0, &protocol) && args->GetString(1, &url) && + args->GetString(2, &title); + if (!ok) + return ProtocolHandler::EmptyProtocolHandler(); + return ProtocolHandler::CreateProtocolHandler(UTF16ToUTF8(protocol), + GURL(UTF16ToUTF8(url)), + title); +} + +void HandlerOptionsHandler::Observe( + int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) { + if (type == chrome::NOTIFICATION_PROTOCOL_HANDLER_REGISTRY_CHANGED) + UpdateHandlerList(); + else + NOTREACHED(); +} diff --git a/chrome/browser/ui/webui/options2/handler_options_handler.h b/chrome/browser/ui/webui/options2/handler_options_handler.h new file mode 100644 index 0000000..4699f82 --- /dev/null +++ b/chrome/browser/ui/webui/options2/handler_options_handler.h @@ -0,0 +1,76 @@ +// Copyright (c) 2011 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. + +#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS2_HANDLER_OPTIONS_HANDLER_H_ +#define CHROME_BROWSER_UI_WEBUI_OPTIONS2_HANDLER_OPTIONS_HANDLER_H_ + +#include <string> + +#include "chrome/browser/custom_handlers/protocol_handler_registry.h" +#include "chrome/browser/ui/webui/options2/options_ui2.h" +#include "chrome/common/custom_handlers/protocol_handler.h" +#include "content/public/browser/notification_registrar.h" + +namespace base { +class DictionaryValue; +} + +class HandlerOptionsHandler : public OptionsPage2UIHandler { + public: + HandlerOptionsHandler(); + virtual ~HandlerOptionsHandler(); + + // OptionsPage2UIHandler implementation. + virtual void GetLocalizedValues( + base::DictionaryValue* localized_strings) OVERRIDE; + virtual void Initialize() OVERRIDE; + virtual void RegisterMessages() OVERRIDE; + + // content::NotificationObserver implementation. + virtual void Observe(int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) OVERRIDE; + + private: + // Called when the user toggles whether custom handlers are enabled. + void SetHandlersEnabled(const ListValue* args); + + // Called when the user sets a new default handler for a protocol. + void SetDefault(const ListValue* args); + + // Called when the user clears the default handler for a protocol. + // |args| is the string name of the protocol to clear. + void ClearDefault(const ListValue* args); + + // Parses a ProtocolHandler out of the arguments passed back from the view. + // |args| is a list of [protocol, url, title]. + ProtocolHandler ParseHandlerFromArgs(const ListValue* args) const; + + // Returns a JSON object describing the set of protocol handlers for the + // given protocol. + void GetHandlersForProtocol(const std::string& protocol, + base::DictionaryValue* value); + + // Returns a JSON list of the ignored protocol handlers. + void GetIgnoredHandlers(ListValue* handlers); + + // Called when the JS PasswordManager object is initialized. + void UpdateHandlerList(); + + // Remove a handler. + // |args| is a list of [protocol, url, title]. + void RemoveHandler(const ListValue* args); + + // Remove an ignored handler. + // |args| is a list of [protocol, url, title]. + void RemoveIgnoredHandler(const ListValue* args); + + ProtocolHandlerRegistry* GetProtocolHandlerRegistry(); + + content::NotificationRegistrar notification_registrar_; + + DISALLOW_COPY_AND_ASSIGN(HandlerOptionsHandler); +}; + +#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS2_HANDLER_OPTIONS_HANDLER_H_ diff --git a/chrome/browser/ui/webui/options2/import_data_handler.cc b/chrome/browser/ui/webui/options2/import_data_handler.cc new file mode 100644 index 0000000..76e5b83 --- /dev/null +++ b/chrome/browser/ui/webui/options2/import_data_handler.cc @@ -0,0 +1,177 @@ +// Copyright (c) 2011 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. + +#include "chrome/browser/ui/webui/options2/import_data_handler.h" + +#include <string> + +#include "base/basictypes.h" +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/memory/scoped_ptr.h" +#include "base/string16.h" +#include "base/string_number_conversions.h" +#include "base/string_util.h" +#include "base/threading/thread_restrictions.h" +#include "base/utf_string_conversions.h" +#include "base/values.h" +#include "chrome/browser/importer/external_process_importer_host.h" +#include "chrome/browser/importer/importer_host.h" +#include "chrome/browser/importer/importer_list.h" +#include "chrome/browser/profiles/profile.h" +#include "grit/chromium_strings.h" +#include "grit/generated_resources.h" +#include "ui/base/l10n/l10n_util.h" + +ImportDataHandler::ImportDataHandler() : importer_host_(NULL), + import_did_succeed_(false) { +} + +ImportDataHandler::~ImportDataHandler() { + if (importer_list_) + importer_list_->SetObserver(NULL); + + if (importer_host_) + importer_host_->SetObserver(NULL); +} + +void ImportDataHandler::GetLocalizedValues(DictionaryValue* localized_strings) { + DCHECK(localized_strings); + + static OptionsStringResource resources[] = { + { "importFromLabel", IDS_IMPORT_FROM_LABEL }, + { "importLoading", IDS_IMPORT_LOADING_PROFILES }, + { "importDescription", IDS_IMPORT_ITEMS_LABEL }, + { "importHistory", IDS_IMPORT_HISTORY_CHKBOX }, + { "importFavorites", IDS_IMPORT_FAVORITES_CHKBOX }, + { "importSearch", IDS_IMPORT_SEARCH_ENGINES_CHKBOX }, + { "importPasswords", IDS_IMPORT_PASSWORDS_CHKBOX }, + { "importCommit", IDS_IMPORT_COMMIT }, + { "noProfileFound", IDS_IMPORT_NO_PROFILE_FOUND }, + { "importSucceeded", IDS_IMPORT_SUCCEEDED }, + { "findYourImportedBookmarks", IDS_IMPORT_FIND_YOUR_BOOKMARKS }, + }; + + RegisterStrings(localized_strings, resources, arraysize(resources)); + RegisterTitle(localized_strings, "importDataOverlay", + IDS_IMPORT_SETTINGS_TITLE); +} + +void ImportDataHandler::Initialize() { + Profile* profile = Profile::FromWebUI(web_ui_); + importer_list_ = new ImporterList(profile->GetRequestContext()); + importer_list_->DetectSourceProfiles(this); +} + +void ImportDataHandler::RegisterMessages() { + web_ui_->RegisterMessageCallback("importData", + base::Bind(&ImportDataHandler::ImportData, base::Unretained(this))); +} + +void ImportDataHandler::ImportData(const ListValue* args) { + std::string string_value; + + int browser_index; + if (!args->GetString(0, &string_value) || + !base::StringToInt(string_value, &browser_index)) { + NOTREACHED(); + return; + } + + uint16 selected_items = importer::NONE; + if (args->GetString(1, &string_value) && string_value == "true") { + selected_items |= importer::HISTORY; + } + if (args->GetString(2, &string_value) && string_value == "true") { + selected_items |= importer::FAVORITES; + } + if (args->GetString(3, &string_value) && string_value == "true") { + selected_items |= importer::PASSWORDS; + } + if (args->GetString(4, &string_value) && string_value == "true") { + selected_items |= importer::SEARCH_ENGINES; + } + + const importer::SourceProfile& source_profile = + importer_list_->GetSourceProfileAt(browser_index); + uint16 supported_items = source_profile.services_supported; + + uint16 import_services = (selected_items & supported_items); + if (import_services) { + base::FundamentalValue state(true); + web_ui_->CallJavascriptFunction("ImportDataOverlay.setImportingState", + state); + import_did_succeed_ = false; + + // TODO(csilv): Out-of-process import has only been qualified on MacOS X, + // so we will only use it on that platform since it is required. Remove this + // conditional logic once oop import is qualified for Linux/Windows. + // http://crbug.com/22142 +#if defined(OS_MACOSX) + importer_host_ = new ExternalProcessImporterHost; +#else + importer_host_ = new ImporterHost; +#endif + importer_host_->SetObserver(this); + Profile* profile = Profile::FromWebUI(web_ui_); + importer_host_->StartImportSettings(source_profile, profile, + import_services, + new ProfileWriter(profile), false); + } else { + LOG(WARNING) << "There were no settings to import from '" + << source_profile.importer_name << "'."; + } +} + +void ImportDataHandler::OnSourceProfilesLoaded() { + ListValue browser_profiles; + for (size_t i = 0; i < importer_list_->count(); ++i) { + const importer::SourceProfile& source_profile = + importer_list_->GetSourceProfileAt(i); + uint16 browser_services = source_profile.services_supported; + + DictionaryValue* browser_profile = new DictionaryValue(); + browser_profile->SetString("name", source_profile.importer_name); + browser_profile->SetInteger("index", i); + browser_profile->SetBoolean("history", + (browser_services & importer::HISTORY) != 0); + browser_profile->SetBoolean("favorites", + (browser_services & importer::FAVORITES) != 0); + browser_profile->SetBoolean("passwords", + (browser_services & importer::PASSWORDS) != 0); + browser_profile->SetBoolean("search", + (browser_services & importer::SEARCH_ENGINES) != 0); + + browser_profiles.Append(browser_profile); + } + + web_ui_->CallJavascriptFunction("ImportDataOverlay.updateSupportedBrowsers", + browser_profiles); +} + +void ImportDataHandler::ImportStarted() { +} + +void ImportDataHandler::ImportItemStarted(importer::ImportItem item) { + // TODO(csilv): show progress detail in the web view. +} + +void ImportDataHandler::ImportItemEnded(importer::ImportItem item) { + // TODO(csilv): show progress detail in the web view. + import_did_succeed_ = true; +} + +void ImportDataHandler::ImportEnded() { + importer_host_->SetObserver(NULL); + importer_host_ = NULL; + + if (import_did_succeed_) { + web_ui_->CallJavascriptFunction("ImportDataOverlay.confirmSuccess"); + } else { + base::FundamentalValue state(false); + web_ui_->CallJavascriptFunction("ImportDataOverlay.setImportingState", + state); + web_ui_->CallJavascriptFunction("ImportDataOverlay.dismiss"); + } +} diff --git a/chrome/browser/ui/webui/options2/import_data_handler.h b/chrome/browser/ui/webui/options2/import_data_handler.h new file mode 100644 index 0000000..f537c54 --- /dev/null +++ b/chrome/browser/ui/webui/options2/import_data_handler.h @@ -0,0 +1,59 @@ +// Copyright (c) 2011 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. + +#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS2_IMPORT_DATA_HANDLER_H_ +#define CHROME_BROWSER_UI_WEBUI_OPTIONS2_IMPORT_DATA_HANDLER_H_ +#pragma once + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/memory/ref_counted.h" +#include "chrome/browser/importer/importer_data_types.h" +#include "chrome/browser/importer/importer_list_observer.h" +#include "chrome/browser/importer/importer_progress_observer.h" +#include "chrome/browser/ui/webui/options2/options_ui2.h" + +class ImporterHost; +class ImporterList; + +// Chrome personal stuff import data overlay UI handler. +class ImportDataHandler : public OptionsPage2UIHandler, + public importer::ImporterListObserver, + public importer::ImporterProgressObserver { + public: + ImportDataHandler(); + virtual ~ImportDataHandler(); + + // OptionsPage2UIHandler: + virtual void GetLocalizedValues( + base::DictionaryValue* localized_strings) OVERRIDE; + virtual void Initialize() OVERRIDE; + + // WebUIMessageHandler: + virtual void RegisterMessages() OVERRIDE; + + private: + void ImportData(const base::ListValue* args); + + // importer::ImporterListObserver: + virtual void OnSourceProfilesLoaded() OVERRIDE; + + // importer::ImporterProgressObserver: + virtual void ImportStarted() OVERRIDE; + virtual void ImportItemStarted(importer::ImportItem item) OVERRIDE; + virtual void ImportItemEnded(importer::ImportItem item) OVERRIDE; + virtual void ImportEnded() OVERRIDE; + + scoped_refptr<ImporterList> importer_list_; + + // If non-null it means importing is in progress. ImporterHost takes care + // of deleting itself when import is complete. + ImporterHost* importer_host_; // weak + + bool import_did_succeed_; + + DISALLOW_COPY_AND_ASSIGN(ImportDataHandler); +}; + +#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS2_IMPORT_DATA_HANDLER_H_ diff --git a/chrome/browser/ui/webui/options2/language_options_browsertest.js b/chrome/browser/ui/webui/options2/language_options_browsertest.js new file mode 100644 index 0000000..4f1b87c --- /dev/null +++ b/chrome/browser/ui/webui/options2/language_options_browsertest.js @@ -0,0 +1,24 @@ +// Copyright (c) 2011 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. + +/** + * TestFixture for languages options WebUI testing. + * @extends {testing.Test} + * @constructor + **/ +function LanguagesOptionsWebUITest() {} + +LanguagesOptionsWebUITest.prototype = { + __proto__: testing.Test.prototype, + + /** + * Browse to languages options. + **/ + browsePreload: 'chrome://settings/languages', +}; + +// Test opening languages options has correct location. +TEST_F('LanguagesOptionsWebUITest', 'testOpenLanguagesOptions', function() { + assertEquals(this.browsePreload, document.location.href); +}); diff --git a/chrome/browser/ui/webui/options2/language_options_handler.cc b/chrome/browser/ui/webui/options2/language_options_handler.cc new file mode 100644 index 0000000..64e4d8c --- /dev/null +++ b/chrome/browser/ui/webui/options2/language_options_handler.cc @@ -0,0 +1,118 @@ +// Copyright (c) 2011 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. + +#include "chrome/browser/ui/webui/options2/language_options_handler.h" + +#include <map> +#include <string> +#include <utility> +#include <vector> + +#include "base/basictypes.h" +#include "base/bind.h" +#include "base/command_line.h" +#include "base/i18n/rtl.h" +#include "base/utf_string_conversions.h" +#include "base/values.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/prefs/pref_service.h" +#include "chrome/browser/ui/browser_list.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/common/pref_names.h" +#include "content/public/browser/user_metrics.h" +#include "grit/chromium_strings.h" +#include "grit/generated_resources.h" +#include "ui/base/l10n/l10n_util.h" + +using content::UserMetricsAction; + +LanguageOptionsHandler::LanguageOptionsHandler() { +} + +LanguageOptionsHandler::~LanguageOptionsHandler() { +} + +void LanguageOptionsHandler::GetLocalizedValues( + DictionaryValue* localized_strings) { + LanguageOptionsHandlerCommon::GetLocalizedValues(localized_strings); + + RegisterTitle(localized_strings, "languagePage", + IDS_OPTIONS_SETTINGS_LANGUAGES_DIALOG_TITLE); + localized_strings->SetString("restart_button", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_LANGUAGES_RELAUNCH_BUTTON)); + localized_strings->Set("languageList", GetLanguageList()); +} + +void LanguageOptionsHandler::RegisterMessages() { + LanguageOptionsHandlerCommon::RegisterMessages(); + + web_ui_->RegisterMessageCallback("uiLanguageRestart", + base::Bind(&LanguageOptionsHandler::RestartCallback, + base::Unretained(this))); +} + +ListValue* LanguageOptionsHandler::GetLanguageList() { + // Collect the language codes from the supported accept-languages. + const std::string app_locale = g_browser_process->GetApplicationLocale(); + std::vector<std::string> language_codes; + l10n_util::GetAcceptLanguagesForLocale(app_locale, &language_codes); + + // Map of display name -> {language code, native_display_name}. + // In theory, we should be able to create a map that is sorted by + // display names using ICU comparator, but doing it is hard, thus we'll + // use an auxiliary vector to achieve the same result. + typedef std::pair<std::string, string16> LanguagePair; + typedef std::map<string16, LanguagePair> LanguageMap; + LanguageMap language_map; + // The auxiliary vector mentioned above. + std::vector<string16> display_names; + + // Build the list of display names, and build the language map. + for (size_t i = 0; i < language_codes.size(); ++i) { + string16 display_name = + l10n_util::GetDisplayNameForLocale(language_codes[i], app_locale, + false); + base::i18n::AdjustStringForLocaleDirection(&display_name); + string16 native_display_name = + l10n_util::GetDisplayNameForLocale(language_codes[i], language_codes[i], + false); + base::i18n::AdjustStringForLocaleDirection(&native_display_name); + display_names.push_back(display_name); + language_map[display_name] = + std::make_pair(language_codes[i], native_display_name); + } + DCHECK_EQ(display_names.size(), language_map.size()); + + // Sort display names using locale specific sorter. + l10n_util::SortStrings16(app_locale, &display_names); + + // Build the language list from the language map. + ListValue* language_list = new ListValue(); + for (size_t i = 0; i < display_names.size(); ++i) { + const LanguagePair& pair = language_map[display_names[i]]; + DictionaryValue* dictionary = new DictionaryValue(); + dictionary->SetString("code", pair.first); + dictionary->SetString("displayName", display_names[i]); + dictionary->SetString("nativeDisplayName", pair.second); + language_list->Append(dictionary); + } + + return language_list; +} + +string16 LanguageOptionsHandler::GetProductName() { + return l10n_util::GetStringUTF16(IDS_PRODUCT_NAME); +} + +void LanguageOptionsHandler::SetApplicationLocale( + const std::string& language_code) { + PrefService* pref_service = g_browser_process->local_state(); + pref_service->SetString(prefs::kApplicationLocale, language_code); +} + +void LanguageOptionsHandler::RestartCallback(const ListValue* args) { + content::RecordAction(UserMetricsAction("LanguageOptions_Restart")); + BrowserList::AttemptRestart(); +} diff --git a/chrome/browser/ui/webui/options2/language_options_handler.h b/chrome/browser/ui/webui/options2/language_options_handler.h new file mode 100644 index 0000000..1131b32 --- /dev/null +++ b/chrome/browser/ui/webui/options2/language_options_handler.h @@ -0,0 +1,44 @@ +// Copyright (c) 2011 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. + +#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS2_LANGUAGE_OPTIONS_HANDLER_H_ +#define CHROME_BROWSER_UI_WEBUI_OPTIONS2_LANGUAGE_OPTIONS_HANDLER_H_ +#pragma once + +#include "chrome/browser/ui/webui/options2/language_options_handler_common.h" + +// Language options UI page handler for non-Chrome OS platforms. For Chrome OS, +// see chromeos::CrosLanguageOptionsHandler. +class LanguageOptionsHandler : public LanguageOptionsHandlerCommon { + public: + LanguageOptionsHandler(); + virtual ~LanguageOptionsHandler(); + + // OptionsPageUIHandler implementation. + virtual void GetLocalizedValues( + base::DictionaryValue* localized_strings) OVERRIDE; + + // WebUIMessageHandler implementation. + virtual void RegisterMessages() OVERRIDE; + + // The following static method is public for ease of testing. + + // Gets the list of languages from the given input descriptors. + // The return value will look like: + // [{'code': 'fi', 'displayName': 'Finnish', 'nativeDisplayName': 'suomi'}, + // ...] + static base::ListValue* GetLanguageList(); + + private: + // LanguageOptionsHandlerCommon implementation. + virtual string16 GetProductName() OVERRIDE; + virtual void SetApplicationLocale(const std::string& language_code) OVERRIDE; + + // Called when the restart button is clicked. + void RestartCallback(const base::ListValue* args); + + DISALLOW_COPY_AND_ASSIGN(LanguageOptionsHandler); +}; + +#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS2_LANGUAGE_OPTIONS_HANDLER_H_ diff --git a/chrome/browser/ui/webui/options2/language_options_handler_common.cc b/chrome/browser/ui/webui/options2/language_options_handler_common.cc new file mode 100644 index 0000000..cff8ee7 --- /dev/null +++ b/chrome/browser/ui/webui/options2/language_options_handler_common.cc @@ -0,0 +1,168 @@ +// Copyright (c) 2011 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. + +#include "chrome/browser/ui/webui/options2/language_options_handler_common.h" + +#include <map> +#include <string> +#include <utility> +#include <vector> + +#include "base/basictypes.h" +#include "base/bind.h" +#include "base/command_line.h" +#include "base/stringprintf.h" +#include "base/utf_string_conversions.h" +#include "base/values.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/prefs/pref_service.h" +#include "chrome/browser/ui/browser_list.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/common/pref_names.h" +#include "chrome/common/spellcheck_common.h" +#include "content/public/browser/user_metrics.h" +#include "grit/chromium_strings.h" +#include "grit/generated_resources.h" +#include "ui/base/l10n/l10n_util.h" + +using content::UserMetricsAction; + +LanguageOptionsHandlerCommon::LanguageOptionsHandlerCommon() { +} + +LanguageOptionsHandlerCommon::~LanguageOptionsHandlerCommon() { +} + +void LanguageOptionsHandlerCommon::GetLocalizedValues( + DictionaryValue* localized_strings) { + DCHECK(localized_strings); + string16 product_name = GetProductName(); + localized_strings->SetString("add_button", + l10n_util::GetStringUTF16(IDS_OPTIONS_SETTINGS_LANGUAGES_ADD_BUTTON)); + localized_strings->SetString("languages", + l10n_util::GetStringUTF16(IDS_OPTIONS_SETTINGS_LANGUAGES_LANGUAGES)); + localized_strings->SetString("please_add_another_language", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_LANGUAGES_PLEASE_ADD_ANOTHER_LANGUAGE)); + localized_strings->SetString("remove_button", + l10n_util::GetStringUTF16(IDS_OPTIONS_SETTINGS_LANGUAGES_REMOVE_BUTTON)); + localized_strings->SetString("add_language_instructions", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_LANGUAGES_ADD_LANGUAGE_INSTRUCTIONS)); + localized_strings->SetString("cannot_be_displayed_in_this_language", + l10n_util::GetStringFUTF16( + IDS_OPTIONS_SETTINGS_LANGUAGES_CANNOT_BE_DISPLAYED_IN_THIS_LANGUAGE, + product_name)); + localized_strings->SetString("is_displayed_in_this_language", + l10n_util::GetStringFUTF16( + IDS_OPTIONS_SETTINGS_LANGUAGES_IS_DISPLAYED_IN_THIS_LANGUAGE, + product_name)); + localized_strings->SetString("display_in_this_language", + l10n_util::GetStringFUTF16( + IDS_OPTIONS_SETTINGS_LANGUAGES_DISPLAY_IN_THIS_LANGUAGE, + product_name)); + localized_strings->SetString("this_language_is_currently_in_use", + l10n_util::GetStringFUTF16( + IDS_OPTIONS_SETTINGS_LANGUAGES_THIS_LANGUAGE_IS_CURRENTLY_IN_USE, + product_name)); + localized_strings->SetString("restart_required", + l10n_util::GetStringUTF16(IDS_OPTIONS_RELAUNCH_REQUIRED)); + // OS X uses the OS native spellchecker so no need for these strings. +#if !defined(OS_MACOSX) + localized_strings->SetString("use_this_for_spell_checking", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_USE_THIS_FOR_SPELL_CHECKING)); + localized_strings->SetString("cannot_be_used_for_spell_checking", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_CANNOT_BE_USED_FOR_SPELL_CHECKING)); + localized_strings->SetString("is_used_for_spell_checking", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_IS_USED_FOR_SPELL_CHECKING)); + localized_strings->SetString("enable_spell_check", + l10n_util::GetStringUTF16(IDS_OPTIONS_ENABLE_SPELLCHECK)); + localized_strings->SetString("enable_auto_spell_correction", + l10n_util::GetStringUTF16(IDS_OPTIONS_ENABLE_AUTO_SPELL_CORRECTION)); +#endif // !OS_MACOSX + localized_strings->SetString("add_language_title", + l10n_util::GetStringUTF16(IDS_OPTIONS_LANGUAGES_ADD_TITLE)); + localized_strings->SetString("add_language_select_label", + l10n_util::GetStringUTF16(IDS_OPTIONS_LANGUAGES_ADD_SELECT_LABEL)); + localized_strings->SetString("restart_button", + l10n_util::GetStringUTF16( + IDS_OPTIONS_SETTINGS_LANGUAGES_RELAUNCH_BUTTON)); + + // The following are resources, rather than local strings. + localized_strings->SetString("currentUiLanguageCode", + g_browser_process->GetApplicationLocale()); + localized_strings->Set("spellCheckLanguageCodeSet", + GetSpellCheckLanguageCodeSet()); + localized_strings->Set("uiLanguageCodeSet", GetUILanguageCodeSet()); + + const CommandLine& command_line = *CommandLine::ForCurrentProcess(); + bool experimental_spell_check_features = + command_line.HasSwitch(switches::kExperimentalSpellcheckerFeatures); + localized_strings->SetBoolean("experimentalSpellCheckFeatures", + experimental_spell_check_features); +} + +void LanguageOptionsHandlerCommon::RegisterMessages() { + DCHECK(web_ui_); + web_ui_->RegisterMessageCallback("languageOptionsOpen", + base::Bind( + &LanguageOptionsHandlerCommon::LanguageOptionsOpenCallback, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("spellCheckLanguageChange", + base::Bind( + &LanguageOptionsHandlerCommon::SpellCheckLanguageChangeCallback, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("uiLanguageChange", + base::Bind( + &LanguageOptionsHandlerCommon::UiLanguageChangeCallback, + base::Unretained(this))); +} + +DictionaryValue* LanguageOptionsHandlerCommon::GetUILanguageCodeSet() { + DictionaryValue* dictionary = new DictionaryValue(); + const std::vector<std::string>& available_locales = + l10n_util::GetAvailableLocales(); + for (size_t i = 0; i < available_locales.size(); ++i) { + dictionary->SetBoolean(available_locales[i], true); + } + return dictionary; +} + +DictionaryValue* LanguageOptionsHandlerCommon::GetSpellCheckLanguageCodeSet() { + DictionaryValue* dictionary = new DictionaryValue(); + std::vector<std::string> spell_check_languages; + SpellCheckCommon::SpellCheckLanguages(&spell_check_languages); + for (size_t i = 0; i < spell_check_languages.size(); ++i) { + dictionary->SetBoolean(spell_check_languages[i], true); + } + return dictionary; +} + +void LanguageOptionsHandlerCommon::LanguageOptionsOpenCallback( + const ListValue* args) { + content::RecordAction(UserMetricsAction("LanguageOptions_Open")); +} + +void LanguageOptionsHandlerCommon::UiLanguageChangeCallback( + const ListValue* args) { + const std::string language_code = UTF16ToASCII(ExtractStringValue(args)); + CHECK(!language_code.empty()); + const std::string action = base::StringPrintf( + "LanguageOptions_UiLanguageChange_%s", language_code.c_str()); + content::RecordComputedAction(action); + SetApplicationLocale(language_code); + web_ui_->CallJavascriptFunction("options.LanguageOptions.uiLanguageSaved"); +} + +void LanguageOptionsHandlerCommon::SpellCheckLanguageChangeCallback( + const ListValue* args) { + const std::string language_code = UTF16ToASCII(ExtractStringValue(args)); + CHECK(!language_code.empty()); + const std::string action = base::StringPrintf( + "LanguageOptions_SpellCheckLanguageChange_%s", language_code.c_str()); + content::RecordComputedAction(action); +} diff --git a/chrome/browser/ui/webui/options2/language_options_handler_common.h b/chrome/browser/ui/webui/options2/language_options_handler_common.h new file mode 100644 index 0000000..562a722 --- /dev/null +++ b/chrome/browser/ui/webui/options2/language_options_handler_common.h @@ -0,0 +1,69 @@ +// Copyright (c) 2011 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. + +#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS2_LANGUAGE_OPTIONS_HANDLER_COMMON_H_ +#define CHROME_BROWSER_UI_WEBUI_OPTIONS2_LANGUAGE_OPTIONS_HANDLER_COMMON_H_ +#pragma once + +#include "chrome/browser/ui/webui/options2/options_ui2.h" + +namespace base { +class DictionaryValue; +class ListValue; +} + +// The base class for language options page UI handlers. This class has code +// common to the Chrome OS and non-Chrome OS implementation of the handler. +class LanguageOptionsHandlerCommon : public OptionsPage2UIHandler { + public: + LanguageOptionsHandlerCommon(); + virtual ~LanguageOptionsHandlerCommon(); + + // OptionsPage2UIHandler implementation. + virtual void GetLocalizedValues( + base::DictionaryValue* localized_strings) OVERRIDE; + + // DOMMessageHandler implementation. + virtual void RegisterMessages() OVERRIDE; + + // The following static methods are public for ease of testing. + + // Gets the set of language codes that can be used as UI language. + // The return value will look like: + // {'en-US': true, 'fi': true, 'fr': true, ...} + // + // Note that true in values does not mean anything. We just use the + // dictionary as a set. + static base::DictionaryValue* GetUILanguageCodeSet(); + + // Gets the set of language codes that can be used for spellchecking. + // The return value will look like: + // {'en-US': true, 'fi': true, 'fr': true, ...} + // + // Note that true in values does not mean anything. We just use the + // dictionary as a set. + static base::DictionaryValue* GetSpellCheckLanguageCodeSet(); + + private: + // Returns the name of the product (ex. "Chrome" or "Chrome OS"). + virtual string16 GetProductName() = 0; + + // Sets the application locale. + virtual void SetApplicationLocale(const std::string& language_code) = 0; + + // Called when the language options is opened. + void LanguageOptionsOpenCallback(const base::ListValue* args); + + // Called when the UI language is changed. + // |args| will contain the language code as string (ex. "fr"). + void UiLanguageChangeCallback(const base::ListValue* args); + + // Called when the spell check language is changed. + // |args| will contain the language code as string (ex. "fr"). + void SpellCheckLanguageChangeCallback(const base::ListValue* args); + + DISALLOW_COPY_AND_ASSIGN(LanguageOptionsHandlerCommon); +}; + +#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS2_LANGUAGE_OPTIONS_HANDLER_COMMON_H_ diff --git a/chrome/browser/ui/webui/options2/language_options_handler_unittest.cc b/chrome/browser/ui/webui/options2/language_options_handler_unittest.cc new file mode 100644 index 0000000..c889818 --- /dev/null +++ b/chrome/browser/ui/webui/options2/language_options_handler_unittest.cc @@ -0,0 +1,198 @@ +// Copyright (c) 2011 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. + +#include "chrome/browser/ui/webui/options2/language_options_handler.h" + +#include <string> + +#include "base/memory/scoped_ptr.h" +#include "base/values.h" +#include "testing/gtest/include/gtest/gtest.h" + +#if defined(OS_CHROMEOS) +#include "chrome/browser/chromeos/input_method/ibus_controller.h" +#include "chrome/browser/chromeos/input_method/input_method_manager.h" +#include "chrome/browser/ui/webui/options2/chromeos/cros_language_options_handler.h" +#endif // defined(OS_CHROMEOS) + +#if defined(OS_CHROMEOS) + +using chromeos::input_method::IBusController; +using chromeos::input_method::InputMethodDescriptor; +using chromeos::input_method::InputMethodDescriptors; + +static InputMethodDescriptor GetDesc(IBusController* controller, + const std::string& id, + const std::string& raw_layout, + const std::string& language_code) { + return controller->CreateInputMethodDescriptor(id, "", raw_layout, + language_code); +} + +static InputMethodDescriptors CreateInputMethodDescriptors() { + scoped_ptr<IBusController> controller(IBusController::Create()); + + InputMethodDescriptors descriptors; + descriptors.push_back(GetDesc(controller.get(), "xkb:us::eng", "us", "eng")); + descriptors.push_back(GetDesc(controller.get(), "xkb:fr::fra", "fr", "fra")); + descriptors.push_back(GetDesc(controller.get(), "xkb:be::fra", "be", "fr")); + descriptors.push_back(GetDesc(controller.get(), "mozc", "us", "ja")); + return descriptors; +} + +TEST(LanguageOptionsHandlerTest, GetInputMethodList) { + InputMethodDescriptors descriptors = CreateInputMethodDescriptors(); + scoped_ptr<ListValue> list( + chromeos::CrosLanguageOptionsHandler::GetInputMethodList(descriptors)); + ASSERT_EQ(4U, list->GetSize()); + + DictionaryValue* entry = NULL; + DictionaryValue *language_code_set = NULL; + std::string input_method_id; + std::string display_name; + std::string language_code; + + // As shown below, the list should be input method ids should appear in + // the same order of the descriptors. + ASSERT_TRUE(list->GetDictionary(0, &entry)); + ASSERT_TRUE(entry->GetString("id", &input_method_id)); + ASSERT_TRUE(entry->GetString("displayName", &display_name)); + ASSERT_TRUE(entry->GetDictionary("languageCodeSet", &language_code_set)); + EXPECT_EQ("xkb:us::eng", input_method_id); + // Commented out as it depends on translation in generated_resources.grd + // (i.e. makes the test fragile). + // EXPECT_EQ("English (USA) keyboard layout", display_name); + ASSERT_TRUE(language_code_set->HasKey("en-US")); + ASSERT_TRUE(language_code_set->HasKey("id")); // From kExtraLanguages. + ASSERT_TRUE(language_code_set->HasKey("fil")); // From kExtraLanguages. + + ASSERT_TRUE(list->GetDictionary(1, &entry)); + ASSERT_TRUE(entry->GetString("id", &input_method_id)); + ASSERT_TRUE(entry->GetString("displayName", &display_name)); + ASSERT_TRUE(entry->GetDictionary("languageCodeSet", &language_code_set)); + EXPECT_EQ("xkb:fr::fra", input_method_id); + // Commented out. See above. + // EXPECT_EQ("French keyboard layout", display_name); + ASSERT_TRUE(language_code_set->HasKey("fr")); + + ASSERT_TRUE(list->GetDictionary(2, &entry)); + ASSERT_TRUE(entry->GetString("id", &input_method_id)); + ASSERT_TRUE(entry->GetString("displayName", &display_name)); + ASSERT_TRUE(entry->GetDictionary("languageCodeSet", &language_code_set)); + EXPECT_EQ("xkb:be::fra", input_method_id); + // Commented out. See above. + // EXPECT_EQ("Belgian keyboard layout", display_name); + ASSERT_TRUE(language_code_set->HasKey("fr")); + + ASSERT_TRUE(list->GetDictionary(3, &entry)); + ASSERT_TRUE(entry->GetString("id", &input_method_id)); + ASSERT_TRUE(entry->GetString("displayName", &display_name)); + ASSERT_TRUE(entry->GetDictionary("languageCodeSet", &language_code_set)); + EXPECT_EQ("mozc", input_method_id); + // Commented out. See above. + // EXPECT_EQ("Japanese input method (for US keyboard)", display_name); + ASSERT_TRUE(language_code_set->HasKey("ja")); +} + +TEST(LanguageOptionsHandlerTest, GetLanguageList) { + InputMethodDescriptors descriptors = CreateInputMethodDescriptors(); + scoped_ptr<ListValue> list( + chromeos::CrosLanguageOptionsHandler::GetLanguageList(descriptors)); + ASSERT_EQ(8U, list->GetSize()); + + DictionaryValue* entry = NULL; + std::string language_code; + std::string display_name; + std::string native_display_name; + + // As shown below, the list should be sorted by the display names, + // and these names should not have duplicates. + + // This comes from kExtraLanguages. + ASSERT_TRUE(list->GetDictionary(0, &entry)); + ASSERT_TRUE(entry->GetString("code", &language_code)); + ASSERT_TRUE(entry->GetString("displayName", &display_name)); + ASSERT_TRUE(entry->GetString("nativeDisplayName", &native_display_name)); + EXPECT_EQ("nl", language_code); + EXPECT_EQ("Dutch", display_name); + EXPECT_EQ("Nederlands", native_display_name); + + // This comes from kExtraLanguages. + ASSERT_TRUE(list->GetDictionary(1, &entry)); + ASSERT_TRUE(entry->GetString("code", &language_code)); + ASSERT_TRUE(entry->GetString("displayName", &display_name)); + ASSERT_TRUE(entry->GetString("nativeDisplayName", &native_display_name)); + EXPECT_EQ("en-AU", language_code); + EXPECT_EQ("English (Australia)", display_name); + EXPECT_EQ("English (Australia)", native_display_name); + + ASSERT_TRUE(list->GetDictionary(2, &entry)); + ASSERT_TRUE(entry->GetString("code", &language_code)); + ASSERT_TRUE(entry->GetString("displayName", &display_name)); + ASSERT_TRUE(entry->GetString("nativeDisplayName", &native_display_name)); + EXPECT_EQ("en-US", language_code); + EXPECT_EQ("English (United States)", display_name); + EXPECT_EQ("English (United States)", native_display_name); + + // This comes from kExtraLanguages. + ASSERT_TRUE(list->GetDictionary(3, &entry)); + ASSERT_TRUE(entry->GetString("code", &language_code)); + ASSERT_TRUE(entry->GetString("displayName", &display_name)); + ASSERT_TRUE(entry->GetString("nativeDisplayName", &native_display_name)); + EXPECT_EQ("fil", language_code); + EXPECT_EQ("Filipino", display_name); + EXPECT_EQ("Filipino", native_display_name); + + ASSERT_TRUE(list->GetDictionary(4, &entry)); + ASSERT_TRUE(entry->GetString("code", &language_code)); + ASSERT_TRUE(entry->GetString("displayName", &display_name)); + ASSERT_TRUE(entry->GetString("nativeDisplayName", &native_display_name)); + EXPECT_EQ("fr", language_code); + EXPECT_EQ("French", display_name); + EXPECT_EQ("fran\u00E7ais", native_display_name); + + // This comes from kExtraLanguages. + ASSERT_TRUE(list->GetDictionary(5, &entry)); + ASSERT_TRUE(entry->GetString("code", &language_code)); + ASSERT_TRUE(entry->GetString("displayName", &display_name)); + ASSERT_TRUE(entry->GetString("nativeDisplayName", &native_display_name)); + EXPECT_EQ("id", language_code); + EXPECT_EQ("Indonesian", display_name); + EXPECT_EQ("Bahasa Indonesia", native_display_name); + + ASSERT_TRUE(list->GetDictionary(6, &entry)); + ASSERT_TRUE(entry->GetString("code", &language_code)); + ASSERT_TRUE(entry->GetString("displayName", &display_name)); + ASSERT_TRUE(entry->GetString("nativeDisplayName", &native_display_name)); + EXPECT_EQ("ja", language_code); + EXPECT_EQ("Japanese", display_name); + EXPECT_EQ("\u65E5\u672C\u8A9E", native_display_name); + + // This comes from kExtraLanguages. + ASSERT_TRUE(list->GetDictionary(7, &entry)); + ASSERT_TRUE(entry->GetString("code", &language_code)); + ASSERT_TRUE(entry->GetString("displayName", &display_name)); + ASSERT_TRUE(entry->GetString("nativeDisplayName", &native_display_name)); + EXPECT_EQ("es-419", language_code); + EXPECT_EQ("Spanish (Latin America)", display_name); + EXPECT_EQ("espa\u00F1ol (Latinoam\u00E9rica)", native_display_name); +} +#endif // defined(OS_CHROMEOS) + +#if !defined(OS_MACOSX) +TEST(LanguageOptionsHandlerTest, GetUILanguageCodeSet) { + scoped_ptr<DictionaryValue> dictionary( + LanguageOptionsHandler::GetUILanguageCodeSet()); + EXPECT_TRUE(dictionary->HasKey("en-US")); + // Note that we don't test a false case, as such an expectation will + // fail when we add support for the language. + // EXPECT_FALSE(dictionary->HasKey("no")); +} +#endif // !defined(OS_MACOSX) + +TEST(LanguageOptionsHandlerTest, GetSpellCheckLanguageCodeSet) { + scoped_ptr<DictionaryValue> dictionary( + LanguageOptionsHandler::GetSpellCheckLanguageCodeSet()); + EXPECT_TRUE(dictionary->HasKey("en-US")); +} diff --git a/chrome/browser/ui/webui/options2/manage_profile_handler.cc b/chrome/browser/ui/webui/options2/manage_profile_handler.cc new file mode 100644 index 0000000..4c29788 --- /dev/null +++ b/chrome/browser/ui/webui/options2/manage_profile_handler.cc @@ -0,0 +1,300 @@ +// Copyright (c) 2011 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. + +#include "chrome/browser/ui/webui/options2/manage_profile_handler.h" + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/string_number_conversions.h" +#include "base/utf_string_conversions.h" +#include "base/value_conversions.h" +#include "base/values.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/profiles/gaia_info_update_service.h" +#include "chrome/browser/profiles/profile_info_cache.h" +#include "chrome/browser/profiles/profile_info_util.h" +#include "chrome/browser/profiles/profile_manager.h" +#include "chrome/browser/profiles/profile_metrics.h" +#include "chrome/browser/ui/webui/web_ui_util.h" +#include "chrome/common/chrome_notification_types.h" +#include "content/browser/tab_contents/tab_contents.h" +#include "content/public/browser/notification_service.h" +#include "grit/generated_resources.h" + +ManageProfileHandler::ManageProfileHandler() { +} + +ManageProfileHandler::~ManageProfileHandler() { +} + +void ManageProfileHandler::GetLocalizedValues( + DictionaryValue* localized_strings) { + DCHECK(localized_strings); + + static OptionsStringResource resources[] = { + { "manageProfilesTitle", IDS_PROFILES_MANAGE_TITLE }, + { "manageProfilesNameLabel", IDS_PROFILES_MANAGE_NAME_LABEL }, + { "manageProfilesDuplicateNameError", + IDS_PROFILES_MANAGE_DUPLICATE_NAME_ERROR }, + { "manageProfilesIconLabel", IDS_PROFILES_MANAGE_ICON_LABEL }, + { "deleteProfileTitle", IDS_PROFILES_DELETE_TITLE }, + { "deleteProfileOK", IDS_PROFILES_DELETE_OK_BUTTON_LABEL }, + { "deleteProfileMessage", IDS_PROFILES_DELETE_MESSAGE }, + }; + + RegisterStrings(localized_strings, resources, arraysize(resources)); +} + +void ManageProfileHandler::Initialize() { + registrar_.Add(this, chrome::NOTIFICATION_PROFILE_CACHED_INFO_CHANGED, + content::NotificationService::AllSources()); + SendProfileNames(); +} + +void ManageProfileHandler::RegisterMessages() { + web_ui_->RegisterMessageCallback("setProfileNameAndIcon", + base::Bind(&ManageProfileHandler::SetProfileNameAndIcon, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("deleteProfile", + base::Bind(&ManageProfileHandler::DeleteProfile, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("requestDefaultProfileIcons", + base::Bind(&ManageProfileHandler::RequestDefaultProfileIcons, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("requestProfileInfo", + base::Bind(&ManageProfileHandler::RequestProfileInfo, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("profileIconSelectionChanged", + base::Bind(&ManageProfileHandler::ProfileIconSelectionChanged, + base::Unretained(this))); +} + +void ManageProfileHandler::Observe( + int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) { + if (type == chrome::NOTIFICATION_PROFILE_CACHED_INFO_CHANGED) { + SendProfileNames(); + SendProfileIcons(); + } else { + OptionsPage2UIHandler::Observe(type, source, details); + } +} + +void ManageProfileHandler::RequestDefaultProfileIcons(const ListValue* args) { + SendProfileIcons(); +} + +void ManageProfileHandler::SendProfileIcons() { + ListValue image_url_list; + + // First add the GAIA picture if it's available. + ProfileInfoCache& cache = + g_browser_process->profile_manager()->GetProfileInfoCache(); + Profile* profile = Profile::FromWebUI(web_ui_); + size_t profile_index = cache.GetIndexOfProfileWithPath(profile->GetPath()); + if (profile_index != std::string::npos) { + const gfx::Image* icon = + cache.GetGAIAPictureOfProfileAtIndex(profile_index); + if (icon) { + gfx::Image icon2 = profiles::GetAvatarIconForWebUI(*icon, true); + gaia_picture_url_ = web_ui_util::GetImageDataUrl(icon2); + image_url_list.Append(Value::CreateStringValue(gaia_picture_url_)); + } + } + + // Next add the default avatar icons. + for (size_t i = 0; i < ProfileInfoCache::GetDefaultAvatarIconCount(); i++) { + std::string url = ProfileInfoCache::GetDefaultAvatarIconUrl(i); + image_url_list.Append(Value::CreateStringValue(url)); + } + + web_ui_->CallJavascriptFunction( + "ManageProfileOverlay.receiveDefaultProfileIcons", + image_url_list); +} + +void ManageProfileHandler::SendProfileNames() { + ProfileInfoCache& cache = + g_browser_process->profile_manager()->GetProfileInfoCache(); + DictionaryValue profile_name_dict; + for (size_t i = 0, e = cache.GetNumberOfProfiles(); i < e; ++i) + profile_name_dict.SetBoolean(UTF16ToUTF8(cache.GetNameOfProfileAtIndex(i)), + true); + + web_ui_->CallJavascriptFunction("ManageProfileOverlay.receiveProfileNames", + profile_name_dict); +} + +void ManageProfileHandler::SetProfileNameAndIcon(const ListValue* args) { + DCHECK(args); + + Value* file_path_value; + FilePath profile_file_path; + if (!args->Get(0, &file_path_value) || + !base::GetValueAsFilePath(*file_path_value, &profile_file_path)) + return; + + ProfileInfoCache& cache = + g_browser_process->profile_manager()->GetProfileInfoCache(); + size_t profile_index = cache.GetIndexOfProfileWithPath(profile_file_path); + if (profile_index == std::string::npos) + return; + + string16 new_profile_name; + if (!args->GetString(1, &new_profile_name)) + return; + + if (new_profile_name == cache.GetGAIANameOfProfileAtIndex(profile_index)) { + // Set the profile to use the GAIA name as the profile name. Note, this + // is a little weird if the user typed their GAIA name manually but + // it's not a big deal. + cache.SetIsUsingGAIANameOfProfileAtIndex(profile_index, true); + // Using the GAIA name as the profile name can invalidate the profile index. + profile_index = cache.GetIndexOfProfileWithPath(profile_file_path); + if (profile_index == std::string::npos) + return; + } else { + cache.SetNameOfProfileAtIndex(profile_index, new_profile_name); + // Changing the profile name can invalidate the profile index. + profile_index = cache.GetIndexOfProfileWithPath(profile_file_path); + if (profile_index == std::string::npos) + return; + + cache.SetIsUsingGAIANameOfProfileAtIndex(profile_index, false); + // Unsetting the GAIA name as the profile name can invalidate the profile + // index. + profile_index = cache.GetIndexOfProfileWithPath(profile_file_path); + if (profile_index == std::string::npos) + return; + } + + std::string icon_url; + if (!args->GetString(2, &icon_url)) + return; + + // Metrics logging variable. + bool previously_using_gaia_icon = + cache.IsUsingGAIANameOfProfileAtIndex(profile_index); + + size_t new_icon_index; + if (icon_url == gaia_picture_url_) { + cache.SetIsUsingGAIAPictureOfProfileAtIndex(profile_index, true); + if (!previously_using_gaia_icon) { + // Only log if they changed to the GAIA photo. + // Selection of GAIA photo as avatar is logged as part of the function + // below. + ProfileMetrics::LogProfileSwitchGaia(ProfileMetrics::GAIA_OPT_IN); + } + } else if (cache.IsDefaultAvatarIconUrl(icon_url, &new_icon_index)) { + ProfileMetrics::LogProfileAvatarSelection(new_icon_index); + cache.SetAvatarIconOfProfileAtIndex(profile_index, new_icon_index); + cache.SetIsUsingGAIAPictureOfProfileAtIndex(profile_index, false); + } + + ProfileMetrics::LogProfileUpdate(profile_file_path); +} + +void ManageProfileHandler::DeleteProfile(const ListValue* args) { + DCHECK(args); + + ProfileMetrics::LogProfileDeleteUser(ProfileMetrics::PROFILE_DELETED); + + Value* file_path_value; + FilePath profile_file_path; + if (!args->Get(0, &file_path_value) || + !base::GetValueAsFilePath(*file_path_value, &profile_file_path)) + return; + + g_browser_process->profile_manager()->ScheduleProfileForDeletion( + profile_file_path); +} + +void ManageProfileHandler::RequestProfileInfo(const ListValue* args) { + DCHECK(args); + + Value* index_value; + double index_double; + if (!args->Get(0, &index_value) || !index_value->GetAsDouble(&index_double)) + return; + + int index = static_cast<int>(index_double); + ProfileInfoCache& cache = + g_browser_process->profile_manager()->GetProfileInfoCache(); + int profile_count = cache.GetNumberOfProfiles(); + if (index < 0 && index >= profile_count) + return; + + FilePath profile_path = cache.GetPathOfProfileAtIndex(index); + FilePath current_profile_path = Profile::FromWebUI(web_ui_)->GetPath(); + bool is_current_profile = + profile_path == Profile::FromWebUI(web_ui_)->GetPath(); + + DictionaryValue profile_value; + profile_value.SetString("name", cache.GetNameOfProfileAtIndex(index)); + profile_value.Set("filePath", base::CreateFilePathValue(profile_path)); + profile_value.SetBoolean("isCurrentProfile", is_current_profile); + + bool is_gaia_picture = + cache.IsUsingGAIAPictureOfProfileAtIndex(index) && + cache.GetGAIAPictureOfProfileAtIndex(index); + if (is_gaia_picture) { + gfx::Image icon = profiles::GetAvatarIconForWebUI( + cache.GetAvatarIconOfProfileAtIndex(index), true); + profile_value.SetString("iconURL", web_ui_util::GetImageDataUrl(icon)); + } else { + size_t icon_index = cache.GetAvatarIconIndexOfProfileAtIndex(index); + profile_value.SetString("iconURL", + cache.GetDefaultAvatarIconUrl(icon_index)); + } + + web_ui_->CallJavascriptFunction("ManageProfileOverlay.setProfileInfo", + profile_value); + + // Ensure that we have the most up to date GAIA picture. + if (is_current_profile) { + GAIAInfoUpdateService* service = + Profile::FromWebUI(web_ui_)->GetGAIAInfoUpdateService(); + if (service) + service->Update(); + } +} + +void ManageProfileHandler::ProfileIconSelectionChanged( + const base::ListValue* args) { + DCHECK(args); + + Value* file_path_value; + FilePath file_path; + if (!args->Get(0, &file_path_value) || + !base::GetValueAsFilePath(*file_path_value, &file_path)) { + return; + } + + // Currently this only supports editing the current profile's info. + if (file_path != Profile::FromWebUI(web_ui_)->GetPath()) + return; + + std::string icon_url; + if (!args->GetString(1, &icon_url)) + return; + + if (icon_url != gaia_picture_url_) + return; + + // If the selection is the GAIA picture then also show the GAIA name in the + // text field. + ProfileInfoCache& cache = + g_browser_process->profile_manager()->GetProfileInfoCache(); + size_t i = cache.GetIndexOfProfileWithPath(file_path); + if (i == std::string::npos) + return; + string16 gaia_name = cache.GetGAIANameOfProfileAtIndex(i); + if (gaia_name.empty()) + return; + + StringValue gaia_name_value(gaia_name); + web_ui_->CallJavascriptFunction("ManageProfileOverlay.setProfileName", + gaia_name_value); +} diff --git a/chrome/browser/ui/webui/options2/manage_profile_handler.h b/chrome/browser/ui/webui/options2/manage_profile_handler.h new file mode 100644 index 0000000..8e79f6a --- /dev/null +++ b/chrome/browser/ui/webui/options2/manage_profile_handler.h @@ -0,0 +1,82 @@ +// Copyright (c) 2011 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. + +#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS2_MANAGE_PROFILE_HANDLER_H_ +#define CHROME_BROWSER_UI_WEBUI_OPTIONS2_MANAGE_PROFILE_HANDLER_H_ +#pragma once + +#include "chrome/browser/ui/webui/options2/options_ui2.h" + +// Chrome personal stuff profiles manage overlay UI handler. +class ManageProfileHandler : public OptionsPage2UIHandler { + public: + ManageProfileHandler(); + virtual ~ManageProfileHandler(); + + // OptionsPage2UIHandler: + virtual void GetLocalizedValues( + base::DictionaryValue* localized_strings) OVERRIDE; + virtual void Initialize() OVERRIDE; + + // WebUIMessageHandler: + virtual void RegisterMessages() OVERRIDE; + + // content::NotificationObserver: + virtual void Observe(int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) OVERRIDE; + + private: + // Callback for the "requestDefaultProfileIcons" message. + // Sends the array of default profile icon URLs to WebUI. + // |args| is of the form: [ {string} iconURL ] + void RequestDefaultProfileIcons(const base::ListValue* args); + + // Sends an object to WebUI of the form: + // profileNames = { + // "Profile Name 1": true, + // "Profile Name 2": true, + // ... + // }; + // This is used to detect duplicate profile names. + void SendProfileNames(); + + // Callback for the "setProfileNameAndIcon" message. Sets the name and icon + // of a given profile. + // |args| is of the form: [ + // /*string*/ profileFilePath, + // /*string*/ newProfileName, + // /*string*/ newProfileIconURL + // ] + void SetProfileNameAndIcon(const base::ListValue* args); + + // Callback for the "deleteProfile" message. Deletes the given profile. + // |args| is of the form: [ {string} profileFilePath ] + void DeleteProfile(const base::ListValue* args); + + // Callback for the "requestProfileInfo" message. + // Given |args| of the form: [ {number} profileIndex ] + // Sends an object to WebUI of the form: + // profileInfo = { + // name: "Profile Name", + // iconURL: "chrome://path/to/icon/image", + // filePath: "/path/to/profile/data/on/disk" + // isCurrentProfile: false, + // }; + void RequestProfileInfo(const base::ListValue* args); + + // Callback for the 'profileIconSelectionChanged' message. Used to update the + // name in the manager profile dialog based on the selected icon. + void ProfileIconSelectionChanged(const base::ListValue* args); + + // Send all profile icons to the overlay. + void SendProfileIcons(); + + // URL for the current profile's GAIA picture. + std::string gaia_picture_url_; + + DISALLOW_COPY_AND_ASSIGN(ManageProfileHandler); +}; + +#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS2_MANAGE_PROFILE_HANDLER_H_ diff --git a/chrome/browser/ui/webui/options2/options_browsertest.js b/chrome/browser/ui/webui/options2/options_browsertest.js new file mode 100644 index 0000000..9be9041 --- /dev/null +++ b/chrome/browser/ui/webui/options2/options_browsertest.js @@ -0,0 +1,93 @@ +// Copyright (c) 2011 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. + +/** + * TestFixture for OptionsPage WebUI testing. + * @extends {testing.Test} + * @constructor + */ +function OptionsWebUITest() {} + +OptionsWebUITest.prototype = { + __proto__: testing.Test.prototype, + + /** + * Browse to the options page & call our preLoad(). + */ + browsePreload: 'chrome://settings', + + /** + * Register a mock handler to ensure expectations are met and options pages + * behave correctly. + */ + preLoad: function() { + this.makeAndRegisterMockHandler( + ['coreOptionsInitialize', + 'fetchPrefs', + 'observePrefs', + 'setBooleanPref', + 'setIntegerPref', + 'setDoublePref', + 'setStringPref', + 'setObjectPref', + 'clearPref', + 'coreOptionsUserMetricsAction', + // TODO(scr): Handle this new message: + // getInstantFieldTrialStatus: function() {}, + ]); + + // Register stubs for methods expected to be called before our tests run. + // Specific expectations can be made in the tests themselves. + this.mockHandler.stubs().fetchPrefs(ANYTHING); + this.mockHandler.stubs().observePrefs(ANYTHING); + this.mockHandler.stubs().coreOptionsInitialize(); + }, +}; + +// Crashes on Mac only. See http://crbug.com/79181 +GEN('#if defined(OS_MACOSX)'); +GEN('#define MAYBE_testSetBooleanPrefTriggers ' + + 'DISABLED_testSetBooleanPrefTriggers'); +GEN('#else'); +GEN('#define MAYBE_testSetBooleanPrefTriggers testSetBooleanPrefTriggers'); +GEN('#endif // defined(OS_MACOSX)'); + +TEST_F('OptionsWebUITest', 'MAYBE_testSetBooleanPrefTriggers', function() { + // TODO(dtseng): make generic to click all buttons. + var showHomeButton = $('toolbarShowHomeButton'); + var trueListValue = [ + 'browser.show_home_button', + true, + 'Options_Homepage_HomeButton', + ]; + // Note: this expectation is checked in testing::Test::tearDown. + this.mockHandler.expects(once()).setBooleanPref(trueListValue); + + // Cause the handler to be called. + showHomeButton.click(); + showHomeButton.blur(); +}); + +// Not meant to run on ChromeOS at this time. +// Not finishing in windows. http://crbug.com/81723 +GEN('#if defined(OS_CHROMEOS) || defined(OS_MACOSX) || defined(OS_WIN)'); +GEN('#define MAYBE_testRefreshStaysOnCurrentPage \\'); +GEN(' DISABLED_testRefreshStaysOnCurrentPage'); +GEN('#else'); +GEN('#define MAYBE_testRefreshStaysOnCurrentPage ' + + 'testRefreshStaysOnCurrentPage'); +GEN('#endif'); + +TEST_F('OptionsWebUITest', 'MAYBE_testRefreshStaysOnCurrentPage', function() { + var item = $('advancedPageNav'); + item.onclick(); + window.location.reload(); + var pageInstance = AdvancedOptions.getInstance(); + var topPage = OptionsPage.getTopmostVisiblePage(); + var expectedTitle = pageInstance.title; + var actualTitle = document.title; + assertEquals("chrome://settings/advanced", document.location.href); + assertEquals(expectedTitle, actualTitle); + assertEquals(pageInstance, topPage); +}); diff --git a/chrome/browser/ui/webui/options2/options_sync_setup_handler.cc b/chrome/browser/ui/webui/options2/options_sync_setup_handler.cc new file mode 100644 index 0000000..81e114e --- /dev/null +++ b/chrome/browser/ui/webui/options2/options_sync_setup_handler.cc @@ -0,0 +1,55 @@ +// Copyright (c) 2011 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. + +#include "chrome/browser/ui/webui/options2/options_sync_setup_handler.h" + +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/sync/profile_sync_service.h" + +OptionsSyncSetupHandler::OptionsSyncSetupHandler( + ProfileManager* profile_manager) : SyncSetupHandler2(profile_manager) { +} + +OptionsSyncSetupHandler::~OptionsSyncSetupHandler() { +} + +void OptionsSyncSetupHandler::StepWizardForShowSetupUI() { + ProfileSyncService* service = + Profile::FromWebUI(web_ui_)->GetProfileSyncService(); + DCHECK(service); + + // We should bring up either a login or a configure flow based on the state of + // sync. + if (service->HasSyncSetupCompleted()) { + if (service->IsPassphraseRequiredForDecryption()) { + service->get_wizard().Step(SyncSetupWizard::ENTER_PASSPHRASE); + } else { + service->get_wizard().Step(SyncSetupWizard::CONFIGURE); + } + } else { + service->get_wizard().Step(SyncSetupWizard::GetLoginState()); + } +} + +void OptionsSyncSetupHandler::ShowSetupUI() { + ProfileSyncService* service = + Profile::FromWebUI(web_ui_)->GetProfileSyncService(); + DCHECK(service); + + // The user is trying to manually load a syncSetup URL. We should bring up + // either a login or a configure flow based on the state of sync. + if (service->HasSyncSetupCompleted()) { + if (service->IsPassphraseRequiredForDecryption()) { + service->get_wizard().Step(SyncSetupWizard::ENTER_PASSPHRASE); + } else { + service->get_wizard().Step(SyncSetupWizard::CONFIGURE); + } + } else { + service->get_wizard().Step(SyncSetupWizard::GetLoginState()); + } + + // Show the Sync Setup page. + scoped_ptr<Value> page(Value::CreateStringValue("syncSetup")); + web_ui_->CallJavascriptFunction("OptionsPage.navigateToPage", *page); +} diff --git a/chrome/browser/ui/webui/options2/options_sync_setup_handler.h b/chrome/browser/ui/webui/options2/options_sync_setup_handler.h new file mode 100644 index 0000000..a61df17 --- /dev/null +++ b/chrome/browser/ui/webui/options2/options_sync_setup_handler.h @@ -0,0 +1,22 @@ +// Copyright (c) 2011 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. + +#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS2_OPTIONS_SYNC_SETUP_HANDLER_H_ +#define CHROME_BROWSER_UI_WEBUI_OPTIONS2_OPTIONS_SYNC_SETUP_HANDLER_H_ + +#include "chrome/browser/ui/webui/sync_setup_handler2.h" + +// The handler for Javascript messages related to sync setup UI in the options +// page. +class OptionsSyncSetupHandler : public SyncSetupHandler2 { + public: + explicit OptionsSyncSetupHandler(ProfileManager* profile_manager); + virtual ~OptionsSyncSetupHandler(); + + protected: + virtual void StepWizardForShowSetupUI() OVERRIDE; + virtual void ShowSetupUI() OVERRIDE; +}; + +#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS2_OPTIONS_SYNC_SETUP_HANDLER_H_ diff --git a/chrome/browser/ui/webui/options2/options_ui2.cc b/chrome/browser/ui/webui/options2/options_ui2.cc new file mode 100644 index 0000000..879361a --- /dev/null +++ b/chrome/browser/ui/webui/options2/options_ui2.cc @@ -0,0 +1,368 @@ +// Copyright (c) 2011 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. + +#include "chrome/browser/ui/webui/options2/options_ui2.h" + +#include <algorithm> +#include <vector> + +#include "base/callback.h" +#include "base/command_line.h" +#include "base/memory/singleton.h" +#include "base/message_loop.h" +#include "base/string_piece.h" +#include "base/string_util.h" +#include "base/threading/thread.h" +#include "base/time.h" +#include "base/values.h" +#include "chrome/browser/browser_about_handler.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/ui/webui/options2/advanced_options_handler.h" +#include "chrome/browser/ui/webui/options2/autofill_options_handler.h" +#include "chrome/browser/ui/webui/options2/browser_options_handler.h" +#include "chrome/browser/ui/webui/options2/clear_browser_data_handler.h" +#include "chrome/browser/ui/webui/options2/content_settings_handler.h" +#include "chrome/browser/ui/webui/options2/cookies_view_handler.h" +#include "chrome/browser/ui/webui/options2/core_options_handler.h" +#include "chrome/browser/ui/webui/options2/extension_settings_handler.h" +#include "chrome/browser/ui/webui/options2/font_settings_handler.h" +#include "chrome/browser/ui/webui/options2/handler_options_handler.h" +#include "chrome/browser/ui/webui/options2/import_data_handler.h" +#include "chrome/browser/ui/webui/options2/language_options_handler.h" +#include "chrome/browser/ui/webui/options2/manage_profile_handler.h" +#include "chrome/browser/ui/webui/options2/options_sync_setup_handler.h" +#include "chrome/browser/ui/webui/options2/pack_extension_handler.h" +#include "chrome/browser/ui/webui/options2/password_manager_handler.h" +#include "chrome/browser/ui/webui/options2/personal_options_handler.h" +#include "chrome/browser/ui/webui/options2/search_engine_manager_handler.h" +#include "chrome/browser/ui/webui/options2/stop_syncing_handler.h" +#include "chrome/browser/ui/webui/options2/web_intents_settings_handler.h" +#include "chrome/browser/ui/webui/theme_source.h" +#include "chrome/common/jstemplate_builder.h" +#include "chrome/common/time_format.h" +#include "chrome/common/url_constants.h" +#include "content/browser/renderer_host/render_view_host.h" +#include "content/browser/tab_contents/tab_contents.h" +#include "content/browser/tab_contents/tab_contents_delegate.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/notification_types.h" +#include "grit/chromium_strings.h" +#include "grit/generated_resources.h" +#include "grit/locale_settings.h" +#include "grit/options2_resources.h" +#include "grit/theme_resources.h" +#include "grit/theme_resources_standard.h" +#include "net/base/escape.h" +#include "ui/base/resource/resource_bundle.h" + +#if defined(OS_CHROMEOS) +#include "chrome/browser/ui/webui/options2/chromeos/about_page_handler.h" +#include "chrome/browser/ui/webui/options2/chromeos/accounts_options_handler.h" +#include "chrome/browser/ui/webui/options2/chromeos/bluetooth_options_handler.h" +#include "chrome/browser/ui/webui/options2/chromeos/change_picture_options_handler.h" +#include "chrome/browser/ui/webui/options2/chromeos/core_chromeos_options_handler.h" +#include "chrome/browser/ui/webui/options2/chromeos/cros_language_options_handler.h" +#include "chrome/browser/ui/webui/options2/chromeos/internet_options_handler.h" +#include "chrome/browser/ui/webui/options2/chromeos/language_chewing_handler.h" +#include "chrome/browser/ui/webui/options2/chromeos/language_customize_modifier_keys_handler.h" +#include "chrome/browser/ui/webui/options2/chromeos/language_hangul_handler.h" +#include "chrome/browser/ui/webui/options2/chromeos/language_mozc_handler.h" +#include "chrome/browser/ui/webui/options2/chromeos/language_pinyin_handler.h" +#include "chrome/browser/ui/webui/options2/chromeos/proxy_handler.h" +#include "chrome/browser/ui/webui/options2/chromeos/stats_options_handler.h" +#include "chrome/browser/ui/webui/options2/chromeos/system_options_handler.h" +#include "chrome/browser/ui/webui/options2/chromeos/user_image_source.h" +#include "chrome/browser/ui/webui/options2/chromeos/virtual_keyboard_manager_handler.h" +#endif + +#if defined(USE_NSS) +#include "chrome/browser/ui/webui/options2/certificate_manager_handler.h" +#endif + +static const char kLocalizedStringsFile[] = "strings.js"; +static const char kOptionsBundleJsFile[] = "options_bundle.js"; + +//////////////////////////////////////////////////////////////////////////////// +// +// Options2UIHTMLSource +// +//////////////////////////////////////////////////////////////////////////////// + +class Options2UIHTMLSource : public ChromeURLDataManager::DataSource { + public: + // The constructor takes over ownership of |localized_strings|. + explicit Options2UIHTMLSource(DictionaryValue* localized_strings); + virtual ~Options2UIHTMLSource(); + + // Called when the network layer has requested a resource underneath + // the path we registered. + virtual void StartDataRequest(const std::string& path, + bool is_incognito, + int request_id); + virtual std::string GetMimeType(const std::string&) const; + + private: + // Localized strings collection. + scoped_ptr<DictionaryValue> localized_strings_; + + DISALLOW_COPY_AND_ASSIGN(Options2UIHTMLSource); +}; + +Options2UIHTMLSource::Options2UIHTMLSource(DictionaryValue* localized_strings) + : DataSource(chrome::kChromeUISettingsFrameHost, MessageLoop::current()) { + DCHECK(localized_strings); + localized_strings_.reset(localized_strings); +} + +Options2UIHTMLSource::~Options2UIHTMLSource() {} + +void Options2UIHTMLSource::StartDataRequest(const std::string& path, + bool is_incognito, + int request_id) { + scoped_refptr<RefCountedMemory> response_bytes; + SetFontAndTextDirection(localized_strings_.get()); + + if (path == kLocalizedStringsFile) { + // Return dynamically-generated strings from memory. + std::string strings_js; + jstemplate_builder::AppendJsonJS(localized_strings_.get(), &strings_js); + response_bytes = base::RefCountedString::TakeString(&strings_js); + } else if (path == kOptionsBundleJsFile) { + // Return (and cache) the options javascript code. + response_bytes = ResourceBundle::GetSharedInstance().LoadDataResourceBytes( + IDR_OPTIONS2_BUNDLE_JS); + } else { + // Return (and cache) the main options html page as the default. + response_bytes = ResourceBundle::GetSharedInstance().LoadDataResourceBytes( + IDR_OPTIONS2_HTML); + } + + SendResponse(request_id, response_bytes); +} + +std::string Options2UIHTMLSource::GetMimeType(const std::string& path) const { + if (path == kLocalizedStringsFile || path == kOptionsBundleJsFile) + return "application/javascript"; + + return "text/html"; +} + +//////////////////////////////////////////////////////////////////////////////// +// +// OptionsPage2UIHandler +// +//////////////////////////////////////////////////////////////////////////////// + +OptionsPage2UIHandler::OptionsPage2UIHandler() { +} + +OptionsPage2UIHandler::~OptionsPage2UIHandler() { +} + +bool OptionsPage2UIHandler::IsEnabled() { + return true; +} + +// static +void OptionsPage2UIHandler::RegisterStrings( + DictionaryValue* localized_strings, + const OptionsStringResource* resources, + size_t length) { + for (size_t i = 0; i < length; ++i) { + localized_strings->SetString( + resources[i].name, l10n_util::GetStringUTF16(resources[i].id)); + } +} + +void OptionsPage2UIHandler::RegisterTitle(DictionaryValue* localized_strings, + const std::string& variable_name, + int title_id) { + localized_strings->SetString(variable_name, + l10n_util::GetStringUTF16(title_id)); + localized_strings->SetString(variable_name + "TabTitle", + l10n_util::GetStringFUTF16(IDS_OPTIONS_TAB_TITLE, + l10n_util::GetStringUTF16(IDS_SETTINGS_TITLE), + l10n_util::GetStringUTF16(title_id))); +} + +//////////////////////////////////////////////////////////////////////////////// +// +// Options2UI +// +//////////////////////////////////////////////////////////////////////////////// + +Options2UI::Options2UI(TabContents* contents) + : ChromeWebUI(contents), + initialized_handlers_(false) { + DictionaryValue* localized_strings = new DictionaryValue(); + + CoreOptionsHandler* core_handler; +#if defined(OS_CHROMEOS) + core_handler = new chromeos::CoreChromeOSOptionsHandler(); +#else + core_handler = new CoreOptionsHandler(); +#endif + core_handler->set_handlers_host(this); + AddOptionsPageUIHandler(localized_strings, core_handler); + + AddOptionsPageUIHandler(localized_strings, new AdvancedOptionsHandler()); + AddOptionsPageUIHandler(localized_strings, new AutofillOptionsHandler()); + AddOptionsPageUIHandler(localized_strings, new BrowserOptionsHandler()); + AddOptionsPageUIHandler(localized_strings, new ClearBrowserDataHandler()); + AddOptionsPageUIHandler(localized_strings, new ContentSettingsHandler()); + AddOptionsPageUIHandler(localized_strings, new CookiesViewHandler()); + AddOptionsPageUIHandler(localized_strings, new ExtensionSettingsHandler()); + AddOptionsPageUIHandler(localized_strings, new FontSettingsHandler()); + AddOptionsPageUIHandler(localized_strings, new WebIntentsSettingsHandler()); +#if defined(OS_CHROMEOS) + AddOptionsPageUIHandler(localized_strings, + new chromeos::CrosLanguageOptionsHandler()); +#else + AddOptionsPageUIHandler(localized_strings, new LanguageOptionsHandler()); +#endif + AddOptionsPageUIHandler(localized_strings, new ManageProfileHandler()); + AddOptionsPageUIHandler(localized_strings, new PackExtensionHandler()); + AddOptionsPageUIHandler(localized_strings, new PasswordManagerHandler()); + AddOptionsPageUIHandler(localized_strings, new PersonalOptionsHandler()); + AddOptionsPageUIHandler(localized_strings, new SearchEngineManagerHandler()); + AddOptionsPageUIHandler(localized_strings, new ImportDataHandler()); + AddOptionsPageUIHandler(localized_strings, new StopSyncingHandler()); + AddOptionsPageUIHandler(localized_strings, new OptionsSyncSetupHandler( + g_browser_process->profile_manager())); +#if defined(OS_CHROMEOS) + AddOptionsPageUIHandler(localized_strings, + new chromeos::AboutPageHandler()); + AddOptionsPageUIHandler(localized_strings, + new chromeos::AccountsOptionsHandler()); + AddOptionsPageUIHandler(localized_strings, + new chromeos::BluetoothOptionsHandler()); + AddOptionsPageUIHandler(localized_strings, new InternetOptionsHandler()); + AddOptionsPageUIHandler(localized_strings, + new chromeos::LanguageChewingHandler()); + AddOptionsPageUIHandler(localized_strings, + new chromeos::LanguageCustomizeModifierKeysHandler()); + AddOptionsPageUIHandler(localized_strings, + new chromeos::LanguageHangulHandler()); + AddOptionsPageUIHandler(localized_strings, + new chromeos::LanguageMozcHandler()); + AddOptionsPageUIHandler(localized_strings, + new chromeos::LanguagePinyinHandler()); + AddOptionsPageUIHandler(localized_strings, + new chromeos::VirtualKeyboardManagerHandler()); + AddOptionsPageUIHandler(localized_strings, + new chromeos::ProxyHandler()); + AddOptionsPageUIHandler(localized_strings, + new chromeos::ChangePictureOptionsHandler()); + AddOptionsPageUIHandler(localized_strings, + new chromeos::StatsOptionsHandler()); + AddOptionsPageUIHandler(localized_strings, new SystemOptionsHandler()); +#endif +#if defined(USE_NSS) + AddOptionsPageUIHandler(localized_strings, new CertificateManagerHandler()); +#endif + AddOptionsPageUIHandler(localized_strings, new HandlerOptionsHandler()); + + // |localized_strings| ownership is taken over by this constructor. + Options2UIHTMLSource* html_source = + new Options2UIHTMLSource(localized_strings); + + // Set up the chrome://settings-frame/ source. + Profile* profile = Profile::FromBrowserContext(contents->browser_context()); + profile->GetChromeURLDataManager()->AddDataSource(html_source); + + // Set up the chrome://theme/ source. + ThemeSource* theme = new ThemeSource(profile); + profile->GetChromeURLDataManager()->AddDataSource(theme); + +#if defined(OS_CHROMEOS) + // Set up the chrome://userimage/ source. + chromeos::UserImageSource* user_image_source = + new chromeos::UserImageSource(); + profile->GetChromeURLDataManager()->AddDataSource(user_image_source); +#endif +} + +Options2UI::~Options2UI() { + // Uninitialize all registered handlers. The base class owns them and it will + // eventually delete them. Skip over the generic handler. + for (std::vector<WebUIMessageHandler*>::iterator iter = handlers_.begin() + 1; + iter != handlers_.end(); + ++iter) { + static_cast<OptionsPage2UIHandler*>(*iter)->Uninitialize(); + } +} + +// Override. +void Options2UI::RenderViewCreated(RenderViewHost* render_view_host) { + SetCommandLineString(render_view_host); + ChromeWebUI::RenderViewCreated(render_view_host); +} + +void Options2UI::RenderViewReused(RenderViewHost* render_view_host) { + SetCommandLineString(render_view_host); + ChromeWebUI::RenderViewReused(render_view_host); +} + +void Options2UI::DidBecomeActiveForReusedRenderView() { + // When the renderer is re-used (e.g., for back/forward navigation within + // options), the handlers are torn down and rebuilt, so are no longer + // initialized, but the web page's DOM may remain intact, in which case onload + // won't fire to initilize the handlers. To make sure initialization always + // happens, call reinitializeCore (which is a no-op unless the DOM was already + // initialized). + CallJavascriptFunction("OptionsPage.reinitializeCore"); + + ChromeWebUI::DidBecomeActiveForReusedRenderView(); +} + +// static +RefCountedMemory* Options2UI::GetFaviconResourceBytes() { + return ResourceBundle::GetSharedInstance(). + LoadDataResourceBytes(IDR_SETTINGS_FAVICON); +} + +void Options2UI::InitializeHandlers() { + DCHECK(!GetProfile()->IsOffTheRecord() || Profile::IsGuestSession()); + + // The reinitialize call from DidBecomeActiveForReusedRenderView end up being + // delivered after a new web page DOM has been brought up in an existing + // renderer (due to IPC delays), causing this method to be called twice. If + // that happens, ignore the second call. + if (initialized_handlers_) + return; + initialized_handlers_ = true; + + std::vector<WebUIMessageHandler*>::iterator iter; + // Skip over the generic handler. + for (iter = handlers_.begin() + 1; iter != handlers_.end(); ++iter) { + (static_cast<OptionsPage2UIHandler*>(*iter))->Initialize(); + } +} + +void Options2UI::AddOptionsPageUIHandler(DictionaryValue* localized_strings, + OptionsPage2UIHandler* handler_raw) { + scoped_ptr<OptionsPage2UIHandler> handler(handler_raw); + DCHECK(handler.get()); + // Add only if handler's service is enabled. + if (handler->IsEnabled()) { + handler->GetLocalizedValues(localized_strings); + // Add handler to the list and also pass the ownership. + AddMessageHandler(handler.release()->Attach(this)); + } +} + +void Options2UI::SetCommandLineString(RenderViewHost* render_view_host) { + std::string command_line_string; + +#if defined(OS_WIN) + command_line_string = + WideToASCII(CommandLine::ForCurrentProcess()->GetCommandLineString()); +#else + command_line_string = + CommandLine::ForCurrentProcess()->GetCommandLineString(); +#endif + + render_view_host->SetWebUIProperty("commandLineString", command_line_string); +} diff --git a/chrome/browser/ui/webui/options2/options_ui2.h b/chrome/browser/ui/webui/options2/options_ui2.h new file mode 100644 index 0000000..9a7a759 --- /dev/null +++ b/chrome/browser/ui/webui/options2/options_ui2.h @@ -0,0 +1,111 @@ +// Copyright (c) 2011 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. + +#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS2_OPTIONS_UI_H_ +#define CHROME_BROWSER_UI_WEBUI_OPTIONS2_OPTIONS_UI_H_ +#pragma once + +#include <string> + +#include "base/compiler_specific.h" +#include "base/memory/scoped_ptr.h" +#include "chrome/browser/ui/webui/chrome_url_data_manager.h" +#include "chrome/browser/ui/webui/chrome_web_ui.h" +#include "content/public/browser/notification_observer.h" +#include "content/public/browser/notification_registrar.h" +#include "content/public/browser/notification_types.h" + +// The base class handler of Javascript messages of options pages. +class OptionsPage2UIHandler : public WebUIMessageHandler, + public content::NotificationObserver { + public: + OptionsPage2UIHandler(); + virtual ~OptionsPage2UIHandler(); + + // Is this handler enabled? + virtual bool IsEnabled(); + + // Collects localized strings for options page. + virtual void GetLocalizedValues(base::DictionaryValue* localized_strings) = 0; + + // Initialize the page. Called once the DOM is available for manipulation. + // This will be called only once. + virtual void Initialize() {} + + // Uninitializes the page. Called just before the object is destructed. + virtual void Uninitialize() {} + + // WebUIMessageHandler implementation. + virtual void RegisterMessages() OVERRIDE {} + + // content::NotificationObserver implementation. + virtual void Observe(int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) OVERRIDE {} + + protected: + struct OptionsStringResource { + // The name of the resource in templateData. + const char* name; + // The .grd ID for the resource (IDS_*). + int id; + }; + // A helper for simplifying the process of registering strings in WebUI. + static void RegisterStrings(base::DictionaryValue* localized_strings, + const OptionsStringResource* resources, + size_t length); + + // Registers string resources for a page's header and tab title. + static void RegisterTitle(base::DictionaryValue* localized_strings, + const std::string& variable_name, + int title_id); + + content::NotificationRegistrar registrar_; + + private: + DISALLOW_COPY_AND_ASSIGN(OptionsPage2UIHandler); +}; + +// An interface for common operations that a host of OptionsPage2UIHandlers +// should provide. +class OptionsPage2UIHandlerHost { + public: + virtual void InitializeHandlers() = 0; + + protected: + virtual ~OptionsPage2UIHandlerHost() {} +}; + +// The WebUI for chrome:settings-frame. +class Options2UI : public ChromeWebUI, + public OptionsPage2UIHandlerHost { + public: + explicit Options2UI(TabContents* contents); + virtual ~Options2UI(); + + static RefCountedMemory* GetFaviconResourceBytes(); + + // WebUI implementation. + virtual void RenderViewCreated(RenderViewHost* render_view_host) OVERRIDE; + virtual void RenderViewReused(RenderViewHost* render_view_host) OVERRIDE; + virtual void DidBecomeActiveForReusedRenderView() OVERRIDE; + + // Overridden from OptionsPage2UIHandlerHost: + virtual void InitializeHandlers() OVERRIDE; + + private: + // Adds OptionsPageUiHandler to the handlers list if handler is enabled. + void AddOptionsPageUIHandler(base::DictionaryValue* localized_strings, + OptionsPage2UIHandler* handler); + + // Sets the WebUI CommandLineString property with arguments passed while + // launching chrome. + void SetCommandLineString(RenderViewHost* render_view_host); + + bool initialized_handlers_; + + DISALLOW_COPY_AND_ASSIGN(Options2UI); +}; + +#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS2_OPTIONS_UI_H_ diff --git a/chrome/browser/ui/webui/options2/options_ui_uitest.cc b/chrome/browser/ui/webui/options2/options_ui_uitest.cc new file mode 100644 index 0000000..b292213 --- /dev/null +++ b/chrome/browser/ui/webui/options2/options_ui_uitest.cc @@ -0,0 +1,65 @@ +// Copyright (c) 2011 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. + +#include "chrome/browser/ui/webui/options2/options_ui_uitest.h" + +#include "base/string16.h" +#include "base/utf_string_conversions.h" +#include "chrome/common/url_constants.h" +#include "chrome/test/automation/automation_proxy.h" +#include "chrome/test/automation/browser_proxy.h" +#include "chrome/test/automation/tab_proxy.h" +#include "grit/generated_resources.h" +#include "ui/base/l10n/l10n_util.h" + +OptionsUITest::OptionsUITest() { + dom_automation_enabled_ = true; +} + +void OptionsUITest::NavigateToSettings(scoped_refptr<TabProxy> tab) { + const GURL& url = GURL(chrome::kChromeUISettingsURL); + ASSERT_EQ(AUTOMATION_MSG_NAVIGATION_SUCCESS, + tab->NavigateToURLBlockUntilNavigationsComplete(url, 1)) << url.spec(); +} + +void OptionsUITest::VerifyNavbar(scoped_refptr<TabProxy> tab) { + bool navbar_exist = false; + EXPECT_TRUE(tab->ExecuteAndExtractBool(L"", + L"domAutomationController.send(" + L"!!document.getElementById('navbar'))", &navbar_exist)); + EXPECT_EQ(true, navbar_exist); +} + +void OptionsUITest::VerifyTitle(scoped_refptr<TabProxy> tab) { + std::wstring title; + EXPECT_TRUE(tab->GetTabTitle(&title)); + string16 expected_title = l10n_util::GetStringUTF16(IDS_SETTINGS_TITLE); + EXPECT_NE(WideToUTF16Hack(title).find(expected_title), string16::npos); +} + +void OptionsUITest::VerifySections(scoped_refptr<TabProxy> tab) { +#if defined(OS_CHROMEOS) + const int kExpectedSections = 1 + 7; +#else + const int kExpectedSections = 1 + 4; +#endif + int num_of_sections = 0; + EXPECT_TRUE(tab->ExecuteAndExtractInt(L"", + L"domAutomationController.send(" + L"document.getElementById('navbar').children.length)", &num_of_sections)); + EXPECT_EQ(kExpectedSections, num_of_sections); +} + +TEST_F(OptionsUITest, LoadOptionsByURL) { + scoped_refptr<BrowserProxy> browser(automation()->GetBrowserWindow(0)); + ASSERT_TRUE(browser.get()); + + scoped_refptr<TabProxy> tab = browser->GetActiveTab(); + ASSERT_TRUE(tab.get()); + + NavigateToSettings(tab); + VerifyTitle(tab); + VerifyNavbar(tab); + VerifySections(tab); +} diff --git a/chrome/browser/ui/webui/options2/options_ui_uitest.h b/chrome/browser/ui/webui/options2/options_ui_uitest.h new file mode 100644 index 0000000..3cc6806 --- /dev/null +++ b/chrome/browser/ui/webui/options2/options_ui_uitest.h @@ -0,0 +1,38 @@ +// Copyright (c) 2011 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. + +#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS2_OPTIONS_UI_UITEST_H_ +#define CHROME_BROWSER_UI_WEBUI_OPTIONS2_OPTIONS_UI_UITEST_H_ +#pragma once + +#include "base/memory/scoped_ptr.h" +#include "chrome/test/ui/ui_test.h" + +class TabProxy; + +class OptionsUITest : public UITest { + public: + OptionsUITest(); + + // Navigate to the settings tab and block until complete. + void NavigateToSettings(scoped_refptr<TabProxy> tab); + + // Check navbar's existence. + void VerifyNavbar(scoped_refptr<TabProxy> tab); + + // Verify that the page title is correct. + // The only guarantee we can make about the title of a settings tab is that + // it should contain IDS_SETTINGS_TITLE somewhere. + void VerifyTitle(scoped_refptr<TabProxy> tab); + + // Check section headers in navbar. + // For ChromeOS, there should be 1 + 7: + // Search, Basics, Personal, System, Internet, Under the Hood, + // Users and Extensions. + // For other platforms, there should 1 + 4: + // Search, Basics, Personal, Under the Hood and Extensions. + void VerifySections(scoped_refptr<TabProxy> tab); +}; + +#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS2_OPTIONS_UI_UITEST_H_ diff --git a/chrome/browser/ui/webui/options2/pack_extension_handler.cc b/chrome/browser/ui/webui/options2/pack_extension_handler.cc new file mode 100644 index 0000000..0790482 --- /dev/null +++ b/chrome/browser/ui/webui/options2/pack_extension_handler.cc @@ -0,0 +1,101 @@ +// Copyright (c) 2011 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. + +#include "chrome/browser/ui/webui/options2/pack_extension_handler.h" + +#include "base/bind.h" +#include "base/utf_string_conversions.h" +#include "grit/generated_resources.h" +#include "ui/base/l10n/l10n_util.h" + +PackExtensionHandler::PackExtensionHandler() { +} + +PackExtensionHandler::~PackExtensionHandler() { + if (pack_job_.get()) + pack_job_->ClearClient(); +} + +void PackExtensionHandler::Initialize() { +} + +void PackExtensionHandler::GetLocalizedValues( + DictionaryValue* localized_strings) { + DCHECK(localized_strings); + RegisterTitle(localized_strings, "clearBrowserDataOverlay", + IDS_CLEAR_BROWSING_DATA_TITLE); + + localized_strings->SetString("packExtensionOverlay", + l10n_util::GetStringUTF16(IDS_EXTENSION_PACK_DIALOG_TITLE)); + localized_strings->SetString("packExtensionHeading", + l10n_util::GetStringUTF16(IDS_EXTENSION_PACK_DIALOG_HEADING)); + localized_strings->SetString("packExtensionCommit", + l10n_util::GetStringUTF16(IDS_EXTENSION_PACK_BUTTON)); + localized_strings->SetString("packExtensionRootDir", + l10n_util::GetStringUTF16( + IDS_EXTENSION_PACK_DIALOG_ROOT_DIRECTORY_LABEL)); + localized_strings->SetString("packExtensionPrivateKey", + l10n_util::GetStringUTF16(IDS_EXTENSION_PACK_DIALOG_PRIVATE_KEY_LABEL)); + localized_strings->SetString("packExtensionBrowseButton", + l10n_util::GetStringUTF16(IDS_EXTENSION_PACK_DIALOG_BROWSE)); +} + +void PackExtensionHandler::RegisterMessages() { + // Setup handlers specific to this panel. + web_ui_->RegisterMessageCallback("pack", + base::Bind(&PackExtensionHandler::HandlePackMessage, + base::Unretained(this))); +} + +void PackExtensionHandler::OnPackSuccess(const FilePath& crx_file, + const FilePath& pem_file) { + ListValue results; + web_ui_->CallJavascriptFunction("OptionsPage.closeOverlay", results); + + ShowAlert(UTF16ToUTF8(PackExtensionJob::StandardSuccessMessage(crx_file, + pem_file))); +} + +void PackExtensionHandler::OnPackFailure(const std::string& error) { + ShowAlert(error); +} + +void PackExtensionHandler::HandlePackMessage(const ListValue* args) { + std::string extension_path; + std::string private_key_path; + CHECK_EQ(2U, args->GetSize()); + CHECK(args->GetString(0, &extension_path)); + CHECK(args->GetString(1, &private_key_path)); + + FilePath root_directory = + FilePath::FromWStringHack(UTF8ToWide(extension_path)); + FilePath key_file = FilePath::FromWStringHack(UTF8ToWide(private_key_path)); + + if (root_directory.empty()) { + if (extension_path.empty()) { + ShowAlert(l10n_util::GetStringUTF8( + IDS_EXTENSION_PACK_DIALOG_ERROR_ROOT_REQUIRED)); + } else { + ShowAlert(l10n_util::GetStringUTF8( + IDS_EXTENSION_PACK_DIALOG_ERROR_ROOT_INVALID)); + } + + return; + } + + if (!private_key_path.empty() && key_file.empty()) { + ShowAlert(l10n_util::GetStringUTF8( + IDS_EXTENSION_PACK_DIALOG_ERROR_KEY_INVALID)); + return; + } + + pack_job_ = new PackExtensionJob(this, root_directory, key_file); + pack_job_->Start(); +} + +void PackExtensionHandler::ShowAlert(const std::string& message) { + ListValue arguments; + arguments.Append(Value::CreateStringValue(message)); + web_ui_->CallJavascriptFunction("alert", arguments); +} diff --git a/chrome/browser/ui/webui/options2/pack_extension_handler.h b/chrome/browser/ui/webui/options2/pack_extension_handler.h new file mode 100644 index 0000000..47b6f0a --- /dev/null +++ b/chrome/browser/ui/webui/options2/pack_extension_handler.h @@ -0,0 +1,49 @@ +// Copyright (c) 2011 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. + +#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS2_PACK_EXTENSION_HANDLER_H_ +#define CHROME_BROWSER_UI_WEBUI_OPTIONS2_PACK_EXTENSION_HANDLER_H_ +#pragma once + +#include <string> + +#include "chrome/browser/browsing_data_remover.h" +#include "chrome/browser/extensions/pack_extension_job.h" +#include "chrome/browser/plugin_data_remover_helper.h" +#include "chrome/browser/ui/webui/options2/options_ui2.h" + +// Clear browser data handler page UI handler. +class PackExtensionHandler : public OptionsPage2UIHandler, + public PackExtensionJob::Client { + public: + PackExtensionHandler(); + virtual ~PackExtensionHandler(); + + // OptionsPage2UIHandler implementation. + virtual void Initialize() OVERRIDE; + virtual void GetLocalizedValues(DictionaryValue* localized_strings) OVERRIDE; + + // WebUIMessageHandler implementation. + virtual void RegisterMessages() OVERRIDE; + + // ExtensionPackJob::Client + virtual void OnPackSuccess(const FilePath& crx_file, + const FilePath& key_file) OVERRIDE; + + virtual void OnPackFailure(const std::string& error) OVERRIDE; + + private: + // Javascript callback to start packing an extension. + void HandlePackMessage(const ListValue* args); + + // A function to ask the webpage to show an alert. + void ShowAlert(const std::string& message); + + // Used to package the extension. + scoped_refptr<PackExtensionJob> pack_job_; + + DISALLOW_COPY_AND_ASSIGN(PackExtensionHandler); +}; + +#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS2_PACK_EXTENSION_HANDLER_H_ diff --git a/chrome/browser/ui/webui/options2/password_manager_browsertest.js b/chrome/browser/ui/webui/options2/password_manager_browsertest.js new file mode 100644 index 0000000..98c632d --- /dev/null +++ b/chrome/browser/ui/webui/options2/password_manager_browsertest.js @@ -0,0 +1,25 @@ +// Copyright (c) 2011 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. + +/** + * TestFixture for password manager WebUI testing. + * @extends {testing.Test} + * @constructor + **/ +function PasswordManagerWebUITest() {} + +PasswordManagerWebUITest.prototype = { + __proto__: testing.Test.prototype, + + /** + * Browse to the password manager. + **/ + browsePreload: 'chrome://settings/passwords', +}; + +// Test opening the password manager has correct location. +TEST_F('PasswordManagerWebUITest', 'testOpenPasswordManager', + function() { + assertEquals(this.browsePreload, document.location.href); + }); diff --git a/chrome/browser/ui/webui/options2/password_manager_handler.cc b/chrome/browser/ui/webui/options2/password_manager_handler.cc new file mode 100644 index 0000000..b0990bb --- /dev/null +++ b/chrome/browser/ui/webui/options2/password_manager_handler.cc @@ -0,0 +1,291 @@ +// Copyright (c) 2011 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. + +#include "chrome/browser/ui/webui/options2/password_manager_handler.h" + +#include "base/bind.h" +#include "base/string_number_conversions.h" +#include "base/utf_string_conversions.h" +#include "base/values.h" +#include "chrome/browser/google/google_util.h" +#include "chrome/browser/prefs/pref_service.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/common/chrome_notification_types.h" +#include "chrome/common/pref_names.h" +#include "chrome/common/url_constants.h" +#include "content/public/browser/notification_details.h" +#include "content/public/browser/notification_source.h" +#include "grit/chromium_strings.h" +#include "grit/generated_resources.h" +#include "net/base/net_util.h" +#include "ui/base/l10n/l10n_util.h" +#include "webkit/glue/password_form.h" + +PasswordManagerHandler::PasswordManagerHandler() + : ALLOW_THIS_IN_INITIALIZER_LIST(populater_(this)), + ALLOW_THIS_IN_INITIALIZER_LIST(exception_populater_(this)) { +} + +PasswordManagerHandler::~PasswordManagerHandler() { + PasswordStore* store = GetPasswordStore(); + if (store) + store->RemoveObserver(this); +} + +void PasswordManagerHandler::GetLocalizedValues( + DictionaryValue* localized_strings) { + DCHECK(localized_strings); + + static const OptionsStringResource resources[] = { + { "savedPasswordsTitle", + IDS_PASSWORDS_SHOW_PASSWORDS_TAB_TITLE }, + { "passwordExceptionsTitle", + IDS_PASSWORDS_EXCEPTIONS_TAB_TITLE }, + { "passwordSearchPlaceholder", + IDS_PASSWORDS_PAGE_SEARCH_PASSWORDS }, + { "passwordShowButton", + IDS_PASSWORDS_PAGE_VIEW_SHOW_BUTTON }, + { "passwordHideButton", + IDS_PASSWORDS_PAGE_VIEW_HIDE_BUTTON }, + { "passwordsSiteColumn", + IDS_PASSWORDS_PAGE_VIEW_SITE_COLUMN }, + { "passwordsUsernameColumn", + IDS_PASSWORDS_PAGE_VIEW_USERNAME_COLUMN }, + { "passwordsRemoveButton", + IDS_PASSWORDS_PAGE_VIEW_REMOVE_BUTTON }, + { "passwordsNoPasswordsDescription", + IDS_PASSWORDS_PAGE_VIEW_NO_PASSWORDS_DESCRIPTION }, + { "passwordsNoExceptionsDescription", + IDS_PASSWORDS_PAGE_VIEW_NO_EXCEPTIONS_DESCRIPTION }, + { "passwordsRemoveAllButton", + IDS_PASSWORDS_PAGE_VIEW_REMOVE_ALL_BUTTON }, + { "passwordsRemoveAllTitle", + IDS_PASSWORDS_PAGE_VIEW_CAPTION_DELETE_ALL_PASSWORDS }, + { "passwordsRemoveAllWarning", + IDS_PASSWORDS_PAGE_VIEW_TEXT_DELETE_ALL_PASSWORDS }, + }; + + RegisterStrings(localized_strings, resources, arraysize(resources)); + RegisterTitle(localized_strings, "passwordsPage", + IDS_PASSWORDS_EXCEPTIONS_WINDOW_TITLE); + + localized_strings->SetString("passwordManagerLearnMoreURL", + google_util::AppendGoogleLocaleParam( + GURL(chrome::kPasswordManagerLearnMoreURL)).spec()); +} + +void PasswordManagerHandler::Initialize() { + // Due to the way that handlers are (re)initialized under certain types of + // navigation, we may already be initialized. (See bugs 88986 and 86448.) + // If this is the case, return immediately. This is a hack. + // TODO(mdm): remove this hack once it is no longer necessary. + if (!show_passwords_.GetPrefName().empty()) + return; + + show_passwords_.Init(prefs::kPasswordManagerAllowShowPasswords, + Profile::FromWebUI(web_ui_)->GetPrefs(), this); + // We should not cache web_ui_->GetProfile(). See crosbug.com/6304. + PasswordStore* store = GetPasswordStore(); + if (store) + store->AddObserver(this); +} + +void PasswordManagerHandler::RegisterMessages() { + DCHECK(web_ui_); + + web_ui_->RegisterMessageCallback("updatePasswordLists", + base::Bind(&PasswordManagerHandler::UpdatePasswordLists, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("removeSavedPassword", + base::Bind(&PasswordManagerHandler::RemoveSavedPassword, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("removePasswordException", + base::Bind(&PasswordManagerHandler::RemovePasswordException, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("removeAllSavedPasswords", + base::Bind(&PasswordManagerHandler::RemoveAllSavedPasswords, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("removeAllPasswordExceptions", + base::Bind(&PasswordManagerHandler::RemoveAllPasswordExceptions, + base::Unretained(this))); +} + +void PasswordManagerHandler::OnLoginsChanged() { + UpdatePasswordLists(NULL); +} + +PasswordStore* PasswordManagerHandler::GetPasswordStore() { + return Profile::FromWebUI(web_ui_)-> + GetPasswordStore(Profile::EXPLICIT_ACCESS); +} + +void PasswordManagerHandler::Observe( + int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) { + if (type == chrome::NOTIFICATION_PREF_CHANGED) { + std::string* pref_name = content::Details<std::string>(details).ptr(); + if (*pref_name == prefs::kPasswordManagerAllowShowPasswords) { + UpdatePasswordLists(NULL); + } + } + + OptionsPage2UIHandler::Observe(type, source, details); +} + +void PasswordManagerHandler::UpdatePasswordLists(const ListValue* args) { + // Reset the current lists. + password_list_.reset(); + password_exception_list_.reset(); + + languages_ = Profile::FromWebUI(web_ui_)->GetPrefs()-> + GetString(prefs::kAcceptLanguages); + populater_.Populate(); + exception_populater_.Populate(); +} + +void PasswordManagerHandler::RemoveSavedPassword(const ListValue* args) { + PasswordStore* store = GetPasswordStore(); + if (!store) + return; + std::string string_value = UTF16ToUTF8(ExtractStringValue(args)); + int index; + if (base::StringToInt(string_value, &index) && index >= 0 && + static_cast<size_t>(index) < password_list_.size()) + store->RemoveLogin(*password_list_[index]); +} + +void PasswordManagerHandler::RemovePasswordException( + const ListValue* args) { + PasswordStore* store = GetPasswordStore(); + if (!store) + return; + std::string string_value = UTF16ToUTF8(ExtractStringValue(args)); + int index; + if (base::StringToInt(string_value, &index) && index >= 0 && + static_cast<size_t>(index) < password_exception_list_.size()) + store->RemoveLogin(*password_exception_list_[index]); +} + +void PasswordManagerHandler::RemoveAllSavedPasswords( + const ListValue* args) { + // TODO(jhawkins): This will cause a list refresh for every password in the + // list. Add PasswordStore::RemoveAllLogins(). + PasswordStore* store = GetPasswordStore(); + if (!store) + return; + for (size_t i = 0; i < password_list_.size(); ++i) + store->RemoveLogin(*password_list_[i]); +} + +void PasswordManagerHandler::RemoveAllPasswordExceptions( + const ListValue* args) { + PasswordStore* store = GetPasswordStore(); + if (!store) + return; + for (size_t i = 0; i < password_exception_list_.size(); ++i) + store->RemoveLogin(*password_exception_list_[i]); +} + +void PasswordManagerHandler::SetPasswordList() { + // Due to the way that handlers are (re)initialized under certain types of + // navigation, we may not be initialized yet. (See bugs 88986 and 86448.) + // If this is the case, initialize on demand. This is a hack. + // TODO(mdm): remove this hack once it is no longer necessary. + if (show_passwords_.GetPrefName().empty()) + Initialize(); + + ListValue entries; + bool show_passwords = *show_passwords_; + string16 empty; + for (size_t i = 0; i < password_list_.size(); ++i) { + ListValue* entry = new ListValue(); + entry->Append(new StringValue(net::FormatUrl(password_list_[i]->origin, + languages_))); + entry->Append(new StringValue(password_list_[i]->username_value)); + entry->Append(new StringValue( + show_passwords ? password_list_[i]->password_value : empty)); + entries.Append(entry); + } + + web_ui_->CallJavascriptFunction("PasswordManager.setSavedPasswordsList", + entries); +} + +void PasswordManagerHandler::SetPasswordExceptionList() { + ListValue entries; + for (size_t i = 0; i < password_exception_list_.size(); ++i) { + entries.Append(new StringValue( + net::FormatUrl(password_exception_list_[i]->origin, languages_))); + } + + web_ui_->CallJavascriptFunction("PasswordManager.setPasswordExceptionsList", + entries); +} + +PasswordManagerHandler::ListPopulater::ListPopulater( + PasswordManagerHandler* page) + : page_(page), + pending_login_query_(0) { +} + +PasswordManagerHandler::ListPopulater::~ListPopulater() { +} + +PasswordManagerHandler::PasswordListPopulater::PasswordListPopulater( + PasswordManagerHandler* page) : ListPopulater(page) { +} + +void PasswordManagerHandler::PasswordListPopulater::Populate() { + PasswordStore* store = page_->GetPasswordStore(); + if (store != NULL) { + if (pending_login_query_) + store->CancelRequest(pending_login_query_); + + pending_login_query_ = store->GetAutofillableLogins(this); + } else { + LOG(ERROR) << "No password store! Cannot display passwords."; + } +} + +void PasswordManagerHandler::PasswordListPopulater:: + OnPasswordStoreRequestDone( + CancelableRequestProvider::Handle handle, + const std::vector<webkit_glue::PasswordForm*>& result) { + DCHECK_EQ(pending_login_query_, handle); + pending_login_query_ = 0; + page_->password_list_.reset(); + page_->password_list_.insert(page_->password_list_.end(), + result.begin(), result.end()); + page_->SetPasswordList(); +} + +PasswordManagerHandler::PasswordExceptionListPopulater:: + PasswordExceptionListPopulater(PasswordManagerHandler* page) + : ListPopulater(page) { +} + +void PasswordManagerHandler::PasswordExceptionListPopulater::Populate() { + PasswordStore* store = page_->GetPasswordStore(); + if (store != NULL) { + if (pending_login_query_) + store->CancelRequest(pending_login_query_); + + pending_login_query_ = store->GetBlacklistLogins(this); + } else { + LOG(ERROR) << "No password store! Cannot display exceptions."; + } +} + +void PasswordManagerHandler::PasswordExceptionListPopulater:: + OnPasswordStoreRequestDone( + CancelableRequestProvider::Handle handle, + const std::vector<webkit_glue::PasswordForm*>& result) { + DCHECK_EQ(pending_login_query_, handle); + pending_login_query_ = 0; + page_->password_exception_list_.reset(); + page_->password_exception_list_.insert(page_->password_exception_list_.end(), + result.begin(), result.end()); + page_->SetPasswordExceptionList(); +} diff --git a/chrome/browser/ui/webui/options2/password_manager_handler.h b/chrome/browser/ui/webui/options2/password_manager_handler.h new file mode 100644 index 0000000..389f1af --- /dev/null +++ b/chrome/browser/ui/webui/options2/password_manager_handler.h @@ -0,0 +1,129 @@ +// Copyright (c) 2011 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. + +#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS2_PASSWORD_MANAGER_HANDLER_H_ +#define CHROME_BROWSER_UI_WEBUI_OPTIONS2_PASSWORD_MANAGER_HANDLER_H_ + +#include <string> +#include <vector> + +#include "base/memory/scoped_vector.h" +#include "chrome/browser/password_manager/password_store.h" +#include "chrome/browser/password_manager/password_store_consumer.h" +#include "chrome/browser/prefs/pref_member.h" +#include "chrome/browser/ui/webui/options2/options_ui2.h" + +class PasswordManagerHandler : public OptionsPage2UIHandler, + public PasswordStore::Observer { + public: + PasswordManagerHandler(); + virtual ~PasswordManagerHandler(); + + // OptionsPage2UIHandler implementation. + virtual void GetLocalizedValues(DictionaryValue* localized_strings) OVERRIDE; + virtual void Initialize() OVERRIDE; + virtual void RegisterMessages() OVERRIDE; + + // PasswordStore::Observer implementation. + virtual void OnLoginsChanged() OVERRIDE; + + // content::NotificationObserver implementation. + virtual void Observe(int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) OVERRIDE; + + private: + // The password store associated with the currently active profile. + PasswordStore* GetPasswordStore(); + + // Called when the JS PasswordManager object is initialized. + void UpdatePasswordLists(const ListValue* args); + + // Remove an entry. + // @param value the entry index to be removed. + void RemoveSavedPassword(const ListValue* args); + + // Remove an password exception. + // @param value the entry index to be removed. + void RemovePasswordException(const ListValue* args); + + // Remove all saved passwords + void RemoveAllSavedPasswords(const ListValue* args); + + // Remove All password exceptions + void RemoveAllPasswordExceptions(const ListValue* args); + + // Get password value for the selected entry. + // @param value the selected entry index. + void ShowSelectedPassword(const ListValue* args); + + // Sets the password and exception list contents to the given data. + // We take ownership of the PasswordForms in the vector. + void SetPasswordList(); + void SetPasswordExceptionList(); + + // A short class to mediate requests to the password store. + class ListPopulater : public PasswordStoreConsumer { + public: + explicit ListPopulater(PasswordManagerHandler* page); + virtual ~ListPopulater(); + + // Send a query to the password store to populate a list. + virtual void Populate() = 0; + + // Send the password store's reply back to the handler. + virtual void OnPasswordStoreRequestDone( + CancelableRequestProvider::Handle handle, + const std::vector<webkit_glue::PasswordForm*>& result) = 0; + + protected: + PasswordManagerHandler* page_; + CancelableRequestProvider::Handle pending_login_query_; + }; + + // A short class to mediate requests to the password store for passwordlist. + class PasswordListPopulater : public ListPopulater { + public: + explicit PasswordListPopulater(PasswordManagerHandler* page); + + // Send a query to the password store to populate a password list. + virtual void Populate() OVERRIDE; + + // Send the password store's reply back to the handler. + virtual void OnPasswordStoreRequestDone( + CancelableRequestProvider::Handle handle, + const std::vector<webkit_glue::PasswordForm*>& result) OVERRIDE; + }; + + // A short class to mediate requests to the password store for exceptions. + class PasswordExceptionListPopulater : public ListPopulater { + public: + explicit PasswordExceptionListPopulater(PasswordManagerHandler* page); + + // Send a query to the password store to populate a passwordException list. + virtual void Populate() OVERRIDE; + + // Send the password store's reply back to the handler. + virtual void OnPasswordStoreRequestDone( + CancelableRequestProvider::Handle handle, + const std::vector<webkit_glue::PasswordForm*>& result) OVERRIDE; + }; + + // Password store consumer for populating the password list and exceptions. + PasswordListPopulater populater_; + PasswordExceptionListPopulater exception_populater_; + + ScopedVector<webkit_glue::PasswordForm> password_list_; + ScopedVector<webkit_glue::PasswordForm> password_exception_list_; + + // User's pref + std::string languages_; + + // Whether to show stored passwords or not. + BooleanPrefMember show_passwords_; + + DISALLOW_COPY_AND_ASSIGN(PasswordManagerHandler); +}; + +#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS2_PASSWORD_MANAGER_HANDLER_H_ diff --git a/chrome/browser/ui/webui/options2/personal_options_browsertest.js b/chrome/browser/ui/webui/options2/personal_options_browsertest.js new file mode 100644 index 0000000..222adfa --- /dev/null +++ b/chrome/browser/ui/webui/options2/personal_options_browsertest.js @@ -0,0 +1,24 @@ +// Copyright (c) 2011 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. + +/** + * TestFixture for personal options WebUI testing. + * @extends {testing.Test} + * @constructor + **/ +function PersonalOptionsWebUITest() {} + +PersonalOptionsWebUITest.prototype = { + __proto__: testing.Test.prototype, + + /** + * Browse to personal options. + **/ + browsePreload: 'chrome://settings/personal', +}; + +// Test opening personal options has correct location. +TEST_F('PersonalOptionsWebUITest', 'testOpenPersonalOptions', function() { + assertEquals(this.browsePreload, document.location.href); +}); diff --git a/chrome/browser/ui/webui/options2/personal_options_handler.cc b/chrome/browser/ui/webui/options2/personal_options_handler.cc new file mode 100644 index 0000000..0a2a367 --- /dev/null +++ b/chrome/browser/ui/webui/options2/personal_options_handler.cc @@ -0,0 +1,444 @@ +// Copyright (c) 2011 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. + +#include "chrome/browser/ui/webui/options2/personal_options_handler.h" + +#include <string> + +#include "base/basictypes.h" +#include "base/bind.h" +#include "base/command_line.h" +#include "base/path_service.h" +#include "base/stl_util.h" +#include "base/string_number_conversions.h" +#include "base/utf_string_conversions.h" +#include "base/value_conversions.h" +#include "base/values.h" +#include "build/build_config.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/google/google_util.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/profiles/profile_info_cache.h" +#include "chrome/browser/profiles/profile_info_util.h" +#include "chrome/browser/profiles/profile_manager.h" +#include "chrome/browser/sync/profile_sync_service.h" +#include "chrome/browser/sync/sync_setup_flow.h" +#include "chrome/browser/sync/sync_ui_util.h" +#include "chrome/browser/themes/theme_service.h" +#include "chrome/browser/themes/theme_service_factory.h" +#include "chrome/browser/ui/webui/web_ui_util.h" +#include "chrome/common/chrome_notification_types.h" +#include "chrome/common/chrome_paths.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/common/net/gaia/google_service_auth_error.h" +#include "chrome/common/url_constants.h" +#include "content/browser/tab_contents/tab_contents.h" +#include "content/public/browser/notification_service.h" +#include "content/public/browser/user_metrics.h" +#include "grit/chromium_strings.h" +#include "grit/generated_resources.h" +#include "grit/locale_settings.h" +#include "grit/theme_resources.h" +#include "ui/base/l10n/l10n_util.h" + +#if defined(OS_CHROMEOS) +#include "chrome/browser/chromeos/login/user_manager.h" +#include "chrome/browser/chromeos/options/take_photo_dialog.h" +#include "chrome/browser/ui/browser_window.h" +#include "chrome/browser/ui/views/window.h" +#include "third_party/skia/include/core/SkBitmap.h" +#endif // defined(OS_CHROMEOS) +#if defined(TOOLKIT_GTK) +#include "chrome/browser/ui/gtk/gtk_theme_service.h" +#endif // defined(TOOLKIT_GTK) + +using content::UserMetricsAction; + +PersonalOptionsHandler::PersonalOptionsHandler() { + multiprofile_ = ProfileManager::IsMultipleProfilesEnabled(); +#if defined(OS_CHROMEOS) + registrar_.Add(this, + chrome::NOTIFICATION_LOGIN_USER_IMAGE_CHANGED, + content::NotificationService::AllSources()); +#endif +} + +PersonalOptionsHandler::~PersonalOptionsHandler() { + ProfileSyncService* sync_service = + Profile::FromWebUI(web_ui_)->GetProfileSyncService(); + if (sync_service) + sync_service->RemoveObserver(this); +} + +void PersonalOptionsHandler::GetLocalizedValues( + DictionaryValue* localized_strings) { + DCHECK(localized_strings); + + RegisterTitle(localized_strings, "personalPage", + IDS_OPTIONS_CONTENT_TAB_LABEL); + + + localized_strings->SetString( + "syncOverview", + l10n_util::GetStringFUTF16(IDS_SYNC_OVERVIEW, + l10n_util::GetStringUTF16(IDS_PRODUCT_NAME))); + localized_strings->SetString("syncSection", + l10n_util::GetStringUTF16(IDS_SYNC_OPTIONS_GROUP_NAME)); + localized_strings->SetString("customizeSync", + l10n_util::GetStringUTF16(IDS_SYNC_CUSTOMIZE_BUTTON_LABEL)); + localized_strings->SetString("syncLearnMoreURL", + google_util::StringAppendGoogleLocaleParam(chrome::kSyncLearnMoreURL)); + + localized_strings->SetString("profiles", + l10n_util::GetStringUTF16(IDS_PROFILES_OPTIONS_GROUP_NAME)); + localized_strings->SetString("profilesCreate", + l10n_util::GetStringUTF16(IDS_PROFILES_CREATE_BUTTON_LABEL)); + localized_strings->SetString("profilesManage", + l10n_util::GetStringUTF16(IDS_PROFILES_MANAGE_BUTTON_LABEL)); + localized_strings->SetString("profilesDelete", + l10n_util::GetStringUTF16(IDS_PROFILES_DELETE_BUTTON_LABEL)); + localized_strings->SetString("profilesDeleteSingle", + l10n_util::GetStringUTF16(IDS_PROFILES_DELETE_SINGLE_BUTTON_LABEL)); + localized_strings->SetString("profilesListItemCurrent", + l10n_util::GetStringUTF16(IDS_PROFILES_LIST_ITEM_CURRENT)); + localized_strings->SetString("profilesSingleUser", + l10n_util::GetStringFUTF16(IDS_PROFILES_SINGLE_USER_MESSAGE, + l10n_util::GetStringUTF16(IDS_PRODUCT_NAME))); + + localized_strings->SetString("passwords", + l10n_util::GetStringUTF16(IDS_OPTIONS_PASSWORDS_GROUP_NAME)); + localized_strings->SetString("passwordsAskToSave", + l10n_util::GetStringUTF16(IDS_OPTIONS_PASSWORDS_ASKTOSAVE)); + localized_strings->SetString("passwordsNeverSave", + l10n_util::GetStringUTF16(IDS_OPTIONS_PASSWORDS_NEVERSAVE)); + localized_strings->SetString("manage_passwords", + l10n_util::GetStringUTF16(IDS_OPTIONS_PASSWORDS_MANAGE_PASSWORDS)); +#if defined(OS_MACOSX) + ProfileManager* profile_manager = g_browser_process->profile_manager(); + if (profile_manager->GetNumberOfProfiles() > 1) { + localized_strings->SetString("macPasswordsWarning", + l10n_util::GetStringUTF16(IDS_OPTIONS_PASSWORDS_MAC_WARNING)); + } +#endif + localized_strings->SetString("autologinEnabled", + l10n_util::GetStringUTF16(IDS_OPTIONS_PASSWORDS_AUTOLOGIN)); + + localized_strings->SetString("autofill", + l10n_util::GetStringUTF16(IDS_AUTOFILL_SETTING_WINDOWS_GROUP_NAME)); + localized_strings->SetString("autofillEnabled", + l10n_util::GetStringUTF16(IDS_OPTIONS_AUTOFILL_ENABLE)); + localized_strings->SetString("manageAutofillSettings", + l10n_util::GetStringUTF16(IDS_OPTIONS_MANAGE_AUTOFILL_SETTINGS)); + + localized_strings->SetString("browsingData", + l10n_util::GetStringUTF16(IDS_OPTIONS_BROWSING_DATA_GROUP_NAME)); + localized_strings->SetString("importData", + l10n_util::GetStringUTF16(IDS_OPTIONS_IMPORT_DATA_BUTTON)); + + localized_strings->SetString("themesGallery", + l10n_util::GetStringUTF16(IDS_THEMES_GALLERY_BUTTON)); + localized_strings->SetString("themesGalleryURL", + l10n_util::GetStringUTF16(IDS_THEMES_GALLERY_URL)); + +#if defined(TOOLKIT_GTK) + localized_strings->SetString("appearance", + l10n_util::GetStringUTF16(IDS_APPEARANCE_GROUP_NAME)); + localized_strings->SetString("themesGTKButton", + l10n_util::GetStringUTF16(IDS_THEMES_GTK_BUTTON)); + localized_strings->SetString("themesSetClassic", + l10n_util::GetStringUTF16(IDS_THEMES_SET_CLASSIC)); + localized_strings->SetString("showWindowDecorations", + l10n_util::GetStringUTF16(IDS_SHOW_WINDOW_DECORATIONS_RADIO)); + localized_strings->SetString("hideWindowDecorations", + l10n_util::GetStringUTF16(IDS_HIDE_WINDOW_DECORATIONS_RADIO)); +#else + localized_strings->SetString("themes", + l10n_util::GetStringUTF16(IDS_THEMES_GROUP_NAME)); + localized_strings->SetString("themesReset", + l10n_util::GetStringUTF16(IDS_THEMES_RESET_BUTTON)); +#endif + + // Sync select control. + ListValue* sync_select_list = new ListValue; + ListValue* datatypes = new ListValue; + datatypes->Append(Value::CreateBooleanValue(false)); + datatypes->Append( + Value::CreateStringValue( + l10n_util::GetStringUTF8(IDS_SYNC_OPTIONS_SELECT_DATATYPES))); + sync_select_list->Append(datatypes); + ListValue* everything = new ListValue; + everything->Append(Value::CreateBooleanValue(true)); + everything->Append( + Value::CreateStringValue( + l10n_util::GetStringUTF8(IDS_SYNC_OPTIONS_SELECT_EVERYTHING))); + sync_select_list->Append(everything); + localized_strings->Set("syncSelectList", sync_select_list); + + // Sync page. + localized_strings->SetString("syncPage", + l10n_util::GetStringUTF16(IDS_SYNC_NTP_SYNC_SECTION_TITLE)); + localized_strings->SetString("sync_title", + l10n_util::GetStringUTF16(IDS_CUSTOMIZE_SYNC_DESCRIPTION)); + localized_strings->SetString("syncsettings", + l10n_util::GetStringUTF16(IDS_SYNC_DATATYPE_PREFERENCES)); + localized_strings->SetString("syncbookmarks", + l10n_util::GetStringUTF16(IDS_SYNC_DATATYPE_BOOKMARKS)); + localized_strings->SetString("synctypedurls", + l10n_util::GetStringUTF16(IDS_SYNC_DATATYPE_TYPED_URLS)); + localized_strings->SetString("syncpasswords", + l10n_util::GetStringUTF16(IDS_SYNC_DATATYPE_PASSWORDS)); + localized_strings->SetString("syncextensions", + l10n_util::GetStringUTF16(IDS_SYNC_DATATYPE_EXTENSIONS)); + localized_strings->SetString("syncautofill", + l10n_util::GetStringUTF16(IDS_SYNC_DATATYPE_AUTOFILL)); + localized_strings->SetString("syncthemes", + l10n_util::GetStringUTF16(IDS_SYNC_DATATYPE_THEMES)); + localized_strings->SetString("syncapps", + l10n_util::GetStringUTF16(IDS_SYNC_DATATYPE_APPS)); + localized_strings->SetString("syncsessions", + l10n_util::GetStringUTF16(IDS_SYNC_DATATYPE_TABS)); + +#if defined(OS_CHROMEOS) + localized_strings->SetString("account", + l10n_util::GetStringUTF16(IDS_OPTIONS_PERSONAL_ACCOUNT_GROUP_NAME)); + localized_strings->SetString("enableScreenlock", + l10n_util::GetStringUTF16(IDS_OPTIONS_ENABLE_SCREENLOCKER_CHECKBOX)); + localized_strings->SetString("changePicture", + l10n_util::GetStringUTF16(IDS_OPTIONS_CHANGE_PICTURE)); + if (chromeos::UserManager::Get()->user_is_logged_in()) { + localized_strings->SetString("username", + chromeos::UserManager::Get()->logged_in_user().email()); + } +#endif +} + +void PersonalOptionsHandler::RegisterMessages() { + DCHECK(web_ui_); + web_ui_->RegisterMessageCallback( + "themesReset", + base::Bind(&PersonalOptionsHandler::ThemesReset, + base::Unretained(this))); +#if defined(TOOLKIT_GTK) + web_ui_->RegisterMessageCallback( + "themesSetGTK", + base::Bind(&PersonalOptionsHandler::ThemesSetGTK, + base::Unretained(this))); +#endif + web_ui_->RegisterMessageCallback( + "createProfile", + base::Bind(&PersonalOptionsHandler::CreateProfile, + base::Unretained(this))); +} + +void PersonalOptionsHandler::Observe( + int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) { + if (type == chrome::NOTIFICATION_BROWSER_THEME_CHANGED) { + ObserveThemeChanged(); + } else if (multiprofile_ && + type == chrome::NOTIFICATION_PROFILE_CACHED_INFO_CHANGED) { + SendProfilesInfo(); +#if defined(OS_CHROMEOS) + } else if (type == chrome::NOTIFICATION_LOGIN_USER_IMAGE_CHANGED) { + UpdateAccountPicture(); +#endif + } else { + OptionsPage2UIHandler::Observe(type, source, details); + } +} + +void PersonalOptionsHandler::OnStateChanged() { + string16 status_label; + string16 link_label; + ProfileSyncService* service = + Profile::FromWebUI(web_ui_)->GetProfileSyncService(); + DCHECK(service); + bool managed = service->IsManaged(); + bool sync_setup_completed = service->HasSyncSetupCompleted(); + bool status_has_error = sync_ui_util::GetStatusLabels( + service, sync_ui_util::WITH_HTML, &status_label, &link_label) == + sync_ui_util::SYNC_ERROR; + + string16 start_stop_button_label; + bool is_start_stop_button_visible = false; + bool is_start_stop_button_enabled = false; + if (sync_setup_completed) { + start_stop_button_label = + l10n_util::GetStringUTF16(IDS_SYNC_STOP_SYNCING_BUTTON_LABEL); +#if defined(OS_CHROMEOS) + is_start_stop_button_visible = false; +#else + is_start_stop_button_visible = true; +#endif // defined(OS_CHROMEOS) + is_start_stop_button_enabled = !managed; + } else if (service->SetupInProgress()) { + start_stop_button_label = + l10n_util::GetStringUTF16(IDS_SYNC_NTP_SETUP_IN_PROGRESS); + is_start_stop_button_visible = true; + is_start_stop_button_enabled = false; + } else { + start_stop_button_label = + l10n_util::GetStringFUTF16(IDS_SYNC_START_SYNC_BUTTON_LABEL, + l10n_util::GetStringUTF16(IDS_SHORT_PRODUCT_NAME)); + is_start_stop_button_visible = true; + is_start_stop_button_enabled = !managed; + } + + scoped_ptr<Value> completed(Value::CreateBooleanValue(sync_setup_completed)); + web_ui_->CallJavascriptFunction("PersonalOptions.setSyncSetupCompleted", + *completed); + + scoped_ptr<Value> label(Value::CreateStringValue(status_label)); + web_ui_->CallJavascriptFunction("PersonalOptions.setSyncStatus", *label); + + scoped_ptr<Value> enabled( + Value::CreateBooleanValue(is_start_stop_button_enabled)); + web_ui_->CallJavascriptFunction("PersonalOptions.setStartStopButtonEnabled", + *enabled); + + scoped_ptr<Value> visible( + Value::CreateBooleanValue(is_start_stop_button_visible)); + web_ui_->CallJavascriptFunction("PersonalOptions.setStartStopButtonVisible", + *visible); + + label.reset(Value::CreateStringValue(start_stop_button_label)); + web_ui_->CallJavascriptFunction("PersonalOptions.setStartStopButtonLabel", + *label); + + label.reset(Value::CreateStringValue(link_label)); + web_ui_->CallJavascriptFunction("PersonalOptions.setSyncActionLinkLabel", + *label); + + enabled.reset(Value::CreateBooleanValue(!managed)); + web_ui_->CallJavascriptFunction("PersonalOptions.setSyncActionLinkEnabled", + *enabled); + + visible.reset(Value::CreateBooleanValue(status_has_error)); + web_ui_->CallJavascriptFunction("PersonalOptions.setSyncStatusErrorVisible", + *visible); + + enabled.reset(Value::CreateBooleanValue( + !service->unrecoverable_error_detected())); + web_ui_->CallJavascriptFunction( + "PersonalOptions.setCustomizeSyncButtonEnabled", + *enabled); + + if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kEnableAutologin)) { + visible.reset(Value::CreateBooleanValue( + service->AreCredentialsAvailable())); + web_ui_->CallJavascriptFunction("PersonalOptions.setAutoLoginVisible", + *visible); + } + + // Set profile creation text and button if multi-profiles switch is on. + visible.reset(Value::CreateBooleanValue(multiprofile_)); + web_ui_->CallJavascriptFunction("PersonalOptions.setProfilesSectionVisible", + *visible); + if (multiprofile_) + SendProfilesInfo(); +} + +void PersonalOptionsHandler::ObserveThemeChanged() { + Profile* profile = Profile::FromWebUI(web_ui_); +#if defined(TOOLKIT_GTK) + GtkThemeService* theme_service = GtkThemeService::GetFrom(profile); + bool is_gtk_theme = theme_service->UsingNativeTheme(); + base::FundamentalValue gtk_enabled(!is_gtk_theme); + web_ui_->CallJavascriptFunction( + "options.PersonalOptions.setGtkThemeButtonEnabled", gtk_enabled); +#else + ThemeService* theme_service = ThemeServiceFactory::GetForProfile(profile); + bool is_gtk_theme = false; +#endif + + bool is_classic_theme = !is_gtk_theme && theme_service->UsingDefaultTheme(); + base::FundamentalValue enabled(!is_classic_theme); + web_ui_->CallJavascriptFunction( + "options.PersonalOptions.setThemesResetButtonEnabled", enabled); +} + +void PersonalOptionsHandler::Initialize() { + Profile* profile = Profile::FromWebUI(web_ui_); + + // Listen for theme installation. + registrar_.Add(this, chrome::NOTIFICATION_BROWSER_THEME_CHANGED, + content::Source<ThemeService>( + ThemeServiceFactory::GetForProfile(profile))); + registrar_.Add(this, chrome::NOTIFICATION_PROFILE_CACHED_INFO_CHANGED, + content::NotificationService::AllSources()); + ObserveThemeChanged(); + + ProfileSyncService* sync_service = profile->GetProfileSyncService(); + if (sync_service) { + sync_service->AddObserver(this); + OnStateChanged(); + } else { + web_ui_->CallJavascriptFunction("options.PersonalOptions.hideSyncSection"); + } +} + +void PersonalOptionsHandler::ThemesReset(const ListValue* args) { + content::RecordAction(UserMetricsAction("Options_ThemesReset")); + Profile* profile = Profile::FromWebUI(web_ui_); + ThemeServiceFactory::GetForProfile(profile)->UseDefaultTheme(); +} + +#if defined(TOOLKIT_GTK) +void PersonalOptionsHandler::ThemesSetGTK(const ListValue* args) { + content::RecordAction(UserMetricsAction("Options_GtkThemeSet")); + Profile* profile = Profile::FromWebUI(web_ui_); + ThemeServiceFactory::GetForProfile(profile)->SetNativeTheme(); +} +#endif + +#if defined(OS_CHROMEOS) +void PersonalOptionsHandler::UpdateAccountPicture() { + std::string email = chromeos::UserManager::Get()->logged_in_user().email(); + if (!email.empty()) { + web_ui_->CallJavascriptFunction("PersonalOptions.updateAccountPicture"); + base::StringValue email_value(email); + web_ui_->CallJavascriptFunction("AccountsOptions.updateAccountPicture", + email_value); + } +} +#endif + +void PersonalOptionsHandler::SendProfilesInfo() { + ProfileInfoCache& cache = + g_browser_process->profile_manager()->GetProfileInfoCache(); + ListValue profile_info_list; + FilePath current_profile_path = + web_ui_->tab_contents()->browser_context()->GetPath(); + for (size_t i = 0, e = cache.GetNumberOfProfiles(); i < e; ++i) { + DictionaryValue* profile_value = new DictionaryValue(); + FilePath profile_path = cache.GetPathOfProfileAtIndex(i); + profile_value->SetString("name", cache.GetNameOfProfileAtIndex(i)); + profile_value->Set("filePath", base::CreateFilePathValue(profile_path)); + profile_value->SetBoolean("isCurrentProfile", + profile_path == current_profile_path); + + bool is_gaia_picture = + cache.IsUsingGAIAPictureOfProfileAtIndex(i) && + cache.GetGAIAPictureOfProfileAtIndex(i); + if (is_gaia_picture) { + gfx::Image icon = profiles::GetAvatarIconForWebUI( + cache.GetAvatarIconOfProfileAtIndex(i), true); + profile_value->SetString("iconURL", web_ui_util::GetImageDataUrl(icon)); + } else { + size_t icon_index = cache.GetAvatarIconIndexOfProfileAtIndex(i); + profile_value->SetString("iconURL", + cache.GetDefaultAvatarIconUrl(icon_index)); + } + + profile_info_list.Append(profile_value); + } + + web_ui_->CallJavascriptFunction("PersonalOptions.setProfilesInfo", + profile_info_list); +} + +void PersonalOptionsHandler::CreateProfile(const ListValue* args) { + ProfileManager::CreateMultiProfileAsync(); +} diff --git a/chrome/browser/ui/webui/options2/personal_options_handler.h b/chrome/browser/ui/webui/options2/personal_options_handler.h new file mode 100644 index 0000000..4ffe0b6 --- /dev/null +++ b/chrome/browser/ui/webui/options2/personal_options_handler.h @@ -0,0 +1,70 @@ +// Copyright (c) 2011 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. + +#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS2_PERSONAL_OPTIONS_HANDLER_H_ +#define CHROME_BROWSER_UI_WEBUI_OPTIONS2_PERSONAL_OPTIONS_HANDLER_H_ +#pragma once + +#include "base/basictypes.h" +#include "chrome/browser/sync/profile_sync_service.h" +#include "chrome/browser/ui/webui/options2/options_ui2.h" +#if defined(OS_CHROMEOS) +#include "content/public/browser/notification_registrar.h" +#endif + +// Chrome personal options page UI handler. +class PersonalOptionsHandler : public OptionsPage2UIHandler, + public ProfileSyncServiceObserver { + public: + PersonalOptionsHandler(); + virtual ~PersonalOptionsHandler(); + + // OptionsPage2UIHandler implementation. + virtual void GetLocalizedValues(DictionaryValue* localized_strings) OVERRIDE; + virtual void Initialize() OVERRIDE; + + // WebUIMessageHandler implementation. + virtual void RegisterMessages() OVERRIDE; + + // content::NotificationObserver implementation. + virtual void Observe(int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) OVERRIDE; + + // ProfileSyncServiceObserver implementation. + virtual void OnStateChanged() OVERRIDE; + + private: + void ObserveThemeChanged(); + void ThemesReset(const ListValue* args); +#if defined(TOOLKIT_GTK) + void ThemesSetGTK(const ListValue* args); +#endif + +#if defined(OS_CHROMEOS) + void UpdateAccountPicture(); + content::NotificationRegistrar registrar_; +#endif + + // Sends an array of Profile objects to javascript. + // 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 + // }; + void SendProfilesInfo(); + + // Asynchronously opens a new browser window to create a new profile. + // |args| is not used. + void CreateProfile(const ListValue* args); + + // True if the multiprofiles switch is enabled. + bool multiprofile_; + + DISALLOW_COPY_AND_ASSIGN(PersonalOptionsHandler); +}; + +#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS2_PERSONAL_OPTIONS_HANDLER_H_ diff --git a/chrome/browser/ui/webui/options2/search_engine_manager_browsertest.js b/chrome/browser/ui/webui/options2/search_engine_manager_browsertest.js new file mode 100644 index 0000000..9fe2165 --- /dev/null +++ b/chrome/browser/ui/webui/options2/search_engine_manager_browsertest.js @@ -0,0 +1,33 @@ +// Copyright (c) 2011 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. + +/** + * TestFixture for search engine manager WebUI testing. + * @extends {testing.Test} + * @constructor + **/ +function SearchEngineManagerWebUITest() {} + +SearchEngineManagerWebUITest.prototype = { + __proto__: testing.Test.prototype, + + /** + * Browse to the search engine manager. + **/ + browsePreload: 'chrome://settings/searchEngines', +}; + +// See crosbug.com/22673 +GEN('#if defined(OS_CHROMEOS)'); +GEN('#define MAYBE_testOpenSearchEngineManager ' + + 'DISABLED_testOpenSearchEngineManager'); +GEN('#else'); +GEN('#define MAYBE_testOpenSearchEngineManager testOpenSearchEngineManager'); +GEN('#endif // defined(OS_CHROMEOS)'); + +// Test opening the search engine manager has correct location. +TEST_F('SearchEngineManagerWebUITest', 'MAYBE_testOpenSearchEngineManager', + function() { + assertEquals(this.browsePreload, document.location.href); + }); diff --git a/chrome/browser/ui/webui/options2/search_engine_manager_handler.cc b/chrome/browser/ui/webui/options2/search_engine_manager_handler.cc new file mode 100644 index 0000000..1e289d8 --- /dev/null +++ b/chrome/browser/ui/webui/options2/search_engine_manager_handler.cc @@ -0,0 +1,313 @@ +// Copyright (c) 2011 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. + +#include "chrome/browser/ui/webui/options2/search_engine_manager_handler.h" + +#include "base/bind.h" +#include "base/string_number_conversions.h" +#include "base/utf_string_conversions.h" +#include "base/values.h" +#include "chrome/browser/extensions/extension_service.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/search_engines/template_url.h" +#include "chrome/browser/search_engines/template_url_service.h" +#include "chrome/browser/ui/search_engines/keyword_editor_controller.h" +#include "chrome/browser/ui/search_engines/template_url_table_model.h" +#include "chrome/common/extensions/extension.h" +#include "chrome/common/url_constants.h" +#include "grit/generated_resources.h" +#include "grit/locale_settings.h" +#include "ui/base/l10n/l10n_util.h" + +namespace { + +enum EngineInfoIndexes { + ENGINE_NAME, + ENGINE_KEYWORD, + ENGINE_URL, +}; + +}; // namespace + +SearchEngineManagerHandler::SearchEngineManagerHandler() { +} + +SearchEngineManagerHandler::~SearchEngineManagerHandler() { + if (list_controller_.get() && list_controller_->table_model()) + list_controller_->table_model()->SetObserver(NULL); +} + +void SearchEngineManagerHandler::Initialize() { + list_controller_.reset( + new KeywordEditorController(Profile::FromWebUI(web_ui_))); + if (list_controller_.get()) { + list_controller_->table_model()->SetObserver(this); + OnModelChanged(); + } +} + +void SearchEngineManagerHandler::GetLocalizedValues( + base::DictionaryValue* localized_strings) { + DCHECK(localized_strings); + + RegisterTitle(localized_strings, "searchEngineManagerPage", + IDS_SEARCH_ENGINES_EDITOR_WINDOW_TITLE); + localized_strings->SetString("defaultSearchEngineListTitle", + l10n_util::GetStringUTF16(IDS_SEARCH_ENGINES_EDITOR_MAIN_SEPARATOR)); + localized_strings->SetString("otherSearchEngineListTitle", + l10n_util::GetStringUTF16(IDS_SEARCH_ENGINES_EDITOR_OTHER_SEPARATOR)); + localized_strings->SetString("extensionKeywordsListTitle", + l10n_util::GetStringUTF16( + IDS_SEARCH_ENGINES_EDITOR_EXTENSIONS_SEPARATOR)); + localized_strings->SetString("manageExtensionsLinkText", + l10n_util::GetStringUTF16(IDS_MANAGE_EXTENSIONS)); + localized_strings->SetString("searchEngineTableNameHeader", + l10n_util::GetStringUTF16(IDS_SEARCH_ENGINES_EDITOR_DESCRIPTION_COLUMN)); + localized_strings->SetString("searchEngineTableKeywordHeader", + l10n_util::GetStringUTF16(IDS_SEARCH_ENGINES_EDITOR_KEYWORD_COLUMN)); + localized_strings->SetString("searchEngineTableURLHeader", + l10n_util::GetStringUTF16(IDS_SEARCH_ENGINES_EDITOR_EDIT_BUTTON)); + localized_strings->SetString("makeDefaultSearchEngineButton", + l10n_util::GetStringUTF16(IDS_SEARCH_ENGINES_EDITOR_MAKE_DEFAULT_BUTTON)); + localized_strings->SetString("searchEngineTableNamePlaceholder", + l10n_util::GetStringUTF16(IDS_SEARCH_ENGINE_ADD_NEW_NAME_PLACEHOLDER)); + localized_strings->SetString("searchEngineTableKeywordPlaceholder", + l10n_util::GetStringUTF16(IDS_SEARCH_ENGINE_ADD_NEW_KEYWORD_PLACEHOLDER)); + localized_strings->SetString("searchEngineTableURLPlaceholder", + l10n_util::GetStringUTF16(IDS_SEARCH_ENGINE_ADD_NEW_URL_PLACEHOLDER)); + localized_strings->SetString("editSearchEngineInvalidTitleToolTip", + l10n_util::GetStringUTF16(IDS_SEARCH_ENGINES_INVALID_TITLE_TT)); + localized_strings->SetString("editSearchEngineInvalidKeywordToolTip", + l10n_util::GetStringUTF16(IDS_SEARCH_ENGINES_INVALID_KEYWORD_TT)); + localized_strings->SetString("editSearchEngineInvalidURLToolTip", + l10n_util::GetStringUTF16(IDS_SEARCH_ENGINES_INVALID_URL_TT)); +} + +void SearchEngineManagerHandler::RegisterMessages() { + web_ui_->RegisterMessageCallback( + "managerSetDefaultSearchEngine", + base::Bind(&SearchEngineManagerHandler::SetDefaultSearchEngine, + base::Unretained(this))); + web_ui_->RegisterMessageCallback( + "removeSearchEngine", + base::Bind(&SearchEngineManagerHandler::RemoveSearchEngine, + base::Unretained(this))); + web_ui_->RegisterMessageCallback( + "editSearchEngine", + base::Bind(&SearchEngineManagerHandler::EditSearchEngine, + base::Unretained(this))); + web_ui_->RegisterMessageCallback( + "checkSearchEngineInfoValidity", + base::Bind(&SearchEngineManagerHandler::CheckSearchEngineInfoValidity, + base::Unretained(this))); + web_ui_->RegisterMessageCallback( + "searchEngineEditCancelled", + base::Bind(&SearchEngineManagerHandler::EditCancelled, + base::Unretained(this))); + web_ui_->RegisterMessageCallback( + "searchEngineEditCompleted", + base::Bind(&SearchEngineManagerHandler::EditCompleted, + base::Unretained(this))); +} + +void SearchEngineManagerHandler::OnModelChanged() { + if (!list_controller_->loaded()) + return; + + // Find the default engine. + const TemplateURL* default_engine = + list_controller_->url_model()->GetDefaultSearchProvider(); + int default_index = list_controller_->table_model()->IndexOfTemplateURL( + default_engine); + + // Build the first list (default search engine options). + ListValue defaults_list; + int last_default_engine_index = + list_controller_->table_model()->last_search_engine_index(); + for (int i = 0; i < last_default_engine_index; ++i) { + defaults_list.Append(CreateDictionaryForEngine(i, i == default_index)); + } + + // Build the second list (other search templates). + ListValue others_list; + if (last_default_engine_index < 0) + last_default_engine_index = 0; + int engine_count = list_controller_->table_model()->RowCount(); + for (int i = last_default_engine_index; i < engine_count; ++i) { + others_list.Append(CreateDictionaryForEngine(i, i == default_index)); + } + + // Build the extension keywords list. + ListValue keyword_list; + ExtensionService* extension_service = + Profile::FromWebUI(web_ui_)->GetExtensionService(); + if (extension_service) { + const ExtensionSet* extensions = extension_service->extensions(); + for (ExtensionSet::const_iterator it = extensions->begin(); + it != extensions->end(); ++it) { + if ((*it)->omnibox_keyword().size() > 0) + keyword_list.Append(CreateDictionaryForExtension(*(*it))); + } + } + + web_ui_->CallJavascriptFunction("SearchEngineManager.updateSearchEngineList", + defaults_list, others_list, keyword_list); +} + +void SearchEngineManagerHandler::OnItemsChanged(int start, int length) { + OnModelChanged(); +} + +void SearchEngineManagerHandler::OnItemsAdded(int start, int length) { + OnModelChanged(); +} + +void SearchEngineManagerHandler::OnItemsRemoved(int start, int length) { + OnModelChanged(); +} + +base::DictionaryValue* SearchEngineManagerHandler::CreateDictionaryForExtension( + const Extension& extension) { + base::DictionaryValue* dict = new base::DictionaryValue(); + dict->SetString("name", extension.name()); + dict->SetString("displayName", extension.name()); + dict->SetString("keyword", extension.omnibox_keyword()); + GURL icon = extension.GetIconURL(16, ExtensionIconSet::MATCH_BIGGER); + dict->SetString("iconURL", icon.spec()); + dict->SetString("url", string16()); + return dict; +} + +base::DictionaryValue* SearchEngineManagerHandler::CreateDictionaryForEngine( + int index, bool is_default) { + TemplateURLTableModel* table_model = list_controller_->table_model(); + const TemplateURL* template_url = list_controller_->GetTemplateURL(index); + + base::DictionaryValue* dict = new base::DictionaryValue(); + dict->SetString("name", template_url->short_name()); + dict->SetString("displayName", table_model->GetText( + index, IDS_SEARCH_ENGINES_EDITOR_DESCRIPTION_COLUMN)); + dict->SetString("keyword", table_model->GetText( + index, IDS_SEARCH_ENGINES_EDITOR_KEYWORD_COLUMN)); + dict->SetString("url", template_url->url()->DisplayURL()); + dict->SetBoolean("urlLocked", template_url->prepopulate_id() > 0); + GURL icon_url = template_url->GetFaviconURL(); + if (icon_url.is_valid()) + dict->SetString("iconURL", icon_url.spec()); + dict->SetString("modelIndex", base::IntToString(index)); + + if (list_controller_->CanRemove(template_url)) + dict->SetString("canBeRemoved", "1"); + if (list_controller_->CanMakeDefault(template_url)) + dict->SetString("canBeDefault", "1"); + if (is_default) + dict->SetString("default", "1"); + if (list_controller_->CanEdit(template_url)) + dict->SetString("canBeEdited", "1"); + + return dict; +} + +void SearchEngineManagerHandler::SetDefaultSearchEngine(const ListValue* args) { + int index; + if (!ExtractIntegerValue(args, &index)) { + NOTREACHED(); + return; + } + if (index < 0 || index >= list_controller_->table_model()->RowCount()) + return; + + list_controller_->MakeDefaultTemplateURL(index); +} + +void SearchEngineManagerHandler::RemoveSearchEngine(const ListValue* args) { + int index; + if (!ExtractIntegerValue(args, &index)) { + NOTREACHED(); + return; + } + if (index < 0 || index >= list_controller_->table_model()->RowCount()) + return; + + if (list_controller_->CanRemove(list_controller_->GetTemplateURL(index))) + list_controller_->RemoveTemplateURL(index); +} + +void SearchEngineManagerHandler::EditSearchEngine(const ListValue* args) { + int index; + if (!ExtractIntegerValue(args, &index)) { + NOTREACHED(); + return; + } + // Allow -1, which means we are adding a new engine. + if (index < -1 || index >= list_controller_->table_model()->RowCount()) + return; + + const TemplateURL* edit_url = NULL; + if (index != -1) + edit_url = list_controller_->GetTemplateURL(index); + edit_controller_.reset(new EditSearchEngineController( + edit_url, this, Profile::FromWebUI(web_ui_))); +} + +void SearchEngineManagerHandler::OnEditedKeyword( + const TemplateURL* template_url, + const string16& title, + const string16& keyword, + const std::string& url) { + if (template_url) { + list_controller_->ModifyTemplateURL(template_url, title, keyword, url); + } else { + list_controller_->AddTemplateURL(title, keyword, url); + } + edit_controller_.reset(); +} + +void SearchEngineManagerHandler::CheckSearchEngineInfoValidity( + const ListValue* args) +{ + if (!edit_controller_.get()) + return; + string16 name; + string16 keyword; + std::string url; + std::string modelIndex; + if (!args->GetString(ENGINE_NAME, &name) || + !args->GetString(ENGINE_KEYWORD, &keyword) || + !args->GetString(ENGINE_URL, &url) || + !args->GetString(3, &modelIndex)) { + NOTREACHED(); + return; + } + + base::DictionaryValue validity; + validity.SetBoolean("name", edit_controller_->IsTitleValid(name)); + validity.SetBoolean("keyword", edit_controller_->IsKeywordValid(keyword)); + validity.SetBoolean("url", edit_controller_->IsURLValid(url)); + StringValue indexValue(modelIndex); + web_ui_->CallJavascriptFunction("SearchEngineManager.validityCheckCallback", + validity, indexValue); +} + +void SearchEngineManagerHandler::EditCancelled(const ListValue* args) { + if (!edit_controller_.get()) + return; + edit_controller_->CleanUpCancelledAdd(); + edit_controller_.reset(); +} + +void SearchEngineManagerHandler::EditCompleted(const ListValue* args) { + if (!edit_controller_.get()) + return; + string16 name; + string16 keyword; + std::string url; + if (!args->GetString(ENGINE_NAME, &name) || + !args->GetString(ENGINE_KEYWORD, &keyword) || + !args->GetString(ENGINE_URL, &url)) { + NOTREACHED(); + return; + } + edit_controller_->AcceptAddOrEdit(name, keyword, url); +} diff --git a/chrome/browser/ui/webui/options2/search_engine_manager_handler.h b/chrome/browser/ui/webui/options2/search_engine_manager_handler.h new file mode 100644 index 0000000..aa6815d --- /dev/null +++ b/chrome/browser/ui/webui/options2/search_engine_manager_handler.h @@ -0,0 +1,79 @@ +// Copyright (c) 2011 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. + +#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS2_SEARCH_ENGINE_MANAGER_HANDLER_H_ +#define CHROME_BROWSER_UI_WEBUI_OPTIONS2_SEARCH_ENGINE_MANAGER_HANDLER_H_ + +#include "chrome/browser/ui/search_engines/edit_search_engine_controller.h" +#include "chrome/browser/ui/webui/options2/options_ui2.h" +#include "ui/base/models/table_model_observer.h" + +class Extension; +class KeywordEditorController; + +class SearchEngineManagerHandler : public OptionsPage2UIHandler, + public ui::TableModelObserver, + public EditSearchEngineControllerDelegate { + public: + SearchEngineManagerHandler(); + virtual ~SearchEngineManagerHandler(); + + virtual void Initialize() OVERRIDE; + + // OptionsPage2UIHandler implementation. + virtual void GetLocalizedValues( + base::DictionaryValue* localized_strings) OVERRIDE; + + // ui::TableModelObserver implementation. + virtual void OnModelChanged() OVERRIDE; + virtual void OnItemsChanged(int start, int length) OVERRIDE; + virtual void OnItemsAdded(int start, int length) OVERRIDE; + virtual void OnItemsRemoved(int start, int length) OVERRIDE; + + // EditSearchEngineControllerDelegate implementation. + virtual void OnEditedKeyword(const TemplateURL* template_url, + const string16& title, + const string16& keyword, + const std::string& url) OVERRIDE; + + virtual void RegisterMessages() OVERRIDE; + + private: + scoped_ptr<KeywordEditorController> list_controller_; + scoped_ptr<EditSearchEngineController> edit_controller_; + + // Removes the search engine at the given index. Called from WebUI. + void RemoveSearchEngine(const base::ListValue* args); + + // Sets the search engine at the given index to be default. Called from WebUI. + void SetDefaultSearchEngine(const base::ListValue* args); + + // Starts an edit session for the search engine at the given index. If the + // index is -1, starts editing a new search engine instead of an existing one. + // Called from WebUI. + void EditSearchEngine(const base::ListValue* args); + + // Validates the given search engine values, and reports the results back + // to WebUI. Called from WebUI. + void CheckSearchEngineInfoValidity(const base::ListValue* args); + + // Called when an edit is cancelled. + // Called from WebUI. + void EditCancelled(const base::ListValue* args); + + // Called when an edit is finished and should be saved. + // Called from WebUI. + void EditCompleted(const base::ListValue* args); + + // Returns a dictionary to pass to WebUI representing the given search engine. + base::DictionaryValue* CreateDictionaryForEngine(int index, bool is_default); + + // Returns a dictionary to pass to WebUI representing the extension. + base::DictionaryValue* CreateDictionaryForExtension( + const Extension& extension); + + DISALLOW_COPY_AND_ASSIGN(SearchEngineManagerHandler); +}; + +#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS2_SEARCH_ENGINE_MANAGER_HANDLER_H_ diff --git a/chrome/browser/ui/webui/options2/stop_syncing_handler.cc b/chrome/browser/ui/webui/options2/stop_syncing_handler.cc new file mode 100644 index 0000000..550cc4c --- /dev/null +++ b/chrome/browser/ui/webui/options2/stop_syncing_handler.cc @@ -0,0 +1,54 @@ +// Copyright (c) 2011 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. + +#include "chrome/browser/ui/webui/options2/stop_syncing_handler.h" + +#include "base/basictypes.h" +#include "base/bind.h" +#include "base/utf_string_conversions.h" +#include "base/values.h" +#include "chrome/browser/google/google_util.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/profiles/profile_manager.h" +#include "chrome/browser/sync/profile_sync_service.h" +#include "chrome/common/url_constants.h" +#include "grit/chromium_strings.h" +#include "grit/generated_resources.h" +#include "ui/base/l10n/l10n_util.h" + +StopSyncingHandler::StopSyncingHandler() { +} + +StopSyncingHandler::~StopSyncingHandler() { +} + +void StopSyncingHandler::GetLocalizedValues( + DictionaryValue* localized_strings) { + DCHECK(localized_strings); + localized_strings->SetString("stopSyncingExplanation", + l10n_util::GetStringFUTF16( + IDS_SYNC_STOP_SYNCING_EXPLANATION_LABEL, + l10n_util::GetStringUTF16(IDS_PRODUCT_NAME), + ASCIIToUTF16(google_util::StringAppendGoogleLocaleParam( + chrome::kSyncGoogleDashboardURL)))); + localized_strings->SetString("stopSyncingTitle", + l10n_util::GetStringUTF16(IDS_SYNC_STOP_SYNCING_DIALOG_TITLE)); + localized_strings->SetString("stopSyncingConfirm", + l10n_util::GetStringUTF16(IDS_SYNC_STOP_SYNCING_CONFIRM_BUTTON_LABEL)); +} + +void StopSyncingHandler::RegisterMessages() { + DCHECK(web_ui_); + web_ui_->RegisterMessageCallback("stopSyncing", + base::Bind(&StopSyncingHandler::StopSyncing, base::Unretained(this))); +} + +void StopSyncingHandler::StopSyncing(const ListValue* args){ + ProfileSyncService* service = + Profile::FromWebUI(web_ui_)->GetProfileSyncService(); + if (service != NULL && ProfileSyncService::IsSyncEnabled()) { + service->DisableForUser(); + ProfileSyncService::SyncEvent(ProfileSyncService::STOP_FROM_OPTIONS); + } +} diff --git a/chrome/browser/ui/webui/options2/stop_syncing_handler.h b/chrome/browser/ui/webui/options2/stop_syncing_handler.h new file mode 100644 index 0000000..2c8b626 --- /dev/null +++ b/chrome/browser/ui/webui/options2/stop_syncing_handler.h @@ -0,0 +1,29 @@ +// Copyright (c) 2011 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. + +#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS2_STOP_SYNCING_HANDLER_H_ +#define CHROME_BROWSER_UI_WEBUI_OPTIONS2_STOP_SYNCING_HANDLER_H_ + +#include "chrome/browser/ui/webui/options2/options_ui2.h" + +// Chrome personal stuff stop syncing overlay UI handler. +class StopSyncingHandler : public OptionsPage2UIHandler { + public: + StopSyncingHandler(); + virtual ~StopSyncingHandler(); + + // OptionsPage2UIHandler implementation. + virtual void GetLocalizedValues( + base::DictionaryValue* localized_strings) OVERRIDE; + + // WebUIMessageHandler implementation. + virtual void RegisterMessages() OVERRIDE; + + private: + void StopSyncing(const base::ListValue* args); + + DISALLOW_COPY_AND_ASSIGN(StopSyncingHandler); +}; + +#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS2_STOP_SYNCING_HANDLER_H_ diff --git a/chrome/browser/ui/webui/options2/web_intents_settings_handler.cc b/chrome/browser/ui/webui/options2/web_intents_settings_handler.cc new file mode 100644 index 0000000..874cf834 --- /dev/null +++ b/chrome/browser/ui/webui/options2/web_intents_settings_handler.cc @@ -0,0 +1,160 @@ +// Copyright (c) 2011 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. + +#include "chrome/browser/ui/webui/options2/web_intents_settings_handler.h" + +#include "base/bind.h" +#include "base/utf_string_conversions.h" +#include "base/values.h" +#include "chrome/browser/browsing_data_appcache_helper.h" +#include "chrome/browser/browsing_data_database_helper.h" +#include "chrome/browser/browsing_data_file_system_helper.h" +#include "chrome/browser/browsing_data_indexed_db_helper.h" +#include "chrome/browser/browsing_data_local_storage_helper.h" +#include "chrome/browser/intents/web_intents_registry.h" +#include "chrome/browser/intents/web_intents_registry_factory.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/webdata/web_data_service.h" +#include "content/browser/webui/web_ui.h" +#include "grit/generated_resources.h" +#include "net/url_request/url_request_context_getter.h" +#include "ui/base/l10n/l10n_util.h" + +WebIntentsSettingsHandler::WebIntentsSettingsHandler() + : web_intents_registry_(NULL), + batch_update_(false) { +} + +WebIntentsSettingsHandler::~WebIntentsSettingsHandler() { +} + +void WebIntentsSettingsHandler::GetLocalizedValues( + DictionaryValue* localized_strings) { + DCHECK(localized_strings); + + static OptionsStringResource resources[] = { + { "intentsDomain", IDS_INTENTS_DOMAIN_COLUMN_HEADER }, + { "intentsServiceData", IDS_INTENTS_SERVICE_DATA_COLUMN_HEADER }, + { "manageIntents", IDS_INTENTS_MANAGE_BUTTON }, + { "removeIntent", IDS_INTENTS_REMOVE_INTENT_BUTTON }, + }; + + RegisterStrings(localized_strings, resources, arraysize(resources)); + RegisterTitle(localized_strings, "intentsViewPage", + IDS_INTENTS_MANAGER_WINDOW_TITLE); +} + +void WebIntentsSettingsHandler::RegisterMessages() { + web_ui_->RegisterMessageCallback("removeIntent", + base::Bind(&WebIntentsSettingsHandler::RemoveIntent, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("loadIntents", + base::Bind(&WebIntentsSettingsHandler::LoadChildren, + base::Unretained(this))); +} + +void WebIntentsSettingsHandler::TreeNodesAdded(ui::TreeModel* model, + ui::TreeModelNode* parent, + int start, + int count) { + SendChildren(intents_tree_model_->GetRoot()); +} + +void WebIntentsSettingsHandler::TreeNodesRemoved(ui::TreeModel* model, + ui::TreeModelNode* parent, + int start, + int count) { + SendChildren(intents_tree_model_->GetRoot()); +} + +void WebIntentsSettingsHandler::TreeModelBeginBatch(WebIntentsModel* model) { + batch_update_ = true; +} + +void WebIntentsSettingsHandler::TreeModelEndBatch(WebIntentsModel* model) { + batch_update_ = false; + + SendChildren(intents_tree_model_->GetRoot()); +} + +void WebIntentsSettingsHandler::EnsureWebIntentsModelCreated() { + if (intents_tree_model_.get()) return; + + Profile* profile = Profile::FromWebUI(web_ui_); + web_intents_registry_ = WebIntentsRegistryFactory::GetForProfile(profile); + intents_tree_model_.reset(new WebIntentsModel(web_intents_registry_)); + intents_tree_model_->AddWebIntentsTreeObserver(this); +} + +void WebIntentsSettingsHandler::RemoveIntent(const base::ListValue* args) { + std::string node_path; + if (!args->GetString(0, &node_path)) { + return; + } + + EnsureWebIntentsModelCreated(); + + WebIntentsTreeNode* node = intents_tree_model_->GetTreeNode(node_path); + if (node->Type() == WebIntentsTreeNode::TYPE_ORIGIN) { + RemoveOrigin(node); + } else if (node->Type() == WebIntentsTreeNode::TYPE_SERVICE) { + ServiceTreeNode* snode = static_cast<ServiceTreeNode*>(node); + RemoveService(snode); + } +} + +void WebIntentsSettingsHandler::RemoveOrigin(WebIntentsTreeNode* node) { + // TODO(gbillock): This is a known batch update. Worth optimizing? + while (!node->empty()) { + WebIntentsTreeNode* cnode = node->GetChild(0); + CHECK(cnode->Type() == WebIntentsTreeNode::TYPE_SERVICE); + ServiceTreeNode* snode = static_cast<ServiceTreeNode*>(cnode); + RemoveService(snode); + } + delete intents_tree_model_->Remove(node->parent(), node); +} + +void WebIntentsSettingsHandler::RemoveService(ServiceTreeNode* snode) { + webkit_glue::WebIntentServiceData service; + service.service_url = GURL(snode->ServiceUrl()); + service.action = snode->Action(); + string16 stype; + if (snode->Types().GetString(0, &stype)) { + service.type = stype; // Really need to iterate here. + } + service.title = snode->ServiceName(); + web_intents_registry_->UnregisterIntentProvider(service); + delete intents_tree_model_->Remove(snode->parent(), snode); +} + +void WebIntentsSettingsHandler::LoadChildren(const base::ListValue* args) { + EnsureWebIntentsModelCreated(); + + std::string node_path; + if (!args->GetString(0, &node_path)) { + SendChildren(intents_tree_model_->GetRoot()); + return; + } + + WebIntentsTreeNode* node = intents_tree_model_->GetTreeNode(node_path); + SendChildren(node); +} + +void WebIntentsSettingsHandler::SendChildren(WebIntentsTreeNode* parent) { + // Early bailout during batch updates. We'll get one after the batch concludes + // with batch_update_ set false. + if (batch_update_) return; + + ListValue* children = new ListValue; + intents_tree_model_->GetChildNodeList(parent, 0, parent->child_count(), + children); + + ListValue args; + args.Append(parent == intents_tree_model_->GetRoot() ? + Value::CreateNullValue() : + Value::CreateStringValue(intents_tree_model_->GetTreeNodeId(parent))); + args.Append(children); + + web_ui_->CallJavascriptFunction("IntentsView.loadChildren", args); +} diff --git a/chrome/browser/ui/webui/options2/web_intents_settings_handler.h b/chrome/browser/ui/webui/options2/web_intents_settings_handler.h new file mode 100644 index 0000000..186b295 --- /dev/null +++ b/chrome/browser/ui/webui/options2/web_intents_settings_handler.h @@ -0,0 +1,77 @@ +// Copyright (c) 2011 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. + +#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS2_WEB_INTENTS_SETTINGS_HANDLER_H_ +#define CHROME_BROWSER_UI_WEBUI_OPTIONS2_WEB_INTENTS_SETTINGS_HANDLER_H_ +#pragma once + +#include "base/compiler_specific.h" +#include "base/memory/scoped_ptr.h" +#include "chrome/browser/ui/intents/web_intents_model.h" +#include "chrome/browser/ui/webui/options2/options_ui2.h" + +class WebIntentsRegistry; + +// Manage setting up the backing data for the web intents options page. +class WebIntentsSettingsHandler : public OptionsPage2UIHandler, + public WebIntentsModel::Observer { + public: + WebIntentsSettingsHandler(); + virtual ~WebIntentsSettingsHandler(); + + // OptionsPage2UIHandler implementation. + virtual void GetLocalizedValues( + base::DictionaryValue* localized_strings) OVERRIDE; + virtual void RegisterMessages() OVERRIDE; + + // WebIntentsModel::Observer implementation. + virtual void TreeNodesAdded(ui::TreeModel* model, + ui::TreeModelNode* parent, + int start, + int count) OVERRIDE; + virtual void TreeNodesRemoved(ui::TreeModel* model, + ui::TreeModelNode* parent, + int start, + int count) OVERRIDE; + virtual void TreeNodeChanged(ui::TreeModel* model, + ui::TreeModelNode* node) OVERRIDE {} + virtual void TreeModelBeginBatch(WebIntentsModel* model) OVERRIDE; + virtual void TreeModelEndBatch(WebIntentsModel* model) OVERRIDE; + + private: + // Creates the WebIntentsModel if neccessary. + void EnsureWebIntentsModelCreated(); + + // Updates search filter for cookies tree model. + void UpdateSearchResults(const base::ListValue* args); + + // Remove all sites data. + void RemoveAll(const base::ListValue* args); + + // Remove selected sites data. + void RemoveIntent(const base::ListValue* args); + + // Helper functions for removals. + void RemoveOrigin(WebIntentsTreeNode* node); + void RemoveService(ServiceTreeNode* snode); + + // Trigger for SendChildren to load the JS model. + void LoadChildren(const base::ListValue* args); + + // Get children nodes data and pass it to 'IntentsView.loadChildren' to + // update the WebUI. + void SendChildren(WebIntentsTreeNode* parent); + + WebIntentsRegistry* web_intents_registry_; // Weak pointer. + + // Backing data model for the intents list. + scoped_ptr<WebIntentsModel> intents_tree_model_; + + // Flag to indicate whether there is a batch update in progress. + bool batch_update_; + + DISALLOW_COPY_AND_ASSIGN(WebIntentsSettingsHandler); +}; + +#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS2_WEB_INTENTS_SETTINGS_HANDLER_H_ diff --git a/chrome/browser/ui/webui/sync_promo_handler2.cc b/chrome/browser/ui/webui/sync_promo_handler2.cc new file mode 100644 index 0000000..9788d45 --- /dev/null +++ b/chrome/browser/ui/webui/sync_promo_handler2.cc @@ -0,0 +1,288 @@ +// Copyright (c) 2011 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. + +#include "chrome/browser/ui/webui/sync_promo_handler2.h" + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/metrics/histogram.h" +#include "base/time.h" +#include "chrome/browser/prefs/pref_service.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/sync/profile_sync_service.h" +#include "chrome/browser/sync/sync_setup_flow.h" +#include "chrome/browser/tabs/tab_strip_model.h" +#include "chrome/browser/ui/browser.h" +#include "chrome/browser/ui/browser_list.h" +#include "chrome/browser/ui/webui/sync_promo_trial.h" +#include "chrome/browser/ui/webui/sync_promo_ui.h" +#include "chrome/common/chrome_notification_types.h" +#include "chrome/common/extensions/extension_constants.h" +#include "chrome/common/pref_names.h" +#include "chrome/common/url_constants.h" +#include "content/browser/tab_contents/tab_contents.h" +#include "content/public/browser/notification_details.h" +#include "content/public/browser/notification_service.h" + +namespace { + +// User actions on the sync promo (aka "Sign in to Chrome"). +enum SyncPromoUserFlowActionEnums { + SYNC_PROMO_VIEWED, + SYNC_PROMO_LEARN_MORE_CLICKED, + SYNC_PROMO_ACCOUNT_HELP_CLICKED, + SYNC_PROMO_CREATE_ACCOUNT_CLICKED, + SYNC_PROMO_SKIP_CLICKED, + SYNC_PROMO_SIGN_IN_ATTEMPTED, + SYNC_PROMO_SIGNED_IN_SUCCESSFULLY, + SYNC_PROMO_ADVANCED_CLICKED, + SYNC_PROMO_ENCRYPTION_HELP_CLICKED, + SYNC_PROMO_CANCELLED_AFTER_SIGN_IN, + SYNC_PROMO_CONFIRMED_AFTER_SIGN_IN, + SYNC_PROMO_CLOSED_TAB, + SYNC_PROMO_CLOSED_WINDOW, + SYNC_PROMO_LEFT_DURING_THROBBER, + SYNC_PROMO_BUCKET_BOUNDARY, + SYNC_PROMO_FIRST_VALID_JS_ACTION = SYNC_PROMO_LEARN_MORE_CLICKED, + SYNC_PROMO_LAST_VALID_JS_ACTION = SYNC_PROMO_CONFIRMED_AFTER_SIGN_IN, +}; + +// This was added because of the need to change the existing UMA enum for the +// sync promo mid-flight. Ideally these values would be contiguous, but the +// real world is not always ideal. +static bool IsValidUserFlowAction(int action) { + return (action >= SYNC_PROMO_FIRST_VALID_JS_ACTION && + action <= SYNC_PROMO_LAST_VALID_JS_ACTION) || + action == SYNC_PROMO_LEFT_DURING_THROBBER; +} + +} // namespace + +SyncPromoHandler2::SyncPromoHandler2(ProfileManager* profile_manager) + : SyncSetupHandler2(profile_manager), + window_already_closed_(false) { +} + +SyncPromoHandler2::~SyncPromoHandler2() { +} + +// static +void SyncPromoHandler2::RegisterUserPrefs(PrefService* prefs) { + prefs->RegisterIntegerPref(prefs::kSyncPromoViewCount, 0, + PrefService::UNSYNCABLE_PREF); + prefs->RegisterBooleanPref(prefs::kSyncPromoShowNTPBubble, false, + PrefService::UNSYNCABLE_PREF); +} + +WebUIMessageHandler* SyncPromoHandler2::Attach(WebUI* web_ui) { + DCHECK(web_ui); + // Keep a reference to the preferences service for convenience and it's + // probably a little faster that getting it via Profile::FromWebUI() every + // time we need to interact with preferences. + prefs_ = Profile::FromWebUI(web_ui)->GetPrefs(); + DCHECK(prefs_); + // Ignore events from view-source:chrome://syncpromo. + if (!web_ui->tab_contents()->controller().GetActiveEntry()-> + IsViewSourceMode()) { + // Listen to see if the tab we're in gets closed. + registrar_.Add(this, content::NOTIFICATION_TAB_CLOSING, + content::Source<NavigationController>( + &web_ui->tab_contents()->controller())); + // Listen to see if the window we're in gets closed. + registrar_.Add(this, chrome::NOTIFICATION_BROWSER_CLOSING, + content::NotificationService::AllSources()); + } + return SyncSetupHandler2::Attach(web_ui); +} + +void SyncPromoHandler2::RegisterMessages() { + web_ui_->RegisterMessageCallback("SyncPromo:Close", + base::Bind(&SyncPromoHandler2::HandleCloseSyncPromo, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("SyncPromo:Initialize", + base::Bind(&SyncPromoHandler2::HandleInitializeSyncPromo, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("SyncPromo:RecordSignInAttempts", + base::Bind(&SyncPromoHandler2::HandleRecordSignInAttempts, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("SyncPromo:RecordThrobberTime", + base::Bind(&SyncPromoHandler2::HandleRecordThrobberTime, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("SyncPromo:ShowAdvancedSettings", + base::Bind(&SyncPromoHandler2::HandleShowAdvancedSettings, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("SyncPromo:UserFlowAction", + base::Bind(&SyncPromoHandler2::HandleUserFlowAction, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("SyncPromo:UserSkipped", + base::Bind(&SyncPromoHandler2::HandleUserSkipped, + base::Unretained(this))); + SyncSetupHandler2::RegisterMessages(); +} + +void SyncPromoHandler2::ShowGaiaSuccessAndClose() { + if (sync_promo_trial::IsExperimentActive()) + sync_promo_trial::RecordUserSignedIn(); + + SyncSetupHandler2::ShowGaiaSuccessAndClose(); +} + +void SyncPromoHandler2::ShowGaiaSuccessAndSettingUp() { + if (sync_promo_trial::IsExperimentActive()) + sync_promo_trial::RecordUserSignedIn(); + + SyncSetupHandler2::ShowGaiaSuccessAndSettingUp(); +} + +void SyncPromoHandler2::ShowConfigure(const base::DictionaryValue& args) { + bool usePassphrase = false; + args.GetBoolean("usePassphrase", &usePassphrase); + + if (usePassphrase) { + // If a passphrase is required then we must show the configure pane. + SyncSetupHandler2::ShowConfigure(args); + } else { + // If no passphrase is required then skip the configure pane and sync + // everything by default. This makes the first run experience simpler. + // Note, there's an advanced link in the sync promo that takes users + // to Settings where the configure pane is not skipped. + SyncConfiguration configuration; + configuration.sync_everything = true; + DCHECK(flow()); + flow()->OnUserConfigured(configuration); + } +} + +void SyncPromoHandler2::Observe(int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) { + switch (type) { + case content::NOTIFICATION_TAB_CLOSING: { + if (!window_already_closed_) + RecordUserFlowAction(SYNC_PROMO_CLOSED_TAB); + break; + } + case chrome::NOTIFICATION_BROWSER_CLOSING: { + // Make sure we're in the tab strip of the closing window. + Browser* browser = content::Source<Browser>(source).ptr(); + if (browser->tabstrip_model()->GetWrapperIndex( + web_ui_->tab_contents()) != TabStripModel::kNoTab) { + RecordUserFlowAction(SYNC_PROMO_CLOSED_WINDOW); + window_already_closed_ = true; + } + break; + } + default: { + NOTREACHED(); + } + } +} + +void SyncPromoHandler2::StepWizardForShowSetupUI() { + ProfileSyncService* service = + Profile::FromWebUI(web_ui_)->GetProfileSyncService(); + service->get_wizard().Step(SyncSetupWizard::GetLoginState()); +} + +void SyncPromoHandler2::ShowSetupUI() { + // We don't need to do anything here; The UI for the sync promo is already + // displayed. +} + +void SyncPromoHandler2::HandleCloseSyncPromo(const base::ListValue* args) { + CloseSyncSetup(); + + // If the user has signed in then set the pref to show them NTP bubble + // confirming that they're signed in. + std::string username = prefs_->GetString(prefs::kGoogleServicesUsername); + if (!username.empty()) + prefs_->SetBoolean(prefs::kSyncPromoShowNTPBubble, true); + + GURL url = SyncPromoUI::GetNextPageURLForSyncPromoURL( + web_ui_->tab_contents()->GetURL()); + web_ui_->tab_contents()->OpenURL(url, GURL(), CURRENT_TAB, + content::PAGE_TRANSITION_LINK); +} + +void SyncPromoHandler2::HandleInitializeSyncPromo(const base::ListValue* args) { + // If the promo is also the Chrome launch page, we want to show the title and + // log an event if we are running an experiment. + bool is_launch_page = SyncPromoUI::GetIsLaunchPageForSyncPromoURL( + web_ui_->tab_contents()->GetURL()); + if (is_launch_page && sync_promo_trial::IsExperimentActive()) + sync_promo_trial::RecordUserSawMessage(); + base::FundamentalValue visible(is_launch_page); + web_ui_->CallJavascriptFunction("SyncSetupOverlay.setPromoTitleVisible", + visible); + + OpenSyncSetup(); + // We don't need to compute anything for this, just do this every time. + RecordUserFlowAction(SYNC_PROMO_VIEWED); + // Increment view count first and show natural numbers in stats rather than 0 + // based starting point (if it happened to be our first time showing this). + IncrementViewCountBy(1); + // Record +1 for every view. This is the only thing we record that's not part + // of the user flow histogram. + UMA_HISTOGRAM_COUNTS("SyncPromo.NumTimesViewed", GetViewCount()); +} + +void SyncPromoHandler2::HandleShowAdvancedSettings( + const base::ListValue* args) { + CloseSyncSetup(); + std::string url(chrome::kChromeUISettingsURL); + url += chrome::kSyncSetupSubPage; + web_ui_->tab_contents()->OpenURL(GURL(url), GURL(), CURRENT_TAB, + content::PAGE_TRANSITION_LINK); + RecordUserFlowAction(SYNC_PROMO_ADVANCED_CLICKED); +} + +// TODO(dbeam): Replace with metricsHandler:recordHistogramTime when it exists. +void SyncPromoHandler2::HandleRecordThrobberTime(const base::ListValue* args) { + double time_double; + CHECK(args->GetDouble(0, &time_double)); + UMA_HISTOGRAM_TIMES("SyncPromo.ThrobberTime", + base::TimeDelta::FromMilliseconds(time_double)); +} + +// TODO(dbeam): Replace with metricsHandler:recordHistogramCount when it exists. +void SyncPromoHandler2::HandleRecordSignInAttempts(const base::ListValue* args) { + double count_double; + CHECK(args->GetDouble(0, &count_double)); + UMA_HISTOGRAM_COUNTS("SyncPromo.SignInAttempts", count_double); +} + +void SyncPromoHandler2::HandleUserFlowAction(const base::ListValue* args) { + double action_double; + CHECK(args->GetDouble(0, &action_double)); + int action = static_cast<int>(action_double); + + if (IsValidUserFlowAction(action)) + RecordUserFlowAction(action); + else + NOTREACHED() << "Attempt to record invalid user flow action on sync promo."; +} + +void SyncPromoHandler2::HandleUserSkipped(const base::ListValue* args) { + SyncPromoUI::SetUserSkippedSyncPromo(Profile::FromWebUI(web_ui_)); + RecordUserFlowAction(SYNC_PROMO_SKIP_CLICKED); +} + +int SyncPromoHandler2::GetViewCount() const { + // The locally persistent number of times the user has seen the sync promo. + return prefs_->GetInteger(prefs::kSyncPromoViewCount); +} + +int SyncPromoHandler2::IncrementViewCountBy(unsigned int amount) { + // Let the user increment by 0 if they really want. It might be useful for a + // weird way of sending preference change notifications... + int adjusted = GetViewCount() + amount; + prefs_->SetInteger(prefs::kSyncPromoViewCount, adjusted); + return adjusted; +} + +void SyncPromoHandler2::RecordUserFlowAction(int action) { + // Send an enumeration to our single user flow histogram. + UMA_HISTOGRAM_ENUMERATION("SyncPromo.UserFlow", action, + SYNC_PROMO_BUCKET_BOUNDARY); +} diff --git a/chrome/browser/ui/webui/sync_promo_handler2.h b/chrome/browser/ui/webui/sync_promo_handler2.h new file mode 100644 index 0000000..c2d3c8f --- /dev/null +++ b/chrome/browser/ui/webui/sync_promo_handler2.h @@ -0,0 +1,97 @@ +// Copyright (c) 2011 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. + +#ifndef CHROME_BROWSER_UI_WEBUI_SYNC_PROMO_HANDLER_H_ +#define CHROME_BROWSER_UI_WEBUI_SYNC_PROMO_HANDLER_H_ +#pragma once + +#include "chrome/browser/ui/webui/sync_setup_handler2.h" + +class PrefService; + +// The handler for JavaScript messages related to the "sync promo" page. +class SyncPromoHandler2 : public SyncSetupHandler2 { + public: + explicit SyncPromoHandler2(ProfileManager* profile_manager); + virtual ~SyncPromoHandler2(); + + // Called to register our preferences before we use them (so there will be a + // default if not present yet). + static void RegisterUserPrefs(PrefService* prefs); + + // WebUIMessageHandler implementation. + virtual WebUIMessageHandler* Attach(WebUI* web_ui) OVERRIDE; + virtual void RegisterMessages() OVERRIDE; + + // SyncSetupFlowHandler implementation. + virtual void ShowGaiaSuccessAndClose() OVERRIDE; + virtual void ShowGaiaSuccessAndSettingUp() OVERRIDE; + virtual void ShowConfigure(const base::DictionaryValue& args) OVERRIDE; + + // content::NotificationObserver implementation. + virtual void Observe(int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) OVERRIDE; + + protected: + virtual void StepWizardForShowSetupUI() OVERRIDE; + + virtual void ShowSetupUI() OVERRIDE; + + private: + // JavaScript callback handler to close the sync promo. + void HandleCloseSyncPromo(const base::ListValue* args); + + // JavaScript callback handler to initialize the sync promo. + void HandleInitializeSyncPromo(const base::ListValue* args); + + // JavaScript handler to record the duration for which the throbber was + // visible during an attempted sign-in flow. + void HandleRecordThrobberTime(const base::ListValue* args); + + // JavaScript handler to record the number of times a user attempted to sign + // in to chrome while they were on the sync promo page. + void HandleRecordSignInAttempts(const base::ListValue* args); + + // JavaScript callback handler to switch the advanced sync settings. |args| is + // the list of arguments passed from JS and should be an empty list. + void HandleShowAdvancedSettings(const base::ListValue* args); + + // JavaScript callback handler to record user actions on the sync promo. + void HandleUserFlowAction(const base::ListValue* args); + + // JavaScript callback handler for when a user clicks skip. + void HandleUserSkipped(const base::ListValue* args); + + // Return the number of times the user with the current profile has seen the + // sync promo. + int GetViewCount() const; + + // Increment the local view count by the specified non-negative integer + // amount. Returns the new total view count. + int IncrementViewCountBy(unsigned int amount); + + // Record a user's flow through the promo to our histogram in UMA. + void RecordUserFlowAction(int action); + + // Load any experiments that run on the promo page. + void LoadPromoExperiments(); + + // Use this to register for certain notifications (currently when tabs or + // windows close). + content::NotificationRegistrar registrar_; + + // Weak reference that's initialized and checked in Attach() (after that + // guaranteed to be non-NULL). + PrefService* prefs_; + + // If the user closes the whole window we'll get a close notification from the + // tab as well, so this bool acts as a small mutex to only report the close + // method once. + bool window_already_closed_; + + DISALLOW_COPY_AND_ASSIGN(SyncPromoHandler2); +}; + +#endif // CHROME_BROWSER_UI_WEBUI_SYNC_PROMO_HANDLER_H_ diff --git a/chrome/browser/ui/webui/sync_setup_handler2.cc b/chrome/browser/ui/webui/sync_setup_handler2.cc new file mode 100644 index 0000000..d47e6a2 --- /dev/null +++ b/chrome/browser/ui/webui/sync_setup_handler2.cc @@ -0,0 +1,772 @@ +// Copyright (c) 2011 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. + +#include "chrome/browser/ui/webui/sync_setup_handler2.h" + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/json/json_reader.h" +#include "base/json/json_writer.h" +#include "base/metrics/histogram.h" +#include "base/utf_string_conversions.h" +#include "base/values.h" +#include "chrome/browser/google/google_util.h" +#include "chrome/browser/prefs/pref_service.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/profiles/profile_info_cache.h" +#include "chrome/browser/profiles/profile_manager.h" +#include "chrome/browser/profiles/profile_metrics.h" +#include "chrome/browser/sync/profile_sync_service.h" +#include "chrome/browser/sync/protocol/service_constants.h" +#include "chrome/browser/sync/signin_manager.h" +#include "chrome/browser/sync/syncable/model_type.h" +#include "chrome/browser/sync/sync_setup_flow.h" +#include "chrome/browser/sync/util/oauth.h" +#include "chrome/browser/ui/browser_list.h" +#include "chrome/browser/ui/webui/sync_promo_trial.h" +#include "chrome/browser/ui/webui/sync_promo_ui.h" +#include "chrome/browser/ui/webui/user_selectable_sync_type.h" +#include "chrome/common/net/gaia/gaia_constants.h" +#include "chrome/common/pref_names.h" +#include "chrome/common/url_constants.h" +#include "content/browser/tab_contents/tab_contents.h" +#include "grit/chromium_strings.h" +#include "grit/generated_resources.h" +#include "grit/locale_settings.h" +#include "ui/base/l10n/l10n_util.h" + +using l10n_util::GetStringFUTF16; +using l10n_util::GetStringUTF16; + +namespace { + +bool GetAuthData(const std::string& json, + std::string* username, + std::string* password, + std::string* captcha, + std::string* access_code) { + scoped_ptr<Value> parsed_value(base::JSONReader::Read(json, false)); + if (!parsed_value.get() || !parsed_value->IsType(Value::TYPE_DICTIONARY)) + return false; + + DictionaryValue* result = static_cast<DictionaryValue*>(parsed_value.get()); + if (!result->GetString("user", username) || + !result->GetString("pass", password) || + !result->GetString("captcha", captcha) || + !result->GetString("access_code", access_code)) { + return false; + } + return true; +} + +bool GetConfiguration(const std::string& json, SyncConfiguration* config) { + scoped_ptr<Value> parsed_value(base::JSONReader::Read(json, false)); + if (!parsed_value.get() || !parsed_value->IsType(Value::TYPE_DICTIONARY)) + return false; + + DictionaryValue* result = static_cast<DictionaryValue*>(parsed_value.get()); + if (!result->GetBoolean("syncAllDataTypes", &config->sync_everything)) + return false; + + // These values need to be kept in sync with where they are written in + // choose_datatypes.html. + bool sync_bookmarks; + if (!result->GetBoolean("syncBookmarks", &sync_bookmarks)) + return false; + if (sync_bookmarks) + config->data_types.Put(syncable::BOOKMARKS); + + bool sync_preferences; + if (!result->GetBoolean("syncPreferences", &sync_preferences)) + return false; + if (sync_preferences) + config->data_types.Put(syncable::PREFERENCES); + + bool sync_themes; + if (!result->GetBoolean("syncThemes", &sync_themes)) + return false; + if (sync_themes) + config->data_types.Put(syncable::THEMES); + + bool sync_passwords; + if (!result->GetBoolean("syncPasswords", &sync_passwords)) + return false; + if (sync_passwords) + config->data_types.Put(syncable::PASSWORDS); + + bool sync_autofill; + if (!result->GetBoolean("syncAutofill", &sync_autofill)) + return false; + if (sync_autofill) + config->data_types.Put(syncable::AUTOFILL); + + bool sync_extensions; + if (!result->GetBoolean("syncExtensions", &sync_extensions)) + return false; + if (sync_extensions) { + config->data_types.Put(syncable::EXTENSIONS); + config->data_types.Put(syncable::EXTENSION_SETTINGS); + } + + bool sync_typed_urls; + if (!result->GetBoolean("syncTypedUrls", &sync_typed_urls)) + return false; + if (sync_typed_urls) + config->data_types.Put(syncable::TYPED_URLS); + + bool sync_sessions; + if (!result->GetBoolean("syncSessions", &sync_sessions)) + return false; + if (sync_sessions) + config->data_types.Put(syncable::SESSIONS); + + bool sync_apps; + if (!result->GetBoolean("syncApps", &sync_apps)) + return false; + if (sync_apps) { + config->data_types.Put(syncable::APPS); + config->data_types.Put(syncable::APP_SETTINGS); + } + + // Encryption settings. + if (!result->GetBoolean("encryptAllData", &config->encrypt_all)) + return false; + + // Passphrase settings. + bool have_passphrase; + if (!result->GetBoolean("usePassphrase", &have_passphrase)) + return false; + + if (have_passphrase) { + bool is_gaia; + if (!result->GetBoolean("isGooglePassphrase", &is_gaia)) + return false; + std::string passphrase; + if (!result->GetString("passphrase", &passphrase)) + return false; + // The user provided a passphrase - pass it off to SyncSetupFlow as either + // the secondary or GAIA passphrase as appropriate. + if (is_gaia) { + config->set_gaia_passphrase = true; + config->gaia_passphrase = passphrase; + } else { + config->set_secondary_passphrase = true; + config->secondary_passphrase = passphrase; + } + } + return true; +} + +bool HasConfigurationChanged(const SyncConfiguration& config, + Profile* profile) { + CHECK(profile); + + // This function must be updated every time a new sync datatype is added to + // the sync preferences page. + COMPILE_ASSERT(17 == syncable::MODEL_TYPE_COUNT, + UpdateCustomConfigHistogram); + + // If service is null or if this is a first time configuration, return true. + ProfileSyncService* service = profile->GetProfileSyncService(); + if (!service || !service->HasSyncSetupCompleted()) + return true; + + if ((config.set_secondary_passphrase || config.set_gaia_passphrase) && + !service->IsUsingSecondaryPassphrase()) + return true; + + if (config.encrypt_all != service->EncryptEverythingEnabled()) + return true; + + PrefService* pref_service = profile->GetPrefs(); + CHECK(pref_service); + + if (config.sync_everything != + pref_service->GetBoolean(prefs::kSyncKeepEverythingSynced)) + return true; + + // Only check the data types that are explicitly listed on the sync + // preferences page. + const syncable::ModelTypeSet types = config.data_types; + if (((types.Has(syncable::BOOKMARKS)) != + pref_service->GetBoolean(prefs::kSyncBookmarks)) || + ((types.Has(syncable::PREFERENCES)) != + pref_service->GetBoolean(prefs::kSyncPreferences)) || + ((types.Has(syncable::THEMES)) != + pref_service->GetBoolean(prefs::kSyncThemes)) || + ((types.Has(syncable::PASSWORDS)) != + pref_service->GetBoolean(prefs::kSyncPasswords)) || + ((types.Has(syncable::AUTOFILL)) != + pref_service->GetBoolean(prefs::kSyncAutofill)) || + ((types.Has(syncable::EXTENSIONS)) != + pref_service->GetBoolean(prefs::kSyncExtensions)) || + ((types.Has(syncable::TYPED_URLS)) != + pref_service->GetBoolean(prefs::kSyncTypedUrls)) || + ((types.Has(syncable::SESSIONS)) != + pref_service->GetBoolean(prefs::kSyncSessions)) || + ((types.Has(syncable::APPS)) != + pref_service->GetBoolean(prefs::kSyncApps))) + return true; + + return false; +} + +bool GetPassphrase(const std::string& json, std::string* passphrase) { + scoped_ptr<Value> parsed_value(base::JSONReader::Read(json, false)); + if (!parsed_value.get() || !parsed_value->IsType(Value::TYPE_DICTIONARY)) + return false; + + DictionaryValue* result = static_cast<DictionaryValue*>(parsed_value.get()); + return result->GetString("passphrase", passphrase); +} + +string16 NormalizeUserName(const string16& user) { + if (user.find_first_of(ASCIIToUTF16("@")) != string16::npos) + return user; + return user + ASCIIToUTF16("@") + ASCIIToUTF16(DEFAULT_SIGNIN_DOMAIN); +} + +bool AreUserNamesEqual(const string16& user1, const string16& user2) { + return NormalizeUserName(user1) == NormalizeUserName(user2); +} + +} // namespace + +SyncSetupHandler2::SyncSetupHandler2(ProfileManager* profile_manager) + : flow_(NULL), + profile_manager_(profile_manager) { +} + +SyncSetupHandler2::~SyncSetupHandler2() { + // This case is hit when the user performs a back navigation. + if (flow_) + flow_->OnDialogClosed(""); +} + +void SyncSetupHandler2::GetLocalizedValues(DictionaryValue* localized_strings) { + GetStaticLocalizedValues(localized_strings, web_ui_); +} + +void SyncSetupHandler2::GetStaticLocalizedValues( + DictionaryValue* localized_strings, + WebUI* web_ui) { + DCHECK(localized_strings); + + localized_strings->SetString( + "invalidPasswordHelpURL", + google_util::StringAppendGoogleLocaleParam( + chrome::kInvalidPasswordHelpURL)); + localized_strings->SetString( + "cannotAccessAccountURL", + google_util::StringAppendGoogleLocaleParam( + chrome::kCanNotAccessAccountURL)); + localized_strings->SetString( + "introduction", + GetStringFUTF16(IDS_SYNC_LOGIN_INTRODUCTION, + GetStringUTF16(IDS_PRODUCT_NAME))); + localized_strings->SetString( + "chooseDataTypesInstructions", + GetStringFUTF16(IDS_SYNC_CHOOSE_DATATYPES_INSTRUCTIONS, + GetStringUTF16(IDS_PRODUCT_NAME))); + localized_strings->SetString( + "encryptionInstructions", + GetStringFUTF16(IDS_SYNC_ENCRYPTION_INSTRUCTIONS, + GetStringUTF16(IDS_PRODUCT_NAME))); + localized_strings->SetString( + "encryptionHelpURL", + google_util::StringAppendGoogleLocaleParam( + chrome::kSyncEncryptionHelpURL)); + localized_strings->SetString( + "passphraseEncryptionMessage", + GetStringFUTF16(IDS_SYNC_PASSPHRASE_ENCRYPTION_MESSAGE, + GetStringUTF16(IDS_PRODUCT_NAME))); + localized_strings->SetString( + "passphraseRecover", + GetStringFUTF16(IDS_SYNC_PASSPHRASE_RECOVER, + ASCIIToUTF16(google_util::StringAppendGoogleLocaleParam( + chrome::kSyncGoogleDashboardURL)))); + localized_strings->SetString( + "promoTitle", + GetStringFUTF16(IDS_SYNC_PROMO_TITLE, + GetStringUTF16(IDS_PRODUCT_NAME))); + localized_strings->SetString( + "promoMessageTitle", + GetStringFUTF16(IDS_SYNC_PROMO_MESSAGE_TITLE, + GetStringUTF16(IDS_SHORT_PRODUCT_NAME))); + localized_strings->SetString( + "syncEverythingHelpURL", + google_util::StringAppendGoogleLocaleParam( + chrome::kSyncEverythingLearnMoreURL)); + + // The experimental body string only appears if we are on the launch page + // version of the Sync Promo. + int message_body_resource_id = IDS_SYNC_PROMO_MESSAGE_BODY_A; + if (web_ui && SyncPromoUI::GetIsLaunchPageForSyncPromoURL( + web_ui->tab_contents()->GetURL())) { + message_body_resource_id = sync_promo_trial::GetMessageBodyResID(); + } + localized_strings->SetString( + "promoMessageBody", + GetStringUTF16(message_body_resource_id)); + + std::string create_account_url = google_util::StringAppendGoogleLocaleParam( + chrome::kSyncCreateNewAccountURL); + string16 create_account = GetStringUTF16(IDS_SYNC_CREATE_ACCOUNT); + create_account= UTF8ToUTF16("<a id='create-account-link' target='_blank' " + "class='account-link' href='" + create_account_url + "'>") + + create_account + UTF8ToUTF16("</a>"); + localized_strings->SetString("createAccountLinkHTML", + GetStringFUTF16(IDS_SYNC_CREATE_ACCOUNT_PREFIX, create_account)); + + static OptionsStringResource resources[] = { + { "syncSetupOverlayTitle", IDS_SYNC_SETUP_TITLE }, + { "syncSetupConfigureTitle", IDS_SYNC_SETUP_CONFIGURE_TITLE }, + { "cannotBeBlank", IDS_SYNC_CANNOT_BE_BLANK }, + { "emailLabel", IDS_SYNC_LOGIN_EMAIL_NEW_LINE }, + { "passwordLabel", IDS_SYNC_LOGIN_PASSWORD_NEW_LINE }, + { "invalidCredentials", IDS_SYNC_INVALID_USER_CREDENTIALS }, + { "signin", IDS_SYNC_SIGNIN }, + { "couldNotConnect", IDS_SYNC_LOGIN_COULD_NOT_CONNECT }, + { "unrecoverableError", IDS_SYNC_UNRECOVERABLE_ERROR }, + { "errorLearnMore", IDS_LEARN_MORE }, + { "unrecoverableErrorHelpURL", IDS_SYNC_UNRECOVERABLE_ERROR_HELP_URL }, + { "cannotAccessAccount", IDS_SYNC_CANNOT_ACCESS_ACCOUNT }, + { "cancel", IDS_CANCEL }, + { "settingUp", IDS_SYNC_LOGIN_SETTING_UP }, + { "errorSigningIn", IDS_SYNC_ERROR_SIGNING_IN }, + { "signinHeader", IDS_SYNC_PROMO_SIGNIN_HEADER}, + { "captchaInstructions", IDS_SYNC_GAIA_CAPTCHA_INSTRUCTIONS }, + { "invalidAccessCode", IDS_SYNC_INVALID_ACCESS_CODE_LABEL }, + { "enterAccessCode", IDS_SYNC_ENTER_ACCESS_CODE_LABEL }, + { "getAccessCodeHelp", IDS_SYNC_ACCESS_CODE_HELP_LABEL }, + { "getAccessCodeURL", IDS_SYNC_GET_ACCESS_CODE_URL }, + { "syncAllDataTypes", IDS_SYNC_EVERYTHING }, + { "chooseDataTypes", IDS_SYNC_CHOOSE_DATATYPES }, + { "bookmarks", IDS_SYNC_DATATYPE_BOOKMARKS }, + { "preferences", IDS_SYNC_DATATYPE_PREFERENCES }, + { "autofill", IDS_SYNC_DATATYPE_AUTOFILL }, + { "themes", IDS_SYNC_DATATYPE_THEMES }, + { "passwords", IDS_SYNC_DATATYPE_PASSWORDS }, + { "extensions", IDS_SYNC_DATATYPE_EXTENSIONS }, + { "typedURLs", IDS_SYNC_DATATYPE_TYPED_URLS }, + { "apps", IDS_SYNC_DATATYPE_APPS }, + { "openTabs", IDS_SYNC_DATATYPE_TABS }, + { "syncZeroDataTypesError", IDS_SYNC_ZERO_DATA_TYPES_ERROR }, + { "serviceUnavailableError", IDS_SYNC_SETUP_ABORTED_BY_PENDING_CLEAR }, + { "encryptAllLabel", IDS_SYNC_ENCRYPT_ALL_LABEL }, + { "googleOption", IDS_SYNC_PASSPHRASE_OPT_GOOGLE }, + { "explicitOption", IDS_SYNC_PASSPHRASE_OPT_EXPLICIT }, + { "sectionGoogleMessage", IDS_SYNC_PASSPHRASE_MSG_GOOGLE }, + { "sectionExplicitMessage", IDS_SYNC_PASSPHRASE_MSG_EXPLICIT }, + { "passphraseLabel", IDS_SYNC_PASSPHRASE_LABEL }, + { "confirmLabel", IDS_SYNC_CONFIRM_PASSPHRASE_LABEL }, + { "emptyErrorMessage", IDS_SYNC_EMPTY_PASSPHRASE_ERROR }, + { "mismatchErrorMessage", IDS_SYNC_PASSPHRASE_MISMATCH_ERROR }, + { "passphraseWarning", IDS_SYNC_PASSPHRASE_WARNING }, + { "customizeLinkLabel", IDS_SYNC_CUSTOMIZE_LINK_LABEL }, + { "confirmSyncPreferences", IDS_SYNC_CONFIRM_SYNC_PREFERENCES }, + { "syncEverything", IDS_SYNC_SYNC_EVERYTHING }, + { "useDefaultSettings", IDS_SYNC_USE_DEFAULT_SETTINGS }, + { "passphraseSectionTitle", IDS_SYNC_PASSPHRASE_SECTION_TITLE }, + { "privacyDashboardLink", IDS_SYNC_PRIVACY_DASHBOARD_LINK_LABEL }, + { "enterPassphraseTitle", IDS_SYNC_ENTER_PASSPHRASE_TITLE }, + { "enterPassphraseBody", IDS_SYNC_ENTER_PASSPHRASE_BODY }, + { "enterOtherPassphraseBody", IDS_SYNC_ENTER_OTHER_PASSPHRASE_BODY }, + { "enterGooglePassphraseBody", IDS_SYNC_ENTER_GOOGLE_PASSPHRASE_BODY }, + { "passphraseLabel", IDS_SYNC_PASSPHRASE_LABEL }, + { "incorrectPassphrase", IDS_SYNC_INCORRECT_PASSPHRASE }, + { "passphraseWarning", IDS_SYNC_PASSPHRASE_WARNING }, + { "cancelWarningHeader", IDS_SYNC_PASSPHRASE_CANCEL_WARNING_HEADER }, + { "cancelWarning", IDS_SYNC_PASSPHRASE_CANCEL_WARNING }, + { "yes", IDS_SYNC_PASSPHRASE_CANCEL_YES }, + { "no", IDS_SYNC_PASSPHRASE_CANCEL_NO }, + { "sectionExplicitMessagePrefix", IDS_SYNC_PASSPHRASE_MSG_EXPLICIT_PREFIX }, + { "sectionExplicitMessagePostfix", + IDS_SYNC_PASSPHRASE_MSG_EXPLICIT_POSTFIX }, + { "encryptedDataTypesTitle", IDS_SYNC_ENCRYPTION_DATA_TYPES_TITLE }, + { "encryptSensitiveOption", IDS_SYNC_ENCRYPT_SENSITIVE_DATA }, + { "encryptAllOption", IDS_SYNC_ENCRYPT_ALL_DATA }, + { "encryptAllOption", IDS_SYNC_ENCRYPT_ALL_DATA }, + { "aspWarningText", IDS_SYNC_ASP_PASSWORD_WARNING_TEXT }, + { "promoPageTitle", IDS_SYNC_PROMO_TAB_TITLE}, + { "promoSkipButton", IDS_SYNC_PROMO_SKIP_BUTTON}, + { "promoAdvanced", IDS_SYNC_PROMO_ADVANCED}, + { "promoLearnMoreShow", IDS_SYNC_PROMO_LEARN_MORE_SHOW}, + { "promoLearnMoreHide", IDS_SYNC_PROMO_LEARN_MORE_HIDE}, + { "promoInformation", IDS_SYNC_PROMO_INFORMATION}, + }; + + RegisterStrings(localized_strings, resources, arraysize(resources)); +} + +void SyncSetupHandler2::Initialize() { +} + +void SyncSetupHandler2::OnGetOAuthTokenSuccess(const std::string& oauth_token) { + flow_->OnUserSubmittedOAuth(oauth_token); +} + +void SyncSetupHandler2::OnGetOAuthTokenFailure( + const GoogleServiceAuthError& error) { + CloseSyncSetup(); +} + +void SyncSetupHandler2::RegisterMessages() { + web_ui_->RegisterMessageCallback("SyncSetupDidClosePage", + base::Bind(&SyncSetupHandler2::OnDidClosePage, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("SyncSetupSubmitAuth", + base::Bind(&SyncSetupHandler2::HandleSubmitAuth, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("SyncSetupConfigure", + base::Bind(&SyncSetupHandler2::HandleConfigure, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("SyncSetupPassphrase", + base::Bind(&SyncSetupHandler2::HandlePassphraseEntry, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("SyncSetupPassphraseCancel", + base::Bind(&SyncSetupHandler2::HandlePassphraseCancel, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("SyncSetupAttachHandler", + base::Bind(&SyncSetupHandler2::HandleAttachHandler, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("SyncSetupShowErrorUI", + base::Bind(&SyncSetupHandler2::HandleShowErrorUI, + base::Unretained(this))); + web_ui_->RegisterMessageCallback("SyncSetupShowSetupUI", + base::Bind(&SyncSetupHandler2::HandleShowSetupUI, + base::Unretained(this))); +} + +// Ideal(?) solution here would be to mimic the ClientLogin overlay. Since +// this UI must render an external URL, that overlay cannot be used directly. +// The current implementation is functional, but fails asthetically. +// TODO(rickcam): Bug 90711: Update UI for OAuth sign-in flow +void SyncSetupHandler2::ShowOAuthLogin() { + DCHECK(browser_sync::IsUsingOAuth()); + + Profile* profile = Profile::FromWebUI(web_ui_); + oauth_login_.reset(new GaiaOAuthFetcher(this, + profile->GetRequestContext(), + profile, + GaiaConstants::kSyncServiceOAuth)); + oauth_login_->SetAutoFetchLimit(GaiaOAuthFetcher::OAUTH1_REQUEST_TOKEN); + oauth_login_->StartGetOAuthToken(); +} + +void SyncSetupHandler2::ShowGaiaLogin(const DictionaryValue& args) { + DCHECK(!browser_sync::IsUsingOAuth()); + StringValue page("login"); + web_ui_->CallJavascriptFunction( + "SyncSetupOverlay.showSyncSetupPage", page, args); +} + +void SyncSetupHandler2::ShowGaiaSuccessAndClose() { + web_ui_->CallJavascriptFunction("SyncSetupOverlay.showSuccessAndClose"); +} + +void SyncSetupHandler2::ShowGaiaSuccessAndSettingUp() { + web_ui_->CallJavascriptFunction("SyncSetupOverlay.showSuccessAndSettingUp"); +} + +void SyncSetupHandler2::ShowConfigure(const DictionaryValue& args) { + StringValue page("configure"); + web_ui_->CallJavascriptFunction( + "SyncSetupOverlay.showSyncSetupPage", page, args); +} + +void SyncSetupHandler2::ShowPassphraseEntry(const DictionaryValue& args) { + StringValue page("passphrase"); + web_ui_->CallJavascriptFunction( + "SyncSetupOverlay.showSyncSetupPage", page, args); +} + +void SyncSetupHandler2::ShowSettingUp() { + StringValue page("settingUp"); + web_ui_->CallJavascriptFunction( + "SyncSetupOverlay.showSyncSetupPage", page); +} + +void SyncSetupHandler2::ShowSetupDone(const string16& user) { + StringValue page("done"); + web_ui_->CallJavascriptFunction( + "SyncSetupOverlay.showSyncSetupPage", page); + + // Suppress the sync promo once the user signs into sync. This way the user + // doesn't see the sync promo even if they sign out of sync later on. + SyncPromoUI::SetUserSkippedSyncPromo(Profile::FromWebUI(web_ui_)); + + Profile* profile = Profile::FromWebUI(web_ui_); + ProfileSyncService* service = profile->GetProfileSyncService(); + if (!service->HasSyncSetupCompleted()) { + FilePath profile_file_path = profile->GetPath(); + ProfileMetrics::LogProfileSyncSignIn(profile_file_path); + } +} + +void SyncSetupHandler2::SetFlow(SyncSetupFlow* flow) { + flow_ = flow; +} + +void SyncSetupHandler2::Focus() { + static_cast<RenderViewHostDelegate*>(web_ui_->tab_contents())->Activate(); +} + +void SyncSetupHandler2::OnDidClosePage(const ListValue* args) { + CloseSyncSetup(); +} + +void SyncSetupHandler2::HandleSubmitAuth(const ListValue* args) { + std::string json; + if (!args->GetString(0, &json)) { + NOTREACHED() << "Could not read JSON argument"; + return; + } + + if (json.empty()) + return; + + std::string username, password, captcha, access_code; + if (!GetAuthData(json, &username, &password, &captcha, &access_code)) { + // The page sent us something that we didn't understand. + // This probably indicates a programming error. + NOTREACHED(); + return; + } + + string16 error_message; + if (!IsLoginAuthDataValid(username, &error_message)) { + ShowLoginErrorMessage(error_message); + return; + } + + if (flow_) + flow_->OnUserSubmittedAuth(username, password, captcha, access_code); +} + +void SyncSetupHandler2::HandleConfigure(const ListValue* args) { + std::string json; + if (!args->GetString(0, &json)) { + NOTREACHED() << "Could not read JSON argument"; + return; + } + if (json.empty()) { + NOTREACHED(); + return; + } + + SyncConfiguration configuration; + if (!GetConfiguration(json, &configuration)) { + // The page sent us something that we didn't understand. + // This probably indicates a programming error. + NOTREACHED(); + return; + } + + // We do not do UMA logging during unit tests. + if (web_ui_) { + Profile* profile = Profile::FromWebUI(web_ui_); + if (HasConfigurationChanged(configuration, profile)) { + UMA_HISTOGRAM_BOOLEAN("Sync.SyncEverything", + configuration.sync_everything); + if (!configuration.sync_everything) { + // Only log the data types that are explicitly listed on the sync + // preferences page. + const syncable::ModelTypeSet types = configuration.data_types; + if (types.Has(syncable::BOOKMARKS)) + UMA_HISTOGRAM_ENUMERATION( + "Sync.CustomSync", BOOKMARKS, SELECTABLE_DATATYPE_COUNT + 1); + if (types.Has(syncable::PREFERENCES)) + UMA_HISTOGRAM_ENUMERATION( + "Sync.CustomSync", PREFERENCES, SELECTABLE_DATATYPE_COUNT + 1); + if (types.Has(syncable::PASSWORDS)) + UMA_HISTOGRAM_ENUMERATION( + "Sync.CustomSync", PASSWORDS, SELECTABLE_DATATYPE_COUNT + 1); + if (types.Has(syncable::AUTOFILL)) + UMA_HISTOGRAM_ENUMERATION( + "Sync.CustomSync", AUTOFILL, SELECTABLE_DATATYPE_COUNT + 1); + if (types.Has(syncable::THEMES)) + UMA_HISTOGRAM_ENUMERATION( + "Sync.CustomSync", THEMES, SELECTABLE_DATATYPE_COUNT + 1); + if (types.Has(syncable::TYPED_URLS)) + UMA_HISTOGRAM_ENUMERATION( + "Sync.CustomSync", TYPED_URLS, SELECTABLE_DATATYPE_COUNT + 1); + if (types.Has(syncable::EXTENSIONS)) + UMA_HISTOGRAM_ENUMERATION( + "Sync.CustomSync", EXTENSIONS, SELECTABLE_DATATYPE_COUNT + 1); + if (types.Has(syncable::SESSIONS)) + UMA_HISTOGRAM_ENUMERATION( + "Sync.CustomSync", SESSIONS, SELECTABLE_DATATYPE_COUNT + 1); + if (types.Has(syncable::APPS)) + UMA_HISTOGRAM_ENUMERATION( + "Sync.CustomSync", APPS, SELECTABLE_DATATYPE_COUNT + 1); + COMPILE_ASSERT(17 == syncable::MODEL_TYPE_COUNT, + UpdateCustomConfigHistogram); + COMPILE_ASSERT(9 == SELECTABLE_DATATYPE_COUNT, + UpdateCustomConfigHistogram); + } + UMA_HISTOGRAM_BOOLEAN("Sync.EncryptAllData", configuration.encrypt_all); + UMA_HISTOGRAM_BOOLEAN("Sync.CustomPassphrase", + configuration.set_gaia_passphrase || + configuration.set_secondary_passphrase); + } + } + + DCHECK(flow_); + flow_->OnUserConfigured(configuration); + + ProfileMetrics::LogProfileSyncInfo(ProfileMetrics::SYNC_CUSTOMIZE); + if (configuration.encrypt_all) { + ProfileMetrics::LogProfileSyncInfo(ProfileMetrics::SYNC_ENCRYPT); + } + if (configuration.set_secondary_passphrase) { + ProfileMetrics::LogProfileSyncInfo(ProfileMetrics::SYNC_PASSPHRASE); + } + if (!configuration.sync_everything) { + ProfileMetrics::LogProfileSyncInfo(ProfileMetrics::SYNC_CHOOSE); + } +} + +void SyncSetupHandler2::HandlePassphraseEntry(const ListValue* args) { + std::string json; + if (!args->GetString(0, &json)) { + NOTREACHED() << "Could not read JSON argument"; + return; + } + + if (json.empty()) + return; + + std::string passphrase; + if (!GetPassphrase(json, &passphrase)) { + // Couldn't understand what the page sent. Indicates a programming error. + NOTREACHED(); + return; + } + + DCHECK(flow_); + flow_->OnPassphraseEntry(passphrase); +} + +void SyncSetupHandler2::HandlePassphraseCancel(const ListValue* args) { + DCHECK(flow_); + flow_->OnPassphraseCancel(); +} + +void SyncSetupHandler2::HandleAttachHandler(const ListValue* args) { + OpenSyncSetup(); +} + +void SyncSetupHandler2::HandleShowErrorUI(const ListValue* args) { + DCHECK(!flow_); + + Profile* profile = Profile::FromWebUI(web_ui_); + ProfileSyncService* service = profile->GetProfileSyncService(); + DCHECK(service); + + service->ShowErrorUI(); +} + +void SyncSetupHandler2::HandleShowSetupUI(const ListValue* args) { + DCHECK(!flow_); + if (FocusExistingWizard()) { + CloseOverlay(); + return; + } + ShowSetupUI(); +} + +void SyncSetupHandler2::CloseSyncSetup() { + if (flow_) { + flow_->OnDialogClosed(std::string()); + flow_ = NULL; + } +} + +void SyncSetupHandler2::OpenSyncSetup() { + DCHECK(web_ui_); + DCHECK(!flow_); + + Profile* profile = Profile::FromWebUI(web_ui_); + ProfileSyncService* service = profile->GetProfileSyncService(); + if (!service) { + // If there's no sync service, the user tried to manually invoke a syncSetup + // URL, but sync features are disabled. We need to close the overlay for + // this (rare) case. + CloseOverlay(); + return; + } + + // If the wizard is already visible, it must be attached to another flow + // handler. + if (FocusExistingWizard()) { + CloseOverlay(); + return; + } + + // Attach this as the sync setup handler, before calling ShowSetupUI(). + if (!service->get_wizard().AttachSyncSetupHandler(this)) { + LOG(ERROR) << "SyncSetupHandler attach failed!"; + CloseOverlay(); + return; + } + + ShowSetupUI(); +} + +// Private member functions. + +bool SyncSetupHandler2::FocusExistingWizard() { + Profile* profile = Profile::FromWebUI(web_ui_); + ProfileSyncService* service = profile->GetProfileSyncService(); + if (!service) + return false; + + // If the wizard is already visible, focus it. + if (service->get_wizard().IsVisible()) { + service->get_wizard().Focus(); + return true; + } + return false; +} + +void SyncSetupHandler2::CloseOverlay() { + web_ui_->CallJavascriptFunction("OptionsPage.closeOverlay"); +} + +bool SyncSetupHandler2::IsLoginAuthDataValid(const std::string& username, + string16* error_message) { + // Happens during unit tests. + if (!web_ui_ || !profile_manager_) + return true; + + if (username.empty()) + return true; + + // Check if the username is already in use by another profile. + const ProfileInfoCache& cache = profile_manager_->GetProfileInfoCache(); + size_t current_profile_index = cache.GetIndexOfProfileWithPath( + Profile::FromWebUI(web_ui_)->GetPath()); + string16 username_utf16 = UTF8ToUTF16(username); + + for (size_t i = 0; i < cache.GetNumberOfProfiles(); ++i) { + if (i != current_profile_index && AreUserNamesEqual( + cache.GetUserNameOfProfileAtIndex(i), username_utf16)) { + *error_message = l10n_util::GetStringUTF16( + IDS_SYNC_USER_NAME_IN_USE_ERROR); + return false; + } + } + + return true; +} + +void SyncSetupHandler2::ShowLoginErrorMessage(const string16& error_message) { + DictionaryValue args; + Profile* profile = Profile::FromWebUI(web_ui_); + ProfileSyncService* service = profile->GetProfileSyncService(); + SyncSetupFlow::GetArgsForGaiaLogin(service, &args); + args.SetString("error_message", error_message); + ShowGaiaLogin(args); +} diff --git a/chrome/browser/ui/webui/sync_setup_handler2.h b/chrome/browser/ui/webui/sync_setup_handler2.h new file mode 100644 index 0000000..a5bc2e5 --- /dev/null +++ b/chrome/browser/ui/webui/sync_setup_handler2.h @@ -0,0 +1,117 @@ +// Copyright (c) 2011 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. + +#ifndef CHROME_BROWSER_UI_WEBUI_SYNC_SETUP_HANDLER2_H_ +#define CHROME_BROWSER_UI_WEBUI_SYNC_SETUP_HANDLER2_H_ + +#include "base/memory/scoped_ptr.h" +#include "chrome/browser/net/gaia/gaia_oauth_consumer.h" +#include "chrome/browser/net/gaia/gaia_oauth_fetcher.h" +#include "chrome/browser/sync/sync_setup_flow_handler.h" +#include "chrome/browser/ui/webui/options2/options_ui2.h" + +class SyncSetupFlow; +class ProfileManager; + +class SyncSetupHandler2 : public GaiaOAuthConsumer, + public OptionsPage2UIHandler, + public SyncSetupFlowHandler { + public: + // Constructs a new SyncSetupHandler. |profile_manager| may be NULL. + explicit SyncSetupHandler2(ProfileManager* profile_manager); + virtual ~SyncSetupHandler2(); + + // OptionsPageUIHandler implementation. + virtual void GetLocalizedValues(base::DictionaryValue* localized_strings) + OVERRIDE; + virtual void Initialize() OVERRIDE; + virtual void RegisterMessages() OVERRIDE; + + // SyncSetupFlowHandler implementation. + virtual void ShowOAuthLogin() OVERRIDE; + virtual void ShowGaiaLogin(const base::DictionaryValue& args) OVERRIDE; + virtual void ShowGaiaSuccessAndClose() OVERRIDE; + virtual void ShowGaiaSuccessAndSettingUp() OVERRIDE; + virtual void ShowConfigure(const base::DictionaryValue& args) OVERRIDE; + virtual void ShowPassphraseEntry(const base::DictionaryValue& args) OVERRIDE; + virtual void ShowSettingUp() OVERRIDE; + virtual void ShowSetupDone(const string16& user) OVERRIDE; + virtual void SetFlow(SyncSetupFlow* flow) OVERRIDE; + virtual void Focus() OVERRIDE; + + // GaiaOAuthConsumer implementation. + virtual void OnGetOAuthTokenSuccess(const std::string& oauth_token) OVERRIDE; + virtual void OnGetOAuthTokenFailure( + const GoogleServiceAuthError& error) OVERRIDE; + + static void GetStaticLocalizedValues( + base::DictionaryValue* localized_strings, + WebUI* web_ui); + + // Initializes the sync setup flow and shows the setup UI. + void OpenSyncSetup(); + + // Terminates the sync setup flow. + void CloseSyncSetup(); + + protected: + FRIEND_TEST_ALL_PREFIXES(SyncSetupWizardTest, InitialStepLogin); + FRIEND_TEST_ALL_PREFIXES(SyncSetupWizardTest, ChooseDataTypesSetsPrefs); + FRIEND_TEST_ALL_PREFIXES(SyncSetupWizardTest, DialogCancelled); + FRIEND_TEST_ALL_PREFIXES(SyncSetupWizardTest, InvalidTransitions); + FRIEND_TEST_ALL_PREFIXES(SyncSetupWizardTest, FullSuccessfulRunSetsPref); + FRIEND_TEST_ALL_PREFIXES(SyncSetupWizardTest, AbortedByPendingClear); + FRIEND_TEST_ALL_PREFIXES(SyncSetupWizardTest, DiscreteRunGaiaLogin); + FRIEND_TEST_ALL_PREFIXES(SyncSetupWizardTest, DiscreteRunChooseDataTypes); + FRIEND_TEST_ALL_PREFIXES(SyncSetupWizardTest, + DiscreteRunChooseDataTypesAbortedByPendingClear); + FRIEND_TEST_ALL_PREFIXES(SyncSetupWizardTest, EnterPassphraseRequired); + + // Callbacks from the page. Protected in order to be called by the + // SyncSetupWizardTest. + void OnDidClosePage(const base::ListValue* args); + void HandleSubmitAuth(const base::ListValue* args); + void HandleConfigure(const base::ListValue* args); + void HandlePassphraseEntry(const base::ListValue* args); + void HandlePassphraseCancel(const base::ListValue* args); + void HandleAttachHandler(const base::ListValue* args); + void HandleShowErrorUI(const base::ListValue* args); + void HandleShowSetupUI(const base::ListValue* args); + + SyncSetupFlow* flow() { return flow_; } + + // Subclasses must implement to step the SyncSetupWizard to the correct state + // before showing the Setup UI. + virtual void StepWizardForShowSetupUI() = 0; + + // Subclasses must implement this to show the setup UI that's appropriate + // for the page this is contained in. + virtual void ShowSetupUI() = 0; + + private: + // If a wizard already exists, focus it and return true. + bool FocusExistingWizard(); + + // Invokes the javascript call to close the setup overlay. + void CloseOverlay(); + + // Returns true if the given login data is valid, false otherwise. If the + // login data is not valid then on return |error_message| will be set to a + // localized error message. Note, |error_message| must not be NULL. + bool IsLoginAuthDataValid(const std::string& username, + string16* error_message); + + // Displays the given error message in the login UI. + void ShowLoginErrorMessage(const string16& error_message); + + // Weak reference. + SyncSetupFlow* flow_; + scoped_ptr<GaiaOAuthFetcher> oauth_login_; + // Weak reference to the profile manager. + ProfileManager* const profile_manager_; + + DISALLOW_COPY_AND_ASSIGN(SyncSetupHandler2); +}; + +#endif // CHROME_BROWSER_UI_WEBUI_SYNC_SETUP_HANDLER2_H_ diff --git a/chrome/browser/ui/webui/uber/uber_ui.cc b/chrome/browser/ui/webui/uber/uber_ui.cc index 47a6ea2..ded7554 100644 --- a/chrome/browser/ui/webui/uber/uber_ui.cc +++ b/chrome/browser/ui/webui/uber/uber_ui.cc @@ -10,7 +10,7 @@ #include "chrome/browser/ui/webui/chrome_web_ui_data_source.h" #include "chrome/browser/ui/webui/chrome_web_ui_factory.h" #include "chrome/browser/ui/webui/extensions/extensions_ui.h" -#include "chrome/browser/ui/webui/options/options_ui.h" +#include "chrome/browser/ui/webui/options2/options_ui2.h" #include "chrome/common/url_constants.h" #include "content/browser/tab_contents/tab_contents.h" #include "grit/browser_resources.h" @@ -33,7 +33,7 @@ UberUI::UberUI(TabContents* contents) : ChromeWebUI(contents) { Profile* profile = Profile::FromBrowserContext(contents->browser_context()); profile->GetChromeURLDataManager()->AddDataSource(CreateUberHTMLSource()); - RegisterSubpage(chrome::kChromeUISettingsURL); + RegisterSubpage(chrome::kChromeUISettingsFrameURL); RegisterSubpage(chrome::kChromeUIExtensionsFrameURL); } diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi index c9c54a6..2ab9bd9 100644 --- a/chrome/chrome_browser.gypi +++ b/chrome/chrome_browser.gypi @@ -3920,6 +3920,98 @@ 'browser/ui/webui/options/stop_syncing_handler.h', 'browser/ui/webui/options/web_intents_settings_handler.cc', 'browser/ui/webui/options/web_intents_settings_handler.h', + 'browser/ui/webui/options2/advanced_options_handler.cc', + 'browser/ui/webui/options2/advanced_options_handler.h', + 'browser/ui/webui/options2/advanced_options_utils.h', + 'browser/ui/webui/options2/advanced_options_utils_mac.mm', + 'browser/ui/webui/options2/advanced_options_utils_win.cc', + 'browser/ui/webui/options2/advanced_options_utils_x11.cc', + 'browser/ui/webui/options2/autofill_options_handler.cc', + 'browser/ui/webui/options2/autofill_options_handler.h', + 'browser/ui/webui/options2/browser_options_handler.cc', + 'browser/ui/webui/options2/browser_options_handler.h', + 'browser/ui/webui/options2/certificate_manager_handler.cc', + 'browser/ui/webui/options2/certificate_manager_handler.h', + 'browser/ui/webui/options2/chromeos/about_page_handler.cc', + 'browser/ui/webui/options2/chromeos/about_page_handler.h', + 'browser/ui/webui/options2/chromeos/accounts_options_handler.cc', + 'browser/ui/webui/options2/chromeos/accounts_options_handler.h', + 'browser/ui/webui/options2/chromeos/bluetooth_options_handler.cc', + 'browser/ui/webui/options2/chromeos/bluetooth_options_handler.h', + 'browser/ui/webui/options2/chromeos/change_picture_options_handler.cc', + 'browser/ui/webui/options2/chromeos/change_picture_options_handler.h', + 'browser/ui/webui/options2/chromeos/core_chromeos_options_handler.cc', + 'browser/ui/webui/options2/chromeos/core_chromeos_options_handler.h', + 'browser/ui/webui/options2/chromeos/cros_language_options_handler.cc', + 'browser/ui/webui/options2/chromeos/cros_language_options_handler.h', + 'browser/ui/webui/options2/chromeos/internet_options_handler.cc', + 'browser/ui/webui/options2/chromeos/internet_options_handler.h', + 'browser/ui/webui/options2/chromeos/language_chewing_handler.cc', + 'browser/ui/webui/options2/chromeos/language_chewing_handler.h', + 'browser/ui/webui/options2/chromeos/language_customize_modifier_keys_handler.cc', + 'browser/ui/webui/options2/chromeos/language_customize_modifier_keys_handler.h', + 'browser/ui/webui/options2/chromeos/language_hangul_handler.cc', + 'browser/ui/webui/options2/chromeos/language_hangul_handler.h', + 'browser/ui/webui/options2/chromeos/language_mozc_handler.cc', + 'browser/ui/webui/options2/chromeos/language_mozc_handler.h', + 'browser/ui/webui/options2/chromeos/language_options_util.cc', + 'browser/ui/webui/options2/chromeos/language_options_util.h', + 'browser/ui/webui/options2/chromeos/language_pinyin_handler.cc', + 'browser/ui/webui/options2/chromeos/language_pinyin_handler.h', + 'browser/ui/webui/options2/chromeos/proxy_handler.cc', + 'browser/ui/webui/options2/chromeos/proxy_handler.h', + 'browser/ui/webui/options2/chromeos/stats_options_handler.cc', + 'browser/ui/webui/options2/chromeos/stats_options_handler.h', + 'browser/ui/webui/options2/chromeos/system_options_handler.cc', + 'browser/ui/webui/options2/chromeos/system_options_handler.h', + 'browser/ui/webui/options2/chromeos/system_settings_provider.cc', + 'browser/ui/webui/options2/chromeos/system_settings_provider.h', + 'browser/ui/webui/options2/chromeos/user_image_source.cc', + 'browser/ui/webui/options2/chromeos/user_image_source.h', + 'browser/ui/webui/options2/chromeos/virtual_keyboard_manager_handler.cc', + 'browser/ui/webui/options2/chromeos/virtual_keyboard_manager_handler.h', + 'browser/ui/webui/options2/clear_browser_data_handler.cc', + 'browser/ui/webui/options2/clear_browser_data_handler.h', + 'browser/ui/webui/options2/content_settings_handler.cc', + 'browser/ui/webui/options2/content_settings_handler.h', + 'browser/ui/webui/options2/cookies_view_handler.cc', + 'browser/ui/webui/options2/cookies_view_handler.h', + 'browser/ui/webui/options2/core_options_handler.cc', + 'browser/ui/webui/options2/core_options_handler.h', + 'browser/ui/webui/options2/extension_settings_handler.h', + 'browser/ui/webui/options2/extension_settings_handler.cc', + 'browser/ui/webui/options2/font_settings_handler.cc', + 'browser/ui/webui/options2/font_settings_handler.h', + 'browser/ui/webui/options2/font_settings_utils.h', + 'browser/ui/webui/options2/font_settings_utils_mac.mm', + 'browser/ui/webui/options2/font_settings_utils_win.cc', + 'browser/ui/webui/options2/font_settings_utils_x11.cc', + 'browser/ui/webui/options2/handler_options_handler.cc', + 'browser/ui/webui/options2/handler_options_handler.h', + 'browser/ui/webui/options2/import_data_handler.cc', + 'browser/ui/webui/options2/import_data_handler.h', + 'browser/ui/webui/options2/language_options_handler.cc', + 'browser/ui/webui/options2/language_options_handler.h', + 'browser/ui/webui/options2/language_options_handler_common.cc', + 'browser/ui/webui/options2/language_options_handler_common.h', + 'browser/ui/webui/options2/manage_profile_handler.cc', + 'browser/ui/webui/options2/manage_profile_handler.h', + 'browser/ui/webui/options2/options_sync_setup_handler.cc', + 'browser/ui/webui/options2/options_sync_setup_handler.h', + 'browser/ui/webui/options2/options_ui2.cc', + 'browser/ui/webui/options2/options_ui2.h', + 'browser/ui/webui/options2/password_manager_handler.cc', + 'browser/ui/webui/options2/password_manager_handler.h', + 'browser/ui/webui/options2/pack_extension_handler.h', + 'browser/ui/webui/options2/pack_extension_handler.cc', + 'browser/ui/webui/options2/personal_options_handler.cc', + 'browser/ui/webui/options2/personal_options_handler.h', + 'browser/ui/webui/options2/search_engine_manager_handler.cc', + 'browser/ui/webui/options2/search_engine_manager_handler.h', + 'browser/ui/webui/options2/stop_syncing_handler.cc', + 'browser/ui/webui/options2/stop_syncing_handler.h', + 'browser/ui/webui/options2/web_intents_settings_handler.cc', + 'browser/ui/webui/options2/web_intents_settings_handler.h', 'browser/ui/webui/plugins_ui.cc', 'browser/ui/webui/plugins_ui.h', 'browser/ui/webui/policy_ui.cc', @@ -3954,12 +4046,16 @@ 'browser/ui/webui/sync_internals_ui.h', 'browser/ui/webui/sync_promo_handler.cc', 'browser/ui/webui/sync_promo_handler.h', + 'browser/ui/webui/sync_promo_handler2.cc', + 'browser/ui/webui/sync_promo_handler2.h', 'browser/ui/webui/sync_promo_trial.cc', 'browser/ui/webui/sync_promo_trial.h', 'browser/ui/webui/sync_promo_ui.cc', 'browser/ui/webui/sync_promo_ui.h', 'browser/ui/webui/sync_setup_handler.cc', 'browser/ui/webui/sync_setup_handler.h', + 'browser/ui/webui/sync_setup_handler2.cc', + 'browser/ui/webui/sync_setup_handler2.h', 'browser/ui/webui/task_manager_dialog.cc', 'browser/ui/webui/task_manager_dialog.h', 'browser/ui/webui/task_manager_handler.cc', @@ -4101,6 +4197,7 @@ ['exclude', '^browser/chromeos'], ['exclude', '^browser/ui/webui/chromeos'], ['exclude', '^browser/ui/webui/options/chromeos'], + ['exclude', '^browser/ui/webui/options2/chromeos'], ['exclude', 'browser/extensions/api/terminal/terminal_private_api.cc'], ['exclude', 'browser/extensions/api/terminal/terminal_private_api.h'], ['exclude', 'browser/extensions/extension_input_ime_api.cc'], @@ -4367,6 +4464,8 @@ 'browser/certificate_manager_model.h', 'browser/ui/webui/options/certificate_manager_handler.cc', 'browser/ui/webui/options/certificate_manager_handler.h', + 'browser/ui/webui/options2/certificate_manager_handler.cc', + 'browser/ui/webui/options2/certificate_manager_handler.h', 'browser/ui/webui/ssl_client_certificate_selector_webui.cc', 'browser/ui/webui/ssl_client_certificate_selector_webui.h', ], diff --git a/chrome/chrome_repack_resources.gypi b/chrome/chrome_repack_resources.gypi index d8d2a03..0a0c1b2 100644 --- a/chrome/chrome_repack_resources.gypi +++ b/chrome/chrome_repack_resources.gypi @@ -10,6 +10,7 @@ '<(grit_out_dir)/devtools_resources.pak', '<(grit_out_dir)/net_internals_resources.pak', '<(grit_out_dir)/options_resources.pak', + '<(grit_out_dir)/options2_resources.pak', '<(grit_out_dir)/quota_internals_resources.pak', '<(grit_out_dir)/shared_resources.pak', '<(grit_out_dir)/sync_internals_resources.pak', diff --git a/chrome/chrome_resources.gyp b/chrome/chrome_resources.gyp index 3eccfa0..59b6143 100644 --- a/chrome/chrome_resources.gyp +++ b/chrome/chrome_resources.gyp @@ -39,6 +39,13 @@ 'includes': [ '../build/grit_action.gypi' ], }, { + 'action_name': 'options2_resources', + 'variables': { + 'grit_grd_file': 'browser/resources/options2_resources.grd', + }, + 'includes': [ '../build/grit_action.gypi' ], + }, + { 'action_name': 'quota_internals_resources', 'variables': { 'grit_grd_file': 'browser/resources/quota_internals_resources.grd', diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi index 5ece9ee..c393186 100644 --- a/chrome/chrome_tests.gypi +++ b/chrome/chrome_tests.gypi @@ -917,6 +917,7 @@ ['chromeos==0', { 'sources/': [ ['exclude', '^browser/ui/webui/options/chromeos/'], + ['exclude', '^browser/ui/webui/options2/chromeos/'], ], }], ], @@ -2132,6 +2133,7 @@ ['exclude', '^browser/ui/webui/chromeos/imageburner/'], ['exclude', '^browser/ui/webui/chromeos/login'], ['exclude', '^browser/ui/webui/options/chromeos/'], + ['exclude', '^browser/ui/webui/options2/chromeos/'], ], }], ['toolkit_uses_gtk == 1', { diff --git a/chrome/common/url_constants.cc b/chrome/common/url_constants.cc index 5d313e95c..8ef0d66 100644 --- a/chrome/common/url_constants.cc +++ b/chrome/common/url_constants.cc @@ -65,6 +65,9 @@ const char kChromeUIPolicyURL[] = "chrome://policy/"; const char kChromeUIPrintURL[] = "chrome://print/"; const char kChromeUISessionsURL[] = "chrome://sessions/"; const char kChromeUISettingsURL[] = "chrome://settings/"; +// settings-frame is the URL used to directly access the new settings page in +// the UberPage, AKA options2. +const char kChromeUISettingsFrameURL[] = "chrome://settings-frame/"; const char kChromeUIShorthangURL[] = "chrome://shorthang/"; const char kChromeUISSLClientCertificateSelectorURL[] = "chrome://select-cert/"; const char kChromeUISyncPromoURL[] = "chrome://syncpromo/"; @@ -169,6 +172,7 @@ const char kChromeUIQuotaInternalsHost[] = "quota-internals"; const char kChromeUIResourcesHost[] = "resources"; const char kChromeUISessionsHost[] = "sessions"; const char kChromeUISettingsHost[] = "settings"; +const char kChromeUISettingsFrameHost[] = "settings-frame"; const char kChromeUIShorthangHost[] = "shorthang"; const char kChromeUISSLClientCertificateSelectorHost[] = "select-cert"; const char kChromeUIStatsHost[] = "stats"; diff --git a/chrome/common/url_constants.h b/chrome/common/url_constants.h index 15f5051..9b710c2 100644 --- a/chrome/common/url_constants.h +++ b/chrome/common/url_constants.h @@ -60,6 +60,7 @@ extern const char kChromeUIPolicyURL[]; extern const char kChromeUIPrintURL[]; extern const char kChromeUISessionsURL[]; extern const char kChromeUISettingsURL[]; +extern const char kChromeUISettingsFrameURL[]; extern const char kChromeUIShorthangURL[]; extern const char kChromeUISSLClientCertificateSelectorURL[]; extern const char kChromeUISyncPromoURL[]; @@ -161,6 +162,7 @@ extern const char kChromeUIQuotaInternalsHost[]; extern const char kChromeUIResourcesHost[]; extern const char kChromeUISessionsHost[]; extern const char kChromeUISettingsHost[]; +extern const char kChromeUISettingsFrameHost[]; extern const char kChromeUIShorthangHost[]; extern const char kChromeUISSLClientCertificateSelectorHost[]; extern const char kChromeUIStatsHost[]; diff --git a/tools/gritsettings/resource_ids b/tools/gritsettings/resource_ids index c7bdb8f..fe4e3e5 100644 --- a/tools/gritsettings/resource_ids +++ b/tools/gritsettings/resource_ids @@ -156,6 +156,9 @@ "chrome/browser/resources/options_resources.grd": { "includes": [22000], }, + "chrome/browser/resources/options2_resources.grd": { + "includes": [22200], + }, "cloud_print/virtual_driver/win/install/virtual_driver_setup_resources.grd": { "messages": [22500], }, |