summaryrefslogtreecommitdiff
path: root/chromium/chrome/browser/resources/options
diff options
context:
space:
mode:
authorAndras Becsi <andras.becsi@digia.com>2014-03-18 13:16:26 +0100
committerFrederik Gladhorn <frederik.gladhorn@digia.com>2014-03-20 15:55:39 +0100
commit3f0f86b0caed75241fa71c95a5d73bc0164348c5 (patch)
tree92b9fb00f2e9e90b0be2262093876d4f43b6cd13 /chromium/chrome/browser/resources/options
parente90d7c4b152c56919d963987e2503f9909a666d2 (diff)
downloadqtwebengine-chromium-3f0f86b0caed75241fa71c95a5d73bc0164348c5.tar.gz
Update to new stable branch 1750
This also includes an updated ninja and chromium dependencies needed on Windows. Change-Id: Icd597d80ed3fa4425933c9f1334c3c2e31291c42 Reviewed-by: Zoltan Arvai <zarvai@inf.u-szeged.hu> Reviewed-by: Zeno Albisser <zeno.albisser@digia.com>
Diffstat (limited to 'chromium/chrome/browser/resources/options')
-rw-r--r--chromium/chrome/browser/resources/options/2x/yellow_gear.pngbin0 -> 2745 bytes
-rw-r--r--chromium/chrome/browser/resources/options/OWNERS3
-rw-r--r--chromium/chrome/browser/resources/options/alert_overlay.css7
-rw-r--r--chromium/chrome/browser/resources/options/alert_overlay.html13
-rw-r--r--chromium/chrome/browser/resources/options/alert_overlay.js152
-rw-r--r--chromium/chrome/browser/resources/options/arrow_next.pngbin0 -> 200 bytes
-rw-r--r--chromium/chrome/browser/resources/options/autofill_edit_address_overlay.html75
-rw-r--r--chromium/chrome/browser/resources/options/autofill_edit_address_overlay.js301
-rw-r--r--chromium/chrome/browser/resources/options/autofill_edit_creditcard_overlay.html31
-rw-r--r--chromium/chrome/browser/resources/options/autofill_edit_creditcard_overlay.js206
-rw-r--r--chromium/chrome/browser/resources/options/autofill_edit_overlay.css84
-rw-r--r--chromium/chrome/browser/resources/options/autofill_options.css52
-rw-r--r--chromium/chrome/browser/resources/options/autofill_options.html40
-rw-r--r--chromium/chrome/browser/resources/options/autofill_options.js216
-rw-r--r--chromium/chrome/browser/resources/options/autofill_options_list.js508
-rw-r--r--chromium/chrome/browser/resources/options/browser_options.css427
-rw-r--r--chromium/chrome/browser/resources/options/browser_options.html863
-rw-r--r--chromium/chrome/browser/resources/options/browser_options.js1621
-rw-r--r--chromium/chrome/browser/resources/options/browser_options_profile_list.js129
-rw-r--r--chromium/chrome/browser/resources/options/browser_options_startup_page_list.js321
-rw-r--r--chromium/chrome/browser/resources/options/certificate_backup_overlay.html40
-rw-r--r--chromium/chrome/browser/resources/options/certificate_backup_overlay.js117
-rw-r--r--chromium/chrome/browser/resources/options/certificate_edit_ca_trust_overlay.html39
-rw-r--r--chromium/chrome/browser/resources/options/certificate_edit_ca_trust_overlay.js164
-rw-r--r--chromium/chrome/browser/resources/options/certificate_import_error_overlay.html15
-rw-r--r--chromium/chrome/browser/resources/options/certificate_import_error_overlay.js68
-rw-r--r--chromium/chrome/browser/resources/options/certificate_manager.css32
-rw-r--r--chromium/chrome/browser/resources/options/certificate_manager.html144
-rw-r--r--chromium/chrome/browser/resources/options/certificate_manager.js256
-rw-r--r--chromium/chrome/browser/resources/options/certificate_restore_overlay.html19
-rw-r--r--chromium/chrome/browser/resources/options/certificate_restore_overlay.js101
-rw-r--r--chromium/chrome/browser/resources/options/certificate_tree.css17
-rw-r--r--chromium/chrome/browser/resources/options/certificate_tree.js163
-rw-r--r--chromium/chrome/browser/resources/options/chromeos/2x/warning.pngbin0 -> 1037 bytes
-rw-r--r--chromium/chrome/browser/resources/options/chromeos/OWNERS15
-rw-r--r--chromium/chrome/browser/resources/options/chromeos/accounts_options.html69
-rw-r--r--chromium/chrome/browser/resources/options/chromeos/accounts_options.js154
-rw-r--r--chromium/chrome/browser/resources/options/chromeos/accounts_options_page.css98
-rw-r--r--chromium/chrome/browser/resources/options/chromeos/accounts_user_list.js194
-rw-r--r--chromium/chrome/browser/resources/options/chromeos/accounts_user_name_edit.js128
-rw-r--r--chromium/chrome/browser/resources/options/chromeos/bluetooth.css162
-rw-r--r--chromium/chrome/browser/resources/options/chromeos/bluetooth_add_device_overlay.html23
-rw-r--r--chromium/chrome/browser/resources/options/chromeos/bluetooth_add_device_overlay.js95
-rw-r--r--chromium/chrome/browser/resources/options/chromeos/bluetooth_device_list.js347
-rw-r--r--chromium/chrome/browser/resources/options/chromeos/bluetooth_pair_device_overlay.html28
-rw-r--r--chromium/chrome/browser/resources/options/chromeos/bluetooth_pair_device_overlay.js398
-rw-r--r--chromium/chrome/browser/resources/options/chromeos/browser_options.css7
-rw-r--r--chromium/chrome/browser/resources/options/chromeos/change_picture_options.css195
-rw-r--r--chromium/chrome/browser/resources/options/chromeos/change_picture_options.html34
-rw-r--r--chromium/chrome/browser/resources/options/chromeos/change_picture_options.js320
-rw-r--r--chromium/chrome/browser/resources/options/chromeos/display_options.css96
-rw-r--r--chromium/chrome/browser/resources/options/chromeos/display_options.html56
-rw-r--r--chromium/chrome/browser/resources/options/chromeos/display_options.js863
-rw-r--r--chromium/chrome/browser/resources/options/chromeos/display_overscan.css63
-rw-r--r--chromium/chrome/browser/resources/options/chromeos/display_overscan.html31
-rw-r--r--chromium/chrome/browser/resources/options/chromeos/display_overscan.js160
-rw-r--r--chromium/chrome/browser/resources/options/chromeos/internet_detail.css109
-rw-r--r--chromium/chrome/browser/resources/options/chromeos/internet_detail.html708
-rw-r--r--chromium/chrome/browser/resources/options/chromeos/internet_detail.js1265
-rw-r--r--chromium/chrome/browser/resources/options/chromeos/internet_detail_ip_address_field.js111
-rw-r--r--chromium/chrome/browser/resources/options/chromeos/keyboard_overlay.css7
-rw-r--r--chromium/chrome/browser/resources/options/chromeos/keyboard_overlay.html100
-rw-r--r--chromium/chrome/browser/resources/options/chromeos/keyboard_overlay.js66
-rw-r--r--chromium/chrome/browser/resources/options/chromeos/network_list.js1174
-rw-r--r--chromium/chrome/browser/resources/options/chromeos/overscan_arrows.pngbin0 -> 557 bytes
-rw-r--r--chromium/chrome/browser/resources/options/chromeos/overscan_arrows_2x.pngbin0 -> 946 bytes
-rw-r--r--chromium/chrome/browser/resources/options/chromeos/overscan_shift.pngbin0 -> 482 bytes
-rw-r--r--chromium/chrome/browser/resources/options/chromeos/overscan_shift_2x.pngbin0 -> 895 bytes
-rw-r--r--chromium/chrome/browser/resources/options/chromeos/overscan_shift_rtl.pngbin0 -> 571 bytes
-rw-r--r--chromium/chrome/browser/resources/options/chromeos/overscan_shift_rtl_2x.pngbin0 -> 938 bytes
-rw-r--r--chromium/chrome/browser/resources/options/chromeos/pointer_overlay.css11
-rw-r--r--chromium/chrome/browser/resources/options/chromeos/pointer_overlay.html50
-rw-r--r--chromium/chrome/browser/resources/options/chromeos/pointer_overlay.js78
-rw-r--r--chromium/chrome/browser/resources/options/chromeos/preferred_networks.html17
-rw-r--r--chromium/chrome/browser/resources/options/chromeos/preferred_networks.js164
-rw-r--r--chromium/chrome/browser/resources/options/chromeos/proxy_rules_list.js140
-rw-r--r--chromium/chrome/browser/resources/options/chromeos/warning.pngbin0 -> 489 bytes
-rw-r--r--chromium/chrome/browser/resources/options/clear_browser_data_overlay.css39
-rw-r--r--chromium/chrome/browser/resources/options/clear_browser_data_overlay.html99
-rw-r--r--chromium/chrome/browser/resources/options/clear_browser_data_overlay.js209
-rw-r--r--chromium/chrome/browser/resources/options/confirm_dialog.js118
-rw-r--r--chromium/chrome/browser/resources/options/content_settings.css124
-rw-r--r--chromium/chrome/browser/resources/options/content_settings.html601
-rw-r--r--chromium/chrome/browser/resources/options/content_settings.js314
-rw-r--r--chromium/chrome/browser/resources/options/content_settings2.html13
-rw-r--r--chromium/chrome/browser/resources/options/content_settings2.js88
-rw-r--r--chromium/chrome/browser/resources/options/content_settings_exceptions_area.html140
-rw-r--r--chromium/chrome/browser/resources/options/content_settings_exceptions_area.js670
-rw-r--r--chromium/chrome/browser/resources/options/content_settings_ui.js67
-rw-r--r--chromium/chrome/browser/resources/options/controlled_setting.css127
-rw-r--r--chromium/chrome/browser/resources/options/controlled_setting.js236
-rw-r--r--chromium/chrome/browser/resources/options/cookies_list.js921
-rw-r--r--chromium/chrome/browser/resources/options/cookies_view.css198
-rw-r--r--chromium/chrome/browser/resources/options/cookies_view.html28
-rw-r--r--chromium/chrome/browser/resources/options/cookies_view.js140
-rw-r--r--chromium/chrome/browser/resources/options/deletable_item_list.js196
-rw-r--r--chromium/chrome/browser/resources/options/do_not_track_confirm_overlay.css7
-rw-r--r--chromium/chrome/browser/resources/options/do_not_track_confirm_overlay.html23
-rw-r--r--chromium/chrome/browser/resources/options/editable_text_field.js375
-rw-r--r--chromium/chrome/browser/resources/options/factory_reset_overlay.css7
-rw-r--r--chromium/chrome/browser/resources/options/factory_reset_overlay.html16
-rw-r--r--chromium/chrome/browser/resources/options/factory_reset_overlay.js49
-rw-r--r--chromium/chrome/browser/resources/options/font_settings.css62
-rw-r--r--chromium/chrome/browser/resources/options/font_settings.html113
-rw-r--r--chromium/chrome/browser/resources/options/font_settings.js255
-rw-r--r--chromium/chrome/browser/resources/options/geolocation_options.js34
-rw-r--r--chromium/chrome/browser/resources/options/handler_options.css55
-rw-r--r--chromium/chrome/browser/resources/options/handler_options.html38
-rw-r--r--chromium/chrome/browser/resources/options/handler_options.js80
-rw-r--r--chromium/chrome/browser/resources/options/handler_options_list.js227
-rw-r--r--chromium/chrome/browser/resources/options/home_page_overlay.css16
-rw-r--r--chromium/chrome/browser/resources/options/home_page_overlay.html53
-rw-r--r--chromium/chrome/browser/resources/options/home_page_overlay.js155
-rw-r--r--chromium/chrome/browser/resources/options/import_data_overlay.css30
-rw-r--r--chromium/chrome/browser/resources/options/import_data_overlay.html122
-rw-r--r--chromium/chrome/browser/resources/options/import_data_overlay.js277
-rw-r--r--chromium/chrome/browser/resources/options/inline_editable_list.js459
-rw-r--r--chromium/chrome/browser/resources/options/language_add_language_overlay.html16
-rw-r--r--chromium/chrome/browser/resources/options/language_add_language_overlay.js61
-rw-r--r--chromium/chrome/browser/resources/options/language_dictionary_overlay.css47
-rw-r--r--chromium/chrome/browser/resources/options/language_dictionary_overlay.html20
-rw-r--r--chromium/chrome/browser/resources/options/language_dictionary_overlay.js118
-rw-r--r--chromium/chrome/browser/resources/options/language_dictionary_overlay_word_list.js239
-rw-r--r--chromium/chrome/browser/resources/options/language_list.js440
-rw-r--r--chromium/chrome/browser/resources/options/language_options.css191
-rw-r--r--chromium/chrome/browser/resources/options/language_options.html136
-rw-r--r--chromium/chrome/browser/resources/options/language_options.js1304
-rw-r--r--chromium/chrome/browser/resources/options/manage_profile_overlay.css130
-rw-r--r--chromium/chrome/browser/resources/options/manage_profile_overlay.html124
-rw-r--r--chromium/chrome/browser/resources/options/manage_profile_overlay.js704
-rw-r--r--chromium/chrome/browser/resources/options/managed_user_create_confirm.css46
-rw-r--r--chromium/chrome/browser/resources/options/managed_user_create_confirm.html13
-rw-r--r--chromium/chrome/browser/resources/options/managed_user_create_confirm.js118
-rw-r--r--chromium/chrome/browser/resources/options/managed_user_import.css75
-rw-r--r--chromium/chrome/browser/resources/options/managed_user_import.html24
-rw-r--r--chromium/chrome/browser/resources/options/managed_user_import.js238
-rw-r--r--chromium/chrome/browser/resources/options/managed_user_learn_more.css40
-rw-r--r--chromium/chrome/browser/resources/options/managed_user_learn_more.html17
-rw-r--r--chromium/chrome/browser/resources/options/managed_user_learn_more.js42
-rw-r--r--chromium/chrome/browser/resources/options/managed_user_list.js117
-rw-r--r--chromium/chrome/browser/resources/options/media_galleries_list.js59
-rw-r--r--chromium/chrome/browser/resources/options/media_galleries_manager_overlay.html19
-rw-r--r--chromium/chrome/browser/resources/options/media_galleries_manager_overlay.js81
-rw-r--r--chromium/chrome/browser/resources/options/options.html190
-rw-r--r--chromium/chrome/browser/resources/options/options.js259
-rw-r--r--chromium/chrome/browser/resources/options/options_bundle.js117
-rw-r--r--chromium/chrome/browser/resources/options/options_focus_manager.js34
-rw-r--r--chromium/chrome/browser/resources/options/options_page.css459
-rw-r--r--chromium/chrome/browser/resources/options/options_page.js1006
-rw-r--r--chromium/chrome/browser/resources/options/options_settings_app.css47
-rw-r--r--chromium/chrome/browser/resources/options/options_settings_app.js52
-rw-r--r--chromium/chrome/browser/resources/options/password_manager.css28
-rw-r--r--chromium/chrome/browser/resources/options/password_manager.html35
-rw-r--r--chromium/chrome/browser/resources/options/password_manager.js250
-rw-r--r--chromium/chrome/browser/resources/options/password_manager_list.css58
-rw-r--r--chromium/chrome/browser/resources/options/password_manager_list.js342
-rw-r--r--chromium/chrome/browser/resources/options/pref_ui.js557
-rw-r--r--chromium/chrome/browser/resources/options/preferences.js338
-rw-r--r--chromium/chrome/browser/resources/options/profiles_icon_grid.js68
-rw-r--r--chromium/chrome/browser/resources/options/reset_profile_settings_banner.css77
-rw-r--r--chromium/chrome/browser/resources/options/reset_profile_settings_banner.html18
-rw-r--r--chromium/chrome/browser/resources/options/reset_profile_settings_banner.js103
-rw-r--r--chromium/chrome/browser/resources/options/reset_profile_settings_overlay.css62
-rw-r--r--chromium/chrome/browser/resources/options/reset_profile_settings_overlay.html45
-rw-r--r--chromium/chrome/browser/resources/options/reset_profile_settings_overlay.js96
-rw-r--r--chromium/chrome/browser/resources/options/search_box.html10
-rw-r--r--chromium/chrome/browser/resources/options/search_engine_manager.css81
-rw-r--r--chromium/chrome/browser/resources/options/search_engine_manager.html25
-rw-r--r--chromium/chrome/browser/resources/options/search_engine_manager.js131
-rw-r--r--chromium/chrome/browser/resources/options/search_engine_manager_engine_list.js343
-rw-r--r--chromium/chrome/browser/resources/options/search_page.css74
-rw-r--r--chromium/chrome/browser/resources/options/search_page.html12
-rw-r--r--chromium/chrome/browser/resources/options/search_page.js572
-rw-r--r--chromium/chrome/browser/resources/options/settings_dialog.js70
-rw-r--r--chromium/chrome/browser/resources/options/spelling_confirm_overlay.css8
-rw-r--r--chromium/chrome/browser/resources/options/spelling_confirm_overlay.html19
-rw-r--r--chromium/chrome/browser/resources/options/startup_overlay.css41
-rw-r--r--chromium/chrome/browser/resources/options/startup_overlay.html25
-rw-r--r--chromium/chrome/browser/resources/options/startup_overlay.js175
-rw-r--r--chromium/chrome/browser/resources/options/startup_section.html53
-rw-r--r--chromium/chrome/browser/resources/options/subpages_tab_controls.css74
-rw-r--r--chromium/chrome/browser/resources/options/sync_section.html77
-rw-r--r--chromium/chrome/browser/resources/options/yellow_gear.pngbin0 -> 1116 bytes
183 files changed, 30991 insertions, 0 deletions
diff --git a/chromium/chrome/browser/resources/options/2x/yellow_gear.png b/chromium/chrome/browser/resources/options/2x/yellow_gear.png
new file mode 100644
index 00000000000..087c8a329a6
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/2x/yellow_gear.png
Binary files differ
diff --git a/chromium/chrome/browser/resources/options/OWNERS b/chromium/chrome/browser/resources/options/OWNERS
new file mode 100644
index 00000000000..8da21baca39
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/OWNERS
@@ -0,0 +1,3 @@
+dbeam@chromium.org
+estade@chromium.org
+jhawkins@chromium.org
diff --git a/chromium/chrome/browser/resources/options/alert_overlay.css b/chromium/chrome/browser/resources/options/alert_overlay.css
new file mode 100644
index 00000000000..b2d9960a9a1
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/alert_overlay.css
@@ -0,0 +1,7 @@
+/* Copyright (c) 2012 The Chromium Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file. */
+
+#alertOverlayMessage {
+ width: 400px;
+}
diff --git a/chromium/chrome/browser/resources/options/alert_overlay.html b/chromium/chrome/browser/resources/options/alert_overlay.html
new file mode 100644
index 00000000000..2c96ff14f79
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/alert_overlay.html
@@ -0,0 +1,13 @@
+<div id="alertOverlay" class="page" hidden>
+ <div class="close-button"></div>
+ <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" class="default-button" type="submit"></button>
+ </div>
+ </div>
+</div>
diff --git a/chromium/chrome/browser/resources/options/alert_overlay.js b/chromium/chrome/browser/resources/options/alert_overlay.js
new file mode 100644
index 00000000000..aab11765e67
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/alert_overlay.js
@@ -0,0 +1,152 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+ var OptionsPage = options.OptionsPage;
+
+ /**
+ * 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_();
+ };
+ },
+
+ /** @override */
+ get nestingLevel() {
+ // AlertOverlay is special in that it is not tied to one page or overlay.
+ // Set the nesting level arbitrarily high so as to always be recognized as
+ // the top-most visible page.
+ return 99;
+ },
+
+ /**
+ * 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;
+ },
+
+ /** @override */
+ 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/chromium/chrome/browser/resources/options/arrow_next.png b/chromium/chrome/browser/resources/options/arrow_next.png
new file mode 100644
index 00000000000..7d626c0e8cd
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/arrow_next.png
Binary files differ
diff --git a/chromium/chrome/browser/resources/options/autofill_edit_address_overlay.html b/chromium/chrome/browser/resources/options/autofill_edit_address_overlay.html
new file mode 100644
index 00000000000..44ff77f147c
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/autofill_edit_address_overlay.html
@@ -0,0 +1,75 @@
+<div id="autofill-edit-address-overlay" class="page" hidden>
+ <div class="close-button"></div>
+ <h1 id="autofill-address-title"></h1>
+ <div class="content-area">
+ <div>
+ <div id="autofill-name-labels">
+ <span i18n-content="autofillFirstNameLabel"></span>
+ <span i18n-content="autofillMiddleNameLabel"></span>
+ <span i18n-content="autofillLastNameLabel"></span>
+ </div>
+ </div>
+ <div>
+ <list id="full-name-list"></list>
+ </div>
+
+ <label class="settings-row">
+ <div i18n-content="autofillCompanyNameLabel"></div>
+ <input id="company-name" type="text">
+ </label>
+
+ <label class="settings-row">
+ <div i18n-content="autofillAddrLine1Label"></div>
+ <input id="addr-line-1" type="text">
+ </label>
+
+ <label class="settings-row">
+ <div i18n-content="autofillAddrLine2Label"></div>
+ <input id="addr-line-2" type="text">
+ </label>
+
+ <div class="input-group settings-row">
+ <label>
+ <div i18n-content="autofillCityLabel"></div>
+ <input id="city" type="text">
+ </label>
+
+ <label>
+ <div id="state-label"></div>
+ <input id="state" type="text">
+ </label>
+
+ <label>
+ <div id="postal-code-label"></div>
+ <input id="postal-code" type="text">
+ </label>
+ </div>
+
+ <div class="settings-row">
+ <div i18n-content="autofillCountryLabel"></div>
+ <select id="country"></select>
+ </div>
+
+ <div class="input-group settings-row">
+ <div>
+ <div i18n-content="autofillPhoneLabel"></div>
+ <list id="phone-list"
+ i18n-values="placeholder:autofillAddPhonePlaceholder"></list>
+ </div>
+
+ <div>
+ <div i18n-content="autofillEmailLabel"></div>
+ <list id="email-list"
+ i18n-values="placeholder:autofillAddEmailPlaceholder"></list>
+ </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"
+ class="default-button" i18n-content="ok" disabled></button>
+ </div>
+</div>
diff --git a/chromium/chrome/browser/resources/options/autofill_edit_address_overlay.js b/chromium/chrome/browser/resources/options/autofill_edit_address_overlay.js
new file mode 100644
index 00000000000..94b6a5d307d
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/autofill_edit_address_overlay.js
@@ -0,0 +1,301 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+ /** @const */ var OptionsPage = options.OptionsPage;
+ /** @const */ var 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',
+ loadTimeData.getString('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_();
+ };
+
+ // TODO(jhawkins): Investigate other possible solutions.
+ $('autofill-edit-address-apply-button').onclick = function(event) {
+ // Blur active element to ensure that pending changes are committed.
+ if (document.activeElement)
+ document.activeElement.blur();
+ // Blurring is delayed for list elements. Queue save and close to
+ // ensure that pending changes have been applied.
+ setTimeout(function() {
+ self.saveAddress_();
+ self.dismissOverlay_();
+ }, 0);
+ };
+
+ // Prevent 'blur' events on the OK and cancel buttons, which can trigger
+ // insertion of new placeholder elements. The addition of placeholders
+ // affects layout, which interferes with being able to click on the
+ // buttons.
+ $('autofill-edit-address-apply-button').onmousedown = function(event) {
+ event.preventDefault();
+ };
+ $('autofill-edit-address-cancel-button').onmousedown = function(event) {
+ event.preventDefault();
+ };
+
+ self.guid = '';
+ self.populateCountryList_();
+ self.clearInputFields_();
+ self.connectInputEvents_();
+ },
+
+ /**
+ * Specifically catch the situations in which the overlay is cancelled
+ * externally (e.g. by pressing <Esc>), so that the input fields and
+ * GUID can be properly cleared.
+ * @override
+ */
+ handleCancel: function() {
+ this.dismissOverlay_();
+ },
+
+ /**
+ * 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);
+
+ // Add special entry for adding new values.
+ var augmentedList = entries.slice();
+ augmentedList.push(null);
+ list.dataModel = new ArrayDataModel(augmentedList);
+
+ // Update the status of the 'OK' button.
+ this.inputFieldChanged_();
+
+ list.dataModel.addEventListener('splice',
+ this.inputFieldChanged_.bind(this));
+ list.dataModel.addEventListener('change',
+ this.inputFieldChanged_.bind(this));
+ },
+
+ /**
+ * 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 ||
+ loadTimeData.getString('defaultCountryCode');
+
+ var details = loadTimeData.getValue('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 countryList = loadTimeData.getValue('autofillCountrySelectList');
+
+ // Add the countries to the country <select> list.
+ var countrySelect = $('country');
+ // Add an empty option.
+ countrySelect.appendChild(new Option('', ''));
+ for (var i = 0; i < countryList.length; i++) {
+ var option = new Option(countryList[i].name,
+ countryList[i].value);
+ option.disabled = countryList[i].value == 'separator';
+ countrySelect.appendChild(option);
+ }
+ },
+
+ /**
+ * Clears the value of each input field.
+ * @private
+ */
+ clearInputFields_: function() {
+ this.setMultiValueList_('full-name-list', []);
+ $('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.setMultiValueList_('full-name-list', 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.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/chromium/chrome/browser/resources/options/autofill_edit_creditcard_overlay.html b/chromium/chrome/browser/resources/options/autofill_edit_creditcard_overlay.html
new file mode 100644
index 00000000000..3c5b07f5a57
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/autofill_edit_creditcard_overlay.html
@@ -0,0 +1,31 @@
+<div id="autofill-edit-credit-card-overlay" class="page" hidden>
+ <div class="close-button"></div>
+ <h1 id="autofill-credit-card-title"></h1>
+ <div class="content-area">
+ <label class="settings-row">
+ <div i18n-content="nameOnCardLabel"></div>
+ <input id="name-on-card" type="text">
+ </label>
+
+ <label class="settings-row">
+ <div i18n-content="creditCardNumberLabel"></div>
+ <input id="credit-card-number" type="text">
+ </label>
+
+ <div class="settings-row">
+ <div id="creditCardExpirationLabel"
+ i18n-content="creditCardExpirationDateLabel"></div>
+ <select id="expiration-month" aria-labelledby="creditCardExpirationLabel">
+ </select>
+ <select id="expiration-year" aria-labelledby="creditCardExpirationLabel">
+ </select>
+ </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"
+ class="default-button" i18n-content="ok" disabled></button>
+ </div>
+</div>
diff --git a/chromium/chrome/browser/resources/options/autofill_edit_creditcard_overlay.js b/chromium/chrome/browser/resources/options/autofill_edit_creditcard_overlay.js
new file mode 100644
index 00000000000..d836a1539b7
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/autofill_edit_creditcard_overlay.js
@@ -0,0 +1,206 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+ /** @const */ var OptionsPage = options.OptionsPage;
+
+ /**
+ * AutofillEditCreditCardOverlay class
+ * Encapsulated handling of the 'Add Page' overlay page.
+ * @class
+ */
+ function AutofillEditCreditCardOverlay() {
+ OptionsPage.call(this, 'autofillEditCreditCard',
+ loadTimeData.getString('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.clearInputFields_();
+ self.connectInputEvents_();
+ self.setDefaultSelectOptions_();
+ },
+
+ /**
+ * Specifically catch the situations in which the overlay is cancelled
+ * externally (e.g. by pressing <Esc>), so that the input fields and
+ * GUID can be properly cleared.
+ * @override
+ */
+ handleCancel: function() {
+ this.dismissOverlay_();
+ },
+
+ /**
+ * Clears any uncommitted input, 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
+ */
+ 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.loadCreditCard = function(creditCard) {
+ AutofillEditCreditCardOverlay.getInstance().loadCreditCard_(creditCard);
+ };
+
+ AutofillEditCreditCardOverlay.setTitle = function(title) {
+ $('autofill-credit-card-title').textContent = title;
+ };
+
+ // Export
+ return {
+ AutofillEditCreditCardOverlay: AutofillEditCreditCardOverlay
+ };
+});
diff --git a/chromium/chrome/browser/resources/options/autofill_edit_overlay.css b/chromium/chrome/browser/resources/options/autofill_edit_overlay.css
new file mode 100644
index 00000000000..166f0cd3da1
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/autofill_edit_overlay.css
@@ -0,0 +1,84 @@
+/* Copyright (c) 2012 The Chromium Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file. */
+
+#autofill-edit-address-overlay {
+ min-width: 55em;
+}
+
+#autofill-edit-credit-card-overlay {
+ min-width: 500px;
+}
+
+#full-name-list input,
+#company-name,
+#addr-line-1,
+#addr-line-2 {
+ width: 16em;
+}
+
+#country {
+ max-width: 450px;
+}
+
+#autofill-edit-address-overlay list {
+ -webkit-margin-start: -3px;
+ /* Min height is a multiple of the list item height (32px). */
+ min-height: 32px;
+}
+
+#autofill-edit-address-overlay list div.static-text {
+ -webkit-border-radius: 2px;
+ -webkit-box-flex: 1;
+ -webkit-padding-end: 4px;
+ -webkit-padding-start: 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;
+}
+
+:-webkit-any(#autofill-edit-credit-card-overlay, #autofill-edit-address-overlay)
+ .settings-row div + :-webkit-any(input, select) {
+ margin-top: 4px;
+}
+
+#autofill-name-labels {
+ display: -webkit-inline-box;
+}
+
+#autofill-name-labels span {
+ -webkit-box-flex: 1;
+ display: block;
+}
+
+#full-name-list {
+ display: inline-block;
+}
+
+#full-name-list div[role='listitem'] > div {
+ display: -webkit-box;
+}
+
+#full-name-list div[role='listitem'] > div > div,
+#autofill-name-labels span {
+ -webkit-margin-end: 5px;
+ width: 16em;
+}
+
+:-webkit-any(#phone-list, #email-list) div[role='listitem'] > div > div,
+:-webkit-any(#phone-list, #email-list) input {
+ width: 14em;
+}
+
+.input-group > * {
+ -webkit-box-orient: vertical;
+ -webkit-margin-end: 2px;
+ display: -webkit-inline-box;
+ vertical-align: top;
+}
+
+#autofill-edit-credit-card-overlay .content-area > *:first-child {
+ margin-top: 0;
+}
diff --git a/chromium/chrome/browser/resources/options/autofill_options.css b/chromium/chrome/browser/resources/options/autofill_options.css
new file mode 100644
index 00000000000..e777c941248
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/autofill_options.css
@@ -0,0 +1,52 @@
+/* Copyright (c) 2012 The Chromium Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file. */
+
+#autofill-options {
+ width: 550px;
+}
+
+#autofill-help {
+ bottom: 18px;
+ position: absolute;
+}
+
+#autofill-options list {
+ min-height: 172px;
+}
+
+.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: top;
+}
+
+#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: 5px 5px;
+}
+
+#autofill-options .list-inline-button {
+ margin-top: 0;
+ vertical-align: top;
+}
+
+#autofill-options div[role='listitem']:not(:hover):not([selected])
+ .list-inline-button {
+ display: none;
+}
diff --git a/chromium/chrome/browser/resources/options/autofill_options.html b/chromium/chrome/browser/resources/options/autofill_options.html
new file mode 100644
index 00000000000..27a869aced5
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/autofill_options.html
@@ -0,0 +1,40 @@
+<div id="autofill-options" class="page" hidden>
+ <div class="close-button"></div>
+ <h1 i18n-content="autofillOptionsPage"></h1>
+ <div class="content-area">
+<if expr="is_macosx">
+ <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>
+ <div class="action-area">
+ <a id="autofill-help" target="_blank" i18n-values="href:helpUrl"
+ i18n-content="helpButton">
+ </a>
+ <div class="button-strip">
+ <button id="autofill-options-confirm" class="default-button"
+ i18n-content="done"></button>
+ </div>
+ </div>
+</div>
diff --git a/chromium/chrome/browser/resources/options/autofill_options.js b/chromium/chrome/browser/resources/options/autofill_options.js
new file mode 100644
index 00000000000..65fb71964ae
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/autofill_options.js
@@ -0,0 +1,216 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+ var OptionsPage = options.OptionsPage;
+ var ArrayDataModel = cr.ui.ArrayDataModel;
+
+ /////////////////////////////////////////////////////////////////////////////
+ // AutofillOptions class:
+
+ /**
+ * Encapsulated handling of Autofill options page.
+ * @constructor
+ */
+ function AutofillOptions() {
+ OptionsPage.call(this,
+ 'autofill',
+ loadTimeData.getString('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_();
+ };
+ $('autofill-options-confirm').onclick = function(event) {
+ OptionsPage.closeOverlay();
+ };
+
+ // 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 and modifying the overlay title.
+ * @private
+ */
+ showAddAddressOverlay_: function() {
+ var title = loadTimeData.getString('addAddressTitle');
+ AutofillEditAddressOverlay.setTitle(title);
+ OptionsPage.navigateToPage('autofillEditAddress');
+ },
+
+ /**
+ * Shows the 'Add credit card' overlay, specifically by loading the
+ * 'Edit credit card' overlay and modifying the overlay title.
+ * @private
+ */
+ showAddCreditCardOverlay_: function() {
+ var title = loadTimeData.getString('addCreditCardTitle');
+ AutofillEditCreditCardOverlay.setTitle(title);
+ 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 or credit card represented by |guid|.
+ * @param {string} guid The GUID of the address to remove.
+ * @private
+ */
+ removeData_: function(guid) {
+ chrome.send('removeData', [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 = loadTimeData.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 = loadTimeData.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.removeData = function(guid) {
+ AutofillOptions.getInstance().removeData_(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/chromium/chrome/browser/resources/options/autofill_options_list.js b/chromium/chrome/browser/resources/options/autofill_options_list.js
new file mode 100644
index 00000000000..0be698d3e2a
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/autofill_options_list.js
@@ -0,0 +1,508 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options.autofillOptions', function() {
+ /** @const */ var DeletableItem = options.DeletableItem;
+ /** @const */ var DeletableItemList = options.DeletableItemList;
+ /** @const */ var InlineEditableItem = options.InlineEditableItem;
+ /** @const */ var InlineEditableItemList = options.InlineEditableItemList;
+
+ function AutofillEditProfileButton(guid, edit) {
+ var editButtonEl = document.createElement('button');
+ editButtonEl.className = 'list-inline-button custom-appearance';
+ editButtonEl.textContent =
+ loadTimeData.getString('autofillEditProfileButton');
+ editButtonEl.onclick = function(e) { edit(guid); };
+
+ editButtonEl.onmousedown = function(e) {
+ // Don't select the row when clicking the button.
+ e.stopPropagation();
+ // Don't focus on the button when clicking it.
+ e.preventDefault();
+ };
+
+ 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,
+
+ /** @override */
+ 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,
+
+ /** @override */
+ 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('img');
+ 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,
+
+ /** @override */
+ 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 {string} This item's value.
+ * @protected
+ */
+ value_: function() {
+ return this.input.value;
+ },
+
+ /**
+ * @param {Object} value The value to test.
+ * @return {boolean} True if the given value is non-empty.
+ * @protected
+ */
+ valueIsNonEmpty_: function(value) {
+ return !!value;
+ },
+
+ /**
+ * @return {boolean} 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,
+
+ /** @override */
+ 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 =
+ loadTimeData.getString('autofillAddFirstNamePlaceholder');
+ this.middleNameInput.placeholder =
+ loadTimeData.getString('autofillAddMiddleNamePlaceholder');
+ this.lastNameInput.placeholder =
+ loadTimeData.getString('autofillAddLastNamePlaceholder');
+ this.deletable = false;
+ }
+
+ this.addEventListener('commitedit', this.onEditCommitted_);
+ },
+
+ /** @override */
+ value_: function() {
+ return [this.firstNameInput.value,
+ this.middleNameInput.value,
+ this.lastNameInput.value];
+ },
+
+ /** @override */
+ valueIsNonEmpty_: function(value) {
+ return value[0] || value[1] || value[2];
+ },
+
+ /** @override */
+ 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];
+ },
+
+ /** @override */
+ 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);
+ },
+
+ /** @override */
+ activateItemAtIndex: function(index) {
+ AutofillOptions.loadAddressEditor(this.dataModel.item(index)[0]);
+ },
+
+ /** @override */
+ createItem: function(entry) {
+ return new AddressListItem(entry);
+ },
+
+ /** @override */
+ deleteItemAtIndex: function(index) {
+ AutofillOptions.removeData(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);
+ },
+
+ /** @override */
+ activateItemAtIndex: function(index) {
+ AutofillOptions.loadCreditCardEditor(this.dataModel.item(index)[0]);
+ },
+
+ /** @override */
+ createItem: function(entry) {
+ return new CreditCardListItem(entry);
+ },
+
+ /** @override */
+ deleteItemAtIndex: function(index) {
+ AutofillOptions.removeData(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,
+
+ /** @override */
+ createItem: function(entry) {
+ return new ValuesListItem(this, entry);
+ },
+
+ /** @override */
+ deleteItemAtIndex: function(index) {
+ this.dataModel.splice(index, 1);
+ },
+
+ /** @override */
+ 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,
+
+ /** @override */
+ 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,
+
+ /** @override */
+ 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/chromium/chrome/browser/resources/options/browser_options.css b/chromium/chrome/browser/resources/options/browser_options.css
new file mode 100644
index 00000000000..a3dcba14d10
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/browser_options.css
@@ -0,0 +1,427 @@
+/* Copyright (c) 2012 The Chromium Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file. */
+
+#change-home-page-section {
+ margin-left: 30px;
+}
+
+#home-page-url {
+ display: inline-block;
+ max-width: 400px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ vertical-align: top;
+}
+
+#default-browser-state,
+#profiles-supervised-dashboard-tip {
+ margin-top: 6px;
+}
+
+#sync-overview p {
+ display: inline;
+}
+
+#account-picture-wrapper {
+ float: left;
+ margin: 0 2px 10px 0;
+}
+
+html[dir=rtl] #account-picture-wrapper {
+ float: right;
+}
+
+#account-picture-control {
+ border: 1px solid rgba(0, 0, 0, 0.3);
+ border-radius: 4px;
+ display: inline-block;
+ padding: 3px;
+ position: relative;
+}
+
+#account-picture {
+ height: 56px;
+ vertical-align: middle;
+ width: 56px;
+}
+
+#account-picture:disabled {
+ cursor: default;
+}
+
+#change-picture-caption {
+ background: rgba(0, 0, 0, 0.5);
+ bottom: 0;
+ color: white;
+ cursor: pointer;
+ font-size: small;
+ margin: 3px 0;
+ position: absolute;
+ text-align: center;
+ visibility: hidden;
+ /* Width of #account-picture. */
+ width: 56px;
+}
+
+#account-picture:not(:disabled):hover + #change-picture-caption,
+#account-picture:not(:disabled) + #change-picture-caption:hover {
+ visibility: visible;
+}
+
+#account-picture-indicator {
+ -webkit-margin-end: 3px;
+}
+
+#sync-general {
+ -webkit-margin-start: 76px;
+ margin-bottom: 10px;
+}
+
+#sync-buttons {
+ clear: both;
+}
+
+#profiles-list {
+ margin-bottom: 10px;
+ min-height: 0;
+}
+
+#profiles-list .profile-name {
+ -webkit-box-flex: 1;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+#profiles-list > * {
+ height: 40px;
+}
+
+#profiles-list:focus {
+ border-color: rgb(77, 144, 254);
+}
+
+.profile-img {
+ height: 31px;
+ padding: 3px;
+ vertical-align: middle;
+ width: 38px;
+}
+
+.profile-item-current {
+ font-weight: bold;
+}
+
+#profiles-buttons {
+ white-space: nowrap;
+}
+
+.sync-error {
+ background: rgb(255, 219, 219);
+ border: 1px solid rgb(206, 76, 76);
+ border-radius: 2px;
+ padding: 10px;
+}
+
+.sync-error .link-button {
+ margin: 0 1ex;
+ padding: 0;
+}
+
+#mac-passwords-warning {
+ margin-top: 10px;
+}
+
+input[type='range'] {
+ vertical-align: middle;
+}
+
+/* CSS tweak to fix crbug.com/151788. Inconsistencies in the CSS rules across
+ * platforms and elements. Too risky to attempt a general fix for M23 at this
+ * time. This fix addresses the immediate problem in the bug report by forcing
+ * the button to align consistently with its neighboring select element.
+ * TODO(kevers): Revisit padding rules for select and buttons to ensure
+ * consistency in the size and baseline across all platforms. */
+#manage-default-search-engines {
+ padding-bottom: 0;
+ padding-top: 0;
+}
+
+/* Override a platform specific rule in Widgets that may no longer be relevant.
+ * Too late in the development cycle to update Widgets.css due to the number
+ * of pages that depend on it.
+ * TODO(kevers): Rivisit padding rules. */
+#default-search-engine {
+ padding-bottom: 0;
+}
+
+/* Internet settings */
+
+#network-settings {
+ position: relative;
+}
+
+#network-list {
+ min-height: 0;
+ width: 320px;
+}
+
+#download-location-label.disabled {
+ color: #999;
+}
+
+.network-group {
+ -webkit-box-orient: horizontal;
+ height: 42px;
+ overflow: visible;
+}
+
+list:not([disabled]) > .network-group:hover,
+list:not([disabled]) > .network-group[selected] {
+ background-color: #f8f8f8 !important;
+ background-image: -webkit-linear-gradient(rgba(255, 255, 255, 0.8),
+ rgba(255, 255, 255, 0)) !important;
+ box-shadow: inset 0 0 1px 1px #f0f0f0;
+}
+
+.network-group-labels {
+ -webkit-box-flex: 1;
+ -webkit-box-orient: vertical;
+ display: -webkit-box;
+ padding-top: 3px;
+}
+
+.network-icon,
+.network-menu-item-icon {
+ -webkit-margin-end: 8px;
+ background-position: left top;
+ background-size: 25px;
+ height: 25px;
+ width: 25px;
+}
+
+.other-cellulars > .network-menu-item-icon {
+ background-position: left top;
+}
+
+@-webkit-keyframes connecting-animation {
+ 0% {
+ background-position: 0 25%;
+ }
+ 12.5% {
+ background-position: 0 50%;
+ }
+ 25% {
+ background-position: 0 75%;
+ }
+ 37.5% {
+ background-position: 0 100%;
+ }
+ 50% {
+ background-position: 0 100%;
+ }
+ 62.5% {
+ background-position: 0 75%;
+ }
+ 75% {
+ background-position: 0 50%;
+ }
+ 87.5% {
+ background-position: 0 25%;
+ }
+}
+
+.network-add-connection,
+.network-control-active,
+.network-control-inactive {
+ background-position: center center !important;
+ background-repeat: no-repeat;
+}
+
+.network-add-connection {
+ background-image: url('chrome://theme/IDR_NETWORK_ADD_CONNECTION');
+ background-size: 16px;
+}
+
+.network-control-inactive {
+ background-image: none;
+}
+
+.network-control-active {
+ background-image: url('chrome://theme/IDR_PROFILE_SELECTED');
+ background-size: 16px;
+}
+
+.network-options-button {
+ -webkit-box-flex: 0;
+ -webkit-transform: scale(0.6);
+ background-image: none;
+ background-position: center center;
+ display: block;
+ opacity: 0.5;
+ vertical-align: middle;
+ width: 19px;
+}
+
+.network-group > .controlled-setting-indicator,
+.network-menu-item > .controlled-setting-indicator {
+ -webkit-margin-end: 5px;
+}
+
+.network-options-button:hover {
+ opacity: 1;
+}
+
+@-webkit-keyframes vpn-connecting-animation {
+ from {
+ opacity: 1;
+ }
+ to {
+ opacity: 0.2;
+ }
+}
+
+.network-connecting {
+ -webkit-animation: connecting-animation 1s step-end infinite;
+}
+
+.network-vpn.network-connecting {
+ -webkit-animation: vpn-connecting-animation 500ms alternate infinite;
+}
+
+.network-title {
+ font-weight: 600;
+ line-height: 120%;
+}
+
+.network-subtitle {
+ color: #333;
+ display: inline-block;
+ line-height: 100%;
+ max-width: 260px;
+ opacity: 0.4;
+ overflow: hidden;
+ padding-bottom: 3px;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.network-selector {
+ background: right center no-repeat;
+ background-image: url('../../../../ui/webui/resources/images/select.png');
+ padding-right: 20px;
+}
+
+.network-menu {
+ -webkit-box-shadow:
+ 0 0 0 1px rgba(0,0,0,0.1),
+ 0 5px 1px 1px rgba(0,0,0,0.1),
+ 0 5px 2px 1px rgba(0,0,0,0.1),
+ 0 5px 12px 1px rgba(0,0,0,0.5);
+ background: #fff;
+ display: block;
+ position: absolute;
+ width: 320px;
+ z-index: 1;
+}
+
+.network-menu-item {
+ -webkit-box-align: center;
+ -webkit-box-orient: horizontal;
+ display: -webkit-box;
+ height: 32px;
+ margin-left: 4px;
+ margin-right: 4px;
+}
+
+.network-menu-item-label {
+ -webkit-box-flex: 1;
+ color: #555;
+ display: block;
+ overflow-x: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.active-network {
+ color: black;
+ font-weight: bold;
+}
+
+.network-disabled-control {
+ color: #999;
+}
+
+/* Restrict the size of the networks menu, by limiting the number of
+ visible networks. */
+.network-menu-group {
+ max-height: 192px;
+ overflow-x: hidden;
+ overflow-y: auto;
+ text-overflow: ellipsis;
+}
+
+.network-menu-item:hover {
+ background-color: #eee;
+}
+
+.network-menu > hr {
+ opacity: 0.4;
+}
+
+#shared-proxies {
+ margin-top: 12px;
+}
+
+#web-content-section select,
+.web-content-select-label {
+ min-width: 145px;
+}
+
+.web-content-select-label > span:only-of-type {
+ display: inline-block;
+ min-width: 100px;
+}
+
+#timezone-value {
+ display: inline-block;
+ vertical-align: baseline;
+}
+
+#privacy-explanation {
+ line-height: 1.8em;
+}
+
+#advanced-settings {
+ height: 0;
+ margin-top: 8px;
+ overflow: hidden;
+}
+
+#auto-open-file-types-label {
+ padding: 0.45em 0
+}
+
+.sliding {
+ -webkit-transition: height 200ms;
+ overflow-y: hidden;
+}
+
+#keyboard-overlay .option-value > select {
+ width: 100%;
+}
+
+#keyboard-overlay table {
+ /* Same as .settings-row {margin}. */
+ -webkit-border-vertical-spacing: 0.65em;
+}
+
+#accessibility-autoclick .controlled-setting-with-label {
+ -webkit-box-align: baseline;
+}
+
+#accessibility-autoclick label + select {
+ /* Same as .controlled-setting-with-label > input + span. */
+ -webkit-margin-start: 0.6em;
+}
diff --git a/chromium/chrome/browser/resources/options/browser_options.html b/chromium/chrome/browser/resources/options/browser_options.html
new file mode 100644
index 00000000000..22c61ff490b
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/browser_options.html
@@ -0,0 +1,863 @@
+<div id="settings" class="page" hidden>
+ <header>
+ <h1 i18n-content="settingsTitle"></h1>
+ </header>
+ <include src="reset_profile_settings_banner.html">
+<if expr="not pp_ifdef('chromeos')">
+ <include src="sync_section.html">
+</if>
+<if expr="pp_ifdef('chromeos')">
+ <section>
+ <h3 i18n-content="sectionTitleInternet"></h3>
+ <div id="network-settings">
+ <list id="network-list"></list>
+ <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="network-menus"></div>
+ </div>
+ </section>
+</if>
+<if expr="not pp_ifdef('chromeos')">
+ <include src="startup_section.html">
+</if>
+ <section>
+ <h3 i18n-content="sectionTitleAppearance"></h3>
+ <div class="settings-row">
+<if expr="pp_ifdef('chromeos')">
+ <button id="set-wallpaper" i18n-content="setWallpaper"
+ guest-visibility="disabled"></button>
+</if>
+<if expr="not pp_ifdef('chromeos') and is_posix and not is_macosx">
+ <button id="themes-gallery" i18n-content="themesGallery"></button>
+ <button id="themes-native-button"
+ i18n-content="themesNativeButton"></button>
+ <button id="themes-reset"
+ i18n-content="themesSetClassic"></button>
+</if>
+<if expr="pp_ifdef('chromeos') or is_win or is_macosx">
+ <button id="themes-gallery" i18n-content="themesGallery"></button>
+ <button id="themes-reset" i18n-content="themesReset"></button>
+</if>
+ </div>
+ <div class="checkbox" guest-visibility="disabled">
+ <span class="controlled-setting-with-label">
+ <input id="show-home-button" type="checkbox"
+ pref="browser.show_home_button"
+ metric="Options_Homepage_HomeButton">
+ <span>
+ <label for="show-home-button" i18n-content="homePageShowHomeButton">
+ </label>
+ <span class="controlled-setting-indicator"
+ pref="browser.show_home_button">
+ </span>
+ </span>
+ </span>
+ </div>
+ <div id="change-home-page-section" hidden>
+ <div id="change-home-page-section-container" guest-visibility="disabled">
+ <span id="home-page-ntp" class="home-page-label"
+ i18n-content="homePageNtp"></span>
+ <span id="home-page-url" class="home-page-label"></span>
+ <button id="change-home-page" class="link-button"
+ i18n-content="changeHomePage"></button>
+ </div>
+ </div>
+ <div class="checkbox" guest-visibility="disabled">
+ <span class="controlled-setting-with-label">
+ <input id="show-bookmark-bars" type="checkbox"
+ pref="bookmark_bar.show_on_all_tabs"
+ metric="Options_ShowBookmarksBar">
+ <span>
+ <label for="show-bookmark-bars"
+ i18n-content="toolbarShowBookmarksBar">
+ </label>
+ <span class="controlled-setting-indicator"
+ pref="bookmark_bar.show_on_all_tabs">
+ </span>
+ </span>
+ </span>
+ </div>
+<if expr="not pp_ifdef('toolkit_views') and is_posix and not is_macosx">
+ <div class="checkbox"><label>
+ <input id="show-window-decorations" type="checkbox"
+ pref="browser.custom_chrome_frame" metric="Options_CustomFrame"
+ inverted_pref>
+ <span i18n-content="showWindowDecorations"></span>
+ </label></div>
+</if>
+ </section>
+<if expr="pp_ifdef('chromeos')">
+ <section>
+ <h3 i18n-content="sectionTitleDevice"></h3>
+ <div>
+ <span i18n-content="deviceGroupDescription"></span>
+ <div id="touchpad-settings" class="settings-row" hidden>
+ <span class="option-name" i18n-content="touchpadSpeed"></span>
+ <input id="sensitivity-range" type="range" min="1" max="5"
+ pref="settings.touchpad.sensitivity2" class="touch-slider">
+ </div>
+ <div id="mouse-settings" class="settings-row" hidden>
+ <span class="option-name" i18n-content="mouseSpeed"></span>
+ <input id="sensitivity-range" type="range" min="1" max="5"
+ pref="settings.mouse.sensitivity2" class="touch-slider">
+ </div>
+ <div id="no-pointing-devices" i18n-content="noPointingDevices"
+ class="settings-row" hidden>
+ </div>
+ <div class="settings-row">
+ <button id="pointer-settings-button" hidden>
+ </button>
+ <button id="keyboard-settings-button"
+ i18n-content="keyboardSettingsButtonTitle">
+ </button>
+ <span id="display-options-section">
+ <button id="display-options" i18n-content="displayOptions">
+ </button>
+ </span>
+ </div>
+ </div>
+ </section>
+</if>
+ <section>
+ <h3 i18n-content="sectionTitleSearch"></h3>
+ <div>
+ <label for="default-search-engine" class="settings-row"
+ i18n-values=".innerHTML:defaultSearchGroupLabel">
+ </label>
+ <div class="settings-row">
+ <select id="default-search-engine" class="weakrtl"></select>
+ <span class="controlled-setting-indicator"
+ pref="default_search_provider.enabled">
+ </span>
+ <button id="manage-default-search-engines"
+ i18n-content="defaultSearchManageEngines">
+ </button>
+ </div>
+ </div>
+ </section>
+ <section id="sync-users-section" guest-visibility="hidden">
+ <h3 i18n-content="sectionTitleUsers"></h3>
+<if expr="pp_ifdef('chromeos')">
+ <include src="sync_section.html">
+</if>
+ <div id="profiles-section" hidden>
+ <list id="profiles-list" class="settings-list" hidden></list>
+ <div id="profiles-single-message" class="settings-row"
+ i18n-content="profilesSingleUser">
+ </div>
+ <div id="profiles-buttons">
+ <button id="profiles-create" i18n-content="profilesCreate"></button>
+<if expr="pp_ifdef('enable_settings_app')">
+ <button id="profiles-app-list-switch"
+ i18n-content="profilesAppListSwitch" hidden>
+ </button>
+</if>
+ <button id="profiles-manage" i18n-content="profilesManage" disabled>
+ </button>
+ <button id="profiles-delete" i18n-content="profilesDelete"></button>
+<if expr="not pp_ifdef('chromeos')">
+ <button id="import-data" i18n-content="importData"></button>
+</if>
+ </div>
+ </div>
+ <div id="profiles-supervised-dashboard-tip"
+ i18n-values=".innerHTML:profilesSupervisedDashboardTip" hidden>
+ </div>
+ </section>
+<if expr="not pp_ifdef('chromeos')">
+ <section>
+ <h3 i18n-content="sectionTitleDefaultBrowser"></h3>
+ <div>
+ <button id="set-as-default-browser"
+ i18n-content="defaultBrowserUseAsDefault" hidden>
+ </button>
+ <div id="default-browser-state" i18n-content="defaultBrowserUnknown">
+ </div>
+ <div id="auto-launch-option" class="checkbox" hidden>
+ <label id="auto-launch-label">
+ <input id="auto-launch" type="checkbox">
+ <span i18n-content="autoLaunchText"></span>
+ </label>
+ </div>
+ </div>
+ </section>
+</if> <!-- not pp_ifdef('chromeos') -->
+<div id="advanced-settings" hidden>
+<div id="advanced-settings-container">
+<if expr="pp_ifdef('chromeos')">
+ <section>
+ <h3 i18n-content="datetimeTitle"></h3>
+ <div class="option-control-table">
+ <div guest-visibility="disabled">
+ <label for="timezone-select" class="option-name"
+ i18n-content="timezone"></label>
+ <div id="timezone-value">
+ <select id="timezone-select" class="control"
+ i18n-options="timezoneList" data-type="string"
+ pref="cros.system.timezone">
+ </select>
+ </div>
+ </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>
+</if>
+ <section id="privacy-section">
+ <h3 i18n-content="advancedSectionTitlePrivacy"></h3>
+ <div>
+ <div class="settings-row">
+ <button id="privacyContentSettingsButton"
+ i18n-content="privacyContentSettingsButton"></button>
+ <button id="privacyClearDataButton"
+ i18n-content="privacyClearDataButton"></button>
+ </div>
+ <p id="privacy-explanation" class="settings-row">
+ <span i18n-content="improveBrowsingExperience"></span>
+ <span i18n-content="disableWebServices"></span>
+ <a target="_blank" i18n-content="learnMore"
+ i18n-values="href:privacyLearnMoreURL"></a>
+ </p>
+ <div class="checkbox">
+ <span class="controlled-setting-with-label">
+ <input id="alternateErrorPagesEnabled"
+ pref="alternate_error_pages.enabled"
+ metric="Options_LinkDoctorCheckbox" type="checkbox">
+ <span>
+ <label for="alternateErrorPagesEnabled"
+ i18n-content="linkDoctorPref">
+ </label>
+ <span class="controlled-setting-indicator"
+ pref="alternate_error_pages.enabled">
+ </span>
+ </span>
+ </span>
+ </div>
+ <div class="checkbox" guest-visibility="disabled">
+ <span class="controlled-setting-with-label">
+ <input id="searchSuggestEnabled" pref="search.suggest_enabled"
+ metric="Options_UseSuggestCheckbox" type="checkbox">
+ <span>
+ <label for="searchSuggestEnabled" i18n-content="suggestPref">
+ </label>
+ <span class="controlled-setting-indicator"
+ pref="search.suggest_enabled">
+ </span>
+ </span>
+ </span>
+ </div>
+ <div class="checkbox" guest-visibility="disabled">
+ <span class="controlled-setting-with-label">
+ <input id="dnsPrefetchingEnabled" pref="dns_prefetching.enabled"
+ metric="Options_DnsPrefetchCheckbox" type="checkbox">
+ <span>
+ <label for="dnsPrefetchingEnabled"
+ i18n-content="networkPredictionEnabledDescription">
+ </label>
+ <span class="controlled-setting-indicator"
+ pref="dns_prefetching.enabled">
+ </span>
+ </span>
+ </span>
+ </div>
+ <div class="checkbox">
+ <span class="controlled-setting-with-label">
+ <input id="safeBrowsingEnabled" pref="safebrowsing.enabled"
+ metric="Options_SafeBrowsingCheckbox" type="checkbox">
+ <span>
+ <label for="safeBrowsingEnabled"
+ i18n-content="safeBrowsingEnableProtection">
+ </label>
+ <span class="controlled-setting-indicator"
+ pref="safebrowsing.enabled">
+ </span>
+ </span>
+ </span>
+ </div>
+<if expr="pp_ifdef('_google_chrome')">
+ <div class="checkbox">
+ <span class="controlled-setting-with-label">
+ <input id="spelling-enabled-control" type="checkbox"
+ pref="spellcheck.use_spelling_service" dialog-pref>
+ <span>
+ <label for="spelling-enabled-control" i18n-content="spellingPref">
+ </label>
+ <span id="spelling-enabled-indicator"
+ class="controlled-setting-indicator"
+ pref="spellcheck.use_spelling_service" dialog-pref>
+ </span>
+ </span>
+ </span>
+ </div>
+ <div id="metricsReportingSetting" class="checkbox">
+ <span class="controlled-setting-with-label">
+<if expr="pp_ifdef('chromeos')">
+ <input id="metricsReportingEnabled"
+ pref="cros.metrics.reportingEnabled" type="checkbox">
+ <span>
+ <label for="metricsReportingEnabled" i18n-content="enableLogging">
+ </label>
+ <span class="controlled-setting-indicator"
+ pref="cros.metrics.reportingEnabled">
+ </span>
+ </span>
+</if>
+<if expr="not pp_ifdef('chromeos')">
+ <input id="metricsReportingEnabled"
+ pref="user_experience_metrics.reporting_enabled" type="checkbox">
+ <span>
+ <label for="metricsReportingEnabled" i18n-content="enableLogging">
+ </label>
+ <span class="controlled-setting-indicator"
+ pref="user_experience_metrics.reporting_enabled">
+ </span>
+ </span>
+</if>
+ </span>
+ </div>
+</if> <!-- pp_ifdef('_google_chrome') -->
+ <div class="checkbox">
+ <label>
+ <input id="do-not-track-enabled" pref="enable_do_not_track"
+ metric="Options_DoNotTrackCheckbox" type="checkbox" dialog-pref>
+ <span i18n-content="doNotTrack"></span>
+ </label>
+ </div>
+<if expr="pp_ifdef('chromeos')">
+ <div class="checkbox">
+ <span class="controlled-setting-with-label">
+ <input id="content-protection-attestation-enabled" type="checkbox"
+ pref="cros.device.attestation_for_content_protection_enabled">
+ <span>
+ <label for="content-protection-attestation-enabled"
+ i18n-content="enableContentProtectionAttestation">
+ </label>
+ <a target="_blank" i18n-content="learnMore"
+ i18n-values="href:contentProtectionAttestationLearnMoreURL">
+ </a>
+ <span class="controlled-setting-indicator"
+ pref="cros.device.attestation_for_content_protection_enabled">
+ </span>
+ </span>
+ </span>
+ </div>
+</if>
+ </div>
+ </section>
+<if expr="pp_ifdef('chromeos')">
+ <!-- By default, the bluetooth section is hidden. It is only visible if a
+ bluetooth adapter is discovered -->
+ <section id="bluetooth-devices" hidden>
+ <h3 i18n-content="bluetooth"></h3>
+ <div id="bluetooth-options-div">
+ <div class="checkbox">
+ <label>
+ <input type="checkbox" id="enable-bluetooth">
+ <span i18n-content="enableBluetooth">
+ </label>
+ </div>
+ <div class="settings-list bluetooth-device-list" hidden>
+ <list id="bluetooth-paired-devices-list"></list>
+ <div id="bluetooth-paired-devices-list-empty-placeholder"
+ class="bluetooth-empty-list-label" hidden>
+ <span i18n-content="bluetoothNoDevices"></span>
+ </div>
+ </div>
+ <div id="bluetooth-button-group">
+ <button id="bluetooth-add-device"
+ i18n-content="addBluetoothDevice" hidden></button>
+ <button id="bluetooth-reconnect-device"
+ i18n-content="bluetoothConnectDevice" disabled hidden></button>
+ </div>
+ </div>
+ </section>
+</if> <!-- pp_ifdef('chromeos') -->
+ <section id="passwords-and-autofill-section">
+ <h3 i18n-content="passwordsAndAutofillGroupName"></h3>
+ <div class="checkbox">
+ <span class="controlled-setting-with-label">
+ <input id="autofill-enabled" pref="autofill.enabled"
+ metric="Options_FormAutofill" type="checkbox">
+ <span>
+ <label for="autofill-enabled" i18n-content="autofillEnabled"></label>
+ <span class="controlled-setting-indicator" pref="autofill.enabled">
+ </span>
+ <button id="autofill-settings" class="link-button"
+ i18n-content="manageAutofillSettings">
+ </button>
+ </span>
+ </span>
+ </div>
+ <div class="checkbox">
+ <span class="controlled-setting-with-label">
+ <input id="password-manager-enabled"
+ pref="profile.password_manager_enabled"
+ metric="Options_PasswordManager" type="checkbox">
+ <span>
+ <label for="password-manager-enabled"
+ i18n-content="passwordManagerEnabled">
+ </label>
+ <span class="controlled-setting-indicator"
+ pref="profile.password_manager_enabled">
+ </span>
+ <button id="manage-passwords" class="link-button"
+ i18n-content="managePasswords">
+ </button>
+ </span>
+ </span>
+ </div>
+ <div class="checkbox" id="password-generation-checkbox">
+ <label>
+ <input id="password-generation-enabled" pref="password_generation.enabled"
+ metric="Options_PasswordGenerationCheckbox" type="checkbox">
+ <span i18n-content="passwordGenerationEnabledDescription"></span>
+ </label>
+ </div>
+<if expr="is_macosx">
+ <div id="mac-passwords-warning" i18n-content="macPasswordsWarning" hidden>
+ </div>
+</if>
+ </section>
+ <section id="web-content-section">
+ <h3 i18n-content="advancedSectionTitleContent"></h3>
+ <div>
+ <div class="settings-row">
+ <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>
+ <span id="font-size-indicator"
+ class="controlled-setting-indicator"></span>
+ <button id="fontSettingsCustomizeFontsButton"
+ i18n-content="fontSettingsCustomizeFontsButton"></button>
+ </div>
+ <div class="settings-row">
+ <label class="web-content-select-label">
+ <span i18n-content="defaultZoomFactorLabel"></span>
+ <select id="defaultZoomFactor" dataType="double"></select>
+ </label>
+ </div>
+<if expr="is_macosx">
+ <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 id="network-section">
+ <h3 i18n-content="advancedSectionTitleNetwork"></h3>
+ <div>
+ <span id="proxiesLabel" class="settings-row"></span>
+ <div class="settings-row">
+ <button id="proxiesConfigureButton"
+ i18n-content="proxiesConfigureButton"></button>
+ <span class="controlled-setting-indicator" pref="proxy" plural></span>
+ </div>
+ </div>
+ </section>
+</if>
+ <section id="languages-section">
+ <h3 i18n-content="advancedSectionTitleLanguages"></h3>
+ <span class="settings-row" i18n-content="languageSectionLabel"></span>
+ <div class="settings-row">
+ <button id="language-button"
+ i18n-content="languageAndSpellCheckSettingsButton"></button>
+ </div>
+ <div class="checkbox">
+ <span class="controlled-setting-with-label">
+ <input id="enableTranslate" pref="translate.enabled"
+ metric="Options_Translate" type="checkbox">
+ <span>
+ <label for="enableTranslate" i18n-content="translateEnableTranslate">
+ </label>
+ <span class="controlled-setting-indicator" pref="translate.enabled">
+ </span>
+ <button id="manage-languages" class="link-button"
+ i18n-content="manageLanguages"></button>
+ </span>
+ </span>
+ </div>
+ </section>
+ <section id="downloads-section">
+ <h3 i18n-content="downloadLocationGroupName"></h3>
+ <div>
+ <div class="settings-row">
+ <label>
+ <span id="download-location-label"
+ i18n-content="downloadLocationBrowseTitle">
+ </span>
+ <input id="downloadLocationPath" class="weakrtl" type="text"
+ size="36" readonly>
+ </label>
+ <button id="downloadLocationChangeButton"
+ i18n-content="downloadLocationChangeButton">
+ </button>
+ <span class="controlled-setting-indicator"
+ pref="download.default_directory">
+ </span>
+ </div>
+ <div class="checkbox">
+ <span class="controlled-setting-with-label">
+ <input id="prompt-for-download" type="checkbox"
+ pref="download.prompt_for_download"
+ metric="Options_AskForSaveLocation">
+ <span>
+ <label for="prompt-for-download"
+ i18n-content="downloadLocationAskForSaveLocation">
+ </label>
+ <span class="controlled-setting-indicator"
+ pref="download.prompt_for_download">
+ </span>
+ </span>
+ </span>
+ </div>
+<if expr="pp_ifdef('chromeos')">
+ <div class="checkbox" id="disable-drive-row" guest-visibility="disabled">
+ <span class="controlled-setting-with-label">
+ <input id="drive-disabled" type="checkbox"
+ pref="gdata.disabled"
+ metric="Options_DisableGData">
+ <span>
+ <label for="drive-disabled" i18n-content="disableGData"></label>
+ <span class="controlled-setting-indicator" pref="gdata.disabled">
+ </span>
+ </span>
+ </span>
+ </div>
+</if>
+<if expr="not pp_ifdef('chromeos')">
+ <div id="auto-open-file-types-section" hidden>
+ <div id="auto-open-file-types-container">
+ <div id="auto-open-file-types-label"
+ i18n-content="autoOpenFileTypesInfo"></div>
+ <button id="autoOpenFileTypesResetToDefault"
+ i18n-content="autoOpenFileTypesResetToDefault"></button>
+ </div>
+ </div>
+</if>
+ </div>
+ </section>
+ <section>
+ <h3 i18n-content="advancedSectionTitleSecurity"></h3>
+ <div>
+<if expr="pp_ifdef('use_nss') or is_win or is_macosx">
+ <div class="settings-row">
+ <button id="certificatesManageButton"
+ i18n-content="certificatesManageButton"></button>
+ </div>
+</if>
+ <div class="checkbox">
+ <label>
+ <input id="sslCheckRevocation" pref="ssl.rev_checking.enabled"
+ type="checkbox">
+ <span i18n-content="sslCheckRevocation"></span>
+ </label>
+ </div>
+ </div>
+ </section>
+ <section id="cloud-print-connector-section">
+ <h3 i18n-content="advancedSectionTitleCloudPrint"></h3>
+<if expr="pp_ifdef('enable_mdns')">
+ <div id="cloudprint-options-mdns" hidden>
+ <div class="settings-row">
+ <span i18n-content="cloudPrintOptionLabel"></span>
+ <a target="_blank" i18n-content="learnMore"
+ i18n-values="href:cloudPrintLearnMoreURL"></a>
+ </div>
+ <button id="cloudPrintDevicesPageButton"
+ i18n-content="cloudPrintDevicesPageButton">
+ </button>
+
+ <div class="checkbox"
+ i18n-values=".hidden: cloudPrintHideNotificationsCheckbox">
+ <span class="controlled-setting-with-label">
+ <input id="local-discovery-notifications-enabled"
+ pref="local_discovery.notifications_enabled"
+ type="checkbox"
+ metric="LocalDiscoveryNotificationsDisabled_Settings" />
+ <span>
+ <label for="local-discovery-notifications-enabled"
+ i18n-content="cloudPrintEnableNotificationsLabel">
+ </label>
+ <span class="controlled-setting-indicator"
+ pref="local_discovery.notifications_enabled">
+ </span>
+ </span>
+ </div>
+ </div>
+</if>
+
+ <div id="cloudprint-options-nomdns">
+<if expr="pp_ifdef('chromeos')">
+ <div>
+ <span i18n-content="cloudPrintOptionLabel"></span>
+ <a target="_blank" i18n-content="learnMore"
+ i18n-values="href:cloudPrintLearnMoreURL"></a>
+ </div>
+</if>
+
+<if expr="not pp_ifdef('chromeos')">
+ <p id="cloudPrintConnectorLabel" class="settings-row"
+ i18n-content="cloudPrintConnectorDisabledLabel"></p>
+</if>
+
+ <div class="settings-row">
+<if expr="not pp_ifdef('chromeos')">
+ <button id="cloudPrintConnectorSetupButton"
+ i18n-content="cloudPrintConnectorDisabledButton"></button>
+</if>
+
+ <button id="cloudPrintManageButton"
+ i18n-content="cloudPrintManageButton">
+ </button>
+ </div>
+ </div>
+ </section>
+
+<if expr="pp_ifdef('chromeos')">
+ <include src="startup_section.html">
+ <section>
+ <h3 i18n-content="accessibilityTitle"></h3>
+ <div class="option-control-table">
+ <p id="accessibility-explanation" class="settings-row">
+ <span i18n-content="accessibilityExplanation"></span>
+ <a target="_blank" i18n-content="learnMore"
+ i18n-values="href:accessibilityLearnMoreURL"></a>
+ </p>
+ <div class="option-name">
+ <div class="checkbox">
+ <span class="controlled-setting-with-label">
+ <input id="accessibility-should-always-show-menu"
+ pref="settings.a11y.enable_menu" type="checkbox">
+ <span>
+ <label for="accessibility-should-always-show-menu"
+ i18n-content="accessibilityAlwaysShowMenu">
+ </label>
+ <span class="controlled-setting-indicator"
+ pref="settings.a11y.enable_menu">
+ </span>
+ </span>
+ </div>
+ </div>
+ <div class="option-name">
+ <div class="checkbox">
+ <span class="controlled-setting-with-label">
+ <input id="accessibility-large-cursor-check"
+ pref="settings.a11y.large_cursor_enabled" type="checkbox">
+ <span>
+ <label for="accessibility-large-cursor-check"
+ i18n-content="accessibilityLargeCursor">
+ </label>
+ <span class="controlled-setting-indicator"
+ pref="settings.a11y.large_cursor_enabled">
+ </span>
+ </span>
+ </span>
+ </div>
+ </div>
+ <div class="option-name">
+ <div class="checkbox">
+ <span class="controlled-setting-with-label">
+ <input id="accessibility-high-contrast-check"
+ pref="settings.a11y.high_contrast_enabled" type="checkbox">
+ <span>
+ <label for="accessibility-high-contrast-check"
+ i18n-content="accessibilityHighContrast">
+ </label>
+ <span class="controlled-setting-indicator"
+ pref="settings.a11y.high_contrast_enabled">
+ </span>
+ </span>
+ </span>
+ </div>
+ </div>
+ <div id="accessibility-sticky-keys" class="option-name" hidden>
+ <div class="checkbox">
+ <span class="controlled-setting-with-label">
+ <input id="accessibility-sticky-keys-check"
+ pref="settings.a11y.sticky_keys_enabled" type="checkbox">
+ <span>
+ <label for="accessibility-sticky-keys-check"
+ i18n-content="accessibilityStickyKeys">
+ </label>
+ <span class="controlled-setting-indicator"
+ pref="settings.a11y.sticky_keys_enabled">
+ </span>
+ </span>
+ </span>
+ </div>
+ </div>
+ <div class="option-name">
+ <div class="checkbox">
+ <span class="controlled-setting-with-label">
+ <input id="accessibility-spoken-feedback-check"
+ pref="settings.accessibility" type="checkbox">
+ <span>
+ <label for="accessibility-spoken-feedback-check"
+ i18n-content="accessibilitySpokenFeedback">
+ </label>
+ <span class="controlled-setting-indicator"
+ pref="settings.accessibility">
+ </span>
+ </span>
+ </span>
+ <div id="accessibility-settings" hidden>
+ <button id="accessibility-settings-button"
+ i18n-content="accessibilitySettings"></button>
+ </div>
+ </div>
+ </div>
+ <div class="option-name">
+ <div class="checkbox">
+ <span class="controlled-setting-with-label">
+ <input id="accessibility-screen-magnifier-check"
+ pref="settings.a11y.screen_magnifier" type="checkbox">
+ <span>
+ <label for="accessibility-screen-magnifier-check"
+ i18n-content="accessibilityScreenMagnifier">
+ </label>
+ <span class="controlled-setting-indicator"
+ pref="settings.a11y.screen_magnifier">
+ </span>
+ </span>
+ </span>
+ </div>
+ </div>
+ <div class="option-name" id="accessibility-tap-dragging">
+ <div class="checkbox">
+ <label>
+ <input id="accessibility-tap-dragging-check"
+ pref="settings.touchpad.enable_tap_dragging" type="checkbox">
+ <span i18n-content="accessibilityTapDragging"></span>
+ </label>
+ </div>
+ </div>
+ <div class="option-name" id="accessibility-autoclick">
+ <div class="checkbox">
+ <span class="controlled-setting-with-label">
+ <input id="accessibility-autoclick-check"
+ pref="settings.a11y.autoclick" type="checkbox">
+ <span>
+ <div>
+ <div>
+ <label for="accessibility-autoclick-check"
+ i18n-content="accessibilityAutoclick">
+ </label>
+ <span class="controlled-setting-indicator"
+ pref="settings.a11y.autoclick">
+ </span>
+ </div>
+ <div>
+ <label for="accessibility-autoclick-dropdown"
+ i18n-content="accessibilityAutoclickDropdown">
+ </label>
+ <select id="accessibility-autoclick-dropdown" class="control"
+ data-type="number"
+ pref="settings.a11y.autoclick_delay_ms">
+ <option value="200"
+ i18n-content="autoclickDelayExtremelyShort"></option>
+ <option value="400"
+ i18n-content="autoclickDelayVeryShort"></option>
+ <option value="600" i18n-content="autoclickDelayShort"></option>
+ <option value="800" i18n-content="autoclickDelayLong"></option>
+ <option value="1000"
+ i18n-content="autoclickDelayVeryLong"></option>
+ </select>
+ <span class="controlled-setting-indicator"
+ pref="settings.a11y.autoclick_delay_ms">
+ </span>
+ </div>
+ </div>
+ </span>
+ </span>
+ </div>
+ </div>
+ </section>
+<if expr="pp_ifdef('chromeos')">
+ <section id="factory-reset-section" hidden>
+ <h3 i18n-content="factoryResetTitle"></h3>
+ <div>
+ <span class="settings-row" i18n-content="factoryResetDescription">
+ </span>
+ <button id="factory-reset-restart"
+ i18n-content="factoryResetRestart">
+ </button>
+ </div>
+ </section>
+</if>
+</if>
+<if expr="not pp_ifdef('chromeos')">
+ <section id="system-section">
+ <h3 i18n-content="advancedSectionTitleSystem"></h3>
+<if expr="not is_macosx">
+ <div class="checkbox">
+ <span class="controlled-setting-with-label">
+ <input id="backgroundModeCheckbox" pref="background_mode.enabled"
+ type="checkbox">
+ <span>
+ <label for="backgroundModeCheckbox"
+ i18n-content="backgroundModeCheckbox">
+ </label>
+ <span class="controlled-setting-indicator"
+ pref="background_mode.enabled">
+ </span>
+ </span>
+ </span>
+ </div>
+</if>
+ <div class="checkbox">
+ <span class="controlled-setting-with-label">
+ <input id="gpu-mode-checkbox"
+ pref="hardware_acceleration_mode.enabled" type="checkbox">
+ <span>
+ <label for="gpu-mode-checkbox"
+ i18n-content="gpuModeCheckbox">
+ </label>
+ <span id="gpu-mode-reset-restart"
+ i18n-values=".innerHTML:gpuModeResetRestart">
+ </span>
+ </span>
+ </span>
+ </div>
+ </section>
+</if>
+ <section id="reset-profile-settings-section" hidden>
+ <h3 i18n-content="resetProfileSettingsSectionTitle"></h3>
+ <div>
+ <span class="settings-row" i18n-content="resetProfileSettingsDescription">
+ </span>
+ <button id="reset-profile-settings" i18n-content="resetProfileSettings">
+ </button>
+ </div>
+ </section>
+ </div> <!-- advanced-settings-container -->
+ </div> <!-- advanced-settings -->
+ <footer>
+ <button id="advanced-settings-expander" class="link-button"
+ i18n-content="showAdvancedSettings">
+ </button>
+ </footer>
+ </div>
diff --git a/chromium/chrome/browser/resources/options/browser_options.js b/chromium/chrome/browser/resources/options/browser_options.js
new file mode 100644
index 00000000000..60cacd18495
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/browser_options.js
@@ -0,0 +1,1621 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+ var OptionsPage = options.OptionsPage;
+ var ArrayDataModel = cr.ui.ArrayDataModel;
+ var RepeatingButton = cr.ui.RepeatingButton;
+
+ //
+ // BrowserOptions class
+ // Encapsulated handling of browser options page.
+ //
+ function BrowserOptions() {
+ OptionsPage.call(this, 'settings', loadTimeData.getString('settingsTitle'),
+ 'settings');
+ }
+
+ cr.addSingletonGetter(BrowserOptions);
+
+ BrowserOptions.prototype = {
+ __proto__: options.OptionsPage.prototype,
+
+ /**
+ * Keeps track of whether the user is signed in or not.
+ * @type {boolean}
+ * @private
+ */
+ signedIn_: false,
+
+ /**
+ * Keeps track of whether |onShowHomeButtonChanged_| has been called. See
+ * |onShowHomeButtonChanged_|.
+ * @type {boolean}
+ * @private
+ */
+ onShowHomeButtonChangedCalled_: false,
+
+ /**
+ * Track if page initialization is complete. All C++ UI handlers have the
+ * chance to manipulate page content within their InitializePage methods.
+ * This flag is set to true after all initializers have been called.
+ * @type {boolean}
+ * @private
+ */
+ initializationComplete_: false,
+
+ /** @override */
+ initializePage: function() {
+ OptionsPage.prototype.initializePage.call(this);
+ var self = this;
+
+ // Ensure that navigation events are unblocked on uber page. A reload of
+ // the settings page while an overlay is open would otherwise leave uber
+ // page in a blocked state, where tab switching is not possible.
+ uber.invokeMethodOnParent('stopInterceptingEvents');
+
+ window.addEventListener('message', this.handleWindowMessage_.bind(this));
+
+ $('advanced-settings-expander').onclick = function() {
+ self.toggleSectionWithAnimation_(
+ $('advanced-settings'),
+ $('advanced-settings-container'));
+
+ // If the link was focused (i.e., it was activated using the keyboard)
+ // and it was used to show the section (rather than hiding it), focus
+ // the first element in the container.
+ if (document.activeElement === $('advanced-settings-expander') &&
+ $('advanced-settings').style.height === '') {
+ var focusElement = $('advanced-settings-container').querySelector(
+ 'button, input, list, select, a[href]');
+ if (focusElement)
+ focusElement.focus();
+ }
+ }
+
+ $('advanced-settings').addEventListener('webkitTransitionEnd',
+ this.updateAdvancedSettingsExpander_.bind(this));
+
+ if (cr.isChromeOS)
+ UIAccountTweaks.applyGuestModeVisibility(document);
+
+ // Sync (Sign in) section.
+ this.updateSyncState_(loadTimeData.getValue('syncData'));
+
+ $('start-stop-sync').onclick = function(event) {
+ if (self.signedIn_)
+ SyncSetupOverlay.showStopSyncingUI();
+ else if (cr.isChromeOS)
+ SyncSetupOverlay.showSetupUI();
+ else
+ SyncSetupOverlay.startSignIn();
+ };
+ $('customize-sync').onclick = function(event) {
+ SyncSetupOverlay.showSetupUI();
+ };
+
+ // Internet connection section (ChromeOS only).
+ if (cr.isChromeOS) {
+ options.network.NetworkList.decorate($('network-list'));
+ options.network.NetworkList.refreshNetworkData(
+ loadTimeData.getValue('networkData'));
+ }
+
+ // On Startup section.
+ Preferences.getInstance().addEventListener('session.restore_on_startup',
+ this.onRestoreOnStartupChanged_.bind(this));
+ Preferences.getInstance().addEventListener(
+ 'session.startup_urls',
+ function(event) {
+ $('startup-set-pages').disabled = event.value.disabled;
+ });
+
+ $('startup-set-pages').onclick = function() {
+ OptionsPage.navigateToPage('startup');
+ };
+
+ // Appearance section.
+ Preferences.getInstance().addEventListener('browser.show_home_button',
+ this.onShowHomeButtonChanged_.bind(this));
+
+ Preferences.getInstance().addEventListener('homepage',
+ this.onHomePageChanged_.bind(this));
+ Preferences.getInstance().addEventListener('homepage_is_newtabpage',
+ this.onHomePageIsNtpChanged_.bind(this));
+
+ $('change-home-page').onclick = function(event) {
+ OptionsPage.navigateToPage('homePageOverlay');
+ };
+
+ if ($('set-wallpaper')) {
+ $('set-wallpaper').onclick = function(event) {
+ chrome.send('openWallpaperManager');
+ };
+ }
+
+ $('themes-gallery').onclick = function(event) {
+ window.open(loadTimeData.getString('themesGalleryURL'));
+ };
+ $('themes-reset').onclick = function(event) {
+ chrome.send('themesReset');
+ };
+
+ if (loadTimeData.getBoolean('profileIsManaged')) {
+ if ($('themes-native-button')) {
+ $('themes-native-button').disabled = true;
+ $('themes-native-button').hidden = true;
+ }
+ // Supervised users have just one default theme, even on Linux. So use
+ // the same button for Linux as for the other platforms.
+ $('themes-reset').textContent = loadTimeData.getString('themesReset');
+ }
+
+ // Device section (ChromeOS only).
+ if (cr.isChromeOS) {
+ $('keyboard-settings-button').onclick = function(evt) {
+ OptionsPage.navigateToPage('keyboard-overlay');
+ };
+ $('pointer-settings-button').onclick = function(evt) {
+ OptionsPage.navigateToPage('pointer-overlay');
+ };
+ }
+
+ // Search section.
+ $('manage-default-search-engines').onclick = function(event) {
+ OptionsPage.navigateToPage('searchEngines');
+ chrome.send('coreOptionsUserMetricsAction',
+ ['Options_ManageSearchEngines']);
+ };
+ $('default-search-engine').addEventListener('change',
+ this.setDefaultSearchEngine_);
+ // Without this, the bubble would overlap the uber frame navigation pane
+ // and would not get mouse event as explained in crbug.com/311421.
+ document.querySelector(
+ '#default-search-engine + .controlled-setting-indicator').location =
+ cr.ui.ArrowLocation.TOP_START;
+
+ // Users section.
+ if (loadTimeData.valueExists('profilesInfo')) {
+ $('profiles-section').hidden = false;
+
+ var profilesList = $('profiles-list');
+ options.browser_options.ProfileList.decorate(profilesList);
+ profilesList.autoExpands = true;
+
+ // The profiles info data in |loadTimeData| might be stale.
+ this.setProfilesInfo_(loadTimeData.getValue('profilesInfo'));
+ chrome.send('requestProfilesInfo');
+
+ profilesList.addEventListener('change',
+ this.setProfileViewButtonsStatus_);
+ $('profiles-create').onclick = function(event) {
+ ManageProfileOverlay.showCreateDialog();
+ };
+ if (OptionsPage.isSettingsApp()) {
+ $('profiles-app-list-switch').onclick = function(event) {
+ var selectedProfile = self.getSelectedProfileItem_();
+ chrome.send('switchAppListProfile', [selectedProfile.filePath]);
+ };
+ }
+ $('profiles-manage').onclick = function(event) {
+ ManageProfileOverlay.showManageDialog();
+ };
+ $('profiles-delete').onclick = function(event) {
+ var selectedProfile = self.getSelectedProfileItem_();
+ if (selectedProfile)
+ ManageProfileOverlay.showDeleteDialog(selectedProfile);
+ };
+ if (loadTimeData.getBoolean('profileIsManaged')) {
+ $('profiles-create').disabled = true;
+ $('profiles-delete').disabled = true;
+ $('profiles-list').canDeleteItems = false;
+ }
+ }
+
+ if (cr.isChromeOS) {
+ // Username (canonical email) of the currently logged in user or
+ // |kGuestUser| if a guest session is active.
+ this.username_ = loadTimeData.getString('username');
+
+ this.updateAccountPicture_();
+
+ $('account-picture').onclick = this.showImagerPickerOverlay_;
+ $('change-picture-caption').onclick = this.showImagerPickerOverlay_;
+
+ $('manage-accounts-button').onclick = function(event) {
+ OptionsPage.navigateToPage('accounts');
+ chrome.send('coreOptionsUserMetricsAction',
+ ['Options_ManageAccounts']);
+ };
+ } else {
+ $('import-data').onclick = function(event) {
+ ImportDataOverlay.show();
+ chrome.send('coreOptionsUserMetricsAction', ['Import_ShowDlg']);
+ };
+
+ if ($('themes-native-button')) {
+ $('themes-native-button').onclick = function(event) {
+ chrome.send('themesSetNative');
+ };
+ }
+ }
+
+ // Default browser section.
+ if (!cr.isChromeOS) {
+ $('set-as-default-browser').onclick = function(event) {
+ chrome.send('becomeDefaultBrowser');
+ };
+
+ $('auto-launch').onclick = this.handleAutoLaunchChanged_;
+ }
+
+ // Privacy section.
+ $('privacyContentSettingsButton').onclick = function(event) {
+ OptionsPage.navigateToPage('content');
+ OptionsPage.showTab($('cookies-nav-tab'));
+ chrome.send('coreOptionsUserMetricsAction',
+ ['Options_ContentSettings']);
+ };
+ $('privacyClearDataButton').onclick = function(event) {
+ OptionsPage.navigateToPage('clearBrowserData');
+ chrome.send('coreOptionsUserMetricsAction', ['Options_ClearData']);
+ };
+ $('privacyClearDataButton').hidden = OptionsPage.isSettingsApp();
+ // 'metricsReportingEnabled' element is only present on Chrome branded
+ // builds, and the 'metricsReportingCheckboxAction' message is only
+ // handled on ChromeOS.
+ if ($('metricsReportingEnabled') && cr.isChromeOS) {
+ $('metricsReportingEnabled').onclick = function(event) {
+ chrome.send('metricsReportingCheckboxAction',
+ [String(event.currentTarget.checked)]);
+ };
+ }
+
+ // Bluetooth (CrOS only).
+ if (cr.isChromeOS) {
+ options.system.bluetooth.BluetoothDeviceList.decorate(
+ $('bluetooth-paired-devices-list'));
+
+ $('bluetooth-add-device').onclick =
+ this.handleAddBluetoothDevice_.bind(this);
+
+ $('enable-bluetooth').onchange = function(event) {
+ var state = $('enable-bluetooth').checked;
+ chrome.send('bluetoothEnableChange', [Boolean(state)]);
+ };
+
+ $('bluetooth-reconnect-device').onclick = function(event) {
+ var device = $('bluetooth-paired-devices-list').selectedItem;
+ var address = device.address;
+ chrome.send('updateBluetoothDevice', [address, 'connect']);
+ OptionsPage.closeOverlay();
+ };
+
+ $('bluetooth-paired-devices-list').addEventListener('change',
+ function() {
+ var item = $('bluetooth-paired-devices-list').selectedItem;
+ var disabled = !item || item.connected || !item.connectable;
+ $('bluetooth-reconnect-device').disabled = disabled;
+ });
+ }
+
+ // Passwords and Forms section.
+ $('autofill-settings').onclick = function(event) {
+ OptionsPage.navigateToPage('autofill');
+ chrome.send('coreOptionsUserMetricsAction',
+ ['Options_ShowAutofillSettings']);
+ };
+ $('manage-passwords').onclick = function(event) {
+ OptionsPage.navigateToPage('passwords');
+ OptionsPage.showTab($('passwords-nav-tab'));
+ chrome.send('coreOptionsUserMetricsAction',
+ ['Options_ShowPasswordManager']);
+ };
+ if (cr.isChromeOS && UIAccountTweaks.loggedInAsGuest()) {
+ // Disable and turn off Autofill in guest mode.
+ var autofillEnabled = $('autofill-enabled');
+ autofillEnabled.disabled = true;
+ autofillEnabled.checked = false;
+ cr.dispatchSimpleEvent(autofillEnabled, 'change');
+ $('autofill-settings').disabled = true;
+
+ // Disable and turn off Password Manager in guest mode.
+ var passwordManagerEnabled = $('password-manager-enabled');
+ passwordManagerEnabled.disabled = true;
+ passwordManagerEnabled.checked = false;
+ cr.dispatchSimpleEvent(passwordManagerEnabled, 'change');
+ $('manage-passwords').disabled = true;
+ }
+
+ if (cr.isMac) {
+ $('mac-passwords-warning').hidden =
+ !loadTimeData.getBoolean('multiple_profiles');
+ }
+
+ // Network section.
+ if (!cr.isChromeOS) {
+ $('proxiesConfigureButton').onclick = function(event) {
+ chrome.send('showNetworkProxySettings');
+ };
+ }
+
+ // Web Content section.
+ $('fontSettingsCustomizeFontsButton').onclick = function(event) {
+ OptionsPage.navigateToPage('fonts');
+ chrome.send('coreOptionsUserMetricsAction', ['Options_FontSettings']);
+ };
+ $('defaultFontSize').onchange = function(event) {
+ var value = event.target.options[event.target.selectedIndex].value;
+ Preferences.setIntegerPref(
+ 'webkit.webprefs.default_fixed_font_size',
+ value - OptionsPage.SIZE_DIFFERENCE_FIXED_STANDARD, true);
+ chrome.send('defaultFontSizeAction', [String(value)]);
+ };
+ $('defaultZoomFactor').onchange = function(event) {
+ chrome.send('defaultZoomFactorAction',
+ [String(event.target.options[event.target.selectedIndex].value)]);
+ };
+
+ // Languages section.
+ var showLanguageOptions = function(event) {
+ OptionsPage.navigateToPage('languages');
+ chrome.send('coreOptionsUserMetricsAction',
+ ['Options_LanuageAndSpellCheckSettings']);
+ };
+ $('language-button').onclick = showLanguageOptions;
+ $('manage-languages').onclick = showLanguageOptions;
+
+ // Downloads section.
+ Preferences.getInstance().addEventListener('download.default_directory',
+ this.onDefaultDownloadDirectoryChanged_.bind(this));
+ $('downloadLocationChangeButton').onclick = function(event) {
+ chrome.send('selectDownloadLocation');
+ };
+ if (!cr.isChromeOS) {
+ $('autoOpenFileTypesResetToDefault').onclick = function(event) {
+ chrome.send('autoOpenFileTypesAction');
+ };
+ } else {
+ $('disable-drive-row').hidden =
+ UIAccountTweaks.loggedInAsLocallyManagedUser();
+ }
+
+ // HTTPS/SSL section.
+ if (cr.isWindows || cr.isMac) {
+ $('certificatesManageButton').onclick = function(event) {
+ chrome.send('showManageSSLCertificates');
+ };
+ } else {
+ $('certificatesManageButton').onclick = function(event) {
+ OptionsPage.navigateToPage('certificates');
+ chrome.send('coreOptionsUserMetricsAction',
+ ['Options_ManageSSLCertificates']);
+ };
+ }
+
+ // Cloud Print section.
+ // 'cloudPrintProxyEnabled' is true for Chrome branded builds on
+ // certain platforms, or could be enabled by a lab.
+ if (!cr.isChromeOS) {
+ $('cloudPrintConnectorSetupButton').onclick = function(event) {
+ if ($('cloudPrintManageButton').style.display == 'none') {
+ // Disable the button, set its text to the intermediate state.
+ $('cloudPrintConnectorSetupButton').textContent =
+ loadTimeData.getString('cloudPrintConnectorEnablingButton');
+ $('cloudPrintConnectorSetupButton').disabled = true;
+ chrome.send('showCloudPrintSetupDialog');
+ } else {
+ chrome.send('disableCloudPrintConnector');
+ }
+ };
+ }
+ $('cloudPrintManageButton').onclick = function(event) {
+ chrome.send('showCloudPrintManagePage');
+ };
+
+ if (loadTimeData.getBoolean('cloudPrintShowMDnsOptions')) {
+ $('cloudprint-options-mdns').hidden = false;
+ $('cloudprint-options-nomdns').hidden = true;
+ $('cloudPrintDevicesPageButton').onclick = function() {
+ chrome.send('showCloudPrintDevicesPage');
+ };
+ }
+
+ // Accessibility section (CrOS only).
+ if (cr.isChromeOS) {
+ var updateAccessibilitySettingsButton = function() {
+ $('accessibility-settings').hidden =
+ !($('accessibility-spoken-feedback-check').checked);
+ };
+ Preferences.getInstance().addEventListener(
+ 'settings.accessibility',
+ updateAccessibilitySettingsButton);
+ $('accessibility-settings-button').onclick = function(event) {
+ window.open(loadTimeData.getString('accessibilitySettingsURL'));
+ };
+ $('accessibility-spoken-feedback-check').onchange = function(event) {
+ chrome.send('spokenFeedbackChange',
+ [$('accessibility-spoken-feedback-check').checked]);
+ updateAccessibilitySettingsButton();
+ };
+ updateAccessibilitySettingsButton();
+
+ $('accessibility-high-contrast-check').onchange = function(event) {
+ chrome.send('highContrastChange',
+ [$('accessibility-high-contrast-check').checked]);
+ };
+
+ var updateDelayDropdown = function() {
+ $('accessibility-autoclick-dropdown').disabled =
+ !$('accessibility-autoclick-check').checked;
+ };
+ Preferences.getInstance().addEventListener(
+ $('accessibility-autoclick-check').getAttribute('pref'),
+ updateDelayDropdown);
+
+ $('accessibility-sticky-keys').hidden =
+ !loadTimeData.getBoolean('enableStickyKeys');
+ }
+
+ // Display management section (CrOS only).
+ if (cr.isChromeOS) {
+ $('display-options').onclick = function(event) {
+ OptionsPage.navigateToPage('display');
+ chrome.send('coreOptionsUserMetricsAction',
+ ['Options_Display']);
+ };
+ }
+
+ // Factory reset section (CrOS only).
+ if (cr.isChromeOS) {
+ $('factory-reset-restart').onclick = function(event) {
+ OptionsPage.navigateToPage('factoryResetData');
+ };
+ }
+
+ // System section.
+ if (!cr.isChromeOS) {
+ var updateGpuRestartButton = function() {
+ $('gpu-mode-reset-restart').hidden =
+ loadTimeData.getBoolean('gpuEnabledAtStart') ==
+ $('gpu-mode-checkbox').checked;
+ };
+ Preferences.getInstance().addEventListener(
+ $('gpu-mode-checkbox').getAttribute('pref'),
+ updateGpuRestartButton);
+ $('gpu-mode-reset-restart-button').onclick = function(event) {
+ chrome.send('restartBrowser');
+ };
+ updateGpuRestartButton();
+ }
+
+ // Reset profile settings section.
+ $('reset-profile-settings').onclick = function(event) {
+ OptionsPage.navigateToPage('resetProfileSettings');
+ };
+ $('reset-profile-settings-section').hidden =
+ !loadTimeData.getBoolean('enableResetProfileSettings');
+ },
+
+ /** @override */
+ didShowPage: function() {
+ $('search-field').focus();
+ },
+
+ /**
+ * Called after all C++ UI handlers have called InitializePage to notify
+ * that initialization is complete.
+ * @private
+ */
+ notifyInitializationComplete_: function() {
+ this.initializationComplete_ = true;
+ cr.dispatchSimpleEvent(document, 'initializationComplete');
+ },
+
+ /**
+ * Event listener for the 'session.restore_on_startup' pref.
+ * @param {Event} event The preference change event.
+ * @private
+ */
+ onRestoreOnStartupChanged_: function(event) {
+ /** @const */ var showHomePageValue = 0;
+
+ if (event.value.value == showHomePageValue) {
+ // If the user previously selected "Show the homepage", the
+ // preference will already be migrated to "Open a specific page". So
+ // the only way to reach this code is if the 'restore on startup'
+ // preference is managed.
+ assert(event.value.controlledBy);
+
+ // Select "open the following pages" and lock down the list of URLs
+ // to reflect the intention of the policy.
+ $('startup-show-pages').checked = true;
+ StartupOverlay.getInstance().setControlsDisabled(true);
+ } else {
+ // Re-enable the controls in the startup overlay if necessary.
+ StartupOverlay.getInstance().updateControlStates();
+ }
+ },
+
+ /**
+ * Handler for messages sent from the main uber page.
+ * @param {Event} e The 'message' event from the uber page.
+ * @private
+ */
+ handleWindowMessage_: function(e) {
+ if (e.data.method == 'frameSelected')
+ $('search-field').focus();
+ },
+
+ /**
+ * Shows the given section.
+ * @param {HTMLElement} section The section to be shown.
+ * @param {HTMLElement} container The container for the section. Must be
+ * inside of |section|.
+ * @param {boolean} animate Indicate if the expansion should be animated.
+ * @private
+ */
+ showSection_: function(section, container, animate) {
+ if (animate)
+ this.addTransitionEndListener_(section);
+
+ // Unhide
+ section.hidden = false;
+ section.style.height = '0px';
+
+ var expander = function() {
+ // Reveal the section using a WebKit transition if animating.
+ if (animate) {
+ section.classList.add('sliding');
+ section.style.height = container.offsetHeight + 'px';
+ } else {
+ section.style.height = 'auto';
+ }
+ };
+
+ // Delay starting the transition if animating so that hidden change will
+ // be processed.
+ if (animate)
+ setTimeout(expander, 0);
+ else
+ expander();
+ },
+
+ /**
+ * Shows the given section, with animation.
+ * @param {HTMLElement} section The section to be shown.
+ * @param {HTMLElement} container The container for the section. Must be
+ * inside of |section|.
+ * @private
+ */
+ showSectionWithAnimation_: function(section, container) {
+ this.showSection_(section, container, /*animate */ true);
+ },
+
+ /**
+ * See showSectionWithAnimation_.
+ */
+ hideSectionWithAnimation_: function(section, container) {
+ this.addTransitionEndListener_(section);
+
+ // Before we start hiding the section, we need to set
+ // the height to a pixel value.
+ section.style.height = container.offsetHeight + 'px';
+
+ // Delay starting the transition so that the height change will be
+ // processed.
+ setTimeout(function() {
+ // Hide the section using a WebKit transition.
+ section.classList.add('sliding');
+ section.style.height = '0px';
+ }, 0);
+ },
+
+ /**
+ * See showSectionWithAnimation_.
+ */
+ toggleSectionWithAnimation_: function(section, container) {
+ if (section.style.height == '')
+ this.showSectionWithAnimation_(section, container);
+ else
+ this.hideSectionWithAnimation_(section, container);
+ },
+
+ /**
+ * Scrolls the settings page to make the section visible auto-expanding
+ * advanced settings if required. The transition is not animated. This
+ * method is used to ensure that a section associated with an overlay
+ * is visible when the overlay is closed.
+ * @param {!Element} section The section to make visible.
+ * @private
+ */
+ scrollToSection_: function(section) {
+ var advancedSettings = $('advanced-settings');
+ var container = $('advanced-settings-container');
+ if (advancedSettings.hidden && section.parentNode == container) {
+ this.showSection_($('advanced-settings'),
+ $('advanced-settings-container'),
+ /* animate */ false);
+ this.updateAdvancedSettingsExpander_();
+ }
+
+ if (!this.initializationComplete_) {
+ var self = this;
+ var callback = function() {
+ document.removeEventListener('initializationComplete', callback);
+ self.scrollToSection_(section);
+ };
+ document.addEventListener('initializationComplete', callback);
+ return;
+ }
+
+ var pageContainer = $('page-container');
+ // pageContainer.offsetTop is relative to the screen.
+ var pageTop = pageContainer.offsetTop;
+ var sectionBottom = section.offsetTop + section.offsetHeight;
+ // section.offsetTop is relative to the 'page-container'.
+ var sectionTop = section.offsetTop;
+ if (pageTop + sectionBottom > document.body.scrollHeight ||
+ pageTop + sectionTop < 0) {
+ // Currently not all layout updates are guaranteed to precede the
+ // initializationComplete event (for example 'set-as-default-browser'
+ // button) leaving some uncertainty in the optimal scroll position.
+ // The section is placed approximately in the middle of the screen.
+ var top = Math.min(0, document.body.scrollHeight / 2 - sectionBottom);
+ pageContainer.style.top = top + 'px';
+ pageContainer.oldScrollTop = -top;
+ }
+ },
+
+ /**
+ * Adds a |webkitTransitionEnd| listener to the given section so that
+ * it can be animated. The listener will only be added to a given section
+ * once, so this can be called as multiple times.
+ * @param {HTMLElement} section The section to be animated.
+ * @private
+ */
+ addTransitionEndListener_: function(section) {
+ if (section.hasTransitionEndListener_)
+ return;
+
+ section.addEventListener('webkitTransitionEnd',
+ this.onTransitionEnd_.bind(this));
+ section.hasTransitionEndListener_ = true;
+ },
+
+ /**
+ * Called after an animation transition has ended.
+ * @private
+ */
+ onTransitionEnd_: function(event) {
+ if (event.propertyName != 'height')
+ return;
+
+ var section = event.target;
+
+ // Disable WebKit transitions.
+ section.classList.remove('sliding');
+
+ if (section.style.height == '0px') {
+ // Hide the content so it can't get tab focus.
+ section.hidden = true;
+ section.style.height = '';
+ } else {
+ // Set the section height to 'auto' to allow for size changes
+ // (due to font change or dynamic content).
+ section.style.height = 'auto';
+ }
+ },
+
+ updateAdvancedSettingsExpander_: function() {
+ var expander = $('advanced-settings-expander');
+ if ($('advanced-settings').style.height == '')
+ expander.textContent = loadTimeData.getString('showAdvancedSettings');
+ else
+ expander.textContent = loadTimeData.getString('hideAdvancedSettings');
+ },
+
+ /**
+ * Updates the sync section with the given state.
+ * @param {Object} syncData A bunch of data records that describe the status
+ * of the sync system.
+ * @private
+ */
+ updateSyncState_: function(syncData) {
+ if (!syncData.signinAllowed &&
+ (!syncData.supervisedUser || !cr.isChromeOS)) {
+ $('sync-section').hidden = true;
+ return;
+ }
+
+ $('sync-section').hidden = false;
+
+ var subSection = $('sync-section').firstChild;
+ while (subSection) {
+ if (subSection.nodeType == Node.ELEMENT_NODE)
+ subSection.hidden = syncData.supervisedUser;
+ subSection = subSection.nextSibling;
+ }
+
+ if (syncData.supervisedUser) {
+ $('account-picture-wrapper').hidden = false;
+ $('sync-general').hidden = false;
+ $('sync-status').hidden = true;
+ return;
+ }
+
+ // If the user gets signed out while the advanced sync settings dialog is
+ // visible, say, due to a dashboard clear, close the dialog.
+ // However, if the user gets signed out as a result of abandoning first
+ // time sync setup, do not call closeOverlay as it will redirect the
+ // browser to the main settings page and override any in-progress
+ // user-initiated navigation. See crbug.com/278030.
+ // Note: SyncSetupOverlay.closeOverlay is a no-op if the overlay is
+ // already hidden.
+ if (this.signedIn_ && !syncData.signedIn && !syncData.setupInProgress)
+ SyncSetupOverlay.closeOverlay();
+
+ this.signedIn_ = syncData.signedIn;
+
+ // Display the "advanced settings" button if we're signed in and sync is
+ // not managed/disabled. If the user is signed in, but sync is disabled,
+ // this button is used to re-enable sync.
+ var customizeSyncButton = $('customize-sync');
+ customizeSyncButton.hidden = !this.signedIn_ ||
+ syncData.managed ||
+ !syncData.syncSystemEnabled;
+
+ // Only modify the customize button's text if the new text is different.
+ // Otherwise, it can affect search-highlighting in the settings page.
+ // See http://crbug.com/268265.
+ var customizeSyncButtonNewText = syncData.setupCompleted ?
+ loadTimeData.getString('customizeSync') :
+ loadTimeData.getString('syncButtonTextStart');
+ if (customizeSyncButton.textContent != customizeSyncButtonNewText)
+ customizeSyncButton.textContent = customizeSyncButtonNewText;
+
+ // Disable the "sign in" button if we're currently signing in, or if we're
+ // already signed in and signout is not allowed.
+ var signInButton = $('start-stop-sync');
+ signInButton.disabled = syncData.setupInProgress ||
+ !syncData.signoutAllowed;
+ if (!syncData.signoutAllowed)
+ $('start-stop-sync-indicator').setAttribute('controlled-by', 'policy');
+ else
+ $('start-stop-sync-indicator').removeAttribute('controlled-by');
+
+ // Hide the "sign in" button on Chrome OS, and show it on desktop Chrome.
+ signInButton.hidden = cr.isChromeOS;
+
+ signInButton.textContent =
+ this.signedIn_ ?
+ loadTimeData.getString('syncButtonTextStop') :
+ syncData.setupInProgress ?
+ loadTimeData.getString('syncButtonTextInProgress') :
+ loadTimeData.getString('syncButtonTextSignIn');
+ $('start-stop-sync-indicator').hidden = signInButton.hidden;
+
+ // TODO(estade): can this just be textContent?
+ $('sync-status-text').innerHTML = syncData.statusText;
+ var statusSet = syncData.statusText.length != 0;
+ $('sync-overview').hidden = statusSet;
+ $('sync-status').hidden = !statusSet;
+
+ $('sync-action-link').textContent = syncData.actionLinkText;
+ // Don't show the action link if it is empty or undefined.
+ $('sync-action-link').hidden = syncData.actionLinkText.length == 0;
+ $('sync-action-link').disabled = syncData.managed ||
+ !syncData.syncSystemEnabled;
+
+ // On Chrome OS, sign out the user and sign in again to get fresh
+ // credentials on auth errors.
+ $('sync-action-link').onclick = function(event) {
+ if (cr.isChromeOS && syncData.hasError)
+ SyncSetupOverlay.doSignOutOnAuthError();
+ else
+ SyncSetupOverlay.showSetupUI();
+ };
+
+ if (syncData.hasError)
+ $('sync-status').classList.add('sync-error');
+ else
+ $('sync-status').classList.remove('sync-error');
+
+ // Disable the "customize / set up sync" button if sync has an
+ // unrecoverable error. Also disable the button if sync has not been set
+ // up and the user is being presented with a link to re-auth.
+ // See crbug.com/289791.
+ customizeSyncButton.disabled =
+ syncData.hasUnrecoverableError ||
+ (!syncData.setupCompleted && !$('sync-action-link').hidden);
+
+ // Move #enable-auto-login-checkbox to a different location on CrOS.
+ if (cr.isChromeOs) {
+ $('sync-general').insertBefore($('sync-status').nextSibling,
+ $('enable-auto-login-checkbox'));
+ }
+ $('enable-auto-login-checkbox').hidden = !syncData.autoLoginVisible;
+ },
+
+ /**
+ * Update the UI depending on whether the current profile manages any
+ * supervised users.
+ * @param {boolean} value True if the current profile manages any supervised
+ * users.
+ */
+ updateManagesSupervisedUsers_: function(value) {
+ $('profiles-supervised-dashboard-tip').hidden = !value;
+ },
+
+ /**
+ * Get the start/stop sync button DOM element. Used for testing.
+ * @return {DOMElement} The start/stop sync button.
+ * @private
+ */
+ getStartStopSyncButton_: function() {
+ return $('start-stop-sync');
+ },
+
+ /**
+ * Event listener for the 'show home button' preference. Shows/hides the
+ * UI for changing the home page with animation, unless this is the first
+ * time this function is called, in which case there is no animation.
+ * @param {Event} event The preference change event.
+ */
+ onShowHomeButtonChanged_: function(event) {
+ var section = $('change-home-page-section');
+ if (this.onShowHomeButtonChangedCalled_) {
+ var container = $('change-home-page-section-container');
+ if (event.value.value)
+ this.showSectionWithAnimation_(section, container);
+ else
+ this.hideSectionWithAnimation_(section, container);
+ } else {
+ section.hidden = !event.value.value;
+ this.onShowHomeButtonChangedCalled_ = true;
+ }
+ },
+
+ /**
+ * Event listener for the 'homepage is NTP' preference. Updates the label
+ * next to the 'Change' button.
+ * @param {Event} event The preference change event.
+ */
+ onHomePageIsNtpChanged_: function(event) {
+ if (!event.value.uncommitted) {
+ $('home-page-url').hidden = event.value.value;
+ $('home-page-ntp').hidden = !event.value.value;
+ }
+ },
+
+ /**
+ * Event listener for changes to the homepage preference. Updates the label
+ * next to the 'Change' button.
+ * @param {Event} event The preference change event.
+ */
+ onHomePageChanged_: function(event) {
+ if (!event.value.uncommitted)
+ $('home-page-url').textContent = this.stripHttp_(event.value.value);
+ },
+
+ /**
+ * Removes the 'http://' from a URL, like the omnibox does. If the string
+ * doesn't start with 'http://' it is returned unchanged.
+ * @param {string} url The url to be processed
+ * @return {string} The url with the 'http://' removed.
+ */
+ stripHttp_: function(url) {
+ return url.replace(/^http:\/\//, '');
+ },
+
+ /**
+ * Shows the autoLaunch preference and initializes its checkbox value.
+ * @param {bool} enabled Whether autolaunch is enabled or or not.
+ * @private
+ */
+ updateAutoLaunchState_: function(enabled) {
+ $('auto-launch-option').hidden = false;
+ $('auto-launch').checked = enabled;
+ },
+
+ /**
+ * Called when the value of the download.default_directory preference
+ * changes.
+ * @param {Event} event Change event.
+ * @private
+ */
+ onDefaultDownloadDirectoryChanged_: function(event) {
+ $('downloadLocationPath').value = event.value.value;
+ if (cr.isChromeOS) {
+ // On ChromeOS, replace /special/drive/root with Drive for drive paths,
+ // /home/chronos/user/Downloads or /home/chronos/u-<hash>/Downloads
+ // with Downloads for local paths, and '/' with ' \u203a ' (angled quote
+ // sign) everywhere. The modified path is used only for display purpose.
+ var path = $('downloadLocationPath').value;
+ path = path.replace(/^\/special\/drive\/root/, 'Google Drive');
+ path = path.replace(/^\/home\/chronos\/(user|u-[^\/]*)\//, '');
+ path = path.replace(/\//g, ' \u203a ');
+ $('downloadLocationPath').value = path;
+ }
+ $('download-location-label').classList.toggle('disabled',
+ event.value.disabled);
+ $('downloadLocationChangeButton').disabled = event.value.disabled;
+ },
+
+ /**
+ * Update the Default Browsers section based on the current state.
+ * @param {string} statusString Description of the current default state.
+ * @param {boolean} isDefault Whether or not the browser is currently
+ * default.
+ * @param {boolean} canBeDefault Whether or not the browser can be default.
+ * @private
+ */
+ updateDefaultBrowserState_: function(statusString, isDefault,
+ canBeDefault) {
+ if (!cr.isChromeOS) {
+ var label = $('default-browser-state');
+ label.textContent = statusString;
+
+ $('set-as-default-browser').hidden = !canBeDefault || isDefault;
+ }
+ },
+
+ /**
+ * Clears the search engine popup.
+ * @private
+ */
+ clearSearchEngines_: function() {
+ $('default-search-engine').textContent = '';
+ },
+
+ /**
+ * Updates the search engine popup with the given entries.
+ * @param {Array} engines List of available search engines.
+ * @param {number} defaultValue The value of the current default engine.
+ * @param {boolean} defaultManaged Whether the default search provider is
+ * managed. If true, the default search provider can't be changed.
+ * @private
+ */
+ updateSearchEngines_: function(engines, defaultValue, defaultManaged) {
+ this.clearSearchEngines_();
+ engineSelect = $('default-search-engine');
+ engineSelect.disabled = defaultManaged;
+ if (defaultManaged && defaultValue == -1)
+ return;
+ engineCount = engines.length;
+ var defaultIndex = -1;
+ for (var i = 0; i < engineCount; i++) {
+ var engine = engines[i];
+ var option = new Option(engine.name, engine.index);
+ if (defaultValue == option.value)
+ defaultIndex = i;
+ engineSelect.appendChild(option);
+ }
+ if (defaultIndex >= 0)
+ engineSelect.selectedIndex = defaultIndex;
+ },
+
+ /**
+ * Set the default search engine based on the popup selection.
+ * @private
+ */
+ setDefaultSearchEngine_: function() {
+ var engineSelect = $('default-search-engine');
+ var selectedIndex = engineSelect.selectedIndex;
+ if (selectedIndex >= 0) {
+ var selection = engineSelect.options[selectedIndex];
+ chrome.send('setDefaultSearchEngine', [String(selection.value)]);
+ }
+ },
+
+ /**
+ * Sets or clear whether Chrome should Auto-launch on computer startup.
+ * @private
+ */
+ handleAutoLaunchChanged_: function() {
+ chrome.send('toggleAutoLaunch', [$('auto-launch').checked]);
+ },
+
+ /**
+ * Get the selected profile item from the profile list. This also works
+ * correctly if the list is not displayed.
+ * @return {Object} the profile item object, or null if nothing is selected.
+ * @private
+ */
+ getSelectedProfileItem_: function() {
+ var profilesList = $('profiles-list');
+ if (profilesList.hidden) {
+ if (profilesList.dataModel.length > 0)
+ return profilesList.dataModel.item(0);
+ } else {
+ return profilesList.selectedItem;
+ }
+ return null;
+ },
+
+ /**
+ * Helper function to set the status of profile view buttons to disabled or
+ * enabled, depending on the number of profiles and selection status of the
+ * profiles list.
+ * @private
+ */
+ setProfileViewButtonsStatus_: function() {
+ var profilesList = $('profiles-list');
+ var selectedProfile = profilesList.selectedItem;
+ var hasSelection = selectedProfile != null;
+ var hasSingleProfile = profilesList.dataModel.length == 1;
+ var isManaged = loadTimeData.getBoolean('profileIsManaged');
+ $('profiles-manage').disabled = !hasSelection ||
+ !selectedProfile.isCurrentProfile;
+ if (hasSelection && !selectedProfile.isCurrentProfile)
+ $('profiles-manage').title = loadTimeData.getString('currentUserOnly');
+ else
+ $('profiles-manage').title = '';
+ $('profiles-delete').disabled = isManaged ||
+ (!hasSelection && !hasSingleProfile);
+ if (OptionsPage.isSettingsApp()) {
+ $('profiles-app-list-switch').disabled = !hasSelection ||
+ selectedProfile.isCurrentProfile;
+ }
+ var importData = $('import-data');
+ if (importData) {
+ importData.disabled = $('import-data').disabled = hasSelection &&
+ !selectedProfile.isCurrentProfile;
+ }
+ },
+
+ /**
+ * Display the correct dialog layout, depending on how many profiles are
+ * available.
+ * @param {number} numProfiles The number of profiles to display.
+ * @private
+ */
+ setProfileViewSingle_: function(numProfiles) {
+ var hasSingleProfile = numProfiles == 1;
+ $('profiles-list').hidden = hasSingleProfile;
+ $('profiles-single-message').hidden = !hasSingleProfile;
+ $('profiles-manage').hidden =
+ hasSingleProfile || OptionsPage.isSettingsApp();
+ $('profiles-delete').textContent = hasSingleProfile ?
+ loadTimeData.getString('profilesDeleteSingle') :
+ loadTimeData.getString('profilesDelete');
+ if (OptionsPage.isSettingsApp())
+ $('profiles-app-list-switch').hidden = hasSingleProfile;
+ },
+
+ /**
+ * Adds all |profiles| to the list.
+ * @param {Array.<Object>} profiles An array of profile info objects.
+ * each object is of the form:
+ * profileInfo = {
+ * name: "Profile Name",
+ * iconURL: "chrome://path/to/icon/image",
+ * filePath: "/path/to/profile/data/on/disk",
+ * isCurrentProfile: false
+ * };
+ * @private
+ */
+ setProfilesInfo_: function(profiles) {
+ this.setProfileViewSingle_(profiles.length);
+ // add it to the list, even if the list is hidden so we can access it
+ // later.
+ $('profiles-list').dataModel = new ArrayDataModel(profiles);
+
+ // Received new data. If showing the "manage" overlay, keep it up to
+ // date. If showing the "delete" overlay, close it.
+ if (ManageProfileOverlay.getInstance().visible &&
+ !$('manage-profile-overlay-manage').hidden) {
+ ManageProfileOverlay.showManageDialog();
+ } else {
+ ManageProfileOverlay.getInstance().visible = false;
+ }
+
+ this.setProfileViewButtonsStatus_();
+ },
+
+ /**
+ * Reports managed user import errors to the ManagedUserImportOverlay.
+ * @param {string} error The error message to display.
+ * @private
+ */
+ showManagedUserImportError_: function(error) {
+ ManagedUserImportOverlay.onError(error);
+ },
+
+ /**
+ * Reports successful importing of a managed user to
+ * the ManagedUserImportOverlay.
+ * @private
+ */
+ showManagedUserImportSuccess_: function() {
+ ManagedUserImportOverlay.onSuccess();
+ },
+
+ /**
+ * Reports an error to the "create" overlay during profile creation.
+ * @param {string} error The error message to display.
+ * @private
+ */
+ showCreateProfileError_: function(error) {
+ CreateProfileOverlay.onError(error);
+ },
+
+ /**
+ * Sends a warning message to the "create" overlay during profile creation.
+ * @param {string} warning The warning message to display.
+ * @private
+ */
+ showCreateProfileWarning_: function(warning) {
+ CreateProfileOverlay.onWarning(warning);
+ },
+
+ /**
+ * Reports successful profile creation to the "create" overlay.
+ * @param {Object} profileInfo An object of the form:
+ * profileInfo = {
+ * name: "Profile Name",
+ * filePath: "/path/to/profile/data/on/disk"
+ * isManaged: (true|false),
+ * };
+ * @private
+ */
+ showCreateProfileSuccess_: function(profileInfo) {
+ CreateProfileOverlay.onSuccess(profileInfo);
+ },
+
+ /**
+ * Returns the currently active profile for this browser window.
+ * @return {Object} A profile info object.
+ * @private
+ */
+ getCurrentProfile_: function() {
+ for (var i = 0; i < $('profiles-list').dataModel.length; i++) {
+ var profile = $('profiles-list').dataModel.item(i);
+ if (profile.isCurrentProfile)
+ return profile;
+ }
+
+ assert(false,
+ 'There should always be a current profile, but none found.');
+ },
+
+ setNativeThemeButtonEnabled_: function(enabled) {
+ var button = $('themes-native-button');
+ if (button)
+ button.disabled = !enabled;
+ },
+
+ setThemesResetButtonEnabled_: function(enabled) {
+ $('themes-reset').disabled = !enabled;
+ },
+
+ setAccountPictureManaged_: function(managed) {
+ var picture = $('account-picture');
+ if (managed || UIAccountTweaks.loggedInAsGuest()) {
+ picture.disabled = true;
+ ChangePictureOptions.closeOverlay();
+ } else {
+ picture.disabled = false;
+ }
+
+ // Create a synthetic pref change event decorated as
+ // CoreOptionsHandler::CreateValueForPref() does.
+ var event = new Event('account-picture');
+ if (managed)
+ event.value = { controlledBy: 'policy' };
+ else
+ event.value = {};
+ $('account-picture-indicator').handlePrefChange(event);
+ },
+
+ /**
+ * (Re)loads IMG element with current user account picture.
+ * @private
+ */
+ updateAccountPicture_: function() {
+ var picture = $('account-picture');
+ if (picture) {
+ picture.src = 'chrome://userimage/' + this.username_ + '?id=' +
+ Date.now();
+ }
+ },
+
+ /**
+ * Handle the 'add device' button click.
+ * @private
+ */
+ handleAddBluetoothDevice_: function() {
+ chrome.send('findBluetoothDevices');
+ OptionsPage.showPageByName('bluetooth', false);
+ },
+
+ /**
+ * Enables or disables the Manage SSL Certificates button.
+ * @private
+ */
+ enableCertificateButton_: function(enabled) {
+ $('certificatesManageButton').disabled = !enabled;
+ },
+
+ /**
+ * Enables factory reset section.
+ * @private
+ */
+ enableFactoryResetSection_: function() {
+ $('factory-reset-section').hidden = false;
+ },
+
+ /**
+ * Set the checked state of the metrics reporting checkbox.
+ * @private
+ */
+ setMetricsReportingCheckboxState_: function(checked, disabled) {
+ $('metricsReportingEnabled').checked = checked;
+ $('metricsReportingEnabled').disabled = disabled;
+ },
+
+ /**
+ * @private
+ */
+ setMetricsReportingSettingVisibility_: function(visible) {
+ if (visible)
+ $('metricsReportingSetting').style.display = 'block';
+ else
+ $('metricsReportingSetting').style.display = 'none';
+ },
+
+ /**
+ * Set the visibility of the password generation checkbox.
+ * @private
+ */
+ setPasswordGenerationSettingVisibility_: function(visible) {
+ if (visible)
+ $('password-generation-checkbox').style.display = 'block';
+ else
+ $('password-generation-checkbox').style.display = 'none';
+ },
+
+ /**
+ * Set the font size selected item. This item actually reflects two
+ * preferences: the default font size and the default fixed font size.
+ *
+ * @param {Object} pref Information about the font size preferences.
+ * @param {number} pref.value The value of the default font size pref.
+ * @param {boolean} pref.disabled True if either pref not user modifiable.
+ * @param {string} pref.controlledBy The source of the pref value(s) if
+ * either pref is currently not controlled by the user.
+ * @private
+ */
+ setFontSize_: function(pref) {
+ var selectCtl = $('defaultFontSize');
+ selectCtl.disabled = pref.disabled;
+ // Create a synthetic pref change event decorated as
+ // CoreOptionsHandler::CreateValueForPref() does.
+ var event = new Event('synthetic-font-size');
+ event.value = {
+ value: pref.value,
+ controlledBy: pref.controlledBy,
+ disabled: pref.disabled
+ };
+ $('font-size-indicator').handlePrefChange(event);
+
+ for (var i = 0; i < selectCtl.options.length; i++) {
+ if (selectCtl.options[i].value == pref.value) {
+ selectCtl.selectedIndex = i;
+ if ($('Custom'))
+ selectCtl.remove($('Custom').index);
+ return;
+ }
+ }
+
+ // Add/Select Custom Option in the font size label list.
+ if (!$('Custom')) {
+ var option = new Option(loadTimeData.getString('fontSizeLabelCustom'),
+ -1, false, true);
+ option.setAttribute('id', 'Custom');
+ selectCtl.add(option);
+ }
+ $('Custom').selected = true;
+ },
+
+ /**
+ * Populate the page zoom selector with values received from the caller.
+ * @param {Array} items An array of items to populate the selector.
+ * each object is an array with three elements as follows:
+ * 0: The title of the item (string).
+ * 1: The value of the item (number).
+ * 2: Whether the item should be selected (boolean).
+ * @private
+ */
+ setupPageZoomSelector_: function(items) {
+ var element = $('defaultZoomFactor');
+
+ // Remove any existing content.
+ element.textContent = '';
+
+ // Insert new child nodes into select element.
+ var value, title, selected;
+ for (var i = 0; i < items.length; i++) {
+ title = items[i][0];
+ value = items[i][1];
+ selected = items[i][2];
+ element.appendChild(new Option(title, value, false, selected));
+ }
+ },
+
+ /**
+ * Shows/hides the autoOpenFileTypesResetToDefault button and label, with
+ * animation.
+ * @param {boolean} display Whether to show the button and label or not.
+ * @private
+ */
+ setAutoOpenFileTypesDisplayed_: function(display) {
+ if (cr.isChromeOS)
+ return;
+
+ if ($('advanced-settings').hidden) {
+ // If the Advanced section is hidden, don't animate the transition.
+ $('auto-open-file-types-section').hidden = !display;
+ } else {
+ if (display) {
+ this.showSectionWithAnimation_(
+ $('auto-open-file-types-section'),
+ $('auto-open-file-types-container'));
+ } else {
+ this.hideSectionWithAnimation_(
+ $('auto-open-file-types-section'),
+ $('auto-open-file-types-container'));
+ }
+ }
+ },
+
+ /**
+ * Set the enabled state for the proxy settings button.
+ * @private
+ */
+ setupProxySettingsSection_: function(disabled, extensionControlled) {
+ if (!cr.isChromeOS) {
+ $('proxiesConfigureButton').disabled = disabled;
+ $('proxiesLabel').textContent =
+ loadTimeData.getString(extensionControlled ?
+ 'proxiesLabelExtension' : 'proxiesLabelSystem');
+ }
+ },
+
+ /**
+ * Set the Cloud Print proxy UI to enabled, disabled, or processing.
+ * @private
+ */
+ setupCloudPrintConnectorSection_: function(disabled, label, allowed) {
+ if (!cr.isChromeOS) {
+ $('cloudPrintConnectorLabel').textContent = label;
+ if (disabled || !allowed) {
+ $('cloudPrintConnectorSetupButton').textContent =
+ loadTimeData.getString('cloudPrintConnectorDisabledButton');
+ $('cloudPrintManageButton').style.display = 'none';
+ } else {
+ $('cloudPrintConnectorSetupButton').textContent =
+ loadTimeData.getString('cloudPrintConnectorEnabledButton');
+ $('cloudPrintManageButton').style.display = 'inline';
+ }
+ $('cloudPrintConnectorSetupButton').disabled = !allowed;
+ }
+ },
+
+ /**
+ * @private
+ */
+ removeCloudPrintConnectorSection_: function() {
+ if (!cr.isChromeOS) {
+ var connectorSectionElm = $('cloud-print-connector-section');
+ if (connectorSectionElm)
+ connectorSectionElm.parentNode.removeChild(connectorSectionElm);
+ }
+ },
+
+ /**
+ * Set the initial state of the spoken feedback checkbox.
+ * @private
+ */
+ setSpokenFeedbackCheckboxState_: function(checked) {
+ $('accessibility-spoken-feedback-check').checked = checked;
+ },
+
+ /**
+ * Set the initial state of the high contrast checkbox.
+ * @private
+ */
+ setHighContrastCheckboxState_: function(checked) {
+ $('accessibility-high-contrast-check').checked = checked;
+ },
+
+ /**
+ * Set the initial state of the virtual keyboard checkbox.
+ * @private
+ */
+ setVirtualKeyboardCheckboxState_: function(checked) {
+ // TODO(zork): Update UI
+ },
+
+ /**
+ * Show/hide mouse settings slider.
+ * @private
+ */
+ showMouseControls_: function(show) {
+ $('mouse-settings').hidden = !show;
+ },
+
+ /**
+ * Show/hide touchpad-related settings.
+ * @private
+ */
+ showTouchpadControls_: function(show) {
+ $('touchpad-settings').hidden = !show;
+ $('accessibility-tap-dragging').hidden = !show;
+ },
+
+ /**
+ * Activate the Bluetooth settings section on the System settings page.
+ * @private
+ */
+ showBluetoothSettings_: function() {
+ $('bluetooth-devices').hidden = false;
+ },
+
+ /**
+ * Dectivates the Bluetooth settings section from the System settings page.
+ * @private
+ */
+ hideBluetoothSettings_: function() {
+ $('bluetooth-devices').hidden = true;
+ },
+
+ /**
+ * Sets the state of the checkbox indicating if Bluetooth is turned on. The
+ * state of the "Find devices" button and the list of discovered devices may
+ * also be affected by a change to the state.
+ * @param {boolean} checked Flag Indicating if Bluetooth is turned on.
+ * @private
+ */
+ setBluetoothState_: function(checked) {
+ $('enable-bluetooth').checked = checked;
+ $('bluetooth-paired-devices-list').parentNode.hidden = !checked;
+ $('bluetooth-add-device').hidden = !checked;
+ $('bluetooth-reconnect-device').hidden = !checked;
+ // Flush list of previously discovered devices if bluetooth is turned off.
+ if (!checked) {
+ $('bluetooth-paired-devices-list').clear();
+ $('bluetooth-unpaired-devices-list').clear();
+ } else {
+ chrome.send('getPairedBluetoothDevices');
+ }
+ },
+
+ /**
+ * Adds an element to the list of available Bluetooth devices. If an element
+ * with a matching address is found, the existing element is updated.
+ * @param {{name: string,
+ * address: string,
+ * paired: boolean,
+ * connected: boolean}} device
+ * Decription of the Bluetooth device.
+ * @private
+ */
+ addBluetoothDevice_: function(device) {
+ var list = $('bluetooth-unpaired-devices-list');
+ // Display the "connecting" (already paired or not yet paired) and the
+ // paired devices in the same list.
+ if (device.paired || device.connecting) {
+ // Test to see if the device is currently in the unpaired list, in which
+ // case it should be removed from that list.
+ var index = $('bluetooth-unpaired-devices-list').find(device.address);
+ if (index != undefined)
+ $('bluetooth-unpaired-devices-list').deleteItemAtIndex(index);
+ list = $('bluetooth-paired-devices-list');
+ } else {
+ // Test to see if the device is currently in the paired list, in which
+ // case it should be removed from that list.
+ var index = $('bluetooth-paired-devices-list').find(device.address);
+ if (index != undefined)
+ $('bluetooth-paired-devices-list').deleteItemAtIndex(index);
+ }
+ list.appendDevice(device);
+
+ // One device can be in the process of pairing. If found, display
+ // the Bluetooth pairing overlay.
+ if (device.pairing)
+ BluetoothPairing.showDialog(device);
+ },
+
+ /**
+ * Removes an element from the list of available devices.
+ * @param {string} address Unique address of the device.
+ * @private
+ */
+ removeBluetoothDevice_: function(address) {
+ var index = $('bluetooth-unpaired-devices-list').find(address);
+ if (index != undefined) {
+ $('bluetooth-unpaired-devices-list').deleteItemAtIndex(index);
+ } else {
+ index = $('bluetooth-paired-devices-list').find(address);
+ if (index != undefined)
+ $('bluetooth-paired-devices-list').deleteItemAtIndex(index);
+ }
+ },
+
+ /**
+ * Shows the overlay dialog for changing the user avatar image.
+ * @private
+ */
+ showImagerPickerOverlay_: function() {
+ OptionsPage.navigateToPage('changePicture');
+ }
+ };
+
+ //Forward public APIs to private implementations.
+ [
+ 'addBluetoothDevice',
+ 'enableCertificateButton',
+ 'enableFactoryResetSection',
+ 'getCurrentProfile',
+ 'getStartStopSyncButton',
+ 'hideBluetoothSettings',
+ 'notifyInitializationComplete',
+ 'removeBluetoothDevice',
+ 'removeCloudPrintConnectorSection',
+ 'scrollToSection',
+ 'setAccountPictureManaged',
+ 'setAutoOpenFileTypesDisplayed',
+ 'setBluetoothState',
+ 'setFontSize',
+ 'setNativeThemeButtonEnabled',
+ 'setHighContrastCheckboxState',
+ 'setMetricsReportingCheckboxState',
+ 'setMetricsReportingSettingVisibility',
+ 'setPasswordGenerationSettingVisibility',
+ 'setProfilesInfo',
+ 'setSpokenFeedbackCheckboxState',
+ 'setThemesResetButtonEnabled',
+ 'setVirtualKeyboardCheckboxState',
+ 'setupCloudPrintConnectorSection',
+ 'setupPageZoomSelector',
+ 'setupProxySettingsSection',
+ 'showBluetoothSettings',
+ 'showCreateProfileError',
+ 'showCreateProfileSuccess',
+ 'showCreateProfileWarning',
+ 'showManagedUserImportError',
+ 'showManagedUserImportSuccess',
+ 'showMouseControls',
+ 'showTouchpadControls',
+ 'updateAccountPicture',
+ 'updateAutoLaunchState',
+ 'updateDefaultBrowserState',
+ 'updateManagesSupervisedUsers',
+ 'updateSearchEngines',
+ 'updateStartupPages',
+ 'updateSyncState',
+ ].forEach(function(name) {
+ BrowserOptions[name] = function() {
+ var instance = BrowserOptions.getInstance();
+ return instance[name + '_'].apply(instance, arguments);
+ };
+ });
+
+ if (cr.isChromeOS) {
+ /**
+ * Returns username (canonical email) of the user logged in (ChromeOS only).
+ * @return {string} user email.
+ */
+ // TODO(jhawkins): Investigate the use case for this method.
+ BrowserOptions.getLoggedInUsername = function() {
+ return BrowserOptions.getInstance().username_;
+ };
+ }
+
+ // Export
+ return {
+ BrowserOptions: BrowserOptions
+ };
+});
diff --git a/chromium/chrome/browser/resources/options/browser_options_profile_list.js b/chromium/chrome/browser/resources/options/browser_options_profile_list.js
new file mode 100644
index 00000000000..7a60ca3cf16
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/browser_options_profile_list.js
@@ -0,0 +1,129 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options.browser_options', function() {
+ /** @const */ var DeletableItem = options.DeletableItem;
+ /** @const */ var DeletableItemList = options.DeletableItemList;
+ /** @const */ var ListSingleSelectionModel = cr.ui.ListSingleSelectionModel;
+
+ /**
+ * 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,
+
+ /**
+ * @type {string} the file path of this profile list item.
+ */
+ get profilePath() {
+ return this.profileInfo_.filePath;
+ },
+
+ /**
+ * @type {boolean} whether this profile is managed.
+ */
+ get isManaged() {
+ return this.profileInfo_.isManaged;
+ },
+
+ /** @override */
+ decorate: function() {
+ DeletableItem.prototype.decorate.call(this);
+
+ var profileInfo = this.profileInfo_;
+
+ var iconEl = this.ownerDocument.createElement('img');
+ iconEl.className = 'profile-img';
+ iconEl.style.content = imageset(profileInfo.iconURL + '@scalefactorx');
+ this.contentElement.appendChild(iconEl);
+
+ var nameEl = this.ownerDocument.createElement('div');
+ nameEl.className = 'profile-name';
+ if (profileInfo.isCurrentProfile)
+ nameEl.classList.add('profile-item-current');
+ this.contentElement.appendChild(nameEl);
+
+ var displayName = profileInfo.name;
+ if (profileInfo.isCurrentProfile) {
+ displayName = loadTimeData.getStringF('profilesListItemCurrent',
+ profileInfo.name);
+ }
+ nameEl.textContent = displayName;
+
+ // Ensure that the button cannot be tabbed to for accessibility reasons.
+ this.closeButtonElement.tabIndex = -1;
+ },
+ };
+
+ var ProfileList = cr.ui.define('list');
+
+ ProfileList.prototype = {
+ __proto__: DeletableItemList.prototype,
+
+ /** @override */
+ decorate: function() {
+ DeletableItemList.prototype.decorate.call(this);
+ this.selectionModel = new ListSingleSelectionModel();
+ },
+
+ /** @override */
+ createItem: function(pageInfo) {
+ var item = new ProfileListItem(pageInfo);
+ item.deletable = this.canDeleteItems_;
+ return item;
+ },
+
+ /** @override */
+ deleteItemAtIndex: function(index) {
+ if (loadTimeData.getBoolean('profileIsManaged'))
+ return;
+ ManageProfileOverlay.showDeleteDialog(this.dataModel.item(index));
+ },
+
+ /** @override */
+ 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);
+ },
+
+ /**
+ * Sets whether items in this list are deletable.
+ */
+ set canDeleteItems(value) {
+ this.canDeleteItems_ = value;
+ },
+
+ /**
+ * If false, items in this list will not be deltable.
+ * @private
+ */
+ canDeleteItems_: true,
+ };
+
+ return {
+ ProfileList: ProfileList
+ };
+});
+
diff --git a/chromium/chrome/browser/resources/options/browser_options_startup_page_list.js b/chromium/chrome/browser/resources/options/browser_options_startup_page_list.js
new file mode 100644
index 00000000000..6afed9a081d
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/browser_options_startup_page_list.js
@@ -0,0 +1,321 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options.browser_options', function() {
+ /** @const */ var AutocompleteList = cr.ui.AutocompleteList;
+ /** @const */ var InlineEditableItem = options.InlineEditableItem;
+ /** @const */ var 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,
+
+ /** @override */
+ decorate: function() {
+ InlineEditableItem.prototype.decorate.call(this);
+
+ var pageInfo = this.pageInfo_;
+
+ if (pageInfo.modelIndex == -1) {
+ this.isPlaceholder = true;
+ pageInfo.title = loadTimeData.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 = getFaviconImageSet(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.className = 'weakrtl';
+ urlField.placeholder = loadTimeData.getString('startupPagesPlaceholder');
+ 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;
+ },
+
+ /** @override */
+ get currentInputIsValid() {
+ return this.urlField_.validity.valid;
+ },
+
+ /** @override */
+ 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,
+
+ /** @override */
+ 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));
+ },
+
+ /** @override */
+ createItem: function(pageInfo) {
+ var item = new StartupPageListItem(pageInfo);
+ item.urlField_.disabled = this.disabled;
+ return item;
+ },
+
+ /** @override */
+ deleteItemAtIndex: function(index) {
+ chrome.send('removeStartupPages', [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);
+
+ if (!(dropTarget instanceof StartupPageListItem) ||
+ dropTarget.pageInfo_.modelIndex == -1) {
+ return;
+ }
+
+ this.hideDropMarker_();
+
+ // Insert the selection at the new position.
+ var newIndex = this.dataModel.indexOf(dropTarget.pageInfo_);
+ if (this.dropPos == 'below')
+ newIndex += 1;
+
+ // If there are selected indexes, it was a re-order.
+ if (this.selectionModel.selectedIndexes.length > 0) {
+ chrome.send('dragDropStartupPage',
+ [newIndex, this.selectionModel.selectedIndexes]);
+ return;
+ }
+
+ // Otherwise it was potentially a drop of new data (e.g. a bookmark).
+ var url = e.dataTransfer.getData('url');
+ if (url) {
+ e.preventDefault();
+ chrome.send('addStartupPage', [url, newIndex]);
+ }
+ },
+
+ /**
+ * 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/chromium/chrome/browser/resources/options/certificate_backup_overlay.html b/chromium/chrome/browser/resources/options/certificate_backup_overlay.html
new file mode 100644
index 00000000000..89cdba866d1
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/certificate_backup_overlay.html
@@ -0,0 +1,40 @@
+<div id="certificateBackupOverlay" class="page" hidden>
+ <div class="close-button"></div>
+ <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" class="default-button"
+ type="submit" i18n-content="ok" disabled>
+ </button>
+ </div>
+ </div>
+</div>
diff --git a/chromium/chrome/browser/resources/options/certificate_backup_overlay.js b/chromium/chrome/browser/resources/options/certificate_backup_overlay.js
new file mode 100644
index 00000000000..c904a47f9b9
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/certificate_backup_overlay.js
@@ -0,0 +1,117 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+ /** @const */ var 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_();
+ };
+ var onBackupPasswordInput = function(event) {
+ self.comparePasswords_();
+ };
+ $('certificateBackupPassword').oninput = onBackupPasswordInput;
+ $('certificateBackupPassword2').oninput = onBackupPasswordInput;
+
+ 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/chromium/chrome/browser/resources/options/certificate_edit_ca_trust_overlay.html b/chromium/chrome/browser/resources/options/certificate_edit_ca_trust_overlay.html
new file mode 100644
index 00000000000..4dabea286a8
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/certificate_edit_ca_trust_overlay.html
@@ -0,0 +1,39 @@
+<div id="certificateEditCaTrustOverlay" class="page" hidden>
+ <h1><span i18n-content="certificateEditCaTitle"></span></h1>
+ <div class="close-button"></div>
+ <div class="content-area">
+ <div>
+ <span id="certificateEditCaTrustDescription"></span>
+ </div>
+ <section>
+ <h3><span i18n-content="certificateEditTrustLabel"></span></h3>
+ <div class="checkbox">
+ <label id="certificateCaTrustSSLLabel">
+ <input id="certificateCaTrustSSLCheckbox" type="checkbox">
+ <span i18n-content="certificateCaTrustSSLLabel"></span>
+ </label>
+ </div>
+ <div class="checkbox">
+ <label id="certificateCaTrustEmailLabel">
+ <input id="certificateCaTrustEmailCheckbox" type="checkbox">
+ <span i18n-content="certificateCaTrustEmailLabel"></span>
+ </label>
+ </div>
+ <div class="checkbox">
+ <label id="certificateCaTrustObjSignLabel">
+ <input id="certificateCaTrustObjSignCheckbox" type="checkbox">
+ <span i18n-content="certificateCaTrustObjSignLabel"></span>
+ </label>
+ </div>
+ </section>
+ </div>
+ <div class="action-area">
+ <div class="button-strip">
+ <button id="certificateEditCaTrustCancelButton" type="reset"
+ i18n-content="cancel"></button>
+ <button id="certificateEditCaTrustOkButton" class="default-button"
+ type="submit" i18n-content="ok">
+ </button>
+ </div>
+ </div>
+</div>
diff --git a/chromium/chrome/browser/resources/options/certificate_edit_ca_trust_overlay.js b/chromium/chrome/browser/resources/options/certificate_edit_ca_trust_overlay.js
new file mode 100644
index 00000000000..5f017c8e932
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/certificate_edit_ca_trust_overlay.js
@@ -0,0 +1,164 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+ /** @const */ var 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 =
+ loadTimeData.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 =
+ loadTimeData.getStringF('certificateImportCaDescriptionFormat',
+ certName);
+ CertificateEditCaTrustOverlay.populateTrust(false, false, false);
+ OptionsPage.navigateToPage('certificateEditCaTrustOverlay');
+ }
+
+ CertificateEditCaTrustOverlay.dismiss = function() {
+ CertificateEditCaTrustOverlay.getInstance().dismissOverlay_();
+ };
+
+ // Export
+ return {
+ CertificateEditCaTrustOverlay: CertificateEditCaTrustOverlay
+ };
+});
diff --git a/chromium/chrome/browser/resources/options/certificate_import_error_overlay.html b/chromium/chrome/browser/resources/options/certificate_import_error_overlay.html
new file mode 100644
index 00000000000..b36d4db0b83
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/certificate_import_error_overlay.html
@@ -0,0 +1,15 @@
+<div id="certificateImportErrorOverlay" class="page" hidden>
+ <div class="close-button"></div>
+ <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" class="default-button"
+ type="submit" i18n-content="done">
+ </button>
+ </div>
+ </div>
+</div>
diff --git a/chromium/chrome/browser/resources/options/certificate_import_error_overlay.js b/chromium/chrome/browser/resources/options/certificate_import_error_overlay.js
new file mode 100644
index 00000000000..4eed1d25d57
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/certificate_import_error_overlay.js
@@ -0,0 +1,68 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+
+ var OptionsPage = options.OptionsPage;
+
+ /**
+ * 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 = loadTimeData.getStringF('certificateImportErrorFormat',
+ certErrors[i].name,
+ certErrors[i].error);
+ ul.appendChild(li);
+ }
+
+ OptionsPage.navigateToPage('certificateImportErrorOverlay');
+ }
+
+ // Export
+ return {
+ CertificateImportErrorOverlay: CertificateImportErrorOverlay
+ };
+
+});
diff --git a/chromium/chrome/browser/resources/options/certificate_manager.css b/chromium/chrome/browser/resources/options/certificate_manager.css
new file mode 100644
index 00000000000..0516e5b580a
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/certificate_manager.css
@@ -0,0 +1,32 @@
+/* Copyright (c) 2012 The Chromium Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file. */
+
+#certificateManagerPage {
+ min-width: 700px;
+}
+
+#certificateRestoreOverlay h1 {
+ /* Leave enough space for the close-button. */
+ /* TODO(KGR): make dialogs to use flex-boxes instead of relying on padding. */
+ padding-right: 35px;
+}
+
+/* Force tab strip to extend to the left and right edges of the window. */
+#certificate-manager-content-area {
+ padding: 6px 0 6px 0;
+}
+
+#certificate-manager-content-area .subpages-tab-contents {
+ padding-left: 28px;
+ padding-right: 14px;
+}
+
+.certificate-tree-table {
+ width: 100%;
+}
+
+.certificate-tree {
+ /* TODO(mattm): BLAH. Make this not statically sized. */
+ height: 300px;
+}
diff --git a/chromium/chrome/browser/resources/options/certificate_manager.html b/chromium/chrome/browser/resources/options/certificate_manager.html
new file mode 100644
index 00000000000..3d28e7f2b9f
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/certificate_manager.html
@@ -0,0 +1,144 @@
+<div id="certificateManagerPage" class="page" hidden>
+ <div class="close-button"></div>
+ <h1 i18n-content="certificateManagerPage"></h1>
+ <div id="certificate-manager-content-area" class="content-area">
+ <!-- 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="otherCertsTabTitle"></span>
+ <span class="active-tab-label" i18n-content="otherCertsTabTitle">
+ </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>
+ <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>
+ <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="otherCertsTabDescription"></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>
+ <div class="action-area">
+ <div class="button-strip">
+ <button id="certificate-confirm" class="default-button"
+ i18n-content="done">
+ </button>
+ </div>
+ </div>
+</div>
diff --git a/chromium/chrome/browser/resources/options/certificate_manager.js b/chromium/chrome/browser/resources/options/certificate_manager.js
new file mode 100644
index 00000000000..5620b80c6c2
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/certificate_manager.js
@@ -0,0 +1,256 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+ var OptionsPage = options.OptionsPage;
+
+ /////////////////////////////////////////////////////////////////////////////
+ // 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(
+ loadTimeData.getStringF(id + 'DeleteConfirm', data.name),
+ loadTimeData.getString(id + 'DeleteImpact'),
+ loadTimeData.getString('ok'),
+ loadTimeData.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.isCert;
+ var readOnly = !!data && data.readonly;
+ var extractable = !!data && data.extractable;
+ var hasChildren = this.tree.items.length > 0;
+ var isPolicy = !!data && data.policy;
+ this.viewButton.disabled = !isCert;
+ if (this.editButton !== null)
+ this.editButton.disabled = !isCert || isPolicy;
+ if (this.backupButton !== null)
+ this.backupButton.disabled = !isCert || !extractable;
+ if (this.backupAllButton !== null)
+ this.backupAllButton.disabled = !hasChildren;
+ if (this.exportButton !== null)
+ this.exportButton.disabled = !isCert;
+ this.deleteButton.disabled = !isCert || readOnly || isPolicy;
+ },
+
+ /**
+ * 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',
+ loadTimeData.getString('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_);
+
+ $('certificate-confirm').onclick = function() {
+ OptionsPage.closeOverlay();
+ };
+ },
+
+ 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/chromium/chrome/browser/resources/options/certificate_restore_overlay.html b/chromium/chrome/browser/resources/options/certificate_restore_overlay.html
new file mode 100644
index 00000000000..d68fafb4fee
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/certificate_restore_overlay.html
@@ -0,0 +1,19 @@
+<div id="certificateRestoreOverlay" class="page" hidden>
+ <div class="close-button"></div>
+ <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" class="default-button"
+ type="submit" i18n-content="ok">
+ </button>
+ </div>
+ </div>
+</div>
diff --git a/chromium/chrome/browser/resources/options/certificate_restore_overlay.js b/chromium/chrome/browser/resources/options/certificate_restore_overlay.js
new file mode 100644
index 00000000000..08b8ee7920f
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/certificate_restore_overlay.js
@@ -0,0 +1,101 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+ /** @const */ var 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_();
+ },
+
+ /** @override */
+ didShowPage: function() {
+ $('certificateRestorePassword').focus();
+ },
+
+ /**
+ * 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/chromium/chrome/browser/resources/options/certificate_tree.css b/chromium/chrome/browser/resources/options/certificate_tree.css
new file mode 100644
index 00000000000..ee83aed86f8
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/certificate_tree.css
@@ -0,0 +1,17 @@
+/* Copyright (c) 2012 The Chromium Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file. */
+
+span.cert-untrusted {
+ background-color: pink;
+ border: 1px solid red;
+ border-radius: 3px;
+ margin-right: 3px;
+ padding-left: 1px;
+ padding-right: 1px;
+}
+
+span.cert-policy {
+ margin-left: 3px;
+ vertical-align: middle;
+}
diff --git a/chromium/chrome/browser/resources/options/certificate_tree.js b/chromium/chrome/browser/resources/options/certificate_tree.js
new file mode 100644
index 00000000000..057e57b9605
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/certificate_tree.js
@@ -0,0 +1,163 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+ /** @const */ var Tree = cr.ui.Tree;
+ /** @const */ var TreeItem = cr.ui.TreeItem;
+
+ /**
+ * Creates a new tree folder for certificate data.
+ * @param {Object=} data Data used to create a certificate tree folder.
+ * @constructor
+ * @extends {TreeItem}
+ */
+ function CertificateTreeFolder(data) {
+ data.isCert = false;
+ var treeFolder = new TreeItem({
+ label: data.name,
+ data: data
+ });
+ treeFolder.__proto__ = CertificateTreeFolder.prototype;
+
+ if (data.icon)
+ treeFolder.icon = data.icon;
+
+ return treeFolder;
+ }
+
+ CertificateTreeFolder.prototype = {
+ __proto__: TreeItem.prototype,
+
+ /**
+ * The tree path id/.
+ * @type {string}
+ */
+ get pathId() {
+ return this.data.id;
+ }
+ };
+
+ /**
+ * 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) {
+ data.isCert = true;
+ // 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.classList.add('cert-untrusted');
+ badge.textContent = loadTimeData.getString('badgeCertUntrusted');
+ treeItem.labelElement.insertBefore(
+ badge, treeItem.labelElement.firstChild);
+ }
+
+ if (data.policy) {
+ var policyIndicator = new options.ControlledSettingIndicator();
+ policyIndicator.controlledBy = 'policy';
+ policyIndicator.setAttribute(
+ 'textpolicy', loadTimeData.getString('certPolicyInstalled'));
+ policyIndicator.classList.add('cert-policy');
+ treeItem.labelElement.appendChild(policyIndicator);
+ }
+
+ return treeItem;
+ }
+
+ CertificateTreeItem.prototype = {
+ __proto__: TreeItem.prototype,
+
+ /**
+ * The tree path id/.
+ * @type {string}
+ */
+ get pathId() {
+ return this.parentItem.pathId + ',' + 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,
+
+ /** @override */
+ decorate: function() {
+ Tree.prototype.decorate.call(this);
+ this.treeLookup_ = {};
+ },
+
+ /** @override */
+ addAt: function(child, index) {
+ Tree.prototype.addAt.call(this, child, index);
+ if (child.data && child.data.id)
+ this.treeLookup_[child.data.id] = child;
+ },
+
+ /** @override */
+ 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 CertificateTreeFolder(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/chromium/chrome/browser/resources/options/chromeos/2x/warning.png b/chromium/chrome/browser/resources/options/chromeos/2x/warning.png
new file mode 100644
index 00000000000..b28ab3643b1
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/chromeos/2x/warning.png
Binary files differ
diff --git a/chromium/chrome/browser/resources/options/chromeos/OWNERS b/chromium/chrome/browser/resources/options/chromeos/OWNERS
new file mode 100644
index 00000000000..1b75ad21d63
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/chromeos/OWNERS
@@ -0,0 +1,15 @@
+achuith@chromium.org
+dpolukhin@chromium.org
+nkostylev@chromium.org
+stevenjb@chromium.org
+xiyuan@chromium.org
+zelidrag@chromium.org
+
+# Display options.
+mukai@chromium.org
+
+# Network configuration.
+per-file internet_detail*=pneubeck@chromium.org
+per-file network_list*=pneubeck@chromium.org
+per-file preferred_networks*=pneubeck@chromium.org
+per-file proxy_rules_list*=pneubeck@chromium.org
diff --git a/chromium/chrome/browser/resources/options/chromeos/accounts_options.html b/chromium/chrome/browser/resources/options/chromeos/accounts_options.html
new file mode 100644
index 00000000000..d0fc3bd892b
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/chromeos/accounts_options.html
@@ -0,0 +1,69 @@
+<div id="accountsPage" class="page" hidden>
+ <div class="close-button"></div>
+ <h1 i18n-content="accountsPage"></h1>
+ <div class="content-area">
+ <div class="option">
+ <div id="ownerOnlyWarning" hidden>
+ <span i18n-content="owner_only"></span>
+ <span i18n-content="ownerUserId"></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>&nbsp;</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>
+ </div>
+ <div class="action-area">
+ <div class="button-strip">
+ <button id="accounts-options-overlay-confirm" class="default-button"
+ i18n-content="done">
+ </button>
+ </div>
+ </div>
+</div>
diff --git a/chromium/chrome/browser/resources/options/chromeos/accounts_options.js b/chromium/chrome/browser/resources/options/chromeos/accounts_options.js
new file mode 100644
index 00000000000..eddbbaf3d31
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/chromeos/accounts_options.js
@@ -0,0 +1,154 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+ var OptionsPage = options.OptionsPage;
+
+ /////////////////////////////////////////////////////////////////////////////
+ // AccountsOptions class:
+
+ /**
+ * Encapsulated handling of ChromeOS accounts options page.
+ * @constructor
+ */
+ function AccountsOptions(model) {
+ OptionsPage.call(this, 'accounts',
+ loadTimeData.getString('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, do not show the user list.
+ // If the current user is not the owner, or the device is enterprise
+ // managed, show a warning that settings cannot be modified.
+ this.showWhitelist_ = UIAccountTweaks.currentUserIsOwner();
+ if (this.showWhitelist_) {
+ options.accounts.UserList.decorate(userList);
+ } else {
+ $('ownerOnlyWarning').hidden = false;
+ this.managed = AccountsOptions.whitelistIsManaged();
+ }
+
+ this.addEventListener('visibleChange', this.handleVisibleChange_);
+
+ $('useWhitelistCheck').addEventListener('change',
+ this.handleUseWhitelistCheckChange_.bind(this));
+
+ Preferences.getInstance().addEventListener(
+ $('useWhitelistCheck').pref,
+ this.handleUseWhitelistPrefChange_.bind(this));
+
+ $('accounts-options-overlay-confirm').onclick =
+ OptionsPage.closeOverlay.bind(OptionsPage);
+ },
+
+ /**
+ * 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 whitelist is managed by policy or not.
+ */
+ AccountsOptions.whitelistIsManaged = function() {
+ return loadTimeData.getBoolean('whitelist_is_managed');
+ };
+
+ /**
+ * 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/chromium/chrome/browser/resources/options/chromeos/accounts_options_page.css b/chromium/chrome/browser/resources/options/chromeos/accounts_options_page.css
new file mode 100644
index 00000000000..bba94eacfa5
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/chromeos/accounts_options_page.css
@@ -0,0 +1,98 @@
+/* Copyright (c) 2012 The Chromium Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file. */
+
+.user-list-table {
+ border: 1px solid lightgrey;
+ border-collapse: collapse;
+ border-spacing: 0;
+}
+
+.user-name-edit-row {
+ background-color: rgb(235, 239, 250);
+ border: 1px solid lightgrey;
+ padding: 5px;
+}
+
+.user-list-item {
+ padding: 2px;
+}
+
+.user-icon {
+ border: 1px solid black;
+ height: 26px;
+ width: 26px;
+}
+
+.user-email-label {
+ -webkit-margin-start: 10px;
+}
+
+.user-name-label {
+ -webkit-margin-start: 10px;
+ color: darkgray;
+}
+
+.user-email-name-block {
+ -webkit-box-flex: 1;
+ max-width: 318px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.remove-user-button {
+ background-image: -webkit-image-set(
+ url('../../../../../ui/resources/default_100_percent/close_2.png') 1x,
+ url('../../../../../ui/resources/default_200_percent/close_2.png') 2x);
+ height: 16px;
+ width: 16px;
+}
+
+.remove-user-button:hover {
+ background-image: -webkit-image-set(
+ url('../../../../../ui/resources/default_100_percent/close_2_hover.png')
+ 1x,
+ url('../../../../../ui/resources/default_200_percent/close_2_hover.png')
+ 2x);
+}
+
+#userList {
+ height: 166px;
+ padding: 5px;
+ width: 366px;
+}
+
+#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 {
+ -webkit-padding-start: 20px;
+ background-image: url('warning.png');
+ background-repeat: no-repeat;
+ margin-bottom: 10px;
+ margin-top: 10px;
+ min-height: 17px;
+ padding-bottom: 1px;
+}
+
+input#userNameEdit:invalid {
+ background-color: rgb(255, 102, 102);
+}
diff --git a/chromium/chrome/browser/resources/options/chromeos/accounts_user_list.js b/chromium/chrome/browser/resources/options/chromeos/accounts_user_list.js
new file mode 100644
index 00000000000..419ab3b3709
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/chromeos/accounts_user_list.js
@@ -0,0 +1,194 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options.accounts', function() {
+ /** @const */ var List = cr.ui.List;
+ /** @const */ var ListItem = cr.ui.ListItem;
+ /** @const */ var 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',
+
+ /** @override */
+ 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.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 {Object} 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,
+
+ /** @override */
+ 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 ?
+ loadTimeData.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 ?
+ loadTimeData.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/chromium/chrome/browser/resources/options/chromeos/accounts_user_name_edit.js b/chromium/chrome/browser/resources/options/chromeos/accounts_user_name_edit.js
new file mode 100644
index 00000000000..88d2878d11f
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/chromeos/accounts_user_name_edit.js
@@ -0,0 +1,128 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options.accounts', function() {
+ /**
+ * Email alias only, assuming it's a gmail address.
+ * e.g. 'john'
+ * {name: 'john', email: 'john@gmail.com'}
+ * @const
+ */
+ var format1String =
+ '^\\s*([\\w\\.!#\\$%&\'\\*\\+-\\/=\\?\\^`\\{\\|\\}~]+)\\s*$';
+ /**
+ * Email address only.
+ * e.g. 'john@chromium.org'
+ * {name: 'john', email: 'john@chromium.org'}
+ * @const
+ */
+ var 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
+ */
+ var 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.onkeydown = this.handleKeyDown_.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 */ var format1 = new RegExp(format1String);
+ /** @const */ var format2 = new RegExp(format2String);
+ /** @const */ var 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 down event.
+ * @private
+ * @param {!Event} e The keydown event object.
+ */
+ handleKeyDown_: function(e) {
+ if (e.keyIdentifier == 'Enter') {
+ var user = this.parse(this.value);
+ if (user) {
+ var event = new Event('add');
+ event.user = user;
+ this.dispatchEvent(event);
+ }
+ this.select();
+ // Avoid double-handling so the dialog doesn't close.
+ e.stopPropagation();
+ }
+ }
+ };
+
+ return {
+ UserNameEdit: UserNameEdit
+ };
+});
+
diff --git a/chromium/chrome/browser/resources/options/chromeos/bluetooth.css b/chromium/chrome/browser/resources/options/chromeos/bluetooth.css
new file mode 100644
index 00000000000..e6a91e40705
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/chromeos/bluetooth.css
@@ -0,0 +1,162 @@
+/* Copyright (c) 2012 The Chromium Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file. */
+
+.bluetooth-device-list {
+ margin: 10px 0;
+ padding: 5px 10px;
+}
+
+.bluetooth-device[notconnectable] {
+ color: gray;
+}
+
+.bluetooth-device[connected] {
+ font-weight: bold; /* semibold */
+}
+
+#bluetooth-options .bluetooth-device-list {
+ margin: 0 10px;
+}
+
+#bluetooth-options .button-strip {
+ -webkit-box-pack: justify;
+}
+
+#bluetooth-options .button-strip #bluetooth-scanning-label {
+ -webkit-box-flex: 1;
+ display: block;
+}
+
+#bluetooth-scanning-label {
+ -webkit-margin-start: 5px;
+ color: #999;
+}
+
+#bluetooth-scanning-icon {
+ height: 20px;
+ opacity: 0.66;
+ vertical-align: middle;
+ width: 20px;
+}
+
+#bluetooth-paired-devices-list {
+ min-height: 96px !important;
+}
+
+#bluetooth-paired-devices-list,
+#bluetooth-unpaired-devices-list {
+ /* Prevent dialog from expanding if many devices are found. */
+ max-height: 192px;
+ overflow-x: hidden;
+ overflow-y: auto;
+}
+
+.bluetooth-empty-list-label {
+ box-sizing: border-box;
+ color: #999;
+ padding-top: 32px;
+ text-align: center;
+}
+
+#bluetooth-paired-devices-list-empty-placeholder {
+ height: 96px;
+}
+
+#bluetooth-unpaired-devices-list-empty-placeholder {
+ height: 192px;
+}
+
+/* Fix the dimensions of the message area so that the dialog does not change
+ change size during the pairing process as the message changes. Sized
+ generously to accomodate the longest of the messages. */
+#bluetooth-pairing-message-area {
+ display: table;
+ height: 160px;
+ padding: 6px 0 !important;
+ width: 420px;
+}
+
+/* Force the message to be vertical centered so that a shorter message does not
+ look out of place when there is room for a much longer message. */
+#bluetooth-pairing-message-contents {
+ display: table-cell;
+ vertical-align: middle;
+}
+
+#bluetooth-pairing-instructions,
+#bluetooth-pairing-passkey-display,
+#bluetooth-pairing-passkey-entry,
+#bluetooth-pairing-pincode-entry,
+#bluetooth-passkey,
+#bluetooth-pincode {
+ text-align: center;
+}
+
+#bluetooth-pairing-instructions {
+ margin: 10px;
+}
+
+#bluetooth-pairing-passkey-display,
+#bluetooth-pairing-passkey-entry,
+#bluetooth-pairing-pincode-entry {
+ margin: 40px 0;
+}
+
+.bluetooth-keyboard-button {
+ -webkit-padding-end: 15px;
+ -webkit-padding-start: 15px;
+ background-image: -webkit-gradient(linear,
+ left top,
+ left bottom,
+ color-stop(0, #e9e9e9),
+ color-stop(1, #f5f5f5));
+ border: 1px solid #d4d4d4;
+ border-radius: 4px;
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.07),
+ inset 0 1px 1px 1px #fff,
+ inset 0 -1px 1px 1px #ddd;
+ color: #666;
+ display: inline-block;
+ font-size: 14px;
+ font-weight: 600;
+ height: 38px;
+ line-height: 38px;
+ margin: 0 10px 0 0;
+ position: relative;
+ text-align: center;
+ text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
+ vertical-align: baseline;
+}
+
+.bluetooth-keyboard-button:last-child {
+ margin: 0;
+}
+
+#bluetooth-enter-key {
+ min-width: 54px;
+}
+
+.bluetooth-passkey-char {
+ -webkit-margin-start: 45px;
+ color: #999;
+ font-size: 20px;
+ font-weight: 600; /* semibold */
+ padding-bottom: 5px;
+}
+
+.bluetooth-passkey-char:first-child {
+ -webkit-margin-start: 0;
+}
+
+.bluetooth-keyboard-button.key-typed {
+ border: 1px solid #ccc;
+ box-shadow: 0 0 0 1px #888,
+ inset 0 1px 1px 1px #fff,
+ inset 0 -1px 1px 1px #eee;
+ color: #222;
+}
+
+.bluetooth-keyboard-button.key-pin {
+ color: #222;
+}
diff --git a/chromium/chrome/browser/resources/options/chromeos/bluetooth_add_device_overlay.html b/chromium/chrome/browser/resources/options/chromeos/bluetooth_add_device_overlay.html
new file mode 100644
index 00000000000..534d9634d30
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/chromeos/bluetooth_add_device_overlay.html
@@ -0,0 +1,23 @@
+<div id="bluetooth-options" class="page" hidden>
+ <div class="close-button"></div>
+ <h1 i18n-content="bluetoothAddDeviceTitle"></h1>
+ <div class="settings-list bluetooth-device-list content-area">
+ <list id="bluetooth-unpaired-devices-list"></list>
+ <div id="bluetooth-unpaired-devices-list-empty-placeholder"
+ class="bluetooth-empty-list-label" hidden>
+ <span i18n-content="bluetoothNoDevicesFound"></span>
+ </div>
+ </div>
+ <div class="action-area button-strip">
+ <button id="bluetooth-add-device-cancel-button" type="reset"
+ i18n-content="cancel">
+ </button>
+ <button id="bluetooth-add-device-apply-button" type="submit"
+ class="default-button" i18n-content="bluetoothConnectDevice" disabled>
+ </button>
+ <span id="bluetooth-scanning-label"
+ i18n-content="bluetoothScanning">
+ </span>
+ <div id="bluetooth-scanning-icon" class="inline-spinner"></div>
+ </div>
+</div>
diff --git a/chromium/chrome/browser/resources/options/chromeos/bluetooth_add_device_overlay.js b/chromium/chrome/browser/resources/options/chromeos/bluetooth_add_device_overlay.js
new file mode 100644
index 00000000000..3b076a1f26f
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/chromeos/bluetooth_add_device_overlay.js
@@ -0,0 +1,95 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+ /** @const */ var OptionsPage = options.OptionsPage;
+
+ /**
+ * Encapsulated handling of the Bluetooth options page.
+ * @constructor
+ */
+ function BluetoothOptions() {
+ OptionsPage.call(this,
+ 'bluetooth',
+ loadTimeData.getString('bluetoothOptionsPageTabTitle'),
+ 'bluetooth-options');
+ }
+
+ cr.addSingletonGetter(BluetoothOptions);
+
+ BluetoothOptions.prototype = {
+ __proto__: OptionsPage.prototype,
+
+ /**
+ * The list of available (unpaired) bluetooth devices.
+ * @type {DeletableItemList}
+ * @private
+ */
+ deviceList_: null,
+
+ /** @override */
+ initializePage: function() {
+ OptionsPage.prototype.initializePage.call(this);
+ this.createDeviceList_();
+
+ $('bluetooth-add-device-cancel-button').onclick = function(event) {
+ chrome.send('stopBluetoothDeviceDiscovery');
+ OptionsPage.closeOverlay();
+ };
+
+ var self = this;
+ $('bluetooth-add-device-apply-button').onclick = function(event) {
+ var device = self.deviceList_.selectedItem;
+ var address = device.address;
+ chrome.send('stopBluetoothDeviceDiscovery');
+ OptionsPage.closeOverlay();
+ device.pairing = 'bluetoothStartConnecting';
+ options.BluetoothPairing.showDialog(device);
+ chrome.send('updateBluetoothDevice', [address, 'connect']);
+ };
+
+ $('bluetooth-unpaired-devices-list').addEventListener('change',
+ function() {
+ var item = $('bluetooth-unpaired-devices-list').selectedItem;
+ // The "bluetooth-add-device-apply-button" should be enabled for devices
+ // that can be paired or remembered. Devices not supporting pairing will
+ // be just remembered and later reported as "item.paired" = true. The
+ // button should be disabled in any other case:
+ // * No item is selected (item is undefined).
+ // * Paired devices (item.paired is true) are already paired and a new
+ // pairing attempt will fail. Paired devices could appear in this list
+ // shortly after the pairing initiated in another window finishes.
+ // * "Connecting" devices (item.connecting is true) are in the process
+ // of a pairing or connection. Another attempt to pair before the
+ // ongoing pair finishes will fail, so the button should be disabled.
+ var disabled = !item || item.paired || item.connecting;
+ $('bluetooth-add-device-apply-button').disabled = disabled;
+ });
+ },
+
+ /**
+ * Creates, decorates and initializes the bluetooth device list.
+ * @private
+ */
+ createDeviceList_: function() {
+ this.deviceList_ = $('bluetooth-unpaired-devices-list');
+ options.system.bluetooth.BluetoothDeviceList.decorate(this.deviceList_);
+ }
+ };
+
+ /**
+ * Automatically start the device discovery process if the
+ * "Add device" dialog is visible.
+ */
+ BluetoothOptions.updateDiscovery = function() {
+ var page = BluetoothOptions.getInstance();
+ if (page && page.visible)
+ chrome.send('findBluetoothDevices');
+ }
+
+ // Export
+ return {
+ BluetoothOptions: BluetoothOptions
+ };
+});
diff --git a/chromium/chrome/browser/resources/options/chromeos/bluetooth_device_list.js b/chromium/chrome/browser/resources/options/chromeos/bluetooth_device_list.js
new file mode 100644
index 00000000000..ede390fe4db
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/chromeos/bluetooth_device_list.js
@@ -0,0 +1,347 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options.system.bluetooth', function() {
+ /** @const */ var ArrayDataModel = cr.ui.ArrayDataModel;
+ /** @const */ var DeletableItem = options.DeletableItem;
+ /** @const */ var DeletableItemList = options.DeletableItemList;
+ /** @const */ var ListSingleSelectionModel = cr.ui.ListSingleSelectionModel;
+
+ /**
+ * Bluetooth settings constants.
+ */
+ function Constants() {}
+
+ /**
+ * Creates a new bluetooth list item.
+ * @param {{name: string,
+ * address: string,
+ * paired: boolean,
+ * connected: boolean,
+ * connecting: boolean,
+ * connectable: boolean,
+ * pairing: string|undefined,
+ * passkey: number|undefined,
+ * pincode: string|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, but not for connecting
+ // devices.
+ el.deletable = device.paired && !device.connecting;
+ return el;
+ }
+
+ BluetoothListItem.prototype = {
+ __proto__: DeletableItem.prototype,
+
+ /**
+ * Description of the Bluetooth device.
+ * @type {{name: string,
+ * address: string,
+ * paired: boolean,
+ * connected: boolean,
+ * connecting: boolean,
+ * connectable: boolean,
+ * pairing: string|undefined,
+ * passkey: number|undefined,
+ * pincode: string|undefined,
+ * entered: number|undefined}}
+ */
+ data: null,
+
+ /** @override */
+ decorate: function() {
+ DeletableItem.prototype.decorate.call(this);
+ var label = this.ownerDocument.createElement('div');
+ label.className = 'bluetooth-device-label';
+ this.classList.add('bluetooth-device');
+ // There are four kinds of devices we want to distinguish:
+ // * Connecting devices: in bold with a "connecting" label,
+ // * Connected devices: in bold,
+ // * Paired, not connected but connectable devices: regular and
+ // * Paired, not connected and not connectable devices: grayed out.
+ this.connected = this.data.connecting ||
+ (this.data.paired && this.data.connected);
+ this.notconnectable = this.data.paired && !this.data.connecting &&
+ !this.data.connected && !this.data.connectable;
+ // "paired" devices are those that are remembered but not connected.
+ this.paired = this.data.paired && !this.data.connected &&
+ this.data.connectable;
+
+ var content = this.data.name;
+ // Update the device's label according to its state. A "connecting" device
+ // can be in the process of connecting and pairing, so we check connecting
+ // first.
+ if (this.data.connecting) {
+ content = loadTimeData.getStringF('bluetoothDeviceConnecting',
+ this.data.name);
+ }
+ 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,
+
+ /**
+ * Height of a list entry in px.
+ * @type {number}
+ * @private
+ */
+ itemHeight_: 32,
+
+ /**
+ * Width of a list entry in px.
+ * @type {number}
+ * @private.
+ */
+ itemWidth_: 400,
+
+ /** @override */
+ decorate: function() {
+ DeletableItemList.prototype.decorate.call(this);
+ // Force layout of all items even if not in the viewport to address
+ // errors in scroll positioning when the list is hidden during initial
+ // layout. The impact on performance should be minimal given that the
+ // list is not expected to grow very large. Fixed height items are also
+ // required to avoid caching incorrect sizes during layout of a hidden
+ // list.
+ this.autoExpands = true;
+ this.fixedHeight = true;
+ this.clear();
+ this.selectionModel = new ListSingleSelectionModel();
+ },
+
+ /**
+ * 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,
+ * paired: boolean,
+ * connected: boolean,
+ * connecting: boolean,
+ * connectable: boolean,
+ * pairing: string|undefined,
+ * passkey: number|undefined,
+ * pincode: string|undefined,
+ * entered: number|undefined}} device
+ * Description of the bluetooth device.
+ * @return {boolean} True if the devies was successfully added or updated.
+ */
+ appendDevice: function(device) {
+ var selectedDevice = this.getSelectedDevice_();
+ 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);
+ }
+ this.updateListVisibility_();
+ if (selectedDevice)
+ this.setSelectedDevice_(selectedDevice);
+ return true;
+ },
+
+ /**
+ * Forces a revailidation of the list content. Deleting a single item from
+ * the list results in a stale cache requiring an invalidation.
+ * @param {string=} opt_selection Optional address of device to select
+ * after refreshing the list.
+ */
+ refresh: function(opt_selection) {
+ // TODO(kevers): Investigate if the stale cache issue can be fixed in
+ // cr.ui.list.
+ var selectedDevice = opt_selection ? opt_selection :
+ this.getSelectedDevice_();
+ this.invalidate();
+ this.redraw();
+ if (selectedDevice)
+ this.setSelectedDevice_(selectedDevice);
+ },
+
+ /**
+ * Retrieves the address of the selected device, or null if no device is
+ * selected.
+ * @return {?string} Address of selected device or null.
+ * @private
+ */
+ getSelectedDevice_: function() {
+ var selection = this.selectedItem;
+ if (selection)
+ return selection.address;
+ return null;
+ },
+
+ /**
+ * Selects the device with the matching address.
+ * @param {string} address The unique address of the device.
+ * @private
+ */
+ setSelectedDevice_: function(address) {
+ var index = this.find(address);
+ if (index != undefined)
+ this.selectionModel.selectRange(index, index);
+ },
+
+ /**
+ * Perges all devices from the list.
+ */
+ clear: function() {
+ this.dataModel = new ArrayDataModel([]);
+ this.redraw();
+ this.updateListVisibility_();
+ },
+
+ /**
+ * 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;
+ }
+ },
+
+ /** @override */
+ createItem: function(entry) {
+ return new BluetoothListItem(entry);
+ },
+
+ /**
+ * Overrides the default implementation, which is used to compute the
+ * size of an element in the list. The default implementation relies
+ * on adding a placeholder item to the list and fetching its size and
+ * position. This strategy does not work if an item is added to the list
+ * while it is hidden, as the computed metrics will all be zero in that
+ * case.
+ * @return {{height: number, marginTop: number, marginBottom: number,
+ * width: number, marginLeft: number, marginRight: number}}
+ * The height and width of the item, taking margins into account,
+ * and the margins themselves.
+ */
+ measureItem: function() {
+ return {
+ height: this.itemHeight_,
+ marginTop: 0,
+ marginBotton: 0,
+ width: this.itemWidth_,
+ marginLeft: 0,
+ marginRight: 0
+ };
+ },
+
+ /**
+ * Override the default implementation to return a predetermined size,
+ * which in turns allows proper layout of items even if the list is hidden.
+ * @return {height: number, width: number} Dimensions of a single item in
+ * the list of bluetooth device.
+ * @private.
+ */
+ getDefaultItemSize_: function() {
+ return {
+ height: this.itemHeight_,
+ width: this.itemWidth_
+ };
+ },
+
+ /**
+ * Override base implementation of handleClick_, which unconditionally
+ * removes the item. In this case, removal of the element is deferred
+ * pending confirmation from the Bluetooth adapter.
+ * @param {Event} e The click event object.
+ * @private
+ */
+ handleClick_: function(e) {
+ if (this.disabled)
+ return;
+
+ var target = e.target;
+ if (!target.classList.contains('row-delete-button'))
+ return;
+
+ var item = this.getListItemAncestor(target);
+ var selected = this.selectionModel.selectedIndex;
+ var index = this.getIndexOfListItem(item);
+ if (item && item.deletable) {
+ if (selected != index)
+ this.setSelectedDevice_(item.data.address);
+ // Device is busy until we hear back from the Bluetooth adapter.
+ // Prevent double removal request.
+ item.deletable = false;
+ // TODO(kevers): Provide visual feedback that the device is busy.
+
+ // Inform the bluetooth adapter that we are disconnecting or
+ // forgetting the device.
+ chrome.send('updateBluetoothDevice',
+ [item.data.address, item.connected ? 'disconnect' : 'forget']);
+ }
+ },
+
+ /** @override */
+ deleteItemAtIndex: function(index) {
+ var selectedDevice = this.getSelectedDevice_();
+ this.dataModel.splice(index, 1);
+ this.refresh(selectedDevice);
+ this.updateListVisibility_();
+ },
+
+ /**
+ * If the list has an associated empty list placholder then update the
+ * visibility of the list and placeholder.
+ * @private
+ */
+ updateListVisibility_: function() {
+ var empty = this.dataModel.length == 0;
+ var listPlaceHolderID = this.id + '-empty-placeholder';
+ if ($(listPlaceHolderID)) {
+ if (this.hidden != empty) {
+ this.hidden = empty;
+ $(listPlaceHolderID).hidden = !empty;
+ this.refresh();
+ }
+ }
+ },
+ };
+
+ cr.defineProperty(BluetoothListItem, 'connected', cr.PropertyKind.BOOL_ATTR);
+
+ cr.defineProperty(BluetoothListItem, 'paired', cr.PropertyKind.BOOL_ATTR);
+
+ cr.defineProperty(BluetoothListItem, 'connecting', cr.PropertyKind.BOOL_ATTR);
+
+ cr.defineProperty(BluetoothListItem, 'notconnectable',
+ cr.PropertyKind.BOOL_ATTR);
+
+ return {
+ BluetoothListItem: BluetoothListItem,
+ BluetoothDeviceList: BluetoothDeviceList,
+ Constants: Constants
+ };
+});
diff --git a/chromium/chrome/browser/resources/options/chromeos/bluetooth_pair_device_overlay.html b/chromium/chrome/browser/resources/options/chromeos/bluetooth_pair_device_overlay.html
new file mode 100644
index 00000000000..95b9c0339d5
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/chromeos/bluetooth_pair_device_overlay.html
@@ -0,0 +1,28 @@
+<div id="bluetooth-pairing" class="page" hidden>
+ <div class="close-button"></div>
+ <h1 i18n-content="bluetoothAddDeviceTitle"></h1>
+ <div id="bluetooth-pairing-message-area" class="content-area">
+ <div id="bluetooth-pairing-message-contents">
+ <div id="bluetooth-pairing-instructions"></div>
+ <div id="bluetooth-pairing-passkey-display" hidden></div>
+ <div id="bluetooth-pairing-passkey-entry" hidden>
+ <input id="bluetooth-passkey" maxlength="6" type="text">
+ </div>
+ <div id="bluetooth-pairing-pincode-entry" hidden>
+ <input id="bluetooth-pincode" maxlength="16" type="text">
+ </div>
+ </div>
+ </div>
+ <div class="action-area button-strip">
+ <button id="bluetooth-pair-device-cancel-button" type="reset"
+ i18n-content="cancel" hidden></button>
+ <button id="bluetooth-pair-device-connect-button" type="reset"
+ i18n-content="bluetoothConnectDevice" hidden></button>
+ <button id="bluetooth-pair-device-reject-button" type="reset"
+ i18n-content="bluetoothRejectPasskey" hidden></button>
+ <button id="bluetooth-pair-device-accept-button" type="reset"
+ i18n-content="bluetoothAcceptPasskey" hidden></button>
+ <button id="bluetooth-pair-device-dismiss-button" type="reset"
+ i18n-content="bluetoothDismissError" hidden></button>
+ </div>
+</div>
diff --git a/chromium/chrome/browser/resources/options/chromeos/bluetooth_pair_device_overlay.js b/chromium/chrome/browser/resources/options/chromeos/bluetooth_pair_device_overlay.js
new file mode 100644
index 00000000000..a206e8bf72a
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/chromeos/bluetooth_pair_device_overlay.js
@@ -0,0 +1,398 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+ /** @const */ var OptionsPage = options.OptionsPage;
+
+ /**
+ * Enumeration of possible states during pairing. The value associated with
+ * each state maps to a localized string in the global variable
+ * |loadTimeData|.
+ * @enum {string}
+ */
+ var PAIRING = {
+ STARTUP: 'bluetoothStartConnecting',
+ ENTER_PIN_CODE: 'bluetoothEnterPinCode',
+ ENTER_PASSKEY: 'bluetoothEnterPasskey',
+ REMOTE_PIN_CODE: 'bluetoothRemotePinCode',
+ REMOTE_PASSKEY: 'bluetoothRemotePasskey',
+ CONFIRM_PASSKEY: 'bluetoothConfirmPasskey',
+ CONNECT_FAILED: 'bluetoothConnectFailed',
+ CANCELED: 'bluetoothPairingCanceled',
+ DISMISSED: 'bluetoothPairingDismissed', // pairing dismissed(succeeded or
+ // canceled).
+ };
+
+ /**
+ * List of IDs for conditionally visible elements in the dialog.
+ * @type {Array.<string>}
+ * @const
+ */
+ var ELEMENTS = ['bluetooth-pairing-passkey-display',
+ 'bluetooth-pairing-passkey-entry',
+ 'bluetooth-pairing-pincode-entry',
+ 'bluetooth-pair-device-connect-button',
+ 'bluetooth-pair-device-cancel-button',
+ 'bluetooth-pair-device-accept-button',
+ 'bluetooth-pair-device-reject-button',
+ 'bluetooth-pair-device-dismiss-button'];
+
+ /**
+ * Encapsulated handling of the Bluetooth device pairing page.
+ * @constructor
+ */
+ function BluetoothPairing() {
+ OptionsPage.call(this,
+ 'bluetoothPairing',
+ loadTimeData.getString('bluetoothOptionsPageTabTitle'),
+ 'bluetooth-pairing');
+ }
+
+ cr.addSingletonGetter(BluetoothPairing);
+
+ BluetoothPairing.prototype = {
+ __proto__: OptionsPage.prototype,
+
+ /**
+ * Description of the bluetooth device.
+ * @type {{name: string,
+ * address: string,
+ * paired: boolean,
+ * connected: boolean,
+ * connecting: boolean,
+ * connectable: boolean,
+ * pairing: string|undefined,
+ * passkey: number|undefined,
+ * pincode: string|undefined,
+ * entered: number|undefined}}
+ * @private.
+ */
+ device_: null,
+
+ /**
+ * Can the dialog be programmatically dismissed.
+ * @type {boolean}
+ */
+ dismissible_: true,
+
+ /** @override */
+ initializePage: function() {
+ OptionsPage.prototype.initializePage.call(this);
+ var self = this;
+ $('bluetooth-pair-device-cancel-button').onclick = function() {
+ OptionsPage.closeOverlay();
+ };
+ $('bluetooth-pair-device-reject-button').onclick = function() {
+ chrome.send('updateBluetoothDevice',
+ [self.device_.address, 'reject']);
+ OptionsPage.closeOverlay();
+ };
+ $('bluetooth-pair-device-connect-button').onclick = function() {
+ var args = [self.device_.address, 'connect'];
+ var passkey = self.device_.passkey;
+ if (passkey)
+ args.push(String(passkey));
+ else if (!$('bluetooth-pairing-passkey-entry').hidden)
+ args.push($('bluetooth-passkey').value);
+ else if (!$('bluetooth-pairing-pincode-entry').hidden)
+ args.push($('bluetooth-pincode').value);
+ chrome.send('updateBluetoothDevice', args);
+ // Prevent sending a 'connect' command twice.
+ $('bluetooth-pair-device-connect-button').disabled = true;
+ };
+ $('bluetooth-pair-device-accept-button').onclick = function() {
+ chrome.send('updateBluetoothDevice',
+ [self.device_.address, 'accept']);
+ OptionsPage.closeOverlay();
+ };
+ $('bluetooth-pair-device-dismiss-button').onclick = function() {
+ OptionsPage.closeOverlay();
+ };
+ $('bluetooth-passkey').oninput = function() {
+ var inputField = $('bluetooth-passkey');
+ var value = inputField.value;
+ // Note that using <input type="number"> is insufficient to restrict
+ // the input as it allows negative numbers and does not limit the
+ // number of charactes typed even if a range is set. Furthermore,
+ // it sometimes produces strange repaint artifacts.
+ var filtered = value.replace(/[^0-9]/g, '');
+ if (filtered != value)
+ inputField.value = filtered;
+ $('bluetooth-pair-device-connect-button').disabled =
+ inputField.value.length == 0;
+ }
+ $('bluetooth-pincode').oninput = function() {
+ $('bluetooth-pair-device-connect-button').disabled =
+ $('bluetooth-pincode').value.length == 0;
+ }
+ $('bluetooth-passkey').addEventListener('keydown',
+ this.keyDownEventHandler_.bind(this));
+ $('bluetooth-pincode').addEventListener('keydown',
+ this.keyDownEventHandler_.bind(this));
+ },
+
+ /** @override */
+ didClosePage: function() {
+ if (this.device_.pairing != PAIRING.DISMISSED &&
+ this.device_.pairing != PAIRING.CONNECT_FAILED) {
+ this.device_.pairing = PAIRING.CANCELED;
+ chrome.send('updateBluetoothDevice',
+ [this.device_.address, 'cancel']);
+ }
+ },
+
+ /**
+ * Override to prevent showing the overlay if the Bluetooth device details
+ * have not been specified. Prevents showing an empty dialog if the user
+ * quits and restarts Chrome while in the process of pairing with a device.
+ * @return {boolean} True if the overlay can be displayed.
+ */
+ canShowPage: function() {
+ return this.device_ && this.device_.address && this.device_.pairing;
+ },
+
+ /**
+ * Sets input focus on the passkey or pincode field if appropriate.
+ */
+ didShowPage: function() {
+ if (!$('bluetooth-pincode').hidden)
+ $('bluetooth-pincode').focus();
+ else if (!$('bluetooth-passkey').hidden)
+ $('bluetooth-passkey').focus();
+ },
+
+ /**
+ * Configures the overlay for pairing a device.
+ * @param {Object} device Description of the bluetooth device.
+ */
+ update: function(device) {
+ this.device_ = {};
+ for (key in device)
+ this.device_[key] = device[key];
+ // Update the pairing instructions.
+ var instructionsEl = $('bluetooth-pairing-instructions');
+ this.clearElement_(instructionsEl);
+ this.dismissible_ = ('dismissible' in device) ?
+ device.dismissible : true;
+
+ var message = loadTimeData.getString(device.pairing);
+ message = message.replace('%1', this.device_.name);
+ instructionsEl.textContent = message;
+
+ // Update visibility of dialog elements.
+ if (this.device_.passkey) {
+ this.updatePasskey_();
+ if (this.device_.pairing == PAIRING.CONFIRM_PASSKEY) {
+ // Confirming a match between displayed passkeys.
+ this.displayElements_(['bluetooth-pairing-passkey-display',
+ 'bluetooth-pair-device-accept-button',
+ 'bluetooth-pair-device-reject-button']);
+ } else {
+ // Remote entering a passkey.
+ this.displayElements_(['bluetooth-pairing-passkey-display',
+ 'bluetooth-pair-device-cancel-button']);
+ }
+ } else if (this.device_.pincode) {
+ this.updatePinCode_();
+ this.displayElements_(['bluetooth-pairing-passkey-display',
+ 'bluetooth-pair-device-cancel-button']);
+ } else if (this.device_.pairing == PAIRING.ENTER_PIN_CODE) {
+ // Prompting the user to enter a PIN code.
+ this.displayElements_(['bluetooth-pairing-pincode-entry',
+ 'bluetooth-pair-device-connect-button',
+ 'bluetooth-pair-device-cancel-button']);
+ $('bluetooth-pincode').value = '';
+ } else if (this.device_.pairing == PAIRING.ENTER_PASSKEY) {
+ // Prompting the user to enter a passkey.
+ this.displayElements_(['bluetooth-pairing-passkey-entry',
+ 'bluetooth-pair-device-connect-button',
+ 'bluetooth-pair-device-cancel-button']);
+ $('bluetooth-passkey').value = '';
+ } else if (this.device_.pairing == PAIRING.STARTUP) {
+ // Starting the pairing process.
+ this.displayElements_(['bluetooth-pair-device-cancel-button']);
+ } else {
+ // Displaying an error message.
+ this.displayElements_(['bluetooth-pair-device-dismiss-button']);
+ }
+ // User is required to enter a passkey or pincode before the connect
+ // button can be enabled. The 'oninput' methods for the input fields
+ // determine when the connect button becomes active.
+ $('bluetooth-pair-device-connect-button').disabled = true;
+ },
+
+ /**
+ * Handles the ENTER key for the passkey or pincode entry field.
+ * @return {Event} a keydown event.
+ * @private
+ */
+ keyDownEventHandler_: function(event) {
+ /** @const */ var ENTER_KEY_CODE = 13;
+ if (event.keyCode == ENTER_KEY_CODE) {
+ var button = $('bluetooth-pair-device-connect-button');
+ if (!button.hidden)
+ button.click();
+ }
+ },
+
+ /**
+ * Updates the visibility of elements in the dialog.
+ * @param {Array.<string>} list List of conditionally visible elements that
+ * are to be made visible.
+ * @private
+ */
+ displayElements_: function(list) {
+ var enabled = {};
+ for (var i = 0; i < list.length; i++) {
+ var key = list[i];
+ enabled[key] = true;
+ }
+ for (var i = 0; i < ELEMENTS.length; i++) {
+ var key = ELEMENTS[i];
+ $(key).hidden = !enabled[key];
+ }
+ },
+
+ /**
+ * Removes all children from an element.
+ * @param {!Element} element Target element to clear.
+ */
+ clearElement_: function(element) {
+ var child = element.firstChild;
+ while (child) {
+ element.removeChild(child);
+ child = element.firstChild;
+ }
+ },
+
+ /**
+ * Formats an element for displaying the passkey.
+ */
+ updatePasskey_: function() {
+ var passkeyEl = $('bluetooth-pairing-passkey-display');
+ var keyClass = this.device_.pairing == PAIRING.REMOTE_PASSKEY ?
+ 'bluetooth-keyboard-button' : 'bluetooth-passkey-char';
+ this.clearElement_(passkeyEl);
+ var key = String(this.device_.passkey);
+ // Passkey should always have 6 digits.
+ key = '000000'.substring(0, 6 - key.length) + key;
+ var progress = this.device_.entered;
+ for (var i = 0; i < key.length; i++) {
+ var keyEl = document.createElement('span');
+ keyEl.textContent = key.charAt(i);
+ keyEl.className = keyClass;
+ if (progress == undefined)
+ keyEl.classList.add('key-pin');
+ else if (i < progress)
+ keyEl.classList.add('key-typed');
+ passkeyEl.appendChild(keyEl);
+ }
+ if (this.device_.pairing == PAIRING.REMOTE_PASSKEY) {
+ // Add enter key.
+ var label = loadTimeData.getString('bluetoothEnterKey');
+ var keyEl = document.createElement('span');
+ keyEl.textContent = label;
+ keyEl.className = keyClass;
+ keyEl.id = 'bluetooth-enter-key';
+ if (progress == undefined)
+ keyEl.classList.add('key-pin');
+ else if (progress > key.length)
+ keyEl.classList.add('key-typed');
+ passkeyEl.appendChild(keyEl);
+ }
+ passkeyEl.hidden = false;
+ },
+
+ /**
+ * Formats an element for displaying the PIN code.
+ */
+ updatePinCode_: function() {
+ var passkeyEl = $('bluetooth-pairing-passkey-display');
+ var keyClass = this.device_.pairing == PAIRING.REMOTE_PIN_CODE ?
+ 'bluetooth-keyboard-button' : 'bluetooth-passkey-char';
+ this.clearElement_(passkeyEl);
+ var key = String(this.device_.pincode);
+ for (var i = 0; i < key.length; i++) {
+ var keyEl = document.createElement('span');
+ keyEl.textContent = key.charAt(i);
+ keyEl.className = keyClass;
+ keyEl.classList.add('key-pin');
+ passkeyEl.appendChild(keyEl);
+ }
+ if (this.device_.pairing == PAIRING.REMOTE_PIN_CODE) {
+ // Add enter key.
+ var label = loadTimeData.getString('bluetoothEnterKey');
+ var keyEl = document.createElement('span');
+ keyEl.textContent = label;
+ keyEl.className = keyClass;
+ keyEl.classList.add('key-pin');
+ keyEl.id = 'bluetooth-enter-key';
+ passkeyEl.appendChild(keyEl);
+ }
+ passkeyEl.hidden = false;
+ },
+ };
+
+ /**
+ * Configures the device pairing instructions and displays the pairing
+ * overlay.
+ * @param {Object} device Description of the Bluetooth device.
+ */
+ BluetoothPairing.showDialog = function(device) {
+ BluetoothPairing.getInstance().update(device);
+ OptionsPage.showPageByName('bluetoothPairing', false);
+ };
+
+ /**
+ * Displays a message from the Bluetooth adapter.
+ * @param {{string: label,
+ * string: address} data Data for constructing the message.
+ */
+ BluetoothPairing.showMessage = function(data) {
+ var name = data.address;
+ if (name.length == 0)
+ return;
+ var dialog = BluetoothPairing.getInstance();
+ if (dialog.device_ && name == dialog.device_.address &&
+ dialog.device_.pairing == PAIRING.CANCELED) {
+ // Do not show any error message after cancelation of the pairing.
+ return;
+ }
+
+ var list = $('bluetooth-paired-devices-list');
+ if (list) {
+ var index = list.find(name);
+ if (index == undefined) {
+ list = $('bluetooth-unpaired-devices-list');
+ index = list.find(name);
+ }
+ if (index != undefined) {
+ var entry = list.dataModel.item(index);
+ if (entry && entry.name)
+ name = entry.name;
+ }
+ }
+ BluetoothPairing.showDialog({name: name,
+ address: data.address,
+ pairing: data.label,
+ dismissible: false});
+ };
+
+ /**
+ * Closes the Bluetooth pairing dialog.
+ */
+ BluetoothPairing.dismissDialog = function() {
+ var overlay = OptionsPage.getTopmostVisiblePage();
+ var dialog = BluetoothPairing.getInstance();
+ if (overlay == dialog && dialog.dismissible_) {
+ dialog.device_.pairing = PAIRING.DISMISSED;
+ OptionsPage.closeOverlay();
+ }
+ };
+
+ // Export
+ return {
+ BluetoothPairing: BluetoothPairing
+ };
+});
diff --git a/chromium/chrome/browser/resources/options/chromeos/browser_options.css b/chromium/chrome/browser/resources/options/chromeos/browser_options.css
new file mode 100644
index 00000000000..e02cae24c7d
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/chromeos/browser_options.css
@@ -0,0 +1,7 @@
+/* Copyright 2013 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. */
+
+#sync-section {
+ min-height: 64px;
+}
diff --git a/chromium/chrome/browser/resources/options/chromeos/change_picture_options.css b/chromium/chrome/browser/resources/options/chromeos/change_picture_options.css
new file mode 100644
index 00000000000..ab0d497df17
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/chromeos/change_picture_options.css
@@ -0,0 +1,195 @@
+/* Copyright (c) 2012 The Chromium Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file. */
+
+#user-images-area {
+ display: -webkit-box;
+}
+
+#user-image-grid {
+ -webkit-user-drag: none;
+ -webkit-user-select: none;
+ height: 264px;
+ margin: 10px;
+ outline: none;
+ /* Necessary for correct metrics calculation by grid.js. */
+ overflow: hidden;
+ padding: 0;
+ width: 530px;
+}
+
+#user-image-grid * {
+ margin: 0;
+ padding: 0;
+}
+
+#user-image-grid img {
+ background-color: white;
+ height: 64px;
+ vertical-align: middle;
+ width: 64px;
+}
+
+#user-image-grid > li {
+ border: 1px solid rgba(0, 0, 0, 0.15);
+ border-radius: 4px;
+ display: inline-block;
+ margin: 8px;
+ padding: 3px;
+}
+
+#user-image-grid [selected] {
+ border: 2px solid rgb(0, 102, 204);
+ padding: 2px;
+}
+
+/**
+ * #user-image-preview can have the following classes:
+ * .default-image: one of the default images is selected (including the grey
+ * silhouette);
+ * .profile-image: profile image is selected;
+ * .online: camera is streaming video;
+ * .camera: camera (live or photo) is selected;
+ * .live: camera is in live mode (no photo taken yet/last photo removed).
+ */
+
+#user-image-preview {
+ margin: 20px 10px 0 0;
+ max-width: 220px;
+ position: relative;
+}
+
+#user-image-preview .perspective-box {
+ -webkit-perspective: 600px;
+}
+
+#user-image-preview-img {
+ background: white;
+ border: solid 1px #cacaca;
+ border-radius: 4px;
+ max-height: 220px;
+ max-width: 220px;
+ padding: 2px;
+}
+
+.camera.live #user-image-preview-img {
+ display: none;
+}
+
+.animation #user-image-preview-img {
+ -webkit-transition: -webkit-transform 200ms linear;
+}
+
+.camera.flip-x #user-image-preview-img {
+ -webkit-transform: rotateY(180deg);
+}
+
+.user-image-stream-area {
+ display: none;
+ position: relative;
+}
+
+.camera.live .user-image-stream-area {
+ display: block;
+}
+
+#user-image-stream-crop {
+ /* TODO(ivankr): temporary workaround for crbug.com/142347. */
+ -webkit-transform: rotateY(360deg);
+ -webkit-transition: -webkit-transform 200ms linear;
+ height: 220px;
+ overflow: hidden;
+ position: relative;
+ width: 220px;
+}
+
+.flip-x #user-image-stream-crop {
+ -webkit-transform: rotateY(180deg);
+}
+
+/* TODO(ivankr): specify dimensions from real capture size. */
+.user-image-stream {
+ border: solid 1px #cacaca;
+ height: 220px;
+ /* Center image for 4:3 aspect ratio. */
+ left: -16.6%;
+ position: absolute;
+ visibility: hidden;
+}
+
+.online .user-image-stream {
+ visibility: visible;
+}
+
+.user-image-stream-area .spinner {
+ display: none;
+ height: 44px;
+ left: 50%;
+ margin: -22px 0 0 -22px;
+ position: absolute;
+ top: 50%;
+ width: 44px;
+}
+
+.camera.live:not(.online) .user-image-stream-area .spinner {
+ display: block;
+}
+
+#flip-photo {
+ -webkit-transition: opacity 75ms linear;
+ background: url('chrome://theme/IDR_MIRROR_FLIP') no-repeat;
+ border: none;
+ bottom: 44px; /* 8px + image bottom. */
+ display: block;
+ height: 32px;
+ opacity: 0;
+ position: absolute;
+ right: 8px;
+ width: 32px;
+}
+
+/* TODO(merkulova): remove when webkit crbug.com/126479 is fixed. */
+.flip-trick {
+ -webkit-transform: translateZ(1px);
+}
+
+html[dir=rtl] #flip-photo {
+ left: 8px;
+ right: auto;
+}
+
+/* "Flip photo" button is hidden during flip animation. */
+.camera.online:not(.animation) #flip-photo,
+.camera:not(.live):not(.animation) #flip-photo {
+ opacity: 0.75;
+}
+
+#discard-photo,
+#take-photo {
+ display: none;
+ height: 25px;
+ margin: 4px 1px;
+ padding: 0;
+ width: 220px;
+}
+
+.camera:not(.live) #discard-photo {
+ background: url('chrome://theme/IDR_USER_IMAGE_RECYCLE')
+ no-repeat center 0;
+ display: block;
+}
+
+.camera.live.online #take-photo {
+ background: url('chrome://theme/IDR_USER_IMAGE_CAPTURE')
+ no-repeat center -1px;
+ display: block;
+}
+
+#user-image-attribution {
+ -webkit-padding-start: 34px;
+ line-height: 26px;
+}
+
+#user-image-author-website {
+ -webkit-padding-start: 5px;
+}
diff --git a/chromium/chrome/browser/resources/options/chromeos/change_picture_options.html b/chromium/chrome/browser/resources/options/chromeos/change_picture_options.html
new file mode 100644
index 00000000000..571555750df
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/chromeos/change_picture_options.html
@@ -0,0 +1,34 @@
+<div id="change-picture-page" class="page" hidden>
+ <div class="close-button"></div>
+ <h1 i18n-content="changePicturePage"></h1>
+ <div class="content-area">
+ <div i18n-content="changePicturePageDescription"></div>
+ <div id="user-images-area">
+ <grid id="user-image-grid" class="user-image-picker"></grid>
+ <div id="user-image-preview">
+ <img id="user-image-preview-img" i18n-values="alt:previewAltText">
+ <div class="user-image-stream-area">
+ <div class="perspective-box">
+ <div id="user-image-stream-crop">
+ <video class="user-image-stream" autoplay></video>
+ </div>
+ </div>
+ <div class="spinner"></div>
+ </div>
+ <button id="flip-photo" class="custom-appearance"></button>
+ <button id="discard-photo"></button>
+ <button id="take-photo"></button>
+ </div>
+ </div>
+ <div id="user-image-attribution">
+ <span i18n-content="authorCredit"></span>
+ <strong id="user-image-author-name"></strong>
+ <a id="user-image-author-website" target="_blank"></a>
+ </div>
+ </div>
+ <div class="action-area">
+ <div class="button-strip">
+ <button id="change-picture-overlay-confirm" i18n-content="done"></button>
+ </div>
+ </div>
+</div>
diff --git a/chromium/chrome/browser/resources/options/chromeos/change_picture_options.js b/chromium/chrome/browser/resources/options/chromeos/change_picture_options.js
new file mode 100644
index 00000000000..51ed5364e26
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/chromeos/change_picture_options.js
@@ -0,0 +1,320 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+
+ var OptionsPage = options.OptionsPage;
+ var UserImagesGrid = options.UserImagesGrid;
+ var ButtonImages = UserImagesGrid.ButtonImages;
+
+ /**
+ * Array of button URLs used on this page.
+ * @type {Array.<string>}
+ * @const
+ */
+ var ButtonImageUrls = [
+ ButtonImages.TAKE_PHOTO,
+ ButtonImages.CHOOSE_FILE
+ ];
+
+ /////////////////////////////////////////////////////////////////////////////
+ // ChangePictureOptions class:
+
+ /**
+ * Encapsulated handling of ChromeOS change picture options page.
+ * @constructor
+ */
+ function ChangePictureOptions() {
+ OptionsPage.call(
+ this,
+ 'changePicture',
+ loadTimeData.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 = $('user-image-grid');
+ UserImagesGrid.decorate(imageGrid);
+
+ // Preview image will track the selected item's URL.
+ var previewElement = $('user-image-preview');
+ previewElement.oncontextmenu = function(e) { e.preventDefault(); };
+
+ imageGrid.previewElement = previewElement;
+ imageGrid.selectionType = 'default';
+ imageGrid.flipPhotoElement = $('flip-photo');
+
+ imageGrid.addEventListener('select',
+ this.handleImageSelected_.bind(this));
+ imageGrid.addEventListener('activate',
+ this.handleImageActivated_.bind(this));
+ imageGrid.addEventListener('phototaken',
+ this.handlePhotoTaken_.bind(this));
+ imageGrid.addEventListener('photoupdated',
+ this.handlePhotoTaken_.bind(this));
+
+ // Set the title for "Take Photo" button.
+ imageGrid.cameraTitle = loadTimeData.getString('takePhoto');
+
+ // Add the "Choose file" button.
+ imageGrid.addItem(ButtonImages.CHOOSE_FILE,
+ loadTimeData.getString('chooseFile'),
+ this.handleChooseFile_.bind(this)).type = 'file';
+
+ // Profile image data.
+ this.profileImage_ = imageGrid.addItem(
+ ButtonImages.PROFILE_PICTURE,
+ loadTimeData.getString('profilePhotoLoading'));
+ this.profileImage_.type = 'profile';
+
+ $('take-photo').addEventListener(
+ 'click', this.handleTakePhoto_.bind(this));
+ $('discard-photo').addEventListener(
+ 'click', imageGrid.discardPhoto.bind(imageGrid));
+
+ // Toggle 'animation' class for the duration of WebKit transition.
+ $('flip-photo').addEventListener(
+ 'click', function(e) {
+ previewElement.classList.add('animation');
+ imageGrid.flipPhoto = !imageGrid.flipPhoto;
+ });
+ $('user-image-stream-crop').addEventListener(
+ 'webkitTransitionEnd', function(e) {
+ previewElement.classList.remove('animation');
+ });
+ $('user-image-preview-img').addEventListener(
+ 'webkitTransitionEnd', function(e) {
+ previewElement.classList.remove('animation');
+ });
+
+ // Old user image data (if present).
+ this.oldImage_ = null;
+
+ $('change-picture-overlay-confirm').addEventListener(
+ 'click', this.closeOverlay_.bind(this));
+
+ chrome.send('onChangePicturePageInitialized');
+ },
+
+ /**
+ * Called right after the page has been shown to user.
+ */
+ didShowPage: function() {
+ var imageGrid = $('user-image-grid');
+ // Reset camera element.
+ imageGrid.cameraImage = null;
+ imageGrid.updateAndFocus();
+ chrome.send('onChangePicturePageShown');
+ },
+
+ /**
+ * Called right before the page is hidden.
+ */
+ willHidePage: function() {
+ var imageGrid = $('user-image-grid');
+ imageGrid.blur(); // Make sure the image grid is not active.
+ imageGrid.stopCamera();
+ if (this.oldImage_) {
+ imageGrid.removeItem(this.oldImage_);
+ this.oldImage_ = null;
+ }
+ },
+
+ /**
+ * Called right after the page has been hidden.
+ */
+ // TODO(ivankr): both callbacks are required as only one of them is called
+ // depending on the way the page was closed, see http://crbug.com/118923.
+ didClosePage: function() {
+ this.willHidePage();
+ },
+
+ /**
+ * Closes the overlay, returning to the main settings page.
+ * @private
+ */
+ closeOverlay_: function() {
+ if (!$('change-picture-page').hidden)
+ OptionsPage.closeOverlay();
+ },
+
+ /**
+ * Handles "Take photo" button click.
+ * @private
+ */
+ handleTakePhoto_: function() {
+ $('user-image-grid').takePhoto();
+ },
+
+ /**
+ * Handle photo captured event.
+ * @param {Event} e Event with 'dataURL' property containing a data URL.
+ */
+ handlePhotoTaken_: function(e) {
+ chrome.send('photoTaken', [e.dataURL]);
+ },
+
+ /**
+ * Handles "Choose a file" button activation.
+ * @private
+ */
+ handleChooseFile_: function() {
+ chrome.send('chooseFile');
+ this.closeOverlay_();
+ },
+
+ /**
+ * Handles image selection change.
+ * @param {Event} e Selection change Event.
+ * @private
+ */
+ handleImageSelected_: function(e) {
+ var imageGrid = $('user-image-grid');
+ var url = imageGrid.selectedItemUrl;
+ // Ignore selection change caused by program itself and selection of one
+ // of the action buttons.
+ if (!imageGrid.inProgramSelection &&
+ url != ButtonImages.TAKE_PHOTO && url != ButtonImages.CHOOSE_FILE) {
+ chrome.send('selectImage', [url, imageGrid.selectionType]);
+ }
+ // Start/stop camera on (de)selection.
+ if (!imageGrid.inProgramSelection &&
+ imageGrid.selectionType != e.oldSelectionType) {
+ if (imageGrid.selectionType == 'camera') {
+ imageGrid.startCamera(
+ function() {
+ // Start capture if camera is still the selected item.
+ return imageGrid.selectedItem == imageGrid.cameraImage;
+ });
+ } else {
+ imageGrid.stopCamera();
+ }
+ }
+ // Update image attribution text.
+ var image = imageGrid.selectedItem;
+ $('user-image-author-name').textContent = image.author;
+ $('user-image-author-website').textContent = image.website;
+ $('user-image-author-website').href = image.website;
+ $('user-image-attribution').style.visibility =
+ (image.author || image.website) ? 'visible' : 'hidden';
+ },
+
+ /**
+ * Handles image activation (by pressing Enter).
+ * @private
+ */
+ handleImageActivated_: function() {
+ switch ($('user-image-grid').selectedItemUrl) {
+ case ButtonImages.TAKE_PHOTO:
+ this.handleTakePhoto_();
+ break;
+ case ButtonImages.CHOOSE_FILE:
+ this.handleChooseFile_();
+ break;
+ default:
+ this.closeOverlay_();
+ break;
+ }
+ },
+
+ /**
+ * Adds or updates old user image taken from file/camera (neither a profile
+ * image nor a default one).
+ * @param {string} imageUrl Old user image, as data or internal URL.
+ * @private
+ */
+ setOldImage_: function(imageUrl) {
+ var imageGrid = $('user-image-grid');
+ if (this.oldImage_) {
+ this.oldImage_ = imageGrid.updateItem(this.oldImage_, imageUrl);
+ } else {
+ // Insert next to the profile image.
+ var pos = imageGrid.indexOf(this.profileImage_) + 1;
+ this.oldImage_ = imageGrid.addItem(imageUrl, undefined, undefined, pos);
+ this.oldImage_.type = 'old';
+ 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 = $('user-image-grid');
+ this.profileImage_ = imageGrid.updateItem(
+ this.profileImage_, imageUrl, loadTimeData.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) {
+ $('user-image-grid').selectedItemUrl = url;
+ },
+
+ /**
+ * @param {boolean} present Whether camera is detected.
+ */
+ setCameraPresent_: function(present) {
+ $('user-image-grid').cameraPresent = present;
+ },
+
+ /**
+ * Appends default images to the image grid. Should only be called once.
+ * @param {Array.<{url: string, author: string, website: string}>}
+ * imagesData An array of default images data, including URL, author and
+ * website.
+ * @private
+ */
+ setDefaultImages_: function(imagesData) {
+ var imageGrid = $('user-image-grid');
+ for (var i = 0, data; data = imagesData[i]; i++) {
+ var item = imageGrid.addItem(data.url);
+ item.type = 'default';
+ item.author = data.author || '';
+ item.website = data.website || '';
+ }
+ },
+ };
+
+ // Forward public APIs to private implementations.
+ [
+ 'closeOverlay',
+ 'setCameraPresent',
+ 'setDefaultImages',
+ 'setOldImage',
+ 'setProfileImage',
+ 'setSelectedImage',
+ ].forEach(function(name) {
+ ChangePictureOptions[name] = function() {
+ var instance = ChangePictureOptions.getInstance();
+ return instance[name + '_'].apply(instance, arguments);
+ };
+ });
+
+ // Export
+ return {
+ ChangePictureOptions: ChangePictureOptions
+ };
+
+});
diff --git a/chromium/chrome/browser/resources/options/chromeos/display_options.css b/chromium/chrome/browser/resources/options/chromeos/display_options.css
new file mode 100644
index 00000000000..3f738791e5c
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/chromeos/display_options.css
@@ -0,0 +1,96 @@
+/* Copyright (c) 2012 The Chromium Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file. */
+
+#display-options-page {
+ background-color: rgb(240, 240, 240);
+}
+
+#display-options-content-area {
+ padding: 0;
+}
+
+#display-options-displays-view-host {
+ padding: 20px 0 20px 0;
+}
+
+#display-options-displays-view {
+ overflow: hidden;
+ position: relative;
+ width: 100%;
+}
+
+#display-options-displays-view-mirroring {
+ margin: 20px 0 20px 0;
+}
+
+#display-configurations {
+ -webkit-padding-end: 0;
+ -webkit-padding-start: 15px;
+ background-color: white;
+ border-top: 1px solid lightgrey;
+ padding-bottom: 15px;
+ padding-top: 15px;
+}
+
+/* The arrow at the border #display-configurations to point the focused display.
+ * This is achieved by a square rotated by 45-deg, and it has border at the
+ * upper-half, which were left/top before the rotation. */
+#display-configuration-arrow {
+ -webkit-transform: rotate(45deg);
+ background-color: white;
+ border-left: 1px solid lightgrey;
+ border-top: 1px solid lightgrey;
+ height: 20px;
+ position: absolute;
+ width: 20px;
+ z-index: 1;
+}
+
+#selected-display-data-container {
+ z-index: 2;
+}
+
+#selected-display-name {
+ font-size: large;
+ font-weight: normal;
+ margin-top: 5px;
+ padding: 0;
+}
+
+.selected-display-option-row {
+ margin-top: 10px;
+}
+
+.selected-display-option-title {
+ display: inline-block;
+ margin-right: 10px;
+}
+
+.displays-display {
+ -webkit-user-select: none;
+ background: rgb(240, 240, 240);
+ border: solid 1px;
+ box-sizing: border-box;
+ font-weight: bold;
+ position: absolute;
+ text-align: center;
+ z-index: 2;
+}
+
+.display-mirrored {
+ border: solid 1px;
+}
+
+.displays-focused {
+ border: solid 2px rgb(0, 138, 255);
+ color: rgb(0, 138, 255);
+}
+
+#display-options-toggle-mirroring {
+ margin-right: 5px;
+}
+
+.display-options-button {
+ width: 130px;
+}
diff --git a/chromium/chrome/browser/resources/options/chromeos/display_options.html b/chromium/chrome/browser/resources/options/chromeos/display_options.html
new file mode 100644
index 00000000000..989e7ed3da0
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/chromeos/display_options.html
@@ -0,0 +1,56 @@
+<div id="display-options-page" class="page" hidden>
+ <div class="close-button"></div>
+ <h1 i18n-content="displayOptionsPage"></h1>
+ <div class="content-area" id="display-options-content-area">
+ <div id="display-options-displays-view-host">
+ </div>
+ <div id="display-configurations">
+ <div id="selected-display-data-container">
+ <div id="selected-display-name"></div>
+ <div class="selected-display-option-row">
+ <div class="selected-display-option-title"
+ i18n-content="selectedDisplayTitleOptions">
+ </div>
+ <button id="display-options-toggle-mirroring"
+ class="display-options-button" i18n-content="startMirroring">
+ </button>
+ <button id="display-options-set-primary"
+ class="display-options-button" i18n-content="setPrimary">
+ </div>
+ <div class="selected-display-option-row">
+ <div class="selected-display-option-title"
+ i18n-content="selectedDisplayTitleResolution">
+ </div>
+ <select id="display-options-resolution-selection"
+ class="display-options-button">
+ </select>
+ </div>
+ <div class="selected-display-option-row">
+ <div class="selected-display-option-title"
+ i18n-content="selectedDisplayTitleOrientation">
+ </div>
+ <select id="display-options-orientation-selection"
+ class="display-options-button">
+ <option value="0" i18n-content="orientation0"></option>
+ <option value="90" i18n-content="orientation90"></option>
+ <option value="180" i18n-content="orientation180"></option>
+ <option value="270" i18n-content="orientation270"></option>
+ </select>
+ </div>
+ <div class="selected-display-option-row">
+ <div class="selected-display-option-title"
+ i18n-content="selectedDisplayTitleOverscan">
+ </div>
+ <button id="selected-display-start-calibrating-overscan"
+ class="display-options-button"
+ i18n-content="startCalibratingOverscan">
+ </button>
+ </div>
+ </div>
+ </div>
+ <!-- The arrow of display-configuration is achieved by a div
+ rotated by 45deg. -->
+ <div id="display-configuration-arrow">
+ </div>
+ </div>
+</div>
diff --git a/chromium/chrome/browser/resources/options/chromeos/display_options.js b/chromium/chrome/browser/resources/options/chromeos/display_options.js
new file mode 100644
index 00000000000..3b000ca283e
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/chromeos/display_options.js
@@ -0,0 +1,863 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+ var OptionsPage = options.OptionsPage;
+
+ // The scale ratio of the display rectangle to its original size.
+ /** @const */ var VISUAL_SCALE = 1 / 10;
+
+ // The number of pixels to share the edges between displays.
+ /** @const */ var MIN_OFFSET_OVERLAP = 5;
+
+ /**
+ * Enumeration of secondary display layout. The value has to be same as the
+ * values in ash/display/display_controller.cc.
+ * @enum {number}
+ */
+ var SecondaryDisplayLayout = {
+ TOP: 0,
+ RIGHT: 1,
+ BOTTOM: 2,
+ LEFT: 3
+ };
+
+ /**
+ * Calculates the bounds of |element| relative to the page.
+ * @param {HTMLElement} element The element to be known.
+ * @return {Object} The object for the bounds, with x, y, width, and height.
+ */
+ function getBoundsInPage(element) {
+ var bounds = {
+ x: element.offsetLeft,
+ y: element.offsetTop,
+ width: element.offsetWidth,
+ height: element.offsetHeight
+ };
+ var parent = element.offsetParent;
+ while (parent && parent != document.body) {
+ bounds.x += parent.offsetLeft;
+ bounds.y += parent.offsetTop;
+ parent = parent.offsetParent;
+ }
+ return bounds;
+ }
+
+ /**
+ * Gets the position of |point| to |rect|, left, right, top, or bottom.
+ * @param {Object} rect The base rectangle with x, y, width, and height.
+ * @param {Object} point The point to check the position.
+ * @return {SecondaryDisplayLayout} The position of the calculated point.
+ */
+ function getPositionToRectangle(rect, point) {
+ // Separates the area into four (LEFT/RIGHT/TOP/BOTTOM) by the diagonals of
+ // the rect, and decides which area the display should reside.
+ var diagonalSlope = rect.height / rect.width;
+ var topDownIntercept = rect.y - rect.x * diagonalSlope;
+ var bottomUpIntercept = rect.y + rect.height + rect.x * diagonalSlope;
+
+ if (point.y > topDownIntercept + point.x * diagonalSlope) {
+ if (point.y > bottomUpIntercept - point.x * diagonalSlope)
+ return SecondaryDisplayLayout.BOTTOM;
+ else
+ return SecondaryDisplayLayout.LEFT;
+ } else {
+ if (point.y > bottomUpIntercept - point.x * diagonalSlope)
+ return SecondaryDisplayLayout.RIGHT;
+ else
+ return SecondaryDisplayLayout.TOP;
+ }
+ }
+
+ /**
+ * Encapsulated handling of the 'Display' page.
+ * @constructor
+ */
+ function DisplayOptions() {
+ OptionsPage.call(this, 'display',
+ loadTimeData.getString('displayOptionsPageTabTitle'),
+ 'display-options-page');
+ }
+
+ cr.addSingletonGetter(DisplayOptions);
+
+ DisplayOptions.prototype = {
+ __proto__: OptionsPage.prototype,
+
+ /**
+ * Whether the current output status is mirroring displays or not.
+ * @private
+ */
+ mirroring_: false,
+
+ /**
+ * The current secondary display layout.
+ * @private
+ */
+ layout_: SecondaryDisplayLayout.RIGHT,
+
+ /**
+ * The array of current output displays. It also contains the display
+ * rectangles currently rendered on screen.
+ * @private
+ */
+ displays_: [],
+
+ /**
+ * The index for the currently focused display in the options UI. null if
+ * no one has focus.
+ * @private
+ */
+ focusedIndex_: null,
+
+ /**
+ * The primary display.
+ * @private
+ */
+ primaryDisplay_: null,
+
+ /**
+ * The secondary display.
+ * @private
+ */
+ secondaryDisplay_: null,
+
+ /**
+ * The container div element which contains all of the display rectangles.
+ * @private
+ */
+ displaysView_: null,
+
+ /**
+ * The scale factor of the actual display size to the drawn display
+ * rectangle size.
+ * @private
+ */
+ visualScale_: VISUAL_SCALE,
+
+ /**
+ * The location where the last touch event happened. This is used to
+ * prevent unnecessary dragging events happen. Set to null unless it's
+ * during touch events.
+ * @private
+ */
+ lastTouchLocation_: null,
+
+ /**
+ * Initialize the page.
+ */
+ initializePage: function() {
+ OptionsPage.prototype.initializePage.call(this);
+
+ $('display-options-toggle-mirroring').onclick = function() {
+ this.mirroring_ = !this.mirroring_;
+ chrome.send('setMirroring', [this.mirroring_]);
+ }.bind(this);
+
+ var container = $('display-options-displays-view-host');
+ container.onmousemove = this.onMouseMove_.bind(this);
+ window.addEventListener('mouseup', this.endDragging_.bind(this), true);
+ container.ontouchmove = this.onTouchMove_.bind(this);
+ container.ontouchend = this.endDragging_.bind(this);
+
+ $('display-options-set-primary').onclick = function() {
+ chrome.send('setPrimary', [this.displays_[this.focusedIndex_].id]);
+ }.bind(this);
+ $('display-options-resolution-selection').onchange = function(ev) {
+ var display = this.displays_[this.focusedIndex_];
+ var resolution = display.resolutions[ev.target.value];
+ if (resolution.scale) {
+ chrome.send('setUIScale', [display.id, resolution.scale]);
+ } else {
+ chrome.send('setResolution',
+ [display.id, resolution.width, resolution.height]);
+ }
+ }.bind(this);
+ $('display-options-orientation-selection').onchange = function(ev) {
+ chrome.send('setOrientation', [this.displays_[this.focusedIndex_].id,
+ ev.target.value]);
+ }.bind(this);
+ $('selected-display-start-calibrating-overscan').onclick = function() {
+ // Passes the target display ID. Do not specify it through URL hash,
+ // we do not care back/forward.
+ var displayOverscan = options.DisplayOverscan.getInstance();
+ displayOverscan.setDisplayId(this.displays_[this.focusedIndex_].id);
+ OptionsPage.navigateToPage('displayOverscan');
+ }.bind(this);
+
+ chrome.send('getDisplayInfo');
+ },
+
+ /** @override */
+ didShowPage: function() {
+ var optionTitles = document.getElementsByClassName(
+ 'selected-display-option-title');
+ var maxSize = 0;
+ for (var i = 0; i < optionTitles.length; i++)
+ maxSize = Math.max(maxSize, optionTitles[i].clientWidth);
+ for (var i = 0; i < optionTitles.length; i++)
+ optionTitles[i].style.width = maxSize + 'px';
+ },
+
+ /** @override */
+ onVisibilityChanged_: function() {
+ OptionsPage.prototype.onVisibilityChanged_(this);
+ if (this.visible)
+ chrome.send('getDisplayInfo');
+ },
+
+ /**
+ * Mouse move handler for dragging display rectangle.
+ * @param {Event} e The mouse move event.
+ * @private
+ */
+ onMouseMove_: function(e) {
+ return this.processDragging_(e, {x: e.pageX, y: e.pageY});
+ },
+
+ /**
+ * Touch move handler for dragging display rectangle.
+ * @param {Event} e The touch move event.
+ * @private
+ */
+ onTouchMove_: function(e) {
+ if (e.touches.length != 1)
+ return true;
+
+ var touchLocation = {x: e.touches[0].pageX, y: e.touches[0].pageY};
+ // Touch move events happen even if the touch location doesn't change, but
+ // it doesn't need to process the dragging. Since sometimes the touch
+ // position changes slightly even though the user doesn't think to move
+ // the finger, very small move is just ignored.
+ /** @const */ var IGNORABLE_TOUCH_MOVE_PX = 1;
+ var xDiff = Math.abs(touchLocation.x - this.lastTouchLocation_.x);
+ var yDiff = Math.abs(touchLocation.y - this.lastTouchLocation_.y);
+ if (xDiff <= IGNORABLE_TOUCH_MOVE_PX &&
+ yDiff <= IGNORABLE_TOUCH_MOVE_PX) {
+ return true;
+ }
+
+ this.lastTouchLocation_ = touchLocation;
+ return this.processDragging_(e, touchLocation);
+ },
+
+ /**
+ * Mouse down handler for dragging display rectangle.
+ * @param {Event} e The mouse down event.
+ * @private
+ */
+ onMouseDown_: function(e) {
+ if (this.mirroring_)
+ return true;
+
+ if (e.button != 0)
+ return true;
+
+ e.preventDefault();
+ return this.startDragging_(e.target, {x: e.pageX, y: e.pageY});
+ },
+
+ /**
+ * Touch start handler for dragging display rectangle.
+ * @param {Event} e The touch start event.
+ * @private
+ */
+ onTouchStart_: function(e) {
+ if (this.mirroring_)
+ return true;
+
+ if (e.touches.length != 1)
+ return false;
+
+ e.preventDefault();
+ var touch = e.touches[0];
+ this.lastTouchLocation_ = {x: touch.pageX, y: touch.pageY};
+ return this.startDragging_(e.target, this.lastTouchLocation_);
+ },
+
+ /**
+ * Collects the current data and sends it to Chrome.
+ * @private
+ */
+ applyResult_: function() {
+ // Offset is calculated from top or left edge.
+ var primary = this.primaryDisplay_;
+ var secondary = this.secondaryDisplay_;
+ var offset;
+ if (this.layout_ == SecondaryDisplayLayout.LEFT ||
+ this.layout_ == SecondaryDisplayLayout.RIGHT) {
+ offset = secondary.div.offsetTop - primary.div.offsetTop;
+ } else {
+ offset = secondary.div.offsetLeft - primary.div.offsetLeft;
+ }
+ chrome.send('setDisplayLayout',
+ [this.layout_, offset / this.visualScale_]);
+ },
+
+ /**
+ * Snaps the region [point, width] to [basePoint, baseWidth] if
+ * the [point, width] is close enough to the base's edge.
+ * @param {number} point The starting point of the region.
+ * @param {number} width The width of the region.
+ * @param {number} basePoint The starting point of the base region.
+ * @param {number} baseWidth The width of the base region.
+ * @return {number} The moved point. Returns point itself if it doesn't
+ * need to snap to the edge.
+ * @private
+ */
+ snapToEdge_: function(point, width, basePoint, baseWidth) {
+ // If the edge of the regions is smaller than this, it will snap to the
+ // base's edge.
+ /** @const */ var SNAP_DISTANCE_PX = 16;
+
+ var startDiff = Math.abs(point - basePoint);
+ var endDiff = Math.abs(point + width - (basePoint + baseWidth));
+ // Prefer the closer one if both edges are close enough.
+ if (startDiff < SNAP_DISTANCE_PX && startDiff < endDiff)
+ return basePoint;
+ else if (endDiff < SNAP_DISTANCE_PX)
+ return basePoint + baseWidth - width;
+
+ return point;
+ },
+
+ /**
+ * Processes the actual dragging of display rectangle.
+ * @param {Event} e The event which triggers this drag.
+ * @param {Object} eventLocation The location where the event happens.
+ * @private
+ */
+ processDragging_: function(e, eventLocation) {
+ if (!this.dragging_)
+ return true;
+
+ var index = -1;
+ for (var i = 0; i < this.displays_.length; i++) {
+ if (this.displays_[i] == this.dragging_.display) {
+ index = i;
+ break;
+ }
+ }
+ if (index < 0)
+ return true;
+
+ e.preventDefault();
+
+ // Note that current code of moving display-rectangles doesn't work
+ // if there are >=3 displays. This is our assumption for M21.
+ // TODO(mukai): Fix the code to allow >=3 displays.
+ var newPosition = {
+ x: this.dragging_.originalLocation.x +
+ (eventLocation.x - this.dragging_.eventLocation.x),
+ y: this.dragging_.originalLocation.y +
+ (eventLocation.y - this.dragging_.eventLocation.y)
+ };
+
+ var baseDiv = this.dragging_.display.isPrimary ?
+ this.secondaryDisplay_.div : this.primaryDisplay_.div;
+ var draggingDiv = this.dragging_.display.div;
+
+ newPosition.x = this.snapToEdge_(newPosition.x, draggingDiv.offsetWidth,
+ baseDiv.offsetLeft, baseDiv.offsetWidth);
+ newPosition.y = this.snapToEdge_(newPosition.y, draggingDiv.offsetHeight,
+ baseDiv.offsetTop, baseDiv.offsetHeight);
+
+ var newCenter = {
+ x: newPosition.x + draggingDiv.offsetWidth / 2,
+ y: newPosition.y + draggingDiv.offsetHeight / 2
+ };
+
+ var baseBounds = {
+ x: baseDiv.offsetLeft,
+ y: baseDiv.offsetTop,
+ width: baseDiv.offsetWidth,
+ height: baseDiv.offsetHeight
+ };
+ switch (getPositionToRectangle(baseBounds, newCenter)) {
+ case SecondaryDisplayLayout.RIGHT:
+ this.layout_ = this.dragging_.display.isPrimary ?
+ SecondaryDisplayLayout.LEFT : SecondaryDisplayLayout.RIGHT;
+ break;
+ case SecondaryDisplayLayout.LEFT:
+ this.layout_ = this.dragging_.display.isPrimary ?
+ SecondaryDisplayLayout.RIGHT : SecondaryDisplayLayout.LEFT;
+ break;
+ case SecondaryDisplayLayout.TOP:
+ this.layout_ = this.dragging_.display.isPrimary ?
+ SecondaryDisplayLayout.BOTTOM : SecondaryDisplayLayout.TOP;
+ break;
+ case SecondaryDisplayLayout.BOTTOM:
+ this.layout_ = this.dragging_.display.isPrimary ?
+ SecondaryDisplayLayout.TOP : SecondaryDisplayLayout.BOTTOM;
+ break;
+ }
+
+ if (this.layout_ == SecondaryDisplayLayout.LEFT ||
+ this.layout_ == SecondaryDisplayLayout.RIGHT) {
+ if (newPosition.y > baseDiv.offsetTop + baseDiv.offsetHeight)
+ this.layout_ = this.dragging_.display.isPrimary ?
+ SecondaryDisplayLayout.TOP : SecondaryDisplayLayout.BOTTOM;
+ else if (newPosition.y + draggingDiv.offsetHeight <
+ baseDiv.offsetTop)
+ this.layout_ = this.dragging_.display.isPrimary ?
+ SecondaryDisplayLayout.BOTTOM : SecondaryDisplayLayout.TOP;
+ } else {
+ if (newPosition.x > baseDiv.offsetLeft + baseDiv.offsetWidth)
+ this.layout_ = this.dragging_.display.isPrimary ?
+ SecondaryDisplayLayout.LEFT : SecondaryDisplayLayout.RIGHT;
+ else if (newPosition.x + draggingDiv.offsetWidth <
+ baseDiv.offstLeft)
+ this.layout_ = this.dragging_.display.isPrimary ?
+ SecondaryDisplayLayout.RIGHT : SecondaryDisplayLayout.LEFT;
+ }
+
+ var layoutToBase;
+ if (!this.dragging_.display.isPrimary) {
+ layoutToBase = this.layout_;
+ } else {
+ switch (this.layout_) {
+ case SecondaryDisplayLayout.RIGHT:
+ layoutToBase = SecondaryDisplayLayout.LEFT;
+ break;
+ case SecondaryDisplayLayout.LEFT:
+ layoutToBase = SecondaryDisplayLayout.RIGHT;
+ break;
+ case SecondaryDisplayLayout.TOP:
+ layoutToBase = SecondaryDisplayLayout.BOTTOM;
+ break;
+ case SecondaryDisplayLayout.BOTTOM:
+ layoutToBase = SecondaryDisplayLayout.TOP;
+ break;
+ }
+ }
+
+ switch (layoutToBase) {
+ case SecondaryDisplayLayout.RIGHT:
+ draggingDiv.style.left =
+ baseDiv.offsetLeft + baseDiv.offsetWidth + 'px';
+ draggingDiv.style.top = newPosition.y + 'px';
+ break;
+ case SecondaryDisplayLayout.LEFT:
+ draggingDiv.style.left =
+ baseDiv.offsetLeft - draggingDiv.offsetWidth + 'px';
+ draggingDiv.style.top = newPosition.y + 'px';
+ break;
+ case SecondaryDisplayLayout.TOP:
+ draggingDiv.style.top =
+ baseDiv.offsetTop - draggingDiv.offsetHeight + 'px';
+ draggingDiv.style.left = newPosition.x + 'px';
+ break;
+ case SecondaryDisplayLayout.BOTTOM:
+ draggingDiv.style.top =
+ baseDiv.offsetTop + baseDiv.offsetHeight + 'px';
+ draggingDiv.style.left = newPosition.x + 'px';
+ break;
+ }
+
+ return false;
+ },
+
+ /**
+ * start dragging of a display rectangle.
+ * @param {HTMLElement} target The event target.
+ * @param {Object} eventLocation The object to hold the location where
+ * this event happens.
+ * @private
+ */
+ startDragging_: function(target, eventLocation) {
+ this.focusedIndex_ = null;
+ for (var i = 0; i < this.displays_.length; i++) {
+ var display = this.displays_[i];
+ if (display.div == target ||
+ (target.offsetParent && target.offsetParent == display.div)) {
+ this.focusedIndex_ = i;
+ break;
+ }
+ }
+
+ for (var i = 0; i < this.displays_.length; i++) {
+ var display = this.displays_[i];
+ display.div.className = 'displays-display';
+ if (i != this.focusedIndex_)
+ continue;
+
+ display.div.classList.add('displays-focused');
+ if (this.displays_.length > 1) {
+ this.dragging_ = {
+ display: display,
+ originalLocation: {
+ x: display.div.offsetLeft, y: display.div.offsetTop
+ },
+ eventLocation: eventLocation
+ };
+ }
+ }
+
+ this.updateSelectedDisplayDescription_();
+ return false;
+ },
+
+ /**
+ * finish the current dragging of displays.
+ * @param {Event} e The event which triggers this.
+ * @private
+ */
+ endDragging_: function(e) {
+ this.lastTouchLocation_ = null;
+ if (this.dragging_) {
+ // Make sure the dragging location is connected.
+ var baseDiv = this.dragging_.display.isPrimary ?
+ this.secondaryDisplay_.div : this.primaryDisplay_.div;
+ var draggingDiv = this.dragging_.display.div;
+ if (this.layout_ == SecondaryDisplayLayout.LEFT ||
+ this.layout_ == SecondaryDisplayLayout.RIGHT) {
+ var top = Math.max(draggingDiv.offsetTop,
+ baseDiv.offsetTop - draggingDiv.offsetHeight +
+ MIN_OFFSET_OVERLAP);
+ top = Math.min(top,
+ baseDiv.offsetTop + baseDiv.offsetHeight -
+ MIN_OFFSET_OVERLAP);
+ draggingDiv.style.top = top + 'px';
+ } else {
+ var left = Math.max(draggingDiv.offsetLeft,
+ baseDiv.offsetLeft - draggingDiv.offsetWidth +
+ MIN_OFFSET_OVERLAP);
+ left = Math.min(left,
+ baseDiv.offsetLeft + baseDiv.offsetWidth -
+ MIN_OFFSET_OVERLAP);
+ draggingDiv.style.left = left + 'px';
+ }
+ var originalPosition = this.dragging_.display.originalPosition;
+ if (originalPosition.x != draggingDiv.offsetLeft ||
+ originalPosition.y != draggingDiv.offsetTop)
+ this.applyResult_();
+ this.dragging_ = null;
+ }
+ this.updateSelectedDisplayDescription_();
+ return false;
+ },
+
+ /**
+ * Updates the description of selected display section for mirroring mode.
+ * @private
+ */
+ updateSelectedDisplaySectionMirroring_: function() {
+ $('display-configuration-arrow').hidden = true;
+ $('display-options-set-primary').disabled = true;
+ $('display-options-toggle-mirroring').disabled = false;
+ $('selected-display-start-calibrating-overscan').disabled = true;
+ $('display-options-orientation-selection').disabled = true;
+ var display = this.displays_[0];
+ $('selected-display-name').textContent =
+ loadTimeData.getString('mirroringDisplay');
+ var resolution = $('display-options-resolution-selection');
+ var option = document.createElement('option');
+ option.value = 'default';
+ option.textContent = display.width + 'x' + display.height;
+ resolution.appendChild(option);
+ resolution.disabled = true;
+ },
+
+ /**
+ * Updates the description of selected display section when no display is
+ * selected.
+ * @private
+ */
+ updateSelectedDisplaySectionNoSelected_: function() {
+ $('display-configuration-arrow').hidden = true;
+ $('display-options-set-primary').disabled = true;
+ $('display-options-toggle-mirroring').disabled = true;
+ $('selected-display-start-calibrating-overscan').disabled = true;
+ $('display-options-orientation-selection').disabled = true;
+ $('selected-display-name').textContent = '';
+ var resolution = $('display-options-resolution-selection');
+ resolution.appendChild(document.createElement('option'));
+ resolution.disabled = true;
+ },
+
+ /**
+ * Updates the description of selected display section for the selected
+ * display.
+ * @param {Object} display The selected display object.
+ * @private
+ */
+ updateSelectedDisplaySectionForDisplay_: function(display) {
+ var arrow = $('display-configuration-arrow');
+ arrow.hidden = false;
+ // Adding 1 px to the position to fit the border line and the border in
+ // arrow precisely.
+ arrow.style.top = $('display-configurations').offsetTop -
+ arrow.offsetHeight / 2 + 'px';
+ arrow.style.left = display.div.offsetLeft +
+ display.div.offsetWidth / 2 - arrow.offsetWidth / 2 + 'px';
+
+ $('display-options-set-primary').disabled = display.isPrimary;
+ $('display-options-toggle-mirroring').disabled =
+ (this.displays_.length <= 1);
+ $('selected-display-start-calibrating-overscan').disabled =
+ display.isInternal;
+
+ var orientation = $('display-options-orientation-selection');
+ orientation.disabled = false;
+ var orientationOptions = orientation.getElementsByTagName('option');
+ orientationOptions[display.orientation].selected = true;
+
+ $('selected-display-name').textContent = display.name;
+
+ var resolution = $('display-options-resolution-selection');
+ if (display.resolutions.length <= 1) {
+ var option = document.createElement('option');
+ option.value = 'default';
+ option.textContent = display.width + 'x' + display.height;
+ option.selected = true;
+ resolution.appendChild(option);
+ resolution.disabled = true;
+ } else {
+ for (var i = 0; i < display.resolutions.length; i++) {
+ var option = document.createElement('option');
+ option.value = i;
+ option.textContent = display.resolutions[i].width + 'x' +
+ display.resolutions[i].height;
+ if (display.resolutions[i].isBest) {
+ option.textContent += ' ' +
+ loadTimeData.getString('annotateBest');
+ }
+ option.selected = display.resolutions[i].selected;
+ resolution.appendChild(option);
+ }
+ resolution.disabled = (display.resolutions.length <= 1);
+ }
+ },
+
+ /**
+ * Updates the description of the selected display section.
+ * @private
+ */
+ updateSelectedDisplayDescription_: function() {
+ var resolution = $('display-options-resolution-selection');
+ resolution.textContent = '';
+ var orientation = $('display-options-orientation-selection');
+ var orientationOptions = orientation.getElementsByTagName('option');
+ for (var i = 0; i < orientationOptions.length; i++)
+ orientationOptions.selected = false;
+
+ if (this.mirroring_) {
+ this.updateSelectedDisplaySectionMirroring_();
+ } else if (this.focusedIndex_ == null ||
+ this.displays_[this.focusedIndex_] == null) {
+ this.updateSelectedDisplaySectionNoSelected_();
+ } else {
+ this.updateSelectedDisplaySectionForDisplay_(
+ this.displays_[this.focusedIndex_]);
+ }
+ },
+
+ /**
+ * Clears the drawing area for display rectangles.
+ * @private
+ */
+ resetDisplaysView_: function() {
+ var displaysViewHost = $('display-options-displays-view-host');
+ displaysViewHost.removeChild(displaysViewHost.firstChild);
+ this.displaysView_ = document.createElement('div');
+ this.displaysView_.id = 'display-options-displays-view';
+ displaysViewHost.appendChild(this.displaysView_);
+ },
+
+ /**
+ * Lays out the display rectangles for mirroring.
+ * @private
+ */
+ layoutMirroringDisplays_: function() {
+ // Offset pixels for secondary display rectangles. The offset includes the
+ // border width.
+ /** @const */ var MIRRORING_OFFSET_PIXELS = 3;
+ // Always show two displays because there must be two displays when
+ // the display_options is enabled. Don't rely on displays_.length because
+ // there is only one display from chrome's perspective in mirror mode.
+ /** @const */ var MIN_NUM_DISPLAYS = 2;
+ /** @const */ var MIRRORING_VERTICAL_MARGIN = 20;
+
+ // The width/height should be same as the first display:
+ var width = Math.ceil(this.displays_[0].width * this.visualScale_);
+ var height = Math.ceil(this.displays_[0].height * this.visualScale_);
+
+ var numDisplays = Math.max(MIN_NUM_DISPLAYS, this.displays_.length);
+
+ var totalWidth = width + numDisplays * MIRRORING_OFFSET_PIXELS;
+ var totalHeight = height + numDisplays * MIRRORING_OFFSET_PIXELS;
+
+ this.displaysView_.style.height = totalHeight + 'px';
+ this.displaysView_.classList.add(
+ 'display-options-displays-view-mirroring');
+
+ // The displays should be centered.
+ var offsetX =
+ $('display-options-displays-view').offsetWidth / 2 - totalWidth / 2;
+
+ for (var i = 0; i < numDisplays; i++) {
+ var div = document.createElement('div');
+ div.className = 'displays-display';
+ div.style.top = i * MIRRORING_OFFSET_PIXELS + 'px';
+ div.style.left = i * MIRRORING_OFFSET_PIXELS + offsetX + 'px';
+ div.style.width = width + 'px';
+ div.style.height = height + 'px';
+ div.style.zIndex = i;
+ // set 'display-mirrored' class for the background display rectangles.
+ if (i != numDisplays - 1)
+ div.classList.add('display-mirrored');
+ this.displaysView_.appendChild(div);
+ }
+ },
+
+ /**
+ * Layouts the display rectangles according to the current layout_.
+ * @private
+ */
+ layoutDisplays_: function() {
+ var maxWidth = 0;
+ var maxHeight = 0;
+ var boundingBox = {left: 0, right: 0, top: 0, bottom: 0};
+ for (var i = 0; i < this.displays_.length; i++) {
+ var display = this.displays_[i];
+ boundingBox.left = Math.min(boundingBox.left, display.x);
+ boundingBox.right = Math.max(
+ boundingBox.right, display.x + display.width);
+ boundingBox.top = Math.min(boundingBox.top, display.y);
+ boundingBox.bottom = Math.max(
+ boundingBox.bottom, display.y + display.height);
+ maxWidth = Math.max(maxWidth, display.width);
+ maxHeight = Math.max(maxHeight, display.height);
+ }
+
+ // Make the margin around the bounding box.
+ var areaWidth = boundingBox.right - boundingBox.left + maxWidth;
+ var areaHeight = boundingBox.bottom - boundingBox.top + maxHeight;
+
+ // Calculates the scale by the width since horizontal size is more strict.
+ // TODO(mukai): Adds the check of vertical size in case.
+ this.visualScale_ = Math.min(
+ VISUAL_SCALE, this.displaysView_.offsetWidth / areaWidth);
+
+ // Prepare enough area for thisplays_view by adding the maximum height.
+ this.displaysView_.style.height =
+ Math.ceil(areaHeight * this.visualScale_) + 'px';
+
+ var boundingCenter = {
+ x: Math.floor((boundingBox.right + boundingBox.left) *
+ this.visualScale_ / 2),
+ y: Math.floor((boundingBox.bottom + boundingBox.top) *
+ this.visualScale_ / 2)
+ };
+
+ // Centering the bounding box of the display rectangles.
+ var offset = {
+ x: Math.floor(this.displaysView_.offsetWidth / 2 -
+ (boundingBox.right + boundingBox.left) * this.visualScale_ / 2),
+ y: Math.floor(this.displaysView_.offsetHeight / 2 -
+ (boundingBox.bottom + boundingBox.top) * this.visualScale_ / 2)
+ };
+
+ for (var i = 0; i < this.displays_.length; i++) {
+ var display = this.displays_[i];
+ var div = document.createElement('div');
+ display.div = div;
+
+ div.className = 'displays-display';
+ if (i == this.focusedIndex_)
+ div.classList.add('displays-focused');
+
+ if (display.isPrimary) {
+ this.primaryDisplay_ = display;
+ } else {
+ this.secondaryDisplay_ = display;
+ }
+ var displayNameContainer = document.createElement('div');
+ displayNameContainer.textContent = display.name;
+ div.appendChild(displayNameContainer);
+ display.nameContainer = displayNameContainer;
+ display.div.style.width =
+ Math.floor(display.width * this.visualScale_) + 'px';
+ var newHeight = Math.floor(display.height * this.visualScale_);
+ display.div.style.height = newHeight + 'px';
+ div.style.left =
+ Math.floor(display.x * this.visualScale_) + offset.x + 'px';
+ div.style.top =
+ Math.floor(display.y * this.visualScale_) + offset.y + 'px';
+ display.nameContainer.style.marginTop =
+ (newHeight - display.nameContainer.offsetHeight) / 2 + 'px';
+
+ div.onmousedown = this.onMouseDown_.bind(this);
+ div.ontouchstart = this.onTouchStart_.bind(this);
+
+ this.displaysView_.appendChild(div);
+
+ // Set the margin top to place the display name at the middle of the
+ // rectangle. Note that this has to be done after it's added into the
+ // |displaysView_|. Otherwise its offsetHeight is yet 0.
+ displayNameContainer.style.marginTop =
+ (div.offsetHeight - displayNameContainer.offsetHeight) / 2 + 'px';
+ display.originalPosition = {x: div.offsetLeft, y: div.offsetTop};
+ }
+ },
+
+ /**
+ * Called when the display arrangement has changed.
+ * @param {boolean} mirroring Whether current mode is mirroring or not.
+ * @param {Array} displays The list of the display information.
+ * @param {SecondaryDisplayLayout} layout The layout strategy.
+ * @param {number} offset The offset of the secondary display.
+ * @private
+ */
+ onDisplayChanged_: function(mirroring, displays, layout, offset) {
+ if (!this.visible)
+ return;
+
+ var hasExternal = false;
+ for (var i = 0; i < displays.length; i++) {
+ if (!displays[i].isInternal) {
+ hasExternal = true;
+ break;
+ }
+ }
+
+ this.layout_ = layout;
+
+ $('display-options-toggle-mirroring').textContent =
+ loadTimeData.getString(
+ mirroring ? 'stopMirroring' : 'startMirroring');
+
+ // Focus to the first display next to the primary one when |displays| list
+ // is updated.
+ if (mirroring) {
+ this.focusedIndex_ = null;
+ } else if (this.mirroring_ != mirroring ||
+ this.displays_.length != displays.length) {
+ this.focusedIndex_ = 0;
+ }
+
+ this.mirroring_ = mirroring;
+ this.displays_ = displays;
+
+ this.resetDisplaysView_();
+ if (this.mirroring_)
+ this.layoutMirroringDisplays_();
+ else
+ this.layoutDisplays_();
+
+ this.updateSelectedDisplayDescription_();
+ }
+ };
+
+ DisplayOptions.setDisplayInfo = function(
+ mirroring, displays, layout, offset) {
+ DisplayOptions.getInstance().onDisplayChanged_(
+ mirroring, displays, layout, offset);
+ };
+
+ // Export
+ return {
+ DisplayOptions: DisplayOptions
+ };
+});
diff --git a/chromium/chrome/browser/resources/options/chromeos/display_overscan.css b/chromium/chrome/browser/resources/options/chromeos/display_overscan.css
new file mode 100644
index 00000000000..3a09e408e12
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/chromeos/display_overscan.css
@@ -0,0 +1,63 @@
+/* Copyright (c) 2013 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. */
+
+#display-overscan-content-area {
+ margin: 20px;
+ padding: 0;
+}
+
+#display-overscan-operations-table {
+ position: absolute;
+ width: 100%;
+}
+
+#display-overscan-operations-table td {
+ font-size: 12px;
+ text-align: center;
+ width: 50%;
+}
+
+.display-overscan-operation-image {
+ padding-bottom: 30px;
+}
+
+#display-overscan-operation-images-row {
+ vertical-align: middle;
+}
+
+#display-overscan-operation-arrows {
+ background-image: -webkit-image-set(
+ url('overscan_arrows.png') 1x,
+ url('overscan_arrows_2x.png') 2x);
+ background-position: center;
+ background-repeat: no-repeat;
+ height: 51px;
+ width: 100%;
+}
+
+#display-overscan-operation-shift {
+ background-image: -webkit-image-set(
+ url('overscan_shift.png') 1x,
+ url('overscan_shift_2x.png') 2x);
+ background-position: center;
+ background-repeat: no-repeat;
+ height: 23px;
+ width: 100%;
+}
+
+html[dir=rtl] #display-overscan-operation-shift {
+ background-image: -webkit-image-set(
+ url('overscan_shift_rtl.png') 1x,
+ url('overscan_shift_rtl_2x.png') 2x);
+}
+
+#display-overscan-button-strip {
+ bottom: 0;
+ position: absolute;
+ width: 100%;
+}
+
+#display-overscan-buttons-spacer {
+ -webkit-box-flex: 1;
+}
diff --git a/chromium/chrome/browser/resources/options/chromeos/display_overscan.html b/chromium/chrome/browser/resources/options/chromeos/display_overscan.html
new file mode 100644
index 00000000000..53c280affad
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/chromeos/display_overscan.html
@@ -0,0 +1,31 @@
+<div id="display-overscan-page" class="page" hidden>
+ <div class="close-button"></div>
+ <div class="content-area" id="display-overscan-content-area">
+ <table id="display-overscan-operations-table">
+ <tr id="display-overscan-operation-images-row">
+ <td class="display-overscan-operation-image">
+ <div id="display-overscan-operation-arrows"></div></td>
+ <td class="display-overscan-operation-image">
+ <div id="display-overscan-operation-shift"></div></td>
+ </tr>
+ <tr>
+ <td><span i18n-content="shrinkAndExpand"></span></td>
+ <td><span i18n-content="move"></span></td>
+ </tr>
+ </table>
+ <!-- Specify 'reversed' to prevernt re-reversing the button order by
+ options_page. -->
+ <div class="button-strip" id="display-overscan-button-strip" reversed>
+ <button id="display-overscan-operation-reset"
+ i18n-content="overscanReset">
+ </button>
+ <div id="display-overscan-buttons-spacer"></div>
+ <button id="display-overscan-operation-ok"
+ i18n-content="overscanOK">
+ </button>
+ <button id="display-overscan-operation-cancel"
+ i18n-content="overscanCancel">
+ </button>
+ </div>
+ </div>
+</div>
diff --git a/chromium/chrome/browser/resources/options/chromeos/display_overscan.js b/chromium/chrome/browser/resources/options/chromeos/display_overscan.js
new file mode 100644
index 00000000000..0060d6cd6ff
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/chromeos/display_overscan.js
@@ -0,0 +1,160 @@
+// Copyright (c) 2013 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;
+
+ /**
+ * Encapsulated handling of the 'DisplayOverscan' page.
+ * @constructor
+ */
+ function DisplayOverscan() {
+ OptionsPage.call(this, 'displayOverscan',
+ loadTimeData.getString('displayOverscanPageTabTitle'),
+ 'display-overscan-page');
+ }
+
+ cr.addSingletonGetter(DisplayOverscan);
+
+ DisplayOverscan.prototype = {
+ __proto__: OptionsPage.prototype,
+
+ /**
+ * The ID of the target display.
+ * @private
+ */
+ id_: null,
+
+ /**
+ * The keyboard event handler function.
+ * @private
+ */
+ keyHandler_: null,
+
+ /**
+ * Initialize the page.
+ */
+ initializePage: function() {
+ OptionsPage.prototype.initializePage.call(this);
+
+ this.keyHandler_ = this.handleKeyevent_.bind(this);
+ $('display-overscan-operation-reset').onclick = function() {
+ chrome.send('reset');
+ };
+ $('display-overscan-operation-ok').onclick = function() {
+ chrome.send('commit');
+ OptionsPage.closeOverlay();
+ };
+ $('display-overscan-operation-cancel').onclick = function() {
+ OptionsPage.cancelOverlay();
+ };
+ },
+
+ /** @override */
+ handleCancel: function() {
+ // signals the cancel event.
+ chrome.send('cancel');
+ OptionsPage.closeOverlay();
+ },
+
+ /** @override */
+ didShowPage: function() {
+ if (this.id_ == null) {
+ OptionsPage.cancelOverlay();
+ return;
+ }
+
+ window.addEventListener('keydown', this.keyHandler_);
+ // Sets up the size of the overscan dialog based on DisplayOptions dialog.
+ var displayOptionsPage = $('display-options-page');
+ var displayOverscanPage = $('display-overscan-page');
+ displayOverscanPage.style.width =
+ displayOptionsPage.offsetWidth - 20 + 'px';
+ displayOverscanPage.style.minWidth = displayOverscanPage.style.width;
+ displayOverscanPage.style.height =
+ displayOptionsPage.offsetHeight - 50 + 'px';
+
+ // Moves the table to describe operation at the middle of the contents
+ // vertically.
+ var operationsTable = $('display-overscan-operations-table');
+ var buttonsContainer = $('display-overscan-button-strip');
+ operationsTable.style.top = buttonsContainer.offsetTop / 2 -
+ operationsTable.offsetHeight / 2 + 'px';
+
+ $('display-overscan-operation-cancel').focus();
+ chrome.send('start', [this.id_]);
+ },
+
+ /** @override */
+ didClosePage: function() {
+ window.removeEventListener('keydown', this.keyHandler_);
+ },
+
+ /**
+ * Called when the overscan calibration is canceled at the system level,
+ * such like the display is disconnected.
+ * @private
+ */
+ onOverscanCanceled_: function() {
+ if (OptionsPage.getTopmostVisiblePage() == this)
+ OptionsPage.cancelOverlay();
+ },
+
+ /**
+ * Sets the target display id. This method has to be called before
+ * navigating to this page.
+ * @param {string} id The target display id.
+ */
+ setDisplayId: function(id) {
+ this.id_ = id;
+ },
+
+ /**
+ * Key event handler to make the effect of display rectangle.
+ * @param {Event} event The keyboard event.
+ * @private
+ */
+ handleKeyevent_: function(event) {
+ switch (event.keyCode) {
+ case 37: // left arrow
+ if (event.shiftKey)
+ chrome.send('move', ['horizontal', -1]);
+ else
+ chrome.send('resize', ['horizontal', -1]);
+ event.preventDefault();
+ break;
+ case 38: // up arrow
+ if (event.shiftKey)
+ chrome.send('move', ['vertical', -1]);
+ else
+ chrome.send('resize', ['vertical', -1]);
+ event.preventDefault();
+ break;
+ case 39: // right arrow
+ if (event.shiftKey)
+ chrome.send('move', ['horizontal', 1]);
+ else
+ chrome.send('resize', ['horizontal', 1]);
+ event.preventDefault();
+ break;
+ case 40: // bottom arrow
+ if (event.shiftKey)
+ chrome.send('move', ['vertical', 1]);
+ else
+ chrome.send('resize', ['vertical', 1]);
+ event.preventDefault();
+ break;
+ }
+ }
+ };
+
+ DisplayOverscan.onOverscanCanceled = function() {
+ DisplayOverscan.getInstance().onOverscanCanceled_();
+ };
+
+ // Export
+ return {
+ DisplayOverscan: DisplayOverscan
+ };
+});
diff --git a/chromium/chrome/browser/resources/options/chromeos/internet_detail.css b/chromium/chrome/browser/resources/options/chromeos/internet_detail.css
new file mode 100644
index 00000000000..a027a56259f
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/chromeos/internet_detail.css
@@ -0,0 +1,109 @@
+/* Copyright (c) 2012 The Chromium Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file. */
+
+/* Force tab strip to extend to the left and right edges of the window. */
+#internet-details-content-area {
+ -webkit-box-orient: vertical;
+ display: -webkit-box;
+ padding: 6px 0 0 0;
+}
+
+#network-details-header {
+ -webkit-padding-start: 20px;
+ margin: 0;
+ padding-bottom: 12px;
+ padding-top: 32px;
+}
+
+#network-details-title {
+ font-size: 18px;
+}
+
+#network-details-subtitle-status {
+ color: rgb(53, 174, 71);
+}
+
+
+/* Fix the height of the subpages so that the dialog does not resize when the
+ user switches tabs. */
+#internet-details-content-area > .subpages-tab-contents {
+ -webkit-box-flex: 1;
+ -webkit-box-sizing: border-box;
+ -webkit-padding-end: 10px;
+ height: 390px;
+ min-width: 480px;
+ overflow-y: auto;
+}
+
+/* Avoid additional margins between text fields and controlled setting
+ indicators as the fields in these dialogs have sufficient spacing around
+ them already. */
+#internet-details-content-area
+ input:-webkit-any([type='text'],[type='url'],:not([type])) +
+ .controlled-setting-indicator {
+ -webkit-margin-start: 0;
+}
+
+#vpn-tab td {
+ padding: 0;
+}
+
+#vpn-tab .option-value:not(input) {
+ padding: 4px;
+}
+
+#ip-config-list {
+ min-height: 96px !important;
+}
+
+/* Minimum and maximum height are integer multiples of the height of a list
+ entry. */
+#ignored-host-list {
+ -webkit-margin-start: 0;
+ border: 1px solid #bfbfbf;
+ min-height: 64px;
+ width: 400px;
+}
+
+#ignored-host-list[disabled] {
+ background-color: rgb(235, 235, 228);
+ color: #999;
+ opacity: 1;
+}
+
+#new-host {
+ -webkit-margin-start: 0;
+ margin-top: 8px;
+}
+
+#ipconfig-section {
+ border-top: 1px solid #eee;
+ margin-bottom: 8px;
+ padding-top: 8px;
+}
+
+#ipconfig-dns-section {
+ border-top: 1px solid #eee;
+ padding-top: 8px;
+}
+
+#user-dns-settings:not([selected]) {
+ display: none;
+}
+
+.dns-display {
+ -webkit-margin-start: 24px;
+ -webkit-transition: opacity 150ms ease-in-out;
+ color: #bbb;
+ font-style: italic;
+}
+
+.dns-display:not([selected]) {
+ -webkit-transition: opacity 150ms ease-in-out;
+ display: none;
+}
+
+.proxy-subsection {
+ padding-left: 24px;
+}
diff --git a/chromium/chrome/browser/resources/options/chromeos/internet_detail.html b/chromium/chrome/browser/resources/options/chromeos/internet_detail.html
new file mode 100644
index 00000000000..556c70adb51
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/chromeos/internet_detail.html
@@ -0,0 +1,708 @@
+<div id="details-internet-page" class="page" hidden>
+ <div class="close-button"></div>
+ <!-- Network header -->
+ <div id="network-details-header">
+ <div id="network-details-title"></div>
+ <div id="network-details-subtitle">
+ <span id="network-details-subtitle-status"></span>
+ <span id="network-details-subtitle-separator"> - </span>
+ <span id="network-details-subtitle-type"></span>
+ </div>
+ </div>
+ <div id="internet-details-content-area" class="content-area">
+ <!-- Navigation tabs -->
+ <div id="details-tab-strip" class="subpages-nav-tabs">
+ <span id="wifi-network-nav-tab" class="tab wifi-details"
+ tab-contents="wifi-network-tab">
+ <span class="tab-label"
+ i18n-content="wifiNetworkTabLabel"></span>
+ <span class="active-tab-label"
+ i18n-content="wifiNetworkTabLabel"></span>
+ </span>
+ <span id="vpn-nav-tab" class="tab vpn-details"
+ tab-contents="vpn-tab">
+ <span class="tab-label"
+ i18n-content="vpnTabLabel"></span>
+ <span class="active-tab-label" i18n-content="vpnTabLabel"></span>
+ </span>
+ <span id="wimax-network-nav-tab" class="tab wimax-details"
+ tab-contents="wimax-network-tab">
+ <span class="tab-label"
+ i18n-content="wimaxConnTabLabel"></span>
+ <span class="active-tab-label"
+ i18n-content="wimaxConnTabLabel"></span>
+ </span>
+ <span id="cellular-conn-nav-tab" class="tab cellular-details"
+ tab-contents="cellular-conn-tab">
+ <span class="tab-label"
+ i18n-content="cellularConnTabLabel"></span>
+ <span class="active-tab-label"
+ i18n-content="cellularConnTabLabel"></span>
+ </span>
+ <span id="cellular-device-nav-tab" class="tab cellular-details"
+ tab-contents="cellular-device-tab">
+ <span class="tab-label"
+ i18n-content="cellularDeviceTabLabel"></span>
+ <span class="active-tab-label"
+ i18n-content="cellularDeviceTabLabel"></span>
+ </span>
+ <span id="internet-nav-tab" class="tab network-details"
+ tab-contents="internet-tab">
+ <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>
+ <span id="internet-proxy-nav-tab" class="tab proxy-details"
+ tab-contents="network-proxy-tab">
+ <span class="tab-label" i18n-content="proxyTabLabel"></span>
+ <span class="active-tab-label" i18n-content="proxyTabLabel"></span>
+ </span>
+ </div>
+ <div id="wifi-network-tab" class="subpages-tab-contents wifi-details">
+ <section>
+ <table class="option-control-table">
+ <tr id="prefer-network">
+ <td>
+ <div class="checkbox">
+ <span class="controlled-setting-with-label">
+ <input id="prefer-network-wifi" type="checkbox">
+ <span>
+ <label for="prefer-network-wifi"
+ i18n-content="inetPreferredNetwork">
+ </label>
+ <span class="controlled-setting-indicator" data="preferred"
+ for="prefer-network-wifi">
+ </span>
+ </span>
+ </span>
+ </div>
+ </td>
+ </tr>
+ <tr class="auto-connect-network">
+ <td>
+ <div class="checkbox">
+ <span class="controlled-setting-with-label">
+ <input id="auto-connect-network-wifi" type="checkbox">
+ <span>
+ <label for="auto-connect-network-wifi"
+ i18n-content="inetAutoConnectNetwork">
+ </label>
+ <span class="controlled-setting-indicator"
+ data="autoConnect" for="auto-connect-network-wifi">
+ </span>
+ </span>
+ </span>
+ </div>
+ </td>
+ </tr>
+ </table>
+ </section>
+ <section>
+ <table id="wifi-settings-table">
+ <tr>
+ <td class="option-name" i18n-content="connectionState"></td>
+ <td id="wifi-connection-state" class="option-value"></td>
+ <tr>
+ <td class="option-name" i18n-content="inetSsid"></td>
+ <td id="wifi-ssid" class="option-value"></td>
+ </tr>
+ <tr id="wifi-bssid-entry">
+ <td class="option-name" i18n-content="inetBssid"></td>
+ <td id="wifi-bssid" class="option-value"></td>
+ </tr>
+ <tr class="wifi-network-setting">
+ <td class="option-name" i18n-content="inetAddress"></td>
+ <td id="wifi-ip-address" class="option-value"></td>
+ </tr>
+ <tr class="wifi-network-setting">
+ <td class="option-name" i18n-content="inetNetmask"></td>
+ <td id="wifi-netmask" class="option-value"></td>
+ </tr>
+ <tr class="wifi-network-setting">
+ <td class="option-name" i18n-content="inetGateway"></td>
+ <td id="wifi-gateway" class="option-value"></td>
+ </tr>
+ <tr class="wifi-network-setting">
+ <td class="option-name" i18n-content="inetNameServers"></td>
+ <td id="wifi-name-servers" class="option-value"></td>
+ </tr>
+ <tr id="wifi-security-entry">
+ <td class="options-name" i18n-content="inetEncryption"></td>
+ <td id="wifi-security" class="option-value"></td>
+ </tr>
+ <tr>
+ <td class="options-name" i18n-content="inetFrequency"></td>
+ <td id="wifi-frequency" class="option-value"></td>
+ </tr>
+ <tr>
+ <td class="options-name" i18n-content="inetSignalStrength"></td>
+ <td id="wifi-signal-strength" class="option-value"></td>
+ </tr>
+ <tr id="wifi-hardware-address-entry">
+ <td class="option-name" i18n-content="hardwareAddress"></td>
+ <td id="wifi-hardware-address" class="option-value"></td>
+ </tr>
+ </table>
+ </section>
+ <section>
+ <table class="option-control-table">
+ <tr>
+ <td id="password-details" class="option-name"
+ i18n-content="inetPassProtected">
+ </td>
+ </tr>
+ <tr>
+ <td id="wifi-shared-network" class="option-name"
+ i18n-content="inetNetworkShared">
+ </td>
+ </tr>
+ </table>
+ </section>
+ </div>
+ <div id="wimax-network-tab" class="subpages-tab-contents wimax-details">
+ <section>
+ <table class="option-control-table">
+ <tr class="auto-connect-network">
+ <td>
+ <div class="checkbox">
+ <span class="controlled-setting-with-label">
+ <input id="auto-connect-network-wimax" type="checkbox">
+ <span>
+ <label for="auto-connect-network-wimax"
+ i18n-content="inetAutoConnectNetwork">
+ </label>
+ <span class="controlled-setting-indicator"
+ data="autoConnect" for="auto-connect-network-wimax">
+ </span>
+ </span>
+ </span>
+ </div>
+ </td>
+ </tr>
+ </table>
+ </section>
+ <section>
+ <table id="wimax-settings-table">
+ <tr>
+ <td class="option-name" i18n-content="connectionState"></td>
+ <td id="wimax-connection-state" class="option-value"></td>
+ </tr>
+ <tr id="wimax-eap-identity-entry">
+ <td class="option-name" i18n-content="inetUsername"></td>
+ <td id="wimax-eap-identity" class="option-value"></td>
+ </tr>
+ <tr>
+ <td class="options-name" i18n-content="inetSignalStrength"></td>
+ <td id="wimax-signal-strength" class="option-value"></td>
+ </tr>
+ </table>
+ </section>
+ <section>
+ <table class="option-control-table">
+ <tr>
+ <td id="wimax-shared-network" class="option-name"
+ i18n-content="inetNetworkShared">
+ </td>
+ </tr>
+ </table>
+ </section>
+ </div>
+ <div id="vpn-tab" class="subpages-tab-contents vpn-details">
+ <section>
+ <table class="option-control-table">
+ <tr class="auto-connect-network">
+ <td>
+ <div class="checkbox">
+ <span class="controlled-setting-with-label">
+ <input id="auto-connect-network-vpn" type="checkbox">
+ <span>
+ <label for="auto-connect-network-vpn"
+ i18n-content="inetAutoConnectNetwork">
+ </label>
+ <span class="controlled-setting-indicator"
+ data="autoConnect" for="auto-connect-network-vpn">
+ </span>
+ </span>
+ </span>
+ </div>
+ </td>
+ </tr>
+ <tr>
+ <td class="option-name" i18n-content="inetServiceName"></td>
+ <td id="inet-service-name" class="option-value"></td>
+ </tr>
+ <tr>
+ <td class="option-name" i18n-content="inetServerHostname"></td>
+ <td>
+ <input class="option-value" id="inet-server-hostname"></input>
+ <span class="controlled-setting-indicator"
+ data="serverHostname" for="inet-server-hostname"></span>
+ </td>
+ </tr>
+ <tr>
+ <td class="option-name" i18n-content="inetProviderType"></td>
+ <td id="inet-provider-type" class="option-value"></td>
+ </tr>
+ <tr>
+ <td class="option-name" i18n-content="inetUsername"></td>
+ <td id="inet-username" class="option-value"></td>
+ </tr>
+ </table>
+ </section>
+ </div>
+ <div id="cellular-conn-tab" class="subpages-tab-contents cellular-details">
+ <section id="cellular-network-options">
+ <table class="option-control-table">
+ <tr>
+ <td class="option-name" i18n-content="serviceName"></td>
+ <td id="service-name" class="option-value">
+ <select id="select-carrier">
+ </select>
+ <div id="switch-carrier-spinner" class="inline-spinner" hidden>
+ </div>
+ </td>
+ </tr>
+ <tr>
+ <td class="option-name" i18n-content="networkTechnology"></td>
+ <td id="network-technology" class="option-value"></td>
+ </tr>
+ <tr>
+ <td class="option-name" i18n-content="activationState"></td>
+ <td id="activation-state" class="option-value"></td>
+ </tr>
+ <tr>
+ <td class="option-name" i18n-content="roamingState"></td>
+ <td id="roaming-state" class="option-value"></td>
+ </tr>
+ <tr>
+ <td class="option-name" i18n-content="restrictedPool"></td>
+ <td id="restricted-pool" class="option-value"></td>
+ </tr>
+ <tr>
+ <td class="option-name" i18n-content="operatorName"></td>
+ <td id="operator-name" class="option-value"></td>
+ </tr>
+ <tr>
+ <td class="option-name" i18n-content="operatorCode"></td>
+ <td id="operator-code" class="option-value"></td>
+ </tr>
+ <tr>
+ <td class="option-name" i18n-content="errorState"></td>
+ <td id="error-state" class="option-value"></td>
+ </tr>
+ <tr class="gsm-only apn-list-view">
+ <td class="option-name" i18n-content="cellularApnLabel"></td>
+ <td id="cellular-apn-label" class="option-value">
+ <select id="select-apn">
+ <option value="-1" i18n-content="cellularApnOther">
+ </option>
+ </select>
+ <span class="controlled-setting-indicator" data="providerApnList"
+ for="select-apn"></span>
+ </td>
+ </tr>
+ <tr class="gsm-only apn-details-view">
+ <td class="option-name" i18n-content="cellularApnLabel"></td>
+ <td id="cellular-apn-label" class="option-value">
+ <input id="cellular-apn" type="text">
+ </td>
+ </tr>
+ <tr class="gsm-only apn-details-view">
+ <td class="option-name" i18n-content="cellularApnUsername"></td>
+ <td id="cellular-apn-username-label" class="option-value">
+ <input id="cellular-apn-username" type="text">
+ </td>
+ </tr>
+ <tr class="gsm-only apn-details-view">
+ <td class="option-name" i18n-content="cellularApnPassword"></td>
+ <td id="cellular-apn-password-label" class="option-value">
+ <input id="cellular-apn-password" type="password">
+ </td>
+ </tr>
+ <tr class="gsm-only apn-details-view">
+ <td class="option-name"></td>
+ <td class="option-value">
+ <button id="cellular-apn-use-default"
+ i18n-content="cellularApnUseDefault"></button>
+ <button id="cellular-apn-set"
+ i18n-content="cellularApnSet"></button>
+ <button id="cellular-apn-cancel"
+ i18n-content="cellularApnCancel"></button>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="checkbox">
+ <span class="controlled-setting-with-label">
+ <input id="auto-connect-network-cellular" type="checkbox">
+ <span>
+ <label for="auto-connect-network-cellular"
+ i18n-content="inetAutoConnectNetwork">
+ </label>
+ <span class="controlled-setting-indicator"
+ data="autoConnect" for="auto-connect-network-cellular">
+ </span>
+ </span>
+ </span>
+ </div>
+ </td>
+ </tr>
+ </table>
+ </section>
+ </div>
+ <div id="cellular-device-tab" class="subpages-tab-contents
+ cellular-details">
+ <section id="cellular-device-options">
+ <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="model-id" class="option-value"></td>
+ </tr>
+ <tr>
+ <td class="option-name" i18n-content="firmwareRevision"></td>
+ <td id="firmware-revision" class="option-value"></td>
+ </tr>
+ <tr>
+ <td class="option-name" i18n-content="hardwareRevision"></td>
+ <td id="hardware-revision" class="option-value"></td>
+ </tr>
+ <tr class="cdma-only">
+ <td class="option-name" i18n-content="prlVersion"></td>
+ <td id="prl-version" class="option-value"></td>
+ </tr>
+ <tr>
+ <td class="option-name">MEID:</td>
+ <td id="meid" class="option-value"></td>
+ </tr>
+ <tr class="gsm-only">
+ <td class="option-name">ICCID:</td>
+ <td id="iccid" 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/MSID:</td>
+ <td id="min" class="option-value"></td>
+ </tr>
+ </table>
+ </section>
+ </div>
+ <div id="internet-tab" class="subpages-tab-contents">
+ <section id="advanced-section">
+ <table class="option-control-table">
+ <tr>
+ <td class="option-name" i18n-content="connectionState"></td>
+ <td id="connection-state" class="option-value"></td>
+ </tr>
+ <tr id="hardware-address-row">
+ <td class="option-name" i18n-content="hardwareAddress"></td>
+ <td id="hardware-address" class="option-value"></td>
+ </tr>
+ </table>
+ </section>
+ <section id="ipconfig-section">
+ <div id="ip-automatic-configuration" class="checkbox">
+ <span class="controlled-setting-with-label">
+ <input id="ip-automatic-configuration-checkbox"
+ type="checkbox">
+ <span>
+ <label for="ip-automatic-configuration-checkbox"
+ i18n-content="ipAutomaticConfiguration">
+ </label>
+ <span class="controlled-setting-indicator" data="ipconfigDHCP"
+ for="ip-automatic-configuration">
+ </span>
+ </span>
+ </span>
+ </div>
+ <div>
+ <table id="ip-address-settings">
+ <tr>
+ <td class="spacer" width="14px"></td>
+ <td class="option-name" i18n-content="inetAddress"></td>
+ <td><div id="ip-address"></div></td>
+ </tr>
+ <tr>
+ <td class="spacer" width="14px"></td>
+ <td class="option-name" id="ip-netmask-label"
+ i18n-content="inetNetmask"></td>
+ <td><div id="ip-netmask"></div></td>
+ </tr>
+ <tr>
+ <td class="spacer" width="14px"></td>
+ <td class="option-name" i18n-content="inetGateway"></td>
+ <td><div id="ip-gateway"></div></td>
+ </tr>
+ </table>
+ </section>
+ <section id="ipconfig-dns-section">
+ <div class="radio">
+ <label>
+ <input id="automatic-dns-radio" type="radio" name="dnstype"
+ value="automatic">
+ <span i18n-content="automaticNameServers"></span>
+ </label>
+ </div>
+ <div id="automatic-dns-display" class="dns-display"></div>
+ <div class="radio">
+ <label>
+ <input id="google-dns-radio" type="radio" name="dnstype"
+ value="google">
+ <span id="google-dns-label"></span>
+ </label>
+ </div>
+ <div id="google-dns-display" class="dns-display"></div>
+ <div class="radio">
+ <label>
+ <input id="user-dns-radio" type="radio" name="dnstype"
+ value="user">
+ <span i18n-content="userNameServers"></span>
+ </label>
+ </div>
+ <table id="user-dns-settings">
+ <tr>
+ <td class="spacer" width="14px"></td>
+ <td>
+ <div id="ipconfig-dns1" i18n-placeholder-text="userNameServer1"
+ allow-empty>
+ </div>
+ </td>
+ <td>
+ <div id="ipconfig-dns2" i18n-placeholder-text="userNameServer2"
+ allow-empty>
+ </div>
+ </td>
+ </tr>
+ <tr>
+ <td class="spacer" width="14px"></td>
+ <td>
+ <div id="ipconfig-dns3" i18n-placeholder-text="userNameServer3"
+ allow-empty>
+ </div>
+ </td>
+ <td>
+ <div id="ipconfig-dns4" i18n-placeholder-text="userNameServer4"
+ allow-empty>
+ </div>
+ </td>
+ </tr>
+ </table>
+ </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">
+ <span class="controlled-setting-with-label">
+ <input id="sim-card-lock-enabled" type="checkbox">
+ <span>
+ <label for="sim-card-lock-enabled" i18n-content="lockSimCard">
+ </label>
+ <span class="controlled-setting-indicator"
+ data="simCardLockEnabled" for="sim-card-lock-enabled">
+ </span>
+ </span>
+ </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 id="network-proxy-tab" class="subpages-tab-contents">
+ <section>
+ <div id="network-proxy-info-banner" hidden>
+ <span id="banner-text" class="page-banner-text"></span>
+ </div>
+ <div class="radio">
+ <label>
+ <input id="direct-proxy" type="radio" name="proxytype" value="1"
+ pref="cros.session.proxy.type">
+ <span i18n-content="proxyDirectInternetConnection"></span>
+ </label>
+ </div>
+ <div class="radio">
+ <label>
+ <input id="auto-proxy" type="radio" name="proxytype" value="3"
+ pref="cros.session.proxy.type">
+ <span i18n-content="proxyAutomatic"></span>
+ </label>
+ </div>
+ <div class="proxy-subsection" id="auto-proxy-parms">
+ <div class="checkbox">
+ <label>
+ <input id="proxy-use-pac-url" type="checkbox"
+ pref="cros.session.proxy.usepacurl">
+ <span i18n-content="proxyUseConfigUrl"></span>
+ </label>
+ </div>
+ <div>
+ <label>
+ <input id="proxy-pac-url" type="url" size="50"
+ pref="cros.session.proxy.pacurl">
+ </label>
+ </div>
+ </div>
+ <div class="radio">
+ <label>
+ <input id="manual-proxy" type="radio" name="proxytype" value="2"
+ pref="cros.session.proxy.type">
+ <span i18n-content="proxyManual"></span>
+ </label>
+ </div>
+ <div class="proxy-subsection" id="manual-proxy-parms">
+ <div class="checkbox">
+ <label>
+ <input id="proxy-all-protocols" type="checkbox"
+ pref="cros.session.proxy.single">
+ <span i18n-content="sameProxyProtocols"></span>
+ </label>
+ </div>
+ <div id="single-proxy">
+ <table>
+ <tr>
+ <td>
+ <span i18n-content="httpProxy"></span>
+ <input id="proxy-host-single-name" type="text" size="25"
+ pref="cros.session.proxy.singlehttp" disabled>
+ </td>
+ <td>
+ <span i18n-content="proxyPort"></span>
+ <input id="proxy-host-single-port" size="4"
+ pref="cros.session.proxy.singlehttpport" disabled>
+ </td>
+ </tr>
+ </table>
+ </div>
+ <div id="multi-proxy">
+ <table>
+ <tr>
+ <td>
+ <span i18n-content="httpProxy"></span>
+ </td>
+ <td>
+ <input id="proxy-host-name" type="text" size="25"
+ pref="cros.session.proxy.httpurl" disabled>
+ </td>
+ <td>
+ <span i18n-content="proxyPort"></span>
+ </td>
+ <td>
+ <input id="proxy-host-port" size="4"
+ pref="cros.session.proxy.httpport" disabled>
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <span i18n-content="secureHttpProxy"></span>
+ </td>
+ <td>
+ <input id="secure-proxy-host-name" type="text" size="25"
+ pref="cros.session.proxy.httpsurl" disabled>
+ </td>
+ <td>
+ <span i18n-content="proxyPort"></span>
+ </td>
+ <td>
+ <input id="secure-proxy-port" size="4"
+ pref="cros.session.proxy.httpsport" disabled>
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <span i18n-content="ftpProxy"></span>
+ </td>
+ <td>
+ <input id="ftp-proxy" type="text" size="25"
+ pref="cros.session.proxy.ftpurl" disabled>
+ </td>
+ <td>
+ <span i18n-content="proxyPort"></span>
+ </td>
+ <td>
+ <input id="ftp-proxy-port" size="4"
+ pref="cros.session.proxy.ftpport" disabled>
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <span i18n-content="socksHost"></span>
+ </td>
+ <td>
+ <input id="socks-host" type="text" size="25"
+ pref="cros.session.proxy.socks" disabled>
+ </td>
+ <td>
+ <span i18n-content="proxyPort"></span>
+ </td>
+ <td>
+ <input id="socks-port" size="4"
+ pref="cros.session.proxy.socksport" disabled>
+ </td>
+ </tr>
+ </table>
+ </div>
+ <div id="advanced-config">
+ <div class="option vbox flex">
+ <div i18n-content="proxyBypass"></div>
+ <list id="ignored-host-list"></list>
+ <input id="new-host" type="url" size="30">
+ <button id="add-host" i18n-content="addHost"></button>
+ <button id="remove-host" i18n-content="removeHost"></button>
+ </div>
+ </div>
+ </div>
+ <div class="proxy-subsection" id="web-proxy-auto-discovery">
+ <span i18n-content="webProxyAutoDiscoveryUrl"></span>
+ <input id="web-proxy-auto-discovery-url" type="url" disabled>
+ </div>
+ </section>
+ </div>
+ </div>
+ <div class="action-area">
+ <div class="button-strip">
+ <!-- TODO(dbeam): Clarify style guide regarding tag wrap. -->
+ <button id="details-internet-dismiss" class="default-button"
+ i18n-content="detailsInternetDismiss">
+ </button>
+ <button id="details-internet-login" i18n-content="connectButton">
+ </button>
+ <button id="details-internet-disconnect" i18n-content="disconnectButton">
+ </button>
+ <button id="details-internet-configure" i18n-content="configureButton">
+ </button>
+ <button id="activate-details" i18n-content="activateButton"></button>
+ <button id="buyplan-details" i18n-content="buyplanButton"></button>
+ <button id="view-account-details" i18n-content="viewAccountButton">
+ </button>
+ </div>
+ </div>
+</div>
diff --git a/chromium/chrome/browser/resources/options/chromeos/internet_detail.js b/chromium/chrome/browser/resources/options/chromeos/internet_detail.js
new file mode 100644
index 00000000000..cbc07f21d2c
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/chromeos/internet_detail.js
@@ -0,0 +1,1265 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options.internet', function() {
+ var OptionsPage = options.OptionsPage;
+ /** @const */ var ArrayDataModel = cr.ui.ArrayDataModel;
+ /** @const */ var IPAddressField = options.internet.IPAddressField;
+
+ /**
+ * Network settings constants. These enums must match their C++
+ * counterparts.
+ */
+ function Constants() {}
+
+ // Network types:
+ Constants.TYPE_UNKNOWN = 'UNKNOWN';
+ Constants.TYPE_ETHERNET = 'ethernet';
+ Constants.TYPE_WIFI = 'wifi';
+ Constants.TYPE_WIMAX = 'wimax';
+ Constants.TYPE_BLUETOOTH = 'bluetooth';
+ Constants.TYPE_CELLULAR = 'cellular';
+ Constants.TYPE_VPN = 'vpn';
+
+ /*
+ * Helper function to set hidden attribute for elements matching a selector.
+ * @param {string} selector CSS selector for extracting a list of elements.
+ * @param {bool} hidden New hidden value.
+ */
+ function updateHidden(selector, hidden) {
+ var elements = cr.doc.querySelectorAll(selector);
+ for (var i = 0, el; el = elements[i]; i++) {
+ el.hidden = hidden;
+ }
+ }
+
+ /*
+ * Helper function to update the properties of the data object from the
+ * properties in the update object.
+ * @param {object} data object to update.
+ * @param {object} object containing the updated properties.
+ */
+ function updateDataObject(data, update) {
+ for (prop in update) {
+ if (prop in data)
+ data[prop] = update[prop];
+ }
+ }
+
+ /**
+ * Monitor pref change of given element.
+ * @param {Element} el Target element.
+ */
+ function observePrefsUI(el) {
+ Preferences.getInstance().addEventListener(el.pref, handlePrefUpdate);
+ }
+
+ /**
+ * UI pref change handler.
+ * @param {Event} e The update event.
+ */
+ function handlePrefUpdate(e) {
+ DetailsInternetPage.getInstance().updateControls();
+ }
+
+ /**
+ * Simple helper method for converting a field to a string. It is used to
+ * easily assign an empty string from fields that may be unknown or undefined.
+ * @param {object} value that should be converted to a string.
+ * @return {string} the result.
+ */
+ function stringFromValue(value) {
+ return value ? String(value) : '';
+ }
+
+ /**
+ * Sends the 'checked' state of a control to chrome for a network.
+ * @param {string} path The service path of the network.
+ * @param {string} message The message to send to chrome.
+ * @param {HTMLInputElement} checkbox The checkbox storing the value to send.
+ */
+ function sendCheckedIfEnabled(path, message, checkbox) {
+ if (!checkbox.hidden && !checkbox.disabled)
+ chrome.send(message, [path, checkbox.checked ? 'true' : 'false']);
+ }
+
+ /////////////////////////////////////////////////////////////////////////////
+ // DetailsInternetPage class:
+
+ /**
+ * Encapsulated handling of ChromeOS internet details overlay page.
+ * @constructor
+ */
+ function DetailsInternetPage() {
+ OptionsPage.call(this,
+ 'detailsInternetPage',
+ null,
+ 'details-internet-page');
+ }
+
+ 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);
+ var params = parseQueryParams(window.location);
+ this.initializePageContents_(params);
+ this.showNetworkDetails_(params);
+ },
+
+ /**
+ * Auto-activates the network details dialog if network information
+ * is included in the URL.
+ */
+ showNetworkDetails_: function(params) {
+ var servicePath = params.servicePath;
+ if (!servicePath || !servicePath.length)
+ return;
+ var networkType = ''; // ignored for 'options'
+ chrome.send('networkCommand', [networkType, servicePath, 'options']);
+ },
+
+
+ /**
+ * Initializes the contents of the page.
+ */
+ initializePageContents_: function(params) {
+ $('details-internet-dismiss').addEventListener('click', function(event) {
+ DetailsInternetPage.setDetails();
+ });
+
+ $('details-internet-login').addEventListener('click', function(event) {
+ DetailsInternetPage.setDetails();
+ DetailsInternetPage.loginFromDetails();
+ });
+
+ $('details-internet-disconnect').addEventListener('click',
+ function(event) {
+ DetailsInternetPage.setDetails();
+ DetailsInternetPage.disconnectNetwork();
+ });
+
+ $('details-internet-configure').addEventListener('click',
+ function(event) {
+ DetailsInternetPage.setDetails();
+ DetailsInternetPage.configureNetwork();
+ });
+
+ $('activate-details').addEventListener('click', function(event) {
+ DetailsInternetPage.activateFromDetails();
+ });
+
+ $('buyplan-details').addEventListener('click', function(event) {
+ var data = $('connection-state').data;
+ chrome.send('buyDataPlan', [data.servicePath]);
+ OptionsPage.closeOverlay();
+ });
+
+ $('view-account-details').addEventListener('click', function(event) {
+ var data = $('connection-state').data;
+ chrome.send('showMorePlanInfo', [data.servicePath]);
+ OptionsPage.closeOverlay();
+ });
+
+ $('cellular-apn-use-default').addEventListener('click', function(event) {
+ var data = $('connection-state').data;
+ var apnSelector = $('select-apn');
+
+ if (data.userApnIndex != -1) {
+ apnSelector.remove(data.userApnIndex);
+ data.userApnIndex = -1;
+ }
+
+ if (data.providerApnList.value.length > 0) {
+ var iApn = 0;
+ var defaultApn = data.providerApnList.value[iApn];
+ data.apn.apn = stringFromValue(defaultApn.apn);
+ data.apn.username = stringFromValue(defaultApn.username);
+ data.apn.password = stringFromValue(defaultApn.password);
+ chrome.send('setApn', [data.servicePath,
+ data.apn.apn,
+ data.apn.username,
+ 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;
+ }
+ updateHidden('.apn-list-view', false);
+ updateHidden('.apn-details-view', true);
+ });
+
+ $('cellular-apn-set').addEventListener('click', function(event) {
+ if ($('cellular-apn').value == '')
+ return;
+
+ var data = $('connection-state').data;
+ var apnSelector = $('select-apn');
+
+ data.apn.apn = stringFromValue($('cellular-apn').value);
+ data.apn.username = stringFromValue($('cellular-apn-username').value);
+ data.apn.password = stringFromValue($('cellular-apn-password').value);
+ data.userApn = {
+ 'apn': data.apn.apn,
+ 'username': data.apn.username,
+ 'password': data.apn.password
+ };
+ chrome.send('setApn', [data.servicePath,
+ data.apn.apn,
+ data.apn.username,
+ 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;
+
+ updateHidden('.apn-list-view', false);
+ updateHidden('.apn-details-view', true);
+ });
+
+ $('cellular-apn-cancel').addEventListener('click', function(event) {
+ $('select-apn').selectedIndex = $('connection-state').data.selectedApn;
+ updateHidden('.apn-list-view', false);
+ updateHidden('.apn-details-view', true);
+ });
+
+ $('select-apn').addEventListener('change', function(event) {
+ var data = $('connection-state').data;
+ var apnSelector = $('select-apn');
+ if (apnSelector[apnSelector.selectedIndex].value != -1) {
+ var apnList = data.providerApnList.value;
+ chrome.send('setApn', [data.servicePath,
+ stringFromValue(apnList[apnSelector.selectedIndex].apn),
+ stringFromValue(apnList[apnSelector.selectedIndex].username),
+ stringFromValue(apnList[apnSelector.selectedIndex].password)]
+ );
+ data.selectedApn = apnSelector.selectedIndex;
+ } else if (apnSelector.selectedIndex == data.userApnIndex) {
+ chrome.send('setApn', [data.servicePath,
+ stringFromValue(data.userApn.apn),
+ stringFromValue(data.userApn.username),
+ stringFromValue(data.userApn.password)]);
+ data.selectedApn = apnSelector.selectedIndex;
+ } else {
+ $('cellular-apn').value = stringFromValue(data.apn.apn);
+ $('cellular-apn-username').value = stringFromValue(data.apn.username);
+ $('cellular-apn-password').value = stringFromValue(data.apn.password);
+
+ updateHidden('.apn-list-view', true);
+ updateHidden('.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');
+ });
+
+ // Proxy
+ ['proxy-host-single-port',
+ 'secure-proxy-port',
+ 'socks-port',
+ 'ftp-proxy-port',
+ 'proxy-host-port'
+ ].forEach(function(id) {
+ options.PrefPortNumber.decorate($(id));
+ });
+
+ options.proxyexceptions.ProxyExceptions.decorate($('ignored-host-list'));
+ $('remove-host').addEventListener('click',
+ this.handleRemoveProxyExceptions_);
+ $('add-host').addEventListener('click', this.handleAddProxyException_);
+ $('direct-proxy').addEventListener('click', this.disableManualProxy_);
+ $('manual-proxy').addEventListener('click', this.enableManualProxy_);
+ $('auto-proxy').addEventListener('click', this.disableManualProxy_);
+ $('proxy-all-protocols').addEventListener('click',
+ this.toggleSingleProxy_);
+ $('proxy-use-pac-url').addEventListener('change',
+ this.handleAutoConfigProxy_);
+
+ observePrefsUI($('direct-proxy'));
+ observePrefsUI($('manual-proxy'));
+ observePrefsUI($('auto-proxy'));
+ observePrefsUI($('proxy-all-protocols'));
+ observePrefsUI($('proxy-use-pac-url'));
+
+ $('ip-automatic-configuration-checkbox').addEventListener('click',
+ this.handleIpAutoConfig_);
+ $('automatic-dns-radio').addEventListener('click',
+ this.handleNameServerTypeChange_);
+ $('google-dns-radio').addEventListener('click',
+ this.handleNameServerTypeChange_);
+ $('user-dns-radio').addEventListener('click',
+ this.handleNameServerTypeChange_);
+
+ // We only load this string if we have the string data available
+ // because the proxy settings page on the login screen re-uses the
+ // proxy sub-page from the internet options, and it doesn't ever
+ // show the DNS settings, so we don't need this string there.
+ // The string isn't available because
+ // chrome://settings-frame/strings.js (where the string is
+ // stored) is not accessible from the login screen.
+ // TODO(pneubeck): Remove this once i18n of the proxy dialog on the login
+ // page is fixed. http://crbug.com/242865
+ if (loadTimeData.data_) {
+ $('google-dns-label').innerHTML =
+ loadTimeData.getString('googleNameServers');
+ }
+ },
+
+ /**
+ * Handler for "add" event fired from userNameEdit.
+ * @param {Event} e Add event fired from userNameEdit.
+ * @private
+ */
+ handleAddProxyException_: function(e) {
+ var exception = $('new-host').value;
+ $('new-host').value = '';
+
+ exception = exception.trim();
+ if (exception)
+ $('ignored-host-list').addException(exception);
+ },
+
+ /**
+ * Handler for when the remove button is clicked
+ * @param {Event} e The click event.
+ * @private
+ */
+ handleRemoveProxyExceptions_: function(e) {
+ var selectedItems = $('ignored-host-list').selectedItems;
+ for (var x = 0; x < selectedItems.length; x++) {
+ $('ignored-host-list').removeException(selectedItems[x]);
+ }
+ },
+
+ /**
+ * Handler for when the IP automatic configuration checkbox is clicked.
+ * @param {Event} e The click event.
+ * @private
+ */
+ handleIpAutoConfig_: function(e) {
+ var checked = $('ip-automatic-configuration-checkbox').checked;
+ var fields = [$('ip-address'), $('ip-netmask'), $('ip-gateway')];
+ for (var i = 0; i < fields.length; ++i) {
+ fields[i].editable = !checked;
+ if (checked) {
+ var model = fields[i].model;
+ model.value = model.automatic;
+ fields[i].model = model;
+ }
+ }
+ if (!checked)
+ $('ip-address').focus();
+ },
+
+ /**
+ * Handler for when the name server selection changes.
+ * @param {Event} e The click event.
+ * @private
+ */
+ handleNameServerTypeChange_: function(event) {
+ var type = event.target.value;
+ DetailsInternetPage.updateNameServerDisplay(type);
+ },
+
+ /**
+ * 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.
+ $('ipconfig-section').hidden = !this.connected && this.deviceConnected;
+ $('ipconfig-dns-section').hidden =
+ !this.connected && this.deviceConnected;
+
+ // Network type related.
+ updateHidden('#details-internet-page .cellular-details', !this.cellular);
+ updateHidden('#details-internet-page .wifi-details', !this.wireless);
+ updateHidden('#details-internet-page .wimax-details', !this.wimax);
+ updateHidden('#details-internet-page .vpn-details', !this.vpn);
+ updateHidden('#details-internet-page .proxy-details', !this.showProxy);
+ // Conditionally call updateHidden on .gsm-only, so that we don't unhide
+ // a previously hidden element.
+ if (this.gsm)
+ updateHidden('#details-internet-page .cdma-only', true);
+ else
+ updateHidden('#details-internet-page .gsm-only', true);
+ /* Network information merged into the Wifi tab for wireless networks
+ unless the option is set for enabling a static IP configuration. */
+ updateHidden('#details-internet-page .network-details',
+ (this.wireless && !this.showStaticIPConfig) || this.vpn);
+ updateHidden('#details-internet-page .wifi-network-setting',
+ this.showStaticIPConfig);
+
+ // Wifi - Password and shared.
+ updateHidden('#details-internet-page #password-details',
+ !this.wireless || !this.password);
+ updateHidden('#details-internet-page #wifi-shared-network',
+ !this.shared);
+ updateHidden('#details-internet-page #prefer-network',
+ !this.showPreferred);
+
+ // WiMAX.
+ updateHidden('#details-internet-page #wimax-shared-network',
+ !this.shared);
+
+ // Proxy
+ this.updateProxyBannerVisibility_();
+ this.toggleSingleProxy_();
+ if ($('manual-proxy').checked)
+ this.enableManualProxy_();
+ else
+ this.disableManualProxy_();
+ },
+
+ /**
+ * Updates info banner visibility state. This function shows the banner
+ * if proxy is managed or shared-proxies is off for shared network.
+ * @private
+ */
+ updateProxyBannerVisibility_: function() {
+ var bannerDiv = $('network-proxy-info-banner');
+ if (!loadTimeData.data_) {
+ // TODO(pneubeck): This temporarily prevents an exception below until
+ // i18n of the proxy dialog on the login page is
+ // fixed. http://crbug.com/242865
+ bannerDiv.hidden = true;
+ return;
+ }
+
+ // Show banner and determine its message if necessary.
+ var controlledBy = $('direct-proxy').controlledBy;
+ if (!controlledBy || controlledBy == '') {
+ bannerDiv.hidden = true;
+ } else {
+ bannerDiv.hidden = false;
+ // The possible banner texts are loaded in proxy_handler.cc.
+ var bannerText = 'proxyBanner' + controlledBy.charAt(0).toUpperCase() +
+ controlledBy.slice(1);
+ $('banner-text').textContent = loadTimeData.getString(bannerText);
+ }
+ },
+
+ /**
+ * Handler for when the user clicks on the checkbox to allow a
+ * single proxy usage.
+ * @private
+ * @param {Event} e Click Event.
+ */
+ toggleSingleProxy_: function(e) {
+ if ($('proxy-all-protocols').checked) {
+ $('multi-proxy').hidden = true;
+ $('single-proxy').hidden = false;
+ } else {
+ $('multi-proxy').hidden = false;
+ $('single-proxy').hidden = true;
+ }
+ },
+
+ /**
+ * Handler for when the user clicks on the checkbox to enter
+ * auto configuration URL.
+ * @private
+ * @param {Event} e Click Event.
+ */
+ handleAutoConfigProxy_: function(e) {
+ $('proxy-pac-url').disabled = !$('proxy-use-pac-url').checked;
+ },
+
+ /**
+ * Handler for selecting a radio button that will disable the manual
+ * controls.
+ * @private
+ * @param {Event} e Click event.
+ */
+ disableManualProxy_: function(e) {
+ $('ignored-host-list').disabled = true;
+ $('new-host').disabled = true;
+ $('remove-host').disabled = true;
+ $('add-host').disabled = true;
+ $('proxy-all-protocols').disabled = true;
+ $('proxy-host-name').disabled = true;
+ $('proxy-host-port').disabled = true;
+ $('proxy-host-single-name').disabled = true;
+ $('proxy-host-single-port').disabled = true;
+ $('secure-proxy-host-name').disabled = true;
+ $('secure-proxy-port').disabled = true;
+ $('ftp-proxy').disabled = true;
+ $('ftp-proxy-port').disabled = true;
+ $('socks-host').disabled = true;
+ $('socks-port').disabled = true;
+ $('proxy-use-pac-url').disabled = $('auto-proxy').disabled ||
+ !$('auto-proxy').checked;
+ $('proxy-pac-url').disabled = $('proxy-use-pac-url').disabled ||
+ !$('proxy-use-pac-url').checked;
+ $('auto-proxy-parms').hidden = !$('auto-proxy').checked;
+ $('manual-proxy-parms').hidden = !$('manual-proxy').checked;
+ },
+
+ /**
+ * Handler for selecting a radio button that will enable the manual
+ * controls.
+ * @private
+ * @param {Event} e Click event.
+ */
+ enableManualProxy_: function(e) {
+ $('ignored-host-list').redraw();
+ var allDisabled = $('manual-proxy').disabled;
+ $('ignored-host-list').disabled = allDisabled;
+ $('new-host').disabled = allDisabled;
+ $('remove-host').disabled = allDisabled;
+ $('add-host').disabled = allDisabled;
+ $('proxy-all-protocols').disabled = allDisabled;
+ $('proxy-host-name').disabled = allDisabled;
+ $('proxy-host-port').disabled = allDisabled;
+ $('proxy-host-single-name').disabled = allDisabled;
+ $('proxy-host-single-port').disabled = allDisabled;
+ $('secure-proxy-host-name').disabled = allDisabled;
+ $('secure-proxy-port').disabled = allDisabled;
+ $('ftp-proxy').disabled = allDisabled;
+ $('ftp-proxy-port').disabled = allDisabled;
+ $('socks-host').disabled = allDisabled;
+ $('socks-port').disabled = allDisabled;
+ $('proxy-use-pac-url').disabled = true;
+ $('proxy-pac-url').disabled = true;
+ $('auto-proxy-parms').hidden = !$('auto-proxy').checked;
+ $('manual-proxy-parms').hidden = !$('manual-proxy').checked;
+ },
+ };
+
+ /**
+ * Enables or Disables all buttons that provide operations on the cellular
+ * network.
+ */
+ DetailsInternetPage.changeCellularButtonsState = function(disable) {
+ var buttonsToDisableList =
+ new Array('details-internet-login',
+ 'details-internet-disconnect',
+ 'details-internet-configure',
+ 'activate-details',
+ 'buyplan-details',
+ 'view-account-details');
+
+ for (var i = 0; i < buttonsToDisableList.length; ++i) {
+ button = $(buttonsToDisableList[i]);
+ button.disabled = disable;
+ }
+ };
+
+ /**
+ * Shows a spinner while the carrier is changed.
+ */
+ DetailsInternetPage.showCarrierChangeSpinner = function(visible) {
+ $('switch-carrier-spinner').hidden = !visible;
+ // Disable any buttons that allow us to operate on cellular networks.
+ DetailsInternetPage.changeCellularButtonsState(visible);
+ };
+
+ /**
+ * Changes the network carrier.
+ */
+ DetailsInternetPage.handleCarrierChanged = function() {
+ var carrierSelector = $('select-carrier');
+ var carrier = carrierSelector[carrierSelector.selectedIndex].textContent;
+ DetailsInternetPage.showCarrierChangeSpinner(true);
+ var data = $('connection-state').data;
+ chrome.send('setCarrier', [data.servicePath, carrier]);
+ };
+
+ /**
+ * Performs minimal initialization of the InternetDetails dialog in
+ * preparation for showing proxy-setttings.
+ */
+ DetailsInternetPage.initializeProxySettings = function() {
+ var detailsPage = DetailsInternetPage.getInstance();
+ detailsPage.initializePageContents_();
+ };
+
+ /**
+ * Displays the InternetDetails dialog with only the proxy settings visible.
+ */
+ DetailsInternetPage.showProxySettings = function() {
+ var detailsPage = DetailsInternetPage.getInstance();
+ $('network-details-header').hidden = true;
+ $('buyplan-details').hidden = true;
+ $('activate-details').hidden = true;
+ $('view-account-details').hidden = true;
+ $('web-proxy-auto-discovery').hidden = true;
+ detailsPage.cellular = false;
+ detailsPage.wireless = false;
+ detailsPage.vpn = false;
+ detailsPage.showProxy = true;
+ updateHidden('#internet-tab', true);
+ updateHidden('#details-tab-strip', true);
+ updateHidden('#details-internet-page .action-area', true);
+ detailsPage.updateControls();
+ detailsPage.visible = true;
+ };
+
+ /**
+ * Initializes even handling for keyboard driven flow.
+ */
+ DetailsInternetPage.initializeKeyboardFlow = function() {
+ keyboard.initializeKeyboardFlow();
+ };
+
+ DetailsInternetPage.updateProxySettings = function(type) {
+ var proxyHost = null,
+ proxyPort = null;
+
+ if (type == 'cros.session.proxy.singlehttp') {
+ proxyHost = 'proxy-host-single-name';
+ proxyPort = 'proxy-host-single-port';
+ }else if (type == 'cros.session.proxy.httpurl') {
+ proxyHost = 'proxy-host-name';
+ proxyPort = 'proxy-host-port';
+ }else if (type == 'cros.session.proxy.httpsurl') {
+ proxyHost = 'secure-proxy-host-name';
+ proxyPort = 'secure-proxy-port';
+ }else if (type == 'cros.session.proxy.ftpurl') {
+ proxyHost = 'ftp-proxy';
+ proxyPort = 'ftp-proxy-port';
+ }else if (type == 'cros.session.proxy.socks') {
+ proxyHost = 'socks-host';
+ proxyPort = 'socks-port';
+ }else {
+ return;
+ }
+
+ var hostValue = $(proxyHost).value;
+ if (hostValue.indexOf(':') !== -1) {
+ if (hostValue.match(/:/g).length == 1) {
+ hostValue = hostValue.split(':');
+ $(proxyHost).value = hostValue[0];
+ $(proxyPort).value = hostValue[1];
+ }
+ }
+ };
+
+ DetailsInternetPage.updateCarrier = function() {
+ DetailsInternetPage.showCarrierChangeSpinner(false);
+ };
+
+ DetailsInternetPage.updateSecurityTab = function(requirePin) {
+ $('sim-card-lock-enabled').checked = requirePin;
+ $('change-pin').hidden = !requirePin;
+ };
+
+ DetailsInternetPage.loginFromDetails = function() {
+ var data = $('connection-state').data;
+ var servicePath = data.servicePath;
+ chrome.send('networkCommand', [String(data.type),
+ servicePath,
+ 'connect']);
+ OptionsPage.closeOverlay();
+ };
+
+ DetailsInternetPage.disconnectNetwork = function() {
+ var data = $('connection-state').data;
+ var servicePath = data.servicePath;
+ chrome.send('networkCommand', [String(data.type),
+ servicePath,
+ 'disconnect']);
+ OptionsPage.closeOverlay();
+ };
+
+ DetailsInternetPage.configureNetwork = function() {
+ var data = $('connection-state').data;
+ var servicePath = data.servicePath;
+ chrome.send('networkCommand', [String(data.type),
+ servicePath,
+ 'configure']);
+ OptionsPage.closeOverlay();
+ };
+
+ DetailsInternetPage.activateFromDetails = function() {
+ var data = $('connection-state').data;
+ var servicePath = data.servicePath;
+ if (data.type == Constants.TYPE_CELLULAR) {
+ chrome.send('networkCommand', [String(data.type),
+ servicePath,
+ 'activate']);
+ }
+ OptionsPage.closeOverlay();
+ };
+
+ DetailsInternetPage.setDetails = function() {
+ var data = $('connection-state').data;
+ var servicePath = data.servicePath;
+ if (data.type == Constants.TYPE_WIFI) {
+ sendCheckedIfEnabled(servicePath, 'setPreferNetwork',
+ $('prefer-network-wifi'));
+ sendCheckedIfEnabled(servicePath, 'setAutoConnect',
+ $('auto-connect-network-wifi'));
+ } else if (data.type == Constants.TYPE_WIMAX) {
+ sendCheckedIfEnabled(servicePath, 'setAutoConnect',
+ $('auto-connect-network-wimax'));
+ } else if (data.type == Constants.TYPE_CELLULAR) {
+ sendCheckedIfEnabled(servicePath, 'setAutoConnect',
+ $('auto-connect-network-cellular'));
+ } else if (data.type == Constants.TYPE_VPN) {
+ chrome.send('setServerHostname',
+ [servicePath,
+ $('inet-server-hostname').value]);
+ sendCheckedIfEnabled(servicePath, 'setAutoConnect',
+ $('auto-connect-network-vpn'));
+ }
+
+ var nameServerTypes = ['automatic', 'google', 'user'];
+ var nameServerType = 'automatic';
+ for (var i = 0; i < nameServerTypes.length; ++i) {
+ if ($(nameServerTypes[i] + '-dns-radio').checked) {
+ nameServerType = nameServerTypes[i];
+ break;
+ }
+ }
+
+ // Skip any empty values.
+ var userNameServers = [];
+ for (var i = 1; i <= 4; ++i) {
+ var nameServerField = $('ipconfig-dns' + i);
+ if (nameServerField && nameServerField.model &&
+ nameServerField.model.value) {
+ userNameServers.push(nameServerField.model.value);
+ }
+ }
+
+ userNameServers = userNameServers.join(',');
+
+ chrome.send('setIPConfig',
+ [servicePath,
+ Boolean($('ip-automatic-configuration-checkbox').checked),
+ $('ip-address').model.value || '',
+ $('ip-netmask').model.value || '',
+ $('ip-gateway').model.value || '',
+ nameServerType,
+ userNameServers]);
+ OptionsPage.closeOverlay();
+ };
+
+ DetailsInternetPage.updateNameServerDisplay = function(type) {
+ var editable = type == 'user';
+ var fields = [$('ipconfig-dns1'), $('ipconfig-dns2'),
+ $('ipconfig-dns3'), $('ipconfig-dns4')];
+ for (var i = 0; i < fields.length; ++i) {
+ fields[i].editable = editable;
+ }
+ if (editable)
+ $('ipconfig-dns1').focus();
+
+ var automaticDns = $('automatic-dns-display');
+ var googleDns = $('google-dns-display');
+ var userDns = $('user-dns-settings');
+ switch (type) {
+ case 'automatic':
+ automaticDns.setAttribute('selected', '');
+ googleDns.removeAttribute('selected');
+ userDns.removeAttribute('selected');
+ break;
+ case 'google':
+ automaticDns.removeAttribute('selected');
+ googleDns.setAttribute('selected', '');
+ userDns.removeAttribute('selected');
+ break;
+ case 'user':
+ automaticDns.removeAttribute('selected');
+ googleDns.removeAttribute('selected');
+ userDns.setAttribute('selected', '');
+ break;
+ }
+ };
+
+ DetailsInternetPage.updateConnectionButtonVisibilty = function(data) {
+ $('details-internet-login').hidden = data.connected;
+ $('details-internet-login').disabled = data.disableConnectButton;
+
+ if (!data.connected &&
+ ((data.type == Constants.TYPE_WIFI && data.encryption) ||
+ data.type == Constants.TYPE_WIMAX ||
+ data.type == Constants.TYPE_VPN)) {
+ $('details-internet-configure').hidden = false;
+ } else {
+ $('details-internet-configure').hidden = true;
+ }
+
+ if (data.type == Constants.TYPE_ETHERNET)
+ $('details-internet-disconnect').hidden = true;
+ else
+ $('details-internet-disconnect').hidden = !data.connected;
+ };
+
+ DetailsInternetPage.updateConnectionData = function(update) {
+ var detailsPage = DetailsInternetPage.getInstance();
+ if (!detailsPage.visible)
+ return;
+
+ var data = $('connection-state').data;
+ if (!data)
+ return;
+
+ if (update.servicePath != data.servicePath)
+ return;
+
+ // Update our cached data object.
+ updateDataObject(data, update);
+
+ detailsPage.deviceConnected = data.deviceConnected;
+ detailsPage.connecting = data.connecting;
+ detailsPage.connected = data.connected;
+ $('connection-state').textContent = data.connectionState;
+
+ this.updateConnectionButtonVisibilty(data);
+
+ if (data.type == Constants.TYPE_WIFI) {
+ $('wifi-connection-state').textContent = data.connectionState;
+ } else if (data.type == Constants.TYPE_WIMAX) {
+ $('wimax-connection-state').textContent = data.connectionState;
+ } else if (data.type == Constants.TYPE_CELLULAR) {
+ $('activation-state').textContent = data.activationState;
+
+ $('buyplan-details').hidden = !data.showBuyButton;
+ $('view-account-details').hidden = !data.showViewAccountButton;
+
+ $('activate-details').hidden = !data.showActivateButton;
+ if (data.showActivateButton)
+ $('details-internet-login').hidden = true;
+ }
+
+ $('connection-state').data = data;
+ };
+
+ DetailsInternetPage.showDetailedInfo = function(data) {
+ var detailsPage = DetailsInternetPage.getInstance();
+
+ // Populate header
+ $('network-details-title').textContent = data.networkName;
+ var statusKey = data.connected ? 'networkConnected' :
+ 'networkNotConnected';
+ $('network-details-subtitle-status').textContent =
+ loadTimeData.getString(statusKey);
+ var typeKey = null;
+ switch (data.type) {
+ case Constants.TYPE_ETHERNET:
+ typeKey = 'ethernetTitle';
+ break;
+ case Constants.TYPE_WIFI:
+ typeKey = 'wifiTitle';
+ break;
+ case Constants.TYPE_WIMAX:
+ typeKey = 'wimaxTitle';
+ break;
+ case Constants.TYPE_CELLULAR:
+ typeKey = 'cellularTitle';
+ break;
+ case Constants.TYPE_VPN:
+ typeKey = 'vpnTitle';
+ break;
+ }
+ var typeLabel = $('network-details-subtitle-type');
+ var typeSeparator = $('network-details-subtitle-separator');
+ if (typeKey) {
+ typeLabel.textContent = loadTimeData.getString(typeKey);
+ typeLabel.hidden = false;
+ typeSeparator.hidden = false;
+ } else {
+ typeLabel.hidden = true;
+ typeSeparator.hidden = true;
+ }
+
+ // TODO(chocobo): Is this hack to cache the data here reasonable?
+ // TODO(kevers): Find more appropriate place to cache data.
+ $('connection-state').data = data;
+
+ $('buyplan-details').hidden = true;
+ $('activate-details').hidden = true;
+ $('view-account-details').hidden = true;
+
+ this.updateConnectionButtonVisibilty(data);
+
+ $('web-proxy-auto-discovery').hidden = true;
+
+ detailsPage.deviceConnected = data.deviceConnected;
+ detailsPage.connecting = data.connecting;
+ detailsPage.connected = data.connected;
+ detailsPage.showProxy = data.showProxy;
+ if (detailsPage.showProxy)
+ chrome.send('selectNetwork', [data.servicePath]);
+
+ detailsPage.showStaticIPConfig = data.showStaticIPConfig;
+ $('connection-state').textContent = data.connectionState;
+
+ var ipAutoConfig = data.ipAutoConfig ? 'automatic' : 'user';
+ $('ip-automatic-configuration-checkbox').checked = data.ipAutoConfig;
+ var inetAddress = {autoConfig: ipAutoConfig};
+ var inetNetmask = {autoConfig: ipAutoConfig};
+ var inetGateway = {autoConfig: ipAutoConfig};
+
+ if (data.ipconfig.value) {
+ inetAddress.automatic = data.ipconfig.value.address;
+ inetAddress.value = data.ipconfig.value.address;
+ inetNetmask.automatic = data.ipconfig.value.netmask;
+ inetNetmask.value = data.ipconfig.value.netmask;
+ inetGateway.automatic = data.ipconfig.value.gateway;
+ inetGateway.value = data.ipconfig.value.gateway;
+ if (data.ipconfig.value.webProxyAutoDiscoveryUrl) {
+ $('web-proxy-auto-discovery').hidden = false;
+ $('web-proxy-auto-discovery-url').value =
+ data.ipconfig.value.webProxyAutoDiscoveryUrl;
+ }
+ }
+
+ // Override the "automatic" values with the real saved DHCP values,
+ // if they are set.
+ if (data.savedIP.address) {
+ inetAddress.automatic = data.savedIP.address;
+ inetAddress.value = data.savedIP.address;
+ }
+ if (data.savedIP.netmask) {
+ inetNetmask.automatic = data.savedIP.netmask;
+ inetNetmask.value = data.savedIP.netmask;
+ }
+ if (data.savedIP.gateway) {
+ inetGateway.automatic = data.savedIP.gateway;
+ inetGateway.value = data.savedIP.gateway;
+ }
+
+ if (ipAutoConfig == 'user') {
+ if (data.staticIP.value.address) {
+ inetAddress.value = data.staticIP.value.address;
+ inetAddress.user = data.staticIP.value.address;
+ }
+ if (data.staticIP.value.netmask) {
+ inetNetmask.value = data.staticIP.value.netmask;
+ inetNetmask.user = data.staticIP.value.netmask;
+ }
+ if (data.staticIP.value.gateway) {
+ inetGateway.value = data.staticIP.value.gateway;
+ inetGateway.user = data.staticIP.value.gateway;
+ }
+ }
+
+ var configureAddressField = function(field, model) {
+ IPAddressField.decorate(field);
+ field.model = model;
+ field.editable = model.autoConfig == 'user';
+ };
+
+ configureAddressField($('ip-address'), inetAddress);
+ configureAddressField($('ip-netmask'), inetNetmask);
+ configureAddressField($('ip-gateway'), inetGateway);
+
+ var inetNameServers = '';
+ if (data.ipconfig.value && data.ipconfig.value.nameServers) {
+ inetNameServers = data.ipconfig.value.nameServers;
+ $('automatic-dns-display').textContent = inetNameServers;
+ }
+
+ if (data.savedIP && data.savedIP.nameServers)
+ $('automatic-dns-display').textContent = data.savedIP.nameServers;
+
+ if (data.nameServersGoogle)
+ $('google-dns-display').textContent = data.nameServersGoogle;
+
+ var nameServersUser = [];
+ if (data.staticIP.value.nameServers)
+ nameServersUser = data.staticIP.value.nameServers.split(',');
+
+ var nameServerModels = [];
+ for (var i = 0; i < 4; ++i)
+ nameServerModels.push({value: nameServersUser[i] || ''});
+
+ $(data.nameServerType + '-dns-radio').checked = true;
+ configureAddressField($('ipconfig-dns1'), nameServerModels[0]);
+ configureAddressField($('ipconfig-dns2'), nameServerModels[1]);
+ configureAddressField($('ipconfig-dns3'), nameServerModels[2]);
+ configureAddressField($('ipconfig-dns4'), nameServerModels[3]);
+
+ DetailsInternetPage.updateNameServerDisplay(data.nameServerType);
+
+ if (data.hardwareAddress) {
+ $('hardware-address').textContent = data.hardwareAddress;
+ $('hardware-address-row').style.display = 'table-row';
+ } else {
+ // This is most likely a device without a hardware address.
+ $('hardware-address-row').style.display = 'none';
+ }
+ if (data.type == Constants.TYPE_WIFI) {
+ OptionsPage.showTab($('wifi-network-nav-tab'));
+ detailsPage.wireless = true;
+ detailsPage.vpn = false;
+ detailsPage.ethernet = false;
+ detailsPage.cellular = false;
+ detailsPage.gsm = false;
+ detailsPage.wimax = false;
+ detailsPage.shared = data.shared;
+ $('wifi-connection-state').textContent = data.connectionState;
+ $('wifi-ssid').textContent = data.ssid;
+ if (data.bssid && data.bssid.length > 0) {
+ $('wifi-bssid').textContent = data.bssid;
+ $('wifi-bssid-entry').hidden = false;
+ } else {
+ $('wifi-bssid-entry').hidden = true;
+ }
+ $('wifi-ip-address').textContent = inetAddress.value;
+ $('wifi-netmask').textContent = inetNetmask.value;
+ $('wifi-gateway').textContent = inetGateway.value;
+ $('wifi-name-servers').textContent = inetNameServers;
+ if (data.encryption && data.encryption.length > 0) {
+ $('wifi-security').textContent = data.encryption;
+ $('wifi-security-entry').hidden = false;
+ } else {
+ $('wifi-security-entry').hidden = true;
+ }
+ // Frequency is in MHz.
+ var frequency = loadTimeData.getString('inetFrequencyFormat');
+ frequency = frequency.replace('$1', data.frequency);
+ $('wifi-frequency').textContent = frequency;
+ // Signal strength as percentage.
+ var signalStrength = loadTimeData.getString('inetSignalStrengthFormat');
+ signalStrength = signalStrength.replace('$1', data.strength);
+ $('wifi-signal-strength').textContent = signalStrength;
+ if (data.hardwareAddress) {
+ $('wifi-hardware-address').textContent = data.hardwareAddress;
+ $('wifi-hardware-address-entry').hidden = false;
+ } else {
+ $('wifi-hardware-address-entry').hidden = true;
+ }
+ detailsPage.showPreferred = data.showPreferred;
+ $('prefer-network-wifi').checked = data.preferred.value;
+ $('prefer-network-wifi').disabled = !data.remembered;
+ $('auto-connect-network-wifi').checked = data.autoConnect.value;
+ $('auto-connect-network-wifi').disabled = !data.remembered;
+ detailsPage.password = data.encrypted;
+ } else if (data.type == Constants.TYPE_WIMAX) {
+ OptionsPage.showTab($('wimax-network-nav-tab'));
+ detailsPage.wimax = true;
+ detailsPage.wireless = false;
+ detailsPage.vpn = false;
+ detailsPage.ethernet = false;
+ detailsPage.cellular = false;
+ detailsPage.gsm = false;
+ detailsPage.shared = data.shared;
+ detailsPage.showPreferred = data.showPreferred;
+ $('wimax-connection-state').textContent = data.connectionState;
+ $('auto-connect-network-wimax').checked = data.autoConnect.value;
+ $('auto-connect-network-wimax').disabled = !data.remembered;
+ if (data.identity) {
+ $('wimax-eap-identity').textContent = data.identity;
+ $('wimax-eap-identity-entry').hidden = false;
+ } else {
+ $('wimax-eap-identity-entry').hidden = true;
+ }
+ // Signal strength as percentage.
+ var signalStrength = loadTimeData.getString('inetSignalStrengthFormat');
+ signalStrength = signalStrength.replace('$1', data.strength);
+ $('wimax-signal-strength').textContent = signalStrength;
+ } else if (data.type == Constants.TYPE_CELLULAR) {
+ OptionsPage.showTab($('cellular-conn-nav-tab'));
+ detailsPage.ethernet = false;
+ detailsPage.wireless = false;
+ detailsPage.wimax = false;
+ detailsPage.vpn = false;
+ detailsPage.cellular = true;
+ if (data.showCarrierSelect && data.currentCarrierIndex != -1) {
+ var carrierSelector = $('select-carrier');
+ carrierSelector.onchange = DetailsInternetPage.handleCarrierChanged;
+ carrierSelector.options.length = 0;
+ for (var i = 0; i < data.carriers.length; ++i) {
+ var option = document.createElement('option');
+ option.textContent = data.carriers[i];
+ carrierSelector.add(option);
+ }
+ carrierSelector.selectedIndex = data.currentCarrierIndex;
+ } else {
+ $('service-name').textContent = data.serviceName;
+ }
+
+ $('network-technology').textContent = data.networkTechnology;
+ $('activation-state').textContent = data.activationState;
+ $('roaming-state').textContent = data.roamingState;
+ $('restricted-pool').textContent = data.restrictedPool;
+ $('error-state').textContent = data.errorState;
+ $('manufacturer').textContent = data.manufacturer;
+ $('model-id').textContent = data.modelId;
+ $('firmware-revision').textContent = data.firmwareRevision;
+ $('hardware-revision').textContent = data.hardwareRevision;
+ $('mdn').textContent = data.mdn;
+ $('operator-name').textContent = data.operatorName;
+ $('operator-code').textContent = data.operatorCode;
+
+ // Make sure that GSM/CDMA specific properties that shouldn't be hidden
+ // are visible.
+ updateHidden('#details-internet-page .gsm-only', false);
+ updateHidden('#details-internet-page .cdma-only', false);
+
+ // Show IMEI/ESN/MEID/MIN/PRL only if they are available.
+ (function() {
+ var setContentOrHide = function(property) {
+ var value = data[property];
+ if (value)
+ $(property).textContent = value;
+ else
+ $(property).parentElement.hidden = true;
+ };
+ setContentOrHide('esn');
+ setContentOrHide('imei');
+ setContentOrHide('meid');
+ setContentOrHide('min');
+ setContentOrHide('prl-version');
+ })();
+ detailsPage.gsm = data.gsm;
+ if (data.gsm) {
+ $('iccid').textContent = stringFromValue(data.iccid);
+ $('imsi').textContent = stringFromValue(data.imsi);
+
+ var apnSelector = $('select-apn');
+ // 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 localizedName = apnList[i].localizedName;
+ var name = localizedName ? localizedName : apnList[i].name;
+ var apn = apnList[i].apn;
+ option.textContent = name ? (name + ' (' + apn + ')') : apn;
+ option.value = i;
+ // data.apn and data.lastGoodApn will always be defined, however
+ // data.apn.apn and data.lastGoodApn.apn may not be. This is not a
+ // problem, as apnList[i].apn will always be defined and the
+ // comparisons below will work as expected.
+ if ((data.apn.apn == apn &&
+ data.apn.username == apnList[i].username &&
+ data.apn.password == apnList[i].password) ||
+ (!data.apn.apn &&
+ data.lastGoodApn.apn == 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;
+ updateHidden('.apn-list-view', false);
+ updateHidden('.apn-details-view', true);
+ DetailsInternetPage.updateSecurityTab(data.simCardLockEnabled.value);
+ }
+ $('auto-connect-network-cellular').checked = data.autoConnect.value;
+ $('auto-connect-network-cellular').disabled = false;
+
+ $('buyplan-details').hidden = !data.showBuyButton;
+ $('view-account-details').hidden = !data.showViewAccountButton;
+ $('activate-details').hidden = !data.showActivateButton;
+ if (data.showActivateButton) {
+ $('details-internet-login').hidden = true;
+ }
+ } else if (data.type == Constants.TYPE_VPN) {
+ OptionsPage.showTab($('vpn-nav-tab'));
+ detailsPage.wireless = false;
+ detailsPage.wimax = false;
+ detailsPage.vpn = true;
+ detailsPage.ethernet = false;
+ detailsPage.cellular = false;
+ detailsPage.gsm = false;
+ $('inet-service-name').textContent = data.serviceName;
+ $('inet-provider-type').textContent = data.providerType;
+ $('inet-username').textContent = data.username;
+ var inetServerHostname = $('inet-server-hostname');
+ inetServerHostname.value = data.serverHostname.value;
+ inetServerHostname.resetHandler = function() {
+ OptionsPage.hideBubble();
+ inetServerHostname.value = data.serverHostname.recommendedValue;
+ };
+ $('auto-connect-network-vpn').checked = data.autoConnect.value;
+ $('auto-connect-network-vpn').disabled = false;
+ } else {
+ OptionsPage.showTab($('internet-nav-tab'));
+ detailsPage.ethernet = true;
+ detailsPage.wireless = false;
+ detailsPage.wimax = false;
+ detailsPage.vpn = false;
+ detailsPage.cellular = false;
+ detailsPage.gsm = false;
+ }
+
+ // Update controlled option indicators.
+ indicators = cr.doc.querySelectorAll(
+ '#details-internet-page .controlled-setting-indicator');
+ for (var i = 0; i < indicators.length; i++) {
+ var propName = indicators[i].getAttribute('data');
+ if (!propName || !data[propName])
+ continue;
+ var propData = data[propName];
+ // Create a synthetic pref change event decorated as
+ // CoreOptionsHandler::CreateValueForPref() does.
+ var event = new Event(name);
+ event.value = {
+ value: propData.value,
+ controlledBy: propData.controlledBy,
+ recommendedValue: propData.recommendedValue,
+ };
+ indicators[i].handlePrefChange(event);
+ var forElement = $(indicators[i].getAttribute('for'));
+ if (forElement) {
+ if (propData.controlledBy == 'policy')
+ forElement.disabled = true;
+ if (forElement.resetHandler)
+ indicators[i].resetHandler = forElement.resetHandler;
+ }
+ }
+
+ detailsPage.updateControls();
+
+ // 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);
+ };
+
+ return {
+ DetailsInternetPage: DetailsInternetPage
+ };
+});
diff --git a/chromium/chrome/browser/resources/options/chromeos/internet_detail_ip_address_field.js b/chromium/chrome/browser/resources/options/chromeos/internet_detail_ip_address_field.js
new file mode 100644
index 00000000000..d0a096c77d0
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/chromeos/internet_detail_ip_address_field.js
@@ -0,0 +1,111 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options.internet', function() {
+ /** @const */ var EditableTextField = options.EditableTextField;
+
+ /**
+ * The regular expression that matches an IP address. String to match against
+ * should have all whitespace stripped already.
+ * @const
+ * @type {RegExp}
+ */
+ var singleIp_ = /^([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)$/;
+
+ /**
+ * Creates a new field specifically for entering IP addresses.
+ * @constructor
+ */
+ function IPAddressField() {
+ var el = cr.doc.createElement('div');
+ IPAddressField.decorate(el);
+ return el;
+ }
+
+ /**
+ * Decorates an element as a inline-editable list item. Note that this is
+ * a subclass of IPAddressField.
+ * @param {!HTMLElement} el The element to decorate.
+ */
+ IPAddressField.decorate = function(el) {
+ el.__proto__ = IPAddressField.prototype;
+ el.decorate();
+ };
+
+ IPAddressField.prototype = {
+ __proto__: EditableTextField.prototype,
+
+ /** @override */
+ decorate: function() {
+ EditableTextField.prototype.decorate.call(this);
+ },
+
+ /**
+ * Indicates whether or not empty values are allowed.
+ * @type {boolean}
+ */
+ get allowEmpty() {
+ return this.hasAttribute('allow-empty');
+ },
+
+ /** @override */
+ get currentInputIsValid() {
+ if (!this.editField.value && this.allowEmpty)
+ return true;
+
+ // Make sure it's only got numbers and ".", there are the correct
+ // count of them, and they are all within the correct range.
+ var fieldValue = this.editField.value.replace(/\s/g, '');
+ var matches = singleIp_.exec(fieldValue);
+ var rangeCorrect = true;
+ if (matches != null) {
+ for (var i = 1; i < matches.length; ++i) {
+ var value = parseInt(matches[i], 10);
+ if (value < 0 || value > 255) {
+ rangeCorrect = false;
+ break;
+ }
+ }
+ }
+ return this.editField.validity.valid && matches != null &&
+ rangeCorrect && matches.length == 5;
+ },
+
+ /** @override */
+ get hasBeenEdited() {
+ return this.editField.value != this.model.value;
+ },
+
+ /**
+ * Overrides superclass to mutate the input during a successful commit. For
+ * the purposes of entering IP addresses, this just means stripping off
+ * whitespace and leading zeros from each of the octets so that they conform
+ * to the normal format for IP addresses.
+ * @override
+ * @param {string} value Input IP address to be mutated.
+ * @return {string} mutated IP address.
+ */
+ mutateInput: function(value) {
+ if (!value)
+ return value;
+
+ var fieldValue = value.replace(/\s/g, '');
+ var matches = singleIp_.exec(fieldValue);
+ var result = [];
+
+ // If we got this far, matches shouldn't be null, but make sure.
+ if (matches != null) {
+ // starting at one because the first match element contains the entire
+ // match, and we don't care about that.
+ for (var i = 1; i < matches.length; ++i)
+ result.push(parseInt(matches[i], 10));
+ }
+ return result.join('.');
+ },
+ };
+
+ return {
+ IPAddressField: IPAddressField,
+ };
+});
diff --git a/chromium/chrome/browser/resources/options/chromeos/keyboard_overlay.css b/chromium/chrome/browser/resources/options/chromeos/keyboard_overlay.css
new file mode 100644
index 00000000000..316e07af902
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/chromeos/keyboard_overlay.css
@@ -0,0 +1,7 @@
+/* Copyright 2013 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. */
+
+#send-function-keys-description {
+ color: gray;
+}
diff --git a/chromium/chrome/browser/resources/options/chromeos/keyboard_overlay.html b/chromium/chrome/browser/resources/options/chromeos/keyboard_overlay.html
new file mode 100644
index 00000000000..039c52e7954
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/chromeos/keyboard_overlay.html
@@ -0,0 +1,100 @@
+<div id="keyboard-overlay" class="page" hidden>
+ <div class="close-button"></div>
+ <h1 i18n-content="keyboardOverlayTitle"></h1>
+ <div class="content-area">
+ <table class="option-control-table">
+ <tr>
+ <td>
+ <label class="option-name" for="remap-search-key-to"
+ i18n-content="remapSearchKeyToContent">
+ </label>
+ </td>
+ <td class="option-value">
+ <select id="remap-search-key-to" class="control"
+ data-type="number" i18n-options="remapSearchKeyToValue"
+ pref="settings.language.xkb_remap_search_key_to" dialog-pref>
+ </select>
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <label class="option-name" for="remap-control-key-to"
+ i18n-content="remapControlKeyToContent">
+ </label>
+ </td>
+ <td class="option-value">
+ <select id="remap-control-key-to" class="control"
+ data-type="number" i18n-options="remapControlKeyToValue"
+ pref="settings.language.xkb_remap_control_key_to" dialog-pref>
+ </select>
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <label class="option-name" for="remap-alt-key-to"
+ i18n-content="remapAltKeyToContent">
+ </label>
+ </td>
+ <td class="option-value">
+ <select id="remap-alt-key-to" class="control" data-type="number"
+ pref="settings.language.xkb_remap_alt_key_to"
+ i18n-options="remapAltKeyToValue" dialog-pref></select>
+ </td>
+ </tr>
+ <!-- The caps lock section is hidden by default. This is only visible
+ when --has-chromeos-keyboard flag is not passed. -->
+ <tr id="caps-lock-remapping-section" hidden>
+ <td>
+ <label class="option-name" for="remap-caps-lock-key-to"
+ i18n-content="remapCapsLockKeyToContent">
+ </label>
+ </td>
+ <td class="option-value">
+ <select id="remap-caps-lock-key-to" class="control"
+ data-type="number"
+ pref="settings.language.remap_caps_lock_key_to"
+ i18n-options="remapCapsLockKeyToValue" dialog-pref></select>
+ </td>
+ </tr>
+ <!-- The diamond key section is hidden by default. This is only visible
+ when --has-chromeos-diamond-key flag is passed. -->
+ <tr id="diamond-key-remapping-section" hidden>
+ <td>
+ <label class="option-name" for="remap-diamond-key-to"
+ i18n-content="remapDiamondKeyToContent">
+ </label>
+ </td>
+ <td class="option-value">
+ <select id="remap-diamond-key-to" class="control"
+ data-type="number"
+ pref="settings.language.remap_diamond_key_to"
+ i18n-options="remapDiamondKeyToValue" dialog-pref></select>
+ </td>
+ </tr>
+ </table>
+ <div class="settings-row">
+ <div class="checkbox">
+ <label>
+ <input id="send-function-keys" type="checkbox"
+ pref="settings.language.send_function_keys" dialog-pref>
+ <span i18n-content="sendFunctionKeys"></span>
+ </label>
+ </div>
+ <label id="send-function-keys-description" for="send-function-keys"
+ i18n-content="sendFunctionKeysDescription">
+ </label>
+ </div>
+ </div>
+ <div class="content-area">
+ <button id="languages-and-input-settings" class="link-button"
+ i18n-content="changeLanguageAndInputSettings"></button>
+ </div>
+ <div class="action-area">
+ <div class="button-strip">
+ <button id="keyboard-cancel" type="reset" i18n-content="cancel"></button>
+ <button id="keyboard-confirm" class="default-button" type="submit"
+ i18n-content="ok">
+ </button>
+ </div>
+ </div>
+</div>
diff --git a/chromium/chrome/browser/resources/options/chromeos/keyboard_overlay.js b/chromium/chrome/browser/resources/options/chromeos/keyboard_overlay.js
new file mode 100644
index 00000000000..39c8a694fb3
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/chromeos/keyboard_overlay.js
@@ -0,0 +1,66 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+
+ /**
+ * Encapsulated handling of the keyboard overlay.
+ * @constructor
+ */
+ function KeyboardOverlay() {
+ options.SettingsDialog.call(this, 'keyboard-overlay',
+ loadTimeData.getString('keyboardOverlayTitle'),
+ 'keyboard-overlay',
+ $('keyboard-confirm'), $('keyboard-cancel'));
+ }
+
+ cr.addSingletonGetter(KeyboardOverlay);
+
+ KeyboardOverlay.prototype = {
+ __proto__: options.SettingsDialog.prototype,
+
+ /**
+ * Initializes the page. This method is called in initialize.
+ */
+ initializePage: function() {
+ options.SettingsDialog.prototype.initializePage.call(this);
+
+ $('languages-and-input-settings').onclick = function(e) {
+ OptionsPage.navigateToPage('languages');
+ };
+ },
+
+ /**
+ * Show/hide the caps lock remapping section.
+ * @private
+ */
+ showCapsLockOptions_: function(show) {
+ $('caps-lock-remapping-section').hidden = !show;
+ },
+
+ /**
+ * Show/hide the diamond key remapping section.
+ * @private
+ */
+ showDiamondKeyOptions_: function(show) {
+ $('diamond-key-remapping-section').hidden = !show;
+ },
+ };
+
+ // Forward public APIs to private implementations.
+ [
+ 'showCapsLockOptions',
+ 'showDiamondKeyOptions',
+ ].forEach(function(name) {
+ KeyboardOverlay[name] = function() {
+ var instance = KeyboardOverlay.getInstance();
+ return instance[name + '_'].apply(instance, arguments);
+ };
+ });
+
+ // Export
+ return {
+ KeyboardOverlay: KeyboardOverlay
+ };
+});
diff --git a/chromium/chrome/browser/resources/options/chromeos/network_list.js b/chromium/chrome/browser/resources/options/chromeos/network_list.js
new file mode 100644
index 00000000000..4d20937fa5f
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/chromeos/network_list.js
@@ -0,0 +1,1174 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options.network', function() {
+
+ var ArrayDataModel = cr.ui.ArrayDataModel;
+ var List = cr.ui.List;
+ var ListItem = cr.ui.ListItem;
+ var ListSingleSelectionModel = cr.ui.ListSingleSelectionModel;
+ var Menu = cr.ui.Menu;
+ var MenuItem = cr.ui.MenuItem;
+ var ControlledSettingIndicator = options.ControlledSettingIndicator;
+
+ /**
+ * Network settings constants. These enums usually match their C++
+ * counterparts.
+ */
+ function Constants() {}
+
+ // Network types:
+ Constants.TYPE_UNKNOWN = 'UNKNOWN';
+ Constants.TYPE_ETHERNET = 'ethernet';
+ Constants.TYPE_WIFI = 'wifi';
+ Constants.TYPE_WIMAX = 'wimax';
+ Constants.TYPE_BLUETOOTH = 'bluetooth';
+ Constants.TYPE_CELLULAR = 'cellular';
+ Constants.TYPE_VPN = 'vpn';
+
+ // 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;
+
+ /**
+ * Order in which controls are to appear in the network list sorted by key.
+ */
+ Constants.NETWORK_ORDER = ['ethernet',
+ 'wifi',
+ 'wimax',
+ 'cellular',
+ 'vpn',
+ 'addConnection'];
+
+ /**
+ * Mapping of network category titles to the network type.
+ */
+ var categoryMap = {
+ 'cellular': Constants.TYPE_CELLULAR,
+ 'ethernet': Constants.TYPE_ETHERNET,
+ 'wimax': Constants.TYPE_WIMAX,
+ 'wifi': Constants.TYPE_WIFI,
+ 'vpn': Constants.TYPE_VPN
+ };
+
+ /**
+ * ID of the menu that is currently visible.
+ * @type {?string}
+ * @private
+ */
+ var activeMenu_ = null;
+
+ /**
+ * Indicates if cellular networks are available.
+ * @type {boolean}
+ * @private
+ */
+ var cellularAvailable_ = false;
+
+ /**
+ * Indicates if cellular networks are enabled.
+ * @type {boolean}
+ * @private
+ */
+ var cellularEnabled_ = false;
+
+ /**
+ * Indicates if cellular device supports network scanning.
+ * @type {boolean}
+ * @private
+ */
+ var cellularSupportsScan_ = false;
+
+ /**
+ * Indicates if WiMAX networks are available.
+ * @type {boolean}
+ * @private
+ */
+ var wimaxAvailable_ = false;
+
+ /**
+ * Indicates if WiMAX networks are enabled.
+ * @type {boolean}
+ * @private
+ */
+ var wimaxEnabled_ = false;
+
+ /**
+ * Indicates if mobile data roaming is enabled.
+ * @type {boolean}
+ * @private
+ */
+ var enableDataRoaming_ = false;
+
+ /**
+ * Icon to use when not connected to a particular type of network.
+ * @type {!Object.<string, string>} Mapping of network type to icon data url.
+ * @private
+ */
+ var defaultIcons_ = {};
+
+ /**
+ * Contains the current logged in user type, which is one of 'none',
+ * 'regular', 'owner', 'guest', 'retail-mode', 'public-account',
+ * 'locally-managed', and 'kiosk-app', or empty string if the data has not
+ * been set.
+ * @type {string}
+ * @private
+ */
+ var loggedInUserType_ = '';
+
+ /**
+ * Create an element in the network list for controlling network
+ * connectivity.
+ * @param {Object} data Description of the network list or command.
+ * @constructor
+ */
+ function NetworkListItem(data) {
+ var el = cr.doc.createElement('li');
+ el.data_ = {};
+ for (var key in data)
+ el.data_[key] = data[key];
+ NetworkListItem.decorate(el);
+ return el;
+ }
+
+ /**
+ * Decorate an element as a NetworkListItem.
+ * @param {!Element} el The element to decorate.
+ */
+ NetworkListItem.decorate = function(el) {
+ el.__proto__ = NetworkListItem.prototype;
+ el.decorate();
+ };
+
+ NetworkListItem.prototype = {
+ __proto__: ListItem.prototype,
+
+ /**
+ * Description of the network group or control.
+ * @type {Object.<string,Object>}
+ * @private
+ */
+ data_: null,
+
+ /**
+ * Element for the control's subtitle.
+ * @type {?Element}
+ * @private
+ */
+ subtitle_: null,
+
+ /**
+ * Icon for the network control.
+ * @type {?Element}
+ * @private
+ */
+ icon_: null,
+
+ /**
+ * Indicates if in the process of connecting to a network.
+ * @type {boolean}
+ * @private
+ */
+ connecting_: false,
+
+ /**
+ * Description of the network control.
+ * @type {Object}
+ */
+ get data() {
+ return this.data_;
+ },
+
+ /**
+ * Text label for the subtitle.
+ * @type {string}
+ */
+ set subtitle(text) {
+ if (text)
+ this.subtitle_.textContent = text;
+ this.subtitle_.hidden = !text;
+ },
+
+ /**
+ * URL for the network icon.
+ * @type {string}
+ */
+ set iconURL(iconURL) {
+ this.icon_.style.backgroundImage = url(iconURL);
+ },
+
+ /**
+ * Type of network icon. Each type corresponds to a CSS rule.
+ * @type {string}
+ */
+ set iconType(type) {
+ if (defaultIcons_[type])
+ this.iconURL = defaultIcons_[type];
+ else
+ this.icon_.classList.add('network-' + type);
+ },
+
+ /**
+ * Indicates if the network is in the process of being connected.
+ * @type {boolean}
+ */
+ set connecting(state) {
+ this.connecting_ = state;
+ if (state)
+ this.icon_.classList.add('network-connecting');
+ else
+ this.icon_.classList.remove('network-connecting');
+ },
+
+ /**
+ * Indicates if the network is in the process of being connected.
+ * @type {boolean}
+ */
+ get connecting() {
+ return this.connecting_;
+ },
+
+ /**
+ * Set the direction of the text.
+ * @param {string} direction The direction of the text, e.g. 'ltr'.
+ */
+ setSubtitleDirection: function(direction) {
+ this.subtitle_.dir = direction;
+ },
+
+ /**
+ * Indicate that the selector arrow should be shown.
+ */
+ showSelector: function() {
+ this.subtitle_.classList.add('network-selector');
+ },
+
+ /**
+ * Adds an indicator to show that the network is policy managed.
+ */
+ showManagedNetworkIndicator: function() {
+ this.appendChild(new ManagedNetworkIndicator());
+ },
+
+ /** @override */
+ decorate: function() {
+ ListItem.prototype.decorate.call(this);
+ this.className = 'network-group';
+ this.icon_ = this.ownerDocument.createElement('div');
+ this.icon_.className = 'network-icon';
+ this.appendChild(this.icon_);
+ var textContent = this.ownerDocument.createElement('div');
+ textContent.className = 'network-group-labels';
+ this.appendChild(textContent);
+ var categoryLabel = this.ownerDocument.createElement('div');
+ var title = this.data_.key + 'Title';
+ categoryLabel.className = 'network-title';
+ categoryLabel.textContent = loadTimeData.getString(title);
+ textContent.appendChild(categoryLabel);
+ this.subtitle_ = this.ownerDocument.createElement('div');
+ this.subtitle_.className = 'network-subtitle';
+ textContent.appendChild(this.subtitle_);
+ },
+ };
+
+ /**
+ * Creates a control that displays a popup menu when clicked.
+ * @param {Object} data Description of the control.
+ */
+ function NetworkMenuItem(data) {
+ var el = new NetworkListItem(data);
+ el.__proto__ = NetworkMenuItem.prototype;
+ el.decorate();
+ return el;
+ }
+
+ NetworkMenuItem.prototype = {
+ __proto__: NetworkListItem.prototype,
+
+ /**
+ * Popup menu element.
+ * @type {?Element}
+ * @private
+ */
+ menu_: null,
+
+ /** @override */
+ decorate: function() {
+ this.subtitle = null;
+ if (this.data.iconType)
+ this.iconType = this.data.iconType;
+ this.addEventListener('click', function() {
+ this.showMenu();
+ });
+ },
+
+ /**
+ * Retrieves the ID for the menu.
+ */
+ getMenuName: function() {
+ return this.data_.key + '-network-menu';
+ },
+
+ /**
+ * Creates a popup menu for the control.
+ * @return {Element} The newly created menu.
+ */
+ createMenu: function() {
+ if (this.data.menu) {
+ var menu = this.ownerDocument.createElement('div');
+ menu.id = this.getMenuName();
+ menu.className = 'network-menu';
+ menu.hidden = true;
+ Menu.decorate(menu);
+ for (var i = 0; i < this.data.menu.length; i++) {
+ var entry = this.data.menu[i];
+ createCallback_(menu, null, entry.label, entry.command);
+ }
+ return menu;
+ }
+ return null;
+ },
+
+ canUpdateMenu: function() {
+ return false;
+ },
+
+ /**
+ * Displays a popup menu.
+ */
+ showMenu: function() {
+ var rebuild = false;
+ // Force a rescan if opening the menu for WiFi networks to ensure the
+ // list is up to date. Networks are periodically rescanned, but depending
+ // on timing, there could be an excessive delay before the first rescan
+ // unless forced.
+ var rescan = !activeMenu_ && this.data_.key == 'wifi';
+ if (!this.menu_) {
+ rebuild = true;
+ var existing = $(this.getMenuName());
+ if (existing) {
+ if (this.updateMenu())
+ return;
+ closeMenu_();
+ }
+ this.menu_ = this.createMenu();
+ this.menu_.addEventListener('mousedown', function(e) {
+ // Prevent blurring of list, which would close the menu.
+ e.preventDefault();
+ });
+ var parent = $('network-menus');
+ if (existing)
+ parent.replaceChild(this.menu_, existing);
+ else
+ parent.appendChild(this.menu_);
+ }
+ var top = this.offsetTop + this.clientHeight;
+ var menuId = this.getMenuName();
+ if (menuId != activeMenu_ || rebuild) {
+ closeMenu_();
+ activeMenu_ = menuId;
+ this.menu_.style.setProperty('top', top + 'px');
+ this.menu_.hidden = false;
+ }
+ if (rescan)
+ chrome.send('refreshNetworks');
+ },
+ };
+
+ /**
+ * Creates a control for selecting or configuring a network connection based
+ * on the type of connection (e.g. wifi versus vpn).
+ * @param {{key: string,
+ * networkList: Array.<Object>} data Description of the network.
+ * @constructor
+ */
+ function NetworkSelectorItem(data) {
+ var el = new NetworkMenuItem(data);
+ el.__proto__ = NetworkSelectorItem.prototype;
+ el.decorate();
+ return el;
+ }
+
+ NetworkSelectorItem.prototype = {
+ __proto__: NetworkMenuItem.prototype,
+
+ /** @override */
+ decorate: function() {
+ // TODO(kevers): Generalize method of setting default label.
+ var policyManaged = false;
+ var defaultMessage = this.data_.key == 'wifi' ?
+ 'networkOffline' : 'networkNotConnected';
+ this.subtitle = loadTimeData.getString(defaultMessage);
+ var list = this.data_.networkList;
+ var candidateURL = null;
+ for (var i = 0; i < list.length; i++) {
+ var networkDetails = list[i];
+ if (networkDetails.connecting || networkDetails.connected) {
+ this.subtitle = networkDetails.networkName;
+ this.setSubtitleDirection('ltr');
+ policyManaged = networkDetails.policyManaged;
+ candidateURL = networkDetails.iconURL;
+ // Only break when we see a connecting network as it is possible to
+ // have a connected network and a connecting network at the same
+ // time.
+ if (networkDetails.connecting) {
+ this.connecting = true;
+ candidateURL = null;
+ break;
+ }
+ }
+ }
+ if (candidateURL)
+ this.iconURL = candidateURL;
+ else
+ this.iconType = this.data.key;
+
+ this.showSelector();
+
+ if (policyManaged)
+ this.showManagedNetworkIndicator();
+
+ if (activeMenu_ == this.getMenuName()) {
+ // Menu is already showing and needs to be updated. Explicitly calling
+ // show menu will force the existing menu to be replaced. The call
+ // is deferred in order to ensure that position of this element has
+ // beem properly updated.
+ var self = this;
+ setTimeout(function() {self.showMenu();}, 0);
+ }
+ },
+
+ /**
+ * Creates a menu for selecting, configuring or disconnecting from a
+ * network.
+ * @return {Element} The newly created menu.
+ */
+ createMenu: function() {
+ var menu = this.ownerDocument.createElement('div');
+ menu.id = this.getMenuName();
+ menu.className = 'network-menu';
+ menu.hidden = true;
+ Menu.decorate(menu);
+ var addendum = [];
+ if (this.data_.key == 'wifi') {
+ addendum.push({label: loadTimeData.getString('joinOtherNetwork'),
+ command: 'add',
+ data: {networkType: Constants.TYPE_WIFI,
+ servicePath: ''}});
+ } else if (this.data_.key == 'cellular') {
+ if (cellularEnabled_ && cellularSupportsScan_) {
+ entry = {
+ label: loadTimeData.getString('otherCellularNetworks'),
+ command: createAddConnectionCallback_(Constants.TYPE_CELLULAR),
+ addClass: ['other-cellulars'],
+ data: {}
+ };
+ addendum.push(entry);
+ }
+
+ var label = enableDataRoaming_ ? 'disableDataRoaming' :
+ 'enableDataRoaming';
+ var disabled = loggedInUserType_ != 'owner';
+ var entry = {label: loadTimeData.getString(label),
+ data: {}};
+ if (disabled) {
+ entry.command = null;
+ entry.tooltip =
+ loadTimeData.getString('dataRoamingDisableToggleTooltip');
+ } else {
+ var self = this;
+ entry.command = function() {
+ options.Preferences.setBooleanPref(
+ 'cros.signed.data_roaming_enabled',
+ !enableDataRoaming_, true);
+ // Force revalidation of the menu the next time it is displayed.
+ self.menu_ = null;
+ };
+ }
+ addendum.push(entry);
+ }
+ var list = this.data.rememberedNetworks;
+ if (list && list.length > 0) {
+ var callback = function(list) {
+ $('remembered-network-list').clear();
+ var dialog = options.PreferredNetworks.getInstance();
+ OptionsPage.showPageByName('preferredNetworksPage', false);
+ dialog.update(list);
+ };
+ addendum.push({label: loadTimeData.getString('preferredNetworks'),
+ command: callback,
+ data: list});
+ }
+
+ var networkGroup = this.ownerDocument.createElement('div');
+ networkGroup.className = 'network-menu-group';
+ list = this.data.networkList;
+ var empty = !list || list.length == 0;
+ if (list) {
+ for (var i = 0; i < list.length; i++) {
+ var data = list[i];
+ this.createNetworkOptionsCallback_(networkGroup, data);
+ if (data.connected) {
+ if (data.networkType == Constants.TYPE_VPN) {
+ // Add separator
+ addendum.push({});
+ var i18nKey = 'disconnectNetwork';
+ addendum.push({label: loadTimeData.getString(i18nKey),
+ command: 'disconnect',
+ data: data});
+ }
+ }
+ }
+ }
+ if (this.data_.key == 'wifi' || this.data_.key == 'wimax' ||
+ this.data_.key == 'cellular') {
+ addendum.push({});
+ if (this.data_.key == 'wifi') {
+ addendum.push({label: loadTimeData.getString('turnOffWifi'),
+ command: function() {
+ chrome.send('disableWifi');
+ },
+ data: {}});
+ } else if (this.data_.key == 'wimax') {
+ addendum.push({label: loadTimeData.getString('turnOffWimax'),
+ command: function() {
+ chrome.send('disableWimax');
+ },
+ data: {}});
+ } else if (this.data_.key == 'cellular') {
+ addendum.push({label: loadTimeData.getString('turnOffCellular'),
+ command: function() {
+ chrome.send('disableCellular');
+ },
+ data: {}});
+ }
+ }
+ if (!empty)
+ menu.appendChild(networkGroup);
+ if (addendum.length > 0) {
+ var separator = false;
+ if (!empty) {
+ menu.appendChild(MenuItem.createSeparator());
+ separator = true;
+ }
+ for (var i = 0; i < addendum.length; i++) {
+ var value = addendum[i];
+ if (value.data) {
+ var item = createCallback_(menu, value.data, value.label,
+ value.command);
+ if (value.tooltip)
+ item.title = value.tooltip;
+ if (value.addClass)
+ item.classList.add(value.addClass);
+ separator = false;
+ } else if (!separator) {
+ menu.appendChild(MenuItem.createSeparator());
+ separator = true;
+ }
+ }
+ }
+ return menu;
+ },
+
+ /**
+ * Determines if a menu can be updated on the fly. Menus that cannot be
+ * updated are fully regenerated using createMenu. The advantage of
+ * updating a menu is that it can preserve ordering of networks avoiding
+ * entries from jumping around after an update.
+ */
+ canUpdateMenu: function() {
+ return this.data_.key == 'wifi' && activeMenu_ == this.getMenuName();
+ },
+
+ /**
+ * Updates an existing menu. Updated menus preserve ordering of prior
+ * entries. During the update process, the ordering may differ from the
+ * preferred ordering as determined by the network library. If the
+ * ordering becomes potentially out of sync, then the updated menu is
+ * marked for disposal on close. Reopening the menu will force a
+ * regeneration, which will in turn fix the ordering.
+ * @return {boolean} True if successfully updated.
+ */
+ updateMenu: function() {
+ if (!this.canUpdateMenu())
+ return false;
+ var oldMenu = $(this.getMenuName());
+ var group = oldMenu.getElementsByClassName('network-menu-group')[0];
+ if (!group)
+ return false;
+ var newMenu = this.createMenu();
+ var discardOnClose = false;
+ var oldNetworkButtons = this.extractNetworkConnectButtons_(oldMenu);
+ var newNetworkButtons = this.extractNetworkConnectButtons_(newMenu);
+ for (var key in oldNetworkButtons) {
+ if (newNetworkButtons[key]) {
+ group.replaceChild(newNetworkButtons[key].button,
+ oldNetworkButtons[key].button);
+ if (newNetworkButtons[key].index != oldNetworkButtons[key].index)
+ discardOnClose = true;
+ newNetworkButtons[key] = null;
+ } else {
+ // Leave item in list to prevent network items from jumping due to
+ // deletions.
+ oldNetworkButtons[key].disabled = true;
+ discardOnClose = true;
+ }
+ }
+ for (var key in newNetworkButtons) {
+ var entry = newNetworkButtons[key];
+ if (entry) {
+ group.appendChild(entry.button);
+ discardOnClose = true;
+ }
+ }
+ oldMenu.data = {discardOnClose: discardOnClose};
+ return true;
+ },
+
+ /**
+ * Extracts a mapping of network names to menu element and position.
+ * @param {!Element} menu The menu to process.
+ * @return {Object.<string, Element>} Network mapping.
+ * @private
+ */
+ extractNetworkConnectButtons_: function(menu) {
+ var group = menu.getElementsByClassName('network-menu-group')[0];
+ var networkButtons = {};
+ if (!group)
+ return networkButtons;
+ var buttons = group.getElementsByClassName('network-menu-item');
+ for (var i = 0; i < buttons.length; i++) {
+ var label = buttons[i].data.label;
+ networkButtons[label] = {index: i, button: buttons[i]};
+ }
+ return networkButtons;
+ },
+
+ /**
+ * Adds a menu item for showing network details.
+ * @param {!Element} parent The parent element.
+ * @param {Object} data Description of the network.
+ * @private
+ */
+ createNetworkOptionsCallback_: function(parent, data) {
+ var menuItem = createCallback_(parent,
+ data,
+ data.networkName,
+ 'options',
+ data.iconURL);
+ if (data.policyManaged)
+ menuItem.appendChild(new ManagedNetworkIndicator());
+ if (data.connected || data.connecting) {
+ var label = menuItem.getElementsByClassName(
+ 'network-menu-item-label')[0];
+ label.classList.add('active-network');
+ }
+ }
+ };
+
+ /**
+ * Creates a button-like control for configurating internet connectivity.
+ * @param {{key: string,
+ * subtitle: string,
+ * command: function} data Description of the network control.
+ * @constructor
+ */
+ function NetworkButtonItem(data) {
+ var el = new NetworkListItem(data);
+ el.__proto__ = NetworkButtonItem.prototype;
+ el.decorate();
+ return el;
+ }
+
+ NetworkButtonItem.prototype = {
+ __proto__: NetworkListItem.prototype,
+
+ /** @override */
+ decorate: function() {
+ if (this.data.subtitle)
+ this.subtitle = this.data.subtitle;
+ else
+ this.subtitle = null;
+ if (this.data.command)
+ this.addEventListener('click', this.data.command);
+ if (this.data.iconURL)
+ this.iconURL = this.data.iconURL;
+ else if (this.data.iconType)
+ this.iconType = this.data.iconType;
+ if (this.data.policyManaged)
+ this.showManagedNetworkIndicator();
+ },
+ };
+
+ /**
+ * Adds a command to a menu for modifying network settings.
+ * @param {!Element} menu Parent menu.
+ * @param {!Object} data Description of the network.
+ * @param {!string} label Display name for the menu item.
+ * @param {?(string|function)} command Callback function or name
+ * of the command for |networkCommand|.
+ * @param {?string=} opt_iconURL Optional URL to an icon for the menu item.
+ * @return {!Element} The created menu item.
+ * @private
+ */
+ function createCallback_(menu, data, label, command, opt_iconURL) {
+ var button = menu.ownerDocument.createElement('div');
+ button.className = 'network-menu-item';
+
+ var buttonIcon = menu.ownerDocument.createElement('div');
+ buttonIcon.className = 'network-menu-item-icon';
+ button.appendChild(buttonIcon);
+ if (opt_iconURL)
+ buttonIcon.style.backgroundImage = url(opt_iconURL);
+
+ var buttonLabel = menu.ownerDocument.createElement('span');
+ buttonLabel.className = 'network-menu-item-label';
+ buttonLabel.textContent = label;
+ button.appendChild(buttonLabel);
+ var callback = null;
+ if (typeof command == 'string') {
+ var type = data.networkType;
+ var path = data.servicePath;
+ callback = function() {
+ chrome.send('networkCommand',
+ [type, path, command]);
+ closeMenu_();
+ };
+ } else if (command != null) {
+ if (data) {
+ callback = function() {
+ command(data);
+ closeMenu_();
+ };
+ } else {
+ callback = function() {
+ command();
+ closeMenu_();
+ };
+ }
+ }
+ if (callback != null)
+ button.addEventListener('click', callback);
+ else
+ buttonLabel.classList.add('network-disabled-control');
+
+ button.data = {label: label};
+ MenuItem.decorate(button);
+ menu.appendChild(button);
+ return button;
+ }
+
+ /**
+ * A list of controls for manipulating network connectivity.
+ * @constructor
+ */
+ var NetworkList = cr.ui.define('list');
+
+ NetworkList.prototype = {
+ __proto__: List.prototype,
+
+ /** @override */
+ decorate: function() {
+ List.prototype.decorate.call(this);
+ this.startBatchUpdates();
+ this.autoExpands = true;
+ this.dataModel = new ArrayDataModel([]);
+ this.selectionModel = new ListSingleSelectionModel();
+ this.addEventListener('blur', this.onBlur_.bind(this));
+ this.selectionModel.addEventListener('change',
+ this.onSelectionChange_.bind(this));
+
+ // Wi-Fi control is always visible.
+ this.update({key: 'wifi', networkList: []});
+
+ var entryAddWifi = {
+ label: loadTimeData.getString('addConnectionWifi'),
+ command: createAddConnectionCallback_(Constants.TYPE_WIFI)
+ };
+ var entryAddVPN = {
+ label: loadTimeData.getString('addConnectionVPN'),
+ command: createAddConnectionCallback_(Constants.TYPE_VPN)
+ };
+ this.update({key: 'addConnection',
+ iconType: 'add-connection',
+ menu: [entryAddWifi, entryAddVPN]
+ });
+
+ var prefs = options.Preferences.getInstance();
+ prefs.addEventListener('cros.signed.data_roaming_enabled',
+ function(event) {
+ enableDataRoaming_ = event.value.value;
+ });
+ this.endBatchUpdates();
+ },
+
+ /**
+ * When the list loses focus, unselect all items in the list and close the
+ * active menu.
+ * @private
+ */
+ onBlur_: function() {
+ this.selectionModel.unselectAll();
+ closeMenu_();
+ },
+
+ /**
+ * Close bubble and menu when a different list item is selected.
+ * @param {Event} event Event detailing the selection change.
+ * @private
+ */
+ onSelectionChange_: function(event) {
+ OptionsPage.hideBubble();
+ // A list item may temporarily become unselected while it is constructing
+ // its menu. The menu should therefore only be closed if a different item
+ // is selected, not when the menu's owner item is deselected.
+ if (activeMenu_) {
+ for (var i = 0; i < event.changes.length; ++i) {
+ if (event.changes[i].selected) {
+ var item = this.dataModel.item(event.changes[i].index);
+ if (!item.getMenuName || item.getMenuName() != activeMenu_) {
+ closeMenu_();
+ return;
+ }
+ }
+ }
+ }
+ },
+
+ /**
+ * Finds the index of a network item within the data model based on
+ * category.
+ * @param {string} key Unique key for the item in the list.
+ * @return {number} The index of the network item, or |undefined| if it is
+ * not found.
+ */
+ indexOf: function(key) {
+ var size = this.dataModel.length;
+ for (var i = 0; i < size; i++) {
+ var entry = this.dataModel.item(i);
+ if (entry.key == key)
+ return i;
+ }
+ },
+
+ /**
+ * Updates a network control.
+ * @param {Object.<string,string>} data Description of the entry.
+ */
+ update: function(data) {
+ this.startBatchUpdates();
+ var index = this.indexOf(data.key);
+ if (index == undefined) {
+ // Find reference position for adding the element. We cannot hide
+ // individual list elements, thus we need to conditionally add or
+ // remove elements and cannot rely on any element having a fixed index.
+ for (var i = 0; i < Constants.NETWORK_ORDER.length; i++) {
+ if (data.key == Constants.NETWORK_ORDER[i]) {
+ data.sortIndex = i;
+ break;
+ }
+ }
+ var referenceIndex = -1;
+ for (var i = 0; i < this.dataModel.length; i++) {
+ var entry = this.dataModel.item(i);
+ if (entry.sortIndex < data.sortIndex)
+ referenceIndex = i;
+ else
+ break;
+ }
+ if (referenceIndex == -1) {
+ // Prepend to the start of the list.
+ this.dataModel.splice(0, 0, data);
+ } else if (referenceIndex == this.dataModel.length) {
+ // Append to the end of the list.
+ this.dataModel.push(data);
+ } else {
+ // Insert after the reference element.
+ this.dataModel.splice(referenceIndex + 1, 0, data);
+ }
+ } else {
+ var entry = this.dataModel.item(index);
+ data.sortIndex = entry.sortIndex;
+ this.dataModel.splice(index, 1, data);
+ }
+ this.endBatchUpdates();
+ },
+
+ /** @override */
+ createItem: function(entry) {
+ if (entry.networkList)
+ return new NetworkSelectorItem(entry);
+ if (entry.command)
+ return new NetworkButtonItem(entry);
+ if (entry.menu)
+ return new NetworkMenuItem(entry);
+ },
+
+ /**
+ * Deletes an element from the list.
+ * @param {string} key Unique identifier for the element.
+ */
+ deleteItem: function(key) {
+ var index = this.indexOf(key);
+ if (index != undefined)
+ this.dataModel.splice(index, 1);
+ },
+
+ /**
+ * Updates the state of a toggle button.
+ * @param {string} key Unique identifier for the element.
+ * @param {boolean} active Whether the control is active.
+ */
+ updateToggleControl: function(key, active) {
+ var index = this.indexOf(key);
+ if (index != undefined) {
+ var entry = this.dataModel.item(index);
+ entry.iconType = active ? 'control-active' :
+ 'control-inactive';
+ this.update(entry);
+ }
+ }
+ };
+
+ /**
+ * Sets the default icon to use for each network type if disconnected.
+ * @param {!Object.<string, string>} data Mapping of network type to icon
+ * data url.
+ */
+ NetworkList.setDefaultNetworkIcons = function(data) {
+ defaultIcons_ = Object.create(data);
+ };
+
+ /**
+ * Sets the current logged in user type.
+ * @param {string} userType Current logged in user type.
+ */
+ NetworkList.updateLoggedInUserType = function(userType) {
+ loggedInUserType_ = String(userType);
+ };
+
+ /**
+ * Chrome callback for updating network controls.
+ * @param {Object} data Description of available network devices and their
+ * corresponding state.
+ */
+ NetworkList.refreshNetworkData = function(data) {
+ var networkList = $('network-list');
+ networkList.startBatchUpdates();
+ cellularAvailable_ = data.cellularAvailable;
+ cellularEnabled_ = data.cellularEnabled;
+ cellularSupportsScan_ = data.cellularSupportsScan;
+ wimaxAvailable_ = data.wimaxAvailable;
+ wimaxEnabled_ = data.wimaxEnabled;
+
+ // Only show Ethernet control if connected.
+ var ethernetConnection = getConnection_(data.wiredList);
+ if (ethernetConnection) {
+ var type = String(Constants.TYPE_ETHERNET);
+ var path = ethernetConnection.servicePath;
+ var ethernetOptions = function() {
+ chrome.send('networkCommand',
+ [type, path, 'options']);
+ };
+ networkList.update({key: 'ethernet',
+ subtitle: loadTimeData.getString('networkConnected'),
+ iconURL: ethernetConnection.iconURL,
+ command: ethernetOptions,
+ policyManaged: ethernetConnection.policyManaged});
+ } else {
+ networkList.deleteItem('ethernet');
+ }
+
+ if (data.wifiEnabled)
+ loadData_('wifi', data.wirelessList, data.rememberedList);
+ else
+ addEnableNetworkButton_('wifi', 'enableWifi', 'wifi');
+
+ // Only show cellular control if available.
+ if (data.cellularAvailable) {
+ if (data.cellularEnabled)
+ loadData_('cellular', data.wirelessList, data.rememberedList);
+ else
+ addEnableNetworkButton_('cellular', 'enableCellular', 'cellular');
+ } else {
+ networkList.deleteItem('cellular');
+ }
+
+ // Only show cellular control if available.
+ if (data.wimaxAvailable) {
+ if (data.wimaxEnabled)
+ loadData_('wimax', data.wirelessList, data.rememberedList);
+ else
+ addEnableNetworkButton_('wimax', 'enableWimax', 'cellular');
+ } else {
+ networkList.deleteItem('wimax');
+ }
+
+ // Only show VPN control if there is at least one VPN configured.
+ if (data.vpnList.length > 0)
+ loadData_('vpn', data.vpnList, data.rememberedList);
+ else
+ networkList.deleteItem('vpn');
+ networkList.endBatchUpdates();
+ };
+
+ /**
+ * Replaces a network menu with a button for reenabling the type of network.
+ * @param {string} name The type of network (wifi, cellular or wimax).
+ * @param {string} command The command for reenabling the network.
+ * @param {string} type of icon (wifi or cellular).
+ * @private
+ */
+ function addEnableNetworkButton_(name, command, icon) {
+ var subtitle = loadTimeData.getString('networkDisabled');
+ var enableNetwork = function() {
+ chrome.send(command);
+ };
+ var networkList = $('network-list');
+ networkList.update({key: name,
+ subtitle: subtitle,
+ iconType: icon,
+ command: enableNetwork});
+ }
+
+ /**
+ * Element for indicating a policy managed network.
+ * @constructor
+ */
+ function ManagedNetworkIndicator() {
+ var el = cr.doc.createElement('span');
+ el.__proto__ = ManagedNetworkIndicator.prototype;
+ el.decorate();
+ return el;
+ }
+
+ ManagedNetworkIndicator.prototype = {
+ __proto__: ControlledSettingIndicator.prototype,
+
+ /** @override */
+ decorate: function() {
+ ControlledSettingIndicator.prototype.decorate.call(this);
+ this.controlledBy = 'policy';
+ var policyLabel = loadTimeData.getString('managedNetwork');
+ this.setAttribute('textPolicy', policyLabel);
+ this.removeAttribute('tabindex');
+ },
+
+ /** @override */
+ handleEvent: function(event) {
+ // Prevent focus blurring as that would close any currently open menu.
+ if (event.type == 'mousedown')
+ return;
+ ControlledSettingIndicator.prototype.handleEvent.call(this, event);
+ },
+
+ /**
+ * Handle mouse events received by the bubble, preventing focus blurring as
+ * that would close any currently open menu and preventing propagation to
+ * any elements located behind the bubble.
+ * @param {Event} Mouse event.
+ */
+ stopEvent: function(event) {
+ event.preventDefault();
+ event.stopPropagation();
+ },
+
+ /** @override */
+ toggleBubble_: function() {
+ if (activeMenu_ && !$(activeMenu_).contains(this))
+ closeMenu_();
+ ControlledSettingIndicator.prototype.toggleBubble_.call(this);
+ if (this.showingBubble) {
+ var bubble = OptionsPage.getVisibleBubble();
+ bubble.addEventListener('mousedown', this.stopEvent);
+ bubble.addEventListener('click', this.stopEvent);
+ }
+ },
+ };
+
+ /**
+ * Updates the list of available networks and their status, filtered by
+ * network type.
+ * @param {string} category The type of network.
+ * @param {Array} available The list of available networks and their status.
+ * @param {Array} remembered The list of remmebered networks.
+ */
+ function loadData_(category, available, remembered) {
+ var data = {key: category};
+ var type = categoryMap[category];
+ var availableNetworks = [];
+ for (var i = 0; i < available.length; i++) {
+ if (available[i].networkType == type)
+ availableNetworks.push(available[i]);
+ }
+ data.networkList = availableNetworks;
+ if (remembered) {
+ var rememberedNetworks = [];
+ for (var i = 0; i < remembered.length; i++) {
+ if (remembered[i].networkType == type)
+ rememberedNetworks.push(remembered[i]);
+ }
+ data.rememberedNetworks = rememberedNetworks;
+ }
+ $('network-list').update(data);
+ }
+
+ /**
+ * Hides the currently visible menu.
+ * @private
+ */
+ function closeMenu_() {
+ if (activeMenu_) {
+ var menu = $(activeMenu_);
+ menu.hidden = true;
+ if (menu.data && menu.data.discardOnClose)
+ menu.parentNode.removeChild(menu);
+ activeMenu_ = null;
+ }
+ }
+
+ /**
+ * Fetches the active connection.
+ * @param {Array.<Object>} networkList List of networks.
+ * @return {boolean} True if connected or connecting to a network.
+ * @private
+ */
+ function getConnection_(networkList) {
+ if (!networkList)
+ return null;
+ for (var i = 0; i < networkList.length; i++) {
+ var entry = networkList[i];
+ if (entry.connected || entry.connecting)
+ return entry;
+ }
+ return null;
+ }
+
+ /**
+ * Create a callback function that adds a new connection of the given type.
+ * @param {!number} type A network type Constants.TYPE_*.
+ * @return {function()} The created callback.
+ * @private
+ */
+ function createAddConnectionCallback_(type) {
+ return function() {
+ chrome.send('networkCommand', [String(type), '', 'add']);
+ };
+ }
+
+ /**
+ * Whether the Network list is disabled. Only used for display purpose.
+ * @type {boolean}
+ */
+ cr.defineProperty(NetworkList, 'disabled', cr.PropertyKind.BOOL_ATTR);
+
+ // Export
+ return {
+ NetworkList: NetworkList
+ };
+});
diff --git a/chromium/chrome/browser/resources/options/chromeos/overscan_arrows.png b/chromium/chrome/browser/resources/options/chromeos/overscan_arrows.png
new file mode 100644
index 00000000000..6f3e394a063
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/chromeos/overscan_arrows.png
Binary files differ
diff --git a/chromium/chrome/browser/resources/options/chromeos/overscan_arrows_2x.png b/chromium/chrome/browser/resources/options/chromeos/overscan_arrows_2x.png
new file mode 100644
index 00000000000..10171c6ad69
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/chromeos/overscan_arrows_2x.png
Binary files differ
diff --git a/chromium/chrome/browser/resources/options/chromeos/overscan_shift.png b/chromium/chrome/browser/resources/options/chromeos/overscan_shift.png
new file mode 100644
index 00000000000..7f4bef5dd70
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/chromeos/overscan_shift.png
Binary files differ
diff --git a/chromium/chrome/browser/resources/options/chromeos/overscan_shift_2x.png b/chromium/chrome/browser/resources/options/chromeos/overscan_shift_2x.png
new file mode 100644
index 00000000000..7d23fdfd6d9
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/chromeos/overscan_shift_2x.png
Binary files differ
diff --git a/chromium/chrome/browser/resources/options/chromeos/overscan_shift_rtl.png b/chromium/chrome/browser/resources/options/chromeos/overscan_shift_rtl.png
new file mode 100644
index 00000000000..a42d926e221
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/chromeos/overscan_shift_rtl.png
Binary files differ
diff --git a/chromium/chrome/browser/resources/options/chromeos/overscan_shift_rtl_2x.png b/chromium/chrome/browser/resources/options/chromeos/overscan_shift_rtl_2x.png
new file mode 100644
index 00000000000..fb89e2f9b8f
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/chromeos/overscan_shift_rtl_2x.png
Binary files differ
diff --git a/chromium/chrome/browser/resources/options/chromeos/pointer_overlay.css b/chromium/chrome/browser/resources/options/chromeos/pointer_overlay.css
new file mode 100644
index 00000000000..9076ef1929c
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/chromeos/pointer_overlay.css
@@ -0,0 +1,11 @@
+/* Copyright (c) 2012 The Chromium Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file. */
+
+#pointer-overlay > .content-area > :not([hidden]) + :nth-child(2) {
+ margin-top: 20px;
+}
+
+#touchpad-scroll-direction {
+ margin-top: 12px;
+}
diff --git a/chromium/chrome/browser/resources/options/chromeos/pointer_overlay.html b/chromium/chrome/browser/resources/options/chromeos/pointer_overlay.html
new file mode 100644
index 00000000000..562522e3e05
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/chromeos/pointer_overlay.html
@@ -0,0 +1,50 @@
+<div id="pointer-overlay" class="page" hidden>
+ <div class="close-button"></div>
+ <div class="content-area">
+ <section id="pointer-section-touchpad" hidden>
+ <h3 i18n-content="pointerOverlaySectionTitleTouchpad"></h3>
+ <div class="checkbox">
+ <label>
+ <input type="checkbox" metric="Options_TouchpadTapToClick"
+ pref="settings.touchpad.enable_tap_to_click" dialog-pref>
+ <span i18n-content="enableTapToClick"></span>
+ </label>
+ </div>
+ <div class="radio" id="touchpad-scroll-direction">
+ <label>
+ <input type="radio" name="touchpad-scroll-direction" value="false"
+ metric="Options_TouchpadNaturalScroll"
+ pref="settings.touchpad.natural_scroll" dialog-pref>
+ <span i18n-content="traditionalScroll"></span>
+ </label>
+ </div>
+ <div class="radio">
+ <label>
+ <input type="radio" name="touchpad-scroll-direction" value="true"
+ metric="Options_TouchpadNaturalScroll"
+ pref="settings.touchpad.natural_scroll" dialog-pref>
+ <span i18n-values=".innerHTML:naturalScroll"></span>
+ </label>
+ </div>
+ </section>
+ <section id="pointer-section-mouse" hidden>
+ <h3 i18n-content="pointerOverlaySectionTitleMouse"></h3>
+ <div class="checkbox">
+ <label>
+ <input type="checkbox" metric="Options_MousePrimaryRight"
+ pref="settings.mouse.primary_right" dialog-pref>
+ <span i18n-content="primaryMouseRight"></span>
+ </label>
+ </div>
+ </section>
+ </div>
+ <div class="action-area">
+ <div class="button-strip">
+ <button id="pointer-overlay-cancel" type="reset" i18n-content="cancel">
+ </button>
+ <button id="pointer-overlay-confirm" class="default-button" type="submit"
+ i18n-content="ok">
+ </button>
+ </div>
+ </div>
+</div>
diff --git a/chromium/chrome/browser/resources/options/chromeos/pointer_overlay.js b/chromium/chrome/browser/resources/options/chromeos/pointer_overlay.js
new file mode 100644
index 00000000000..d6a4de9177b
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/chromeos/pointer_overlay.js
@@ -0,0 +1,78 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+ /** @const */ var SettingsDialog = options.SettingsDialog;
+
+ /**
+ * PointerOverlay class
+ * Dialog that allows users to set pointer settings (touchpad/mouse).
+ * @extends {SettingsDialog}
+ */
+ function PointerOverlay() {
+ // The title is updated dynamically in the setTitle method as pointer
+ // devices are discovered or removed.
+ SettingsDialog.call(this, 'pointer-overlay',
+ '', 'pointer-overlay',
+ $('pointer-overlay-confirm'),
+ $('pointer-overlay-cancel'));
+ }
+
+ cr.addSingletonGetter(PointerOverlay);
+
+ PointerOverlay.prototype = {
+ __proto__: SettingsDialog.prototype,
+
+ /**
+ * Initialize the page.
+ */
+ initializePage: function() {
+ // Call base class implementation to start preference initialization.
+ SettingsDialog.prototype.initializePage.call(this);
+ },
+ };
+
+ /**
+ * Sets the visibility state of the touchpad group.
+ * @param {boolean} show True to show, false to hide.
+ */
+ PointerOverlay.showTouchpadControls = function(show) {
+ $('pointer-section-touchpad').hidden = !show;
+ };
+
+ /**
+ * Sets the visibility state of the mouse group.
+ * @param {boolean} show True to show, false to hide.
+ */
+ PointerOverlay.showMouseControls = function(show) {
+ $('pointer-section-mouse').hidden = !show;
+ };
+
+ /**
+ * Updates the title of the pointer dialog. The title is set dynamically
+ * based on whether a touchpad, mouse or both are present. The label on the
+ * button that activates the overlay is also updated to stay in sync. A
+ * message is displayed in the main settings page if no pointer devices are
+ * available.
+ * @param {string} label i18n key for the overlay title.
+ */
+ PointerOverlay.setTitle = function(label) {
+ var button = $('pointer-settings-button');
+ var noPointersLabel = $('no-pointing-devices');
+ if (label.length > 0) {
+ var title = loadTimeData.getString(label);
+ button.textContent = title;
+ button.hidden = false;
+ noPointersLabel.hidden = true;
+ } else {
+ button.hidden = true;
+ noPointersLabel.hidden = false;
+ }
+ };
+
+ // Export
+ return {
+ PointerOverlay: PointerOverlay
+ };
+});
diff --git a/chromium/chrome/browser/resources/options/chromeos/preferred_networks.html b/chromium/chrome/browser/resources/options/chromeos/preferred_networks.html
new file mode 100644
index 00000000000..47b0d120a8b
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/chromeos/preferred_networks.html
@@ -0,0 +1,17 @@
+<div id="preferredNetworksPage" class="page" hidden>
+ <div class="close-button"></div>
+ <h1 id="preferred-networks-page-title"
+ i18n-content="preferredNetworksPage"></h1>
+ <div class="content-area">
+ <div class="settings-list">
+ <list id="remembered-network-list"></list>
+ </div>
+ </div>
+ <div class="action-area">
+ <div class="button-strip">
+ <button id="preferred-networks-confirm" class="default-button"
+ i18n-content="done">
+ </button>
+ </div>
+ </div>
+</div>
diff --git a/chromium/chrome/browser/resources/options/chromeos/preferred_networks.js b/chromium/chrome/browser/resources/options/chromeos/preferred_networks.js
new file mode 100644
index 00000000000..b6b461be94c
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/chromeos/preferred_networks.js
@@ -0,0 +1,164 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+
+ var OptionsPage = options.OptionsPage;
+ var ArrayDataModel = cr.ui.ArrayDataModel;
+ var DeletableItem = options.DeletableItem;
+ var DeletableItemList = options.DeletableItemList;
+
+ /////////////////////////////////////////////////////////////////////////////
+ // NetworkPreferences class:
+
+ /**
+ * Encapsulated handling of ChromeOS network preferences page.
+ * @constructor
+ */
+ function PreferredNetworks(model) {
+ OptionsPage.call(this,
+ 'preferredNetworksPage',
+ null,
+ 'preferredNetworksPage');
+ }
+
+ cr.addSingletonGetter(PreferredNetworks);
+
+ PreferredNetworks.prototype = {
+ __proto__: OptionsPage.prototype,
+
+ /**
+ * Initializes the preferred networks page.
+ */
+ initializePage: function() {
+ OptionsPage.prototype.initializePage.call(this);
+ PreferredNetworkList.decorate($('remembered-network-list'));
+ $('preferred-networks-confirm').onclick =
+ OptionsPage.closeOverlay.bind(OptionsPage);
+ },
+
+ update: function(rememberedNetworks) {
+ var list = $('remembered-network-list');
+ list.clear();
+ for (var i = 0; i < rememberedNetworks.length; i++) {
+ list.append(rememberedNetworks[i]);
+ }
+ list.redraw();
+ }
+
+ };
+
+ /**
+ * Creates a list entry for a remembered network.
+ * @param{{networkName: string,
+ networkType: string,
+ servicePath: string}} data
+ * Description of the network.
+ * @constructor
+ */
+ function PreferredNetworkListItem(data) {
+ var el = cr.doc.createElement('div');
+ el.__proto__ = PreferredNetworkListItem.prototype;
+ el.data = {};
+ for (var key in data)
+ el.data[key] = data[key];
+ el.decorate();
+ return el;
+ }
+
+ PreferredNetworkListItem.prototype = {
+ __proto__: DeletableItem.prototype,
+
+ /**
+ * Description of the network.
+ * @type {{networkName: string,
+ * networkType: string,
+ * servicePath: string}}
+ */
+ data: null,
+
+ /** @override */
+ decorate: function() {
+ DeletableItem.prototype.decorate.call(this);
+ var label = this.ownerDocument.createElement('div');
+ label.textContent = this.data.networkName;
+ if (this.data.policyManaged)
+ this.deletable = false;
+ this.contentElement.appendChild(label);
+ }
+ };
+
+ /**
+ * Class for displaying a list of preferred networks.
+ * @constructor
+ * @extends {options.DeletableItemList}
+ */
+ var PreferredNetworkList = cr.ui.define('list');
+
+ PreferredNetworkList.prototype = {
+ __proto__: DeletableItemList.prototype,
+
+ /** @override */
+ decorate: function() {
+ DeletableItemList.prototype.decorate.call(this);
+ this.addEventListener('blur', this.onBlur_);
+ this.clear();
+ },
+
+ /**
+ * When the list loses focus, unselect all items in the list.
+ * @private
+ */
+ onBlur_: function() {
+ this.selectionModel.unselectAll();
+ },
+
+ /** @override */
+ createItem: function(entry) {
+ return new PreferredNetworkListItem(entry);
+ },
+
+ /** @override */
+ deleteItemAtIndex: function(index) {
+ var item = this.dataModel.item(index);
+ if (item) {
+ // Inform the network library that we are forgetting this network.
+ chrome.send('networkCommand',
+ [item.networkType,
+ item.servicePath,
+ '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();
+ },
+
+ /**
+ * Purges all networks from the list.
+ */
+ clear: function() {
+ this.dataModel = new ArrayDataModel([]);
+ this.redraw();
+ },
+
+ /**
+ * Adds a remembered network to the list.
+ * @param {{networkName: string,
+ networkType: string,
+ servicePath: string} data
+ * Description of the network.
+ */
+ append: function(data) {
+ this.dataModel.push(data);
+ }
+ };
+
+ // Export
+ return {
+ PreferredNetworks: PreferredNetworks
+ };
+
+});
diff --git a/chromium/chrome/browser/resources/options/chromeos/proxy_rules_list.js b/chromium/chrome/browser/resources/options/chromeos/proxy_rules_list.js
new file mode 100644
index 00000000000..1e65b4c0200
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/chromeos/proxy_rules_list.js
@@ -0,0 +1,140 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options.proxyexceptions', function() {
+ /** @const */ var List = cr.ui.List;
+ /** @const */ var ListItem = cr.ui.ListItem;
+ /** @const */ var 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',
+
+ /** @override */
+ decorate: function() {
+ List.prototype.decorate.call(this);
+ this.autoExpands = true;
+
+ // 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.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(), true);
+ }
+ };
+
+ /**
+ * Creates a new exception list item.
+ * @param {Object} 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,
+
+ /** @override */
+ 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/chromium/chrome/browser/resources/options/chromeos/warning.png b/chromium/chrome/browser/resources/options/chromeos/warning.png
new file mode 100644
index 00000000000..53713ba3f90
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/chromeos/warning.png
Binary files differ
diff --git a/chromium/chrome/browser/resources/options/clear_browser_data_overlay.css b/chromium/chrome/browser/resources/options/clear_browser_data_overlay.css
new file mode 100644
index 00000000000..171d42a415b
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/clear_browser_data_overlay.css
@@ -0,0 +1,39 @@
+/* Copyright (c) 2012 The Chromium Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file. */
+
+#clear-browser-data-overlay {
+ width: 500px;
+}
+
+#clear-data-checkboxes {
+ -webkit-padding-start: 8px;
+ margin: 5px 0;
+}
+
+#cbd-throbber {
+ margin: 4px 10px;
+ vertical-align: middle;
+ visibility: hidden;
+}
+
+#flash-storage-settings {
+ padding-top: 5px;
+}
+
+#clear-browser-data-info-banner {
+ background-color: rgb(249, 237, 184);
+ border: solid 1px rgb(237, 201, 103);
+ border-radius: 2px;
+ font-weight: bold;
+ margin: 0 20px 11px 17px;
+ padding: 6px 8px;
+}
+
+#some-stuff-remains-footer > p {
+ margin: 0;
+}
+
+#some-stuff-remains-footer button {
+ padding: 0;
+}
diff --git a/chromium/chrome/browser/resources/options/clear_browser_data_overlay.html b/chromium/chrome/browser/resources/options/clear_browser_data_overlay.html
new file mode 100644
index 00000000000..1debae7e6ef
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/clear_browser_data_overlay.html
@@ -0,0 +1,99 @@
+<div id="clear-browser-data-overlay" class="page" hidden>
+ <div class="close-button"></div>
+ <h1 i18n-content="clearBrowserDataOverlay"></h1>
+ <div id="clear-browser-data-info-banner" hidden>
+ <span i18n-content="clearBrowserDataInfoBar"></span>
+ </div>
+ <div id="cbd-content-area" class="content-area">
+ <span i18n-content="clearBrowserDataLabel"></span>
+ <select id="clear-browser-data-time-period"
+ i18n-options="clearBrowserDataTimeList"
+ pref="browser.clear_data.time_period"
+ data-type="number">
+ </select>
+ <div id="clear-data-checkboxes">
+ <div id="delete-browsing-history-container" class="checkbox">
+ <label>
+ <input id="delete-browsing-history-checkbox"
+ pref="browser.clear_data.browsing_history" type="checkbox">
+ <span i18n-content="deleteBrowsingHistoryCheckbox"></span>
+ </label>
+ </div>
+ <div id="delete-download-history-container" class="checkbox">
+ <label>
+ <input id="delete-download-history-checkbox"
+ pref="browser.clear_data.download_history" type="checkbox">
+ <span i18n-content="deleteDownloadHistoryCheckbox"></span>
+ </label>
+ </div>
+ <div id="delete-cookies-container" class="checkbox">
+ <label>
+ <input id="delete-cookies-checkbox"
+ 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 id="delete-cache-container" class="checkbox">
+ <label>
+ <input id="delete-cache-checkbox"
+ pref="browser.clear_data.cache" type="checkbox">
+ <span i18n-content="deleteCacheCheckbox"></span>
+ </label>
+ </div>
+ <div id="delete-passwords-container" class="checkbox">
+ <label>
+ <input id="delete-passwords-checkbox"
+ pref="browser.clear_data.passwords" type="checkbox">
+ <span i18n-content="deletePasswordsCheckbox"></span>
+ </label>
+ </div>
+ <div id="delete-form-data-container" class="checkbox">
+ <label>
+ <input id="delete-form-data-checkbox"
+ pref="browser.clear_data.form_data" type="checkbox">
+ <span i18n-content="deleteFormDataCheckbox"></span>
+ </label>
+ </div>
+ <div id="delete-hosted-apps-data-container" class="checkbox">
+ <label>
+ <input id="delete-hosted-apps-data-checkbox"
+ pref="browser.clear_data.hosted_apps_data" type="checkbox">
+ <span i18n-content="deleteHostedAppsDataCheckbox"></span>
+ </label>
+ </div>
+ <div id="deauthorize-content-licenses-container"
+ class="checkbox pepper-flash-settings">
+ <label>
+ <input id="deauthorize-content-licenses-checkbox"
+ pref="browser.clear_data.content_licenses" type="checkbox">
+ <span i18n-content="deauthorizeContentLicensesCheckbox"></span>
+ </label>
+ </div>
+ </div>
+ <div id="flash-storage-settings" class="flash-plugin-area">
+ <a target="_blank" i18n-content="flash_storage_settings"
+ i18n-values="href:flash_storage_url"></a>
+ </div>
+ </div>
+ <div class="action-area">
+ <div class="hbox stretch">
+ <a target="_blank" i18n-content="learnMore"
+ i18n-values="href:clearBrowsingDataLearnMoreUrl"></a>
+ </div>
+ <div class="action-area-right">
+ <div id="cbd-throbber" class="throbber"></div>
+ <div class="button-strip">
+ <button id="clear-browser-data-dismiss" i18n-content="cancel"></button>
+ <button id="clear-browser-data-commit" class="default-button"
+ i18n-content="clearBrowserDataCommit">
+ </button>
+ </div>
+ </div>
+ </div>
+ <div id="some-stuff-remains-footer" class="gray-bottom-bar">
+ <p><!--This is filled by JavaScript--></p>
+ </div>
+</div>
diff --git a/chromium/chrome/browser/resources/options/clear_browser_data_overlay.js b/chromium/chrome/browser/resources/options/clear_browser_data_overlay.js
new file mode 100644
index 00000000000..563a41a88ff
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/clear_browser_data_overlay.js
@@ -0,0 +1,209 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+ var OptionsPage = options.OptionsPage;
+
+ /**
+ * ClearBrowserDataOverlay class
+ * Encapsulated handling of the 'Clear Browser Data' overlay page.
+ * @class
+ */
+ function ClearBrowserDataOverlay() {
+ OptionsPage.call(this, 'clearBrowserData',
+ loadTimeData.getString('clearBrowserDataOverlayTabTitle'),
+ 'clear-browser-data-overlay');
+ }
+
+ cr.addSingletonGetter(ClearBrowserDataOverlay);
+
+ ClearBrowserDataOverlay.prototype = {
+ // Inherit ClearBrowserDataOverlay from OptionsPage.
+ __proto__: OptionsPage.prototype,
+
+ // Whether deleting history and downloads is allowed.
+ allowDeletingHistory_: true,
+
+ /**
+ * 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',
+ 'browser.clear_data.hosted_apps_data',
+ 'browser.clear_data.content_licenses'];
+ types.forEach(function(type) {
+ Preferences.getInstance().addEventListener(type, f);
+ });
+
+ var checkboxes = document.querySelectorAll(
+ '#cbd-content-area input[type=checkbox]');
+ for (var i = 0; i < checkboxes.length; i++) {
+ checkboxes[i].onclick = f;
+ }
+ this.updateCommitButtonState_();
+
+ this.createStuffRemainsFooter_();
+
+ $('clear-browser-data-dismiss').onclick = function(event) {
+ ClearBrowserDataOverlay.dismiss();
+ };
+ $('clear-browser-data-commit').onclick = function(event) {
+ ClearBrowserDataOverlay.setClearingState(true);
+ chrome.send('performClearBrowserData');
+ };
+
+ var show = loadTimeData.getBoolean('showDeleteBrowsingHistoryCheckboxes');
+ this.showDeleteHistoryCheckboxes_(show);
+ },
+
+ // Create a footer that explains that some content is not cleared by the
+ // clear browsing history dialog.
+ createStuffRemainsFooter_: function() {
+ // The localized string is of the form "Saved [content settings] and
+ // {search engines} will not be cleared and may reflect your browsing
+ // habits.". The following parses out the parts in brackts and braces and
+ // converts them into buttons whereas the remainders are represented as
+ // span elements.
+ var footer =
+ document.querySelector('#some-stuff-remains-footer p');
+ var footerFragments =
+ loadTimeData.getString('contentSettingsAndSearchEnginesRemain')
+ .split(/([|#])/);
+ for (var i = 0; i < footerFragments.length;) {
+ var buttonId = '';
+ if (i + 2 < footerFragments.length) {
+ if (footerFragments[i] == '|' && footerFragments[i + 2] == '|') {
+ buttonId = 'open-content-settings-from-clear-browsing-data';
+ } else if (footerFragments[i] == '#' &&
+ footerFragments[i + 2] == '#') {
+ buttonId = 'open-search-engines-from-clear-browsing-data';
+ }
+ }
+
+ if (buttonId != '') {
+ var button = document.createElement('button');
+ button.setAttribute('id', buttonId);
+ button.setAttribute('class', 'link-button');
+ button.textContent = footerFragments[i + 1];
+ footer.appendChild(button);
+ i += 3;
+ } else {
+ var span = document.createElement('span');
+ span.textContent = footerFragments[i];
+ footer.appendChild(span);
+ i += 1;
+ }
+ }
+ $('open-content-settings-from-clear-browsing-data').onclick =
+ function(event) {
+ OptionsPage.navigateToPage('content');
+ }
+ $('open-search-engines-from-clear-browsing-data').onclick =
+ function(event) {
+ OptionsPage.navigateToPage('searchEngines');
+ }
+ },
+
+ // Set the enabled state of the commit button.
+ updateCommitButtonState_: function() {
+ var checkboxes = document.querySelectorAll(
+ '#cbd-content-area input[type=checkbox]');
+ var isChecked = false;
+ for (var i = 0; i < checkboxes.length; i++) {
+ if (checkboxes[i].checked) {
+ isChecked = true;
+ break;
+ }
+ }
+ $('clear-browser-data-commit').disabled = !isChecked;
+ },
+
+ setAllowDeletingHistory: function(allowed) {
+ this.allowDeletingHistory_ = allowed;
+ },
+
+ showDeleteHistoryCheckboxes_: function(show) {
+ if (!show) {
+ $('delete-browsing-history-container').hidden = true;
+ $('delete-download-history-container').hidden = true;
+ }
+ },
+
+ /** @override */
+ didShowPage: function() {
+ var allowed = ClearBrowserDataOverlay.getInstance().allowDeletingHistory_;
+ ClearBrowserDataOverlay.updateHistoryCheckboxes(allowed);
+ },
+ };
+
+ //
+ // Chrome callbacks
+ //
+ /**
+ * Updates the disabled status of the browsing-history and downloads
+ * checkboxes, also unchecking them if they are disabled. This is called in
+ * response to a change in the corresponding preference.
+ */
+ ClearBrowserDataOverlay.updateHistoryCheckboxes = function(allowed) {
+ $('delete-browsing-history-checkbox').disabled = !allowed;
+ $('delete-download-history-checkbox').disabled = !allowed;
+ if (!allowed) {
+ $('delete-browsing-history-checkbox').checked = false;
+ $('delete-download-history-checkbox').checked = false;
+ }
+ ClearBrowserDataOverlay.getInstance().setAllowDeletingHistory(allowed);
+ };
+
+ ClearBrowserDataOverlay.setClearingState = function(state) {
+ $('delete-browsing-history-checkbox').disabled = state;
+ $('delete-download-history-checkbox').disabled = state;
+ $('delete-cache-checkbox').disabled = state;
+ $('delete-cookies-checkbox').disabled = state;
+ $('delete-passwords-checkbox').disabled = state;
+ $('delete-form-data-checkbox').disabled = state;
+ $('delete-hosted-apps-data-checkbox').disabled = state;
+ $('deauthorize-content-licenses-checkbox').disabled = state;
+ $('clear-browser-data-time-period').disabled = state;
+ $('cbd-throbber').style.visibility = state ? 'visible' : 'hidden';
+ $('clear-browser-data-dismiss').disabled = state;
+
+ if (state)
+ $('clear-browser-data-commit').disabled = true;
+ else
+ ClearBrowserDataOverlay.getInstance().updateCommitButtonState_();
+ };
+
+ ClearBrowserDataOverlay.setBannerVisibility = function(args) {
+ var visible = args[0];
+ $('clear-browser-data-info-banner').hidden = !visible;
+ };
+
+ 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/chromium/chrome/browser/resources/options/confirm_dialog.js b/chromium/chrome/browser/resources/options/confirm_dialog.js
new file mode 100644
index 00000000000..acf34e6db02
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/confirm_dialog.js
@@ -0,0 +1,118 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+ /** @const */ var OptionsPage = options.OptionsPage;
+
+ /**
+ * A dialog that will pop up when the user attempts to set the value of the
+ * Boolean |pref| to |true|, asking for confirmation. If the user clicks OK,
+ * the new value is committed to Chrome. If the user clicks Cancel or leaves
+ * the settings page, the new value is discarded.
+ * @constructor
+ * @param {string} name See OptionsPage constructor.
+ * @param {string} title See OptionsPage constructor.
+ * @param {string} pageDivName See OptionsPage constructor.
+ * @param {HTMLInputElement} okButton The confirmation button element.
+ * @param {HTMLInputElement} cancelButton The cancellation button element.
+ * @param {string} pref The pref that requires confirmation.
+ * @param {string} metric User metrics identifier.
+ * @param {string} confirmed_pref A pref used to remember whether the user has
+ * confirmed the dialog before. This ensures that the user is presented
+ * with the dialog only once. If left |undefined| or |null|, the dialog
+ * will pop up every time the user attempts to set |pref| to |true|.
+ * @extends {OptionsPage}
+ */
+ function ConfirmDialog(name, title, pageDivName, okButton, cancelButton, pref,
+ metric, confirmed_pref) {
+ OptionsPage.call(this, name, title, pageDivName);
+ this.okButton = okButton;
+ this.cancelButton = cancelButton;
+ this.pref = pref;
+ this.metric = metric;
+ this.confirmed_pref = confirmed_pref;
+ this.confirmed_ = false;
+ }
+
+ ConfirmDialog.prototype = {
+ // Set up the prototype chain
+ __proto__: OptionsPage.prototype,
+
+ /**
+ * Handle changes to |pref|. Only uncommitted changes are relevant as these
+ * originate from user and need to be explicitly committed to take effect.
+ * Pop up the dialog or commit the change, depending on whether confirmation
+ * is needed.
+ * @param {Event} event Change event.
+ * @private
+ */
+ onPrefChanged_: function(event) {
+ if (!event.value.uncommitted)
+ return;
+
+ if (event.value.value && !this.confirmed_)
+ OptionsPage.showPageByName(this.name, false);
+ else
+ Preferences.getInstance().commitPref(this.pref, this.metric);
+ },
+
+ /**
+ * Handle changes to |confirmed_pref| by caching them.
+ * @param {Event} event Change event.
+ * @private
+ */
+ onConfirmedChanged_: function(event) {
+ this.confirmed_ = event.value.value;
+ },
+
+ /** @override */
+ initializePage: function() {
+ this.okButton.onclick = this.handleConfirm.bind(this);
+ this.cancelButton.onclick = this.handleCancel.bind(this);
+ Preferences.getInstance().addEventListener(
+ this.pref, this.onPrefChanged_.bind(this));
+ if (this.confirmed_pref) {
+ Preferences.getInstance().addEventListener(
+ this.confirmed_pref, this.onConfirmedChanged_.bind(this));
+ }
+ },
+
+ /**
+ * Handle the confirm button by committing the |pref| change. If
+ * |confirmed_pref| has been specified, also remember that the dialog has
+ * been confirmed to avoid bringing it up in the future.
+ */
+ handleConfirm: function() {
+ OptionsPage.closeOverlay();
+
+ Preferences.getInstance().commitPref(this.pref, this.metric);
+ if (this.confirmed_pref)
+ Preferences.setBooleanPref(this.confirmed_pref, true, true);
+ },
+
+ /**
+ * Handle the cancel button by rolling back the |pref| change without it
+ * ever taking effect.
+ */
+ handleCancel: function() {
+ OptionsPage.closeOverlay();
+
+ Preferences.getInstance().rollbackPref(this.pref);
+ },
+
+ /**
+ * When a user navigates away from a confirm dialog, treat as a cancel.
+ * @protected
+ * @override
+ */
+ willHidePage: function() {
+ if (this.visible)
+ Preferences.getInstance().rollbackPref(this.pref);
+ },
+ };
+
+ return {
+ ConfirmDialog: ConfirmDialog
+ };
+});
diff --git a/chromium/chrome/browser/resources/options/content_settings.css b/chromium/chrome/browser/resources/options/content_settings.css
new file mode 100644
index 00000000000..30e01d197fc
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/content_settings.css
@@ -0,0 +1,124 @@
+/* Copyright (c) 2012 The Chromium Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file. */
+
+#content-settings-page {
+ min-width: 620px;
+}
+
+#content-settings-exceptions-area {
+ min-width: 540px;
+}
+
+.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 {
+ /* Same as .checkbox and .radio padding. Using padding instead of margin
+ * to ensure minimum height for tap target. */
+ padding: 7px 0;
+}
+
+#group-indicator {
+ margin-left: 5px;
+}
+
+div[role='listitem'][controlled-by] {
+ color: #666;
+ font-style: italic;
+ position: relative;
+}
+
+.section-header {
+ -webkit-margin-start: -18px;
+ margin-bottom: 0.8em;
+}
+
+.section-header > h3 {
+ display: inline;
+}
+
+.settings-list div[role='listitem'][controlled-by='policy'],
+.settings-list div[role='listitem'][controlled-by='extension'] {
+ background: rgb(250, 230, 146);
+ border-bottom: 1px solid rgb(201, 189, 141);
+ border-top: 0;
+}
+
+.sublabel {
+ -webkit-margin-start: 2em;
+}
+
+.media-device-control {
+ display: table-row;
+}
+
+.media-device-control > span {
+ display: table-cell;
+ min-width: 145px;
+}
+
+.media-device-control > select {
+ display: table-cell;
+ margin: 5px 0;
+ width: 12em;
+}
+
+.exception-setting.media-audio-setting {
+ width: 6em;
+}
+
+.exception-setting.media-video-setting {
+ width: 6.5em;
+}
+
+#media-column-header {
+ display: -webkit-box;
+}
+
+#media-audio-column {
+ width: 6em;
+}
+
+#media-video-column {
+ width: 8.5em;
+}
diff --git a/chromium/chrome/browser/resources/options/content_settings.html b/chromium/chrome/browser/resources/options/content_settings.html
new file mode 100644
index 00000000000..5cebc041b44
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/content_settings.html
@@ -0,0 +1,601 @@
+<div id="content-settings-page" class="page" hidden>
+ <div class="close-button"></div>
+ <h1 i18n-content="contentSettingsPage"></h1>
+ <div class="content-area">
+ <!-- Cookie filter tab contents -->
+ <section>
+ <h3 i18n-content="cookies_tab_label"></h3>
+ <div>
+ <div class="radio">
+ <span class="controlled-setting-with-label">
+ <input id="cookies-allow" type="radio" name="cookies" value="allow">
+ <span>
+ <label for="cookies-allow" i18n-content="cookies_allow"></label>
+ <span class="controlled-setting-indicator"
+ content-setting="cookies" value="allow">
+ </span>
+ </span>
+ </span>
+ </div>
+ <div class="radio">
+ <span class="controlled-setting-with-label">
+ <input id="cookies-session" type="radio" name="cookies"
+ value="session">
+ <span>
+ <label for="cookies-session" i18n-content="cookies_session_only">
+ </label>
+ <span class="controlled-setting-indicator"
+ content-setting="cookies" value="session">
+ </span>
+ </span>
+ </span>
+ </div>
+ <div class="radio">
+ <span class="controlled-setting-with-label">
+ <input id="cookies-block" type="radio" name="cookies" value="block">
+ <span>
+ <label for="cookies-block" i18n-content="cookies_block"></label>
+ <span class="controlled-setting-indicator"
+ content-setting="cookies" value="block">
+ </span>
+ </span>
+ </span>
+ </div>
+ <div class="checkbox">
+ <span class="controlled-setting-with-label">
+ <input id="block-third-party-cookies"
+ pref="profile.block_third_party_cookies" type="checkbox">
+ <span>
+ <label for="block-third-party-cookies"
+ i18n-content="cookies_block_3rd_party">
+ </label>
+ <span class="controlled-setting-indicator"
+ pref="profile.block_third_party_cookies">
+ </span>
+ </span>
+ </span>
+ </div>
+ <!-- TODO(jochen): remove the div with the clear cookies on exit option
+ once this has shipped. -->
+ <div class="checkbox" guest-visibility="disabled" hidden>
+ <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>
+ <div class="settings-row">
+ <button class="exceptions-list-button" contentType="cookies"
+ i18n-content="manageExceptions"></button>
+ <button id="show-cookies-button"
+ i18n-content="cookies_show_cookies"></button>
+ </div>
+ </div>
+ </section>
+ <!-- Image filter -->
+ <section>
+ <h3 i18n-content="images_tab_label"></h3>
+ <div>
+ <div class="radio">
+ <span class="controlled-setting-with-label">
+ <input id="images-allow" type="radio" name="images" value="allow">
+ <span>
+ <label for="images-allow" i18n-content="images_allow"></label>
+ <span class="controlled-setting-indicator"
+ content-setting="images" value="allow">
+ </span>
+ </span>
+ </span>
+ </div>
+ <div class="radio">
+ <span class="controlled-setting-with-label">
+ <input id="images-block" type="radio" name="images" value="block">
+ <span>
+ <label for="images-block" i18n-content="images_block"></label>
+ <span class="controlled-setting-indicator"
+ content-setting="images" value="block">
+ </span>
+ </span>
+ </span>
+ </div>
+ <div class="settings-row">
+ <button class="exceptions-list-button" contentType="images"
+ i18n-content="manageExceptions"></button>
+ </div>
+ </div>
+ </section>
+ <!-- JavaScript filter -->
+ <section>
+ <h3 i18n-content="javascript_tab_label"></h3>
+ <div>
+ <div class="radio">
+ <span class="controlled-setting-with-label">
+ <input id="javascript-allow" type="radio" name="javascript"
+ value="allow">
+ <span>
+ <label for="javascript-allow" i18n-content="javascript_allow">
+ </label>
+ <span class="controlled-setting-indicator"
+ content-setting="javascript" value="allow">
+ </span>
+ </span>
+ </span>
+ </div>
+ <div class="radio">
+ <span class="controlled-setting-with-label">
+ <input id="javascript-block" type="radio" name="javascript"
+ value="block">
+ <span>
+ <label for="javascript-block" i18n-content="javascript_block">
+ </label>
+ <span class="controlled-setting-indicator"
+ content-setting="javascript" value="block">
+ </span>
+ </span>
+ </span>
+ </div>
+ <div class="settings-row">
+ <button class="exceptions-list-button" contentType="javascript"
+ i18n-content="manageExceptions"></button>
+ </div>
+ </div>
+ </section>
+ <!-- Handlers settings -->
+ <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>
+ <div class="settings-row">
+ <button id="manage-handlers-button" contentType="handlers"
+ i18n-content="manage_handlers"></button>
+ </div>
+ </div>
+ </section>
+ <!-- Plug-ins filter -->
+ <section>
+ <h3 i18n-content="plugins_tab_label"></h3>
+ <div>
+ <div class="radio">
+ <span class="controlled-setting-with-label">
+ <input id="plugins-allow" type="radio" name="plugins" value="allow">
+ <span>
+ <label for="plugins-allow" i18n-content="plugins_allow"></label>
+ <span class="controlled-setting-indicator"
+ content-setting="plugins" value="allow">
+ </span>
+ </span>
+ </span>
+ </div>
+ <div id="click_to_play" class="radio">
+ <span class="controlled-setting-with-label">
+ <input id="plugins-ask" type="radio" name="plugins" value="ask">
+ <span>
+ <label for="plugins-ask" i18n-content="plugins_ask"></label>
+ <span class="controlled-setting-indicator"
+ content-setting="plugins" value="ask">
+ </span>
+ </span>
+ </span>
+ </div>
+ <div class="radio">
+ <span class="controlled-setting-with-label">
+ <input id="plugins-block" type="radio" name="plugins" value="block">
+ <span>
+ <label for="plugins-block" i18n-content="plugins_block"></label>
+ <span class="controlled-setting-indicator"
+ content-setting="plugins" value="block">
+ </span>
+ </span>
+ </span>
+ </div>
+ <div class="settings-row">
+ <button class="exceptions-list-button" contentType="plugins"
+ i18n-content="manageExceptions"></button>
+ </div>
+ <div id="disable-plugins-container">
+ <a href="chrome://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">
+ <span class="controlled-setting-with-label">
+ <input id="popups-allow" type="radio" name="popups" value="allow">
+ <span>
+ <label for="popups-allow" i18n-content="popups_allow"></label>
+ <span class="controlled-setting-indicator"
+ content-setting="popups" value="allow">
+ </span>
+ </span>
+ </span>
+ </div>
+ <div class="radio">
+ <span class="controlled-setting-with-label">
+ <input id="popups-block" type="radio" name="popups" value="block">
+ <span>
+ <label for="popups-block" i18n-content="popups_block"></label>
+ <span class="controlled-setting-indicator"
+ content-setting="popups" value="block">
+ </span>
+ </span>
+ </span>
+ </div>
+ <div class="settings-row">
+ <button class="exceptions-list-button" contentType="popups"
+ i18n-content="manageExceptions"></button>
+ </div>
+ </div>
+ </section>
+ <!-- Location filter -->
+ <section>
+ <h3 i18n-content="location_tab_label"></h3>
+ <div>
+ <div class="radio">
+ <span class="controlled-setting-with-label">
+ <input id="location-allow" type="radio" name="location"
+ value="allow">
+ <span>
+ <label for="location-allow" i18n-content="location_allow"></label>
+ <span class="controlled-setting-indicator"
+ content-setting="location" value="allow">
+ </span>
+ </span>
+ </span>
+ </div>
+ <div class="radio">
+ <span class="controlled-setting-with-label">
+ <input id="location-ask" type="radio" name="location" value="ask">
+ <span>
+ <label for="location-ask" i18n-content="location_ask"></label>
+ <span class="controlled-setting-indicator"
+ content-setting="location" value="ask">
+ </span>
+ </span>
+ </span>
+ </div>
+ <div class="radio">
+ <span class="controlled-setting-with-label">
+ <input id="location-block" type="radio" name="location"
+ value="block">
+ <span>
+ <label for="location-block" i18n-content="location_block"></label>
+ <span class="controlled-setting-indicator"
+ content-setting="location" value="block">
+ </span>
+ </span>
+ </span>
+ </div>
+<if expr="pp_ifdef('enable_google_now')">
+ <div class="checkbox" id="geolocationCheckbox" hidden>
+ <span class="controlled-setting-with-label">
+ <input id="googleGeolocationAccessEnabled"
+ pref="googlegeolocationaccess.enabled"
+ metric="Options_GoogleGeolocationAccessCheckbox"
+ type="checkbox">
+ <span>
+ <label for="googleGeolocationAccessEnabled"
+ i18n-content="googleGeolocationAccessEnable">
+ </label>
+ <span class="controlled-setting-indicator"
+ pref="googlegeolocationaccess.enabled">
+ </span>
+ </span>
+ </span>
+ </div>
+</if>
+ <div class="settings-row">
+ <button class="exceptions-list-button" contentType="location"
+ i18n-content="manageExceptions"></button>
+ </div>
+ </div>
+ </section>
+ <!-- Notifications filter tab contents -->
+ <section id="notifications-section">
+ <h3 i18n-content="notifications_tab_label"></h3>
+ <div>
+ <div class="radio">
+ <span class="controlled-setting-with-label">
+ <input id="notifications-allow" type="radio" name="notifications"
+ value="allow">
+ <span>
+ <label for="notifications-allow"
+ i18n-content="notifications_allow">
+ </label>
+ <span class="controlled-setting-indicator"
+ content-setting="notifications" value="allow">
+ </span>
+ </span>
+ </span>
+ </div>
+ <div class="radio">
+ <span class="controlled-setting-with-label">
+ <input id="notifications-ask" type="radio" name="notifications"
+ value="ask">
+ <span>
+ <label for="notifications-ask" i18n-content="notifications_ask">
+ </label>
+ <span class="controlled-setting-indicator"
+ content-setting="notifications" value="ask">
+ </span>
+ </span>
+ </span>
+ </div>
+ <div class="radio">
+ <span class="controlled-setting-with-label">
+ <input id="notifications-block" type="radio" name="notifications"
+ value="block">
+ <span>
+ <label for="notifications-block"
+ i18n-content="notifications_block">
+ </label>
+ <span class="controlled-setting-indicator"
+ content-setting="notifications" value="block">
+ </span>
+ </span>
+ </span>
+ </div>
+ <div class="settings-row">
+ <button class="exceptions-list-button" contentType="notifications"
+ i18n-content="manageExceptions"></button>
+ </div>
+ </div>
+ </section>
+ <!-- Fullscreen filter -->
+ <section>
+ <h3 i18n-content="fullscreen_tab_label"></h3>
+ <div class="settings-row">
+ <button class="exceptions-list-button" contentType="fullscreen"
+ i18n-content="manageExceptions"></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>
+ <div class="settings-row">
+ <button class="exceptions-list-button" contentType="mouselock"
+ i18n-content="manageExceptions"></button>
+ </div>
+ </div>
+ </section>
+<if expr="pp_ifdef('chromeos') or is_win">
+ <!-- Protected Content filter -->
+ <section guest-visibility="disabled">
+ <h3 i18n-content="protectedContentTabLabel"
+ class="content-settings-header"></h3>
+ <div>
+ <div class="settings-row">
+ <p i18n-content="protectedContentInfo"></p>
+ </div>
+ <div class="checkbox">
+ <label>
+ <input pref="settings.privacy.drm_enabled" type="checkbox">
+ <span i18n-content="protectedContentEnable"></span>
+ </label>
+ </div>
+ <div class="settings-row">
+ <button id="protected-content-exceptions"
+ class="exceptions-list-button" contentType="protectedContent"
+ i18n-content="manageExceptions"></button>
+ </div>
+ </div>
+ </section>
+</if>
+ <!-- Media Stream capture device filter -->
+ <section>
+ <div class="section-header">
+ <h3 i18n-content="mediaStreamTabLabel"></h3>
+ <span id="media-indicator"
+ class="controlled-setting-indicator group-indicator"></span>
+ </div>
+ <div>
+ <div class="media-device-control">
+ <span i18n-content="mediaSelectMicLabel"></span>
+ <select id="media-select-mic" class="weakrtl"></select>
+ </div>
+ <div class="media-device-control">
+ <span i18n-content="mediaSelectCameraLabel"></span>
+ <select id="media-select-camera" class="weakrtl"></select>
+ </div>
+ <div class="radio">
+ <span class="controlled-setting-with-label">
+ <input id="media-stream-ask" type="radio" name="media-stream"
+ value="ask">
+ <span>
+ <label id="media-stream-ask-label" for="media-stream-ask"
+ i18n-content="mediaStreamAsk">
+ </label>
+ <span class="controlled-setting-indicator"
+ content-setting="media-stream" value="ask">
+ </span>
+ </span>
+ </span>
+ </div>
+ <div class="radio">
+ <span class="controlled-setting-with-label">
+ <input id="media-stream-block" type="radio" name="media-stream"
+ value="block">
+ <span>
+ <label id="media-stream-block-label" for="media-stream-block"
+ i18n-content="mediaStreamBlock">
+ </label>
+ <span class="controlled-setting-indicator"
+ content-setting="media-stream" value="block">
+ </span>
+ </span>
+ </span>
+ </div>
+ <div class="settings-row">
+ <button class="exceptions-list-button" contentType="media-stream"
+ i18n-content="manageExceptions"></button>
+ </div>
+ <div id="media-pepper-flash-default" class="pepper-flash-settings">
+ <span i18n-content="mediaPepperFlashDefaultDivergedLabel"></span>
+ <a target="_blank" i18n-content="mediaPepperFlashChangeLink"
+ i18n-values="href:mediaPepperFlashGlobalPrivacyURL"></a>
+ </div>
+ </div>
+ </section>
+ <!-- PPAPI broker -->
+ <section>
+ <h3 i18n-content="ppapiBrokerTabLabel"></h3>
+ <div>
+ <div class="radio">
+ <label>
+ <input type="radio" name="ppapi-broker" value="allow">
+ <span i18n-content="ppapi_broker_allow"></span>
+ </label>
+ </div>
+ <div class="radio">
+ <label>
+ <input type="radio" name="ppapi-broker" value="ask">
+ <span i18n-content="ppapi_broker_ask"></span>
+ </label>
+ </div>
+ <div class="radio">
+ <label>
+ <input type="radio" name="ppapi-broker" value="block">
+ <span i18n-content="ppapi_broker_block"></span>
+ </label>
+ </div>
+ <div class="settings-row">
+ <button class="exceptions-list-button" contentType="ppapi-broker"
+ i18n-content="manageExceptions"></button>
+ </div>
+ </div>
+ </section>
+ <section id="media-galleries-section" hidden>
+ <h3 i18n-content="mediaGalleriesSectionLabel"></h3>
+ <div class="settings-row">
+ <button id="manage-galleries-button"
+ i18n-content="manageGalleriesButton"></button>
+ </div>
+ </section>
+ <!-- Automatic Downloads filter -->
+ <section>
+ <h3 i18n-content="multiple-automatic-downloads_header"></h3>
+ <div>
+ <div class="radio">
+ <span class="controlled-setting-with-label">
+ <input id="multiple-automatic-downloads_allow" type="radio"
+ name="multiple-automatic-downloads" value="allow">
+ <span>
+ <label for="multiple-automatic-downloads_allow"
+ i18n-content="multiple-automatic-downloads_allow">
+ </label>
+ <span class="controlled-setting-indicator"
+ content-setting="multiple-automatic-downloads" value="allow">
+ </span>
+ </span>
+ </span>
+ </div>
+ <div class="radio">
+ <span class="controlled-setting-with-label">
+ <input id="multiple-automatic-downloads_ask" type="radio"
+ name="multiple-automatic-downloads" value="ask">
+ <span>
+ <label for="multiple-automatic-downloads_ask"
+ i18n-content="multiple-automatic-downloads_ask">
+ </label>
+ <span class="controlled-setting-indicator"
+ content-setting="multiple-automatic-downloads" value="ask">
+ </span>
+ </span>
+ </span>
+ </div>
+ <div class="radio">
+ <span class="controlled-setting-with-label">
+ <input id="multiple-automatic-downloads_block" type="radio"
+ name="multiple-automatic-downloads" value="block">
+ <span>
+ <label for="multiple-automatic-downloads_block"
+ i18n-content="multiple-automatic-downloads_block">
+ </label>
+ <span class="controlled-setting-indicator"
+ content-setting="multiple-automatic-downloads" value="block">
+ </span>
+ </span>
+ </span>
+ </div>
+ <div class="settings-row">
+ <button class="exceptions-list-button"
+ contentType="multiple-automatic-downloads"
+ i18n-content="manageExceptions"></button>
+ </div>
+ </div>
+ </section>
+ <!-- MIDI system exclusive messages filter -->
+ <section id="experimental-web-midi-settings" hidden="true">
+ <h3 i18n-content="midi-sysex_header"></h3>
+ <div>
+ <div class="radio">
+ <label>
+ <input type="radio" name="midi-sysex" value="allow">
+ <span i18n-content="midiSysExAllow"></span>
+ </label>
+ </div>
+ <div class="radio">
+ <label>
+ <input type="radio" name="midi-sysex" value="ask">
+ <span i18n-content="midiSysExAsk"></span>
+ </label>
+ </div>
+ <div class="radio">
+ <label>
+ <input type="radio" name="midi-sysex" value="block">
+ <span i18n-content="midiSysExBlock"></span>
+ </label>
+ </div>
+ <div class="settings-row">
+ <button class="exceptions-list-button" contentType="midi-sysex"
+ i18n-content="manageExceptions"></button>
+ </div>
+ </div>
+ </section>
+ </div>
+ <div class="action-area">
+ <div class="button-strip">
+ <button id="content-settings-overlay-confirm" class="default-button"
+ i18n-content="done">
+ </button>
+ </div>
+ </div>
+</div>
diff --git a/chromium/chrome/browser/resources/options/content_settings.js b/chromium/chrome/browser/resources/options/content_settings.js
new file mode 100644
index 00000000000..160d36f6d79
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/content_settings.js
@@ -0,0 +1,314 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+if (!loadTimeData.getBoolean('newContentSettings')) {
+
+cr.define('options', function() {
+ /** @const */ var OptionsPage = options.OptionsPage;
+
+ //////////////////////////////////////////////////////////////////////////////
+ // ContentSettings class:
+
+ /**
+ * Encapsulated handling of content settings page.
+ * @constructor
+ */
+ function ContentSettings() {
+ this.activeNavTab = null;
+ OptionsPage.call(this, 'content',
+ loadTimeData.getString('contentSettingsPageTabTitle'),
+ 'content-settings-page');
+ }
+
+ cr.addSingletonGetter(ContentSettings);
+
+ ContentSettings.prototype = {
+ __proto__: OptionsPage.prototype,
+
+ initializePage: function() {
+ OptionsPage.prototype.initializePage.call(this);
+
+ 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();
+
+ // 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.currentTarget.getAttribute('contentType');
+ var url = page.name + '#' + hash;
+ window.history.pushState({pageName: page.name},
+ page.title,
+ '/' + url);
+
+ // Navigate after the history has been replaced in order to have the
+ // correct hash loaded.
+ OptionsPage.showPageByName('contentExceptions', false);
+
+ uber.invokeMethodOnParent('setPath', {path: url});
+ uber.invokeMethodOnParent('setTitle',
+ {title: loadTimeData.getString(hash + 'TabTitle')});
+ };
+ }
+
+ var manageHandlersButton = $('manage-handlers-button');
+ if (manageHandlersButton) {
+ manageHandlersButton.onclick = function(event) {
+ OptionsPage.navigateToPage('handlers');
+ };
+ }
+
+ $('manage-galleries-button').onclick = function(event) {
+ OptionsPage.navigateToPage('manageGalleries');
+ };
+
+ if (cr.isChromeOS)
+ UIAccountTweaks.applyGuestModeVisibility(document);
+
+ // Cookies filter page ---------------------------------------------------
+ $('show-cookies-button').onclick = function(event) {
+ chrome.send('coreOptionsUserMetricsAction', ['Options_ShowCookies']);
+ OptionsPage.navigateToPage('cookies');
+ };
+
+ $('content-settings-overlay-confirm').onclick =
+ OptionsPage.closeOverlay.bind(OptionsPage);
+
+ $('media-pepper-flash-default').hidden = true;
+ $('media-pepper-flash-exceptions').hidden = true;
+
+ $('media-select-mic').addEventListener('change',
+ ContentSettings.setDefaultMicrophone_);
+ $('media-select-camera').addEventListener('change',
+ ContentSettings.setDefaultCamera_);
+ },
+ };
+
+ 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) {
+ var managedBy = dict[group].managedBy;
+ var controlledBy = managedBy == 'policy' || managedBy == 'extension' ?
+ managedBy : null;
+ document.querySelector('input[type=radio][name=' + group + '][value=' +
+ dict[group].value + ']').checked = true;
+ var radios = document.querySelectorAll('input[type=radio][name=' +
+ group + ']');
+ for (var i = 0, len = radios.length; i < len; i++) {
+ radios[i].disabled = (managedBy != 'default');
+ radios[i].controlledBy = controlledBy;
+ }
+ var indicators = document.querySelectorAll(
+ 'span.controlled-setting-indicator[content-setting=' + group + ']');
+ if (indicators.length == 0)
+ continue;
+ // Create a synthetic pref change event decorated as
+ // CoreOptionsHandler::CreateValueForPref() does.
+ var event = new Event(group);
+ event.value = {
+ value: dict[group].value,
+ controlledBy: controlledBy,
+ };
+ for (var i = 0; i < indicators.length; i++)
+ indicators[i].handlePrefChange(event);
+ }
+ };
+
+ /**
+ * Updates the labels and indicators for the Media settings. Those require
+ * special handling because they are backed by multiple prefs and can change
+ * their scope based on the managed state of the backing prefs.
+ * @param {Object} mediaSettings A dictionary containing the following fields:
+ * {String} askText The label for the ask radio button.
+ * {String} blockText The label for the block radio button.
+ * {Boolean} cameraDisabled Whether to disable the camera dropdown.
+ * {Boolean} micDisabled Whether to disable the microphone dropdown.
+ * {Boolean} showBubble Wether to show the managed icon and bubble for the
+ * media label.
+ * {String} bubbleText The text to use inside the bubble if it is shown.
+ */
+ ContentSettings.updateMediaUI = function(mediaSettings) {
+ $('media-stream-ask-label').innerHTML =
+ loadTimeData.getString(mediaSettings.askText);
+ $('media-stream-block-label').innerHTML =
+ loadTimeData.getString(mediaSettings.blockText);
+
+ if (mediaSettings.micDisabled)
+ $('media-select-mic').disabled = true;
+ if (mediaSettings.cameraDisabled)
+ $('media-select-camera').disabled = true;
+
+ OptionsPage.hideBubble();
+ // Create a synthetic pref change event decorated as
+ // CoreOptionsHandler::CreateValueForPref() does.
+ // TODO(arv): It was not clear what event type this should use?
+ var event = new Event('undefined');
+ event.value = {};
+
+ if (mediaSettings.showBubble) {
+ event.value = { controlledBy: 'policy' };
+ $('media-indicator').setAttribute(
+ 'textpolicy', loadTimeData.getString(mediaSettings.bubbleText));
+ $('media-indicator').location = cr.ui.ArrowLocation.TOP_START;
+ }
+
+ $('media-indicator').handlePrefChange(event);
+ };
+
+ /**
+ * 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);
+ };
+
+ /**
+ * Shows/hides the link to the Pepper Flash camera and microphone default
+ * settings.
+ * Please note that whether the link is actually showed or not is also
+ * affected by the style class pepper-flash-settings.
+ */
+ ContentSettings.showMediaPepperFlashDefaultLink = function(show) {
+ $('media-pepper-flash-default').hidden = !show;
+ }
+
+ /**
+ * Shows/hides the link to the Pepper Flash camera and microphone
+ * site-specific settings.
+ * Please note that whether the link is actually showed or not is also
+ * affected by the style class pepper-flash-settings.
+ */
+ ContentSettings.showMediaPepperFlashExceptionsLink = function(show) {
+ $('media-pepper-flash-exceptions').hidden = !show;
+ }
+
+ /**
+ * Shows/hides the whole Web MIDI settings.
+ * @param {bool} show Wether to show the whole Web MIDI settings.
+ */
+ ContentSettings.showExperimentalWebMIDISettings = function(show) {
+ $('experimental-web-midi-settings').hidden = !show;
+ }
+
+ /**
+ * Updates the microphone/camera devices menu with the given entries.
+ * @param {string} type The device type.
+ * @param {Array} devices List of available devices.
+ * @param {string} defaultdevice The unique id of the current default device.
+ */
+ ContentSettings.updateDevicesMenu = function(type, devices, defaultdevice) {
+ var deviceSelect = '';
+ if (type == 'mic') {
+ deviceSelect = $('media-select-mic');
+ } else if (type == 'camera') {
+ deviceSelect = $('media-select-camera');
+ } else {
+ console.error('Unknown device type for <device select> UI element: ' +
+ type);
+ return;
+ }
+
+ deviceSelect.textContent = '';
+
+ var deviceCount = devices.length;
+ var defaultIndex = -1;
+ for (var i = 0; i < deviceCount; i++) {
+ var device = devices[i];
+ var option = new Option(device.name, device.id);
+ if (option.value == defaultdevice)
+ defaultIndex = i;
+ deviceSelect.appendChild(option);
+ }
+ if (defaultIndex >= 0)
+ deviceSelect.selectedIndex = defaultIndex;
+ };
+
+ /**
+ * Enables/disables the protected content exceptions button.
+ * @param {bool} enable Whether to enable the button.
+ */
+ ContentSettings.enableProtectedContentExceptions = function(enable) {
+ var exceptionsButton = $('protected-content-exceptions');
+ if (exceptionsButton) {
+ exceptionsButton.disabled = !enable;
+ }
+ }
+
+ /**
+ * Set the default microphone device based on the popup selection.
+ * @private
+ */
+ ContentSettings.setDefaultMicrophone_ = function() {
+ var deviceSelect = $('media-select-mic');
+ chrome.send('setDefaultCaptureDevice', ['mic', deviceSelect.value]);
+ };
+
+ /**
+ * Set the default camera device based on the popup selection.
+ * @private
+ */
+ ContentSettings.setDefaultCamera_ = function() {
+ var deviceSelect = $('media-select-camera');
+ chrome.send('setDefaultCaptureDevice', ['camera', deviceSelect.value]);
+ };
+
+ // Export
+ return {
+ ContentSettings: ContentSettings
+ };
+
+});
+
+}
diff --git a/chromium/chrome/browser/resources/options/content_settings2.html b/chromium/chrome/browser/resources/options/content_settings2.html
new file mode 100644
index 00000000000..e886bfdeb78
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/content_settings2.html
@@ -0,0 +1,13 @@
+<div id="content-settings-page2" class="page" hidden>
+ <div class="close-button"></div>
+ <h1 i18n-content="contentSettingsPage"></h1>
+ <div class="content-area">
+ This space intentionally left blank.
+ </div>
+ <div class="action-area">
+ <div class="button-strip">
+ <button id="content-settings-overlay-confirm2" i18n-content="done">
+ </button>
+ </div>
+ </div>
+</div>
diff --git a/chromium/chrome/browser/resources/options/content_settings2.js b/chromium/chrome/browser/resources/options/content_settings2.js
new file mode 100644
index 00000000000..7c6d95137e3
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/content_settings2.js
@@ -0,0 +1,88 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+if (loadTimeData.getBoolean('newContentSettings')) {
+
+cr.define('options', function() {
+ /** @const */ var OptionsPage = options.OptionsPage;
+
+ //////////////////////////////////////////////////////////////////////////////
+ // ContentSettings class:
+
+ /**
+ * Encapsulated handling of content settings page.
+ * @constructor
+ */
+ function ContentSettings() {
+ this.activeNavTab = null;
+ OptionsPage.call(this, 'content',
+ loadTimeData.getString('contentSettingsPageTabTitle'),
+ 'content-settings-page2');
+ }
+
+ cr.addSingletonGetter(ContentSettings);
+
+ ContentSettings.prototype = {
+ __proto__: OptionsPage.prototype,
+
+ initializePage: function() {
+ OptionsPage.prototype.initializePage.call(this);
+
+ $('content-settings-overlay-confirm2').onclick =
+ OptionsPage.closeOverlay.bind(OptionsPage);
+ },
+ };
+
+ ContentSettings.updateHandlersEnabledRadios = function(enabled) {
+ // Not implemented.
+ };
+
+ /**
+ * 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) {
+ // Not implemented.
+ };
+
+ /**
+ * 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) {
+ // Not implemented.
+ };
+
+ ContentSettings.setHandlers = function(list) {
+ // Not implemented.
+ };
+
+ ContentSettings.setIgnoredHandlers = function(list) {
+ // Not implemented.
+ };
+
+ ContentSettings.setOTRExceptions = function(type, list) {
+ // Not implemented.
+ };
+
+ /**
+ * Enables the Pepper Flash camera and microphone settings.
+ * Please note that whether the settings are actually showed or not is also
+ * affected by the style class pepper-flash-settings.
+ */
+ ContentSettings.enablePepperFlashCameraMicSettings = function() {
+ // Not implemented.
+ }
+
+ // Export
+ return {
+ ContentSettings: ContentSettings
+ };
+
+});
+
+}
diff --git a/chromium/chrome/browser/resources/options/content_settings_exceptions_area.html b/chromium/chrome/browser/resources/options/content_settings_exceptions_area.html
new file mode 100644
index 00000000000..1306379022b
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/content_settings_exceptions_area.html
@@ -0,0 +1,140 @@
+<div id="content-settings-exceptions-area" class="page" hidden>
+ <div class="close-button"></div>
+ <h1></h1>
+ <div class="content-area">
+ <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" target="_blank"
+ i18n-content="flash_storage_settings">
+ </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="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>
+ <span class="otr-explanation" i18n-content="otr_exceptions_explanation">
+ </span>
+ <list mode="otr"></list>
+ </div>
+ </div>
+ <div contentType="protectedContent">
+ <list mode="normal"></list>
+ <div>
+ <span class="otr-explanation" i18n-content="otr_exceptions_explanation">
+ </span>
+ <list mode="otr"></list>
+ </div>
+ </div>
+ <div id="media-column-header" class="media-header">
+ <div id="exception-pattern-column"></div>
+ <div id="media-audio-column" i18n-content=mediaAudioExceptionHeader>
+ </div>
+ <div id="media-video-column" i18n-content="mediaVideoExceptionHeader">
+ </div>
+ </div>
+ <div contentType="media-stream">
+ <list mode="normal"></list>
+ <div>
+ <span class="otr-explanation" i18n-content="otr_exceptions_explanation">
+ </span>
+ <list mode="otr"></list>
+ </div>
+ <div id="media-pepper-flash-exceptions" class="pepper-flash-settings">
+ <span i18n-content="mediaPepperFlashExceptionsDivergedLabel"></span>
+ <a target="_blank" i18n-content="mediaPepperFlashChangeLink"
+ i18n-values="href:mediaPepperFlashWebsitePrivacyURL"></a>
+ </div>
+ </div>
+ <div contentType="ppapi-broker">
+ <list mode="normal"></list>
+ <div>
+ <span class="otr-explanation" i18n-content="otr_exceptions_explanation">
+ </span>
+ <list mode="otr"></list>
+ </div>
+ </div>
+ <div contentType="multiple-automatic-downloads">
+ <list mode="normal"></list>
+ </div>
+ <div contentType="midi-sysex">
+ <list mode="normal"></list>
+ <div>
+ <span class="otr-explanation" i18n-content="otr_exceptions_explanation">
+ </span>
+ <list mode="otr"></list>
+ </div>
+ </div>
+ </div>
+ <div class="action-area">
+ <div class="hbox stretch">
+ <a target="_blank" i18n-content="learnMore"
+ i18n-values="href:exceptionsLearnMoreUrl"></a>
+ </div>
+ <div class="action-area-right">
+ <div class="button-strip">
+ <button id="content-settings-exceptions-overlay-confirm"
+ class="default-button" i18n-content="done">
+ </button>
+ </div>
+ </div>
+ </div>
+</div>
diff --git a/chromium/chrome/browser/resources/options/content_settings_exceptions_area.js b/chromium/chrome/browser/resources/options/content_settings_exceptions_area.js
new file mode 100644
index 00000000000..31c6a5ed07e
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/content_settings_exceptions_area.js
@@ -0,0 +1,670 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options.contentSettings', function() {
+ /** @const */ var ControlledSettingIndicator =
+ options.ControlledSettingIndicator;
+ /** @const */ var InlineEditableItemList = options.InlineEditableItemList;
+ /** @const */ var InlineEditableItem = options.InlineEditableItem;
+ /** @const */ var 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 = loadTimeData.getString('allowException');
+ optionAllow.value = 'allow';
+ select.appendChild(optionAllow);
+
+ if (this.enableAskOption) {
+ var optionAsk = cr.doc.createElement('option');
+ optionAsk.textContent = loadTimeData.getString('askException');
+ optionAsk.value = 'ask';
+ select.appendChild(optionAsk);
+ }
+
+ if (this.contentType == 'cookies') {
+ var optionSession = cr.doc.createElement('option');
+ optionSession.textContent = loadTimeData.getString('sessionException');
+ optionSession.value = 'session';
+ select.appendChild(optionSession);
+ }
+
+ if (this.contentType != 'fullscreen') {
+ var optionBlock = cr.doc.createElement('option');
+ optionBlock.textContent = loadTimeData.getString('blockException');
+ optionBlock.value = 'block';
+ select.appendChild(optionBlock);
+ }
+
+ if (this.isEmbeddingRule()) {
+ this.patternLabel.classList.add('sublabel');
+ this.editable = false;
+ }
+
+ if (this.setting == 'default') {
+ // Items that don't have their own settings (parents of 'embedded on'
+ // items) aren't deletable.
+ this.deletable = false;
+ this.editable = false;
+ }
+
+ this.addEditField(select, this.settingLabel);
+ this.contentElement.appendChild(select);
+ select.className = 'exception-setting';
+ select.setAttribute('aria-labelledby', 'exception-behavior-column');
+
+ if (this.pattern)
+ select.setAttribute('displaymode', 'edit');
+
+ if (this.contentType == 'media-stream') {
+ this.settingLabel.classList.add('media-audio-setting');
+
+ var videoSettingLabel = cr.doc.createElement('span');
+ videoSettingLabel.textContent = this.videoSettingForDisplay();
+ videoSettingLabel.className = 'exception-setting';
+ videoSettingLabel.classList.add('media-video-setting');
+ videoSettingLabel.setAttribute('displaymode', 'static');
+ this.contentElement.appendChild(videoSettingLabel);
+ }
+
+ // 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, geolocation and media-stream is disabled for
+ // now.
+ if (this.contentType == 'notifications' ||
+ this.contentType == 'location' ||
+ this.contentType == 'media-stream') {
+ this.editable = false;
+ }
+
+ // If the source of the content setting exception is not a user
+ // preference, that source controls the exception and the user cannot edit
+ // or delete it.
+ var controlledBy =
+ this.dataItem.source && this.dataItem.source != 'preference' ?
+ this.dataItem.source : null;
+
+ if (controlledBy) {
+ this.setAttribute('controlled-by', controlledBy);
+ this.deletable = false;
+ this.editable = false;
+ }
+
+ if (controlledBy == 'policy' || controlledBy == 'extension') {
+ this.querySelector('.row-delete-button').hidden = true;
+ var indicator = ControlledSettingIndicator();
+ indicator.setAttribute('content-exception', this.contentType);
+ // Create a synthetic pref change event decorated as
+ // CoreOptionsHandler::CreateValueForPref() does.
+ var event = new Event(this.contentType);
+ event.value = { controlledBy: controlledBy };
+ indicator.handlePrefChange(event);
+ this.appendChild(indicator);
+ }
+
+ // If the exception comes from a hosted app, display the name and the
+ // icon of the app.
+ if (controlledBy == 'HostedApp') {
+ this.title =
+ loadTimeData.getString('set_by') + ' ' + this.dataItem.appName;
+ var button = this.querySelector('.row-delete-button');
+ // Use the host app's favicon (16px, match bigger size).
+ // See c/b/ui/webui/extensions/extension_icon_source.h
+ // for a description of the chrome://extension-icon URL.
+ button.style.backgroundImage =
+ 'url(\'chrome://extension-icon/' + this.dataItem.appId + '/16/1\')';
+ }
+
+ 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_);
+ },
+
+ isEmbeddingRule: function() {
+ return this.dataItem.embeddingOrigin &&
+ this.dataItem.embeddingOrigin !== this.dataItem.origin;
+ },
+
+ /**
+ * The pattern (e.g., a URL) for the exception.
+ *
+ * @type {string}
+ */
+ get pattern() {
+ if (!this.isEmbeddingRule()) {
+ return this.dataItem.origin;
+ } else {
+ return loadTimeData.getStringF('embeddedOnHost',
+ this.dataItem.embeddingOrigin);
+ }
+
+ return this.dataItem.displayPattern;
+ },
+ set pattern(pattern) {
+ if (!this.editable)
+ console.error('Tried to change uneditable 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.
+ *
+ * @return {string} The display string.
+ */
+ settingForDisplay: function() {
+ return this.getDisplayStringForSetting(this.setting);
+ },
+
+ /**
+ * media video specific function.
+ * Gets a human-readable video setting string.
+ *
+ * @return {string} The display string.
+ */
+ videoSettingForDisplay: function() {
+ return this.getDisplayStringForSetting(this.dataItem.video);
+ },
+
+ /**
+ * Gets a human-readable display string for setting.
+ *
+ * @param {string} setting The setting to be displayed.
+ * @return {string} The display string.
+ */
+ getDisplayStringForSetting: function(setting) {
+ if (setting == 'allow')
+ return loadTimeData.getString('allowException');
+ else if (setting == 'block')
+ return loadTimeData.getString('blockException');
+ else if (setting == 'ask')
+ return loadTimeData.getString('askException');
+ else if (setting == 'session')
+ return loadTimeData.getString('sessionException');
+ else if (setting == 'default')
+ return '';
+
+ console.error('Unknown setting: [' + setting + ']');
+ return '';
+ },
+
+ /**
+ * 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;
+ },
+
+ /** @override */
+ get currentInputIsValid() {
+ return this.inputValidityKnown && this.inputIsValid;
+ },
+
+ /** @override */
+ 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 =
+ loadTimeData.getString('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 = '';
+ },
+
+ /** @override */
+ 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');
+
+ // Whether the exceptions in this list allow an 'Ask every time' option.
+ this.enableAskOption = this.contentType == 'plugins';
+
+ 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() {
+ // Exceptions of the following lists are not editable for now.
+ return !(this.contentType == 'notifications' ||
+ this.contentType == 'location' ||
+ this.contentType == 'fullscreen' ||
+ this.contentType == 'media-stream');
+ },
+
+ /**
+ * 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([]);
+ }
+ },
+
+ /** @override */
+ deleteItemAtIndex: function(index) {
+ var listItem = this.getListItemByIndex(index);
+ if (!listItem.deletable)
+ return;
+
+ var dataItem = listItem.dataItem;
+ var args = [listItem.contentType];
+ if (listItem.contentType == 'notifications')
+ args.push(dataItem.origin, dataItem.setting);
+ else
+ args.push(listItem.mode, dataItem.origin, dataItem.embeddingOrigin);
+
+ chrome.send('removeException', args);
+ },
+ };
+
+ var OptionsPage = options.OptionsPage;
+
+ /**
+ * Encapsulated handling of content settings list subpage.
+ *
+ * @constructor
+ */
+ function ContentSettingsExceptionsArea() {
+ OptionsPage.call(this, 'contentExceptions',
+ loadTimeData.getString('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(false);
+
+ // If the user types in the URL without a hash, show just cookies.
+ this.showList('cookies');
+
+ $('content-settings-exceptions-overlay-confirm').onclick =
+ OptionsPage.closeOverlay.bind(OptionsPage);
+ },
+
+ /**
+ * Shows one list and hides all others.
+ *
+ * @param {string} type The content type.
+ */
+ showList: function(type) {
+ var header = this.pageDiv.querySelector('h1');
+ header.textContent = loadTimeData.getString(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;
+ }
+
+ var mediaHeader = this.pageDiv.querySelector('.media-header');
+ mediaHeader.hidden = type != 'media-stream';
+ },
+
+ /**
+ * 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(true);
+ };
+
+ /**
+ * Hides the incognito exceptions lists and optionally clears them as well.
+ * @param {boolean} clear Whether to clear the lists.
+ */
+ ContentSettingsExceptionsArea.hideOTRLists = function(clear) {
+ var otrLists = document.querySelectorAll('list[mode=otr]');
+
+ for (var i = 0; i < otrLists.length; i++) {
+ otrLists[i].parentNode.hidden = true;
+ if (clear)
+ otrLists[i].reset();
+ }
+ };
+
+ return {
+ ExceptionsListItem: ExceptionsListItem,
+ ExceptionsAddRowListItem: ExceptionsAddRowListItem,
+ ExceptionsList: ExceptionsList,
+ ContentSettingsExceptionsArea: ContentSettingsExceptionsArea,
+ };
+});
diff --git a/chromium/chrome/browser/resources/options/content_settings_ui.js b/chromium/chrome/browser/resources/options/content_settings_ui.js
new file mode 100644
index 00000000000..dc7b0c496a2
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/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/chromium/chrome/browser/resources/options/controlled_setting.css b/chromium/chrome/browser/resources/options/controlled_setting.css
new file mode 100644
index 00000000000..089e27dbbd7
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/controlled_setting.css
@@ -0,0 +1,127 @@
+/* Copyright 2013 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. */
+
+/* Controlled setting indicator and bubble. */
+
+.controlled-setting-with-label {
+ -webkit-box-align: center;
+ display: -webkit-box;
+ padding-bottom: 7px;
+ padding-top: 7px;
+}
+
+.controlled-setting-with-label > input + span {
+ -webkit-box-align: center;
+ -webkit-box-flex: 1;
+ -webkit-margin-start: 0.6em;
+ display: -webkit-box;
+}
+
+.controlled-setting-with-label > input:disabled + span label {
+ color: #999;
+}
+
+.controlled-setting-with-label label {
+ display: inline;
+ padding: 0;
+}
+
+input:-webkit-any([type='text'],[type='url'],:not([type])) +
+ .controlled-setting-indicator {
+ -webkit-margin-start: 5px;
+}
+
+.controlled-setting-indicator:not([controlled-by]) {
+ display: none;
+}
+
+.controlled-setting-indicator[controlled-by='policy'] > div {
+ background-image: url('chrome://theme/IDR_CONTROLLED_SETTING_MANDATORY');
+}
+
+.controlled-setting-indicator[controlled-by='owner'] > div {
+ background-image: url('chrome://theme/IDR_CONTROLLED_SETTING_MANDATORY');
+}
+
+.controlled-setting-indicator[controlled-by='extension'] > div {
+ background-image: url('chrome://theme/IDR_CONTROLLED_SETTING_EXTENSION');
+}
+
+.controlled-setting-indicator:-webkit-any([controlled-by='recommended'],
+ [controlled-by='hasRecommendation']) > div {
+ background-image: url('chrome://theme/IDR_CONTROLLED_SETTING_MANDATORY');
+}
+
+.controlled-setting-bubble-content {
+ -webkit-padding-start: 30px;
+ background-repeat: no-repeat;
+ background-size: 22px;
+ min-height: 32px;
+}
+
+.controlled-setting-bubble-content[controlled-by='policy'] {
+ background-image: url('chrome://theme/IDR_CONTROLLED_SETTING_MANDATORY');
+}
+
+.controlled-setting-bubble-content[controlled-by='owner'] {
+ background-image: url('chrome://theme/IDR_CONTROLLED_SETTING_MANDATORY');
+}
+
+.controlled-setting-bubble-content[controlled-by='extension'] {
+ background-image: url('chrome://theme/IDR_CONTROLLED_SETTING_EXTENSION');
+}
+
+.controlled-setting-bubble-content:-webkit-any([controlled-by='recommended'],
+ [controlled-by='hasRecommendation']) {
+ background-image: url('chrome://theme/IDR_CONTROLLED_SETTING_MANDATORY');
+}
+
+html[dir='rtl'] .controlled-setting-bubble-content {
+ background-position: right top;
+}
+
+.controlled-setting-bubble-action {
+ padding: 0 !important;
+}
+
+.controlled-setting-bubble-content-row {
+ height: 35px;
+ position: relative;
+}
+
+.controlled-setting-bubble-extension-name {
+ -webkit-padding-start: 30px;
+ background-repeat: no-repeat;
+ font-weight: bold;
+ height: 24px;
+ margin-top: -12px;
+ overflow: hidden;
+ padding-top: 3px;
+ position: absolute;
+ text-overflow: ellipsis;
+ top: 50%;
+ white-space: nowrap;
+ width: 215px;
+}
+
+html[dir='rtl'] .controlled-setting-bubble-extension-name {
+ background-position: right top;
+}
+
+.controlled-setting-bubble-extension-manage-link {
+ margin-top: -0.5em;
+ position: absolute;
+ top: 50%;
+}
+
+.controlled-setting-bubble-extension-disable-button {
+ bottom: 0;
+ position: absolute;
+ right: 0;
+}
+
+html[dir='rtl'] .controlled-setting-bubble-extension-disable-button {
+ left: 0;
+ right: auto;
+}
diff --git a/chromium/chrome/browser/resources/options/controlled_setting.js b/chromium/chrome/browser/resources/options/controlled_setting.js
new file mode 100644
index 00000000000..28674bd888b
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/controlled_setting.js
@@ -0,0 +1,236 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+ var 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__: cr.ui.BubbleButton.prototype,
+
+ /**
+ * Decorates the base element to show the proper icon.
+ */
+ decorate: function() {
+ cr.ui.BubbleButton.prototype.decorate.call(this);
+ this.classList.add('controlled-setting-indicator');
+
+ // If there is a pref, track its controlledBy and recommendedValue
+ // properties in order to be able to bring up the correct bubble.
+ if (this.pref) {
+ Preferences.getInstance().addEventListener(
+ this.pref, this.handlePrefChange.bind(this));
+ this.resetHandler = this.clearAssociatedPref_;
+ }
+ },
+
+ /**
+ * The given handler will be called when the user clicks on the 'reset to
+ * recommended value' link shown in the indicator bubble. The |this| object
+ * will be the indicator itself.
+ * @param {function()} handler The handler to be called.
+ */
+ set resetHandler(handler) {
+ this.resetHandler_ = handler;
+ },
+
+ /**
+ * Clears the preference associated with this indicator.
+ * @private
+ */
+ clearAssociatedPref_: function() {
+ Preferences.clearPref(this.pref, !this.dialogPref);
+ },
+
+ /* Handle changes to the associated pref by hiding any currently visible
+ * bubble and updating the controlledBy property.
+ * @param {Event} event Pref change event.
+ */
+ handlePrefChange: function(event) {
+ OptionsPage.hideBubble();
+ if (event.value.controlledBy) {
+ if (!this.value || String(event.value.value) == this.value) {
+ this.controlledBy = event.value.controlledBy;
+ if (event.value.extension) {
+ if (this.pref == 'session.restore_on_startup' ||
+ this.pref == 'homepage_is_newtabpage') {
+ // Special case for the restore on startup, which is implied
+ // by the startup pages settings being controlled by an
+ // extension, and same for the home page as NTP, so we don't want
+ // to show two buttons in these cases.
+ // TODO(mad): Find a better way to handle this.
+ this.controlledBy = null;
+ } else {
+ this.extensionId = event.value.extension.id;
+ this.extensionIcon = event.value.extension.icon;
+ this.extensionName = event.value.extension.name;
+ }
+ }
+ } else {
+ this.controlledBy = null;
+ }
+ } else if (event.value.recommendedValue != undefined) {
+ this.controlledBy =
+ !this.value || String(event.value.recommendedValue) == this.value ?
+ 'hasRecommendation' : null;
+ } else {
+ this.controlledBy = null;
+ }
+ },
+
+ /**
+ * Open or close a bubble with further information about the pref.
+ * @private
+ */
+ toggleBubble_: function() {
+ if (this.showingBubble) {
+ OptionsPage.hideBubble();
+ } else {
+ var self = this;
+
+ // Construct the bubble text.
+ if (this.hasAttribute('plural')) {
+ var defaultStrings = {
+ 'policy': loadTimeData.getString('controlledSettingsPolicy'),
+ 'extension': loadTimeData.getString('controlledSettingsExtension'),
+ 'extensionWithName': loadTimeData.getString(
+ 'controlledSettingsExtensionWithName'),
+ };
+ } else {
+ var defaultStrings = {
+ 'policy': loadTimeData.getString('controlledSettingPolicy'),
+ 'extension': loadTimeData.getString('controlledSettingExtension'),
+ 'extensionWithName': loadTimeData.getString(
+ 'controlledSettingExtensionWithName'),
+ 'recommended':
+ loadTimeData.getString('controlledSettingRecommended'),
+ 'hasRecommendation':
+ loadTimeData.getString('controlledSettingHasRecommendation'),
+ };
+ if (cr.isChromeOS) {
+ defaultStrings.owner =
+ loadTimeData.getString('controlledSettingOwner');
+ }
+ }
+
+ // No controller, no bubble.
+ if (!this.controlledBy || !(this.controlledBy in defaultStrings))
+ return;
+
+ var text = defaultStrings[this.controlledBy];
+ if (this.controlledBy == 'extension' && this.extensionName)
+ text = defaultStrings.extensionWithName;
+
+ // Apply text overrides.
+ if (this.hasAttribute('text' + this.controlledBy))
+ text = this.getAttribute('text' + this.controlledBy);
+
+ // Create the DOM tree.
+ var content = document.createElement('div');
+ content.className = 'controlled-setting-bubble-content';
+ content.setAttribute('controlled-by', this.controlledBy);
+ content.textContent = text;
+
+ if (this.controlledBy == 'hasRecommendation' && this.resetHandler_ &&
+ !this.readOnly) {
+ var container = document.createElement('div');
+ var action = document.createElement('button');
+ action.classList.add('link-button');
+ action.classList.add('controlled-setting-bubble-action');
+ action.textContent =
+ loadTimeData.getString('controlledSettingFollowRecommendation');
+ action.addEventListener('click', function(event) {
+ self.resetHandler_();
+ });
+ container.appendChild(action);
+ content.appendChild(container);
+ } else if (this.controlledBy == 'extension' && this.extensionName) {
+ var extensionContainer =
+ $('extension-controlled-settings-bubble-template').
+ cloneNode(true);
+ // No need for an id anymore, and thus remove to avoid id collision.
+ extensionContainer.removeAttribute('id');
+ extensionContainer.hidden = false;
+
+ var extensionName = extensionContainer.querySelector(
+ '.controlled-setting-bubble-extension-name');
+ extensionName.textContent = this.extensionName;
+ extensionName.style.backgroundImage =
+ 'url("' + this.extensionIcon + '")';
+
+ var manageLink = extensionContainer.querySelector(
+ '.controlled-setting-bubble-extension-manage-link');
+ manageLink.onclick = function() {
+ uber.invokeMethodOnWindow(
+ window.top, 'showPage', {pageId: 'extensions'});
+ };
+
+ var disableButton = extensionContainer.querySelector('button');
+ var extensionId = this.extensionId;
+ disableButton.onclick = function() {
+ chrome.send('disableExtension', [extensionId]);
+ };
+ content.appendChild(extensionContainer);
+ }
+
+ OptionsPage.showBubble(content, this.image, this, this.location);
+ }
+ },
+ };
+
+ /**
+ * The name of the associated preference.
+ * @type {string}
+ */
+ cr.defineProperty(ControlledSettingIndicator, 'pref', cr.PropertyKind.ATTR);
+
+ /**
+ * Whether this indicator is part of a dialog. If so, changes made to the
+ * associated preference take effect in the settings UI immediately but are
+ * only actually committed when the user confirms the dialog. If the user
+ * cancels the dialog instead, the changes are rolled back in the settings UI
+ * and never committed.
+ * @type {boolean}
+ */
+ cr.defineProperty(ControlledSettingIndicator, 'dialogPref',
+ cr.PropertyKind.BOOL_ATTR);
+
+ /**
+ * The value of the associated preference that the indicator represents. If
+ * this is not set, the indicator will be visible whenever any value is
+ * enforced or recommended. If it is set, the indicator will be visible only
+ * when the enforced or recommended value matches the value it represents.
+ * This allows multiple indicators to be created for a set of radio buttons,
+ * ensuring that only one of them is visible at a time.
+ */
+ cr.defineProperty(ControlledSettingIndicator, 'value',
+ cr.PropertyKind.ATTR);
+
+ /**
+ * The status of the associated preference:
+ * - 'policy': A specific value is enfoced by policy.
+ * - 'extension': A specific value is enforced by an extension.
+ * - 'recommended': A value is recommended by policy. The user could
+ * override this recommendation but has not done so.
+ * - 'hasRecommendation': A value is recommended by policy. The user has
+ * overridden this recommendation.
+ * - unset: The value is controlled by the user alone.
+ * @type {string}
+ */
+ cr.defineProperty(ControlledSettingIndicator, 'controlledBy',
+ cr.PropertyKind.ATTR);
+
+ // Export.
+ return {
+ ControlledSettingIndicator: ControlledSettingIndicator
+ };
+});
diff --git a/chromium/chrome/browser/resources/options/cookies_list.js b/chromium/chrome/browser/resources/options/cookies_list.js
new file mode 100644
index 00000000000..c20ac63c158
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/cookies_list.js
@@ -0,0 +1,921 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+ /** @const */ var DeletableItemList = options.DeletableItemList;
+ /** @const */ var DeletableItem = options.DeletableItem;
+ /** @const */ var ArrayDataModel = cr.ui.ArrayDataModel;
+ /** @const */ var 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 */ var 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']],
+ 'server_bound_cert': [['serverId', 'label_server_bound_cert_server_id'],
+ ['certType', 'label_server_bound_cert_type'],
+ ['created', 'label_server_bound_cert_created']],
+ 'flash_lso': [['domain', 'label_cookie_domain']],
+ };
+
+ /**
+ * 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;
+ }
+
+ /**
+ * Adds information about an app that protects this data item to the
+ * @{code element}.
+ * @param {Element} element The DOM element the information should be
+ appended to.
+ * @param {{id: string, name: string}} appInfo Information about an app.
+ */
+ function addAppInfo(element, appInfo) {
+ var img = element.ownerDocument.createElement('img');
+ img.src = 'chrome://extension-icon/' + appInfo.id + '/16/1';
+ element.title = loadTimeData.getString('label_protected_by_apps') +
+ ' ' + appInfo.name;
+ img.className = 'protecting-app';
+ element.appendChild(img);
+ }
+
+ 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,
+
+ /** @override */
+ 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 = loadTimeData.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,
+ serverBoundCerts: 0,
+ };
+ if (this.origin)
+ this.origin.collectSummaryInfo(info);
+
+ var list = [];
+ if (info.cookies > 1)
+ list.push(loadTimeData.getStringF('cookie_plural', info.cookies));
+ else if (info.cookies > 0)
+ list.push(loadTimeData.getString('cookie_singular'));
+ if (info.database || info.indexedDb)
+ list.push(loadTimeData.getString('cookie_database_storage'));
+ if (info.localStorage)
+ list.push(loadTimeData.getString('cookie_local_storage'));
+ if (info.appCache)
+ list.push(loadTimeData.getString('cookie_app_cache'));
+ if (info.fileSystem)
+ list.push(loadTimeData.getString('cookie_file_system'));
+ if (info.serverBoundCerts)
+ list.push(loadTimeData.getString('cookie_server_bound_cert'));
+ if (info.flashLSO)
+ list.push(loadTimeData.getString('cookie_flash_lso'));
+
+ 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;
+
+ var apps = info.appsProtectingThis;
+ for (var key in apps) {
+ addAppInfo(this.dataChild, apps[key]);
+ }
+
+ 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;
+ } else if (this.data.type == 'server_bound_cert') {
+ info.serverBoundCerts++;
+ } else if (this.data.type == 'flash_lso') {
+ info.flashLSO = true;
+ }
+
+ var apps = this.data.appsProtectingThis;
+ if (apps) {
+ if (!info.appsProtectingThis)
+ info.appsProtectingThis = {};
+ apps.forEach(function(appInfo) {
+ info.appsProtectingThis[appInfo.id] = appInfo;
+ });
+ }
+ }
+ },
+
+ /**
+ * 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);
+ return;
+ }
+
+ if (!this.data || this.data.hasChildren)
+ return;
+
+ var text = '';
+ switch (this.data.type) {
+ case 'cookie':
+ case 'database':
+ text = this.data.name;
+ break;
+ default:
+ text = loadTimeData.getString('cookie_' + this.data.type);
+ }
+ 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.tabIndex = 0;
+ div.textContent = text;
+ var apps = this.data.appsProtectingThis;
+ if (apps)
+ apps.forEach(addAppInfo.bind(null, div));
+
+ var index = item.appendItem(this, div);
+ div.onclick = function() {
+ item.selectedIndex = (item.selectedIndex == index) ? -1 : 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 && 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];
+ else
+ nodes[name].textContent = '';
+ }
+ 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() {
+ // 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.
+ if (this.parent_ == undefined || parent.listIndex != -1) {
+ // Setting the parent is somewhat tricky because the CookieListItem
+ // constructor has side-effects on the |origin| that it wraps. Every
+ // time a CookieListItem is created for an |origin|, it registers
+ // itself as the parent of the |origin|.
+ // The List implementation may create a temporary CookieListItem item
+ // that wraps the |origin| of the very first entry of the CokiesList,
+ // when the List is redrawn the first time. This temporary
+ // CookieListItem is fresh (has listIndex = -1) and is never inserted
+ // into the List. Therefore it gets never updated. This destroys the
+ // chain of parent pointers.
+ // This is the stack trace:
+ // CookieListItem
+ // CookiesList.createItem
+ // List.measureItem
+ // List.getDefaultItemSize_
+ // List.getDefaultItemHeight_
+ // List.getIndexForListOffset_
+ // List.getItemsInViewPort
+ // List.redraw
+ // List.endBatchUpdates
+ // CookiesList.loadChildren
+ 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,
+
+ /** @override */
+ decorate: function() {
+ DeletableItemList.prototype.decorate.call(this);
+ this.classList.add('cookie-list');
+ this.dataModel = new ArrayDataModel([]);
+ 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 = loadTimeData.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
+ /** @override */
+ 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
+ /** @override */
+ deleteItemAtIndex: function(index) {
+ var item = this.dataModel.item(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.dataModel.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.dataModel.splice(0, this.dataModel.length);
+ 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/chromium/chrome/browser/resources/options/cookies_view.css b/chromium/chrome/browser/resources/options/cookies_view.css
new file mode 100644
index 00000000000..ab7a62c1c7f
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/cookies_view.css
@@ -0,0 +1,198 @@
+/* Copyright (c) 2012 The Chromium Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file. */
+
+/* Styles for the cookies list page. */
+.cookies-view-page {
+ height: 90%;
+ margin-left: -15px;
+ width: 720px;
+}
+
+/* Styles for the cookies list elements in cookies_view.html. */
+.cookies-list {
+ -webkit-box-flex: 1;
+ /* This property overrides the |min-height: 192px;| property above due to
+ * special behavior of the cookies list. */
+ border: 1px solid #D9D9D9;
+ margin: 0;
+ margin-top: 5px;
+ min-height: 0;
+}
+
+.cookies-list-content-area {
+ -webkit-box-orient: vertical;
+ display: -webkit-box;
+ overflow-y: hidden;
+}
+
+.cookies-column-headers {
+ -webkit-box-align: baseline;
+ -webkit-box-orient: horizontal;
+ display: -webkit-box;
+ position: relative;
+ width: 100%;
+}
+
+.cookies-column-headers > * {
+ display: block;
+}
+
+.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 {
+ -webkit-padding-start: 7px;
+ width: 14em;
+}
+
+.cookies-data-column {
+ -webkit-box-flex: 1;
+ -webkit-padding-start: 7px;
+}
+
+/* Enable animating the height of items. */
+list.cookie-list .deletable-item {
+ -webkit-transition: height 150ms 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: 14em;
+}
+
+list.cookie-list > .deletable-item[selected] .cookie-site {
+ -webkit-user-select: text;
+}
+
+.cookie-data {
+ display: inline-block;
+ max-width: 410px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.cookie-size {
+ display: inline-block;
+ float: right;
+ margin-right: 0;
+}
+
+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: 14em;
+ -webkit-padding-start: 7px;
+ -webkit-transition: 150ms 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: rgb(224, 233, 245);
+ border: 1px solid rgb(131, 146, 174);
+ border-radius: 5px;
+ display: inline-block;
+ font-size: 85%;
+ height: auto;
+ margin: 2px 4px 2px 0;
+ max-width: 120px;
+ min-width: 40px;
+ overflow: hidden;
+ padding: 0 3px;
+ text-align: center;
+ text-overflow: ellipsis;
+}
+
+.cookie-items .cookie-item:hover {
+ background: rgb(238, 243, 249);
+ border-color: rgb(100, 113, 135);
+}
+
+.cookie-items .cookie-item[selected] {
+ background: rgb(245, 248, 248);
+ border-color: #B2B2B2;
+}
+
+.cookie-items .cookie-item[selected]:hover {
+ background: rgb(245, 248, 248);
+ border-color: rgb(100, 113, 135);
+}
+
+.cookie-items .cookie-item .protecting-app,
+.cookie-data .protecting-app {
+ margin-bottom: -3px;
+ margin-left: 4px;
+}
+
+/* Styles for the cookie details box. */
+.cookie-details {
+ background: rgb(245, 248, 248);
+ border: 1px solid #B2B2B2;
+ border-radius: 5px;
+ 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/chromium/chrome/browser/resources/options/cookies_view.html b/chromium/chrome/browser/resources/options/cookies_view.html
new file mode 100644
index 00000000000..0cc136b6630
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/cookies_view.html
@@ -0,0 +1,28 @@
+<div id="cookies-view-page" class="page cookies-view-page" hidden>
+ <div class="close-button"></div>
+ <h1 i18n-content="cookiesViewPage"></h1>
+ <div class="content-area cookies-list-content-area">
+ <div class="cookies-column-headers">
+ <div class="cookies-site-column">
+ <h3 i18n-content="cookie_domain"></h3>
+ </div>
+ <div class="cookies-data-column">
+ <h3 i18n-content="cookie_local_data"></h3>
+ </div>
+ <div class="cookies-header-controls">
+ <button class="remove-all-cookies-button"
+ i18n-content="remove_all_cookie"></button>
+ <input type="search" class="cookies-search-box"
+ i18n-values="placeholder:search_cookies" incremental>
+ </div>
+ </div>
+ <list id="cookies-list" class="cookies-list"></list>
+ </div>
+ <div class="action-area">
+ <div class="button-strip">
+ <button class="cookies-view-overlay-confirm default-button"
+ i18n-content="done">
+ </button>
+ </div>
+ </div>
+</div>
diff --git a/chromium/chrome/browser/resources/options/cookies_view.js b/chromium/chrome/browser/resources/options/cookies_view.js
new file mode 100644
index 00000000000..4d496684c42
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/cookies_view.js
@@ -0,0 +1,140 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+
+ var OptionsPage = options.OptionsPage;
+
+ /////////////////////////////////////////////////////////////////////////////
+ // CookiesView class:
+
+ /**
+ * Encapsulated handling of the cookies and other site data page.
+ * @constructor
+ */
+ function CookiesView(model) {
+ OptionsPage.call(this, 'cookies',
+ loadTimeData.getString('cookiesViewPageTabTitle'),
+ 'cookies-view-page');
+ }
+
+ 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, empty string if the query is empty.
+ * @type {string}
+ * @private
+ */
+ lastQuery_: '',
+
+ initializePage: function() {
+ OptionsPage.prototype.initializePage.call(this);
+
+ var searchBox = this.pageDiv.querySelector('.cookies-search-box');
+ searchBox.addEventListener(
+ 'search', this.handleSearchQueryChange_.bind(this));
+ searchBox.onkeydown = function(e) {
+ // Prevent the overlay from handling this event.
+ if (e.keyIdentifier == 'Enter')
+ e.stopPropagation();
+ };
+
+ this.pageDiv.querySelector('.remove-all-cookies-button').onclick =
+ function(e) {
+ chrome.send('removeAllCookies');
+ };
+
+ var cookiesList = this.pageDiv.querySelector('.cookies-list');
+ options.CookiesList.decorate(cookiesList);
+
+ this.addEventListener('visibleChange', this.handleVisibleChange_);
+
+ this.pageDiv.querySelector('.cookies-view-overlay-confirm').onclick =
+ OptionsPage.closeOverlay.bind(OptionsPage);
+ },
+
+ /** @override */
+ didShowPage: function() {
+ this.pageDiv.querySelector('.cookies-search-box').value = '';
+ this.lastQuery_ = '';
+ },
+
+ /**
+ * Search cookie using text in |cookies-search-box|.
+ */
+ searchCookie: function() {
+ this.queryDelayTimerId_ = 0;
+ var filter = this.pageDiv.querySelector('.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;
+
+ chrome.send('reloadCookies');
+
+ if (!this.initialized_) {
+ this.initialized_ = true;
+ this.searchCookie();
+ } else {
+ this.pageDiv.querySelector('.cookies-list').redraw();
+ }
+
+ this.pageDiv.querySelector('.cookies-search-box').focus();
+ },
+ };
+
+ // 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/chromium/chrome/browser/resources/options/deletable_item_list.js b/chromium/chrome/browser/resources/options/deletable_item_list.js
new file mode 100644
index 00000000000..403dc0fc1dc
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/deletable_item_list.js
@@ -0,0 +1,196 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+ /** @const */ var List = cr.ui.List;
+ /** @const */ var 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,
+
+ /** @override */
+ 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 row-delete-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_;
+ },
+
+ /**
+ * Returns the close button element.
+ * @return {HTMLElement} The close |<button>| element.
+ */
+ get closeButtonElement() {
+ return this.closeButtonElement_;
+ },
+
+ /* 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 or to focus on the close
+ * button.
+ * @param {Event} e The mouse down/up event object.
+ * @private
+ */
+ handleMouseDownUpOnClose_: function(e) {
+ if (e.target.disabled)
+ return;
+ e.stopPropagation();
+ e.preventDefault();
+ },
+ };
+
+ var DeletableItemList = cr.ui.define('list');
+
+ DeletableItemList.prototype = {
+ __proto__: List.prototype,
+
+ /** @override */
+ 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('row-delete-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/chromium/chrome/browser/resources/options/do_not_track_confirm_overlay.css b/chromium/chrome/browser/resources/options/do_not_track_confirm_overlay.css
new file mode 100644
index 00000000000..96950c10265
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/do_not_track_confirm_overlay.css
@@ -0,0 +1,7 @@
+/* Copyright (c) 2012 The Chromium Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file. */
+
+#do-not-track-confirm-overlay {
+ width: 500px;
+}
diff --git a/chromium/chrome/browser/resources/options/do_not_track_confirm_overlay.html b/chromium/chrome/browser/resources/options/do_not_track_confirm_overlay.html
new file mode 100644
index 00000000000..e39d00f0af1
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/do_not_track_confirm_overlay.html
@@ -0,0 +1,23 @@
+<div id="do-not-track-confirm-overlay" class="page" hidden>
+ <div class="close-button"></div>
+ <h1 i18n-content="doNotTrackConfirmOverlay"></h1>
+ <div class="content-area">
+ <span id="do-not-track-confirm-text"
+ i18n-content="doNotTrackConfirmMessage">
+ </span>
+ <a target="_blank" i18n-content="learnMore"
+ i18n-values="href:doNotTrackLearnMoreURL">
+ </a>
+ </div>
+ <div class="action-area">
+ <div class="button-strip">
+ <button id="do-not-track-confirm-ok" class="default-button"
+ i18n-content="doNotTrackConfirmEnable">
+ </button>
+ <button id="do-not-track-confirm-cancel"
+ i18n-content="doNotTrackConfirmDisable"
+ class="cancel-button">
+ </button>
+ </div>
+ </div>
+</div>
diff --git a/chromium/chrome/browser/resources/options/editable_text_field.js b/chromium/chrome/browser/resources/options/editable_text_field.js
new file mode 100644
index 00000000000..8c5787249cc
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/editable_text_field.js
@@ -0,0 +1,375 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+ var EditableTextField = cr.ui.define('div');
+
+ /**
+ * Decorates an element as an editable text field.
+ * @param {!HTMLElement} el The element to decorate.
+ */
+ EditableTextField.decorate = function(el) {
+ el.__proto__ = EditableTextField.prototype;
+ el.decorate();
+ };
+
+ EditableTextField.prototype = {
+ __proto__: HTMLDivElement.prototype,
+
+ /**
+ * The actual input element in this field.
+ * @type {?HTMLElement}
+ * @private
+ */
+ editField_: null,
+
+ /**
+ * The static text displayed when this field isn't editable.
+ * @type {?HTMLElement}
+ * @private
+ */
+ staticText_: null,
+
+ /**
+ * The data model for this field.
+ * @type {?Object}
+ * @private
+ */
+ model_: null,
+
+ /**
+ * Whether or not the current edit should be considered canceled, rather
+ * than committed, when editing ends.
+ * @type {boolean}
+ * @private
+ */
+ editCanceled_: true,
+
+ /** @override */
+ decorate: function() {
+ this.classList.add('editable-text-field');
+
+ this.createEditableTextCell();
+
+ if (this.hasAttribute('i18n-placeholder-text')) {
+ var identifier = this.getAttribute('i18n-placeholder-text');
+ var localizedText = loadTimeData.getString(identifier);
+ if (localizedText)
+ this.setAttribute('placeholder-text', localizedText);
+ }
+
+ this.addEventListener('keydown', this.handleKeyDown_);
+ this.editField_.addEventListener('focus', this.handleFocus_.bind(this));
+ this.editField_.addEventListener('blur', this.handleBlur_.bind(this));
+ this.checkForEmpty_();
+ },
+
+ /**
+ * Indicates that this field has no value in the model, and the placeholder
+ * text (if any) should be shown.
+ * @type {boolean}
+ */
+ get empty() {
+ return this.hasAttribute('empty');
+ },
+
+ /**
+ * The placeholder text to be used when the model or its value is empty.
+ * @type {string}
+ */
+ get placeholderText() {
+ return this.getAttribute('placeholder-text');
+ },
+ set placeholderText(text) {
+ if (text)
+ this.setAttribute('placeholder-text', text);
+ else
+ this.removeAttribute('placeholder-text');
+
+ this.checkForEmpty_();
+ },
+
+ /**
+ * Returns the input element in this text field.
+ * @type {HTMLElement} The element that is the actual input field.
+ */
+ get editField() {
+ return this.editField_;
+ },
+
+ /**
+ * 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.editCanceled_ = false;
+
+ if (this.empty) {
+ this.removeAttribute('empty');
+ if (this.editField)
+ this.editField.value = '';
+ }
+ if (this.editField) {
+ this.editField.focus();
+ this.editField.select();
+ }
+ } else {
+ if (!this.editCanceled_ && this.hasBeenEdited &&
+ this.currentInputIsValid) {
+ this.updateStaticValues_();
+ cr.dispatchSimpleEvent(this, 'commitedit', true);
+ } else {
+ this.resetEditableValues_();
+ cr.dispatchSimpleEvent(this, 'canceledit', true);
+ }
+ this.checkForEmpty_();
+ }
+ },
+
+ /**
+ * Whether the item is editable.
+ * @type {boolean}
+ */
+ get editable() {
+ return this.hasAttribute('editable');
+ },
+ set editable(editable) {
+ if (this.editable == editable)
+ return;
+
+ if (editable)
+ this.setAttribute('editable', '');
+ else
+ this.removeAttribute('editable');
+ this.editable_ = editable;
+ },
+
+ /**
+ * The data model for this field.
+ * @type {Object}
+ */
+ get model() {
+ return this.model_;
+ },
+ set model(model) {
+ this.model_ = model;
+ this.checkForEmpty_(); // This also updates the editField value.
+ this.updateStaticValues_();
+ },
+
+ /**
+ * 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 overridden by subclasses if a different element should be
+ * focused.
+ * @type {?HTMLElement}
+ */
+ get initialFocusElement() {
+ return this.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 overridden 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 overridden
+ * by subclasses to return false when nothing has changed to avoid
+ * unnecessary commits.
+ * @type {boolean}
+ */
+ get hasBeenEdited() {
+ return true;
+ },
+
+ /**
+ * Mutates the input during a successful commit. Can be overridden to
+ * provide a way to "clean up" valid input so that it conforms to a
+ * desired format. Will only be called when commit succeeds for valid
+ * input, or when the model is set.
+ * @param {string} value Input text to be mutated.
+ * @return {string} mutated text.
+ */
+ mutateInput: function(value) {
+ return value;
+ },
+
+ /**
+ * Creates a div containing an <input>, as well as static text, keeping
+ * references to them so they can be manipulated.
+ * @param {string} text The text of the cell.
+ * @private
+ */
+ createEditableTextCell: function(text) {
+ // This function should only be called once.
+ if (this.editField_)
+ return;
+
+ var container = this.ownerDocument.createElement('div');
+
+ var textEl = this.ownerDocument.createElement('div');
+ textEl.className = 'static-text';
+ textEl.textContent = text;
+ textEl.setAttribute('displaymode', 'static');
+ this.appendChild(textEl);
+ this.staticText_ = textEl;
+
+ var inputEl = this.ownerDocument.createElement('input');
+ inputEl.className = 'editable-text';
+ inputEl.type = 'text';
+ inputEl.value = text;
+ inputEl.setAttribute('displaymode', 'edit');
+ inputEl.staticVersion = textEl;
+ this.appendChild(inputEl);
+ this.editField_ = inputEl;
+ },
+
+ /**
+ * Resets the editable version of any controls created by
+ * createEditableTextCell to match the static text.
+ * @private
+ */
+ resetEditableValues_: function() {
+ var editField = this.editField_;
+ var staticLabel = editField.staticVersion;
+ if (!staticLabel)
+ return;
+
+ if (editField instanceof HTMLInputElement)
+ editField.value = staticLabel.textContent;
+
+ editField.setCustomValidity('');
+ },
+
+ /**
+ * Sets the static version of any controls created by createEditableTextCell
+ * 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. Also
+ * updates the model's value with the mutated value of the edit field.
+ * @private
+ */
+ updateStaticValues_: function() {
+ var editField = this.editField_;
+ var staticLabel = editField.staticVersion;
+ if (!staticLabel)
+ return;
+
+ if (editField instanceof HTMLInputElement) {
+ staticLabel.textContent = editField.value;
+ this.model_.value = this.mutateInput(editField.value);
+ }
+ },
+
+ /**
+ * Checks to see if the model or its value are empty. If they are, then set
+ * the edit field to the placeholder text, if any, and if not, set it to the
+ * model's value.
+ * @private
+ */
+ checkForEmpty_: function() {
+ var editField = this.editField_;
+ if (!editField)
+ return;
+
+ if (!this.model_ || !this.model_.value) {
+ this.setAttribute('empty', '');
+ editField.value = this.placeholderText || '';
+ } else {
+ this.removeAttribute('empty');
+ editField.value = this.model_.value;
+ }
+ },
+
+ /**
+ * Called when this widget receives focus.
+ * @param {Event} e the focus event.
+ * @private
+ */
+ handleFocus_: function(e) {
+ if (this.editing)
+ return;
+
+ this.editing = true;
+ if (this.editField_)
+ this.editField_.focus();
+ },
+
+ /**
+ * Called when this widget loses focus.
+ * @param {Event} e the blur event.
+ * @private
+ */
+ handleBlur_: function(e) {
+ if (!this.editing)
+ return;
+
+ this.editing = false;
+ },
+
+ /**
+ * Called when a key is pressed. Handles committing and canceling edits.
+ * @param {Event} e The key down event.
+ * @private
+ */
+ handleKeyDown_: function(e) {
+ if (!this.editing)
+ return;
+
+ var endEdit;
+ switch (e.keyIdentifier) {
+ case 'U+001B': // Esc
+ this.editCanceled_ = true;
+ endEdit = true;
+ break;
+ case 'Enter':
+ if (this.currentInputIsValid)
+ endEdit = true;
+ break;
+ }
+
+ if (endEdit) {
+ // Blurring will trigger the edit to end.
+ 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();
+ }
+ },
+ };
+
+ /**
+ * Takes care of committing changes to EditableTextField items when the
+ * window loses focus.
+ */
+ window.addEventListener('blur', function(e) {
+ var itemAncestor = findAncestor(document.activeElement, function(node) {
+ return node instanceof EditableTextField;
+ });
+ if (itemAncestor)
+ document.activeElement.blur();
+ });
+
+ return {
+ EditableTextField: EditableTextField,
+ };
+});
diff --git a/chromium/chrome/browser/resources/options/factory_reset_overlay.css b/chromium/chrome/browser/resources/options/factory_reset_overlay.css
new file mode 100644
index 00000000000..c7369e3fa50
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/factory_reset_overlay.css
@@ -0,0 +1,7 @@
+/* Copyright (c) 2012 The Chromium Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file. */
+
+#factory-reset-overlay {
+ max-width: 550px;
+}
diff --git a/chromium/chrome/browser/resources/options/factory_reset_overlay.html b/chromium/chrome/browser/resources/options/factory_reset_overlay.html
new file mode 100644
index 00000000000..21df28f9403
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/factory_reset_overlay.html
@@ -0,0 +1,16 @@
+<div id="factory-reset-overlay" class="page" hidden>
+ <div class="close-button"></div>
+ <h1 i18n-content="factoryResetHeading"></h1>
+ <div class="content-area">
+ <span i18n-content="factoryResetWarning"></span>
+ <a i18n-values="href:factoryResetHelpUrl"
+ i18n-content="errorLearnMore" target="_blank"></a>
+ </div>
+ <div class="action-area button-strip">
+ <button id="factory-reset-data-dismiss" i18n-content="cancel">
+ </button>
+ <button id="factory-reset-data-restart" class="default-button"
+ i18n-content="factoryResetDataRestart">
+ </button>
+ </div>
+</div>
diff --git a/chromium/chrome/browser/resources/options/factory_reset_overlay.js b/chromium/chrome/browser/resources/options/factory_reset_overlay.js
new file mode 100644
index 00000000000..a3d3c6e5d74
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/factory_reset_overlay.js
@@ -0,0 +1,49 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+ var OptionsPage = options.OptionsPage;
+
+ /**
+ * FactoryResetOverlay class
+ * Encapsulated handling of the Factory Reset confirmation overlay page.
+ * @class
+ */
+ function FactoryResetOverlay() {
+ OptionsPage.call(this, 'factoryResetData',
+ loadTimeData.getString('factoryResetTitle'),
+ 'factory-reset-overlay');
+ }
+
+ cr.addSingletonGetter(FactoryResetOverlay);
+
+ FactoryResetOverlay.prototype = {
+ // Inherit FactoryResetOverlay from OptionsPage.
+ __proto__: OptionsPage.prototype,
+
+ /**
+ * Initialize the page.
+ */
+ initializePage: function() {
+ // Call base class implementation to starts preference initialization.
+ OptionsPage.prototype.initializePage.call(this);
+
+ $('factory-reset-data-dismiss').onclick = function(event) {
+ FactoryResetOverlay.dismiss();
+ };
+ $('factory-reset-data-restart').onclick = function(event) {
+ chrome.send('performFactoryResetRestart');
+ };
+ },
+ };
+
+ FactoryResetOverlay.dismiss = function() {
+ OptionsPage.closeOverlay();
+ };
+
+ // Export
+ return {
+ FactoryResetOverlay: FactoryResetOverlay
+ };
+});
diff --git a/chromium/chrome/browser/resources/options/font_settings.css b/chromium/chrome/browser/resources/options/font_settings.css
new file mode 100644
index 00000000000..f999c586f2b
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/font_settings.css
@@ -0,0 +1,62 @@
+/* Copyright (c) 2012 The Chromium Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file. */
+
+#font-settings > section {
+ overflow: hidden;
+}
+
+#font-settings .action-area {
+ -webkit-box-pack: start;
+}
+
+#font-settings .action-area .spacer {
+ -webkit-box-flex: 1;
+ display: -webkit-box;
+}
+
+#font-settings .button-strip {
+ width: 100%;
+}
+
+.font-setting-container {
+ display: -webkit-box;
+}
+
+#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 {
+ direction: ltr;
+ height: 70px;
+ overflow: hidden;
+ width: 270px;
+}
+
+.font-settings-huge {
+ float: right;
+}
+
+html[dir=rtl] .font-settings-huge {
+ float: left;
+}
diff --git a/chromium/chrome/browser/resources/options/font_settings.html b/chromium/chrome/browser/resources/options/font_settings.html
new file mode 100644
index 00000000000..da3106019e3
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/font_settings.html
@@ -0,0 +1,113 @@
+<div id="font-settings" class="page" hidden>
+ <div class="close-button"></div>
+ <h1 i18n-content="fontSettingsPage"></h1>
+ <div class="content-area">
+ <section>
+ <h3 i18n-content="fontSettingsStandard"></h3>
+ <div class="font-setting-container">
+ <div class="font-input-div">
+ <div>
+ <select id="standard-font-family" class="font-input"
+ data-type="string" metric="Options_ChangeStandardFont"
+ pref="webkit.webprefs.fonts.standard.Zyyy">
+ </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>
+ </div>
+ </section>
+ <section>
+ <h3 i18n-content="fontSettingsSerif"></h3>
+ <div class="font-setting-container">
+ <div class="font-input-div">
+ <div>
+ <select id="serif-font-family" class="font-input" data-type="string"
+ pref="webkit.webprefs.fonts.serif.Zyyy"
+ metric="Options_ChangeSerifFont">
+ </select>
+ </div>
+ </div>
+ <div id="serif-font-sample" class="font-sample-div"></div>
+ </div>
+ </section>
+ <section>
+ <h3 i18n-content="fontSettingsSansSerif"></h3>
+ <div class="font-setting-container">
+ <div class="font-input-div">
+ <div>
+ <select id="sans-serif-font-family" class="font-input"
+ data-type="string" metric="Options_ChangeSansSerifFont"
+ pref="webkit.webprefs.fonts.sansserif.Zyyy">
+ </select>
+ </div>
+ </div>
+ <div id="sans-serif-font-sample" class="font-sample-div"></div>
+ </div>
+ </section>
+ <section>
+ <h3 i18n-content="fontSettingsFixedWidth"></h3>
+ <div class="font-setting-container">
+ <div class="font-input-div">
+ <div>
+ <select id="fixed-font-family" class="font-input" data-type="string"
+ pref="webkit.webprefs.fonts.fixed.Zyyy"
+ metric="Options_ChangeFixedFont">
+ </select>
+ </div>
+ </div>
+ <div id="fixed-font-sample" class="font-sample-div"></div>
+ </div>
+ </section>
+ <section>
+ <h3 i18n-content="fontSettingsMinimumSize"></h3>
+ <div class="font-setting-container">
+ <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>
+ </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>
+ <div class="action-area">
+ <div class="button-strip">
+ <span id="advanced-font-settings-install" hidden
+ i18n-values=".innerHTML:advancedFontSettingsInstall"></span>
+ <a id="advanced-font-settings-options" href="#" hidden
+ i18n-content="advancedFontSettingsOptions"></a>
+ <span class="spacer"></span>
+ <button id="font-settings-confirm" class="default-button"
+ i18n-content="done">
+ </button>
+ </div>
+ </div>
+</div>
diff --git a/chromium/chrome/browser/resources/options/font_settings.js b/chromium/chrome/browser/resources/options/font_settings.js
new file mode 100644
index 00000000000..0b3f3f70a9b
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/font_settings.js
@@ -0,0 +1,255 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+
+ var OptionsPage = options.OptionsPage;
+
+ /**
+ * FontSettings class
+ * Encapsulated handling of the 'Fonts and Encoding' page.
+ * @class
+ */
+ function FontSettings() {
+ OptionsPage.call(this,
+ 'fonts',
+ loadTimeData.getString('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.addEventListener(
+ 'change', this.standardRangeChanged_.bind(this, standardFontRange));
+ standardFontRange.customChangeHandler =
+ this.standardFontSizeChanged_.bind(standardFontRange);
+
+ var minimumFontRange = $('minimum-font-size');
+ minimumFontRange.valueMap = [6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,
+ 18, 20, 22, 24];
+ minimumFontRange.addEventListener(
+ 'change', this.minimumRangeChanged_.bind(this, minimumFontRange));
+ minimumFontRange.customChangeHandler =
+ this.minimumFontSizeChanged_.bind(minimumFontRange);
+
+ var placeholder = loadTimeData.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);
+ });
+
+ $('font-settings-confirm').onclick = function() {
+ OptionsPage.closeOverlay();
+ };
+
+ $('advanced-font-settings-options').onclick = function() {
+ chrome.send('openAdvancedFontSettingsOptions');
+ };
+ },
+
+ /**
+ * 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;
+ }
+ },
+
+ /**
+ * Handler that is called when the user changes the position of the standard
+ * font size slider. This allows the UI to show a preview of the change
+ * before the slider has been released and the associated prefs updated.
+ * @param {Element} el The slider input element.
+ * @param {Event} event Change event.
+ * @private
+ */
+ standardRangeChanged_: function(el, event) {
+ var size = el.mapPositionToPref(el.value);
+ var fontSampleEl = $('standard-font-sample');
+ this.setUpFontSample_(fontSampleEl, size, fontSampleEl.style.fontFamily,
+ true);
+
+ fontSampleEl = $('serif-font-sample');
+ this.setUpFontSample_(fontSampleEl, size, fontSampleEl.style.fontFamily,
+ true);
+
+ fontSampleEl = $('sans-serif-font-sample');
+ this.setUpFontSample_(fontSampleEl, size, fontSampleEl.style.fontFamily,
+ true);
+
+ fontSampleEl = $('fixed-font-sample');
+ this.setUpFontSample_(fontSampleEl,
+ size - OptionsPage.SIZE_DIFFERENCE_FIXED_STANDARD,
+ fontSampleEl.style.fontFamily, false);
+ },
+
+ /**
+ * Sets the 'default_fixed_font_size' preference when the user changes the
+ * standard font size.
+ * @param {Event} event Change event.
+ * @private
+ */
+ standardFontSizeChanged_: function(event) {
+ var size = this.mapPositionToPref(this.value);
+ Preferences.setIntegerPref(
+ 'webkit.webprefs.default_fixed_font_size',
+ size - OptionsPage.SIZE_DIFFERENCE_FIXED_STANDARD, true);
+ return false;
+ },
+
+ /**
+ * Handler that is called when the user changes the position of the minimum
+ * font size slider. This allows the UI to show a preview of the change
+ * before the slider has been released and the associated prefs updated.
+ * @param {Element} el The slider input element.
+ * @param {Event} event Change event.
+ * @private
+ */
+ minimumRangeChanged_: function(el, event) {
+ var size = el.mapPositionToPref(el.value);
+ var fontSampleEl = $('minimum-font-sample');
+ this.setUpFontSample_(fontSampleEl, size, fontSampleEl.style.fontFamily,
+ true);
+ },
+
+ /**
+ * Sets the 'minimum_logical_font_size' preference when the user changes the
+ * minimum font size.
+ * @param {Event} event Change event.
+ * @private
+ */
+ minimumFontSizeChanged_: function(event) {
+ var size = this.mapPositionToPref(this.value);
+ Preferences.setIntegerPref(
+ 'webkit.webprefs.minimum_logical_font_size', size, true);
+ return false;
+ },
+
+ /**
+ * 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 +
+ loadTimeData.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];
+ dir = items[i][2];
+ if (text) {
+ selected = value == selectedValue;
+ var option = new Option(text, value, false, selected);
+ option.dir = dir;
+ element.appendChild(option);
+ } 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);
+ };
+
+ /**
+ * @param {boolean} available Whether the Advanced Font Settings Extension
+ * is installed and enabled.
+ */
+ FontSettings.notifyAdvancedFontSettingsAvailability = function(available) {
+ $('advanced-font-settings-install').hidden = available;
+ $('advanced-font-settings-options').hidden = !available;
+ };
+
+ // Export
+ return {
+ FontSettings: FontSettings
+ };
+});
+
diff --git a/chromium/chrome/browser/resources/options/geolocation_options.js b/chromium/chrome/browser/resources/options/geolocation_options.js
new file mode 100644
index 00000000000..1215ea9e11f
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/geolocation_options.js
@@ -0,0 +1,34 @@
+// Copyright 2013 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;
+
+ /**
+ * GeolocationOptions class
+ * Handles initialization of the geolocation options.
+ * @constructor
+ * @class
+ */
+ function GeolocationOptions() {
+ OptionsPage.call(this,
+ 'geolocationOptions',
+ loadTimeData.getString('geolocationOptionsPageTabTitle'),
+ 'geolocationCheckbox');
+ };
+
+ cr.addSingletonGetter(GeolocationOptions);
+
+ GeolocationOptions.prototype = {
+ __proto__: OptionsPage.prototype
+ };
+
+ // TODO(robliao): Determine if a full unroll is necessary
+ // (http://crbug.com/306613).
+ GeolocationOptions.showGeolocationOption = function() {};
+
+ return {
+ GeolocationOptions: GeolocationOptions
+ };
+});
diff --git a/chromium/chrome/browser/resources/options/handler_options.css b/chromium/chrome/browser/resources/options/handler_options.css
new file mode 100644
index 00000000000..116e5e7435f
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/handler_options.css
@@ -0,0 +1,55 @@
+/* Copyright (c) 2012 The Chromium Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file. */
+
+.handlers-column-headers {
+ display: -webkit-box;
+ font-size: 13px;
+ font-weight: bold;
+}
+
+.handlers-type-column {
+ -webkit-margin-end: 10px;
+ -webkit-margin-start: 14px;
+ width: 100px;
+}
+
+.handlers-site-column {
+ max-width: 180px;
+}
+
+.handlers-site-column select {
+ max-width: 170px;
+}
+
+.handlers-remove-column {
+ -webkit-box-flex: 1;
+}
+
+.handlers-remove-link {
+ -webkit-transition: 150ms opacity;
+ color: #555;
+ cursor: pointer;
+ opacity: 0;
+ padding-left: 14px;
+ text-decoration: underline;
+}
+
+div > .handlers-remove-column {
+ opacity: 0;
+}
+
+div:not(.none):hover > .handlers-remove-column {
+ opacity: 1;
+}
+
+#handlers {
+ min-height: 250px;
+}
+
+#handler-options list {
+ border: solid 1px #D9D9D9;
+ border-radius: 2px;
+ margin-bottom: 10px;
+ margin-top: 4px;
+}
diff --git a/chromium/chrome/browser/resources/options/handler_options.html b/chromium/chrome/browser/resources/options/handler_options.html
new file mode 100644
index 00000000000..4f45f018a3a
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/handler_options.html
@@ -0,0 +1,38 @@
+<div id="handler-options" class="page" hidden>
+ <div class="close-button"></div>
+ <h1 i18n-content="handlersPage"></h1>
+ <div class="content-area">
+ <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>
+ <div class="action-area">
+ <div class="button-strip">
+ <button id="handler-options-overlay-confirm" class="default-button"
+ i18n-content="done">
+ </button>
+ </div>
+ </div>
+</div>
diff --git a/chromium/chrome/browser/resources/options/handler_options.js b/chromium/chrome/browser/resources/options/handler_options.js
new file mode 100644
index 00000000000..894e13f72c0
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/handler_options.js
@@ -0,0 +1,80 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+ /** @const */ var OptionsPage = options.OptionsPage;
+
+ /////////////////////////////////////////////////////////////////////////////
+ // HandlerOptions class:
+
+ /**
+ * Encapsulated handling of handler options page.
+ * @constructor
+ */
+ function HandlerOptions() {
+ this.activeNavTab = null;
+ OptionsPage.call(this,
+ 'handlers',
+ loadTimeData.getString('handlersPageTabTitle'),
+ 'handler-options');
+ }
+
+ cr.addSingletonGetter(HandlerOptions);
+
+ HandlerOptions.prototype = {
+ __proto__: OptionsPage.prototype,
+
+ /**
+ * The handlers list.
+ * @type {DeletableItemList}
+ * @private
+ */
+ handlersList_: null,
+
+ /** @override */
+ initializePage: function() {
+ OptionsPage.prototype.initializePage.call(this);
+
+ this.createHandlersList_();
+
+ $('handler-options-overlay-confirm').onclick =
+ OptionsPage.closeOverlay.bind(OptionsPage);
+ },
+
+ /**
+ * 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 {Array} 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 {Array} 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/chromium/chrome/browser/resources/options/handler_options_list.js b/chromium/chrome/browser/resources/options/handler_options_list.js
new file mode 100644
index 00000000000..5572a55445f
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/handler_options_list.js
@@ -0,0 +1,227 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+ /** @const */ var ArrayDataModel = cr.ui.ArrayDataModel;
+ /** @const */ var List = cr.ui.List;
+ /** @const */ var ListItem = cr.ui.ListItem;
+ /** @const */ var HandlerOptions = options.HandlerOptions;
+ /** @const */ var DeletableItem = options.DeletableItem;
+ /** @const */ var DeletableItemList = options.DeletableItemList;
+
+ /**
+ * 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,
+
+ /** @override */
+ 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 =
+ loadTimeData.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 =
+ loadTimeData.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);
+ },
+
+ /** @override */
+ 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,
+
+ /** @override */
+ 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/chromium/chrome/browser/resources/options/home_page_overlay.css b/chromium/chrome/browser/resources/options/home_page_overlay.css
new file mode 100644
index 00000000000..853b256874c
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/home_page_overlay.css
@@ -0,0 +1,16 @@
+/* Copyright (c) 2012 The Chromium Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file. */
+
+#homepage-url-field {
+ bottom: 1px;
+ display: block;
+ margin-left: 10px;
+ position: relative;
+}
+
+/* -webkit-margin-start doesn't work as you'd hope on .weakrtl elements. */
+html[dir='rtl'] #homepage-url-field {
+ margin-left: 0;
+ margin-right: 10px;
+}
diff --git a/chromium/chrome/browser/resources/options/home_page_overlay.html b/chromium/chrome/browser/resources/options/home_page_overlay.html
new file mode 100644
index 00000000000..7fc78398d50
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/home_page_overlay.html
@@ -0,0 +1,53 @@
+<div id="home-page-overlay" class="page" role="dialog" hidden>
+ <div class="close-button"></div>
+ <h1 i18n-content="homePageOverlay"></h1>
+ <div class="content-area">
+ <div class="radio">
+ <span class="controlled-setting-with-label">
+ <input id="homepage-use-ntp" type="radio" name="homepage"
+ pref="homepage_is_newtabpage" value="true"
+ metric="Options_Homepage_IsNewTabPage" dialog-pref>
+ <span>
+ <label for="homepage-use-ntp" i18n-content="homePageUseNewTab">
+ </label>
+ <span class="controlled-setting-indicator"
+ pref="homepage_is_newtabpage" value="true" dialog-pref>
+ </span>
+ </span>
+ </span>
+ </div>
+ <div class="radio">
+ <span class="controlled-setting-with-label">
+ <input id="homepage-use-url" type="radio" name="homepage"
+ pref="homepage_is_newtabpage" value="false"
+ metric="Options_Homepage_IsNewTabPage" dialog-pref>
+ <span>
+ <label id="homepage-use-url-label" i18n-content="homePageUseURL"
+ for="homepage-use-url">
+ </label>
+ <span class="controlled-setting-indicator"
+ pref="homepage_is_newtabpage" value="false" dialog-pref>
+ </span>
+ <input id="homepage-url-field" type="url" data-type="url"
+ class="weakrtl favicon-cell stretch" pref="homepage"
+ aria-labelledby="homepage-use-url-label"
+ dialog-pref>
+ </input>
+ <span id="homepage-url-field-indicator"
+ class="controlled-setting-indicator" pref="homepage"
+ dialog-pref>
+ </span>
+ </span>
+ </span>
+ </div>
+ </div>
+ <div class="action-area">
+ <div class="button-strip">
+ <button id="home-page-cancel" type="reset" i18n-content="cancel">
+ </button>
+ <button id="home-page-confirm" class="default-button"
+ i18n-content="ok">
+ </button>
+ </div>
+ </div>
+</div>
diff --git a/chromium/chrome/browser/resources/options/home_page_overlay.js b/chromium/chrome/browser/resources/options/home_page_overlay.js
new file mode 100644
index 00000000000..6649f930e2e
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/home_page_overlay.js
@@ -0,0 +1,155 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+ /** @const */ var OptionsPage = options.OptionsPage;
+ /** @const */ var SettingsDialog = options.SettingsDialog;
+
+ /**
+ * HomePageOverlay class
+ * Dialog that allows users to set the home page.
+ * @extends {SettingsDialog}
+ */
+ function HomePageOverlay() {
+ SettingsDialog.call(this, 'homePageOverlay',
+ loadTimeData.getString('homePageOverlayTabTitle'),
+ 'home-page-overlay',
+ $('home-page-confirm'), $('home-page-cancel'));
+ }
+
+ cr.addSingletonGetter(HomePageOverlay);
+
+ HomePageOverlay.prototype = {
+ __proto__: SettingsDialog.prototype,
+
+ /**
+ * An autocomplete list that can be attached to the home page URL field.
+ * @type {cr.ui.AutocompleteList}
+ * @private
+ */
+ autocompleteList_: null,
+
+ /**
+ * Initialize the page.
+ */
+ initializePage: function() {
+ // Call base class implementation to start preference initialization.
+ SettingsDialog.prototype.initializePage.call(this);
+
+ var self = this;
+ options.Preferences.getInstance().addEventListener(
+ 'homepage_is_newtabpage',
+ this.handleHomepageIsNTPPrefChange.bind(this));
+
+ var urlField = $('homepage-url-field');
+ urlField.addEventListener('keydown', function(event) {
+ // Don't auto-submit when the user selects something from the
+ // auto-complete list.
+ if (event.keyIdentifier == 'Enter' && !self.autocompleteList_.hidden)
+ event.stopPropagation();
+ });
+ urlField.addEventListener('change', this.updateFavicon_.bind(this));
+
+ var suggestionList = new cr.ui.AutocompleteList();
+ suggestionList.autoExpands = true;
+ suggestionList.requestSuggestions =
+ this.requestAutocompleteSuggestions_.bind(this);
+ $('home-page-overlay').appendChild(suggestionList);
+ this.autocompleteList_ = suggestionList;
+
+ urlField.addEventListener('focus', function(event) {
+ self.autocompleteList_.attachToInput(urlField);
+ });
+ urlField.addEventListener('blur', function(event) {
+ self.autocompleteList_.detach();
+ });
+ },
+
+ /** @override */
+ didShowPage: function() {
+ this.updateFavicon_();
+ },
+
+ /**
+ * Updates the state of the homepage text input and its controlled setting
+ * indicator when the |homepage_is_newtabpage| pref changes. The input is
+ * enabled only if the homepage is not the NTP. The indicator is always
+ * enabled but treats the input's value as read-only if the homepage is the
+ * NTP.
+ * @param {Event} Pref change event.
+ */
+ handleHomepageIsNTPPrefChange: function(event) {
+ var urlField = $('homepage-url-field');
+ var urlFieldIndicator = $('homepage-url-field-indicator');
+ urlField.setDisabled('homepage-is-ntp', event.value.value);
+ urlFieldIndicator.readOnly = event.value.value;
+ },
+
+ /**
+ * Updates the background of the url field to show the favicon for the
+ * URL that is currently typed in.
+ * @private
+ */
+ updateFavicon_: function() {
+ var urlField = $('homepage-url-field');
+ urlField.style.backgroundImage = getFaviconImageSet(urlField.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('requestAutocompleteSuggestionsForHomePage', [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;
+ },
+
+ /**
+ * Sets the 'show home button' and 'home page is new tab page' preferences.
+ * (The home page url preference is set automatically by the SettingsDialog
+ * code.)
+ */
+ handleConfirm: function() {
+ // Strip whitespace.
+ var urlField = $('homepage-url-field');
+ var homePageValue = urlField.value.replace(/\s*/g, '');
+ urlField.value = homePageValue;
+
+ // Don't save an empty URL for the home page. If the user left the field
+ // empty, switch to the New Tab page.
+ if (!homePageValue)
+ $('homepage-use-ntp').checked = true;
+
+ SettingsDialog.prototype.handleConfirm.call(this);
+ },
+ };
+
+ HomePageOverlay.updateAutocompleteSuggestions = function() {
+ var instance = HomePageOverlay.getInstance();
+ instance.updateAutocompleteSuggestions_.apply(instance, arguments);
+ };
+
+ // Export
+ return {
+ HomePageOverlay: HomePageOverlay
+ };
+});
diff --git a/chromium/chrome/browser/resources/options/import_data_overlay.css b/chromium/chrome/browser/resources/options/import_data_overlay.css
new file mode 100644
index 00000000000..d7c076d3ed8
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/import_data_overlay.css
@@ -0,0 +1,30 @@
+/* Copyright (c) 2012 The Chromium Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file. */
+
+#import-data-overlay {
+ width: 400px;
+}
+
+#import-from-div {
+ margin-bottom: 20px;
+}
+
+#import-checkboxes > div:not(:first-child) {
+ -webkit-padding-start: 8px;
+}
+
+#import-throbber {
+ margin: 4px 10px;
+ vertical-align: middle;
+ visibility: hidden;
+}
+
+#import-success-header {
+ font-size: 1.2em;
+}
+
+#import-success-image {
+ margin: 20px;
+ text-align: center;
+}
diff --git a/chromium/chrome/browser/resources/options/import_data_overlay.html b/chromium/chrome/browser/resources/options/import_data_overlay.html
new file mode 100644
index 00000000000..57215089b87
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/import_data_overlay.html
@@ -0,0 +1,122 @@
+<div id="import-data-overlay" class="page" hidden>
+ <div class="close-button"></div>
+ <h1 i18n-content="importDataOverlay"></h1>
+ <div class="content-area">
+ <div class="import-data-configure">
+ <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 class="checkbox">
+ <span id="import-history-with-label"
+ class="controlled-setting-with-label">
+ <input id="import-history" type="checkbox" pref="import_history">
+ <span>
+ <label for="import-history" i18n-content="importHistory"></label>
+ <span class="controlled-setting-indicator" pref="import_history">
+ </span>
+ </span>
+ </span>
+ </div>
+ <div class="checkbox">
+ <span id="import-favorites-with-label"
+ class="controlled-setting-with-label">
+ <input id="import-favorites" type="checkbox"
+ pref="import_bookmarks">
+ <span>
+ <label for="import-favorites" i18n-content="importFavorites">
+ </label>
+ <span class="controlled-setting-indicator"
+ pref="import_bookmarks">
+ </span>
+ </span>
+ </span>
+ </div>
+ <div class="checkbox">
+ <span id="import-passwords-with-label"
+ class="controlled-setting-with-label">
+ <input id="import-passwords" type="checkbox"
+ pref="import_saved_passwords">
+ <span>
+ <label for="import-passwords" i18n-content="importPasswords">
+ </label>
+ <span class="controlled-setting-indicator"
+ pref="import_saved_passwords">
+ </span>
+ </span>
+ </span>
+ </div>
+ <div class="checkbox">
+ <span id="import-search-with-label"
+ class="controlled-setting-with-label">
+ <input id="import-search" type="checkbox"
+ pref="import_search_engine">
+ <span>
+ <label for="import-search" i18n-content="importSearch"></label>
+ <span class="controlled-setting-indicator"
+ pref="import_search_engine">
+ </span>
+ </span>
+ </span>
+ </div>
+ </div>
+ </div>
+ <div class="import-data-success" hidden>
+ <div id="import-success-header">
+ <strong i18n-content="importSucceeded"></strong>
+ </div>
+ <div id="import-success-image">
+ <img src="../../../../ui/webui/resources/images/success.png">
+ </div>
+ <div id="import-find-your-bookmarks">
+ <span i18n-content="findYourImportedBookmarks"></span>
+ <div class="checkbox">
+ <span class="controlled-setting-with-label">
+ <input id="import-data-show-bookmarks-bar"
+ pref="bookmark_bar.show_on_all_tabs"
+ metric="Options_ShowBookmarksBar" type="checkbox">
+ <span>
+ <label for="import-data-show-bookmarks-bar"
+ i18n-content="toolbarShowBookmarksBar">
+ </label>
+ <span class="controlled-setting-indicator"
+ pref="bookmark_bar.show_on_all_tabs">
+ </span>
+ </span>
+ </span>
+ </div>
+ </div>
+ </div>
+ </div>
+ <div class="action-area">
+ <div class="import-data-configure">
+ <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-choose-file" i18n-content="importChooseFile">
+ </button>
+ <button id="import-data-commit" class="default-button"
+ i18n-content="importCommit">
+ </button>
+ </div>
+ </div>
+ </div>
+ <div class="import-data-success" hidden>
+ <div class="action-area-right">
+ <div class="button-strip">
+ <button id="import-data-confirm" class="default-button"
+ i18n-content="done">
+ </button>
+ </div>
+ </div>
+ </div>
+ </div>
+ <div id="mac-password-keychain" class="gray-bottom-bar">
+ <span i18n-content="macPasswordKeychain"></span>
+ </div>
+</div>
diff --git a/chromium/chrome/browser/resources/options/import_data_overlay.js b/chromium/chrome/browser/resources/options/import_data_overlay.js
new file mode 100644
index 00000000000..16b8cca6ff2
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/import_data_overlay.js
@@ -0,0 +1,277 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+ var OptionsPage = options.OptionsPage;
+
+ /**
+ * ImportDataOverlay class
+ * Encapsulated handling of the 'Import Data' overlay page.
+ * @class
+ */
+ function ImportDataOverlay() {
+ OptionsPage.call(this,
+ 'importData',
+ loadTimeData.getString('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_();
+ self.updateBottomBar_();
+ };
+
+ $('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-choose-file').onclick = function() {
+ chrome.send('chooseBookmarksFile');
+ };
+
+ $('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.setAllControlsEnabled_(false);
+ },
+
+ /**
+ * Sets the 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;
+ $('import-choose-file').disabled = !$('import-favorites').checked;
+ },
+
+ /**
+ * Sets the enabled state of all the checkboxes and the commit button.
+ * @private
+ */
+ setAllControlsEnabled_: function(enabled) {
+ var checkboxes =
+ document.querySelectorAll('#import-checkboxes input[type=checkbox]');
+ for (var i = 0; i < checkboxes.length; i++)
+ this.setUpCheckboxState_(checkboxes[i], enabled);
+ $('import-data-commit').disabled = !enabled;
+ $('import-choose-file').hidden = !enabled;
+ $('mac-password-keychain').hidden = !enabled;
+ },
+
+ /**
+ * Sets the enabled state of a checkbox element.
+ * @param {Object} checkbox A checkbox element.
+ * @param {boolean} enabled The enabled state of the checkbox. If false,
+ * the checkbox is disabled. If true, the checkbox is enabled.
+ * @private
+ */
+ setUpCheckboxState_: function(checkbox, enabled) {
+ checkbox.setDisabled('noProfileData', !enabled);
+ },
+
+ /**
+ * Update the enabled and visible states of all the checkboxes.
+ * @private
+ */
+ updateCheckboxes_: function() {
+ var index = $('import-browsers').selectedIndex;
+ var bookmarksFileSelected = index == this.browserProfiles.length - 1;
+ $('import-choose-file').hidden = !bookmarksFileSelected;
+ $('import-data-commit').hidden = bookmarksFileSelected;
+
+ 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);
+ var checkboxWithLabel = $('import-' + importOptions[i] + '-with-label');
+ checkboxWithLabel.style.display = enable ? '' : 'none';
+ }
+ },
+
+ /**
+ * Show or hide gray message at the bottom.
+ * @private
+ */
+ updateBottomBar_: function() {
+ var index = $('import-browsers').selectedIndex;
+ var browserProfile;
+ if (this.browserProfiles.length > index)
+ browserProfile = this.browserProfiles[index];
+ var enable = browserProfile && browserProfile['show_bottom_bar'];
+ $('mac-password-keychain').hidden = !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(loadTimeData.getString('noProfileFound'), 0);
+ browserSelect.appendChild(option);
+
+ this.setAllControlsEnabled_(false);
+ } else {
+ this.setAllControlsEnabled_(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_();
+ this.updateBottomBar_();
+ }
+ },
+
+ /**
+ * 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], true);
+ },
+
+ /**
+ * Update the dialog layout to reflect success state.
+ * @param {boolean} success If true, show success dialog elements.
+ * @private
+ */
+ updateSuccessState_: function(success) {
+ var sections = document.querySelectorAll('.import-data-configure');
+ for (var i = 0; i < sections.length; i++)
+ sections[i].hidden = success;
+
+ sections = document.querySelectorAll('.import-data-success');
+ for (var i = 0; i < sections.length; i++)
+ sections[i].hidden = !success;
+ },
+ };
+
+ 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} importing True if an import operation is in progress.
+ */
+ ImportDataOverlay.setImportingState = function(importing) {
+ var checkboxes =
+ document.querySelectorAll('#import-checkboxes input[type=checkbox]');
+ for (var i = 0; i < checkboxes.length; i++)
+ checkboxes[i].setDisabled('Importing', importing);
+ if (!importing)
+ ImportDataOverlay.getInstance().updateCheckboxes_();
+ $('import-browsers').disabled = importing;
+ $('import-throbber').style.visibility = importing ? '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-find-your-bookmarks').hidden = !showBookmarksMessage;
+ ImportDataOverlay.getInstance().updateSuccessState_(true);
+ };
+
+ /**
+ * Show the import data overlay.
+ */
+ ImportDataOverlay.show = function() {
+ // Make sure that any previous import success message is hidden, and
+ // we're showing the UI to import further data.
+ ImportDataOverlay.getInstance().updateSuccessState_(false);
+ ImportDataOverlay.getInstance().validateCommitButton_();
+
+ OptionsPage.navigateToPage('importData');
+ };
+
+ // Export
+ return {
+ ImportDataOverlay: ImportDataOverlay
+ };
+});
diff --git a/chromium/chrome/browser/resources/options/inline_editable_list.js b/chromium/chrome/browser/resources/options/inline_editable_list.js
new file mode 100644
index 00000000000..7a502fa46ba
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/inline_editable_list.js
@@ -0,0 +1,459 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+ /** @const */ var DeletableItem = options.DeletableItem;
+ /** @const */ var 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,
+
+ /** @override */
+ decorate: function() {
+ DeletableItem.prototype.decorate.call(this);
+
+ this.editFields_ = [];
+ this.addEventListener('mousedown', this.handleMouseDown_);
+ this.addEventListener('keydown', this.handleKeyDown_);
+ this.addEventListener('leadChange', this.handleLeadChange_);
+ },
+
+ /** @override */
+ 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;
+
+ if (focusElement) {
+ var self = this;
+ // We should delay to give focus on |focusElement| if this is called
+ // in mousedown event handler. If we did give focus immediately, Blink
+ // would try to focus on an ancestor of the mousedown target element,
+ // and remove focus from |focusElement|.
+ if (focusElement.staticVersion &&
+ focusElement.staticVersion.hasAttribute('tabindex')) {
+ setTimeout(function() {
+ if (self.editing) {
+ if (focusElement.disabled)
+ self.parentNode.focus();
+ self.focusAndMaybeSelect_(focusElement);
+ }
+ focusElement.staticVersion.removeAttribute('tabindex');
+ }, 0);
+ } else {
+ this.focusAndMaybeSelect_(focusElement);
+ }
+ }
+ } 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);
+ }
+ }
+ },
+
+ /**
+ * Focus on the specified element, and select the editable text in it
+ * if possible.
+ * @param {!Element} control An element to be focused.
+ * @private
+ */
+ focusAndMaybeSelect_: function(control) {
+ control.focus();
+ if (control.tagName == 'INPUT')
+ control.select();
+ },
+
+ /**
+ * 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 overridden 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 overridden 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 overridden 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');
+ var textEl;
+ if (!this.isPlaceholder) {
+ 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');
+ } 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);
+ }
+
+ // In some cases 'focus' event may arrive before 'input'.
+ // To make sure revalidation is triggered we postpone 'focus' handling.
+ var handler = this.handleFocus_.bind(this);
+ inputEl.addEventListener('focus', function() {
+ window.setTimeout(function() {
+ if (inputEl.ownerDocument.activeElement == inputEl)
+ handler();
+ }, 0);
+ });
+ container.appendChild(inputEl);
+ this.addEditField(inputEl, textEl);
+
+ return container;
+ },
+
+ /**
+ * Register an edit field.
+ * @param {!Element} control An editable element. It's a form control
+ * element typically.
+ * @param {Element} staticElement An element representing non-editable
+ * state.
+ */
+ addEditField: function(control, staticElement) {
+ control.staticVersion = staticElement;
+ this.editFields_.push(control);
+ },
+
+ /**
+ * 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 when a key is pressed. Handles committing and canceling edits.
+ * @param {Event} e The key down event.
+ * @private
+ */
+ handleKeyDown_: function(e) {
+ if (!this.editing)
+ return;
+
+ var endEdit = false;
+ var handledKey = true;
+ switch (e.keyIdentifier) {
+ case 'U+001B': // Esc
+ this.editCancelled_ = true;
+ endEdit = true;
+ break;
+ case 'Enter':
+ if (this.currentInputIsValid)
+ endEdit = true;
+ break;
+ default:
+ handledKey = false;
+ }
+ if (handledKey) {
+ // 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();
+ }
+ if (endEdit) {
+ // Blurring will trigger the edit to end; see InlineEditableItemList.
+ this.ownerDocument.activeElement.blur();
+ }
+ },
+
+ /**
+ * 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].staticVersion == clickTarget)
+ clickTarget.tabIndex = 0;
+ 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,
+
+ /** @override */
+ 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 {boolean} True if the placeholder element should be focused on
+ * edit commit.
+ */
+ shouldFocusPlaceholder: function() {
+ return true;
+ },
+ };
+
+ // Export
+ return {
+ InlineEditableItem: InlineEditableItem,
+ InlineEditableItemList: InlineEditableItemList,
+ };
+});
diff --git a/chromium/chrome/browser/resources/options/language_add_language_overlay.html b/chromium/chrome/browser/resources/options/language_add_language_overlay.html
new file mode 100644
index 00000000000..ca8947a6f53
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/language_add_language_overlay.html
@@ -0,0 +1,16 @@
+<div id="add-language-overlay-page" class="page" role="dialog" hidden>
+ <div class="close-button"></div>
+ <h1 i18n-content="addLanguageTitle"></h1>
+ <div class="content-area">
+ <label for="add-language-overlay-language-list"
+ i18n-content="addLanguageSelectLabel"></label>
+ <select id="add-language-overlay-language-list"></select>
+ </div>
+ <div class="action-area button-strip">
+ <button id="add-language-overlay-cancel-button" i18n-content="cancel">
+ </button>
+ <button id="add-language-overlay-ok-button" class="default-button"
+ i18n-content="ok">
+ </button>
+ </div>
+</div>
diff --git a/chromium/chrome/browser/resources/options/language_add_language_overlay.js b/chromium/chrome/browser/resources/options/language_add_language_overlay.js
new file mode 100644
index 00000000000..f91d170922c
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/language_add_language_overlay.js
@@ -0,0 +1,61 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+///////////////////////////////////////////////////////////////////////////////
+// AddLanguageOverlay class:
+
+cr.define('options', function() {
+ /** @const */ var OptionsPage = options.OptionsPage;
+
+ /**
+ * Encapsulated handling of ChromeOS add language overlay page.
+ * @constructor
+ */
+ function AddLanguageOverlay() {
+ OptionsPage.call(this, 'addLanguage',
+ loadTimeData.getString('addButton'),
+ '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 = loadTimeData.getValue('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;
+
+ var option = cr.doc.createElement('option');
+ option.value = language.code;
+ option.textContent = displayText;
+ addLanguageList.appendChild(option);
+ }
+ },
+ };
+
+ return {
+ AddLanguageOverlay: AddLanguageOverlay
+ };
+});
diff --git a/chromium/chrome/browser/resources/options/language_dictionary_overlay.css b/chromium/chrome/browser/resources/options/language_dictionary_overlay.css
new file mode 100644
index 00000000000..b8400479207
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/language_dictionary_overlay.css
@@ -0,0 +1,47 @@
+/* Copyright (c) 2012 The Chromium Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file. */
+
+#language-dictionary-overlay-no-matches {
+ -webkit-margin-after: 0;
+ -webkit-margin-before: 0;
+ line-height: 2.5em;
+ padding: 0 3px;
+}
+
+#language-dictionary-overlay-page h1 {
+ margin-right: 16em;
+ padding-top: 25px;
+}
+
+html[dir=rtl] #language-dictionary-overlay-page h1 {
+ margin-left: 16em;
+ margin-right: auto;
+}
+
+#language-dictionary-overlay-search-field {
+ position: absolute;
+ right: 32px;
+ top: 21px;
+}
+
+html[dir=rtl] #language-dictionary-overlay-search-field {
+ left: 32px;
+ right: auto;
+}
+
+#language-dictionary-overlay-word-list {
+ height: 20em;
+}
+
+#language-dictionary-overlay-word-list.no-search-matches {
+ height: 17.5em;
+}
+
+#language-dictionary-overlay-word-list > * {
+ height: 2.5em;
+}
+
+.language-dictionary-overlay-word-list-item {
+ width: 30em;
+}
diff --git a/chromium/chrome/browser/resources/options/language_dictionary_overlay.html b/chromium/chrome/browser/resources/options/language_dictionary_overlay.html
new file mode 100644
index 00000000000..34b64055c2e
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/language_dictionary_overlay.html
@@ -0,0 +1,20 @@
+<div id="language-dictionary-overlay-page" class="page" hidden>
+ <div class="close-button"></div>
+ <h1 i18n-content="languageDictionaryOverlayTitle"></h1>
+ <input id="language-dictionary-overlay-search-field" type="search"
+ i18n-values="placeholder:languageDictionaryOverlaySearchPlaceholder;
+ aria-label:languageDictionaryOverlaySearchPlaceholder"
+ incremental>
+ <div class="content-area">
+ <div class="settings-list">
+ <p id="language-dictionary-overlay-no-matches"
+ i18n-content="languageDictionaryOverlayNoMatches" hidden></p>
+ <list id="language-dictionary-overlay-word-list"></list>
+ </div>
+ </div>
+ <div class="action-area button-strip">
+ <button id="language-dictionary-overlay-done-button" class="default-button"
+ i18n-content="done">
+ </button>
+ </div>
+</div>
diff --git a/chromium/chrome/browser/resources/options/language_dictionary_overlay.js b/chromium/chrome/browser/resources/options/language_dictionary_overlay.js
new file mode 100644
index 00000000000..63152995569
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/language_dictionary_overlay.js
@@ -0,0 +1,118 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+ /** @const */ var DictionaryWordsList =
+ options.dictionary_words.DictionaryWordsList;
+ /** @const */ var OptionsPage = options.OptionsPage;
+
+ /**
+ * Adding and removing words in custom spelling dictionary.
+ * @constructor
+ * @extends {options.OptionsPage}
+ */
+ function EditDictionaryOverlay() {
+ OptionsPage.call(this, 'editDictionary',
+ loadTimeData.getString('languageDictionaryOverlayPage'),
+ 'language-dictionary-overlay-page');
+ }
+
+ cr.addSingletonGetter(EditDictionaryOverlay);
+
+ EditDictionaryOverlay.prototype = {
+ __proto__: OptionsPage.prototype,
+
+ /**
+ * A list of words in the dictionary.
+ * @type {DictionaryWordsList}
+ * @private
+ */
+ wordList_: null,
+
+ /**
+ * The input field for searching for words in the dictionary.
+ * @type {HTMLElement}
+ * @private
+ */
+ searchField_: null,
+
+ /**
+ * The paragraph of text that indicates that search returned no results.
+ * @type {HTMLElement}
+ * @private
+ */
+ noMatchesLabel_: null,
+
+ /**
+ * Initializes the edit dictionary overlay.
+ * @override
+ */
+ initializePage: function() {
+ OptionsPage.prototype.initializePage.call(this);
+
+ this.wordList_ = $('language-dictionary-overlay-word-list');
+ DictionaryWordsList.decorate(this.wordList_);
+ this.wordList_.onWordListChanged = function() {
+ this.onWordListChanged_();
+ }.bind(this);
+
+ this.searchField_ = $('language-dictionary-overlay-search-field');
+ this.searchField_.onsearch = function(e) {
+ this.wordList_.search(e.currentTarget.value);
+ }.bind(this);
+ this.searchField_.onkeydown = function(e) {
+ // Don't propagate enter key events. Otherwise the default button will
+ // activate.
+ if (e.keyIdentifier == 'Enter')
+ e.stopPropagation();
+ };
+
+ this.noMatchesLabel_ = getRequiredElement(
+ 'language-dictionary-overlay-no-matches');
+
+ $('language-dictionary-overlay-done-button').onclick = function(e) {
+ OptionsPage.closeOverlay();
+ };
+ },
+
+ /**
+ * Refresh the dictionary words when the page is displayed.
+ * @override
+ */
+ didShowPage: function() {
+ chrome.send('refreshDictionaryWords');
+ },
+
+ /**
+ * Update the view based on the changes in the word list.
+ * @private
+ */
+ onWordListChanged_: function() {
+ if (this.searchField_.value.length > 0 && this.wordList_.empty) {
+ this.noMatchesLabel_.hidden = false;
+ this.wordList_.classList.add('no-search-matches');
+ } else {
+ this.noMatchesLabel_.hidden = true;
+ this.wordList_.classList.remove('no-search-matches');
+ }
+ },
+ };
+
+ EditDictionaryOverlay.setWordList = function(entries) {
+ EditDictionaryOverlay.getInstance().wordList_.setWordList(entries);
+ };
+
+ EditDictionaryOverlay.updateWords = function(add_words, remove_words) {
+ EditDictionaryOverlay.getInstance().wordList_.addWords(add_words);
+ EditDictionaryOverlay.getInstance().wordList_.removeWords(remove_words);
+ };
+
+ EditDictionaryOverlay.getWordListForTesting = function() {
+ return EditDictionaryOverlay.getInstance().wordList_;
+ };
+
+ return {
+ EditDictionaryOverlay: EditDictionaryOverlay
+ };
+});
diff --git a/chromium/chrome/browser/resources/options/language_dictionary_overlay_word_list.js b/chromium/chrome/browser/resources/options/language_dictionary_overlay_word_list.js
new file mode 100644
index 00000000000..611c56f090f
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/language_dictionary_overlay_word_list.js
@@ -0,0 +1,239 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options.dictionary_words', function() {
+ /** @const */ var InlineEditableItemList = options.InlineEditableItemList;
+ /** @const */ var InlineEditableItem = options.InlineEditableItem;
+
+ /**
+ * Creates a new dictionary word list item.
+ * @param {string} dictionaryWord The dictionary word.
+ * @param {function(string)} addDictionaryWordCallback Callback for
+ * adding a dictionary word.
+ * @constructor
+ * @extends {cr.ui.InlineEditableItem}
+ */
+ function DictionaryWordsListItem(dictionaryWord, addDictionaryWordCallback) {
+ var el = cr.doc.createElement('div');
+ el.dictionaryWord_ = dictionaryWord;
+ el.addDictionaryWordCallback_ = addDictionaryWordCallback;
+ DictionaryWordsListItem.decorate(el);
+ return el;
+ }
+
+ /**
+ * Decorates an HTML element as a dictionary word list item.
+ * @param {HTMLElement} el The element to decorate.
+ */
+ DictionaryWordsListItem.decorate = function(el) {
+ el.__proto__ = DictionaryWordsListItem.prototype;
+ el.decorate();
+ };
+
+ DictionaryWordsListItem.prototype = {
+ __proto__: InlineEditableItem.prototype,
+
+ /** @override */
+ decorate: function() {
+ InlineEditableItem.prototype.decorate.call(this);
+ if (this.dictionaryWord_ == '')
+ this.isPlaceholder = true;
+ else
+ this.editable = false;
+
+ var wordEl = this.createEditableTextCell(this.dictionaryWord_);
+ wordEl.classList.add('weakrtl');
+ wordEl.classList.add('language-dictionary-overlay-word-list-item');
+ this.contentElement.appendChild(wordEl);
+
+ var wordField = wordEl.querySelector('input');
+ wordField.classList.add('language-dictionary-overlay-word-list-item');
+ if (this.isPlaceholder) {
+ wordField.placeholder =
+ loadTimeData.getString('languageDictionaryOverlayAddWordLabel');
+ }
+
+ this.addEventListener('commitedit', this.onEditCommitted_.bind(this));
+ },
+
+ /** @override */
+ get hasBeenEdited() {
+ return this.querySelector('input').value.length > 0;
+ },
+
+ /**
+ * Adds a word to the dictionary.
+ * @param {Event} e Edit committed event.
+ * @private
+ */
+ onEditCommitted_: function(e) {
+ var input = e.currentTarget.querySelector('input');
+ this.addDictionaryWordCallback_(input.value);
+ input.value = '';
+ },
+ };
+
+ /**
+ * A list of words in the dictionary.
+ * @extends {cr.ui.InlineEditableItemList}
+ */
+ var DictionaryWordsList = cr.ui.define('list');
+
+ DictionaryWordsList.prototype = {
+ __proto__: InlineEditableItemList.prototype,
+
+ /**
+ * The function to notify that the word list has changed.
+ * @type {function()}
+ */
+ onWordListChanged: null,
+
+ /**
+ * The list of all words in the dictionary. Used to generate a filtered word
+ * list in the |search(searchTerm)| method.
+ * @type {Array}
+ * @private
+ */
+ allWordsList_: null,
+
+ /**
+ * The list of words that the user removed, but |DictionaryWordList| has not
+ * received a notification of their removal yet.
+ * @type {Array}
+ * @private
+ */
+ removedWordsList_: [],
+
+ /**
+ * Adds a dictionary word.
+ * @param {string} dictionaryWord The word to add.
+ * @private
+ */
+ addDictionaryWord_: function(dictionaryWord) {
+ this.allWordsList_.push(dictionaryWord);
+ this.dataModel.splice(this.dataModel.length - 1, 0, dictionaryWord);
+ this.onWordListChanged();
+ chrome.send('addDictionaryWord', [dictionaryWord]);
+ },
+
+ /**
+ * Searches the list for the matching words.
+ * @param {string} searchTerm The search term.
+ */
+ search: function(searchTerm) {
+ var filteredWordList = this.allWordsList_.filter(
+ function(element, index, array) {
+ return element.indexOf(searchTerm) > -1;
+ });
+ filteredWordList.push('');
+ this.dataModel = new cr.ui.ArrayDataModel(filteredWordList);
+ this.onWordListChanged();
+ },
+
+ /**
+ * Sets the list of dictionary words.
+ * @param {Array} entries The list of dictionary words.
+ */
+ setWordList: function(entries) {
+ this.allWordsList_ = entries.slice(0);
+ // Empty string is a placeholder for entering new words.
+ entries.push('');
+ this.dataModel = new cr.ui.ArrayDataModel(entries);
+ this.onWordListChanged();
+ },
+
+ /**
+ * Adds non-duplicate dictionary words.
+ * @param {Array} entries The list of dictionary words.
+ */
+ addWords: function(entries) {
+ var toAdd = [];
+ for (var i = 0; i < entries.length; i++) {
+ if (this.allWordsList_.indexOf(entries[i]) == -1) {
+ this.allWordsList_.push(entries[i]);
+ toAdd.push(entries[i]);
+ }
+ }
+ if (toAdd.length == 0)
+ return;
+ for (var i = 0; i < toAdd.length; i++)
+ this.dataModel.splice(this.dataModel.length - 1, 0, toAdd[i]);
+ this.onWordListChanged();
+ },
+
+ /**
+ * Removes dictionary words that are not in |removedWordsList_|. If a word
+ * is in |removedWordsList_|, then removes the word from there instead.
+ * @param {Array} entries The list of dictionary words.
+ */
+ removeWords: function(entries) {
+ var index;
+ var toRemove = [];
+ for (var i = 0; i < entries.length; i++) {
+ index = this.removedWordsList_.indexOf(entries[i]);
+ if (index > -1) {
+ this.removedWordsList_.splice(index, 1);
+ } else {
+ index = this.allWordsList_.indexOf(entries[i]);
+ if (index > -1) {
+ this.allWordsList_.splice(index, 1);
+ toRemove.push(entries[i]);
+ }
+ }
+ }
+ if (toRemove.length == 0)
+ return;
+ for (var i = 0; i < toRemove.length; i++) {
+ index = this.dataModel.indexOf(toRemove[i]);
+ if (index > -1)
+ this.dataModel.splice(index, 1);
+ }
+ this.onWordListChanged();
+ },
+
+ /**
+ * Returns true if the data model contains no words, otherwise returns
+ * false.
+ * @type {boolean}
+ */
+ get empty() {
+ // A data model without dictionary words contains one placeholder for
+ // entering new words.
+ return this.dataModel.length < 2;
+ },
+
+ /** @override */
+ createItem: function(dictionaryWord) {
+ return new DictionaryWordsListItem(
+ dictionaryWord,
+ this.addDictionaryWord_.bind(this));
+ },
+
+ /** @override */
+ deleteItemAtIndex: function(index) {
+ // The last element in the data model is an undeletable placeholder for
+ // entering new words.
+ assert(index > -1 && index < this.dataModel.length - 1);
+ var item = this.dataModel.item(index);
+ var allWordsListIndex = this.allWordsList_.indexOf(item);
+ assert(allWordsListIndex > -1);
+ this.allWordsList_.splice(allWordsListIndex, 1);
+ this.dataModel.splice(index, 1);
+ this.removedWordsList_.push(item);
+ this.onWordListChanged();
+ chrome.send('removeDictionaryWord', [item]);
+ },
+
+ /** @override */
+ shouldFocusPlaceholder: function() {
+ return false;
+ },
+ };
+
+ return {
+ DictionaryWordsList: DictionaryWordsList
+ };
+
+});
+
diff --git a/chromium/chrome/browser/resources/options/language_list.js b/chromium/chrome/browser/resources/options/language_list.js
new file mode 100644
index 00000000000..d4ea5bd4f22
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/language_list.js
@@ -0,0 +1,440 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+ /** @const */ var ArrayDataModel = cr.ui.ArrayDataModel;
+ /** @const */ var DeletableItem = options.DeletableItem;
+ /** @const */ var DeletableItemList = options.DeletableItemList;
+ /** @const */ var List = cr.ui.List;
+ /** @const */ var ListItem = cr.ui.ListItem;
+ /** @const */ var ListSingleSelectionModel = cr.ui.ListSingleSelectionModel;
+
+ /**
+ * Creates a new Language list item.
+ * @param {Object} languageInfo The information of the language.
+ * @constructor
+ * @extends {DeletableItem.ListItem}
+ */
+ function LanguageListItem(languageInfo) {
+ var el = cr.doc.createElement('li');
+ el.__proto__ = LanguageListItem.prototype;
+ el.language_ = languageInfo;
+ el.decorate();
+ return el;
+ };
+
+ LanguageListItem.prototype = {
+ __proto__: DeletableItem.prototype,
+
+ /**
+ * The language code of this language.
+ * @type {string}
+ * @private
+ */
+ languageCode_: null,
+
+ /** @override */
+ decorate: function() {
+ DeletableItem.prototype.decorate.call(this);
+
+ var languageCode = this.language_.code;
+ 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.dir = this.language_.textDirection;
+ this.languageName.textContent = this.language_.displayName;
+ this.contentElement.appendChild(this.languageName);
+ this.title = this.language_.nativeDisplayName;
+ 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 information of a language from the given language code.
+ * @param {string} languageCode Language code (ex. "fr").
+ */
+ LanguageList.getLanguageInfoFromLanguageCode = function(languageCode) {
+ // Build the language code to language info dictionary at first time.
+ if (!this.languageCodeToLanguageInfo_) {
+ this.languageCodeToLanguageInfo_ = {};
+ var languageList = loadTimeData.getValue('languageList');
+ for (var i = 0; i < languageList.length; i++) {
+ var languageInfo = languageList[i];
+ this.languageCodeToLanguageInfo_[languageInfo.code] = languageInfo;
+ }
+ }
+
+ return this.languageCodeToLanguageInfo_[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.getLanguageInfoFromLanguageCode(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',
+
+ /** @override */
+ 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) {
+ languageInfo = LanguageList.getLanguageInfoFromLanguageCode(languageCode);
+ return new LanguageListItem(languageInfo);
+ },
+
+ /*
+ * 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();
+ },
+
+ /*
+ * Clears the selection
+ */
+ clearSelection: function() {
+ this.selectionModel.unselectAll();
+ },
+
+ /*
+ * 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;
+ },
+
+ /** @override */
+ 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() {
+ chrome.send('updateLanguageList', [this.dataModel.slice()]);
+ cr.dispatchSimpleEvent(this, 'save');
+ },
+
+ /**
+ * 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/chromium/chrome/browser/resources/options/language_options.css b/chromium/chrome/browser/resources/options/language_options.css
new file mode 100644
index 00000000000..4e86bbbe02b
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/language_options.css
@@ -0,0 +1,191 @@
+/* Copyright (c) 2012 The Chromium Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file. */
+
+.language-options {
+ display: -webkit-box;
+ margin: 10px 0;
+}
+
+.language-options-lower-left button,
+#language-options-details button {
+ min-width: 70px;
+}
+
+.language-options h3 {
+ -webkit-margin-start: 12px;
+ font-size: 100%;
+ font-weight: bold;
+ margin-bottom: 12px;
+ margin-top: 12px;
+}
+
+.language-options-contents {
+ min-height: 28px;
+ padding: 0 12px 4px;
+}
+
+.language-options-contents > span:not(.input-method-label) {
+ display: inline-block;
+ margin: 1px;
+ padding: 0.42em 10px;
+}
+
+.language-options-header,
+.language-options-footer {
+ line-height: 1.2em;
+ margin: 10px 0;
+}
+
+#language-options-languages,
+#language-options-details {
+ border: 1px solid #ccc;
+ height: 400px;
+ padding: 0;
+ vertical-align: top;
+}
+
+#language-options-languages {
+ -webkit-box-orient: vertical;
+ background-color: rgb(235, 239, 249);
+ display: -webkit-box;
+ width: 300px;
+}
+
+.language-options-lower-left {
+ -webkit-box-flex: 0;
+ -webkit-padding-start: 12px;
+ padding-bottom: 10px;
+}
+
+#language-options-details {
+ /* To share the center line with the left pane. */
+ -webkit-margin-start: -1px;
+ width: 360px;
+}
+
+#language-options-details h3:not(:first-of-type) {
+ margin-top: 24px;
+}
+
+.language-options-notification {
+ background-color: rgb(255, 247, 193);
+ margin: 0 0 4px;
+ padding: 8px 30px 8px 12px;
+}
+
+.language-options-notification > div {
+ margin-bottom: 4px;
+}
+
+#language-options-input-method-list button {
+ -webkit-margin-start: 20px;
+ display: block;
+ /* Same margin as .settings-row. */
+ margin-bottom: 0.65em;
+ margin-top: 0.65em;
+}
+
+#language-options-list {
+ -webkit-box-flex: 1;
+ outline: none;
+ padding: 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-bottom: 2px;
+ padding-top: 2px;
+}
+
+#language-options-list-dropmarker {
+ background-clip: padding-box;
+ background-color: hsl(214, 91%, 65%);
+ border: 3px solid hsl(214, 91%, 65%);
+ border-bottom-color: transparent;
+ border-radius: 0;
+ border-top-color: transparent;
+ box-sizing: border-box;
+ display: none;
+ height: 8px;
+ overflow: hidden;
+ pointer-events: none;
+ position: fixed;
+ z-index: 10;
+}
+
+/* TODO(kochi): This is temporary copy from new_tab.css */
+/* Notification */
+
+#notification {
+ -webkit-transition: opacity 150ms;
+ background-color: hsl(52, 100%, 80%);
+ border: 1px solid rgb(211, 211, 211);
+ border-radius: 6px;
+ color: black;
+ display: table;
+ font-weight: bold;
+ /* Set the height and margin so that the element does not use any vertical
+ space. */
+ height: 16px;
+ margin: -44px auto 12px auto;
+ opacity: 0;
+ padding: 7px 15px;
+ pointer-events: none;
+ position: relative;
+ white-space: nowrap;
+ z-index: 1;
+}
+
+#notification > * {
+ display: table-cell;
+ max-width: 500px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+#notification.show {
+ -webkit-transition: opacity 1s;
+ opacity: 1;
+ pointer-events: all;
+}
+
+#notification .link {
+ -webkit-appearance: none;
+ -webkit-padding-start: 20px;
+ background: none;
+ border: 0;
+ color: rgba(0, 102, 204, 0.3);
+ cursor: pointer;
+ text-decoration: underline;
+}
+
+#notification .link-color {
+ color: rgb(0, 102, 204);
+}
+
+#chewing-max-chi-symbol-len {
+ height: 30%;
+ width: 100px;
+}
+
+#add-language-overlay-page .content-area {
+ padding-bottom: 10px;
+}
+
+/* TODO(hshi): Remove this temporary hack once the bug is fixed in Chrome. */
+#add-language-overlay-language-list {
+ width: -webkit-calc(100% - 4px);
+}
+
+.standalone-link-button {
+ padding: 0;
+}
diff --git a/chromium/chrome/browser/resources/options/language_options.html b/chromium/chrome/browser/resources/options/language_options.html
new file mode 100644
index 00000000000..e2d420cb61a
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/language_options.html
@@ -0,0 +1,136 @@
+<div id="languagePage" class="page" hidden>
+ <div class="close-button"></div>
+ <h1 i18n-content="languagePage"></h1>
+ <div class="content-area">
+ <div id="notification">
+ <span>&nbsp;</span>
+ <span class="link"><span class="link-color"></span></span>
+ </div>
+ <div class="language-options-header">
+ <div i18n-content="addLanguageInstructions"></div>
+<if expr="pp_ifdef('chromeos')">
+ <div i18n-content="inputMethodInstructions"></div>
+</if>
+ </div>
+ <div class="language-options">
+ <div id="language-options-languages">
+ <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="addButton">
+ </button>
+ </div>
+ <div id="language-options-list-dropmarker"></div>
+ </div>
+ <div id="language-options-details">
+ <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"
+ i18n-content="displayInThisLanguage">
+ </button>
+ <span id="language-options-ui-language-message" hidden></span>
+ </div>
+</if>
+<!-- Chrome uses the native OS spellchecker in OS X, so don't display the
+ dictionary pane. -->
+<if expr="not is_macosx">
+ <div id="language-options-spellcheck" class="language-options-contents">
+ <button id="language-options-spell-check-language-button"
+ i18n-content="useThisForSpellChecking">
+ </button>
+ <span id="language-options-spell-check-language-message" hidden>
+ </span>
+ <span id="language-options-dictionary-downloading-message"
+ i18n-content="downloadingDictionary" hidden>
+ </span>
+ </div>
+ <div id="language-options-dictionary-download-failed-message"
+ class="language-options-notification" hidden>
+ <div i18n-content="downloadFailed"></div>
+ <div id="language-options-dictionary-download-fail-help-message"
+ i18n-content="downloadFailHelp" hidden>
+ </div>
+ <button id="dictionary-download-retry-button"
+ i18n-content="retryButton">
+ </button>
+ </div>
+ <div id="language-options-ui-notification-bar"
+ class="language-options-notification" hidden>
+ <div i18n-content="restartRequired"></div>
+<if expr="pp_ifdef('chromeos')">
+ <button id="language-options-ui-restart-button"
+ i18n-content="restartButton">
+ </button>
+</if>
+ </div>
+</if>
+ <div id="language-options-offer-to-translate"
+ class="language-options-contents" hidden>
+ <div class="checkbox">
+ <label>
+ <input type="checkbox" id="offer-to-translate-in-this-language">
+ <span class="offer-to-translate-label"
+ i18n-content="offerToTranslateInThisLanguage"></span>
+ </label>
+ </div>
+ <span id="cannot-translate-in-this-language"
+ i18n-content="cannotTranslateInThisLanguage" hidden></span>
+ </div>
+<if expr="pp_ifdef('chromeos')">
+ <h3 i18n-content="inputMethod"></h3>
+ <div id="language-options-input-method-template" class="input-method"
+ hidden>
+ <div class="checkbox">
+ <label>
+ <input type="checkbox">
+ <span class="input-method-label"></span>
+ </label>
+ </div>
+ </div>
+ <div id="language-options-input-method-list"
+ class="language-options-contents">
+ <span id="language-options-input-method-none"
+ i18n-content="noInputMethods" hidden>
+ </span>
+ </div>
+</if>
+ </div>
+ </div>
+ <div class="language-options-footer">
+<if expr="pp_ifdef('chromeos')">
+ <div i18n-content="switchInputMethodsHint"></div>
+ <div i18n-content="selectPreviousInputMethodHint"></div>
+ <button id="edit-dictionary-button"
+ class="link-button standalone-link-button"
+ i18n-content="languageDictionaryOverlayTitle"></button>
+</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="enableSpellCheck"></span>
+ </label>
+ <button id="edit-dictionary-button" class="link-button"
+ i18n-content="languageDictionaryOverlayTitle" hidden></button>
+ </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="enableAutoSpellCorrection"></span>
+ </label>
+ </div>
+</if>
+ </div>
+ </div>
+ <div class="action-area">
+ <div class="button-strip">
+ <button id="language-confirm" class="default-button" i18n-content="done">
+ </button>
+ </div>
+ </div>
+</div>
diff --git a/chromium/chrome/browser/resources/options/language_options.js b/chromium/chrome/browser/resources/options/language_options.js
new file mode 100644
index 00000000000..3ebf7d2665b
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/language_options.js
@@ -0,0 +1,1304 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// TODO(kochi): Generalize the notification as a component and put it
+// in js/cr/ui/notification.js .
+
+cr.define('options', function() {
+ /** @const */ var OptionsPage = options.OptionsPage;
+ /** @const */ var LanguageList = options.LanguageList;
+
+ /**
+ * Spell check dictionary download status.
+ * @type {Enum}
+ */
+ /** @const*/ var DOWNLOAD_STATUS = {
+ IN_PROGRESS: 1,
+ FAILED: 2
+ };
+
+ /**
+ * The preference is a boolean that enables/disables spell checking.
+ * @type {string}
+ * @const
+ */
+ var ENABLE_SPELL_CHECK_PREF = 'browser.enable_spellchecking';
+
+ /**
+ * The preference is a CSV string that describes preload engines
+ * (i.e. active input methods).
+ * @type {string}
+ * @const
+ */
+ var PRELOAD_ENGINES_PREF = 'settings.language.preload_engines';
+
+ /**
+ * The preference that lists the extension IMEs that are enabled in the
+ * language menu.
+ * @type {string}
+ * @const
+ */
+ var ENABLED_EXTENSION_IME_PREF = 'settings.language.enabled_extension_imes';
+
+ /**
+ * The preference that lists the languages which are not translated.
+ * @type {string}
+ * @const
+ */
+ var TRANSLATE_BLOCKED_LANGUAGES_PREF = 'translate_blocked_languages';
+
+ /**
+ * The preference key that is a string that describes the spell check
+ * dictionary language, like "en-US".
+ * @type {string}
+ * @const
+ */
+ var SPELL_CHECK_DICTIONARY_PREF = 'spellcheck.dictionary';
+
+ /**
+ * The preference that indicates if the Translate feature is enabled.
+ * @type {string}
+ * @const
+ */
+ var ENABLE_TRANSLATE = 'translate.enabled';
+
+ /////////////////////////////////////////////////////////////////////////////
+ // LanguageOptions class:
+
+ /**
+ * Encapsulated handling of ChromeOS language options page.
+ * @constructor
+ */
+ function LanguageOptions(model) {
+ OptionsPage.call(this, 'languages',
+ loadTimeData.getString('languagePageTabTitle'),
+ 'languagePage');
+ }
+
+ cr.addSingletonGetter(LanguageOptions);
+
+ // Inherit LanguageOptions from OptionsPage.
+ LanguageOptions.prototype = {
+ __proto__: OptionsPage.prototype,
+
+ /* For recording the prospective language (the next locale after relaunch).
+ * @type {?string}
+ * @private
+ */
+ prospectiveUiLanguageCode_: null,
+
+ /*
+ * Map from language code to spell check dictionary download status for that
+ * language.
+ * @type {Array}
+ * @private
+ */
+ spellcheckDictionaryDownloadStatus_: [],
+
+ /**
+ * Number of times a spell check dictionary download failed.
+ * @type {int}
+ * @private
+ */
+ spellcheckDictionaryDownloadFailures_: 0,
+
+ /**
+ * The list of preload engines, like ['mozc', 'pinyin'].
+ * @type {Array}
+ * @private
+ */
+ preloadEngines_: [],
+
+ /**
+ * The list of extension IMEs that are enabled out of the language menu.
+ * @type {Array}
+ * @private
+ */
+ enabledExtensionImes_: [],
+
+ /**
+ * The list of the languages which is not translated.
+ * @type {Array}
+ * @private
+ */
+ translateBlockedLanguages_: [],
+
+ /**
+ * The list of the languages supported by Translate server
+ * @type {Array}
+ * @private
+ */
+ translateSupportedLanguages_: [],
+
+ /**
+ * The preference is a string that describes the spell check dictionary
+ * language, like "en-US".
+ * @type {string}
+ * @private
+ */
+ spellCheckDictionary_: '',
+
+ /**
+ * The map of language code to input method IDs, like:
+ * {'ja': ['mozc', 'mozc-jp'], 'zh-CN': ['pinyin'], ...}
+ * @type {Object}
+ * @private
+ */
+ languageCodeToInputMethodIdsMap_: {},
+
+ /**
+ * The value that indicates if Translate feature is enabled or not.
+ * @type {boolean}
+ * @private
+ */
+ enableTranslate_: false,
+
+ /**
+ * Initializes LanguageOptions page.
+ * Calls base class implementation to start 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.prospectiveUiLanguageCode_ =
+ loadTimeData.getString('prospectiveUiLanguageCode');
+ this.addEventListener('visibleChange',
+ this.handleVisibleChange_.bind(this));
+
+ if (cr.isChromeOS) {
+ this.initializeInputMethodList_();
+ this.initializeLanguageCodeToInputMethodIdsMap_();
+ }
+
+ var checkbox = $('offer-to-translate-in-this-language');
+ checkbox.addEventListener('click',
+ this.handleOfferToTranslateCheckboxClick_.bind(this));
+
+ Preferences.getInstance().addEventListener(
+ TRANSLATE_BLOCKED_LANGUAGES_PREF,
+ this.handleTranslateBlockedLanguagesPrefChange_.bind(this));
+ Preferences.getInstance().addEventListener(SPELL_CHECK_DICTIONARY_PREF,
+ this.handleSpellCheckDictionaryPrefChange_.bind(this));
+ Preferences.getInstance().addEventListener(ENABLE_TRANSLATE,
+ this.handleEnableTranslatePrefChange_.bind(this));
+ this.translateSupportedLanguages_ =
+ loadTimeData.getValue('translateSupportedLanguages');
+
+ // 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);
+ this.addBlockedLanguage_(addLanguageCode);
+ } else {
+ OptionsPage.navigateToPage('addLanguage');
+ }
+ }.bind(this);
+
+ if (!cr.isMac) {
+ // Set up the button for editing custom spelling dictionary.
+ $('edit-dictionary-button').onclick = function(e) {
+ OptionsPage.navigateToPage('editDictionary');
+ };
+ $('dictionary-download-retry-button').onclick = function(e) {
+ chrome.send('retryDictionaryDownload');
+ };
+ }
+
+ // Listen to add language dialog ok button.
+ $('add-language-overlay-ok-button').addEventListener(
+ 'click', this.handleAddLanguageOkButtonClick_.bind(this));
+
+ if (!cr.isChromeOS) {
+ // Show experimental features if enabled.
+ if (loadTimeData.getBoolean('enableSpellingAutoCorrect'))
+ $('auto-spell-correction-option').hidden = false;
+
+ // Handle spell check enable/disable.
+ if (!cr.isMac) {
+ Preferences.getInstance().addEventListener(
+ ENABLE_SPELL_CHECK_PREF,
+ this.updateEnableSpellCheck_.bind(this));
+ }
+ }
+
+ // Handle clicks on "Use this language for spell checking" button.
+ if (!cr.isMac) {
+ var spellCheckLanguageButton = getRequiredElement(
+ 'language-options-spell-check-language-button');
+ spellCheckLanguageButton.addEventListener(
+ 'click',
+ this.handleSpellCheckLanguageButtonClick_.bind(this));
+ }
+
+ if (cr.isChromeOS) {
+ $('language-options-ui-restart-button').onclick = function() {
+ chrome.send('uiLanguageRestart');
+ };
+ }
+
+ $('language-confirm').onclick =
+ OptionsPage.closeOverlay.bind(OptionsPage);
+ },
+
+ /**
+ * Initializes the input method list.
+ */
+ initializeInputMethodList_: function() {
+ var inputMethodList = $('language-options-input-method-list');
+ var inputMethodPrototype = $('language-options-input-method-template');
+
+ // 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.
+ this.appendInputMethodElement_(loadTimeData.getValue('inputMethodList'));
+ this.appendInputMethodElement_(loadTimeData.getValue('extensionImeList'));
+ this.appendComponentExtensionIme_(
+ loadTimeData.getValue('componentExtensionImeList'));
+
+ // Listen to pref change once the input method list is initialized.
+ Preferences.getInstance().addEventListener(
+ PRELOAD_ENGINES_PREF,
+ this.handlePreloadEnginesPrefChange_.bind(this));
+ Preferences.getInstance().addEventListener(
+ ENABLED_EXTENSION_IME_PREF,
+ this.handleEnabledExtensionsPrefChange_.bind(this));
+ },
+
+ /**
+ * Appends input method lists based on component extension ime list.
+ * @param {!Array} componentExtensionImeList A list of input method
+ * descriptors.
+ * @private
+ */
+ appendComponentExtensionIme_: function(componentExtensionImeList) {
+ this.appendInputMethodElement_(componentExtensionImeList);
+
+ for (var i = 0; i < componentExtensionImeList.length; i++) {
+ var inputMethod = componentExtensionImeList[i];
+ for (var languageCode in inputMethod.languageCodeSet) {
+ if (languageCode in this.languageCodeToInputMethodIdsMap_) {
+ this.languageCodeToInputMethodIdsMap_[languageCode].push(
+ inputMethod.id);
+ } else {
+ this.languageCodeToInputMethodIdsMap_[languageCode] =
+ [inputMethod.id];
+ }
+ }
+ }
+ },
+
+ /**
+ * Appends input methods into input method list.
+ * @param {!Array} inputMethods A list of input method descriptors.
+ * @private
+ */
+ appendInputMethodElement_: function(inputMethods) {
+ var inputMethodList = $('language-options-input-method-list');
+ var inputMethodTemplate = $('language-options-input-method-template');
+
+ for (var i = 0; i < inputMethods.length; i++) {
+ var inputMethod = inputMethods[i];
+ var element = inputMethodTemplate.cloneNode(true);
+ element.id = '';
+ element.languageCodeSet = inputMethod.languageCodeSet;
+
+ var input = element.querySelector('input');
+ input.inputMethodId = inputMethod.id;
+ var span = element.querySelector('span');
+ span.textContent = inputMethod.displayName;
+
+ if (inputMethod.optionsPage) {
+ var button = document.createElement('button');
+ button.textContent = loadTimeData.getString('configure');
+ button.inputMethodId = inputMethod.id;
+ button.onclick = function(inputMethodId, e) {
+ chrome.send('inputMethodOptionsOpen', [inputMethodId]);
+ }.bind(this, inputMethod.id);
+ element.appendChild(button);
+ }
+
+ // Listen to user clicks.
+ input.addEventListener('click',
+ this.handleCheckboxClick_.bind(this));
+ inputMethodList.appendChild(element);
+ }
+ },
+
+ /**
+ * Adds a language to the preference 'translate_blocked_languages'. If
+ * |langCode| is already added, nothing happens. |langCode| is converted
+ * to a Translate language synonym before added.
+ * @param {string} langCode A language code like 'en'
+ * @private
+ */
+ addBlockedLanguage_: function(langCode) {
+ langCode = this.convertLangCodeForTranslation_(langCode);
+ if (this.translateBlockedLanguages_.indexOf(langCode) == -1) {
+ this.translateBlockedLanguages_.push(langCode);
+ Preferences.setListPref(TRANSLATE_BLOCKED_LANGUAGES_PREF,
+ this.translateBlockedLanguages_, true);
+ }
+ },
+
+ /**
+ * Removes a language from the preference 'translate_blocked_languages'.
+ * If |langCode| doesn't exist in the preference, nothing happens.
+ * |langCode| is converted to a Translate language synonym before removed.
+ * @param {string} langCode A language code like 'en'
+ * @private
+ */
+ removeBlockedLanguage_: function(langCode) {
+ langCode = this.convertLangCodeForTranslation_(langCode);
+ if (this.translateBlockedLanguages_.indexOf(langCode) != -1) {
+ this.translateBlockedLanguages_ =
+ this.translateBlockedLanguages_.filter(
+ function(langCodeNotTranslated) {
+ return langCodeNotTranslated != langCode;
+ });
+ Preferences.setListPref(TRANSLATE_BLOCKED_LANGUAGES_PREF,
+ this.translateBlockedLanguages_, true);
+ }
+ },
+
+ /**
+ * 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();
+
+ // If there's no selection, just return.
+ if (!languageCode)
+ return;
+
+ // 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.updateOfferToTranslateCheckbox_(languageCode);
+
+ if (cr.isWindows || cr.isChromeOS)
+ this.updateUiLanguageButton_(languageCode);
+
+ this.updateSelectedLanguageName_(languageCode);
+
+ if (!cr.isMac)
+ this.updateSpellCheckLanguageButton_(languageCode);
+
+ if (cr.isChromeOS)
+ this.updateInputMethodList_(languageCode);
+
+ this.updateLanguageListInAddLanguageOverlay_();
+ },
+
+ /**
+ * Happens when a user changes back to the language they're currently using.
+ */
+ currentLocaleWasReselected: function() {
+ this.updateUiLanguageButton_(
+ loadTimeData.getString('currentUiLanguageCode'));
+ },
+
+ /**
+ * 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];
+ if (!inputMethodIds)
+ continue;
+
+ // 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 = loadTimeData.getValue('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 languageInfo = LanguageList.getLanguageInfoFromLanguageCode(
+ languageCode);
+ var languageDisplayName = languageInfo.displayName;
+ var languageNativeDisplayName = languageInfo.nativeDisplayName;
+ var textDirection = languageInfo.textDirection;
+
+ // 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');
+ languageName.textContent = languageDisplayName;
+ languageName.dir = textDirection;
+ },
+
+ /**
+ * Updates the UI language button.
+ * @param {string} languageCode Language code (ex. "fr").
+ * @private
+ */
+ updateUiLanguageButton_: function(languageCode) {
+ var uiLanguageButton = $('language-options-ui-language-button');
+ var uiLanguageMessage = $('language-options-ui-language-message');
+ var uiLanguageNotification = $('language-options-ui-notification-bar');
+
+ // Remove the event listener and add it back if useful.
+ uiLanguageButton.onclick = null;
+
+ // Unhide the language button every time, as it could've been previously
+ // hidden by a language change.
+ uiLanguageButton.hidden = false;
+
+ if (languageCode == this.prospectiveUiLanguageCode_) {
+ uiLanguageMessage.textContent =
+ loadTimeData.getString('isDisplayedInThisLanguage');
+ showMutuallyExclusiveNodes(
+ [uiLanguageButton, uiLanguageMessage, uiLanguageNotification], 1);
+ } else if (languageCode in loadTimeData.getValue('uiLanguageCodeSet')) {
+ if (cr.isChromeOS && UIAccountTweaks.loggedInAsGuest()) {
+ // 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;
+ uiLanguageMessage.hidden = true;
+ } else {
+ uiLanguageButton.textContent =
+ loadTimeData.getString('displayInThisLanguage');
+ showMutuallyExclusiveNodes(
+ [uiLanguageButton, uiLanguageMessage, uiLanguageNotification], 0);
+ uiLanguageButton.onclick = function(e) {
+ chrome.send('uiLanguageChange', [languageCode]);
+ };
+ }
+ } else {
+ uiLanguageMessage.textContent =
+ loadTimeData.getString('cannotBeDisplayedInThisLanguage');
+ showMutuallyExclusiveNodes(
+ [uiLanguageButton, uiLanguageMessage, uiLanguageNotification], 1);
+ }
+ },
+
+ /**
+ * Updates the spell check language button.
+ * @param {string} languageCode Language code (ex. "fr").
+ * @private
+ */
+ updateSpellCheckLanguageButton_: function(languageCode) {
+ var spellCheckLanguageSection = $('language-options-spellcheck');
+ var spellCheckLanguageButton =
+ $('language-options-spell-check-language-button');
+ var spellCheckLanguageMessage =
+ $('language-options-spell-check-language-message');
+ var dictionaryDownloadInProgress =
+ $('language-options-dictionary-downloading-message');
+ var dictionaryDownloadFailed =
+ $('language-options-dictionary-download-failed-message');
+ var dictionaryDownloadFailHelp =
+ $('language-options-dictionary-download-fail-help-message');
+ spellCheckLanguageSection.hidden = false;
+ spellCheckLanguageMessage.hidden = true;
+ spellCheckLanguageButton.hidden = true;
+ dictionaryDownloadInProgress.hidden = true;
+ dictionaryDownloadFailed.hidden = true;
+ dictionaryDownloadFailHelp.hidden = true;
+
+ if (languageCode == this.spellCheckDictionary_) {
+ if (!(languageCode in this.spellcheckDictionaryDownloadStatus_)) {
+ spellCheckLanguageMessage.textContent =
+ loadTimeData.getString('isUsedForSpellChecking');
+ showMutuallyExclusiveNodes(
+ [spellCheckLanguageButton, spellCheckLanguageMessage], 1);
+ } else if (this.spellcheckDictionaryDownloadStatus_[languageCode] ==
+ DOWNLOAD_STATUS.IN_PROGRESS) {
+ dictionaryDownloadInProgress.hidden = false;
+ } else if (this.spellcheckDictionaryDownloadStatus_[languageCode] ==
+ DOWNLOAD_STATUS.FAILED) {
+ spellCheckLanguageSection.hidden = true;
+ dictionaryDownloadFailed.hidden = false;
+ if (this.spellcheckDictionaryDownloadFailures_ > 1)
+ dictionaryDownloadFailHelp.hidden = false;
+ }
+ } else if (languageCode in
+ loadTimeData.getValue('spellCheckLanguageCodeSet')) {
+ spellCheckLanguageButton.textContent =
+ loadTimeData.getString('useThisForSpellChecking');
+ showMutuallyExclusiveNodes(
+ [spellCheckLanguageButton, spellCheckLanguageMessage], 0);
+ spellCheckLanguageButton.languageCode = languageCode;
+ } else if (!languageCode) {
+ spellCheckLanguageButton.hidden = true;
+ spellCheckLanguageMessage.hidden = true;
+ } else {
+ spellCheckLanguageMessage.textContent =
+ loadTimeData.getString('cannotBeUsedForSpellChecking');
+ showMutuallyExclusiveNodes(
+ [spellCheckLanguageButton, spellCheckLanguageMessage], 1);
+ }
+ },
+
+ /**
+ * Updates the checkbox for stopping translation.
+ * @param {string} languageCode Language code (ex. "fr").
+ * @private
+ */
+ updateOfferToTranslateCheckbox_: function(languageCode) {
+ var div = $('language-options-offer-to-translate');
+
+ // Translation server supports Chinese (Transitional) and Chinese
+ // (Simplified) but not 'general' Chinese. To avoid ambiguity, we don't
+ // show this preference when general Chinese is selected.
+ if (languageCode != 'zh') {
+ div.hidden = false;
+ } else {
+ div.hidden = true;
+ return;
+ }
+
+ var offerToTranslate = div.querySelector('div');
+ var cannotTranslate = $('cannot-translate-in-this-language');
+ var nodes = [offerToTranslate, cannotTranslate];
+
+ var convertedLangCode = this.convertLangCodeForTranslation_(languageCode);
+ if (this.translateSupportedLanguages_.indexOf(convertedLangCode) != -1) {
+ showMutuallyExclusiveNodes(nodes, 0);
+ } else {
+ showMutuallyExclusiveNodes(nodes, 1);
+ return;
+ }
+
+ var checkbox = $('offer-to-translate-in-this-language');
+
+ if (!this.enableTranslate_) {
+ checkbox.disabled = true;
+ checkbox.checked = false;
+ return;
+ }
+
+ // If the language corresponds to the default target language (in most
+ // cases, the user's locale language), "Offer to translate" checkbox
+ // should be always unchecked.
+ var defaultTargetLanguage =
+ loadTimeData.getString('defaultTargetLanguage');
+ if (convertedLangCode == defaultTargetLanguage) {
+ checkbox.disabled = true;
+ checkbox.checked = false;
+ return;
+ }
+
+ checkbox.disabled = false;
+
+ var blockedLanguages = this.translateBlockedLanguages_;
+ var checked = blockedLanguages.indexOf(convertedLangCode) == -1;
+ checkbox.checked = checked;
+ },
+
+ /**
+ * 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 methods = inputMethodList.querySelectorAll('.input-method');
+ for (var i = 0; i < methods.length; i++) {
+ var method = methods[i];
+ if (languageCode in method.languageCodeSet) {
+ method.hidden = false;
+ var input = method.querySelector('input');
+ // Give it focus if the ID matches.
+ if (input.inputMethodId == focusInputMethodId) {
+ input.focus();
+ }
+ } else {
+ method.hidden = true;
+ }
+ }
+
+ $('language-options-input-method-none').hidden =
+ (languageCode in this.languageCodeToInputMethodIdsMap_);
+
+ 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 options = addLanguageList.querySelectorAll('option');
+ assert(options.length > 0);
+ var selectedFirstItem = false;
+ for (var i = 0; i < options.length; i++) {
+ var option = options[i];
+ option.hidden = option.value in languageCodeSet;
+ if (!option.hidden && !selectedFirstItem) {
+ // Select first visible item, otherwise previously selected hidden
+ // item will be selected by default at the next time.
+ option.selected = true;
+ selectedFirstItem = true;
+ }
+ }
+ },
+
+ /**
+ * 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 enabledExtensionImePref change.
+ * @param {Event} e Change event.
+ * @private
+ */
+ handleEnabledExtensionsPrefChange_: function(e) {
+ var value = e.value.value;
+ this.enabledExtensionImes_ = value.split(',');
+ this.updateCheckboxesFromEnabledExtensions_();
+ },
+
+ /**
+ * Handles offer-to-translate checkbox's click event.
+ * @param {Event} e Click event.
+ * @private
+ */
+ handleOfferToTranslateCheckboxClick_: function(e) {
+ var checkbox = e.target;
+ var checked = checkbox.checked;
+
+ var languageOptionsList = $('language-options-list');
+ var selectedLanguageCode = languageOptionsList.getSelectedLanguageCode();
+
+ if (checked)
+ this.removeBlockedLanguage_(selectedLanguageCode);
+ else
+ this.addBlockedLanguage_(selectedLanguageCode);
+ },
+
+ /**
+ * Handles input method checkbox's click event.
+ * @param {Event} e Click event.
+ * @private
+ */
+ handleCheckboxClick_: function(e) {
+ var checkbox = e.target;
+
+ if (checkbox.inputMethodId.match(/^_ext_ime_/)) {
+ this.updateEnabledExtensionsFromCheckboxes_();
+ this.saveEnabledExtensionPref_();
+ return;
+ }
+ if (this.preloadEngines_.length == 1 && !checkbox.checked) {
+ // Don't allow disabling the last input method.
+ this.showNotification_(
+ loadTimeData.getString('pleaseAddAnotherInputMethod'),
+ loadTimeData.getString('okButton'));
+ 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_();
+ },
+
+ handleAddLanguageOkButtonClick_: function() {
+ var languagesSelect = $('add-language-overlay-language-list');
+ var selectedIndex = languagesSelect.selectedIndex;
+ if (selectedIndex >= 0) {
+ var selection = languagesSelect.options[selectedIndex];
+ var langCode = String(selection.value);
+ $('language-options-list').addLanguage(langCode);
+ this.addBlockedLanguage_(langCode);
+ 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 a UI language.
+ if (languageCode == this.prospectiveUiLanguageCode_)
+ 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;
+ if (!cr.IsMac)
+ $('edit-dictionary-button').hidden = value;
+ },
+
+ /**
+ * Handles translateBlockedLanguagesPref change.
+ * @param {Event} e Change event.
+ * @private
+ */
+ handleTranslateBlockedLanguagesPrefChange_: function(e) {
+ this.translateBlockedLanguages_ = e.value.value;
+ this.updateOfferToTranslateCheckbox_(
+ $('language-options-list').getSelectedLanguageCode());
+ },
+
+ /**
+ * Handles spellCheckDictionaryPref change.
+ * @param {Event} e Change event.
+ * @private
+ */
+ handleSpellCheckDictionaryPrefChange_: function(e) {
+ var languageCode = e.value.value;
+ this.spellCheckDictionary_ = languageCode;
+ if (!cr.isMac) {
+ this.updateSpellCheckLanguageButton_(
+ $('language-options-list').getSelectedLanguageCode());
+ }
+ },
+
+ /**
+ * Handles translate.enabled change.
+ * @param {Event} e Change event.
+ * @private
+ */
+ handleEnableTranslatePrefChange_: function(e) {
+ var enabled = e.value.value;
+ this.enableTranslate_ = enabled;
+ this.updateOfferToTranslateCheckbox_(
+ $('language-options-list').getSelectedLanguageCode());
+ },
+
+ /**
+ * Handles spellCheckLanguageButton click.
+ * @param {Event} e Click event.
+ * @private
+ */
+ handleSpellCheckLanguageButtonClick_: function(e) {
+ var languageCode = e.target.languageCode;
+ // Save the preference.
+ Preferences.setStringPref(SPELL_CHECK_DICTIONARY_PREF,
+ languageCode, true);
+ 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];
+
+ // If this language doesn't have any input methods, it can be deleted.
+ if (!inputMethodIds)
+ return true;
+
+ 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]];
+ if (!inputMethodIdsForAnotherLanguage)
+ continue;
+
+ 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 enabled extension preference.
+ * @private
+ */
+ saveEnabledExtensionPref_: function() {
+ Preferences.setStringPref(ENABLED_EXTENSION_IME_PREF,
+ this.enabledExtensionImes_.join(','), true);
+ },
+
+ /**
+ * Updates the checkboxes in the input method list from the enabled
+ * extensions preference.
+ * @private
+ */
+ updateCheckboxesFromEnabledExtensions_: function() {
+ // Convert the list into a dictonary for simpler lookup.
+ var dictionary = {};
+ for (var i = 0; i < this.enabledExtensionImes_.length; i++)
+ dictionary[this.enabledExtensionImes_[i]] = true;
+
+ var inputMethodList = $('language-options-input-method-list');
+ var checkboxes = inputMethodList.querySelectorAll('input');
+ for (var i = 0; i < checkboxes.length; i++) {
+ if (checkboxes[i].inputMethodId.match(/^_ext_ime_/))
+ checkboxes[i].checked = (checkboxes[i].inputMethodId in dictionary);
+ }
+ var configureButtons = inputMethodList.querySelectorAll('button');
+ for (var i = 0; i < configureButtons.length; i++) {
+ if (configureButtons[i].inputMethodId.match(/^_ext_ime_/)) {
+ configureButtons[i].hidden =
+ !(configureButtons[i].inputMethodId in dictionary);
+ }
+ }
+ },
+
+ /**
+ * Updates the enabled extensions preference from the checkboxes in the
+ * input method list.
+ * @private
+ */
+ updateEnabledExtensionsFromCheckboxes_: function() {
+ this.enabledExtensionImes_ = [];
+ var inputMethodList = $('language-options-input-method-list');
+ var checkboxes = inputMethodList.querySelectorAll('input');
+ for (var i = 0; i < checkboxes.length; i++) {
+ if (checkboxes[i].inputMethodId.match(/^_ext_ime_/)) {
+ if (checkboxes[i].checked)
+ this.enabledExtensionImes_.push(checkboxes[i].inputMethodId);
+ }
+ }
+ },
+
+ /**
+ * Saves the preload engines preference.
+ * @private
+ */
+ savePreloadEnginesPref_: function() {
+ Preferences.setStringPref(PRELOAD_ENGINES_PREF,
+ this.preloadEngines_.join(','), true);
+ },
+
+ /**
+ * 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++) {
+ if (!checkboxes[i].inputMethodId.match(/^_ext_ime_/))
+ checkboxes[i].checked = (checkboxes[i].inputMethodId in dictionary);
+ }
+ var configureButtons = inputMethodList.querySelectorAll('button');
+ for (var i = 0; i < configureButtons.length; i++) {
+ if (!configureButtons[i].inputMethodId.match(/^_ext_ime_/)) {
+ configureButtons[i].hidden =
+ !(configureButtons[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].inputMethodId.match(/^_ext_ime_/)) {
+ 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 = {};
+ var list = loadTimeData.getValue('inputMethodList');
+ for (var i = 0; i < list.length; i++) {
+ dictionary[list[i].id] = true;
+ }
+
+ var enabledPreloadEngines = [];
+ 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.
+ // Component Extension IME should be handled same as preloadEngines and
+ // "_comp_" is the special prefix of its ID.
+ if ((preloadEngines[i] in dictionary && !(preloadEngines[i] in seen)) ||
+ /^_comp_/.test(preloadEngines[i])) {
+ enabledPreloadEngines.push(preloadEngines[i]);
+ seen[preloadEngines[i]] = true;
+ }
+ }
+ return enabledPreloadEngines;
+ },
+
+ // 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();
+ },
+
+ onDictionaryDownloadBegin_: function(languageCode) {
+ this.spellcheckDictionaryDownloadStatus_[languageCode] =
+ DOWNLOAD_STATUS.IN_PROGRESS;
+ if (!cr.isMac &&
+ languageCode ==
+ $('language-options-list').getSelectedLanguageCode()) {
+ this.updateSpellCheckLanguageButton_(languageCode);
+ }
+ },
+
+ onDictionaryDownloadSuccess_: function(languageCode) {
+ delete this.spellcheckDictionaryDownloadStatus_[languageCode];
+ this.spellcheckDictionaryDownloadFailures_ = 0;
+ if (!cr.isMac &&
+ languageCode ==
+ $('language-options-list').getSelectedLanguageCode()) {
+ this.updateSpellCheckLanguageButton_(languageCode);
+ }
+ },
+
+ onDictionaryDownloadFailure_: function(languageCode) {
+ this.spellcheckDictionaryDownloadStatus_[languageCode] =
+ DOWNLOAD_STATUS.FAILED;
+ this.spellcheckDictionaryDownloadFailures_++;
+ if (!cr.isMac &&
+ languageCode ==
+ $('language-options-list').getSelectedLanguageCode()) {
+ this.updateSpellCheckLanguageButton_(languageCode);
+ }
+ },
+
+ /*
+ * Converts the language code for Translation. There are some differences
+ * between the language set for Translation and that for Accept-Language.
+ * @param {string} languageCode The language code like 'fr'.
+ * @return {string} The converted language code.
+ * @private
+ */
+ convertLangCodeForTranslation_: function(languageCode) {
+ var tokens = languageCode.split('-');
+ var main = tokens[0];
+
+ // See also: chrome/renderer/translate/translate_helper.cc.
+ var synonyms = {
+ 'nb': 'no',
+ 'he': 'iw',
+ 'jv': 'jw',
+ 'fil': 'tl',
+ };
+
+ if (main in synonyms) {
+ return synonyms[main];
+ } else if (main == 'zh') {
+ // In Translation, general Chinese is not used, and the sub code is
+ // necessary as a language code for Translate server.
+ return languageCode;
+ }
+
+ return main;
+ },
+ };
+
+ /**
+ * Shows the node at |index| in |nodes|, hides all others.
+ * @param {Array<HTMLElement>} nodes The nodes to be shown or hidden.
+ * @param {number} index The index of |nodes| to show.
+ */
+ function showMutuallyExclusiveNodes(nodes, index) {
+ assert(index >= 0 && index < nodes.length);
+ for (var i = 0; i < nodes.length; ++i) {
+ assert(nodes[i] instanceof HTMLElement); // TODO(dbeam): Ignore null?
+ nodes[i].hidden = i != index;
+ }
+ }
+
+ /**
+ * Chrome callback for when the UI language preference is saved.
+ * @param {string} languageCode The newly selected language to use.
+ */
+ LanguageOptions.uiLanguageSaved = function(languageCode) {
+ this.prospectiveUiLanguageCode_ = languageCode;
+
+ // If the user is no longer on the same language code, ignore.
+ if ($('language-options-list').getSelectedLanguageCode() != languageCode)
+ return;
+
+ // Special case for when a user changes to a different language, and changes
+ // back to the same language without having restarted Chrome or logged
+ // in/out of ChromeOS.
+ if (languageCode == loadTimeData.getString('currentUiLanguageCode')) {
+ LanguageOptions.getInstance().currentLocaleWasReselected();
+ return;
+ }
+
+ // Otherwise, show a notification telling the user that their changes will
+ // only take effect after restart.
+ showMutuallyExclusiveNodes([$('language-options-ui-language-button'),
+ $('language-options-ui-notification-bar')], 1);
+ };
+
+ LanguageOptions.onDictionaryDownloadBegin = function(languageCode) {
+ LanguageOptions.getInstance().onDictionaryDownloadBegin_(languageCode);
+ };
+
+ LanguageOptions.onDictionaryDownloadSuccess = function(languageCode) {
+ LanguageOptions.getInstance().onDictionaryDownloadSuccess_(languageCode);
+ };
+
+ LanguageOptions.onDictionaryDownloadFailure = function(languageCode) {
+ LanguageOptions.getInstance().onDictionaryDownloadFailure_(languageCode);
+ };
+
+ LanguageOptions.onComponentManagerInitialized = function(componentImes) {
+ LanguageOptions.getInstance().appendComponentExtensionIme_(componentImes);
+ };
+
+ // Export
+ return {
+ LanguageOptions: LanguageOptions
+ };
+});
diff --git a/chromium/chrome/browser/resources/options/manage_profile_overlay.css b/chromium/chrome/browser/resources/options/manage_profile_overlay.css
new file mode 100644
index 00000000000..760030db3d2
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/manage_profile_overlay.css
@@ -0,0 +1,130 @@
+/* Copyright (c) 2012 The Chromium Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file. */
+
+#manage-profile-overlay {
+ width: 612px;
+}
+
+.profile-icon-grid-item {
+ height: 31px;
+ margin: 2px 4px;
+ padding: 4px;
+ width: 38px;
+}
+
+.profile-icon {
+ height: 31px;
+ width: 38px;
+}
+
+#create-profile-name-input-container,
+#manage-profile-name-input-container {
+ margin-top: 5px;
+}
+
+#create-profile-name,
+#manage-profile-name {
+ margin-left: 10px;
+}
+
+#create-profile-name:invalid,
+#manage-profile-name:invalid {
+ background-color: pink;
+}
+
+#create-profile-error-bubble,
+#manage-profile-error-bubble {
+ -webkit-transition: max-height 200ms, padding 200ms;
+ background-color: rgb(238, 185, 57);
+ 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%;
+}
+
+#create-profile-error-bubble[hidden],
+#manage-profile-error-bubble[hidden] {
+ display: block !important;
+ max-height: 0;
+ padding: 0 10px;
+}
+
+#create-profile-icon-grid,
+#manage-profile-icon-grid {
+ background-color: rgba(255, 255, 255, 0.75);
+ padding: 2px;
+}
+
+:-webkit-any(#create-profile-content, #manage-profile-content) >
+ :not(:last-child) {
+ margin-bottom: 10px;
+}
+
+:-webkit-any(#create-profile-content, #manage-profile-content) >
+ :not(:first-child) {
+ margin-top: 10px;
+}
+
+:-webkit-any(#create-profile-content, #manage-profile-content) >
+ .name-input-container {
+ margin-top: 5px;
+}
+
+:-webkit-any(#create-profile-content, #manage-profile-content) >
+ .name-label-container {
+ margin-bottom: 5px;
+}
+
+#create-profile-content {
+ padding-bottom: 0;
+}
+
+.action-area-shortcut-container {
+ -webkit-box-flex: 1;
+}
+
+/* Proper spacing for the buttons. */
+#remove-shortcut-button,
+#add-shortcut-button {
+ -webkit-margin-end: 10px;
+}
+
+#delete-managed-profile-addendum {
+ -webkit-padding-start: 48px;
+ margin-top: 10px;
+}
+
+html[dir='ltr'] #delete-profile-icon {
+ float: left;
+ margin-right: 10px;
+}
+
+html[dir='rtl'] #delete-profile-icon {
+ float: right;
+ margin-left: 10px;
+}
+
+#create-profile-managed-not-signed-in {
+ color: #999;
+}
+
+#create-profile-managed-not-signed-in-label,
+#create-profile-managed-account-details-out-of-date-label {
+ white-space: pre-wrap;
+ word-wrap: break-word;
+}
+
+#create-profile-managed-content-area {
+ padding-top: 0;
+}
+
+#import-existing-managed-user-link {
+ left: 17px;
+ position: absolute;
+}
diff --git a/chromium/chrome/browser/resources/options/manage_profile_overlay.html b/chromium/chrome/browser/resources/options/manage_profile_overlay.html
new file mode 100644
index 00000000000..42ccfb21cae
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/manage_profile_overlay.html
@@ -0,0 +1,124 @@
+<div id="manage-profile-overlay" class="page" hidden>
+ <div class="close-button"></div>
+ <!-- Dialog for managing profiles. -->
+ <div id="manage-profile-overlay-manage" hidden>
+ <h1 i18n-content="manageProfile"></h1>
+ <div id="manage-profile-content" class="content-area">
+ <div id="manage-profile-icon-label"
+ i18n-content="manageProfilesIconLabel">
+ </div>
+ <grid id="manage-profile-icon-grid"></grid>
+ <div id="manage-profile-name-input-container">
+ <label>
+ <span id="manage-profile-name-label" for="manage-profile-name"
+ i18n-content="manageProfilesNameLabel">
+ </span>
+ <input id="manage-profile-name" type="text" required>
+ </label>
+ </div>
+ <div id="manage-profile-error-bubble" hidden></div>
+ </div>
+ <div class="action-area">
+ <div class="action-area-shortcut-container">
+ <button id="remove-shortcut-button"
+ i18n-content="removeProfileShortcutButton" hidden>
+ </button>
+ <button id="add-shortcut-button"
+ i18n-content="createProfileShortcutButton" hidden>
+ </button>
+ </div>
+ <div class="button-strip">
+ <button id="manage-profile-cancel" i18n-content="cancel"></button>
+ <button id="manage-profile-ok" i18n-content="ok"
+ class="default-button"></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">
+ <img id="delete-profile-icon" class="profile-icon">
+ <div id="delete-profile-text"></div>
+ </div>
+ <div id="delete-managed-profile-addendum"
+ i18n-values=".innerHTML:deleteManagedProfileAddendum" hidden>
+ </div>
+ </div>
+ <div class="action-area button-strip">
+ <button id="delete-profile-cancel" i18n-content="cancel"></button>
+ <button id="delete-profile-ok" class="default-button"
+ i18n-content="deleteProfileOK"></button>
+ </div>
+ </div>
+ <!-- Dialog for creating profiles. -->
+ <div id="manage-profile-overlay-create" hidden>
+ <h1 i18n-content="createProfileTitle"></h1>
+ <div id="create-profile-content" class="content-area">
+ <div id="create-profile-instructions"></div>
+ <grid id="create-profile-icon-grid"></grid>
+ <div id="create-profile-name-input-container">
+ <label>
+ <span id="create-profile-name-label" for="create-profile-name"
+ i18n-content="manageProfilesNameLabel">
+ </span>
+ <input id="create-profile-name" type="text" required>
+ </label>
+ </div>
+ <div id="create-profile-error-bubble" hidden></div>
+ </div>
+ <div id="create-profile-managed-content-area" class="content-area">
+ <div id="create-shortcut-container" class="checkbox" hidden>
+ <label>
+ <input id="create-shortcut" type="checkbox">
+ <span for="create-shortcut"
+ i18n-content="createProfileShortcutCheckbox">
+ </span>
+ </label>
+ </div>
+ <div id="create-profile-managed-container" class="checkbox">
+ <label>
+ <input id="create-profile-managed" type="checkbox">
+ <span id="create-profile-managed-signed-in">
+ <span id="create-profile-managed-signed-in-label"></span>
+ <span id="create-profile-managed-account-details-out-of-date-label"
+ hidden>
+ </span>
+ <button id="create-profile-managed-signed-in-learn-more-link"
+ class="link-button" i18n-content="learnMore">
+ </button>
+ <button id="create-profile-managed-sign-in-again-link"
+ class="link-button"
+ i18n-content="manageProfilesManagedSignInAgainLink" hidden>
+ </button>
+ </span>
+ <span id="create-profile-managed-not-signed-in" hidden>
+ <span id="create-profile-managed-not-signed-in-label"
+ i18n-content="manageProfilesManagedNotSignedInLabel">
+ </span>
+ <button id="create-profile-managed-not-signed-in-link"
+ class="link-button"
+ i18n-content="manageProfilesManagedNotSignedInLink">
+ </button>
+ </span>
+ </label>
+ <span id="create-profile-managed-indicator"
+ class="bubble-button controlled-setting-indicator">
+ </span>
+ </div>
+ </div>
+ <div class="action-area">
+ <div id="create-profile-throbber" class="throbber"></div>
+ <button id="import-existing-managed-user-link" class="link-button"
+ i18n-content="importExistingManagedUserLink">
+ </button>
+ <div class="button-strip">
+ <button id="create-profile-cancel" i18n-content="cancel"></button>
+ <button id="create-profile-ok" i18n-content="createProfileConfirm"
+ class="default-button">
+ </button>
+ </div>
+ </div>
+ </div>
+</div>
diff --git a/chromium/chrome/browser/resources/options/manage_profile_overlay.js b/chromium/chrome/browser/resources/options/manage_profile_overlay.js
new file mode 100644
index 00000000000..2d6e64c717f
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/manage_profile_overlay.js
@@ -0,0 +1,704 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+ var OptionsPage = options.OptionsPage;
+ var ArrayDataModel = cr.ui.ArrayDataModel;
+
+ /**
+ * ManageProfileOverlay class
+ * Encapsulated handling of the 'Manage profile...' overlay page.
+ * @constructor
+ * @class
+ */
+ function ManageProfileOverlay() {
+ OptionsPage.call(this, 'manageProfile',
+ loadTimeData.getString('manageProfileTabTitle'),
+ '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;
+ options.ProfilesIconGrid.decorate($('manage-profile-icon-grid'));
+ options.ProfilesIconGrid.decorate($('create-profile-icon-grid'));
+ self.registerCommonEventHandlers_('create',
+ self.submitCreateProfile_.bind(self));
+ self.registerCommonEventHandlers_('manage',
+ self.submitManageChanges_.bind(self));
+
+ // Override the create-profile-ok and create-* keydown handlers, to avoid
+ // closing the overlay until we finish creating the profile.
+ $('create-profile-ok').onclick = function(event) {
+ self.submitCreateProfile_();
+ };
+
+ $('create-profile-cancel').onclick = function(event) {
+ CreateProfileOverlay.cancelCreateProfile();
+ };
+
+ $('import-existing-managed-user-link').hidden =
+ !loadTimeData.getBoolean('allowCreateExistingManagedUsers');
+
+ $('manage-profile-cancel').onclick =
+ $('delete-profile-cancel').onclick = function(event) {
+ OptionsPage.closeOverlay();
+ };
+ $('delete-profile-ok').onclick = function(event) {
+ OptionsPage.closeOverlay();
+ if (BrowserOptions.getCurrentProfile().isManaged)
+ return;
+ chrome.send('deleteProfile', [self.profileInfo_.filePath]);
+ };
+ $('add-shortcut-button').onclick = function(event) {
+ chrome.send('addProfileShortcut', [self.profileInfo_.filePath]);
+ };
+ $('remove-shortcut-button').onclick = function(event) {
+ chrome.send('removeProfileShortcut', [self.profileInfo_.filePath]);
+ };
+
+ $('create-profile-managed-signed-in-learn-more-link').onclick =
+ function(event) {
+ OptionsPage.navigateToPage('managedUserLearnMore');
+ return false;
+ };
+
+ $('create-profile-managed-not-signed-in-link').onclick = function(event) {
+ // The signin process will open an overlay to configure sync, which
+ // would replace this overlay. It's smoother to close this one now.
+ // TODO(pamg): Move the sync-setup overlay to a higher layer so this one
+ // can stay open under it, after making sure that doesn't break anything
+ // else.
+ OptionsPage.closeOverlay();
+ SyncSetupOverlay.startSignIn();
+ };
+
+ $('create-profile-managed-sign-in-again-link').onclick = function(event) {
+ OptionsPage.closeOverlay();
+ SyncSetupOverlay.showSetupUI();
+ };
+
+ $('import-existing-managed-user-link').onclick = function(event) {
+ OptionsPage.closeOverlay();
+ OptionsPage.navigateToPage('managedUserImport');
+ };
+ },
+
+ /** @override */
+ didShowPage: function() {
+ chrome.send('requestDefaultProfileIcons');
+
+ // Just ignore the manage profile dialog on Chrome OS, they use /accounts.
+ if (!cr.isChromeOS && window.location.pathname == '/manageProfile')
+ ManageProfileOverlay.getInstance().prepareForManageDialog_();
+
+ // When editing a profile, initially hide the "add shortcut" and
+ // "remove shortcut" buttons and ask the handler which to show. It will
+ // call |receiveHasProfileShortcuts|, which will show the appropriate one.
+ $('remove-shortcut-button').hidden = true;
+ $('add-shortcut-button').hidden = true;
+
+ if (loadTimeData.getBoolean('profileShortcutsEnabled')) {
+ var profileInfo = ManageProfileOverlay.getInstance().profileInfo_;
+ chrome.send('requestHasProfileShortcuts', [profileInfo.filePath]);
+ }
+
+ var manageNameField = $('manage-profile-name');
+ // Supervised users cannot edit their names.
+ if (manageNameField.disabled)
+ $('manage-profile-ok').focus();
+ else
+ manageNameField.focus();
+ },
+
+ /**
+ * Registers event handlers that are common between create and manage modes.
+ * @param {string} mode A label that specifies the type of dialog
+ * box which is currently being viewed (i.e. 'create' or
+ * 'manage').
+ * @param {function()} submitFunction The function that should be called
+ * when the user chooses to submit (e.g. by clicking the OK button).
+ * @private
+ */
+ registerCommonEventHandlers_: function(mode, submitFunction) {
+ var self = this;
+ $(mode + '-profile-icon-grid').addEventListener('change', function(e) {
+ self.onIconGridSelectionChanged_(mode);
+ });
+ $(mode + '-profile-name').oninput = function(event) {
+ self.onNameChanged_(event, mode);
+ };
+ $(mode + '-profile-ok').onclick = function(event) {
+ OptionsPage.closeOverlay();
+ submitFunction();
+ };
+ },
+
+ /**
+ * 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,
+ * isManaged: false
+ * };
+ * @param {string} mode A label that specifies the type of dialog
+ * box which is currently being viewed (i.e. 'create' or
+ * 'manage').
+ * @private
+ */
+ setProfileInfo_: function(profileInfo, mode) {
+ this.iconGridSelectedURL_ = profileInfo.iconURL;
+ this.profileInfo_ = profileInfo;
+ $(mode + '-profile-name').value = profileInfo.name;
+ $(mode + '-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(iconGrid, iconURLs) {
+ $(iconGrid).dataModel = new ArrayDataModel(iconURLs);
+
+ if (this.profileInfo_)
+ $(iconGrid).selectedItem = this.profileInfo_.iconURL;
+
+ var grid = $(iconGrid);
+ // Recalculate the measured item size.
+ grid.measured_ = null;
+ grid.columns = 0;
+ grid.redraw();
+ },
+
+ /**
+ * Callback to set the initial values when creating a new profile.
+ * @param {Object} profileInfo An object of the form:
+ * profileInfo = {
+ * name: "Profile Name",
+ * iconURL: "chrome://path/to/icon/image",
+ * };
+ * @private
+ */
+ receiveNewProfileDefaults_: function(profileInfo) {
+ ManageProfileOverlay.setProfileInfo(profileInfo, 'create');
+ $('create-profile-name-label').hidden = false;
+ $('create-profile-name').hidden = false;
+ // Trying to change the focus if this isn't the topmost overlay can
+ // instead cause the FocusManager to override another overlay's focus,
+ // e.g. if an overlay above this one is in the process of being reloaded.
+ // But the C++ handler calls this method directly on ManageProfileOverlay,
+ // so check the pageDiv to also include its subclasses (in particular
+ // CreateProfileOverlay, which has higher sub-overlays).
+ if (OptionsPage.getTopmostVisiblePage().pageDiv == this.pageDiv) {
+ // This will only have an effect if the 'create-profile-name' element
+ // is visible, i.e. if the overlay is in create mode.
+ $('create-profile-name').focus();
+ }
+ $('create-profile-ok').disabled = false;
+ },
+
+ /**
+ * 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;
+ },
+
+ /**
+ * Callback to show the add/remove shortcut buttons when in edit mode,
+ * called by the handler as a result of the 'requestHasProfileShortcuts_'
+ * message.
+ * @param {boolean} hasShortcuts Whether profile has any existing shortcuts.
+ * @private
+ */
+ receiveHasProfileShortcuts_: function(hasShortcuts) {
+ $('add-shortcut-button').hidden = hasShortcuts;
+ $('remove-shortcut-button').hidden = !hasShortcuts;
+ },
+
+ /**
+ * Display the error bubble, with |errorText| in the bubble.
+ * @param {string} errorText The string to display as an error.
+ * @param {string} mode A label that specifies the type of dialog
+ * box which is currently being viewed (i.e. 'create' or
+ * 'manage').
+ * @param {boolean} disableOKButton True if the dialog's OK button should be
+ * disabled when the error bubble is shown. It will be (re-)enabled when
+ * the error bubble is hidden.
+ * @private
+ */
+ showErrorBubble_: function(errorText, mode, disableOKButton) {
+ var nameErrorEl = $(mode + '-profile-error-bubble');
+ nameErrorEl.hidden = false;
+ nameErrorEl.textContent = errorText;
+
+ if (disableOKButton)
+ $(mode + '-profile-ok').disabled = true;
+ },
+
+ /**
+ * Hide the error bubble.
+ * @param {string} mode A label that specifies the type of dialog
+ * box which is currently being viewed (i.e. 'create' or
+ * 'manage').
+ * @private
+ */
+ hideErrorBubble_: function(mode) {
+ $(mode + '-profile-error-bubble').hidden = true;
+ $(mode + '-profile-ok').disabled = false;
+ },
+
+ /**
+ * oninput callback for <input> field.
+ * @param {Event} event The event object.
+ * @param {string} mode A label that specifies the type of dialog
+ * box which is currently being viewed (i.e. 'create' or
+ * 'manage').
+ * @private
+ */
+ onNameChanged_: function(event, mode) {
+ var newName = event.target.value;
+ var oldName = this.profileInfo_.name;
+
+ if (newName == oldName) {
+ this.hideErrorBubble_(mode);
+ } else if (this.profileNames_[newName] != undefined) {
+ var errorText =
+ loadTimeData.getString('manageProfilesDuplicateNameError');
+ this.showErrorBubble_(errorText, mode, true);
+ } else {
+ this.hideErrorBubble_(mode);
+
+ var nameIsValid = $(mode + '-profile-name').validity.valid;
+ $(mode + '-profile-ok').disabled = !nameIsValid;
+ }
+ },
+
+ /**
+ * Called when the user clicks "OK" or hits enter. Saves the newly changed
+ * profile info.
+ * @private
+ */
+ submitManageChanges_: function() {
+ var name = $('manage-profile-name').value;
+ var iconURL = $('manage-profile-icon-grid').selectedItem;
+
+ chrome.send('setProfileIconAndName',
+ [this.profileInfo_.filePath, iconURL, name]);
+ },
+
+ /**
+ * Called when the user clicks "OK" or hits enter. Creates the profile
+ * using the information in the dialog.
+ * @private
+ */
+ submitCreateProfile_: function() {
+ // This is visual polish: the UI to access this should be disabled for
+ // managed users, and the back end will prevent user creation anyway.
+ if (this.profileInfo_ && this.profileInfo_.isManaged)
+ return;
+
+ this.hideErrorBubble_('create');
+ CreateProfileOverlay.updateCreateInProgress(true);
+
+ // Get the user's chosen name and icon, or default if they do not
+ // wish to customize their profile.
+ var name = $('create-profile-name').value;
+ var iconUrl = $('create-profile-icon-grid').selectedItem;
+ var createShortcut = $('create-shortcut').checked;
+ var isManaged = $('create-profile-managed').checked;
+ var existingManagedUserId = '';
+
+ // 'createProfile' is handled by the CreateProfileHandler.
+ chrome.send('createProfile',
+ [name, iconUrl, createShortcut,
+ isManaged, existingManagedUserId]);
+ },
+
+ /**
+ * Called when the selected icon in the icon grid changes.
+ * @param {string} mode A label that specifies the type of dialog
+ * box which is currently being viewed (i.e. 'create' or
+ * 'manage').
+ * @private
+ */
+ onIconGridSelectionChanged_: function(mode) {
+ var iconURL = $(mode + '-profile-icon-grid').selectedItem;
+ if (!iconURL || iconURL == this.iconGridSelectedURL_)
+ return;
+ this.iconGridSelectedURL_ = iconURL;
+ if (this.profileInfo_ && this.profileInfo_.filePath) {
+ chrome.send('profileIconSelectionChanged',
+ [this.profileInfo_.filePath, iconURL]);
+ }
+ },
+
+ /**
+ * Updates the contents of the "Manage Profile" section of the dialog,
+ * and shows that section.
+ * @private
+ */
+ prepareForManageDialog_: function() {
+ var profileInfo = BrowserOptions.getCurrentProfile();
+ ManageProfileOverlay.setProfileInfo(profileInfo, 'manage');
+ $('manage-profile-overlay-create').hidden = true;
+ $('manage-profile-overlay-manage').hidden = false;
+ $('manage-profile-overlay-delete').hidden = true;
+ $('manage-profile-name').disabled = profileInfo.isManaged;
+ this.hideErrorBubble_('manage');
+ },
+
+ /**
+ * Display the "Manage Profile" dialog.
+ * @private
+ */
+ showManageDialog_: function() {
+ this.prepareForManageDialog_();
+ OptionsPage.navigateToPage('manageProfile');
+ },
+
+ /**
+ * Display the "Delete Profile" dialog.
+ * @param {Object} profileInfo The profile object of the profile to delete.
+ * @private
+ */
+ showDeleteDialog_: function(profileInfo) {
+ if (BrowserOptions.getCurrentProfile().isManaged)
+ return;
+
+ ManageProfileOverlay.setProfileInfo(profileInfo, 'manage');
+ $('manage-profile-overlay-create').hidden = true;
+ $('manage-profile-overlay-manage').hidden = true;
+ $('manage-profile-overlay-delete').hidden = false;
+ $('delete-profile-icon').style.content =
+ imageset(profileInfo.iconURL + '@scalefactorx');
+ $('delete-profile-text').textContent =
+ loadTimeData.getStringF('deleteProfileMessage', profileInfo.name);
+ $('delete-managed-profile-addendum').hidden = !profileInfo.isManaged;
+
+ // Because this dialog isn't useful when refreshing or as part of the
+ // history, don't create a history entry for it when showing.
+ OptionsPage.showPageByName('manageProfile', false);
+ },
+
+ /**
+ * Display the "Create Profile" dialog.
+ * @private
+ */
+ showCreateDialog_: function() {
+ OptionsPage.navigateToPage('createProfile');
+ },
+ };
+
+ // Forward public APIs to private implementations.
+ [
+ 'receiveDefaultProfileIcons',
+ 'receiveNewProfileDefaults',
+ 'receiveProfileNames',
+ 'receiveHasProfileShortcuts',
+ 'setProfileInfo',
+ 'setProfileName',
+ 'showManageDialog',
+ 'showDeleteDialog',
+ 'showCreateDialog',
+ ].forEach(function(name) {
+ ManageProfileOverlay[name] = function() {
+ var instance = ManageProfileOverlay.getInstance();
+ return instance[name + '_'].apply(instance, arguments);
+ };
+ });
+
+ function CreateProfileOverlay() {
+ OptionsPage.call(this, 'createProfile',
+ loadTimeData.getString('createProfileTabTitle'),
+ 'manage-profile-overlay');
+ };
+
+ cr.addSingletonGetter(CreateProfileOverlay);
+
+ CreateProfileOverlay.prototype = {
+ // Inherit from ManageProfileOverlay.
+ __proto__: ManageProfileOverlay.prototype,
+
+ // The signed-in email address of the current profile, or empty if they're
+ // not signed in.
+ signedInEmail_: '',
+
+ /** @override */
+ canShowPage: function() {
+ return !BrowserOptions.getCurrentProfile().isManaged;
+ },
+
+ /**
+ * Configures the overlay to the "create user" mode.
+ * @override
+ */
+ didShowPage: function() {
+ chrome.send('requestCreateProfileUpdate');
+ chrome.send('requestDefaultProfileIcons');
+ chrome.send('requestNewProfileDefaults');
+
+ $('manage-profile-overlay-create').hidden = false;
+ $('manage-profile-overlay-manage').hidden = true;
+ $('manage-profile-overlay-delete').hidden = true;
+ $('create-profile-instructions').textContent =
+ loadTimeData.getStringF('createProfileInstructions');
+ this.hideErrorBubble_();
+ this.updateCreateInProgress_(false);
+
+ var shortcutsEnabled = loadTimeData.getBoolean('profileShortcutsEnabled');
+ $('create-shortcut-container').hidden = !shortcutsEnabled;
+ $('create-shortcut').checked = shortcutsEnabled;
+
+ $('create-profile-name-label').hidden = true;
+ $('create-profile-name').hidden = true;
+ $('create-profile-ok').disabled = true;
+
+ $('create-profile-managed').checked = false;
+ $('create-profile-managed-signed-in').disabled = true;
+ $('create-profile-managed-signed-in').hidden = true;
+ $('create-profile-managed-not-signed-in').hidden = true;
+ },
+
+ /** @override */
+ handleCancel: function() {
+ this.cancelCreateProfile_();
+ },
+
+ /** @override */
+ showErrorBubble_: function(errorText) {
+ ManageProfileOverlay.getInstance().showErrorBubble_(errorText,
+ 'create',
+ false);
+ },
+
+ /** @override */
+ hideErrorBubble_: function() {
+ ManageProfileOverlay.getInstance().hideErrorBubble_('create');
+ },
+
+ /**
+ * Updates the UI when a profile create step begins or ends.
+ * Note that hideErrorBubble_() also enables the "OK" button, so it
+ * must be called before this function if both are used.
+ * @param {boolean} inProgress True if the UI should be updated to show that
+ * profile creation is now in progress.
+ * @private
+ */
+ updateCreateInProgress_: function(inProgress) {
+ this.createInProgress_ = inProgress;
+ this.updateCreateManagedUserCheckbox_();
+
+ $('create-profile-icon-grid').disabled = inProgress;
+ $('create-profile-name').disabled = inProgress;
+ $('create-shortcut').disabled = inProgress;
+ $('create-profile-ok').disabled = inProgress;
+
+ $('create-profile-throbber').hidden = !inProgress;
+ },
+
+ /**
+ * Cancels the creation of the a profile. It is safe to call this even
+ * when no profile is in the process of being created.
+ * @private
+ */
+ cancelCreateProfile_: function() {
+ OptionsPage.closeOverlay();
+ chrome.send('cancelCreateProfile');
+ this.hideErrorBubble_();
+ this.updateCreateInProgress_(false);
+ },
+
+ /**
+ * Shows an error message describing an error that occurred while creating
+ * a new profile.
+ * Called by BrowserOptions via the BrowserOptionsHandler.
+ * @param {string} error The error message to display.
+ * @private
+ */
+ onError_: function(error) {
+ this.updateCreateInProgress_(false);
+ this.showErrorBubble_(error);
+ },
+
+ /**
+ * Shows a warning message giving information while creating a new profile.
+ * Called by BrowserOptions via the BrowserOptionsHandler.
+ * @param {string} warning The warning message to display.
+ * @private
+ */
+ onWarning_: function(warning) {
+ this.showErrorBubble_(warning);
+ },
+
+ /**
+ * For new supervised users, shows a confirmation page after successfully
+ * creating a new profile; otherwise, the handler will open a new window.
+ * @param {Object} profileInfo An object of the form:
+ * profileInfo = {
+ * name: "Profile Name",
+ * filePath: "/path/to/profile/data/on/disk"
+ * isManaged: (true|false),
+ * };
+ * @private
+ */
+ onSuccess_: function(profileInfo) {
+ this.updateCreateInProgress_(false);
+ OptionsPage.closeOverlay();
+ if (profileInfo.isManaged) {
+ profileInfo.custodianEmail = this.signedInEmail_;
+ ManagedUserCreateConfirmOverlay.setProfileInfo(profileInfo);
+ OptionsPage.showPageByName('managedUserCreateConfirm', false);
+ BrowserOptions.updateManagesSupervisedUsers(true);
+ }
+ },
+
+ /**
+ * Updates the signed-in or not-signed-in UI when in create mode. Called by
+ * the handler in response to the 'requestCreateProfileUpdate' message.
+ * updateManagedUsersAllowed_ is expected to be called after this is, and
+ * will update additional UI elements.
+ * @param {string} email The email address of the currently signed-in user.
+ * An empty string indicates that the user is not signed in.
+ * @param {boolean} hasError Whether the user's sign-in credentials are
+ * still valid.
+ * @private
+ */
+ updateSignedInStatus_: function(email, hasError) {
+ this.signedInEmail_ = email;
+ this.hasError_ = hasError;
+ var isSignedIn = email !== '';
+ $('create-profile-managed-signed-in').hidden = !isSignedIn;
+ $('create-profile-managed-not-signed-in').hidden = isSignedIn;
+
+ if (isSignedIn) {
+ var accountDetailsOutOfDate =
+ $('create-profile-managed-account-details-out-of-date-label');
+ accountDetailsOutOfDate.textContent = loadTimeData.getStringF(
+ 'manageProfilesManagedAccountDetailsOutOfDate', email);
+ accountDetailsOutOfDate.hidden = !hasError;
+
+ $('create-profile-managed-signed-in-label').textContent =
+ loadTimeData.getStringF(
+ 'manageProfilesManagedSignedInLabel', email);
+ $('create-profile-managed-signed-in-label').hidden = hasError;
+
+ $('create-profile-managed-sign-in-again-link').hidden = !hasError;
+ $('create-profile-managed-signed-in-learn-more-link').hidden = hasError;
+ }
+
+ this.updateImportExistingManagedUserLink_(isSignedIn && !hasError);
+ },
+
+ /**
+ * Enables/disables the 'import existing managed users' link button.
+ * It also updates the button text.
+ * @param {boolean} enable True to enable the link button and
+ * false otherwise.
+ * @private
+ */
+ updateImportExistingManagedUserLink_: function(enable) {
+ var importManagedUserElement = $('import-existing-managed-user-link');
+ importManagedUserElement.disabled = !enable;
+ importManagedUserElement.textContent = enable ?
+ loadTimeData.getString('importExistingManagedUserLink') :
+ loadTimeData.getString('signInToImportManagedUsers');
+ },
+
+ /**
+ * Sets whether creating managed users is allowed or not. Called by the
+ * handler in response to the 'requestCreateProfileUpdate' message or a
+ * change in the (policy-controlled) pref that prohibits creating managed
+ * users, after the signed-in status has been updated.
+ * @param {boolean} allowed True if creating managed users should be
+ * allowed.
+ * @private
+ */
+ updateManagedUsersAllowed_: function(allowed) {
+ this.managedUsersAllowed_ = allowed;
+ this.updateCreateManagedUserCheckbox_();
+
+ $('create-profile-managed-not-signed-in-link').hidden = !allowed;
+ if (!allowed) {
+ $('create-profile-managed-indicator').setAttribute('controlled-by',
+ 'policy');
+ } else {
+ $('create-profile-managed-indicator').removeAttribute('controlled-by');
+ }
+ },
+
+ /**
+ * Updates the status of the "create managed user" checkbox. Called from
+ * updateManagedUsersAllowed_() or updateCreateInProgress_().
+ * updateSignedInStatus_() does not call this method directly, because it
+ * will be followed by a call to updateManagedUsersAllowed_().
+ * @private
+ */
+ updateCreateManagedUserCheckbox_: function() {
+ $('create-profile-managed').disabled =
+ !this.managedUsersAllowed_ || this.createInProgress_ ||
+ this.signedInEmail_ == '' || this.hasError_;
+ },
+ };
+
+ // Forward public APIs to private implementations.
+ [
+ 'cancelCreateProfile',
+ 'onError',
+ 'onSuccess',
+ 'onWarning',
+ 'updateCreateInProgress',
+ 'updateManagedUsersAllowed',
+ 'updateSignedInStatus',
+ ].forEach(function(name) {
+ CreateProfileOverlay[name] = function() {
+ var instance = CreateProfileOverlay.getInstance();
+ return instance[name + '_'].apply(instance, arguments);
+ };
+ });
+
+ // Export
+ return {
+ ManageProfileOverlay: ManageProfileOverlay,
+ CreateProfileOverlay: CreateProfileOverlay,
+ };
+});
diff --git a/chromium/chrome/browser/resources/options/managed_user_create_confirm.css b/chromium/chrome/browser/resources/options/managed_user_create_confirm.css
new file mode 100644
index 00000000000..a1e8af8b40b
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/managed_user_create_confirm.css
@@ -0,0 +1,46 @@
+/* Copyright 2013 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. */
+
+#managed-user-created {
+ width: 722px;
+}
+
+#managed-user-created-title {
+ padding: 20px;
+ word-wrap: break-word;
+}
+
+@media only screen and (max-height:400px) {
+
+/* Omit the image on very small screens. */
+#managed-user-created-image {
+ display: none;
+}
+
+} /* @media only screen and (max-height:400px) */
+
+#managed-user-created-image {
+ -webkit-border-radius: 3px 3px 0 0;
+ -webkit-box-flex: 5;
+ background-image: -webkit-image-set(
+ url('../../../../ui/resources/default_100_percent/supervised_illustration_done.png') 1x,
+ url('../../../../ui/resources/default_200_percent/supervised_illustration_done.png') 2x);
+ background-position: center;
+ height: 344px;
+}
+
+#managed-user-created-text {
+ padding: 0 20px 0 20px;
+ white-space: pre-wrap;
+ word-wrap: break-word;
+}
+
+#managed-user-created-switch {
+ max-width: 600px;
+ word-wrap: break-word;
+}
+
+#managed-user-created-action-area {
+ padding: 20px;
+}
diff --git a/chromium/chrome/browser/resources/options/managed_user_create_confirm.html b/chromium/chrome/browser/resources/options/managed_user_create_confirm.html
new file mode 100644
index 00000000000..346b9f015a6
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/managed_user_create_confirm.html
@@ -0,0 +1,13 @@
+<div id="managed-user-created" class="page" hidden>
+ <div class="close-button"></div>
+ <!-- Overlay for the confirmation after creating a supervised user. -->
+ <div id="managed-user-created-image"></div>
+ <h1 id="managed-user-created-title"></h1>
+ <div id="managed-user-created-text" class="content-area"></div>
+ <div id="managed-user-created-action-area" class="action-area button-strip">
+ <button id="managed-user-created-done"
+ i18n-content="managedUserCreatedDone">
+ </button>
+ <button id="managed-user-created-switch"></button>
+ </div>
+</div>
diff --git a/chromium/chrome/browser/resources/options/managed_user_create_confirm.js b/chromium/chrome/browser/resources/options/managed_user_create_confirm.js
new file mode 100644
index 00000000000..de91bf9be9d
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/managed_user_create_confirm.js
@@ -0,0 +1,118 @@
+// Copyright 2013 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;
+
+ /**
+ * ManagedUserCreateConfirm class.
+ * Encapsulated handling of the confirmation overlay page when creating a
+ * managed user.
+ * @constructor
+ * @class
+ */
+ function ManagedUserCreateConfirmOverlay() {
+ OptionsPage.call(this, 'managedUserCreateConfirm',
+ '', // The title will be based on the new profile name.
+ 'managed-user-created');
+ };
+
+ cr.addSingletonGetter(ManagedUserCreateConfirmOverlay);
+
+ ManagedUserCreateConfirmOverlay.prototype = {
+ // Inherit from OptionsPage.
+ __proto__: OptionsPage.prototype,
+
+ // Info about the newly created profile.
+ profileInfo_: null,
+
+ /**
+ * Initialize the page.
+ */
+ initializePage: function() {
+ OptionsPage.prototype.initializePage.call(this);
+
+ $('managed-user-created-done').onclick = function(event) {
+ OptionsPage.closeOverlay();
+ };
+
+ var self = this;
+
+ $('managed-user-created-switch').onclick = function(event) {
+ OptionsPage.closeOverlay();
+ chrome.send('switchToProfile', [self.profileInfo_.filePath]);
+ };
+ },
+
+ /** @override */
+ didShowPage: function() {
+ $('managed-user-created-switch').focus();
+ },
+
+ /**
+ * Sets the profile info used in the dialog and updates the profile name
+ * displayed. Called by the profile creation overlay when this overlay is
+ * opened.
+ * @param {Object} info An object of the form:
+ * info = {
+ * name: "Profile Name",
+ * filePath: "/path/to/profile/data/on/disk",
+ * isManaged: (true|false)
+ * custodianEmail: "example@gmail.com"
+ * };
+ * @private
+ */
+ setProfileInfo_: function(info) {
+ function HTMLEscape(original) {
+ return original.replace(/&/g, '&amp;')
+ .replace(/</g, '&lt;')
+ .replace(/>/g, '&gt;')
+ .replace(/"/g, '&quot;')
+ .replace(/'/g, '&#39;');
+ }
+
+ var MAX_LENGTH = 50;
+ function elide(original) {
+ if (original.length <= MAX_LENGTH)
+ return original;
+ return original.substring(0, MAX_LENGTH - 3) + '...';
+ }
+
+ this.profileInfo_ = info;
+ var elidedName = elide(info.name);
+ $('managed-user-created-title').textContent =
+ loadTimeData.getStringF('managedUserCreatedTitle', elidedName);
+ $('managed-user-created-switch').textContent =
+ loadTimeData.getStringF('managedUserCreatedSwitch', elidedName);
+
+ // HTML-escape the user-supplied strings before putting them into
+ // innerHTML. This is probably excessive for the email address, but
+ // belt-and-suspenders is cheap here.
+ $('managed-user-created-text').innerHTML =
+ loadTimeData.getStringF('managedUserCreatedText',
+ HTMLEscape(elidedName),
+ HTMLEscape(elide(info.custodianEmail)));
+ },
+
+ /** @override */
+ canShowPage: function() {
+ return this.profileInfo_ != null;
+ },
+ };
+
+ // Forward public APIs to private implementations.
+ [
+ 'setProfileInfo',
+ ].forEach(function(name) {
+ ManagedUserCreateConfirmOverlay[name] = function() {
+ var instance = ManagedUserCreateConfirmOverlay.getInstance();
+ return instance[name + '_'].apply(instance, arguments);
+ };
+ });
+
+ // Export
+ return {
+ ManagedUserCreateConfirmOverlay: ManagedUserCreateConfirmOverlay,
+ };
+});
diff --git a/chromium/chrome/browser/resources/options/managed_user_import.css b/chromium/chrome/browser/resources/options/managed_user_import.css
new file mode 100644
index 00000000000..9dae8f1db4e
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/managed_user_import.css
@@ -0,0 +1,75 @@
+/* Copyright 2013 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. */
+
+#create-new-user-link {
+ position: absolute;
+}
+
+#managed-user-import {
+ width: 612px;
+}
+
+#managed-user-import-text {
+ padding-bottom: 10px;
+ padding-left: 17px;
+ white-space: pre-wrap;
+ word-wrap: break-word;
+}
+
+#managed-user-list {
+ height: 240px;
+ margin-bottom: 10px;
+}
+
+#managed-user-list .profile-name {
+ -webkit-box-flex: 1;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+#managed-user-list > * {
+ height: 40px;
+}
+
+#managed-user-list:focus {
+ border-color: rgb(77, 144, 254);
+}
+
+#select-avatar-grid {
+ background-color: rgba(255, 255, 255, 0.75);
+ padding: 2px;
+}
+
+#managed-user-import-error-bubble {
+ -webkit-transition: max-height 200ms, padding 200ms;
+ background-color: rgb(238, 185, 57);
+ border-radius: 4px;
+ font-weight: bold;
+ margin-left: auto;
+ margin-right: auto;
+ margin-top: 10px;
+ max-height: 50px;
+ overflow: hidden;
+ padding: 1px 10px;
+ text-align: center;
+ width: 80%;
+}
+
+#managed-user-import-error-bubble[hidden] {
+ max-height: 0;
+}
+
+.profile-img-disabled {
+ opacity: 0.4;
+}
+
+.profile-name-disabled {
+ color: rgb(153, 153, 153);
+}
+
+.already-on-this-device {
+ padding-left: 20px;
+ padding-right: 6px;
+}
diff --git a/chromium/chrome/browser/resources/options/managed_user_import.html b/chromium/chrome/browser/resources/options/managed_user_import.html
new file mode 100644
index 00000000000..db2674dc953
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/managed_user_import.html
@@ -0,0 +1,24 @@
+<div id="managed-user-import" class="page" hidden>
+ <div class="close-button"></div>
+ <!-- Overlay to import an existing managed user during user creation -->
+ <h1 id="managed-user-import-title"></h1>
+ <div id="managed-user-import-text">
+ </div>
+ <div id="managed-user-import-content-area" class="content-area">
+ <list id="managed-user-list" class="settings-list"></list>
+ <grid id="select-avatar-grid" hidden></grid>
+ </div>
+ <div id="managed-user-import-error-bubble" hidden></div>
+ <div id="managed-user-import-action-area" class="action-area">
+ <button id="create-new-user-link" class="link-button"
+ i18n-content="createNewUserLink">
+ </button>
+ <div class="button-strip">
+ <div id="managed-user-import-throbber" class="throbber"></div>
+ <button id="managed-user-import-cancel" i18n-content="cancel">
+ </button>
+ <button id="managed-user-import-ok" class="default-button">
+ </button>
+ </div>
+ </div>
+</div> \ No newline at end of file
diff --git a/chromium/chrome/browser/resources/options/managed_user_import.js b/chromium/chrome/browser/resources/options/managed_user_import.js
new file mode 100644
index 00000000000..70a54dd2cab
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/managed_user_import.js
@@ -0,0 +1,238 @@
+// Copyright 2013 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;
+
+ /**
+ * ManagedUserImportOverlay class.
+ * Encapsulated handling of the 'Import existing managed user' overlay page.
+ * @constructor
+ * @class
+ */
+ function ManagedUserImportOverlay() {
+ var title = loadTimeData.getString('managedUserImportTitle');
+ OptionsPage.call(this, 'managedUserImport',
+ title, 'managed-user-import');
+ };
+
+ cr.addSingletonGetter(ManagedUserImportOverlay);
+
+ ManagedUserImportOverlay.prototype = {
+ // Inherit from OptionsPage.
+ __proto__: OptionsPage.prototype,
+
+ /** @override */
+ canShowPage: function() {
+ return !BrowserOptions.getCurrentProfile().isManaged;
+ },
+
+ /**
+ * Initialize the page.
+ */
+ initializePage: function() {
+ // Call base class implementation to start preference initialization.
+ OptionsPage.prototype.initializePage.call(this);
+
+ var managedUserList = $('managed-user-list');
+ options.managedUserOptions.ManagedUserList.decorate(managedUserList);
+
+ var avatarGrid = $('select-avatar-grid');
+ options.ProfilesIconGrid.decorate(avatarGrid);
+ var avatarIcons = loadTimeData.getValue('avatarIcons');
+ avatarGrid.dataModel = new ArrayDataModel(avatarIcons);
+
+ managedUserList.addEventListener('change', function(event) {
+ var managedUser = managedUserList.selectedItem;
+ if (!managedUser)
+ return;
+
+ $('managed-user-import-ok').disabled =
+ managedUserList.selectedItem.onCurrentDevice;
+ });
+
+ var self = this;
+ $('managed-user-import-cancel').onclick = function(event) {
+ OptionsPage.closeOverlay();
+ self.updateImportInProgress_(false);
+
+ // 'cancelCreateProfile' is handled by CreateProfileHandler.
+ chrome.send('cancelCreateProfile');
+ };
+
+ $('managed-user-import-ok').onclick =
+ this.showAvatarGridOrSubmit_.bind(this);
+
+ $('create-new-user-link').onclick = function(event) {
+ OptionsPage.closeOverlay();
+ OptionsPage.navigateToPage('createProfile');
+ };
+ },
+
+ /**
+ * @override
+ */
+ didShowPage: function() {
+ chrome.send('requestManagedUserImportUpdate');
+
+ this.updateImportInProgress_(false);
+ $('managed-user-import-error-bubble').hidden = true;
+ $('managed-user-import-ok').disabled = true;
+ $('select-avatar-grid').hidden = true;
+ $('managed-user-list').hidden = false;
+
+ $('managed-user-import-ok').textContent =
+ loadTimeData.getString('managedUserImportOk');
+ $('managed-user-import-text').textContent =
+ loadTimeData.getString('managedUserImportText');
+ $('managed-user-import-title').textContent =
+ loadTimeData.getString('managedUserImportTitle');
+ },
+
+ /**
+ * Called when the user clicks the "OK" button. In case the managed
+ * user being imported has no avatar in sync, it shows the avatar
+ * icon grid. In case the avatar grid is visible or the managed user
+ * already has an avatar stored in sync, it proceeds with importing
+ * the managed user.
+ * @private
+ */
+ showAvatarGridOrSubmit_: function() {
+ var managedUser = $('managed-user-list').selectedItem;
+ if (!managedUser)
+ return;
+
+ $('managed-user-import-error-bubble').hidden = true;
+
+ if ($('select-avatar-grid').hidden && managedUser.needAvatar) {
+ this.showAvatarGridHelper_();
+ return;
+ }
+
+ var avatarUrl = managedUser.needAvatar ?
+ $('select-avatar-grid').selectedItem : managedUser.iconURL;
+
+ this.updateImportInProgress_(true);
+
+ // 'createProfile' is handled by CreateProfileHandler.
+ chrome.send('createProfile', [managedUser.name, avatarUrl,
+ false, true, managedUser.id]);
+ },
+
+ /**
+ * Hides the 'managed user list' and shows the avatar grid instead.
+ * It also updates the overlay text and title to instruct the user
+ * to choose an avatar for the supervised user.
+ * @private
+ */
+ showAvatarGridHelper_: function() {
+ $('managed-user-list').hidden = true;
+ $('select-avatar-grid').hidden = false;
+ $('select-avatar-grid').redraw();
+ $('select-avatar-grid').selectedItem =
+ loadTimeData.getValue('avatarIcons')[0];
+
+ $('managed-user-import-ok').textContent =
+ loadTimeData.getString('managedUserSelectAvatarOk');
+ $('managed-user-import-text').textContent =
+ loadTimeData.getString('managedUserSelectAvatarText');
+ $('managed-user-import-title').textContent =
+ loadTimeData.getString('managedUserSelectAvatarTitle');
+ },
+
+ /**
+ * Updates the UI according to the importing state.
+ * @param {boolean} inProgress True to indicate that
+ * importing is in progress and false otherwise.
+ * @private
+ */
+ updateImportInProgress_: function(inProgress) {
+ $('managed-user-import-ok').disabled = inProgress;
+ $('managed-user-list').disabled = inProgress;
+ $('select-avatar-grid').disabled = inProgress;
+ $('create-new-user-link').disabled = inProgress;
+ $('managed-user-import-throbber').hidden = !inProgress;
+ },
+
+ /**
+ * Adds all the existing |managedUsers| to the list. If |managedUsers|
+ * is undefined, then the list is cleared.
+ * @param {Array.<Object>} managedUsers An array of managed user objects.
+ * Each object is of the form:
+ * managedUser = {
+ * id: "Managed User ID",
+ * name: "Managed User Name",
+ * iconURL: "chrome://path/to/icon/image",
+ * onCurrentDevice: true or false,
+ * needAvatar: true or false
+ * }
+ * @private
+ */
+ receiveExistingManagedUsers_: function(managedUsers) {
+ if (!managedUsers) {
+ $('managed-user-list').dataModel = null;
+ return;
+ }
+
+ managedUsers.sort(function(a, b) {
+ return a.name.localeCompare(b.name);
+ });
+
+ $('managed-user-list').dataModel = new ArrayDataModel(managedUsers);
+ if (managedUsers.length == 0) {
+ this.onError_(loadTimeData.getString('noExistingManagedUsers'));
+ $('managed-user-import-ok').disabled = true;
+ }
+ },
+
+ /**
+ * @private
+ */
+ hideErrorBubble_: function() {
+ $('managed-user-import-error-bubble').hidden = true;
+ },
+
+ /**
+ * Displays an error message if an error occurs while
+ * importing a managed user.
+ * Called by BrowserOptions via the BrowserOptionsHandler.
+ * @param {string} error The error message to display.
+ * @private
+ */
+ onError_: function(error) {
+ var errorBubble = $('managed-user-import-error-bubble');
+ errorBubble.hidden = false;
+ errorBubble.textContent = error;
+ this.updateImportInProgress_(false);
+ },
+
+ /**
+ * Closes the overlay if importing the managed user was successful.
+ * @private
+ */
+ onSuccess_: function() {
+ this.updateImportInProgress_(false);
+ OptionsPage.closeOverlay();
+ },
+ };
+
+ // Forward public APIs to private implementations.
+ [
+ 'hideErrorBubble',
+ 'onError',
+ 'onSuccess',
+ 'receiveExistingManagedUsers',
+ ].forEach(function(name) {
+ ManagedUserImportOverlay[name] = function() {
+ var instance = ManagedUserImportOverlay.getInstance();
+ return instance[name + '_'].apply(instance, arguments);
+ };
+ });
+
+ // Export
+ return {
+ ManagedUserImportOverlay: ManagedUserImportOverlay,
+ };
+});
diff --git a/chromium/chrome/browser/resources/options/managed_user_learn_more.css b/chromium/chrome/browser/resources/options/managed_user_learn_more.css
new file mode 100644
index 00000000000..d22a9a7b841
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/managed_user_learn_more.css
@@ -0,0 +1,40 @@
+/* Copyright 2013 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. */
+
+#managed-user-learn-more {
+ width: 722px;
+}
+
+#managed-user-learn-more-title {
+ padding: 20px;
+}
+
+#managed-user-learn-more-text {
+ padding: 0 20px 0 20px;
+ white-space: pre-wrap;
+ word-wrap: break-word;
+}
+
+@media only screen and (max-height:500px) {
+
+/* Omit the image on very small screens. */
+#managed-user-learn-more-image {
+ display: none;
+}
+
+} /* @media only screen and (max-height:500px) */
+
+#managed-user-learn-more-image {
+ -webkit-border-radius: 3px 3px 0 0;
+ -webkit-box-flex: 1;
+ background-image: -webkit-image-set(
+ url('../../../../ui/resources/default_100_percent/supervised_illustration_start.png') 1x,
+ url('../../../../ui/resources/default_200_percent/supervised_illustration_start.png') 2x);
+ background-position: center;
+ height: 344px;
+}
+
+#managed-user-learn-more-action-area {
+ padding: 20px;
+}
diff --git a/chromium/chrome/browser/resources/options/managed_user_learn_more.html b/chromium/chrome/browser/resources/options/managed_user_learn_more.html
new file mode 100644
index 00000000000..5eea88128e6
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/managed_user_learn_more.html
@@ -0,0 +1,17 @@
+<div id="managed-user-learn-more" class="page" hidden>
+ <div class="close-button"></div>
+ <!-- Overlay for the 'Learn more' link when creating a supervised user. -->
+ <div id="managed-user-learn-more-image"></div>
+ <h1 id="managed-user-learn-more-title"
+ i18n-content="managedUserLearnMoreTitle">
+ </h1>
+ <div id="managed-user-learn-more-text" class="content-area"
+ i18n-values=".innerHTML:managedUserLearnMoreText">
+ </div>
+ <div id="managed-user-learn-more-action-area"
+ class="action-area button-strip">
+ <button id="managed-user-learn-more-done"
+ i18n-content="managedUserLearnMoreDone">
+ </button>
+ </div>
+</div>
diff --git a/chromium/chrome/browser/resources/options/managed_user_learn_more.js b/chromium/chrome/browser/resources/options/managed_user_learn_more.js
new file mode 100644
index 00000000000..ab9d76ddd78
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/managed_user_learn_more.js
@@ -0,0 +1,42 @@
+// Copyright 2013 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;
+
+ /**
+ * ManagedUserLearnMore class.
+ * Encapsulated handling of the 'Learn more...' overlay page.
+ * @constructor
+ * @class
+ */
+ function ManagedUserLearnMoreOverlay() {
+ OptionsPage.call(this, 'managedUserLearnMore',
+ loadTimeData.getString('managedUserLearnMoreTitle'),
+ 'managed-user-learn-more');
+ };
+
+ cr.addSingletonGetter(ManagedUserLearnMoreOverlay);
+
+ ManagedUserLearnMoreOverlay.prototype = {
+ // Inherit from OptionsPage.
+ __proto__: OptionsPage.prototype,
+
+ /**
+ * Initialize the page.
+ */
+ initializePage: function() {
+ OptionsPage.prototype.initializePage.call(this);
+
+ $('managed-user-learn-more-done').onclick = function(event) {
+ OptionsPage.closeOverlay();
+ };
+ },
+ };
+
+ // Export
+ return {
+ ManagedUserLearnMoreOverlay: ManagedUserLearnMoreOverlay,
+ };
+});
diff --git a/chromium/chrome/browser/resources/options/managed_user_list.js b/chromium/chrome/browser/resources/options/managed_user_list.js
new file mode 100644
index 00000000000..4af84c64c14
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/managed_user_list.js
@@ -0,0 +1,117 @@
+// Copyright 2013 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.managedUserOptions', function() {
+ /** @const */ var List = cr.ui.List;
+ /** @const */ var ListItem = cr.ui.ListItem;
+ /** @const */ var ListSingleSelectionModel = cr.ui.ListSingleSelectionModel;
+
+ /**
+ * Create a new managed user list item.
+ * @param {Object} entry The managed user this item represents.
+ * It has the following form:
+ * managedUser = {
+ * id: "Managed User ID",
+ * name: "Managed User Name",
+ * iconURL: "chrome://path/to/icon/image",
+ * onCurrentDevice: true or false,
+ * needAvatar: true or false
+ * }
+ * @constructor
+ * @extends {cr.ui.ListItem}
+ */
+ function ManagedUserListItem(entry) {
+ var el = cr.doc.createElement('div');
+ el.managedUser_ = entry;
+ el.__proto__ = ManagedUserListItem.prototype;
+ el.decorate();
+ return el;
+ }
+
+ ManagedUserListItem.prototype = {
+ __proto__: ListItem.prototype,
+
+ /**
+ * @type {string} the ID of this managed user list item.
+ */
+ get id() {
+ return this.managedUser_.id;
+ },
+
+ /**
+ * @type {string} the name of this managed user list item.
+ */
+ get name() {
+ return this.managedUser_.name;
+ },
+
+ /**
+ * @type {string} the path to the avatar icon of this managed
+ * user list item.
+ */
+ get iconURL() {
+ return this.managedUser_.iconURL;
+ },
+
+ /** @override */
+ decorate: function() {
+ ListItem.prototype.decorate.call(this);
+ var managedUser = this.managedUser_;
+
+ // Add the avatar.
+ var iconElement = this.ownerDocument.createElement('img');
+ iconElement.className = 'profile-img';
+ iconElement.style.content =
+ imageset(managedUser.iconURL + '@scalefactorx');
+ this.appendChild(iconElement);
+
+ // Add the profile name.
+ var nameElement = this.ownerDocument.createElement('div');
+ nameElement.className = 'profile-name';
+ nameElement.textContent = managedUser.name;
+ this.appendChild(nameElement);
+
+ if (managedUser.onCurrentDevice) {
+ iconElement.className += ' profile-img-disabled';
+ nameElement.className += ' profile-name-disabled';
+
+ // Add "(already on this device)" message.
+ var alreadyOnDeviceElement = this.ownerDocument.createElement('div');
+ alreadyOnDeviceElement.className =
+ 'profile-name-disabled already-on-this-device';
+ alreadyOnDeviceElement.textContent =
+ loadTimeData.getString('managedUserAlreadyOnThisDevice');
+ this.appendChild(alreadyOnDeviceElement);
+ }
+ },
+ };
+
+ /**
+ * Create a new managed users list.
+ * @constructor
+ * @extends {cr.ui.List}
+ */
+ var ManagedUserList = cr.ui.define('list');
+
+ ManagedUserList.prototype = {
+ __proto__: List.prototype,
+
+ /** @override */
+ createItem: function(entry) {
+ return new ManagedUserListItem(entry);
+ },
+
+ /** @override */
+ decorate: function() {
+ List.prototype.decorate.call(this);
+ this.selectionModel = new ListSingleSelectionModel();
+ this.autoExpands = true;
+ },
+ };
+
+ return {
+ ManagedUserListItem: ManagedUserListItem,
+ ManagedUserList: ManagedUserList,
+ };
+});
diff --git a/chromium/chrome/browser/resources/options/media_galleries_list.js b/chromium/chrome/browser/resources/options/media_galleries_list.js
new file mode 100644
index 00000000000..8d892bcdd00
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/media_galleries_list.js
@@ -0,0 +1,59 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+ /** @const */ var DeletableItem = options.DeletableItem;
+ /** @const */ var DeletableItemList = options.DeletableItemList;
+
+ /**
+ * @constructor
+ * @extends {DeletableItem}
+ */
+ function MediaGalleriesListItem(galleryInfo) {
+ var el = cr.doc.createElement('div');
+ el.galleryInfo_ = galleryInfo;
+ el.__proto__ = MediaGalleriesListItem.prototype;
+ el.decorate();
+ return el;
+ }
+
+ MediaGalleriesListItem.prototype = {
+ __proto__: DeletableItem.prototype,
+
+ decorate: function() {
+ DeletableItem.prototype.decorate.call(this);
+
+ var span = this.ownerDocument.createElement('span');
+ span.textContent = this.galleryInfo_.displayName;
+ this.contentElement.appendChild(span);
+ this.contentElement.title = this.galleryInfo_.path;
+ },
+ };
+
+ var MediaGalleriesList = cr.ui.define('list');
+
+ MediaGalleriesList.prototype = {
+ __proto__: DeletableItemList.prototype,
+
+ /** @override */
+ decorate: function() {
+ DeletableItemList.prototype.decorate.call(this);
+ this.autoExpands_ = true;
+ },
+
+ /** @override */
+ createItem: function(galleryInfo) {
+ return new MediaGalleriesListItem(galleryInfo);
+ },
+
+ /** @override */
+ deleteItemAtIndex: function(index) {
+ chrome.send('forgetGallery', [this.dataModel.item(index).id]);
+ },
+ };
+
+ return {
+ MediaGalleriesList: MediaGalleriesList
+ };
+});
diff --git a/chromium/chrome/browser/resources/options/media_galleries_manager_overlay.html b/chromium/chrome/browser/resources/options/media_galleries_manager_overlay.html
new file mode 100644
index 00000000000..05c4db1c6af
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/media_galleries_manager_overlay.html
@@ -0,0 +1,19 @@
+<div id="manage-media-galleries-overlay" class="page" hidden>
+ <div class="close-button"></div>
+ <h1 i18n-content="manageMediaGalleries"></h1>
+ <div class="content-area">
+ <list id="available-galleries-list"></list>
+ </div>
+ <div class="action-area">
+ <div class="stretch">
+ <button id="new-media-gallery" i18n-content="addNewGalleryButton"
+ disabled></button>
+ </div>
+ </stretch>
+ <div class="button-strip">
+ <button id="manage-media-confirm" class="default-button"
+ i18n-content="done">
+ </button>
+ </div>
+ </div>
+</div>
diff --git a/chromium/chrome/browser/resources/options/media_galleries_manager_overlay.js b/chromium/chrome/browser/resources/options/media_galleries_manager_overlay.js
new file mode 100644
index 00000000000..ac8e5271580
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/media_galleries_manager_overlay.js
@@ -0,0 +1,81 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+ /** @const */ var ArrayDataModel = cr.ui.ArrayDataModel;
+ /** @const */ var OptionsPage = options.OptionsPage;
+
+ /**
+ * This class is an overlay which allows the user to add or remove media
+ * galleries, and displays known media galleries.
+ * @constructor
+ * @extends {OptionsPage}
+ */
+ function MediaGalleriesManager() {
+ OptionsPage.call(this, 'manageGalleries',
+ loadTimeData.getString('manageMediaGalleriesTabTitle'),
+ 'manage-media-galleries-overlay');
+ }
+
+ cr.addSingletonGetter(MediaGalleriesManager);
+
+ MediaGalleriesManager.prototype = {
+ __proto__: OptionsPage.prototype,
+
+ /**
+ * Decorate the overlay and set up event handlers.
+ */
+ initializePage: function() {
+ OptionsPage.prototype.initializePage.call(this);
+
+ this.availableGalleriesList_ = $('available-galleries-list');
+ options.MediaGalleriesList.decorate(this.availableGalleriesList_);
+
+ $('new-media-gallery').addEventListener('click', function() {
+ chrome.send('addNewGallery');
+ });
+
+ $('manage-media-confirm').addEventListener(
+ 'click', OptionsPage.closeOverlay.bind(OptionsPage));
+
+ this.addEventListener('visibleChange', this.handleVisibleChange_);
+ },
+
+ /** @private */
+ handleVisibleChange_: function() {
+ if (!this.visible)
+ return;
+
+ chrome.send('initializeMediaGalleries');
+
+ if (this.availableGalleriesList_)
+ this.availableGalleriesList_.redraw();
+ },
+
+ /**
+ * @param {Array} galleries List of structs describibing galleries.
+ * @private
+ */
+ setAvailableMediaGalleries_: function(galleries) {
+ $('available-galleries-list').dataModel = new ArrayDataModel(galleries);
+ $('new-media-gallery').disabled = false;
+ $('media-galleries-section').hidden = false;
+ },
+ },
+
+ // Forward public APIs to private implementations.
+ [
+ 'setAvailableMediaGalleries',
+ ].forEach(function(name) {
+ MediaGalleriesManager[name] = function() {
+ var instance = MediaGalleriesManager.getInstance();
+ return instance[name + '_'].apply(instance, arguments);
+ };
+ });
+
+ // Export
+ return {
+ MediaGalleriesManager: MediaGalleriesManager
+ };
+});
diff --git a/chromium/chrome/browser/resources/options/options.html b/chromium/chrome/browser/resources/options/options.html
new file mode 100644
index 00000000000..0d1f0cbb896
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/options.html
@@ -0,0 +1,190 @@
+<!DOCTYPE HTML>
+<html id="t" i18n-values="dir:textdirection">
+<head>
+<meta charset="utf-8">
+<title i18n-content="optionsPageTitle"></title>
+<link rel="stylesheet" href="chrome://resources/css/bubble.css">
+<link rel="stylesheet" href="chrome://resources/css/bubble_button.css">
+<link rel="stylesheet" href="chrome://resources/css/chrome_shared.css">
+<link rel="stylesheet" href="chrome://resources/css/list.css">
+<link rel="stylesheet" href="chrome://resources/css/overlay.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="../uber/uber_shared.css">
+<link rel="stylesheet" href="options_page.css">
+<link rel="stylesheet" href="alert_overlay.css">
+<link rel="stylesheet" href="autofill_edit_overlay.css">
+<link rel="stylesheet" href="autofill_options.css">
+<link rel="stylesheet" href="browser_options.css">
+<if expr="pp_ifdef('chromeos')">
+ <link rel="stylesheet" href="chromeos/browser_options.css">
+</if>
+<link rel="stylesheet" href="clear_browser_data_overlay.css">
+<link rel="stylesheet" href="content_settings.css">
+<link rel="stylesheet" href="controlled_setting.css">
+<link rel="stylesheet" href="cookies_view.css">
+<link rel="stylesheet" href="do_not_track_confirm_overlay.css">
+<link rel="stylesheet" href="font_settings.css">
+<link rel="stylesheet" href="handler_options.css">
+<link rel="stylesheet" href="home_page_overlay.css">
+<link rel="stylesheet" href="import_data_overlay.css">
+<if expr="not is_macosx">
+<link rel="stylesheet" href="language_dictionary_overlay.css">
+</if>
+<link rel="stylesheet" href="language_options.css">
+<link rel="stylesheet" href="manage_profile_overlay.css">
+<link rel="stylesheet" href="managed_user_create_confirm.css">
+<link rel="stylesheet" href="managed_user_import.css">
+<link rel="stylesheet" href="managed_user_learn_more.css">
+<link rel="stylesheet" href="password_manager.css">
+<link rel="stylesheet" href="password_manager_list.css">
+<link rel="stylesheet" href="reset_profile_settings_banner.css">
+<link rel="stylesheet" href="reset_profile_settings_overlay.css">
+<link rel="stylesheet" href="search_engine_manager.css">
+<link rel="stylesheet" href="search_page.css">
+<link rel="stylesheet" href="spelling_confirm_overlay.css">
+<link rel="stylesheet" href="subpages_tab_controls.css">
+<link rel="stylesheet" href="startup_overlay.css">
+<link rel="stylesheet" href="../sync_setup_overlay.css">
+<if expr="pp_ifdef('chromeos')">
+<link rel="stylesheet" href="chromeos/accounts_options_page.css">
+<link rel="stylesheet" href="chromeos/bluetooth.css">
+<link rel="stylesheet" href="chromeos/change_picture_options.css">
+<link rel="stylesheet" href="chromeos/display_options.css">
+<link rel="stylesheet" href="chromeos/display_overscan.css">
+<link rel="stylesheet" href="chromeos/internet_detail.css">
+<link rel="stylesheet" href="chromeos/keyboard_overlay.css">
+<link rel="stylesheet" href="chromeos/pointer_overlay.css">
+<link rel="stylesheet" href="factory_reset_overlay.css">
+</if>
+<if expr="pp_ifdef('use_nss')">
+<link rel="stylesheet" href="certificate_manager.css">
+<link rel="stylesheet" href="certificate_tree.css">
+</if>
+<if expr="pp_ifdef('enable_settings_app')">
+<link rel="stylesheet" href="options_settings_app.css">
+</if>
+<script src="chrome://resources/css/tree.css.js"></script>
+<script src="chrome://resources/js/cr.js"></script>
+<script src="chrome://resources/js/event_tracker.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/touch_handler.js"></script>
+<script src="chrome://resources/js/cr/ui/array_data_model.js"></script>
+<script src="chrome://resources/js/cr/ui/bubble.js"></script>
+<script src="chrome://resources/js/cr/ui/bubble_button.js"></script>
+<script src="chrome://resources/js/cr/ui/focus_manager.js"></script>
+<script src="chrome://resources/js/cr/ui/focus_outline_manager.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/menu_item.js"></script>
+<script src="chrome://resources/js/cr/ui/menu.js"></script>
+<script src="chrome://resources/js/cr/ui/autocomplete_list.js"></script>
+<script src="chrome://resources/js/cr/ui/grid.js"></script>
+<script src="chrome://resources/js/cr/ui/overlay.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/load_time_data.js"></script>
+<script src="chrome://resources/js/util.js"></script>
+
+<script src="chrome://settings-frame/strings.js"></script>
+<script src="chrome://settings-frame/options_bundle.js"></script>
+</head>
+
+<body class="uber-frame"
+ i18n-values=".style.fontFamily:fontfamily;.style.fontSize:fontsize">
+<div id="overlay-container-1" class="overlay transparent" hidden>
+ <include src="autofill_options.html">
+ <include src="clear_browser_data_overlay.html">
+ <include src="content_settings.html">
+ <include src="content_settings2.html">
+ <include src="do_not_track_confirm_overlay.html">
+ <include src="font_settings.html">
+ <include src="home_page_overlay.html">
+ <include src="import_data_overlay.html">
+ <include src="language_options.html">
+ <include src="manage_profile_overlay.html">
+ <include src="managed_user_create_confirm.html">
+ <include src="managed_user_import.html">
+ <include src="password_manager.html">
+ <include src="reset_profile_settings_overlay.html">
+ <include src="search_engine_manager.html">
+ <include src="spelling_confirm_overlay.html">
+ <include src="startup_overlay.html">
+ <include src="../sync_setup_overlay.html">
+<if expr="pp_ifdef('chromeos')">
+ <include src="chromeos/accounts_options.html">
+ <include src="chromeos/bluetooth_add_device_overlay.html">
+ <include src="chromeos/bluetooth_pair_device_overlay.html">
+ <include src="chromeos/change_picture_options.html">
+ <include src="chromeos/display_options.html">
+ <include src="chromeos/keyboard_overlay.html">
+ <include src="chromeos/pointer_overlay.html">
+ <include src="factory_reset_overlay.html">
+</if>
+<if expr="pp_ifdef('use_nss')">
+ <include src="certificate_manager.html">
+</if>
+</div>
+<div id="overlay-container-2" 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="content_settings_exceptions_area.html">
+ <include src="cookies_view.html">
+ <include src="handler_options.html">
+ <include src="language_add_language_overlay.html">
+ <include src="managed_user_learn_more.html">
+<if expr="not is_macosx">
+ <include src="language_dictionary_overlay.html">
+</if>
+ <include src="media_galleries_manager_overlay.html">
+<if expr="pp_ifdef('chromeos')">
+ <include src="chromeos/display_overscan.html">
+ <include src="chromeos/internet_detail.html">
+ <include src="chromeos/preferred_networks.html">
+</if>
+<if expr="not is_win and not is_macosx">
+ <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="overlay-container-3" class="overlay transparent" hidden>
+</div>
+<div id="extension-controlled-settings-bubble-template" hidden>
+ <div class="controlled-setting-bubble-content-row">
+ <div class="controlled-setting-bubble-extension-name"></div>
+ </div>
+ <div class="controlled-setting-bubble-content-row">
+ <div class="controlled-setting-bubble-extension-manage-link link-button"
+ i18n-content="controlledSettingManageExtensions"></div>
+ <button class='controlled-setting-bubble-extension-disable-button'
+ i18n-content="controlledSettingDisableExtension"></button>
+ </div>
+</div>
+
+<div id="main-content">
+ <div id="mainview">
+ <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_box.html">
+ <include src="search_page.html">
+ <include src="browser_options.html">
+ </div>
+ </div>
+ </div>
+</div>
+
+<script src="chrome://resources/js/i18n_template2.js"></script>
+</body>
+</html>
diff --git a/chromium/chrome/browser/resources/options/options.js b/chromium/chrome/browser/resources/options/options.js
new file mode 100644
index 00000000000..1aeb352c218
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/options.js
@@ -0,0 +1,259 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+var AddLanguageOverlay = options.AddLanguageOverlay;
+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 ConfirmDialog = options.ConfirmDialog;
+var ContentSettingsExceptionsArea =
+ options.contentSettings.ContentSettingsExceptionsArea;
+var ContentSettings = options.ContentSettings;
+var CookiesView = options.CookiesView;
+var CreateProfileOverlay = options.CreateProfileOverlay;
+var EditDictionaryOverlay = cr.IsMac ? null : options.EditDictionaryOverlay;
+var FactoryResetOverlay = options.FactoryResetOverlay;
+<if expr="pp_ifdef('enable_google_now')">
+var GeolocationOptions = options.GeolocationOptions;
+</if>
+var FontSettings = options.FontSettings;
+var HandlerOptions = options.HandlerOptions;
+var HomePageOverlay = options.HomePageOverlay;
+var ImportDataOverlay = options.ImportDataOverlay;
+var LanguageOptions = options.LanguageOptions;
+var ManageProfileOverlay = options.ManageProfileOverlay;
+var ManagedUserCreateConfirmOverlay = options.ManagedUserCreateConfirmOverlay;
+var ManagedUserImportOverlay = options.ManagedUserImportOverlay;
+var ManagedUserLearnMoreOverlay = options.ManagedUserLearnMoreOverlay;
+var MediaGalleriesManager = options.MediaGalleriesManager;
+var OptionsFocusManager = options.OptionsFocusManager;
+var OptionsPage = options.OptionsPage;
+var PasswordManager = options.PasswordManager;
+var Preferences = options.Preferences;
+var PreferredNetworks = options.PreferredNetworks;
+var ResetProfileSettingsBanner = options.ResetProfileSettingsBanner;
+var ResetProfileSettingsOverlay = options.ResetProfileSettingsOverlay;
+var SearchEngineManager = options.SearchEngineManager;
+var SearchPage = options.SearchPage;
+var StartupOverlay = options.StartupOverlay;
+var SyncSetupOverlay = options.SyncSetupOverlay;
+
+/**
+ * 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);
+
+ // Top level pages.
+ OptionsPage.register(SearchPage.getInstance());
+ OptionsPage.register(BrowserOptions.getInstance());
+
+ // Overlays.
+ OptionsPage.registerOverlay(AddLanguageOverlay.getInstance(),
+ LanguageOptions.getInstance());
+ OptionsPage.registerOverlay(AlertOverlay.getInstance());
+ OptionsPage.registerOverlay(AutofillEditAddressOverlay.getInstance(),
+ AutofillOptions.getInstance());
+ OptionsPage.registerOverlay(AutofillEditCreditCardOverlay.getInstance(),
+ AutofillOptions.getInstance());
+ OptionsPage.registerOverlay(AutofillOptions.getInstance(),
+ BrowserOptions.getInstance(),
+ [$('autofill-settings')]);
+ OptionsPage.registerOverlay(ClearBrowserDataOverlay.getInstance(),
+ BrowserOptions.getInstance(),
+ [$('privacyClearDataButton')]);
+ OptionsPage.registerOverlay(
+ new ConfirmDialog(
+ 'doNotTrackConfirm',
+ loadTimeData.getString('doNotTrackConfirmOverlayTabTitle'),
+ 'do-not-track-confirm-overlay',
+ $('do-not-track-confirm-ok'),
+ $('do-not-track-confirm-cancel'),
+ $('do-not-track-enabled').pref,
+ $('do-not-track-enabled').metric),
+ BrowserOptions.getInstance());
+ // 'spelling-enabled-control' element is only present on Chrome branded
+ // builds.
+ if ($('spelling-enabled-control')) {
+ OptionsPage.registerOverlay(
+ new ConfirmDialog(
+ 'spellingConfirm',
+ loadTimeData.getString('spellingConfirmOverlayTabTitle'),
+ 'spelling-confirm-overlay',
+ $('spelling-confirm-ok'),
+ $('spelling-confirm-cancel'),
+ $('spelling-enabled-control').pref,
+ $('spelling-enabled-control').metric),
+ BrowserOptions.getInstance());
+ }
+ OptionsPage.registerOverlay(ContentSettings.getInstance(),
+ BrowserOptions.getInstance(),
+ [$('privacyContentSettingsButton')]);
+ OptionsPage.registerOverlay(ContentSettingsExceptionsArea.getInstance(),
+ ContentSettings.getInstance());
+ OptionsPage.registerOverlay(CookiesView.getInstance(),
+ ContentSettings.getInstance(),
+ [$('privacyContentSettingsButton'),
+ $('show-cookies-button')]);
+ OptionsPage.registerOverlay(CreateProfileOverlay.getInstance(),
+ BrowserOptions.getInstance());
+ if (!cr.isMac) {
+ OptionsPage.registerOverlay(EditDictionaryOverlay.getInstance(),
+ LanguageOptions.getInstance(),
+ [$('edit-dictionary-button')]);
+ }
+ OptionsPage.registerOverlay(FontSettings.getInstance(),
+ BrowserOptions.getInstance(),
+ [$('fontSettingsCustomizeFontsButton')]);
+ if (HandlerOptions && $('manage-handlers-button')) {
+ OptionsPage.registerOverlay(HandlerOptions.getInstance(),
+ ContentSettings.getInstance(),
+ [$('manage-handlers-button')]);
+ }
+ OptionsPage.registerOverlay(HomePageOverlay.getInstance(),
+ BrowserOptions.getInstance(),
+ [$('change-home-page')]);
+ OptionsPage.registerOverlay(ImportDataOverlay.getInstance(),
+ BrowserOptions.getInstance());
+ OptionsPage.registerOverlay(LanguageOptions.getInstance(),
+ BrowserOptions.getInstance(),
+ [$('language-button'),
+ $('manage-languages')]);
+ OptionsPage.registerOverlay(ManageProfileOverlay.getInstance(),
+ BrowserOptions.getInstance());
+ if (!cr.isChromeOS) {
+ OptionsPage.registerOverlay(ManagedUserCreateConfirmOverlay.getInstance(),
+ BrowserOptions.getInstance());
+ if (loadTimeData.getBoolean('allowCreateExistingManagedUsers')) {
+ OptionsPage.registerOverlay(ManagedUserImportOverlay.getInstance(),
+ BrowserOptions.getInstance());
+ }
+ OptionsPage.registerOverlay(ManagedUserLearnMoreOverlay.getInstance(),
+ CreateProfileOverlay.getInstance());
+ }
+ OptionsPage.registerOverlay(MediaGalleriesManager.getInstance(),
+ ContentSettings.getInstance(),
+ [$('manage-galleries-button')]);
+ OptionsPage.registerOverlay(PasswordManager.getInstance(),
+ BrowserOptions.getInstance(),
+ [$('manage-passwords')]);
+ OptionsPage.registerOverlay(ResetProfileSettingsOverlay.getInstance(),
+ BrowserOptions.getInstance(),
+ [$('reset-profile-settings')]);
+ OptionsPage.registerOverlay(SearchEngineManager.getInstance(),
+ BrowserOptions.getInstance(),
+ [$('manage-default-search-engines')]);
+ OptionsPage.registerOverlay(StartupOverlay.getInstance(),
+ BrowserOptions.getInstance());
+ OptionsPage.registerOverlay(SyncSetupOverlay.getInstance(),
+ BrowserOptions.getInstance(),
+ [$('customize-sync')]);
+ if (cr.isChromeOS) {
+ OptionsPage.registerOverlay(AccountsOptions.getInstance(),
+ BrowserOptions.getInstance(),
+ [$('manage-accounts-button')]);
+ OptionsPage.registerOverlay(BluetoothOptions.getInstance(),
+ BrowserOptions.getInstance(),
+ [$('bluetooth-add-device')]);
+ OptionsPage.registerOverlay(BluetoothPairing.getInstance(),
+ BrowserOptions.getInstance());
+ OptionsPage.registerOverlay(FactoryResetOverlay.getInstance(),
+ BrowserOptions.getInstance(),
+ [$('factory-reset-restart')]);
+ OptionsPage.registerOverlay(ChangePictureOptions.getInstance(),
+ BrowserOptions.getInstance(),
+ [$('account-picture')]);
+ OptionsPage.registerOverlay(DetailsInternetPage.getInstance(),
+ BrowserOptions.getInstance());
+ OptionsPage.registerOverlay(DisplayOptions.getInstance(),
+ BrowserOptions.getInstance(),
+ [$('display-options')]);
+ OptionsPage.registerOverlay(DisplayOverscan.getInstance(),
+ DisplayOptions.getInstance());
+ OptionsPage.registerOverlay(KeyboardOverlay.getInstance(),
+ BrowserOptions.getInstance(),
+ [$('keyboard-settings-button')]);
+ OptionsPage.registerOverlay(PointerOverlay.getInstance(),
+ BrowserOptions.getInstance(),
+ [$('pointer-settings-button')]);
+ OptionsPage.registerOverlay(PreferredNetworks.getInstance(),
+ BrowserOptions.getInstance());
+ }
+
+ if (!cr.isWindows && !cr.isMac) {
+ OptionsPage.registerOverlay(CertificateBackupOverlay.getInstance(),
+ CertificateManager.getInstance());
+ OptionsPage.registerOverlay(CertificateEditCaTrustOverlay.getInstance(),
+ CertificateManager.getInstance());
+ OptionsPage.registerOverlay(CertificateImportErrorOverlay.getInstance(),
+ CertificateManager.getInstance());
+ OptionsPage.registerOverlay(CertificateManager.getInstance(),
+ BrowserOptions.getInstance(),
+ [$('certificatesManageButton')]);
+ OptionsPage.registerOverlay(CertificateRestoreOverlay.getInstance(),
+ CertificateManager.getInstance());
+ }
+
+ cr.ui.FocusManager.disableMouseFocusOnButtons();
+ OptionsFocusManager.getInstance().initialize();
+ Preferences.getInstance().initialize();
+ ResetProfileSettingsBanner.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(/\/$/, '');
+ OptionsPage.showPageByName(pageName, true, {replaceState: true});
+ } 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);
+ };
+ }
+
+ window.setTimeout(function() {
+ document.documentElement.classList.remove('loading');
+ });
+}
+
+document.documentElement.classList.add('loading');
+document.addEventListener('DOMContentLoaded', load);
+
+/**
+ * Listener for the |beforeunload| event.
+ */
+window.onbeforeunload = function() {
+ options.OptionsPage.willClose();
+};
+
+/**
+ * Listener for the |popstate| event.
+ * @param {Event} e The |popstate| event.
+ */
+window.onpopstate = function(e) {
+ options.OptionsPage.setState(e.state);
+};
diff --git a/chromium/chrome/browser/resources/options/options_bundle.js b/chromium/chrome/browser/resources/options/options_bundle.js
new file mode 100644
index 00000000000..8f450648e5a
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/options_bundle.js
@@ -0,0 +1,117 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// 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="controlled_setting.js"></include>
+<include src="deletable_item_list.js"></include>
+<include src="editable_text_field.js"></include>
+<include src="inline_editable_list.js"></include>
+<include src="options_page.js"></include>
+<include src="pref_ui.js"></include>
+<include src="settings_dialog.js"></include>
+<if expr="pp_ifdef('chromeos')">
+<include src="../chromeos/user_images_grid.js"></include>
+// DO NOT BREAK THE FOLLOWING INCLUDE LINE INTO SEPARATE LINES!
+// Even though the include line spans more than 80 characters,
+// The grit html inlining parser will leave the end tag behind,
+// causing a runtime JS break.
+<include src="../../../../ui/webui/resources/js/chromeos/ui_account_tweaks.js"></include>
+<include src="chromeos/change_picture_options.js"></include>
+<include src="chromeos/internet_detail_ip_address_field.js"></include>
+<include src="chromeos/internet_detail.js"></include>
+<include src="chromeos/network_list.js"></include>
+<include src="chromeos/preferred_networks.js"></include>
+<include src="chromeos/bluetooth_device_list.js"></include>
+<include src="chromeos/bluetooth_add_device_overlay.js"></include>
+<include src="chromeos/bluetooth_pair_device_overlay.js"></include>
+<include src="chromeos/accounts_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/display_options.js"></include>
+<include src="chromeos/display_overscan.js"></include>
+<include src="chromeos/keyboard_overlay.js"></include>
+<include src="chromeos/pointer_overlay.js"></include>
+var AccountsOptions = options.AccountsOptions;
+var ChangePictureOptions = options.ChangePictureOptions;
+var DetailsInternetPage = options.internet.DetailsInternetPage;
+var DisplayOptions = options.DisplayOptions;
+var DisplayOverscan = options.DisplayOverscan;
+var BluetoothOptions = options.BluetoothOptions;
+var BluetoothPairing = options.BluetoothPairing;
+var KeyboardOverlay = options.KeyboardOverlay;
+var PointerOverlay = options.PointerOverlay;
+var UIAccountTweaks = uiAccountTweaks.UIAccountTweaks;
+</if>
+<if expr="pp_ifdef('use_nss')">
+<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="alert_overlay.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_profile_list.js"></include>
+<include src="browser_options_startup_page_list.js"></include>
+<include src="clear_browser_data_overlay.js"></include>
+<include src="confirm_dialog.js"></include>
+<include src="content_settings.js"></include>
+<include src="content_settings2.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="factory_reset_overlay.js"></include>
+<include src="font_settings.js"></include>
+<if expr="pp_ifdef('enable_google_now')">
+<include src="geolocation_options.js"></include>
+</if>
+<include src="handler_options.js"></include>
+<include src="handler_options_list.js"></include>
+<include src="home_page_overlay.js"></include>
+<include src="import_data_overlay.js"></include>
+<include src="language_add_language_overlay.js"></include>
+<if expr="not is_macosx">
+<include src="language_dictionary_overlay_word_list.js"></include>
+<include src="language_dictionary_overlay.js"></include>
+</if>
+<include src="language_list.js"></include>
+<include src="language_options.js"></include>
+<include src="manage_profile_overlay.js"></include>
+<include src="managed_user_create_confirm.js"</include>
+<include src="managed_user_import.js"></include>
+<include src="managed_user_learn_more.js"</include>
+<include src="managed_user_list.js"></include>
+<include src="media_galleries_list.js"></include>
+<include src="media_galleries_manager_overlay.js"></include>
+<include src="options_focus_manager.js"></include>
+<include src="password_manager.js"></include>
+<include src="password_manager_list.js"></include>
+<include src="profiles_icon_grid.js"></include>
+<include src="reset_profile_settings_banner.js"></include>
+<include src="reset_profile_settings_overlay.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="startup_overlay.js"></include>
+<include src="../sync_setup_overlay.js"></include>
+<include src="../uber/uber_utils.js"></include>
+<include src="options.js"></include>
+<if expr="pp_ifdef('enable_settings_app')">
+<include src="options_settings_app.js"></include>
+</if>
diff --git a/chromium/chrome/browser/resources/options/options_focus_manager.js b/chromium/chrome/browser/resources/options/options_focus_manager.js
new file mode 100644
index 00000000000..972e1fea0db
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/options_focus_manager.js
@@ -0,0 +1,34 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+ var FocusManager = cr.ui.FocusManager;
+ var OptionsPage = options.OptionsPage;
+
+ function OptionsFocusManager() {
+ }
+
+ cr.addSingletonGetter(OptionsFocusManager);
+
+ OptionsFocusManager.prototype = {
+ __proto__: FocusManager.prototype,
+
+ /** @override */
+ getFocusParent: function() {
+ var topPage = OptionsPage.getTopmostVisiblePage().pageDiv;
+
+ // The default page and search page include a search field that is a
+ // sibling of the rest of the page instead of a child. Thus, use the
+ // parent node to allow the search field to receive focus.
+ if (topPage.parentNode.id == 'page-container')
+ return topPage.parentNode;
+
+ return topPage;
+ },
+ };
+
+ return {
+ OptionsFocusManager: OptionsFocusManager,
+ };
+});
diff --git a/chromium/chrome/browser/resources/options/options_page.css b/chromium/chrome/browser/resources/options/options_page.css
new file mode 100644
index 00000000000..b4223ec7e32
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/options_page.css
@@ -0,0 +1,459 @@
+/* Copyright (c) 2012 The Chromium Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file. */
+
+body {
+ position: relative;
+}
+
+#main-content {
+ bottom: 0;
+ display: -webkit-box;
+ left: 0;
+ position: absolute;
+ right: 0;
+ top: 0;
+}
+
+#mainview {
+ -webkit-box-align: stretch;
+ bottom: 0;
+ left: 0;
+ margin: 0;
+ position: absolute;
+ right: 0;
+ top: 0;
+ z-index: 1;
+}
+
+#mainview-content {
+ min-height: 100%;
+ position: relative;
+}
+
+#page-container {
+ box-sizing: border-box;
+ max-width: 888px;
+ min-width: 600px;
+}
+
+body.uber-frame #searchBox {
+ position: fixed;
+ z-index: 4;
+}
+
+div.disabled {
+ color: #999;
+}
+
+.settings-row {
+ display: block;
+ margin: 0.65em 0;
+}
+
+.hbox {
+ -webkit-box-orient: horizontal;
+ display: -webkit-box;
+}
+
+.vbox {
+ -webkit-box-orient: vertical;
+ display: -webkit-box;
+}
+
+.box-align-center {
+ -webkit-box-align: center;
+}
+
+.stretch {
+ -webkit-box-flex: 1;
+}
+
+.frozen {
+ position: fixed;
+}
+
+#overlay-container-1 {
+ z-index: 11;
+}
+#overlay-container-2 {
+ z-index: 12;
+}
+#overlay-container-3 {
+ z-index: 13;
+}
+
+.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;
+}
+
+.bottom-strip {
+ border-top: none;
+ bottom: 0;
+ padding: 12px;
+ position: absolute;
+ right: 0;
+}
+
+/* Omit top padding (currently only on #settings) whenever the search page is
+ * showing.
+ */
+#searchPage:not([hidden]) + #settings {
+ padding-top: 0;
+}
+
+.page list {
+ /* Min height is a multiple of the list item height (32) */
+ min-height: 192px;
+}
+
+.option {
+ margin-top: 0;
+}
+
+.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;
+}
+
+
+/* Editable text field properties */
+.editable-text-field > * {
+ -webkit-box-align: center;
+ -webkit-transition: 150ms background-color;
+ border: none;
+ box-sizing: border-box;
+ display: -webkit-box;
+ height: 20px;
+ margin: 0;
+}
+
+.editable-text-field > .spacer {
+ /* The above height rule should not apply to spacers. */
+ height: 0;
+}
+
+.editable-text-field .editable-text {
+ padding: 2px 3px;
+}
+
+.editable-text-field .static-text {
+ height: 24px;
+ overflow: hidden;
+ padding: 3px 4px;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.editable-text-field:not([editable]) > [displaymode='edit'] {
+ display: none;
+}
+
+.editable-text-field[editable] > [displaymode='static'] {
+ display: none;
+}
+
+.editable-text-field[empty] > input[type='text'] {
+ color: #ccc;
+ font-style: italic;
+}
+
+.editable-text-field[disabled] {
+ opacity: 0.6;
+}
+
+/* Editable List properties */
+list > * {
+ -webkit-box-align: center;
+ -webkit-transition: 150ms background-color;
+ border: none;
+ border-radius: 0; /* TODO(dbeam): Is this necessary? */
+ box-sizing: border-box;
+ display: -webkit-box;
+ height: 32px;
+ margin: 0;
+}
+
+list > .spacer {
+ /* The above height rule should not apply to spacers. When redraw is called
+ on the list they will be given an explicit element height but this ensures
+ they have 0 height to begin with. */
+ height: 0;
+}
+
+list:not([disabled]) > :hover {
+ background-color: rgb(228, 236, 247);
+}
+
+/* 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: rgb(187, 206, 233);
+ background-image: none;
+}
+
+list[hasElementFocus] > [lead],
+list[hasElementFocus] > [lead][selected] {
+ border-bottom: 1px solid rgb(120, 146, 180);
+ border-top: 1px solid rgb(120, 146, 180);
+}
+
+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: #666;
+}
+
+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 .row-delete-button {
+ -webkit-transition: 150ms opacity;
+ background-color: transparent;
+ /* TODO(stuartmorgan): Replace with real images once they are available. */
+ background-image: -webkit-image-set(
+ url('../../../../ui/resources/default_100_percent/close_2.png') 1x,
+ url('../../../../ui/resources/default_200_percent/close_2.png') 2x);
+ border: none;
+ display: block;
+ height: 16px;
+ opacity: 1;
+ width: 16px;
+}
+
+list > *:not(:hover):not([selected]):not([lead]) .row-delete-button,
+list:not([hasElementFocus]) > *:not(:hover):not([selected]) .row-delete-button,
+list[disabled] .row-delete-button,
+list .row-delete-button[disabled] {
+ opacity: 0;
+ pointer-events: none;
+}
+
+/* HostedApp entries use the disabled closing button to display the App's
+ * favicon, as an indicator that instead of deleting the permission here
+ * the user has to remove the hosted app.*/
+list div[role='listitem'][managedby='HostedApp'] .row-delete-button {
+ opacity: 1;
+}
+
+list .row-delete-button:hover {
+ background-image: -webkit-image-set(
+ url('../../../../ui/resources/default_100_percent/close_2_hover.png') 1x,
+ url('../../../../ui/resources/default_200_percent/close_2_hover.png') 2x);
+}
+
+list .row-delete-button:active {
+ background-image: -webkit-image-set(
+ url('../../../../ui/resources/default_100_percent/close_2_pressed.png')
+ 1x,
+ url('../../../../ui/resources/default_200_percent/close_2_pressed.png')
+ 2x);
+}
+
+list .static-text {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+list[type='text'][inlineeditable] input {
+ box-sizing: border-box;
+ margin: 0;
+ width: 100%;
+}
+
+list > :not([editing]) [displaymode='edit'] {
+ display: none;
+}
+
+list > [editing] [displaymode='static'] {
+ /* Don't use display:none because we need to keep an element focusable. */
+ left: 0;
+ opacity: 0;
+ pointer-events: none;
+ position: absolute;
+ top: -10em;
+}
+
+list > [editing] input:invalid {
+ /* TODO(stuartmorgan): Replace with validity badge */
+ background-color: pink;
+}
+
+.list-inline-button {
+ -webkit-appearance: none;
+ -webkit-transition: opacity 150ms;
+ background: rgb(138, 170, 237);
+ border: none;
+ border-radius: 2px;
+ color: white;
+ font-weight: bold;
+ opacity: 0.7;
+}
+
+.list-inline-button:hover {
+ opacity: 1;
+}
+
+.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;
+ background-size: 16px;
+}
+
+input[type='url'].favicon-cell {
+ -webkit-padding-start: 22px;
+ background-position-x: 4px;
+}
+
+html[dir=rtl] input.favicon-cell {
+ background-position-x: -webkit-calc(100% - 4px);
+}
+
+list .favicon-cell {
+ -webkit-margin-start: 7px;
+ -webkit-padding-start: 26px;
+ display: block;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ 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 */
+list[hasElementFocus] {
+<if expr="not is_macosx">
+ outline: 1px solid rgba(0, 128, 256, 0.5);
+ outline-offset: -2px;
+</if>
+<if expr="is_macosx">
+ /* This matches the native list outline on Mac */
+ outline-color: rgb(117, 154, 217);
+ outline-offset: -1px;
+ outline-style: auto;
+ outline-width: 5px;
+</if>
+}
+
+.suboption {
+ -webkit-margin-start: 23px;
+}
+
+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: rgb(187, 206, 233);
+}
+
+html:not(.focus-outline-visible)
+:enabled:focus:-webkit-any(input[type='checkbox'], input[type='radio']) {
+ /* Cancel border-color for :focus specified in widgets.css. */
+ border-color: rgba(0, 0, 0, 0.25);
+}
+
+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,
+html:not([enablePepperFlashSettings]) .pepper-flash-settings {
+ display: none;
+}
diff --git a/chromium/chrome/browser/resources/options/options_page.js b/chromium/chrome/browser/resources/options/options_page.js
new file mode 100644
index 00000000000..befb7c81398
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/options_page.js
@@ -0,0 +1,1006 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+ /** @const */ var FocusOutlineManager = cr.ui.FocusOutlineManager;
+
+ /////////////////////////////////////////////////////////////////////////////
+ // OptionsPage class:
+
+ /**
+ * Base class for options page.
+ * @constructor
+ * @param {string} name Options page name.
+ * @param {string} title Options page title, used for history.
+ * @extends {EventTarget}
+ */
+ function OptionsPage(name, title, pageDivName) {
+ this.name = name;
+ this.title = title;
+ this.pageDivName = pageDivName;
+ this.pageDiv = $(this.pageDivName);
+ // |pageDiv.page| is set to the page object (this) when the page is visible
+ // to track which page is being shown when multiple pages can share the same
+ // underlying div.
+ this.pageDiv.page = null;
+ this.tab = null;
+ this.lastFocusedElement = null;
+ }
+
+ /**
+ * This is the absolute difference maintained between standard and
+ * fixed-width font sizes. Refer http://crbug.com/91922.
+ * @const
+ */
+ OptionsPage.SIZE_DIFFERENCE_FIXED_STANDARD = 3;
+
+ /**
+ * Offset of page container in pixels, to allow room for side menu.
+ * Simplified settings pages can override this if they don't use the menu.
+ * The default (155) comes from -webkit-margin-start in uber_shared.css
+ * @private
+ */
+ OptionsPage.horizontalOffset = 155;
+
+ /**
+ * 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 = {};
+
+ /**
+ * 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 and overlay pages.
+ * @param {string} pageName Page name.
+ * @param {boolean} updateHistory True if we should update the history after
+ * showing the page.
+ * @param {Object=} opt_propertyBag An optional bag of properties including
+ * replaceState (if history state should be replaced instead of pushed).
+ * @private
+ */
+ OptionsPage.showPageByName = function(pageName,
+ updateHistory,
+ opt_propertyBag) {
+ // If |opt_propertyBag| is non-truthy, homogenize to object.
+ opt_propertyBag = opt_propertyBag || {};
+
+ // If a bubble is currently being shown, hide it.
+ this.hideBubble();
+
+ // 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_(!!opt_propertyBag.replaceState);
+ 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 an overlay. This can happen for special
+ // pages like Search.
+ var isRootPageLocked =
+ rootPage && rootPage.sticky && targetPage.parentPage;
+
+ var allPageNames = Array.prototype.concat.call(
+ Object.keys(this.registeredPages),
+ Object.keys(this.registeredOverlayPages));
+
+ // Notify pages if they will be hidden.
+ for (var i = 0; i < allPageNames.length; ++i) {
+ var name = allPageNames[i];
+ var page = this.registeredPages[name] ||
+ this.registeredOverlayPages[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 i = 0; i < allPageNames.length; ++i) {
+ var name = allPageNames[i];
+ var page = this.registeredPages[name] ||
+ this.registeredOverlayPages[name];
+ if (!page.parentPage && isRootPageLocked)
+ continue;
+ page.visible = name == pageName || page.isAncestorOfPage(targetPage);
+ }
+
+ // Update the history and current location.
+ if (updateHistory)
+ this.updateHistoryState_(!!opt_propertyBag.replaceState);
+
+ // Update tab title.
+ this.setTitle_(targetPage.title);
+
+ // Update focus if any other control was focused on the previous page,
+ // or the previous page is not known.
+ if (document.activeElement != document.body &&
+ (!rootPage || rootPage.pageDiv.contains(document.activeElement))) {
+ targetPage.focus();
+ }
+
+ // Notify pages if they were shown.
+ for (var i = 0; i < allPageNames.length; ++i) {
+ var name = allPageNames[i];
+ var page = this.registeredPages[name] ||
+ this.registeredOverlayPages[name];
+ if (!page.parentPage && isRootPageLocked)
+ continue;
+ if (!targetPageWasVisible && page.didShowPage &&
+ (name == pageName || page.isAncestorOfPage(targetPage))) {
+ page.didShowPage();
+ }
+ }
+ };
+
+ /**
+ * Sets the title of the page. This is accomplished by calling into the
+ * parent page API.
+ * @param {string} title The title string.
+ * @private
+ */
+ OptionsPage.setTitle_ = function(title) {
+ uber.invokeMethodOnParent('setTitle', {title: title});
+ };
+
+ /**
+ * Scrolls the page to the correct position (the top when opening an overlay,
+ * or the old scroll position a previously hidden overlay becomes visible).
+ * @private
+ */
+ OptionsPage.updateScrollPosition_ = function() {
+ var container = $('page-container');
+ var scrollTop = container.oldScrollTop || 0;
+ container.oldScrollTop = undefined;
+ window.scroll(scrollLeftForDocument(document), scrollTop);
+ };
+
+ /**
+ * Pushes the current page onto the history stack, overriding the last page
+ * if it is the generic chrome://settings/.
+ * @param {boolean} replace If true, allow no history events to be created.
+ * @param {object=} opt_params A bag of optional params, including:
+ * {boolean} ignoreHash Whether to include the hash or not.
+ * @private
+ */
+ OptionsPage.updateHistoryState_ = function(replace, opt_params) {
+ var page = this.getTopmostVisiblePage();
+ var path = window.location.pathname + window.location.hash;
+ if (path)
+ path = path.slice(1).replace(/\/(?:#|$)/, ''); // Remove trailing slash.
+
+ // Update tab title.
+ this.setTitle_(page.title);
+
+ // The page is already in history (the user may have clicked the same link
+ // twice). Do nothing.
+ if (path == page.name && !OptionsPage.isLoading())
+ return;
+
+ var hash = opt_params && opt_params.ignoreHash ? '' : window.location.hash;
+
+ // If settings are embedded, tell the outer page to set its "path" to the
+ // inner frame's path.
+ var outerPath = (page == this.getDefaultPage() ? '' : page.name) + hash;
+ uber.invokeMethodOnParent('setPath', {path: outerPath});
+
+ // If there is no path, the current location is chrome://settings/.
+ // Override this with the new page.
+ var historyFunction = path && !replace ? window.history.pushState :
+ window.history.replaceState;
+ historyFunction.call(window.history,
+ {pageName: page.name},
+ page.title,
+ '/' + page.name + hash);
+ };
+
+ /**
+ * 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;
+
+ // Save the currently focused element in the page for restoration later.
+ var currentPage = this.getTopmostVisiblePage();
+ if (currentPage)
+ currentPage.lastFocusedElement = document.activeElement;
+
+ if ((!rootPage || !rootPage.sticky) &&
+ overlay.parentPage &&
+ !overlay.parentPage.visible) {
+ this.showPageByName(overlay.parentPage.name, false);
+ }
+
+ if (!overlay.visible) {
+ overlay.visible = true;
+ if (overlay.didShowPage) overlay.didShowPage();
+ }
+
+ // Update tab title.
+ this.setTitle_(overlay.title);
+
+ // Change focus to the overlay if any other control was focused by keyboard
+ // before. Otherwise, no one should have focus.
+ if (document.activeElement != document.body) {
+ if (FocusOutlineManager.forDocument(document).visible) {
+ overlay.focus();
+ } else if (!overlay.pageDiv.contains(document.activeElement)) {
+ document.activeElement.blur();
+ }
+ }
+
+ if ($('search-field').value == '') {
+ var section = overlay.associatedSection;
+ if (section)
+ options.BrowserOptions.scrollToSection(section);
+ }
+
+ 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() {
+ var topmostPage = null;
+ for (var name in this.registeredOverlayPages) {
+ var page = this.registeredOverlayPages[name];
+ if (page.visible &&
+ (!topmostPage || page.nestingLevel > topmostPage.nestingLevel)) {
+ topmostPage = page;
+ }
+ }
+ return topmostPage;
+ };
+
+ /**
+ * Restores the last focused element on a given page.
+ */
+ OptionsPage.restoreLastFocusedElement_ = function() {
+ var currentPage = this.getTopmostVisiblePage();
+ if (currentPage.lastFocusedElement)
+ currentPage.lastFocusedElement.focus();
+ };
+
+ /**
+ * 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_(false, {ignoreHash: true});
+
+ this.restoreLastFocusedElement_();
+ };
+
+ /**
+ * Cancels (closes) the overlay, due to the user pressing <Esc>.
+ */
+ OptionsPage.cancelOverlay = function() {
+ // Blur the active element to ensure any changed pref value is saved.
+ document.activeElement.blur();
+ var overlay = this.getVisibleOverlay_();
+ // Let the overlay handle the <Esc> if it wants to.
+ if (overlay.handleCancel) {
+ overlay.handleCancel();
+ this.restoreLastFocusedElement_();
+ } else {
+ this.closeOverlay();
+ }
+ };
+
+ /**
+ * 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 pages which are currently visible, ordered by nesting level
+ * (ascending).
+ * @return {Array.OptionPage} The pages which are currently visible, ordered
+ * by nesting level (ascending).
+ */
+ OptionsPage.getVisiblePages_ = function() {
+ var visiblePages = [];
+ for (var name in this.registeredPages) {
+ var page = this.registeredPages[name];
+ if (page.visible)
+ visiblePages[page.nestingLevel] = page;
+ }
+ return visiblePages;
+ };
+
+ /**
+ * 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_();
+ };
+
+ /**
+ * Returns the currently visible bubble, or null if no bubble is visible.
+ * @return {AutoCloseBubble} The bubble currently being shown.
+ */
+ OptionsPage.getVisibleBubble = function() {
+ var bubble = OptionsPage.bubble_;
+ return bubble && !bubble.hidden ? bubble : null;
+ };
+
+ /**
+ * Shows an informational bubble displaying |content| and pointing at the
+ * |target| element. If |content| has focusable elements, they join the
+ * current page's tab order as siblings of |domSibling|.
+ * @param {HTMLDivElement} content The content of the bubble.
+ * @param {HTMLElement} target The element at which the bubble points.
+ * @param {HTMLElement} domSibling The element after which the bubble is added
+ * to the DOM.
+ * @param {cr.ui.ArrowLocation} location The arrow location.
+ */
+ OptionsPage.showBubble = function(content, target, domSibling, location) {
+ OptionsPage.hideBubble();
+
+ var bubble = new cr.ui.AutoCloseBubble;
+ bubble.anchorNode = target;
+ bubble.domSibling = domSibling;
+ bubble.arrowLocation = location;
+ bubble.content = content;
+ bubble.show();
+ OptionsPage.bubble_ = bubble;
+ };
+
+ /**
+ * Hides the currently visible bubble, if any.
+ */
+ OptionsPage.hideBubble = function() {
+ if (OptionsPage.bubble_)
+ OptionsPage.bubble_.hide();
+ };
+
+ /**
+ * 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;
+ 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 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]);
+ }
+
+ // Sanity check.
+ for (var i = 0; i < associatedControls.length; ++i) {
+ assert(associatedControls[i], 'Invalid element passed.');
+ }
+ }
+
+ // Reverse the button strip for views. See the documentation of
+ // reverseButtonStripIfNecessary_() for an explanation of why this is done.
+ if (cr.isViews)
+ this.reverseButtonStripIfNecessary_(overlay);
+
+ overlay.tab = undefined;
+ overlay.isOverlay = true;
+ overlay.initializePage();
+ };
+
+ /**
+ * Reverses the child elements of a button strip if it hasn't already been
+ * reversed. 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 http://webk.it/62664 for
+ * more information.
+ * @param {Object} overlay The overlay containing the button strip to reverse.
+ * @private
+ */
+ OptionsPage.reverseButtonStripIfNecessary_ = function(overlay) {
+ var buttonStrips =
+ overlay.pageDiv.querySelectorAll('.button-strip:not([reversed])');
+
+ // 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]);
+
+ buttonStrip.setAttribute('reversed', '');
+ }
+ };
+
+ /**
+ * Callback for window.onpopstate to handle back/forward navigations.
+ * @param {Object} data State data pushed into history.
+ */
+ OptionsPage.setState = function(data) {
+ if (data && data.pageName) {
+ var currentOverlay = this.getVisibleOverlay_();
+ var lowercaseName = data.pageName.toLowerCase();
+ var newPage = this.registeredPages[lowercaseName] ||
+ this.registeredOverlayPages[lowercaseName] ||
+ this.getDefaultPage();
+ if (currentOverlay && !currentOverlay.isAncestorOfPage(newPage)) {
+ currentOverlay.visible = false;
+ if (currentOverlay.didClosePage) currentOverlay.didClosePage();
+ }
+ 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 the root page container.
+ * @param {boolean} freeze Whether the page should be frozen.
+ * @private
+ */
+ OptionsPage.setRootPageFrozen_ = function(freeze) {
+ var container = $('page-container');
+ 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 = scrollTopForDocument(document);
+ 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 the root page based on the current
+ * page stack.
+ */
+ OptionsPage.updateRootPageFreezeState = function() {
+ var topPage = OptionsPage.getTopmostVisiblePage();
+ if (topPage)
+ this.setRootPageFrozen_(topPage.isOverlay);
+ };
+
+ /**
+ * Initializes the complete options page. This will cause all C++ handlers to
+ * be invoked to do final setup.
+ */
+ OptionsPage.initialize = function() {
+ chrome.send('coreOptionsInitialize');
+ uber.onContentFrameLoaded();
+ FocusOutlineManager.forDocument(document);
+ document.addEventListener('scroll', this.handleScroll_.bind(this));
+
+ // Trigger the scroll handler manually to set the initial state.
+ this.handleScroll_();
+
+ // Shake the dialog if the user clicks outside the dialog bounds.
+ var containers = [$('overlay-container-1'), $('overlay-container-2')];
+ for (var i = 0; i < containers.length; i++) {
+ var overlay = containers[i];
+ cr.ui.overlay.setupOverlay(overlay);
+ overlay.addEventListener('cancelOverlay',
+ OptionsPage.cancelOverlay.bind(OptionsPage));
+ }
+
+ cr.ui.overlay.globalInitialization();
+ };
+
+ /**
+ * 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 the page is scrolled; moves elements that are position:fixed
+ * but should only behave as if they are fixed for vertical scrolling.
+ * @private
+ */
+ OptionsPage.handleScroll_ = function() {
+ 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 (isRTL()) {
+ e.style.right = OptionsPage.horizontalOffset + 'px';
+ } else {
+ var scrollLeft = scrollLeftForDocument(document);
+ e.style.left = OptionsPage.horizontalOffset - scrollLeft + 'px';
+ }
+ };
+
+ /**
+ * Change the horizontal offset used to reposition elements while showing an
+ * overlay from the default.
+ */
+ OptionsPage.setHorizontalOffset = function(value) {
+ OptionsPage.horizontalOffset = value;
+ };
+
+ OptionsPage.setClearPluginLSODataEnabled = function(enabled) {
+ if (enabled) {
+ document.documentElement.setAttribute(
+ 'flashPluginSupportsClearSiteData', '');
+ } else {
+ document.documentElement.removeAttribute(
+ 'flashPluginSupportsClearSiteData');
+ }
+ if (navigator.plugins['Shockwave Flash'])
+ document.documentElement.setAttribute('hasFlashPlugin', '');
+ };
+
+ OptionsPage.setPepperFlashSettingsEnabled = function(enabled) {
+ if (enabled) {
+ document.documentElement.setAttribute(
+ 'enablePepperFlashSettings', '');
+ } else {
+ document.documentElement.removeAttribute(
+ 'enablePepperFlashSettings');
+ }
+ };
+
+ OptionsPage.setIsSettingsApp = function() {
+ document.documentElement.classList.add('settings-app');
+ };
+
+ OptionsPage.isSettingsApp = function() {
+ return document.documentElement.classList.contains('settings-app');
+ };
+
+ /**
+ * Whether the page is still loading (i.e. onload hasn't finished running).
+ * @return {boolean} Whether the page is still loading.
+ */
+ OptionsPage.isLoading = function() {
+ return document.documentElement.classList.contains('loading');
+ };
+
+ 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() {},
+
+ /**
+ * Sets focus on the first focusable element. Override for a custom focus
+ * strategy.
+ */
+ focus: function() {
+ // Do not change focus if any control on this page is already focused.
+ if (this.pageDiv.contains(document.activeElement))
+ return;
+
+ var elements = this.pageDiv.querySelectorAll(
+ 'input, list, select, textarea, button');
+ for (var i = 0; i < elements.length; i++) {
+ var element = elements[i];
+ // Try to focus. If fails, then continue.
+ element.focus();
+ if (document.activeElement == element)
+ return;
+ }
+ },
+
+ /**
+ * Gets the container div for this page if it is an overlay.
+ * @type {HTMLElement}
+ */
+ get container() {
+ assert(this.isOverlay);
+ return this.pageDiv.parentNode;
+ },
+
+ /**
+ * Gets page visibility state.
+ * @type {boolean}
+ */
+ get visible() {
+ // If this is an overlay dialog it is no longer considered visible while
+ // the overlay is fading out. See http://crbug.com/118629.
+ if (this.isOverlay &&
+ this.container.classList.contains('transparent')) {
+ return false;
+ }
+ if (this.pageDiv.hidden)
+ return false;
+ return this.pageDiv.page == this;
+ },
+
+ /**
+ * Sets page visibility.
+ * @type {boolean}
+ */
+ set visible(visible) {
+ if ((this.visible && visible) || (!this.visible && !visible))
+ return;
+
+ // If using an overlay, the visibility of the dialog is toggled at the
+ // same time as the overlay to show the dialog's out transition. This
+ // is handled in setOverlayVisible.
+ if (this.isOverlay) {
+ this.setOverlayVisible_(visible);
+ } else {
+ this.pageDiv.page = this;
+ this.pageDiv.hidden = !visible;
+ this.onVisibilityChanged_();
+ }
+
+ cr.dispatchPropertyChange(this, 'visible', visible, !visible);
+ },
+
+ /**
+ * Shows or hides an overlay (including any visible dialog).
+ * @param {boolean} visible Whether the overlay should be visible or not.
+ * @private
+ */
+ setOverlayVisible_: function(visible) {
+ assert(this.isOverlay);
+ var pageDiv = this.pageDiv;
+ var container = this.container;
+
+ if (visible)
+ uber.invokeMethodOnParent('beginInterceptingEvents');
+
+ 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');
+
+ // Hide all dialogs in this container since a different one may have
+ // been previously visible before fading out.
+ var pages = container.querySelectorAll('.page');
+ for (var i = 0; i < pages.length; i++)
+ pages[i].hidden = true;
+ // Show the new dialog.
+ pageDiv.hidden = false;
+ pageDiv.page = this;
+ }
+ return;
+ }
+
+ var self = this;
+ var loading = OptionsPage.isLoading();
+ if (!loading) {
+ // TODO(flackr): Use an event delegate to avoid having to subscribe and
+ // unsubscribe for webkitTransitionEnd events.
+ container.addEventListener('webkitTransitionEnd', function f(e) {
+ if (e.target != e.currentTarget || e.propertyName != 'opacity')
+ return;
+ container.removeEventListener('webkitTransitionEnd', f);
+ self.fadeCompleted_();
+ });
+ }
+
+ if (visible) {
+ container.hidden = false;
+ pageDiv.hidden = false;
+ pageDiv.page = this;
+ // NOTE: This is a hacky way to force the container to layout which
+ // will allow us to trigger the webkit transition.
+ container.scrollTop;
+
+ this.pageDiv.removeAttribute('aria-hidden');
+ if (this.parentPage) {
+ this.parentPage.pageDiv.parentElement.setAttribute('aria-hidden',
+ true);
+ }
+ container.classList.remove('transparent');
+ this.onVisibilityChanged_();
+ } else {
+ // Kick change events for text fields.
+ if (pageDiv.contains(document.activeElement))
+ document.activeElement.blur();
+ container.classList.add('transparent');
+ }
+
+ if (loading)
+ this.fadeCompleted_();
+ },
+
+ /**
+ * Called when a container opacity transition finishes.
+ * @private
+ */
+ fadeCompleted_: function() {
+ if (this.container.classList.contains('transparent')) {
+ this.pageDiv.hidden = true;
+ this.container.hidden = true;
+
+ if (this.parentPage)
+ this.parentPage.pageDiv.parentElement.removeAttribute('aria-hidden');
+
+ if (this.nestingLevel == 1)
+ uber.invokeMethodOnParent('stopInterceptingEvents');
+
+ this.onVisibilityChanged_();
+ }
+ },
+
+ /**
+ * Called when a page is shown or hidden to update the root options page
+ * based on this page's visibility.
+ * @private
+ */
+ onVisibilityChanged_: function() {
+ OptionsPage.updateRootPageFreezeState();
+
+ if (this.isOverlay && !this.visible)
+ OptionsPage.updateScrollPosition_();
+ },
+
+ /**
+ * 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 The potential descendent of this page.
+ * @return {boolean} True if |page| is nested under this 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/chromium/chrome/browser/resources/options/options_settings_app.css b/chromium/chrome/browser/resources/options/options_settings_app.css
new file mode 100644
index 00000000000..e1bd65801d2
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/options_settings_app.css
@@ -0,0 +1,47 @@
+/* Copyright 2012 The Chromium Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file. */
+
+/* Don't use the large margin used for the navigation bar. Settings App uses a
+ * 20px margin for headings + 18px for sections.
+ */
+html.settings-app body.uber-frame {
+ -webkit-margin-start: 38px;
+}
+
+/* There is a tweak in uber_shared.css to improve touch events around the
+ * navigation bar (which is not used for the settings app) in reaction to a
+ * webkit bug (wk95204). We need to reset back to the original style.
+ */
+@media(pointer:coarse) {
+ html.settings-app body.uber-frame section {
+ -webkit-padding-start: 18px;
+ }
+ html.settings-app body.uber-frame section > h3 {
+ -webkit-margin-start: -18px;
+ }
+}
+
+html.settings-app body.uber-frame header {
+ left: 20px;
+ min-width: 400px;
+}
+
+html.settings-app #content-settings-page .content-area {
+ -webkit-margin-start: 18px;
+}
+
+/* Settings App is narrower due to no navigation margin, so the roomy language
+ * overlay needs to be trimmed down. 25% is taken off the original height and
+ * the width is trimmed proportional to (half) the reduction due to the removal
+ * of the left margin.
+ */
+html.settings-app .language-options-left {
+ height: 300px;
+ width: 228px;
+}
+
+html.settings-app .language-options-right {
+ height: 300px;
+ width: 288px;
+}
diff --git a/chromium/chrome/browser/resources/options/options_settings_app.js b/chromium/chrome/browser/resources/options/options_settings_app.js
new file mode 100644
index 00000000000..02ab5569aaa
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/options_settings_app.js
@@ -0,0 +1,52 @@
+// Copyright 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+(function() {
+ if (document.location != 'chrome://settings-frame/options_settings_app.html')
+ return;
+
+ OptionsPage.setIsSettingsApp();
+
+ // Override the offset in the options page.
+ OptionsPage.setHorizontalOffset(38);
+
+ document.addEventListener('DOMContentLoaded', function() {
+ // Hide everything by default.
+ var sections = document.querySelectorAll('section');
+ for (var i = 0; i < sections.length; i++)
+ sections[i].hidden = true;
+
+ var whitelistedSections = [
+ 'advanced-settings',
+ 'downloads-section',
+ 'handlers-section',
+ 'languages-section',
+ 'media-galleries-section',
+ 'network-section',
+ 'notifications-section',
+ 'sync-section'
+ ];
+
+ for (var i = 0; i < whitelistedSections.length; i++)
+ $(whitelistedSections[i]).hidden = false;
+
+ // Avoid showing an empty Users section on ash. Note that profiles-section
+ // is actually a div element, rather than section, so is not hidden after
+ // the querySelectorAll(), above.
+ $('sync-users-section').hidden = $('profiles-section').hidden;
+
+ // Hide Import bookmarks and settings button.
+ $('import-data').hidden = true;
+
+ // Hide create / edit / delete profile buttons.
+ $('profiles-create').hidden = true;
+ $('profiles-delete').hidden = true;
+ $('profiles-manage').hidden = true;
+
+ // Remove the 'X'es on profiles in the profile list.
+ $('profiles-list').canDeleteItems = false;
+ });
+
+ loadTimeData.overrideValues(loadTimeData.getValue('settingsApp'));
+}());
diff --git a/chromium/chrome/browser/resources/options/password_manager.css b/chromium/chrome/browser/resources/options/password_manager.css
new file mode 100644
index 00000000000..18314ef98e8
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/password_manager.css
@@ -0,0 +1,28 @@
+/* Copyright (c) 2012 The Chromium Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#password-manager > div.content-area {
+ width: 600px;
+}
+
+#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%;
+}
+
+#passwords-title {
+ display: inline-block;
+}
diff --git a/chromium/chrome/browser/resources/options/password_manager.html b/chromium/chrome/browser/resources/options/password_manager.html
new file mode 100644
index 00000000000..dfecba6cd07
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/password_manager.html
@@ -0,0 +1,35 @@
+<div id="password-manager" class="page" hidden>
+ <div class="close-button"></div>
+ <h1 i18n-content="passwordsPage"></h1>
+ <div class="content-area">
+ <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>
+ </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>
+ <div class="action-area">
+ <div class="button-strip">
+ <button id="password-manager-confirm" i18n-content="done"></button>
+ </div>
+ </div>
+</div>
diff --git a/chromium/chrome/browser/resources/options/password_manager.js b/chromium/chrome/browser/resources/options/password_manager.js
new file mode 100644
index 00000000000..36c04edc604
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/password_manager.js
@@ -0,0 +1,250 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+ /** @const */ var OptionsPage = options.OptionsPage;
+ /** @const */ var ArrayDataModel = cr.ui.ArrayDataModel;
+
+ /////////////////////////////////////////////////////////////////////////////
+ // PasswordManager class:
+
+ /**
+ * Encapsulated handling of password and exceptions page.
+ * @constructor
+ */
+ function PasswordManager() {
+ this.activeNavTab = null;
+ OptionsPage.call(this,
+ 'passwords',
+ loadTimeData.getString('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,
+
+ /** @override */
+ initializePage: function() {
+ OptionsPage.prototype.initializePage.call(this);
+
+ $('password-manager-confirm').onclick = function() {
+ OptionsPage.closeOverlay();
+ };
+
+ $('password-search-box').addEventListener('search',
+ this.handleSearchQueryChange_.bind(this));
+
+ this.createSavedPasswordsList_();
+ this.createPasswordExceptionsList_();
+ },
+
+ /** @override */
+ canShowPage: function() {
+ return !(cr.isChromeOS && UIAccountTweaks.loggedInAsGuest());
+ },
+
+ /** @override */
+ 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].toLowerCase().indexOf(query.toLowerCase()) >= 0 ||
+ entry[1].toLowerCase().indexOf(query.toLowerCase()) >= 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_);
+ },
+
+ /**
+ * Reveals the password for a saved password entry. This is called by the
+ * backend after it has authenticated the user.
+ * @param {number} index The original index of the entry in the model.
+ * @param {string} password The saved password.
+ */
+ showPassword_: function(index, password) {
+ var model = this.savedPasswordsList_.dataModel;
+ if (this.lastQuery_) {
+ // When a filter is active, |index| does not represent the current
+ // index in the model, but each entry stores its original index, so
+ // we can find the item using a linear search.
+ for (var i = 0; i < model.length; ++i) {
+ if (model.item(i)[3] == index) {
+ index = i;
+ break;
+ }
+ }
+ }
+
+ // Reveal the password in the UI.
+ var item = this.savedPasswordsList_.getListItemByIndex(index);
+ item.showPassword(password);
+ },
+ };
+
+ /**
+ * Removes a saved password.
+ * @param {number} rowIndex indicating the row to remove.
+ */
+ PasswordManager.removeSavedPassword = function(rowIndex) {
+ chrome.send('removeSavedPassword', [String(rowIndex)]);
+ };
+
+ /**
+ * Removes a password exception.
+ * @param {number} rowIndex indicating the row to remove.
+ */
+ PasswordManager.removePasswordException = function(rowIndex) {
+ chrome.send('removePasswordException', [String(rowIndex)]);
+ };
+
+ PasswordManager.requestShowPassword = function(index) {
+ chrome.send('requestShowPassword', [index]);
+ };
+
+ // Forward public APIs to private implementations on the singleton instance.
+ [
+ 'setSavedPasswordsList',
+ 'setPasswordExceptionsList',
+ 'showPassword'
+ ].forEach(function(name) {
+ PasswordManager[name] = function() {
+ var instance = PasswordManager.getInstance();
+ return instance[name + '_'].apply(instance, arguments);
+ };
+ });
+
+ // Export
+ return {
+ PasswordManager: PasswordManager
+ };
+
+});
diff --git a/chromium/chrome/browser/resources/options/password_manager_list.css b/chromium/chrome/browser/resources/options/password_manager_list.css
new file mode 100644
index 00000000000..8688619c287
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/password_manager_list.css
@@ -0,0 +1,58 @@
+/* Copyright (c) 2012 The Chromium Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file. */
+
+#saved-passwords-list .list-inline-button {
+ -webkit-transition: opacity 150ms;
+ background: rgb(138, 170, 237);
+ font-size: 0.9em;
+ height: 18px;
+ padding: 0 2px;
+ position: absolute;
+ top: 3px;
+}
+
+html[dir='ltr'] #saved-passwords-list .list-inline-button {
+ right: 2px;
+}
+
+html[dir='rtl'] #saved-passwords-list .list-inline-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/chromium/chrome/browser/resources/options/password_manager_list.js b/chromium/chrome/browser/resources/options/password_manager_list.js
new file mode 100644
index 00000000000..9442dee28a5
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/password_manager_list.js
@@ -0,0 +1,342 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options.passwordManager', function() {
+ /** @const */ var ArrayDataModel = cr.ui.ArrayDataModel;
+ /** @const */ var DeletableItemList = options.DeletableItemList;
+ /** @const */ var DeletableItem = options.DeletableItem;
+ /** @const */ var List = cr.ui.List;
+
+ /**
+ * Creates a new passwords list item.
+ * @param {ArrayDataModel} dataModel The data model that contains this 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.
+ * @param {boolean} showPasswords If true, add a button to the element to
+ * allow the user to reveal the saved password.
+ * @constructor
+ * @extends {cr.ui.ListItem}
+ */
+ function PasswordListItem(dataModel, entry, showPasswords) {
+ var el = cr.doc.createElement('div');
+ el.dataItem = entry;
+ el.dataModel = dataModel;
+ el.__proto__ = PasswordListItem.prototype;
+ el.decorate(showPasswords);
+
+ return el;
+ }
+
+ PasswordListItem.prototype = {
+ __proto__: DeletableItem.prototype,
+
+ /** @override */
+ 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;
+
+ // The favicon URL is prefixed with "origin/", which essentially removes
+ // the URL path past the top-level domain and ensures that a scheme (e.g.,
+ // http) is being used. This ensures that the favicon returned is the
+ // default favicon for the domain and that the URL has a scheme if none
+ // is present in the password manager.
+ urlLabel.style.backgroundImage = getFaviconImageSet(
+ 'origin/' + this.url, 16);
+ 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);
+ this.passwordField = passwordInput;
+
+ // The show/hide button.
+ if (showPasswords) {
+ var button = this.ownerDocument.createElement('button');
+ button.hidden = true;
+ button.className = 'list-inline-button custom-appearance';
+ button.textContent = loadTimeData.getString('passwordShowButton');
+ button.addEventListener('click', this.onClick_.bind(this), true);
+ button.addEventListener('mousedown', function(event) {
+ // Don't focus on this button by mousedown.
+ event.preventDefault();
+ // Don't handle list item selection. It causes focus change.
+ event.stopPropagation();
+ }, false);
+ passwordInputDiv.appendChild(button);
+ this.passwordShowButton = button;
+ }
+
+ this.contentElement.appendChild(passwordInputDiv);
+ },
+
+ /** @override */
+ selectionChanged: function() {
+ var input = this.passwordField;
+ var button = this.passwordShowButton;
+ // The 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;
+ }
+ },
+
+ /**
+ * Reveals the plain text password of this entry.
+ */
+ showPassword: function(password) {
+ this.passwordField.value = password;
+ this.passwordField.type = 'text';
+
+ var button = this.passwordShowButton;
+ if (button)
+ button.textContent = loadTimeData.getString('passwordHideButton');
+ },
+
+ /**
+ * Hides the plain text password of this entry.
+ */
+ hidePassword: function() {
+ this.passwordField.type = 'password';
+
+ var button = this.passwordShowButton;
+ if (button)
+ button.textContent = loadTimeData.getString('passwordShowButton');
+ },
+
+ /**
+ * Get the original index of this item in the data model.
+ * @return {number} The index.
+ * @private
+ */
+ getOriginalIndex_: function() {
+ var index = this.dataItem[3];
+ return index ? index : this.dataModel.indexOf(this.dataItem);
+ },
+
+ /**
+ * On-click event handler. Swaps the type of the input field from password
+ * to text and back.
+ * @private
+ */
+ onClick_: function(event) {
+ if (this.passwordField.type == 'password') {
+ // After the user is authenticated, showPassword() will be called.
+ PasswordManager.requestShowPassword(this.getOriginalIndex_());
+ } else {
+ this.hidePassword();
+ }
+ },
+
+ /**
+ * 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;
+
+ // The favicon URL is prefixed with "origin/", which essentially removes
+ // the URL path past the top-level domain and ensures that a scheme (e.g.,
+ // http) is being used. This ensures that the favicon returned is the
+ // default favicon for the domain and that the URL has a scheme if none
+ // is present in the password manager.
+ urlLabel.style.backgroundImage = getFaviconImageSet(
+ 'origin/' + this.url, 16);
+ 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,
+
+ /** @override */
+ 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();
+ },
+
+ /** @override */
+ createItem: function(entry) {
+ var showPasswords = this.showPasswords_;
+
+ if (loadTimeData.getBoolean('disableShowPasswords'))
+ showPasswords = false;
+
+ return new PasswordListItem(this.dataModel, entry, showPasswords);
+ },
+
+ /** @override */
+ 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,
+
+ /** @override */
+ createItem: function(entry) {
+ return new PasswordExceptionsListItem(entry);
+ },
+
+ /** @override */
+ 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/chromium/chrome/browser/resources/options/pref_ui.js b/chromium/chrome/browser/resources/options/pref_ui.js
new file mode 100644
index 00000000000..0775306d776
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/pref_ui.js
@@ -0,0 +1,557 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+
+ var 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;
+ }
+
+ /////////////////////////////////////////////////////////////////////////////
+ // PrefInputElement class:
+
+ // Define a constructor that uses an input element as its underlying element.
+ var PrefInputElement = cr.ui.define('input');
+
+ PrefInputElement.prototype = {
+ // Set up the prototype chain
+ __proto__: HTMLInputElement.prototype,
+
+ /**
+ * Initialization function for the cr.ui framework.
+ */
+ decorate: function() {
+ var self = this;
+
+ // Listen for user events.
+ this.addEventListener('change', this.handleChange_.bind(this));
+
+ // Listen for pref changes.
+ Preferences.getInstance().addEventListener(this.pref, function(event) {
+ if (event.value.uncommitted && !self.dialogPref)
+ return;
+ self.updateStateFromPref_(event);
+ updateDisabledState_(self, 'notUserModifiable', event.value.disabled);
+ self.controlledBy = event.value.controlledBy;
+ });
+ },
+
+ /**
+ * Handle changes to the input element's state made by the user. If a custom
+ * change handler does not suppress it, a default handler is invoked that
+ * updates the associated pref.
+ * @param {Event} event Change event.
+ * @private
+ */
+ handleChange_: function(event) {
+ if (!this.customChangeHandler(event))
+ this.updatePrefFromState_();
+ },
+
+ /**
+ * Update the input element's state when the associated pref changes.
+ * @param {Event} event Pref change event.
+ * @private
+ */
+ updateStateFromPref_: function(event) {
+ this.value = event.value.value;
+ },
+
+ /**
+ * See |updateDisabledState_| above.
+ */
+ setDisabled: function(reason, disabled) {
+ updateDisabledState_(this, reason, disabled);
+ },
+
+ /**
+ * Custom change handler that is invoked first when the user makes changes
+ * to the input element's state. If it returns false, a default handler is
+ * invoked next that updates the associated pref. If it returns true, the
+ * default handler is suppressed (i.e., this works like stopPropagation or
+ * cancelBubble).
+ * @param {Event} event Input element change event.
+ */
+ customChangeHandler: function(event) {
+ return false;
+ },
+ };
+
+ /**
+ * The name of the associated preference.
+ * @type {string}
+ */
+ cr.defineProperty(PrefInputElement, 'pref', cr.PropertyKind.ATTR);
+
+ /**
+ * The data type of the associated preference, only relevant for derived
+ * classes that support different data types.
+ * @type {string}
+ */
+ cr.defineProperty(PrefInputElement, 'dataType', cr.PropertyKind.ATTR);
+
+ /**
+ * Whether this input element is part of a dialog. If so, changes take effect
+ * in the settings UI immediately but are only actually committed when the
+ * user confirms the dialog. If the user cancels the dialog instead, the
+ * changes are rolled back in the settings UI and never committed.
+ * @type {boolean}
+ */
+ cr.defineProperty(PrefInputElement, 'dialogPref', cr.PropertyKind.BOOL_ATTR);
+
+ /**
+ * Whether the associated preference is controlled by a source other than the
+ * user's setting (can be 'policy', 'extension', 'recommended' or unset).
+ * @type {string}
+ */
+ cr.defineProperty(PrefInputElement, 'controlledBy', cr.PropertyKind.ATTR);
+
+ /**
+ * The user metric string.
+ * @type {string}
+ */
+ cr.defineProperty(PrefInputElement, 'metric', cr.PropertyKind.ATTR);
+
+ /////////////////////////////////////////////////////////////////////////////
+ // PrefCheckbox class:
+
+ // 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__: PrefInputElement.prototype,
+
+ /**
+ * Initialization function for the cr.ui framework.
+ */
+ decorate: function() {
+ PrefInputElement.prototype.decorate.call(this);
+ this.type = 'checkbox';
+ },
+
+ /**
+ * Update the associated pref when when the user makes changes to the
+ * checkbox state.
+ * @private
+ */
+ updatePrefFromState_: function() {
+ var value = this.inverted_pref ? !this.checked : this.checked;
+ Preferences.setBooleanPref(this.pref, value,
+ !this.dialogPref, this.metric);
+ },
+
+ /**
+ * Update the checkbox state when the associated pref changes.
+ * @param {Event} event Pref change event.
+ * @private
+ */
+ updateStateFromPref_: function(event) {
+ var value = Boolean(event.value.value);
+ this.checked = this.inverted_pref ? !value : value;
+ },
+ };
+
+ /**
+ * Whether the mapping between checkbox state and associated pref is inverted.
+ * @type {boolean}
+ */
+ cr.defineProperty(PrefCheckbox, 'inverted_pref', cr.PropertyKind.BOOL_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__: PrefInputElement.prototype,
+
+ /**
+ * Initialization function for the cr.ui framework.
+ */
+ decorate: function() {
+ PrefInputElement.prototype.decorate.call(this);
+ this.type = 'number';
+ },
+
+ /**
+ * Update the associated pref when when the user inputs a number.
+ * @private
+ */
+ updatePrefFromState_: function() {
+ if (this.validity.valid) {
+ Preferences.setIntegerPref(this.pref, this.value,
+ !this.dialogPref, this.metric);
+ }
+ },
+ };
+
+ /////////////////////////////////////////////////////////////////////////////
+ // 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__: PrefInputElement.prototype,
+
+ /**
+ * Initialization function for the cr.ui framework.
+ */
+ decorate: function() {
+ PrefInputElement.prototype.decorate.call(this);
+ this.type = 'radio';
+ },
+
+ /**
+ * Update the associated pref when when the user selects the radio button.
+ * @private
+ */
+ updatePrefFromState_: function() {
+ if (this.value == 'true' || this.value == 'false') {
+ Preferences.setBooleanPref(this.pref,
+ this.value == String(this.checked),
+ !this.dialogPref, this.metric);
+ } else {
+ Preferences.setIntegerPref(this.pref, this.value,
+ !this.dialogPref, this.metric);
+ }
+ },
+
+ /**
+ * Update the radio button state when the associated pref changes.
+ * @param {Event} event Pref change event.
+ * @private
+ */
+ updateStateFromPref_: function(event) {
+ this.checked = this.value == String(event.value.value);
+ },
+ };
+
+ /////////////////////////////////////////////////////////////////////////////
+ // 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__: PrefInputElement.prototype,
+
+ /**
+ * The map from slider position to corresponding pref value.
+ */
+ valueMap: undefined,
+
+ /**
+ * Initialization function for the cr.ui framework.
+ */
+ decorate: function() {
+ PrefInputElement.prototype.decorate.call(this);
+ this.type = 'range';
+
+ // Listen for user events.
+ // TODO(jhawkins): Add onmousewheel handling once the associated WK bug is
+ // fixed.
+ // https://bugs.webkit.org/show_bug.cgi?id=52256
+ this.addEventListener('keyup', this.handleRelease_.bind(this));
+ this.addEventListener('mouseup', this.handleRelease_.bind(this));
+ },
+
+ /**
+ * Update the associated pref when when the user releases the slider.
+ * @private
+ */
+ updatePrefFromState_: function() {
+ Preferences.setIntegerPref(this.pref, this.mapPositionToPref(this.value),
+ !this.dialogPref, this.metric);
+ },
+
+ /**
+ * Ignore changes to the slider position made by the user while the slider
+ * has not been released.
+ * @private
+ */
+ handleChange_: function() {
+ },
+
+ /**
+ * Handle changes to the slider position made by the user when the slider is
+ * released. If a custom change handler does not suppress it, a default
+ * handler is invoked that updates the associated pref.
+ * @param {Event} event Change event.
+ * @private
+ */
+ handleRelease_: function(event) {
+ if (!this.customChangeHandler(event))
+ this.updatePrefFromState_();
+ },
+
+ /**
+ * Update the slider position when the associated pref changes.
+ * @param {Event} event Pref change event.
+ * @private
+ */
+ updateStateFromPref_: function(event) {
+ var value = event.value.value;
+ this.value = this.valueMap ? this.valueMap.indexOf(value) : value;
+ },
+
+ /**
+ * Map slider position to the range of values provided by the client,
+ * represented by |valueMap|.
+ * @param {number} position The slider position to map.
+ */
+ mapPositionToPref: function(position) {
+ return this.valueMap ? this.valueMap[position] : position;
+ },
+ };
+
+ /////////////////////////////////////////////////////////////////////////////
+ // 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__: PrefInputElement.prototype,
+
+ /**
+ * Update the associated pref when when the user selects an item.
+ * @private
+ */
+ updatePrefFromState_: function() {
+ var value = this.options[this.selectedIndex].value;
+ switch (this.dataType) {
+ case 'number':
+ Preferences.setIntegerPref(this.pref, value,
+ !this.dialogPref, this.metric);
+ break;
+ case 'double':
+ Preferences.setDoublePref(this.pref, value,
+ !this.dialogPref, this.metric);
+ break;
+ case 'boolean':
+ Preferences.setBooleanPref(this.pref, value == 'true',
+ !this.dialogPref, this.metric);
+ break;
+ case 'string':
+ Preferences.setStringPref(this.pref, value,
+ !this.dialogPref, this.metric);
+ break;
+ default:
+ console.error('Unknown data type for <select> UI element: ' +
+ this.dataType);
+ }
+ },
+
+ /**
+ * Update the selected item when the associated pref changes.
+ * @param {Event} event Pref change event.
+ * @private
+ */
+ updateStateFromPref_: function(event) {
+ // Make sure the value is a string, because the value is stored as a
+ // string in the HTMLOptionElement.
+ value = String(event.value.value);
+
+ var found = false;
+ for (var i = 0; i < this.options.length; i++) {
+ if (this.options[i].value == value) {
+ this.selectedIndex = i;
+ found = true;
+ }
+ }
+
+ // Item not found, select first item.
+ if (!found)
+ this.selectedIndex = 0;
+
+ // The "onchange" event automatically fires when the user makes a manual
+ // change. It should never be fired for a programmatic change. However,
+ // these two lines were here already and it is hard to tell who may be
+ // relying on them.
+ if (this.onchange)
+ this.onchange(event);
+ },
+ };
+
+ /////////////////////////////////////////////////////////////////////////////
+ // 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__: PrefInputElement.prototype,
+
+ /**
+ * Initialization function for the cr.ui framework.
+ */
+ decorate: function() {
+ PrefInputElement.prototype.decorate.call(this);
+ var self = this;
+
+ // Listen for user events.
+ window.addEventListener('unload', function() {
+ if (document.activeElement == self)
+ self.blur();
+ });
+ },
+
+ /**
+ * Update the associated pref when when the user inputs text.
+ * @private
+ */
+ updatePrefFromState_: function(event) {
+ switch (this.dataType) {
+ case 'number':
+ Preferences.setIntegerPref(this.pref, this.value,
+ !this.dialogPref, this.metric);
+ break;
+ case 'double':
+ Preferences.setDoublePref(this.pref, this.value,
+ !this.dialogPref, this.metric);
+ break;
+ case 'url':
+ Preferences.setURLPref(this.pref, this.value,
+ !this.dialogPref, this.metric);
+ break;
+ default:
+ Preferences.setStringPref(this.pref, this.value,
+ !this.dialogPref, this.metric);
+ break;
+ }
+ },
+ };
+
+ /////////////////////////////////////////////////////////////////////////////
+ // PrefPortNumber class:
+
+ // Define a constructor that uses an input element as its underlying element.
+ var PrefPortNumber = cr.ui.define('input');
+
+ PrefPortNumber.prototype = {
+ // Set up the prototype chain
+ __proto__: PrefTextField.prototype,
+
+ /**
+ * Initialization function for the cr.ui framework.
+ */
+ decorate: function() {
+ var self = this;
+ self.type = 'text';
+ self.dataType = 'number';
+ PrefTextField.prototype.decorate.call(this);
+ self.oninput = function() {
+ // Note that using <input type="number"> is insufficient to restrict
+ // the input as it allows negative numbers and does not limit the
+ // number of charactes typed even if a range is set. Furthermore,
+ // it sometimes produces strange repaint artifacts.
+ var filtered = self.value.replace(/[^0-9]/g, '');
+ if (filtered != self.value)
+ self.value = filtered;
+ };
+ }
+ };
+
+ /////////////////////////////////////////////////////////////////////////////
+ // 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 for pref changes.
+ // This element behaves like a normal button and does not 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) {
+ updateDisabledState_(self, 'notUserModifiable',
+ event.value.disabled && !event.value.value);
+ self.controlledBy = event.value.controlledBy;
+ });
+ },
+
+ /**
+ * See |updateDisabledState_| above.
+ */
+ setDisabled: function(reason, disabled) {
+ updateDisabledState_(this, reason, disabled);
+ },
+ };
+
+ /**
+ * The name of the associated preference.
+ * @type {string}
+ */
+ cr.defineProperty(PrefButton, 'pref', cr.PropertyKind.ATTR);
+
+ /**
+ * Whether the associated preference is controlled by a source other than the
+ * user's setting (can be 'policy', 'extension', 'recommended' or unset).
+ * @type {string}
+ */
+ cr.defineProperty(PrefButton, 'controlledBy', cr.PropertyKind.ATTR);
+
+ // Export
+ return {
+ PrefCheckbox: PrefCheckbox,
+ PrefNumber: PrefNumber,
+ PrefRadio: PrefRadio,
+ PrefRange: PrefRange,
+ PrefSelect: PrefSelect,
+ PrefTextField: PrefTextField,
+ PrefPortNumber: PrefPortNumber,
+ PrefButton: PrefButton
+ };
+
+});
diff --git a/chromium/chrome/browser/resources/options/preferences.js b/chromium/chrome/browser/resources/options/preferences.js
new file mode 100644
index 00000000000..6f8cc829417
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/preferences.js
@@ -0,0 +1,338 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+
+ /////////////////////////////////////////////////////////////////////////////
+ // Preferences class:
+
+ /**
+ * Preferences class manages access to Chrome profile preferences.
+ * @constructor
+ */
+ function Preferences() {
+ // Map of registered preferences.
+ this.registeredPreferences_ = {};
+ }
+
+ cr.addSingletonGetter(Preferences);
+
+ /**
+ * Sets a Boolean preference and signals its new value.
+ * @param {string} name Preference name.
+ * @param {boolean} value New preference value.
+ * @param {boolean} commit Whether to commit the change to Chrome.
+ * @param {string} metric User metrics identifier.
+ */
+ Preferences.setBooleanPref = function(name, value, commit, metric) {
+ if (!commit) {
+ Preferences.getInstance().setPrefNoCommit_(name, 'bool', Boolean(value));
+ return;
+ }
+
+ var argumentList = [name, Boolean(value)];
+ if (metric != undefined) argumentList.push(metric);
+ chrome.send('setBooleanPref', argumentList);
+ };
+
+ /**
+ * Sets an integer preference and signals its new value.
+ * @param {string} name Preference name.
+ * @param {number} value New preference value.
+ * @param {boolean} commit Whether to commit the change to Chrome.
+ * @param {string} metric User metrics identifier.
+ */
+ Preferences.setIntegerPref = function(name, value, commit, metric) {
+ if (!commit) {
+ Preferences.getInstance().setPrefNoCommit_(name, 'int', Number(value));
+ return;
+ }
+
+ var argumentList = [name, Number(value)];
+ if (metric != undefined) argumentList.push(metric);
+ chrome.send('setIntegerPref', argumentList);
+ };
+
+ /**
+ * Sets a double-valued preference and signals its new value.
+ * @param {string} name Preference name.
+ * @param {number} value New preference value.
+ * @param {boolean} commit Whether to commit the change to Chrome.
+ * @param {string} metric User metrics identifier.
+ */
+ Preferences.setDoublePref = function(name, value, commit, metric) {
+ if (!commit) {
+ Preferences.getInstance().setPrefNoCommit_(name, 'double', Number(value));
+ return;
+ }
+
+ var argumentList = [name, Number(value)];
+ if (metric != undefined) argumentList.push(metric);
+ chrome.send('setDoublePref', argumentList);
+ };
+
+ /**
+ * Sets a string preference and signals its new value.
+ * @param {string} name Preference name.
+ * @param {string} value New preference value.
+ * @param {boolean} commit Whether to commit the change to Chrome.
+ * @param {string} metric User metrics identifier.
+ */
+ Preferences.setStringPref = function(name, value, commit, metric) {
+ if (!commit) {
+ Preferences.getInstance().setPrefNoCommit_(name, 'string', String(value));
+ return;
+ }
+
+ var argumentList = [name, String(value)];
+ if (metric != undefined) argumentList.push(metric);
+ chrome.send('setStringPref', argumentList);
+ };
+
+ /**
+ * Sets a string preference that represents a URL and signals its new value.
+ * The value will be fixed to be a valid URL when it gets committed to Chrome.
+ * @param {string} name Preference name.
+ * @param {string} value New preference value.
+ * @param {boolean} commit Whether to commit the change to Chrome.
+ * @param {string} metric User metrics identifier.
+ */
+ Preferences.setURLPref = function(name, value, commit, metric) {
+ if (!commit) {
+ Preferences.getInstance().setPrefNoCommit_(name, 'url', String(value));
+ return;
+ }
+
+ var argumentList = [name, String(value)];
+ if (metric != undefined) argumentList.push(metric);
+ chrome.send('setURLPref', argumentList);
+ };
+
+ /**
+ * Sets a JSON list preference and signals its new value.
+ * @param {string} name Preference name.
+ * @param {Array} value New preference value.
+ * @param {boolean} commit Whether to commit the change to Chrome.
+ * @param {string} metric User metrics identifier.
+ */
+ Preferences.setListPref = function(name, value, commit, metric) {
+ if (!commit) {
+ Preferences.getInstance().setPrefNoCommit_(name, 'list', value);
+ return;
+ }
+
+ var argumentList = [name, JSON.stringify(value)];
+ if (metric != undefined) argumentList.push(metric);
+ chrome.send('setListPref', argumentList);
+ };
+
+ /**
+ * Clears the user setting for a preference and signals its new effective
+ * value.
+ * @param {string} name Preference name.
+ * @param {boolean} commit Whether to commit the change to Chrome.
+ * @param {string} metric User metrics identifier.
+ */
+ Preferences.clearPref = function(name, commit, metric) {
+ if (!commit) {
+ Preferences.getInstance().clearPrefNoCommit_(name);
+ return;
+ }
+
+ var argumentList = [name];
+ if (metric != undefined) argumentList.push(metric);
+ chrome.send('clearPref', argumentList);
+ };
+
+ Preferences.prototype = {
+ __proto__: cr.EventTarget.prototype,
+
+ /**
+ * 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);
+ if (!(type in this.registeredPreferences_))
+ this.registeredPreferences_[type] = {};
+ },
+
+ /**
+ * 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.
+ * @private
+ */
+ 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 Event(prefix + prefName);
+ this.registeredPreferences_[prefix + prefName].orig = dict[prefName];
+ event.value = dict[prefName];
+ this.dispatchEvent(event);
+ }
+ }
+ },
+
+ /**
+ * Sets a preference and signals its new value. The change is propagated
+ * throughout the UI code but is not committed to Chrome yet. The new value
+ * and its data type are stored so that commitPref() can later be used to
+ * invoke the appropriate set*Pref() method and actually commit the change.
+ * @param {string} name Preference name.
+ * @param {string} type Preference data type.
+ * @param {*} value New preference value.
+ * @private
+ */
+ setPrefNoCommit_: function(name, type, value) {
+ var pref = this.registeredPreferences_[name];
+ pref.action = 'set';
+ pref.type = type;
+ pref.value = value;
+
+ var event = new Event(name);
+ // Decorate pref value as CoreOptionsHandler::CreateValueForPref() does.
+ event.value = {
+ value: value,
+ recommendedValue: pref.orig.recommendedValue,
+ disabled: pref.orig.disabled,
+ uncommitted: true,
+ };
+ this.dispatchEvent(event);
+ },
+
+ /**
+ * Clears a preference and signals its new value. The change is propagated
+ * throughout the UI code but is not committed to Chrome yet.
+ * @param {string} name Preference name.
+ * @private
+ */
+ clearPrefNoCommit_: function(name) {
+ var pref = this.registeredPreferences_[name];
+ pref.action = 'clear';
+ delete pref.type;
+ delete pref.value;
+
+ var event = new Event(name);
+ // Decorate pref value as CoreOptionsHandler::CreateValueForPref() does.
+ event.value = {
+ value: pref.orig.recommendedValue,
+ controlledBy: 'recommended',
+ recommendedValue: pref.orig.recommendedValue,
+ disabled: pref.orig.disabled,
+ uncommitted: true,
+ };
+ this.dispatchEvent(event);
+ },
+
+ /**
+ * Commits a preference change to Chrome and signals the new preference
+ * value. Does nothing if there is no uncommitted change.
+ * @param {string} name Preference name.
+ * @param {string} metric User metrics identifier.
+ */
+ commitPref: function(name, metric) {
+ var pref = this.registeredPreferences_[name];
+ switch (pref.action) {
+ case 'set':
+ switch (pref.type) {
+ case 'bool':
+ Preferences.setBooleanPref(name, pref.value, true, metric);
+ break;
+ case 'int':
+ Preferences.setIntegerPref(name, pref.value, true, metric);
+ break;
+ case 'double':
+ Preferences.setDoublePref(name, pref.value, true, metric);
+ break;
+ case 'string':
+ Preferences.setStringPref(name, pref.value, true, metric);
+ break;
+ case 'url':
+ Preferences.setURLPref(name, pref.value, true, metric);
+ break;
+ case 'list':
+ Preferences.setListPref(name, pref.value, true, metric);
+ break;
+ }
+ break;
+ case 'clear':
+ Preferences.clearPref(name, true, metric);
+ break;
+ }
+ delete pref.action;
+ delete pref.type;
+ delete pref.value;
+ },
+
+ /**
+ * Rolls back a preference change and signals the original preference value.
+ * Does nothing if there is no uncommitted change.
+ * @param {string} name Preference name.
+ */
+ rollbackPref: function(name) {
+ var pref = this.registeredPreferences_[name];
+ if (!pref.action)
+ return;
+
+ delete pref.action;
+ delete pref.type;
+ delete pref.value;
+
+ var event = new Event(name);
+ event.value = pref.orig;
+ event.value.uncommitted = true;
+ 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 Event(notification[0]);
+ event.value = notification[1];
+ prefs = Preferences.getInstance();
+ prefs.registeredPreferences_[notification[0]] = {orig: notification[1]};
+ prefs.dispatchEvent(event);
+ };
+
+ // Export
+ return {
+ Preferences: Preferences
+ };
+
+});
diff --git a/chromium/chrome/browser/resources/options/profiles_icon_grid.js b/chromium/chrome/browser/resources/options/profiles_icon_grid.js
new file mode 100644
index 00000000000..a35b23fe436
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/profiles_icon_grid.js
@@ -0,0 +1,68 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+ /** @const */ var ListItem = cr.ui.ListItem;
+ /** @const */ var Grid = cr.ui.Grid;
+ /** @const */ var 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,
+
+ /** @override */
+ decorate: function() {
+ ListItem.prototype.decorate.call(this);
+ var imageEl = cr.doc.createElement('img');
+ imageEl.className = 'profile-icon';
+ imageEl.style.content = imageset(this.iconURL_ + '@scalefactorx');
+ this.appendChild(imageEl);
+
+ this.className = 'profile-icon-grid-item';
+ },
+ };
+
+ var ProfilesIconGrid = cr.ui.define('grid');
+
+ ProfilesIconGrid.prototype = {
+ __proto__: Grid.prototype,
+
+ /** @override */
+ decorate: function() {
+ Grid.prototype.decorate.call(this);
+ this.selectionModel = new ListSingleSelectionModel();
+ },
+
+ /** @override */
+ createItem: function(iconURL) {
+ return new ProfilesIconGridItem(iconURL);
+ },
+ };
+
+ return {
+ ProfilesIconGrid: ProfilesIconGrid
+ };
+});
+
diff --git a/chromium/chrome/browser/resources/options/reset_profile_settings_banner.css b/chromium/chrome/browser/resources/options/reset_profile_settings_banner.css
new file mode 100644
index 00000000000..21087f8aebd
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/reset_profile_settings_banner.css
@@ -0,0 +1,77 @@
+/* Copyright 2013 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. */
+
+#reset-profile-settings-banner {
+ background-color: #f5f5f5;
+ border-color: #c8c8c8;
+ border-radius: 3px;
+ border-style: solid;
+ border-width: 1px;
+ margin-bottom: 24px;
+ margin-top: 20px;
+ position: relative;
+ width: 716px;
+}
+
+#reset-profile-settings-banner > .close-button {
+ background-image: url('chrome://theme/IDR_CLOSE_DIALOG');
+ background-position: center;
+ background-repeat: no-repeat;
+ height: 14px;
+ opacity: 0.5;
+ position: absolute;
+ right: 4px;
+ top: 4px;
+ width: 14px;
+}
+
+html[dir='rtl'] #reset-profile-settings-banner > .close-button {
+ left: 4px;
+ right: auto;
+}
+
+#reset-profile-settings-banner > .close-button:hover {
+ background-image: url('chrome://theme/IDR_CLOSE_DIALOG_H');
+}
+
+#reset-profile-settings-banner > .close-button:active {
+ background-image: url('chrome://theme/IDR_CLOSE_DIALOG_P');
+}
+
+#reset-profile-settings-banner .content-area {
+ -webkit-box-align: center;
+ display: -webkit-box;
+ padding: 17px;
+}
+
+#reset-profile-settings-banner .content-area .badge {
+ background-image: url(yellow_gear.png);
+ background-position: center;
+ background-repeat: no-repeat;
+ height: 55px;
+ width: 58px;
+}
+
+#reset-profile-settings-banner .content-area .text {
+ -webkit-box-flex: 1.0;
+ -webkit-margin-start: 18px;
+}
+
+#reset-profile-settings-banner .content-area .text p {
+ -webkit-margin-after: 0;
+ -webkit-margin-before: 0;
+}
+
+#reset-profile-settings-banner .content-area .button-area {
+ -webkit-margin-start: 54px;
+}
+
+#reset-profile-settings-banner .nowrap {
+ white-space: nowrap;
+}
+
+#reset-profile-settings-banner button {
+ margin-bottom: 1px;
+ margin-right: 0;
+}
diff --git a/chromium/chrome/browser/resources/options/reset_profile_settings_banner.html b/chromium/chrome/browser/resources/options/reset_profile_settings_banner.html
new file mode 100644
index 00000000000..77b6d592eca
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/reset_profile_settings_banner.html
@@ -0,0 +1,18 @@
+<div id="reset-profile-settings-banner" hidden>
+ <div id="reset-profile-settings-banner-close" class="close-button"></div>
+ <div class="content-area">
+ <div class="badge"></div>
+ <div class="text">
+ <p>
+ <span i18n-values=".innerHTML:resetProfileSettingsBannerText">
+ </span>
+ <a class="nowrap" i18n-values="href:resetProfileSettingsLearnMoreUrl"
+ i18n-content="learnMore" target="_blank"></a>
+ </p>
+ </div>
+ <div class="button-area">
+ <button id="reset-profile-settings-banner-activate"
+ i18n-content="resetProfileSettings"></button>
+ </div>
+ </div>
+</div>
diff --git a/chromium/chrome/browser/resources/options/reset_profile_settings_banner.js b/chromium/chrome/browser/resources/options/reset_profile_settings_banner.js
new file mode 100644
index 00000000000..4517e9151c7
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/reset_profile_settings_banner.js
@@ -0,0 +1,103 @@
+// Copyright 2013 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.
+
+// Note: the native-side handler for this is ResetProfileSettingsHandler.
+
+cr.define('options', function() {
+ /** @const */ var OptionsPage = options.OptionsPage;
+
+ /**
+ * ResetProfileSettingsBanner class
+ * Provides encapsulated handling of the Reset Profile Settings banner.
+ * @constructor
+ */
+ function ResetProfileSettingsBanner() {}
+
+ cr.addSingletonGetter(ResetProfileSettingsBanner);
+
+ ResetProfileSettingsBanner.prototype = {
+ /**
+ * Whether or not the banner has already been dismissed.
+ *
+ * This is needed because of the surprising ordering of asynchronous
+ * JS<->native calls when the settings page is opened with specifying a
+ * given sub-page, e.g. chrome://settings/resetProfileSettings.
+ *
+ * In such a case, ResetProfileSettingsOverlay's didShowPage(), which calls
+ * our dismiss() method, would be called before the native Handlers'
+ * InitalizePage() methods have an effect in the JS, which includes calling
+ * our show() method. This would mean that the banner would be first
+ * dismissed, then shown. We want to prevent this.
+ *
+ * @type {boolean}
+ * @private
+ */
+ hadBeenDismissed_: false,
+
+ /**
+ * Initializes the banner's event handlers.
+ */
+ initialize: function() {
+ $('reset-profile-settings-banner-close').onclick = function(event) {
+ chrome.send('metricsHandler:recordAction',
+ ['AutomaticReset_WebUIBanner_ManuallyClosed']);
+ ResetProfileSettingsBanner.dismiss();
+ };
+ $('reset-profile-settings-banner-activate').onclick = function(event) {
+ chrome.send('metricsHandler:recordAction',
+ ['AutomaticReset_WebUIBanner_ResetClicked']);
+ OptionsPage.navigateToPage('resetProfileSettings');
+ };
+ },
+
+ /**
+ * Called by the native code to show the banner if needed.
+ * @private
+ */
+ show_: function() {
+ if (!this.hadBeenDismissed_) {
+ chrome.send('metricsHandler:recordAction',
+ ['AutomaticReset_WebUIBanner_BannerShown']);
+ this.setVisibility_(true);
+ }
+ },
+
+ /**
+ * Called when the banner should be closed as a result of something taking
+ * place on the WebUI page, i.e. when its close button is pressed, or when
+ * the confirmation dialog for the profile settings reset feature is opened.
+ * @private
+ */
+ dismiss_: function() {
+ chrome.send('onDismissedResetProfileSettingsBanner');
+ this.hadBeenDismissed_ = true;
+ this.setVisibility_(false);
+ },
+
+ /**
+ * Sets whether or not the reset profile settings banner shall be visible.
+ * @param {boolean} show Whether or not to show the banner.
+ * @private
+ */
+ setVisibility_: function(show) {
+ $('reset-profile-settings-banner').hidden = !show;
+ }
+ };
+
+ // Forward public APIs to private implementations.
+ [
+ 'show',
+ 'dismiss',
+ ].forEach(function(name) {
+ ResetProfileSettingsBanner[name] = function() {
+ var instance = ResetProfileSettingsBanner.getInstance();
+ return instance[name + '_'].apply(instance, arguments);
+ };
+ });
+
+ // Export
+ return {
+ ResetProfileSettingsBanner: ResetProfileSettingsBanner
+ };
+});
diff --git a/chromium/chrome/browser/resources/options/reset_profile_settings_overlay.css b/chromium/chrome/browser/resources/options/reset_profile_settings_overlay.css
new file mode 100644
index 00000000000..3614e0ab740
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/reset_profile_settings_overlay.css
@@ -0,0 +1,62 @@
+/* Copyright 2013 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. */
+
+#reset-profile-settings-overlay {
+ width: 500px;
+}
+
+#reset-profile-settings-content-area {
+ -webkit-box-flex: 0;
+}
+
+#reset-profile-settings-throbber {
+ margin: 4px 10px;
+ vertical-align: middle;
+ visibility: hidden;
+}
+
+#feedback-bar {
+ -webkit-box-flex: 1;
+ -webkit-box-orient: vertical;
+}
+
+#feedback-template {
+ -webkit-box-flex: 1;
+ overflow-y: auto;
+}
+
+#feedback-template table {
+ table-layout: fixed;
+ width: 100%;
+}
+
+#feedback-template table td {
+ word-wrap: break-word;
+}
+
+#feedback-template .key {
+ padding-right: 5px;
+ text-align: right;
+ vertical-align: top;
+}
+
+#feedback-template .value {
+ color: #333;
+ text-align: left;
+ white-space: pre-line;
+}
+
+#expand-feedback {
+ background: center bottom no-repeat;
+ background-image: url('chrome://theme/IDR_QUESTION_MARK');
+ display: inline-block;
+ height: 14px;
+ opacity: 0.33;
+ vertical-align: text-top;
+ width: 14px;
+}
+
+#expand-feedback:hover {
+ opacity: 1;
+} \ No newline at end of file
diff --git a/chromium/chrome/browser/resources/options/reset_profile_settings_overlay.html b/chromium/chrome/browser/resources/options/reset_profile_settings_overlay.html
new file mode 100644
index 00000000000..e348c818d91
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/reset_profile_settings_overlay.html
@@ -0,0 +1,45 @@
+<div id="reset-profile-settings-overlay" class="page" hidden>
+ <div class="close-button"></div>
+ <h1 i18n-content="resetProfileSettingsOverlay"></h1>
+ <div id="reset-profile-settings-content-area" class="content-area">
+ <span i18n-content="resetProfileSettingsExplanation"></span>
+ </div>
+ <div class="action-area">
+ <div class="hbox stretch">
+ <a target="_blank" i18n-content="learnMore"
+ i18n-values="href:resetProfileSettingsLearnMoreUrl">
+ </a>
+ </div>
+ <div class="action-area-right">
+ <div id="reset-profile-settings-throbber" class="throbber"></div>
+ <div class="button-strip">
+ <button id="reset-profile-settings-dismiss" i18n-content="cancel">
+ </button>
+ <button id="reset-profile-settings-commit"
+ i18n-content="resetProfileSettingsCommit">
+ </button>
+ </div>
+ </div>
+ </div>
+ <div id="feedback-bar" class="gray-bottom-bar checkbox">
+ <span class="controlled-setting-with-label">
+ <input id="send-settings" type="checkbox" checked>
+ <span>
+ <label for="send-settings" i18n-content="resetProfileSettingsFeedback">
+ </label>
+ <div id='expand-feedback'></div>
+ </span>
+ </span>
+ <div id="feedback-template" hidden>
+ <div>
+ <table>
+ <tr jsselect="feedbackInfo">
+ <td class="key"><span jscontent="key">KEY</span></td>
+ <td class="value"><span jscontent="value">VALUE</span></td>
+ </tr>
+ </table>
+ </div>
+ </div>
+ </div>
+</div>
+<script src="chrome://resources/js/jstemplate_compiled.js"></script> \ No newline at end of file
diff --git a/chromium/chrome/browser/resources/options/reset_profile_settings_overlay.js b/chromium/chrome/browser/resources/options/reset_profile_settings_overlay.js
new file mode 100644
index 00000000000..ab4d830b714
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/reset_profile_settings_overlay.js
@@ -0,0 +1,96 @@
+// Copyright 2013 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 ResetProfileSettingsBanner = options.ResetProfileSettingsBanner;
+
+ /**
+ * ResetProfileSettingsOverlay class
+ * Encapsulated handling of the 'Reset Profile Settings' overlay page.
+ * @class
+ */
+ function ResetProfileSettingsOverlay() {
+ OptionsPage.call(
+ this, 'resetProfileSettings',
+ loadTimeData.getString('resetProfileSettingsOverlayTabTitle'),
+ 'reset-profile-settings-overlay');
+ }
+
+ cr.addSingletonGetter(ResetProfileSettingsOverlay);
+
+ ResetProfileSettingsOverlay.prototype = {
+ // Inherit ResetProfileSettingsOverlay from OptionsPage.
+ __proto__: OptionsPage.prototype,
+
+ /**
+ * Initialize the page.
+ */
+ initializePage: function() {
+ OptionsPage.prototype.initializePage.call(this);
+
+ $('reset-profile-settings-dismiss').onclick = function(event) {
+ ResetProfileSettingsOverlay.dismiss();
+ };
+ $('reset-profile-settings-commit').onclick = function(event) {
+ ResetProfileSettingsOverlay.setResettingState(true);
+ chrome.send('performResetProfileSettings',
+ [$('send-settings').checked]);
+ };
+ $('expand-feedback').onclick = function(event) {
+ var feedbackTemplate = $('feedback-template');
+ feedbackTemplate.hidden = !feedbackTemplate.hidden;
+ };
+ },
+
+ /** @override */
+ didShowPage: function() {
+ ResetProfileSettingsBanner.dismiss();
+ chrome.send('onShowResetProfileDialog');
+ },
+ };
+
+ /**
+ * Enables/disables UI elements after/while Chrome is performing a reset.
+ * @param {boolean} state If true, UI elements are disabled.
+ */
+ ResetProfileSettingsOverlay.setResettingState = function(state) {
+ $('reset-profile-settings-throbber').style.visibility =
+ state ? 'visible' : 'hidden';
+ $('reset-profile-settings-dismiss').disabled = state;
+ $('reset-profile-settings-commit').disabled = state;
+ };
+
+ /**
+ * Chrome callback to notify ResetProfileSettingsOverlay that the reset
+ * operation has terminated.
+ */
+ ResetProfileSettingsOverlay.doneResetting = function() {
+ // The delay gives the user some feedback that the resetting
+ // actually worked. Otherwise the dialog just vanishes instantly in most
+ // cases.
+ window.setTimeout(function() {
+ ResetProfileSettingsOverlay.dismiss();
+ }, 200);
+ };
+
+ /**
+ * Dismisses the overlay.
+ */
+ ResetProfileSettingsOverlay.dismiss = function() {
+ OptionsPage.closeOverlay();
+ ResetProfileSettingsOverlay.setResettingState(false);
+ };
+
+ ResetProfileSettingsOverlay.setFeedbackInfo = function(feedbackListData) {
+ var input = new JsEvalContext(feedbackListData);
+ var output = $('feedback-template');
+ jstProcess(input, output);
+ };
+
+ // Export
+ return {
+ ResetProfileSettingsOverlay: ResetProfileSettingsOverlay
+ };
+});
diff --git a/chromium/chrome/browser/resources/options/search_box.html b/chromium/chrome/browser/resources/options/search_box.html
new file mode 100644
index 00000000000..f27e1e7bc56
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/search_box.html
@@ -0,0 +1,10 @@
+<div id="searchBox" class="page">
+ <header >
+ <span id="browser-options-search-field-container"
+ class="search-field-container">
+ <input id="search-field" type="search"
+ i18n-values="placeholder:searchPlaceholder;
+ aria-label:searchPlaceholder" incremental>
+ </span>
+ </header>
+</div>
diff --git a/chromium/chrome/browser/resources/options/search_engine_manager.css b/chromium/chrome/browser/resources/options/search_engine_manager.css
new file mode 100644
index 00000000000..c19734da4e4
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/search_engine_manager.css
@@ -0,0 +1,81 @@
+/* Copyright (c) 2012 The Chromium Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file. */
+
+#search-engine-manager-page {
+ width: 700px;
+}
+
+.search-engine-list input {
+ -webkit-box-flex: 1;
+ display: -webkit-box;
+}
+
+.search-engine-list > div {
+ display: -webkit-box;
+}
+
+.search-engine-list .favicon {
+ background-position: center center;
+ background-repeat: no-repeat;
+ background-size: 16px;
+ height: 16px;
+ line-height: 16px;
+ padding: 0 7px;
+ width: 16px;
+}
+
+.search-engine-list .name-column {
+ -webkit-box-align: center;
+ -webkit-padding-end: 1ex;
+ box-sizing: border-box;
+ display: -webkit-box;
+ width: 30%;
+}
+
+.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: 44%;
+}
+
+.search-engine-list .keyword-column,
+.search-engine-list .url-column {
+ color: #666;
+}
+
+.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 {
+ -webkit-box-align: center;
+ display: -webkit-box;
+}
+
+.search-engine-list .url-column :first-child {
+ -webkit-box-flex: 1;
+}
+
+.search-engine-list .url-column .list-inline-button {
+ margin-top: 0;
+ padding: 1px 6px 2px 6px;
+}
+
+.search-engine-list > :not(:hover):not([editing]) .url-column
+ .list-inline-button {
+ display: none;
+}
+
+/* End temporary Make Default button styling */
diff --git a/chromium/chrome/browser/resources/options/search_engine_manager.html b/chromium/chrome/browser/resources/options/search_engine_manager.html
new file mode 100644
index 00000000000..0f9d8566d69
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/search_engine_manager.html
@@ -0,0 +1,25 @@
+<div id="search-engine-manager-page" class="page" hidden>
+ <div class="close-button"></div>
+ <h1 i18n-content="searchEngineManagerPage"></h1>
+ <div class="content-area">
+ <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>
+ </div>
+ <div class="action-area">
+ <div class="button-strip">
+ <button id="search-engine-manager-confirm" type="submit"
+ class="default-button" i18n-content="done">
+ </button>
+ </div>
+ </div>
+</div>
diff --git a/chromium/chrome/browser/resources/options/search_engine_manager.js b/chromium/chrome/browser/resources/options/search_engine_manager.js
new file mode 100644
index 00000000000..7d8df03c1ae
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/search_engine_manager.js
@@ -0,0 +1,131 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+ /** @const */ var OptionsPage = options.OptionsPage;
+ /** @const */ var ArrayDataModel = cr.ui.ArrayDataModel;
+
+ /**
+ * Encapsulated handling of search engine management page.
+ * @constructor
+ */
+ function SearchEngineManager() {
+ this.activeNavTab = null;
+ OptionsPage.call(this, 'searchEngines',
+ loadTimeData.getString('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_);
+
+ $('search-engine-manager-confirm').onclick = function() {
+ OptionsPage.closeOverlay();
+ };
+ },
+
+ /**
+ * 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 all lists; those without a matching modelIndex will ignore it.
+ SearchEngineManager.getInstance().defaultsList_.validationComplete(
+ validity, modelIndex);
+ SearchEngineManager.getInstance().othersList_.validationComplete(
+ validity, modelIndex);
+ SearchEngineManager.getInstance().extensionList_.validationComplete(
+ validity, modelIndex);
+ };
+
+ // Export
+ return {
+ SearchEngineManager: SearchEngineManager
+ };
+
+});
+
diff --git a/chromium/chrome/browser/resources/options/search_engine_manager_engine_list.js b/chromium/chrome/browser/resources/options/search_engine_manager_engine_list.js
new file mode 100644
index 00000000000..9bc12b9f8b6
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/search_engine_manager_engine_list.js
@@ -0,0 +1,343 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options.search_engines', function() {
+ /** @const */ var ControlledSettingIndicator =
+ options.ControlledSettingIndicator;
+ /** @const */ var InlineEditableItemList = options.InlineEditableItemList;
+ /** @const */ var InlineEditableItem = options.InlineEditableItem;
+ /** @const */ var 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,
+
+ /** @override */
+ 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';
+ if (!this.isPlaceholder) {
+ faviconDivEl.style.backgroundImage = imageset(
+ 'chrome://favicon/size/16@scalefactorx/iconurl/' + engine.iconURL);
+ }
+ 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);
+ // Extensions should not display a URL column.
+ if (!engine.isExtension) {
+ 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 =
+ 'custom-appearance list-inline-button';
+ makeDefaultButtonEl.textContent =
+ loadTimeData.getString('makeDefaultSearchEngineButton');
+ makeDefaultButtonEl.onclick = function(e) {
+ chrome.send('managerSetDefaultSearchEngine', [engine.modelIndex]);
+ };
+ makeDefaultButtonEl.onmousedown = function(e) {
+ // Don't select the row when clicking the button.
+ e.stopPropagation();
+ // Don't focus on the button.
+ e.preventDefault();
+ };
+ 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 (engine.isExtension)
+ this.nameField_.disabled = true;
+
+ if (this.isPlaceholder) {
+ this.nameField_.placeholder =
+ loadTimeData.getString('searchEngineTableNamePlaceholder');
+ this.keywordField_.placeholder =
+ loadTimeData.getString('searchEngineTableKeywordPlaceholder');
+ this.urlField_.placeholder =
+ loadTimeData.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;
+ this.querySelector('.row-delete-button').hidden = true;
+ var indicator = ControlledSettingIndicator();
+ indicator.setAttribute('setting', 'search-engine');
+ // Create a synthetic pref change event decorated as
+ // CoreOptionsHandler::CreateValueForPref() does.
+ var event = new Event(this.contentType);
+ if (engine.extension) {
+ event.value = { controlledBy: 'extension' };
+ // TODO(mad): add id, name, and icon once we solved the issue with the
+ // search engine manager in http://crbug.com/314507.
+ } else {
+ event.value = { controlledBy: 'policy' };
+ }
+ indicator.handlePrefChange(event);
+ this.appendChild(indicator);
+ }
+ },
+
+ /** @override */
+ get currentInputIsValid() {
+ return !this.waitingForValidation_ && this.currentlyValid_;
+ },
+
+ /** @override */
+ 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(
+ loadTimeData.getString('editSearchEngineInvalidTitleToolTip'));
+ }
+
+ if (validity.keyword) {
+ this.keywordField_.setCustomValidity('');
+ } else {
+ this.keywordField_.setCustomValidity(
+ loadTimeData.getString('editSearchEngineInvalidKeywordToolTip'));
+ }
+
+ if (validity.url) {
+ this.urlField_.setCustomValidity('');
+ } else {
+ this.urlField_.setCustomValidity(
+ loadTimeData.getString('editSearchEngineInvalidURLToolTip'));
+ }
+
+ this.currentlyValid_ = validity.name && validity.keyword && validity.url;
+ },
+ };
+
+ var SearchEngineList = cr.ui.define('list');
+
+ SearchEngineList.prototype = {
+ __proto__: InlineEditableItemList.prototype,
+
+ /** @override */
+ createItem: function(searchEngine) {
+ return new SearchEngineListItem(searchEngine);
+ },
+
+ /** @override */
+ 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/chromium/chrome/browser/resources/options/search_page.css b/chromium/chrome/browser/resources/options/search_page.css
new file mode 100644
index 00000000000..b634928989a
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/search_page.css
@@ -0,0 +1,74 @@
+/* Copyright (c) 2012 The Chromium Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file. */
+
+.search-hidden {
+ display: none !important;
+}
+
+.search-highlighted {
+ background-color: rgba(255, 240, 120, 0.9);
+}
+
+/* Container for the elements that make up the search bubble. */
+.search-bubble {
+ left: 0;
+ margin-top: 5px;
+ pointer-events: none;
+ position: absolute;
+ top: -1000px; /* Minor hack: position off-screen by default. */
+ /* Create a z-context for search-bubble-innards, its after and before. */
+ z-index: 1;
+}
+
+/* Contains the text content of the bubble. */
+.search-bubble-innards {
+ background: -webkit-linear-gradient(rgba(255, 248, 172, 0.9),
+ rgba(255, 243, 128, 0.9));
+ border-radius: 2px;
+ padding: 4px 10px;
+ text-align: center;
+ width: 100px;
+}
+
+/* Provides the border around the bubble (has to be behind ::after). */
+.search-bubble-innards::before {
+ border: 1px solid rgb(220, 198, 72);
+ border-radius: 2px;
+ bottom: -1px;
+ content: '';
+ left: -1px;
+ position: absolute;
+ right: -1px;
+ top: -1px;
+ z-index: -2;
+}
+
+/* Provides the arrow which points at the anchor element. */
+.search-bubble-innards::after {
+ -webkit-transform: rotate(45deg);
+ background:
+ -webkit-linear-gradient(-45deg, rgb(251, 255, 181),
+ rgb(255, 248, 172) 50%,
+ rgba(255, 248, 172, 0));
+ border: 1px solid rgb(220, 198, 72);
+ border-bottom-color: transparent;
+ border-right-color: transparent;
+ content: '';
+ height: 12px;
+ left: 53px;
+ position: absolute;
+ top: -7px;
+ width: 12px;
+ z-index: -1;
+}
+
+.search-bubble-wrapper {
+ position: relative;
+}
+
+/* #mainview is here to win specificity. :( */
+#mainview #searchPage.page,
+#mainview #searchBox.page {
+ padding-bottom: 0;
+}
diff --git a/chromium/chrome/browser/resources/options/search_page.html b/chromium/chrome/browser/resources/options/search_page.html
new file mode 100644
index 00000000000..073c505d9f7
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/search_page.html
@@ -0,0 +1,12 @@
+<div id="searchPage" class="page" hidden>
+ <header>
+ <h1 i18n-content="searchPage"></h1>
+ </header>
+ <div id="searchPageNoMatches" hidden>
+ <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/chromium/chrome/browser/resources/options/search_page.js b/chromium/chrome/browser/resources/options/search_page.js
new file mode 100644
index 00000000000..234399d7af2
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/search_page.js
@@ -0,0 +1,572 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+ /** @const */ var OptionsPage = options.OptionsPage;
+
+ /**
+ * Encapsulated handling of a search bubble.
+ * @constructor
+ */
+ function SearchBubble(text) {
+ var el = cr.doc.createElement('div');
+ SearchBubble.decorate(el);
+ el.content = text;
+ return el;
+ }
+
+ SearchBubble.decorate = function(el) {
+ el.__proto__ = SearchBubble.prototype;
+ el.decorate();
+ };
+
+ SearchBubble.prototype = {
+ __proto__: HTMLDivElement.prototype,
+
+ decorate: function() {
+ this.className = 'search-bubble';
+
+ this.innards_ = cr.doc.createElement('div');
+ this.innards_.className = 'search-bubble-innards';
+ this.appendChild(this.innards_);
+
+ // 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.
+ this.intervalId = setInterval(this.updatePosition.bind(this), 250);
+ },
+
+ /**
+ * Sets the text message in the bubble.
+ * @param {string} text The text the bubble will show.
+ */
+ set content(text) {
+ this.innards_.textContent = text;
+ },
+
+ /**
+ * 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',
+ loadTimeData.getString('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);
+
+ this.searchField = $('search-field');
+
+ // Handle search events. (No need to throttle, WebKit's search field
+ // will do that automatically.)
+ this.searchField.onsearch = function(e) {
+ this.setSearchText_(e.currentTarget.value);
+ }.bind(this);
+
+ // Install handler for key presses.
+ document.addEventListener('keydown',
+ this.keyDownEventHandler_.bind(this));
+ },
+
+ /** @override */
+ 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 =
+ decodeURIComponent(hash.slice(1).replace(/\+/g, ' '));
+ } else if (!this.searchField.value) {
+ // This should only happen if the user goes directly to
+ // chrome://settings-frame/search
+ OptionsPage.showDefaultPage();
+ return;
+ }
+
+ // Move 'advanced' sections into the main settings page to allow
+ // searching.
+ if (!this.advancedSections_) {
+ this.advancedSections_ =
+ $('advanced-settings-container').querySelectorAll('section');
+ for (var i = 0, section; section = this.advancedSections_[i]; i++)
+ $('settings').appendChild(section);
+ }
+ }
+
+ 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 (active) {
+ if (childDiv.tagName != 'SECTION')
+ 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);
+ this.searchField.focus();
+ } else {
+ // After hiding all page content, remove any search results.
+ this.unhighlightMatches_();
+ this.removeSearchBubbles_();
+
+ // Move 'advanced' sections back into their original container.
+ if (this.advancedSections_) {
+ for (var i = 0, section; section = this.advancedSections_[i]; i++)
+ $('advanced-settings-container').appendChild(section);
+ this.advancedSections_ = null;
+ }
+ }
+ },
+
+ /**
+ * 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);
+
+ // Set the hash on the current page, and the enclosing uber page
+ var hash = text ? '#' + encodeURIComponent(text) : '';
+ var path = text ? this.name : '';
+ window.location.hash = hash;
+ uber.invokeMethodOnParent('setPath', {path: path + hash});
+
+ // Toggle the search page if necessary.
+ if (text) {
+ if (!this.searchActive_)
+ OptionsPage.showPageByName(this.name, false);
+ } else {
+ if (this.searchActive_)
+ OptionsPage.showPageByName(OptionsPage.getDefaultPage().name, false);
+
+ this.insideSetSearchText_ = false;
+ return;
+ }
+
+ var foundMatches = false;
+
+ // Remove any prior search results.
+ this.unhighlightMatches_();
+ this.removeSearchBubbles_();
+
+ var pagesToSearch = this.getSearchablePages_();
+ for (var key in pagesToSearch) {
+ var page = pagesToSearch[key];
+ var elements = page.pageDiv.querySelectorAll('section');
+ for (var i = 0, node; node = elements[i]; i++) {
+ node.classList.add('search-hidden');
+ }
+ }
+
+ var bubbleControls = [];
+
+ // 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 for hilighting search terms.
+ var regExp = new RegExp('(' + searchText + ')', 'ig');
+
+ if (searchText.length) {
+ // Search all top-level sections for anchored string matches.
+ for (var key in pagesToSearch) {
+ var page = pagesToSearch[key];
+ var elements =
+ page.pageDiv.querySelectorAll('section');
+ for (var i = 0, node; node = elements[i]; i++) {
+ if (this.highlightMatches_(regExp, node)) {
+ node.classList.remove('search-hidden');
+ if (!node.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) {
+ var page = subPagesToSearch[key];
+ if (this.highlightMatches_(regExp, page.pageDiv)) {
+ this.revealAssociatedSections_(page);
+
+ bubbleControls =
+ bubbleControls.concat(this.getAssociatedControls_(page));
+
+ foundMatches = true;
+ }
+ }
+ }
+
+ // Configure elements on the search results page based on search results.
+ $('searchPageNoMatches').hidden = foundMatches;
+
+ // 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;
+ },
+
+ /**
+ * Reveal the associated section for |subpage|, as well as the one for its
+ * |parentPage|, and its |parentPage|'s |parentPage|, etc.
+ * @private
+ */
+ revealAssociatedSections_: function(subpage) {
+ for (var page = subpage; page; page = page.parentPage) {
+ var section = page.associatedSection;
+ if (section)
+ section.classList.remove('search-hidden');
+ }
+ },
+
+ /**
+ * @return {!Array.<HTMLElement>} all the associated controls for |subpage|,
+ * including |subpage.associatedControls| as well as any controls on parent
+ * pages that are indirectly necessary to get to the subpage.
+ * @private
+ */
+ getAssociatedControls_: function(subpage) {
+ var controls = [];
+ for (var page = subpage; page; page = page.parentPage) {
+ if (page.associatedControls)
+ controls = controls.concat(page.associatedControls);
+ }
+ return controls;
+ },
+
+ /**
+ * Wraps matches in spans.
+ * @param {RegExp} regExp The search query (in regexp form).
+ * @param {Element} element An HTML container element to recursively search
+ * within.
+ * @return {boolean} true if the element was changed.
+ * @private
+ */
+ highlightMatches_: function(regExp, 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) {
+ var textContent = node.nodeValue;
+ // Perform a search and replace on the text node value.
+ var split = textContent.split(regExp);
+ if (split.length > 1) {
+ found = true;
+ var nextNode = walker.nextNode();
+ var parentNode = node.parentNode;
+ // Use existing node as placeholder to determine where to insert the
+ // replacement content.
+ for (var i = 0; i < split.length; ++i) {
+ if (i % 2 == 0) {
+ parentNode.insertBefore(document.createTextNode(split[i]), node);
+ } else {
+ var span = document.createElement('span');
+ span.className = 'search-highlighted';
+ span.textContent = split[i];
+ parentNode.insertBefore(span, node);
+ }
+ }
+ // Remove old node.
+ parentNode.removeChild(node);
+ node = nextNode;
+ } 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.
+ * @return {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.
+ * @return {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 */ var ESCAPE_KEY_CODE = 27;
+ /** @const */ var 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/chromium/chrome/browser/resources/options/settings_dialog.js b/chromium/chrome/browser/resources/options/settings_dialog.js
new file mode 100644
index 00000000000..7391b8695d3
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/settings_dialog.js
@@ -0,0 +1,70 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @fileoverview Base class for dialogs that require saving preferences on
+ * confirm and resetting preference inputs on cancel.
+ */
+
+cr.define('options', function() {
+ /** @const */ var OptionsPage = options.OptionsPage;
+
+ /**
+ * Base class for settings dialogs.
+ * @constructor
+ * @param {string} name See OptionsPage constructor.
+ * @param {string} title See OptionsPage constructor.
+ * @param {string} pageDivName See OptionsPage constructor.
+ * @param {HTMLInputElement} okButton The confirmation button element.
+ * @param {HTMLInputElement} cancelButton The cancellation button element.
+ * @extends {OptionsPage}
+ */
+ function SettingsDialog(name, title, pageDivName, okButton, cancelButton) {
+ OptionsPage.call(this, name, title, pageDivName);
+ this.okButton = okButton;
+ this.cancelButton = cancelButton;
+ }
+
+ SettingsDialog.prototype = {
+ __proto__: OptionsPage.prototype,
+
+ /** @override */
+ initializePage: function() {
+ this.okButton.onclick = this.handleConfirm.bind(this);
+ this.cancelButton.onclick = this.handleCancel.bind(this);
+ },
+
+ /**
+ * Handles the confirm button by saving the dialog preferences.
+ */
+ handleConfirm: function() {
+ OptionsPage.closeOverlay();
+
+ var prefs = Preferences.getInstance();
+ var els = this.pageDiv.querySelectorAll('[dialog-pref]');
+ for (var i = 0; i < els.length; i++) {
+ if (els[i].pref)
+ prefs.commitPref(els[i].pref, els[i].metric);
+ }
+ },
+
+ /**
+ * Handles the cancel button by closing the overlay.
+ */
+ handleCancel: function() {
+ OptionsPage.closeOverlay();
+
+ var prefs = Preferences.getInstance();
+ var els = this.pageDiv.querySelectorAll('[dialog-pref]');
+ for (var i = 0; i < els.length; i++) {
+ if (els[i].pref)
+ prefs.rollbackPref(els[i].pref);
+ }
+ },
+ };
+
+ return {
+ SettingsDialog: SettingsDialog
+ };
+});
diff --git a/chromium/chrome/browser/resources/options/spelling_confirm_overlay.css b/chromium/chrome/browser/resources/options/spelling_confirm_overlay.css
new file mode 100644
index 00000000000..434c26c3f0c
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/spelling_confirm_overlay.css
@@ -0,0 +1,8 @@
+/* Copyright (c) 2012 The Chromium Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#spelling-confirm-overlay {
+ width: 500px;
+}
diff --git a/chromium/chrome/browser/resources/options/spelling_confirm_overlay.html b/chromium/chrome/browser/resources/options/spelling_confirm_overlay.html
new file mode 100644
index 00000000000..fb239994f08
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/spelling_confirm_overlay.html
@@ -0,0 +1,19 @@
+<div id="spelling-confirm-overlay" class="page" hidden>
+ <div class="close-button"></div>
+ <h1 i18n-content="spellingConfirmOverlay"></h1>
+ <div class="content-area">
+ <span id="spelling-confirm-text" i18n-content="spellingConfirmMessage">
+ </span>
+ <a id="spelling-confirm-learn-more" target="_blank" i18n-content="learnMore"
+ i18n-values="href:privacyLearnMoreURL"></a>
+ </div>
+ <div class="action-area">
+ <div class="button-strip">
+ <button id="spelling-confirm-cancel" i18n-content="spellingConfirmDisable"
+ class="cancel-button"></button>
+ <button id="spelling-confirm-ok" class="default-button"
+ i18n-content="spellingConfirmEnable">
+ </button>
+ </div>
+ </div>
+</div>
diff --git a/chromium/chrome/browser/resources/options/startup_overlay.css b/chromium/chrome/browser/resources/options/startup_overlay.css
new file mode 100644
index 00000000000..dc74debad7b
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/startup_overlay.css
@@ -0,0 +1,41 @@
+/* Copyright (c) 2012 The Chromium Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file. */
+
+#startup-overlay {
+ min-width: 500px;
+}
+
+#startupPagesList {
+ margin-bottom: 20px;
+ 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: 2px solid hsl(214, 91%, 65%);
+ border-bottom-color: transparent;
+ border-radius: 0;
+ border-top-color: transparent;
+ box-sizing: border-box;
+ display: none;
+ height: 6px;
+ overflow: hidden;
+ pointer-events: none;
+ position: fixed;
+ z-index: 10;
+}
diff --git a/chromium/chrome/browser/resources/options/startup_overlay.html b/chromium/chrome/browser/resources/options/startup_overlay.html
new file mode 100644
index 00000000000..645bb7ce3bc
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/startup_overlay.html
@@ -0,0 +1,25 @@
+<div id="startup-overlay" class="page" hidden>
+ <div class="close-button"></div>
+ <h1 i18n-content="startupPagesOverlay"></h1>
+ <!-- This <input> element is always hidden. It needs to be here so that
+ its 'controlled-by' attribute will get set when the urls preference is
+ managed by a policy, so that the managed prefs bar will show up.
+ -->
+ <input type="text" pref="session.startup_urls" hidden>
+ <div class="content-area">
+ <list id="startupPagesList"></list>
+ </div>
+ <div class="action-area">
+ <span class="hbox stretch">
+ <button id="startupUseCurrentButton"
+ i18n-content="startupUseCurrent"></button>
+ </span>
+ <div class="button-strip">
+ <button id="startup-overlay-cancel" i18n-content="cancel"></button>
+ <button id="startup-overlay-confirm" class="default-button"
+ i18n-content="ok">
+ </button>
+ </div>
+ </div>
+ <div id="startupPagesListDropmarker"></div>
+</div>
diff --git a/chromium/chrome/browser/resources/options/startup_overlay.js b/chromium/chrome/browser/resources/options/startup_overlay.js
new file mode 100644
index 00000000000..d63b49d1027
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/startup_overlay.js
@@ -0,0 +1,175 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+ /** @const */ var ArrayDataModel = cr.ui.ArrayDataModel;
+ /** @const */ var OptionsPage = options.OptionsPage;
+ /** @const */ var SettingsDialog = options.SettingsDialog;
+
+ /**
+ * StartupOverlay class
+ * Encapsulated handling of the 'Set Startup pages' overlay page.
+ * @constructor
+ * @class
+ */
+ function StartupOverlay() {
+ SettingsDialog.call(this, 'startup',
+ loadTimeData.getString('startupPagesOverlayTabTitle'),
+ 'startup-overlay',
+ $('startup-overlay-confirm'),
+ $('startup-overlay-cancel'));
+ };
+
+ cr.addSingletonGetter(StartupOverlay);
+
+ StartupOverlay.prototype = {
+ __proto__: SettingsDialog.prototype,
+
+ /**
+ * An autocomplete list that can be attached to a text field during editing.
+ * @type {HTMLElement}
+ * @private
+ */
+ autocompleteList_: null,
+
+ startup_pages_pref_: {
+ 'name': 'session.startup_urls',
+ 'disabled': false
+ },
+
+ /**
+ * Initialize the page.
+ */
+ initializePage: function() {
+ SettingsDialog.prototype.initializePage.call(this);
+
+ var self = this;
+
+ var startupPagesList = $('startupPagesList');
+ options.browser_options.StartupPageList.decorate(startupPagesList);
+ startupPagesList.autoExpands = true;
+
+ $('startupUseCurrentButton').onclick = function(event) {
+ chrome.send('setStartupPagesToCurrentPages');
+ };
+
+ Preferences.getInstance().addEventListener(
+ this.startup_pages_pref_.name,
+ this.handleStartupPageListChange_.bind(this));
+
+ var suggestionList = new cr.ui.AutocompleteList();
+ suggestionList.autoExpands = true;
+ suggestionList.requestSuggestions =
+ this.requestAutocompleteSuggestions_.bind(this);
+ $('startup-overlay').appendChild(suggestionList);
+ this.autocompleteList_ = suggestionList;
+ startupPagesList.autocompleteList = suggestionList;
+ },
+
+ /** @override */
+ handleConfirm: function() {
+ SettingsDialog.prototype.handleConfirm.call(this);
+ chrome.send('commitStartupPrefChanges');
+ // Set the startup behavior to "open specific set of pages" so that the
+ // pages the user selected actually get opened on startup.
+ Preferences.setIntegerPref('session.restore_on_startup', 4, true);
+ },
+
+ /** @override */
+ handleCancel: function() {
+ SettingsDialog.prototype.handleCancel.call(this);
+ chrome.send('cancelStartupPrefChanges');
+ },
+
+ /**
+ * Sets the enabled state of the custom startup page list
+ * @param {boolean} disable True to disable, false to enable
+ */
+ setControlsDisabled: function(disable) {
+ 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;
+ },
+
+ /**
+ * Enables or disables the the custom startup page list controls
+ * based on the whether the 'pages to restore on startup' pref is enabled.
+ */
+ updateControlStates: function() {
+ this.setControlsDisabled(
+ this.startup_pages_pref_.disabled);
+ },
+
+ /**
+ * Handles change events of the preference
+ * 'session.startup_urls'.
+ * @param {event} preference changed event.
+ * @private
+ */
+ handleStartupPageListChange_: function(event) {
+ this.startup_pages_pref_.disabled = event.value.disabled;
+ this.updateControlStates();
+ },
+
+ /**
+ * 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;
+ },
+
+ /**
+ * 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('requestAutocompleteSuggestionsForStartupPages', [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;
+ },
+ };
+
+ // Forward public APIs to private implementations.
+ [
+ 'updateStartupPages',
+ 'updateAutocompleteSuggestions',
+ ].forEach(function(name) {
+ StartupOverlay[name] = function() {
+ var instance = StartupOverlay.getInstance();
+ return instance[name + '_'].apply(instance, arguments);
+ };
+ });
+
+ // Export
+ return {
+ StartupOverlay: StartupOverlay
+ };
+});
diff --git a/chromium/chrome/browser/resources/options/startup_section.html b/chromium/chrome/browser/resources/options/startup_section.html
new file mode 100644
index 00000000000..2eaea81c0bf
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/startup_section.html
@@ -0,0 +1,53 @@
+<section id="startup-section" guest-visibility="hidden">
+ <h3 i18n-content="sectionTitleStartup"></h3>
+ <div>
+ <div class="radio">
+ <span class="controlled-setting-with-label">
+ <input id="startup-newtab" type="radio" name="startup" value="5"
+ pref="session.restore_on_startup"
+ metric="Options_Startup_NewTab">
+ <span>
+ <label for="startup-newtab" i18n-content="startupShowNewTab"></label>
+ <span class="controlled-setting-indicator"
+ pref="session.restore_on_startup" value="5">
+ </span>
+ </span>
+ </span>
+ </div>
+ <div class="radio">
+ <span class="controlled-setting-with-label">
+ <input id="startup-restore-session" type="radio" name="startup"
+ value="1" pref="session.restore_on_startup"
+ metric="Options_Startup_LastSession">
+ <span>
+ <label for="startup-restore-session"
+ i18n-content="startupRestoreLastSession">
+ </label>
+ <span class="controlled-setting-indicator"
+ pref="session.restore_on_startup" value="1">
+ </span>
+ </span>
+ </span>
+ </div>
+ <div class="radio">
+ <span class="controlled-setting-with-label">
+ <input id="startup-show-pages" type="radio" name="startup"
+ pref="session.restore_on_startup"
+ value="4" metric="Options_Startup_Custom">
+ <span>
+ <label for="startup-show-pages" i18n-content="startupShowPages">
+ </label>
+ <span class="controlled-setting-indicator"
+ pref="session.restore_on_startup" value="4">
+ </span>
+ <button id="startup-set-pages" class="link-button"
+ i18n-content="startupSetPages">
+ </button>
+ <span class="controlled-setting-indicator"
+ pref="session.startup_urls">
+ </span>
+ </span>
+ </label>
+ </div>
+ </div>
+</section>
diff --git a/chromium/chrome/browser/resources/options/subpages_tab_controls.css b/chromium/chrome/browser/resources/options/subpages_tab_controls.css
new file mode 100644
index 00000000000..c49c07715fe
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/subpages_tab_controls.css
@@ -0,0 +1,74 @@
+/* Copyright (c) 2012 The Chromium Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file. */
+
+.subpages-nav-tabs .tab {
+ padding: 4px 8px;
+ position: relative;
+}
+
+.subpages-nav-tabs .active-tab {
+ -webkit-box-shadow: 8px -8px 12px -6px rgb(240, 240, 240);
+ background: white;
+ border: 1px solid #ddd;
+ border-bottom: 2px solid white;
+ border-top-left-radius: 3px;
+ border-top-right-radius: 3px;
+ position: relative;
+}
+
+/* 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,
+.subpages-nav-tabs .tab-label:hover {
+ font-weight: bold;
+}
+
+.subpages-nav-tabs .tab-label {
+ left: 9px;
+ position: absolute;
+ top: 5px;
+}
+
+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 {
+ background-image: -webkit-gradient(
+ linear,
+ left top,
+ left bottom,
+ color-stop(0, rgb(255,255,255)),
+ color-stop(0.6, rgb(255,255,255)),
+ color-stop(0.8, rgb(250, 250, 250)),
+ color-stop(1.0, rgb(242,242,242))
+ );
+ border-bottom: 1px solid #ddd;
+ padding: 4px 20px;
+}
+
+.subpages-tab-contents {
+ -webkit-padding-start: 10px;
+ display: none;
+ padding-top: 15px;
+}
+
+.active-tab-contents {
+ display: block;
+}
diff --git a/chromium/chrome/browser/resources/options/sync_section.html b/chromium/chrome/browser/resources/options/sync_section.html
new file mode 100644
index 00000000000..fdf89885921
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/sync_section.html
@@ -0,0 +1,77 @@
+<if expr="not pp_ifdef('chromeos')">
+<section id="sync-section">
+ <h3 i18n-content="sectionTitleSync"></h3>
+</if>
+<if expr="pp_ifdef('chromeos')">
+<div id="sync-section">
+</if>
+
+ <div id="sync-overview" class="settings-row" hidden>
+ <p i18n-content="syncOverview"></p>
+ <a i18n-values="href:syncLearnMoreURL" i18n-content="learnMore"
+ target="_blank"></a>
+ </div>
+
+<if expr="pp_ifdef('chromeos')">
+ <div id="account-picture-wrapper">
+ <div id="account-picture-control">
+ <input type="image" id="account-picture" tabindex="0"
+ alt="" i18n-values="aria-label:changePicture">
+ <div id="change-picture-caption" i18n-content="changePicture"></div>
+ </div>
+ <span id="account-picture-indicator" class="controlled-setting-indicator">
+ </span>
+ </div>
+ <div id="sync-general">
+</if> <!-- pp_ifdef('chromeos') -->
+
+ <div id="sync-status" class="settings-row" hidden>
+ <span id="sync-status-text"></span>
+ <button id="sync-action-link" class="link-button"></button>
+ </div>
+
+<if expr="pp_ifdef('chromeos')">
+ <div class="checkbox">
+ <span class="controlled-setting-with-label">
+ <input id="enable-screen-lock" type="checkbox"
+ pref="settings.enable_screen_lock">
+ <span>
+ <label for="enable-screen-lock" i18n-content="enableScreenlock">
+ </label>
+ <span class="controlled-setting-indicator"
+ pref="settings.enable_screen_lock">
+ </span>
+ </span>
+ </span>
+ </div>
+ </div>
+</if> <!-- pp_ifdef('chromeos') -->
+
+ <div id="sync-buttons" class="settings-row">
+ <button id="start-stop-sync" hidden></button>
+ <span id="start-stop-sync-indicator"
+ class="controlled-setting-indicator" hidden>
+ </span>
+ <button id="customize-sync" i18n-content="customizeSync"
+ pref="sync.managed" hidden>
+ </button>
+<if expr="pp_ifdef('chromeos')">
+ <button id="manage-accounts-button"
+ i18n-content="manageAccountsButtonTitle">
+ </button>
+</if> <!-- pp_ifdef('chromeos') -->
+ <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>
+
+<if expr="not pp_ifdef('chromeos')">
+</section>
+</if>
+<if expr="pp_ifdef('chromeos')">
+</div>
+</if>
diff --git a/chromium/chrome/browser/resources/options/yellow_gear.png b/chromium/chrome/browser/resources/options/yellow_gear.png
new file mode 100644
index 00000000000..4134d3d2ad9
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/yellow_gear.png
Binary files differ