summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorjhawkins@chromium.org <jhawkins@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-12-14 18:47:26 +0000
committerjhawkins@chromium.org <jhawkins@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-12-14 18:47:26 +0000
commit846c3eceac6eb556f77218c50ab16244ebbb9146 (patch)
tree259ee74cab54e263c15b06eec22dcca2d9abf2a8
parent34608cfed25d4ed2275af1ceb7ff978d0a48ef17 (diff)
downloadchromium_src-846c3eceac6eb556f77218c50ab16244ebbb9146.zip
chromium_src-846c3eceac6eb556f77218c50ab16244ebbb9146.tar.gz
chromium_src-846c3eceac6eb556f77218c50ab16244ebbb9146.tar.bz2
Options2: Pull the trigger.
Options2 is a copy of the resources for and implementation of chrome://settings that will be pared down significantly for UberPage (see bug). BUG=100885 TEST=none R=csilv Review URL: http://codereview.chromium.org/8895023 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@114462 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--base/path_service.cc6
-rw-r--r--chrome/browser/resources/options2/OWNERS4
-rw-r--r--chrome/browser/resources/options2/about_page.css34
-rw-r--r--chrome/browser/resources/options2/about_page.html119
-rw-r--r--chrome/browser/resources/options2/about_page.js220
-rw-r--r--chrome/browser/resources/options2/advanced_options.css33
-rw-r--r--chrome/browser/resources/options2/advanced_options.html232
-rw-r--r--chrome/browser/resources/options2/advanced_options.js266
-rw-r--r--chrome/browser/resources/options2/alert_overlay.css3
-rw-r--r--chrome/browser/resources/options2/alert_overlay.html12
-rw-r--r--chrome/browser/resources/options2/alert_overlay.js144
-rw-r--r--chrome/browser/resources/options2/autocomplete_list.js239
-rw-r--r--chrome/browser/resources/options2/autofill_edit_address_overlay.html99
-rw-r--r--chrome/browser/resources/options2/autofill_edit_address_overlay.js325
-rw-r--r--chrome/browser/resources/options2/autofill_edit_creditcard_overlay.html30
-rw-r--r--chrome/browser/resources/options2/autofill_edit_creditcard_overlay.js205
-rw-r--r--chrome/browser/resources/options2/autofill_options.css37
-rw-r--r--chrome/browser/resources/options2/autofill_options.html38
-rw-r--r--chrome/browser/resources/options2/autofill_options.js230
-rw-r--r--chrome/browser/resources/options2/autofill_options_list.js506
-rw-r--r--chrome/browser/resources/options2/autofill_overlay.css95
-rw-r--r--chrome/browser/resources/options2/browser_options.html128
-rw-r--r--chrome/browser/resources/options2/browser_options.js366
-rw-r--r--chrome/browser/resources/options2/browser_options_page.css95
-rw-r--r--chrome/browser/resources/options2/browser_options_startup_page_list.js310
-rw-r--r--chrome/browser/resources/options2/certificate_backup_overlay.html38
-rw-r--r--chrome/browser/resources/options2/certificate_backup_overlay.js116
-rw-r--r--chrome/browser/resources/options2/certificate_edit_ca_trust_overlay.html33
-rw-r--r--chrome/browser/resources/options2/certificate_edit_ca_trust_overlay.js164
-rw-r--r--chrome/browser/resources/options2/certificate_import_error_overlay.html13
-rw-r--r--chrome/browser/resources/options2/certificate_import_error_overlay.js68
-rw-r--r--chrome/browser/resources/options2/certificate_manager.css15
-rw-r--r--chrome/browser/resources/options2/certificate_manager.html129
-rw-r--r--chrome/browser/resources/options2/certificate_manager.js253
-rw-r--r--chrome/browser/resources/options2/certificate_restore_overlay.html17
-rw-r--r--chrome/browser/resources/options2/certificate_restore_overlay.js97
-rw-r--r--chrome/browser/resources/options2/certificate_tree.css8
-rw-r--r--chrome/browser/resources/options2/certificate_tree.js128
-rw-r--r--chrome/browser/resources/options2/chromeos/accounts_options.html63
-rw-r--r--chrome/browser/resources/options2/chromeos/accounts_options.js165
-rw-r--r--chrome/browser/resources/options2/chromeos/accounts_options_page.css87
-rw-r--r--chrome/browser/resources/options2/chromeos/accounts_user_list.js194
-rw-r--r--chrome/browser/resources/options2/chromeos/accounts_user_name_edit.js121
-rw-r--r--chrome/browser/resources/options2/chromeos/bluetooth_device_list.js232
-rw-r--r--chrome/browser/resources/options2/chromeos/bluetooth_list_element.js387
-rw-r--r--chrome/browser/resources/options2/chromeos/cellular_plan_element.js132
-rw-r--r--chrome/browser/resources/options2/chromeos/change_picture_options.css32
-rw-r--r--chrome/browser/resources/options2/chromeos/change_picture_options.html5
-rw-r--r--chrome/browser/resources/options2/chromeos/change_picture_options.js267
-rw-r--r--chrome/browser/resources/options2/chromeos/internet_detail.html364
-rw-r--r--chrome/browser/resources/options2/chromeos/internet_detail.js204
-rw-r--r--chrome/browser/resources/options2/chromeos/internet_detail_ip_config_list.js99
-rw-r--r--chrome/browser/resources/options2/chromeos/internet_network_element.js316
-rw-r--r--chrome/browser/resources/options2/chromeos/internet_options.html59
-rw-r--r--chrome/browser/resources/options2/chromeos/internet_options.js709
-rw-r--r--chrome/browser/resources/options2/chromeos/internet_options_page.css171
-rw-r--r--chrome/browser/resources/options2/chromeos/language_chewing_options.html141
-rw-r--r--chrome/browser/resources/options2/chromeos/language_customize_modifier_keys_overlay.html37
-rw-r--r--chrome/browser/resources/options2/chromeos/language_hangul_options.html18
-rw-r--r--chrome/browser/resources/options2/chromeos/language_mozc_options.html135
-rw-r--r--chrome/browser/resources/options2/chromeos/language_pinyin_options.html148
-rw-r--r--chrome/browser/resources/options2/chromeos/proxy.css10
-rw-r--r--chrome/browser/resources/options2/chromeos/proxy.html141
-rw-r--r--chrome/browser/resources/options2/chromeos/proxy_options.js244
-rw-r--r--chrome/browser/resources/options2/chromeos/proxy_rules_list.js139
-rw-r--r--chrome/browser/resources/options2/chromeos/system_options.html125
-rw-r--r--chrome/browser/resources/options2/chromeos/system_options.js202
-rw-r--r--chrome/browser/resources/options2/chromeos/system_options_page.css167
-rw-r--r--chrome/browser/resources/options2/chromeos/virtual_keyboard.css24
-rw-r--r--chrome/browser/resources/options2/chromeos/virtual_keyboard.html13
-rw-r--r--chrome/browser/resources/options2/chromeos/virtual_keyboard.js92
-rw-r--r--chrome/browser/resources/options2/chromeos/virtual_keyboard_list.js146
-rw-r--r--chrome/browser/resources/options2/clear_browser_data_overlay.css24
-rw-r--r--chrome/browser/resources/options2/clear_browser_data_overlay.html73
-rw-r--r--chrome/browser/resources/options2/clear_browser_data_overlay.js110
-rw-r--r--chrome/browser/resources/options2/content_settings.css74
-rw-r--r--chrome/browser/resources/options2/content_settings.html279
-rw-r--r--chrome/browser/resources/options2/content_settings.js154
-rw-r--r--chrome/browser/resources/options2/content_settings_exceptions_area.html73
-rw-r--r--chrome/browser/resources/options2/content_settings_exceptions_area.js552
-rw-r--r--chrome/browser/resources/options2/content_settings_ui.js67
-rw-r--r--chrome/browser/resources/options2/controlled_setting.js138
-rw-r--r--chrome/browser/resources/options2/cookies_list.js853
-rw-r--r--chrome/browser/resources/options2/cookies_view.css182
-rw-r--r--chrome/browser/resources/options2/cookies_view.html15
-rw-r--r--chrome/browser/resources/options2/cookies_view.js140
-rw-r--r--chrome/browser/resources/options2/deletable_item_list.js185
-rw-r--r--chrome/browser/resources/options2/extension_list.js764
-rw-r--r--chrome/browser/resources/options2/extension_settings.css274
-rw-r--r--chrome/browser/resources/options2/extension_settings.html40
-rw-r--r--chrome/browser/resources/options2/extension_settings.js184
-rw-r--r--chrome/browser/resources/options2/font_settings.css41
-rw-r--r--chrome/browser/resources/options2/font_settings.html82
-rw-r--r--chrome/browser/resources/options2/font_settings.js234
-rw-r--r--chrome/browser/resources/options2/handler_options.css57
-rw-r--r--chrome/browser/resources/options2/handler_options.html28
-rw-r--r--chrome/browser/resources/options2/handler_options.js77
-rw-r--r--chrome/browser/resources/options2/handler_options_list.js229
-rw-r--r--chrome/browser/resources/options2/import_data_overlay.css23
-rw-r--r--chrome/browser/resources/options2/import_data_overlay.html70
-rw-r--r--chrome/browser/resources/options2/import_data_overlay.js222
-rw-r--r--chrome/browser/resources/options2/inline_editable_list.js414
-rw-r--r--chrome/browser/resources/options2/instant_confirm_overlay.html16
-rw-r--r--chrome/browser/resources/options2/instant_confirm_overlay.js39
-rw-r--r--chrome/browser/resources/options2/intents_list.js707
-rw-r--r--chrome/browser/resources/options2/intents_view.css181
-rw-r--r--chrome/browser/resources/options2/intents_view.html12
-rw-r--r--chrome/browser/resources/options2/intents_view.js83
-rw-r--r--chrome/browser/resources/options2/language_add_language_overlay.html28
-rw-r--r--chrome/browser/resources/options2/language_add_language_overlay.js73
-rw-r--r--chrome/browser/resources/options2/language_list.js487
-rw-r--r--chrome/browser/resources/options2/language_options.css239
-rw-r--r--chrome/browser/resources/options2/language_options.html78
-rw-r--r--chrome/browser/resources/options2/language_options.js815
-rw-r--r--chrome/browser/resources/options2/manage_profile_overlay.css88
-rw-r--r--chrome/browser/resources/options2/manage_profile_overlay.html38
-rw-r--r--chrome/browser/resources/options2/manage_profile_overlay.js265
-rw-r--r--chrome/browser/resources/options2/options.html188
-rw-r--r--chrome/browser/resources/options2/options.js247
-rw-r--r--chrome/browser/resources/options2/options_bundle.js94
-rw-r--r--chrome/browser/resources/options2/options_page.css736
-rw-r--r--chrome/browser/resources/options2/options_page.js1076
-rw-r--r--chrome/browser/resources/options2/pack_extension_overlay.css18
-rw-r--r--chrome/browser/resources/options2/pack_extension_overlay.html28
-rw-r--r--chrome/browser/resources/options2/pack_extension_overlay.js90
-rw-r--r--chrome/browser/resources/options2/password_manager.css31
-rw-r--r--chrome/browser/resources/options2/password_manager.html28
-rw-r--r--chrome/browser/resources/options2/password_manager.js228
-rw-r--r--chrome/browser/resources/options2/password_manager_list.css69
-rw-r--r--chrome/browser/resources/options2/password_manager_list.js283
-rw-r--r--chrome/browser/resources/options2/personal_options.css66
-rw-r--r--chrome/browser/resources/options2/personal_options.html162
-rw-r--r--chrome/browser/resources/options2/personal_options.js372
-rw-r--r--chrome/browser/resources/options2/personal_options_profile_list.js105
-rw-r--r--chrome/browser/resources/options2/pref_ui.js723
-rw-r--r--chrome/browser/resources/options2/preferences.js185
-rw-r--r--chrome/browser/resources/options2/profiles_icon_grid.js68
-rw-r--r--chrome/browser/resources/options2/search_engine_manager.css64
-rw-r--r--chrome/browser/resources/options2/search_engine_manager.html19
-rw-r--r--chrome/browser/resources/options2/search_engine_manager.js125
-rw-r--r--chrome/browser/resources/options2/search_engine_manager_engine_list.js316
-rw-r--r--chrome/browser/resources/options2/search_page.css36
-rw-r--r--chrome/browser/resources/options2/search_page.html10
-rw-r--r--chrome/browser/resources/options2/search_page.js598
-rw-r--r--chrome/browser/resources/options2/subpages_tab_controls.css66
-rw-r--r--chrome/browser/resources/options2_resources.grd15
-rw-r--r--chrome/browser/resources/uber/uber.html2
-rw-r--r--chrome/browser/ui/browser_navigator.cc1
-rw-r--r--chrome/browser/ui/webui/chrome_web_ui_factory.cc6
-rw-r--r--chrome/browser/ui/webui/options/chromeos/bluetooth_options_handler.cc2
-rw-r--r--chrome/browser/ui/webui/options/options_ui.h3
-rw-r--r--chrome/browser/ui/webui/options2/OWNERS4
-rw-r--r--chrome/browser/ui/webui/options2/advanced_options_browsertest.js63
-rw-r--r--chrome/browser/ui/webui/options2/advanced_options_handler.cc643
-rw-r--r--chrome/browser/ui/webui/options2/advanced_options_handler.h182
-rw-r--r--chrome/browser/ui/webui/options2/advanced_options_utils.h25
-rw-r--r--chrome/browser/ui/webui/options2/advanced_options_utils_mac.mm41
-rw-r--r--chrome/browser/ui/webui/options2/advanced_options_utils_win.cc68
-rw-r--r--chrome/browser/ui/webui/options2/advanced_options_utils_x11.cc140
-rw-r--r--chrome/browser/ui/webui/options2/autofill_options_browsertest.js24
-rw-r--r--chrome/browser/ui/webui/options2/autofill_options_handler.cc614
-rw-r--r--chrome/browser/ui/webui/options2/autofill_options_handler.h89
-rw-r--r--chrome/browser/ui/webui/options2/browser_options_browsertest.js24
-rw-r--r--chrome/browser/ui/webui/options2/browser_options_handler.cc496
-rw-r--r--chrome/browser/ui/webui/options2/browser_options_handler.h136
-rw-r--r--chrome/browser/ui/webui/options2/certificate_manager_browsertest.js33
-rw-r--r--chrome/browser/ui/webui/options2/certificate_manager_handler.cc1046
-rw-r--r--chrome/browser/ui/webui/options2/certificate_manager_handler.h171
-rw-r--r--chrome/browser/ui/webui/options2/chromeos/about_page_handler.cc419
-rw-r--r--chrome/browser/ui/webui/options2/chromeos/about_page_handler.h74
-rw-r--r--chrome/browser/ui/webui/options2/chromeos/accounts_options_handler.cc132
-rw-r--r--chrome/browser/ui/webui/options2/chromeos/accounts_options_handler.h40
-rw-r--r--chrome/browser/ui/webui/options2/chromeos/bluetooth_options_handler.cc411
-rw-r--r--chrome/browser/ui/webui/options2/chromeos/bluetooth_options_handler.h138
-rw-r--r--chrome/browser/ui/webui/options2/chromeos/change_picture_options_handler.cc344
-rw-r--r--chrome/browser/ui/webui/options2/chromeos/change_picture_options_handler.h113
-rw-r--r--chrome/browser/ui/webui/options2/chromeos/core_chromeos_options_handler.cc268
-rw-r--r--chrome/browser/ui/webui/options2/chromeos/core_chromeos_options_handler.h53
-rw-r--r--chrome/browser/ui/webui/options2/chromeos/cros_language_options_handler.cc242
-rw-r--r--chrome/browser/ui/webui/options2/chromeos/cros_language_options_handler.h73
-rw-r--r--chrome/browser/ui/webui/options2/chromeos/guest_mode_options_ui_uitest.cc43
-rw-r--r--chrome/browser/ui/webui/options2/chromeos/internet_options_handler.cc1372
-rw-r--r--chrome/browser/ui/webui/options2/chromeos/internet_options_handler.h148
-rw-r--r--chrome/browser/ui/webui/options2/chromeos/language_chewing_handler.cc103
-rw-r--r--chrome/browser/ui/webui/options2/chromeos/language_chewing_handler.h35
-rw-r--r--chrome/browser/ui/webui/options2/chromeos/language_customize_modifier_keys_handler.cc79
-rw-r--r--chrome/browser/ui/webui/options2/chromeos/language_customize_modifier_keys_handler.h30
-rw-r--r--chrome/browser/ui/webui/options2/chromeos/language_hangul_handler.cc47
-rw-r--r--chrome/browser/ui/webui/options2/chromeos/language_hangul_handler.h38
-rw-r--r--chrome/browser/ui/webui/options2/chromeos/language_mozc_handler.cc70
-rw-r--r--chrome/browser/ui/webui/options2/chromeos/language_mozc_handler.h34
-rw-r--r--chrome/browser/ui/webui/options2/chromeos/language_options_util.cc18
-rw-r--r--chrome/browser/ui/webui/options2/chromeos/language_options_util.h83
-rw-r--r--chrome/browser/ui/webui/options2/chromeos/language_pinyin_handler.cc67
-rw-r--r--chrome/browser/ui/webui/options2/chromeos/language_pinyin_handler.h34
-rw-r--r--chrome/browser/ui/webui/options2/chromeos/proxy_handler.cc86
-rw-r--r--chrome/browser/ui/webui/options2/chromeos/proxy_handler.h33
-rw-r--r--chrome/browser/ui/webui/options2/chromeos/stats_options_handler.cc45
-rw-r--r--chrome/browser/ui/webui/options2/chromeos/stats_options_handler.h38
-rw-r--r--chrome/browser/ui/webui/options2/chromeos/system_options_handler.cc170
-rw-r--r--chrome/browser/ui/webui/options2/chromeos/system_options_handler.h50
-rw-r--r--chrome/browser/ui/webui/options2/chromeos/system_settings_provider.cc333
-rw-r--r--chrome/browser/ui/webui/options2/chromeos/system_settings_provider.h76
-rw-r--r--chrome/browser/ui/webui/options2/chromeos/user_image_source.cc53
-rw-r--r--chrome/browser/ui/webui/options2/chromeos/user_image_source.h44
-rw-r--r--chrome/browser/ui/webui/options2/chromeos/virtual_keyboard_manager_handler.cc237
-rw-r--r--chrome/browser/ui/webui/options2/chromeos/virtual_keyboard_manager_handler.h78
-rw-r--r--chrome/browser/ui/webui/options2/chromeos/virtual_keyboard_manager_handler_unittest.cc550
-rw-r--r--chrome/browser/ui/webui/options2/clear_browser_data_handler.cc143
-rw-r--r--chrome/browser/ui/webui/options2/clear_browser_data_handler.h45
-rw-r--r--chrome/browser/ui/webui/options2/content_options_browsertest.js24
-rw-r--r--chrome/browser/ui/webui/options2/content_settings_exception_area_browsertest.js26
-rw-r--r--chrome/browser/ui/webui/options2/content_settings_handler.cc879
-rw-r--r--chrome/browser/ui/webui/options2/content_settings_handler.h113
-rw-r--r--chrome/browser/ui/webui/options2/cookies_view_browsertest.js24
-rw-r--r--chrome/browser/ui/webui/options2/cookies_view_handler.cc222
-rw-r--r--chrome/browser/ui/webui/options2/cookies_view_handler.h69
-rw-r--r--chrome/browser/ui/webui/options2/core_options_handler.cc466
-rw-r--r--chrome/browser/ui/webui/options2/core_options_handler.h151
-rw-r--r--chrome/browser/ui/webui/options2/extension_settings_handler.cc785
-rw-r--r--chrome/browser/ui/webui/options2/extension_settings_handler.h200
-rw-r--r--chrome/browser/ui/webui/options2/font_settings_browsertest.js24
-rw-r--r--chrome/browser/ui/webui/options2/font_settings_handler.cc211
-rw-r--r--chrome/browser/ui/webui/options2/font_settings_handler.h55
-rw-r--r--chrome/browser/ui/webui/options2/font_settings_utils.h22
-rw-r--r--chrome/browser/ui/webui/options2/font_settings_utils_mac.mm38
-rw-r--r--chrome/browser/ui/webui/options2/font_settings_utils_win.cc11
-rw-r--r--chrome/browser/ui/webui/options2/font_settings_utils_x11.cc10
-rw-r--r--chrome/browser/ui/webui/options2/handler_options_handler.cc207
-rw-r--r--chrome/browser/ui/webui/options2/handler_options_handler.h76
-rw-r--r--chrome/browser/ui/webui/options2/import_data_handler.cc177
-rw-r--r--chrome/browser/ui/webui/options2/import_data_handler.h59
-rw-r--r--chrome/browser/ui/webui/options2/language_options_browsertest.js24
-rw-r--r--chrome/browser/ui/webui/options2/language_options_handler.cc118
-rw-r--r--chrome/browser/ui/webui/options2/language_options_handler.h44
-rw-r--r--chrome/browser/ui/webui/options2/language_options_handler_common.cc168
-rw-r--r--chrome/browser/ui/webui/options2/language_options_handler_common.h69
-rw-r--r--chrome/browser/ui/webui/options2/language_options_handler_unittest.cc198
-rw-r--r--chrome/browser/ui/webui/options2/manage_profile_handler.cc300
-rw-r--r--chrome/browser/ui/webui/options2/manage_profile_handler.h82
-rw-r--r--chrome/browser/ui/webui/options2/options_browsertest.js93
-rw-r--r--chrome/browser/ui/webui/options2/options_sync_setup_handler.cc55
-rw-r--r--chrome/browser/ui/webui/options2/options_sync_setup_handler.h22
-rw-r--r--chrome/browser/ui/webui/options2/options_ui2.cc368
-rw-r--r--chrome/browser/ui/webui/options2/options_ui2.h111
-rw-r--r--chrome/browser/ui/webui/options2/options_ui_uitest.cc65
-rw-r--r--chrome/browser/ui/webui/options2/options_ui_uitest.h38
-rw-r--r--chrome/browser/ui/webui/options2/pack_extension_handler.cc101
-rw-r--r--chrome/browser/ui/webui/options2/pack_extension_handler.h49
-rw-r--r--chrome/browser/ui/webui/options2/password_manager_browsertest.js25
-rw-r--r--chrome/browser/ui/webui/options2/password_manager_handler.cc291
-rw-r--r--chrome/browser/ui/webui/options2/password_manager_handler.h129
-rw-r--r--chrome/browser/ui/webui/options2/personal_options_browsertest.js24
-rw-r--r--chrome/browser/ui/webui/options2/personal_options_handler.cc444
-rw-r--r--chrome/browser/ui/webui/options2/personal_options_handler.h70
-rw-r--r--chrome/browser/ui/webui/options2/search_engine_manager_browsertest.js33
-rw-r--r--chrome/browser/ui/webui/options2/search_engine_manager_handler.cc313
-rw-r--r--chrome/browser/ui/webui/options2/search_engine_manager_handler.h79
-rw-r--r--chrome/browser/ui/webui/options2/stop_syncing_handler.cc54
-rw-r--r--chrome/browser/ui/webui/options2/stop_syncing_handler.h29
-rw-r--r--chrome/browser/ui/webui/options2/web_intents_settings_handler.cc160
-rw-r--r--chrome/browser/ui/webui/options2/web_intents_settings_handler.h77
-rw-r--r--chrome/browser/ui/webui/sync_promo_handler2.cc288
-rw-r--r--chrome/browser/ui/webui/sync_promo_handler2.h97
-rw-r--r--chrome/browser/ui/webui/sync_setup_handler2.cc772
-rw-r--r--chrome/browser/ui/webui/sync_setup_handler2.h117
-rw-r--r--chrome/browser/ui/webui/uber/uber_ui.cc4
-rw-r--r--chrome/chrome_browser.gypi99
-rw-r--r--chrome/chrome_repack_resources.gypi1
-rw-r--r--chrome/chrome_resources.gyp7
-rw-r--r--chrome/chrome_tests.gypi2
-rw-r--r--chrome/common/url_constants.cc4
-rw-r--r--chrome/common/url_constants.h2
-rw-r--r--tools/gritsettings/resource_ids3
274 files changed, 44270 insertions, 7 deletions
diff --git a/base/path_service.cc b/base/path_service.cc
index 256318c..47278c3 100644
--- a/base/path_service.cc
+++ b/base/path_service.cc
@@ -162,7 +162,7 @@ bool PathService::GetFromOverrides(int key, FilePath* result) {
PathData* path_data = GetPathData();
base::AutoLock scoped_lock(path_data->lock);
- // check for an overriden version.
+ // check for an overridden version.
PathMap::const_iterator it = path_data->overrides.find(key);
if (it != path_data->overrides.end()) {
*result = it->second;
@@ -230,13 +230,13 @@ bool PathService::Override(int key, const FilePath& path) {
// Make sure the directory exists. We need to do this before we translate
// this to the absolute path because on POSIX, AbsolutePath fails if called
- // on a non-existant path.
+ // on a non-existent path.
if (!file_util::PathExists(file_path) &&
!file_util::CreateDirectory(file_path))
return false;
// We need to have an absolute path, as extensions and plugins don't like
- // relative paths, and will glady crash the browser in CHECK()s if they get a
+ // relative paths, and will gladly crash the browser in CHECK()s if they get a
// relative path.
if (!file_util::AbsolutePath(&file_path))
return false;
diff --git a/chrome/browser/resources/options2/OWNERS b/chrome/browser/resources/options2/OWNERS
new file mode 100644
index 0000000..67257e6
--- /dev/null
+++ b/chrome/browser/resources/options2/OWNERS
@@ -0,0 +1,4 @@
+csilv@chromium.org
+estade@chromium.org
+jhawkins@chromium.org
+stuartmorgan@chromium.org
diff --git a/chrome/browser/resources/options2/about_page.css b/chrome/browser/resources/options2/about_page.css
new file mode 100644
index 0000000..a64f9b8
--- /dev/null
+++ b/chrome/browser/resources/options2/about_page.css
@@ -0,0 +1,34 @@
+#aboutPage {
+ -webkit-user-select: text;
+}
+
+.loading {
+ font-style: italic;
+}
+
+#channelSelect {
+ margin-bottom: 5px;
+}
+
+#channelWarning {
+ color: red;
+ font-weight: bold;
+}
+
+.update-icon {
+ width: 17px;
+ height: 17px;
+ display: inline-block;
+ vertical-align: middle;
+ background-repeat: no-repeat;
+}
+
+.update-icon.fail {
+ background-image: url('../../../app/theme/update_fail.png');
+}
+.update-icon.available {
+ background-image: url('../../../app/theme/update_available.png');
+}
+.update-icon.up-to-date {
+ background-image: url('../../../app/theme/update_uptodate.png');
+}
diff --git a/chrome/browser/resources/options2/about_page.html b/chrome/browser/resources/options2/about_page.html
new file mode 100644
index 0000000..3ed27e1
--- /dev/null
+++ b/chrome/browser/resources/options2/about_page.html
@@ -0,0 +1,119 @@
+<div id="aboutPage" class="page" hidden>
+ <h1 i18n-content="product"></h1>
+ <div id="aboutPageLessInfo">
+ <section>
+ <div>
+ <!-- White space is significant between spans. -->
+ <div>
+ <span i18n-content="browser"></span>
+ <span i18n-content="browser_version"></span>
+ </div>
+ <div>
+<if expr="not pp_ifdef('chromeos')">
+ <span i18n-content="os"></span> <span id="osVersion0">
+</if>
+<if expr="pp_ifdef('chromeos')">
+ <span i18n-content="platform"></span> <span id="osVersion0">
+</if>
+ <span class="loading" i18n-content="loading"></span></span></div>
+ <div><span i18n-content="firmware"></span> <span id="osFirmware0">
+ <span class="loading" i18n-content="loading"></span></span></div>
+ <div>
+ <button id="moreInfoButton" class="link-button"
+ i18n-content="more_info"></button>
+ </div>
+ </div>
+ </section>
+ </div>
+ <div id="aboutPageMoreInfo" hidden>
+ <section>
+ <h3 i18n-content="channel"></h3>
+ <div>
+ <select id="channelSelect">
+ <option value="stable-channel" i18n-content="stable"></option>
+ <option value="beta-channel" i18n-content="beta"></option>
+ <option value="dev-channel" i18n-content="dev"></option>
+ </select>
+ <div id="channelWarningBlock" hidden>
+ <div id="channelWarning" i18n-content="channel_warning_header"></div>
+ <div i18n-content="channel_warning_text"></div>
+ </div>
+ </div>
+ </section>
+ <section>
+ <h3 i18n-content="browser"></h3>
+ <div i18n-content="browser_version"></div>
+ </section>
+ <section>
+<if expr="not pp_ifdef('chromeos')">
+ <h3 i18n-content="os"></h3>
+</if>
+<if expr="pp_ifdef('chromeos')">
+ <h3 i18n-content="platform"></h3>
+</if>
+ <div id="osVersion1">
+ <span class="loading" i18n-content="loading"></span>
+ </div>
+ </section>
+ <section>
+ <h3 i18n-content="firmware"></h3>
+ <div id="osFirmware1">
+ <span class="loading" i18n-content="loading"></span>
+ </section>
+ <section>
+ <h3>WebKit</h3>
+ <div i18n-content="webkit_version"></div>
+ </section>
+ <section>
+ <h3 i18n-content="js_engine"></h3>
+ <div i18n-content="js_engine_version"></div>
+ </section>
+ <section>
+ <h3 i18n-content="user_agent"></h3>
+ <div i18n-content="user_agent_info"></div>
+ </section>
+ <section>
+ <h3 i18n-content="command_line"></h3>
+ <div i18n-content="command_line_info"></div>
+ </section>
+ </div>
+ <section>
+ <div>
+ <div i18n-content="copyright"></div>
+ <div>
+ <!-- Odd formatting to avoid unwanted spaces between elements. -->
+ <span i18n-content="license_content_0">
+ </span><a target="_blank"
+ i18n-values="href:license_link_0"
+ i18n-content="license_link_content_0">
+ </a><span i18n-content="license_content_1">
+ </span><a target="_blank"
+ i18n-values="href:license_link_1"
+ i18n-content="license_link_content_1">
+ </a><span i18n-content="license_content_2">
+ </span>
+ </div>
+ <div>
+ <span i18n-content="cros_license_content_0">
+ </span><a target="_blank"
+ i18n-values="href:cros_license_link_0"
+ i18n-content="cros_license_link_content_0">
+ </a><span i18n-content="cros_license_content_1">
+ </span>
+ </div>
+ </div>
+ </section>
+ <section>
+ <div>
+ <div>
+ <div id="updateIcon" class="update-icon up-to-date"></div>
+ <span id="updateStatus" i18n-content="update_status"></span>
+ </div>
+ <div>
+ <!-- TODO seanparent: fill in last checked. -->
+ <!-- <span i18n-content="last_check"></span> -->
+ <button id="checkNow" i18n-content="check_now" disabled></button>
+ </div>
+ </div>
+ </section>
+</div>
diff --git a/chrome/browser/resources/options2/about_page.js b/chrome/browser/resources/options2/about_page.js
new file mode 100644
index 0000000..6f608f0
--- /dev/null
+++ b/chrome/browser/resources/options2/about_page.js
@@ -0,0 +1,220 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+ const OptionsPage = options.OptionsPage;
+
+ /**
+ * The number of milliseconds used for showing a message.
+ * @type {number}
+ */
+ const MESSAGE_DELAY_MS = 1000; // 1 sec.
+
+ /**
+ * Encapsulated handling of about page.
+ */
+ function AboutPage() {
+ OptionsPage.call(this, 'about', templateData.aboutPageTabTitle,
+ 'aboutPage');
+ }
+
+ cr.addSingletonGetter(AboutPage);
+
+ AboutPage.prototype = {
+ // Inherit AboutPage from OptionsPage.
+ __proto__: OptionsPage.prototype,
+
+ /**
+ * The queue is used for updating the status message with delay, like:
+ * [["Check for update...", 1000], ["Chrome OS is up to date", 0]]
+ * @type {!Array.<!Array>}
+ */
+ statusMessageQueue_: [],
+
+ /**
+ * True if the status message queue flush started.
+ * @type {boolean}
+ */
+ statusMessageQueueFlushStarted_: false,
+
+ /**
+ * The selected release channel.
+ * @type {string}
+ */
+ selectedChannel_: '',
+
+ // Initialize AboutPage.
+ initializePage: function() {
+ // Call base class implementation to start preference initialization.
+ OptionsPage.prototype.initializePage.call(this);
+
+ $('checkNow').onclick = function(event) {
+ chrome.send('CheckNow');
+ };
+
+ $('moreInfoButton').onclick = function(event) {
+ $('aboutPageLessInfo').hidden = true;
+ $('aboutPageMoreInfo').hidden = false;
+ };
+
+ if (!AccountsOptions.currentUserIsOwner()) {
+ $('channelSelect').disabled = true;
+ } else {
+ var self = this;
+ $('channelSelect').onchange = function(event) {
+ self.selectedOptionOnChange_(event.target.value);
+ };
+ }
+
+ // Notify the handler that the page is ready.
+ chrome.send('PageReady');
+ },
+
+ // Update the Default Browsers section based on the current state.
+ updateOSVersion_: function(versionString) {
+ $('osVersion0').textContent = versionString;
+ $('osVersion1').textContent = versionString;
+ },
+
+ updateOSFirmware_: function(firmwareString) {
+ $('osFirmware0').textContent = firmwareString;
+ $('osFirmware1').textContent = firmwareString;
+ },
+
+ /**
+ * Updates the status message like "Checking for update...".
+ * @param {string} message The message to be shown.
+ * @param {boolean} insertDelay show the message for a while.
+ * @private
+ */
+ updateStatus_: function(message, insertDelay) {
+ // Add the message to the queue with delay if needed.
+ // The delay is inserted so users can read the message.
+ var delayMs = insertDelay ? MESSAGE_DELAY_MS : 0;
+ this.statusMessageQueue_.push([message, delayMs]);
+ // Start the periodic flusher if not started.
+ if (this.statusMessageQueueFlushStarted_ == false) {
+ this.flushStatusMessageQueuePeriodically_();
+ }
+ },
+
+ /**
+ * Flushes the status message queue periodically using a timer.
+ * @private
+ */
+ flushStatusMessageQueuePeriodically_: function() {
+ // Stop the periodic flusher if the queue becomes empty.
+ if (this.statusMessageQueue_.length == 0) {
+ this.statusMessageQueueFlushStarted_ = false;
+ return;
+ }
+ this.statusMessageQueueFlushStarted_ = true;
+
+ // Update the status message.
+ var pair = this.statusMessageQueue_.shift();
+ var message = pair[0];
+ var delayMs = pair[1];
+ $('updateStatus').textContent = message;
+
+ // Schedule the next flush with delay as needed.
+ var self = this;
+ window.setTimeout(
+ function() { self.flushStatusMessageQueuePeriodically_() },
+ delayMs);
+ },
+
+ updateEnable_: function(enable) {
+ $('checkNow').disabled = !enable;
+ },
+
+ setReleaseChannel_: function(channel) {
+ // Write the value into the pref which will end up in the policy.
+ // Eventually, the update engine will use the policy value as the
+ // source truth for the update channel (see http://crosbug/17015).
+ Preferences.setStringPref("cros.system.releaseChannel", channel);
+ this.selectedChannel_ = channel;
+ chrome.send('SetReleaseTrack', [channel]);
+ },
+
+ selectedOptionOnChange_: function(value) {
+ if (value == 'dev-channel') {
+ // Open confirm dialog.
+ var self = this;
+ AlertOverlay.show(
+ localStrings.getString('channel_warning_header'),
+ localStrings.getString('channel_warning_text'),
+ localStrings.getString('ok'),
+ localStrings.getString('cancel'),
+ function() {
+ // Ok, so set release track and update selected channel.
+ $('channelWarningBlock').hidden = false;
+ self.setReleaseChannel_(value); },
+ function() {
+ // Cancel, so switch back to previous selected channel.
+ self.updateSelectedOption_(self.selectedChannel_); }
+ );
+ } else {
+ $('channelWarningBlock').hidden = true;
+ this.setReleaseChannel_(value);
+ }
+ },
+
+ // Updates the selected option in 'channelSelect' <select> element.
+ updateSelectedOption_: function(value) {
+ var options = $('channelSelect').querySelectorAll('option');
+ for (var i = 0; i < options.length; i++) {
+ var option = options[i];
+ if (option.value == value) {
+ option.selected = true;
+ this.selectedChannel_ = value;
+ }
+ }
+ if (value == 'dev-channel')
+ $('channelWarningBlock').hidden = false;
+ },
+
+ // Changes the "check now" button to "restart now" button.
+ changeToRestartButton_: function() {
+ $('checkNow').textContent = localStrings.getString('restart_now');
+ $('checkNow').disabled = false;
+ $('checkNow').onclick = function(event) {
+ chrome.send('RestartNow');
+ };
+ },
+ };
+
+ AboutPage.updateOSVersionCallback = function(versionString) {
+ AboutPage.getInstance().updateOSVersion_(versionString);
+ };
+
+ AboutPage.updateOSFirmwareCallback = function(firmwareString) {
+ AboutPage.getInstance().updateOSFirmware_(firmwareString);
+ };
+
+ AboutPage.updateStatusCallback = function(message, insertDelay) {
+ AboutPage.getInstance().updateStatus_(message, insertDelay);
+ };
+
+ AboutPage.updateEnableCallback = function(enable) {
+ AboutPage.getInstance().updateEnable_(enable);
+ };
+
+ AboutPage.updateSelectedOptionCallback = function(value) {
+ AboutPage.getInstance().updateSelectedOption_(value);
+ };
+
+ AboutPage.setUpdateImage = function(state) {
+ $('updateIcon').className= 'update-icon ' + state;
+ };
+
+ AboutPage.changeToRestartButton = function() {
+ AboutPage.getInstance().changeToRestartButton_();
+ };
+
+ // Export
+ return {
+ AboutPage: AboutPage
+ };
+
+});
diff --git a/chrome/browser/resources/options2/advanced_options.css b/chrome/browser/resources/options2/advanced_options.css
new file mode 100644
index 0000000..bf53be2
--- /dev/null
+++ b/chrome/browser/resources/options2/advanced_options.css
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2011 The Chromium Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#advancedPage .section-group:not(:first-child) {
+ margin-top: 10px;
+}
+
+#advancedPage .section-group:not(:last-child) {
+ margin-bottom: 10px;
+}
+
+#advancedPage select,
+#advancedPage .web-content-select-label {
+ min-width: 145px;
+}
+
+#advancedPage .web-content-select-label > span:only-of-type {
+ display: inline-block;
+ min-width: 100px;
+}
+
+#download-location-group {
+ margin: 10px 0 5px;
+ min-width: 470px;
+}
+
+#fontSettingsCustomizeFontsButton,
+#privacyClearDataButton {
+ margin-left: 10px;
+}
diff --git a/chrome/browser/resources/options2/advanced_options.html b/chrome/browser/resources/options2/advanced_options.html
new file mode 100644
index 0000000..525eb99
--- /dev/null
+++ b/chrome/browser/resources/options2/advanced_options.html
@@ -0,0 +1,232 @@
+<div id="advancedPage" class="page" hidden>
+ <h1 i18n-content="advancedPage"></h1>
+ <div class="displaytable">
+ <section>
+ <h3 i18n-content="advancedSectionTitlePrivacy"></h3>
+ <div>
+ <div class="section-group">
+ <button id="privacyContentSettingsButton"
+ i18n-content="privacyContentSettingsButton"></button>
+ <button id="privacyClearDataButton"
+ i18n-content="privacyClearDataButton"></button>
+ </div>
+ <div i18n-content="improveBrowsingExperience"
+ class="informational-text">
+ </div>
+ <div>
+ <span i18n-content="disableWebServices"
+ class="informational-text">
+ </span>
+ <a target="_blank" i18n-content="learnMore"
+ i18n-values="href:privacyLearnMoreURL"></a>
+ </div>
+ <div class="checkbox">
+ <label>
+ <input id="alternateErrorPagesEnabled"
+ pref="alternate_error_pages.enabled"
+ metric="Options_LinkDoctorCheckbox" type="checkbox">
+ <span i18n-content="linkDoctorPref"></span>
+ </label>
+ </div>
+ <div class="checkbox">
+ <label>
+ <input id="searchSuggestEnabled" pref="search.suggest_enabled"
+ metric="Options_UseSuggestCheckbox" type="checkbox">
+ <span i18n-content="suggestPref"></span>
+ </label>
+ </div>
+ <div class="checkbox">
+ <label>
+ <input id="dnsPrefetchingEnabled" pref="dns_prefetching.enabled"
+ metric="Options_DnsPrefetchCheckbox" type="checkbox">
+ <span i18n-content="networkPredictionEnabledDescription"></span>
+ </label>
+ </div>
+ <div class="checkbox">
+ <label>
+ <input id="safeBrowsingEnabled" pref="safebrowsing.enabled"
+ metric="Options_SafeBrowsingCheckbox" type="checkbox">
+ <span i18n-content="safeBrowsingEnableProtection"></span>
+ </label>
+ </div>
+<if expr="pp_ifdef('_google_chrome') and pp_ifdef('chromeos')">
+ <div id="metricsReportingSetting" class="checkbox">
+ <label>
+ <input id="metricsReportingEnabled"
+ pref="cros.metrics.reportingEnabled" type="checkbox">
+ <span id="metricsReportingEnabledText"
+ i18n-content="enableLogging">
+ </span>
+ </label>
+ </div>
+</if>
+<if expr="pp_ifdef('_google_chrome') and not pp_ifdef('chromeos')">
+ <div id="metricsReportingSetting" class="checkbox">
+ <label>
+ <input id="metricsReportingEnabled" type="checkbox">
+ <span i18n-content="enableLogging"></span>
+ </label>
+ </div>
+</if>
+ </div>
+ </section>
+ <section>
+ <h3 i18n-content="advancedSectionTitleContent"></h3>
+ <div>
+ <div class="section-group">
+ <label class="web-content-select-label">
+ <span i18n-content="defaultFontSizeLabel"></span>
+ <select id="defaultFontSize">
+ <option value="9" i18n-content="fontSizeLabelVerySmall">
+ </option>
+ <option value="12" i18n-content="fontSizeLabelSmall"></option>
+ <option value="16" i18n-content="fontSizeLabelMedium"></option>
+ <option value="20" i18n-content="fontSizeLabelLarge"></option>
+ <option value="24" i18n-content="fontSizeLabelVeryLarge">
+ </option>
+ </select>
+ </label>
+ <button id="fontSettingsCustomizeFontsButton"
+ i18n-content="fontSettingsCustomizeFontsButton"></button>
+ </div>
+ <div class="section-group">
+ <label class="web-content-select-label">
+ <span i18n-content="defaultZoomFactorLabel"></span>
+ <select id="defaultZoomFactor" dataType="double"></select>
+ </label>
+ </div>
+<if expr="not pp_ifdef('chromeos') or os == 'win32'">
+ <div class="section-group">
+</if>
+<if expr="not pp_ifdef('chromeos')">
+ <button id="language-button"
+ i18n-content="languageAndSpellCheckSettingsButton"></button>
+</if>
+<if expr="not pp_ifdef('chromeos') or os == 'win32'">
+ </div>
+</if>
+<if expr="os == 'darwin'">
+ <div class="checkbox">
+ <label>
+ <input id="tabsToLinksPref" pref="webkit.webprefs.tabs_to_links"
+ metric="Options_TabsToLinks" type="checkbox">
+ <span i18n-content="tabsToLinksPref"></span>
+ </label>
+ </div>
+</if>
+ </div>
+ </section>
+<if expr="not pp_ifdef('chromeos')">
+ <section>
+ <h3 i18n-content="advancedSectionTitleNetwork"></h3>
+ <div>
+ <div id="proxiesLabel"></div>
+ <div class="section-group">
+ <button id="proxiesConfigureButton"
+ i18n-content="proxiesConfigureButton"></button>
+ </div>
+ </div>
+ </section>
+</if>
+ <section>
+ <h3 i18n-content="advancedSectionTitleTranslate"></h3>
+ <div class="checkbox">
+ <label>
+ <input id="enableTranslate" pref="translate.enabled"
+ metric="Options_Translate" type="checkbox">
+ <span i18n-content="translateEnableTranslate"></span>
+ </label>
+ </div>
+ </section>
+<if expr="not pp_ifdef('chromeos')">
+ <section>
+ <h3 i18n-content="downloadLocationGroupName"></h3>
+ <div>
+ <div id="download-location-group">
+ <label>
+ <span i18n-content="downloadLocationBrowseTitle"></span>
+ <input id="downloadLocationPath" class="weakrtl" type="text"
+ pref="download.default_directory" size="36">
+ </label>
+ <button id="downloadLocationChangeButton"
+ pref="download.prompt_for_download"
+ i18n-content="downloadLocationChangeButton"></button>
+ </div>
+ <div class="checkbox">
+ <label>
+ <input type="checkbox"
+ pref="download.prompt_for_download"
+ metric="Options_AskForSaveLocation">
+ <span i18n-content="downloadLocationAskForSaveLocation"></span>
+ </label>
+ </div>
+ <div id="auto-open-file-types-label"
+ i18n-content="autoOpenFileTypesInfo"></div>
+ <div class="section-group">
+ <button id="autoOpenFileTypesResetToDefault"
+ i18n-content="autoOpenFileTypesResetToDefault"></button>
+ </div>
+ </div>
+ </section>
+</if>
+ <section>
+ <h3 i18n-content="advancedSectionTitleSecurity"></h3>
+ <div>
+ <div class="section-group">
+ <button id="certificatesManageButton"
+ i18n-content="certificatesManageButton"></button>
+ </div>
+ <div class="checkbox">
+ <label>
+ <input id="sslCheckRevocation" type="checkbox">
+ <span i18n-content="sslCheckRevocation"></span>
+ </label>
+ </div>
+ </div>
+ </section>
+<if expr="not pp_ifdef('chromeos')">
+ <section id="cloud-print-proxy-section">
+ <h3 i18n-content="advancedSectionTitleCloudPrint"></h3>
+ <div>
+ <div id="cloudPrintProxyLabel"
+ i18n-content="cloudPrintProxyDisabledLabel"></div>
+ <div class="section-group">
+ <button id="cloudPrintProxySetupButton"
+ i18n-content="cloudPrintProxyDisabledButton"></button>
+ <button id="cloudPrintProxyManageButton"
+ i18n-content="cloudPrintProxyEnabledManageButton"></button>
+ </div>
+ </div>
+ </section>
+</if>
+<if expr="pp_ifdef('chromeos')">
+ <section id="cloud-print-proxy-section">
+ <h3 i18n-content="advancedSectionTitleCloudPrint"></h3>
+ <div>
+ <div>
+ <span i18n-content="cloudPrintChromeosOptionLabel"
+ class="informational-text">
+ </span>
+ <a target="_blank" i18n-content="learnMore"
+ i18n-values="href:cloudPrintLearnMoreURL"></a>
+ </div>
+ <div class="section-group">
+ <button id="cloudPrintProxyManageButton"
+ i18n-content="cloudPrintChromeosOptionButton"></button>
+ </div>
+ </div>
+ </section>
+</if>
+<if expr="os != 'darwin' and not pp_ifdef('chromeos')">
+ <section id="background-section">
+ <h3 i18n-content="advancedSectionTitleBackground"></h3>
+ <div class="checkbox">
+ <label>
+ <input id="backgroundModeCheckbox" type="checkbox">
+ <span i18n-content="backgroundModeCheckbox"></span>
+ </label>
+ </div>
+ </section>
+</if>
+ </div>
+</div>
diff --git a/chrome/browser/resources/options2/advanced_options.js b/chrome/browser/resources/options2/advanced_options.js
new file mode 100644
index 0000000..fb1a809
--- /dev/null
+++ b/chrome/browser/resources/options2/advanced_options.js
@@ -0,0 +1,266 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+
+var OptionsPage = options.OptionsPage;
+
+ //
+ // AdvancedOptions class
+ // Encapsulated handling of advanced options page.
+ //
+ function AdvancedOptions() {
+ OptionsPage.call(this, 'advanced', templateData.advancedPageTabTitle,
+ 'advancedPage');
+ }
+
+ cr.addSingletonGetter(AdvancedOptions);
+
+ AdvancedOptions.prototype = {
+ // Inherit AdvancedOptions from OptionsPage.
+ __proto__: options.OptionsPage.prototype,
+
+ /**
+ * Initializes the page.
+ */
+ initializePage: function() {
+ // Call base class implementation to starts preference initialization.
+ OptionsPage.prototype.initializePage.call(this);
+
+ // Set up click handlers for buttons.
+ $('privacyContentSettingsButton').onclick = function(event) {
+ OptionsPage.navigateToPage('content');
+ OptionsPage.showTab($('cookies-nav-tab'));
+ chrome.send('coreOptionsUserMetricsAction',
+ ['Options_ContentSettings']);
+ };
+ $('privacyClearDataButton').onclick = function(event) {
+ OptionsPage.navigateToPage('clearBrowserData');
+ chrome.send('coreOptionsUserMetricsAction', ['Options_ClearData']);
+ };
+
+ // 'metricsReportingEnabled' element is only present on Chrome branded
+ // builds.
+ if ($('metricsReportingEnabled')) {
+ $('metricsReportingEnabled').onclick = function(event) {
+ chrome.send('metricsReportingCheckboxAction',
+ [String(event.target.checked)]);
+ };
+ }
+
+ if (!cr.isChromeOS) {
+ $('autoOpenFileTypesResetToDefault').onclick = function(event) {
+ chrome.send('autoOpenFileTypesAction');
+ };
+ }
+
+ $('fontSettingsCustomizeFontsButton').onclick = function(event) {
+ OptionsPage.navigateToPage('fonts');
+ chrome.send('coreOptionsUserMetricsAction', ['Options_FontSettings']);
+ };
+ $('defaultFontSize').onchange = function(event) {
+ chrome.send('defaultFontSizeAction',
+ [String(event.target.options[event.target.selectedIndex].value)]);
+ };
+ $('defaultZoomFactor').onchange = function(event) {
+ chrome.send('defaultZoomFactorAction',
+ [String(event.target.options[event.target.selectedIndex].value)]);
+ };
+
+ $('language-button').onclick = function(event) {
+ OptionsPage.navigateToPage('languages');
+ chrome.send('coreOptionsUserMetricsAction',
+ ['Options_LanuageAndSpellCheckSettings']);
+ };
+
+ if (cr.isWindows || cr.isMac) {
+ $('certificatesManageButton').onclick = function(event) {
+ chrome.send('showManageSSLCertificates');
+ };
+ } else {
+ $('certificatesManageButton').onclick = function(event) {
+ OptionsPage.navigateToPage('certificates');
+ chrome.send('coreOptionsUserMetricsAction',
+ ['Options_ManageSSLCertificates']);
+ };
+ }
+
+ if (!cr.isChromeOS) {
+ $('proxiesConfigureButton').onclick = function(event) {
+ chrome.send('showNetworkProxySettings');
+ };
+ $('downloadLocationChangeButton').onclick = function(event) {
+ chrome.send('selectDownloadLocation');
+ };
+ // This text field is always disabled. Setting ".disabled = true" isn't
+ // enough, since a policy can disable it but shouldn't re-enable when
+ // it is removed.
+ $('downloadLocationPath').setDisabled('readonly', true);
+ }
+
+ $('sslCheckRevocation').onclick = function(event) {
+ chrome.send('checkRevocationCheckboxAction',
+ [String($('sslCheckRevocation').checked)]);
+ };
+
+ if ($('backgroundModeCheckbox')) {
+ $('backgroundModeCheckbox').onclick = function(event) {
+ chrome.send('backgroundModeAction',
+ [String($('backgroundModeCheckbox').checked)]);
+ };
+ }
+
+ // 'cloudPrintProxyEnabled' is true for Chrome branded builds on
+ // certain platforms, or could be enabled by a lab.
+ if (!cr.isChromeOS) {
+ $('cloudPrintProxySetupButton').onclick = function(event) {
+ if ($('cloudPrintProxyManageButton').style.display == 'none') {
+ // Disable the button, set it's text to the intermediate state.
+ $('cloudPrintProxySetupButton').textContent =
+ localStrings.getString('cloudPrintProxyEnablingButton');
+ $('cloudPrintProxySetupButton').disabled = true;
+ chrome.send('showCloudPrintSetupDialog');
+ } else {
+ chrome.send('disableCloudPrintProxy');
+ }
+ };
+ }
+ $('cloudPrintProxyManageButton').onclick = function(event) {
+ chrome.send('showCloudPrintManagePage');
+ };
+
+ }
+ };
+
+ //
+ // Chrome callbacks
+ //
+
+ // Set the checked state of the metrics reporting checkbox.
+ AdvancedOptions.SetMetricsReportingCheckboxState = function(
+ checked, disabled) {
+ $('metricsReportingEnabled').checked = checked;
+ $('metricsReportingEnabled').disabled = disabled;
+ if (disabled)
+ $('metricsReportingEnabledText').className = 'disable-services-span';
+ }
+
+ AdvancedOptions.SetMetricsReportingSettingVisibility = function(visible) {
+ if (visible) {
+ $('metricsReportingSetting').style.display = 'block';
+ } else {
+ $('metricsReportingSetting').style.display = 'none';
+ }
+ }
+
+ // Set the font size selected item.
+ AdvancedOptions.SetFontSize = function(font_size_value) {
+ var selectCtl = $('defaultFontSize');
+ for (var i = 0; i < selectCtl.options.length; i++) {
+ if (selectCtl.options[i].value == font_size_value) {
+ selectCtl.selectedIndex = i;
+ if ($('Custom'))
+ selectCtl.remove($('Custom').index);
+ return;
+ }
+ }
+
+ // Add/Select Custom Option in the font size label list.
+ if (!$('Custom')) {
+ var option = new Option(localStrings.getString('fontSizeLabelCustom'),
+ -1, false, true);
+ option.setAttribute("id", "Custom");
+ selectCtl.add(option);
+ }
+ $('Custom').selected = true;
+ };
+
+ /**
+ * Populate the page zoom selector with values received from the caller.
+ * @param {Array} items An array of items to populate the selector.
+ * each object is an array with three elements as follows:
+ * 0: The title of the item (string).
+ * 1: The value of the item (number).
+ * 2: Whether the item should be selected (boolean).
+ */
+ AdvancedOptions.SetupPageZoomSelector = function(items) {
+ var element = $('defaultZoomFactor');
+
+ // Remove any existing content.
+ element.textContent = '';
+
+ // Insert new child nodes into select element.
+ var value, title, selected;
+ for (var i = 0; i < items.length; i++) {
+ title = items[i][0];
+ value = items[i][1];
+ selected = items[i][2];
+ element.appendChild(new Option(title, value, false, selected));
+ }
+ };
+
+ // Set the enabled state for the autoOpenFileTypesResetToDefault button.
+ AdvancedOptions.SetAutoOpenFileTypesDisabledAttribute = function(disabled) {
+ if (!cr.isChromeOS) {
+ $('autoOpenFileTypesResetToDefault').disabled = disabled;
+
+ if (disabled)
+ $('auto-open-file-types-label').classList.add('disabled');
+ else
+ $('auto-open-file-types-label').classList.remove('disabled');
+ }
+ };
+
+ // Set the enabled state for the proxy settings button.
+ AdvancedOptions.SetupProxySettingsSection = function(disabled, label) {
+ if (!cr.isChromeOS) {
+ $('proxiesConfigureButton').disabled = disabled;
+ $('proxiesLabel').textContent = label;
+ }
+ };
+
+ // Set the checked state for the sslCheckRevocation checkbox.
+ AdvancedOptions.SetCheckRevocationCheckboxState = function(
+ checked, disabled) {
+ $('sslCheckRevocation').checked = checked;
+ $('sslCheckRevocation').disabled = disabled;
+ };
+
+ // Set the checked state for the backgroundModeCheckbox element.
+ AdvancedOptions.SetBackgroundModeCheckboxState = function(checked) {
+ $('backgroundModeCheckbox').checked = checked;
+ };
+
+ // Set the Cloud Print proxy UI to enabled, disabled, or processing.
+ AdvancedOptions.SetupCloudPrintProxySection = function(
+ disabled, label, allowed) {
+ if (!cr.isChromeOS) {
+ $('cloudPrintProxyLabel').textContent = label;
+ if (disabled || !allowed) {
+ $('cloudPrintProxySetupButton').textContent =
+ localStrings.getString('cloudPrintProxyDisabledButton');
+ $('cloudPrintProxyManageButton').style.display = 'none';
+ } else {
+ $('cloudPrintProxySetupButton').textContent =
+ localStrings.getString('cloudPrintProxyEnabledButton');
+ $('cloudPrintProxyManageButton').style.display = 'inline';
+ }
+ $('cloudPrintProxySetupButton').disabled = !allowed;
+ }
+ };
+
+ AdvancedOptions.RemoveCloudPrintProxySection = function() {
+ if (!cr.isChromeOS) {
+ var proxySectionElm = $('cloud-print-proxy-section');
+ if (proxySectionElm)
+ proxySectionElm.parentNode.removeChild(proxySectionElm);
+ }
+ };
+
+ // Export
+ return {
+ AdvancedOptions: AdvancedOptions
+ };
+
+});
diff --git a/chrome/browser/resources/options2/alert_overlay.css b/chrome/browser/resources/options2/alert_overlay.css
new file mode 100644
index 0000000..de5c236
--- /dev/null
+++ b/chrome/browser/resources/options2/alert_overlay.css
@@ -0,0 +1,3 @@
+#alertOverlayMessage {
+ width: 400px;
+}
diff --git a/chrome/browser/resources/options2/alert_overlay.html b/chrome/browser/resources/options2/alert_overlay.html
new file mode 100644
index 0000000..3de00e7
--- /dev/null
+++ b/chrome/browser/resources/options2/alert_overlay.html
@@ -0,0 +1,12 @@
+<div id="alertOverlay" class="page" hidden>
+ <h1 id="alertOverlayTitle"></h1>
+ <div class="content-area">
+ <div id="alertOverlayMessage"></div>
+ </div>
+ <div class="action-area">
+ <div class="button-strip">
+ <button id="alertOverlayCancel" type="reset"></button>
+ <button id="alertOverlayOk" type="submit"></button>
+ </div>
+ </div>
+</div>
diff --git a/chrome/browser/resources/options2/alert_overlay.js b/chrome/browser/resources/options2/alert_overlay.js
new file mode 100644
index 0000000..eb1fb8e
--- /dev/null
+++ b/chrome/browser/resources/options2/alert_overlay.js
@@ -0,0 +1,144 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+ var OptionsPage = options.OptionsPage;
+
+ /**
+ * AlertOverlay class
+ * Encapsulated handling of a generic alert.
+ * @class
+ */
+ function AlertOverlay() {
+ OptionsPage.call(this, 'alertOverlay', '', 'alertOverlay');
+ }
+
+ cr.addSingletonGetter(AlertOverlay);
+
+ AlertOverlay.prototype = {
+ // Inherit AlertOverlay from OptionsPage.
+ __proto__: OptionsPage.prototype,
+
+ /**
+ * Whether the page can be shown. Used to make sure the page is only
+ * shown via AlertOverlay.Show(), and not via the address bar.
+ * @private
+ */
+ canShow_: false,
+
+ /**
+ * Initialize the page.
+ */
+ initializePage: function() {
+ // Call base class implementation to start preference initialization.
+ OptionsPage.prototype.initializePage.call(this);
+
+ var self = this;
+ $('alertOverlayOk').onclick = function(event) {
+ self.handleOK_();
+ };
+
+ $('alertOverlayCancel').onclick = function(event) {
+ self.handleCancel_();
+ };
+ },
+
+ /**
+ * Handle the 'ok' button. Clear the overlay and call the ok callback if
+ * available.
+ * @private
+ */
+ handleOK_: function() {
+ OptionsPage.closeOverlay();
+ if (this.okCallback != undefined) {
+ this.okCallback.call();
+ }
+ },
+
+ /**
+ * Handle the 'cancel' button. Clear the overlay and call the cancel
+ * callback if available.
+ * @private
+ */
+ handleCancel_: function() {
+ OptionsPage.closeOverlay();
+ if (this.cancelCallback != undefined) {
+ this.cancelCallback.call();
+ }
+ },
+
+ /**
+ * The page is getting hidden. Don't let it be shown again.
+ */
+ willHidePage: function() {
+ canShow_ = false;
+ },
+
+ /** @inheritDoc */
+ canShowPage: function() {
+ return this.canShow_;
+ },
+ };
+
+ /**
+ * Show an alert overlay with the given message, button titles, and
+ * callbacks.
+ * @param {string} title The alert title to display to the user.
+ * @param {string} message The alert message to display to the user.
+ * @param {string} okTitle The title of the OK button. If undefined or empty,
+ * no button is shown.
+ * @param {string} cancelTitle The title of the cancel button. If undefined or
+ * empty, no button is shown.
+ * @param {function} okCallback A function to be called when the user presses
+ * the ok button. The alert window will be closed automatically. Can be
+ * undefined.
+ * @param {function} cancelCallback A function to be called when the user
+ * presses the cancel button. The alert window will be closed
+ * automatically. Can be undefined.
+ */
+ AlertOverlay.show = function(
+ title, message, okTitle, cancelTitle, okCallback, cancelCallback) {
+ if (title != undefined) {
+ $('alertOverlayTitle').textContent = title;
+ $('alertOverlayTitle').style.display = 'block';
+ } else {
+ $('alertOverlayTitle').style.display = 'none';
+ }
+
+ if (message != undefined) {
+ $('alertOverlayMessage').textContent = message;
+ $('alertOverlayMessage').style.display = 'block';
+ } else {
+ $('alertOverlayMessage').style.display = 'none';
+ }
+
+ if (okTitle != undefined && okTitle != '') {
+ $('alertOverlayOk').textContent = okTitle;
+ $('alertOverlayOk').style.display = 'block';
+ } else {
+ $('alertOverlayOk').style.display = 'none';
+ }
+
+ if (cancelTitle != undefined && cancelTitle != '') {
+ $('alertOverlayCancel').textContent = cancelTitle;
+ $('alertOverlayCancel').style.display = 'inline';
+ } else {
+ $('alertOverlayCancel').style.display = 'none';
+ }
+
+ var alertOverlay = AlertOverlay.getInstance();
+ alertOverlay.okCallback = okCallback;
+ alertOverlay.cancelCallback = cancelCallback;
+ alertOverlay.canShow_ = true;
+
+ // Intentionally don't show the URL in the location bar as we don't want
+ // people trying to navigate here by hand.
+ OptionsPage.showPageByName('alertOverlay', false);
+ }
+
+ // Export
+ return {
+ AlertOverlay: AlertOverlay
+ };
+});
diff --git a/chrome/browser/resources/options2/autocomplete_list.js b/chrome/browser/resources/options2/autocomplete_list.js
new file mode 100644
index 0000000..2aeb195
--- /dev/null
+++ b/chrome/browser/resources/options2/autocomplete_list.js
@@ -0,0 +1,239 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+ const ArrayDataModel = cr.ui.ArrayDataModel;
+ const List = cr.ui.List;
+ const ListItem = cr.ui.ListItem;
+
+ /**
+ * Creates a new autocomplete list item.
+ * @param {Object} pageInfo The page this item represents.
+ * @constructor
+ * @extends {cr.ui.ListItem}
+ */
+ function AutocompleteListItem(pageInfo) {
+ var el = cr.doc.createElement('div');
+ el.pageInfo_ = pageInfo;
+ AutocompleteListItem.decorate(el);
+ return el;
+ }
+
+ /**
+ * Decorates an element as an autocomplete list item.
+ * @param {!HTMLElement} el The element to decorate.
+ */
+ AutocompleteListItem.decorate = function(el) {
+ el.__proto__ = AutocompleteListItem.prototype;
+ el.decorate();
+ };
+
+ AutocompleteListItem.prototype = {
+ __proto__: ListItem.prototype,
+
+ /** @inheritDoc */
+ decorate: function() {
+ ListItem.prototype.decorate.call(this);
+
+ var title = this.pageInfo_['title'];
+ var url = this.pageInfo_['displayURL'];
+ var titleEl = this.ownerDocument.createElement('span');
+ titleEl.className = 'title';
+ titleEl.textContent = title || url;
+ this.appendChild(titleEl);
+
+ if (title && title.length > 0 && url != title) {
+ var separatorEl = this.ownerDocument.createTextNode(' - ');
+ this.appendChild(separatorEl);
+
+ var urlEl = this.ownerDocument.createElement('span');
+ urlEl.className = 'url';
+ urlEl.textContent = url;
+ this.appendChild(urlEl);
+ }
+ },
+ };
+
+ /**
+ * Creates a new autocomplete list popup.
+ * @constructor
+ * @extends {cr.ui.List}
+ */
+ var AutocompleteList = cr.ui.define('list');
+
+ AutocompleteList.prototype = {
+ __proto__: List.prototype,
+
+ /**
+ * The text field the autocomplete popup is currently attached to, if any.
+ * @type {HTMLElement}
+ * @private
+ */
+ targetInput_: null,
+
+ /**
+ * Keydown event listener to attach to a text field.
+ * @type {Function}
+ * @private
+ */
+ textFieldKeyHandler_: null,
+
+ /**
+ * Input event listener to attach to a text field.
+ * @type {Function}
+ * @private
+ */
+ textFieldInputHandler_: null,
+
+ /**
+ * A function to call when new suggestions are needed.
+ * @type {Function}
+ * @private
+ */
+ suggestionUpdateRequestCallback_: null,
+
+ /** @inheritDoc */
+ decorate: function() {
+ List.prototype.decorate.call(this);
+ this.classList.add('autocomplete-suggestions');
+ this.selectionModel = new cr.ui.ListSingleSelectionModel;
+
+ this.textFieldKeyHandler_ = this.handleAutocompleteKeydown_.bind(this);
+ var self = this;
+ this.textFieldInputHandler_ = function(e) {
+ if (self.suggestionUpdateRequestCallback_)
+ self.suggestionUpdateRequestCallback_(self.targetInput_.value);
+ };
+ this.addEventListener('change', function(e) {
+ var input = self.targetInput;
+ if (!input || !self.selectedItem)
+ return;
+ input.value = self.selectedItem['url'];
+ // Programatically change the value won't trigger a change event, but
+ // clients are likely to want to know when changes happen, so fire one.
+ var changeEvent = document.createEvent('Event');
+ changeEvent.initEvent('change', true, true);
+ input.dispatchEvent(changeEvent);
+ });
+ // Start hidden; adding suggestions will unhide.
+ this.hidden = true;
+ },
+
+ /** @inheritDoc */
+ createItem: function(pageInfo) {
+ return new AutocompleteListItem(pageInfo);
+ },
+
+ /**
+ * The suggestions to show.
+ * @type {Array}
+ */
+ set suggestions(suggestions) {
+ this.dataModel = new ArrayDataModel(suggestions);
+ this.hidden = !this.targetInput_ || suggestions.length == 0;
+ },
+
+ /**
+ * A function to call when the attached input field's contents change.
+ * The function should take one string argument, which will be the text
+ * to autocomplete from.
+ * @type {Function}
+ */
+ set suggestionUpdateRequestCallback(callback) {
+ this.suggestionUpdateRequestCallback_ = callback;
+ },
+
+ /**
+ * Attaches the popup to the given input element. Requires
+ * that the input be wrapped in a block-level container of the same width.
+ * @param {HTMLElement} input The input element to attach to.
+ */
+ attachToInput: function(input) {
+ if (this.targetInput_ == input)
+ return;
+
+ this.detach();
+ this.targetInput_ = input;
+ this.style.width = input.getBoundingClientRect().width + 'px';
+ this.hidden = false; // Necessary for positionPopupAroundElement to work.
+ cr.ui.positionPopupAroundElement(input, this, cr.ui.AnchorType.BELOW)
+ // Start hidden; when the data model gets results the list will show.
+ this.hidden = true;
+
+ input.addEventListener('keydown', this.textFieldKeyHandler_, true);
+ input.addEventListener('input', this.textFieldInputHandler_);
+ },
+
+ /**
+ * Detaches the autocomplete popup from its current input element, if any.
+ */
+ detach: function() {
+ var input = this.targetInput_
+ if (!input)
+ return;
+
+ input.removeEventListener('keydown', this.textFieldKeyHandler_);
+ input.removeEventListener('input', this.textFieldInputHandler_);
+ this.targetInput_ = null;
+ this.suggestions = [];
+ },
+
+ /**
+ * Makes sure that the suggestion list matches the width of the input it is.
+ * attached to. Should be called any time the input is resized.
+ */
+ syncWidthToInput: function() {
+ var input = this.targetInput_
+ if (input)
+ this.style.width = input.getBoundingClientRect().width + 'px';
+ },
+
+ /**
+ * The text field the autocomplete popup is currently attached to, if any.
+ * @return {HTMLElement}
+ */
+ get targetInput() {
+ return this.targetInput_;
+ },
+
+ /**
+ * Handles input field key events that should be interpreted as autocomplete
+ * commands.
+ * @param {Event} event The keydown event.
+ * @private
+ */
+ handleAutocompleteKeydown_: function(event) {
+ if (this.hidden)
+ return;
+ var handled = false;
+ switch (event.keyIdentifier) {
+ case 'U+001B': // Esc
+ this.suggestions = [];
+ handled = true;
+ break;
+ case 'Enter':
+ var hadSelection = this.selectedItem != null;
+ this.suggestions = [];
+ // Only count the event as handled if a selection is being commited.
+ handled = hadSelection;
+ break;
+ case 'Up':
+ case 'Down':
+ this.dispatchEvent(event);
+ handled = true;
+ break;
+ }
+ // Don't let arrow keys affect the text field, or bubble up to, e.g.,
+ // an enclosing list item.
+ if (handled) {
+ event.preventDefault();
+ event.stopPropagation();
+ }
+ },
+ };
+
+ return {
+ AutocompleteList: AutocompleteList
+ };
+});
diff --git a/chrome/browser/resources/options2/autofill_edit_address_overlay.html b/chrome/browser/resources/options2/autofill_edit_address_overlay.html
new file mode 100644
index 0000000..43a13c7
--- /dev/null
+++ b/chrome/browser/resources/options2/autofill_edit_address_overlay.html
@@ -0,0 +1,99 @@
+<div id="autofill-edit-address-overlay" class="page" hidden>
+ <h1 id="autofill-address-title"></h1>
+ <div class="content-area">
+ <div id="autofill-name-labels">
+ <label for="first-name">
+ <span i18n-content="autofillFirstNameLabel"></span>
+ </label>
+ <label for="middle-name">
+ <span i18n-content="autofillMiddleNameLabel"></span>
+ </label>
+ <label for="last-name">
+ <span i18n-content="autofillLastNameLabel"></span>
+ </label>
+ </div>
+ <list id="full-name-list"></list>
+ <div class="input">
+ <label>
+ <div><span i18n-content="autofillCompanyNameLabel"></span></div>
+ <input id="company-name" type="text" class="autofill-form">
+ </label>
+ </div>
+ <div class="input">
+ <label>
+ <div><span i18n-content="autofillAddrLine1Label"></span></div>
+ <input id="addr-line-1" type="text" class="autofill-form">
+ </label>
+ </div>
+ <div class="input">
+ <label>
+ <div><span i18n-content="autofillAddrLine2Label"></span></div>
+ <input id="addr-line-2" type="text" class="autofill-form">
+ </label>
+ </div>
+ <div class="table">
+ <div class="row">
+ <div class="input cell">
+ <label for="city">
+ <span i18n-content="autofillCityLabel"></span>
+ </label>
+ </div>
+ <div class="input cell">
+ <label id="state-label" for="state"></label>
+ </div>
+ <div class="input cell">
+ <label id="postal-code-label" for="postal-code"></label>
+ </div>
+ </div>
+ <div class="row">
+ <div class="input cell">
+ <input id="city" type="text" class="autofill-form">
+ </div>
+ <div class="input cell">
+ <input id="state" type="text" class="autofill-form">
+ </div>
+ <div class="input cell">
+ <input id="postal-code" type="text" class="autofill-form">
+ </div>
+ </div>
+ </div>
+ <div class="input">
+ <label>
+ <div>
+ <span i18n-content="autofillCountryLabel"></span>
+ </div>
+ <select id="country"></select>
+ </label>
+ </div>
+ <div class="table">
+ <div class="row">
+ <div class="input cell">
+ <label for="phone-list">
+ <span i18n-content="autofillPhoneLabel"></span>
+ </label>
+ </div>
+ <div class="input cell">
+ <label for="email-list">
+ <span i18n-content="autofillEmailLabel"></span>
+ </label>
+ </div>
+ </div>
+ <div class="row">
+ <div class="input cell">
+ <list id="phone-list"
+ i18n-values="placeholder:autofillAddPhonePlaceholder"></list>
+ </div>
+ <div class="input cell">
+ <list id="email-list"
+ i18n-values="placeholder:autofillAddEmailPlaceholder"></list>
+ </div>
+ </div>
+ </div>
+ </div>
+ <div class="action-area button-strip">
+ <button id="autofill-edit-address-cancel-button" type="reset"
+ i18n-content="cancel"></button>
+ <button id="autofill-edit-address-apply-button" type="submit"
+ i18n-content="ok" disabled></button>
+ </div>
+</div>
diff --git a/chrome/browser/resources/options2/autofill_edit_address_overlay.js b/chrome/browser/resources/options2/autofill_edit_address_overlay.js
new file mode 100644
index 0000000..f41fd8c
--- /dev/null
+++ b/chrome/browser/resources/options2/autofill_edit_address_overlay.js
@@ -0,0 +1,325 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+ const OptionsPage = options.OptionsPage;
+ const ArrayDataModel = cr.ui.ArrayDataModel;
+
+ // The GUID of the loaded address.
+ var guid;
+
+ /**
+ * AutofillEditAddressOverlay class
+ * Encapsulated handling of the 'Add Page' overlay page.
+ * @class
+ */
+ function AutofillEditAddressOverlay() {
+ OptionsPage.call(this, 'autofillEditAddress',
+ templateData.autofillEditAddressTitle,
+ 'autofill-edit-address-overlay');
+ }
+
+ cr.addSingletonGetter(AutofillEditAddressOverlay);
+
+ AutofillEditAddressOverlay.prototype = {
+ __proto__: OptionsPage.prototype,
+
+ /**
+ * Initializes the page.
+ */
+ initializePage: function() {
+ OptionsPage.prototype.initializePage.call(this);
+
+ this.createMultiValueLists_();
+
+ var self = this;
+ $('autofill-edit-address-cancel-button').onclick = function(event) {
+ self.dismissOverlay_();
+ }
+ $('autofill-edit-address-apply-button').onclick = function(event) {
+ self.saveAddress_();
+ self.dismissOverlay_();
+ }
+
+ self.guid = '';
+ self.populateCountryList_();
+ self.clearInputFields_();
+ self.connectInputEvents_();
+ },
+
+ /**
+ * Creates, decorates and initializes the multi-value lists for full name,
+ * phone, and email.
+ * @private
+ */
+ createMultiValueLists_: function() {
+ var list = $('full-name-list');
+ options.autofillOptions.AutofillNameValuesList.decorate(list);
+ list.autoExpands = true;
+
+ list = $('phone-list');
+ options.autofillOptions.AutofillPhoneValuesList.decorate(list);
+ list.autoExpands = true;
+
+ list = $('email-list');
+ options.autofillOptions.AutofillValuesList.decorate(list);
+ list.autoExpands = true;
+ },
+
+ /**
+ * Updates the data model for the list named |listName| with the values from
+ * |entries|.
+ * @param {String} listName The id of the list.
+ * @param {Array} entries The list of items to be added to the list.
+ */
+ setMultiValueList_: function(listName, entries) {
+ // Add data entries.
+ var list = $(listName);
+ list.dataModel = new ArrayDataModel(entries);
+
+ // Add special entry for adding new values.
+ list.dataModel.splice(list.dataModel.length, 0, null);
+
+ // Update the status of the 'OK' button.
+ this.inputFieldChanged_();
+
+ var self = this;
+ list.dataModel.addEventListener(
+ 'splice', function(event) { self.inputFieldChanged_(); });
+ list.dataModel.addEventListener(
+ 'change', function(event) { self.inputFieldChanged_(); });
+ },
+
+ /**
+ * Updates the data model for the name list with the values from |entries|.
+ * @param {Array} names The list of names to be added to the list.
+ */
+ setNameList_: function(names) {
+ // Add the given |names| as backing data for the list.
+ var list = $('full-name-list');
+ list.dataModel = new ArrayDataModel(names);
+
+ // Add special entry for adding new values.
+ list.dataModel.splice(list.dataModel.length, 0, null);
+
+ var self = this;
+ list.dataModel.addEventListener(
+ 'splice', function(event) { self.inputFieldChanged_(); });
+ list.dataModel.addEventListener(
+ 'change', function(event) { self.inputFieldChanged_(); });
+ },
+
+ /**
+ * Clears any uncommitted input, resets the stored GUID and dismisses the
+ * overlay.
+ * @private
+ */
+ dismissOverlay_: function() {
+ this.clearInputFields_();
+ this.guid = '';
+ OptionsPage.closeOverlay();
+ },
+
+ /**
+ * Aggregates the values in the input fields into an array and sends the
+ * array to the Autofill handler.
+ * @private
+ */
+ saveAddress_: function() {
+ var address = new Array();
+ address[0] = this.guid;
+ var list = $('full-name-list');
+ address[1] = list.dataModel.slice(0, list.dataModel.length - 1);
+ address[2] = $('company-name').value;
+ address[3] = $('addr-line-1').value;
+ address[4] = $('addr-line-2').value;
+ address[5] = $('city').value;
+ address[6] = $('state').value;
+ address[7] = $('postal-code').value;
+ address[8] = $('country').value;
+ list = $('phone-list');
+ address[9] = list.dataModel.slice(0, list.dataModel.length - 1);
+ list = $('email-list');
+ address[10] = list.dataModel.slice(0, list.dataModel.length - 1);
+
+ chrome.send('setAddress', address);
+ },
+
+ /**
+ * Connects each input field to the inputFieldChanged_() method that enables
+ * or disables the 'Ok' button based on whether all the fields are empty or
+ * not.
+ * @private
+ */
+ connectInputEvents_: function() {
+ var self = this;
+ $('company-name').oninput = $('addr-line-1').oninput =
+ $('addr-line-2').oninput = $('city').oninput = $('state').oninput =
+ $('postal-code').oninput = function(event) {
+ self.inputFieldChanged_();
+ }
+
+ $('country').onchange = function(event) {
+ self.countryChanged_();
+ }
+ },
+
+ /**
+ * Checks the values of each of the input fields and disables the 'Ok'
+ * button if all of the fields are empty.
+ * @private
+ */
+ inputFieldChanged_: function() {
+ // Length of lists are tested for <= 1 due to the "add" placeholder item
+ // in the list.
+ var disabled =
+ $('full-name-list').items.length <= 1 &&
+ !$('company-name').value &&
+ !$('addr-line-1').value && !$('addr-line-2').value &&
+ !$('city').value && !$('state').value && !$('postal-code').value &&
+ !$('country').value && $('phone-list').items.length <= 1 &&
+ $('email-list').items.length <= 1;
+ $('autofill-edit-address-apply-button').disabled = disabled;
+ },
+
+ /**
+ * Updates the postal code and state field labels appropriately for the
+ * selected country.
+ * @private
+ */
+ countryChanged_: function() {
+ var countryCode = $('country').value;
+ if (!countryCode)
+ countryCode = templateData.defaultCountryCode;
+
+ var details = templateData.autofillCountryData[countryCode];
+ var postal = $('postal-code-label');
+ postal.textContent = details['postalCodeLabel'];
+ $('state-label').textContent = details['stateLabel'];
+
+ // Also update the 'Ok' button as needed.
+ this.inputFieldChanged_();
+ },
+
+ /**
+ * Populates the country <select> list.
+ * @private
+ */
+ populateCountryList_: function() {
+ var countryData = templateData.autofillCountryData;
+ var defaultCountryCode = templateData.defaultCountryCode;
+
+ // Build an array of the country names and their corresponding country
+ // codes, so that we can sort and insert them in order.
+ var countries = [];
+ for (var countryCode in countryData) {
+ var country = {
+ countryCode: countryCode,
+ name: countryData[countryCode]['name']
+ };
+ countries.push(country);
+ }
+
+ // Sort the countries in alphabetical order by name.
+ countries = countries.sort(function(a, b) {
+ return a.name < b.name ? -1 : 1;
+ });
+
+ // Insert the empty and default countries at the beginning of the array.
+ var emptyCountry = {
+ countryCode: '',
+ name: ''
+ };
+ var defaultCountry = {
+ countryCode: defaultCountryCode,
+ name: countryData[defaultCountryCode]['name']
+ };
+ var separator = {
+ countryCode: '',
+ name: '---',
+ disabled: true
+ }
+ countries.unshift(emptyCountry, defaultCountry, separator);
+
+ // Add the countries to the country <select> list.
+ var countryList = $('country');
+ for (var i = 0; i < countries.length; i++) {
+ var country = new Option(countries[i].name, countries[i].countryCode);
+ country.disabled = countries[i].disabled;
+ countryList.appendChild(country)
+ }
+ },
+
+ /**
+ * Clears the value of each input field.
+ * @private
+ */
+ clearInputFields_: function() {
+ this.setNameList_([]);
+ $('company-name').value = '';
+ $('addr-line-1').value = '';
+ $('addr-line-2').value = '';
+ $('city').value = '';
+ $('state').value = '';
+ $('postal-code').value = '';
+ $('country').value = '';
+ this.setMultiValueList_('phone-list', []);
+ this.setMultiValueList_('email-list', []);
+
+ this.countryChanged_();
+ },
+
+ /**
+ * Loads the address data from |address|, sets the input fields based on
+ * this data and stores the GUID of the address.
+ * @private
+ */
+ loadAddress_: function(address) {
+ this.setInputFields_(address);
+ this.inputFieldChanged_();
+ this.guid = address['guid'];
+ },
+
+ /**
+ * Sets the value of each input field according to |address|
+ * @private
+ */
+ setInputFields_: function(address) {
+ this.setNameList_(address['fullName']);
+ $('company-name').value = address['companyName'];
+ $('addr-line-1').value = address['addrLine1'];
+ $('addr-line-2').value = address['addrLine2'];
+ $('city').value = address['city'];
+ $('state').value = address['state'];
+ $('postal-code').value = address['postalCode'];
+ $('country').value = address['country'];
+ this.setMultiValueList_('phone-list', address['phone']);
+ this.setMultiValueList_('email-list', address['email']);
+
+ this.countryChanged_();
+ },
+ };
+
+ AutofillEditAddressOverlay.clearInputFields = function() {
+ AutofillEditAddressOverlay.getInstance().clearInputFields_();
+ };
+
+ AutofillEditAddressOverlay.loadAddress = function(address) {
+ AutofillEditAddressOverlay.getInstance().loadAddress_(address);
+ };
+
+ AutofillEditAddressOverlay.setTitle = function(title) {
+ $('autofill-address-title').textContent = title;
+ };
+
+ AutofillEditAddressOverlay.setValidatedPhoneNumbers = function(numbers) {
+ AutofillEditAddressOverlay.getInstance().setMultiValueList_('phone-list',
+ numbers);
+ };
+
+ // Export
+ return {
+ AutofillEditAddressOverlay: AutofillEditAddressOverlay
+ };
+});
diff --git a/chrome/browser/resources/options2/autofill_edit_creditcard_overlay.html b/chrome/browser/resources/options2/autofill_edit_creditcard_overlay.html
new file mode 100644
index 0000000..fa3825a
--- /dev/null
+++ b/chrome/browser/resources/options2/autofill_edit_creditcard_overlay.html
@@ -0,0 +1,30 @@
+<div id="autofill-edit-credit-card-overlay" class="page" hidden>
+ <h1 id="autofill-credit-card-title"></h1>
+ <div class="content-area">
+ <div class="input">
+ <label>
+ <span i18n-content="nameOnCardLabel"></span><br>
+ <input id="name-on-card" type="text">
+ </label>
+ </div>
+ <div class="input">
+ <label>
+ <span i18n-content="creditCardNumberLabel"></span><br>
+ <input id="credit-card-number" type="text">
+ </label>
+ </div>
+ <div class="input">
+ <label>
+ <span i18n-content="creditCardExpirationDateLabel"></span><br>
+ <select id="expiration-month"></select>
+ <select id="expiration-year"></select>
+ </label>
+ </div>
+ </div>
+ <div class="action-area button-strip">
+ <button id="autofill-edit-credit-card-cancel-button" type="reset"
+ i18n-content="cancel"></button>
+ <button id="autofill-edit-credit-card-apply-button" type="submit"
+ i18n-content="ok" disabled></button>
+ </div>
+</div>
diff --git a/chrome/browser/resources/options2/autofill_edit_creditcard_overlay.js b/chrome/browser/resources/options2/autofill_edit_creditcard_overlay.js
new file mode 100644
index 0000000..8402a10
--- /dev/null
+++ b/chrome/browser/resources/options2/autofill_edit_creditcard_overlay.js
@@ -0,0 +1,205 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+ const OptionsPage = options.OptionsPage;
+
+ // The GUID of the loaded credit card.
+ var guid_;
+
+ /**
+ * AutofillEditCreditCardOverlay class
+ * Encapsulated handling of the 'Add Page' overlay page.
+ * @class
+ */
+ function AutofillEditCreditCardOverlay() {
+ OptionsPage.call(this, 'autofillEditCreditCard',
+ templateData.autofillEditCreditCardTitle,
+ 'autofill-edit-credit-card-overlay');
+ }
+
+ cr.addSingletonGetter(AutofillEditCreditCardOverlay);
+
+ AutofillEditCreditCardOverlay.prototype = {
+ __proto__: OptionsPage.prototype,
+
+ /**
+ * Initializes the page.
+ */
+ initializePage: function() {
+ OptionsPage.prototype.initializePage.call(this);
+
+ var self = this;
+ $('autofill-edit-credit-card-cancel-button').onclick = function(event) {
+ self.dismissOverlay_();
+ }
+ $('autofill-edit-credit-card-apply-button').onclick = function(event) {
+ self.saveCreditCard_();
+ self.dismissOverlay_();
+ }
+
+ self.guid_ = '';
+ self.hasEditedNumber_ = false;
+ self.clearInputFields_();
+ self.connectInputEvents_();
+ self.setDefaultSelectOptions_();
+ },
+
+ /**
+ * Clears any uncommitted input, and dismisses the overlay.
+ * @private
+ */
+ dismissOverlay_: function() {
+ this.clearInputFields_();
+ this.guid_ = '';
+ this.hasEditedNumber_ = false;
+ OptionsPage.closeOverlay();
+ },
+
+ /**
+ * Aggregates the values in the input fields into an array and sends the
+ * array to the Autofill handler.
+ * @private
+ */
+ saveCreditCard_: function() {
+ var creditCard = new Array(5);
+ creditCard[0] = this.guid_;
+ creditCard[1] = $('name-on-card').value;
+ creditCard[2] = $('credit-card-number').value;
+ creditCard[3] = $('expiration-month').value;
+ creditCard[4] = $('expiration-year').value;
+ chrome.send('setCreditCard', creditCard);
+ },
+
+ /**
+ * Connects each input field to the inputFieldChanged_() method that enables
+ * or disables the 'Ok' button based on whether all the fields are empty or
+ * not.
+ * @private
+ */
+ connectInputEvents_: function() {
+ var ccNumber = $('credit-card-number');
+ $('name-on-card').oninput = ccNumber.oninput =
+ $('expiration-month').onchange = $('expiration-year').onchange =
+ this.inputFieldChanged_.bind(this);
+ },
+
+ /**
+ * Checks the values of each of the input fields and disables the 'Ok'
+ * button if all of the fields are empty.
+ * @param {Event} opt_event Optional data for the 'input' event.
+ * @private
+ */
+ inputFieldChanged_: function(opt_event) {
+ var disabled = !$('name-on-card').value && !$('credit-card-number').value;
+ $('autofill-edit-credit-card-apply-button').disabled = disabled;
+ },
+
+ /**
+ * Sets the default values of the options in the 'Expiration date' select
+ * controls.
+ * @private
+ */
+ setDefaultSelectOptions_: function() {
+ // Set the 'Expiration month' default options.
+ var expirationMonth = $('expiration-month');
+ expirationMonth.options.length = 0;
+ for (var i = 1; i <= 12; ++i) {
+ var text;
+ if (i < 10)
+ text = '0' + i;
+ else
+ text = i;
+
+ var option = document.createElement('option');
+ option.text = text;
+ option.value = text;
+ expirationMonth.add(option, null);
+ }
+
+ // Set the 'Expiration year' default options.
+ var expirationYear = $('expiration-year');
+ expirationYear.options.length = 0;
+
+ var date = new Date();
+ var year = parseInt(date.getFullYear());
+ for (var i = 0; i < 10; ++i) {
+ var text = year + i;
+ var option = document.createElement('option');
+ option.text = text;
+ option.value = text;
+ expirationYear.add(option, null);
+ }
+ },
+
+ /**
+ * Clears the value of each input field.
+ * @private
+ */
+ clearInputFields_: function() {
+ $('name-on-card').value = '';
+ $('credit-card-number').value = '';
+ $('expiration-month').selectedIndex = 0;
+ $('expiration-year').selectedIndex = 0;
+
+ // Reset the enabled status of the 'Ok' button.
+ this.inputFieldChanged_();
+ },
+
+ /**
+ * Sets the value of each input field according to |creditCard|
+ * @private
+ */
+ setInputFields_: function(creditCard) {
+ $('name-on-card').value = creditCard['nameOnCard'];
+ $('credit-card-number').value = creditCard['creditCardNumber'];
+
+ // The options for the year select control may be out-dated at this point,
+ // e.g. the user opened the options page before midnight on New Year's Eve
+ // and then loaded a credit card profile to edit in the new year, so
+ // reload the select options just to be safe.
+ this.setDefaultSelectOptions_();
+
+ var idx = parseInt(creditCard['expirationMonth'], 10);
+ $('expiration-month').selectedIndex = idx - 1;
+
+ expYear = creditCard['expirationYear'];
+ var date = new Date();
+ var year = parseInt(date.getFullYear());
+ for (var i = 0; i < 10; ++i) {
+ var text = year + i;
+ if (expYear == String(text))
+ $('expiration-year').selectedIndex = i;
+ }
+ },
+
+ /**
+ * Loads the credit card data from |creditCard|, sets the input fields based
+ * on this data and stores the GUID of the credit card.
+ * @private
+ */
+ loadCreditCard_: function(creditCard) {
+ this.setInputFields_(creditCard);
+ this.inputFieldChanged_();
+ this.guid_ = creditCard['guid'];
+ },
+ };
+
+ AutofillEditCreditCardOverlay.clearInputFields = function(title) {
+ AutofillEditCreditCardOverlay.getInstance().clearInputFields_();
+ };
+
+ AutofillEditCreditCardOverlay.loadCreditCard = function(creditCard) {
+ AutofillEditCreditCardOverlay.getInstance().loadCreditCard_(creditCard);
+ };
+
+ AutofillEditCreditCardOverlay.setTitle = function(title) {
+ $('autofill-credit-card-title').textContent = title;
+ };
+
+ // Export
+ return {
+ AutofillEditCreditCardOverlay: AutofillEditCreditCardOverlay
+ };
+});
diff --git a/chrome/browser/resources/options2/autofill_options.css b/chrome/browser/resources/options2/autofill_options.css
new file mode 100644
index 0000000..55521b27
--- /dev/null
+++ b/chrome/browser/resources/options2/autofill_options.css
@@ -0,0 +1,37 @@
+.autofill-list-item {
+ -webkit-box-flex: 1;
+ -webkit-padding-start: 8px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.autofill-list-item + img {
+ -webkit-padding-end: 20px;
+ vertical-align: middle;
+}
+
+#autofill-options > div:last-child {
+ margin-top: 15px;
+}
+
+#autofill-options > div.settings-list > div:last-child {
+ border-top: 1px solid #d9d9d9;
+ padding: 5px 10px;
+}
+
+#autofill-add-address,
+#autofill-add-creditcard {
+ margin: 2px 0;
+}
+
+.autofill-list-item + button,
+.autofill-list-item + img + button {
+ background: #8aaaed !important; /* Gets overwritten by raw-button:hover */
+ color: #fff;
+ margin-top: 0;
+}
+
+#address-list > div:not(:hover) * button,
+#creditcard-list > div:not(:hover) * button {
+ display: none;
+}
diff --git a/chrome/browser/resources/options2/autofill_options.html b/chrome/browser/resources/options2/autofill_options.html
new file mode 100644
index 0000000..bfbd175
--- /dev/null
+++ b/chrome/browser/resources/options2/autofill_options.html
@@ -0,0 +1,38 @@
+<div id="autofill-options" class="page" hidden>
+ <h1 i18n-content="autofillOptionsPage"></h1>
+<if expr="os == 'darwin'">
+ <div class="checkbox">
+ <label>
+ <input pref="autofill.auxiliary_profiles_enabled" type="checkbox"
+ metric="Options_AutofillAuxiliaryProfiles">
+ <span i18n-content="auxiliaryProfilesEnabled"></span>
+ </label>
+ </div>
+</if>
+ <h3 i18n-content="autofillAddresses"></h3>
+ <div class="settings-list">
+ <list id="address-list"></list>
+ <div>
+ <button id="autofill-add-address" i18n-content="autofillAddAddress">
+ </button>
+ </div>
+ </div>
+ <h3 i18n-content="autofillCreditCards"></h3>
+ <div class="settings-list">
+ <list id="creditcard-list"></list>
+ <div>
+ <button id="autofill-add-creditcard"
+ i18n-content="autofillAddCreditCard"></button>
+ </div>
+ </div>
+ <div>
+<if expr="pp_ifdef('chromeos')">
+ <a href="https://www.google.com/support/chromeos/bin/answer.py?answer=142893"
+ target="_blank" i18n-content="helpButton"></a>
+</if>
+<if expr="not pp_ifdef('chromeos')">
+ <a href="https://www.google.com/support/chrome/bin/answer.py?answer=142893"
+ target="_blank" i18n-content="helpButton"></a>
+</if>
+ </div>
+</div>
diff --git a/chrome/browser/resources/options2/autofill_options.js b/chrome/browser/resources/options2/autofill_options.js
new file mode 100644
index 0000000..0b3a305
--- /dev/null
+++ b/chrome/browser/resources/options2/autofill_options.js
@@ -0,0 +1,230 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+ const OptionsPage = options.OptionsPage;
+ const ArrayDataModel = cr.ui.ArrayDataModel;
+
+ /////////////////////////////////////////////////////////////////////////////
+ // AutofillOptions class:
+
+ /**
+ * Encapsulated handling of Autofill options page.
+ * @constructor
+ */
+ function AutofillOptions() {
+ OptionsPage.call(this,
+ 'autofill',
+ templateData.autofillOptionsPageTabTitle,
+ 'autofill-options');
+ }
+
+ cr.addSingletonGetter(AutofillOptions);
+
+ AutofillOptions.prototype = {
+ __proto__: OptionsPage.prototype,
+
+ /**
+ * The address list.
+ * @type {DeletableItemList}
+ * @private
+ */
+ addressList_: null,
+
+ /**
+ * The credit card list.
+ * @type {DeletableItemList}
+ * @private
+ */
+ creditCardList_: null,
+
+ initializePage: function() {
+ OptionsPage.prototype.initializePage.call(this);
+
+ this.createAddressList_();
+ this.createCreditCardList_();
+
+ var self = this;
+ $('autofill-add-address').onclick = function(event) {
+ self.showAddAddressOverlay_();
+ };
+ $('autofill-add-creditcard').onclick = function(event) {
+ self.showAddCreditCardOverlay_();
+ };
+
+ // TODO(jhawkins): What happens when Autofill is disabled whilst on the
+ // Autofill options page?
+ },
+
+ /**
+ * Creates, decorates and initializes the address list.
+ * @private
+ */
+ createAddressList_: function() {
+ this.addressList_ = $('address-list');
+ options.autofillOptions.AutofillAddressList.decorate(this.addressList_);
+ this.addressList_.autoExpands = true;
+ },
+
+ /**
+ * Creates, decorates and initializes the credit card list.
+ * @private
+ */
+ createCreditCardList_: function() {
+ this.creditCardList_ = $('creditcard-list');
+ options.autofillOptions.AutofillCreditCardList.decorate(
+ this.creditCardList_);
+ this.creditCardList_.autoExpands = true;
+ },
+
+ /**
+ * Shows the 'Add address' overlay, specifically by loading the
+ * 'Edit address' overlay, emptying the input fields and modifying the
+ * overlay title.
+ * @private
+ */
+ showAddAddressOverlay_: function() {
+ var title = localStrings.getString('addAddressTitle');
+ AutofillEditAddressOverlay.setTitle(title);
+ AutofillEditAddressOverlay.clearInputFields();
+ OptionsPage.navigateToPage('autofillEditAddress');
+ },
+
+ /**
+ * Shows the 'Add credit card' overlay, specifically by loading the
+ * 'Edit credit card' overlay, emptying the input fields and modifying the
+ * overlay title.
+ * @private
+ */
+ showAddCreditCardOverlay_: function() {
+ var title = localStrings.getString('addCreditCardTitle');
+ AutofillEditCreditCardOverlay.setTitle(title);
+ AutofillEditCreditCardOverlay.clearInputFields();
+ OptionsPage.navigateToPage('autofillEditCreditCard');
+ },
+
+ /**
+ * Updates the data model for the address list with the values from
+ * |entries|.
+ * @param {Array} entries The list of addresses.
+ */
+ setAddressList_: function(entries) {
+ this.addressList_.dataModel = new ArrayDataModel(entries);
+ },
+
+ /**
+ * Updates the data model for the credit card list with the values from
+ * |entries|.
+ * @param {Array} entries The list of credit cards.
+ */
+ setCreditCardList_: function(entries) {
+ this.creditCardList_.dataModel = new ArrayDataModel(entries);
+ },
+
+ /**
+ * Removes the Autofill address represented by |guid|.
+ * @param {String} guid The GUID of the address to remove.
+ * @private
+ */
+ removeAddress_: function(guid) {
+ chrome.send('removeAddress', [guid]);
+ },
+
+ /**
+ * Removes the Autofill credit card represented by |guid|.
+ * @param {String} guid The GUID of the credit card to remove.
+ * @private
+ */
+ removeCreditCard_: function(guid) {
+ chrome.send('removeCreditCard', [guid]);
+ },
+
+ /**
+ * Requests profile data for the address represented by |guid| from the
+ * PersonalDataManager. Once the data is loaded, the AutofillOptionsHandler
+ * calls showEditAddressOverlay().
+ * @param {String} guid The GUID of the address to edit.
+ * @private
+ */
+ loadAddressEditor_: function(guid) {
+ chrome.send('loadAddressEditor', [guid]);
+ },
+
+ /**
+ * Requests profile data for the credit card represented by |guid| from the
+ * PersonalDataManager. Once the data is loaded, the AutofillOptionsHandler
+ * calls showEditCreditCardOverlay().
+ * @param {String} guid The GUID of the credit card to edit.
+ * @private
+ */
+ loadCreditCardEditor_: function(guid) {
+ chrome.send('loadCreditCardEditor', [guid]);
+ },
+
+ /**
+ * Shows the 'Edit address' overlay, using the data in |address| to fill the
+ * input fields. |address| is a list with one item, an associative array
+ * that contains the address data.
+ * @private
+ */
+ showEditAddressOverlay_: function(address) {
+ var title = localStrings.getString('editAddressTitle');
+ AutofillEditAddressOverlay.setTitle(title);
+ AutofillEditAddressOverlay.loadAddress(address);
+ OptionsPage.navigateToPage('autofillEditAddress');
+ },
+
+ /**
+ * Shows the 'Edit credit card' overlay, using the data in |credit_card| to
+ * fill the input fields. |address| is a list with one item, an associative
+ * array that contains the credit card data.
+ * @private
+ */
+ showEditCreditCardOverlay_: function(creditCard) {
+ var title = localStrings.getString('editCreditCardTitle');
+ AutofillEditCreditCardOverlay.setTitle(title);
+ AutofillEditCreditCardOverlay.loadCreditCard(creditCard);
+ OptionsPage.navigateToPage('autofillEditCreditCard');
+ },
+ };
+
+ AutofillOptions.setAddressList = function(entries) {
+ AutofillOptions.getInstance().setAddressList_(entries);
+ };
+
+ AutofillOptions.setCreditCardList = function(entries) {
+ AutofillOptions.getInstance().setCreditCardList_(entries);
+ };
+
+ AutofillOptions.removeAddress = function(guid) {
+ AutofillOptions.getInstance().removeAddress_(guid);
+ };
+
+ AutofillOptions.removeCreditCard = function(guid) {
+ AutofillOptions.getInstance().removeCreditCard_(guid);
+ };
+
+ AutofillOptions.loadAddressEditor = function(guid) {
+ AutofillOptions.getInstance().loadAddressEditor_(guid);
+ };
+
+ AutofillOptions.loadCreditCardEditor = function(guid) {
+ AutofillOptions.getInstance().loadCreditCardEditor_(guid);
+ };
+
+ AutofillOptions.editAddress = function(address) {
+ AutofillOptions.getInstance().showEditAddressOverlay_(address);
+ };
+
+ AutofillOptions.editCreditCard = function(creditCard) {
+ AutofillOptions.getInstance().showEditCreditCardOverlay_(creditCard);
+ };
+
+ // Export
+ return {
+ AutofillOptions: AutofillOptions
+ };
+
+});
+
diff --git a/chrome/browser/resources/options2/autofill_options_list.js b/chrome/browser/resources/options2/autofill_options_list.js
new file mode 100644
index 0000000..c9ed67c
--- /dev/null
+++ b/chrome/browser/resources/options2/autofill_options_list.js
@@ -0,0 +1,506 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options.autofillOptions', function() {
+ const DeletableItem = options.DeletableItem;
+ const DeletableItemList = options.DeletableItemList;
+ const InlineEditableItem = options.InlineEditableItem;
+ const InlineEditableItemList = options.InlineEditableItemList;
+
+ function AutofillEditProfileButton(guid, edit) {
+ var editButtonEl = document.createElement('button');
+ editButtonEl.className = 'raw-button custom-appearance';
+ editButtonEl.textContent =
+ templateData.autofillEditProfileButton;
+ editButtonEl.onclick = function(e) { edit(guid); };
+
+ // Don't select the row when clicking the button.
+ editButtonEl.onmousedown = function(e) {
+ e.stopPropagation();
+ };
+
+ return editButtonEl;
+ }
+
+ /**
+ * Creates a new address list item.
+ * @param {Array} entry An array of the form [guid, label].
+ * @constructor
+ * @extends {options.DeletableItem}
+ */
+ function AddressListItem(entry) {
+ var el = cr.doc.createElement('div');
+ el.guid = entry[0];
+ el.label = entry[1];
+ el.__proto__ = AddressListItem.prototype;
+ el.decorate();
+
+ return el;
+ }
+
+ AddressListItem.prototype = {
+ __proto__: DeletableItem.prototype,
+
+ /** @inheritDoc */
+ decorate: function() {
+ DeletableItem.prototype.decorate.call(this);
+
+ // The stored label.
+ var label = this.ownerDocument.createElement('div');
+ label.className = 'autofill-list-item';
+ label.textContent = this.label;
+ this.contentElement.appendChild(label);
+
+ // The 'Edit' button.
+ var editButtonEl = new AutofillEditProfileButton(
+ this.guid,
+ AutofillOptions.loadAddressEditor);
+ this.contentElement.appendChild(editButtonEl);
+ },
+ };
+
+ /**
+ * Creates a new credit card list item.
+ * @param {Array} entry An array of the form [guid, label, icon].
+ * @constructor
+ * @extends {options.DeletableItem}
+ */
+ function CreditCardListItem(entry) {
+ var el = cr.doc.createElement('div');
+ el.guid = entry[0];
+ el.label = entry[1];
+ el.icon = entry[2];
+ el.description = entry[3];
+ el.__proto__ = CreditCardListItem.prototype;
+ el.decorate();
+
+ return el;
+ }
+
+ CreditCardListItem.prototype = {
+ __proto__: DeletableItem.prototype,
+
+ /** @inheritDoc */
+ decorate: function() {
+ DeletableItem.prototype.decorate.call(this);
+
+ // The stored label.
+ var label = this.ownerDocument.createElement('div');
+ label.className = 'autofill-list-item';
+ label.textContent = this.label;
+ this.contentElement.appendChild(label);
+
+ // The credit card icon.
+ var icon = this.ownerDocument.createElement('image');
+ icon.src = this.icon;
+ icon.alt = this.description;
+ this.contentElement.appendChild(icon);
+
+ // The 'Edit' button.
+ var editButtonEl = new AutofillEditProfileButton(
+ this.guid,
+ AutofillOptions.loadCreditCardEditor);
+ this.contentElement.appendChild(editButtonEl);
+ },
+ };
+
+ /**
+ * Creates a new value list item.
+ * @param {AutofillValuesList} list The parent list of this item.
+ * @param {String} entry A string value.
+ * @constructor
+ * @extends {options.InlineEditableItem}
+ */
+ function ValuesListItem(list, entry) {
+ var el = cr.doc.createElement('div');
+ el.list = list;
+ el.value = entry ? entry : '';
+ el.__proto__ = ValuesListItem.prototype;
+ el.decorate();
+
+ return el;
+ }
+
+ ValuesListItem.prototype = {
+ __proto__: InlineEditableItem.prototype,
+
+ /** @inheritDoc */
+ decorate: function() {
+ InlineEditableItem.prototype.decorate.call(this);
+
+ // Note: This must be set prior to calling |createEditableTextCell|.
+ this.isPlaceholder = !this.value;
+
+ // The stored value.
+ var cell = this.createEditableTextCell(this.value);
+ this.contentElement.appendChild(cell);
+ this.input = cell.querySelector('input');
+
+ if (this.isPlaceholder) {
+ this.input.placeholder = this.list.getAttribute('placeholder');
+ this.deletable = false;
+ }
+
+ this.addEventListener('commitedit', this.onEditCommitted_);
+ },
+
+ /**
+ * @return This item's value.
+ * @protected
+ */
+ value_: function() {
+ return this.input.value;
+ },
+
+ /**
+ * @param {Object} value The value to test.
+ * @return true if the given value is non-empty.
+ * @protected
+ */
+ valueIsNonEmpty_: function(value) {
+ return !!value;
+ },
+
+ /**
+ * @return true if value1 is logically equal to value2.
+ */
+ valuesAreEqual_: function(value1, value2) {
+ return value1 === value2;
+ },
+
+ /**
+ * Clears the item's value.
+ * @protected
+ */
+ clearValue_: function() {
+ this.input.value = '';
+ },
+
+ /**
+ * Called when committing an edit.
+ * If this is an "Add ..." item, committing a non-empty value adds that
+ * value to the end of the values list, but also leaves this "Add ..." item
+ * in place.
+ * @param {Event} e The end event.
+ * @private
+ */
+ onEditCommitted_: function(e) {
+ var value = this.value_();
+ var i = this.list.items.indexOf(this);
+ if (i < this.list.dataModel.length &&
+ this.valuesAreEqual_(value, this.list.dataModel.item(i))) {
+ return;
+ }
+
+ var entries = this.list.dataModel.slice();
+ if (this.valueIsNonEmpty_(value) &&
+ !entries.some(this.valuesAreEqual_.bind(this, value))) {
+ // Update with new value.
+ if (this.isPlaceholder) {
+ // It is important that updateIndex is done before validateAndSave.
+ // Otherwise we can not be sure about AddRow index.
+ this.list.dataModel.updateIndex(i);
+ this.list.validateAndSave(i, 0, value);
+ } else {
+ this.list.validateAndSave(i, 1, value);
+ }
+ } else {
+ // Reject empty values and duplicates.
+ if (!this.isPlaceholder)
+ this.list.dataModel.splice(i, 1);
+ else
+ this.clearValue_();
+ }
+ },
+ };
+
+ /**
+ * Creates a new name value list item.
+ * @param {AutofillNameValuesList} list The parent list of this item.
+ * @param {array} entry An array of [first, middle, last] names.
+ * @constructor
+ * @extends {options.ValuesListItem}
+ */
+ function NameListItem(list, entry) {
+ var el = cr.doc.createElement('div');
+ el.list = list;
+ el.first = entry ? entry[0] : '';
+ el.middle = entry ? entry[1] : '';
+ el.last = entry ? entry[2] : '';
+ el.__proto__ = NameListItem.prototype;
+ el.decorate();
+
+ return el;
+ }
+
+ NameListItem.prototype = {
+ __proto__: ValuesListItem.prototype,
+
+ /** @inheritDoc */
+ decorate: function() {
+ InlineEditableItem.prototype.decorate.call(this);
+
+ // Note: This must be set prior to calling |createEditableTextCell|.
+ this.isPlaceholder = !this.first && !this.middle && !this.last;
+
+ // The stored value.
+ // For the simulated static "input element" to display correctly, the
+ // value must not be empty. We use a space to force the UI to render
+ // correctly when the value is logically empty.
+ var cell = this.createEditableTextCell(this.first);
+ this.contentElement.appendChild(cell);
+ this.firstNameInput = cell.querySelector('input');
+
+ cell = this.createEditableTextCell(this.middle);
+ this.contentElement.appendChild(cell);
+ this.middleNameInput = cell.querySelector('input');
+
+ cell = this.createEditableTextCell(this.last);
+ this.contentElement.appendChild(cell);
+ this.lastNameInput = cell.querySelector('input');
+
+ if (this.isPlaceholder) {
+ this.firstNameInput.placeholder =
+ templateData.autofillAddFirstNamePlaceholder;
+ this.middleNameInput.placeholder =
+ templateData.autofillAddMiddleNamePlaceholder;
+ this.lastNameInput.placeholder =
+ templateData.autofillAddLastNamePlaceholder;
+ this.deletable = false;
+ }
+
+ this.addEventListener('commitedit', this.onEditCommitted_);
+ },
+
+ /** @inheritDoc */
+ value_: function() {
+ return [ this.firstNameInput.value,
+ this.middleNameInput.value,
+ this.lastNameInput.value ];
+ },
+
+ /** @inheritDoc */
+ valueIsNonEmpty_: function(value) {
+ return value[0] || value[1] || value[2];
+ },
+
+ /** @inheritDoc */
+ valuesAreEqual_: function(value1, value2) {
+ // First, check for null values.
+ if (!value1 || !value2)
+ return value1 == value2;
+
+ return value1[0] === value2[0] &&
+ value1[1] === value2[1] &&
+ value1[2] === value2[2];
+ },
+
+ /** @inheritDoc */
+ clearValue_: function() {
+ this.firstNameInput.value = '';
+ this.middleNameInput.value = '';
+ this.lastNameInput.value = '';
+ },
+ };
+
+ /**
+ * Base class for shared implementation between address and credit card lists.
+ * @constructor
+ * @extends {options.DeletableItemList}
+ */
+ var AutofillProfileList = cr.ui.define('list');
+
+ AutofillProfileList.prototype = {
+ __proto__: DeletableItemList.prototype,
+
+ decorate: function() {
+ DeletableItemList.prototype.decorate.call(this);
+
+ this.addEventListener('blur', this.onBlur_);
+ },
+
+ /**
+ * When the list loses focus, unselect all items in the list.
+ * @private
+ */
+ onBlur_: function() {
+ this.selectionModel.unselectAll();
+ },
+ };
+
+ /**
+ * Create a new address list.
+ * @constructor
+ * @extends {options.AutofillProfileList}
+ */
+ var AutofillAddressList = cr.ui.define('list');
+
+ AutofillAddressList.prototype = {
+ __proto__: AutofillProfileList.prototype,
+
+ decorate: function() {
+ AutofillProfileList.prototype.decorate.call(this);
+ },
+
+ /** @inheritDoc */
+ activateItemAtIndex: function(index) {
+ AutofillOptions.loadAddressEditor(this.dataModel.item(index)[0]);
+ },
+
+ /** @inheritDoc */
+ createItem: function(entry) {
+ return new AddressListItem(entry);
+ },
+
+ /** @inheritDoc */
+ deleteItemAtIndex: function(index) {
+ AutofillOptions.removeAddress(this.dataModel.item(index)[0]);
+ },
+ };
+
+ /**
+ * Create a new credit card list.
+ * @constructor
+ * @extends {options.DeletableItemList}
+ */
+ var AutofillCreditCardList = cr.ui.define('list');
+
+ AutofillCreditCardList.prototype = {
+ __proto__: AutofillProfileList.prototype,
+
+ decorate: function() {
+ AutofillProfileList.prototype.decorate.call(this);
+ },
+
+ /** @inheritDoc */
+ activateItemAtIndex: function(index) {
+ AutofillOptions.loadCreditCardEditor(this.dataModel.item(index)[0]);
+ },
+
+ /** @inheritDoc */
+ createItem: function(entry) {
+ return new CreditCardListItem(entry);
+ },
+
+ /** @inheritDoc */
+ deleteItemAtIndex: function(index) {
+ AutofillOptions.removeCreditCard(this.dataModel.item(index)[0]);
+ },
+ };
+
+ /**
+ * Create a new value list.
+ * @constructor
+ * @extends {options.InlineEditableItemList}
+ */
+ var AutofillValuesList = cr.ui.define('list');
+
+ AutofillValuesList.prototype = {
+ __proto__: InlineEditableItemList.prototype,
+
+ /** @inheritDoc */
+ createItem: function(entry) {
+ return new ValuesListItem(this, entry);
+ },
+
+ /** @inheritDoc */
+ deleteItemAtIndex: function(index) {
+ this.dataModel.splice(index, 1);
+ },
+
+ /** @inheritDoc */
+ shouldFocusPlaceholder: function() {
+ return false;
+ },
+
+ /**
+ * Called when the list hierarchy as a whole loses or gains focus.
+ * If the list was focused in response to a mouse click, call into the
+ * superclass's implementation. If the list was focused in response to a
+ * keyboard navigation, focus the first item.
+ * If the list loses focus, unselect all the elements.
+ * @param {Event} e The change event.
+ * @private
+ */
+ handleListFocusChange_: function(e) {
+ // We check to see whether there is a selected item as a proxy for
+ // distinguishing between mouse- and keyboard-originated focus events.
+ var selectedItem = this.selectedItem;
+ if (selectedItem)
+ InlineEditableItemList.prototype.handleListFocusChange_.call(this, e);
+
+ if (!e.newValue) {
+ // When the list loses focus, unselect all the elements.
+ this.selectionModel.unselectAll();
+ } else {
+ // When the list gains focus, select the first item if nothing else is
+ // selected.
+ var firstItem = this.getListItemByIndex(0);
+ if (!selectedItem && firstItem && e.newValue)
+ firstItem.handleFocus_();
+ }
+ },
+
+ /**
+ * Called when a new list item should be validated; subclasses are
+ * responsible for implementing if validation is required.
+ * @param {number} index The index of the item that was inserted or changed.
+ * @param {number} remove The number items to remove.
+ * @param {string} value The value of the item to insert.
+ */
+ validateAndSave: function(index, remove, value) {
+ this.dataModel.splice(index, remove, value);
+ },
+ };
+
+ /**
+ * Create a new value list for phone number validation.
+ * @constructor
+ * @extends {options.AutofillValuesList}
+ */
+ var AutofillNameValuesList = cr.ui.define('list');
+
+ AutofillNameValuesList.prototype = {
+ __proto__: AutofillValuesList.prototype,
+
+ /** @inheritDoc */
+ createItem: function(entry) {
+ return new NameListItem(this, entry);
+ },
+ };
+
+ /**
+ * Create a new value list for phone number validation.
+ * @constructor
+ * @extends {options.AutofillValuesList}
+ */
+ var AutofillPhoneValuesList = cr.ui.define('list');
+
+ AutofillPhoneValuesList.prototype = {
+ __proto__: AutofillValuesList.prototype,
+
+ /** @inheritDoc */
+ validateAndSave: function(index, remove, value) {
+ var numbers = this.dataModel.slice(0, this.dataModel.length - 1);
+ numbers.splice(index, remove, value);
+ var info = new Array();
+ info[0] = index;
+ info[1] = numbers;
+ info[2] = $('country').value;
+ chrome.send('validatePhoneNumbers', info);
+ },
+ };
+
+ return {
+ AddressListItem: AddressListItem,
+ CreditCardListItem: CreditCardListItem,
+ ValuesListItem: ValuesListItem,
+ NameListItem: NameListItem,
+ AutofillAddressList: AutofillAddressList,
+ AutofillCreditCardList: AutofillCreditCardList,
+ AutofillValuesList: AutofillValuesList,
+ AutofillNameValuesList: AutofillNameValuesList,
+ AutofillPhoneValuesList: AutofillPhoneValuesList,
+ };
+});
diff --git a/chrome/browser/resources/options2/autofill_overlay.css b/chrome/browser/resources/options2/autofill_overlay.css
new file mode 100644
index 0000000..1bc2db4
--- /dev/null
+++ b/chrome/browser/resources/options2/autofill_overlay.css
@@ -0,0 +1,95 @@
+#autofill-edit-address-overlay {
+ min-width: 510px;
+}
+
+#autofill-edit-credit-card-overlay {
+ min-width: 500px;
+}
+
+div.table {
+ display: table;
+}
+
+div.cell {
+ display: table-cell;
+}
+
+div.row {
+ display: table-row;
+}
+
+div.input {
+ padding: 2px;
+}
+
+/* Size to match large name fields. */
+#company-name, #addr-line-1, #addr-line-2 {
+ width: 206px;
+}
+
+#country {
+ max-width: 450px;
+}
+
+#autofill-edit-address-overlay list {
+ /* Min height is a multiple of the list item height (32) */
+ min-height: 32px;
+ width: 176px;
+}
+
+#autofill-edit-address-overlay list div.static-text {
+ -webkit-box-flex: 1;
+ -webkit-border-radius: 2px;
+ -webkit-padding-start: 4px;
+ -webkit-padding-end: 4px;
+ border: 1px solid darkGray;
+ /* Set the line-height and min-height to match the height of an input element,
+ * so that even empty cells renderer with the correct height.
+ */
+ line-height: 1.75em;
+ min-height: 1.75em;
+ width: 141px;
+}
+
+#autofill-edit-address-overlay list input {
+ width: 151px;
+}
+
+#autofill-name-labels {
+ -webkit-box-orient: horizontal;
+ /* Set the margin to compensate for each list item's close button and
+ * padding.
+ */
+ -webkit-margin-end: 25px;
+ display: -webkit-box;
+}
+
+#autofill-name-labels label {
+ -webkit-box-flex: 1;
+ display: block;
+ /* Set the minimum width to the size of an input element, so that all boxes
+ * have an equal amount of flex space to work with.
+ */
+ min-width: 141px;
+}
+
+#autofill-edit-address-overlay list#full-name-list div.static-text {
+ width: 131px;
+}
+
+#autofill-edit-address-overlay list#full-name-list input {
+ width: 141px;
+}
+
+#autofill-edit-address-overlay list#full-name-list {
+ width: 100%;
+}
+
+#full-name-list div[role="listitem"] > div {
+ -webkit-box-orient: horizontal;
+ display: -webkit-box;
+}
+
+#full-name-list div[role="listitem"] > div > div {
+ -webkit-box-flex: 1;
+}
diff --git a/chrome/browser/resources/options2/browser_options.html b/chrome/browser/resources/options2/browser_options.html
new file mode 100644
index 0000000..2af7b17
--- /dev/null
+++ b/chrome/browser/resources/options2/browser_options.html
@@ -0,0 +1,128 @@
+<div id="browserPage" class="page" hidden>
+ <h1 i18n-content="browserPage"></h1>
+ <div class="displaytable">
+ <section id="startupSection">
+ <h3 i18n-content="startupGroupName"></h3>
+ <div>
+ <div class="radio">
+ <label>
+ <input type="radio" name="startup" value="0"
+ pref="session.restore_on_startup"
+ metric="Options_Startup_Homepage">
+ <span i18n-content="startupShowDefaultAndNewTab"></span>
+ </label>
+ </div>
+ <div class="radio">
+ <label>
+ <input type="radio" name="startup" value="1"
+ pref="session.restore_on_startup"
+ metric="Options_Startup_LastSession">
+ <span i18n-content="startupShowLastSession"></span>
+ </label>
+ </div>
+ <div class="radio">
+ <label>
+ <input id="startupShowPagesButton" type="radio" name="startup"
+ pref="session.restore_on_startup"
+ value="4" metric="Options_Startup_Custom">
+ <span id="startupShowPagesLabel"
+ i18n-content="startupShowPages"></span>
+ </label>
+ </div>
+ <div class="suboption">
+ <div id="startupPageManagement" class="settings-list">
+ <list id="startupPagesList"></list>
+ <div>
+ <button id="startupUseCurrentButton"
+ i18n-content="startupUseCurrent"></button>
+ </div>
+ </div>
+ <div id="startupPagesListDropmarker"></div>
+ </div>
+ </div>
+ </section>
+ <section>
+ <h3 i18n-content="homepageGroupName"></h3>
+ <div>
+ <div class="radio">
+ <label>
+ <input id="homepageUseNTPButton" type="radio" name="homepage"
+ value="true" metric="Options_Homepage_IsNewTabPage"
+ pref="homepage_is_newtabpage">
+ <span i18n-content="homepageUseNewTab"></span>
+ </label>
+ </div>
+ <div id="customHomePageGroup">
+ <div class="radio">
+ <label>
+ <input id="homepageUseURLButton" type="radio" name="homepage"
+ value="false" metric="Options_Homepage_IsNewTabPage"
+ pref="homepage_is_newtabpage">
+ <span i18n-content="homepageUseURL"></span>
+ </label>
+ </div>
+ <div>
+ <input id="homepageURL" class="weakrtl favicon-cell" type="url"
+ data-type="url" pref="homepage">
+ </div>
+ </div>
+ </div>
+ </section>
+ <section>
+ <h3 i18n-content="toolbarGroupName"></h3>
+ <div>
+ <div class="checkbox">
+ <label>
+ <input id="toolbarShowHomeButton" pref="browser.show_home_button"
+ metric="Options_Homepage_HomeButton" type="checkbox">
+ <span i18n-content="toolbarShowHomeButton"></span>
+ </label>
+ </div>
+ <div class="checkbox">
+ <label>
+ <input id="toolbarShowBookmarksBar"
+ pref="bookmark_bar.show_on_all_tabs"
+ metric="Options_ShowBookmarksBar" type="checkbox">
+ <span i18n-content="toolbarShowBookmarksBar"></span>
+ </label>
+ </div>
+ </div>
+ </section>
+ <section>
+ <h3 i18n-content="defaultSearchGroupName"></h3>
+ <div id="defaultSearchEngineGroup">
+ <div>
+ <select id="defaultSearchEngine" class="weakrtl"></select>
+ <button id="defaultSearchManageEnginesButton"
+ i18n-content="defaultSearchManageEngines"></button>
+ </div>
+ <div id="instantOption" class="checkbox">
+ <label id="instantLabel">
+ <!-- TODO(estade): metric? -->
+ <input id="instantFieldTrialCheckbox" type="checkbox"
+ checked="checked" hidden>
+ <input id="instantEnabledCheckbox" type="checkbox"
+ pref="instant.enabled">
+ <span i18n-content="instantName"></span>
+ </label>
+ </div>
+ <div class="suboption informational-text">
+ <span i18n-content="instantWarningText"></span>
+ <a target="_blank" i18n-values="href:instantLearnMoreLink"
+ i18n-content="learnMore"></a>
+ </div>
+ </div>
+ </section>
+<if expr="not pp_ifdef('chromeos')">
+ <section>
+ <h3 i18n-content="defaultBrowserGroupName"></h3>
+ <div>
+ <button id="defaultBrowserUseAsDefaultButton"
+ i18n-content="defaultBrowserUseAsDefault"></button>
+ <div id="defaultBrowserState"
+ i18n-content="defaultBrowserUnknown"></div>
+ </div>
+ </section>
+</if>
+ </div>
+</div>
diff --git a/chrome/browser/resources/options2/browser_options.js b/chrome/browser/resources/options2/browser_options.js
new file mode 100644
index 0000000..ba59d3d
--- /dev/null
+++ b/chrome/browser/resources/options2/browser_options.js
@@ -0,0 +1,366 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+ const OptionsPage = options.OptionsPage;
+ const ArrayDataModel = cr.ui.ArrayDataModel;
+
+ //
+ // BrowserOptions class
+ // Encapsulated handling of browser options page.
+ //
+ function BrowserOptions() {
+ OptionsPage.call(this, 'browser',
+ templateData.browserPageTabTitle,
+ 'browserPage');
+ }
+
+ cr.addSingletonGetter(BrowserOptions);
+
+ BrowserOptions.prototype = {
+ // Inherit BrowserOptions from OptionsPage.
+ __proto__: options.OptionsPage.prototype,
+
+ startup_pages_pref_: {
+ 'name': 'session.urls_to_restore_on_startup',
+ 'disabled': false
+ },
+
+ /**
+ * At autocomplete list that can be attached to a text field during editing.
+ * @type {HTMLElement}
+ * @private
+ */
+ autocompleteList_: null,
+
+ // The cached value of the instant.confirm_dialog_shown preference.
+ instantConfirmDialogShown_: false,
+
+ /**
+ * Initialize BrowserOptions page.
+ */
+ initializePage: function() {
+ // Call base class implementation to start preference initialization.
+ OptionsPage.prototype.initializePage.call(this);
+
+ // Wire up controls.
+ $('startupUseCurrentButton').onclick = function(event) {
+ chrome.send('setStartupPagesToCurrentPages');
+ };
+ $('defaultSearchManageEnginesButton').onclick = function(event) {
+ OptionsPage.navigateToPage('searchEngines');
+ chrome.send('coreOptionsUserMetricsAction',
+ ['Options_ManageSearchEngines']);
+ };
+ $('defaultSearchEngine').onchange = this.setDefaultSearchEngine_;
+
+ var self = this;
+ $('instantEnabledCheckbox').customChangeHandler = function(event) {
+ if (this.checked) {
+ if (self.instantConfirmDialogShown_)
+ chrome.send('enableInstant');
+ else
+ OptionsPage.navigateToPage('instantConfirm');
+ } else {
+ chrome.send('disableInstant');
+ }
+ return true;
+ };
+
+ $('instantFieldTrialCheckbox').addEventListener('change',
+ function(event) {
+ this.checked = true;
+ chrome.send('disableInstant');
+ });
+
+ Preferences.getInstance().addEventListener('instant.confirm_dialog_shown',
+ this.onInstantConfirmDialogShownChanged_.bind(this));
+
+ Preferences.getInstance().addEventListener('instant.enabled',
+ this.onInstantEnabledChanged_.bind(this));
+
+ Preferences.getInstance().addEventListener(
+ $('homepageUseNTPButton').pref,
+ this.onHomepageUseNTPChanged_);
+ var homepageField = $('homepageURL');
+ homepageField.addEventListener('focus', function(event) {
+ self.autocompleteList_.attachToInput(homepageField);
+ });
+ homepageField.addEventListener('blur', function(event) {
+ self.autocompleteList_.detach();
+ });
+ homepageField.addEventListener('keydown', function(event) {
+ // Remove focus when the user hits enter since people expect feedback
+ // indicating that they are done editing.
+ if (event.keyIdentifier == 'Enter')
+ homepageField.blur();
+ });
+
+ // Text fields may change widths when the window changes size, so make
+ // sure the suggestion list stays in sync.
+ window.addEventListener('resize', function() {
+ self.autocompleteList_.syncWidthToInput();
+ });
+
+ // Ensure that changes are committed when closing the page.
+ window.addEventListener('unload', function() {
+ if (document.activeElement == homepageField)
+ homepageField.blur();
+ });
+
+ if (!cr.isChromeOS) {
+ $('defaultBrowserUseAsDefaultButton').onclick = function(event) {
+ chrome.send('becomeDefaultBrowser');
+ };
+ }
+
+ var startupPagesList = $('startupPagesList');
+ options.browser_options.StartupPageList.decorate(startupPagesList);
+ startupPagesList.autoExpands = true;
+
+ // Check if we are in the guest mode.
+ if (cr.commandLine && cr.commandLine.options['--bwsi']) {
+ // Hide the startup section.
+ $('startupSection').hidden = true;
+ } else {
+ // Initialize control enabled states.
+ Preferences.getInstance().addEventListener('session.restore_on_startup',
+ this.updateCustomStartupPageControlStates_.bind(this));
+ Preferences.getInstance().addEventListener(
+ this.startup_pages_pref_.name,
+ this.handleStartupPageListChange_.bind(this));
+
+ this.updateCustomStartupPageControlStates_();
+ }
+
+ var suggestionList = new options.AutocompleteList();
+ suggestionList.autoExpands = true;
+ suggestionList.suggestionUpdateRequestCallback =
+ this.requestAutocompleteSuggestions_.bind(this);
+ $('main-content').appendChild(suggestionList);
+ this.autocompleteList_ = suggestionList;
+ startupPagesList.autocompleteList = suggestionList;
+ },
+
+ /**
+ * Called when the value of the instant.confirm_dialog_shown preference
+ * changes. Cache this value.
+ * @param {Event} event Change event.
+ * @private
+ */
+ onInstantConfirmDialogShownChanged_: function(event) {
+ this.instantConfirmDialogShown_ = event.value['value'];
+ },
+
+ /**
+ * Called when the value of the instant.enabled preference changes. Request
+ * the state of the Instant field trial experiment.
+ * @param {Event} event Change event.
+ * @private
+ */
+ onInstantEnabledChanged_: function(event) {
+ chrome.send('getInstantFieldTrialStatus');
+ },
+
+ /**
+ * Called to set the Instant field trial status.
+ * @param {boolean} enabled If true, the experiment is enabled.
+ * @private
+ */
+ setInstantFieldTrialStatus_: function(enabled) {
+ $('instantEnabledCheckbox').hidden = enabled;
+ $('instantFieldTrialCheckbox').hidden = !enabled;
+ $('instantLabel').htmlFor = enabled ? 'instantFieldTrialCheckbox'
+ : 'instantEnabledCheckbox';
+ },
+
+ /**
+ * Called when the value of the homepage-use-NTP pref changes.
+ * Updates the disabled state of the homepage text field.
+ * Notice that the text field can be disabled for other reasons too
+ * (it can be managed by policy, for instance).
+ * @param {Event} event Change event.
+ * @private
+ */
+ onHomepageUseNTPChanged_: function(event) {
+ var homepageField = $('homepageURL');
+ var homepageUseURLButton = $('homepageUseURLButton');
+ homepageField.setDisabled('radioNotSelected',
+ !homepageUseURLButton.checked);
+ },
+
+ /**
+ * Update the Default Browsers section based on the current state.
+ * @param {string} statusString Description of the current default state.
+ * @param {boolean} isDefault Whether or not the browser is currently
+ * default.
+ * @param {boolean} canBeDefault Whether or not the browser can be default.
+ * @private
+ */
+ updateDefaultBrowserState_: function(statusString, isDefault,
+ canBeDefault) {
+ var label = $('defaultBrowserState');
+ label.textContent = statusString;
+
+ $('defaultBrowserUseAsDefaultButton').disabled = !canBeDefault ||
+ isDefault;
+ },
+
+ /**
+ * Clears the search engine popup.
+ * @private
+ */
+ clearSearchEngines_: function() {
+ $('defaultSearchEngine').textContent = '';
+ },
+
+ /**
+ * Updates the search engine popup with the given entries.
+ * @param {Array} engines List of available search engines.
+ * @param {number} defaultValue The value of the current default engine.
+ * @param {boolean} defaultManaged Whether the default search provider is
+ * managed. If true, the default search provider can't be changed.
+ */
+ updateSearchEngines_: function(engines, defaultValue, defaultManaged) {
+ this.clearSearchEngines_();
+ engineSelect = $('defaultSearchEngine');
+ engineSelect.disabled = defaultManaged;
+ engineCount = engines.length;
+ var defaultIndex = -1;
+ for (var i = 0; i < engineCount; i++) {
+ var engine = engines[i];
+ var option = new Option(engine['name'], engine['index']);
+ if (defaultValue == option.value)
+ defaultIndex = i;
+ engineSelect.appendChild(option);
+ }
+ if (defaultIndex >= 0)
+ engineSelect.selectedIndex = defaultIndex;
+ },
+
+ /**
+ * Returns true if the custom startup page control block should
+ * be enabled.
+ * @returns {boolean} Whether the startup page controls should be
+ * enabled.
+ */
+ shouldEnableCustomStartupPageControls: function(pages) {
+ return $('startupShowPagesButton').checked &&
+ !this.startup_pages_pref_.disabled;
+ },
+
+ /**
+ * Updates the startup pages list with the given entries.
+ * @param {Array} pages List of startup pages.
+ * @private
+ */
+ updateStartupPages_: function(pages) {
+ var model = new ArrayDataModel(pages);
+ // Add a "new page" row.
+ model.push({
+ 'modelIndex': '-1'
+ });
+ $('startupPagesList').dataModel = model;
+ },
+
+ /**
+ * Sets the enabled state of the custom startup page list controls
+ * based on the current startup radio button selection.
+ * @private
+ */
+ updateCustomStartupPageControlStates_: function() {
+ var disable = !this.shouldEnableCustomStartupPageControls();
+ var startupPagesList = $('startupPagesList');
+ startupPagesList.disabled = disable;
+ startupPagesList.setAttribute('tabindex', disable ? -1 : 0);
+ // Explicitly set disabled state for input text elements.
+ var inputs = startupPagesList.querySelectorAll("input[type='text']");
+ for (var i = 0; i < inputs.length; i++)
+ inputs[i].disabled = disable;
+ $('startupUseCurrentButton').disabled = disable;
+ },
+
+ /**
+ * Handle change events of the preference
+ * 'session.urls_to_restore_on_startup'.
+ * @param {event} preference changed event.
+ * @private
+ */
+ handleStartupPageListChange_: function(event) {
+ this.startup_pages_pref_.disabled = event.value['disabled'];
+ this.updateCustomStartupPageControlStates_();
+ },
+
+ /**
+ * Set the default search engine based on the popup selection.
+ */
+ setDefaultSearchEngine_: function() {
+ var engineSelect = $('defaultSearchEngine');
+ var selectedIndex = engineSelect.selectedIndex;
+ if (selectedIndex >= 0) {
+ var selection = engineSelect.options[selectedIndex];
+ chrome.send('setDefaultSearchEngine', [String(selection.value)]);
+ }
+ },
+
+ /**
+ * Sends an asynchronous request for new autocompletion suggestions for the
+ * the given query. When new suggestions are available, the C++ handler will
+ * call updateAutocompleteSuggestions_.
+ * @param {string} query List of autocomplete suggestions.
+ * @private
+ */
+ requestAutocompleteSuggestions_: function(query) {
+ chrome.send('requestAutocompleteSuggestions', [query]);
+ },
+
+ /**
+ * Updates the autocomplete suggestion list with the given entries.
+ * @param {Array} pages List of autocomplete suggestions.
+ * @private
+ */
+ updateAutocompleteSuggestions_: function(suggestions) {
+ var list = this.autocompleteList_;
+ // If the trigger for this update was a value being selected from the
+ // current list, do nothing.
+ if (list.targetInput && list.selectedItem &&
+ list.selectedItem['url'] == list.targetInput.value)
+ return;
+ list.suggestions = suggestions;
+ },
+ };
+
+ BrowserOptions.updateDefaultBrowserState = function(statusString, isDefault,
+ canBeDefault) {
+ if (!cr.isChromeOS) {
+ BrowserOptions.getInstance().updateDefaultBrowserState_(statusString,
+ isDefault,
+ canBeDefault);
+ }
+ };
+
+ BrowserOptions.updateSearchEngines = function(engines, defaultValue,
+ defaultManaged) {
+ BrowserOptions.getInstance().updateSearchEngines_(engines, defaultValue,
+ defaultManaged);
+ };
+
+ BrowserOptions.updateStartupPages = function(pages) {
+ BrowserOptions.getInstance().updateStartupPages_(pages);
+ };
+
+ BrowserOptions.updateAutocompleteSuggestions = function(suggestions) {
+ BrowserOptions.getInstance().updateAutocompleteSuggestions_(suggestions);
+ };
+
+ BrowserOptions.setInstantFieldTrialStatus = function(enabled) {
+ BrowserOptions.getInstance().setInstantFieldTrialStatus_(enabled);
+ };
+
+ // Export
+ return {
+ BrowserOptions: BrowserOptions
+ };
+
+});
diff --git a/chrome/browser/resources/options2/browser_options_page.css b/chrome/browser/resources/options2/browser_options_page.css
new file mode 100644
index 0000000..2be1710
--- /dev/null
+++ b/chrome/browser/resources/options2/browser_options_page.css
@@ -0,0 +1,95 @@
+#startupPageManagement.settings-list > :last-child {
+ border-top: 1px solid #d9d9d9;
+ padding: 5px 10px;
+}
+
+#startupPagesList {
+ min-height: 64px;
+}
+
+#startupPagesList .title {
+ width: 40%;
+}
+
+#startupPagesList .url {
+ -webkit-box-flex: 1;
+ color: #666;
+}
+
+#startupPagesList > * {
+ max-width: 700px;
+}
+
+#startupPagesListDropmarker {
+ background-clip: padding-box;
+ background-color: hsl(214, 91%, 65%);
+ border-bottom-color: transparent;
+ border-radius: 0;
+ border-top-color: transparent;
+ border: 2px solid hsl(214, 91%, 65%);
+ box-sizing: border-box;
+ display: none;
+ height: 6px;
+ overflow: hidden;
+ pointer-events: none;
+ position: fixed;
+ z-index: 10;
+}
+
+#customHomePageGroup {
+ display: -webkit-box;
+ -webkit-box-orient: horizontal;
+}
+
+#customHomePageGroup > :last-child {
+ -webkit-margin-start: 1ex;
+ -webkit-box-flex: 1;
+ position: relative;
+}
+
+#homepageURL {
+ box-sizing: border-box;
+ padding-top: 3px;
+ width: 100%;
+}
+
+#defaultSearchEngineGroup {
+ display: -webkit-box;
+ -webkit-box-orient: vertical;
+}
+
+#defaultSearchEngineGroup > div {
+ display: -webkit-box;
+ -webkit-box-orient: horizontal;
+}
+
+#defaultSearchEngine {
+ display: block;
+ -webkit-box-flex: 1;
+ max-width: 200px;
+}
+
+#defaultSearchManageEnginesButton {
+ margin-top: 0px;
+ -webkit-margin-start: 10px;
+}
+
+#defaultBrowserState {
+ margin-top: 6px;
+}
+
+#instantOption {
+ margin-bottom: 3px;
+ margin-top: 10px
+}
+
+#instantConfirmText {
+ font-family: inherit;
+ white-space: pre-wrap;
+ width: 500px;
+}
+
+#instantConfirmLearnMore {
+ position: absolute;
+ bottom: 18px;
+}
diff --git a/chrome/browser/resources/options2/browser_options_startup_page_list.js b/chrome/browser/resources/options2/browser_options_startup_page_list.js
new file mode 100644
index 0000000..cd10729
--- /dev/null
+++ b/chrome/browser/resources/options2/browser_options_startup_page_list.js
@@ -0,0 +1,310 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options.browser_options', function() {
+ const AutocompleteList = options.AutocompleteList;
+ const InlineEditableItem = options.InlineEditableItem;
+ const InlineEditableItemList = options.InlineEditableItemList;
+
+ /**
+ * Creates a new startup page list item.
+ * @param {Object} pageInfo The page this item represents.
+ * @constructor
+ * @extends {cr.ui.ListItem}
+ */
+ function StartupPageListItem(pageInfo) {
+ var el = cr.doc.createElement('div');
+ el.pageInfo_ = pageInfo;
+ StartupPageListItem.decorate(el);
+ return el;
+ }
+
+ /**
+ * Decorates an element as a startup page list item.
+ * @param {!HTMLElement} el The element to decorate.
+ */
+ StartupPageListItem.decorate = function(el) {
+ el.__proto__ = StartupPageListItem.prototype;
+ el.decorate();
+ };
+
+ StartupPageListItem.prototype = {
+ __proto__: InlineEditableItem.prototype,
+
+ /**
+ * Input field for editing the page url.
+ * @type {HTMLElement}
+ * @private
+ */
+ urlField_: null,
+
+ /** @inheritDoc */
+ decorate: function() {
+ InlineEditableItem.prototype.decorate.call(this);
+
+ var pageInfo = this.pageInfo_;
+
+ if (pageInfo['modelIndex'] == '-1') {
+ this.isPlaceholder = true;
+ pageInfo['title'] = localStrings.getString('startupAddLabel');
+ pageInfo['url'] = '';
+ }
+
+ var titleEl = this.ownerDocument.createElement('div');
+ titleEl.className = 'title';
+ titleEl.classList.add('favicon-cell');
+ titleEl.classList.add('weakrtl');
+ titleEl.textContent = pageInfo['title'];
+ if (!this.isPlaceholder) {
+ titleEl.style.backgroundImage = url('chrome://favicon/' +
+ pageInfo['url']);
+ titleEl.title = pageInfo['tooltip'];
+ }
+
+ this.contentElement.appendChild(titleEl);
+
+ var urlEl = this.createEditableTextCell(pageInfo['url']);
+ urlEl.className = 'url';
+ urlEl.classList.add('weakrtl');
+ this.contentElement.appendChild(urlEl);
+
+ var urlField = urlEl.querySelector('input')
+ urlField.required = true;
+ urlField.className = 'weakrtl';
+ this.urlField_ = urlField;
+
+ this.addEventListener('commitedit', this.onEditCommitted_);
+
+ var self = this;
+ urlField.addEventListener('focus', function(event) {
+ self.parentNode.autocompleteList.attachToInput(urlField);
+ });
+ urlField.addEventListener('blur', function(event) {
+ self.parentNode.autocompleteList.detach();
+ });
+
+ if (!this.isPlaceholder)
+ this.draggable = true;
+ },
+
+ /** @inheritDoc */
+ get currentInputIsValid() {
+ return this.urlField_.validity.valid;
+ },
+
+ /** @inheritDoc */
+ get hasBeenEdited() {
+ return this.urlField_.value != this.pageInfo_['url'];
+ },
+
+ /**
+ * Called when committing an edit; updates the model.
+ * @param {Event} e The end event.
+ * @private
+ */
+ onEditCommitted_: function(e) {
+ var url = this.urlField_.value;
+ if (this.isPlaceholder)
+ chrome.send('addStartupPage', [url]);
+ else
+ chrome.send('editStartupPage', [this.pageInfo_['modelIndex'], url]);
+ },
+ };
+
+ var StartupPageList = cr.ui.define('list');
+
+ StartupPageList.prototype = {
+ __proto__: InlineEditableItemList.prototype,
+
+ /**
+ * An autocomplete suggestion list for URL editing.
+ * @type {AutocompleteList}
+ */
+ autocompleteList: null,
+
+ /**
+ * The drop position information: "below" or "above".
+ */
+ dropPos: null,
+
+ /** @inheritDoc */
+ decorate: function() {
+ InlineEditableItemList.prototype.decorate.call(this);
+
+ // Listen to drag and drop events.
+ this.addEventListener('dragstart', this.handleDragStart_.bind(this));
+ this.addEventListener('dragenter', this.handleDragEnter_.bind(this));
+ this.addEventListener('dragover', this.handleDragOver_.bind(this));
+ this.addEventListener('drop', this.handleDrop_.bind(this));
+ this.addEventListener('dragleave', this.handleDragLeave_.bind(this));
+ this.addEventListener('dragend', this.handleDragEnd_.bind(this));
+ },
+
+ /** @inheritDoc */
+ createItem: function(pageInfo) {
+ var item = new StartupPageListItem(pageInfo);
+ item.urlField_.disabled = this.disabled;
+ return item;
+ },
+
+ /** @inheritDoc */
+ deleteItemAtIndex: function(index) {
+ chrome.send('removeStartupPages', [String(index)]);
+ },
+
+ /*
+ * Computes the target item of drop event.
+ * @param {Event} e The drop or dragover event.
+ * @private
+ */
+ getTargetFromDropEvent_ : function(e) {
+ var target = e.target;
+ // e.target may be an inner element of the list item
+ while (target != null && !(target instanceof StartupPageListItem)) {
+ target = target.parentNode;
+ }
+ return target;
+ },
+
+ /*
+ * Handles the dragstart event.
+ * @param {Event} e The dragstart event.
+ * @private
+ */
+ handleDragStart_: function(e) {
+ // Prevent dragging if the list is disabled.
+ if (this.disabled) {
+ e.preventDefault();
+ return false;
+ }
+
+ var target = e.target;
+ // StartupPageListItem should be the only draggable element type in the
+ // page but let's make sure.
+ if (target instanceof StartupPageListItem) {
+ this.draggedItem = target;
+ this.draggedItem.editable = false;
+ e.dataTransfer.effectAllowed = 'move';
+ // We need to put some kind of data in the drag or it will be
+ // ignored. Use the URL in case the user drags to a text field or the
+ // desktop.
+ e.dataTransfer.setData('text/plain', target.urlField_.value);
+ }
+ },
+
+ /*
+ * Handles the dragenter event.
+ * @param {Event} e The dragenter event.
+ * @private
+ */
+ handleDragEnter_: function(e) {
+ e.preventDefault();
+ },
+
+ /*
+ * Handles the dragover event.
+ * @param {Event} e The dragover event.
+ * @private
+ */
+ handleDragOver_: function(e) {
+ var dropTarget = this.getTargetFromDropEvent_(e);
+ // Determines whether the drop target is to accept the drop.
+ // The drop is only successful on another StartupPageListItem.
+ if (!(dropTarget instanceof StartupPageListItem) ||
+ dropTarget == this.draggedItem || dropTarget.isPlaceholder) {
+ this.hideDropMarker_();
+ return;
+ }
+ // Compute the drop postion. Should we move the dragged item to
+ // below or above the drop target?
+ var rect = dropTarget.getBoundingClientRect();
+ var dy = e.clientY - rect.top;
+ var yRatio = dy / rect.height;
+ var dropPos = yRatio <= .5 ? 'above' : 'below';
+ this.dropPos = dropPos;
+ this.showDropMarker_(dropTarget, dropPos);
+ e.preventDefault();
+ },
+
+ /*
+ * Handles the drop event.
+ * @param {Event} e The drop event.
+ * @private
+ */
+ handleDrop_: function(e) {
+ var dropTarget = this.getTargetFromDropEvent_(e);
+ this.hideDropMarker_();
+
+ // Insert the selection at the new position.
+ var newIndex = this.dataModel.indexOf(dropTarget.pageInfo_);
+ if (this.dropPos == 'below')
+ newIndex += 1;
+
+ var selected = this.selectionModel.selectedIndexes;
+ var stringized_selected = [];
+ for (var j = 0; j < selected.length; j++)
+ stringized_selected.push(String(selected[j]));
+
+ chrome.send('dragDropStartupPage',
+ [String(newIndex), stringized_selected] );
+ },
+
+ /*
+ * Handles the dragleave event.
+ * @param {Event} e The dragleave event
+ * @private
+ */
+ handleDragLeave_: function(e) {
+ this.hideDropMarker_();
+ },
+
+ /**
+ * Handles the dragend event.
+ * @param {Event} e The dragend event
+ * @private
+ */
+ handleDragEnd_: function(e) {
+ this.draggedItem.editable = true;
+ this.draggedItem.updateEditState();
+ },
+
+ /*
+ * Shows and positions the marker to indicate the drop target.
+ * @param {HTMLElement} target The current target list item of drop
+ * @param {string} pos 'below' or 'above'
+ * @private
+ */
+ showDropMarker_ : function(target, pos) {
+ window.clearTimeout(this.hideDropMarkerTimer_);
+ var marker = $('startupPagesListDropmarker');
+ var rect = target.getBoundingClientRect();
+ var markerHeight = 6;
+ if (pos == 'above') {
+ marker.style.top = (rect.top - markerHeight/2) + 'px';
+ } else {
+ marker.style.top = (rect.bottom - markerHeight/2) + 'px';
+ }
+ marker.style.width = rect.width + 'px';
+ marker.style.left = rect.left + 'px';
+ marker.style.display = 'block';
+ },
+
+ /*
+ * Hides the drop marker.
+ * @private
+ */
+ hideDropMarker_ : function() {
+ // Hide the marker in a timeout to reduce flickering as we move between
+ // valid drop targets.
+ window.clearTimeout(this.hideDropMarkerTimer_);
+ this.hideDropMarkerTimer_ = window.setTimeout(function() {
+ $('startupPagesListDropmarker').style.display = '';
+ }, 100);
+ },
+ };
+
+ return {
+ StartupPageList: StartupPageList
+ };
+});
diff --git a/chrome/browser/resources/options2/certificate_backup_overlay.html b/chrome/browser/resources/options2/certificate_backup_overlay.html
new file mode 100644
index 0000000..b0bcea3
--- /dev/null
+++ b/chrome/browser/resources/options2/certificate_backup_overlay.html
@@ -0,0 +1,38 @@
+<div id="certificateBackupOverlay" class="page" hidden>
+ <h1 i18n-content="certificateExportPasswordDescription"></h1>
+ <div class="content-area">
+ <table>
+ <tr>
+ <td>
+ <label for="certificateBackupPassword">
+ <span i18n-content="certificatePasswordLabel"></span>
+ </label>
+ </td>
+ <td>
+ <input id="certificateBackupPassword" type="password">
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <label for="certificateBackupPassword2">
+ <span i18n-content="certificateConfirmPasswordLabel"></span>
+ </label>
+ </td>
+ <td>
+ <input id="certificateBackupPassword2" type="password">
+ </td>
+ </tr>
+ </table>
+ <p>
+ <span i18n-content="certificateExportPasswordHelp"></span>
+ </p>
+ </div>
+ <div class="action-area">
+ <div class="button-strip">
+ <button id="certificateBackupCancelButton" type="reset"
+ i18n-content="cancel"></button>
+ <button id="certificateBackupOkButton" type="submit" i18n-content="ok"
+ disabled></button>
+ </div>
+ </div>
+</div>
diff --git a/chrome/browser/resources/options2/certificate_backup_overlay.js b/chrome/browser/resources/options2/certificate_backup_overlay.js
new file mode 100644
index 0000000..e700347
--- /dev/null
+++ b/chrome/browser/resources/options2/certificate_backup_overlay.js
@@ -0,0 +1,116 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+ const OptionsPage = options.OptionsPage;
+
+ /**
+ * CertificateBackupOverlay class
+ * Encapsulated handling of the 'enter backup password' overlay page.
+ * @class
+ */
+ function CertificateBackupOverlay() {
+ OptionsPage.call(this, 'certificateBackupOverlay',
+ '',
+ 'certificateBackupOverlay');
+ }
+
+ cr.addSingletonGetter(CertificateBackupOverlay);
+
+ CertificateBackupOverlay.prototype = {
+ __proto__: OptionsPage.prototype,
+
+ /**
+ * Initializes the page.
+ */
+ initializePage: function() {
+ OptionsPage.prototype.initializePage.call(this);
+
+ var self = this;
+ $('certificateBackupCancelButton').onclick = function(event) {
+ self.cancelBackup_();
+ }
+ $('certificateBackupOkButton').onclick = function(event) {
+ self.finishBackup_();
+ }
+ $('certificateBackupPassword').oninput =
+ $('certificateBackupPassword2').oninput = function(event) {
+ self.comparePasswords_();
+ }
+
+ self.clearInputFields_();
+ },
+
+ /**
+ * Clears any uncommitted input, and dismisses the overlay.
+ * @private
+ */
+ dismissOverlay_: function() {
+ this.clearInputFields_();
+ OptionsPage.closeOverlay();
+ },
+
+ /**
+ * Attempt the Backup operation.
+ * The overlay will be left up with inputs disabled until the backend
+ * finishes and dismisses it.
+ * @private
+ */
+ finishBackup_: function() {
+ chrome.send('exportPersonalCertificatePasswordSelected',
+ [$('certificateBackupPassword').value]);
+ $('certificateBackupCancelButton').disabled = true;
+ $('certificateBackupOkButton').disabled = true;
+ $('certificateBackupPassword').disabled = true;
+ $('certificateBackupPassword2').disabled = true;
+ },
+
+ /**
+ * Cancel the Backup operation.
+ * @private
+ */
+ cancelBackup_: function() {
+ chrome.send('cancelImportExportCertificate');
+ this.dismissOverlay_();
+ },
+
+ /**
+ * Compares the password fields and sets the button state appropriately.
+ * @private
+ */
+ comparePasswords_: function() {
+ var password1 = $('certificateBackupPassword').value;
+ var password2 = $('certificateBackupPassword2').value;
+ $('certificateBackupOkButton').disabled =
+ !password1 || password1 != password2;
+ },
+
+ /**
+ * Clears the value of each input field.
+ * @private
+ */
+ clearInputFields_: function() {
+ $('certificateBackupPassword').value = '';
+ $('certificateBackupPassword2').value = '';
+ $('certificateBackupPassword').disabled = false;
+ $('certificateBackupPassword2').disabled = false;
+ $('certificateBackupCancelButton').disabled = false;
+ $('certificateBackupOkButton').disabled = true;
+ },
+ };
+
+ CertificateBackupOverlay.show = function() {
+ CertificateBackupOverlay.getInstance().clearInputFields_();
+ OptionsPage.navigateToPage('certificateBackupOverlay');
+ };
+
+ CertificateBackupOverlay.dismiss = function() {
+ CertificateBackupOverlay.getInstance().dismissOverlay_();
+ };
+
+ // Export
+ return {
+ CertificateBackupOverlay: CertificateBackupOverlay
+ };
+});
diff --git a/chrome/browser/resources/options2/certificate_edit_ca_trust_overlay.html b/chrome/browser/resources/options2/certificate_edit_ca_trust_overlay.html
new file mode 100644
index 0000000..b45bfc9
--- /dev/null
+++ b/chrome/browser/resources/options2/certificate_edit_ca_trust_overlay.html
@@ -0,0 +1,33 @@
+<div id="certificateEditCaTrustOverlay" class="page" hidden>
+ <div class="content-area">
+ <p>
+ <span id="certificateEditCaTrustDescription"></span>
+ </p>
+ <p>
+ <span i18n-content="certificateEditTrustLabel"></span>
+ <br>
+ <label id="certificateCaTrustSSLLabel">
+ <input id="certificateCaTrustSSLCheckbox" type="checkbox">
+ <span i18n-content="certificateCaTrustSSLLabel"></span>
+ </label>
+ <br>
+ <label id="certificateCaTrustEmailLabel">
+ <input id="certificateCaTrustEmailCheckbox" type="checkbox">
+ <span i18n-content="certificateCaTrustEmailLabel"></span>
+ </label>
+ <br>
+ <label id="certificateCaTrustObjSignLabel">
+ <input id="certificateCaTrustObjSignCheckbox" type="checkbox">
+ <span i18n-content="certificateCaTrustObjSignLabel"></span>
+ </label>
+ </p>
+ </div>
+ <div class="action-area">
+ <div class="button-strip">
+ <button id="certificateEditCaTrustCancelButton" type="reset"
+ i18n-content="cancel"></button>
+ <button id="certificateEditCaTrustOkButton" type="submit"
+ i18n-content="ok"></button>
+ </div>
+ </div>
+</div>
diff --git a/chrome/browser/resources/options2/certificate_edit_ca_trust_overlay.js b/chrome/browser/resources/options2/certificate_edit_ca_trust_overlay.js
new file mode 100644
index 0000000..86fa5f7
--- /dev/null
+++ b/chrome/browser/resources/options2/certificate_edit_ca_trust_overlay.js
@@ -0,0 +1,164 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+ const OptionsPage = options.OptionsPage;
+
+ /**
+ * CertificateEditCaTrustOverlay class
+ * Encapsulated handling of the 'edit ca trust' and 'import ca' overlay pages.
+ * @class
+ */
+ function CertificateEditCaTrustOverlay() {
+ OptionsPage.call(this, 'certificateEditCaTrustOverlay',
+ '',
+ 'certificateEditCaTrustOverlay');
+ }
+
+ cr.addSingletonGetter(CertificateEditCaTrustOverlay);
+
+ CertificateEditCaTrustOverlay.prototype = {
+ __proto__: OptionsPage.prototype,
+
+ /**
+ * Dismisses the overlay.
+ * @private
+ */
+ dismissOverlay_: function() {
+ OptionsPage.closeOverlay();
+ },
+
+ /**
+ * Enables or disables input fields.
+ * @private
+ */
+ enableInputs_: function(enabled) {
+ $('certificateCaTrustSSLCheckbox').disabled =
+ $('certificateCaTrustEmailCheckbox').disabled =
+ $('certificateCaTrustObjSignCheckbox').disabled =
+ $('certificateEditCaTrustCancelButton').disabled =
+ $('certificateEditCaTrustOkButton').disabled = !enabled;
+ },
+
+ /**
+ * Attempt the Edit operation.
+ * The overlay will be left up with inputs disabled until the backend
+ * finishes and dismisses it.
+ * @private
+ */
+ finishEdit_: function() {
+ // TODO(mattm): Send checked values as booleans. For now send them as
+ // strings, since WebUIBindings::send does not support any other types :(
+ chrome.send('editCaCertificateTrust',
+ [this.certId,
+ $('certificateCaTrustSSLCheckbox').checked.toString(),
+ $('certificateCaTrustEmailCheckbox').checked.toString(),
+ $('certificateCaTrustObjSignCheckbox').checked.toString()]);
+ this.enableInputs_(false);
+ },
+
+ /**
+ * Cancel the Edit operation.
+ * @private
+ */
+ cancelEdit_: function() {
+ this.dismissOverlay_();
+ },
+
+ /**
+ * Attempt the Import operation.
+ * The overlay will be left up with inputs disabled until the backend
+ * finishes and dismisses it.
+ * @private
+ */
+ finishImport_: function() {
+ // TODO(mattm): Send checked values as booleans. For now send them as
+ // strings, since WebUIBindings::send does not support any other types :(
+ chrome.send('importCaCertificateTrustSelected',
+ [$('certificateCaTrustSSLCheckbox').checked.toString(),
+ $('certificateCaTrustEmailCheckbox').checked.toString(),
+ $('certificateCaTrustObjSignCheckbox').checked.toString()]);
+ this.enableInputs_(false);
+ },
+
+ /**
+ * Cancel the Import operation.
+ * @private
+ */
+ cancelImport_: function() {
+ chrome.send('cancelImportExportCertificate');
+ this.dismissOverlay_();
+ },
+ };
+
+ /**
+ * Callback from CertificateManagerHandler with the trust values.
+ * @param {boolean} trustSSL The initial value of SSL trust checkbox.
+ * @param {boolean} trustEmail The initial value of Email trust checkbox.
+ * @param {boolean} trustObjSign The initial value of Object Signing trust
+ */
+ CertificateEditCaTrustOverlay.populateTrust = function(
+ trustSSL, trustEmail, trustObjSign) {
+ $('certificateCaTrustSSLCheckbox').checked = trustSSL;
+ $('certificateCaTrustEmailCheckbox').checked = trustEmail;
+ $('certificateCaTrustObjSignCheckbox').checked = trustObjSign;
+ CertificateEditCaTrustOverlay.getInstance().enableInputs_(true);
+ }
+
+ /**
+ * Show the Edit CA Trust overlay.
+ * @param {string} certId The id of the certificate to be passed to the
+ * certificate manager model.
+ * @param {string} certName The display name of the certificate.
+ * checkbox.
+ */
+ CertificateEditCaTrustOverlay.show = function(certId, certName) {
+ var self = CertificateEditCaTrustOverlay.getInstance();
+ self.certId = certId;
+ $('certificateEditCaTrustCancelButton').onclick = function(event) {
+ self.cancelEdit_();
+ }
+ $('certificateEditCaTrustOkButton').onclick = function(event) {
+ self.finishEdit_();
+ }
+ $('certificateEditCaTrustDescription').textContent =
+ localStrings.getStringF('certificateEditCaTrustDescriptionFormat',
+ certName);
+ self.enableInputs_(false);
+ OptionsPage.navigateToPage('certificateEditCaTrustOverlay');
+ chrome.send('getCaCertificateTrust', [certId]);
+ }
+
+ /**
+ * Show the Import CA overlay.
+ * @param {string} certId The id of the certificate to be passed to the
+ * certificate manager model.
+ * @param {string} certName The display name of the certificate.
+ * checkbox.
+ */
+ CertificateEditCaTrustOverlay.showImport = function(certName) {
+ var self = CertificateEditCaTrustOverlay.getInstance();
+ // TODO(mattm): do we want a view certificate button here like firefox has?
+ $('certificateEditCaTrustCancelButton').onclick = function(event) {
+ self.cancelImport_();
+ }
+ $('certificateEditCaTrustOkButton').onclick = function(event) {
+ self.finishImport_();
+ }
+ $('certificateEditCaTrustDescription').textContent =
+ localStrings.getStringF('certificateImportCaDescriptionFormat',
+ certName);
+ CertificateEditCaTrustOverlay.populateTrust(false, false, false);
+ OptionsPage.navigateToPage('certificateEditCaTrustOverlay');
+ }
+
+ CertificateEditCaTrustOverlay.dismiss = function() {
+ CertificateEditCaTrustOverlay.getInstance().dismissOverlay_();
+ };
+
+ // Export
+ return {
+ CertificateEditCaTrustOverlay: CertificateEditCaTrustOverlay
+ };
+});
diff --git a/chrome/browser/resources/options2/certificate_import_error_overlay.html b/chrome/browser/resources/options2/certificate_import_error_overlay.html
new file mode 100644
index 0000000..02a7e2d
--- /dev/null
+++ b/chrome/browser/resources/options2/certificate_import_error_overlay.html
@@ -0,0 +1,13 @@
+<div id="certificateImportErrorOverlay" class="page" hidden>
+ <h1 id="certificateImportErrorOverlayTitle"></h1>
+ <div class="content-area">
+ <div id="certificateImportErrorOverlayMessage"></div>
+ <ul id="certificateImportErrorOverlayCertErrors"></ul>
+ </div>
+ <div class="action-area">
+ <div class="button-strip">
+ <button id="certificateImportErrorOverlayOk" type="submit"
+ i18n-content="ok"></button>
+ </div>
+ </div>
+</div>
diff --git a/chrome/browser/resources/options2/certificate_import_error_overlay.js b/chrome/browser/resources/options2/certificate_import_error_overlay.js
new file mode 100644
index 0000000..efc99ff
--- /dev/null
+++ b/chrome/browser/resources/options2/certificate_import_error_overlay.js
@@ -0,0 +1,68 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+
+ var OptionsPage = options.OptionsPage;
+
+ /**
+ * CertificateImportErrorOverlay class
+ * Displays a list of certificates and errors.
+ * @class
+ */
+ function CertificateImportErrorOverlay() {
+ OptionsPage.call(this, 'certificateImportErrorOverlay', '',
+ 'certificateImportErrorOverlay');
+ }
+
+ cr.addSingletonGetter(CertificateImportErrorOverlay);
+
+ CertificateImportErrorOverlay.prototype = {
+ // Inherit CertificateImportErrorOverlay from OptionsPage.
+ __proto__: OptionsPage.prototype,
+
+ /**
+ * Initialize the page.
+ */
+ initializePage: function() {
+ // Call base class implementation to start preference initialization.
+ OptionsPage.prototype.initializePage.call(this);
+
+ $('certificateImportErrorOverlayOk').onclick = function(event) {
+ OptionsPage.closeOverlay();
+ };
+ },
+ };
+
+ /**
+ * Show an alert overlay with the given message, button titles, and
+ * callbacks.
+ * @param {string} title The alert title to display to the user.
+ * @param {string} message The alert message to display to the user.
+ * @param {Array} certErrors The list of cert errors. Each error should have
+ * a .name and .error attribute.
+ */
+ CertificateImportErrorOverlay.show = function(title, message, certErrors) {
+ $('certificateImportErrorOverlayTitle').textContent = title;
+ $('certificateImportErrorOverlayMessage').textContent = message;
+
+ ul = $('certificateImportErrorOverlayCertErrors');
+ ul.innerHTML = '';
+ for (var i = 0; i < certErrors.length; ++i) {
+ li = document.createElement("li");
+ li.textContent = localStrings.getStringF('certificateImportErrorFormat',
+ certErrors[i].name,
+ certErrors[i].error);
+ ul.appendChild(li);
+ }
+
+ OptionsPage.navigateToPage('certificateImportErrorOverlay');
+ }
+
+ // Export
+ return {
+ CertificateImportErrorOverlay: CertificateImportErrorOverlay
+ };
+
+});
diff --git a/chrome/browser/resources/options2/certificate_manager.css b/chrome/browser/resources/options2/certificate_manager.css
new file mode 100644
index 0000000..a88d231
--- /dev/null
+++ b/chrome/browser/resources/options2/certificate_manager.css
@@ -0,0 +1,15 @@
+/*
+Copyright (c) 2010 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+*/
+
+.certificate-tree-table {
+ width: 100%;
+}
+
+.certificate-tree {
+ /* TODO(mattm): BLAH. Make this not statically sized. */
+ height: 300px;
+}
+
diff --git a/chrome/browser/resources/options2/certificate_manager.html b/chrome/browser/resources/options2/certificate_manager.html
new file mode 100644
index 0000000..68ea65a
--- /dev/null
+++ b/chrome/browser/resources/options2/certificate_manager.html
@@ -0,0 +1,129 @@
+<div id="certificateManagerPage" class="page" hidden>
+ <h1 i18n-content="certificateManagerPage"></h1>
+ <!-- Navigation tabs -->
+ <div class="subpages-nav-tabs">
+ <span id="personal-certs-nav-tab" class="tab"
+ tab-contents="personalCertsTab">
+ <span class="tab-label"
+ i18n-content="personalCertsTabTitle"></span>
+ <span class="active-tab-label"
+ i18n-content="personalCertsTabTitle"></span>
+ </span>
+ <span id="server-certs-nav-tab" class="tab"
+ tab-contents="serverCertsTab">
+ <span class="tab-label"
+ i18n-content="serverCertsTabTitle"></span>
+ <span class="active-tab-label" i18n-content="serverCertsTabTitle"></span>
+ </span>
+ <span id="ca-certs-nav-tab" class="tab"
+ tab-contents="caCertsTab">
+ <span class="tab-label" i18n-content="caCertsTabTitle"></span>
+ <span class="active-tab-label" i18n-content="caCertsTabTitle"></span>
+ </span>
+ <span id="other-certs-nav-tab" class="tab"
+ tab-contents="otherCertsTab">
+ <span class="tab-label"
+ i18n-content="unknownCertsTabTitle"></span>
+ <span class="active-tab-label" i18n-content="unknownCertsTabTitle"></span>
+ </span>
+ </div>
+ <!-- TODO(mattm): get rid of use of tables? -->
+ <!-- Tab contents -->
+ <div id="personalCertsTab" class="subpages-tab-contents">
+ <table class="certificate-tree-table">
+ <tr><td>
+ <span i18n-content="personalCertsTabDescription"></span>
+ </td></tr>
+ <tr><td>
+ <tree id="personalCertsTab-tree" class="certificate-tree"
+ icon-visibility="parent"></tree>
+ </td></tr>
+ <tr><td>
+ <button id="personalCertsTab-view" i18n-content="view_certificate"
+ disabled></button>
+ <!-- TODO(mattm):
+ <button id="personalCertsTab-backup-all"
+ i18n-content="export_all_certificates"
+ disabled></button>
+ -->
+ <button id="personalCertsTab-import" i18n-content="import_certificate"
+ ></button>
+ <if expr="pp_ifdef('chromeos')">
+ <button id="personalCertsTab-import-and-bind"
+ i18n-content="importAndBindCertificate" disabled></button>
+ </if>
+ <button id="personalCertsTab-backup" i18n-content="export_certificate"
+ disabled></button>
+ <button id="personalCertsTab-delete" i18n-content="delete_certificate"
+ disabled></button>
+ </td></tr>
+ </table>
+ </div>
+ <div id="serverCertsTab" class="subpages-tab-contents">
+ <table class="certificate-tree-table">
+ <tr><td>
+ <span i18n-content="serverCertsTabDescription"></span>
+ </td></tr>
+ <tr><td>
+ <tree id="serverCertsTab-tree" class="certificate-tree"
+ icon-visibility="parent"></tree>
+ </td></tr>
+ <tr><td>
+ <button id="serverCertsTab-view" i18n-content="view_certificate"
+ disabled></button>
+ <!-- TODO(mattm):
+ <button id="serverCertsTab-edit" i18n-content="edit_certificate"
+ disabled></button>
+ -->
+ <button id="serverCertsTab-import" i18n-content="import_certificate"
+ ></button>
+ <button id="serverCertsTab-export" i18n-content="export_certificate"
+ disabled></button>
+ <button id="serverCertsTab-delete" i18n-content="delete_certificate"
+ disabled></button>
+ </td></tr>
+ </table>
+ </div>
+ <div id="caCertsTab" class="subpages-tab-contents">
+ <table class="certificate-tree-table">
+ <tr><td>
+ <span i18n-content="caCertsTabDescription"></span>
+ </td></tr>
+ <tr><td>
+ <tree id="caCertsTab-tree" class="certificate-tree"
+ icon-visibility="parent"></tree>
+ </td></tr>
+ <tr><td>
+ <button id="caCertsTab-view" i18n-content="view_certificate"
+ disabled></button>
+ <button id="caCertsTab-edit" i18n-content="edit_certificate"
+ disabled></button>
+ <button id="caCertsTab-import" i18n-content="import_certificate"
+ ></button>
+ <button id="caCertsTab-export" i18n-content="export_certificate"
+ disabled></button>
+ <button id="caCertsTab-delete" i18n-content="delete_certificate"
+ disabled></button>
+ </td></tr>
+ </table>
+ </div>
+ <div id="otherCertsTab" class="subpages-tab-contents">
+ <table class="certificate-tree-table">
+ <tr><td>
+ <span i18n-content="unknownCertsTabDescription"></span>
+ </td></tr>
+ <tr><td>
+ <tree id="otherCertsTab-tree" class="certificate-tree"
+ icon-visibility="parent"></tree>
+ </td></tr>
+ <tr><td>
+ <button id="otherCertsTab-view" i18n-content="view_certificate"
+ disabled></button>
+ <button id="otherCertsTab-export" i18n-content="export_certificate"
+ disabled></button>
+ <button id="otherCertsTab-delete" i18n-content="delete_certificate"
+ disabled></button>
+ </td></tr>
+ </table>
+ </div>
+</div>
diff --git a/chrome/browser/resources/options2/certificate_manager.js b/chrome/browser/resources/options2/certificate_manager.js
new file mode 100644
index 0000000..59a7efd
--- /dev/null
+++ b/chrome/browser/resources/options2/certificate_manager.js
@@ -0,0 +1,253 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+
+ var OptionsPage = options.OptionsPage;
+
+ /////////////////////////////////////////////////////////////////////////////
+ // CertificateManagerTab class:
+
+ /**
+ * blah
+ * @param {!string} id The id of this tab.
+ */
+ function CertificateManagerTab(id) {
+ this.tree = $(id + '-tree');
+
+ options.CertificatesTree.decorate(this.tree);
+ this.tree.addEventListener('change',
+ this.handleCertificatesTreeChange_.bind(this));
+
+ var tree = this.tree;
+
+ this.viewButton = $(id + '-view');
+ this.viewButton.onclick = function(e) {
+ var selected = tree.selectedItem;
+ chrome.send('viewCertificate', [selected.data.id]);
+ }
+
+ this.editButton = $(id + '-edit');
+ if (this.editButton !== null) {
+ if (id == 'serverCertsTab') {
+ this.editButton.onclick = function(e) {
+ var selected = tree.selectedItem;
+ chrome.send('editServerCertificate', [selected.data.id]);
+ }
+ } else if (id == 'caCertsTab') {
+ this.editButton.onclick = function(e) {
+ var data = tree.selectedItem.data;
+ CertificateEditCaTrustOverlay.show(data.id, data.name);
+ }
+ }
+ }
+
+ this.backupButton = $(id + '-backup');
+ if (this.backupButton !== null) {
+ this.backupButton.onclick = function(e) {
+ var selected = tree.selectedItem;
+ chrome.send('exportPersonalCertificate', [selected.data.id]);
+ }
+ }
+
+ this.backupAllButton = $(id + '-backup-all');
+ if (this.backupAllButton !== null) {
+ this.backupAllButton.onclick = function(e) {
+ chrome.send('exportAllPersonalCertificates', []);
+ }
+ }
+
+ this.importButton = $(id + '-import');
+ if (this.importButton !== null) {
+ if (id == 'personalCertsTab') {
+ this.importButton.onclick = function(e) {
+ chrome.send('importPersonalCertificate', [false]);
+ }
+ } else if (id == 'serverCertsTab') {
+ this.importButton.onclick = function(e) {
+ chrome.send('importServerCertificate', []);
+ }
+ } else if (id == 'caCertsTab') {
+ this.importButton.onclick = function(e) {
+ chrome.send('importCaCertificate', []);
+ }
+ }
+ }
+
+ this.importAndBindButton = $(id + '-import-and-bind');
+ if (this.importAndBindButton !== null) {
+ if (id == 'personalCertsTab') {
+ this.importAndBindButton.onclick = function(e) {
+ chrome.send('importPersonalCertificate', [true]);
+ }
+ }
+ }
+
+ this.exportButton = $(id + '-export');
+ if (this.exportButton !== null) {
+ this.exportButton.onclick = function(e) {
+ var selected = tree.selectedItem;
+ chrome.send('exportCertificate', [selected.data.id]);
+ }
+ }
+
+ this.deleteButton = $(id + '-delete');
+ this.deleteButton.onclick = function(e) {
+ var data = tree.selectedItem.data;
+ AlertOverlay.show(
+ localStrings.getStringF(id + 'DeleteConfirm', data.name),
+ localStrings.getString(id + 'DeleteImpact'),
+ localStrings.getString('ok'),
+ localStrings.getString('cancel'),
+ function() {
+ tree.selectedItem = null;
+ chrome.send('deleteCertificate', [data.id]);
+ });
+ }
+ }
+
+ CertificateManagerTab.prototype = {
+
+ /**
+ * Update button state.
+ * @private
+ * @param {!Object} data The data of the selected item.
+ */
+ updateButtonState: function(data) {
+ var isCert = !!data && data.id.substr(0, 5) == 'cert-';
+ var readOnly = !!data && data.readonly;
+ var hasChildren = this.tree.items.length > 0;
+ this.viewButton.disabled = !isCert;
+ if (this.editButton !== null)
+ this.editButton.disabled = !isCert;
+ if (this.backupButton !== null)
+ this.backupButton.disabled = !isCert;
+ if (this.backupAllButton !== null)
+ this.backupAllButton.disabled = !hasChildren;
+ if (this.exportButton !== null)
+ this.exportButton.disabled = !isCert;
+ this.deleteButton.disabled = !isCert || readOnly;
+ },
+
+ /**
+ * Handles certificate tree selection change.
+ * @private
+ * @param {!Event} e The change event object.
+ */
+ handleCertificatesTreeChange_: function(e) {
+ var data = null;
+ if (this.tree.selectedItem) {
+ data = this.tree.selectedItem.data;
+ }
+
+ this.updateButtonState(data);
+ },
+
+ }
+
+ // TODO(xiyuan): Use notification from backend instead of polling.
+ // TPM token check polling timer.
+ var tpmPollingTimer;
+
+ // Initiate tpm token check if needed.
+ function checkTpmToken() {
+ var importAndBindButton = $('personalCertsTab-import-and-bind');
+
+ if (importAndBindButton && importAndBindButton.disabled)
+ chrome.send('checkTpmTokenReady');
+ }
+
+ // Stop tpm polling timer.
+ function stopTpmTokenCheckPolling() {
+ if (tpmPollingTimer) {
+ window.clearTimeout(tpmPollingTimer);
+ tpmPollingTimer = undefined;
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////////////
+ // CertificateManager class:
+
+ /**
+ * Encapsulated handling of ChromeOS accounts options page.
+ * @constructor
+ */
+ function CertificateManager(model) {
+ OptionsPage.call(this, 'certificates',
+ templateData.certificateManagerPageTabTitle,
+ 'certificateManagerPage');
+ }
+
+ cr.addSingletonGetter(CertificateManager);
+
+ CertificateManager.prototype = {
+ __proto__: OptionsPage.prototype,
+
+ initializePage: function() {
+ OptionsPage.prototype.initializePage.call(this);
+
+ this.personalTab = new CertificateManagerTab('personalCertsTab');
+ this.serverTab = new CertificateManagerTab('serverCertsTab');
+ this.caTab = new CertificateManagerTab('caCertsTab');
+ this.otherTab = new CertificateManagerTab('otherCertsTab');
+
+ this.addEventListener('visibleChange', this.handleVisibleChange_);
+ },
+
+ initalized_: false,
+
+ /**
+ * Handler for OptionsPage's visible property change event.
+ * @private
+ * @param {Event} e Property change event.
+ */
+ handleVisibleChange_: function(e) {
+ if (!this.initalized_ && this.visible) {
+ this.initalized_ = true;
+ OptionsPage.showTab($('personal-certs-nav-tab'));
+ chrome.send('populateCertificateManager');
+ }
+
+ if (cr.isChromeOS) {
+ // Ensure TPM token check on visible and stop polling when hidden.
+ if (this.visible)
+ checkTpmToken();
+ else
+ stopTpmTokenCheckPolling();
+ }
+ }
+ };
+
+ // CertificateManagerHandler callbacks.
+ CertificateManager.onPopulateTree = function(args) {
+ $(args[0]).populate(args[1]);
+ };
+
+ CertificateManager.exportPersonalAskPassword = function(args) {
+ CertificateBackupOverlay.show();
+ };
+
+ CertificateManager.importPersonalAskPassword = function(args) {
+ CertificateRestoreOverlay.show();
+ };
+
+ CertificateManager.onCheckTpmTokenReady = function(ready) {
+ var importAndBindButton = $('personalCertsTab-import-and-bind');
+ if (importAndBindButton) {
+ importAndBindButton.disabled = !ready;
+
+ // Check again after 5 seconds if Tpm is not ready and certificate manager
+ // is still visible.
+ if (!ready && CertificateManager.getInstance().visible)
+ tpmPollingTimer = window.setTimeout(checkTpmToken, 5000);
+ }
+ };
+
+ // Export
+ return {
+ CertificateManagerTab: CertificateManagerTab,
+ CertificateManager: CertificateManager
+ };
+
+});
diff --git a/chrome/browser/resources/options2/certificate_restore_overlay.html b/chrome/browser/resources/options2/certificate_restore_overlay.html
new file mode 100644
index 0000000..30b380d
--- /dev/null
+++ b/chrome/browser/resources/options2/certificate_restore_overlay.html
@@ -0,0 +1,17 @@
+<div id="certificateRestoreOverlay" class="page" hidden>
+ <h1 i18n-content="certificateRestorePasswordDescription"></h1>
+ <div class="content-area">
+ <label id="certificateRestorePasswordLabel">
+ <span i18n-content="certificatePasswordLabel"></span>
+ <input id="certificateRestorePassword" type="password">
+ </label>
+ </div>
+ <div class="action-area">
+ <div class="button-strip">
+ <button id="certificateRestoreCancelButton" type="reset"
+ i18n-content="cancel"></button>
+ <button id="certificateRestoreOkButton" type="submit" i18n-content="ok">
+ </button>
+ </div>
+ </div>
+</div>
diff --git a/chrome/browser/resources/options2/certificate_restore_overlay.js b/chrome/browser/resources/options2/certificate_restore_overlay.js
new file mode 100644
index 0000000..d76a329
--- /dev/null
+++ b/chrome/browser/resources/options2/certificate_restore_overlay.js
@@ -0,0 +1,97 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+ const OptionsPage = options.OptionsPage;
+
+ /**
+ * CertificateRestoreOverlay class
+ * Encapsulated handling of the 'enter restore password' overlay page.
+ * @class
+ */
+ function CertificateRestoreOverlay() {
+ OptionsPage.call(this, 'certificateRestore',
+ '',
+ 'certificateRestoreOverlay');
+ }
+
+ cr.addSingletonGetter(CertificateRestoreOverlay);
+
+ CertificateRestoreOverlay.prototype = {
+ __proto__: OptionsPage.prototype,
+
+ /**
+ * Initializes the page.
+ */
+ initializePage: function() {
+ OptionsPage.prototype.initializePage.call(this);
+
+ var self = this;
+ $('certificateRestoreCancelButton').onclick = function(event) {
+ self.cancelRestore_();
+ }
+ $('certificateRestoreOkButton').onclick = function(event) {
+ self.finishRestore_();
+ }
+
+ self.clearInputFields_();
+ },
+
+ /**
+ * Clears any uncommitted input, and dismisses the overlay.
+ * @private
+ */
+ dismissOverlay_: function() {
+ this.clearInputFields_();
+ OptionsPage.closeOverlay();
+ },
+
+ /**
+ * Attempt the restore operation.
+ * The overlay will be left up with inputs disabled until the backend
+ * finishes and dismisses it.
+ * @private
+ */
+ finishRestore_: function() {
+ chrome.send('importPersonalCertificatePasswordSelected',
+ [$('certificateRestorePassword').value]);
+ $('certificateRestoreCancelButton').disabled = true;
+ $('certificateRestoreOkButton').disabled = true;
+ },
+
+ /**
+ * Cancel the restore operation.
+ * @private
+ */
+ cancelRestore_: function() {
+ chrome.send('cancelImportExportCertificate');
+ this.dismissOverlay_();
+ },
+
+ /**
+ * Clears the value of each input field.
+ * @private
+ */
+ clearInputFields_: function() {
+ $('certificateRestorePassword').value = '';
+ $('certificateRestoreCancelButton').disabled = false;
+ $('certificateRestoreOkButton').disabled = false;
+ },
+ };
+
+ CertificateRestoreOverlay.show = function() {
+ CertificateRestoreOverlay.getInstance().clearInputFields_();
+ OptionsPage.navigateToPage('certificateRestore');
+ };
+
+ CertificateRestoreOverlay.dismiss = function() {
+ CertificateRestoreOverlay.getInstance().dismissOverlay_();
+ };
+
+ // Export
+ return {
+ CertificateRestoreOverlay: CertificateRestoreOverlay
+ };
+
+});
diff --git a/chrome/browser/resources/options2/certificate_tree.css b/chrome/browser/resources/options2/certificate_tree.css
new file mode 100644
index 0000000..566eb21
--- /dev/null
+++ b/chrome/browser/resources/options2/certificate_tree.css
@@ -0,0 +1,8 @@
+span.certUntrusted {
+ background-color: pink;
+ border: 1px solid red;
+ border-radius: 3px;
+ margin-right: 3px;
+ padding-left: 1px;
+ padding-right: 1px;
+}
diff --git a/chrome/browser/resources/options2/certificate_tree.js b/chrome/browser/resources/options2/certificate_tree.js
new file mode 100644
index 0000000..ee5d075
--- /dev/null
+++ b/chrome/browser/resources/options2/certificate_tree.js
@@ -0,0 +1,128 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+ const Tree = cr.ui.Tree;
+ const TreeItem = cr.ui.TreeItem;
+
+ /**
+ * Creates a new tree item for certificate data.
+ * @param {Object=} data Data used to create a certificate tree item.
+ * @constructor
+ * @extends {TreeItem}
+ */
+ function CertificateTreeItem(data) {
+ // TODO(mattm): other columns
+ var treeItem = new TreeItem({
+ label: data.name,
+ data: data
+ });
+ treeItem.__proto__ = CertificateTreeItem.prototype;
+
+ if (data.icon) {
+ treeItem.icon = data.icon;
+ }
+
+ if (data.untrusted) {
+ var badge = document.createElement('span');
+ badge.setAttribute('class', 'certUntrusted');
+ badge.textContent = localStrings.getString("badgeCertUntrusted");
+ treeItem.labelElement.insertBefore(
+ badge, treeItem.labelElement.firstChild);
+ }
+
+ return treeItem;
+ }
+
+ CertificateTreeItem.prototype = {
+ __proto__: TreeItem.prototype,
+
+ /**
+ * The tree path id/.
+ * @type {string}
+ */
+ get pathId() {
+ var parent = this.parentItem;
+ if (parent && parent instanceof CertificateTreeItem) {
+ return parent.pathId + ',' + this.data.id;
+ } else {
+ return this.data.id;
+ }
+ }
+ };
+
+ /**
+ * Creates a new cookies tree.
+ * @param {Object=} opt_propertyBag Optional properties.
+ * @constructor
+ * @extends {Tree}
+ */
+ var CertificatesTree = cr.ui.define('tree');
+
+ CertificatesTree.prototype = {
+ __proto__: Tree.prototype,
+
+ /** @inheritDoc */
+ decorate: function() {
+ Tree.prototype.decorate.call(this);
+ this.treeLookup_ = {};
+ },
+
+ /** @inheritDoc */
+ addAt: function(child, index) {
+ Tree.prototype.addAt.call(this, child, index);
+ if (child.data && child.data.id)
+ this.treeLookup_[child.data.id] = child;
+ },
+
+ /** @inheritDoc */
+ remove: function(child) {
+ Tree.prototype.remove.call(this, child);
+ if (child.data && child.data.id)
+ delete this.treeLookup_[child.data.id];
+ },
+
+ /**
+ * Clears the tree.
+ */
+ clear: function() {
+ // Remove all fields without recreating the object since other code
+ // references it.
+ for (var id in this.treeLookup_){
+ delete this.treeLookup_[id];
+ }
+ this.textContent = '';
+ },
+
+ /**
+ * Populate the tree.
+ * @param {Array} nodesData Nodes data array.
+ */
+ populate: function(nodesData) {
+ this.clear();
+
+ for (var i = 0; i < nodesData.length; ++i) {
+ var subnodes = nodesData[i]['subnodes'];
+ delete nodesData[i]['subnodes'];
+
+ var item = new CertificateTreeItem(nodesData[i]);
+ this.addAt(item, i);
+
+ for (var j = 0; j < subnodes.length; ++j) {
+ var subitem = new CertificateTreeItem(subnodes[j]);
+ item.addAt(subitem, j);
+ }
+ // Make tree expanded by default.
+ item.expanded = true;
+ }
+
+ cr.dispatchSimpleEvent(this, 'change');
+ },
+ };
+
+ return {
+ CertificatesTree: CertificatesTree
+ };
+});
+
diff --git a/chrome/browser/resources/options2/chromeos/accounts_options.html b/chrome/browser/resources/options2/chromeos/accounts_options.html
new file mode 100644
index 0000000..b324c4f
--- /dev/null
+++ b/chrome/browser/resources/options2/chromeos/accounts_options.html
@@ -0,0 +1,63 @@
+<div id="accountsPage" class="page" hidden>
+ <h1 i18n-content="accountsPage"></h1>
+ <div class="displaytable">
+ <section>
+ <div class="option">
+ <div id="ownerOnlyWarning" hidden>
+ <span i18n-content="owner_only"></span>
+ <span i18n-content="owner_user_id"></span>
+ </div>
+ <table class="option-control-table">
+ <tr>
+ <td class="option-name">
+ <div class="checkbox">
+ <label>
+ <input id="allowBwsiCheck" pref="cros.accounts.allowBWSI"
+ type="checkbox">
+ <span i18n-content="allow_BWSI"></span>
+ </label>
+ </div>
+ </td>
+ </tr>
+ <tr>
+ <td class="option-name">
+ <div class="checkbox">
+ <label>
+ <input id="showUserNamesCheck"
+ pref="cros.accounts.showUserNamesOnSignIn" type="checkbox">
+ <span i18n-content="show_user_on_signin"></span>
+ </label>
+ </div>
+ </td>
+ </tr>
+ <tr>
+ <td class="option-name">
+ <div class="checkbox">
+ <label>
+ <input id="useWhitelistCheck" pref="cros.accounts.allowGuest"
+ type="checkbox" inverted_pref>
+ <span i18n-content="use_whitelist"></span>
+ </label>
+ </div>
+ </td>
+ </tr>
+ <tr><td>&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>
+ </section>
+ </div>
+</div>
diff --git a/chrome/browser/resources/options2/chromeos/accounts_options.js b/chrome/browser/resources/options2/chromeos/accounts_options.js
new file mode 100644
index 0000000..af6bf9c
--- /dev/null
+++ b/chrome/browser/resources/options2/chromeos/accounts_options.js
@@ -0,0 +1,165 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+ var OptionsPage = options.OptionsPage;
+
+ /////////////////////////////////////////////////////////////////////////////
+ // AccountsOptions class:
+
+ /**
+ * Encapsulated handling of ChromeOS accounts options page.
+ * @constructor
+ */
+ function AccountsOptions(model) {
+ OptionsPage.call(this, 'accounts', templateData.accountsPageTabTitle,
+ 'accountsPage');
+ // Whether to show the whitelist.
+ this.showWhitelist_ = false;
+ }
+
+ cr.addSingletonGetter(AccountsOptions);
+
+ AccountsOptions.prototype = {
+ // Inherit AccountsOptions from OptionsPage.
+ __proto__: OptionsPage.prototype,
+
+ /**
+ * Initializes AccountsOptions page.
+ */
+ initializePage: function() {
+ // Call base class implementation to starts preference initialization.
+ OptionsPage.prototype.initializePage.call(this);
+
+ // Set up accounts page.
+ var userList = $('userList');
+ userList.addEventListener('remove', this.handleRemoveUser_);
+
+ var userNameEdit = $('userNameEdit');
+ options.accounts.UserNameEdit.decorate(userNameEdit);
+ userNameEdit.addEventListener('add', this.handleAddUser_);
+
+ // If the current user is not the owner, show some warning,
+ // and do not show the user list.
+ this.showWhitelist_ = AccountsOptions.currentUserIsOwner();
+ if (this.showWhitelist_) {
+ options.accounts.UserList.decorate(userList);
+ } else {
+ if (!AccountsOptions.whitelistIsManaged()) {
+ $('ownerOnlyWarning').hidden = false;
+ } else {
+ this.managed = true;
+ }
+ }
+
+ this.addEventListener('visibleChange', this.handleVisibleChange_);
+
+ $('useWhitelistCheck').addEventListener('change',
+ this.handleUseWhitelistCheckChange_.bind(this));
+
+ Preferences.getInstance().addEventListener(
+ $('useWhitelistCheck').pref,
+ this.handleUseWhitelistPrefChange_.bind(this));
+ },
+
+ /**
+ * Update user list control state.
+ * @private
+ */
+ updateControls_: function() {
+ $('userList').disabled =
+ $('userNameEdit').disabled = !this.showWhitelist_ ||
+ AccountsOptions.whitelistIsManaged() ||
+ !$('useWhitelistCheck').checked;
+ },
+
+ /**
+ * Handler for OptionsPage's visible property change event.
+ * @private
+ * @param {Event} e Property change event.
+ */
+ handleVisibleChange_: function(e) {
+ if (this.visible) {
+ this.updateControls_();
+ if (this.showWhitelist_)
+ $('userList').redraw();
+ }
+ },
+
+ /**
+ * Handler for allow guest check change.
+ * @private
+ */
+ handleUseWhitelistCheckChange_: function(e) {
+ // Whitelist existing users when guest login is being disabled.
+ if ($('useWhitelistCheck').checked) {
+ chrome.send('whitelistExistingUsers', []);
+ }
+
+ this.updateControls_();
+ },
+
+ /**
+ * handler for allow guest pref change.
+ * @private
+ */
+ handleUseWhitelistPrefChange_: function(e) {
+ this.updateControls_();
+ },
+
+ /**
+ * Handler for "add" event fired from userNameEdit.
+ * @private
+ * @param {Event} e Add event fired from userNameEdit.
+ */
+ handleAddUser_: function(e) {
+ chrome.send('whitelistUser', [e.user.email, e.user.name]);
+ },
+
+ /**
+ * Handler for "remove" event fired from userList.
+ * @private
+ * @param {Event} e Remove event fired from userList.
+ */
+ handleRemoveUser_: function(e) {
+ chrome.send('unwhitelistUser', [e.user.username]);
+ }
+ };
+
+ /**
+ * Returns whether the current user is owner or not.
+ */
+ AccountsOptions.currentUserIsOwner = function() {
+ return localStrings.getString('current_user_is_owner') == 'true';
+ };
+
+ /**
+ * Returns whether we're currently in guest mode.
+ */
+ AccountsOptions.loggedInAsGuest = function() {
+ return localStrings.getString('logged_in_as_guest') == 'true';
+ };
+
+ /**
+ * Returns whether the whitelist is managed by policy or not.
+ */
+ AccountsOptions.whitelistIsManaged = function() {
+ return localStrings.getString('whitelist_is_managed') == 'true';
+ };
+
+ /**
+ * Update account picture.
+ * @param {string} username User for which to update the image.
+ */
+ AccountsOptions.updateAccountPicture = function(username) {
+ if (this.showWhitelist_)
+ $('userList').updateAccountPicture(username);
+ };
+
+ // Export
+ return {
+ AccountsOptions: AccountsOptions
+ };
+
+});
diff --git a/chrome/browser/resources/options2/chromeos/accounts_options_page.css b/chrome/browser/resources/options2/chromeos/accounts_options_page.css
new file mode 100644
index 0000000..e32d550
--- /dev/null
+++ b/chrome/browser/resources/options2/chromeos/accounts_options_page.css
@@ -0,0 +1,87 @@
+.user-list-table {
+ border: 1px solid lightgrey;
+ border-collapse: collapse;
+ border-spacing: 0;
+}
+
+.user-name-edit-row {
+ border: 1px solid lightgrey;
+ background-color: #ebeffa;
+ padding: 5px;
+}
+
+.user-list-item {
+ padding: 2px;
+}
+
+.user-icon {
+ border: 1px solid black;
+ width: 26px;
+ height: 26px;
+}
+
+.user-email-label {
+ -webkit-margin-start: 10px;
+}
+
+.user-name-label {
+ color: darkgray;
+ -webkit-margin-start: 10px;
+}
+
+.user-email-name-block {
+ -webkit-box-flex: 1;
+ max-width: 318px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.remove-user-button {
+ background-image: url(chrome://theme/IDR_CLOSE_BAR);
+ width: 16px;
+ height: 16px;
+}
+
+.remove-user-button:hover {
+ background-image: url(chrome://theme/IDR_CLOSE_BAR_H);
+}
+
+#userList {
+ padding: 5px;
+ width: 366px;
+ height: 166px;
+}
+
+#userList[disabled],
+#userList[disabled] > [selected],
+#userList[disabled] > :hover {
+ border-color: hsl(0, 0%, 85%);
+}
+
+#userList[disabled] > [selected],
+#userList[disabled] > :hover {
+ background-color: hsl(0,0%,90%);
+}
+
+#userList[disabled] .remove-user-button {
+ visibility: hidden;
+}
+
+#userNameEdit {
+ border: 1px solid lightgrey;
+ width: 366px;
+}
+
+#ownerOnlyWarning {
+ margin-top: 10px;
+ margin-bottom: 10px;
+ padding-bottom: 1px;
+ -webkit-padding-start: 20px;
+ background-repeat: no-repeat;
+ background-image: url('warning.png');
+}
+
+input#userNameEdit:invalid {
+ background-color: #ff6666;
+}
diff --git a/chrome/browser/resources/options2/chromeos/accounts_user_list.js b/chrome/browser/resources/options2/chromeos/accounts_user_list.js
new file mode 100644
index 0000000..3fe33b7
--- /dev/null
+++ b/chrome/browser/resources/options2/chromeos/accounts_user_list.js
@@ -0,0 +1,194 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options.accounts', function() {
+ const List = cr.ui.List;
+ const ListItem = cr.ui.ListItem;
+ const ArrayDataModel = cr.ui.ArrayDataModel;
+
+ /**
+ * Creates a new user list.
+ * @param {Object=} opt_propertyBag Optional properties.
+ * @constructor
+ * @extends {cr.ui.List}
+ */
+ var UserList = cr.ui.define('list');
+
+ UserList.prototype = {
+ __proto__: List.prototype,
+
+ pref: 'cros.accounts.users',
+
+ /** @inheritDoc */
+ decorate: function() {
+ List.prototype.decorate.call(this);
+
+ // HACK(arv): http://crbug.com/40902
+ window.addEventListener('resize', this.redraw.bind(this));
+
+ var self = this;
+
+ // Listens to pref changes.
+ Preferences.getInstance().addEventListener(this.pref,
+ function(event) {
+ self.load_(event.value);
+ });
+ },
+
+ createItem: function(user) {
+ return new UserListItem(user);
+ },
+
+ /**
+ * Finds the index of user by given username (canonicalized email).
+ * @private
+ * @param {string} username The username to look for.
+ * @return {number} The index of the found user or -1 if not found.
+ */
+ indexOf_: function(username) {
+ var dataModel = this.dataModel;
+ if (!dataModel)
+ return -1;
+
+ var length = dataModel.length;
+ for (var i = 0; i < length; ++i) {
+ var user = dataModel.item(i);
+ if (user.username == username) {
+ return i;
+ }
+ }
+
+ return -1;
+ },
+
+ /**
+ * Update given user's account picture.
+ * @param {string} username User for which to update the image.
+ */
+ updateAccountPicture: function(username) {
+ var index = this.indexOf_(username);
+ if (index >= 0) {
+ var item = this.getListItemByIndex(index);
+ if (item)
+ item.updatePicture();
+ }
+ },
+
+ /**
+ * Loads given user list.
+ * @param {Array.<Object>} users An array of user info objects.
+ * @private
+ */
+ load_: function(users) {
+ this.dataModel = new ArrayDataModel(users);
+ },
+
+ /**
+ * Removes given user from the list.
+ * @param {Object} user User info object to be removed from user list.
+ * @private
+ */
+ removeUser_: function(user) {
+ var e = new Event('remove');
+ e.user = user;
+ this.dispatchEvent(e);
+ }
+ };
+
+ /**
+ * Whether the user list is disabled. Only used for display purpose.
+ * @type {boolean}
+ */
+ cr.defineProperty(UserList, 'disabled', cr.PropertyKind.BOOL_ATTR);
+
+ /**
+ * Creates a new user list item.
+ * @param user The user account this represents.
+ * @constructor
+ * @extends {cr.ui.ListItem}
+ */
+ function UserListItem(user) {
+ var el = cr.doc.createElement('div');
+ el.user = user;
+ UserListItem.decorate(el);
+ return el;
+ }
+
+ /**
+ * Decorates an element as a user account item.
+ * @param {!HTMLElement} el The element to decorate.
+ */
+ UserListItem.decorate = function(el) {
+ el.__proto__ = UserListItem.prototype;
+ el.decorate();
+ };
+
+ UserListItem.prototype = {
+ __proto__: ListItem.prototype,
+
+ /** @inheritDoc */
+ decorate: function() {
+ ListItem.prototype.decorate.call(this);
+
+ this.className = 'user-list-item';
+
+ this.icon_ = this.ownerDocument.createElement('img');
+ this.icon_.className = 'user-icon';
+ this.updatePicture();
+
+ var labelEmail = this.ownerDocument.createElement('span');
+ labelEmail.className = 'user-email-label';
+ labelEmail.textContent = this.user.email;
+
+ var labelName = this.ownerDocument.createElement('span');
+ labelName.className = 'user-name-label';
+ labelName.textContent = this.user.owner ?
+ localStrings.getStringF('username_format', this.user.name) :
+ this.user.name;
+
+ var emailNameBlock = this.ownerDocument.createElement('div');
+ emailNameBlock.className = 'user-email-name-block';
+ emailNameBlock.appendChild(labelEmail);
+ emailNameBlock.appendChild(labelName);
+ emailNameBlock.title = this.user.owner ?
+ localStrings.getStringF('username_format', this.user.email) :
+ this.user.email;
+
+ this.appendChild(this.icon_);
+ this.appendChild(emailNameBlock);
+
+ if (!this.user.owner) {
+ var removeButton = this.ownerDocument.createElement('button');
+ removeButton.className =
+ 'raw-button remove-user-button custom-appearance';
+ removeButton.addEventListener(
+ 'click', this.handleRemoveButtonClick_.bind(this));
+ this.appendChild(removeButton);
+ }
+ },
+
+ /**
+ * Handles click on the remove button.
+ * @param {Event} e Click event.
+ * @private
+ */
+ handleRemoveButtonClick_: function(e) {
+ // Handle left button click
+ if (e.button == 0)
+ this.parentNode.removeUser_(this.user);
+ },
+
+ /**
+ * Reloads user picture.
+ */
+ updatePicture: function() {
+ this.icon_.src = 'chrome://userimage/' + this.user.username +
+ '?id=' + (new Date()).getTime();
+ }
+ };
+
+ return {
+ UserList: UserList
+ };
+});
diff --git a/chrome/browser/resources/options2/chromeos/accounts_user_name_edit.js b/chrome/browser/resources/options2/chromeos/accounts_user_name_edit.js
new file mode 100644
index 0000000..025db25
--- /dev/null
+++ b/chrome/browser/resources/options2/chromeos/accounts_user_name_edit.js
@@ -0,0 +1,121 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options.accounts', function() {
+ const Event = cr.Event;
+
+ // Email alias only, assuming it's a gmail address.
+ // e.g. 'john'
+ // {name: 'john', email: 'john@gmail.com'}
+ const format1String =
+ '^\\s*([\\w\\.!#\\$%&\'\\*\\+-\\/=\\?\\^`\\{\\|\\}~]+)\\s*$';
+ // Email address only.
+ // e.g. 'john@chromium.org'
+ // {name: 'john', email: 'john@chromium.org'}
+ const format2String =
+ '^\\s*([\\w\\.!#\\$%&\'\\*\\+-\\/=\\?\\^`\\{\\|\\}~]+)@' +
+ '([A-Za-z0-9\-]{2,63}\\..+)\\s*$';
+ // Full format.
+ // e.g. '"John Doe" <john@chromium.org>'
+ // {name: 'John doe', email: 'john@chromium.org'}
+ const format3String =
+ '^\\s*"{0,1}([^"]+)"{0,1}\\s*' +
+ '<([\\w\\.!#\\$%&\'\\*\\+-\\/=\\?\\^`\\{\\|\\}~]+@' +
+ '[A-Za-z0-9\-]{2,63}\\..+)>\\s*$';
+
+ /**
+ * Creates a new user name edit element.
+ * @param {Object=} opt_propertyBag Optional properties.
+ * @constructor
+ * @extends {HTMLInputElement}
+ */
+ var UserNameEdit = cr.ui.define('input');
+
+ UserNameEdit.prototype = {
+ __proto__: HTMLInputElement.prototype,
+
+ /**
+ * Called when an element is decorated as a user name edit.
+ */
+ decorate: function() {
+ this.pattern = format1String + '|' + format2String + '|' +
+ format3String;
+
+ this.onkeypress = this.handleKeyPress_.bind(this);
+ },
+
+
+ /**
+ * Parses given str for user info.
+ *
+ * Note that the email parsing is based on RFC 5322 and does not support
+ * IMA (Internationalized Email Address). We take only the following chars
+ * as valid for an email alias (aka local-part):
+ * - Letters: a–z, A–Z
+ * - Digits: 0-9
+ * - Characters: ! # $ % & ' * + - / = ? ^ _ ` { | } ~
+ * - Dot: . (Note that we did not cover the cases that dot should not
+ * appear as first or last character and should not appear two or
+ * more times in a row.)
+ *
+ * @param {string} str A string to parse.
+ * @return {{name: string, email: string}} User info parsed from the string.
+ */
+ parse: function(str) {
+ const format1 = new RegExp(format1String);
+ const format2 = new RegExp(format2String);
+ const format3 = new RegExp(format3String);
+
+ var matches = format1.exec(str);
+ if (matches) {
+ return {
+ name: matches[1],
+ email: matches[1] + '@gmail.com'
+ };
+ }
+
+ matches = format2.exec(str);
+ if (matches) {
+ return {
+ name: matches[1],
+ email: matches[1] + '@' + matches[2]
+ };
+ }
+
+ matches = format3.exec(str);
+ if (matches) {
+ return {
+ name: matches[1],
+ email: matches[2]
+ };
+ }
+
+ return null;
+ },
+
+ /**
+ * Handler for key press event.
+ * @private
+ * @param {!Event} e The keypress event object.
+ */
+ handleKeyPress_: function(e) {
+ // Enter
+ if (e.keyCode == 13) {
+ var user = this.parse(this.value);
+ if (user) {
+ var e = new Event('add');
+ e.user = user;
+ this.dispatchEvent(e);
+ }
+
+ this.select();
+ }
+ }
+ };
+
+ return {
+ UserNameEdit: UserNameEdit
+ };
+});
+
diff --git a/chrome/browser/resources/options2/chromeos/bluetooth_device_list.js b/chrome/browser/resources/options2/chromeos/bluetooth_device_list.js
new file mode 100644
index 0000000..6187cbb
--- /dev/null
+++ b/chrome/browser/resources/options2/chromeos/bluetooth_device_list.js
@@ -0,0 +1,232 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options.system.bluetooth', function() {
+ const ArrayDataModel = cr.ui.ArrayDataModel;
+ const DeletableItem = options.DeletableItem;
+ const DeletableItemList = options.DeletableItemList;
+
+ /**
+ * Bluetooth settings constants.
+ */
+ function Constants() {}
+
+ /**
+ * Enumeration of supported device types.
+ * @enum {string}
+ */
+ // TODO(kevers): Prune list based on the set of devices that will be
+ // supported for V1 of the feature. The set will likely be restricted to
+ // mouse and keyboard. Others are temporarily included for testing device
+ // discovery.
+ Constants.DEVICE_TYPE = {
+ COMPUTER: 'computer',
+ HEADSET: 'headset',
+ KEYBOARD: 'input-keyboard',
+ MOUSE: 'input-mouse',
+ PHONE: 'phone',
+ };
+
+ /**
+ * Creates a new bluetooth list item.
+ * @param {{name: string,
+ * address: string,
+ * icon: Constants.DEVICE_TYPE,
+ * paired: boolean,
+ * connected: boolean,
+ * pairing: string|undefined,
+ * passkey: number|undefined,
+ * entered: number|undefined}} device
+ * Description of the Bluetooth device.
+ * @constructor
+ * @extends {options.DeletableItem}
+ */
+ function BluetoothListItem(device) {
+ var el = cr.doc.createElement('div');
+ el.__proto__ = BluetoothListItem.prototype;
+ el.data = {};
+ for (var key in device)
+ el.data[key] = device[key];
+ el.decorate();
+ // Only show the close button for paired devices.
+ el.deletable = device.paired;
+ return el;
+ }
+
+ BluetoothListItem.prototype = {
+ __proto__: DeletableItem.prototype,
+
+ /**
+ * Description of the Bluetooth device.
+ * @type {{name: string,
+ * address: string,
+ * icon: Constants.DEVICE_TYPE,
+ * paired: boolean,
+ * connected: boolean,
+ * pairing: string|undefined,
+ * passkey: number|undefined,
+ * entered: number|undefined}}
+ */
+ data: null,
+
+ /** @inheritDoc */
+ decorate: function() {
+ DeletableItem.prototype.decorate.call(this);
+ var label = this.ownerDocument.createElement('div');
+ label.className = 'bluetooth-device-label';
+ this.classList.add('bluetooth-device');
+ this.connected = this.data.connected;
+ // Though strictly speaking, a connected device will also be paired, we
+ // are interested in tracking paired devices that are not connected.
+ this.paired = this.data.paired && !this.data.connected;
+ this.connecting = !!this.data.pairing;
+ var content = this.data.name;
+ // Update label for devices that are paired but not connected.
+ if (this.paired) {
+ content = content + ' (' +
+ templateData['bluetoothDeviceNotConnected'] + ')';
+ }
+ label.textContent = content;
+ this.contentElement.appendChild(label);
+ },
+ };
+
+ /**
+ * Class for displaying a list of Bluetooth devices.
+ * @constructor
+ * @extends {options.DeletableItemList}
+ */
+ var BluetoothDeviceList = cr.ui.define('list');
+
+ BluetoothDeviceList.prototype = {
+ __proto__: DeletableItemList.prototype,
+
+ /** @inheritDoc */
+ decorate: function() {
+ DeletableItemList.prototype.decorate.call(this);
+ this.addEventListener('blur', this.onBlur_);
+ this.addEventListener('change', this.onChange_);
+ this.clear();
+ },
+
+ /**
+ * When the list loses focus, unselect all items in the list.
+ * @private
+ */
+ onBlur_: function() {
+ // TODO(kevers): Should this be pushed up to the list class?
+ this.selectionModel.unselectAll();
+ },
+
+ /**
+ * Updates the state of the button for adding a Bluetooth device in
+ * response to a change in the selected item.
+ * @private
+ */
+ onChange_: function() {
+ var item = this.selectedItem;
+ var disabled = !item || item.paired || item.conencted;
+ $('bluetooth-add-device-apply-button').disabled = disabled;
+ },
+
+ /**
+ * Adds a bluetooth device to the list of available devices. A check is
+ * made to see if the device is already in the list, in which case the
+ * existing device is updated.
+ * @param {{name: string,
+ * address: string,
+ * icon: Constants.DEVICE_TYPE,
+ * paired: boolean,
+ * connected: boolean,
+ * pairing: string|undefined,
+ * passkey: number|undefined,
+ * entered: number|undefined}} device
+ * Description of the bluetooth device.
+ * @return {boolean} True if the devies was successfully added or updated.
+ */
+ appendDevice: function(device) {
+ if (!this.isSupported_(device))
+ return false;
+ var index = this.find(device.address);
+ if (index == undefined) {
+ this.dataModel.push(device);
+ this.redraw();
+ } else {
+ this.dataModel.splice(index, 1, device);
+ this.redrawItem(index);
+ }
+ return true;
+ },
+
+ /**
+ * Perges all devices from the list.
+ */
+ clear: function() {
+ this.dataModel = new ArrayDataModel([]);
+ this.redraw();
+ },
+
+ /**
+ * Returns the index of the list entry with the matching address.
+ * @param {string} address Unique address of the Bluetooth device.
+ * @return {number|undefined} Index of the matching entry or
+ * undefined if no match found.
+ */
+ find: function(address) {
+ var size = this.dataModel.length;
+ for (var i = 0; i < size; i++) {
+ var entry = this.dataModel.item(i);
+ if (entry.address == address)
+ return i;
+ }
+ },
+
+ /** @inheritDoc */
+ createItem: function(entry) {
+ return new BluetoothListItem(entry);
+ },
+
+ /** @inheritDoc */
+ deleteItemAtIndex: function(index) {
+ var item = this.dataModel.item(index);
+ if (item && (item.connected || item.paired)) {
+ // Inform the bluetooth adapter that we are disconnecting the device.
+ chrome.send('updateBluetoothDevice',
+ [item.address, item.connected ? 'disconnect' : 'forget']);
+ }
+ this.dataModel.splice(index, 1);
+ // Invalidate the list since it has a stale cache after a splice
+ // involving a deletion.
+ this.invalidate();
+ this.redraw();
+ },
+
+ /**
+ * Tests if the bluetooth device is supported based on the type of device.
+ * @param {Object.<string,string>} device Desription of the device.
+ * @return {boolean} true if the device is supported.
+ * @private
+ */
+ isSupported_: function(device) {
+ var target = device.icon;
+ for (var key in Constants.DEVICE_TYPE) {
+ if (Constants.DEVICE_TYPE[key] == target)
+ return true;
+ }
+ return false;
+ }
+ };
+
+ cr.defineProperty(BluetoothListItem, 'connected', cr.PropertyKind.BOOL_ATTR);
+
+ cr.defineProperty(BluetoothListItem, 'paired', cr.PropertyKind.BOOL_ATTR);
+
+ cr.defineProperty(BluetoothListItem, 'connecting', cr.PropertyKind.BOOL_ATTR);
+
+ return {
+ BluetoothListItem: BluetoothListItem,
+ BluetoothDeviceList: BluetoothDeviceList,
+ Constants: Constants
+ };
+});
diff --git a/chrome/browser/resources/options2/chromeos/bluetooth_list_element.js b/chrome/browser/resources/options2/chromeos/bluetooth_list_element.js
new file mode 100644
index 0000000..1b6ef82
--- /dev/null
+++ b/chrome/browser/resources/options2/chromeos/bluetooth_list_element.js
@@ -0,0 +1,387 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options.system.bluetooth', function() {
+ /**
+ * Bluetooth settings constants.
+ */
+ function Constants() {}
+
+ /**
+ * Enumeration of supported device types. Each device type has an
+ * associated icon and CSS style.
+ * @enum {string}
+ */
+ Constants.DEVICE_TYPE = {
+ COMPUTER: 'computer',
+ HEADSET: 'headset',
+ KEYBOARD: 'input-keyboard',
+ MOUSE: 'input-mouse',
+ PHONE: 'phone',
+ };
+
+ /**
+ * Enumeration of possible states for a bluetooth device. The value
+ * associated with each state maps to a localized string in the global
+ * variable 'templateData'.
+ * @enum {string}
+ */
+ Constants.DEVICE_STATUS = {
+ CONNECTED: 'bluetoothDeviceConnected',
+ CONNECTING: 'bluetoothDeviceConnecting',
+ FAILED_PAIRING: 'bluetoothDeviceFailedPairing',
+ NOT_PAIRED: 'bluetoothDeviceNotPaired',
+ PAIRED: 'bluetoothDevicePaired'
+ };
+
+ /**
+ * Enumeration of possible states during pairing. The value associated
+ * with each state maps to a loalized string in the global variable
+ * 'tempateData'.
+ * @enum {string}
+ */
+ Constants.PAIRING = {
+ CONFIRM_PASSKEY: 'bluetoothConfirmPasskey',
+ ENTER_PASSKEY: 'bluetoothEnterPasskey',
+ FAILED_CONNECT_INSTRUCTIONS: 'bluetoothFailedPairingInstructions',
+ REMOTE_PASSKEY: 'bluetoothRemotePasskey'
+ };
+
+ /**
+ * Creates an element for storing a list of bluetooth devices.
+ * @param {Object=} opt_propertyBag Optional properties.
+ * @constructor
+ * @extends {HTMLDivElement}
+ */
+ var BluetoothListElement = cr.ui.define('div');
+
+ BluetoothListElement.prototype = {
+ __proto__: HTMLDivElement.prototype,
+
+ /** @inheritDoc */
+ decorate: function() {
+ },
+
+ /**
+ * Loads given list of bluetooth devices. This list will comprise of
+ * devices that are currently connected. New devices are discovered
+ * via the 'Find devices' button.
+ * @param {Array} devices An array of bluetooth devices.
+ */
+ load: function(devices) {
+ this.textContent = '';
+ for (var i = 0; i < devices.length; i++) {
+ if (this.isSupported_(devices[i]))
+ this.appendChild(new BluetoothItem(devices[i]));
+ }
+ },
+
+ /**
+ * Adds a bluetooth device to the list of available devices. A check is
+ * made to see if the device is already in the list, in which case the
+ * existing device is updated.
+ * @param {Object.<string,string>} device Description of the bluetooth
+ * device.
+ * @return {boolean} True if the devies was successfully added or updated.
+ */
+ appendDevice: function(device) {
+ if (!this.isSupported_(device))
+ return false;
+ var item = new BluetoothItem(device);
+ var existing = this.findDevice(device.address);
+ if (existing)
+ this.replaceChild(item, existing);
+ else
+ this.appendChild(item);
+ return true;
+ },
+
+ /**
+ * Scans the list of elements corresponding to discovered Bluetooth
+ * devices for one with a matching address.
+ * @param {string} address The address of the device.
+ * @return {Element|undefined} Element corresponding to the device address
+ * or undefined if no corresponding element is found.
+ */
+ findDevice: function(address) {
+ var candidate = this.firstChild;
+ while (candidate) {
+ if (candidate.data.address == address)
+ return candidate;
+ candidate = candidate.nextSibling;
+ }
+ },
+
+ /**
+ * Tests if the bluetooth device is supported based on the type of device.
+ * @param {Object.<string,string>} device Desription of the device.
+ * @return {boolean} true if the device is supported.
+ * @private
+ */
+ isSupported_: function(device) {
+ var target = device.icon;
+ for (var key in Constants.DEVICE_TYPE) {
+ if (Constants.DEVICE_TYPE[key] == target)
+ return true;
+ }
+ return false;
+ }
+ };
+
+ /**
+ * Creates an element in the list of bluetooth devices.
+ * @param {{name: string,
+ * address: string,
+ * icon: Constants.DEVICE_TYPE,
+ * paired: boolean,
+ * connected: boolean,
+ * pairing: string|undefined,
+ * passkey: number|undefined,
+ * entered: number|undefined}} device
+ * Decription of the bluetooth device.
+ * @constructor
+ */
+ function BluetoothItem(device) {
+ var el = $('bluetooth-item-template').cloneNode(true);
+ el.__proto__ = BluetoothItem.prototype;
+ el.removeAttribute('id');
+ el.hidden = false;
+ el.data = {};
+ for (var key in device)
+ el.data[key] = device[key];
+ el.decorate();
+ return el;
+ }
+
+ BluetoothItem.prototype = {
+ __proto__: HTMLDivElement.prototype,
+
+ /** @inheritDoc */
+ decorate: function() {
+ this.className = 'bluetooth-item';
+ this.connected = this.data.connected;
+ // Though strictly speaking, a connected device will also be paired,
+ // we are interested in tracking paired devices that are not connected.
+ this.paired = this.data.paired && !this.data.connected;
+ this.connecting = !!this.data.pairing;
+ this.addLabels_();
+ this.addButtons_();
+ },
+
+ /**
+ * Retrieves the descendent element with the matching class name.
+ * @param {string} className The class name for the target element.
+ * @return {Element|undefined} Returns the matching element if
+ * found and unique.
+ * @private
+ */
+ getNodeByClass_:function(className) {
+ var elements = this.getElementsByClassName(className);
+ if (elements && elements.length == 1)
+ return elements[0];
+ },
+
+ /**
+ * Sets the text content for an element.
+ * @param {string} className The class name of the target element.
+ * @param {string} label Text content for the element.
+ * @private
+ */
+ setLabel_: function(className, label) {
+ var el = this.getNodeByClass_(className);
+ el.textContent = label;
+ },
+
+ /**
+ * Adds an element containing the display name, status and device pairing
+ * instructions.
+ * @private
+ */
+ addLabels_: function() {
+ this.setLabel_('network-name-label', this.data.name);
+ var status;
+ if (this.data.connected)
+ status = Constants.DEVICE_STATUS.CONNECTED;
+ else if (this.data.pairing)
+ status = Constants.DEVICE_STATUS.CONNECTING;
+ if (status) {
+ var statusMessage = templateData[status];
+ if (statusMessage)
+ this.setLabel_('network-status-label', statusMessage);
+ if (this.connecting) {
+ var spinner = this.getNodeByClass_('inline-spinner');
+ spinner.hidden = false;
+ }
+ }
+ if (this.data.pairing)
+ this.addPairingInstructions_();
+ },
+
+ /**
+ * Adds instructions on how to complete the pairing process.
+ * @param {!Element} textDiv Target element for inserting the instructions.
+ * @private
+ */
+ addPairingInstructions_: function() {
+ var instructionsEl = this.getNodeByClass_('bluetooth-instructions');
+ var message = templateData[this.data.pairing];
+ var array = this.formatInstructions_(message);
+ for (var i = 0; i < array.length; i++) {
+ instructionsEl.appendChild(array[i]);
+ }
+ if (this.data.pairing == Constants.PAIRING.ENTER_PASSKEY) {
+ var input = this.ownerDocument.createElement('input');
+ input.type = 'text';
+ input.className = 'bluetooth-passkey-field';
+ instructionsEl.appendChild(input);
+ }
+ },
+
+ /**
+ * Formats the pairing instruction, which may contain labels for
+ * substitution. The label '%1' is replaced with the passkey, and '%2'
+ * is replaced with the name of the bluetooth device. Formatting of the
+ * passkey depends on the type of validation.
+ * @param {string} instructions The source instructions to format.
+ * @return {Array.<Element>} Array of formatted elements.
+ */
+ formatInstructions_: function(instructions) {
+ var array = [];
+ var index = instructions.indexOf('%');
+ if (index >= 0) {
+ array.push(this.createTextElement_(instructions.substring(0, index)));
+ var labelPlaceholder = instructions.charAt(index + 1);
+ // ... handle the placeholder
+ switch (labelPlaceholder) {
+ case '1':
+ array.push(this.createPasskeyElement_());
+ break;
+ case '2':
+ array.push(this.createTextElement_(this.data.name));
+ }
+ array = array.concat(this.formatInstructions_(instructions.substring(
+ index + 2)));
+ } else {
+ array.push(this.createTextElement_(instructions));
+ }
+ return array;
+ },
+
+ /**
+ * Formats an element for displaying the passkey.
+ * @return {Element} Element containing the passkey.
+ */
+ createPasskeyElement_: function() {
+ var passkeyEl = document.createElement('div');
+ if (this.data.pairing == Constants.PAIRING.REMOTE_PASSKEY) {
+ passkeyEl.className = 'bluetooth-remote-passkey';
+ var key = String(this.data.passkey);
+ var progress = this.data.entered;
+ for (var i = 0; i < key.length; i++) {
+ var keyEl = document.createElement('div');
+ keyEl.textContent = key.charAt(i);
+ keyEl.className = 'bluetooth-passkey-char';
+ if (i < progress)
+ keyEl.classList.add('key-typed');
+ passkeyEl.appendChild(keyEl);
+ }
+ // Add return key symbol.
+ var keyEl = document.createElement('div');
+ keyEl.className = 'bluetooth-passkey-char';
+ keyEl.textContent = '\u23ce';
+ passkeyEl.appendChild(keyEl);
+ } else {
+ passkeyEl.className = 'bluetooth-confirm-passkey';
+ passkeyEl.textContent = this.data.passkey;
+ }
+ return passkeyEl;
+ },
+
+ /**
+ * Adds a text element.
+ * @param {string} text The text content of the new element.
+ * @param {string=} opt_style Optional parameter for the CSS class for
+ * formatting the text element.
+ * @return {Element} Element containing the text.
+ */
+ createTextElement_: function(text, array, opt_style) {
+ var el = this.ownerDocument.createElement('span');
+ el.textContent = text;
+ if (opt_style)
+ el.className = opt_style;
+ return el;
+ },
+
+ /**
+ * Adds buttons for updating the connectivity of a device.
+ * @private.
+ */
+ addButtons_: function() {
+ var buttonsDiv = this.getNodeByClass_('bluetooth-button-group');
+ var buttonLabelKey = null;
+ var callbackType = null;
+ if (this.connected) {
+ buttonLabelKey = 'bluetoothDisconnectDevice';
+ callbackType = 'disconnect';
+ } else if (this.paired) {
+ buttonLabelKey = 'bluetoothForgetDevice';
+ callbackType = 'forget';
+ } else if (this.connecting) {
+ if (this.data.pairing == Constants.PAIRING.CONFIRM_PASSKEY) {
+ buttonLabelKey = 'bluetoothRejectPasskey';
+ callbackType = 'reject';
+ } else {
+ buttonLabelKey = 'bluetoothCancel';
+ callbackType = 'cancel';
+ }
+ } else {
+ buttonLabelKey = 'bluetoothConnectDevice';
+ callbackType = 'connect';
+ }
+ if (buttonLabelKey && callbackType) {
+ var buttonEl = this.ownerDocument.createElement('button');
+ buttonEl.textContent = localStrings.getString(buttonLabelKey);
+ var self = this;
+ var callback = function(e) {
+ chrome.send('updateBluetoothDevice',
+ [self.data.address, callbackType]);
+ }
+ buttonEl.addEventListener('click', callback);
+ buttonsDiv.appendChild(buttonEl);
+ }
+ if (this.data.pairing == Constants.PAIRING.CONFIRM_PASSKEY ||
+ this.data.pairing == Constants.PAIRING.ENTER_PASSKEY) {
+ var buttonEl = this.ownerDocument.createElement('button');
+ buttonEl.className = 'accept-pairing-button';
+ var msg = this.data.pairing == Constants.PAIRING.CONFIRM_PASSKEY ?
+ 'bluetoothAcceptPasskey' : 'bluetoothConnectDevice';
+ buttonEl.textContent = localStrings.getString(msg);
+ var self = this;
+ var callback = function(e) {
+ var passkey = self.data.passkey;
+ if (self.data.pairing == Constants.PAIRING.ENTER_PASSKEY) {
+ var passkeyField = self.getNodeByClass_('bluetooth-passkey-field');
+ passkey = passkeyField.value;
+ }
+ chrome.send('updateBluetoothDevice',
+ [self.data.address, 'connect', String(passkey)]);
+ }
+ buttonEl.addEventListener('click', callback);
+ buttonsDiv.insertBefore(buttonEl, buttonsDiv.firstChild);
+ }
+ this.appendChild(buttonsDiv);
+ }
+ };
+
+ cr.defineProperty(BluetoothItem, 'connected', cr.PropertyKind.BOOL_ATTR);
+
+ cr.defineProperty(BluetoothItem, 'paired', cr.PropertyKind.BOOL_ATTR);
+
+ cr.defineProperty(BluetoothItem, 'connecting', cr.PropertyKind.BOOL_ATTR);
+
+ return {
+ Constants: Constants,
+ BluetoothListElement: BluetoothListElement
+ };
+});
diff --git a/chrome/browser/resources/options2/chromeos/cellular_plan_element.js b/chrome/browser/resources/options2/chromeos/cellular_plan_element.js
new file mode 100644
index 0000000..2eaa7d3
--- /dev/null
+++ b/chrome/browser/resources/options2/chromeos/cellular_plan_element.js
@@ -0,0 +1,132 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options.internet', function() {
+ /**
+ * Creates a new network list div.
+ * @param {Object=} opt_propertyBag Optional properties.
+ * @constructor
+ * @extends {HTMLDivElement}
+ */
+ var CellularPlanElement = cr.ui.define('div');
+
+ CellularPlanElement.prototype = {
+ __proto__: HTMLDivElement.prototype,
+
+ /** @inheritDoc */
+ decorate: function() {
+ },
+
+ /**
+ * Loads given network list.
+ * @param {Array} networks An array of network object.
+ */
+ load: function(plans) {
+ this.textContent = '';
+ if (!plans || !plans.length) {
+ var noplansDiv = this.ownerDocument.createElement('div');
+ noplansDiv.textContent = localStrings.getString('noPlansFound');
+ this.appendChild(detailsTable);
+ } else {
+ for (var i = 0; i < plans.length; ++i) {
+ this.appendChild(new CellularPlanItem(i, plans[i]));
+ }
+ }
+ }
+ };
+
+ /**
+ * Creates a new network item.
+ * @param {Object} network The network this represents.
+ * @constructor
+ * @extends {HTMLDivElement}
+ */
+ function CellularPlanItem(idx, plan) {
+ var el = cr.doc.createElement('div');
+ el.data = {
+ idx: idx,
+ planType: plan.planType,
+ name: plan.name,
+ planSummary: plan.planSummary,
+ dataRemaining: plan.dataRemaining,
+ planExpires: plan.planExpires,
+ warning: plan.warning
+ };
+ CellularPlanItem.decorate(el);
+ return el;
+ }
+
+
+ /**
+ * Decorates an element as a network item.
+ * @param {!HTMLElement} el The element to decorate.
+ */
+ CellularPlanItem.decorate = function(el) {
+ el.__proto__ = CellularPlanItem.prototype;
+ el.decorate();
+ };
+
+ CellularPlanItem.prototype = {
+ __proto__: HTMLDivElement.prototype,
+
+ /** @inheritDoc */
+ decorate: function() {
+ this.className = 'cellular-plan';
+ var detailsTable = this.createTable_('details-plan-table',
+ 'option-control-table');
+ this.addRow_(detailsTable, 'plan-details-info',
+ 'option-name', 'planSummary', this.data.planSummary);
+ this.addRow_(detailsTable, 'plan-details-info',
+ 'option-name', null, localStrings.getString('planName'),
+ 'option-value', 'planName', this.data.name);
+ this.addRow_(detailsTable, 'plan-details-info',
+ 'option-name', null, localStrings.getString('dataRemaining'),
+ 'option-value', 'dataRemaining', this.data.dataRemaining);
+ this.addRow_(detailsTable, 'plan-details-info',
+ 'option-name', null, localStrings.getString('planExpires'),
+ 'option-value', 'dataRemaining', this.data.planExpires);
+ if (this.data.warning && this.data.warning != "") {
+ this.addRow_(detailsTable, 'plan-details-info',
+ 'option-name', 'planWarning', this.data.warning);
+ }
+ this.appendChild(detailsTable);
+ this.appendChild(this.ownerDocument.createElement('hr'));
+ },
+
+ createTable_: function(tableId, tableClass) {
+ var table = this.ownerDocument.createElement('table');
+ table.id = tableId;
+ table.className = tableClass;
+ return table;
+ },
+
+ addRow_: function(table, rowClass, col1Class, col1Id, col1Value,
+ col2Class, col2Id, col2Value) {
+ var row = this.ownerDocument.createElement('tr');
+ if (rowClass)
+ row.className = rowClass;
+ var col1 = this.ownerDocument.createElement('td');
+ col1.className = col1Class;
+ if (col1Id)
+ col1.id = col1Id;
+ col1.textContent = col1Value;
+ if (!col2Class)
+ col1.setAttribute('colspan','2');
+ row.appendChild(col1);
+ if (col2Class) {
+ var col2 = this.ownerDocument.createElement('td');
+ col2.className = col2Class;
+ if (col2Id)
+ col2.id = col2Id;
+ col2.textContent = col2Value;
+ row.appendChild(col2);
+ }
+ table.appendChild(row);
+ }
+ };
+
+ return {
+ CellularPlanElement: CellularPlanElement
+ };
+});
diff --git a/chrome/browser/resources/options2/chromeos/change_picture_options.css b/chrome/browser/resources/options2/chromeos/change_picture_options.css
new file mode 100644
index 0000000..ced9728
--- /dev/null
+++ b/chrome/browser/resources/options2/chromeos/change_picture_options.css
@@ -0,0 +1,32 @@
+#images-grid {
+ -webkit-user-drag: none;
+ -webkit-user-select: none;
+ margin: 10px;
+ outline: none;
+ padding: 10px;
+}
+
+#images-grid * {
+ margin: 0;
+ padding: 0;
+}
+
+#images-grid img {
+ background-color: white;
+ height: 64px;
+ vertical-align: middle;
+ width: 64px;
+}
+
+#images-grid [role=listitem] {
+ border-radius: 4px;
+ border: 1px solid rgba(0, 0, 0, 0.15);
+ display: inline-block;
+ margin: 10px;
+ padding: 3px;
+}
+
+#images-grid [selected] {
+ border: 2px solid #06c;
+ padding: 2px;
+}
diff --git a/chrome/browser/resources/options2/chromeos/change_picture_options.html b/chrome/browser/resources/options2/chromeos/change_picture_options.html
new file mode 100644
index 0000000..cafb7e6
--- /dev/null
+++ b/chrome/browser/resources/options2/chromeos/change_picture_options.html
@@ -0,0 +1,5 @@
+<div id="change-picture-page" class="page" hidden>
+ <h1 i18n-content="changePicturePage"></h1>
+ <span i18n-content="changePicturePageDescription"></span>
+ <grid id="images-grid"></grid>
+</div>
diff --git a/chrome/browser/resources/options2/chromeos/change_picture_options.js b/chrome/browser/resources/options2/chromeos/change_picture_options.js
new file mode 100644
index 0000000..b986743
--- /dev/null
+++ b/chrome/browser/resources/options2/chromeos/change_picture_options.js
@@ -0,0 +1,267 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+
+ var OptionsPage = options.OptionsPage;
+ var UserImagesGrid = options.UserImagesGrid;
+ var ButtonImages = UserImagesGrid.ButtonImages;
+
+ /**
+ * Array of button URLs used on this page.
+ * @type {Array.<string>}
+ */
+ const ButtonImageUrls = [
+ ButtonImages.TAKE_PHOTO,
+ ButtonImages.CHOOSE_FILE
+ ];
+
+ /////////////////////////////////////////////////////////////////////////////
+ // ChangePictureOptions class:
+
+ /**
+ * Encapsulated handling of ChromeOS change picture options page.
+ * @constructor
+ */
+ function ChangePictureOptions() {
+ OptionsPage.call(
+ this,
+ 'changePicture',
+ localStrings.getString('changePicturePage'),
+ 'change-picture-page');
+ }
+
+ cr.addSingletonGetter(ChangePictureOptions);
+
+ ChangePictureOptions.prototype = {
+ // Inherit ChangePictureOptions from OptionsPage.
+ __proto__: options.OptionsPage.prototype,
+
+ /**
+ * Initializes ChangePictureOptions page.
+ */
+ initializePage: function() {
+ // Call base class implementation to start preferences initialization.
+ OptionsPage.prototype.initializePage.call(this);
+
+ var imageGrid = $('images-grid');
+ UserImagesGrid.decorate(imageGrid);
+
+ imageGrid.addEventListener('change',
+ this.handleImageSelected_.bind(this));
+ imageGrid.addEventListener('activate',
+ this.handleImageActivated_.bind(this));
+ imageGrid.addEventListener('dblclick',
+ this.handleImageDblClick_.bind(this));
+
+ // Add the "Choose file" button.
+ imageGrid.addItem(ButtonImages.CHOOSE_FILE,
+ localStrings.getString('chooseFile'),
+ this.handleChooseFile_.bind(this));
+
+ // Profile image data.
+ this.profileImage_ = imageGrid.addItem(
+ ButtonImages.PROFILE_PICTURE,
+ localStrings.getString('profilePhotoLoading'));
+
+ // Old user image data (if present).
+ this.oldImage_ = null;
+
+ chrome.send('onChangePicturePageInitialized');
+ },
+
+ /**
+ * Called right after the page has been shown to user.
+ */
+ didShowPage: function() {
+ $('images-grid').updateAndFocus();
+ chrome.send('onChangePicturePageShown');
+ },
+
+ /**
+ * Called right before the page is hidden.
+ */
+ willHidePage: function() {
+ var imageGrid = $('images-grid');
+ imageGrid.blur(); // Make sure the image grid is not active.
+ if (this.oldImage_) {
+ imageGrid.removeItem(this.oldImage_);
+ this.oldImage_ = null;
+ }
+ },
+
+ /**
+ * Closes current page, returning back to Personal Stuff page.
+ * @private
+ */
+ closePage_: function() {
+ OptionsPage.navigateToPage('personal');
+ },
+
+ /**
+ * Handles "Take photo" button activation.
+ * @private
+ */
+ handleTakePhoto_: function() {
+ chrome.send('takePhoto');
+ this.closePage_();
+ },
+
+ /**
+ * Handles "Choose a file" button activation.
+ * @private
+ */
+ handleChooseFile_: function() {
+ chrome.send('chooseFile');
+ this.closePage_();
+ },
+
+ /**
+ * Handles image selection change.
+ * @private
+ */
+ handleImageSelected_: function() {
+ var imageGrid = $('images-grid');
+ var url = imageGrid.selectedItemUrl;
+ // Ignore deselection, selection change caused by program itself and
+ // selection of one of the action buttons.
+ if (url &&
+ !imageGrid.inProgramSelection &&
+ ButtonImageUrls.indexOf(url) == -1) {
+ chrome.send('selectImage', [url]);
+ }
+ },
+
+ /**
+ * Handles image activation (by pressing Enter).
+ * @private
+ */
+ handleImageActivated_: function() {
+ switch ($('images-grid').selectedItemUrl) {
+ case ButtonImages.TAKE_PHOTO:
+ this.handleTakePhoto_();
+ break;
+ case ButtonImages.CHOOSE_FILE:
+ this.handleChooseFile_();
+ break;
+ default:
+ this.closePage_();
+ break;
+ }
+ },
+
+ /**
+ * Handles double click on the image grid.
+ * @param {Event} e Double click Event.
+ */
+ handleImageDblClick_: function(e) {
+ // Close page unless the click target is the grid itself or
+ // any of the buttons.
+ var url = e.target.src;
+ if (url && ButtonImageUrls.indexOf(url) == -1)
+ this.closePage_();
+ },
+
+ /**
+ * URL of the current user image.
+ * @type {string}
+ */
+ get currentUserImageUrl() {
+ return 'chrome://userimage/' + PersonalOptions.getLoggedInUsername() +
+ '?id=' + (new Date()).getTime();
+ },
+
+ /**
+ * Notifies about camera presence change.
+ * @param {boolean} present Whether a camera is present or not.
+ * @private
+ */
+ setCameraPresent_: function(present) {
+ var imageGrid = $('images-grid');
+ if (present && !this.takePhotoButton_) {
+ this.takePhotoButton_ = imageGrid.addItem(
+ ButtonImages.TAKE_PHOTO,
+ localStrings.getString('takePhoto'),
+ this.handleTakePhoto_.bind(this),
+ 1);
+ } else if (!present && this.takePhotoButton_) {
+ imageGrid.removeItem(this.takePhotoButton_);
+ this.takePhotoButton_ = null;
+ }
+ },
+
+ /**
+ * Adds or updates old user image taken from file/camera (neither a profile
+ * image nor a default one).
+ * @private
+ */
+ setOldImage_: function() {
+ var imageGrid = $('images-grid');
+ var url = this.currentUserImageUrl;
+ if (this.oldImage_) {
+ this.oldImage_ = imageGrid.updateItem(this.oldImage_, url);
+ } else {
+ // Insert next to the profile image.
+ var pos = imageGrid.indexOf(this.profileImage_) + 1;
+ this.oldImage_ = imageGrid.addItem(url, undefined, undefined, pos);
+ imageGrid.selectedItem = this.oldImage_;
+ }
+ },
+
+ /**
+ * Updates user's profile image.
+ * @param {string} imageUrl Profile image, encoded as data URL.
+ * @param {boolean} select If true, profile image should be selected.
+ * @private
+ */
+ setProfileImage_: function(imageUrl, select) {
+ var imageGrid = $('images-grid');
+ this.profileImage_ = imageGrid.updateItem(
+ this.profileImage_, imageUrl, localStrings.getString('profilePhoto'));
+ if (select)
+ imageGrid.selectedItem = this.profileImage_;
+ },
+
+ /**
+ * Selects user image with the given URL.
+ * @param {string} url URL of the image to select.
+ * @private
+ */
+ setSelectedImage_: function(url) {
+ $('images-grid').selectedItemUrl = url;
+ },
+
+ /**
+ * Appends default images to the image grid. Should only be called once.
+ * @param {Array.<string>} images An array of URLs to default images.
+ * @private
+ */
+ setDefaultImages_: function(images) {
+ var imageGrid = $('images-grid');
+ for (var i = 0, url; url = images[i]; i++) {
+ imageGrid.addItem(url);
+ }
+ },
+ };
+
+ // Forward public APIs to private implementations.
+ [
+ 'setCameraPresent',
+ 'setDefaultImages',
+ 'setOldImage',
+ 'setProfileImage',
+ 'setSelectedImage',
+ ].forEach(function(name) {
+ ChangePictureOptions[name] = function(value1, value2) {
+ ChangePictureOptions.getInstance()[name + '_'](value1, value2);
+ };
+ });
+
+ // Export
+ return {
+ ChangePictureOptions: ChangePictureOptions
+ };
+
+});
+
diff --git a/chrome/browser/resources/options2/chromeos/internet_detail.html b/chrome/browser/resources/options2/chromeos/internet_detail.html
new file mode 100644
index 0000000..d722066
--- /dev/null
+++ b/chrome/browser/resources/options2/chromeos/internet_detail.html
@@ -0,0 +1,364 @@
+<div id="detailsInternetPage" class="page" hidden>
+ <h1 id="inetTitle"></h1>
+ <!-- Navigation tabs -->
+ <div id="details-tab-strip" class="subpages-nav-tabs">
+ <span id="wifiNetworkNavTab" class="tab wifi-details"
+ tab-contents="wifiNetworkTab">
+ <span class="tab-label"
+ i18n-content="wifiNetworkTabLabel"></span>
+ <span class="active-tab-label" i18n-content="wifiNetworkTabLabel"></span>
+ </span>
+ <span id="vpnNavTab" class="tab vpn-details"
+ tab-contents="vpnTab">
+ <span class="tab-label"
+ i18n-content="vpnTabLabel"></span>
+ <span class="active-tab-label" i18n-content="vpnTabLabel"></span>
+ </span>
+ <span id="cellularPlanNavTab" class="tab cellular-details cdma-only"
+ tab-contents="cellularPlanTab">
+ <span class="tab-label"
+ i18n-content="cellularPlanTabLabel"></span>
+ <span class="active-tab-label" i18n-content="cellularPlanTabLabel"></span>
+ </span>
+ <span id="cellularConnNavTab" class="tab cellular-details"
+ tab-contents="cellularConnTab">
+ <span class="tab-label"
+ i18n-content="cellularConnTabLabel"></span>
+ <span class="active-tab-label" i18n-content="cellularConnTabLabel"></span>
+ </span>
+ <span id="cellularDeviceNavTab" class="tab cellular-details"
+ tab-contents="cellularDeviceTab">
+ <span class="tab-label"
+ i18n-content="cellularDeviceTabLabel"></span>
+ <span class="active-tab-label"
+ i18n-content="cellularDeviceTabLabel"></span>
+ </span>
+ <span id="internetNavTab" class="tab" tab-contents="internetTab">
+ <span class="tab-label" i18n-content="networkTabLabel"></span>
+ <span class="active-tab-label" i18n-content="networkTabLabel"></span>
+ </span>
+ <span id="security-nav-tab" class="tab cellular-details gsm-only"
+ tab-contents="security-tab">
+ <span class="tab-label" i18n-content="securityTabLabel"></span>
+ <span class="active-tab-label" i18n-content="securityTabLabel"></span>
+ </span>
+ </div>
+ <div id="wifiNetworkTab" class="subpages-tab-contents wifi-details">
+ <section>
+ <table class="ssid-table">
+ <tr>
+ <td class="option-name" i18n-content="inetSsid"></td>
+ <td id="inetSsid" class="option-value"></td>
+ </tr>
+ </table>
+ </section>
+ <section>
+ <table class="option-control-table">
+ <tr class="prefer-network">
+ <td>
+ <div class="checkbox">
+ <label>
+ <input id="preferNetworkWifi" type="checkbox">
+ <span i18n-content="inetPreferredNetwork"></span>
+ </label>
+ <span class="controlled-setting-indicator" data="preferred"
+ for="preferNetworkWifi"></span>
+ </div>
+ </td>
+ </tr>
+ <tr class="auto-connect-network">
+ <td>
+ <div class="checkbox">
+ <label>
+ <input id="autoConnectNetworkWifi" type="checkbox">
+ <span i18n-content="inetAutoConnectNetwork"></span>
+ </label>
+ <span class="controlled-setting-indicator" data="autoConnect"
+ for="autoConnectNetworkWifi"></span>
+ </div>
+ </td>
+ </tr>
+ </table>
+ </section>
+ <section id="passwordNetwork" class="password-details">
+ <table class="option-control-table">
+ <tr>
+ <td class="option-name" i18n-content="inetPassProtected"></td>
+ </tr>
+ </table>
+ </section>
+ <section id="sharedNetwork" class="shared-network">
+ <table class="option-control-table">
+ <tr>
+ <td class="option-name" i18n-content="inetNetworkShared"></td>
+ </tr>
+ </table>
+ </section>
+ </div>
+ <div id="vpnTab" class="subpages-tab-contents vpn-details">
+ <section>
+ <table class="option-control-table">
+ <tr>
+ <td class="option-name" i18n-content="inetServiceName"></td>
+ <td id="inetServiceName" class="option-value"></td>
+ </tr>
+ <tr>
+ <td class="option-name" i18n-content="inetServerHostname"></td>
+ <td id="inetServerHostname" class="option-value"></td>
+ </tr>
+ <tr>
+ <td class="option-name" i18n-content="inetProviderType"></td>
+ <td id="inetProviderType" class="option-value"></td>
+ </tr>
+ <tr>
+ <td class="option-name" i18n-content="inetUsername"></td>
+ <td id="inetUsername" class="option-value"></td>
+ </tr>
+ </table>
+ </section>
+ </div>
+ <div id="cellularPlanTab"
+ class="subpages-tab-contents cellular-details cdma-only">
+ <section>
+ <div>
+ <table id="details-plan-table" class="option-control-table">
+ <tr class="plan-loading-info">
+ <td i18n-content="planLoading" class="option-value"></td>
+ </tr>
+ <tr class="no-plan-info">
+ <td i18n-content="noPlansFound" class="option-value"></td>
+ </tr>
+ </table>
+ </div>
+ <div id="planList"></div>
+ </section>
+ <section class="plan-details-info">
+ <div class="checkbox">
+ <label>
+ <input id="showPlanNotifications" type="checkbox"
+ pref="settings.internet.mobile.show_plan_notifications">
+ <span i18n-content="showPlanNotifications"></span>
+ </label>
+ </div>
+ </section>
+ </div>
+ <div id="cellularConnTab" class="subpages-tab-contents cellular-details">
+ <section id="cellularNetworkOptions">
+ <table class="option-control-table">
+ <tr>
+ <td class="option-name" i18n-content="serviceName"></td>
+ <td id="serviceName" class="option-value"></td>
+ </tr>
+ <tr>
+ <td class="option-name" i18n-content="networkTechnology"></td>
+ <td id="networkTechnology" class="option-value"></td>
+ </tr>
+ <tr>
+ <td class="option-name" i18n-content="activationState"></td>
+ <td id="activationState" class="option-value"></td>
+ </tr>
+ <tr>
+ <td class="option-name" i18n-content="roamingState"></td>
+ <td id="roamingState" class="option-value"></td>
+ </tr>
+ <tr>
+ <td class="option-name" i18n-content="restrictedPool"></td>
+ <td id="restrictedPool" class="option-value"></td>
+ </tr>
+ <tr class="gsm-only">
+ <td class="option-name" i18n-content="operatorName"></td>
+ <td id="operatorName" class="option-value"></td>
+ </tr>
+ <tr class="gsm-only">
+ <td class="option-name" i18n-content="operatorCode"></td>
+ <td id="operatorCode" class="option-value"></td>
+ </tr>
+ <tr>
+ <td class="option-name" i18n-content="errorState"></td>
+ <td id="errorState" class="option-value"></td>
+ </tr>
+ <tr class="gsm-only apn-list-view">
+ <td class="option-name" i18n-content="cellularApnLabel"></td>
+ <td id="cellularApnLabel" class="option-value">
+ <select id="selectApn">
+ <option value="-1" i18n-content="cellularApnOther">
+ </option>
+ </select>
+ <span class="controlled-setting-indicator" data="providerApnList"
+ for="selectApn"></span>
+ </td>
+ </tr>
+ <tr class="gsm-only apn-details-view">
+ <td class="option-name" i18n-content="cellularApnLabel"></td>
+ <td id="cellularApnLabel" class="option-value">
+ <input id="cellularApn" type="text">
+ </td>
+ </tr>
+ <tr class="gsm-only apn-details-view">
+ <td class="option-name" i18n-content="cellularApnUsername"></td>
+ <td id="cellularApnUsernameLabel" class="option-value">
+ <input id="cellularApnUsername" type="text">
+ </td>
+ </tr>
+ <tr class="gsm-only apn-details-view">
+ <td class="option-name" i18n-content="cellularApnPassword"></td>
+ <td id="cellularApnPasswordLabel" class="option-value">
+ <input id="cellularApnPassword" type="password">
+ </td>
+ </tr>
+ <tr class="gsm-only apn-details-view">
+ <td class="option-name"></td>
+ <td class="option-value">
+ <button id="cellularApnUseDefault"
+ i18n-content="cellularApnUseDefault"></button>
+ <button id="cellularApnSet"
+ i18n-content="cellularApnSet"></button>
+ <button id="cellularApnCancel"
+ i18n-content="cellularApnCancel"></button>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="checkbox">
+ <label>
+ <input id="autoConnectNetworkCellular" type="checkbox">
+ <span i18n-content="inetAutoConnectNetwork"></span>
+ </label>
+ <span class="controlled-setting-indicator" data="autoConnect"
+ for="autoConnectNetworkCellular"></span>
+ </div>
+ </td>
+ </tr>
+ </table>
+ </section>
+ </div>
+ <div id="cellularDeviceTab" class="subpages-tab-contents cellular-details">
+ <section id="cellularDeviceOptions">
+ <table class="option-control-table">
+ <tr>
+ <td class="option-name" i18n-content="manufacturer"></td>
+ <td id="manufacturer" class="option-value"></td>
+ </tr>
+ <tr>
+ <td class="option-name" i18n-content="modelId"></td>
+ <td id="modelId" class="option-value"></td>
+ </tr>
+ <tr>
+ <td class="option-name" i18n-content="firmwareRevision"></td>
+ <td id="firmwareRevision" class="option-value"></td>
+ </tr>
+ <tr>
+ <td class="option-name" i18n-content="hardwareRevision"></td>
+ <td id="hardwareRevision" class="option-value"></td>
+ </tr>
+ <tr>
+ <td class="option-name" i18n-content="prlVersion"></td>
+ <td id="prlVersion" class="option-value"></td>
+ </tr>
+ <tr>
+ <td class="option-name">MEID:</td>
+ <td id="meid" class="option-value"></td>
+ </tr>
+ <tr>
+ <td class="option-name">ESN:</td>
+ <td id="esn" class="option-value"></td>
+ </tr>
+ <tr>
+ <td class="option-name">IMEI:</td>
+ <td id="imei" class="option-value"></td>
+ </tr>
+ <tr class="gsm-only">
+ <td class="option-name">IMSI:</td>
+ <td id="imsi" class="option-value"></td>
+ </tr>
+ <tr>
+ <td class="option-name">MDN:</td>
+ <td id="mdn" class="option-value"></td>
+ </tr>
+ <tr>
+ <td class="option-name">MIN:</td>
+ <td id="min" class="option-value"></td>
+ </tr>
+ </table>
+ </section>
+ </div>
+ <div id="internetTab" class="subpages-tab-contents">
+ <section id="advancedSection">
+ <table class="option-control-table">
+ <tr>
+ <td class="option-name" i18n-content="connectionState"></td>
+ <td id="connectionState" class="option-value"></td>
+ </tr>
+ <tr id="hardwareAddressRow">
+ <td class="option-name" i18n-content="hardwareAddress"></td>
+ <td id="hardwareAddress" class="option-value"></td>
+ </tr>
+ </table>
+ </section>
+ <section id="ipconfigSection">
+ <div>
+ <div id="ipTypeDHCPDiv" class="radio">
+ <label>
+ <input type="radio" name="iptype" id="ipTypeDHCP" value="1">
+ <span i18n-content="useDHCP"></span>
+ </label>
+ <span class="controlled-setting-indicator" data="ipconfigDHCP"
+ for="ipTypeDHCP"></span>
+ </div>
+ <div id="ipTypeStaticDiv" class="radio">
+ <label>
+ <input type="radio" name="iptype" id="ipTypeStatic" value="0">
+ <span i18n-content="useStaticIP"></span>
+ </label>
+ <span class="controlled-setting-indicator" data="ipconfigStatic"
+ for="ipTypeStatic"></span>
+ </div>
+ <div class="suboption">
+ <div id="ipConfigManagement" class="settings-list">
+ <list id="ipConfigList"></list>
+ </div>
+ </div>
+ </div>
+ </section>
+ <section id="change-proxy-section">
+ <div>
+ <button id="change-proxy-button" i18n-content="changeProxyButton">
+ </button>
+ </div>
+ </section>
+ </div>
+ <div id="security-tab"
+ class="subpages-tab-contents cellular-details gsm-only">
+ <div id="cellular-security-options">
+ <section>
+ <div id="sim-pin-lock" class="checkbox">
+ <label>
+ <input id="sim-card-lock-enabled" type="checkbox">
+ <span i18n-content="lockSimCard"></span>
+ </label>
+ <span class="controlled-setting-indicator" data="simCardLockEnabled"
+ for="sim-card-lock-enabled"></span>
+ </div>
+ </section>
+ <section>
+ <div id="change-pin-area">
+ <button id="change-pin" i18n-content="changePinButton"></button>
+ <span class="controlled-setting-indicator" data="simCardLockEnabled"
+ for="change-pin"></span>
+ </div>
+ </section>
+ </div>
+ </div>
+ <div class="action-area bottom-strip button-strip">
+ <button id="detailsInternetDismiss"
+ i18n-content="detailsInternetDismiss"></button>
+ <button id="detailsInternetLogin"
+ i18n-content="connect_button"></button>
+ <button id="detailsInternetDisconnect"
+ i18n-content="disconnect_button"></button>
+ <button id="activateDetails"
+ i18n-content="activate_button"></button>
+ <button id="buyplanDetails"
+ i18n-content="buyplan_button"></button>
+ </div>
+</div>
diff --git a/chrome/browser/resources/options2/chromeos/internet_detail.js b/chrome/browser/resources/options2/chromeos/internet_detail.js
new file mode 100644
index 0000000..5fb1e4e
--- /dev/null
+++ b/chrome/browser/resources/options2/chromeos/internet_detail.js
@@ -0,0 +1,204 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options.internet', function() {
+ var OptionsPage = options.OptionsPage;
+
+ /*
+ * Helper function to set hidden attribute on given element list.
+ * @param {Array} elements List of elements to be updated.
+ * @param {bool} hidden New hidden value.
+ */
+ function updateHidden(elements, hidden) {
+ for (var i = 0, el; el = elements[i]; i++) {
+ el.hidden = hidden;
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////////////
+ // DetailsInternetPage class:
+
+ /**
+ * Encapsulated handling of ChromeOS internet details overlay page.
+ * @constructor
+ */
+ function DetailsInternetPage() {
+ OptionsPage.call(this, 'detailsInternetPage', null, 'detailsInternetPage');
+ }
+
+ cr.addSingletonGetter(DetailsInternetPage);
+
+ DetailsInternetPage.prototype = {
+ __proto__: OptionsPage.prototype,
+
+ /**
+ * Initializes DetailsInternetPage page.
+ * Calls base class implementation to starts preference initialization.
+ */
+ initializePage: function() {
+ OptionsPage.prototype.initializePage.call(this);
+ },
+
+ /**
+ * Update details page controls.
+ * @private
+ */
+ updateControls_: function() {
+ // Only show ipconfig section if network is connected OR if nothing on
+ // this device is connected. This is so that you can fix the ip configs
+ // if you can't connect to any network.
+ // TODO(chocobo): Once ipconfig is moved to flimflam service objects,
+ // we need to redo this logic to allow configuration of all networks.
+ $('ipconfigSection').hidden = !this.connected && this.deviceConnected;
+
+ // Network type related.
+ updateHidden(
+ cr.doc.querySelectorAll('#detailsInternetPage .cellular-details'),
+ !this.cellular);
+ updateHidden(
+ cr.doc.querySelectorAll('#detailsInternetPage .wifi-details'),
+ !this.wireless);
+ updateHidden(
+ cr.doc.querySelectorAll('#detailsInternetPage .vpn-details'),
+ !this.vpn);
+
+ // Cell plan related.
+ $('planList').hidden = this.cellplanloading;
+ updateHidden(
+ cr.doc.querySelectorAll('#detailsInternetPage .no-plan-info'),
+ !this.cellular || this.cellplanloading || this.hascellplan);
+ updateHidden(
+ cr.doc.querySelectorAll('#detailsInternetPage .plan-loading-info'),
+ !this.cellular || this.nocellplan || this.hascellplan);
+ updateHidden(
+ cr.doc.querySelectorAll('#detailsInternetPage .plan-details-info'),
+ !this.cellular || this.nocellplan || this.cellplanloading);
+ updateHidden(
+ cr.doc.querySelectorAll('#detailsInternetPage .gsm-only'),
+ !this.cellular || !this.gsm);
+ updateHidden(
+ cr.doc.querySelectorAll('#detailsInternetPage .cdma-only'),
+ !this.cellular || this.gsm);
+ updateHidden(
+ cr.doc.querySelectorAll('#detailsInternetPage .apn-list-view'),
+ !this.cellular || !this.gsm);
+ updateHidden(
+ cr.doc.querySelectorAll('#detailsInternetPage .apn-details-view'),
+ true);
+
+ // Password and shared.
+ updateHidden(
+ cr.doc.querySelectorAll('#detailsInternetPage .password-details'),
+ !this.wireless || !this.password);
+ updateHidden(
+ cr.doc.querySelectorAll('#detailsInternetPage .shared-network'),
+ !this.shared);
+ updateHidden(
+ cr.doc.querySelectorAll('#detailsInternetPage .prefer-network'),
+ !this.showPreferred);
+ }
+ };
+
+ /**
+ * Whether the underlying network is connected. Only used for display purpose.
+ * @type {boolean}
+ */
+ cr.defineProperty(DetailsInternetPage, 'connected',
+ cr.PropertyKind.JS,
+ DetailsInternetPage.prototype.updateControls_);
+
+ /**
+ * Whether the underlying network is wifi. Only used for display purpose.
+ * @type {boolean}
+ */
+ cr.defineProperty(DetailsInternetPage, 'wireless',
+ cr.PropertyKind.JS,
+ DetailsInternetPage.prototype.updateControls_);
+
+ /**
+ * Whether the underlying network shared wifi. Only used for display purpose.
+ * @type {boolean}
+ */
+ cr.defineProperty(DetailsInternetPage, 'shared',
+ cr.PropertyKind.JS,
+ DetailsInternetPage.prototype.updateControls_);
+
+ /**
+ * Whether the underlying network is a vpn. Only used for display purpose.
+ * @type {boolean}
+ */
+ cr.defineProperty(DetailsInternetPage, 'vpn',
+ cr.PropertyKind.JS,
+ DetailsInternetPage.prototype.updateControls_);
+
+ /**
+ * Whether the underlying network is ethernet. Only used for display purpose.
+ * @type {boolean}
+ */
+ cr.defineProperty(DetailsInternetPage, 'ethernet',
+ cr.PropertyKind.JS,
+ DetailsInternetPage.prototype.updateControls_);
+
+ /**
+ * Whether the underlying network is cellular. Only used for display purpose.
+ * @type {boolean}
+ */
+ cr.defineProperty(DetailsInternetPage, 'cellular',
+ cr.PropertyKind.JS,
+ DetailsInternetPage.prototype.updateControls_);
+
+ /**
+ * Whether the network is loading cell plan. Only used for display purpose.
+ * @type {boolean}
+ */
+ cr.defineProperty(DetailsInternetPage, 'cellplanloading',
+ cr.PropertyKind.JS,
+ DetailsInternetPage.prototype.updateControls_);
+
+ /**
+ * Whether the network has cell plan(s). Only used for display purpose.
+ * @type {boolean}
+ */
+ cr.defineProperty(DetailsInternetPage, 'hascellplan',
+ cr.PropertyKind.JS,
+ DetailsInternetPage.prototype.updateControls_);
+
+ /**
+ * Whether the network has no cell plan. Only used for display purpose.
+ * @type {boolean}
+ */
+ cr.defineProperty(DetailsInternetPage, 'nocellplan',
+ cr.PropertyKind.JS,
+ DetailsInternetPage.prototype.updateControls_);
+
+ /**
+ * Whether the network is gsm. Only used for display purpose.
+ * @type {boolean}
+ */
+ cr.defineProperty(DetailsInternetPage, 'gsm',
+ cr.PropertyKind.JS,
+ DetailsInternetPage.prototype.updateControls_);
+
+ /**
+ * Whether show password details for network. Only used for display purpose.
+ * @type {boolean}
+ */
+ cr.defineProperty(DetailsInternetPage, 'password',
+ cr.PropertyKind.JS,
+ DetailsInternetPage.prototype.updateControls_);
+
+ // TODO(xiyuan): Check to see if it is safe to remove these attributes.
+ cr.defineProperty(DetailsInternetPage, 'hasactiveplan',
+ cr.PropertyKind.JS);
+ cr.defineProperty(DetailsInternetPage, 'activated',
+ cr.PropertyKind.JS);
+ cr.defineProperty(DetailsInternetPage, 'connecting',
+ cr.PropertyKind.JS);
+ cr.defineProperty(DetailsInternetPage, 'connected',
+ cr.PropertyKind.JS);
+
+ return {
+ DetailsInternetPage: DetailsInternetPage
+ };
+});
diff --git a/chrome/browser/resources/options2/chromeos/internet_detail_ip_config_list.js b/chrome/browser/resources/options2/chromeos/internet_detail_ip_config_list.js
new file mode 100644
index 0000000..fe4cb9e
--- /dev/null
+++ b/chrome/browser/resources/options2/chromeos/internet_detail_ip_config_list.js
@@ -0,0 +1,99 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options.internet', function() {
+ const InlineEditableItem = options.InlineEditableItem;
+ const InlineEditableItemList = options.InlineEditableItemList;
+
+ /**
+ * Creates a new ip config list item.
+ * @param {Object} fieldInfo The ip config field this item represents.
+ * @constructor
+ * @extends {cr.ui.ListItem}
+ */
+ function IPConfigListItem(fieldInfo) {
+ var el = cr.doc.createElement('div');
+ el.fieldInfo_ = fieldInfo;
+ IPConfigListItem.decorate(el);
+ return el;
+ }
+
+ /**
+ * Decorates an element as a ip config list item.
+ * @param {!HTMLElement} el The element to decorate.
+ */
+ IPConfigListItem.decorate = function(el) {
+ el.__proto__ = IPConfigListItem.prototype;
+ el.decorate();
+ };
+
+ IPConfigListItem.prototype = {
+ __proto__: InlineEditableItem.prototype,
+
+ /**
+ * Input field for editing the ip config values.
+ * @type {HTMLElement}
+ * @private
+ */
+ valueField_: null,
+
+ /** @inheritDoc */
+ decorate: function() {
+ InlineEditableItem.prototype.decorate.call(this);
+ this.deletable = false;
+
+ var fieldInfo = this.fieldInfo_;
+
+ var nameEl = this.ownerDocument.createElement('div');
+ nameEl.className = 'name';
+ nameEl.textContent = fieldInfo['name'];
+
+ this.contentElement.appendChild(nameEl);
+
+ var valueEl = this.createEditableTextCell(fieldInfo['value']);
+ valueEl.className = 'value';
+ this.contentElement.appendChild(valueEl);
+
+ var valueField = valueEl.querySelector('input')
+ valueField.required = true;
+ this.valueField_ = valueField;
+
+ this.addEventListener('commitedit', this.onEditCommitted_);
+ },
+
+ /** @inheritDoc */
+ get currentInputIsValid() {
+ return this.valueField_.validity.valid;
+ },
+
+ /** @inheritDoc */
+ get hasBeenEdited() {
+ return this.valueField_.value != this.fieldInfo_['value'];
+ },
+
+ /**
+ * Called when committing an edit; updates the model.
+ * @param {Event} e The end event.
+ * @private
+ */
+ onEditCommitted_: function(e) {
+ this.fieldInfo_['value'] = this.valueField_.value;
+ },
+ };
+
+ var IPConfigList = cr.ui.define('list');
+
+ IPConfigList.prototype = {
+ __proto__: InlineEditableItemList.prototype,
+
+ /** @inheritDoc */
+ createItem: function(fieldInfo) {
+ return new IPConfigListItem(fieldInfo);
+ },
+ };
+
+ return {
+ IPConfigList: IPConfigList
+ };
+});
diff --git a/chrome/browser/resources/options2/chromeos/internet_network_element.js b/chrome/browser/resources/options2/chromeos/internet_network_element.js
new file mode 100644
index 0000000..bba5a54
--- /dev/null
+++ b/chrome/browser/resources/options2/chromeos/internet_network_element.js
@@ -0,0 +1,316 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options.internet', function() {
+
+ /**
+ * Network settings constants. These enums usually match their C++
+ * counterparts.
+ */
+ function Constants() {}
+ // Minimum length for wireless network password.
+ Constants.MIN_WIRELESS_PASSWORD_LENGTH = 5;
+ // Minimum length for SSID name.
+ Constants.MIN_WIRELESS_SSID_LENGTH = 1;
+ // Cellular activation states:
+ Constants.ACTIVATION_STATE_UNKNOWN = 0;
+ Constants.ACTIVATION_STATE_ACTIVATED = 1;
+ Constants.ACTIVATION_STATE_ACTIVATING = 2;
+ Constants.ACTIVATION_STATE_NOT_ACTIVATED = 3;
+ Constants.ACTIVATION_STATE_PARTIALLY_ACTIVATED = 4;
+ // Network types:
+ Constants.TYPE_UNKNOWN = 0;
+ Constants.TYPE_ETHERNET = 1;
+ Constants.TYPE_WIFI = 2;
+ Constants.TYPE_WIMAX = 3;
+ Constants.TYPE_BLUETOOTH = 4;
+ Constants.TYPE_CELLULAR = 5;
+ Constants.TYPE_VPN = 6;
+ // ONC sources:
+ Constants.ONC_SOURCE_USER_IMPORT = 1;
+ Constants.ONC_SOURCE_DEVICE_POLICY = 2;
+ Constants.ONC_SOURCE_USER_POLICY = 3;
+
+ /**
+ * Creates a new network list div.
+ * @param {Object=} opt_propertyBag Optional properties.
+ * @constructor
+ * @extends {HTMLDivElement}
+ */
+ var NetworkElement = cr.ui.define('div');
+
+ NetworkElement.prototype = {
+ __proto__: HTMLDivElement.prototype,
+
+ /** @inheritDoc */
+ decorate: function() {
+ this.addEventListener('click', this.handleClick_);
+ },
+
+ /**
+ * Loads given network list.
+ * @param {Array} networks An array of network object.
+ */
+ load: function(networks) {
+ this.textContent = '';
+
+ for (var i = 0; i < networks.length; ++i) {
+ this.appendChild(new NetworkItem(networks[i]));
+ }
+ },
+
+ /**
+ * Handles click on network list and triggers actions when clicked on
+ * a NetworkListItem button.
+ * @private
+ * @param {!Event} e The click event object.
+ */
+ handleClick_: function(e) {
+ // We shouldn't respond to click events selecting an input,
+ // so return on those.
+ if (e.target.tagName == 'INPUT') {
+ return;
+ }
+ // Handle left button click
+ if (e.button == 0) {
+ var el = e.target;
+ // If click is on action buttons of a network item.
+ if (!(el.buttonType && el.networkType && el.servicePath)) {
+ if (el.buttonType) {
+ return;
+ }
+ // If click is on a network item or its label, walk up the DOM tree
+ // to find the network item.
+ var item = el;
+ while (item && !item.data) {
+ item = item.parentNode;
+ }
+ if (item.connecting)
+ return;
+
+ if (item) {
+ var data = item.data;
+ // Don't try to connect to Ethernet or unactivated Cellular.
+ if (data && (data.networkType == 1 ||
+ (data.networkType == 5 && data.activation_state != 1)))
+ return;
+ // If clicked on other networks item.
+ if (data && data.servicePath == '?') {
+ chrome.send('buttonClickCallback',
+ [String(data.networkType),
+ data.servicePath,
+ 'connect']);
+ }
+ }
+ }
+ }
+ }
+ };
+
+ /**
+ * Creates a new network item.
+ * @param {Object} network The network this represents.
+ * @constructor
+ * @extends {HTMLDivElement}
+ */
+ function NetworkItem(network) {
+ var el = cr.doc.createElement('div');
+ el.data = network;
+ NetworkItem.decorate(el);
+ return el;
+ }
+
+
+ /**
+ * Decorates an element as a network item.
+ * @param {!HTMLElement} el The element to decorate.
+ */
+ NetworkItem.decorate = function(el) {
+ el.__proto__ = NetworkItem.prototype;
+ el.decorate();
+ };
+
+ NetworkItem.prototype = {
+ __proto__: HTMLDivElement.prototype,
+
+ /** @inheritDoc */
+ decorate: function() {
+ this.className = 'network-item';
+ this.connectable = this.data.connectable;
+ this.connected = this.data.connected;
+ this.connecting = this.data.connecting;
+ this.other = this.data.servicePath == '?';
+ this.id = this.data.servicePath;
+
+ // Insert a div holding the policy-managed indicator.
+ var policyIndicator = this.ownerDocument.createElement('div');
+ policyIndicator.className = 'controlled-setting-indicator';
+ cr.ui.decorate(policyIndicator, options.ControlledSettingIndicator);
+
+ if (this.data.policyManaged) {
+ policyIndicator.controlledBy = 'policy';
+ policyIndicator.setAttribute('textPolicy',
+ localStrings.getString('managedNetwork'));
+ }
+ this.appendChild(policyIndicator);
+
+ // textDiv holds icon, name and status text.
+ var textDiv = this.ownerDocument.createElement('div');
+ textDiv.className = 'network-item-text';
+ if (this.data.iconURL) {
+ textDiv.style.backgroundImage = url(this.data.iconURL);
+ }
+
+ var nameEl = this.ownerDocument.createElement('div');
+ nameEl.className = 'network-name-label';
+ nameEl.textContent = this.data.networkName;
+ textDiv.appendChild(nameEl);
+
+ if (this.other) {
+ // No status and buttons for "Other..."
+ this.appendChild(textDiv);
+ return;
+ }
+
+ // Only show status text if not empty.
+ if (this.data.networkStatus) {
+ var statusEl = this.ownerDocument.createElement('div');
+ statusEl.className = 'network-status-label';
+ statusEl.textContent = this.data.networkStatus;
+ textDiv.appendChild(statusEl);
+ }
+
+ this.appendChild(textDiv);
+
+ var spacerDiv = this.ownerDocument.createElement('div');
+ spacerDiv.className = 'network-item-box-spacer';
+ this.appendChild(spacerDiv);
+
+ var buttonsDiv = this.ownerDocument.createElement('div');
+ var self = this;
+ if (!this.data.remembered) {
+ var no_plan =
+ this.data.networkType == Constants.TYPE_CELLULAR &&
+ this.data.needs_new_plan;
+ var show_activate =
+ (this.data.networkType == Constants.TYPE_CELLULAR &&
+ this.data.activation_state !=
+ Constants.ACTIVATION_STATE_ACTIVATED &&
+ this.data.activation_state !=
+ Constants.ACTIVATION_STATE_ACTIVATING);
+
+ // Show [Activate] button for non-activated Cellular network.
+ if (show_activate || no_plan) {
+ var button_name = no_plan ? 'buyplan_button' : 'activate_button';
+ buttonsDiv.appendChild(
+ this.createButton_(button_name, 'activate',
+ function(e) {
+ chrome.send('buttonClickCallback',
+ [String(self.data.networkType),
+ self.data.servicePath,
+ 'activate']);
+ }));
+ }
+ // Show disconnect button if not ethernet.
+ if (this.data.networkType != Constants.TYPE_ETHERNET &&
+ this.data.connected) {
+ buttonsDiv.appendChild(
+ this.createButton_('disconnect_button', 'disconnect',
+ function(e) {
+ chrome.send('buttonClickCallback',
+ [String(self.data.networkType),
+ self.data.servicePath,
+ 'disconnect']);
+ }));
+ }
+ if (!this.data.connected && !this.data.connecting) {
+ // connect button (if not ethernet and not showing activate button)
+ if (this.data.networkType != Constants.TYPE_ETHERNET &&
+ !show_activate && !no_plan) {
+ buttonsDiv.appendChild(
+ this.createButton_('connect_button', 'connect',
+ function(e) {
+ chrome.send('buttonClickCallback',
+ [String(self.data.networkType),
+ self.data.servicePath,
+ 'connect']);
+ }));
+ }
+ }
+ if (this.data.connected ||
+ this.data.networkType == Constants.TYPE_ETHERNET ||
+ this.data.networkType == Constants.TYPE_VPN ||
+ this.data.networkType == Constants.TYPE_WIFI ||
+ this.data.networkType == Constants.TYPE_CELLULAR) {
+ buttonsDiv.appendChild(
+ this.createButton_('options_button', 'options',
+ function(e) {
+ options.ProxyOptions.getInstance().setNetworkName(
+ self.data.networkName);
+ chrome.send('buttonClickCallback',
+ [String(self.data.networkType),
+ self.data.servicePath,
+ 'options']);
+ }));
+ }
+ } else {
+ // Put "Forget this network" button.
+ var button = this.createButton_('forget_button', 'forget',
+ function(e) {
+ chrome.send('buttonClickCallback',
+ [String(self.data.networkType),
+ self.data.servicePath,
+ 'forget']);
+ });
+ button.disabled = this.data.policyManaged;
+ buttonsDiv.appendChild(button);
+ }
+ this.appendChild(buttonsDiv);
+ },
+
+ /**
+ * Creates a button for interacting with a network.
+ * @param {Object} name The name of the localStrings to use for the text.
+ * @param {Object} type The type of button.
+ */
+ createButton_: function(name, type, callback) {
+ var buttonEl = this.ownerDocument.createElement('button');
+ buttonEl.buttonType = type;
+ buttonEl.textContent = localStrings.getString(name);
+ buttonEl.addEventListener('click', callback);
+ return buttonEl;
+ }
+ };
+
+ /**
+ * Whether the underlying network is connected. Only used for display purpose.
+ * @type {boolean}
+ */
+ cr.defineProperty(NetworkItem, 'connected', cr.PropertyKind.BOOL_ATTR);
+
+ /**
+ * Whether the underlying network is currently connecting.
+ * Only used for display purpose.
+ * @type {boolean}
+ */
+ cr.defineProperty(NetworkItem, 'connecting', cr.PropertyKind.BOOL_ATTR);
+
+ /**
+ * Whether the underlying network is an other network for adding networks.
+ * Only used for display purpose.
+ * @type {boolean}
+ */
+ cr.defineProperty(NetworkItem, 'other', cr.PropertyKind.BOOL_ATTR);
+
+ /**
+ * Whether the underlying network is connectable.
+ * @type {boolean}
+ */
+ cr.defineProperty(NetworkItem, 'connectable', cr.PropertyKind.BOOL_ATTR);
+
+ return {
+ Constants: Constants,
+ NetworkElement: NetworkElement
+ };
+});
diff --git a/chrome/browser/resources/options2/chromeos/internet_options.html b/chrome/browser/resources/options2/chromeos/internet_options.html
new file mode 100644
index 0000000..651540a
--- /dev/null
+++ b/chrome/browser/resources/options2/chromeos/internet_options.html
@@ -0,0 +1,59 @@
+<div id="internetPage" class="page hide-indicators" hidden>
+ <h1 i18n-content="internetPage"></h1>
+ <div id="locked-network-banner" hidden>
+ <span id="locked-network-icon"></span>
+ <span id="access-locked-text" i18n-content="accessLockedMsg"></span>
+ </div>
+ <div class="displaytable">
+ <section id="wireless-buttons">
+ <h3 i18n-content="generalNetworkingTitle"></h3>
+ <div id="network-general-div">
+ <div id="networking-controls" class="section-group">
+ <button id="enable-wifi" hidden
+ i18n-content="enableWifi"></button>
+ <button id="disable-wifi" hidden
+ i18n-content="disableWifi"></button>
+ <button id="enable-cellular" hidden
+ i18n-content="enableCellular"></button>
+ <button id="disable-cellular" hidden
+ i18n-content="disableCellular"></button>
+ </div>
+ <div id="shared-proxies" class="checkbox">
+ <label>
+ <input id="use-shared-proxies" type="checkbox"
+ pref="settings.use_shared_proxies">
+ <span i18n-content="useSharedProxies"></span>
+ </label>
+ </div>
+ <div id="internet-owner-only-warning" hidden>
+ <span i18n-content="ownerOnly"></span>
+ <span i18n-content="ownerUserId"></span>
+ </div>
+ <div id="data-roaming" class="checkbox">
+ <label>
+ <input id="enable-data-roaming"
+ pref="cros.signed.data_roaming_enabled"
+ metric="Options_Internet_DataRoaming" type="checkbox">
+ <span i18n-content="enableDataRoaming"></span>
+ </label>
+ </div>
+ </div>
+ </section>
+ <section id="wired-section">
+ <h3 i18n-content="wired_title" class="network-title"></h3>
+ <div id="wired-list" class="networks"></div>
+ </section>
+ <section id="wireless-section">
+ <h3 i18n-content="wireless_title" class="network-title"></h3>
+ <div id="wireless-list" class="networks"></div>
+ </section>
+ <section id="vpn-section">
+ <h3 i18n-content="vpn_title" class="network-title"></h3>
+ <div id="vpn-list" class="networks"></div>
+ </section>
+ <section id="remembered-section">
+ <h3 i18n-content="remembered_title" class="network-title"></h3>
+ <div id="remembered-list" class="networks"></div>
+ </section>
+ </div>
+</div>
diff --git a/chrome/browser/resources/options2/chromeos/internet_options.js b/chrome/browser/resources/options2/chromeos/internet_options.js
new file mode 100644
index 0000000..6391c9e
--- /dev/null
+++ b/chrome/browser/resources/options2/chromeos/internet_options.js
@@ -0,0 +1,709 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+ var OptionsPage = options.OptionsPage;
+ const ArrayDataModel = cr.ui.ArrayDataModel;
+
+ /////////////////////////////////////////////////////////////////////////////
+ // InternetOptions class:
+
+ /**
+ * Encapsulated handling of ChromeOS internet options page.
+ * @constructor
+ */
+ function InternetOptions() {
+ OptionsPage.call(this, 'internet', templateData.internetPageTabTitle,
+ 'internetPage');
+ }
+
+ cr.addSingletonGetter(InternetOptions);
+
+ // Inherit InternetOptions from OptionsPage.
+ InternetOptions.prototype = {
+ __proto__: OptionsPage.prototype,
+
+ /**
+ * Initializes InternetOptions page.
+ * Calls base class implementation to starts preference initialization.
+ */
+ initializePage: function() {
+ OptionsPage.prototype.initializePage.call(this);
+
+ if (templateData.accessLocked) {
+ this.accesslocked = true;
+ }
+
+ options.internet.NetworkElement.decorate($('wired-list'));
+ $('wired-list').load(templateData.wiredList);
+ options.internet.NetworkElement.decorate($('wireless-list'));
+ $('wireless-list').load(templateData.wirelessList);
+ options.internet.NetworkElement.decorate($('vpn-list'));
+ $('vpn-list').load(templateData.vpnList);
+ options.internet.NetworkElement.decorate($('remembered-list'));
+ $('remembered-list').load(templateData.rememberedList);
+
+ this.updatePolicyIndicatorVisibility_();
+
+ options.internet.CellularPlanElement.decorate($('planList'));
+
+ $('wired-section').hidden = (templateData.wiredList.length == 0);
+ $('wireless-section').hidden = (templateData.wirelessList.length == 0);
+ $('vpn-section').hidden = (templateData.vpnList.length == 0);
+ $('remembered-section').hidden =
+ (templateData.rememberedList.length == 0);
+ InternetOptions.setupAttributes(templateData);
+ $('detailsInternetDismiss').addEventListener('click', function(event) {
+ InternetOptions.setDetails();
+ });
+ $('detailsInternetLogin').addEventListener('click', function(event) {
+ InternetOptions.setDetails();
+ InternetOptions.loginFromDetails();
+ });
+ $('detailsInternetDisconnect').addEventListener('click', function(event) {
+ InternetOptions.setDetails();
+ InternetOptions.disconnectNetwork();
+ });
+ $('activateDetails').addEventListener('click', function(event) {
+ InternetOptions.activateFromDetails();
+ });
+ $('enable-wifi').addEventListener('click', function(event) {
+ event.target.disabled = true;
+ chrome.send('enableWifi', []);
+ });
+ $('disable-wifi').addEventListener('click', function(event) {
+ event.target.disabled = true;
+ chrome.send('disableWifi', []);
+ });
+ $('enable-cellular').addEventListener('click', function(event) {
+ event.target.disabled = true;
+ chrome.send('enableCellular', []);
+ });
+ $('disable-cellular').addEventListener('click', function(event) {
+ event.target.disabled = true;
+ chrome.send('disableCellular', []);
+ });
+ $('change-proxy-button').addEventListener('click', function(event) {
+ OptionsPage.closeOverlay();
+ OptionsPage.showPageByName('proxy', false);
+ chrome.send('coreOptionsUserMetricsAction',
+ ['Options_ShowProxySettings']);
+ });
+ $('buyplanDetails').addEventListener('click', function(event) {
+ chrome.send('buyDataPlan', []);
+ OptionsPage.closeOverlay();
+ });
+ $('cellularApnUseDefault').addEventListener('click', function(event) {
+ var data = $('connectionState').data;
+ var apnSelector = $('selectApn');
+
+ if (data.userApnIndex != -1) {
+ apnSelector.remove(data.userApnIndex);
+ data.userApnIndex = -1;
+ }
+
+ if (data.providerApnList.value.length > 0) {
+ var iApn = 0;
+ data.apn.apn = data.providerApnList.value[iApn].apn;
+ data.apn.username = data.providerApnList.value[iApn].username;
+ data.apn.password = data.providerApnList.value[iApn].password;
+ chrome.send('setApn', [String(data.servicePath),
+ String(data.apn.apn),
+ String(data.apn.username),
+ String(data.apn.password)]);
+ apnSelector.selectedIndex = iApn;
+ data.selectedApn = iApn;
+ } else {
+ data.apn.apn = '';
+ data.apn.username = '';
+ data.apn.password = '';
+ apnSelector.selectedIndex = -1;
+ data.selectedApn = -1;
+ }
+
+ InternetOptions.prototype.updateHidden_(
+ cr.doc.querySelectorAll('.apn-list-view'),
+ false);
+ InternetOptions.prototype.updateHidden_(
+ cr.doc.querySelectorAll('.apn-details-view'),
+ true);
+ });
+ $('cellularApnSet').addEventListener('click', function(event) {
+ if ($('cellularApn').value == '')
+ return;
+
+ var data = $('connectionState').data;
+ var apnSelector = $('selectApn');
+
+ data.apn.apn = String($('cellularApn').value);
+ data.apn.username = String($('cellularApnUsername').value);
+ data.apn.password = String($('cellularApnPassword').value);
+ chrome.send('setApn', [String(data.servicePath),
+ String(data.apn.apn),
+ String(data.apn.username),
+ String(data.apn.password)]);
+
+ if (data.userApnIndex != -1) {
+ apnSelector.remove(data.userApnIndex);
+ data.userApnIndex = -1;
+ }
+
+ var option = document.createElement('option');
+ option.textContent = data.apn.apn;
+ option.value = -1;
+ option.selected = true;
+ apnSelector.add(option, apnSelector[apnSelector.length - 1]);
+ data.userApnIndex = apnSelector.length - 2
+ data.selectedApn = data.userApnIndex;
+
+ InternetOptions.prototype.updateHidden_(
+ cr.doc.querySelectorAll('.apn-list-view'),
+ false);
+ InternetOptions.prototype.updateHidden_(
+ cr.doc.querySelectorAll('.apn-details-view'),
+ true);
+ });
+ $('cellularApnCancel').addEventListener('click', function(event) {
+ $('selectApn').selectedIndex = $('connectionState').data.selectedApn;
+
+ InternetOptions.prototype.updateHidden_(
+ cr.doc.querySelectorAll('.apn-list-view'),
+ false);
+ InternetOptions.prototype.updateHidden_(
+ cr.doc.querySelectorAll('.apn-details-view'),
+ true);
+ });
+ $('selectApn').addEventListener('change', function(event) {
+ var data = $('connectionState').data;
+ var apnSelector = $('selectApn');
+ if (apnSelector[apnSelector.selectedIndex].value != -1) {
+ var apnList = data.providerApnList.value;
+ chrome.send('setApn', [String(data.servicePath),
+ String(apnList[apnSelector.selectedIndex].apn),
+ String(apnList[apnSelector.selectedIndex].username),
+ String(apnList[apnSelector.selectedIndex].password)
+ ]);
+ data.selectedApn = apnSelector.selectedIndex;
+ } else if (apnSelector.selectedIndex == data.userApnIndex) {
+ chrome.send('setApn', [String(data.servicePath),
+ String(data.apn.apn),
+ String(data.apn.username),
+ String(data.apn.password)]);
+ data.selectedApn = apnSelector.selectedIndex;
+ } else {
+ $('cellularApn').value = data.apn.apn;
+ $('cellularApnUsername').value = data.apn.username;
+ $('cellularApnPassword').value = data.apn.password;
+
+ InternetOptions.prototype.updateHidden_(
+ cr.doc.querySelectorAll('.apn-list-view'),
+ true);
+ InternetOptions.prototype.updateHidden_(
+ cr.doc.querySelectorAll('.apn-details-view'),
+ false);
+ }
+ });
+ $('sim-card-lock-enabled').addEventListener('click', function(event) {
+ var newValue = $('sim-card-lock-enabled').checked;
+ // Leave value as is because user needs to enter PIN code first.
+ // When PIN will be entered and value changed,
+ // we'll update UI to reflect that change.
+ $('sim-card-lock-enabled').checked = !newValue;
+ chrome.send('setSimCardLock', [newValue]);
+ });
+ $('change-pin').addEventListener('click', function(event) {
+ chrome.send('changePin');
+ });
+ this.showNetworkDetails_();
+ },
+
+ showNetworkDetails_: function() {
+ var params = parseQueryParams(window.location);
+ var servicePath = params.servicePath;
+ var networkType = params.networkType;
+ if (!servicePath || !servicePath.length ||
+ !networkType || !networkType.length)
+ return;
+ var networkName = params.networkName;
+ if (networkName)
+ options.ProxyOptions.getInstance().setNetworkName(networkName);
+ chrome.send('buttonClickCallback',
+ [networkType, servicePath, "options"]);
+ },
+
+ updateHidden_: function(elements, hidden) {
+ for (var i = 0, el; el = elements[i]; i++) {
+ el.hidden = hidden;
+ }
+ },
+
+ /**
+ * Update internet page controls.
+ * @private
+ */
+ updateControls_: function() {
+ accesslocked = this.accesslocked;
+
+ $('locked-network-banner').hidden = !accesslocked;
+ $('wireless-buttons').hidden = accesslocked;
+ $('wired-section').hidden = accesslocked;
+ $('wireless-section').hidden = accesslocked;
+ $('vpn-section').hidden = accesslocked;
+ $('remembered-section').hidden = accesslocked;
+
+ // Don't change hidden attribute on OptionsPage divs directly because it
+ // is used in supporting infrastructure now.
+ if (accesslocked && DetailsInternetPage.getInstance().visible)
+ this.closeOverlay();
+ },
+
+ /**
+ * Updates the policy indicator visibility. Space is only allocated for the
+ * policy indicators if there is at least one visible.
+ * @private
+ */
+ updatePolicyIndicatorVisibility_: function() {
+ var page = $('internetPage');
+ if (page.querySelectorAll(
+ '.network-item > .controlled-setting-indicator[controlled-by]')
+ .length) {
+ page.classList.remove('hide-indicators');
+ } else {
+ page.classList.add('hide-indicators');
+ }
+ }
+ };
+
+ /**
+ * Whether access to this page is locked.
+ * @type {boolean}
+ */
+ cr.defineProperty(InternetOptions, 'accesslocked', cr.PropertyKind.JS,
+ InternetOptions.prototype.updateControls_);
+
+ InternetOptions.loginFromDetails = function () {
+ var data = $('connectionState').data;
+ var servicePath = data.servicePath;
+ chrome.send('buttonClickCallback', [String(data.type),
+ servicePath,
+ 'connect']);
+ OptionsPage.closeOverlay();
+ };
+
+ InternetOptions.disconnectNetwork = function () {
+ var data = $('connectionState').data;
+ var servicePath = data.servicePath;
+ chrome.send('buttonClickCallback', [String(data.type),
+ servicePath,
+ 'disconnect']);
+ OptionsPage.closeOverlay();
+ };
+
+ InternetOptions.activateFromDetails = function () {
+ var data = $('connectionState').data;
+ var servicePath = data.servicePath;
+ if (data.type == options.internet.Constants.TYPE_CELLULAR) {
+ chrome.send('buttonClickCallback', [String(data.type),
+ String(servicePath),
+ 'activate']);
+ }
+ OptionsPage.closeOverlay();
+ };
+
+ InternetOptions.setDetails = function () {
+ var data = $('connectionState').data;
+ var servicePath = data.servicePath;
+ if (data.type == options.internet.Constants.TYPE_WIFI) {
+ chrome.send('setPreferNetwork',
+ [String(servicePath),
+ $('preferNetworkWifi').checked ? "true" : "false"]);
+ chrome.send('setAutoConnect',
+ [String(servicePath),
+ $('autoConnectNetworkWifi').checked ? "true" : "false"]);
+ } else if (data.type == options.internet.Constants.TYPE_CELLULAR) {
+ chrome.send('setAutoConnect',
+ [String(servicePath),
+ $('autoConnectNetworkCellular').checked ? "true" : "false"]);
+ }
+
+ var ipConfigList = $('ipConfigList');
+ chrome.send('setIPConfig',[String(servicePath),
+ $('ipTypeDHCP').checked ? "true" : "false",
+ ipConfigList.dataModel.item(0).value,
+ ipConfigList.dataModel.item(1).value,
+ ipConfigList.dataModel.item(2).value,
+ ipConfigList.dataModel.item(3).value]);
+ OptionsPage.closeOverlay();
+ };
+
+ InternetOptions.setupAttributes = function(data) {
+ var buttons = $('wireless-buttons');
+ if (data.wifiEnabled) {
+ $('disable-wifi').disabled = data.wifiBusy;
+ $('disable-wifi').hidden = false;
+ $('enable-wifi').hidden = true;
+ } else {
+ $('enable-wifi').disabled = data.wifiBusy;
+ $('enable-wifi').hidden = false;
+ $('disable-wifi').hidden = true;
+ }
+ if (data.cellularAvailable) {
+ if (data.cellularEnabled) {
+ $('disable-cellular').disabled = data.cellularBusy;
+ $('disable-cellular').hidden = false;
+ $('enable-cellular').hidden = true;
+ } else {
+ $('enable-cellular').disabled = data.cellularBusy;
+ $('enable-cellular').hidden = false;
+ $('disable-cellular').hidden = true;
+ }
+ if (!AccountsOptions.currentUserIsOwner())
+ $('internet-owner-only-warning').hidden = false;
+ $('data-roaming').hidden = false;
+ } else {
+ $('enable-cellular').hidden = true;
+ $('disable-cellular').hidden = true;
+ $('data-roaming').hidden = true;
+ }
+ };
+
+ //
+ //Chrome callbacks
+ //
+ InternetOptions.refreshNetworkData = function (data) {
+ var self = InternetOptions.getInstance();
+ if (data.accessLocked) {
+ self.accesslocked = true;
+ return;
+ }
+ self.accesslocked = false;
+ $('wired-list').load(data.wiredList);
+ $('wireless-list').load(data.wirelessList);
+ $('vpn-list').load(data.vpnList);
+ $('remembered-list').load(data.rememberedList);
+
+ self.updatePolicyIndicatorVisibility_();
+
+ $('wired-section').hidden = (data.wiredList.length == 0);
+ $('wireless-section').hidden = (data.wirelessList.length == 0);
+ $('vpn-section').hidden = (data.vpnList.length == 0);
+ InternetOptions.setupAttributes(data);
+ $('remembered-section').hidden = (data.rememberedList.length == 0);
+ };
+
+ // TODO(xiyuan): This function seems belonging to DetailsInternetPage.
+ InternetOptions.updateCellularPlans = function (data) {
+ var detailsPage = DetailsInternetPage.getInstance();
+ detailsPage.cellplanloading = false;
+ if (data.plans && data.plans.length) {
+ detailsPage.nocellplan = false
+ detailsPage.hascellplan = true;
+ $('planList').load(data.plans);
+ } else {
+ detailsPage.nocellplan = true;
+ detailsPage.hascellplan = false;
+ }
+
+ detailsPage.hasactiveplan = !data.needsPlan;
+ detailsPage.activated = data.activated;
+ if (!data.activated)
+ $('detailsInternetLogin').hidden = true;
+
+ $('buyplanDetails').hidden = !data.showBuyButton;
+ $('activateDetails').hidden = !data.showActivateButton;
+ };
+
+ InternetOptions.updateSecurityTab = function(requirePin) {
+ $('sim-card-lock-enabled').checked = requirePin;
+ $('change-pin').hidden = !requirePin;
+ };
+
+ InternetOptions.showDetailedInfo = function (data) {
+ var detailsPage = DetailsInternetPage.getInstance();
+ // TODO(chocobo): Is this hack to cache the data here reasonable?
+ $('connectionState').data = data;
+ $('buyplanDetails').hidden = true;
+ $('activateDetails').hidden = true;
+ $('detailsInternetLogin').hidden = data.connected;
+ if (data.type == options.internet.Constants.TYPE_ETHERNET)
+ $('detailsInternetDisconnect').hidden = true;
+ else
+ $('detailsInternetDisconnect').hidden = !data.connected;
+
+ detailsPage.deviceConnected = data.deviceConnected;
+ detailsPage.connecting = data.connecting;
+ detailsPage.connected = data.connected;
+ if (data.connected) {
+ $('inetTitle').textContent = localStrings.getString('inetStatus');
+ } else {
+ $('inetTitle').textContent = localStrings.getString('inetConnect');
+ }
+ $('connectionState').textContent = data.connectionState;
+
+ var inetAddress = '';
+ var inetSubnetAddress = '';
+ var inetGateway = '';
+ var inetDns = '';
+ $('ipTypeDHCP').checked = true;
+ if (data.ipconfigStatic.value) {
+ inetAddress = data.ipconfigStatic.value.address;
+ inetSubnetAddress = data.ipconfigStatic.value.subnetAddress;
+ inetGateway = data.ipconfigStatic.value.gateway;
+ inetDns = data.ipconfigStatic.value.dns;
+ $('ipTypeStatic').checked = true;
+ } else if (data.ipconfigDHCP.value) {
+ inetAddress = data.ipconfigDHCP.value.address;
+ inetSubnetAddress = data.ipconfigDHCP.value.subnetAddress;
+ inetGateway = data.ipconfigDHCP.value.gateway;
+ inetDns = data.ipconfigDHCP.value.dns;
+ }
+
+ // Hide the dhcp/static radio if needed.
+ $('ipTypeDHCPDiv').hidden = !data.showStaticIPConfig;
+ $('ipTypeStaticDiv').hidden = !data.showStaticIPConfig;
+
+ // Hide change-proxy-button and change-proxy-section if not showing proxy.
+ $('change-proxy-button').hidden = !data.showProxy;
+ $('change-proxy-section').hidden = !data.showProxy;
+
+ var ipConfigList = $('ipConfigList');
+ ipConfigList.disabled =
+ $('ipTypeDHCP').checked || data.ipconfigStatic.controlledBy ||
+ !data.showStaticIPConfig;
+ options.internet.IPConfigList.decorate(ipConfigList);
+ ipConfigList.autoExpands = true;
+ var model = new ArrayDataModel([]);
+ model.push({
+ 'property': 'inetAddress',
+ 'name': localStrings.getString('inetAddress'),
+ 'value': inetAddress,
+ });
+ model.push({
+ 'property': 'inetSubnetAddress',
+ 'name': localStrings.getString('inetSubnetAddress'),
+ 'value': inetSubnetAddress,
+ });
+ model.push({
+ 'property': 'inetGateway',
+ 'name': localStrings.getString('inetGateway'),
+ 'value': inetGateway,
+ });
+ model.push({
+ 'property': 'inetDns',
+ 'name': localStrings.getString('inetDns'),
+ 'value': inetDns,
+ });
+ ipConfigList.dataModel = model;
+
+ $('ipTypeDHCP').addEventListener('click', function(event) {
+ // disable ipConfigList and switch back to dhcp values (if any)
+ if (data.ipconfigDHCP.value) {
+ var config = data.ipconfigDHCP.value;
+ ipConfigList.dataModel.item(0).value = config.address;
+ ipConfigList.dataModel.item(1).value = config.subnetAddress;
+ ipConfigList.dataModel.item(2).value = config.gateway;
+ ipConfigList.dataModel.item(3).value = config.dns;
+ }
+ ipConfigList.dataModel.updateIndex(0);
+ ipConfigList.dataModel.updateIndex(1);
+ ipConfigList.dataModel.updateIndex(2);
+ ipConfigList.dataModel.updateIndex(3);
+ // Unselect all so we don't keep the currently selected field editable.
+ ipConfigList.selectionModel.unselectAll();
+ ipConfigList.disabled = true;
+ });
+
+ $('ipTypeStatic').addEventListener('click', function(event) {
+ // enable ipConfigList
+ ipConfigList.disabled = false;
+ ipConfigList.focus();
+ ipConfigList.selectionModel.selectedIndex = 0;
+ });
+
+ if (data.hardwareAddress) {
+ $('hardwareAddress').textContent = data.hardwareAddress;
+ $('hardwareAddressRow').style.display = 'table-row';
+ } else {
+ // This is most likely a device without a hardware address.
+ $('hardwareAddressRow').style.display = 'none';
+ }
+ if (data.type == options.internet.Constants.TYPE_WIFI) {
+ OptionsPage.showTab($('wifiNetworkNavTab'));
+ detailsPage.wireless = true;
+ detailsPage.vpn = false;
+ detailsPage.ethernet = false;
+ detailsPage.cellular = false;
+ detailsPage.gsm = false;
+ detailsPage.shared = data.shared;
+ $('inetSsid').textContent = data.ssid;
+ detailsPage.showPreferred = data.showPreferred;
+ $('preferNetworkWifi').checked = data.preferred.value;
+ $('preferNetworkWifi').disabled = !data.remembered;
+ $('autoConnectNetworkWifi').checked = data.autoConnect.value;
+ $('autoConnectNetworkWifi').disabled = !data.remembered;
+ detailsPage.password = data.encrypted;
+ } else if(data.type == options.internet.Constants.TYPE_CELLULAR) {
+ if (!data.gsm)
+ OptionsPage.showTab($('cellularPlanNavTab'));
+ else
+ OptionsPage.showTab($('cellularConnNavTab'));
+ detailsPage.ethernet = false;
+ detailsPage.wireless = false;
+ detailsPage.vpn = false;
+ detailsPage.cellular = true;
+ if (data.carrierUrl) {
+ var a = $('carrierUrl');
+ if (!a) {
+ a = document.createElement('a');
+ $('serviceName').appendChild(a);
+ a.id = 'carrierUrl';
+ a.target = "_blank";
+ }
+ a.href = data.carrierUrl;
+ a.textContent = data.serviceName;
+ } else {
+ $('serviceName').textContent = data.serviceName;
+ }
+ $('networkTechnology').textContent = data.networkTechnology;
+ $('activationState').textContent = data.activationState;
+ $('roamingState').textContent = data.roamingState;
+ $('restrictedPool').textContent = data.restrictedPool;
+ $('errorState').textContent = data.errorState;
+ $('manufacturer').textContent = data.manufacturer;
+ $('modelId').textContent = data.modelId;
+ $('firmwareRevision').textContent = data.firmwareRevision;
+ $('hardwareRevision').textContent = data.hardwareRevision;
+ $('prlVersion').textContent = data.prlVersion;
+ $('meid').textContent = data.meid;
+ $('imei').textContent = data.imei;
+ $('mdn').textContent = data.mdn;
+ $('esn').textContent = data.esn;
+ $('min').textContent = data.min;
+ detailsPage.gsm = data.gsm;
+ if (data.gsm) {
+ $('operatorName').textContent = data.operatorName;
+ $('operatorCode').textContent = data.operatorCode;
+ $('imsi').textContent = data.imsi;
+
+ var apnSelector = $('selectApn');
+ // Clear APN lists, keep only last element that "other".
+ while (apnSelector.length != 1)
+ apnSelector.remove(0);
+ var otherOption = apnSelector[0];
+ data.selectedApn = -1;
+ data.userApnIndex = -1;
+ var apnList = data.providerApnList.value;
+ for (var i = 0; i < apnList.length; i++) {
+ var option = document.createElement('option');
+ var name = apnList[i].localizedName;
+ if (name == '' && apnList[i].name != '')
+ name = apnList[i].name;
+ if (name == '')
+ name = apnList[i].apn;
+ else
+ name = name + ' (' + apnList[i].apn + ')';
+ option.textContent = name;
+ option.value = i;
+ if ((data.apn.apn == apnList[i].apn &&
+ data.apn.username == apnList[i].username &&
+ data.apn.password == apnList[i].password) ||
+ (data.apn.apn == '' &&
+ data.lastGoodApn.apn == apnList[i].apn &&
+ data.lastGoodApn.username == apnList[i].username &&
+ data.lastGoodApn.password == apnList[i].password)) {
+ data.selectedApn = i;
+ }
+ // Insert new option before "other" option.
+ apnSelector.add(option, otherOption);
+ }
+ if (data.selectedApn == -1 && data.apn.apn != '') {
+ var option = document.createElement('option');
+ option.textContent = data.apn.apn;
+ option.value = -1;
+ apnSelector.add(option, otherOption);
+ data.selectedApn = apnSelector.length - 2;
+ data.userApnIndex = data.selectedApn;
+ }
+ apnSelector.selectedIndex = data.selectedApn;
+ InternetOptions.prototype.updateHidden_(
+ cr.doc.querySelectorAll('.apn-list-view'),
+ false);
+ InternetOptions.prototype.updateHidden_(
+ cr.doc.querySelectorAll('.apn-details-view'),
+ true);
+
+ InternetOptions.updateSecurityTab(data.simCardLockEnabled.value);
+ }
+ $('autoConnectNetworkCellular').checked = data.autoConnect.value;
+ $('autoConnectNetworkCellular').disabled = false;
+
+ $('buyplanDetails').hidden = !data.showBuyButton;
+ $('activateDetails').hidden = !data.showActivateButton;
+ if (data.showActivateButton) {
+ $('detailsInternetLogin').hidden = true;
+ }
+
+ detailsPage.hascellplan = false;
+ if (data.connected) {
+ detailsPage.nocellplan = false;
+ detailsPage.cellplanloading = true;
+ chrome.send('refreshCellularPlan', [data.servicePath])
+ } else {
+ detailsPage.nocellplan = true;
+ detailsPage.cellplanloading = false;
+ }
+ } else if (data.type == options.internet.Constants.TYPE_VPN) {
+ OptionsPage.showTab($('vpnNavTab'));
+ detailsPage.wireless = false;
+ detailsPage.vpn = true;
+ detailsPage.ethernet = false;
+ detailsPage.cellular = false;
+ detailsPage.gsm = false;
+ $('inetServiceName').textContent = data.service_name;
+ $('inetServerHostname').textContent = data.server_hostname;
+ $('inetProviderType').textContent = data.provider_type;
+ $('inetUsername').textContent = data.username;
+ } else {
+ OptionsPage.showTab($('internetNavTab'));
+ detailsPage.ethernet = true;
+ detailsPage.wireless = false;
+ detailsPage.vpn = false;
+ detailsPage.cellular = false;
+ detailsPage.gsm = false;
+ }
+
+ // Update controlled option indicators.
+ indicators = cr.doc.querySelectorAll(
+ '#detailsInternetPage .controlled-setting-indicator');
+ for (var i = 0; i < indicators.length; i++) {
+ var dataProperty = indicators[i].getAttribute('data');
+ if (dataProperty && data[dataProperty]) {
+ var controlledBy = data[dataProperty].controlledBy;
+ if (controlledBy) {
+ indicators[i].controlledBy = controlledBy;
+ var forElement = $(indicators[i].getAttribute('for'));
+ if (forElement)
+ forElement.disabled = true;
+ if (forElement.type == 'radio' && !forElement.checked)
+ indicators[i].hidden = true;
+ } else {
+ indicators[i].controlledBy = null;
+ }
+ }
+ }
+
+ // Don't show page name in address bar and in history to prevent people
+ // navigate here by hand and solve issue with page session restore.
+ OptionsPage.showPageByName('detailsInternetPage', false);
+ };
+
+ InternetOptions.invalidNetworkSettings = function () {
+ alert(localStrings.getString('invalidNetworkSettings'));
+ };
+
+ // Export
+ return {
+ InternetOptions: InternetOptions
+ };
+});
diff --git a/chrome/browser/resources/options2/chromeos/internet_options_page.css b/chrome/browser/resources/options2/chromeos/internet_options_page.css
new file mode 100644
index 0000000..c7ab791
--- /dev/null
+++ b/chrome/browser/resources/options2/chromeos/internet_options_page.css
@@ -0,0 +1,171 @@
+#inetTitle {
+ border: none;
+}
+
+#network-general-div {
+ -webkit-margin-start: 15px;
+}
+
+#networking-controls {
+ display: -webkit-box;
+}
+
+.networks {
+ -webkit-margin-start: 15px;
+ padding: 2px;
+}
+
+.network-password {
+ left: 0px;
+ position: relative;
+}
+
+.network-password > input, .network-password > select {
+ width: 200px;
+}
+
+.network-item {
+ -webkit-box-align: center;
+ -webkit-padding-start: 10px;
+ border: 1px solid rgba(255,255,255,0); /* transparent white */
+ border-radius: 2px;
+ display: -webkit-box;
+ height: 35px;
+}
+
+.network-item:not([connecting]):hover {
+ border-color: hsl(214, 91%, 85%);
+ background-color: hsl(214, 91%, 97%);
+}
+
+.network-item[connected] {
+ background-image: -webkit-linear-gradient(rgba(255, 255, 255, 0.8),
+ rgba(255, 255, 255, 0));
+ background-color: hsl(214,91%,89%);
+ border-color: hsl(214, 91%, 65%);
+}
+
+.network-item[connected]:hover {
+ background-color: hsl(214, 91%, 87%);
+ border-color: hsl(214, 91%, 65%);
+}
+
+.network-item[connecting] {
+ background-color: hsl(214, 91%, 97%);
+ border-color: hsl(214, 91%, 85%);
+}
+
+.network-item > .controlled-setting-indicator {
+ -webkit-margin-end: 5px;
+ width: 16px;
+}
+
+.hide-indicators .network-item > .controlled-setting-indicator {
+ display: none;
+}
+
+.network-item-text {
+ -webkit-padding-start: 30px;
+ background: left center no-repeat;
+ cursor: default;
+ display: table-cell;
+ height: 32px;
+ line-height: 100%;
+ max-width: 320px;
+ overflow: hidden;
+ vertical-align: middle;
+}
+
+html[dir='rtl'] .network-item-text {
+ background: right center no-repeat;
+}
+
+.network-item[connected] > * > .network-name-label {
+ font-weight: bold;
+}
+
+.network-status-label {
+ color: grey;
+}
+
+.network-item > * > button {
+ min-width: 100px;
+ visibility: hidden;
+ margin-right: 5px;
+}
+
+.network-item:hover > * > button,
+.network-item[connected] > * > button {
+ visibility: visible;
+}
+
+.network-item-box-spacer {
+ -webkit-box-flex: 1;
+}
+
+.displaytable > section > .network-title {
+ vertical-align: top;
+ padding-top: 20px;
+}
+
+#detailsInternetPage {
+ min-width: 440px;
+ min-height: 420px;
+ padding-bottom: 40px;
+ position: relative;
+}
+
+#details-plan-table {
+ width: 100%;
+}
+
+#planSummary {
+ width: 350px;
+ padding-bottom: 5px;
+}
+
+#planWarning {
+ width: 350px;
+ padding-top: 5px;
+ font-weight: bold;
+}
+
+#locked-network-banner {
+ height: 31px;
+ width: 100%;
+ margin: 0;
+ padding-top: 10px;
+ vertical-align: middle;
+}
+
+#locked-network-icon {
+ background-image: url("chrome://theme/IDR_WARNING");
+ background-repeat: no-repeat;
+ background-position:center;
+ display: inline-block;
+ padding: 5px;
+ height: 21px;
+ vertical-align: middle;
+ width: 24px;
+}
+
+#access-locked-text {
+ vertical-align: middle;
+}
+
+#internet-owner-only-warning {
+ margin: 10px 0;
+ padding-bottom: 1px;
+ -webkit-padding-start: 20px;
+ background-repeat: no-repeat;
+ background-image: url('warning.png');
+}
+
+#ipConfigList .name {
+ width: 40%;
+}
+
+#ipConfigList .value {
+ -webkit-box-flex: 1;
+ color: #666;
+}
diff --git a/chrome/browser/resources/options2/chromeos/language_chewing_options.html b/chrome/browser/resources/options2/chromeos/language_chewing_options.html
new file mode 100644
index 0000000..f21e603
--- /dev/null
+++ b/chrome/browser/resources/options2/chromeos/language_chewing_options.html
@@ -0,0 +1,141 @@
+<div id="languageChewingPage" class="page" hidden>
+ <h1 i18n-content="languageChewingPage"></h1>
+ <section>
+ <table class="option-control-table">
+ <tr>
+ <td class="option-name" colspan="2">
+ <div class="checkbox">
+ <label>
+ <input id="chewing-auto-shift-cur" type="checkbox"
+ pref="settings.language.chewing_auto_shift_cur">
+ <span i18n-content="Chewing_autoShiftCur"></span>
+ </label>
+ </div>
+ </td>
+ </tr>
+ <tr>
+ <td class="option-name" colspan="2">
+ <div class="checkbox">
+ <label>
+ <input id="chewing-add-phrase-direction" type="checkbox"
+ pref="settings.language.chewing_add_phrase_direction">
+ <span i18n-content="Chewing_addPhraseDirection"></span>
+ </label>
+ </div>
+ </td>
+ </tr>
+ <!-- Temporarily disabled. (crosbug.com/14185)
+ <tr>
+ <td class="option-name" colspan="2">
+ <div class="checkbox">
+ <label>
+ <input id="chewing-easy-symbol-input" type="checkbox"
+ pref="settings.language.chewing_easy_symbol_input">
+ <span i18n-content="Chewing_easySymbolInput"></span>
+ </label>
+ </div>
+ </td>
+ </tr>
+ -->
+ <tr>
+ <td class="option-name" colspan="2">
+ <div class="checkbox">
+ <label>
+ <input id="chewing-esc-clean-all-buf" type="checkbox"
+ pref="settings.language.chewing_esc_clean_all_buf">
+ <span i18n-content="Chewing_escCleanAllBuf"></span>
+ </label>
+ </div>
+ </td>
+ </tr>
+ <tr>
+ <td class="option-name" colspan="2">
+ <div class="checkbox">
+ <label>
+ <input id="chewing-force-lowercase-english" type="checkbox"
+ pref="settings.language.chewing_force_lowercase_english">
+ <span i18n-content="Chewing_forceLowercaseEnglish"></span>
+ </label>
+ </div>
+ </td>
+ </tr>
+ <!-- Temporarily disabled. (crosbug.com/14185)
+ <tr>
+ <td class="option-name" colspan="2">
+ <div class="checkbox">
+ <label>
+ <input id="chewing-plain-zhuyin" type="checkbox"
+ pref="settings.language.chewing_plain_zhuyin">
+ <span i18n-content="Chewing_plainZhuyin"></span>
+ </label>
+ </div>
+ </td>
+ </tr>
+ -->
+ <tr>
+ <td class="option-name" colspan="2">
+ <div class="checkbox">
+ <label>
+ <input id="chewing-phrase-choice-rearward" type="checkbox"
+ pref="settings.language.chewing_phrase_choice_rearward">
+ <span i18n-content="Chewing_phraseChoiceRearward"></span>
+ </label>
+ </div>
+ </td>
+ </tr>
+ <tr>
+ <td class="option-name" colspan="2">
+ <div class="checkbox">
+ <label>
+ <input id="chewing-space-as-selection" type="checkbox"
+ pref="settings.language.chewing_space_as_selection">
+ <span i18n-content="Chewing_spaceAsSelection"></span>
+ </label>
+ </div>
+ </td>
+ </tr>
+ <tr>
+ <td class="option-name" i18n-content="Chewing_maxChiSymbolLen"></td>
+ <td class="option-value">
+ <input id="chewing-max-chi-symbol-len" class="control" type="range"
+ pref="settings.language.chewing_max_chi_symbol_len"
+ i18n-values="min:Chewing_maxChiSymbolLenMin;
+ max:Chewing_maxChiSymbolLenMax">
+ <span id="chewing-max-chi-symbol-len-value"></span>
+ </td>
+ </tr>
+ <tr>
+ <td class="option-name" i18n-content="Chewing_candPerPage"></td>
+ <td class="option-value">
+ <select id="chewing-cand-per-page" class="control" data-type="number"
+ pref="settings.language.chewing_cand_per_page"
+ i18n-options="Chewing_candPerPageValue"></select>
+ </td>
+ </tr>
+ <tr>
+ <td class="option-name" i18n-content="Chewing_KBType"></td>
+ <td class="option-value">
+ <select id="chewing-keyboard-type" class="control" data-type="string"
+ pref="settings.language.chewing_keyboard_type"
+ i18n-options="Chewing_KBTypeValue"></select>
+ </td>
+ </tr>
+ <tr>
+ <td class="option-name" i18n-content="Chewing_selKeys"></td>
+ <td class="option-value">
+ <select id="chewing-sel-keys" class="control" data-type="string"
+ pref="settings.language.chewing_sel_keys"
+ i18n-options="Chewing_selKeysValue"></select>
+ </td>
+ </tr>
+ <tr>
+ <td class="option-name" i18n-content="Chewing_hsuSelKeyType"></td>
+ <td class="option-value">
+ <select id="chewing-sel-key-type" class="control" data-type="number"
+ pref="settings.language.chewing_hsu_sel_key_type"
+ i18n-options="Chewing_hsuSelKeyTypeValue"></select>
+ </td>
+ </tr>
+ </table>
+ </section>
+</div>
diff --git a/chrome/browser/resources/options2/chromeos/language_customize_modifier_keys_overlay.html b/chrome/browser/resources/options2/chromeos/language_customize_modifier_keys_overlay.html
new file mode 100644
index 0000000..3adb231
--- /dev/null
+++ b/chrome/browser/resources/options2/chromeos/language_customize_modifier_keys_overlay.html
@@ -0,0 +1,37 @@
+<div id="languageCustomizeModifierKeysOverlay" class="page" hidden>
+ <div class="content-area">
+ <table class="option-control-table">
+ <tr>
+ <td class="option-name" i18n-content="xkbRemapSearchKeyToContent"></td>
+ <td class="option-value">
+ <select id="xkb-remap-search-key-to" class="control"
+ data-type="number" i18n-options="xkbRemapSearchKeyToValue"
+ pref="settings.language.xkb_remap_search_key_to"></select>
+ </td>
+ </tr>
+ <tr>
+ <td class="option-name"
+ i18n-content="xkbRemapControlKeyToContent"></td>
+ <td class="option-value">
+ <select id="xkb-remap-control-key-to" class="control"
+ data-type="number" i18n-options="xkbRemapControlKeyToValue"
+ pref="settings.language.xkb_remap_control_key_to"></select>
+ </td>
+ </tr>
+ <tr>
+ <td class="option-name" i18n-content="xkbRemapAltKeyToContent"></td>
+ <td class="option-value">
+ <select id="xkb-remap-alt-key-to" class="control" data-type="number"
+ pref="settings.language.xkb_remap_alt_key_to"
+ i18n-options="xkbRemapAltKeyToValue"></select>
+ </td>
+ </tr>
+ </table>
+ </div>
+ <div class="action-area">
+ <div class="button-strip">
+ <button id="languageCustomizeModifierKeysOverleyDismissButton"
+ i18n-content="close"></button>
+ </div>
+ </div>
+</div>
diff --git a/chrome/browser/resources/options2/chromeos/language_hangul_options.html b/chrome/browser/resources/options2/chromeos/language_hangul_options.html
new file mode 100644
index 0000000..f70ba76
--- /dev/null
+++ b/chrome/browser/resources/options2/chromeos/language_hangul_options.html
@@ -0,0 +1,18 @@
+<div id="languageHangulPage" class="page" hidden>
+ <h1 i18n-content="languageHangulPage"></h1>
+ <section>
+ <div class="option">
+ <table class="option-control-table">
+ <tr>
+ <td class="option-name" i18n-content="hangul_keyboard_layout"></td>
+ <td class="option-value">
+ <select id="keyboard-layout-select" class="control"
+ data-type="string"
+ pref="settings.language.hangul_keyboard"
+ i18n-options="HangulkeyboardLayoutList"></select>
+ </td>
+ </tr>
+ </table>
+ </div>
+ </section>
+</div>
diff --git a/chrome/browser/resources/options2/chromeos/language_mozc_options.html b/chrome/browser/resources/options2/chromeos/language_mozc_options.html
new file mode 100644
index 0000000..498b317
--- /dev/null
+++ b/chrome/browser/resources/options2/chromeos/language_mozc_options.html
@@ -0,0 +1,135 @@
+<div id="languageMozcPage" class="page" hidden>
+ <h1 i18n-content="languageMozcPage"></h1>
+ <section>
+ <table class="option-control-table">
+ <tr>
+ <td class="option-name" colspan="2">
+ <div class="checkbox">
+ <label>
+ <input id="mozc-incognito-mode"
+ pref="settings.language.mozc_incognito_mode"
+ type="checkbox">
+ <span i18n-content="mozc_incognito_mode"></span>
+ </label>
+ </div>
+ </td>
+ </tr>
+ <tr>
+ <td class="option-name" colspan="2">
+ <div class="checkbox">
+ <label>
+ <input id="mozc-use-auto-ime-turn-off"
+ pref="settings.language.mozc_use_auto_ime_turn_off"
+ type="checkbox">
+ <span i18n-content="mozc_use_auto_ime_turn_off"></span>
+ </label>
+ </div>
+ </td>
+ </tr>
+ <tr>
+ <td class="option-name" colspan="2">
+ <div class="checkbox">
+ <label>
+ <input id="mozc-use-history-suggest" type="checkbox"
+ pref="settings.language.mozc_use_history_suggest">
+ <span i18n-content="mozc_use_history_suggest"></span>
+ </label>
+ </div>
+ </td>
+ </tr>
+ <tr>
+ <td class="option-name" colspan="2">
+ <div class="checkbox">
+ <label>
+ <input id="mozc-use-dictionary-suggest" type="checkbox"
+ pref="settings.language.mozc_use_dictionary_suggest">
+ <span i18n-content="mozc_use_dictionary_suggest"></span>
+ </label>
+ </div>
+ </td>
+ </tr>
+ <tr>
+ <td class="option-name" i18n-content="mozc_preedit_method"></td>
+ <td class="option-value">
+ <select id="mozc-preedit-method" class="control" data-type="string"
+ pref="settings.language.mozc_preedit_method"
+ i18n-options="mozc_preedit_methodValue"></select>
+ </td>
+ </tr>
+ <tr>
+ <td class="option-name" i18n-content="mozc_session_keymap"></td>
+ <td class="option-value">
+ <select id="mozc-session-keymap" class="control" data-type="string"
+ pref="settings.language.mozc_session_keymap"
+ i18n-options="mozc_session_keymapValue"></select>
+ </td>
+ </tr>
+ <tr>
+ <td class="option-name" i18n-content="mozc_punctuation_method"></td>
+ <td class="option-value">
+ <select id="mozc-punctuation-method" class="control"
+ pref="settings.language.mozc_punctuation_method"
+ data-type="string" i18n-options="mozc_punctuation_methodValue">
+ </select>
+ </td>
+ </tr>
+ <tr>
+ <td class="option-name" i18n-content="mozc_symbol_method"></td>
+ <td class="option-value">
+ <select id="mozc-symbol-method" class="control" data-type="string"
+ pref="settings.language.mozc_symbol_method"
+ i18n-options="mozc_symbol_methodValue"></select>
+ </td>
+ </tr>
+ <tr>
+ <td class="option-name" i18n-content="mozc_space_character_form">
+ </td>
+ <td class="option-value">
+ <select id="mozc-space-character-form" class="control"
+ pref="settings.language.mozc_space_character_form"
+ data-type="string" i18n-options="mozc_space_character_formValue">
+ </select>
+ </td>
+ </tr>
+ <tr>
+ <td class="option-name" i18n-content="mozc_history_learning_level">
+ </td>
+ <td class="option-value">
+ <select id="mozc-history-learning-level" class="control"
+ pref="settings.language.mozc_history_learning_level"
+ data-type="string"
+ i18n-options="mozc_history_learning_levelValue"></select>
+ </td>
+ </tr>
+ <tr>
+ <td class="option-name" i18n-content="mozc_shift_key_mode_switch">
+ </td>
+ <td class="option-value">
+ <select id="mozc-shift-key-mode-switch" class="control"
+ pref="settings.language.mozc_shift_key_mode_switch"
+ data-type="string" i18n-options="mozc_shift_key_mode_switchValue">
+ </select>
+ </td>
+ </tr>
+ <tr>
+ <td class="option-name" i18n-content="mozc_numpad_character_form">
+ </td>
+ <td class="option-value">
+ <select id="mozc-numpad-character-form" class="control"
+ pref="settings.language.mozc_numpad_character_form"
+ data-type="string" i18n-options="mozc_numpad_character_formValue">
+ </select>
+ </td>
+ </tr>
+ <tr>
+ <td class="option-name" i18n-content="mozc_suggestions_size">
+ </td>
+ <td class="option-value">
+ <select id="mozc-suggestions-size" class="control" data-type="number"
+ pref="settings.language.mozc_suggestions_size"
+ i18n-options="mozc_suggestions_sizeValue"></select>
+ </td>
+ </tr>
+ </table>
+ </section>
+</div>
diff --git a/chrome/browser/resources/options2/chromeos/language_pinyin_options.html b/chrome/browser/resources/options2/chromeos/language_pinyin_options.html
new file mode 100644
index 0000000..ad72fa4
--- /dev/null
+++ b/chrome/browser/resources/options2/chromeos/language_pinyin_options.html
@@ -0,0 +1,148 @@
+<div id="languagePinyinPage" class="page" hidden>
+ <h1 i18n-content="languagePinyinPage"></h1>
+ <section>
+ <table class="option-control-table">
+ <tr>
+ <td class="option-name" colspan="2">
+ <div class="checkbox">
+ <labl>
+ <input id="pinyin-correct-pinyin"
+ pref="settings.language.pinyin_correct_pinyin"
+ type="checkbox">
+ <span i18n-content="PinyinCorrectPinyin"></span>
+ </label>
+ </div>
+ </td>
+ </tr>
+ <tr>
+ <td class="option-name" colspan="2">
+ <div class="checkbox">
+ <label>
+ <input id="pinyin-fuzzy-pinyin"
+ pref="settings.language.pinyin_fuzzy_pinyin"
+ type="checkbox">
+ <span i18n-content="PinyinFuzzyPinyin"></span>
+ </label>
+ </div>
+ </td>
+ </tr>
+ <tr>
+ <td class="option-name" colspan="2">
+ <div class="checkbox">
+ <label>
+ <input id="pinyin-shift-select-candidate"
+ pref="settings.language.pinyin_shift_select_candidate"
+ type="checkbox">
+ <span i18n-content="PinyinShiftSelectCandidate"></span>
+ </label>
+ </div>
+ </td>
+ </tr>
+ <tr>
+ <td class="option-name" colspan="2">
+ <div class="checkbox">
+ <label>
+ <input id="pinyin-minus-equal-page"
+ pref="settings.language.pinyin_minus_equal_page"
+ type="checkbox">
+ <span i18n-content="PinyinMinusEqualPage"></span>
+ </label>
+ </div>
+ </td>
+ </tr>
+ <tr>
+ <td class="option-name" colspan="2">
+ <div class="checkbox">
+ <label>
+ <input id="pinyin-comma-period-page"
+ pref="settings.language.pinyin_comma_period_page"
+ type="checkbox">
+ <span i18n-content="PinyinCommaPeriodPage"></span>
+ </label>
+ </div>
+ </td>
+ </tr>
+ <tr>
+ <td class="option-name" colspan="2">
+ <div class="checkbox">
+ <label>
+ <input id="pinyin-auto-commit"
+ pref="settings.language.pinyin_auto_commit"
+ type="checkbox">
+ <span i18n-content="PinyinAutoCommit"></span>
+ </label>
+ </div>
+ </td>
+ </tr>
+ <tr>
+ <td class="option-name" colspan="2">
+ <div class="checkbox">
+ <label>
+ <input id="pinyin-double-pinyin"
+ pref="settings.language.pinyin_double_pinyin"
+ type="checkbox">
+ <span i18n-content="PinyinDoublePinyin"></span>
+ </label>
+ </div>
+ </td>
+ </tr>
+ <tr>
+ <td class="option-name" colspan="2">
+ <div class="checkbox">
+ <label>
+ <input id="pinyin-init-chinese"
+ pref="settings.language.pinyin_init_chinese"
+ type="checkbox">
+ <span i18n-content="PinyinInitChinese"></span>
+ </label>
+ </div>
+ </td>
+ </tr>
+ <tr>
+ <td class="option-name" colspan="2">
+ <div class="checkbox">
+ <label>
+ <input id="pinyin-init-full"
+ pref="settings.language.pinyin_init_full"
+ type="checkbox">
+ <span i18n-content="PinyinInitFull"></span>
+ </label>
+ </div>
+ </td>
+ </tr>
+ <tr>
+ <td class="option-name" colspan="2">
+ <div class="checkbox">
+ <label>
+ <input id="pinyin-init-full-punct"
+ pref="settings.language.pinyin_init_full_punct"
+ type="checkbox">
+ <span i18n-content="PinyinInitFullPunct"></span>
+ </label>
+ </div>
+ </td>
+ </tr>
+ <tr>
+ <td class="option-name" colspan="2">
+ <div class="checkbox">
+ <label>
+ <input id="pinyin-init-simplified-chinese"
+ pref="settings.language.pinyin_init_simplified_chinese"
+ type="checkbox">
+ <span i18n-content="PinyinInitSimplifiedChinese"></span>
+ </label>
+ </div>
+ </td>
+ </tr>
+ <tr>
+ <td class="option-name" i18n-content="PinyinDoublePinyinSchema"></td>
+ <td class="option-value">
+ <select id="pinyin-double-pinyin-schema" class="control"
+ pref="settings.language.pinyin_double_pinyin_schema"
+ data-type="string"
+ i18n-options="PinyinDoublePinyinSchemaValue"></select>
+ </td>
+ </tr>
+ </table>
+ </section>
+</div>
diff --git a/chrome/browser/resources/options2/chromeos/proxy.css b/chrome/browser/resources/options2/chromeos/proxy.css
new file mode 100644
index 0000000..28ee236
--- /dev/null
+++ b/chrome/browser/resources/options2/chromeos/proxy.css
@@ -0,0 +1,10 @@
+#ignoredHostList {
+ border: solid 1px #999999;
+ height: 100px;
+ -webkit-margin-start: 0px;
+}
+
+#newHost {
+ -webkit-margin-start: 0px;
+ margin-top: 8px;
+} \ No newline at end of file
diff --git a/chrome/browser/resources/options2/chromeos/proxy.html b/chrome/browser/resources/options2/chromeos/proxy.html
new file mode 100644
index 0000000..0449c7b
--- /dev/null
+++ b/chrome/browser/resources/options2/chromeos/proxy.html
@@ -0,0 +1,141 @@
+<div id="proxyPage" class="page" hidden>
+ <div id="info-banner" class="managed-prefs-banner" hidden>
+ <span id="banner-icon" class="managed-prefs-icon"></span>
+ <span id="banner-text" class="managed-prefs-text"></span>
+ </div>
+ <h1 id="proxy-page-title" i18n-content="proxyPage"></h1>
+ <section>
+ <h3 i18n-content="proxy_config_title"></h3>
+ <div>
+ <div class="radio">
+ <label>
+ <input id="directProxy" type="radio" name="proxytype" value="1"
+ pref="cros.session.proxy.type">
+ <span i18n-content="proxyDirectInternetConnection"></span>
+ </label>
+ </div>
+ <div class="radio">
+ <label>
+ <input id="manualProxy" type="radio" name="proxytype" value="2"
+ pref="cros.session.proxy.type">
+ <span i18n-content="proxyManual"></span>
+ </label>
+ </div>
+ <div class="checkbox">
+ <label>
+ <input id="proxyAllProtocols" type="checkbox"
+ pref="cros.session.proxy.single">
+ <span i18n-content="sameProxyProtocols"></span>
+ </label>
+ </div>
+ <div id="singleProxy">
+ <table>
+ <tr>
+ <td>
+ <span i18n-content="httpProxy"></span>
+ <input id="proxyHostSingleName" type="text" size="30"
+ pref="cros.session.proxy.singlehttp" disabled>
+ </td>
+ <td>
+ <span i18n-content="proxyPort"></span>
+ <input id="proxyHostSinglePort" type="text" data-type="number"
+ size="5" pref="cros.session.proxy.singlehttpport" disabled>
+ </td>
+ </tr>
+ </table>
+ </div>
+ <div id="multiProxy">
+ <table>
+ <tr>
+ <td>
+ <span i18n-content="httpProxy"></span>
+ </td>
+ <td>
+ <input id="proxyHostName" type="text" size="30"
+ pref="cros.session.proxy.httpurl" disabled>
+ </td>
+ <td>
+ <span i18n-content="proxyPort"></span>
+ </td>
+ <td>
+ <input id="proxyHostPort" type="text" data-type="number" size="5"
+ pref="cros.session.proxy.httpport" disabled>
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <span i18n-content="secureHttpProxy"></span>
+ </td>
+ <td>
+ <input id="secureProxyHostName" type="text" size="30"
+ pref="cros.session.proxy.httpsurl" disabled>
+ </td>
+ <td>
+ <span i18n-content="proxyPort"></span>
+ </td>
+ <td>
+ <input id="secureProxyPort" type="text" data-type="number" size="5"
+ pref="cros.session.proxy.httpsport" disabled>
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <span i18n-content="ftpProxy"></span>
+ </td>
+ <td>
+ <input id="ftpProxy" type="text" size="30"
+ pref="cros.session.proxy.ftpurl" disabled>
+ </td>
+ <td>
+ <span i18n-content="proxyPort"></span>
+ </td>
+ <td>
+ <input id="ftpProxyPort" type="text" data-type="number" size="5"
+ pref="cros.session.proxy.ftpport" disabled>
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <span i18n-content="socksHost"></span>
+ </td>
+ <td>
+ <input id="socksHost" type="text" size="30"
+ pref="cros.session.proxy.socks" disabled>
+ </td>
+ <td>
+ <span i18n-content="proxyPort"></span>
+ </td>
+ <td>
+ <input id="socksPort" type="text" data-type="number" size="5"
+ pref="cros.session.proxy.socksport" disabled>
+ </td>
+ </tr>
+ </table>
+ </div>
+ <div class="radio">
+ <label>
+ <input id="autoProxy" type="radio" name="proxytype" value="3"
+ pref="cros.session.proxy.type">
+ <span i18n-content="proxyAutomatic"></span>
+ </label>
+ </div>
+ <div>
+ <label>
+ <span i18n-content="proxyConfigUrl"></span>
+ <input id="proxyConfig" type="url" size="60"
+ pref="cros.session.proxy.pacurl">
+ </label>
+ </div>
+ </div>
+ </section>
+ <section id="advancedConfig">
+ <h3 i18n-content="advanced_proxy_config"></h3>
+ <div class="option">
+ <div i18n-content="proxyBypass"></div>
+ <list id="ignoredHostList"></list>
+ <input id="newHost" type="url" size="30">
+ <button id="addHost" i18n-content="addHost"></button>
+ <button id="removeHost" i18n-content="removeHost"></button>
+ </div>
+ </section>
+</div>
diff --git a/chrome/browser/resources/options2/chromeos/proxy_options.js b/chrome/browser/resources/options2/chromeos/proxy_options.js
new file mode 100644
index 0000000..f42a08d
--- /dev/null
+++ b/chrome/browser/resources/options2/chromeos/proxy_options.js
@@ -0,0 +1,244 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+
+ var OptionsPage = options.OptionsPage;
+ var Preferences = options.Preferences;
+
+ /////////////////////////////////////////////////////////////////////////////
+ // ProxyOptions class:
+
+ /**
+ * Encapsulated handling of ChromeOS proxy options page.
+ * @constructor
+ */
+ function ProxyOptions(model) {
+ OptionsPage.call(this, 'proxy', localStrings.getString('proxyPage'),
+ 'proxyPage');
+ }
+
+ cr.addSingletonGetter(ProxyOptions);
+
+ /**
+ * UI pref change handler.
+ */
+ function handlePrefUpdate(e) {
+ ProxyOptions.getInstance().updateControls();
+ }
+
+ /**
+ * Monitor pref change of given element.
+ */
+ function observePrefsUI(el) {
+ Preferences.getInstance().addEventListener(el.pref, handlePrefUpdate);
+ }
+
+ ProxyOptions.prototype = {
+ // Inherit ProxyOptions from OptionsPage.
+ __proto__: OptionsPage.prototype,
+
+ /**
+ * Initializes ProxyOptions page.
+ */
+ initializePage: function() {
+ // Call base class implementation to start preference initialization.
+ OptionsPage.prototype.initializePage.call(this);
+
+ // Set up ignored page.
+ options.proxyexceptions.ProxyExceptions.decorate($('ignoredHostList'));
+
+ this.addEventListener('visibleChange', this.handleVisibleChange_);
+ $('removeHost').addEventListener('click', this.handleRemoveExceptions_);
+ $('addHost').addEventListener('click', this.handleAddException_);
+ $('directProxy').addEventListener('click', this.disableManual_);
+ $('manualProxy').addEventListener('click', this.enableManual_);
+ $('autoProxy').addEventListener('click', this.disableManual_);
+ $('proxyAllProtocols').addEventListener('click', this.toggleSingle_);
+
+ observePrefsUI($('directProxy'));
+ observePrefsUI($('manualProxy'));
+ observePrefsUI($('autoProxy'));
+ observePrefsUI($('proxyAllProtocols'));
+ },
+
+ proxyListInitialized_: false,
+
+ /**
+ * Update controls state.
+ * @public
+ */
+ updateControls: function() {
+ this.updateBannerVisibility_();
+ this.toggleSingle_();
+ if ($('manualProxy').checked) {
+ this.enableManual_();
+ } else {
+ this.disableManual_();
+ }
+ if (!this.proxyListInitialized_ && this.visible) {
+ this.proxyListInitialized_ = true;
+ $('ignoredHostList').redraw();
+ }
+ },
+
+ /**
+ * Handler for OptionsPage's visible property change event.
+ * @private
+ * @param {Event} e Property change event.
+ */
+ handleVisibleChange_: function(e) {
+ this.updateControls();
+ },
+
+ /**
+ * Updates info banner visibility state. This function shows the banner
+ * if proxy is managed or shared-proxies is off for shared network.
+ * @private
+ */
+ updateBannerVisibility_: function() {
+ var bannerDiv = $('info-banner');
+ // Remove class and listener for click event in case they were added
+ // before and updateBannerVisibility_ is called repeatedly.
+ bannerDiv.classList.remove("clickable");
+ bannerDiv.removeEventListener('click', this.handleSharedProxiesHint_);
+
+ // Show banner and determine its message if necessary.
+ var controlledBy = $('directProxy').controlledBy;
+ if (controlledBy == '') {
+ bannerDiv.hidden = true;
+ } else {
+ bannerDiv.hidden = false;
+ // controlledBy must match strings loaded in proxy_handler.cc and
+ // set in proxy_cros_settings_provider.cc.
+ $('banner-text').textContent = localStrings.getString(controlledBy);
+ if (controlledBy == "enableSharedProxiesBannerText") {
+ bannerDiv.classList.add("clickable");
+ bannerDiv.addEventListener('click', this.handleSharedProxiesHint_);
+ }
+ }
+ },
+
+ /**
+ * Handler for "click" event on yellow banner with enable-shared-proxies
+ * hint.
+ * @private
+ * @param {Event} e Click event fired from info-banner.
+ */
+ handleSharedProxiesHint_: function(e) {
+ OptionsPage.navigateToPage("internet");
+ },
+
+ /**
+ * Handler for when the user clicks on the checkbox to allow a
+ * single proxy usage.
+ * @private
+ * @param {Event} e Click Event.
+ */
+ toggleSingle_: function(e) {
+ if ($('proxyAllProtocols').checked) {
+ $('multiProxy').style.display = 'none';
+ $('singleProxy').style.display = 'block';
+ } else {
+ $('multiProxy').style.display = 'block';
+ $('singleProxy').style.display = 'none';
+ }
+ },
+
+ /**
+ * Handler for selecting a radio button that will disable the manual
+ * controls.
+ * @private
+ * @param {Event} e Click event.
+ */
+ disableManual_: function(e) {
+ $('advancedConfig').hidden = true;
+ $('proxyAllProtocols').disabled = true;
+ $('proxyHostName').disabled = true;
+ $('proxyHostPort').disabled = true;
+ $('proxyHostSingleName').disabled = true;
+ $('proxyHostSinglePort').disabled = true;
+ $('secureProxyHostName').disabled = true;
+ $('secureProxyPort').disabled = true;
+ $('ftpProxy').disabled = true;
+ $('ftpProxyPort').disabled = true;
+ $('socksHost').disabled = true;
+ $('socksPort').disabled = true;
+ $('proxyConfig').disabled = $('autoProxy').disabled ||
+ !$('autoProxy').checked;
+ },
+
+ /**
+ * Handler for selecting a radio button that will enable the manual
+ * controls.
+ * @private
+ * @param {Event} e Click event.
+ */
+ enableManual_: function(e) {
+ $('advancedConfig').hidden = false;
+ $('ignoredHostList').redraw();
+ var all_disabled = $('manualProxy').disabled;
+ $('newHost').disabled = all_disabled;
+ $('removeHost').disabled = all_disabled;
+ $('addHost').disabled = all_disabled;
+ $('proxyAllProtocols').disabled = all_disabled;
+ $('proxyHostName').disabled = all_disabled;
+ $('proxyHostPort').disabled = all_disabled;
+ $('proxyHostSingleName').disabled = all_disabled;
+ $('proxyHostSinglePort').disabled = all_disabled;
+ $('secureProxyHostName').disabled = all_disabled;
+ $('secureProxyPort').disabled = all_disabled;
+ $('ftpProxy').disabled = all_disabled;
+ $('ftpProxyPort').disabled = all_disabled;
+ $('socksHost').disabled = all_disabled;
+ $('socksPort').disabled = all_disabled;
+ $('proxyConfig').disabled = true;
+ },
+
+ /**
+ * Handler for "add" event fired from userNameEdit.
+ * @private
+ * @param {Event} e Add event fired from userNameEdit.
+ */
+ handleAddException_: function(e) {
+ var exception = $('newHost').value;
+ $('newHost').value = '';
+
+ exception = exception.trim();
+ if (exception)
+ $('ignoredHostList').addException(exception);
+ },
+
+ /**
+ * Handler for when the remove button is clicked
+ * @private
+ */
+ handleRemoveExceptions_: function(e) {
+ var selectedItems = $('ignoredHostList').selectedItems;
+ for (var x = 0; x < selectedItems.length; x++) {
+ $('ignoredHostList').removeException(selectedItems[x]);
+ }
+ },
+
+ /**
+ * Sets proxy page title using given network name.
+ * @param {string} network The network name to use in page title.
+ * @public
+ */
+ setNetworkName: function(network) {
+ $('proxy-page-title').textContent =
+ localStrings.getStringF('proxyPageTitleFormat', network);
+ }
+ };
+
+ ProxyOptions.setNetworkName = function(network) {
+ ProxyOptions.getInstance().setNetworkName(network);
+ };
+
+ // Export
+ return {
+ ProxyOptions: ProxyOptions
+ };
+
+});
diff --git a/chrome/browser/resources/options2/chromeos/proxy_rules_list.js b/chrome/browser/resources/options2/chromeos/proxy_rules_list.js
new file mode 100644
index 0000000..7154495
--- /dev/null
+++ b/chrome/browser/resources/options2/chromeos/proxy_rules_list.js
@@ -0,0 +1,139 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options.proxyexceptions', function() {
+ const List = cr.ui.List;
+ const ListItem = cr.ui.ListItem;
+ const ArrayDataModel = cr.ui.ArrayDataModel;
+
+ /**
+ * Creates a new exception list.
+ * @param {Object=} opt_propertyBag Optional properties.
+ * @constructor
+ * @extends {cr.ui.List}
+ */
+ var ProxyExceptions = cr.ui.define('list');
+
+ ProxyExceptions.prototype = {
+ __proto__: List.prototype,
+
+ pref: 'cros.session.proxy.ignorelist',
+
+ /** @inheritDoc */
+ decorate: function() {
+ List.prototype.decorate.call(this);
+
+ // HACK(arv): http://crbug.com/40902
+ window.addEventListener('resize', this.redraw.bind(this));
+
+ this.addEventListener('click', this.handleClick_);
+
+ var self = this;
+
+ // Listens to pref changes.
+ Preferences.getInstance().addEventListener(this.pref,
+ function(event) {
+ self.load_(event.value);
+ });
+ },
+
+ createItem: function(exception) {
+ return new ProxyExceptionsItem(exception);
+ },
+
+ /**
+ * Adds given exception to model and update backend.
+ * @param {Object} exception A exception to be added to exception list.
+ */
+ addException: function(exception) {
+ this.dataModel.push(exception);
+ this.updateBackend_();
+ },
+
+ /**
+ * Removes given exception from model and update backend.
+ */
+ removeException: function(exception) {
+ var dataModel = this.dataModel;
+
+ var index = dataModel.indexOf(exception);
+ if (index >= 0) {
+ dataModel.splice(index, 1);
+ this.updateBackend_();
+ }
+ },
+
+ /**
+ * Handles the clicks on the list and triggers exception removal if the
+ * click is on the remove exception button.
+ * @private
+ * @param {!Event} e The click event object.
+ */
+ handleClick_: function(e) {
+ // Handle left button click
+ if (e.button == 0) {
+ var el = e.target;
+ if (el.className == 'remove-exception-button') {
+ this.removeException(el.parentNode.exception);
+ }
+ }
+ },
+
+ /**
+ * Loads given exception list.
+ * @param {Array} exceptions An array of exception object.
+ */
+ load_: function(exceptions) {
+ this.dataModel = new ArrayDataModel(exceptions);
+ },
+
+ /**
+ * Updates backend.
+ */
+ updateBackend_: function() {
+ Preferences.setListPref(this.pref, this.dataModel.slice());
+ }
+ };
+
+ /**
+ * Creates a new exception list item.
+ * @param exception The exception account this represents.
+ * @constructor
+ * @extends {cr.ui.ListItem}
+ */
+ function ProxyExceptionsItem(exception) {
+ var el = cr.doc.createElement('div');
+ el.exception = exception;
+ ProxyExceptionsItem.decorate(el);
+ return el;
+ }
+
+ /**
+ * Decorates an element as a exception account item.
+ * @param {!HTMLElement} el The element to decorate.
+ */
+ ProxyExceptionsItem.decorate = function(el) {
+ el.__proto__ = ProxyExceptionsItem.prototype;
+ el.decorate();
+ };
+
+ ProxyExceptionsItem.prototype = {
+ __proto__: ListItem.prototype,
+
+ /** @inheritDoc */
+ decorate: function() {
+ ListItem.prototype.decorate.call(this);
+ this.className = 'exception-list-item';
+
+ var labelException = this.ownerDocument.createElement('span');
+ labelException.className = '';
+ labelException.textContent = this.exception;
+ this.appendChild(labelException);
+ }
+ };
+
+ return {
+ ProxyExceptions: ProxyExceptions
+ };
+});
diff --git a/chrome/browser/resources/options2/chromeos/system_options.html b/chrome/browser/resources/options2/chromeos/system_options.html
new file mode 100644
index 0000000..bab4667
--- /dev/null
+++ b/chrome/browser/resources/options2/chromeos/system_options.html
@@ -0,0 +1,125 @@
+<div id="systemPage" class="page" hidden>
+ <h1 i18n-content="systemPage"></h1>
+ <div class="displaytable">
+ <section>
+ <h3 i18n-content="datetimeTitle"></h3>
+ <div class="option-control-table">
+ <span class="option-name" i18n-content="timezone"></span>
+ <div id="timezone-value">
+ <select id="timezone-select" class="control"
+ i18n-options="timezoneList"
+ data-type="string"
+ pref="cros.system.timezone"></select>
+ </div>
+ <div class="checkbox">
+ <label>
+ <input id="use-24hour-clock"
+ pref="settings.clock.use_24hour_clock"
+ type="checkbox">
+ <span i18n-content="use24HourClock"></span>
+ </label>
+ </div>
+ </div>
+ </section>
+ <section>
+ <h3 i18n-content="screen"></h3>
+ <div id="brightness-value">
+ <span i18n-content="brightness"></span>
+ <button id="brightness-decrease-button"
+ i18n-content="brightnessDecrease"></button>
+ <button id="brightness-increase-button"
+ i18n-content="brightnessIncrease"></button>
+ </div>
+ </section>
+ <section id="touchpad-controls" hidden>
+ <h3 i18n-content="touchpad"></h3>
+ <div class="option-control-table">
+ <span class="option-name" i18n-content="sensitivity"></span>
+ <div id="touchpad-value">
+ <div id="slider-control">
+ <input id="sensitivity-range" type="range" min="1" max="5"
+ pref="settings.touchpad.sensitivity2" class="touch-slider">
+ <div>
+ <span i18n-content="sensitivityLess"></span>
+ <span i18n-content="sensitivityMore"
+ class="touchpad-sensitivity-more"></span>
+ </div>
+ </div>
+ </div>
+ <div id="tap-to-click" class="checkbox">
+ <label>
+ <input id="tap-to-click-check"
+ pref="settings.touchpad.enable_tap_to_click"
+ type="checkbox">
+ <span i18n-content="enableTapToClick"></span>
+ </label>
+ </div>
+ </div>
+ </section>
+ <!-- By default, the bluetooth section is hidden. It is only
+ visible if the command line flag --enable_bluetooth is set. -->
+ <section id="bluetooth-devices" hidden>
+ <h3 i18n-content="bluetooth"></h3>
+ <div id="bluetooth-options-div">
+ <div id="bluetooth-buttons">
+ <button id="enable-bluetooth" i18n-content="enableBluetooth" hidden>
+ <button id="disable-bluetooth" i18n-content="disableBluetooth">
+ </div>
+ <div id="no-bluetooth-devices-label"
+ i18n-content="noBluetoothDevicesFound">
+ </div>
+ <div id="bluetooth-device-list">
+ <!-- A list of connected devices is inserted here on page load. -->
+ <!-- The list of devices is updated asynchronously on clicking
+ 'Find Devices'. -->
+ </div>
+ <!-- Template for items in list of Bluetooth devices -->
+ <div id="bluetooth-item-template" class="bluetooth-item" hidden>
+ <div class="bluetooth-item-text">
+ <div class="network-name-label"></div>
+ <div class="bluetooth-status">
+ <span class="network-status-label"></span>
+ <div class="inline-spinner" hidden></div>
+ </div>
+ <div class="bluetooth-instructions"></div>
+ </div>
+ <div class="bluetooth-button-group"></div>
+ </div>
+ <div id = "bluetooth-finder-container">
+ <button id="bluetooth-find-devices"
+ i18n-content="findBluetoothDevices"></button>
+ <div id="bluetooth-scanning-icon"
+ class="inline-spinner transparent"></div>
+ <span id="bluetooth-scanning-label" class="transparent"
+ i18n-content="bluetoothScanning"></span>
+ </div>
+ </div>
+ </section>
+ <section>
+ <h3 i18n-content="language"></h3>
+ <div class="option-control-table">
+ <div class="option-name">
+ <button id="language-button" i18n-content="languageCustomize">
+ </button>
+ </div>
+ <div class="option-name">
+ <button id="modifier-keys-button"
+ i18n-content="modifierKeysCustomize"></button>
+ </div>
+ </div>
+ </section>
+ <section>
+ <h3 i18n-content="accessibilityTitle"></h3>
+ <div class="option-control-table">
+ <div class="option-name">
+ <div class="checkbox">
+ <label>
+ <input id="accesibility-check" type="checkbox">
+ <span i18n-content="accessibility"></span>
+ </label>
+ </div>
+ </div>
+ </div>
+ </section>
+ </div>
+</div>
diff --git a/chrome/browser/resources/options2/chromeos/system_options.js b/chrome/browser/resources/options2/chromeos/system_options.js
new file mode 100644
index 0000000..0a9faeb
--- /dev/null
+++ b/chrome/browser/resources/options2/chromeos/system_options.js
@@ -0,0 +1,202 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+
+ var OptionsPage = options.OptionsPage;
+ var RepeatingButton = cr.ui.RepeatingButton;
+
+ /////////////////////////////////////////////////////////////////////////////
+ // SystemOptions class:
+
+ /**
+ * Encapsulated handling of ChromeOS system options page.
+ * @constructor
+ */
+
+ function SystemOptions() {
+ OptionsPage.call(this, 'system', templateData.systemPageTabTitle,
+ 'systemPage');
+ }
+
+ cr.addSingletonGetter(SystemOptions);
+
+ // Inherit SystemOptions from OptionsPage.
+ SystemOptions.prototype = {
+ __proto__: options.OptionsPage.prototype,
+
+ /**
+ * Initializes SystemOptions page.
+ * Calls base class implementation to starts preference initialization.
+ */
+ initializePage: function() {
+ OptionsPage.prototype.initializePage.call(this);
+
+ // Disable time-related settings if we're not logged in as a real user.
+ if (AccountsOptions.loggedInAsGuest()) {
+ var timezone = $('timezone-select');
+ if (timezone)
+ timezone.disabled = true;
+ var use_24hour_clock = $('use-24hour-clock');
+ if (use_24hour_clock)
+ use_24hour_clock.disabled = true;
+ }
+
+ options.system.bluetooth.BluetoothListElement.decorate(
+ $('bluetooth-device-list'));
+
+ // TODO(kevers): Populate list of connected bluetooth devices.
+ // Set state of 'Enable bluetooth' checkbox.
+ $('bluetooth-find-devices').onclick = function(event) {
+ findBluetoothDevices_();
+ };
+ $('enable-bluetooth').onclick = function(event) {
+ chrome.send('bluetoothEnableChange', [Boolean(true)]);
+ };
+ $('disable-bluetooth').onclick = function(event) {
+ chrome.send('bluetoothEnableChange', [Boolean(false)]);
+ };
+ $('language-button').onclick = function(event) {
+ OptionsPage.navigateToPage('language');
+ };
+ $('modifier-keys-button').onclick = function(event) {
+ OptionsPage.navigateToPage('languageCustomizeModifierKeysOverlay');
+ };
+ $('accesibility-check').onchange = function(event) {
+ chrome.send('accessibilityChange',
+ [String($('accesibility-check').checked)]);
+ };
+ initializeBrightnessButton_('brightness-decrease-button',
+ 'decreaseScreenBrightness');
+ initializeBrightnessButton_('brightness-increase-button',
+ 'increaseScreenBrightness');
+ }
+ };
+
+ /**
+ * Initializes a button for controlling screen brightness.
+ * @param {string} id Button ID.
+ * @param {string} callback Name of the callback function.
+ */
+ function initializeBrightnessButton_(id, callback) {
+ var button = $(id);
+ cr.ui.decorate(button, RepeatingButton);
+ button.repeatInterval = 300;
+ button.addEventListener(RepeatingButton.Event.BUTTON_HELD, function(e) {
+ chrome.send(callback);
+ });
+ }
+
+ /**
+ * Scan for bluetooth devices.
+ * @private
+ */
+ function findBluetoothDevices_() {
+ setVisibility_('bluetooth-scanning-label', true);
+ setVisibility_('bluetooth-scanning-icon', true);
+
+ // Remove devices that are not currently connected.
+ var devices = $('bluetooth-device-list').childNodes;
+ for (var i = devices.length - 1; i >= 0; i--) {
+ var device = devices.item(i);
+ var data = device.data;
+ if (!data || data.status !== 'connected')
+ $('bluetooth-device-list').removeChild(device);
+ }
+ chrome.send('findBluetoothDevices');
+ }
+
+ /**
+ * Sets the visibility of an element.
+ * @param {string} id The id of the element.
+ * @param {boolean} visible True if the element should be made visible.
+ * @private
+ */
+ function setVisibility_(id, visible) {
+ if (visible)
+ $(id).classList.remove("transparent");
+ else
+ $(id).classList.add("transparent");
+ }
+
+ //
+ // Chrome callbacks
+ //
+
+ /**
+ * Set the initial state of the accessibility checkbox.
+ */
+ SystemOptions.SetAccessibilityCheckboxState = function(checked) {
+ $('accesibility-check').checked = checked;
+ };
+
+ /**
+ * Activate the bluetooth settings section on the System settings page.
+ */
+ SystemOptions.showBluetoothSettings = function() {
+ $('bluetooth-devices').hidden = false;
+ };
+
+ /**
+ * Sets the state of the checkbox indicating if bluetooth is turned on. The
+ * state of the "Find devices" button and the list of discovered devices may
+ * also be affected by a change to the state.
+ * @param {boolean} checked Flag Indicating if Bluetooth is turned on.
+ */
+ SystemOptions.setBluetoothState = function(checked) {
+ $('disable-bluetooth').hidden = !checked;
+ $('enable-bluetooth').hidden = checked;
+ $('bluetooth-finder-container').hidden = !checked;
+ $('no-bluetooth-devices-label').hidden = !checked;
+ if (!checked) {
+ setVisibility_('bluetooth-scanning-label', false);
+ setVisibility_('bluetooth-scanning-icon', false);
+ }
+ // Flush list of previously discovered devices if bluetooth is turned off.
+ if (!checked) {
+ var devices = $('bluetooth-device-list').childNodes;
+ for (var i = devices.length - 1; i >= 0; i--) {
+ var device = devices.item(i);
+ $('bluetooth-device-list').removeChild(device);
+ }
+ }
+ }
+
+ /**
+ * Adds an element to the list of available bluetooth devices. If an element
+ * with a matching address is found, the existing element is updated.
+ * @param {{name: string,
+ * address: string,
+ * icon: string,
+ * paired: boolean,
+ * connected: boolean}} device
+ * Decription of the bluetooth device.
+ */
+ SystemOptions.addBluetoothDevice = function(device) {
+ if ($('bluetooth-device-list').appendDevice(device))
+ $('no-bluetooth-devices-label').hidden = true;
+ };
+
+ /**
+ * Hides the scanning label and icon that are used to indicate that a device
+ * search is in progress.
+ */
+ SystemOptions.notifyBluetoothSearchComplete = function() {
+ setVisibility_('bluetooth-scanning-label', false);
+ setVisibility_('bluetooth-scanning-icon', false);
+ };
+
+ /**
+ * Displays the Touchpad Controls section when we detect a touchpad.
+ */
+ SystemOptions.showTouchpadControls = function() {
+ $('touchpad-controls').hidden = false;
+ };
+
+ // Export
+ return {
+ SystemOptions: SystemOptions
+ };
+
+});
diff --git a/chrome/browser/resources/options2/chromeos/system_options_page.css b/chrome/browser/resources/options2/chromeos/system_options_page.css
new file mode 100644
index 0000000..d38ebdc
--- /dev/null
+++ b/chrome/browser/resources/options2/chromeos/system_options_page.css
@@ -0,0 +1,167 @@
+.touchpad-sensitivity-more {
+ float: right;
+}
+
+html[dir=rtl] .touchpad-sensitivity-more {
+ float: left;
+}
+
+.option-name {
+ display: inline;
+}
+
+#timezone-value {
+ display: inline-block;
+ vertical-align: baseline;
+}
+
+#touchpad-value,
+#slider-control {
+ display: inline-block;
+ vertical-align: top;
+}
+
+#bluetooth-options-div {
+ -webkit-box-orient: vertical;
+ display: -webkit-box;
+}
+
+#no-bluetooth-devices-label {
+ -webkit-margin-after: 5px;
+ -webkit-margin-before: 5px;
+ color: gray;
+}
+
+#bluetooth-finder-container,
+#bluetooth-scanning-status {
+ -webkit-box-orient: horizontal;
+ display: -webkit-box;
+ vertical-align: baseline;
+}
+
+#bluetooth-scanning-label,
+#bluetooth-scanning-icon {
+ -webkit-transition: 250ms opacity;
+}
+
+#bluetooth-scanning-label {
+ -webkit-margin-start: 5px;
+ color: gray;
+}
+
+#bluetooth-scanning-icon {
+ -webkit-margin-start: 10px;
+ vertical-align: middle;
+}
+
+#bluetooth-device-list {
+ display: table;
+ width: 100%;
+}
+
+#bluetooth-device-list > * {
+ display: table-row;
+}
+
+#bluetooth-device-list > * > * {
+ border-bottom: 4px solid rgba(255,255,255,1);
+ display: table-cell;
+}
+
+.bluetooth-item > * > button {
+ visibility: hidden;
+ width: 100%;
+}
+
+.bluetooth-item:first-child > * {
+ border-top: 4px solid rgba(255,255,255,1);
+}
+
+.bluetooth-item:hover > * > button,
+.bluetooth-item[connected] > * > button,
+.bluetooth-item[connecting] > * > button {
+ visibility: visible;
+}
+
+.bluetooth-item[connected] {
+ background-color: hsl(214, 91%, 89%);
+ background-image: -webkit-linear-gradient(rgba(255, 255, 255, 0.8),
+ rgba(255, 255, 255, 0));
+ border-color: hsl(214, 91%, 65%);
+}
+
+.bluetooth-item[paired] {
+ color: gray;
+}
+
+.bluetooth-item:hover,
+.bluetooth-item[connecting] {
+ background-color: hsl(214, 91%, 97%);
+ border-color: hsl(214, 91%, 65%);
+}
+
+.bluetooth-item-text {
+ -webkit-padding-after: 3px;
+ -webkit-padding-before: 3px;
+ -webkit-padding-end: 5px;
+ -webkit-padding-start: 5px;
+ width: 100%;
+}
+
+.bluetooth-item-text > * > .inline-spinner {
+ -webkit-margin-start: 5px;
+ -webkit-transform: translateY(3px);
+}
+
+.bluetooth-instructions {
+ -webkit-margin-after: 5px;
+ -webkit-margin-before: 5px;
+ display: block;
+ line-height: 120%;
+}
+
+.bluetooth-remote-passkey {
+ -webkit-box-align: baseline;
+ -webkit-box-orient: horizontal;
+ display: -webkit-inline-box;
+ margin-bottom: 5px;
+ margin-top: 5px;
+}
+
+.bluetooth-confirm-passkey {
+ display: inline;
+ font-weight: bold;
+}
+
+.bluetooth-passkey-char {
+ -webkit-margin-end: 3px;
+ -webkit-margin-start: 3px;
+ border: 1px solid black;
+ display: -webkit-box;
+ font-weight: bold;
+ padding: 2px;
+}
+
+.bluetooth-passkey-char:first-child {
+ -webkit-margin-start: 10px;
+}
+
+.bluetooth-passkey-char:last-child {
+ -webkit-margin-end: 10px;
+}
+
+.bluetooth-passkey-char.key-typed {
+ background-color: hsl(214, 91%, 50%);
+ color: white;
+}
+
+.bluetooth-passkey-field {
+ -webkit-margin-start: 10px;
+ width: 100px;
+}
+
+.bluetooth-button-group {
+ -webkit-padding-end: 5px;
+ vertical-align: middle;
+}
+
diff --git a/chrome/browser/resources/options2/chromeos/virtual_keyboard.css b/chrome/browser/resources/options2/chromeos/virtual_keyboard.css
new file mode 100644
index 0000000..0752b3b
--- /dev/null
+++ b/chrome/browser/resources/options2/chromeos/virtual_keyboard.css
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2011 The Chromium Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+.virtual-keyboard-column-headers {
+ display: -webkit-box;
+ font-size: 13px;
+ font-weight: bold;
+}
+
+.virtual-keyboard-layout-column {
+ width: 250px;
+ -webkit-margin-end: 10px;
+ -webkit-margin-start: 14px;
+}
+
+#virtual-keyboard-manager list {
+ border-radius: 2px;
+ border: solid 1px #D9D9D9;
+ margin-bottom: 10px;
+ margin-top: 4px;
+}
diff --git a/chrome/browser/resources/options2/chromeos/virtual_keyboard.html b/chrome/browser/resources/options2/chromeos/virtual_keyboard.html
new file mode 100644
index 0000000..c2c15fb
--- /dev/null
+++ b/chrome/browser/resources/options2/chromeos/virtual_keyboard.html
@@ -0,0 +1,13 @@
+<div id="virtual-keyboard-manager" class="page" hidden>
+ <h1 i18n-content="virtualKeyboardPage"></h1>
+ <div class="virtual-keyboard-column-headers">
+ <div class="virtual-keyboard-layout-column">
+ <h3 i18n-content="virtualKeyboardLayoutColumnTitle"></h3>
+ </div>
+ <div class="virtual-keyboard-keyboard-column">
+ <h3 i18n-content="virtualKeyboardKeyboardColumnTitle"></h3>
+ </div>
+ </div>
+ <list id="virtual-keyboard-per-layout-list"></list>
+ <!-- TODO(yusukes): Add virtual-keyboards-per-site elements. -->
+</div>
diff --git a/chrome/browser/resources/options2/chromeos/virtual_keyboard.js b/chrome/browser/resources/options2/chromeos/virtual_keyboard.js
new file mode 100644
index 0000000..359d190
--- /dev/null
+++ b/chrome/browser/resources/options2/chromeos/virtual_keyboard.js
@@ -0,0 +1,92 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+ const OptionsPage = options.OptionsPage;
+
+ /////////////////////////////////////////////////////////////////////////////
+ // VirtualKeyboardManager class:
+
+ /**
+ * Virtual keyboard management page.
+ * @constructor
+ */
+ function VirtualKeyboardManager() {
+ this.activeNavTab = null;
+ OptionsPage.call(this,
+ 'virtualKeyboards',
+ // The templateData.virtualKeyboardPageTabTitle is added
+ // in OptionsPageUIHandler::RegisterTitle().
+ templateData.virtualKeyboardPageTabTitle,
+ 'virtual-keyboard-manager');
+ }
+
+ cr.addSingletonGetter(VirtualKeyboardManager);
+
+ VirtualKeyboardManager.prototype = {
+ __proto__: OptionsPage.prototype,
+
+ /**
+ * The virtual keyboards list.
+ * @type {ItemList}
+ * @private
+ */
+ virtualKeyboardsList_: null,
+
+ /** @inheritDoc */
+ initializePage: function() {
+ OptionsPage.prototype.initializePage.call(this);
+ this.createVirtualKeyboardsList_();
+ },
+
+ /** @inheritDoc */
+ didShowPage: function() {
+ chrome.send('updateVirtualKeyboardList');
+ },
+
+ /**
+ * Creates, decorates and initializes the keyboards list.
+ * @private
+ */
+ createVirtualKeyboardsList_: function() {
+ this.virtualKeyboardsList_ = $('virtual-keyboard-per-layout-list');
+ options.VirtualKeyboardsList.decorate(this.virtualKeyboardsList_);
+ this.virtualKeyboardsList_.autoExpands = true;
+ },
+ };
+
+ /**
+ * Sets the list of virtual keyboards shown in the view. This function is
+ * called by C++ code (e.g. chrome/browser/ui/webui/options/chromeos/).
+ * @param {Object} list A list of layouts with their registered virtual
+ * keyboards.
+ */
+ VirtualKeyboardManager.updateVirtualKeyboardList = function(list) {
+ // See virtual_keyboard_list.js for an example of the format the list should
+ // take.
+ var filteredList = list.filter(function(element, index, array) {
+ // Don't show a layout which is supported by only one virtual keyboard
+ // extension.
+ return element.supportedKeyboards.length > 1;
+ });
+
+ // Sort the drop-down menu items by name.
+ filteredList.forEach(function(e) {
+ e.supportedKeyboards.sort(function(e1, e2) {
+ return e1.name > e2.name;
+ });
+ });
+
+ // Sort the list by layout name.
+ $('virtual-keyboard-per-layout-list').setVirtualKeyboardList(
+ filteredList.sort(function(e1, e2) {
+ return e1.layoutName > e2.layoutName;
+ }));
+ };
+
+ // Export
+ return {
+ VirtualKeyboardManager: VirtualKeyboardManager,
+ };
+});
diff --git a/chrome/browser/resources/options2/chromeos/virtual_keyboard_list.js b/chrome/browser/resources/options2/chromeos/virtual_keyboard_list.js
new file mode 100644
index 0000000..650de1a
--- /dev/null
+++ b/chrome/browser/resources/options2/chromeos/virtual_keyboard_list.js
@@ -0,0 +1,146 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+ const ArrayDataModel = cr.ui.ArrayDataModel;
+ const List = cr.ui.List;
+ const ListItem = cr.ui.ListItem;
+ const VirtualKeyboardOptions = options.VirtualKeyboardOptions;
+
+ const localStrings = new LocalStrings();
+
+ /**
+ * Creates a virtual keyboard list item.
+ *
+ * Accepts values in the form
+ * { layout: 'us(dvorak)',
+ * layoutName: 'US Dvorak layout',
+ * preferredKeyboard: 'http://...', [optional]
+ * supportedKeyboards: [
+ * { name: 'Simple Virtual Keyboard',
+ * isSystem: true,
+ * url: 'http://...' },
+ * { name: '3rd party Virtual Keyboard',
+ * isSystem: false,
+ * url: 'http://...' },
+ * ...,
+ * ]
+ * }
+ * @param {Object} entry A dictionary describing the virtual keyboards for a
+ * given layout.
+ * @constructor
+ * @extends {cr.ui.ListItem}
+ */
+ function VirtualKeyboardListItem(entry) {
+ var el = cr.doc.createElement('div');
+ el.dataItem = entry;
+ el.__proto__ = VirtualKeyboardListItem.prototype;
+ el.decorate();
+ return el;
+ }
+
+ VirtualKeyboardListItem.prototype = {
+ __proto__: ListItem.prototype,
+
+ buildWidget_: function(data, delegate) {
+ // Layout name.
+ var layoutNameElement = document.createElement('div');
+ layoutNameElement.textContent = data.layoutName;
+ layoutNameElement.className = 'virtual-keyboard-layout-column';
+ this.appendChild(layoutNameElement);
+
+ // Virtual keyboard selection.
+ var keyboardElement = document.createElement('div');
+ var selectElement = document.createElement('select');
+ var defaultOptionElement = document.createElement('option');
+ defaultOptionElement.selected = (data.preferredKeyboard == null);
+ defaultOptionElement.textContent =
+ localStrings.getString('defaultVirtualKeyboard');
+ defaultOptionElement.value = -1;
+ selectElement.appendChild(defaultOptionElement);
+
+ for (var i = 0; i < data.supportedKeyboards.length; ++i) {
+ var optionElement = document.createElement('option');
+ optionElement.selected =
+ (data.preferredKeyboard != null &&
+ data.preferredKeyboard == data.supportedKeyboards[i].url);
+ optionElement.textContent = data.supportedKeyboards[i].name;
+ optionElement.value = i;
+ selectElement.appendChild(optionElement);
+ }
+
+ selectElement.addEventListener('change', function(e) {
+ var index = e.target.value;
+ if (index == -1) {
+ // The 'Default' menu item is selected. Delete the preference.
+ delegate.clearPreference(data.layout);
+ } else {
+ delegate.setPreference(
+ data.layout, data.supportedKeyboards[index].url);
+ }
+ });
+
+ keyboardElement.appendChild(selectElement);
+ keyboardElement.className = 'virtual-keyboard-keyboard-column';
+ this.appendChild(keyboardElement);
+ },
+
+ /** @inheritDoc */
+ decorate: function() {
+ ListItem.prototype.decorate.call(this);
+
+ var delegate = {
+ clearPreference: function(layout) {
+ // Call a C++ function in chrome/browser/ui/webui/options/chromeos/.
+ chrome.send('clearVirtualKeyboardPreference', [layout]);
+ },
+ setPreference: function(layout, url) {
+ chrome.send('setVirtualKeyboardPreference', [layout, url]);
+ },
+ };
+
+ this.buildWidget_(this.dataItem, delegate);
+ },
+ };
+
+ /**
+ * Create a new virtual keyboard list.
+ * @constructor
+ * @extends {cr.ui.List}
+ */
+ var VirtualKeyboardsList = cr.ui.define('list');
+
+ VirtualKeyboardsList.prototype = {
+ __proto__: List.prototype,
+
+ /** @inheritDoc */
+ createItem: function(entry) {
+ return new VirtualKeyboardListItem(entry);
+ },
+
+ /**
+ * The length of the list.
+ */
+ get length() {
+ return this.dataModel.length;
+ },
+
+ /**
+ * Set the virtual keyboards displayed by this list.
+ * See VirtualKeyboardListItem for an example of the format the list should
+ * take.
+ *
+ * @param {Object} list A list of layouts with their registered virtual
+ * keyboards.
+ */
+ setVirtualKeyboardList: function(list) {
+ this.dataModel = new ArrayDataModel(list);
+ },
+ };
+
+ return {
+ VirtualKeyboardListItem: VirtualKeyboardListItem,
+ VirtualKeyboardsList: VirtualKeyboardsList,
+ };
+});
diff --git a/chrome/browser/resources/options2/clear_browser_data_overlay.css b/chrome/browser/resources/options2/clear_browser_data_overlay.css
new file mode 100644
index 0000000..b2cf716
--- /dev/null
+++ b/chrome/browser/resources/options2/clear_browser_data_overlay.css
@@ -0,0 +1,24 @@
+/*
+Copyright (c) 2011 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+*/
+
+#clearBrowserDataOverlay {
+ min-width: 500px;
+}
+
+#clearBrowserDataOverlay > .content-area label {
+ margin: 5px 0;
+}
+
+#clear-data-checkboxes {
+ -webkit-padding-start: 8px;
+ margin: 5px 0;
+}
+
+#cbdThrobber {
+ margin: 4px 10px;
+ vertical-align: middle;
+ visibility: hidden;
+}
diff --git a/chrome/browser/resources/options2/clear_browser_data_overlay.html b/chrome/browser/resources/options2/clear_browser_data_overlay.html
new file mode 100644
index 0000000..26ee6e1
--- /dev/null
+++ b/chrome/browser/resources/options2/clear_browser_data_overlay.html
@@ -0,0 +1,73 @@
+<div id="clearBrowserDataOverlay" class="page" hidden>
+ <h1 i18n-content="clearBrowserDataOverlay"></h1>
+ <div id="cbdContentArea" class="content-area">
+ <span i18n-content="clearBrowserDataLabel"></span>
+ <select id="clearBrowserDataTimePeriod"
+ i18n-options="clearBrowserDataTimeList"
+ pref="browser.clear_data.time_period"
+ data-type="number">
+ </select>
+ <div id="clear-data-checkboxes">
+ <div class="checkbox">
+ <label>
+ <input id="deleteBrowsingHistoryCheckbox"
+ pref="browser.clear_data.browsing_history" type="checkbox">
+ <span i18n-content="deleteBrowsingHistoryCheckbox"></span>
+ </label>
+ </div>
+ <div class="checkbox">
+ <label>
+ <input id="deleteDownloadHistoryCheckbox"
+ pref="browser.clear_data.download_history" type="checkbox">
+ <span i18n-content="deleteDownloadHistoryCheckbox"></span>
+ </label>
+ </div>
+ <div class="checkbox">
+ <label>
+ <input id="deleteCacheCheckbox"
+ pref="browser.clear_data.cache" type="checkbox">
+ <span i18n-content="deleteCacheCheckbox"></span>
+ </label>
+ </div>
+ <div class="checkbox">
+ <label>
+ <input id="deleteCookiesCheckbox"
+ pref="browser.clear_data.cookies" type="checkbox">
+ <span i18n-content="deleteCookiesFlashCheckbox"
+ class="clear-plugin-lso-data-enabled"></span>
+ <span i18n-content="deleteCookiesCheckbox"
+ class="clear-plugin-lso-data-disabled"></span>
+ </label>
+ </div>
+ <div class="checkbox">
+ <label>
+ <input id="deletePasswordsCheckbox"
+ pref="browser.clear_data.passwords" type="checkbox">
+ <span i18n-content="deletePasswordsCheckbox"></span>
+ </label>
+ </div>
+ <div class="checkbox">
+ <label>
+ <input id="deleteFormDataCheckbox"
+ pref="browser.clear_data.form_data" type="checkbox">
+ <span i18n-content="deleteFormDataCheckbox"></span>
+ </label>
+ </div>
+ </div>
+ </div>
+ <div class="action-area">
+ <div class="flash-plugin-area">
+ <a target="_blank" i18n-content="flash_storage_settings"
+ i18n-values="href:flash_storage_url"></a>
+ </div>
+ <div class="action-area-right">
+ <div id="cbdThrobber" class="throbber"></div>
+ <div class="button-strip">
+ <button id="clearBrowserDataDismiss" i18n-content="cancel"></button>
+ <button id="clearBrowserDataCommit"
+ i18n-content="clearBrowserDataCommit">
+ </button>
+ </div>
+ </div>
+ </div>
+</div>
diff --git a/chrome/browser/resources/options2/clear_browser_data_overlay.js b/chrome/browser/resources/options2/clear_browser_data_overlay.js
new file mode 100644
index 0000000..8533ccc
--- /dev/null
+++ b/chrome/browser/resources/options2/clear_browser_data_overlay.js
@@ -0,0 +1,110 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+ var OptionsPage = options.OptionsPage;
+
+ /**
+ * ClearBrowserDataOverlay class
+ * Encapsulated handling of the 'Clear Browser Data' overlay page.
+ * @class
+ */
+ function ClearBrowserDataOverlay() {
+ OptionsPage.call(this, 'clearBrowserData',
+ templateData.clearBrowserDataOverlayTabTitle,
+ 'clearBrowserDataOverlay');
+ }
+
+ cr.addSingletonGetter(ClearBrowserDataOverlay);
+
+ ClearBrowserDataOverlay.prototype = {
+ // Inherit ClearBrowserDataOverlay from OptionsPage.
+ __proto__: OptionsPage.prototype,
+
+ /**
+ * Initialize the page.
+ */
+ initializePage: function() {
+ // Call base class implementation to starts preference initialization.
+ OptionsPage.prototype.initializePage.call(this);
+
+ var f = this.updateCommitButtonState_.bind(this);
+ var types = ['browser.clear_data.browsing_history',
+ 'browser.clear_data.download_history',
+ 'browser.clear_data.cache',
+ 'browser.clear_data.cookies',
+ 'browser.clear_data.passwords',
+ 'browser.clear_data.form_data'];
+ types.forEach(function(type) {
+ Preferences.getInstance().addEventListener(type, f);
+ });
+
+ var checkboxes = document.querySelectorAll(
+ '#cbdContentArea input[type=checkbox]');
+ for (var i = 0; i < checkboxes.length; i++) {
+ checkboxes[i].onclick = f;
+ }
+ this.updateCommitButtonState_();
+
+ $('clearBrowserDataDismiss').onclick = function(event) {
+ ClearBrowserDataOverlay.dismiss();
+ };
+ $('clearBrowserDataCommit').onclick = function(event) {
+ chrome.send('performClearBrowserData');
+ };
+ },
+
+ // Set the enabled state of the commit button.
+ updateCommitButtonState_: function() {
+ var checkboxes = document.querySelectorAll(
+ '#cbdContentArea input[type=checkbox]');
+ var isChecked = false;
+ for (var i = 0; i < checkboxes.length; i++) {
+ if (checkboxes[i].checked) {
+ isChecked = true;
+ break;
+ }
+ }
+ $('clearBrowserDataCommit').disabled = !isChecked;
+ },
+ };
+
+ //
+ // Chrome callbacks
+ //
+ ClearBrowserDataOverlay.setClearingState = function(state) {
+ $('deleteBrowsingHistoryCheckbox').disabled = state;
+ $('deleteDownloadHistoryCheckbox').disabled = state;
+ $('deleteCacheCheckbox').disabled = state;
+ $('deleteCookiesCheckbox').disabled = state;
+ $('deletePasswordsCheckbox').disabled = state;
+ $('deleteFormDataCheckbox').disabled = state;
+ $('clearBrowserDataTimePeriod').disabled = state;
+ $('cbdThrobber').style.visibility = state ? 'visible' : 'hidden';
+
+ if (state)
+ $('clearBrowserDataCommit').disabled = true;
+ else
+ ClearBrowserDataOverlay.getInstance().updateCommitButtonState_();
+ };
+
+ ClearBrowserDataOverlay.doneClearing = function() {
+ // The delay gives the user some feedback that the clearing
+ // actually worked. Otherwise the dialog just vanishes instantly in most
+ // cases.
+ window.setTimeout(function() {
+ ClearBrowserDataOverlay.dismiss();
+ }, 200);
+ };
+
+ ClearBrowserDataOverlay.dismiss = function() {
+ OptionsPage.closeOverlay();
+ this.setClearingState(false);
+ };
+
+ // Export
+ return {
+ ClearBrowserDataOverlay: ClearBrowserDataOverlay
+ };
+});
diff --git a/chrome/browser/resources/options2/content_settings.css b/chrome/browser/resources/options2/content_settings.css
new file mode 100644
index 0000000..625ab18
--- /dev/null
+++ b/chrome/browser/resources/options2/content_settings.css
@@ -0,0 +1,74 @@
+/*
+Copyright (c) 2011 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+*/
+
+.exception-pattern {
+ -webkit-box-flex: 1;
+ -webkit-margin-end: 10px;
+ -webkit-margin-start: 14px;
+}
+
+.exception-setting {
+ display: inline-block;
+ width: 120px;
+}
+
+select.exception-setting {
+ vertical-align: middle;
+}
+
+#exception-column-headers {
+ -webkit-margin-start: 17px;
+ display: -webkit-box;
+ margin-top: 17px;
+}
+
+#exception-column-headers > div {
+ font-weight: bold;
+}
+
+#exception-pattern-column {
+ -webkit-box-flex: 1;
+}
+
+#exception-behavior-column {
+ width: 145px;
+}
+
+.otr-explanation {
+ font-style: italic;
+}
+
+#content-settings-exceptions-area list {
+ margin-bottom: 10px;
+ margin-top: 4px;
+}
+
+#disable-plugins-container {
+ margin: 7px 0px;
+}
+
+div[role="listitem"][managedby] {
+ color: #666;
+ font-style: italic;
+ position: relative;
+}
+
+.settings-list div[role="listitem"][managedby="policy"],
+.settings-list div[role="listitem"][managedby="extension"] {
+ background: -webkit-linear-gradient(#fff1b5, #fae692);
+ border-top: 0;
+ border-bottom: 1px solid #c9bd8d;
+}
+
+list div[role="listitem"][managedby="policy"] .close-button {
+ background-image: url("chrome://theme/IDR_MANAGED");
+ opacity: 1;
+}
+
+list div[role="listitem"][managedby="extension"] .close-button {
+ background-image: url("chrome://theme/IDR_EXTENSIONS_SECTION_SMALL");
+ opacity: 1;
+}
diff --git a/chrome/browser/resources/options2/content_settings.html b/chrome/browser/resources/options2/content_settings.html
new file mode 100644
index 0000000..6d735f3
--- /dev/null
+++ b/chrome/browser/resources/options2/content_settings.html
@@ -0,0 +1,279 @@
+<div id="content-settings-page" class="page" hidden>
+ <h1 i18n-content="contentSettingsPage"></h1>
+ <div class="displaytable">
+ <!-- Cookie filter tab contents -->
+ <section>
+ <h3 i18n-content="cookies_tab_label"></h3>
+ <div>
+ <div class="radio">
+ <label>
+ <input type="radio" name="cookies" value="allow">
+ <span i18n-content="cookies_allow"></span>
+ </label>
+ </div>
+ <div class="radio">
+ <label>
+ <input type="radio" name="cookies" value="session">
+ <span i18n-content="cookies_session_only"></span>
+ </label>
+ </div>
+ <div class="radio">
+ <label>
+ <input type="radio" name="cookies" value="block">
+ <span i18n-content="cookies_block"></span>
+ </label>
+ </div>
+ <div class="checkbox">
+ <label>
+ <input pref="profile.block_third_party_cookies" type="checkbox">
+ <span i18n-content="cookies_block_3rd_party"></span>
+ </label>
+ </div>
+ <div class="checkbox">
+ <label>
+ <input id="clear-cookies-on-exit"
+ pref="profile.clear_site_data_on_exit" type="checkbox">
+ <span i18n-content="cookies_lso_clear_when_close"
+ class="clear-plugin-lso-data-enabled"></span>
+ <span i18n-content="cookies_clear_when_close"
+ class="clear-plugin-lso-data-disabled"></span>
+ </label>
+ </div>
+ <button class="exceptions-list-button" contentType="cookies"
+ i18n-content="manage_exceptions"></button>
+ <button id="show-cookies-button"
+ i18n-content="cookies_show_cookies"></button>
+ </div>
+ </section>
+ <!-- Image filter -->
+ <section>
+ <h3 i18n-content="images_tab_label"></h3>
+ <div>
+ <div class="radio">
+ <label>
+ <input type="radio" name="images" value="allow">
+ <span i18n-content="images_allow"></span>
+ </label>
+ </div>
+ <div class="radio">
+ <label>
+ <input type="radio" name="images" value="block">
+ <span i18n-content="images_block"></span>
+ </label>
+ </div>
+ <button class="exceptions-list-button" contentType="images"
+ i18n-content="manage_exceptions"></button>
+ </div>
+ </section>
+ <!-- JavaScript filter -->
+ <section>
+ <h3 i18n-content="javascript_tab_label"></h3>
+ <div>
+ <div class="radio">
+ <label>
+ <input type="radio" name="javascript" value="allow">
+ <span i18n-content="javascript_allow"></span>
+ </label>
+ </div>
+ <div class="radio">
+ <label>
+ <input type="radio" name="javascript" value="block">
+ <span i18n-content="javascript_block"></span>
+ </label>
+ </div>
+ <button class="exceptions-list-button" contentType="javascript"
+ i18n-content="manage_exceptions"></button>
+ </div>
+ </section>
+ <!-- Handlers settings -->
+ <if expr="pp_ifdef('enable_register_protocol_handler')">
+ <section id="handlers-section">
+ <h3 i18n-content="handlers_tab_label"></h3>
+ <div>
+ <div class="radio">
+ <label>
+ <input type="radio" name="handlers" value="allow"
+ class="handler-radio">
+ <span i18n-content="handlers_allow"></span>
+ </label>
+ </div>
+ <div class="radio">
+ <label>
+ <input type="radio" name="handlers" value="block"
+ class="handler-radio">
+ <span i18n-content="handlers_block"></span>
+ </label>
+ </div>
+ <button id="manage-handlers-button" contentType="handlers"
+ i18n-content="manage_handlers"></button>
+ </div>
+ </section>
+ </if>
+ <!-- Plug-ins filter -->
+ <section>
+ <h3 i18n-content="plugins_tab_label"></h3>
+ <div>
+ <div class="radio">
+ <label>
+ <input type="radio" name="plugins" value="allow">
+ <span i18n-content="plugins_allow"></span>
+ </label>
+ </div>
+ <div id="click_to_play" class="radio">
+ <label>
+ <input type="radio" name="plugins" value="ask">
+ <span i18n-content="plugins_ask"></span>
+ </label>
+ </div>
+ <div class="radio">
+ <label>
+ <input type="radio" name="plugins" value="block">
+ <span i18n-content="plugins_block"></span>
+ </label>
+ </div>
+ <button class="exceptions-list-button" contentType="plugins"
+ i18n-content="manage_exceptions"></button>
+ <div id="disable-plugins-container">
+ <a href="about:plugins" i18n-content="disableIndividualPlugins"
+ target="_blank"></a>
+ </div>
+ </div>
+ </section>
+ <!-- Pop-ups filter -->
+ <section>
+ <h3 i18n-content="popups_tab_label" class="content-settings-header"></h3>
+ <div>
+ <div class="radio">
+ <label>
+ <input type="radio" name="popups" value="allow">
+ <span i18n-content="popups_allow"></span>
+ </label>
+ </div>
+ <div class="radio">
+ <label>
+ <input type="radio" name="popups" value="block">
+ <span i18n-content="popups_block"></span>
+ </label>
+ </div>
+ <button class="exceptions-list-button" contentType="popups"
+ i18n-content="manage_exceptions"></button>
+ </div>
+ </section>
+ <!-- Location filter -->
+ <section>
+ <h3 i18n-content="location_tab_label"></h3>
+ <div>
+ <div class="radio">
+ <label>
+ <input type="radio" name="location" value="allow">
+ <span i18n-content="location_allow"></span>
+ </label>
+ </div>
+ <div class="radio">
+ <label>
+ <input type="radio" name="location" value="ask">
+ <span i18n-content="location_ask"></span>
+ </label>
+ </div>
+ <div class="radio">
+ <label>
+ <input type="radio" name="location" value="block">
+ <span i18n-content="location_block"></span>
+ </label>
+ </div>
+ <button class="exceptions-list-button" contentType="location"
+ i18n-content="manage_exceptions"></button>
+ </div>
+ </section>
+ <!-- Notifications filter tab contents -->
+ <section>
+ <h3 i18n-content="notifications_tab_label"></h3>
+ <div>
+ <div class="radio">
+ <label>
+ <input type="radio" name="notifications" value="allow">
+ <span i18n-content="notifications_allow"></span>
+ </label>
+ </div>
+ <div class="radio">
+ <label>
+ <input type="radio" name="notifications" value="ask">
+ <span i18n-content="notifications_ask"></span>
+ </label>
+ </div>
+ <div class="radio">
+ <label>
+ <input type="radio" name="notifications" value="block">
+ <span i18n-content="notifications_block"></span>
+ </label>
+ </div>
+ <button class="exceptions-list-button" contentType="notifications"
+ i18n-content="manage_exceptions"></button>
+ </div>
+ </section>
+ <!-- Fullscreen filter -->
+ <section>
+ <h3 i18n-content="fullscreen_tab_label"></h3>
+ <div>
+ <button class="exceptions-list-button" contentType="fullscreen"
+ i18n-content="manage_exceptions"></button>
+ </div>
+ </section>
+ <!-- Mouse Lock filter -->
+ <section>
+ <h3 i18n-content="mouselock_tab_label"></h3>
+ <div>
+ <div class="radio">
+ <label>
+ <input type="radio" name="mouselock" value="allow">
+ <span i18n-content="mouselock_allow"></span>
+ </label>
+ </div>
+ <div class="radio">
+ <label>
+ <input type="radio" name="mouselock" value="ask">
+ <span i18n-content="mouselock_ask"></span>
+ </label>
+ </div>
+ <div class="radio">
+ <label>
+ <input type="radio" name="mouselock" value="block">
+ <span i18n-content="mouselock_block"></span>
+ </label>
+ </div>
+ <button class="exceptions-list-button" contentType="mouselock"
+ i18n-content="manage_exceptions"></button>
+ </div>
+ </section>
+ <!-- Intent registration filter tab contents -->
+ <if expr="pp_ifdef('enable_web_intents')">
+ <section id="intents-section">
+ <h3 i18n-content="intentsTabLabel" class="content-settings-header"></h3>
+ <div>
+ <div class="radio">
+ <label>
+ <input type="radio" name="intents" value="allow">
+ <span i18n-content="intentsAllow"></span>
+ </label>
+ </div>
+ <div class="radio">
+ <label>
+ <input type="radio" name="intents" value="ask">
+ <span i18n-content="intentsAsk"></span>
+ </label>
+ </div>
+ <div class="radio">
+ <label>
+ <input type="radio" name="intents" value="block">
+ <span i18n-content="intentsBlock"></span>
+ </label>
+ </div>
+ <button class="exceptions-list-button" contentType="intents"
+ i18n-content="manage_exceptions"></button>
+ <button id="manage-intents-button" contentType="intents"
+ i18n-content="manageIntents"></button>
+ </div>
+ </section>
+ </if>
+ </div>
+</div>
diff --git a/chrome/browser/resources/options2/content_settings.js b/chrome/browser/resources/options2/content_settings.js
new file mode 100644
index 0000000..94faa90
--- /dev/null
+++ b/chrome/browser/resources/options2/content_settings.js
@@ -0,0 +1,154 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+
+ var OptionsPage = options.OptionsPage;
+
+ //////////////////////////////////////////////////////////////////////////////
+ // ContentSettings class:
+
+ /**
+ * Encapsulated handling of content settings page.
+ * @constructor
+ */
+ function ContentSettings() {
+ this.activeNavTab = null;
+ OptionsPage.call(this, 'content', templateData.contentSettingsPageTabTitle,
+ 'content-settings-page');
+ }
+
+ cr.addSingletonGetter(ContentSettings);
+
+ ContentSettings.prototype = {
+ __proto__: OptionsPage.prototype,
+
+ initializePage: function() {
+ OptionsPage.prototype.initializePage.call(this);
+
+ chrome.send('getContentFilterSettings');
+
+ var exceptionsButtons =
+ this.pageDiv.querySelectorAll('.exceptions-list-button');
+ for (var i = 0; i < exceptionsButtons.length; i++) {
+ exceptionsButtons[i].onclick = function(event) {
+ var page = ContentSettingsExceptionsArea.getInstance();
+ page.showList(
+ event.target.getAttribute('contentType'));
+ OptionsPage.navigateToPage('contentExceptions');
+ // Add on the proper hash for the content type, and store that in the
+ // history so back/forward and tab restore works.
+ var hash = event.target.getAttribute('contentType');
+ window.history.replaceState({pageName: page.name}, page.title,
+ '/' + page.name + "#" + hash);
+ };
+ }
+
+ var manageHandlersButton = $('manage-handlers-button');
+ if (manageHandlersButton) {
+ manageHandlersButton.onclick = function(event) {
+ OptionsPage.navigateToPage('handlers');
+ };
+ }
+
+ var manageIntentsButton = $('manage-intents-button');
+ if (manageIntentsButton) {
+ manageIntentsButton.onclick = function(event) {
+ OptionsPage.navigateToPage('intents');
+ };
+ }
+
+ // Cookies filter page ---------------------------------------------------
+ $('show-cookies-button').onclick = function(event) {
+ chrome.send('coreOptionsUserMetricsAction', ['Options_ShowCookies']);
+ OptionsPage.navigateToPage('cookies');
+ };
+
+ if (!templateData.enable_click_to_play)
+ $('click_to_play').hidden = true;
+
+ if (!templateData.enable_web_intents && $('intent-section'))
+ $('intent-section').hidden = true;
+ },
+ };
+
+ ContentSettings.updateHandlersEnabledRadios = function(enabled) {
+ var selector = '#content-settings-page input[type=radio][value=' +
+ (enabled ? 'allow' : 'block') + '].handler-radio';
+ document.querySelector(selector).checked = true;
+ };
+
+ /**
+ * Sets the values for all the content settings radios.
+ * @param {Object} dict A mapping from radio groups to the checked value for
+ * that group.
+ */
+ ContentSettings.setContentFilterSettingsValue = function(dict) {
+ for (var group in dict) {
+ document.querySelector('input[type=radio][name=' + group + '][value=' +
+ dict[group]['value'] + ']').checked = true;
+ var radios = document.querySelectorAll('input[type=radio][name=' +
+ group + ']');
+ var managedBy = dict[group]['managedBy'];
+ for (var i = 0, len = radios.length; i < len; i++) {
+ radios[i].disabled = (managedBy != 'default');
+ radios[i].controlledBy = managedBy;
+ }
+ }
+ OptionsPage.updateManagedBannerVisibility();
+ };
+
+ /**
+ * Initializes an exceptions list.
+ * @param {string} type The content type that we are setting exceptions for.
+ * @param {Array} list An array of pairs, where the first element of each pair
+ * is the filter string, and the second is the setting (allow/block).
+ */
+ ContentSettings.setExceptions = function(type, list) {
+ var exceptionsList =
+ document.querySelector('div[contentType=' + type + ']' +
+ ' list[mode=normal]');
+ exceptionsList.setExceptions(list);
+ };
+
+ ContentSettings.setHandlers = function(list) {
+ $('handlers-list').setHandlers(list);
+ };
+
+ ContentSettings.setIgnoredHandlers = function(list) {
+ $('ignored-handlers-list').setHandlers(list);
+ };
+
+ ContentSettings.setOTRExceptions = function(type, list) {
+ var exceptionsList =
+ document.querySelector('div[contentType=' + type + ']' +
+ ' list[mode=otr]');
+
+ exceptionsList.parentNode.hidden = false;
+ exceptionsList.setExceptions(list);
+ };
+
+ /**
+ * The browser's response to a request to check the validity of a given URL
+ * pattern.
+ * @param {string} type The content type.
+ * @param {string} mode The browser mode.
+ * @param {string} pattern The pattern.
+ * @param {bool} valid Whether said pattern is valid in the context of
+ * a content exception setting.
+ */
+ ContentSettings.patternValidityCheckComplete =
+ function(type, mode, pattern, valid) {
+ var exceptionsList =
+ document.querySelector('div[contentType=' + type + '] ' +
+ 'list[mode=' + mode + ']');
+ exceptionsList.patternValidityCheckComplete(pattern, valid);
+ };
+
+ // Export
+ return {
+ ContentSettings: ContentSettings
+ };
+
+});
diff --git a/chrome/browser/resources/options2/content_settings_exceptions_area.html b/chrome/browser/resources/options2/content_settings_exceptions_area.html
new file mode 100644
index 0000000..4e95c04
--- /dev/null
+++ b/chrome/browser/resources/options2/content_settings_exceptions_area.html
@@ -0,0 +1,73 @@
+<div id="content-settings-exceptions-area" class="page" hidden>
+ <h1></h1>
+ <div id="exception-column-headers">
+ <div id="exception-pattern-column" i18n-content="exceptionPatternHeader">
+ </div>
+ <div id="exception-behavior-column" i18n-content="exceptionBehaviorHeader">
+ </div>
+ </div>
+ <div contentType="cookies">
+ <list mode="normal"></list>
+ <div>
+ <span class="otr-explanation"
+ i18n-content="otr_exceptions_explanation"></span>
+ <list mode="otr"></list>
+ </div>
+ <div class="flash-plugin-area">
+ <a i18n-values="href:flash_storage_url"
+ i18n-content="flash_storage_settings" target="_blank"></a>
+ </div>
+ </div>
+ <div contentType="images">
+ <list mode="normal"></list>
+ <div>
+ <span class="otr-explanation"
+ i18n-content="otr_exceptions_explanation"></span>
+ <list mode="otr"></list>
+ </div>
+ </div>
+ <div contentType="javascript">
+ <list mode="normal"></list>
+ <div>
+ <span class="otr-explanation"
+ i18n-content="otr_exceptions_explanation"></span>
+ <list mode="otr"></list>
+ </div>
+ </div>
+ <div contentType="plugins">
+ <list mode="normal"></list>
+ <div>
+ <span class="otr-explanation"
+ i18n-content="otr_exceptions_explanation"></span>
+ <list mode="otr"></list>
+ </div>
+ </div>
+ <div contentType="popups">
+ <list mode="normal"></list>
+ <div>
+ <span class="otr-explanation"
+ i18n-content="otr_exceptions_explanation"></span>
+ <list mode="otr"></list>
+ </div>
+ </div>
+ <div contentType="location">
+ <list mode="normal"></list>
+ </div>
+ <div contentType="notifications">
+ <list mode="normal"></list>
+ </div>
+ <div contentType="intents">
+ <list mode="normal"></list>
+ </div>
+ <div contentType="fullscreen">
+ <list mode="normal"></list>
+ <div>
+ <span class="otr-explanation"
+ i18n-content="otr_exceptions_explanation"></span>
+ <list mode="otr"></list>
+ </div>
+ </div>
+ <div contentType="mouselock">
+ <list mode="normal"></list>
+ </div>
+</div>
diff --git a/chrome/browser/resources/options2/content_settings_exceptions_area.js b/chrome/browser/resources/options2/content_settings_exceptions_area.js
new file mode 100644
index 0000000..7adde68
--- /dev/null
+++ b/chrome/browser/resources/options2/content_settings_exceptions_area.js
@@ -0,0 +1,552 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options.contentSettings', function() {
+ const InlineEditableItemList = options.InlineEditableItemList;
+ const InlineEditableItem = options.InlineEditableItem;
+ const ArrayDataModel = cr.ui.ArrayDataModel;
+
+ /**
+ * Creates a new exceptions list item.
+ * @param {string} contentType The type of the list.
+ * @param {string} mode The browser mode, 'otr' or 'normal'.
+ * @param {boolean} enableAskOption Whether to show an 'ask every time'
+ * option in the select.
+ * @param {Object} exception A dictionary that contains the data of the
+ * exception.
+ * @constructor
+ * @extends {options.InlineEditableItem}
+ */
+ function ExceptionsListItem(contentType, mode, enableAskOption, exception) {
+ var el = cr.doc.createElement('div');
+ el.mode = mode;
+ el.contentType = contentType;
+ el.enableAskOption = enableAskOption;
+ el.dataItem = exception;
+ el.__proto__ = ExceptionsListItem.prototype;
+ el.decorate();
+
+ return el;
+ }
+
+ ExceptionsListItem.prototype = {
+ __proto__: InlineEditableItem.prototype,
+
+ /**
+ * Called when an element is decorated as a list item.
+ */
+ decorate: function() {
+ InlineEditableItem.prototype.decorate.call(this);
+
+ this.isPlaceholder = !this.pattern;
+ var patternCell = this.createEditableTextCell(this.pattern);
+ patternCell.className = 'exception-pattern';
+ patternCell.classList.add('weakrtl');
+ this.contentElement.appendChild(patternCell);
+ if (this.pattern)
+ this.patternLabel = patternCell.querySelector('.static-text');
+ var input = patternCell.querySelector('input');
+
+ // TODO(stuartmorgan): Create an createEditableSelectCell abstracting
+ // this code.
+ // Setting label for display mode. |pattern| will be null for the 'add new
+ // exception' row.
+ if (this.pattern) {
+ var settingLabel = cr.doc.createElement('span');
+ settingLabel.textContent = this.settingForDisplay();
+ settingLabel.className = 'exception-setting';
+ settingLabel.setAttribute('displaymode', 'static');
+ this.contentElement.appendChild(settingLabel);
+ this.settingLabel = settingLabel;
+ }
+
+ // Setting select element for edit mode.
+ var select = cr.doc.createElement('select');
+ var optionAllow = cr.doc.createElement('option');
+ optionAllow.textContent = templateData.allowException;
+ optionAllow.value = 'allow';
+ select.appendChild(optionAllow);
+
+ if (this.enableAskOption) {
+ var optionAsk = cr.doc.createElement('option');
+ optionAsk.textContent = templateData.askException;
+ optionAsk.value = 'ask';
+ select.appendChild(optionAsk);
+ }
+
+ if (this.contentType == 'cookies') {
+ var optionSession = cr.doc.createElement('option');
+ optionSession.textContent = templateData.sessionException;
+ optionSession.value = 'session';
+ select.appendChild(optionSession);
+ }
+
+ if (this.contentType != 'fullscreen') {
+ var optionBlock = cr.doc.createElement('option');
+ optionBlock.textContent = templateData.blockException;
+ optionBlock.value = 'block';
+ select.appendChild(optionBlock);
+ }
+
+ this.contentElement.appendChild(select);
+ select.className = 'exception-setting';
+ if (this.pattern)
+ select.setAttribute('displaymode', 'edit');
+
+ // Used to track whether the URL pattern in the input is valid.
+ // This will be true if the browser process has informed us that the
+ // current text in the input is valid. Changing the text resets this to
+ // false, and getting a response from the browser sets it back to true.
+ // It starts off as false for empty string (new exceptions) or true for
+ // already-existing exceptions (which we assume are valid).
+ this.inputValidityKnown = this.pattern;
+ // This one tracks the actual validity of the pattern in the input. This
+ // starts off as true so as not to annoy the user when he adds a new and
+ // empty input.
+ this.inputIsValid = true;
+
+ this.input = input;
+ this.select = select;
+
+ this.updateEditables();
+
+ // Editing notifications and geolocation is disabled for now.
+ if (this.contentType == 'notifications' ||
+ this.contentType == 'location') {
+ this.editable = false;
+ }
+
+ // If the source of the content setting exception is not the user
+ // preference, then the content settings exception is managed and the user
+ // can't edit it.
+ if (this.dataItem.source &&
+ this.dataItem.source != 'preference') {
+ this.setAttribute('managedby', this.dataItem.source);
+ this.deletable = false;
+ this.editable = false;
+ }
+
+ var listItem = this;
+ // Handle events on the editable nodes.
+ input.oninput = function(event) {
+ listItem.inputValidityKnown = false;
+ chrome.send('checkExceptionPatternValidity',
+ [listItem.contentType, listItem.mode, input.value]);
+ };
+
+ // Listen for edit events.
+ this.addEventListener('canceledit', this.onEditCancelled_);
+ this.addEventListener('commitedit', this.onEditCommitted_);
+ },
+
+ /**
+ * The pattern (e.g., a URL) for the exception.
+ * @type {string}
+ */
+ get pattern() {
+ return this.dataItem['displayPattern'];
+ },
+ set pattern(pattern) {
+ this.dataItem['displayPattern'] = pattern;
+ },
+
+ /**
+ * The setting (allow/block) for the exception.
+ * @type {string}
+ */
+ get setting() {
+ return this.dataItem['setting'];
+ },
+ set setting(setting) {
+ this.dataItem['setting'] = setting;
+ },
+
+ /**
+ * Gets a human-readable setting string.
+ * @type {string}
+ */
+ settingForDisplay: function() {
+ var setting = this.setting;
+ if (setting == 'allow')
+ return templateData.allowException;
+ else if (setting == 'block')
+ return templateData.blockException;
+ else if (setting == 'ask')
+ return templateData.askException;
+ else if (setting == 'session')
+ return templateData.sessionException;
+ },
+
+ /**
+ * Update this list item to reflect whether the input is a valid pattern.
+ * @param {boolean} valid Whether said pattern is valid in the context of
+ * a content exception setting.
+ */
+ setPatternValid: function(valid) {
+ if (valid || !this.input.value)
+ this.input.setCustomValidity('');
+ else
+ this.input.setCustomValidity(' ');
+ this.inputIsValid = valid;
+ this.inputValidityKnown = true;
+ },
+
+ /**
+ * Set the <input> to its original contents. Used when the user quits
+ * editing.
+ */
+ resetInput: function() {
+ this.input.value = this.pattern;
+ },
+
+ /**
+ * Copy the data model values to the editable nodes.
+ */
+ updateEditables: function() {
+ this.resetInput();
+
+ var settingOption =
+ this.select.querySelector('[value=\'' + this.setting + '\']');
+ if (settingOption)
+ settingOption.selected = true;
+ },
+
+ /** @inheritDoc */
+ get currentInputIsValid() {
+ return this.inputValidityKnown && this.inputIsValid;
+ },
+
+ /** @inheritDoc */
+ get hasBeenEdited() {
+ var livePattern = this.input.value;
+ var liveSetting = this.select.value;
+ return livePattern != this.pattern || liveSetting != this.setting;
+ },
+
+ /**
+ * Called when committing an edit.
+ * @param {Event} e The end event.
+ * @private
+ */
+ onEditCommitted_: function(e) {
+ var newPattern = this.input.value;
+ var newSetting = this.select.value;
+
+ this.finishEdit(newPattern, newSetting);
+ },
+
+ /**
+ * Called when cancelling an edit; resets the control states.
+ * @param {Event} e The cancel event.
+ * @private
+ */
+ onEditCancelled_: function() {
+ this.updateEditables();
+ this.setPatternValid(true);
+ },
+
+ /**
+ * Editing is complete; update the model.
+ * @param {string} newPattern The pattern that the user entered.
+ * @param {string} newSetting The setting the user chose.
+ */
+ finishEdit: function(newPattern, newSetting) {
+ this.patternLabel.textContent = newPattern;
+ this.settingLabel.textContent = this.settingForDisplay();
+ var oldPattern = this.pattern;
+ this.pattern = newPattern;
+ this.setting = newSetting;
+
+ // TODO(estade): this will need to be updated if geolocation/notifications
+ // become editable.
+ if (oldPattern != newPattern) {
+ chrome.send('removeException',
+ [this.contentType, this.mode, oldPattern]);
+ }
+
+ chrome.send('setException',
+ [this.contentType, this.mode, newPattern, newSetting]);
+ }
+ };
+
+ /**
+ * Creates a new list item for the Add New Item row, which doesn't represent
+ * an actual entry in the exceptions list but allows the user to add new
+ * exceptions.
+ * @param {string} contentType The type of the list.
+ * @param {string} mode The browser mode, 'otr' or 'normal'.
+ * @param {boolean} enableAskOption Whether to show an 'ask every time'
+ * option in the select.
+ * @constructor
+ * @extends {cr.ui.ExceptionsListItem}
+ */
+ function ExceptionsAddRowListItem(contentType, mode, enableAskOption) {
+ var el = cr.doc.createElement('div');
+ el.mode = mode;
+ el.contentType = contentType;
+ el.enableAskOption = enableAskOption;
+ el.dataItem = [];
+ el.__proto__ = ExceptionsAddRowListItem.prototype;
+ el.decorate();
+
+ return el;
+ }
+
+ ExceptionsAddRowListItem.prototype = {
+ __proto__: ExceptionsListItem.prototype,
+
+ decorate: function() {
+ ExceptionsListItem.prototype.decorate.call(this);
+
+ this.input.placeholder = templateData.addNewExceptionInstructions;
+
+ // Do we always want a default of allow?
+ this.setting = 'allow';
+ },
+
+ /**
+ * Clear the <input> and let the placeholder text show again.
+ */
+ resetInput: function() {
+ this.input.value = '';
+ },
+
+ /** @inheritDoc */
+ get hasBeenEdited() {
+ return this.input.value != '';
+ },
+
+ /**
+ * Editing is complete; update the model. As long as the pattern isn't
+ * empty, we'll just add it.
+ * @param {string} newPattern The pattern that the user entered.
+ * @param {string} newSetting The setting the user chose.
+ */
+ finishEdit: function(newPattern, newSetting) {
+ this.resetInput();
+ chrome.send('setException',
+ [this.contentType, this.mode, newPattern, newSetting]);
+ },
+ };
+
+ /**
+ * Creates a new exceptions list.
+ * @constructor
+ * @extends {cr.ui.List}
+ */
+ var ExceptionsList = cr.ui.define('list');
+
+ ExceptionsList.prototype = {
+ __proto__: InlineEditableItemList.prototype,
+
+ /**
+ * Called when an element is decorated as a list.
+ */
+ decorate: function() {
+ InlineEditableItemList.prototype.decorate.call(this);
+
+ this.classList.add('settings-list');
+
+ for (var parentNode = this.parentNode; parentNode;
+ parentNode = parentNode.parentNode) {
+ if (parentNode.hasAttribute('contentType')) {
+ this.contentType = parentNode.getAttribute('contentType');
+ break;
+ }
+ }
+
+ this.mode = this.getAttribute('mode');
+
+ var exceptionList = this;
+
+ // Whether the exceptions in this list allow an 'Ask every time' option.
+ this.enableAskOption = (this.contentType == 'plugins' &&
+ templateData.enable_click_to_play);
+
+ this.autoExpands = true;
+ this.reset();
+ },
+
+ /**
+ * Creates an item to go in the list.
+ * @param {Object} entry The element from the data model for this row.
+ */
+ createItem: function(entry) {
+ if (entry) {
+ return new ExceptionsListItem(this.contentType,
+ this.mode,
+ this.enableAskOption,
+ entry);
+ } else {
+ var addRowItem = new ExceptionsAddRowListItem(this.contentType,
+ this.mode,
+ this.enableAskOption);
+ addRowItem.deletable = false;
+ return addRowItem;
+ }
+ },
+
+ /**
+ * Sets the exceptions in the js model.
+ * @param {Object} entries A list of dictionaries of values, each dictionary
+ * represents an exception.
+ */
+ setExceptions: function(entries) {
+ var deleteCount = this.dataModel.length;
+
+ if (this.isEditable()) {
+ // We don't want to remove the Add New Exception row.
+ deleteCount = deleteCount - 1;
+ }
+
+ var args = [0, deleteCount];
+ args.push.apply(args, entries);
+ this.dataModel.splice.apply(this.dataModel, args);
+ },
+
+ /**
+ * The browser has finished checking a pattern for validity. Update the
+ * list item to reflect this.
+ * @param {string} pattern The pattern.
+ * @param {bool} valid Whether said pattern is valid in the context of
+ * a content exception setting.
+ */
+ patternValidityCheckComplete: function(pattern, valid) {
+ var listItems = this.items;
+ for (var i = 0; i < listItems.length; i++) {
+ var listItem = listItems[i];
+ // Don't do anything for messages for the item if it is not the intended
+ // recipient, or if the response is stale (i.e. the input value has
+ // changed since we sent the request to analyze it).
+ if (pattern == listItem.input.value)
+ listItem.setPatternValid(valid);
+ }
+ },
+
+ /**
+ * Returns whether the rows are editable in this list.
+ */
+ isEditable: function() {
+ // Editing notifications and geolocation is disabled for now.
+ return !(this.contentType == 'notifications' ||
+ this.contentType == 'location' ||
+ this.contentType == 'fullscreen');
+ },
+
+ /**
+ * Removes all exceptions from the js model.
+ */
+ reset: function() {
+ if (this.isEditable()) {
+ // The null creates the Add New Exception row.
+ this.dataModel = new ArrayDataModel([null]);
+ } else {
+ this.dataModel = new ArrayDataModel([]);
+ }
+ },
+
+ /** @inheritDoc */
+ deleteItemAtIndex: function(index) {
+ var listItem = this.getListItemByIndex(index);
+ if (listItem.undeletable)
+ return;
+
+ var dataItem = listItem.dataItem;
+ var args = [listItem.contentType];
+ if (listItem.contentType == 'location')
+ args.push(dataItem['origin'], dataItem['embeddingOrigin']);
+ else if (listItem.contentType == 'notifications')
+ args.push(dataItem['origin'], dataItem['setting']);
+ else
+ args.push(listItem.mode, listItem.pattern);
+
+ chrome.send('removeException', args);
+ },
+ };
+
+ var OptionsPage = options.OptionsPage;
+
+ /**
+ * Encapsulated handling of content settings list subpage.
+ * @constructor
+ */
+ function ContentSettingsExceptionsArea() {
+ OptionsPage.call(this, 'contentExceptions',
+ templateData.contentSettingsPageTabTitle,
+ 'content-settings-exceptions-area');
+ }
+
+ cr.addSingletonGetter(ContentSettingsExceptionsArea);
+
+ ContentSettingsExceptionsArea.prototype = {
+ __proto__: OptionsPage.prototype,
+
+ initializePage: function() {
+ OptionsPage.prototype.initializePage.call(this);
+
+ var exceptionsLists = this.pageDiv.querySelectorAll('list');
+ for (var i = 0; i < exceptionsLists.length; i++) {
+ options.contentSettings.ExceptionsList.decorate(exceptionsLists[i]);
+ }
+
+ ContentSettingsExceptionsArea.hideOTRLists();
+
+ // If the user types in the URL without a hash, show just cookies.
+ this.showList('cookies');
+ },
+
+ /**
+ * Shows one list and hides all others.
+ * @param {string} type The content type.
+ */
+ showList: function(type) {
+ var header = this.pageDiv.querySelector('h1');
+ header.textContent = templateData[type + '_header'];
+
+ var divs = this.pageDiv.querySelectorAll('div[contentType]');
+ for (var i = 0; i < divs.length; i++) {
+ if (divs[i].getAttribute('contentType') == type)
+ divs[i].hidden = false;
+ else
+ divs[i].hidden = true;
+ }
+ },
+
+ /**
+ * Called after the page has been shown. Show the content type for the
+ * location's hash.
+ */
+ didShowPage: function() {
+ var hash = location.hash;
+ if (hash)
+ this.showList(hash.slice(1));
+ },
+ };
+
+ /**
+ * Called when the last incognito window is closed.
+ */
+ ContentSettingsExceptionsArea.OTRProfileDestroyed = function() {
+ this.hideOTRLists();
+ };
+
+ /**
+ * Clears and hides the incognito exceptions lists.
+ */
+ ContentSettingsExceptionsArea.hideOTRLists = function() {
+ var otrLists = document.querySelectorAll('list[mode=otr]');
+
+ for (var i = 0; i < otrLists.length; i++) {
+ otrLists[i].reset();
+ otrLists[i].parentNode.hidden = true;
+ }
+ };
+
+ return {
+ ExceptionsListItem: ExceptionsListItem,
+ ExceptionsAddRowListItem: ExceptionsAddRowListItem,
+ ExceptionsList: ExceptionsList,
+ ContentSettingsExceptionsArea: ContentSettingsExceptionsArea,
+ };
+});
diff --git a/chrome/browser/resources/options2/content_settings_ui.js b/chrome/browser/resources/options2/content_settings_ui.js
new file mode 100644
index 0000000..dc7b0c4
--- /dev/null
+++ b/chrome/browser/resources/options2/content_settings_ui.js
@@ -0,0 +1,67 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+
+ //////////////////////////////////////////////////////////////////////////////
+ // ContentSettingsRadio class:
+
+ // Define a constructor that uses an input element as its underlying element.
+ var ContentSettingsRadio = cr.ui.define('input');
+
+ ContentSettingsRadio.prototype = {
+ __proto__: HTMLInputElement.prototype,
+
+ /**
+ * Initialization function for the cr.ui framework.
+ */
+ decorate: function() {
+ this.type = 'radio';
+ var self = this;
+
+ this.addEventListener('change',
+ function(e) {
+ chrome.send('setContentFilter', [this.name, this.value]);
+ });
+ },
+ };
+
+ /**
+ * Whether the content setting is controlled by something else than the user's
+ * settings (either 'policy' or 'extension').
+ * @type {string}
+ */
+ cr.defineProperty(ContentSettingsRadio, 'controlledBy', cr.PropertyKind.ATTR);
+
+ //////////////////////////////////////////////////////////////////////////////
+ // HandlersEnabledRadio class:
+
+ // Define a constructor that uses an input element as its underlying element.
+ var HandlersEnabledRadio = cr.ui.define('input');
+
+ HandlersEnabledRadio.prototype = {
+ __proto__: HTMLInputElement.prototype,
+
+ /**
+ * Initialization function for the cr.ui framework.
+ */
+ decorate: function() {
+ this.type = 'radio';
+ var self = this;
+
+ this.addEventListener('change',
+ function(e) {
+ chrome.send('setHandlersEnabled', [this.value == 'allow']);
+ });
+ },
+ };
+
+ // Export
+ return {
+ ContentSettingsRadio: ContentSettingsRadio,
+ HandlersEnabledRadio: HandlersEnabledRadio
+ };
+
+});
+
diff --git a/chrome/browser/resources/options2/controlled_setting.js b/chrome/browser/resources/options2/controlled_setting.js
new file mode 100644
index 0000000..0ca4e31
--- /dev/null
+++ b/chrome/browser/resources/options2/controlled_setting.js
@@ -0,0 +1,138 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+ var Preferences = options.Preferences;
+
+ /**
+ * A controlled setting indicator that can be placed on a setting as an
+ * indicator that the value is controlled by some external entity such as
+ * policy or an extension.
+ * @constructor
+ * @extends {HTMLSpanElement}
+ */
+ var ControlledSettingIndicator = cr.ui.define('span');
+
+ ControlledSettingIndicator.prototype = {
+ __proto__: HTMLSpanElement.prototype,
+
+ /**
+ * Decorates the base element to show the proper icon.
+ */
+ decorate: function() {
+ var self = this;
+ var doc = self.ownerDocument;
+
+ // Create the details and summary elements.
+ var detailsContainer = doc.createElement('details');
+ detailsContainer.appendChild(doc.createElement('summary'));
+
+ // This should really create a div element, but that breaks :hover. See
+ // https://bugs.webkit.org/show_bug.cgi?id=72957
+ var bubbleContainer = doc.createElement('p');
+ bubbleContainer.className = 'controlled-setting-bubble';
+ detailsContainer.appendChild(bubbleContainer);
+
+ self.appendChild(detailsContainer);
+
+ // If there is a pref, track its controlledBy property in order to be able
+ // to bring up the correct bubble.
+ if (this.hasAttribute('pref')) {
+ Preferences.getInstance().addEventListener(
+ this.getAttribute('pref'),
+ function(event) {
+ if (event.value) {
+ var controlledBy = event.value['controlledBy'];
+ self.controlledBy = controlledBy ? controlledBy : null;
+ }
+ });
+ }
+
+ self.addEventListener('click', self.show_);
+ },
+
+
+ /**
+ * Closes the bubble.
+ */
+ close: function() {
+ this.querySelector('details').removeAttribute('open');
+ this.ownerDocument.removeEventListener('click', this.closeHandler_, true);
+ },
+
+ /**
+ * Constructs the bubble DOM tree and shows it.
+ * @private
+ */
+ show_: function() {
+ var self = this;
+ var doc = self.ownerDocument;
+
+ // Clear out the old bubble contents.
+ var bubbleContainer = this.querySelector('.controlled-setting-bubble');
+ if (bubbleContainer) {
+ while (bubbleContainer.hasChildNodes())
+ bubbleContainer.removeChild(bubbleContainer.lastChild);
+ }
+
+ // Work out the bubble text.
+ defaultStrings = {
+ 'policy' : localStrings.getString('controlledSettingPolicy'),
+ 'extension' : localStrings.getString('controlledSettingExtension'),
+ 'recommended' : localStrings.getString('controlledSettingRecommended'),
+ };
+
+ // No controller, no bubble.
+ if (!self.controlledBy || !self.controlledBy in defaultStrings)
+ return;
+
+ var text = defaultStrings[self.controlledBy];
+
+ // Apply text overrides.
+ if (self.hasAttribute('text' + self.controlledBy))
+ text = self.getAttribute('text' + self.controlledBy);
+
+ // Create the DOM tree.
+ var bubbleText = doc.createElement('p');
+ bubbleText.className = 'controlled-setting-bubble-text';
+ bubbleText.textContent = text;
+
+ var pref = self.getAttribute('pref');
+ if (self.controlledBy == 'recommended' && pref) {
+ var container = doc.createElement('div');
+ var action = doc.createElement('button');
+ action.classList.add('link-button');
+ action.classList.add('controlled-setting-bubble-action');
+ action.textContent =
+ localStrings.getString('controlledSettingApplyRecommendation');
+ action.addEventListener(
+ 'click',
+ function(e) {
+ Preferences.clearPref(pref);
+ });
+ container.appendChild(action);
+ bubbleText.appendChild(container);
+ }
+
+ bubbleContainer.appendChild(bubbleText);
+
+ // One-time bubble-closing event handler.
+ self.closeHandler_ = this.close.bind(this);
+ doc.addEventListener('click', self.closeHandler_, true);
+ }
+ };
+
+ /**
+ * The controlling entity of the setting. Can take the values "policy",
+ * "extension", "recommended" or be unset.
+ */
+ cr.defineProperty(ControlledSettingIndicator, 'controlledBy',
+ cr.PropertyKind.ATTR,
+ ControlledSettingIndicator.prototype.close);
+
+ // Export.
+ return {
+ ControlledSettingIndicator : ControlledSettingIndicator
+ };
+});
diff --git a/chrome/browser/resources/options2/cookies_list.js b/chrome/browser/resources/options2/cookies_list.js
new file mode 100644
index 0000000..4e70d1d
--- /dev/null
+++ b/chrome/browser/resources/options2/cookies_list.js
@@ -0,0 +1,853 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+ const DeletableItemList = options.DeletableItemList;
+ const DeletableItem = options.DeletableItem;
+ const ArrayDataModel = cr.ui.ArrayDataModel;
+ const ListSingleSelectionModel = cr.ui.ListSingleSelectionModel;
+
+ // This structure maps the various cookie type names from C++ (hence the
+ // underscores) to arrays of the different types of data each has, along with
+ // the i18n name for the description of that data type.
+ const cookieInfo = {
+ 'cookie': [ ['name', 'label_cookie_name'],
+ ['content', 'label_cookie_content'],
+ ['domain', 'label_cookie_domain'],
+ ['path', 'label_cookie_path'],
+ ['sendfor', 'label_cookie_send_for'],
+ ['accessibleToScript', 'label_cookie_accessible_to_script'],
+ ['created', 'label_cookie_created'],
+ ['expires', 'label_cookie_expires'] ],
+ 'app_cache': [ ['manifest', 'label_app_cache_manifest'],
+ ['size', 'label_local_storage_size'],
+ ['created', 'label_cookie_created'],
+ ['accessed', 'label_cookie_last_accessed'] ],
+ 'database': [ ['name', 'label_cookie_name'],
+ ['desc', 'label_webdb_desc'],
+ ['size', 'label_local_storage_size'],
+ ['modified', 'label_local_storage_last_modified'] ],
+ 'local_storage': [ ['origin', 'label_local_storage_origin'],
+ ['size', 'label_local_storage_size'],
+ ['modified', 'label_local_storage_last_modified'] ],
+ 'indexed_db': [ ['origin', 'label_indexed_db_origin'],
+ ['size', 'label_indexed_db_size'],
+ ['modified', 'label_indexed_db_last_modified'] ],
+ 'file_system': [ ['origin', 'label_file_system_origin'],
+ ['persistent', 'label_file_system_persistent_usage' ],
+ ['temporary', 'label_file_system_temporary_usage' ] ],
+ };
+
+ const localStrings = new LocalStrings();
+
+ /**
+ * Returns the item's height, like offsetHeight but such that it works better
+ * when the page is zoomed. See the similar calculation in @{code cr.ui.List}.
+ * This version also accounts for the animation done in this file.
+ * @param {Element} item The item to get the height of.
+ * @return {number} The height of the item, calculated with zooming in mind.
+ */
+ function getItemHeight(item) {
+ var height = item.style.height;
+ // Use the fixed animation target height if set, in case the element is
+ // currently being animated and we'd get an intermediate height below.
+ if (height && height.substr(-2) == 'px')
+ return parseInt(height.substr(0, height.length - 2));
+ return item.getBoundingClientRect().height;
+ }
+
+ /**
+ * Create tree nodes for the objects in the data array, and insert them all
+ * into the given list using its @{code splice} method at the given index.
+ * @param {Array.<Object>} data The data objects for the nodes to add.
+ * @param {number} start The index at which to start inserting the nodes.
+ * @return {Array.<CookieTreeNode>} An array of CookieTreeNodes added.
+ */
+ function spliceTreeNodes(data, start, list) {
+ var nodes = data.map(function(x) { return new CookieTreeNode(x); });
+ // Insert [start, 0] at the beginning of the array of nodes, making it
+ // into the arguments we want to pass to @{code list.splice} below.
+ nodes.splice(0, 0, start, 0);
+ list.splice.apply(list, nodes);
+ // Remove the [start, 0] prefix and return the array of nodes.
+ nodes.splice(0, 2);
+ return nodes;
+ }
+
+ var parentLookup = {};
+ var lookupRequests = {};
+
+ /**
+ * Creates a new list item for sites data. Note that these are created and
+ * destroyed lazily as they scroll into and out of view, so they must be
+ * stateless. We cache the expanded item in @{code CookiesList} though, so it
+ * can keep state. (Mostly just which item is selected.)
+ * @param {Object} origin Data used to create a cookie list item.
+ * @param {CookiesList} list The list that will contain this item.
+ * @constructor
+ * @extends {DeletableItem}
+ */
+ function CookieListItem(origin, list) {
+ var listItem = new DeletableItem(null);
+ listItem.__proto__ = CookieListItem.prototype;
+
+ listItem.origin = origin;
+ listItem.list = list;
+ listItem.decorate();
+
+ // This hooks up updateOrigin() to the list item, makes the top-level
+ // tree nodes (i.e., origins) register their IDs in parentLookup, and
+ // causes them to request their children if they have none. Note that we
+ // have special logic in the setter for the parent property to make sure
+ // that we can still garbage collect list items when they scroll out of
+ // view, even though it appears that we keep a direct reference.
+ if (origin) {
+ origin.parent = listItem;
+ origin.updateOrigin();
+ }
+
+ return listItem;
+ }
+
+ CookieListItem.prototype = {
+ __proto__: DeletableItem.prototype,
+
+ /** @inheritDoc */
+ decorate: function() {
+ this.siteChild = this.ownerDocument.createElement('div');
+ this.siteChild.className = 'cookie-site';
+ this.dataChild = this.ownerDocument.createElement('div');
+ this.dataChild.className = 'cookie-data';
+ this.sizeChild = this.ownerDocument.createElement('div');
+ this.sizeChild.className = 'cookie-size';
+ this.itemsChild = this.ownerDocument.createElement('div');
+ this.itemsChild.className = 'cookie-items';
+ this.infoChild = this.ownerDocument.createElement('div');
+ this.infoChild.className = 'cookie-details';
+ this.infoChild.hidden = true;
+ var remove = this.ownerDocument.createElement('button');
+ remove.textContent = localStrings.getString('remove_cookie');
+ remove.onclick = this.removeCookie_.bind(this);
+ this.infoChild.appendChild(remove);
+ var content = this.contentElement;
+ content.appendChild(this.siteChild);
+ content.appendChild(this.dataChild);
+ content.appendChild(this.sizeChild);
+ content.appendChild(this.itemsChild);
+ this.itemsChild.appendChild(this.infoChild);
+ if (this.origin && this.origin.data) {
+ this.siteChild.textContent = this.origin.data.title;
+ this.siteChild.setAttribute('title', this.origin.data.title);
+ }
+ this.itemList_ = [];
+ },
+
+ /** @type {boolean} */
+ get expanded() {
+ return this.expanded_;
+ },
+ set expanded(expanded) {
+ if (this.expanded_ == expanded)
+ return;
+ this.expanded_ = expanded;
+ if (expanded) {
+ var oldExpanded = this.list.expandedItem;
+ this.list.expandedItem = this;
+ this.updateItems_();
+ if (oldExpanded)
+ oldExpanded.expanded = false;
+ this.classList.add('show-items');
+ } else {
+ if (this.list.expandedItem == this) {
+ this.list.expandedItem = null;
+ }
+ this.style.height = '';
+ this.itemsChild.style.height = '';
+ this.classList.remove('show-items');
+ }
+ },
+
+ /**
+ * The callback for the "remove" button shown when an item is selected.
+ * Requests that the currently selected cookie be removed.
+ * @private
+ */
+ removeCookie_: function() {
+ if (this.selectedIndex_ >= 0) {
+ var item = this.itemList_[this.selectedIndex_];
+ if (item && item.node)
+ chrome.send('removeCookie', [item.node.pathId]);
+ }
+ },
+
+ /**
+ * Disable animation within this cookie list item, in preparation for making
+ * changes that will need to be animated. Makes it possible to measure the
+ * contents without displaying them, to set animation targets.
+ * @private
+ */
+ disableAnimation_: function() {
+ this.itemsHeight_ = getItemHeight(this.itemsChild);
+ this.classList.add('measure-items');
+ },
+
+ /**
+ * Enable animation after changing the contents of this cookie list item.
+ * See @{code disableAnimation_}.
+ * @private
+ */
+ enableAnimation_: function() {
+ if (!this.classList.contains('measure-items'))
+ this.disableAnimation_();
+ this.itemsChild.style.height = '';
+ // This will force relayout in order to calculate the new heights.
+ var itemsHeight = getItemHeight(this.itemsChild);
+ var fixedHeight = getItemHeight(this) + itemsHeight - this.itemsHeight_;
+ this.itemsChild.style.height = this.itemsHeight_ + 'px';
+ // Force relayout before enabling animation, so that if we have
+ // changed things since the last layout, they will not be animated
+ // during subsequent layouts.
+ this.itemsChild.offsetHeight;
+ this.classList.remove('measure-items');
+ this.itemsChild.style.height = itemsHeight + 'px';
+ this.style.height = fixedHeight + 'px';
+ },
+
+ /**
+ * Updates the origin summary to reflect changes in its items.
+ * Both CookieListItem and CookieTreeNode implement this API.
+ * This implementation scans the descendants to update the text.
+ */
+ updateOrigin: function() {
+ var info = {
+ cookies: 0,
+ database: false,
+ localStorage: false,
+ appCache: false,
+ indexedDb: false,
+ fileSystem: false,
+ };
+ if (this.origin)
+ this.origin.collectSummaryInfo(info);
+ var list = [];
+ if (info.cookies > 1)
+ list.push(localStrings.getStringF('cookie_plural', info.cookies));
+ else if (info.cookies > 0)
+ list.push(localStrings.getString('cookie_singular'));
+ if (info.database || info.indexedDb)
+ list.push(localStrings.getString('cookie_database_storage'));
+ if (info.localStorage)
+ list.push(localStrings.getString('cookie_local_storage'));
+ if (info.appCache)
+ list.push(localStrings.getString('cookie_app_cache'));
+ if (info.fileSystem)
+ list.push(localStrings.getString('cookie_file_system'));
+ var text = '';
+ for (var i = 0; i < list.length; ++i)
+ if (text.length > 0)
+ text += ', ' + list[i];
+ else
+ text = list[i];
+ this.dataChild.textContent = text;
+ if (info.quota && info.quota.totalUsage) {
+ this.sizeChild.textContent = info.quota.totalUsage;
+ }
+
+ if (this.expanded)
+ this.updateItems_();
+ },
+
+ /**
+ * Updates the items section to reflect changes, animating to the new state.
+ * Removes existing contents and calls @{code CookieTreeNode.createItems}.
+ * @private
+ */
+ updateItems_: function() {
+ this.disableAnimation_();
+ this.itemsChild.textContent = '';
+ this.infoChild.hidden = true;
+ this.selectedIndex_ = -1;
+ this.itemList_ = [];
+ if (this.origin)
+ this.origin.createItems(this);
+ this.itemsChild.appendChild(this.infoChild);
+ this.enableAnimation_();
+ },
+
+ /**
+ * Append a new cookie node "bubble" to this list item.
+ * @param {CookieTreeNode} node The cookie node to add a bubble for.
+ * @param {Element} div The DOM element for the bubble itself.
+ * @return {number} The index the bubble was added at.
+ */
+ appendItem: function(node, div) {
+ this.itemList_.push({node: node, div: div});
+ this.itemsChild.appendChild(div);
+ return this.itemList_.length - 1;
+ },
+
+ /**
+ * The currently selected cookie node ("cookie bubble") index.
+ * @type {number}
+ * @private
+ */
+ selectedIndex_: -1,
+
+ /**
+ * Get the currently selected cookie node ("cookie bubble") index.
+ * @type {number}
+ */
+ get selectedIndex() {
+ return this.selectedIndex_;
+ },
+
+ /**
+ * Set the currently selected cookie node ("cookie bubble") index to
+ * @{code itemIndex}, unselecting any previously selected node first.
+ * @param {number} itemIndex The index to set as the selected index.
+ */
+ set selectedIndex(itemIndex) {
+ // Get the list index up front before we change anything.
+ var index = this.list.getIndexOfListItem(this);
+ // Unselect any previously selected item.
+ if (this.selectedIndex_ >= 0) {
+ var item = this.itemList_[this.selectedIndex_];
+ if (item && item.div)
+ item.div.removeAttribute('selected');
+ }
+ // Special case: decrementing -1 wraps around to the end of the list.
+ if (itemIndex == -2)
+ itemIndex = this.itemList_.length - 1;
+ // Check if we're going out of bounds and hide the item details.
+ if (itemIndex < 0 || itemIndex >= this.itemList_.length) {
+ this.selectedIndex_ = -1;
+ this.disableAnimation_();
+ this.infoChild.hidden = true;
+ this.enableAnimation_();
+ return;
+ }
+ // Set the new selected item and show the item details for it.
+ this.selectedIndex_ = itemIndex;
+ this.itemList_[itemIndex].div.setAttribute('selected', '');
+ this.disableAnimation_();
+ this.itemList_[itemIndex].node.setDetailText(this.infoChild,
+ this.list.infoNodes);
+ this.infoChild.hidden = false;
+ this.enableAnimation_();
+ // If we're near the bottom of the list this may cause the list item to go
+ // beyond the end of the visible area. Fix it after the animation is done.
+ var list = this.list;
+ window.setTimeout(function() { list.scrollIndexIntoView(index); }, 150);
+ },
+ };
+
+ /**
+ * {@code CookieTreeNode}s mirror the structure of the cookie tree lazily, and
+ * contain all the actual data used to generate the {@code CookieListItem}s.
+ * @param {Object} data The data object for this node.
+ * @constructor
+ */
+ function CookieTreeNode(data) {
+ this.data = data;
+ this.children = [];
+ }
+
+ CookieTreeNode.prototype = {
+ /**
+ * Insert the given list of cookie tree nodes at the given index.
+ * Both CookiesList and CookieTreeNode implement this API.
+ * @param {Array.<Object>} data The data objects for the nodes to add.
+ * @param {number} start The index at which to start inserting the nodes.
+ */
+ insertAt: function(data, start) {
+ var nodes = spliceTreeNodes(data, start, this.children);
+ for (var i = 0; i < nodes.length; i++)
+ nodes[i].parent = this;
+ this.updateOrigin();
+ },
+
+ /**
+ * Remove a cookie tree node from the given index.
+ * Both CookiesList and CookieTreeNode implement this API.
+ * @param {number} index The index of the tree node to remove.
+ */
+ remove: function(index) {
+ if (index < this.children.length) {
+ this.children.splice(index, 1);
+ this.updateOrigin();
+ }
+ },
+
+ /**
+ * Clears all children.
+ * Both CookiesList and CookieTreeNode implement this API.
+ * It is used by CookiesList.loadChildren().
+ */
+ clear: function() {
+ // We might leave some garbage in parentLookup for removed children.
+ // But that should be OK because parentLookup is cleared when we
+ // reload the tree.
+ this.children = [];
+ this.updateOrigin();
+ },
+
+ /**
+ * The counter used by startBatchUpdates() and endBatchUpdates().
+ * @type {number}
+ */
+ batchCount_: 0,
+
+ /**
+ * See cr.ui.List.startBatchUpdates().
+ * Both CookiesList (via List) and CookieTreeNode implement this API.
+ */
+ startBatchUpdates: function() {
+ this.batchCount_++;
+ },
+
+ /**
+ * See cr.ui.List.endBatchUpdates().
+ * Both CookiesList (via List) and CookieTreeNode implement this API.
+ */
+ endBatchUpdates: function() {
+ if (!--this.batchCount_)
+ this.updateOrigin();
+ },
+
+ /**
+ * Requests updating the origin summary to reflect changes in this item.
+ * Both CookieListItem and CookieTreeNode implement this API.
+ */
+ updateOrigin: function() {
+ if (!this.batchCount_ && this.parent)
+ this.parent.updateOrigin();
+ },
+
+ /**
+ * Summarize the information in this node and update @{code info}.
+ * This will recurse into child nodes to summarize all descendants.
+ * @param {Object} info The info object from @{code updateOrigin}.
+ */
+ collectSummaryInfo: function(info) {
+ if (this.children.length > 0) {
+ for (var i = 0; i < this.children.length; ++i)
+ this.children[i].collectSummaryInfo(info);
+ } else if (this.data && !this.data.hasChildren) {
+ if (this.data.type == 'cookie') {
+ info.cookies++;
+ } else if (this.data.type == 'database') {
+ info.database = true;
+ } else if (this.data.type == 'local_storage') {
+ info.localStorage = true;
+ } else if (this.data.type == 'app_cache') {
+ info.appCache = true;
+ } else if (this.data.type == 'indexed_db') {
+ info.indexedDb = true;
+ } else if (this.data.type == 'file_system') {
+ info.fileSystem = true;
+ } else if (this.data.type == 'quota') {
+ info.quota = this.data;
+ }
+ }
+ },
+
+ /**
+ * Create the cookie "bubbles" for this node, recursing into children
+ * if there are any. Append the cookie bubbles to @{code item}.
+ * @param {CookieListItem} item The cookie list item to create items in.
+ */
+ createItems: function(item) {
+ if (this.children.length > 0) {
+ for (var i = 0; i < this.children.length; ++i)
+ this.children[i].createItems(item);
+ } else if (this.data && !this.data.hasChildren) {
+ var text = '';
+ switch (this.data.type) {
+ case 'cookie':
+ case 'database':
+ text = this.data.name;
+ break;
+ case 'local_storage':
+ text = localStrings.getString('cookie_local_storage');
+ break;
+ case 'app_cache':
+ text = localStrings.getString('cookie_app_cache');
+ break;
+ case 'indexed_db':
+ text = localStrings.getString('cookie_indexed_db');
+ break;
+ case 'file_system':
+ text = localStrings.getString('cookie_file_system');
+ break;
+ }
+ if (!text)
+ return;
+ var div = item.ownerDocument.createElement('div');
+ div.className = 'cookie-item';
+ // Help out screen readers and such: this is a clickable thing.
+ div.setAttribute('role', 'button');
+ div.textContent = text;
+ var index = item.appendItem(this, div);
+ div.onclick = function() {
+ if (item.selectedIndex == index)
+ item.selectedIndex = -1;
+ else
+ item.selectedIndex = index;
+ };
+ }
+ },
+
+ /**
+ * Set the detail text to be displayed to that of this cookie tree node.
+ * Uses preallocated DOM elements for each cookie node type from @{code
+ * infoNodes}, and inserts the appropriate elements to @{code element}.
+ * @param {Element} element The DOM element to insert elements to.
+ * @param {Object.<string, {table: Element, info: Object.<string,
+ * Element>}>} infoNodes The map from cookie node types to maps from
+ * cookie attribute names to DOM elements to display cookie attribute
+ * values, created by @{code CookiesList.decorate}.
+ */
+ setDetailText: function(element, infoNodes) {
+ var table;
+ if (this.data && !this.data.hasChildren) {
+ if (cookieInfo[this.data.type]) {
+ var info = cookieInfo[this.data.type];
+ var nodes = infoNodes[this.data.type].info;
+ for (var i = 0; i < info.length; ++i) {
+ var name = info[i][0];
+ if (name != 'id' && this.data[name])
+ nodes[name].textContent = this.data[name];
+ }
+ table = infoNodes[this.data.type].table;
+ }
+ }
+ while (element.childNodes.length > 1)
+ element.removeChild(element.firstChild);
+ if (table)
+ element.insertBefore(table, element.firstChild);
+ },
+
+ /**
+ * The parent of this cookie tree node.
+ * @type {?CookieTreeNode|CookieListItem}
+ */
+ get parent(parent) {
+ // See below for an explanation of this special case.
+ if (typeof this.parent_ == 'number')
+ return this.list_.getListItemByIndex(this.parent_);
+ return this.parent_;
+ },
+ set parent(parent) {
+ if (parent == this.parent)
+ return;
+ if (parent instanceof CookieListItem) {
+ // If the parent is to be a CookieListItem, then we keep the reference
+ // to it by its containing list and list index, rather than directly.
+ // This allows the list items to be garbage collected when they scroll
+ // out of view (except the expanded item, which we cache). This is
+ // transparent except in the setter and getter, where we handle it.
+ this.parent_ = parent.listIndex;
+ this.list_ = parent.list;
+ parent.addEventListener('listIndexChange',
+ this.parentIndexChanged_.bind(this));
+ } else {
+ this.parent_ = parent;
+ }
+ if (this.data && this.data.id) {
+ if (parent)
+ parentLookup[this.data.id] = this;
+ else
+ delete parentLookup[this.data.id];
+ }
+ if (this.data && this.data.hasChildren &&
+ !this.children.length && !lookupRequests[this.data.id]) {
+ lookupRequests[this.data.id] = true;
+ chrome.send('loadCookie', [this.pathId]);
+ }
+ },
+
+ /**
+ * Called when the parent is a CookieListItem whose index has changed.
+ * See the code above that avoids keeping a direct reference to
+ * CookieListItem parents, to allow them to be garbage collected.
+ * @private
+ */
+ parentIndexChanged_: function(event) {
+ if (typeof this.parent_ == 'number') {
+ this.parent_ = event.newValue;
+ // We set a timeout to update the origin, rather than doing it right
+ // away, because this callback may occur while the list items are
+ // being repopulated following a scroll event. Calling updateOrigin()
+ // immediately could trigger relayout that would reset the scroll
+ // position within the list, among other things.
+ window.setTimeout(this.updateOrigin.bind(this), 0);
+ }
+ },
+
+ /**
+ * The cookie tree path id.
+ * @type {string}
+ */
+ get pathId() {
+ var parent = this.parent;
+ if (parent && parent instanceof CookieTreeNode)
+ return parent.pathId + ',' + this.data.id;
+ return this.data.id;
+ },
+ };
+
+ /**
+ * Creates a new cookies list.
+ * @param {Object=} opt_propertyBag Optional properties.
+ * @constructor
+ * @extends {DeletableItemList}
+ */
+ var CookiesList = cr.ui.define('list');
+
+ CookiesList.prototype = {
+ __proto__: DeletableItemList.prototype,
+
+ /** @inheritDoc */
+ decorate: function() {
+ DeletableItemList.prototype.decorate.call(this);
+ this.classList.add('cookie-list');
+ this.data_ = [];
+ this.dataModel = new ArrayDataModel(this.data_);
+ this.addEventListener('keydown', this.handleKeyLeftRight_.bind(this));
+ var sm = new ListSingleSelectionModel();
+ sm.addEventListener('change', this.cookieSelectionChange_.bind(this));
+ sm.addEventListener('leadIndexChange', this.cookieLeadChange_.bind(this));
+ this.selectionModel = sm;
+ this.infoNodes = {};
+ this.fixedHeight = false;
+ var doc = this.ownerDocument;
+ // Create a table for each type of site data (e.g. cookies, databases,
+ // etc.) and save it so that we can reuse it for all origins.
+ for (var type in cookieInfo) {
+ var table = doc.createElement('table');
+ table.className = 'cookie-details-table';
+ var tbody = doc.createElement('tbody');
+ table.appendChild(tbody);
+ var info = {};
+ for (var i = 0; i < cookieInfo[type].length; i++) {
+ var tr = doc.createElement('tr');
+ var name = doc.createElement('td');
+ var data = doc.createElement('td');
+ var pair = cookieInfo[type][i];
+ name.className = 'cookie-details-label';
+ name.textContent = localStrings.getString(pair[1]);
+ data.className = 'cookie-details-value';
+ data.textContent = '';
+ tr.appendChild(name);
+ tr.appendChild(data);
+ tbody.appendChild(tr);
+ info[pair[0]] = data;
+ }
+ this.infoNodes[type] = {table: table, info: info};
+ }
+ },
+
+ /**
+ * Handles key down events and looks for left and right arrows, then
+ * dispatches to the currently expanded item, if any.
+ * @param {Event} e The keydown event.
+ * @private
+ */
+ handleKeyLeftRight_: function(e) {
+ var id = e.keyIdentifier;
+ if ((id == 'Left' || id == 'Right') && this.expandedItem) {
+ var cs = this.ownerDocument.defaultView.getComputedStyle(this);
+ var rtl = cs.direction == 'rtl';
+ if ((!rtl && id == 'Left') || (rtl && id == 'Right'))
+ this.expandedItem.selectedIndex--;
+ else
+ this.expandedItem.selectedIndex++;
+ this.scrollIndexIntoView(this.expandedItem.listIndex);
+ // Prevent the page itself from scrolling.
+ e.preventDefault();
+ }
+ },
+
+ /**
+ * Called on selection model selection changes.
+ * @param {Event} ce The selection change event.
+ * @private
+ */
+ cookieSelectionChange_: function(ce) {
+ ce.changes.forEach(function(change) {
+ var listItem = this.getListItemByIndex(change.index);
+ if (listItem) {
+ if (!change.selected) {
+ // We set a timeout here, rather than setting the item unexpanded
+ // immediately, so that if another item gets set expanded right
+ // away, it will be expanded before this item is unexpanded. It
+ // will notice that, and unexpand this item in sync with its own
+ // expansion. Later, this callback will end up having no effect.
+ window.setTimeout(function() {
+ if (!listItem.selected || !listItem.lead)
+ listItem.expanded = false;
+ }, 0);
+ } else if (listItem.lead) {
+ listItem.expanded = true;
+ }
+ }
+ }, this);
+ },
+
+ /**
+ * Called on selection model lead changes.
+ * @param {Event} pe The lead change event.
+ * @private
+ */
+ cookieLeadChange_: function(pe) {
+ if (pe.oldValue != -1) {
+ var listItem = this.getListItemByIndex(pe.oldValue);
+ if (listItem) {
+ // See cookieSelectionChange_ above for why we use a timeout here.
+ window.setTimeout(function() {
+ if (!listItem.lead || !listItem.selected)
+ listItem.expanded = false;
+ }, 0);
+ }
+ }
+ if (pe.newValue != -1) {
+ var listItem = this.getListItemByIndex(pe.newValue);
+ if (listItem && listItem.selected)
+ listItem.expanded = true;
+ }
+ },
+
+ /**
+ * The currently expanded item. Used by CookieListItem above.
+ * @type {?CookieListItem}
+ */
+ expandedItem: null,
+
+ // from cr.ui.List
+ /** @inheritDoc */
+ createItem: function(data) {
+ // We use the cached expanded item in order to allow it to maintain some
+ // state (like its fixed height, and which bubble is selected).
+ if (this.expandedItem && this.expandedItem.origin == data)
+ return this.expandedItem;
+ return new CookieListItem(data, this);
+ },
+
+ // from options.DeletableItemList
+ /** @inheritDoc */
+ deleteItemAtIndex: function(index) {
+ var item = this.data_[index];
+ if (item) {
+ var pathId = item.pathId;
+ if (pathId)
+ chrome.send('removeCookie', [pathId]);
+ }
+ },
+
+ /**
+ * Insert the given list of cookie tree nodes at the given index.
+ * Both CookiesList and CookieTreeNode implement this API.
+ * @param {Array.<Object>} data The data objects for the nodes to add.
+ * @param {number} start The index at which to start inserting the nodes.
+ */
+ insertAt: function(data, start) {
+ spliceTreeNodes(data, start, this.dataModel);
+ },
+
+ /**
+ * Remove a cookie tree node from the given index.
+ * Both CookiesList and CookieTreeNode implement this API.
+ * @param {number} index The index of the tree node to remove.
+ */
+ remove: function(index) {
+ if (index < this.data_.length)
+ this.dataModel.splice(index, 1);
+ },
+
+ /**
+ * Clears the list.
+ * Both CookiesList and CookieTreeNode implement this API.
+ * It is used by CookiesList.loadChildren().
+ */
+ clear: function() {
+ parentLookup = {};
+ this.data_ = [];
+ this.dataModel = new ArrayDataModel(this.data_);
+ this.redraw();
+ },
+
+ /**
+ * Add tree nodes by given parent.
+ * @param {Object} parent The parent node.
+ * @param {number} start The index at which to start inserting the nodes.
+ * @param {Array} nodesData Nodes data array.
+ * @private
+ */
+ addByParent_: function(parent, start, nodesData) {
+ if (!parent)
+ return;
+
+ parent.startBatchUpdates();
+ parent.insertAt(nodesData, start);
+ parent.endBatchUpdates();
+
+ cr.dispatchSimpleEvent(this, 'change');
+ },
+
+ /**
+ * Add tree nodes by parent id.
+ * This is used by cookies_view.js.
+ * @param {string} parentId Id of the parent node.
+ * @param {number} start The index at which to start inserting the nodes.
+ * @param {Array} nodesData Nodes data array.
+ */
+ addByParentId: function(parentId, start, nodesData) {
+ var parent = parentId ? parentLookup[parentId] : this;
+ this.addByParent_(parent, start, nodesData);
+ },
+
+ /**
+ * Removes tree nodes by parent id.
+ * This is used by cookies_view.js.
+ * @param {string} parentId Id of the parent node.
+ * @param {number} start The index at which to start removing the nodes.
+ * @param {number} count Number of nodes to remove.
+ */
+ removeByParentId: function(parentId, start, count) {
+ var parent = parentId ? parentLookup[parentId] : this;
+ if (!parent)
+ return;
+
+ parent.startBatchUpdates();
+ while (count-- > 0)
+ parent.remove(start);
+ parent.endBatchUpdates();
+
+ cr.dispatchSimpleEvent(this, 'change');
+ },
+
+ /**
+ * Loads the immediate children of given parent node.
+ * This is used by cookies_view.js.
+ * @param {string} parentId Id of the parent node.
+ * @param {Array} children The immediate children of parent node.
+ */
+ loadChildren: function(parentId, children) {
+ if (parentId)
+ delete lookupRequests[parentId];
+ var parent = parentId ? parentLookup[parentId] : this;
+ if (!parent)
+ return;
+
+ parent.startBatchUpdates();
+ parent.clear();
+ this.addByParent_(parent, 0, children);
+ parent.endBatchUpdates();
+ },
+ };
+
+ return {
+ CookiesList: CookiesList
+ };
+});
diff --git a/chrome/browser/resources/options2/cookies_view.css b/chrome/browser/resources/options2/cookies_view.css
new file mode 100644
index 0000000..2aab6b2
--- /dev/null
+++ b/chrome/browser/resources/options2/cookies_view.css
@@ -0,0 +1,182 @@
+/*
+Copyright (c) 2011 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+*/
+
+/* styles for the cookies list elements in cookies_view.html */
+#remove-all-cookies-search-column {
+ bottom: 10px;
+ position: absolute;
+ right: 0;
+}
+
+html[dir=rtl] #remove-all-cookies-search-column {
+ left: 0;
+ right: auto;
+}
+
+#cookies-column-headers {
+ position: relative;
+ width: 100%;
+}
+
+#cookies-column-headers h3 {
+ font-size: 105%;
+ font-weight: bold;
+ margin: 10px 0;
+}
+
+/* notice the width and padding for these columns match up with those below */
+#cookies-site-column {
+ display: inline-block;
+ font-weight: bold;
+ width: 11em;
+}
+
+#cookies-data-column {
+ -webkit-padding-start: 7px;
+ display: inline-block;
+ font-weight: bold;
+}
+
+#cookies-list {
+ border: 1px solid #D9D9D9;
+ margin: 0;
+}
+
+
+/* enable animating the height of items */
+list.cookie-list .deletable-item {
+ -webkit-transition: height .15s ease-in-out;
+}
+
+/* disable webkit-box display */
+list.cookie-list .deletable-item > :first-child {
+ display: block;
+}
+
+/* force the X for deleting an origin to stay at the top */
+list.cookie-list > .deletable-item > .close-button {
+ position: absolute;
+ right: 2px;
+ top: 8px;
+}
+
+html[dir=rtl] list.cookie-list > .deletable-item > .close-button {
+ left: 2px;
+ right: auto;
+}
+
+
+/* styles for the site (aka origin) and its summary */
+.cookie-site {
+ /* notice that the width, margin, and padding match up with those above */
+ -webkit-margin-end: 2px;
+ -webkit-padding-start: 5px;
+ display: inline-block;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ width: 11em;
+}
+
+list.cookie-list > .deletable-item[selected] .cookie-site {
+ -webkit-user-select: text;
+}
+
+.cookie-data {
+ display: inline-block;
+}
+
+.cookie-size {
+ display: inline-block;
+ float: right;
+ margin-right: 3em;
+}
+
+list.cookie-list > .deletable-item[selected] .cookie-data {
+ -webkit-user-select: text;
+}
+
+
+/* styles for the individual items (cookies, etc.) */
+.cookie-items {
+ /* notice that the margin and padding match up with those above */
+ -webkit-margin-start: 11em;
+ -webkit-padding-start: 7px;
+ -webkit-transition: .15s ease-in-out;
+ height: 0;
+ opacity: 0;
+ /* make the cookie items wrap correctly */
+ white-space: normal;
+}
+
+.measure-items .cookie-items {
+ -webkit-transition: none;
+ height: auto;
+ visibility: hidden;
+}
+
+.show-items .cookie-items {
+ opacity: 1;
+}
+
+.cookie-items .cookie-item {
+ background: #E0E9F5;
+ border-radius: 5px;
+ border: 1px solid #8392AE;
+ display: inline-block;
+ font-size: 85%;
+ height: auto;
+ margin: 2px 4px 2px 0;
+ max-width: 100px;
+ min-width: 40px;
+ overflow: hidden;
+ padding: 0 3px;
+ text-align: center;
+ text-overflow: ellipsis;
+}
+
+.cookie-items .cookie-item:hover {
+ background: #EEF3F9;
+ border-color: #647187;
+}
+
+.cookie-items .cookie-item[selected] {
+ background: #F5F8F8;
+ border-color: #B2B2B2;
+}
+
+.cookie-items .cookie-item[selected]:hover {
+ background: #F5F8F8;
+ border-color: #647187;
+}
+
+
+/* styles for the cookie details box */
+.cookie-details {
+ background: #F5F8F8;
+ border-radius: 5px;
+ border: 1px solid #B2B2B2;
+ margin-top: 2px;
+ padding: 5px;
+}
+
+list.cookie-list > .deletable-item[selected] .cookie-details {
+ -webkit-user-select: text;
+}
+
+.cookie-details-table {
+ table-layout: fixed;
+ width: 100%;
+}
+
+.cookie-details-label {
+ vertical-align: top;
+ white-space: pre;
+ width: 10em;
+}
+
+.cookie-details-value {
+ word-wrap: break-word;
+}
diff --git a/chrome/browser/resources/options2/cookies_view.html b/chrome/browser/resources/options2/cookies_view.html
new file mode 100644
index 0000000..1a58487
--- /dev/null
+++ b/chrome/browser/resources/options2/cookies_view.html
@@ -0,0 +1,15 @@
+<div id="cookiesViewPage" class="page" hidden>
+ <h1 i18n-content="cookiesViewPage"></h1>
+ <div id="cookies-column-headers">
+ <div id="cookies-site-column"><h3 i18n-content="cookie_domain"></h3></div>
+ <div id="cookies-data-column"><h3 i18n-content="cookie_local_data"></h3></div>
+ <div id="remove-all-cookies-search-column">
+ <button id="remove-all-cookies-button"
+ i18n-content="remove_all_cookie"></button>
+ <input id="cookies-search-box" type="search"
+ i18n-values="placeholder:search_cookies" incremental
+ results="10" autosave="org.chromium.options.cookies.search">
+ </div>
+ </div>
+ <list id="cookies-list"></list>
+</div>
diff --git a/chrome/browser/resources/options2/cookies_view.js b/chrome/browser/resources/options2/cookies_view.js
new file mode 100644
index 0000000..dafa0cd
--- /dev/null
+++ b/chrome/browser/resources/options2/cookies_view.js
@@ -0,0 +1,140 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+
+ var OptionsPage = options.OptionsPage;
+
+ /////////////////////////////////////////////////////////////////////////////
+ // CookiesView class:
+
+ /**
+ * Encapsulated handling of the cookies and other site data page.
+ * @constructor
+ */
+ function CookiesView(model) {
+ OptionsPage.call(this, 'cookies',
+ templateData.cookiesViewPageTabTitle,
+ 'cookiesViewPage');
+ }
+
+ cr.addSingletonGetter(CookiesView);
+
+ CookiesView.prototype = {
+ __proto__: OptionsPage.prototype,
+
+ /**
+ * The timer id of the timer set on search query change events.
+ * @type {number}
+ * @private
+ */
+ queryDelayTimerId_: 0,
+
+ /**
+ * The most recent search query, or null if the query is empty.
+ * @type {?string}
+ * @private
+ */
+ lastQuery_ : null,
+
+ initializePage: function() {
+ OptionsPage.prototype.initializePage.call(this);
+
+ $('cookies-search-box').addEventListener('search',
+ this.handleSearchQueryChange_.bind(this));
+
+ $('remove-all-cookies-button').onclick = function(e) {
+ chrome.send('removeAllCookies', []);
+ };
+
+ var cookiesList = $('cookies-list');
+ options.CookiesList.decorate(cookiesList);
+ window.addEventListener('resize', this.handleResize_.bind(this));
+
+ this.addEventListener('visibleChange', this.handleVisibleChange_);
+ },
+
+ /**
+ * Search cookie using text in |cookies-search-box|.
+ */
+ searchCookie: function() {
+ this.queryDelayTimerId_ = 0;
+ var filter = $('cookies-search-box').value;
+ if (this.lastQuery_ != filter) {
+ this.lastQuery_ = filter;
+ chrome.send('updateCookieSearchResults', [filter]);
+ }
+ },
+
+ /**
+ * Handles search query changes.
+ * @param {!Event} e The event object.
+ * @private
+ */
+ handleSearchQueryChange_: function(e) {
+ if (this.queryDelayTimerId_)
+ window.clearTimeout(this.queryDelayTimerId_);
+
+ this.queryDelayTimerId_ = window.setTimeout(
+ this.searchCookie.bind(this), 500);
+ },
+
+ initialized_: false,
+
+ /**
+ * Handler for OptionsPage's visible property change event.
+ * @param {Event} e Property change event.
+ * @private
+ */
+ handleVisibleChange_: function(e) {
+ if (!this.visible)
+ return;
+
+ // Resize the cookies list whenever the options page becomes visible.
+ this.handleResize_(null);
+ if (!this.initialized_) {
+ this.initialized_ = true;
+ this.searchCookie();
+ } else {
+ $('cookies-list').redraw();
+ }
+
+ $('cookies-search-box').focus();
+ },
+
+ /**
+ * Handler for when the window changes size. Resizes the cookies list to
+ * match the window height.
+ * @param {?Event} e Window resize event, or null if called directly.
+ * @private
+ */
+ handleResize_: function(e) {
+ if (!this.visible)
+ return;
+ var cookiesList = $('cookies-list');
+ // 25 pixels from the window bottom seems like a visually pleasing amount.
+ var height = window.innerHeight - cookiesList.offsetTop - 25;
+ cookiesList.style.height = height + 'px';
+ },
+ };
+
+ // CookiesViewHandler callbacks.
+ CookiesView.onTreeItemAdded = function(args) {
+ $('cookies-list').addByParentId(args[0], args[1], args[2]);
+ };
+
+ CookiesView.onTreeItemRemoved = function(args) {
+ $('cookies-list').removeByParentId(args[0], args[1], args[2]);
+ };
+
+ CookiesView.loadChildren = function(args) {
+ $('cookies-list').loadChildren(args[0], args[1]);
+ };
+
+ // Export
+ return {
+ CookiesView: CookiesView
+ };
+
+});
diff --git a/chrome/browser/resources/options2/deletable_item_list.js b/chrome/browser/resources/options2/deletable_item_list.js
new file mode 100644
index 0000000..80d4963
--- /dev/null
+++ b/chrome/browser/resources/options2/deletable_item_list.js
@@ -0,0 +1,185 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+ const List = cr.ui.List;
+ const ListItem = cr.ui.ListItem;
+
+ /**
+ * Creates a deletable list item, which has a button that will trigger a call
+ * to deleteItemAtIndex(index) in the list.
+ */
+ var DeletableItem = cr.ui.define('li');
+
+ DeletableItem.prototype = {
+ __proto__: ListItem.prototype,
+
+ /**
+ * The element subclasses should populate with content.
+ * @type {HTMLElement}
+ * @private
+ */
+ contentElement_: null,
+
+ /**
+ * The close button element.
+ * @type {HTMLElement}
+ * @private
+ */
+ closeButtonElement_: null,
+
+ /**
+ * Whether or not this item can be deleted.
+ * @type {boolean}
+ * @private
+ */
+ deletable_: true,
+
+ /** @inheritDoc */
+ decorate: function() {
+ ListItem.prototype.decorate.call(this);
+
+ this.classList.add('deletable-item');
+
+ this.contentElement_ = this.ownerDocument.createElement('div');
+ this.appendChild(this.contentElement_);
+
+ this.closeButtonElement_ = this.ownerDocument.createElement('button');
+ this.closeButtonElement_.className =
+ 'raw-button close-button custom-appearance';
+ this.closeButtonElement_.addEventListener('mousedown',
+ this.handleMouseDownUpOnClose_);
+ this.closeButtonElement_.addEventListener('mouseup',
+ this.handleMouseDownUpOnClose_);
+ this.closeButtonElement_.addEventListener('focus',
+ this.handleFocus_.bind(this));
+ this.appendChild(this.closeButtonElement_);
+ },
+
+ /**
+ * Returns the element subclasses should add content to.
+ * @return {HTMLElement} The element subclasses should popuplate.
+ */
+ get contentElement() {
+ return this.contentElement_;
+ },
+
+ /* Gets/sets the deletable property. An item that is not deletable doesn't
+ * show the delete button (although space is still reserved for it).
+ */
+ get deletable() {
+ return this.deletable_;
+ },
+ set deletable(value) {
+ this.deletable_ = value;
+ this.closeButtonElement_.disabled = !value;
+ },
+
+ /**
+ * Called when a focusable child element receives focus. Selects this item
+ * in the list selection model.
+ * @private
+ */
+ handleFocus_: function() {
+ var list = this.parentNode;
+ var index = list.getIndexOfListItem(this);
+ list.selectionModel.selectedIndex = index;
+ list.selectionModel.anchorIndex = index;
+ },
+
+ /**
+ * Don't let the list have a crack at the event. We don't want clicking the
+ * close button to change the selection of the list.
+ * @param {Event} e The mouse down/up event object.
+ * @private
+ */
+ handleMouseDownUpOnClose_: function(e) {
+ if (!e.target.disabled)
+ e.stopPropagation();
+ },
+ };
+
+ var DeletableItemList = cr.ui.define('list');
+
+ DeletableItemList.prototype = {
+ __proto__: List.prototype,
+
+ /** @inheritDoc */
+ decorate: function() {
+ List.prototype.decorate.call(this);
+ this.addEventListener('click', this.handleClick_);
+ this.addEventListener('keydown', this.handleKeyDown_);
+ },
+
+ /**
+ * Callback for onclick events.
+ * @param {Event} e The click event object.
+ * @private
+ */
+ handleClick_: function(e) {
+ if (this.disabled)
+ return;
+
+ var target = e.target;
+ if (target.classList.contains('close-button')) {
+ var listItem = this.getListItemAncestor(target);
+ var selected = this.selectionModel.selectedIndexes;
+
+ // Check if the list item that contains the close button being clicked
+ // is not in the list of selected items. Only delete this item in that
+ // case.
+ var idx = this.getIndexOfListItem(listItem);
+ if (selected.indexOf(idx) == -1) {
+ this.deleteItemAtIndex(idx);
+ } else {
+ this.deleteSelectedItems_();
+ }
+ }
+ },
+
+ /**
+ * Callback for keydown events.
+ * @param {Event} e The keydown event object.
+ * @private
+ */
+ handleKeyDown_: function(e) {
+ // Map delete (and backspace on Mac) to item deletion (unless focus is
+ // in an input field, where it's intended for text editing).
+ if ((e.keyCode == 46 || (e.keyCode == 8 && cr.isMac)) &&
+ e.target.tagName != 'INPUT') {
+ this.deleteSelectedItems_();
+ // Prevent the browser from going back.
+ e.preventDefault();
+ }
+ },
+
+ /**
+ * Deletes all the currently selected items that are deletable.
+ * @private
+ */
+ deleteSelectedItems_: function() {
+ var selected = this.selectionModel.selectedIndexes;
+ // Reverse through the list of selected indexes to maintain the
+ // correct index values after deletion.
+ for (var j = selected.length - 1; j >= 0; j--) {
+ var index = selected[j];
+ if (this.getListItemByIndex(index).deletable)
+ this.deleteItemAtIndex(index);
+ }
+ },
+
+ /**
+ * Called when an item should be deleted; subclasses are responsible for
+ * implementing.
+ * @param {number} index The index of the item that is being deleted.
+ */
+ deleteItemAtIndex: function(index) {
+ },
+ };
+
+ return {
+ DeletableItemList: DeletableItemList,
+ DeletableItem: DeletableItem,
+ };
+});
diff --git a/chrome/browser/resources/options2/extension_list.js b/chrome/browser/resources/options2/extension_list.js
new file mode 100644
index 0000000..5cbad97
--- /dev/null
+++ b/chrome/browser/resources/options2/extension_list.js
@@ -0,0 +1,764 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+ 'use strict';
+
+ /**
+ * A lookup helper function to find the first node that has an id (starting
+ * at |node| and going up the parent chain).
+ * @param {Element} node The node to start looking at.
+ */
+ function findIdNode(node) {
+ while (node && !node.id) {
+ node = node.parentNode;
+ }
+ return node;
+ }
+
+ /**
+ * Creates a new list of extensions.
+ * @param {Object=} opt_propertyBag Optional properties.
+ * @constructor
+ * @extends {cr.ui.div}
+ */
+ var ExtensionsList = cr.ui.define('div');
+
+ var handlersInstalled = false;
+
+ /**
+ * @type {Object.<string, boolean>} A map from extension id to a boolean
+ * indicating whether its details section is expanded. This persists
+ * between calls to decorate.
+ */
+ var showingDetails = {};
+
+ /**
+ * @type {Object.<string, boolean>} A map from extension id to a boolean
+ * indicating whether the incognito warning is showing. This persists
+ * between calls to decorate.
+ */
+ var showingWarning = {};
+
+ ExtensionsList.prototype = {
+ __proto__: HTMLDivElement.prototype,
+
+ /** @inheritDoc */
+ decorate: function() {
+ this.initControlsAndHandlers_();
+
+ this.deleteExistingExtensionNodes_();
+
+ this.showExtensionNodes_();
+ },
+
+ /**
+ * Initializes the controls (toggle section and button) and installs
+ * handlers.
+ * @private
+ */
+ initControlsAndHandlers_: function() {
+ // Make sure developer mode section is set correctly as per saved setting.
+ var toggleButton = $('toggle-dev-on');
+ var toggleSection = $('dev');
+ if (this.data_.developerMode) {
+ toggleSection.classList.add('dev-open');
+ toggleSection.classList.remove('dev-closed');
+ toggleButton.checked = true;
+ } else {
+ toggleSection.classList.remove('dev-open');
+ toggleSection.classList.add('dev-closed');
+ }
+
+ // Instal global event handlers.
+ if (!handlersInstalled) {
+ var searchPage = SearchPage.getInstance();
+ searchPage.addEventListener('searchChanged',
+ this.searchChangedHandler_.bind(this));
+
+ // Support full keyboard accessibility without making things ugly
+ // for users who click, by hiding some focus outlines when the user
+ // clicks anywhere, but showing them when the user presses any key.
+ this.ownerDocument.body.classList.add('hide-some-focus-outlines');
+ this.ownerDocument.addEventListener('click', (function(e) {
+ this.ownerDocument.body.classList.add('hide-some-focus-outlines');
+ return true;
+ }).bind(this), true);
+ this.ownerDocument.addEventListener('keydown', (function(e) {
+ this.ownerDocument.body.classList.remove('hide-some-focus-outlines');
+ return true;
+ }).bind(this), true);
+
+ handlersInstalled = true;
+ }
+ },
+
+ /**
+ * Deletes the existing Extension nodes from the page to make room for new
+ * ones.
+ * @private
+ */
+ deleteExistingExtensionNodes_: function() {
+ while (this.hasChildNodes()){
+ this.removeChild(this.firstChild);
+ }
+ },
+
+ /**
+ * Handles decorating the details section.
+ * @param {Element} details The div that the details should be attached to.
+ * @param {Object} extension The extension we are showing the details for.
+ * @private
+ */
+ showExtensionNodes_: function() {
+ // Iterate over the extension data and add each item to the list.
+ for (var i = 0; i < this.data_.extensions.length; i++) {
+ var extension = this.data_.extensions[i];
+ var id = extension.id;
+
+ var wrapper = this.ownerDocument.createElement('div');
+
+ var expanded = showingDetails[id];
+ var butterbar = showingWarning[id];
+
+ wrapper.classList.add(expanded ? 'extension-list-item-expanded' :
+ 'extension-list-item-collaped');
+ if (!extension.enabled)
+ wrapper.classList.add('disabled');
+ wrapper.id = id;
+ this.appendChild(wrapper);
+
+ var vboxOuter = this.ownerDocument.createElement('div');
+ vboxOuter.classList.add('vbox');
+ vboxOuter.classList.add('extension-list-item');
+ wrapper.appendChild(vboxOuter);
+
+ var hbox = this.ownerDocument.createElement('div');
+ hbox.classList.add('hbox');
+ vboxOuter.appendChild(hbox);
+
+ // Add a container div for the zippy, so we can extend the hit area.
+ var container = this.ownerDocument.createElement('div');
+ // Clicking anywhere on the div expands/collapses the details.
+ container.classList.add('extension-zippy-container');
+ container.title = expanded ?
+ localStrings.getString('extensionSettingsHideDetails') :
+ localStrings.getString('extensionSettingsShowDetails');
+ container.tabIndex = 0;
+ container.setAttribute('role', 'button');
+ container.setAttribute('aria-controls', extension.id + '_details');
+ container.setAttribute('aria-expanded', expanded);
+ container.addEventListener('click', this.handleZippyClick_.bind(this));
+ container.addEventListener('keydown',
+ this.handleZippyKeyDown_.bind(this));
+ hbox.appendChild(container);
+
+ // On the far left we have the zippy icon.
+ var div = this.ownerDocument.createElement('div');
+ div.id = id + '_zippy';
+ div.classList.add('extension-zippy-default');
+ div.classList.add(expanded ? 'extension-zippy-expanded' :
+ 'extension-zippy-collapsed');
+ container.appendChild(div);
+
+ // Next to it, we have the extension icon.
+ var icon = this.ownerDocument.createElement('img');
+ icon.classList.add('extension-icon');
+ icon.src = extension.icon;
+ hbox.appendChild(icon);
+
+ // Start a vertical box for showing the details.
+ var vbox = this.ownerDocument.createElement('div');
+ vbox.classList.add('vbox');
+ vbox.classList.add('stretch');
+ vbox.classList.add('details-view');
+ hbox.appendChild(vbox);
+
+ div = this.ownerDocument.createElement('div');
+ vbox.appendChild(div);
+
+ // Title comes next.
+ var title = this.ownerDocument.createElement('span');
+ title.classList.add('extension-title');
+ title.textContent = extension.name;
+ vbox.appendChild(title);
+
+ // Followed by version.
+ var version = this.ownerDocument.createElement('span');
+ version.classList.add('extension-version');
+ version.textContent = extension.version;
+ vbox.appendChild(version);
+
+ // And the additional info label (unpacked/crashed).
+ if (extension.terminated || extension.isUnpacked) {
+ var version = this.ownerDocument.createElement('span');
+ version.classList.add('extension-version');
+ version.textContent = extension.terminated ?
+ localStrings.getString('extensionSettingsCrashMessage') :
+ localStrings.getString('extensionSettingsInDevelopment');
+ vbox.appendChild(version);
+ }
+
+ div = this.ownerDocument.createElement('div');
+ vbox.appendChild(div);
+
+ // And below that we have description (if provided).
+ if (extension.description.length > 0) {
+ var description = this.ownerDocument.createElement('span');
+ description.classList.add('extension-description');
+ description.textContent = extension.description;
+ vbox.appendChild(description);
+ }
+
+ // Immediately following the description, we have the
+ // Options link (optional).
+ if (extension.options_url) {
+ var link = this.ownerDocument.createElement('a');
+ link.classList.add('extension-links-trailing');
+ link.textContent = localStrings.getString('extensionSettingsOptions');
+ link.href = '#';
+ link.addEventListener('click', this.handleOptions_.bind(this));
+ vbox.appendChild(link);
+ }
+
+ // Then the optional Visit Website link.
+ if (extension.homepageUrl) {
+ var link = this.ownerDocument.createElement('a');
+ link.classList.add('extension-links-trailing');
+ link.textContent =
+ localStrings.getString('extensionSettingsVisitWebsite');
+ link.href = extension.homepageUrl;
+ vbox.appendChild(link);
+ }
+
+ if (extension.warnings.length > 0) {
+ var warningsDiv = this.ownerDocument.createElement('div');
+ warningsDiv.classList.add('extension-warnings');
+
+ var warningsHeader = this.ownerDocument.createElement('span');
+ warningsHeader.classList.add('extension-warnings-title');
+ warningsHeader.textContent =
+ localStrings.getString('extensionSettingsWarningsTitle');
+ warningsDiv.appendChild(warningsHeader);
+
+ var warningList = this.ownerDocument.createElement('ul');
+ for (var j = 0; j < extension.warnings.length; ++j) {
+ var warningEntry = this.ownerDocument.createElement('li');
+ warningEntry.textContent = extension.warnings[j];
+ warningList.appendChild(warningEntry);
+ }
+ warningsDiv.appendChild(warningList);
+
+ vbox.appendChild(warningsDiv);
+ }
+
+ // And now the details section that is normally hidden.
+ var details = this.ownerDocument.createElement('div');
+ details.classList.add('vbox');
+ vbox.appendChild(details);
+
+ this.decorateDetailsSection_(details, extension, expanded, butterbar);
+
+ // And on the right of the details we have the Enable/Enabled checkbox.
+ div = this.ownerDocument.createElement('div');
+ hbox.appendChild(div);
+
+ var section = this.ownerDocument.createElement('section');
+ section.classList.add('extension-enabling');
+ div.appendChild(section);
+
+ if (!extension.terminated) {
+ // The Enable checkbox.
+ var input = this.ownerDocument.createElement('input');
+ input.addEventListener('click', this.handleEnable_.bind(this));
+ input.type = 'checkbox';
+ input.name = 'toggle-' + id;
+ input.disabled = !extension.mayDisable;
+ if (extension.enabled)
+ input.checked = true;
+ input.id = 'toggle-' + id;
+ section.appendChild(input);
+ var label = this.ownerDocument.createElement('label');
+ label.classList.add('extension-enabling-label');
+ if (extension.enabled)
+ label.classList.add('extension-enabling-label-bold');
+ label.htmlFor = 'toggle-' + id;
+ label.id = 'toggle-' + id + '-label';
+ if (extension.enabled) {
+ // Enabled (with a d).
+ label.textContent =
+ localStrings.getString('extensionSettingsEnabled');
+ } else {
+ // Enable (no d).
+ label.textContent =
+ localStrings.getString('extensionSettingsEnable');
+ }
+ section.appendChild(label);
+ } else {
+ // Extension has been terminated, show a Reload link.
+ var link = this.ownerDocument.createElement('a');
+ link.classList.add('extension-links-trailing');
+ link.id = extension.id;
+ link.textContent =
+ localStrings.getString('extensionSettingsReload');
+ link.href = '#';
+ link.addEventListener('click', this.handleReload_.bind(this));
+ section.appendChild(link);
+ }
+
+ // And, on the far right we have the uninstall button.
+ var button = this.ownerDocument.createElement('button');
+ button.classList.add('extension-delete');
+ button.id = id;
+ if (!extension.mayDisable)
+ button.disabled = true;
+ button.textContent = localStrings.getString('extensionSettingsRemove');
+ button.addEventListener('click', this.handleUninstall_.bind(this));
+ hbox.appendChild(button);
+ }
+
+ // Do one pass to find what the size of the checkboxes should be.
+ var minCheckboxWidth = Infinity;
+ var maxCheckboxWidth = 0;
+ for (var i = 0; i < this.data_.extensions.length; ++i) {
+ var label = $('toggle-' + this.data_.extensions[i].id + '-label');
+ if (label.offsetWidth > maxCheckboxWidth)
+ maxCheckboxWidth = label.offsetWidth;
+ if (label.offsetWidth < minCheckboxWidth)
+ minCheckboxWidth = label.offsetWidth;
+ }
+
+ // Do another pass, making sure checkboxes line up.
+ var difference = maxCheckboxWidth - minCheckboxWidth;
+ for (var i = 0; i < this.data_.extensions.length; ++i) {
+ var label = $('toggle-' + this.data_.extensions[i].id + '-label');
+ if (label.offsetWidth < maxCheckboxWidth)
+ label.style.WebkitMarginEnd = difference.toString() + 'px';
+ }
+ },
+
+ /**
+ * Handles decorating the details section.
+ * @param {Element} details The div that the details should be attached to.
+ * @param {Object} extension The extension we are shoting the details for.
+ * @param {boolean} expanded Whether to show the details expanded or not.
+ * @param {boolean} showButterbar Whether to show the incognito warning or
+ * not.
+ * @private
+ */
+ decorateDetailsSection_: function(details, extension,
+ expanded, showButterbar) {
+ // This container div is needed because vbox display
+ // overrides display:hidden.
+ var detailsContents = this.ownerDocument.createElement('div');
+ detailsContents.classList.add(expanded ? 'extension-details-visible' :
+ 'extension-details-hidden');
+ detailsContents.id = extension.id + '_details';
+ details.appendChild(detailsContents);
+
+ var div = this.ownerDocument.createElement('div');
+ div.classList.add('informative-text');
+ detailsContents.appendChild(div);
+
+ // Keep track of how many items we'll show in the details section.
+ var itemsShown = 0;
+
+ if (this.data_.developerMode) {
+ // First we have the id.
+ var content = this.ownerDocument.createElement('div');
+ content.textContent =
+ localStrings.getString('extensionSettingsExtensionId') +
+ ' ' + extension.id;
+ div.appendChild(content);
+ itemsShown++;
+
+ // Then, the path, if provided by unpacked extension.
+ if (extension.isUnpacked) {
+ content = this.ownerDocument.createElement('div');
+ content.textContent =
+ localStrings.getString('extensionSettingsExtensionPath') +
+ ' ' + extension.path;
+ div.appendChild(content);
+ itemsShown++;
+ }
+
+ // Then, the 'managed, cannot uninstall/disable' message.
+ if (!extension.mayDisable) {
+ content = this.ownerDocument.createElement('div');
+ content.textContent =
+ localStrings.getString('extensionSettingsPolicyControlled');
+ div.appendChild(content);
+ itemsShown++;
+ }
+
+ // Then active views:
+ if (extension.views.length > 0) {
+ var table = this.ownerDocument.createElement('table');
+ table.classList.add('extension-inspect-table');
+ div.appendChild(table);
+ var tr = this.ownerDocument.createElement('tr');
+ table.appendChild(tr);
+ var td = this.ownerDocument.createElement('td');
+ td.classList.add('extension-inspect-left-column');
+ tr.appendChild(td);
+ var span = this.ownerDocument.createElement('span');
+ td.appendChild(span);
+ span.textContent =
+ localStrings.getString('extensionSettingsInspectViews');
+
+ td = this.ownerDocument.createElement('td');
+ for (var i = 0; i < extension.views.length; ++i) {
+ // Then active views:
+ content = this.ownerDocument.createElement('div');
+ var link = this.ownerDocument.createElement('a');
+ link.classList.add('extension-links-view');
+ link.textContent = extension.views[i].path;
+ link.id = extension.id;
+ link.href = '#';
+ link.addEventListener('click', this.sendInspectMessage_.bind(this));
+ content.appendChild(link);
+
+ if (extension.views[i].incognito) {
+ var incognito = this.ownerDocument.createElement('span');
+ incognito.classList.add('extension-links-view');
+ incognito.textContent =
+ localStrings.getString('viewIncognito');
+ content.appendChild(incognito);
+ }
+
+ td.appendChild(content);
+ tr.appendChild(td);
+
+ itemsShown++;
+ }
+ }
+ }
+
+ var content = this.ownerDocument.createElement('div');
+ detailsContents.appendChild(content);
+
+ // Then Reload:
+ if (extension.enabled && extension.allow_reload) {
+ this.addLinkTo_(content,
+ localStrings.getString('extensionSettingsReload'),
+ extension.id,
+ this.handleReload_.bind(this));
+ itemsShown++;
+ }
+
+ // Then Show (Browser Action) Button:
+ if (extension.enabled && extension.enable_show_button) {
+ this.addLinkTo_(content,
+ localStrings.getString('extensionSettingsShowButton'),
+ extension.id,
+ this.handleShowButton_.bind(this));
+ itemsShown++;
+ }
+
+ if (extension.enabled) {
+ // The 'allow in incognito' checkbox.
+ var label = this.ownerDocument.createElement('label');
+ label.classList.add('extension-checkbox-label');
+ content.appendChild(label);
+ var input = this.ownerDocument.createElement('input');
+ input.addEventListener('click',
+ this.handleToggleEnableIncognito_.bind(this));
+ input.id = extension.id;
+ input.type = 'checkbox';
+ if (extension.enabledIncognito)
+ input.checked = true;
+ label.appendChild(input);
+ var span = this.ownerDocument.createElement('span');
+ span.classList.add('extension-checkbox-span');
+ span.textContent =
+ localStrings.getString('extensionSettingsEnableIncognito');
+ label.appendChild(span);
+ itemsShown++;
+ }
+
+ if (extension.enabled && extension.wantsFileAccess) {
+ // The 'allow access to file URLs' checkbox.
+ label = this.ownerDocument.createElement('label');
+ label.classList.add('extension-checkbox-label');
+ content.appendChild(label);
+ var input = this.ownerDocument.createElement('input');
+ input.addEventListener('click',
+ this.handleToggleAllowFileUrls_.bind(this));
+ input.id = extension.id;
+ input.type = 'checkbox';
+ if (extension.allowFileAccess)
+ input.checked = true;
+ label.appendChild(input);
+ var span = this.ownerDocument.createElement('span');
+ span.classList.add('extension-checkbox-span');
+ span.textContent =
+ localStrings.getString('extensionSettingsAllowFileAccess');
+ label.appendChild(span);
+ itemsShown++;
+ }
+
+ if (extension.enabled && !extension.is_hosted_app) {
+ // And add a hidden warning message for allowInIncognito.
+ content = this.ownerDocument.createElement('div');
+ content.id = extension.id + '_incognitoWarning';
+ content.classList.add('butter-bar');
+ content.hidden = !showButterbar;
+ detailsContents.appendChild(content);
+
+ var span = this.ownerDocument.createElement('span');
+ span.innerHTML =
+ localStrings.getString('extensionSettingsIncognitoWarning');
+ content.appendChild(span);
+ itemsShown++;
+ }
+
+ var zippy = extension.id + '_zippy';
+ $(zippy).hidden = !itemsShown;
+
+ // If this isn't expanded now, make sure the newly-added controls
+ // are not part of the tab order.
+ if (!expanded) {
+ var detailsControls = details.querySelectorAll('a, input');
+ for (var i = 0; i < detailsControls.length; i++)
+ detailsControls[i].tabIndex = -1;
+ }
+ },
+
+ /**
+ * A helper function to add contextual actions for extensions (action links)
+ * to the page.
+ */
+ addLinkTo_: function(parent, linkText, id, handler) {
+ var link = this.ownerDocument.createElement('a');
+ link.className = 'extension-links-trailing';
+ link.textContent = linkText;
+ link.id = id;
+ link.href = '#';
+ link.addEventListener('click', handler);
+ parent.appendChild(link);
+ },
+
+ /**
+ * A lookup helper function to find an extension based on an id.
+ * @param {string} id The |id| of the extension to look up.
+ * @private
+ */
+ getExtensionWithId_: function(id) {
+ for (var i = 0; i < this.data_.extensions.length; ++i) {
+ if (this.data_.extensions[i].id == id)
+ return this.data_.extensions[i];
+ }
+ return null;
+ },
+
+ /**
+ * Handles a key down on the zippy icon.
+ * @param {Event} e Key event.
+ * @private
+ */
+ handleZippyKeyDown_: function(e) {
+ if (e.keyCode == 13 || e.keyCode == 32) // Enter or Space.
+ this.handleZippyClick_(e);
+ },
+
+ /**
+ * Handles the mouseclick on the zippy icon (that expands and collapses the
+ * details section).
+ * @param {Event} e Mouse event.
+ * @private
+ */
+ handleZippyClick_: function(e) {
+ var node = findIdNode(e.target.parentNode);
+ var iter = this.firstChild;
+ while (iter) {
+ var zippy = $(iter.id + '_zippy');
+ var details = $(iter.id + '_details');
+ var container = zippy.parentElement;
+ if (iter.id == node.id) {
+ // Toggle visibility.
+ if (iter.classList.contains('extension-list-item-expanded')) {
+ // Hide yo kids! Hide yo wife!
+ showingDetails[iter.id] = false;
+ zippy.classList.remove('extension-zippy-expanded');
+ zippy.classList.add('extension-zippy-collapsed');
+ details.classList.remove('extension-details-visible');
+ details.classList.add('extension-details-hidden');
+ iter.classList.remove('extension-list-item-expanded');
+ iter.classList.add('extension-list-item-collaped');
+ container.setAttribute('aria-expanded', 'false');
+ container.title =
+ localStrings.getString('extensionSettingsShowDetails');
+ var detailsControls = details.querySelectorAll('a, input');
+ for (var i = 0; i < detailsControls.length; i++)
+ detailsControls[i].tabIndex = -1;
+
+ // Hide yo incognito warning.
+ var butterBar =
+ this.querySelector('#' + iter.id + '_incognitoWarning');
+ if (butterBar !== null) {
+ butterBar.hidden = true;
+ showingWarning[iter.id] = false;
+ }
+ } else {
+ // Show the contents.
+ showingDetails[iter.id] = true;
+ zippy.classList.remove('extension-zippy-collapsed');
+ zippy.classList.add('extension-zippy-expanded');
+ details.classList.remove('extension-details-hidden');
+ details.classList.add('extension-details-visible');
+ iter.classList.remove('extension-list-item-collaped');
+ iter.classList.add('extension-list-item-expanded');
+ container.setAttribute('aria-expanded', 'true');
+ container.title =
+ localStrings.getString('extensionSettingsHideDetails');
+ var detailsControls = details.querySelectorAll('a, input');
+ for (var i = 0; i < detailsControls.length; i++)
+ detailsControls[i].tabIndex = 0;
+ }
+ }
+ iter = iter.nextSibling;
+ }
+ },
+
+ /**
+ * Handles the 'searchChanged' event. This is used to limit the number of
+ * items to show in the list, when the user is searching for items with the
+ * search box. Otherwise, if one match is found, the whole list of
+ * extensions would be shown when we only want the matching items to be
+ * found.
+ * @param {Event} e Change event.
+ * @private
+ */
+ searchChangedHandler_: function(e) {
+ var searchString = e.searchText;
+ var child = this.firstChild;
+ while (child) {
+ var extension = this.getExtensionWithId_(child.id);
+ if (searchString.length == 0) {
+ // Show all.
+ child.classList.remove('search-suppress');
+ } else {
+ // If the search string does not appear within the text of the
+ // extension, then hide it.
+ if ((extension.name.toLowerCase().indexOf(searchString) < 0) &&
+ (extension.version.toLowerCase().indexOf(searchString) < 0) &&
+ (extension.description.toLowerCase().indexOf(searchString) < 0)) {
+ // Hide yo extension!
+ child.classList.add('search-suppress');
+ } else {
+ // Show yourself!
+ child.classList.remove('search-suppress');
+ }
+ }
+ child = child.nextSibling;
+ }
+ },
+
+ /**
+ * Handles the Reload Extension functionality.
+ * @param {Event} e Change event.
+ * @private
+ */
+ handleReload_: function(e) {
+ var node = findIdNode(e.target);
+ chrome.send('extensionSettingsReload', [node.id]);
+ },
+
+ /**
+ * Handles the Show (Browser Action) Button functionality.
+ * @param {Event} e Change event.
+ * @private
+ */
+ handleShowButton_: function(e) {
+ var node = findIdNode(e.target);
+ chrome.send('extensionSettingsShowButton', [node.id]);
+ },
+
+ /**
+ * Handles the Enable/Disable Extension functionality.
+ * @param {Event} e Change event.
+ * @private
+ */
+ handleEnable_: function(e) {
+ var node = findIdNode(e.target.parentNode);
+ var extension = this.getExtensionWithId_(node.id);
+ chrome.send('extensionSettingsEnable',
+ [node.id, extension.enabled ? 'false' : 'true']);
+ chrome.send('extensionSettingsRequestExtensionsData');
+ },
+
+ /**
+ * Handles the Uninstall Extension functionality.
+ * @param {Event} e Change event.
+ * @private
+ */
+ handleUninstall_: function(e) {
+ var node = findIdNode(e.target.parentNode);
+ chrome.send('extensionSettingsUninstall', [node.id]);
+ chrome.send('extensionSettingsRequestExtensionsData');
+ },
+
+ /**
+ * Handles the View Options link.
+ * @param {Event} e Change event.
+ * @private
+ */
+ handleOptions_: function(e) {
+ var node = findIdNode(e.target.parentNode);
+ var extension = this.getExtensionWithId_(node.id);
+ chrome.send('extensionSettingsOptions', [extension.id]);
+ e.preventDefault();
+ },
+
+ /**
+ * Handles the Enable Extension In Incognito functionality.
+ * @param {Event} e Change event.
+ * @private
+ */
+ handleToggleEnableIncognito_: function(e) {
+ var node = findIdNode(e.target);
+ var butterBar = document.getElementById(node.id + '_incognitoWarning');
+ butterBar.hidden = !e.target.checked;
+ showingWarning[node.id] = e.target.checked;
+ chrome.send('extensionSettingsEnableIncognito',
+ [node.id, String(e.target.checked)]);
+ },
+
+ /**
+ * Handles the Allow On File URLs functionality.
+ * @param {Event} e Change event.
+ * @private
+ */
+ handleToggleAllowFileUrls_: function(e) {
+ var node = findIdNode(e.target);
+ chrome.send('extensionSettingsAllowFileAccess',
+ [node.id, String(e.target.checked)]);
+ },
+
+ /**
+ * Tell the C++ ExtensionDOMHandler to inspect the page detailed in
+ * |viewData|.
+ * @param {Event} e Change event.
+ * @private
+ */
+ sendInspectMessage_: function(e) {
+ var extension = this.getExtensionWithId_(e.srcElement.id);
+ for (var i = 0; i < extension.views.length; ++i) {
+ if (extension.views[i].path == e.srcElement.innerText) {
+ // TODO(aa): This is ghetto, but WebUIBindings doesn't support sending
+ // anything other than arrays of strings, and this is all going to get
+ // replaced with V8 extensions soon anyway.
+ chrome.send('extensionSettingsInspect', [
+ String(extension.views[i].renderProcessId),
+ String(extension.views[i].renderViewId)
+ ]);
+ }
+ }
+ },
+ };
+
+ return {
+ ExtensionsList: ExtensionsList
+ };
+});
diff --git a/chrome/browser/resources/options2/extension_settings.css b/chrome/browser/resources/options2/extension_settings.css
new file mode 100644
index 0000000..35ca397
--- /dev/null
+++ b/chrome/browser/resources/options2/extension_settings.css
@@ -0,0 +1,274 @@
+/*
+Copyright (c) 2011 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+*/
+
+.details-view {
+ -webkit-padding-end: 10px;
+}
+
+.extension-list-item {
+ padding-bottom: 7px;
+ padding-top: 7px;
+ width: 100%;
+ -webkit-user-select: auto;
+}
+
+/* Get rid of display: table, which causes width issues. */
+#extension-settings .displaytable {
+ display: block;
+}
+/* Get rid of display: table row, which causes width issues. */
+#extension-settings .displaytable > section {
+ display: block;
+}
+/* Get rid of display: table cell, which causes width issues. */
+#extension-settings .displaytable > section > * {
+ display: block;
+}
+
+.extension-settings-content {
+ border-bottom : 0px solid #eee;
+ margin-top: 3px;
+}
+
+#extension-settings-list {
+ min-height: 0;
+ overflow-y: hidden;
+}
+
+/* Get rid of the light-blue background on list item hover. */
+#extension-settings-list:not([disabled]) > :hover {
+ background-color: white;
+ border-color: #CDCDCD;
+}
+
+.butter-bar {
+ background: #FFF299;
+ padding: 2px 5px;
+ border-radius: 3px;
+ white-space: normal;
+}
+
+.search-suppress {
+ display: none;
+ height: 0;
+}
+
+.extension-list-item-collaped {
+ height: auto;
+ margin-bottom: 16px;
+ -webkit-transition: padding 300ms, overflow 300ms, opacity 700ms;
+}
+
+.extension-list-item-expanded {
+ height: auto;
+ margin-bottom: 16px;
+ overflow: visible;
+ -webkit-transition: padding 300ms, overflow 300ms, opacity 700ms;
+}
+
+.extension-settings {
+ overflow-x: hidden;
+}
+
+.extension-icon {
+ height: 48px;
+ vertical-align: text-top;
+ width: 48px;
+ -webkit-padding-start: 15px;
+ -webkit-padding-end: 15px;
+ -webkit-user-select: none;
+}
+
+.extension-title {
+ font-size: 16px;
+ font-weight: 500;
+ -webkit-padding-end: 20px;
+}
+
+.extension-version {
+ font-size: 13px;
+ font-weight: 400;
+ -webkit-padding-end: 7px;
+}
+
+.extension-description {
+ font-size: 13px;
+ white-space: normal;
+ -webkit-padding-end: 5px;
+}
+
+.extension-checkbox-span {
+ -webkit-margin-start: 7px;
+}
+
+.extension-checkbox-label {
+ -webkit-margin-end: 10px;
+}
+
+.extension-delete {
+ -webkit-margin-start: 5px;
+}
+
+.extension-details-hidden {
+ opacity: 0;
+ max-height: 0;
+ -webkit-transition: max-height 400ms, opacity 200ms;
+}
+
+.extension-details-visible {
+ opacity: 1;
+ max-height: 1000px;
+ -webkit-transition: max-height 200ms, opacity 300ms;
+}
+
+.extension-links-view {
+ -webkit-padding-start: 15px;
+}
+
+.extension-links-trailing {
+ -webkit-padding-end: 7px;
+}
+
+.extension-zippy-container {
+ cursor: pointer;
+ width: 20px;
+ -webkit-user-select: none;
+}
+
+.extension-warnings-title {
+ color: red;
+}
+
+.extension-warnings {
+ margin-top: 6px;
+}
+
+.extension-warnings ul {
+ margin: 0;
+}
+
+.extension-warnings > * {
+ white-space: normal;
+}
+
+.informative-text {
+ color: gray;
+}
+
+.extension-zippy-default {
+ background-image: url('zippy.png');
+ background-repeat: no-repeat;
+ background-position: center top;
+ position: absolute;
+ left: 12px;
+ top: 25px;
+ width: 6px;
+ height: 16px;
+ opacity: .25;
+}
+
+.extension-zippy-collapsed {
+ -webkit-transition: -webkit-transform 100ms;
+ -webkit-transform: rotate(0deg);
+}
+
+.extension-zippy-collapsed:hover {
+ opacity: .5;
+ -webkit-transform: rotate(5deg);
+ -webkit-transition: -webkit-transform 100ms, opacity 100ms;
+}
+
+.extension-zippy-expanded {
+ -webkit-transition: -webkit-transform 100ms;
+ -webkit-transform: rotate(90deg);
+}
+
+.extension-zippy-expanded:hover {
+ -webkit-transition: -webkit-transform 100ms;
+ -webkit-transform: rotate(85deg);
+}
+
+.extension-enabling {
+ position: relative;
+ top: 3px;
+}
+
+.extension-enabling-label {
+ color: black;
+ -webkit-padding-start: 3px;
+ -webkit-padding-end: 9px;
+}
+
+.extension-enabling-label-bold {
+ font-weight: bold;
+}
+
+.extension-inspect-table {
+ padding: 0;
+ border-spacing: 0;
+}
+
+.extension-inspect-left-column {
+ vertical-align: text-top;
+}
+
+/* Dev */
+
+.dev-open {
+ border-bottom: 1px solid rgb(205, 205, 205);
+ height: 32px;
+ padding-bottom: 7px;
+ padding-top: 13px;
+ -webkit-padding-start: 4px;
+ -webkit-padding-end: 3px;
+ -webkit-transition: padding 300ms, height 300ms, opacity 700ms;
+}
+.dev-closed {
+ height: 0;
+ opacity: 0;
+ padding-top: 9px;
+ -webkit-padding-start: 4px;
+ -webkit-padding-end: 3px;
+ -webkit-transition: padding 300ms, height 700ms, opacity 200ms;
+}
+
+.dev-button-visible {
+ display: inherit;
+ opacity: 1;
+ -webkit-transition: opacity 200ms;
+}
+
+.dev-button-hidden {
+ display: none;
+}
+
+#suggest-gallery {
+ -webkit-padding-start: 10px;
+}
+
+#dev-toggle {
+ display: block;
+ text-align: end;
+ margin-top: -28px;
+ -webkit-margin-end: 8px;
+}
+
+#get-more-extensions-container {
+ display: -webkit-box;
+}
+
+#get-more-extensions {
+ padding-top: 5px;
+ font-size: 15px;
+ -webkit-padding-start: 10px;
+}
+
+/* Support full keyboard accessibility without making things ugly
+ for users who click, by hiding some focus outlines when the user
+ clicks anywhere, but showing them when the user presses any key. */
+body.hide-some-focus-outlines .extension-zippy-container {
+ outline: none;
+}
diff --git a/chrome/browser/resources/options2/extension_settings.html b/chrome/browser/resources/options2/extension_settings.html
new file mode 100644
index 0000000..2858316
--- /dev/null
+++ b/chrome/browser/resources/options2/extension_settings.html
@@ -0,0 +1,40 @@
+<div id="extension-settings" class="page" hidden>
+ <h1 i18n-content="extensionSettings"></h1>
+ <div id="dev-toggle">
+ <input id="toggle-dev-on" type="checkbox" value="off"></input>
+ <label for="toggle-dev-on" i18n-content="extensionSettingsDeveloperMode" />
+ </div>
+ <div class="displaytable">
+ <div id="dev" class="dev-closed">
+ <table id="dev-table" width="100%">
+ <tr>
+ <td>
+ <button id="load-unpacked"
+ i18n-content="extensionSettingsLoadUnpackedButton"></button>
+ <button id="pack-extension"
+ i18n-content="extensionSettingsPackButton"></button>
+ </td>
+ <td align="right">
+ <button id="update-extensions-now"
+ i18n-content="extensionSettingsUpdateButton"></button>
+ </td>
+ </tr>
+ </table>
+ </div>
+ <section class="extension-settings-content">
+ <div class="extension-settings">
+ <list id="extension-settings-list"></list>
+ </div>
+ </section>
+ </div>
+ <section>
+ <div><strong id="no-extensions"
+ i18n-content="extensionSettingsNoExtensions"
+ hidden="true"></strong></div>
+ <div id="suggest-gallery" hidden="true"></div>
+ <div id="get-more-extensions-container" hidden="true">
+ <img src="chrome://theme/IDR_WEBSTORE_ICON_32">
+ <div id="get-more-extensions"></div>
+ </div>
+ </section>
+</div>
diff --git a/chrome/browser/resources/options2/extension_settings.js b/chrome/browser/resources/options2/extension_settings.js
new file mode 100644
index 0000000..e88ba85
--- /dev/null
+++ b/chrome/browser/resources/options2/extension_settings.js
@@ -0,0 +1,184 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Used for observing function of the backend datasource for this page by
+// tests.
+var webui_responded_ = false;
+
+cr.define('options', function() {
+ var OptionsPage = options.OptionsPage;
+ var ExtensionsList = options.ExtensionsList;
+
+ /**
+ * ExtensionSettings class
+ * Encapsulated handling of the 'Manage Extensions' page.
+ * @class
+ */
+ function ExtensionSettings() {
+ OptionsPage.call(this, 'extensions',
+ templateData.extensionSettingsTabTitle,
+ 'extension-settings');
+ }
+
+ cr.addSingletonGetter(ExtensionSettings);
+
+ ExtensionSettings.prototype = {
+ __proto__: OptionsPage.prototype,
+
+ /**
+ * Initialize the page.
+ */
+ initializePage: function() {
+ OptionsPage.prototype.initializePage.call(this);
+
+ // This will request the data to show on the page and will get a response
+ // back in returnExtensionsData.
+ chrome.send('extensionSettingsRequestExtensionsData');
+
+ // Set up the developer mode button.
+ var toggleDevMode = $('toggle-dev-on');
+ toggleDevMode.addEventListener('click',
+ this.handleToggleDevMode_.bind(this));
+
+ // Setup the gallery related links and text.
+ $('suggest-gallery').innerHTML =
+ localStrings.getString('extensionSettingsSuggestGallery');
+ $('get-more-extensions').innerHTML =
+ localStrings.getString('extensionSettingsGetMoreExtensions');
+
+ // Set up the three dev mode buttons (load unpacked, pack and update).
+ $('load-unpacked').addEventListener('click',
+ this.handleLoadUnpackedExtension_.bind(this));
+ $('pack-extension').addEventListener('click',
+ this.handlePackExtension_.bind(this));
+ $('update-extensions-now').addEventListener('click',
+ this.handleUpdateExtensionNow_.bind(this));
+ },
+
+ /**
+ * Utility function which asks the C++ to show a platform-specific file
+ * select dialog, and fire |callback| with the |filePath| that resulted.
+ * |selectType| can be either 'file' or 'folder'. |operation| can be 'load',
+ * 'packRoot', or 'pem' which are signals to the C++ to do some
+ * operation-specific configuration.
+ * @private
+ */
+ showFileDialog_: function(selectType, operation, callback) {
+ handleFilePathSelected = function(filePath) {
+ callback(filePath);
+ handleFilePathSelected = function() {};
+ };
+
+ chrome.send('extensionSettingsSelectFilePath', [selectType, operation]);
+ },
+
+ /**
+ * Handles the Load Unpacked Extension button.
+ * @param {Event} e Change event.
+ * @private
+ */
+ handleLoadUnpackedExtension_: function(e) {
+ this.showFileDialog_('folder', 'load', function(filePath) {
+ chrome.send('extensionSettingsLoad', [String(filePath)]);
+ });
+
+ chrome.send('coreOptionsUserMetricsAction',
+ ['Options_LoadUnpackedExtension']);
+ },
+
+ /**
+ * Handles the Pack Extension button.
+ * @param {Event} e Change event.
+ * @private
+ */
+ handlePackExtension_: function(e) {
+ OptionsPage.navigateToPage('packExtensionOverlay');
+ chrome.send('coreOptionsUserMetricsAction', ['Options_PackExtension']);
+ },
+
+ /**
+ * Handles the Update Extension Now button.
+ * @param {Event} e Change event.
+ * @private
+ */
+ handleUpdateExtensionNow_: function(e) {
+ chrome.send('extensionSettingsAutoupdate', []);
+ },
+
+ /**
+ * Handles the Toggle Dev Mode button.
+ * @param {Event} e Change event.
+ * @private
+ */
+ handleToggleDevMode_: function(e) {
+ var dev = $('dev');
+ if (!dev.classList.contains('dev-open')) {
+ // Make the Dev section visible.
+ dev.classList.add('dev-open');
+ dev.classList.remove('dev-closed');
+
+ $('load-unpacked').classList.add('dev-button-visible');
+ $('load-unpacked').classList.remove('dev-button-hidden');
+ $('pack-extension').classList.add('dev-button-visible');
+ $('pack-extension').classList.remove('dev-button-hidden');
+ $('update-extensions-now').classList.add('dev-button-visible');
+ $('update-extensions-now').classList.remove('dev-button-hidden');
+ } else {
+ // Hide the Dev section.
+ dev.classList.add('dev-closed');
+ dev.classList.remove('dev-open');
+
+ $('load-unpacked').classList.add('dev-button-hidden');
+ $('load-unpacked').classList.remove('dev-button-visible');
+ $('pack-extension').classList.add('dev-button-hidden');
+ $('pack-extension').classList.remove('dev-button-visible');
+ $('update-extensions-now').classList.add('dev-button-hidden');
+ $('update-extensions-now').classList.remove('dev-button-visible');
+ }
+
+ chrome.send('extensionSettingsToggleDeveloperMode', []);
+ },
+ };
+
+ /**
+ * Called by the dom_ui_ to re-populate the page with data representing
+ * the current state of installed extensions.
+ */
+ ExtensionSettings.returnExtensionsData = function(extensionsData) {
+ webui_responded_ = true;
+
+ $('no-extensions').hidden = true;
+ $('suggest-gallery').hidden = true;
+ $('get-more-extensions-container').hidden = true;
+
+ if (extensionsData.extensions.length > 0) {
+ // Enforce order specified in the data or (if equal) then sort by
+ // extension name (case-insensitive).
+ extensionsData.extensions.sort(function(a, b) {
+ if (a.order == b.order) {
+ a = a.name.toLowerCase();
+ b = b.name.toLowerCase();
+ return a < b ? -1 : (a > b ? 1 : 0);
+ } else {
+ return a.order < b.order ? -1 : 1;
+ }
+ });
+
+ $('get-more-extensions-container').hidden = false;
+ } else {
+ $('no-extensions').hidden = false;
+ $('suggest-gallery').hidden = false;
+ }
+
+ ExtensionsList.prototype.data_ = extensionsData;
+
+ var extensionList = $('extension-settings-list');
+ ExtensionsList.decorate(extensionList);
+ }
+
+ // Export
+ return {
+ ExtensionSettings: ExtensionSettings
+ };
+});
diff --git a/chrome/browser/resources/options2/font_settings.css b/chrome/browser/resources/options2/font_settings.css
new file mode 100644
index 0000000..6d90dbb
--- /dev/null
+++ b/chrome/browser/resources/options2/font_settings.css
@@ -0,0 +1,41 @@
+#font-settings > section {
+ overflow: hidden;
+}
+
+#font-settings input[type="range"] {
+ width: 100%;
+}
+
+#minimum-font-sample {
+ height: 35px;
+ overflow: hidden;
+ width: 270px;
+}
+
+.font-input-div {
+ -webkit-margin-end: 3em;
+ width: 12em;
+}
+
+.font-input-div > div > select {
+ margin-bottom: 10px;
+}
+
+.font-input {
+ width: 100%;
+}
+
+.font-sample-div {
+ height: 70px;
+ overflow: hidden;
+ width: 270px;
+ direction: ltr;
+}
+
+.font-settings-huge {
+ float: right;
+}
+
+html[dir=rtl] .font-settings-huge {
+ float: left;
+}
diff --git a/chrome/browser/resources/options2/font_settings.html b/chrome/browser/resources/options2/font_settings.html
new file mode 100644
index 0000000..c025230
--- /dev/null
+++ b/chrome/browser/resources/options2/font_settings.html
@@ -0,0 +1,82 @@
+<div id="font-settings" class="page" hidden>
+ <h1 i18n-content="fontSettingsPage"></h1>
+ <section>
+ <h3 i18n-content="fontSettingsStandard"></h3>
+ <div class="font-input-div">
+ <div>
+ <select id="standard-font-family" class="font-input" data-type="string"
+ pref="webkit.webprefs.standard_font_family"
+ metric="Options_ChangeStandardFont"></select>
+ </div>
+ <div>
+ <input id="standard-font-size" type="range" min="0" max="24"
+ pref="webkit.webprefs.default_font_size">
+ <div>
+ <span i18n-content="fontSettingsSizeTiny"></span>
+ <span i18n-content="fontSettingsSizeHuge" class="font-settings-huge">
+ </span>
+ </div>
+ </div>
+ </div>
+ <div id="standard-font-sample" class="font-sample-div"></div>
+ </section>
+ <section>
+ <h3 i18n-content="fontSettingsSerif"></h3>
+ <div class="font-input-div">
+ <div>
+ <select id="serif-font-family" class="font-input" data-type="string"
+ pref="webkit.webprefs.serif_font_family"
+ metric="Options_ChangeSerifFont"></select>
+ </div>
+ </div>
+ <div id="serif-font-sample" class="font-sample-div"></div>
+ </section>
+ <section>
+ <h3 i18n-content="fontSettingsSansSerif"></h3>
+ <div class="font-input-div">
+ <div>
+ <select id="sans-serif-font-family" class="font-input"
+ data-type="string"
+ pref="webkit.webprefs.sansserif_font_family"
+ metric="Options_ChangeSansSerifFont"></select>
+ </div>
+ </div>
+ <div id="sans-serif-font-sample" class="font-sample-div"></div>
+ </section>
+ <section>
+ <h3 i18n-content="fontSettingsFixedWidth"></h3>
+ <div class="font-input-div">
+ <div>
+ <select id="fixed-font-family" class="font-input" data-type="string"
+ pref="webkit.webprefs.fixed_font_family"
+ metric="Options_ChangeFixedFont"></select>
+ </div>
+ </div>
+ <div id="fixed-font-sample" class="font-sample-div"></div>
+ </section>
+ <section>
+ <h3 i18n-content="fontSettingsMinimumSize"></h3>
+ <div class="font-input-div">
+ <div>
+ <input id="minimum-font-size" type="range" min="0" max="15"
+ pref="webkit.webprefs.minimum_font_size">
+ <div>
+ <span i18n-content="fontSettingsSizeTiny"></span>
+ <span i18n-content="fontSettingsSizeHuge" class="font-settings-huge">
+ </span>
+ </div>
+ </div>
+ </div>
+ <div id="minimum-font-sample" class="font-sample-div"></div>
+ </section>
+ <section>
+ <h3 i18n-content="fontSettingsEncoding"></h3>
+ <div class="font-input-div">
+ <div>
+ <select id="font-encoding" data-type="string"
+ pref="intl.charset_default"
+ metric="Options_ChangeFontEncoding"></select>
+ </div>
+ </div>
+ </section>
+</div>
diff --git a/chrome/browser/resources/options2/font_settings.js b/chrome/browser/resources/options2/font_settings.js
new file mode 100644
index 0000000..c00f525
--- /dev/null
+++ b/chrome/browser/resources/options2/font_settings.js
@@ -0,0 +1,234 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+
+ var OptionsPage = options.OptionsPage;
+
+ /**
+ * This is the absolute difference maintained between standard and
+ * fixed-width font sizes. Refer http://crbug.com/91922.
+ */
+ const SIZE_DIFFERENCE_FIXED_STANDARD = 3;
+
+ /**
+ * FontSettings class
+ * Encapsulated handling of the 'Fonts and Encoding' page.
+ * @class
+ */
+ function FontSettings() {
+ OptionsPage.call(this,
+ 'fonts',
+ templateData.fontSettingsPageTabTitle,
+ 'font-settings');
+ }
+
+ cr.addSingletonGetter(FontSettings);
+
+ FontSettings.prototype = {
+ __proto__: OptionsPage.prototype,
+
+ /**
+ * Initialize the page.
+ */
+ initializePage: function() {
+ OptionsPage.prototype.initializePage.call(this);
+
+ var standardFontRange = $('standard-font-size');
+ standardFontRange.valueMap = [9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 20,
+ 22, 24, 26, 28, 30, 32, 34, 36, 40, 44, 48, 56, 64, 72];
+ standardFontRange.continuous = false;
+ standardFontRange.notifyChange = this.standardRangeChanged_.bind(this);
+ standardFontRange.notifyPrefChange =
+ this.standardFontSizeChanged_.bind(this);
+
+ var minimumFontRange = $('minimum-font-size');
+ minimumFontRange.valueMap = [6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,
+ 18, 20, 22, 24];
+ minimumFontRange.continuous = false;
+ minimumFontRange.notifyChange = this.minimumRangeChanged_.bind(this);
+ minimumFontRange.notifyPrefChange =
+ this.minimumFontSizeChanged_.bind(this);
+
+ var placeholder = localStrings.getString('fontSettingsPlaceholder');
+ var elements = [$('standard-font-family'), $('serif-font-family'),
+ $('sans-serif-font-family'), $('fixed-font-family'),
+ $('font-encoding')];
+ elements.forEach(function(el) {
+ el.appendChild(new Option(placeholder));
+ el.setDisabled('noFontsAvailable', true);
+ });
+ },
+
+ /**
+ * Called by the options page when this page has been shown.
+ */
+ didShowPage: function() {
+ // The fonts list may be large so we only load it when this page is
+ // loaded for the first time. This makes opening the options window
+ // faster and consume less memory if the user never opens the fonts
+ // dialog.
+ if (!this.hasShown) {
+ chrome.send('fetchFontsData');
+ this.hasShown = true;
+ }
+ },
+
+ /**
+ * Called as the user changes the standard font size. This allows for
+ * reflecting the change in the UI before the preference has been changed.
+ * @param {Element} el The slider input element.
+ * @param {number} value The mapped value currently set by the slider.
+ * @private
+ */
+ standardRangeChanged_: function(el, value) {
+ var fontSampleEl = $('standard-font-sample');
+ this.setUpFontSample_(fontSampleEl, value, fontSampleEl.style.fontFamily,
+ true);
+
+ fontSampleEl = $('serif-font-sample');
+ this.setUpFontSample_(fontSampleEl, value, fontSampleEl.style.fontFamily,
+ true);
+
+ fontSampleEl = $('sans-serif-font-sample');
+ this.setUpFontSample_(fontSampleEl, value, fontSampleEl.style.fontFamily,
+ true);
+
+ fontSampleEl = $('fixed-font-sample');
+ this.setUpFontSample_(fontSampleEl,
+ value - SIZE_DIFFERENCE_FIXED_STANDARD,
+ fontSampleEl.style.fontFamily, false);
+ },
+
+ /**
+ * Sets the 'default_fixed_font_size' preference when the standard font
+ * size has been changed by the user.
+ * @param {Element} el The slider input element.
+ * @param {number} value The mapped value that has been saved.
+ * @private
+ */
+ standardFontSizeChanged_: function(el, value) {
+ Preferences.setIntegerPref('webkit.webprefs.default_fixed_font_size',
+ value - SIZE_DIFFERENCE_FIXED_STANDARD, '');
+ },
+
+ /**
+ * Called as the user changes the miniumum font size. This allows for
+ * reflecting the change in the UI before the preference has been changed.
+ * @param {Element} el The slider input element.
+ * @param {number} value The mapped value currently set by the slider.
+ * @private
+ */
+ minimumRangeChanged_: function(el, value) {
+ var fontSampleEl = $('minimum-font-sample');
+ this.setUpFontSample_(fontSampleEl, value, fontSampleEl.style.fontFamily,
+ true);
+ },
+
+ /**
+ * Sets the 'minimum_logical_font_size' preference when the minimum font
+ * size has been changed by the user.
+ * @param {Element} el The slider input element.
+ * @param {number} value The mapped value that has been saved.
+ * @private
+ */
+ minimumFontSizeChanged_: function(el, value) {
+ Preferences.setIntegerPref('webkit.webprefs.minimum_logical_font_size',
+ value, '');
+ },
+
+ /**
+ * Sets the text, font size and font family of the sample text.
+ * @param {Element} el The div containing the sample text.
+ * @param {number} size The font size of the sample text.
+ * @param {string} font The font family of the sample text.
+ * @param {bool} showSize True if the font size should appear in the sample.
+ * @private
+ */
+ setUpFontSample_: function(el, size, font, showSize) {
+ var prefix = showSize ? (size + ': ') : '';
+ el.textContent = prefix +
+ localStrings.getString('fontSettingsLoremIpsum');
+ el.style.fontSize = size + 'px';
+ if (font)
+ el.style.fontFamily = font;
+ },
+
+ /**
+ * Populates a select list and selects the specified item.
+ * @param {Element} element The select element to populate.
+ * @param {Array} items The array of items from which to populate.
+ * @param {string} selectedValue The selected item.
+ * @private
+ */
+ populateSelect_: function(element, items, selectedValue) {
+ // Remove any existing content.
+ element.textContent = '';
+
+ // Insert new child nodes into select element.
+ var value, text, selected, option;
+ for (var i = 0; i < items.length; i++) {
+ value = items[i][0];
+ text = items[i][1];
+ if (text) {
+ selected = value == selectedValue;
+ element.appendChild(new Option(text, value, false, selected));
+ } else {
+ element.appendChild(document.createElement('hr'));
+ }
+ }
+
+ element.setDisabled('noFontsAvailable', false);
+ }
+ };
+
+ // Chrome callbacks
+ FontSettings.setFontsData = function(fonts, encodings, selectedValues) {
+ FontSettings.getInstance().populateSelect_($('standard-font-family'), fonts,
+ selectedValues[0]);
+ FontSettings.getInstance().populateSelect_($('serif-font-family'), fonts,
+ selectedValues[1]);
+ FontSettings.getInstance().populateSelect_($('sans-serif-font-family'),
+ fonts, selectedValues[2]);
+ FontSettings.getInstance().populateSelect_($('fixed-font-family'), fonts,
+ selectedValues[3]);
+ FontSettings.getInstance().populateSelect_($('font-encoding'), encodings,
+ selectedValues[4]);
+ };
+
+ FontSettings.setUpStandardFontSample = function(font, size) {
+ FontSettings.getInstance().setUpFontSample_($('standard-font-sample'), size,
+ font, true);
+ };
+
+ FontSettings.setUpSerifFontSample = function(font, size) {
+ FontSettings.getInstance().setUpFontSample_($('serif-font-sample'), size,
+ font, true);
+ };
+
+ FontSettings.setUpSansSerifFontSample = function(font, size) {
+ FontSettings.getInstance().setUpFontSample_($('sans-serif-font-sample'),
+ size, font, true);
+ };
+
+ FontSettings.setUpFixedFontSample = function(font, size) {
+ FontSettings.getInstance().setUpFontSample_($('fixed-font-sample'),
+ size, font, false);
+ };
+
+ FontSettings.setUpMinimumFontSample = function(size) {
+ // If size is less than 6, represent it as six in the sample to account
+ // for the minimum logical font size.
+ if (size < 6)
+ size = 6;
+ FontSettings.getInstance().setUpFontSample_($('minimum-font-sample'), size,
+ null, true);
+ };
+
+ // Export
+ return {
+ FontSettings: FontSettings
+ };
+});
+
diff --git a/chrome/browser/resources/options2/handler_options.css b/chrome/browser/resources/options2/handler_options.css
new file mode 100644
index 0000000..0f23446
--- /dev/null
+++ b/chrome/browser/resources/options2/handler_options.css
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2011 The Chromium Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+.handlers-column-headers {
+ display: -webkit-box;
+ font-size: 13px;
+ font-weight: bold;
+}
+
+.handlers-type-column {
+ width: 100px;
+ -webkit-margin-end: 10px;
+ -webkit-margin-start: 14px;
+}
+
+.handlers-site-column {
+ max-width: 180px;
+}
+
+.handlers-site-column select {
+ max-width: 170px;
+}
+
+.handlers-remove-column {
+ -webkit-box-flex: 1;
+}
+
+.handlers-remove-link {
+ color: #555;
+ cursor: pointer;
+ opacity: 0;
+ padding-left: 14px;
+ text-decoration: underline;
+ -webkit-transition: 150ms opacity;
+}
+
+div > .handlers-remove-column {
+ opacity: 0;
+}
+
+div:not(.none):hover > .handlers-remove-column {
+ opacity: 1;
+}
+
+#handlers {
+ min-height: 250px;
+}
+
+#handler-options list {
+ border-radius: 2px;
+ border: solid 1px #D9D9D9;
+ margin-bottom: 10px;
+ margin-top: 4px;
+}
diff --git a/chrome/browser/resources/options2/handler_options.html b/chrome/browser/resources/options2/handler_options.html
new file mode 100644
index 0000000..21284ca
--- /dev/null
+++ b/chrome/browser/resources/options2/handler_options.html
@@ -0,0 +1,28 @@
+<div id="handler-options" class="page" hidden>
+ <h1 i18n-content="handlersPage"></h1>
+ <h3 i18n-content="handlers_active_heading"></h3>
+ <div class="handlers-column-headers">
+ <div class="handlers-type-column">
+ <div i18n-content="handlers_type_column_header"></div>
+ </div>
+ <div class="handlers-site-column">
+ <div i18n-content="handlers_site_column_header"></div>
+ </div>
+ <div class="handlers-remove-column"></div>
+ </div>
+ <list id="handlers-list"></list>
+
+ <div id="ignored-handlers-section">
+ <h3 i18n-content="handlers_ignored_heading"></h3>
+ <div class="handlers-column-headers">
+ <div class="handlers-type-column">
+ <h3 i18n-content="handlers_type_column_header"></h3>
+ </div>
+ <div class="handlers-site-column">
+ <h3 i18n-content="handlers_site_column_header"></h3>
+ </div>
+ <div class="handlers-remove-column"></div>
+ </div>
+ <list id="ignored-handlers-list"></list>
+ </div>
+</div>
diff --git a/chrome/browser/resources/options2/handler_options.js b/chrome/browser/resources/options2/handler_options.js
new file mode 100644
index 0000000..039d9eb
--- /dev/null
+++ b/chrome/browser/resources/options2/handler_options.js
@@ -0,0 +1,77 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+ const OptionsPage = options.OptionsPage;
+
+ /////////////////////////////////////////////////////////////////////////////
+ // HandlerOptions class:
+
+ /**
+ * Encapsulated handling of handler options page.
+ * @constructor
+ */
+ function HandlerOptions() {
+ this.activeNavTab = null;
+ OptionsPage.call(this,
+ 'handlers',
+ templateData.handlersPageTabTitle,
+ 'handler-options');
+ }
+
+ cr.addSingletonGetter(HandlerOptions);
+
+ HandlerOptions.prototype = {
+ __proto__: OptionsPage.prototype,
+
+ /**
+ * The handlers list.
+ * @type {DeletableItemList}
+ * @private
+ */
+ handlersList_: null,
+
+ /** @inheritDoc */
+ initializePage: function() {
+ OptionsPage.prototype.initializePage.call(this);
+
+ this.createHandlersList_();
+ },
+
+ /**
+ * Creates, decorates and initializes the handlers list.
+ * @private
+ */
+ createHandlersList_: function() {
+ this.handlersList_ = $('handlers-list');
+ options.HandlersList.decorate(this.handlersList_);
+ this.handlersList_.autoExpands = true;
+
+ this.ignoredHandlersList_ = $('ignored-handlers-list');
+ options.IgnoredHandlersList.decorate(this.ignoredHandlersList_);
+ this.ignoredHandlersList_.autoExpands = true;
+ },
+ };
+
+ /**
+ * Sets the list of handlers shown by the view.
+ * @param handlers to be shown in the view.
+ */
+ HandlerOptions.setHandlers = function(handlers) {
+ $('handlers-list').setHandlers(handlers);
+ };
+
+ /**
+ * Sets the list of ignored handlers shown by the view.
+ * @param handlers to be shown in the view.
+ */
+ HandlerOptions.setIgnoredHandlers = function(handlers) {
+ $('ignored-handlers-section').hidden = handlers.length == 0;
+ $('ignored-handlers-list').setHandlers(handlers);
+ };
+
+ return {
+ HandlerOptions: HandlerOptions
+ };
+});
diff --git a/chrome/browser/resources/options2/handler_options_list.js b/chrome/browser/resources/options2/handler_options_list.js
new file mode 100644
index 0000000..661956c
--- /dev/null
+++ b/chrome/browser/resources/options2/handler_options_list.js
@@ -0,0 +1,229 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+ const ArrayDataModel = cr.ui.ArrayDataModel;
+ const List = cr.ui.List;
+ const ListItem = cr.ui.ListItem;
+ const HandlerOptions = options.HandlerOptions;
+ const DeletableItem = options.DeletableItem;
+ const DeletableItemList = options.DeletableItemList;
+
+ const localStrings = new LocalStrings();
+
+ /**
+ * Creates a new ignored protocol / content handler list item.
+ *
+ * Accepts values in the form
+ * ['mailto', 'http://www.thesite.com/%s', 'The title of the protocol'],
+ * @param {Object} entry A dictionary describing the handlers for a given
+ * protocol.
+ * @constructor
+ * @extends {cr.ui.DeletableItemList}
+ */
+ function IgnoredHandlersListItem(entry) {
+ var el = cr.doc.createElement('div');
+ el.dataItem = entry;
+ el.__proto__ = IgnoredHandlersListItem.prototype;
+ el.decorate();
+ return el;
+ }
+
+ IgnoredHandlersListItem.prototype = {
+ __proto__: DeletableItem.prototype,
+
+ /** @inheritDoc */
+ decorate: function() {
+ DeletableItem.prototype.decorate.call(this);
+
+ // Protocol.
+ var protocolElement = document.createElement('div');
+ protocolElement.textContent = this.dataItem[0];
+ protocolElement.className = 'handlers-type-column';
+ this.contentElement_.appendChild(protocolElement);
+
+ // Site title.
+ var titleElement = document.createElement('div');
+ titleElement.textContent = this.dataItem[2];
+ titleElement.className = 'handlers-site-column';
+ titleElement.title = this.dataItem[1];
+ this.contentElement_.appendChild(titleElement);
+ },
+ };
+
+
+ var IgnoredHandlersList = cr.ui.define('list');
+
+ IgnoredHandlersList.prototype = {
+ __proto__: DeletableItemList.prototype,
+
+ createItem: function(entry) {
+ return new IgnoredHandlersListItem(entry);
+ },
+
+ deleteItemAtIndex: function(index) {
+ chrome.send('removeIgnoredHandler', [this.dataModel.item(index)]);
+ },
+
+ /**
+ * The length of the list.
+ */
+ get length() {
+ return this.dataModel.length;
+ },
+
+ /**
+ * Set the protocol handlers displayed by this list. See
+ * IgnoredHandlersListItem for an example of the format the list should
+ * take.
+ *
+ * @param {Object} list A list of ignored protocol handlers.
+ */
+ setHandlers: function(list) {
+ this.dataModel = new ArrayDataModel(list);
+ },
+ };
+
+
+
+ /**
+ * Creates a new protocol / content handler list item.
+ *
+ * Accepts values in the form
+ * { protocol: 'mailto',
+ * handlers: [
+ * ['mailto', 'http://www.thesite.com/%s', 'The title of the protocol'],
+ * ...,
+ * ],
+ * }
+ * @param {Object} entry A dictionary describing the handlers for a given
+ * protocol.
+ * @constructor
+ * @extends {cr.ui.ListItem}
+ */
+ function HandlerListItem(entry) {
+ var el = cr.doc.createElement('div');
+ el.dataItem = entry;
+ el.__proto__ = HandlerListItem.prototype;
+ el.decorate();
+ return el;
+ }
+
+ HandlerListItem.prototype = {
+ __proto__: ListItem.prototype,
+
+ buildWidget_: function(data, delegate) {
+ // Protocol.
+ var protocolElement = document.createElement('div');
+ protocolElement.textContent = data.protocol;
+ protocolElement.className = 'handlers-type-column';
+ this.appendChild(protocolElement);
+
+ // Handler selection.
+ var handlerElement = document.createElement('div');
+ var selectElement = document.createElement('select');
+ var defaultOptionElement = document.createElement('option');
+ defaultOptionElement.selected = data.default_handler == -1;
+ defaultOptionElement.textContent =
+ localStrings.getString('handlers_none_handler');
+ defaultOptionElement.value = -1;
+ selectElement.appendChild(defaultOptionElement);
+
+ for (var i = 0; i < data.handlers.length; ++i) {
+ var optionElement = document.createElement('option');
+ optionElement.selected = i == data.default_handler;
+ optionElement.textContent = data.handlers[i][2];
+ optionElement.value = i;
+ selectElement.appendChild(optionElement);
+ }
+
+ selectElement.addEventListener('change', function (e) {
+ var index = e.target.value;
+ if (index == -1) {
+ this.classList.add('none');
+ delegate.clearDefault(data.protocol);
+ } else {
+ handlerElement.classList.remove('none');
+ delegate.setDefault(data.handlers[index]);
+ }
+ });
+ handlerElement.appendChild(selectElement);
+ handlerElement.className = 'handlers-site-column';
+ if (data.default_handler == -1)
+ this.classList.add('none');
+ this.appendChild(handlerElement);
+
+ // Remove link.
+ var removeElement = document.createElement('div');
+ removeElement.textContent =
+ localStrings.getString('handlers_remove_link');
+ removeElement.addEventListener('click', function (e) {
+ var value = selectElement ? selectElement.value : 0;
+ delegate.removeHandler(value, data.handlers[value]);
+ });
+ removeElement.className = 'handlers-remove-column handlers-remove-link';
+ this.appendChild(removeElement);
+ },
+
+ /** @inheritDoc */
+ decorate: function() {
+ ListItem.prototype.decorate.call(this);
+
+ var self = this;
+ var delegate = {
+ removeHandler: function(index, handler) {
+ chrome.send('removeHandler', [handler]);
+ },
+ setDefault: function(handler) {
+ chrome.send('setDefault', [handler]);
+ },
+ clearDefault: function(protocol) {
+ chrome.send('clearDefault', [protocol]);
+ },
+ };
+
+ this.buildWidget_(this.dataItem, delegate);
+ },
+ };
+
+ /**
+ * Create a new passwords list.
+ * @constructor
+ * @extends {cr.ui.List}
+ */
+ var HandlersList = cr.ui.define('list');
+
+ HandlersList.prototype = {
+ __proto__: List.prototype,
+
+ /** @inheritDoc */
+ createItem: function(entry) {
+ return new HandlerListItem(entry);
+ },
+
+ /**
+ * The length of the list.
+ */
+ get length() {
+ return this.dataModel.length;
+ },
+
+ /**
+ * Set the protocol handlers displayed by this list.
+ * See HandlerListItem for an example of the format the list should take.
+ *
+ * @param {Object} list A list of protocols with their registered handlers.
+ */
+ setHandlers: function(list) {
+ this.dataModel = new ArrayDataModel(list);
+ },
+ };
+
+ return {
+ IgnoredHandlersListItem: IgnoredHandlersListItem,
+ IgnoredHandlersList: IgnoredHandlersList,
+ HandlerListItem: HandlerListItem,
+ HandlersList: HandlersList,
+ };
+});
diff --git a/chrome/browser/resources/options2/import_data_overlay.css b/chrome/browser/resources/options2/import_data_overlay.css
new file mode 100644
index 0000000..9db63f8
--- /dev/null
+++ b/chrome/browser/resources/options2/import_data_overlay.css
@@ -0,0 +1,23 @@
+#import-from-div {
+ margin-bottom: 20px;
+}
+
+#import-checkboxes > div:not(:first-child) {
+ -webkit-padding-start: 8px;
+ margin: 5px 0;
+}
+
+#import-throbber {
+ margin: 4px 10px;
+ vertical-align: middle;
+ visibility: hidden;
+}
+
+#import-success-header {
+ font-size: 1.2em;
+}
+
+#import-success-image {
+ text-align: center;
+ margin: 20px;
+}
diff --git a/chrome/browser/resources/options2/import_data_overlay.html b/chrome/browser/resources/options2/import_data_overlay.html
new file mode 100644
index 0000000..355eced
--- /dev/null
+++ b/chrome/browser/resources/options2/import_data_overlay.html
@@ -0,0 +1,70 @@
+<div id="import-data-overlay" class="page" hidden>
+ <h1 i18n-content="importDataOverlay"></h1>
+ <div id="import-data-configure">
+ <div class="content-area">
+ <div id="import-from-div">
+ <span i18n-content="importFromLabel"></span>
+ <select id="import-browsers">
+ <option i18n-content="importLoading"></option>
+ </select>
+ </div>
+ <div id="import-checkboxes">
+ <div i18n-content="importDescription"></div>
+ <div>
+ <input id="import-history" type="checkbox" pref="import_history">
+ <label for="import-history" i18n-content="importHistory"></label>
+ </div>
+ <div>
+ <input id="import-favorites" type="checkbox" pref="import_bookmarks">
+ <label for="import-favorites" i18n-content="importFavorites"></label>
+ </div>
+ <div>
+ <input id="import-passwords" type="checkbox"
+ pref="import_saved_passwords">
+ <label for="import-passwords" i18n-content="importPasswords"></label>
+ </div>
+ <div>
+ <input id="import-search" type="checkbox" pref="import_search_engine">
+ <label for="import-search" i18n-content="importSearch"></label>
+ </div>
+ </div>
+ </div>
+ <div class="action-area">
+ <div class="action-area-right">
+ <div id="import-throbber" class="throbber"></div>
+ <div class="button-strip">
+ <button id="import-data-cancel" i18n-content="cancel"></button>
+ <button id="import-data-commit" i18n-content="importCommit"></button>
+ </div>
+ </div>
+ </div>
+ </div>
+ <div id="import-data-success" hidden>
+ <div class="content-area">
+ <div id="import-success-header">
+ <strong i18n-content="importSucceeded"></strong>
+ </div>
+ <div id="import-success-image">
+ <img src="success-large.png" />
+ </div>
+ <div id="import-find-your-bookmarks">
+ <span i18n-content="findYourImportedBookmarks"></span>
+ <div class="checkbox">
+ <label>
+ <input id="import-data-show-bookmarks-bar"
+ pref="bookmark_bar.show_on_all_tabs"
+ metric="Options_ShowBookmarksBar" type="checkbox">
+ <span i18n-content="toolbarShowBookmarksBar"></span>
+ </label>
+ </div>
+ </div>
+ </div>
+ <div class="action-area">
+ <div class="action-area-right">
+ <div class="button-strip">
+ <button id="import-data-confirm" i18n-content="ok"></button>
+ </div>
+ </div>
+ </div>
+ </div>
+</div>
diff --git a/chrome/browser/resources/options2/import_data_overlay.js b/chrome/browser/resources/options2/import_data_overlay.js
new file mode 100644
index 0000000..2671aad
--- /dev/null
+++ b/chrome/browser/resources/options2/import_data_overlay.js
@@ -0,0 +1,222 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+ var OptionsPage = options.OptionsPage;
+
+ /**
+ * ImportDataOverlay class
+ * Encapsulated handling of the 'Import Data' overlay page.
+ * @class
+ */
+ function ImportDataOverlay() {
+ OptionsPage.call(this,
+ 'importData',
+ templateData.importDataOverlayTabTitle,
+ 'import-data-overlay');
+ }
+
+ cr.addSingletonGetter(ImportDataOverlay);
+
+ ImportDataOverlay.prototype = {
+ // Inherit from OptionsPage.
+ __proto__: OptionsPage.prototype,
+
+ /**
+ * Initialize the page.
+ */
+ initializePage: function() {
+ // Call base class implementation to start preference initialization.
+ OptionsPage.prototype.initializePage.call(this);
+
+ var self = this;
+ var checkboxes =
+ document.querySelectorAll('#import-checkboxes input[type=checkbox]');
+ for (var i = 0; i < checkboxes.length; i++) {
+ checkboxes[i].onchange = function() {
+ self.validateCommitButton_();
+ };
+ }
+
+ $('import-browsers').onchange = function() {
+ self.updateCheckboxes_();
+ self.validateCommitButton_();
+ };
+
+ $('import-data-commit').onclick = function() {
+ chrome.send('importData', [
+ String($('import-browsers').selectedIndex),
+ String($('import-history').checked),
+ String($('import-favorites').checked),
+ String($('import-passwords').checked),
+ String($('import-search').checked)]);
+ };
+
+ $('import-data-cancel').onclick = function() {
+ ImportDataOverlay.dismiss();
+ };
+
+ $('import-data-show-bookmarks-bar').onchange = function() {
+ // Note: The callback 'toggleShowBookmarksBar' is handled within the
+ // browser options handler -- rather than the import data handler --
+ // as the implementation is shared by several clients.
+ chrome.send('toggleShowBookmarksBar');
+ }
+
+ $('import-data-confirm').onclick = function() {
+ ImportDataOverlay.dismiss();
+ };
+
+ // Form controls are disabled until the profile list has been loaded.
+ self.setControlsSensitive_(false);
+ },
+
+ /**
+ * Set enabled and checked state of the commit button.
+ * @private
+ */
+ validateCommitButton_: function() {
+ var somethingToImport =
+ $('import-history').checked || $('import-favorites').checked ||
+ $('import-passwords').checked || $('import-search').checked;
+ $('import-data-commit').disabled = !somethingToImport;
+ },
+
+ /**
+ * Sets the sensitivity of all the checkboxes and the commit button.
+ * @private
+ */
+ setControlsSensitive_: function(sensitive) {
+ var checkboxes =
+ document.querySelectorAll('#import-checkboxes input[type=checkbox]');
+ for (var i = 0; i < checkboxes.length; i++)
+ this.setUpCheckboxState_(checkboxes[i], sensitive);
+ $('import-data-commit').disabled = !sensitive;
+ },
+
+ /**
+ * Set enabled and checked states a checkbox element.
+ * @param {Object} checkbox A checkbox element.
+ * @param {boolean} enabled The enabled state of the chekbox.
+ * @private
+ */
+ setUpCheckboxState_: function(checkbox, enabled) {
+ checkbox.setDisabled("noProfileData", !enabled);
+ },
+
+ /**
+ * Update the enabled and checked states of all checkboxes.
+ * @private
+ */
+ updateCheckboxes_: function() {
+ var index = $('import-browsers').selectedIndex;
+ var browserProfile;
+ if (this.browserProfiles.length > index)
+ browserProfile = this.browserProfiles[index];
+ var importOptions = ['history', 'favorites', 'passwords', 'search'];
+ for (var i = 0; i < importOptions.length; i++) {
+ var checkbox = $('import-' + importOptions[i]);
+ var enable = browserProfile && browserProfile[importOptions[i]];
+ this.setUpCheckboxState_(checkbox, enable);
+ }
+ },
+
+ /**
+ * Update the supported browsers popup with given entries.
+ * @param {array} browsers List of supported browsers name.
+ * @private
+ */
+ updateSupportedBrowsers_: function(browsers) {
+ this.browserProfiles = browsers;
+ var browserSelect = $('import-browsers');
+ browserSelect.remove(0); // Remove the 'Loading...' option.
+ browserSelect.textContent = '';
+ var browserCount = browsers.length;
+
+ if (browserCount == 0) {
+ var option = new Option(templateData.noProfileFound, 0);
+ browserSelect.appendChild(option);
+
+ this.setControlsSensitive_(false);
+ } else {
+ this.setControlsSensitive_(true);
+ for (var i = 0; i < browserCount; i++) {
+ var browser = browsers[i]
+ var option = new Option(browser['name'], browser['index']);
+ browserSelect.appendChild(option);
+ }
+
+ this.updateCheckboxes_();
+ this.validateCommitButton_();
+ }
+ },
+
+ /**
+ * Clear import prefs set when user checks/unchecks a checkbox so that each
+ * checkbox goes back to the default "checked" state (or alternatively, to
+ * the state set by a recommended policy).
+ * @private
+ */
+ clearUserPrefs_: function() {
+ var importPrefs = ['import_history',
+ 'import_bookmarks',
+ 'import_saved_passwords',
+ 'import_search_engine'];
+ for (var i = 0; i < importPrefs.length; i++)
+ Preferences.clearPref(importPrefs[i], undefined);
+ },
+ };
+
+ ImportDataOverlay.clearUserPrefs = function() {
+ ImportDataOverlay.getInstance().clearUserPrefs_();
+ };
+
+ /**
+ * Update the supported browsers popup with given entries.
+ * @param {array} list of supported browsers name.
+ */
+ ImportDataOverlay.updateSupportedBrowsers = function(browsers) {
+ ImportDataOverlay.getInstance().updateSupportedBrowsers_(browsers);
+ };
+
+ /**
+ * Update the UI to reflect whether an import operation is in progress.
+ * @param {boolean} state True if an import operation is in progress.
+ */
+ ImportDataOverlay.setImportingState = function(state) {
+ var checkboxes =
+ document.querySelectorAll('#import-checkboxes input[type=checkbox]');
+ for (var i = 0; i < checkboxes.length; i++)
+ checkboxes[i].setDisabled("Importing", state);
+ if (!state)
+ ImportDataOverlay.getInstance().updateCheckboxes_();
+ $('import-browsers').disabled = state;
+ $('import-throbber').style.visibility = state ? "visible" : "hidden";
+ ImportDataOverlay.getInstance().validateCommitButton_();
+ };
+
+ /**
+ * Remove the import overlay from display.
+ */
+ ImportDataOverlay.dismiss = function() {
+ ImportDataOverlay.clearUserPrefs();
+ OptionsPage.closeOverlay();
+ };
+
+ /**
+ * Show a message confirming the success of the import operation.
+ */
+ ImportDataOverlay.confirmSuccess = function() {
+ var showBookmarksMessage = $('import-favorites').checked;
+ ImportDataOverlay.setImportingState(false);
+ $('import-data-configure').hidden = true;
+ $('import-data-success').hidden = false;
+ $('import-find-your-bookmarks').hidden = !showBookmarksMessage;
+ };
+
+ // Export
+ return {
+ ImportDataOverlay: ImportDataOverlay
+ };
+});
diff --git a/chrome/browser/resources/options2/inline_editable_list.js b/chrome/browser/resources/options2/inline_editable_list.js
new file mode 100644
index 0000000..8aed93b
--- /dev/null
+++ b/chrome/browser/resources/options2/inline_editable_list.js
@@ -0,0 +1,414 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+ const DeletableItem = options.DeletableItem;
+ const DeletableItemList = options.DeletableItemList;
+
+ /**
+ * Creates a new list item with support for inline editing.
+ * @constructor
+ * @extends {options.DeletableListItem}
+ */
+ function InlineEditableItem() {
+ var el = cr.doc.createElement('div');
+ InlineEditableItem.decorate(el);
+ return el;
+ }
+
+ /**
+ * Decorates an element as a inline-editable list item. Note that this is
+ * a subclass of DeletableItem.
+ * @param {!HTMLElement} el The element to decorate.
+ */
+ InlineEditableItem.decorate = function(el) {
+ el.__proto__ = InlineEditableItem.prototype;
+ el.decorate();
+ };
+
+ InlineEditableItem.prototype = {
+ __proto__: DeletableItem.prototype,
+
+ /**
+ * Whether or not this item can be edited.
+ * @type {boolean}
+ * @private
+ */
+ editable_: true,
+
+ /**
+ * Whether or not this is a placeholder for adding a new item.
+ * @type {boolean}
+ * @private
+ */
+ isPlaceholder_: false,
+
+ /**
+ * Fields associated with edit mode.
+ * @type {array}
+ * @private
+ */
+ editFields_: null,
+
+ /**
+ * Whether or not the current edit should be considered cancelled, rather
+ * than committed, when editing ends.
+ * @type {boolean}
+ * @private
+ */
+ editCancelled_: true,
+
+ /**
+ * The editable item corresponding to the last click, if any. Used to decide
+ * initial focus when entering edit mode.
+ * @type {HTMLElement}
+ * @private
+ */
+ editClickTarget_: null,
+
+ /** @inheritDoc */
+ decorate: function() {
+ DeletableItem.prototype.decorate.call(this);
+
+ this.editFields_ = [];
+ this.addEventListener('mousedown', this.handleMouseDown_);
+ this.addEventListener('keydown', this.handleKeyDown_);
+ this.addEventListener('leadChange', this.handleLeadChange_);
+ },
+
+ /** @inheritDoc */
+ selectionChanged: function() {
+ this.updateEditState();
+ },
+
+ /**
+ * Called when this element gains or loses 'lead' status. Updates editing
+ * mode accordingly.
+ * @private
+ */
+ handleLeadChange_: function() {
+ this.updateEditState();
+ },
+
+ /**
+ * Updates the edit state based on the current selected and lead states.
+ */
+ updateEditState: function() {
+ if (this.editable)
+ this.editing = this.selected && this.lead;
+ },
+
+ /**
+ * Whether the user is currently editing the list item.
+ * @type {boolean}
+ */
+ get editing() {
+ return this.hasAttribute('editing');
+ },
+ set editing(editing) {
+ if (this.editing == editing)
+ return;
+
+ if (editing)
+ this.setAttribute('editing', '');
+ else
+ this.removeAttribute('editing');
+
+ if (editing) {
+ this.editCancelled_ = false;
+
+ cr.dispatchSimpleEvent(this, 'edit', true);
+
+ var focusElement = this.editClickTarget_ || this.initialFocusElement;
+ this.editClickTarget_ = null;
+
+ // When this is called in response to the selectedChange event,
+ // the list grabs focus immediately afterwards. Thus we must delay
+ // our focus grab.
+ var self = this;
+ if (focusElement) {
+ window.setTimeout(function() {
+ // Make sure we are still in edit mode by the time we execute.
+ if (self.editing) {
+ focusElement.focus();
+ focusElement.select();
+ }
+ }, 50);
+ }
+ } else {
+ if (!this.editCancelled_ && this.hasBeenEdited &&
+ this.currentInputIsValid) {
+ if (this.isPlaceholder)
+ this.parentNode.focusPlaceholder = true;
+
+ this.updateStaticValues_();
+ cr.dispatchSimpleEvent(this, 'commitedit', true);
+ } else {
+ this.resetEditableValues_();
+ cr.dispatchSimpleEvent(this, 'canceledit', true);
+ }
+ }
+ },
+
+ /**
+ * Whether the item is editable.
+ * @type {boolean}
+ */
+ get editable() {
+ return this.editable_;
+ },
+ set editable(editable) {
+ this.editable_ = editable;
+ if (!editable)
+ this.editing = false;
+ },
+
+ /**
+ * Whether the item is a new item placeholder.
+ * @type {boolean}
+ */
+ get isPlaceholder() {
+ return this.isPlaceholder_;
+ },
+ set isPlaceholder(isPlaceholder) {
+ this.isPlaceholder_ = isPlaceholder;
+ if (isPlaceholder)
+ this.deletable = false;
+ },
+
+ /**
+ * The HTML element that should have focus initially when editing starts,
+ * if a specific element wasn't clicked.
+ * Defaults to the first <input> element; can be overriden by subclasses if
+ * a different element should be focused.
+ * @type {HTMLElement}
+ */
+ get initialFocusElement() {
+ return this.contentElement.querySelector('input');
+ },
+
+ /**
+ * Whether the input in currently valid to submit. If this returns false
+ * when editing would be submitted, either editing will not be ended,
+ * or it will be cancelled, depending on the context.
+ * Can be overrided by subclasses to perform input validation.
+ * @type {boolean}
+ */
+ get currentInputIsValid() {
+ return true;
+ },
+
+ /**
+ * Returns true if the item has been changed by an edit.
+ * Can be overrided by subclasses to return false when nothing has changed
+ * to avoid unnecessary commits.
+ * @type {boolean}
+ */
+ get hasBeenEdited() {
+ return true;
+ },
+
+ /**
+ * Returns a div containing an <input>, as well as static text if
+ * isPlaceholder is not true.
+ * @param {string} text The text of the cell.
+ * @return {HTMLElement} The HTML element for the cell.
+ * @private
+ */
+ createEditableTextCell: function(text) {
+ var container = this.ownerDocument.createElement('div');
+
+ if (!this.isPlaceholder) {
+ var textEl = this.ownerDocument.createElement('div');
+ textEl.className = 'static-text';
+ textEl.textContent = text;
+ textEl.setAttribute('displaymode', 'static');
+ container.appendChild(textEl);
+ }
+
+ var inputEl = this.ownerDocument.createElement('input');
+ inputEl.type = 'text';
+ inputEl.value = text;
+ if (!this.isPlaceholder) {
+ inputEl.setAttribute('displaymode', 'edit');
+ inputEl.staticVersion = textEl;
+ } else {
+ // At this point |this| is not attached to the parent list yet, so give
+ // a short timeout in order for the attachment to occur.
+ var self = this;
+ window.setTimeout(function() {
+ var list = self.parentNode;
+ if (list && list.focusPlaceholder) {
+ list.focusPlaceholder = false;
+ if (list.shouldFocusPlaceholder())
+ inputEl.focus();
+ }
+ }, 50);
+ }
+
+ inputEl.addEventListener('focus', this.handleFocus_.bind(this));
+ container.appendChild(inputEl);
+ this.editFields_.push(inputEl);
+
+ return container;
+ },
+
+ /**
+ * Resets the editable version of any controls created by createEditable*
+ * to match the static text.
+ * @private
+ */
+ resetEditableValues_: function() {
+ var editFields = this.editFields_;
+ for (var i = 0; i < editFields.length; i++) {
+ var staticLabel = editFields[i].staticVersion;
+ if (!staticLabel && !this.isPlaceholder)
+ continue;
+
+ if (editFields[i].tagName == 'INPUT') {
+ editFields[i].value =
+ this.isPlaceholder ? '' : staticLabel.textContent;
+ }
+ // Add more tag types here as new createEditable* methods are added.
+
+ editFields[i].setCustomValidity('');
+ }
+ },
+
+ /**
+ * Sets the static version of any controls created by createEditable*
+ * to match the current value of the editable version. Called on commit so
+ * that there's no flicker of the old value before the model updates.
+ * @private
+ */
+ updateStaticValues_: function() {
+ var editFields = this.editFields_;
+ for (var i = 0; i < editFields.length; i++) {
+ var staticLabel = editFields[i].staticVersion;
+ if (!staticLabel)
+ continue;
+
+ if (editFields[i].tagName == 'INPUT')
+ staticLabel.textContent = editFields[i].value;
+ // Add more tag types here as new createEditable* methods are added.
+ }
+ },
+
+ /**
+ * Called a key is pressed. Handles committing and cancelling edits.
+ * @param {Event} e The key down event.
+ * @private
+ */
+ handleKeyDown_: function(e) {
+ if (!this.editing)
+ return;
+
+ var endEdit = false;
+ switch (e.keyIdentifier) {
+ case 'U+001B': // Esc
+ this.editCancelled_ = true;
+ endEdit = true;
+ break;
+ case 'Enter':
+ if (this.currentInputIsValid)
+ endEdit = true;
+ break;
+ }
+
+ if (endEdit) {
+ // Blurring will trigger the edit to end; see InlineEditableItemList.
+ this.ownerDocument.activeElement.blur();
+ // Make sure that handled keys aren't passed on and double-handled.
+ // (e.g., esc shouldn't both cancel an edit and close a subpage)
+ e.stopPropagation();
+ }
+ },
+
+ /**
+ * Called when the list item is clicked. If the click target corresponds to
+ * an editable item, stores that item to focus when edit mode is started.
+ * @param {Event} e The mouse down event.
+ * @private
+ */
+ handleMouseDown_: function(e) {
+ if (!this.editable || this.editing)
+ return;
+
+ var clickTarget = e.target;
+ var editFields = this.editFields_;
+ for (var i = 0; i < editFields.length; i++) {
+ if (editFields[i] == clickTarget ||
+ editFields[i].staticVersion == clickTarget) {
+ this.editClickTarget_ = editFields[i];
+ return;
+ }
+ }
+ },
+ };
+
+ /**
+ * Takes care of committing changes to inline editable list items when the
+ * window loses focus.
+ */
+ function handleWindowBlurs() {
+ window.addEventListener('blur', function(e) {
+ var itemAncestor = findAncestor(document.activeElement, function(node) {
+ return node instanceof InlineEditableItem;
+ });
+ if (itemAncestor);
+ document.activeElement.blur();
+ });
+ }
+ handleWindowBlurs();
+
+ var InlineEditableItemList = cr.ui.define('list');
+
+ InlineEditableItemList.prototype = {
+ __proto__: DeletableItemList.prototype,
+
+ /**
+ * Focuses the input element of the placeholder if true.
+ * @type {boolean}
+ */
+ focusPlaceholder: false,
+
+ /** @inheritDoc */
+ decorate: function() {
+ DeletableItemList.prototype.decorate.call(this);
+ this.setAttribute('inlineeditable', '');
+ this.addEventListener('hasElementFocusChange',
+ this.handleListFocusChange_);
+ },
+
+ /**
+ * Called when the list hierarchy as a whole loses or gains focus; starts
+ * or ends editing for the lead item if necessary.
+ * @param {Event} e The change event.
+ * @private
+ */
+ handleListFocusChange_: function(e) {
+ var leadItem = this.getListItemByIndex(this.selectionModel.leadIndex);
+ if (leadItem) {
+ if (e.newValue)
+ leadItem.updateEditState();
+ else
+ leadItem.editing = false;
+ }
+ },
+
+ /**
+ * May be overridden by subclasses to disable focusing the placeholder.
+ * @return true if the placeholder element should be focused on edit commit.
+ */
+ shouldFocusPlaceholder: function() {
+ return true;
+ },
+ };
+
+ // Export
+ return {
+ InlineEditableItem: InlineEditableItem,
+ InlineEditableItemList: InlineEditableItemList,
+ };
+});
diff --git a/chrome/browser/resources/options2/instant_confirm_overlay.html b/chrome/browser/resources/options2/instant_confirm_overlay.html
new file mode 100644
index 0000000..1e724ef
--- /dev/null
+++ b/chrome/browser/resources/options2/instant_confirm_overlay.html
@@ -0,0 +1,16 @@
+<div id="instantConfirmOverlay" class="page" hidden>
+ <h1 i18n-content="instantConfirmTitle"></h1>
+ <!-- The text has line breaks, so we must use a pre. -->
+ <div class="content-area">
+ <pre id="instantConfirmText" i18n-content="instantConfirmMessage"></pre>
+ <a id="instantConfirmLearnMore" target="_blank" i18n-content="learnMore"
+ i18n-values="href:instantLearnMoreLink"></a>
+ </div>
+ <div class="action-area">
+ <div class="button-strip">
+ <button id="instantConfirmCancel" i18n-content="cancel"
+ class="cancel-button"></button>
+ <button id="instantConfirmOk" i18n-content="ok"></button>
+ </div>
+ </div>
+</div>
diff --git a/chrome/browser/resources/options2/instant_confirm_overlay.js b/chrome/browser/resources/options2/instant_confirm_overlay.js
new file mode 100644
index 0000000..01a9ee5
--- /dev/null
+++ b/chrome/browser/resources/options2/instant_confirm_overlay.js
@@ -0,0 +1,39 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+ var OptionsPage = options.OptionsPage;
+
+ function InstantConfirmOverlay() {
+ OptionsPage.call(this, 'instantConfirm',
+ templateData.instantConfirmTitle,
+ 'instantConfirmOverlay');
+ };
+
+ cr.addSingletonGetter(InstantConfirmOverlay);
+
+ InstantConfirmOverlay.prototype = {
+ // Inherit from OptionsPage.
+ __proto__: OptionsPage.prototype,
+
+ initializePage: function() {
+ OptionsPage.prototype.initializePage.call(this);
+
+ $('instantConfirmCancel').onclick = function() {
+ OptionsPage.closeOverlay();
+ $('instantEnabledCheckbox').checked = false;
+ };
+
+ $('instantConfirmOk').onclick = function() {
+ OptionsPage.closeOverlay();
+ chrome.send('enableInstant');
+ };
+ },
+ };
+
+ // Export
+ return {
+ InstantConfirmOverlay: InstantConfirmOverlay
+ };
+});
diff --git a/chrome/browser/resources/options2/intents_list.js b/chrome/browser/resources/options2/intents_list.js
new file mode 100644
index 0000000..e2dc21e
--- /dev/null
+++ b/chrome/browser/resources/options2/intents_list.js
@@ -0,0 +1,707 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// TODO(gbillock): refactor this together with CookiesList once we have
+// a better sense from UX design what it'll look like and so what'll be shared.
+cr.define('options', function() {
+ const DeletableItemList = options.DeletableItemList;
+ const DeletableItem = options.DeletableItem;
+ const ArrayDataModel = cr.ui.ArrayDataModel;
+ const ListSingleSelectionModel = cr.ui.ListSingleSelectionModel;
+ const localStrings = new LocalStrings();
+
+ /**
+ * Returns the item's height, like offsetHeight but such that it works better
+ * when the page is zoomed. See the similar calculation in @{code cr.ui.List}.
+ * This version also accounts for the animation done in this file.
+ * @param {Element} item The item to get the height of.
+ * @return {number} The height of the item, calculated with zooming in mind.
+ */
+ function getItemHeight(item) {
+ var height = item.style.height;
+ // Use the fixed animation target height if set, in case the element is
+ // currently being animated and we'd get an intermediate height below.
+ if (height && height.substr(-2) == 'px')
+ return parseInt(height.substr(0, height.length - 2));
+ return item.getBoundingClientRect().height;
+ }
+
+ // Map of parent pathIDs to node objects.
+ var parentLookup = {};
+
+ // Pending requests for child information.
+ var lookupRequests = {};
+
+ /**
+ * Creates a new list item for intent service data. Note that these are
+ * created and destroyed lazily as they scroll into and out of view,
+ * so they must be stateless. We cache the expanded item in
+ * @{code IntentsList} though, so it can keep state.
+ * (Mostly just which item is selected.)
+ *
+ * @param {Object} origin Data used to create an intents list item.
+ * @param {IntentsList} list The list that will contain this item.
+ * @constructor
+ * @extends {DeletableItem}
+ */
+ function IntentsListItem(origin, list) {
+ var listItem = new DeletableItem(null);
+ listItem.__proto__ = IntentsListItem.prototype;
+
+ listItem.origin = origin;
+ listItem.list = list;
+ listItem.decorate();
+
+ // This hooks up updateOrigin() to the list item, makes the top-level
+ // tree nodes (i.e., origins) register their IDs in parentLookup, and
+ // causes them to request their children if they have none. Note that we
+ // have special logic in the setter for the parent property to make sure
+ // that we can still garbage collect list items when they scroll out of
+ // view, even though it appears that we keep a direct reference.
+ if (origin) {
+ origin.parent = listItem;
+ origin.updateOrigin();
+ }
+
+ return listItem;
+ }
+
+ IntentsListItem.prototype = {
+ __proto__: DeletableItem.prototype,
+
+ /** @inheritDoc */
+ decorate: function() {
+ this.siteChild = this.ownerDocument.createElement('div');
+ this.siteChild.className = 'intents-site';
+ this.dataChild = this.ownerDocument.createElement('div');
+ this.dataChild.className = 'intents-data';
+ this.itemsChild = this.ownerDocument.createElement('div');
+ this.itemsChild.className = 'intents-items';
+ this.infoChild = this.ownerDocument.createElement('div');
+ this.infoChild.className = 'intents-details';
+ this.infoChild.hidden = true;
+ var remove = this.ownerDocument.createElement('button');
+ remove.textContent = localStrings.getString('removeIntent');
+ remove.onclick = this.removeIntent_.bind(this);
+ this.infoChild.appendChild(remove);
+ var content = this.contentElement;
+ content.appendChild(this.siteChild);
+ content.appendChild(this.dataChild);
+ content.appendChild(this.itemsChild);
+ this.itemsChild.appendChild(this.infoChild);
+ if (this.origin && this.origin.data) {
+ this.siteChild.textContent = this.origin.data.site;
+ this.siteChild.setAttribute('title', this.origin.data.site);
+ }
+ this.itemList_ = [];
+ },
+
+ /** @type {boolean} */
+ get expanded() {
+ return this.expanded_;
+ },
+ set expanded(expanded) {
+ if (this.expanded_ == expanded)
+ return;
+ this.expanded_ = expanded;
+ if (expanded) {
+ var oldExpanded = this.list.expandedItem;
+ this.list.expandedItem = this;
+ this.updateItems_();
+ if (oldExpanded)
+ oldExpanded.expanded = false;
+ this.classList.add('show-items');
+ this.dataChild.hidden = true;
+ } else {
+ if (this.list.expandedItem == this) {
+ this.list.expandedItem = null;
+ }
+ this.style.height = '';
+ this.itemsChild.style.height = '';
+ this.classList.remove('show-items');
+ this.dataChild.hidden = false;
+ }
+ },
+
+ /**
+ * The callback for the "remove" button shown when an item is selected.
+ * Requests that the currently selected intent service be removed.
+ * @private
+ */
+ removeIntent_: function() {
+ if (this.selectedIndex_ >= 0) {
+ var item = this.itemList_[this.selectedIndex_];
+ if (item && item.node)
+ chrome.send('removeIntent', [item.node.pathId]);
+ }
+ },
+
+ /**
+ * Disable animation within this intents list item, in preparation for
+ * making changes that will need to be animated. Makes it possible to
+ * measure the contents without displaying them, to set animation targets.
+ * @private
+ */
+ disableAnimation_: function() {
+ this.itemsHeight_ = getItemHeight(this.itemsChild);
+ this.classList.add('measure-items');
+ },
+
+ /**
+ * Enable animation after changing the contents of this intents list item.
+ * See @{code disableAnimation_}.
+ * @private
+ */
+ enableAnimation_: function() {
+ if (!this.classList.contains('measure-items'))
+ this.disableAnimation_();
+ this.itemsChild.style.height = '';
+ // This will force relayout in order to calculate the new heights.
+ var itemsHeight = getItemHeight(this.itemsChild);
+ var fixedHeight = getItemHeight(this) + itemsHeight - this.itemsHeight_;
+ this.itemsChild.style.height = this.itemsHeight_ + 'px';
+ // Force relayout before enabling animation, so that if we have
+ // changed things since the last layout, they will not be animated
+ // during subsequent layouts.
+ this.itemsChild.offsetHeight;
+ this.classList.remove('measure-items');
+ this.itemsChild.style.height = itemsHeight + 'px';
+ this.style.height = fixedHeight + 'px';
+ },
+
+ /**
+ * Updates the origin summary to reflect changes in its items.
+ * Both IntentsListItem and IntentsTreeNode implement this API.
+ * This implementation scans the descendants to update the text.
+ */
+ updateOrigin: function() {
+ var text = '';
+ for (var i = 0; i < this.origin.children.length; ++i) {
+ if (text.length > 0)
+ text += ', ' + this.origin.children[i].data.action;
+ else
+ text = this.origin.children[i].data.action;
+ }
+ this.dataChild.textContent = text;
+
+ if (this.expanded)
+ this.updateItems_();
+ },
+
+ /**
+ * Updates the items section to reflect changes, animating to the new state.
+ * Removes existing contents and calls @{code IntentsTreeNode.createItems}.
+ * @private
+ */
+ updateItems_: function() {
+ this.disableAnimation_();
+ this.itemsChild.textContent = '';
+ this.infoChild.hidden = true;
+ this.selectedIndex_ = -1;
+ this.itemList_ = [];
+ if (this.origin)
+ this.origin.createItems(this);
+ this.itemsChild.appendChild(this.infoChild);
+ this.enableAnimation_();
+ },
+
+ /**
+ * Append a new intents node "bubble" to this list item.
+ * @param {IntentsTreeNode} node The intents node to add a bubble for.
+ * @param {Element} div The DOM element for the bubble itself.
+ * @return {number} The index the bubble was added at.
+ */
+ appendItem: function(node, div) {
+ this.itemList_.push({node: node, div: div});
+ this.itemsChild.appendChild(div);
+ return this.itemList_.length - 1;
+ },
+
+ /**
+ * The currently selected intents node ("intents bubble") index.
+ * @type {number}
+ * @private
+ */
+ selectedIndex_: -1,
+
+ /**
+ * Get the currently selected intents node ("intents bubble") index.
+ * @type {number}
+ */
+ get selectedIndex() {
+ return this.selectedIndex_;
+ },
+
+ /**
+ * Set the currently selected intents node ("intents bubble") index to
+ * @{code itemIndex}, unselecting any previously selected node first.
+ * @param {number} itemIndex The index to set as the selected index.
+ * TODO: KILL THIS
+ */
+ set selectedIndex(itemIndex) {
+ // Get the list index up front before we change anything.
+ var index = this.list.getIndexOfListItem(this);
+ // Unselect any previously selected item.
+ if (this.selectedIndex_ >= 0) {
+ var item = this.itemList_[this.selectedIndex_];
+ if (item && item.div)
+ item.div.removeAttribute('selected');
+ }
+ // Special case: decrementing -1 wraps around to the end of the list.
+ if (itemIndex == -2)
+ itemIndex = this.itemList_.length - 1;
+ // Check if we're going out of bounds and hide the item details.
+ if (itemIndex < 0 || itemIndex >= this.itemList_.length) {
+ this.selectedIndex_ = -1;
+ this.disableAnimation_();
+ this.infoChild.hidden = true;
+ this.enableAnimation_();
+ return;
+ }
+ // Set the new selected item and show the item details for it.
+ this.selectedIndex_ = itemIndex;
+ this.itemList_[itemIndex].div.setAttribute('selected', '');
+ this.disableAnimation_();
+ this.infoChild.hidden = false;
+ this.enableAnimation_();
+ // If we're near the bottom of the list this may cause the list item to go
+ // beyond the end of the visible area. Fix it after the animation is done.
+ var list = this.list;
+ window.setTimeout(function() { list.scrollIndexIntoView(index); }, 150);
+ },
+ };
+
+ /**
+ * {@code IntentsTreeNode}s mirror the structure of the intents tree lazily,
+ * and contain all the actual data used to generate the
+ * {@code IntentsListItem}s.
+ * @param {Object} data The data object for this node.
+ * @constructor
+ */
+ function IntentsTreeNode(data) {
+ this.data = data;
+ this.children = [];
+ }
+
+ IntentsTreeNode.prototype = {
+ /**
+ * Insert an intents tree node at the given index.
+ * Both IntentsList and IntentsTreeNode implement this API.
+ * @param {Object} data The data object for the node to add.
+ * @param {number} index The index at which to insert the node.
+ */
+ insertAt: function(data, index) {
+ var child = new IntentsTreeNode(data);
+ this.children.splice(index, 0, child);
+ child.parent = this;
+ this.updateOrigin();
+ },
+
+ /**
+ * Remove an intents tree node from the given index.
+ * Both IntentsList and IntentsTreeNode implement this API.
+ * @param {number} index The index of the tree node to remove.
+ */
+ remove: function(index) {
+ if (index < this.children.length) {
+ this.children.splice(index, 1);
+ this.updateOrigin();
+ }
+ },
+
+ /**
+ * Clears all children.
+ * Both IntentsList and IntentsTreeNode implement this API.
+ * It is used by IntentsList.loadChildren().
+ */
+ clear: function() {
+ // We might leave some garbage in parentLookup for removed children.
+ // But that should be OK because parentLookup is cleared when we
+ // reload the tree.
+ this.children = [];
+ this.updateOrigin();
+ },
+
+ /**
+ * The counter used by startBatchUpdates() and endBatchUpdates().
+ * @type {number}
+ */
+ batchCount_: 0,
+
+ /**
+ * See cr.ui.List.startBatchUpdates().
+ * Both IntentsList (via List) and IntentsTreeNode implement this API.
+ */
+ startBatchUpdates: function() {
+ this.batchCount_++;
+ },
+
+ /**
+ * See cr.ui.List.endBatchUpdates().
+ * Both IntentsList (via List) and IntentsTreeNode implement this API.
+ */
+ endBatchUpdates: function() {
+ if (!--this.batchCount_)
+ this.updateOrigin();
+ },
+
+ /**
+ * Requests updating the origin summary to reflect changes in this item.
+ * Both IntentsListItem and IntentsTreeNode implement this API.
+ */
+ updateOrigin: function() {
+ if (!this.batchCount_ && this.parent)
+ this.parent.updateOrigin();
+ },
+
+ /**
+ * Create the intents services rows for this node.
+ * Append the rows to @{code item}.
+ * @param {IntentsListItem} item The intents list item to create items in.
+ */
+ createItems: function(item) {
+ if (this.children.length > 0) {
+ for (var i = 0; i < this.children.length; ++i)
+ this.children[i].createItems(item);
+ } else if (this.data && !this.data.hasChildren) {
+ var div = item.ownerDocument.createElement('div');
+ div.className = 'intents-item';
+ // Help out screen readers and such: this is a clickable thing.
+ div.setAttribute('role', 'button');
+
+ var divAction = item.ownerDocument.createElement('div');
+ divAction.className = 'intents-item-action';
+ divAction.textContent = this.data.action;
+ div.appendChild(divAction);
+
+ var divTypes = item.ownerDocument.createElement('div');
+ divTypes.className = 'intents-item-types';
+ var text = "";
+ for (var i = 0; i < this.data.types.length; ++i) {
+ if (text != "")
+ text += ", ";
+ text += this.data.types[i];
+ }
+ divTypes.textContent = text;
+ div.appendChild(divTypes);
+
+ var divUrl = item.ownerDocument.createElement('div');
+ divUrl.className = 'intents-item-url';
+ divUrl.textContent = this.data.url;
+ div.appendChild(divUrl);
+
+ var index = item.appendItem(this, div);
+ div.onclick = function() {
+ if (item.selectedIndex == index)
+ item.selectedIndex = -1;
+ else
+ item.selectedIndex = index;
+ };
+ }
+ },
+
+ /**
+ * The parent of this intents tree node.
+ * @type {?IntentsTreeNode|IntentsListItem}
+ */
+ get parent(parent) {
+ // See below for an explanation of this special case.
+ if (typeof this.parent_ == 'number')
+ return this.list_.getListItemByIndex(this.parent_);
+ return this.parent_;
+ },
+ set parent(parent) {
+ if (parent == this.parent)
+ return;
+
+ if (parent instanceof IntentsListItem) {
+ // If the parent is to be a IntentsListItem, then we keep the reference
+ // to it by its containing list and list index, rather than directly.
+ // This allows the list items to be garbage collected when they scroll
+ // out of view (except the expanded item, which we cache). This is
+ // transparent except in the setter and getter, where we handle it.
+ this.parent_ = parent.listIndex;
+ this.list_ = parent.list;
+ parent.addEventListener('listIndexChange',
+ this.parentIndexChanged_.bind(this));
+ } else {
+ this.parent_ = parent;
+ }
+
+
+ if (parent)
+ parentLookup[this.pathId] = this;
+ else
+ delete parentLookup[this.pathId];
+
+ if (this.data && this.data.hasChildren &&
+ !this.children.length && !lookupRequests[this.pathId]) {
+ lookupRequests[this.pathId] = true;
+ chrome.send('loadIntents', [this.pathId]);
+ }
+ },
+
+ /**
+ * Called when the parent is a IntentsListItem whose index has changed.
+ * See the code above that avoids keeping a direct reference to
+ * IntentsListItem parents, to allow them to be garbage collected.
+ * @private
+ */
+ parentIndexChanged_: function(event) {
+ if (typeof this.parent_ == 'number') {
+ this.parent_ = event.newValue;
+ // We set a timeout to update the origin, rather than doing it right
+ // away, because this callback may occur while the list items are
+ // being repopulated following a scroll event. Calling updateOrigin()
+ // immediately could trigger relayout that would reset the scroll
+ // position within the list, among other things.
+ window.setTimeout(this.updateOrigin.bind(this), 0);
+ }
+ },
+
+ /**
+ * The intents tree path id.
+ * @type {string}
+ */
+ get pathId() {
+ var parent = this.parent;
+ if (parent && parent instanceof IntentsTreeNode)
+ return parent.pathId + ',' + this.data.action;
+ return this.data.site;
+ },
+ };
+
+ /**
+ * Creates a new intents list.
+ * @param {Object=} opt_propertyBag Optional properties.
+ * @constructor
+ * @extends {DeletableItemList}
+ */
+ var IntentsList = cr.ui.define('list');
+
+ IntentsList.prototype = {
+ __proto__: DeletableItemList.prototype,
+
+ /** @inheritDoc */
+ decorate: function() {
+ DeletableItemList.prototype.decorate.call(this);
+ this.classList.add('intents-list');
+ this.data_ = [];
+ this.dataModel = new ArrayDataModel(this.data_);
+ this.addEventListener('keydown', this.handleKeyLeftRight_.bind(this));
+ var sm = new ListSingleSelectionModel();
+ sm.addEventListener('change', this.cookieSelectionChange_.bind(this));
+ sm.addEventListener('leadIndexChange', this.cookieLeadChange_.bind(this));
+ this.selectionModel = sm;
+ this.fixedHeight = false;
+ },
+
+ /**
+ * Handles key down events and looks for left and right arrows, then
+ * dispatches to the currently expanded item, if any.
+ * @param {Event} e The keydown event.
+ * @private
+ */
+ handleKeyLeftRight_: function(e) {
+ var id = e.keyIdentifier;
+ if ((id == 'Left' || id == 'Right') && this.expandedItem) {
+ var cs = this.ownerDocument.defaultView.getComputedStyle(this);
+ var rtl = cs.direction == 'rtl';
+ if ((!rtl && id == 'Left') || (rtl && id == 'Right'))
+ this.expandedItem.selectedIndex--;
+ else
+ this.expandedItem.selectedIndex++;
+ this.scrollIndexIntoView(this.expandedItem.listIndex);
+ // Prevent the page itself from scrolling.
+ e.preventDefault();
+ }
+ },
+
+ /**
+ * Called on selection model selection changes.
+ * @param {Event} ce The selection change event.
+ * @private
+ */
+ cookieSelectionChange_: function(ce) {
+ ce.changes.forEach(function(change) {
+ var listItem = this.getListItemByIndex(change.index);
+ if (listItem) {
+ if (!change.selected) {
+ // We set a timeout here, rather than setting the item unexpanded
+ // immediately, so that if another item gets set expanded right
+ // away, it will be expanded before this item is unexpanded. It
+ // will notice that, and unexpand this item in sync with its own
+ // expansion. Later, this callback will end up having no effect.
+ window.setTimeout(function() {
+ if (!listItem.selected || !listItem.lead)
+ listItem.expanded = false;
+ }, 0);
+ } else if (listItem.lead) {
+ listItem.expanded = true;
+ }
+ }
+ }, this);
+ },
+
+ /**
+ * Called on selection model lead changes.
+ * @param {Event} pe The lead change event.
+ * @private
+ */
+ cookieLeadChange_: function(pe) {
+ if (pe.oldValue != -1) {
+ var listItem = this.getListItemByIndex(pe.oldValue);
+ if (listItem) {
+ // See cookieSelectionChange_ above for why we use a timeout here.
+ window.setTimeout(function() {
+ if (!listItem.lead || !listItem.selected)
+ listItem.expanded = false;
+ }, 0);
+ }
+ }
+ if (pe.newValue != -1) {
+ var listItem = this.getListItemByIndex(pe.newValue);
+ if (listItem && listItem.selected)
+ listItem.expanded = true;
+ }
+ },
+
+ /**
+ * The currently expanded item. Used by IntentsListItem above.
+ * @type {?IntentsListItem}
+ */
+ expandedItem: null,
+
+ // from cr.ui.List
+ /** @inheritDoc */
+ createItem: function(data) {
+ // We use the cached expanded item in order to allow it to maintain some
+ // state (like its fixed height, and which bubble is selected).
+ if (this.expandedItem && this.expandedItem.origin == data)
+ return this.expandedItem;
+ return new IntentsListItem(data, this);
+ },
+
+ // from options.DeletableItemList
+ /** @inheritDoc */
+ deleteItemAtIndex: function(index) {
+ var item = this.data_[index];
+ if (item) {
+ var pathId = item.pathId;
+ if (pathId)
+ chrome.send('removeIntent', [pathId]);
+ }
+ },
+
+ /**
+ * Insert an intents tree node at the given index.
+ * Both IntentsList and IntentsTreeNode implement this API.
+ * @param {Object} data The data object for the node to add.
+ * @param {number} index The index at which to insert the node.
+ */
+ insertAt: function(data, index) {
+ this.dataModel.splice(index, 0, new IntentsTreeNode(data));
+ },
+
+ /**
+ * Remove an intents tree node from the given index.
+ * Both IntentsList and IntentsTreeNode implement this API.
+ * @param {number} index The index of the tree node to remove.
+ */
+ remove: function(index) {
+ if (index < this.data_.length)
+ this.dataModel.splice(index, 1);
+ },
+
+ /**
+ * Clears the list.
+ * Both IntentsList and IntentsTreeNode implement this API.
+ * It is used by IntentsList.loadChildren().
+ */
+ clear: function() {
+ parentLookup = {};
+ this.data_ = [];
+ this.dataModel = new ArrayDataModel(this.data_);
+ this.redraw();
+ },
+
+ /**
+ * Add tree nodes by given parent.
+ * Note: this method will be O(n^2) in the general case. Use it only to
+ * populate an empty parent or to insert single nodes to avoid this.
+ * @param {Object} parent The parent node.
+ * @param {number} start Start index of where to insert nodes.
+ * @param {Array} nodesData Nodes data array.
+ * @private
+ */
+ addByParent_: function(parent, start, nodesData) {
+ if (!parent)
+ return;
+
+ parent.startBatchUpdates();
+ for (var i = 0; i < nodesData.length; ++i)
+ parent.insertAt(nodesData[i], start + i);
+ parent.endBatchUpdates();
+
+ cr.dispatchSimpleEvent(this, 'change');
+ },
+
+ /**
+ * Add tree nodes by parent id.
+ * This is used by intents_view.js.
+ * Note: this method will be O(n^2) in the general case. Use it only to
+ * populate an empty parent or to insert single nodes to avoid this.
+ * @param {string} parentId Id of the parent node.
+ * @param {number} start Start index of where to insert nodes.
+ * @param {Array} nodesData Nodes data array.
+ */
+ addByParentId: function(parentId, start, nodesData) {
+ var parent = parentId ? parentLookup[parentId] : this;
+ this.addByParent_(parent, start, nodesData);
+ },
+
+ /**
+ * Removes tree nodes by parent id.
+ * This is used by intents_view.js.
+ * @param {string} parentId Id of the parent node.
+ * @param {number} start Start index of nodes to remove.
+ * @param {number} count Number of nodes to remove.
+ */
+ removeByParentId: function(parentId, start, count) {
+ var parent = parentId ? parentLookup[parentId] : this;
+ if (!parent)
+ return;
+
+ parent.startBatchUpdates();
+ while (count-- > 0)
+ parent.remove(start);
+ parent.endBatchUpdates();
+
+ cr.dispatchSimpleEvent(this, 'change');
+ },
+
+ /**
+ * Loads the immediate children of given parent node.
+ * This is used by intents_view.js.
+ * @param {string} parentId Id of the parent node.
+ * @param {Array} children The immediate children of parent node.
+ */
+ loadChildren: function(parentId, children) {
+ if (parentId)
+ delete lookupRequests[parentId];
+ var parent = parentId ? parentLookup[parentId] : this;
+ if (!parent)
+ return;
+
+ parent.startBatchUpdates();
+ parent.clear();
+ this.addByParent_(parent, 0, children);
+ parent.endBatchUpdates();
+ },
+ };
+
+ return {
+ IntentsList: IntentsList
+ };
+});
diff --git a/chrome/browser/resources/options2/intents_view.css b/chrome/browser/resources/options2/intents_view.css
new file mode 100644
index 0000000..84c274d
--- /dev/null
+++ b/chrome/browser/resources/options2/intents_view.css
@@ -0,0 +1,181 @@
+/*
+Copyright (c) 2011 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+*/
+
+/* Styles for the intents list elements in intents_view.html. */
+
+#intents-column-headers {
+ position: relative;
+ width: 100%;
+}
+
+#intents-column-headers h3 {
+ font-size: 105%;
+ font-weight: bold;
+ margin: 10px 0;
+}
+
+/* Notice the width and padding for these columns match up with those below. */
+#intents-site-column {
+ display: inline-block;
+ font-weight: bold;
+ width: 11em;
+}
+
+#intents-data-column {
+ -webkit-padding-start: 7px;
+ display: inline-block;
+ font-weight: bold;
+}
+
+#intents-list {
+ border: 1px solid #d9d9d9;
+ margin: 0;
+}
+
+/* Enable animating the height of items. */
+list.intents-list .deletable-item {
+ -webkit-transition: height .15s ease-in-out;
+}
+
+/* Disable webkit-box display. */
+list.intents-list .deletable-item > :first-child {
+ display: block;
+}
+
+/* Force the X for deleting an origin to stay at the top. */
+list.intents-list > .deletable-item > .close-button {
+ position: absolute;
+ right: 2px;
+ top: 8px;
+}
+
+html[dir=rtl] list.intents-list > .deletable-item > .close-button {
+ left: 2px;
+ right: auto;
+}
+
+/* Styles for the site (aka origin) and its summary. */
+
+.intents-site {
+ /* Notice that the width, margin, and padding match up with those above. */
+ -webkit-margin-end: 2px;
+ -webkit-padding-start: 5px;
+ display: inline-block;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ width: 11em;
+}
+
+list.intents-list > .deletable-item[selected] .intents-site {
+ -webkit-user-select: text;
+}
+
+.intents-data {
+ display: inline-block;
+}
+
+list.intents-list > .deletable-item[selected] .intents-data {
+ -webkit-user-select: text;
+}
+
+.intents-items {
+ /* Notice that the margin and padding match up with those above. */
+ -webkit-margin-start: 11em;
+ -webkit-padding-start: 7px;
+ -webkit-transition: .15s ease-in-out;
+ display: table;
+ height: 0;
+ opacity: 0;
+ /* Make the intents items wrap correctly. */
+ white-space: normal;
+}
+
+.measure-items .intents-items {
+ -webkit-transition: none;
+ height: auto;
+ visibility: hidden;
+}
+
+.show-items .intents-items {
+ opacity: 1;
+}
+
+.intents-items .intents-item {
+ -webkit-box-orient: horizontal;
+ -webkit-box-pack: start;
+ background: #e0e9f5;
+ border: 1px solid #8392ae;
+ display: table-row;
+ font-size: 85%;
+ height: auto;
+ margin: 2px 4px 2px 0;
+ overflow: hidden;
+ padding: 0 3px;
+ text-align: center;
+ text-overflow: ellipsis;
+}
+
+.intents-item .intents-item-action {
+ display: table-cell;
+ padding: 2px 5px;
+}
+
+.intents-item .intents-item-types {
+ display: table-cell;
+ padding: 2px 5px;
+ overflow: hidden;
+}
+
+.intents-item .intents-item-url {
+ display: table-cell;
+ padding: 2px 5px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.intents-items .intents-item:hover {
+ background: #eef3f9;
+ border-color: #647187;
+}
+
+.intents-items .intents-item[selected] {
+ background: #f5f8f8;
+ border-color: #b2b2b2;
+}
+
+.intents-items .intents-item[selected]:hover {
+ background: #f5f8f8;
+ border-color: #647187;
+}
+
+/* Styles for the intents details box. */
+
+.intents-details {
+ background: #f5f8f8;
+ border-radius: 5px;
+ border: 1px solid #b2b2b2;
+ margin-top: 2px;
+ padding: 5px;
+}
+
+list.intents-list > .deletable-item[selected] .intents-details {
+ -webkit-user-select: text;
+}
+
+.intents-details-table {
+ table-layout: fixed;
+ width: 100%;
+}
+
+.intents-details-label {
+ vertical-align: top;
+ white-space: pre;
+ width: 10em;
+}
+
+.intents-details-value {
+ word-wrap: break-word;
+}
diff --git a/chrome/browser/resources/options2/intents_view.html b/chrome/browser/resources/options2/intents_view.html
new file mode 100644
index 0000000..6803c23
--- /dev/null
+++ b/chrome/browser/resources/options2/intents_view.html
@@ -0,0 +1,12 @@
+<div id="intents-view-page" class="page" hidden>
+ <h1 i18n-content="intentsViewPage"></h1>
+ <div id="intents-column-headers">
+ <div id="intents-site-column">
+ <h3 i18n-content="intentsDomain"></h3>
+ </div>
+ <div id="intents-data-column">
+ <h3 i18n-content="intentsServiceData"></h3>
+ </div>
+ </div>
+ <list id="intents-list"></list>
+</div>
diff --git a/chrome/browser/resources/options2/intents_view.js b/chrome/browser/resources/options2/intents_view.js
new file mode 100644
index 0000000..afc7d68
--- /dev/null
+++ b/chrome/browser/resources/options2/intents_view.js
@@ -0,0 +1,83 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+ var OptionsPage = options.OptionsPage;
+
+ /////////////////////////////////////////////////////////////////////////////
+ // IntentsView class:
+
+ /**
+ * Encapsulated handling of the Intents data page.
+ * @constructor
+ */
+ function IntentsView(model) {
+ OptionsPage.call(this, 'intents',
+ templateData.intentsViewPageTabTitle,
+ 'intents-view-page');
+ }
+
+ cr.addSingletonGetter(IntentsView);
+
+ IntentsView.prototype = {
+ __proto__: OptionsPage.prototype,
+
+ initializePage: function() {
+ OptionsPage.prototype.initializePage.call(this);
+
+ var intentsList = $('intents-list');
+ options.IntentsList.decorate(intentsList);
+ window.addEventListener('resize', this.handleResize_.bind(this));
+
+ this.addEventListener('visibleChange', this.handleVisibleChange_);
+ },
+
+ initialized_: false,
+
+ /**
+ * Handler for OptionsPage's visible property change event.
+ * @param {Event} e Property change event.
+ * @private
+ */
+ handleVisibleChange_: function(e) {
+ if (!this.visible)
+ return;
+
+ // Resize the intents list whenever the options page becomes visible.
+ this.handleResize_(null);
+ if (!this.initialized_) {
+ this.initialized_ = true;
+ chrome.send('loadIntents');
+ } else {
+ $('intents-list').redraw();
+ }
+ },
+
+ /**
+ * Handler for when the window changes size. Resizes the intents list to
+ * match the window height.
+ * @param {?Event} e Window resize event, or null if called directly.
+ * @private
+ */
+ handleResize_: function(e) {
+ if (!this.visible)
+ return;
+ var intentsList = $('intents-list');
+ // 25 pixels from the window bottom seems like a visually pleasing amount.
+ var height = window.innerHeight - intentsList.offsetTop - 25;
+ intentsList.style.height = height + 'px';
+ },
+ };
+
+ // IntentsViewHandler callbacks.
+ IntentsView.loadChildren = function(args) {
+ $('intents-list').loadChildren(args[0], args[1]);
+ };
+
+ // Export
+ return {
+ IntentsView: IntentsView
+ };
+
+});
diff --git a/chrome/browser/resources/options2/language_add_language_overlay.html b/chrome/browser/resources/options2/language_add_language_overlay.html
new file mode 100644
index 0000000..bca2e75
--- /dev/null
+++ b/chrome/browser/resources/options2/language_add_language_overlay.html
@@ -0,0 +1,28 @@
+<div id="add-language-overlay-page" class="page" hidden>
+<!--
+Buttons are too small for touch in the touchui builds.
+Use drop down box for touchui builds instead.
+-->
+<if expr="pp_ifdef('chromeos')">
+ <ul id="add-language-overlay-language-list">
+ </ul>
+ <button id="add-language-overlay-cancel-button"
+ i18n-content="cancel"></button>
+</if>
+<if expr="not pp_ifdef('chromeos')">
+ <h1 i18n-content="add_language_title"></h1>
+ <div class="content-area">
+ <label>
+ <span i18n-content="add_language_select_label"></span>
+ <select id="add-language-overlay-language-list"></select>
+ </label>
+ </div>
+ <div class="action-area">
+ <div class="button-strip">
+ <button id="add-language-overlay-cancel-button" i18n-content="cancel">
+ </button>
+ <button id="add-language-overlay-ok-button" i18n-content="ok"></button>
+ </div>
+ </div>
+</if>
+</div>
diff --git a/chrome/browser/resources/options2/language_add_language_overlay.js b/chrome/browser/resources/options2/language_add_language_overlay.js
new file mode 100644
index 0000000..9fa856d
--- /dev/null
+++ b/chrome/browser/resources/options2/language_add_language_overlay.js
@@ -0,0 +1,73 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+///////////////////////////////////////////////////////////////////////////////
+// AddLanguageOverlay class:
+
+cr.define('options', function() {
+ const OptionsPage = options.OptionsPage;
+
+ /**
+ * Encapsulated handling of ChromeOS add language overlay page.
+ * @constructor
+ */
+ function AddLanguageOverlay() {
+ OptionsPage.call(this, 'addLanguage',
+ localStrings.getString('add_button'),
+ 'add-language-overlay-page');
+ }
+
+ cr.addSingletonGetter(AddLanguageOverlay);
+
+ AddLanguageOverlay.prototype = {
+ // Inherit AddLanguageOverlay from OptionsPage.
+ __proto__: OptionsPage.prototype,
+
+ /**
+ * Initializes AddLanguageOverlay page.
+ * Calls base class implementation to starts preference initialization.
+ */
+ initializePage: function() {
+ // Call base class implementation to starts preference initialization.
+ OptionsPage.prototype.initializePage.call(this);
+
+ // Set up the cancel button.
+ $('add-language-overlay-cancel-button').onclick = function(e) {
+ OptionsPage.closeOverlay();
+ };
+
+ // Create the language list with which users can add a language.
+ var addLanguageList = $('add-language-overlay-language-list');
+ var languageListData = templateData.languageList;
+ for (var i = 0; i < languageListData.length; i++) {
+ var language = languageListData[i];
+ var displayText = language.displayName;
+ // If the native name is different, add it.
+ if (language.displayName != language.nativeDisplayName) {
+ displayText += ' - ' + language.nativeDisplayName;
+ }
+
+ if (cr.isChromeOS) {
+ var button = document.createElement('button');
+ button.className = 'link-button';
+ button.textContent = displayText;
+ button.languageCode = language.code;
+ var li = document.createElement('li');
+ li.languageCode = language.code;
+ li.appendChild(button);
+ addLanguageList.appendChild(li);
+ } else {
+ var option = document.createElement('option');
+ option.value = language.code;
+ option.textContent = displayText;
+ addLanguageList.appendChild(option);
+ }
+ }
+ },
+ };
+
+ return {
+ AddLanguageOverlay: AddLanguageOverlay
+ };
+});
diff --git a/chrome/browser/resources/options2/language_list.js b/chrome/browser/resources/options2/language_list.js
new file mode 100644
index 0000000..3cbcb75
--- /dev/null
+++ b/chrome/browser/resources/options2/language_list.js
@@ -0,0 +1,487 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+ const ArrayDataModel = cr.ui.ArrayDataModel;
+ const DeletableItem = options.DeletableItem;
+ const DeletableItemList = options.DeletableItemList;
+ const List = cr.ui.List;
+ const ListItem = cr.ui.ListItem;
+ const ListSingleSelectionModel = cr.ui.ListSingleSelectionModel;
+
+ /**
+ * Creates a new Language list item.
+ * @param {String} languageCode the languageCode.
+ * @constructor
+ * @extends {DeletableItem.ListItem}
+ */
+ function LanguageListItem(languageCode) {
+ var el = cr.doc.createElement('li');
+ el.__proto__ = LanguageListItem.prototype;
+ el.languageCode_ = languageCode;
+ el.decorate();
+ return el;
+ };
+
+ LanguageListItem.prototype = {
+ __proto__: DeletableItem.prototype,
+
+ /**
+ * The language code of this language.
+ * @type {String}
+ * @private
+ */
+ languageCode_: null,
+
+ /** @inheritDoc */
+ decorate: function() {
+ DeletableItem.prototype.decorate.call(this);
+
+ var languageCode = this.languageCode_;
+ var languageOptions = options.LanguageOptions.getInstance();
+ this.deletable = languageOptions.languageIsDeletable(languageCode);
+ this.languageCode = languageCode;
+ this.languageName = cr.doc.createElement('div');
+ this.languageName.className = 'language-name';
+ this.languageName.textContent =
+ LanguageList.getDisplayNameFromLanguageCode(languageCode);
+ this.contentElement.appendChild(this.languageName);
+ this.title =
+ LanguageList.getNativeDisplayNameFromLanguageCode(languageCode);
+ this.draggable = true;
+ },
+ };
+
+ /**
+ * Creates a new language list.
+ * @param {Object=} opt_propertyBag Optional properties.
+ * @constructor
+ * @extends {cr.ui.List}
+ */
+ var LanguageList = cr.ui.define('list');
+
+ /**
+ * Gets display name from the given language code.
+ * @param {string} languageCode Language code (ex. "fr").
+ */
+ LanguageList.getDisplayNameFromLanguageCode = function(languageCode) {
+ // Build the language code to display name dictionary at first time.
+ if (!this.languageCodeToDisplayName_) {
+ this.languageCodeToDisplayName_ = {};
+ var languageList = templateData.languageList;
+ for (var i = 0; i < languageList.length; i++) {
+ var language = languageList[i];
+ this.languageCodeToDisplayName_[language.code] = language.displayName;
+ }
+ }
+
+ return this.languageCodeToDisplayName_[languageCode];
+ }
+
+ /**
+ * Gets native display name from the given language code.
+ * @param {string} languageCode Language code (ex. "fr").
+ */
+ LanguageList.getNativeDisplayNameFromLanguageCode = function(languageCode) {
+ // Build the language code to display name dictionary at first time.
+ if (!this.languageCodeToNativeDisplayName_) {
+ this.languageCodeToNativeDisplayName_ = {};
+ var languageList = templateData.languageList;
+ for (var i = 0; i < languageList.length; i++) {
+ var language = languageList[i];
+ this.languageCodeToNativeDisplayName_[language.code] =
+ language.nativeDisplayName;
+ }
+ }
+
+ return this.languageCodeToNativeDisplayName_[languageCode];
+ }
+
+ /**
+ * Returns true if the given language code is valid.
+ * @param {string} languageCode Language code (ex. "fr").
+ */
+ LanguageList.isValidLanguageCode = function(languageCode) {
+ // Having the display name for the language code means that the
+ // language code is valid.
+ if (LanguageList.getDisplayNameFromLanguageCode(languageCode)) {
+ return true;
+ }
+ return false;
+ }
+
+ LanguageList.prototype = {
+ __proto__: DeletableItemList.prototype,
+
+ // The list item being dragged.
+ draggedItem: null,
+ // The drop position information: "below" or "above".
+ dropPos: null,
+ // The preference is a CSV string that describes preferred languages
+ // in Chrome OS. The language list is used for showing the language
+ // list in "Language and Input" options page.
+ preferredLanguagesPref: 'settings.language.preferred_languages',
+ // The preference is a CSV string that describes accept languages used
+ // for content negotiation. To be more precise, the list will be used
+ // in "Accept-Language" header in HTTP requests.
+ acceptLanguagesPref: 'intl.accept_languages',
+
+ /** @inheritDoc */
+ decorate: function() {
+ DeletableItemList.prototype.decorate.call(this);
+ this.selectionModel = new ListSingleSelectionModel;
+
+ // HACK(arv): http://crbug.com/40902
+ window.addEventListener('resize', this.redraw.bind(this));
+
+ // Listen to pref change.
+ if (cr.isChromeOS) {
+ Preferences.getInstance().addEventListener(this.preferredLanguagesPref,
+ this.handlePreferredLanguagesPrefChange_.bind(this));
+ } else {
+ Preferences.getInstance().addEventListener(this.acceptLanguagesPref,
+ this.handleAcceptLanguagesPrefChange_.bind(this));
+ }
+
+ // Listen to drag and drop events.
+ this.addEventListener('dragstart', this.handleDragStart_.bind(this));
+ this.addEventListener('dragenter', this.handleDragEnter_.bind(this));
+ this.addEventListener('dragover', this.handleDragOver_.bind(this));
+ this.addEventListener('drop', this.handleDrop_.bind(this));
+ this.addEventListener('dragleave', this.handleDragLeave_.bind(this));
+ },
+
+ createItem: function(languageCode) {
+ return new LanguageListItem(languageCode);
+ },
+
+ /*
+ * For each item, determines whether it's deletable.
+ */
+ updateDeletable: function() {
+ var items = this.items;
+ for (var i = 0; i < items.length; ++i) {
+ var item = items[i];
+ var languageCode = item.languageCode;
+ var languageOptions = options.LanguageOptions.getInstance();
+ item.deletable = languageOptions.languageIsDeletable(languageCode);
+ }
+ },
+
+ /*
+ * Adds a language to the language list.
+ * @param {string} languageCode language code (ex. "fr").
+ */
+ addLanguage: function(languageCode) {
+ // It shouldn't happen but ignore the language code if it's
+ // null/undefined, or already present.
+ if (!languageCode || this.dataModel.indexOf(languageCode) >= 0) {
+ return;
+ }
+ this.dataModel.push(languageCode);
+ // Select the last item, which is the language added.
+ this.selectionModel.selectedIndex = this.dataModel.length - 1;
+
+ this.savePreference_();
+ },
+
+ /*
+ * Gets the language codes of the currently listed languages.
+ */
+ getLanguageCodes: function() {
+ return this.dataModel.slice();
+ },
+
+ /*
+ * Gets the language code of the selected language.
+ */
+ getSelectedLanguageCode: function() {
+ return this.selectedItem;
+ },
+
+ /*
+ * Selects the language by the given language code.
+ * @returns {boolean} True if the operation is successful.
+ */
+ selectLanguageByCode: function(languageCode) {
+ var index = this.dataModel.indexOf(languageCode);
+ if (index >= 0) {
+ this.selectionModel.selectedIndex = index;
+ return true;
+ }
+ return false;
+ },
+
+ /** @inheritDoc */
+ deleteItemAtIndex: function(index) {
+ if (index >= 0) {
+ this.dataModel.splice(index, 1);
+ // Once the selected item is removed, there will be no selected item.
+ // Select the item pointed by the lead index.
+ index = this.selectionModel.leadIndex;
+ this.savePreference_();
+ }
+ return index;
+ },
+
+ /*
+ * Computes the target item of drop event.
+ * @param {Event} e The drop or dragover event.
+ * @private
+ */
+ getTargetFromDropEvent_ : function(e) {
+ var target = e.target;
+ // e.target may be an inner element of the list item
+ while (target != null && !(target instanceof ListItem)) {
+ target = target.parentNode;
+ }
+ return target;
+ },
+
+ /*
+ * Handles the dragstart event.
+ * @param {Event} e The dragstart event.
+ * @private
+ */
+ handleDragStart_: function(e) {
+ var target = e.target;
+ // ListItem should be the only draggable element type in the page,
+ // but just in case.
+ if (target instanceof ListItem) {
+ this.draggedItem = target;
+ e.dataTransfer.effectAllowed = 'move';
+ // We need to put some kind of data in the drag or it will be
+ // ignored. Use the display name in case the user drags to a text
+ // field or the desktop.
+ e.dataTransfer.setData('text/plain', target.title);
+ }
+ },
+
+ /*
+ * Handles the dragenter event.
+ * @param {Event} e The dragenter event.
+ * @private
+ */
+ handleDragEnter_: function(e) {
+ e.preventDefault();
+ },
+
+ /*
+ * Handles the dragover event.
+ * @param {Event} e The dragover event.
+ * @private
+ */
+ handleDragOver_: function(e) {
+ var dropTarget = this.getTargetFromDropEvent_(e);
+ // Determines whether the drop target is to accept the drop.
+ // The drop is only successful on another ListItem.
+ if (!(dropTarget instanceof ListItem) ||
+ dropTarget == this.draggedItem) {
+ this.hideDropMarker_();
+ return;
+ }
+ // Compute the drop postion. Should we move the dragged item to
+ // below or above the drop target?
+ var rect = dropTarget.getBoundingClientRect();
+ var dy = e.clientY - rect.top;
+ var yRatio = dy / rect.height;
+ var dropPos = yRatio <= .5 ? 'above' : 'below';
+ this.dropPos = dropPos;
+ this.showDropMarker_(dropTarget, dropPos);
+ e.preventDefault();
+ },
+
+ /*
+ * Handles the drop event.
+ * @param {Event} e The drop event.
+ * @private
+ */
+ handleDrop_: function(e) {
+ var dropTarget = this.getTargetFromDropEvent_(e);
+ this.hideDropMarker_();
+
+ // Delete the language from the original position.
+ var languageCode = this.draggedItem.languageCode;
+ var originalIndex = this.dataModel.indexOf(languageCode);
+ this.dataModel.splice(originalIndex, 1);
+ // Insert the language to the new position.
+ var newIndex = this.dataModel.indexOf(dropTarget.languageCode);
+ if (this.dropPos == 'below')
+ newIndex += 1;
+ this.dataModel.splice(newIndex, 0, languageCode);
+ // The cursor should move to the moved item.
+ this.selectionModel.selectedIndex = newIndex;
+ // Save the preference.
+ this.savePreference_();
+ },
+
+ /*
+ * Handles the dragleave event.
+ * @param {Event} e The dragleave event
+ * @private
+ */
+ handleDragLeave_ : function(e) {
+ this.hideDropMarker_();
+ },
+
+ /*
+ * Shows and positions the marker to indicate the drop target.
+ * @param {HTMLElement} target The current target list item of drop
+ * @param {string} pos 'below' or 'above'
+ * @private
+ */
+ showDropMarker_ : function(target, pos) {
+ window.clearTimeout(this.hideDropMarkerTimer_);
+ var marker = $('language-options-list-dropmarker');
+ var rect = target.getBoundingClientRect();
+ var markerHeight = 8;
+ if (pos == 'above') {
+ marker.style.top = (rect.top - markerHeight/2) + 'px';
+ } else {
+ marker.style.top = (rect.bottom - markerHeight/2) + 'px';
+ }
+ marker.style.width = rect.width + 'px';
+ marker.style.left = rect.left + 'px';
+ marker.style.display = 'block';
+ },
+
+ /*
+ * Hides the drop marker.
+ * @private
+ */
+ hideDropMarker_ : function() {
+ // Hide the marker in a timeout to reduce flickering as we move between
+ // valid drop targets.
+ window.clearTimeout(this.hideDropMarkerTimer_);
+ this.hideDropMarkerTimer_ = window.setTimeout(function() {
+ $('language-options-list-dropmarker').style.display = '';
+ }, 100);
+ },
+
+ /**
+ * Handles preferred languages pref change.
+ * @param {Event} e The change event object.
+ * @private
+ */
+ handlePreferredLanguagesPrefChange_: function(e) {
+ var languageCodesInCsv = e.value.value;
+ var languageCodes = languageCodesInCsv.split(',');
+
+ // Add the UI language to the initial list of languages. This is to avoid
+ // a bug where the UI language would be removed from the preferred
+ // language list by sync on first login.
+ // See: crosbug.com/14283
+ languageCodes.push(navigator.language);
+ languageCodes = this.filterBadLanguageCodes_(languageCodes);
+ this.load_(languageCodes);
+ },
+
+ /**
+ * Handles accept languages pref change.
+ * @param {Event} e The change event object.
+ * @private
+ */
+ handleAcceptLanguagesPrefChange_: function(e) {
+ var languageCodesInCsv = e.value.value;
+ var languageCodes = this.filterBadLanguageCodes_(
+ languageCodesInCsv.split(','));
+ this.load_(languageCodes);
+ },
+
+ /**
+ * Loads given language list.
+ * @param {Array} languageCodes List of language codes.
+ * @private
+ */
+ load_: function(languageCodes) {
+ // Preserve the original selected index. See comments below.
+ var originalSelectedIndex = (this.selectionModel ?
+ this.selectionModel.selectedIndex : -1);
+ this.dataModel = new ArrayDataModel(languageCodes);
+ if (originalSelectedIndex >= 0 &&
+ originalSelectedIndex < this.dataModel.length) {
+ // Restore the original selected index if the selected index is
+ // valid after the data model is loaded. This is neeeded to keep
+ // the selected language after the languge is added or removed.
+ this.selectionModel.selectedIndex = originalSelectedIndex;
+ // The lead index should be updated too.
+ this.selectionModel.leadIndex = originalSelectedIndex;
+ } else if (this.dataModel.length > 0){
+ // Otherwise, select the first item if it's not empty.
+ // Note that ListSingleSelectionModel won't select an item
+ // automatically, hence we manually select the first item here.
+ this.selectionModel.selectedIndex = 0;
+ }
+ },
+
+ /**
+ * Saves the preference.
+ */
+ savePreference_: function() {
+ // Encode the language codes into a CSV string.
+ if (cr.isChromeOS)
+ Preferences.setStringPref(this.preferredLanguagesPref,
+ this.dataModel.slice().join(','));
+ // Save the same language list as accept languages preference as
+ // well, but we need to expand the language list, to make it more
+ // acceptable. For instance, some web sites don't understand 'en-US'
+ // but 'en'. See crosbug.com/9884.
+ var acceptLanguages = this.expandLanguageCodes(this.dataModel.slice());
+ Preferences.setStringPref(this.acceptLanguagesPref,
+ acceptLanguages.join(','));
+ cr.dispatchSimpleEvent(this, 'save');
+ },
+
+ /**
+ * Expands language codes to make these more suitable for Accept-Language.
+ * Example: ['en-US', 'ja', 'en-CA'] => ['en-US', 'en', 'ja', 'en-CA'].
+ * 'en' won't appear twice as this function eliminates duplicates.
+ * @param {Array} languageCodes List of language codes.
+ * @private
+ */
+ expandLanguageCodes: function(languageCodes) {
+ var expandedLanguageCodes = [];
+ var seen = {}; // Used to eliminiate duplicates.
+ for (var i = 0; i < languageCodes.length; i++) {
+ var languageCode = languageCodes[i];
+ if (!(languageCode in seen)) {
+ expandedLanguageCodes.push(languageCode);
+ seen[languageCode] = true;
+ }
+ var parts = languageCode.split('-');
+ if (!(parts[0] in seen)) {
+ expandedLanguageCodes.push(parts[0]);
+ seen[parts[0]] = true;
+ }
+ }
+ return expandedLanguageCodes;
+ },
+
+ /**
+ * Filters bad language codes in case bad language codes are
+ * stored in the preference. Removes duplicates as well.
+ * @param {Array} languageCodes List of language codes.
+ * @private
+ */
+ filterBadLanguageCodes_: function(languageCodes) {
+ var filteredLanguageCodes = [];
+ var seen = {};
+ for (var i = 0; i < languageCodes.length; i++) {
+ // Check if the the language code is valid, and not
+ // duplicate. Otherwise, skip it.
+ if (LanguageList.isValidLanguageCode(languageCodes[i]) &&
+ !(languageCodes[i] in seen)) {
+ filteredLanguageCodes.push(languageCodes[i]);
+ seen[languageCodes[i]] = true;
+ }
+ }
+ return filteredLanguageCodes;
+ },
+ };
+
+ return {
+ LanguageList: LanguageList,
+ LanguageListItem: LanguageListItem
+ };
+});
diff --git a/chrome/browser/resources/options2/language_options.css b/chrome/browser/resources/options2/language_options.css
new file mode 100644
index 0000000..55b7aa8
--- /dev/null
+++ b/chrome/browser/resources/options2/language_options.css
@@ -0,0 +1,239 @@
+.language-options {
+ display: -webkit-box;
+ margin: 10px 0;
+}
+
+.language-options-lower-left button,
+.language-options-right button {
+ min-width: 70px;
+}
+
+.language-options h3 {
+ -webkit-margin-start: 12px;
+ font-size: 100%;
+ font-weight: bold;
+ margin-top: 12px;
+}
+
+.language-options-contents {
+ -webkit-padding-start: 12px;
+ -webkit-padding-end: 12px;
+ padding-bottom: 10px;
+}
+
+.language-options-header, .language-options-footer {
+ margin: 10px 0;
+}
+
+.language-options-left, .language-options-right {
+ border: 1px solid #cccccc;
+ vertical-align: top;
+ padding: 0;
+ height: 400px;
+}
+
+.language-options-left {
+ -webkit-box-orient: vertical;
+ display: -webkit-box;
+ background-color: #ebeff9;
+ width: 300px;
+}
+
+/* On OS X we use the native OS spellchecker, so don't display the dictionary */
+/* pane. */
+html[os=mac] .language-options-left {
+ background-color: white;
+}
+
+html[os=mac] .language-options-right {
+ visibility: hidden;
+}
+
+.language-options-lower-left {
+ -webkit-box-flex: 0;
+ -webkit-padding-start: 12px;
+ padding-bottom: 10px;
+}
+
+.language-options-right {
+ /* To share the center line with the left pane. */
+ -webkit-margin-start: -1px;
+ width: 360px;
+}
+
+.language-options-notification {
+ display: none;
+ background-color: #fff29e;
+ border-top: 1px solid #ccc;
+ border-bottom: 1px solid #ccc;
+ padding: 12px 30px 12px 12px;
+}
+
+#language-options-input-method-list button {
+ display: block;
+ -webkit-margin-start: 20px;
+}
+
+#language-options-ui-language-button {
+ width: 95%;
+ -webkit-margin-start: 10px;
+}
+
+#language-options-spell-check-language-button {
+ width: 95%;
+ -webkit-margin-start: 10px;
+}
+
+#language-options-input-method-list label {
+ margin: 4px 0;
+}
+
+#language-options-list {
+ -webkit-box-flex: 1;
+ outline: none;
+ padding: 1px 0 0;
+ width: 100%;
+}
+
+#language-options-list .language-name {
+ -webkit-box-flex: 1;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+#language-options-list li {
+ -webkit-padding-start: 12px;
+ padding-top: 2px;
+ padding-bottom: 2px;
+}
+
+#language-options-list-dropmarker {
+ background-color: hsl(214, 91%, 65%);
+ background-clip: padding-box;
+ border: 3px solid hsl(214, 91%, 65%);
+ border-bottom-color: transparent;
+ border-top-color: transparent;
+ border-radius: 0;
+ box-sizing: border-box;
+ display: none;
+ height: 8px;
+ overflow: hidden;
+ pointer-events: none;
+ position: fixed;
+ z-index: 10;
+}
+
+#language-options-ui-restart-button {
+ margin-top: 12px;
+}
+
+/*
+ * In ChromeOS we present the language choices as a big page of links.
+ */
+
+html[os=chromeos] #add-language-overlay-language-list {
+ -webkit-column-count: 2;
+ -webkit-column-gap: 20px;
+}
+
+html[os=chromeos] #add-language-overlay-cancel-button {
+ /* Place the button in the center. */
+ display: block;
+ margin: auto;
+ margin-top: 15px;
+}
+
+html[os=chromeos] #add-language-overlay-page {
+ width: 800px;
+ padding: 20px;
+}
+
+html[os=chromeos] #add-language-overlay-page button.link-button {
+ padding: 0;
+ text-align: left;
+}
+
+html[os=chromeos] #add-language-overlay-page ul {
+ padding: 0;
+ margin: 0;
+}
+
+/* TODO(kochi): This is temporary copy from new_tab.css */
+/* Notification */
+
+#notification {
+ position: relative;
+ background-color: hsl(52, 100%, 80%);
+ border: 1px solid rgb(211, 211, 211);
+ border-radius: 6px;
+ padding: 7px 15px;
+ white-space: nowrap;
+ display: table;
+ /* Set the height and margin so that the element does not use any vertical
+ space */
+ height: 16px;
+ margin: -44px auto 12px auto;
+ font-weight: bold;
+ opacity: 0;
+ pointer-events: none;
+ -webkit-transition: opacity 150ms;
+ z-index: 1;
+ color: black;
+}
+
+#notification > * {
+ display: table-cell;
+ max-width: 500px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+#notification.show {
+ opacity: 1;
+ pointer-events: all;
+ -webkit-transition: opacity 1s;
+}
+
+#notification .link {
+ cursor: pointer;
+ text-decoration: underline;
+ -webkit-appearance: none;
+ border: 0;
+ background: none;
+ color: rgba(0, 102, 204, 0.3);
+ -webkit-padding-start: 20px;
+}
+
+#notification .link-color {
+ color: rgb(0, 102, 204);
+}
+
+#chewing-max-chi-symbol-len {
+ width: 100px;
+ height: 30%;
+}
+
+#add-language-overlay-page .content-area {
+ padding-bottom: 10px;
+}
+
+.text-button,
+.text-button:active,
+.text-button:focus,
+.text-button:hover {
+ -webkit-box-shadow: none;
+ background: transparent none;
+ border-color: transparent;
+ color: #000;
+}
+
+button[disabled].text-button,
+button[disabled].text-button:active,
+button[disabled].text-button:focus,
+button[disabled].text-button:hover {
+ -webkit-box-shadow: none;
+ background: transparent none;
+ border-color: transparent;
+ color: #AAA;
+}
diff --git a/chrome/browser/resources/options2/language_options.html b/chrome/browser/resources/options2/language_options.html
new file mode 100644
index 0000000..46373c1
--- /dev/null
+++ b/chrome/browser/resources/options2/language_options.html
@@ -0,0 +1,78 @@
+<div id="languagePage" class="page" hidden>
+ <h1 i18n-content="languagePage"></h1>
+ <div id="notification">
+ <span>&nbsp;</span>
+ <span class="link"><span class="link-color"></span></span>
+ </div>
+ <div class="language-options-header">
+ <div i18n-content="add_language_instructions"></div>
+<if expr="pp_ifdef('chromeos')">
+ <div i18n-content="input_method_instructions"></div>
+</if>
+ </div>
+ <div class="language-options">
+ <div class="language-options-left">
+ <h3 i18n-content="languages"></h3>
+ <list id="language-options-list"></list>
+ <div class="language-options-lower-left">
+ <button id="language-options-add-button"
+ i18n-content="add_button"></button>
+ </div>
+ <div id="language-options-list-dropmarker"></div>
+ </div>
+ <div class="language-options-right">
+ <h3 id="language-options-language-name"></h3>
+<if expr="os == 'win32' or pp_ifdef('chromeos')">
+ <div class="language-options-contents">
+ <button id="language-options-ui-language-button"></button>
+ </div>
+</if>
+ <div class="language-options-contents">
+ <button id="language-options-spell-check-language-button"></button>
+ </div>
+ <div id="language-options-ui-notification-bar"
+ class="language-options-notification">
+ <div i18n-content="restart_required"></div>
+<if expr="pp_ifdef('chromeos')">
+ <button id="language-options-ui-restart-button"
+ i18n-content="restart_button"></button>
+</if>
+ </div>
+<if expr="pp_ifdef('chromeos')">
+ <h3 i18n-content="input_method"></h3>
+ <div id="language-options-input-method-list"
+ class="language-options-contents">
+ </div>
+</if>
+<if expr="pp_ifdef('chromeos') and pp_ifdef('use_virtual_keyboard')">
+ <div class="language-options-contents">
+ <button id="language-options-virtual-keyboard"
+ i18n-content="virtual_keyboard_button"></button>
+ </div>
+</if>
+ </div>
+ </div>
+ <div class="language-options-footer">
+<if expr="pp_ifdef('chromeos')">
+ <div i18n-content="switch_input_methods_hint"></div>
+ <div i18n-content="select_previous_input_method_hint"></div>
+</if>
+<if expr="not pp_ifdef('chromeos') and not is_macosx">
+ <div id="spell-check-option" class="checkbox">
+ <label>
+ <input id="enable-spell-check" pref="browser.enable_spellchecking"
+ metric="Options_SpellCheck" type="checkbox">
+ <span i18n-content="enable_spell_check"></span>
+ </label>
+ </div>
+ <div id="auto-spell-correction-option" class="checkbox" hidden>
+ <label>
+ <input id="enable-auto-spell-correction"
+ pref="browser.enable_autospellcorrect"
+ metric="Options_AutoSpellCorrection" type="checkbox">
+ <span i18n-content="enable_auto_spell_correction"></span>
+ </label>
+ </div>
+</if>
+ </div>
+</div>
diff --git a/chrome/browser/resources/options2/language_options.js b/chrome/browser/resources/options2/language_options.js
new file mode 100644
index 0000000..47674ed
--- /dev/null
+++ b/chrome/browser/resources/options2/language_options.js
@@ -0,0 +1,815 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// TODO(kochi): Generalize the notification as a component and put it
+// in js/cr/ui/notification.js .
+
+cr.define('options', function() {
+ const OptionsPage = options.OptionsPage;
+ const LanguageList = options.LanguageList;
+
+ // Some input methods like Chinese Pinyin have config pages.
+ // This is the map of the input method names to their config page names.
+ const INPUT_METHOD_ID_TO_CONFIG_PAGE_NAME = {
+ 'mozc': 'languageMozc',
+ 'mozc-chewing': 'languageChewing',
+ 'mozc-dv': 'languageMozc',
+ 'mozc-hangul': 'languageHangul',
+ 'mozc-jp': 'languageMozc',
+ 'pinyin': 'languagePinyin',
+ 'pinyin-dv': 'languagePinyin',
+ };
+
+ /////////////////////////////////////////////////////////////////////////////
+ // LanguageOptions class:
+
+ /**
+ * Encapsulated handling of ChromeOS language options page.
+ * @constructor
+ */
+ function LanguageOptions(model) {
+ OptionsPage.call(this, 'languages', templateData.languagePageTabTitle,
+ 'languagePage');
+ }
+
+ cr.addSingletonGetter(LanguageOptions);
+
+ // Inherit LanguageOptions from OptionsPage.
+ LanguageOptions.prototype = {
+ __proto__: OptionsPage.prototype,
+
+ /**
+ * Initializes LanguageOptions page.
+ * Calls base class implementation to starts preference initialization.
+ */
+ initializePage: function() {
+ OptionsPage.prototype.initializePage.call(this);
+
+ var languageOptionsList = $('language-options-list');
+ LanguageList.decorate(languageOptionsList);
+
+ languageOptionsList.addEventListener('change',
+ this.handleLanguageOptionsListChange_.bind(this));
+ languageOptionsList.addEventListener('save',
+ this.handleLanguageOptionsListSave_.bind(this));
+
+ this.addEventListener('visibleChange',
+ this.handleVisibleChange_.bind(this));
+
+ if (cr.isChromeOS) {
+ this.initializeInputMethodList_();
+ this.initializeLanguageCodeToInputMethodIdsMap_();
+ }
+ Preferences.getInstance().addEventListener(this.spellCheckDictionaryPref,
+ this.handleSpellCheckDictionaryPrefChange_.bind(this));
+
+ // Set up add button.
+ $('language-options-add-button').onclick = function(e) {
+ // Add the language without showing the overlay if it's specified in
+ // the URL hash (ex. lang_add=ja). Used for automated testing.
+ var match = document.location.hash.match(/\blang_add=([\w-]+)/);
+ if (match) {
+ var addLanguageCode = match[1];
+ $('language-options-list').addLanguage(addLanguageCode);
+ } else {
+ OptionsPage.navigateToPage('addLanguage');
+ }
+ };
+
+ if (cr.isChromeOS) {
+ // Listen to user clicks on the add language list.
+ var addLanguageList = $('add-language-overlay-language-list');
+ addLanguageList.addEventListener('click',
+ this.handleAddLanguageListClick_.bind(this));
+ } else {
+ // Listen to add language dialog ok button.
+ var addLanguageOkButton = $('add-language-overlay-ok-button');
+ addLanguageOkButton.addEventListener('click',
+ this.handleAddLanguageOkButtonClick_.bind(this));
+
+ // Show experimental features if enabled.
+ if (templateData.experimentalSpellCheckFeatures == 'true')
+ $('auto-spell-correction-option').hidden = false;
+
+ // Handle spell check enable/disable.
+ if (!cr.isMac) {
+ Preferences.getInstance().addEventListener(
+ this.enableSpellCheckPref,
+ this.updateEnableSpellCheck_.bind(this));
+ }
+ }
+
+ // Listen to user clicks on the "Change touch keyboard settings..."
+ // button (if it exists).
+ var virtualKeyboardButton = $('language-options-virtual-keyboard');
+ if (virtualKeyboardButton) {
+ // TODO(yusukes): would be better to hide the button if no virtual
+ // keyboard is registered.
+ virtualKeyboardButton.onclick = function(e) {
+ OptionsPage.navigateToPage('virtualKeyboards');
+ };
+ }
+ },
+
+ // The preference is a boolean that enables/disables spell checking.
+ enableSpellCheckPref: 'browser.enable_spellchecking',
+ // The preference is a CSV string that describes preload engines
+ // (i.e. active input methods).
+ preloadEnginesPref: 'settings.language.preload_engines',
+ // The list of preload engines, like ['mozc', 'pinyin'].
+ preloadEngines_: [],
+ // The preference is a string that describes the spell check
+ // dictionary language, like "en-US".
+ spellCheckDictionaryPref: 'spellcheck.dictionary',
+ spellCheckDictionary_: "",
+ // The map of language code to input method IDs, like:
+ // {'ja': ['mozc', 'mozc-jp'], 'zh-CN': ['pinyin'], ...}
+ languageCodeToInputMethodIdsMap_: {},
+
+ /**
+ * Initializes the input method list.
+ */
+ initializeInputMethodList_: function() {
+ var inputMethodList = $('language-options-input-method-list');
+ var inputMethodListData = templateData.inputMethodList;
+
+ // Add all input methods, but make all of them invisible here. We'll
+ // change the visibility in handleLanguageOptionsListChange_() based
+ // on the selected language. Note that we only have less than 100
+ // input methods, so creating DOM nodes at once here should be ok.
+ for (var i = 0; i < inputMethodListData.length; i++) {
+ var inputMethod = inputMethodListData[i];
+ var input = document.createElement('input');
+ input.type = 'checkbox';
+ input.inputMethodId = inputMethod.id;
+ // Listen to user clicks.
+ input.addEventListener('click',
+ this.handleCheckboxClick_.bind(this));
+ var label = document.createElement('label');
+ label.appendChild(input);
+ // Adding a space between the checkbox and the text. This is a bit
+ // dirty, but we rely on a space character for all other checkboxes.
+ label.appendChild(document.createTextNode(
+ ' ' + inputMethod.displayName));
+ label.style.display = 'none';
+ label.languageCodeSet = inputMethod.languageCodeSet;
+ // Add the configure button if the config page is present for this
+ // input method.
+ if (inputMethod.id in INPUT_METHOD_ID_TO_CONFIG_PAGE_NAME) {
+ var pageName = INPUT_METHOD_ID_TO_CONFIG_PAGE_NAME[inputMethod.id];
+ var button = this.createConfigureInputMethodButton_(inputMethod.id,
+ pageName);
+ label.appendChild(button);
+ }
+
+ inputMethodList.appendChild(label);
+ }
+ // Listen to pref change once the input method list is initialized.
+ Preferences.getInstance().addEventListener(this.preloadEnginesPref,
+ this.handlePreloadEnginesPrefChange_.bind(this));
+ },
+
+ /**
+ * Creates a configure button for the given input method ID.
+ * @param {string} inputMethodId Input method ID (ex. "pinyin").
+ * @param {string} pageName Name of the config page (ex. "languagePinyin").
+ * @private
+ */
+ createConfigureInputMethodButton_: function(inputMethodId, pageName) {
+ var button = document.createElement('button');
+ button.textContent = localStrings.getString('configure');
+ button.onclick = function(e) {
+ // Prevent the default action (i.e. changing the checked property
+ // of the checkbox). The button click here should not be handled
+ // as checkbox click.
+ e.preventDefault();
+ chrome.send('inputMethodOptionsOpen', [inputMethodId]);
+ OptionsPage.navigateToPage(pageName);
+ }
+ return button;
+ },
+
+ /**
+ * Handles OptionsPage's visible property change event.
+ * @param {Event} e Property change event.
+ * @private
+ */
+ handleVisibleChange_: function(e) {
+ if (this.visible) {
+ $('language-options-list').redraw();
+ chrome.send('languageOptionsOpen');
+ }
+ },
+
+ /**
+ * Handles languageOptionsList's change event.
+ * @param {Event} e Change event.
+ * @private
+ */
+ handleLanguageOptionsListChange_: function(e) {
+ var languageOptionsList = $('language-options-list');
+ var languageCode = languageOptionsList.getSelectedLanguageCode();
+ // Select the language if it's specified in the URL hash (ex. lang=ja).
+ // Used for automated testing.
+ var match = document.location.hash.match(/\blang=([\w-]+)/);
+ if (match) {
+ var specifiedLanguageCode = match[1];
+ if (languageOptionsList.selectLanguageByCode(specifiedLanguageCode)) {
+ languageCode = specifiedLanguageCode;
+ }
+ }
+ this.updateSelectedLanguageName_(languageCode);
+ if (cr.isWindows || cr.isChromeOS)
+ this.updateUiLanguageButton_(languageCode);
+ if (!cr.isMac)
+ this.updateSpellCheckLanguageButton_(languageCode);
+ if (cr.isChromeOS)
+ this.updateInputMethodList_(languageCode);
+ this.updateLanguageListInAddLanguageOverlay_();
+ },
+
+ /**
+ * Handles languageOptionsList's save event.
+ * @param {Event} e Save event.
+ * @private
+ */
+ handleLanguageOptionsListSave_: function(e) {
+ if (cr.isChromeOS) {
+ // Sort the preload engines per the saved languages before save.
+ this.preloadEngines_ = this.sortPreloadEngines_(this.preloadEngines_);
+ this.savePreloadEnginesPref_();
+ }
+ },
+
+ /**
+ * Sorts preloadEngines_ by languageOptionsList's order.
+ * @param {Array} preloadEngines List of preload engines.
+ * @return {Array} Returns sorted preloadEngines.
+ * @private
+ */
+ sortPreloadEngines_: function(preloadEngines) {
+ // For instance, suppose we have two languages and associated input
+ // methods:
+ //
+ // - Korean: hangul
+ // - Chinese: pinyin
+ //
+ // The preloadEngines preference should look like "hangul,pinyin".
+ // If the user reverse the order, the preference should be reorderd
+ // to "pinyin,hangul".
+ var languageOptionsList = $('language-options-list');
+ var languageCodes = languageOptionsList.getLanguageCodes();
+
+ // Convert the list into a dictonary for simpler lookup.
+ var preloadEngineSet = {};
+ for (var i = 0; i < preloadEngines.length; i++) {
+ preloadEngineSet[preloadEngines[i]] = true;
+ }
+
+ // Create the new preload engine list per the language codes.
+ var newPreloadEngines = [];
+ for (var i = 0; i < languageCodes.length; i++) {
+ var languageCode = languageCodes[i];
+ var inputMethodIds = this.languageCodeToInputMethodIdsMap_[
+ languageCode];
+ // Check if we have active input methods associated with the language.
+ for (var j = 0; j < inputMethodIds.length; j++) {
+ var inputMethodId = inputMethodIds[j];
+ if (inputMethodId in preloadEngineSet) {
+ // If we have, add it to the new engine list.
+ newPreloadEngines.push(inputMethodId);
+ // And delete it from the set. This is necessary as one input
+ // method can be associated with more than one language thus
+ // we should avoid having duplicates in the new list.
+ delete preloadEngineSet[inputMethodId];
+ }
+ }
+ }
+
+ return newPreloadEngines;
+ },
+
+ /**
+ * Initializes the map of language code to input method IDs.
+ * @private
+ */
+ initializeLanguageCodeToInputMethodIdsMap_: function() {
+ var inputMethodList = templateData.inputMethodList;
+ for (var i = 0; i < inputMethodList.length; i++) {
+ var inputMethod = inputMethodList[i];
+ for (var languageCode in inputMethod.languageCodeSet) {
+ if (languageCode in this.languageCodeToInputMethodIdsMap_) {
+ this.languageCodeToInputMethodIdsMap_[languageCode].push(
+ inputMethod.id);
+ } else {
+ this.languageCodeToInputMethodIdsMap_[languageCode] =
+ [inputMethod.id];
+ }
+ }
+ }
+ },
+
+ /**
+ * Updates the currently selected language name.
+ * @param {string} languageCode Language code (ex. "fr").
+ * @private
+ */
+ updateSelectedLanguageName_: function(languageCode) {
+ var languageDisplayName = LanguageList.getDisplayNameFromLanguageCode(
+ languageCode);
+ var languageNativeDisplayName =
+ LanguageList.getNativeDisplayNameFromLanguageCode(languageCode);
+ // If the native name is different, add it.
+ if (languageDisplayName != languageNativeDisplayName) {
+ languageDisplayName += ' - ' + languageNativeDisplayName;
+ }
+ // Update the currently selected language name.
+ var languageName = $('language-options-language-name');
+ if (languageDisplayName) {
+ languageName.hidden = false;
+ languageName.textContent = languageDisplayName;
+ } else {
+ languageName.hidden = true;
+ }
+ },
+
+ /**
+ * Updates the UI language button.
+ * @param {string} languageCode Language code (ex. "fr").
+ * @private
+ */
+ updateUiLanguageButton_: function(languageCode) {
+ var uiLanguageButton = $('language-options-ui-language-button');
+ // Check if the language code matches the current UI language.
+ if (languageCode == templateData.currentUiLanguageCode) {
+ // If it matches, the button just says that the UI language is
+ // currently in use.
+ uiLanguageButton.textContent =
+ localStrings.getString('is_displayed_in_this_language');
+ // Make it look like a text label.
+ uiLanguageButton.className = 'text-button';
+ // Remove the event listner.
+ uiLanguageButton.onclick = undefined;
+ } else if (languageCode in templateData.uiLanguageCodeSet) {
+ // If the language is supported as UI language, users can click on
+ // the button to change the UI language.
+ if (cr.commandLine && cr.commandLine.options['--bwsi']) {
+ // In the guest mode for ChromeOS, changing UI language does not make
+ // sense because it does not take effect after browser restart.
+ uiLanguageButton.hidden = true;
+ } else {
+ uiLanguageButton.textContent =
+ localStrings.getString('display_in_this_language');
+ uiLanguageButton.className = '';
+ // Send the change request to Chrome.
+ uiLanguageButton.onclick = function(e) {
+ chrome.send('uiLanguageChange', [languageCode]);
+ }
+ }
+ if (cr.isChromeOS) {
+ $('language-options-ui-restart-button').onclick = function(e) {
+ chrome.send('uiLanguageRestart');
+ }
+ }
+ } else {
+ // If the language is not supported as UI language, the button
+ // just says that Chromium OS cannot be displayed in this language.
+ uiLanguageButton.textContent =
+ localStrings.getString('cannot_be_displayed_in_this_language');
+ uiLanguageButton.className = 'text-button';
+ uiLanguageButton.onclick = undefined;
+ }
+ uiLanguageButton.style.display = 'block';
+ $('language-options-ui-notification-bar').style.display = 'none';
+ },
+
+ /**
+ * Updates the spell check language button.
+ * @param {string} languageCode Language code (ex. "fr").
+ * @private
+ */
+ updateSpellCheckLanguageButton_: function(languageCode) {
+ var display = 'block';
+ var spellCheckLanguageButton = $(
+ 'language-options-spell-check-language-button');
+ // Check if the language code matches the current spell check language.
+ if (languageCode == this.spellCheckDictionary_) {
+ // If it matches, the button just says that the spell check language is
+ // currently in use.
+ spellCheckLanguageButton.textContent =
+ localStrings.getString('is_used_for_spell_checking');
+ // Make it look like a text label.
+ spellCheckLanguageButton.className = 'text-button';
+ // Remove the event listner.
+ spellCheckLanguageButton.onclick = undefined;
+ } else if (languageCode in templateData.spellCheckLanguageCodeSet) {
+ // If the language is supported as spell check language, users can
+ // click on the button to change the spell check language.
+ spellCheckLanguageButton.textContent =
+ localStrings.getString('use_this_for_spell_checking');
+ spellCheckLanguageButton.className = '';
+ spellCheckLanguageButton.languageCode = languageCode;
+ // Add an event listner to the click event.
+ spellCheckLanguageButton.addEventListener('click',
+ this.handleSpellCheckLanguageButtonClick_.bind(this));
+ } else if (!languageCode) {
+ display = 'none';
+ } else {
+ // If the language is not supported as spell check language, the
+ // button just says that this language cannot be used for spell
+ // checking.
+ spellCheckLanguageButton.textContent =
+ localStrings.getString('cannot_be_used_for_spell_checking');
+ spellCheckLanguageButton.className = 'text-button';
+ spellCheckLanguageButton.onclick = undefined;
+ }
+ spellCheckLanguageButton.style.display = display;
+ $('language-options-ui-notification-bar').style.display = 'none';
+ },
+
+ /**
+ * Updates the input method list.
+ * @param {string} languageCode Language code (ex. "fr").
+ * @private
+ */
+ updateInputMethodList_: function(languageCode) {
+ // Give one of the checkboxes or buttons focus, if it's specified in the
+ // URL hash (ex. focus=mozc). Used for automated testing.
+ var focusInputMethodId = -1;
+ var match = document.location.hash.match(/\bfocus=([\w:-]+)\b/);
+ if (match) {
+ focusInputMethodId = match[1];
+ }
+ // Change the visibility of the input method list. Input methods that
+ // matches |languageCode| will become visible.
+ var inputMethodList = $('language-options-input-method-list');
+ var labels = inputMethodList.querySelectorAll('label');
+ for (var i = 0; i < labels.length; i++) {
+ var label = labels[i];
+ if (languageCode in label.languageCodeSet) {
+ label.style.display = 'block';
+ var input = label.childNodes[0];
+ // Give it focus if the ID matches.
+ if (input.inputMethodId == focusInputMethodId) {
+ input.focus();
+ }
+ } else {
+ label.style.display = 'none';
+ }
+ }
+
+ if (focusInputMethodId == 'add') {
+ $('language-options-add-button').focus();
+ }
+ },
+
+ /**
+ * Updates the language list in the add language overlay.
+ * @param {string} languageCode Language code (ex. "fr").
+ * @private
+ */
+ updateLanguageListInAddLanguageOverlay_: function(languageCode) {
+ // Change the visibility of the language list in the add language
+ // overlay. Languages that are already active will become invisible,
+ // so that users don't add the same language twice.
+ var languageOptionsList = $('language-options-list');
+ var languageCodes = languageOptionsList.getLanguageCodes();
+ var languageCodeSet = {};
+ for (var i = 0; i < languageCodes.length; i++) {
+ languageCodeSet[languageCodes[i]] = true;
+ }
+ var addLanguageList = $('add-language-overlay-language-list');
+ var lis = addLanguageList.querySelectorAll('li');
+ for (var i = 0; i < lis.length; i++) {
+ // The first child button knows the language code.
+ var button = lis[i].childNodes[0];
+ if (button.languageCode in languageCodeSet) {
+ lis[i].style.display = 'none';
+ } else {
+ lis[i].style.display = 'block';
+ }
+ }
+ },
+
+ /**
+ * Handles preloadEnginesPref change.
+ * @param {Event} e Change event.
+ * @private
+ */
+ handlePreloadEnginesPrefChange_: function(e) {
+ var value = e.value.value;
+ this.preloadEngines_ = this.filterBadPreloadEngines_(value.split(','));
+ this.updateCheckboxesFromPreloadEngines_();
+ $('language-options-list').updateDeletable();
+ },
+
+ /**
+ * Handles input method checkbox's click event.
+ * @param {Event} e Click event.
+ * @private
+ */
+ handleCheckboxClick_ : function(e) {
+ var checkbox = e.target;
+ if (this.preloadEngines_.length == 1 && !checkbox.checked) {
+ // Don't allow disabling the last input method.
+ this.showNotification_(
+ localStrings.getString('please_add_another_input_method'),
+ localStrings.getString('ok_button'));
+ checkbox.checked = true;
+ return;
+ }
+ if (checkbox.checked) {
+ chrome.send('inputMethodEnable', [checkbox.inputMethodId]);
+ } else {
+ chrome.send('inputMethodDisable', [checkbox.inputMethodId]);
+ }
+ this.updatePreloadEnginesFromCheckboxes_();
+ this.preloadEngines_ = this.sortPreloadEngines_(this.preloadEngines_);
+ this.savePreloadEnginesPref_();
+ },
+
+ /**
+ * Handles add language list's click event.
+ * @param {Event} e Click event.
+ */
+ handleAddLanguageListClick_ : function(e) {
+ var languageOptionsList = $('language-options-list');
+ var languageCode = e.target.languageCode;
+ // languageCode can be undefined, if click was made on some random
+ // place in the overlay, rather than a button. Ignore it.
+ if (!languageCode) {
+ return;
+ }
+ languageOptionsList.addLanguage(languageCode);
+ var inputMethodIds = this.languageCodeToInputMethodIdsMap_[languageCode];
+ // Enable the first input method for the language added.
+ if (inputMethodIds && inputMethodIds[0] &&
+ // Don't add the input method it's already present. This can
+ // happen if the same input method is shared among multiple
+ // languages (ex. English US keyboard is used for English US and
+ // Filipino).
+ this.preloadEngines_.indexOf(inputMethodIds[0]) == -1) {
+ this.preloadEngines_.push(inputMethodIds[0]);
+ this.updateCheckboxesFromPreloadEngines_();
+ this.savePreloadEnginesPref_();
+ }
+ OptionsPage.closeOverlay();
+ },
+
+ /**
+ * Handles add language dialog ok button.
+ */
+ handleAddLanguageOkButtonClick_ : function() {
+ var languagesSelect = $('add-language-overlay-language-list');
+ var selectedIndex = languagesSelect.selectedIndex;
+ if (selectedIndex >= 0) {
+ var selection = languagesSelect.options[selectedIndex];
+ $('language-options-list').addLanguage(String(selection.value));
+ OptionsPage.closeOverlay();
+ }
+ },
+
+ /**
+ * Checks if languageCode is deletable or not.
+ * @param {String} languageCode the languageCode to check for deletability.
+ */
+ languageIsDeletable: function(languageCode) {
+ // Don't allow removing the language if it's as UI language.
+ if (languageCode == templateData.currentUiLanguageCode)
+ return false;
+ return (!cr.isChromeOS ||
+ this.canDeleteLanguage_(languageCode));
+ },
+
+ /**
+ * Handles browse.enable_spellchecking change.
+ * @param {Event} e Change event.
+ * @private
+ */
+ updateEnableSpellCheck_: function() {
+ var value = !$('enable-spell-check').checked;
+
+ $('language-options-spell-check-language-button').disabled = value;
+ },
+
+ /**
+ * Handles spellCheckDictionaryPref change.
+ * @param {Event} e Change event.
+ * @private
+ */
+ handleSpellCheckDictionaryPrefChange_: function(e) {
+ var languageCode = e.value.value
+ this.spellCheckDictionary_ = languageCode;
+ var languageOptionsList = $('language-options-list');
+ var selectedLanguageCode = languageOptionsList.getSelectedLanguageCode();
+ this.updateSpellCheckLanguageButton_(selectedLanguageCode);
+ },
+
+ /**
+ * Handles spellCheckLanguageButton click.
+ * @param {Event} e Click event.
+ * @private
+ */
+ handleSpellCheckLanguageButtonClick_: function(e) {
+ var languageCode = e.target.languageCode;
+ // Save the preference.
+ Preferences.setStringPref(this.spellCheckDictionaryPref,
+ languageCode);
+ chrome.send('spellCheckLanguageChange', [languageCode]);
+ },
+
+ /**
+ * Checks whether it's possible to remove the language specified by
+ * languageCode and returns true if possible. This function returns false
+ * if the removal causes the number of preload engines to be zero.
+ *
+ * @param {string} languageCode Language code (ex. "fr").
+ * @return {boolean} Returns true on success.
+ * @private
+ */
+ canDeleteLanguage_: function(languageCode) {
+ // First create the set of engines to be removed from input methods
+ // associated with the language code.
+ var enginesToBeRemovedSet = {};
+ var inputMethodIds = this.languageCodeToInputMethodIdsMap_[languageCode];
+ for (var i = 0; i < inputMethodIds.length; i++) {
+ enginesToBeRemovedSet[inputMethodIds[i]] = true;
+ }
+
+ // Then eliminate engines that are also used for other active languages.
+ // For instance, if "xkb:us::eng" is used for both English and Filipino.
+ var languageCodes = $('language-options-list').getLanguageCodes();
+ for (var i = 0; i < languageCodes.length; i++) {
+ // Skip the target language code.
+ if (languageCodes[i] == languageCode) {
+ continue;
+ }
+ // Check if input methods used in this language are included in
+ // enginesToBeRemovedSet. If so, eliminate these from the set, so
+ // we don't remove this time.
+ var inputMethodIdsForAnotherLanguage =
+ this.languageCodeToInputMethodIdsMap_[languageCodes[i]];
+ for (var j = 0; j < inputMethodIdsForAnotherLanguage.length; j++) {
+ var inputMethodId = inputMethodIdsForAnotherLanguage[j];
+ if (inputMethodId in enginesToBeRemovedSet) {
+ delete enginesToBeRemovedSet[inputMethodId];
+ }
+ }
+ }
+
+ // Update the preload engine list with the to-be-removed set.
+ var newPreloadEngines = [];
+ for (var i = 0; i < this.preloadEngines_.length; i++) {
+ if (!(this.preloadEngines_[i] in enginesToBeRemovedSet)) {
+ newPreloadEngines.push(this.preloadEngines_[i]);
+ }
+ }
+ // Don't allow this operation if it causes the number of preload
+ // engines to be zero.
+ return (newPreloadEngines.length > 0);
+ },
+
+ /**
+ * Saves the preload engines preference.
+ * @private
+ */
+ savePreloadEnginesPref_: function() {
+ Preferences.setStringPref(this.preloadEnginesPref,
+ this.preloadEngines_.join(','));
+ },
+
+ /**
+ * Updates the checkboxes in the input method list from the preload
+ * engines preference.
+ * @private
+ */
+ updateCheckboxesFromPreloadEngines_: function() {
+ // Convert the list into a dictonary for simpler lookup.
+ var dictionary = {};
+ for (var i = 0; i < this.preloadEngines_.length; i++) {
+ dictionary[this.preloadEngines_[i]] = true;
+ }
+
+ var inputMethodList = $('language-options-input-method-list');
+ var checkboxes = inputMethodList.querySelectorAll('input');
+ for (var i = 0; i < checkboxes.length; i++) {
+ checkboxes[i].checked = (checkboxes[i].inputMethodId in dictionary);
+ }
+ },
+
+ /**
+ * Updates the preload engines preference from the checkboxes in the
+ * input method list.
+ * @private
+ */
+ updatePreloadEnginesFromCheckboxes_: function() {
+ this.preloadEngines_ = [];
+ var inputMethodList = $('language-options-input-method-list');
+ var checkboxes = inputMethodList.querySelectorAll('input');
+ for (var i = 0; i < checkboxes.length; i++) {
+ if (checkboxes[i].checked) {
+ this.preloadEngines_.push(checkboxes[i].inputMethodId);
+ }
+ }
+ var languageOptionsList = $('language-options-list');
+ languageOptionsList.updateDeletable();
+ },
+
+ /**
+ * Filters bad preload engines in case bad preload engines are
+ * stored in the preference. Removes duplicates as well.
+ * @param {Array} preloadEngines List of preload engines.
+ * @private
+ */
+ filterBadPreloadEngines_: function(preloadEngines) {
+ // Convert the list into a dictonary for simpler lookup.
+ var dictionary = {};
+ for (var i = 0; i < templateData.inputMethodList.length; i++) {
+ dictionary[templateData.inputMethodList[i].id] = true;
+ }
+
+ var filteredPreloadEngines = [];
+ var seen = {};
+ for (var i = 0; i < preloadEngines.length; i++) {
+ // Check if the preload engine is present in the
+ // dictionary, and not duplicate. Otherwise, skip it.
+ if (preloadEngines[i] in dictionary && !(preloadEngines[i] in seen)) {
+ filteredPreloadEngines.push(preloadEngines[i]);
+ seen[preloadEngines[i]] = true;
+ }
+ }
+ return filteredPreloadEngines;
+ },
+
+ // TODO(kochi): This is an adapted copy from new_tab.js.
+ // If this will go as final UI, refactor this to share the component with
+ // new new tab page.
+ /**
+ * Shows notification
+ * @private
+ */
+ notificationTimeout_: null,
+ showNotification_ : function(text, actionText, opt_delay) {
+ var notificationElement = $('notification');
+ var actionLink = notificationElement.querySelector('.link-color');
+ var delay = opt_delay || 10000;
+
+ function show() {
+ window.clearTimeout(this.notificationTimeout_);
+ notificationElement.classList.add('show');
+ document.body.classList.add('notification-shown');
+ }
+
+ function hide() {
+ window.clearTimeout(this.notificationTimeout_);
+ notificationElement.classList.remove('show');
+ document.body.classList.remove('notification-shown');
+ // Prevent tabbing to the hidden link.
+ actionLink.tabIndex = -1;
+ // Setting tabIndex to -1 only prevents future tabbing to it. If,
+ // however, the user switches window or a tab and then moves back to
+ // this tab the element may gain focus. We therefore make sure that we
+ // blur the element so that the element focus is not restored when
+ // coming back to this window.
+ actionLink.blur();
+ }
+
+ function delayedHide() {
+ this.notificationTimeout_ = window.setTimeout(hide, delay);
+ }
+
+ notificationElement.firstElementChild.textContent = text;
+ actionLink.textContent = actionText;
+
+ actionLink.onclick = hide;
+ actionLink.onkeydown = function(e) {
+ if (e.keyIdentifier == 'Enter') {
+ hide();
+ }
+ };
+ notificationElement.onmouseover = show;
+ notificationElement.onmouseout = delayedHide;
+ actionLink.onfocus = show;
+ actionLink.onblur = delayedHide;
+ // Enable tabbing to the link now that it is shown.
+ actionLink.tabIndex = 0;
+
+ show();
+ delayedHide();
+ }
+ };
+
+ /**
+ * Chrome callback for when the UI language preference is saved.
+ */
+ LanguageOptions.uiLanguageSaved = function() {
+ $('language-options-ui-language-button').style.display = 'none';
+ $('language-options-ui-notification-bar').style.display = 'block';
+ };
+
+ // Export
+ return {
+ LanguageOptions: LanguageOptions
+ };
+});
diff --git a/chrome/browser/resources/options2/manage_profile_overlay.css b/chrome/browser/resources/options2/manage_profile_overlay.css
new file mode 100644
index 0000000..aa17290
--- /dev/null
+++ b/chrome/browser/resources/options2/manage_profile_overlay.css
@@ -0,0 +1,88 @@
+/* Copyright (c) 2011 The Chromium Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#manage-profile-overlay {
+ width: 500px;
+}
+
+.profile-icon-grid-item {
+ border: none !important;
+ height: 31px;
+ margin: 4px 6px;
+ padding: 4px;
+ width: 38px;
+}
+
+.profile-icon {
+ height: 31px;
+ width: 38px;
+}
+
+#manage-profile-content > :first-child {
+ margin-bottom: 5px;
+}
+
+#manage-profile-content > :last-child {
+ margin-top: 5px;
+}
+
+#manage-profile-content > :not(:first-child):not(:last-child) {
+ margin-top: 5px;
+ margin-bottom: 5px;
+}
+
+#manage-profile-name-div {
+ -webkit-box-align: baseline;
+ -webkit-box-orient: horizontal;
+ display: -webkit-box;
+}
+
+#manage-profile-name-label {
+ -webkit-margin-end: 20px;
+}
+
+#manage-profile-name {
+ -webkit-box-flex: 1;
+ display: block;
+}
+
+#manage-profile-name:invalid {
+ background-color: pink;
+}
+
+#manage-profile-error-bubble {
+ -webkit-transition: max-height 200ms, padding 200ms;
+ background-color: #eeb939;
+ border-radius: 4px;
+ font-weight: bold;
+ margin-left: auto;
+ margin-right: auto;
+ max-height: 50px;
+ overflow: hidden;
+ padding: 1px 10px;
+ text-align: center;
+ width: 80%;
+}
+
+#manage-profile-error-bubble[hidden] {
+ display: block !important;
+ max-height: 0;
+ padding: 0 10px;
+}
+
+#manage-profile-icon-grid {
+ background-color: rgba(255, 255, 255, 0.75);
+ border: 1px solid rgba(0, 0, 0, 0.3);
+ padding: 2px;
+}
+
+#delete-profile-message {
+ background-repeat: no-repeat;
+ -webkit-padding-start: 48px;
+}
+
+html[dir='rtl'] #delete-profile-message {
+ background-position: right;
+} \ No newline at end of file
diff --git a/chrome/browser/resources/options2/manage_profile_overlay.html b/chrome/browser/resources/options2/manage_profile_overlay.html
new file mode 100644
index 0000000..afc4801
--- /dev/null
+++ b/chrome/browser/resources/options2/manage_profile_overlay.html
@@ -0,0 +1,38 @@
+<div id="manage-profile-overlay" class="page" hidden>
+ <!-- Dialog for managing profiles. -->
+ <div id="manage-profile-overlay-manage" hidden>
+ <h1 i18n-content="manageProfilesTitle"></h1>
+ <div id="manage-profile-content" class="content-area">
+ <div id="manage-profile-name-div">
+ <span id="manage-profile-name-label"
+ i18n-content="manageProfilesNameLabel"></span>
+ <input id="manage-profile-name" type="text" required>
+ </div>
+ <div id="manage-profile-error-bubble" hidden>
+ </div>
+ <div id="manage-profile-icon-label"
+ i18n-content="manageProfilesIconLabel"></div>
+ <grid id="manage-profile-icon-grid"></grid>
+ </div>
+ <div class="action-area">
+ <div class="button-strip">
+ <button id="manage-profile-cancel" i18n-content="cancel"></button>
+ <button id="manage-profile-ok" i18n-content="ok"></button>
+ </div>
+ </div>
+ </div>
+ <!-- Dialog for deleting profiles. -->
+ <div id="manage-profile-overlay-delete" hidden>
+ <h1 i18n-content="deleteProfileTitle"></h1>
+ <div class="content-area">
+ <div id="delete-profile-message"></div>
+ </div>
+ <div class="action-area">
+ <div class="button-strip">
+ <button id="delete-profile-cancel" i18n-content="cancel"></button>
+ <button id="delete-profile-ok"
+ i18n-content="deleteProfileOK"></button>
+ </div>
+ </div>
+ </div>
+</div>
diff --git a/chrome/browser/resources/options2/manage_profile_overlay.js b/chrome/browser/resources/options2/manage_profile_overlay.js
new file mode 100644
index 0000000..18cccf2
--- /dev/null
+++ b/chrome/browser/resources/options2/manage_profile_overlay.js
@@ -0,0 +1,265 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+ var OptionsPage = options.OptionsPage;
+ var ArrayDataModel = cr.ui.ArrayDataModel;
+
+ const localStrings = new LocalStrings();
+
+ /**
+ * ManageProfileOverlay class
+ * Encapsulated handling of the 'Manage profile...' overlay page.
+ * @constructor
+ * @class
+ */
+ function ManageProfileOverlay() {
+ OptionsPage.call(this,
+ 'manageProfile',
+ templateData.manageProfileOverlayTabTitle,
+ 'manage-profile-overlay');
+ };
+
+ cr.addSingletonGetter(ManageProfileOverlay);
+
+ ManageProfileOverlay.prototype = {
+ // Inherit from OptionsPage.
+ __proto__: OptionsPage.prototype,
+
+ // Info about the currently managed/deleted profile.
+ profileInfo_: null,
+
+ // An object containing all known profile names.
+ profileNames_: {},
+
+ // The currently selected icon in the icon grid.
+ iconGridSelectedURL_: null,
+
+ /**
+ * Initialize the page.
+ */
+ initializePage: function() {
+ // Call base class implementation to start preference initialization.
+ OptionsPage.prototype.initializePage.call(this);
+
+ var self = this;
+ var iconGrid = $('manage-profile-icon-grid');
+ options.ProfilesIconGrid.decorate(iconGrid);
+ iconGrid.addEventListener('change', function(e) {
+ self.onIconGridSelectionChanged_();
+ });
+
+ $('manage-profile-name').oninput = this.onNameChanged_.bind(this);
+ $('manage-profile-cancel').onclick =
+ $('delete-profile-cancel').onclick = function(event) {
+ OptionsPage.closeOverlay();
+ };
+ $('manage-profile-ok').onclick = function(event) {
+ OptionsPage.closeOverlay();
+ self.submitManageChanges_();
+ };
+ $('delete-profile-ok').onclick = function(event) {
+ OptionsPage.closeOverlay();
+ chrome.send('deleteProfile', [self.profileInfo_.filePath]);
+ };
+ },
+
+ /** @inheritDoc */
+ didShowPage: function() {
+ chrome.send('requestDefaultProfileIcons');
+
+ // Use the hash to specify the profile index.
+ var hash = location.hash;
+ if (hash) {
+ $('manage-profile-overlay-manage').hidden = false;
+ $('manage-profile-overlay-delete').hidden = true;
+ ManageProfileOverlay.getInstance().hideErrorBubble_();
+
+ chrome.send('requestProfileInfo', [parseInt(hash.slice(1), 10)]);
+ }
+
+ $('manage-profile-name').focus();
+ },
+
+ /**
+ * Set the profile info used in the dialog.
+ * @param {Object} profileInfo An object of the form:
+ * profileInfo = {
+ * name: "Profile Name",
+ * iconURL: "chrome://path/to/icon/image",
+ * filePath: "/path/to/profile/data/on/disk"
+ * isCurrentProfile: false,
+ * };
+ * @private
+ */
+ setProfileInfo_: function(profileInfo) {
+ this.iconGridSelectedURL_ = profileInfo.iconURL;
+ this.profileInfo_ = profileInfo;
+ $('manage-profile-name').value = profileInfo.name;
+ $('manage-profile-icon-grid').selectedItem = profileInfo.iconURL;
+ },
+
+ /**
+ * Sets the name of the currently edited profile.
+ * @private
+ */
+ setProfileName_: function(name) {
+ if (this.profileInfo_)
+ this.profileInfo_.name = name;
+ $('manage-profile-name').value = name;
+ },
+
+ /**
+ * Set an array of default icon URLs. These will be added to the grid that
+ * the user will use to choose their profile icon.
+ * @param {Array.<string>} iconURLs An array of icon URLs.
+ * @private
+ */
+ receiveDefaultProfileIcons_: function(iconURLs) {
+ $('manage-profile-icon-grid').dataModel = new ArrayDataModel(iconURLs);
+
+ // Changing the dataModel resets the selectedItem. Re-select it, if there
+ // is one.
+ if (this.profileInfo_)
+ $('manage-profile-icon-grid').selectedItem = this.profileInfo_.iconURL;
+
+ var grid = $('manage-profile-icon-grid');
+ // Recalculate the measured item size.
+ grid.measured_ = null;
+ grid.columns = 0;
+ grid.redraw();
+ },
+
+ /**
+ * Set a dictionary of all profile names. These are used to prevent the
+ * user from naming two profiles the same.
+ * @param {Object} profileNames A dictionary of profile names.
+ * @private
+ */
+ receiveProfileNames_: function(profileNames) {
+ this.profileNames_ = profileNames;
+ },
+
+ /**
+ * Display the error bubble, with |errorText| in the bubble.
+ * @param {string} errorText The localized string id to display as an error.
+ * @private
+ */
+ showErrorBubble_: function(errorText) {
+ var nameErrorEl = $('manage-profile-error-bubble');
+ nameErrorEl.hidden = false;
+ nameErrorEl.textContent = localStrings.getString(errorText);
+
+ $('manage-profile-ok').disabled = true;
+ },
+
+ /**
+ * Hide the error bubble.
+ * @private
+ */
+ hideErrorBubble_: function() {
+ $('manage-profile-error-bubble').hidden = true;
+ $('manage-profile-ok').disabled = false;
+ },
+
+ /**
+ * oninput callback for <input> field.
+ * @param event The event object
+ * @private
+ */
+ onNameChanged_: function(event) {
+ var newName = event.target.value;
+ var oldName = this.profileInfo_.name;
+
+ if (newName == oldName) {
+ this.hideErrorBubble_();
+ } else if (this.profileNames_[newName] != undefined) {
+ this.showErrorBubble_('manageProfilesDuplicateNameError');
+ } else {
+ this.hideErrorBubble_();
+
+ var nameIsValid = $('manage-profile-name').validity.valid;
+ $('manage-profile-ok').disabled = !nameIsValid;
+ }
+ },
+
+ /**
+ * Called when the user clicks "OK". Saves the newly changed profile info.
+ * @private
+ */
+ submitManageChanges_: function() {
+ var name = $('manage-profile-name').value;
+ var iconURL = $('manage-profile-icon-grid').selectedItem;
+ chrome.send('setProfileNameAndIcon',
+ [this.profileInfo_.filePath, name, iconURL]);
+ },
+
+ /**
+ * Called when the selected icon in the icon grid changes.
+ * @private
+ */
+ onIconGridSelectionChanged_: function() {
+ var iconURL = $('manage-profile-icon-grid').selectedItem;
+ if (!iconURL || iconURL == this.iconGridSelectedURL_)
+ return;
+ this.iconGridSelectedURL_ = iconURL;
+ chrome.send('profileIconSelectionChanged',
+ [this.profileInfo_.filePath, iconURL]);
+ },
+
+ /**
+ * Display the "Manage Profile" dialog.
+ * @param {Object} profileInfo The profile object of the profile to manage.
+ * @private
+ */
+ showManageDialog_: function(profileInfo) {
+ ManageProfileOverlay.setProfileInfo(profileInfo);
+ $('manage-profile-overlay-manage').hidden = false;
+ $('manage-profile-overlay-delete').hidden = true;
+ ManageProfileOverlay.getInstance().hideErrorBubble_();
+
+ // Intentionally don't show the URL in the location bar as we don't want
+ // people trying to navigate here by hand.
+ OptionsPage.showPageByName('manageProfile', false);
+ },
+
+ /**
+ * Display the "Delete Profile" dialog.
+ * @param {Object} profileInfo The profile object of the profile to delete.
+ * @private
+ */
+ showDeleteDialog_: function(profileInfo) {
+ ManageProfileOverlay.setProfileInfo(profileInfo);
+ $('manage-profile-overlay-manage').hidden = true;
+ $('manage-profile-overlay-delete').hidden = false;
+ $('delete-profile-message').textContent =
+ localStrings.getStringF('deleteProfileMessage', profileInfo.name);
+ $('delete-profile-message').style.backgroundImage = 'url("' +
+ profileInfo.iconURL + '")';
+
+ // Intentionally don't show the URL in the location bar as we don't want
+ // people trying to navigate here by hand.
+ OptionsPage.showPageByName('manageProfile', false);
+ },
+ };
+
+ // Forward public APIs to private implementations.
+ [
+ 'receiveDefaultProfileIcons',
+ 'receiveProfileNames',
+ 'setProfileInfo',
+ 'setProfileName',
+ 'showManageDialog',
+ 'showDeleteDialog',
+ ].forEach(function(name) {
+ ManageProfileOverlay[name] = function(value) {
+ ManageProfileOverlay.getInstance()[name + '_'](value);
+ };
+ });
+
+ // Export
+ return {
+ ManageProfileOverlay: ManageProfileOverlay
+ };
+});
diff --git a/chrome/browser/resources/options2/options.html b/chrome/browser/resources/options2/options.html
new file mode 100644
index 0000000..5751729
--- /dev/null
+++ b/chrome/browser/resources/options2/options.html
@@ -0,0 +1,188 @@
+<!DOCTYPE HTML>
+<html id="t" i18n-values="dir:textdirection">
+<head>
+<meta charset="utf-8">
+<!-- Set the title to that of the default page so that the title doesn't flash
+ on load (for the most common case). -->
+<title i18n-content="browserPageTabTitle"></title>
+
+<link rel="icon" href="../../../app/theme/settings_favicon.png">
+<link rel="stylesheet" href="chrome://resources/css/button.css">
+<link rel="stylesheet" href="chrome://resources/css/checkbox.css">
+<link rel="stylesheet" href="chrome://resources/css/list.css">
+<link rel="stylesheet" href="chrome://resources/css/chrome_shared.css">
+<link rel="stylesheet" href="chrome://resources/css/select.css">
+<link rel="stylesheet" href="chrome://resources/css/spinner.css">
+<link rel="stylesheet" href="chrome://resources/css/throbber.css">
+<link rel="stylesheet" href="chrome://resources/css/tree.css">
+<link rel="stylesheet" href="options_page.css">
+<link rel="stylesheet" href="advanced_options.css">
+<link rel="stylesheet" href="alert_overlay.css">
+<link rel="stylesheet" href="autofill_options.css">
+<link rel="stylesheet" href="autofill_overlay.css">
+<link rel="stylesheet" href="browser_options_page.css">
+<link rel="stylesheet" href="clear_browser_data_overlay.css">
+<link rel="stylesheet" href="content_settings.css">
+<link rel="stylesheet" href="cookies_view.css">
+<link rel="stylesheet" href="extension_settings.css">
+<link rel="stylesheet" href="font_settings.css">
+<if expr="pp_ifdef('enable_register_protocol_handler')">
+ <link rel="stylesheet" href="handler_options.css">
+</if>
+<link rel="stylesheet" href="import_data_overlay.css">
+<if expr="pp_ifdef('enable_web_intents')">
+ <link rel="stylesheet" href="intents_view.css">
+</if>
+<link rel="stylesheet" href="language_options.css">
+<link rel="stylesheet" href="manage_profile_overlay.css">
+<link rel="stylesheet" href="pack_extension_overlay.css">
+<link rel="stylesheet" href="password_manager.css">
+<link rel="stylesheet" href="password_manager_list.css">
+<link rel="stylesheet" href="personal_options.css">
+<link rel="stylesheet" href="search_engine_manager.css">
+<link rel="stylesheet" href="search_page.css">
+<link rel="stylesheet" href="subpages_tab_controls.css">
+<link rel="stylesheet" href="../sync_setup_overlay.css">
+<if expr="pp_ifdef('chromeos')">
+ <link rel="stylesheet" href="about_page.css">
+ <link rel="stylesheet" href="chromeos/accounts_options_page.css">
+ <link rel="stylesheet" href="chromeos/change_picture_options.css">
+ <link rel="stylesheet" href="chromeos/internet_options_page.css">
+ <link rel="stylesheet" href="chromeos/proxy.css">
+ <link rel="stylesheet" href="chromeos/system_options_page.css">
+</if>
+<if expr="pp_ifdef('chromeos') and pp_ifdef('use_virtual_keyboard')">
+ <link rel="stylesheet" href="chromeos/virtual_keyboard.css">
+</if>
+
+<if expr="not pp_ifdef('win32') and not pp_ifdef('darwin')">
+ <link rel="stylesheet" href="certificate_manager.css">
+ <link rel="stylesheet" href="certificate_tree.css">
+</if>
+
+<script src="chrome://resources/css/tree.css.js"></script>
+
+<script src="chrome://resources/js/cr.js"></script>
+<script src="chrome://resources/js/cr/command_line.js"></script>
+<script src="chrome://resources/js/cr/event_target.js"></script>
+<script src="chrome://resources/js/cr/ui.js"></script>
+<script src="chrome://resources/js/cr/ui/array_data_model.js"></script>
+<script src="chrome://resources/js/cr/ui/list_selection_model.js"></script>
+<script src="chrome://resources/js/cr/ui/list_selection_controller.js"></script>
+<script src="chrome://resources/js/cr/ui/list_single_selection_model.js"></script>
+<script src="chrome://resources/js/cr/ui/list_item.js"></script>
+<script src="chrome://resources/js/cr/ui/list.js"></script>
+<script src="chrome://resources/js/cr/ui/grid.js"></script>
+<script src="chrome://resources/js/cr/ui/position_util.js"></script>
+<script src="chrome://resources/js/cr/ui/repeating_button.js"></script>
+<script src="chrome://resources/js/cr/ui/tree.js"></script>
+<script src="chrome://resources/js/local_strings.js"></script>
+<script src="chrome://resources/js/util.js"></script>
+<script src="chrome://settings-frame/options_bundle.js"></script>
+</head>
+<body i18n-values=".style.fontFamily:fontfamily;">
+<div id="overlay" class="overlay transparent" hidden>
+ <include src="alert_overlay.html">
+ <include src="autofill_edit_address_overlay.html">
+ <include src="autofill_edit_creditcard_overlay.html">
+ <include src="clear_browser_data_overlay.html">
+ <include src="import_data_overlay.html">
+ <include src="instant_confirm_overlay.html">
+ <include src="language_add_language_overlay.html">
+ <include src="manage_profile_overlay.html">
+ <include src="pack_extension_overlay.html">
+ <include src="../sync_setup_overlay.html">
+ <if expr="pp_ifdef('chromeos')">
+ <include
+ src="chromeos/language_customize_modifier_keys_overlay.html">
+ <include src="chromeos/internet_detail.html">
+ </if>
+ <if expr="not pp_ifdef('win32') and not pp_ifdef('darwin')">
+ <include src="certificate_restore_overlay.html">
+ <include src="certificate_backup_overlay.html">
+ <include src="certificate_edit_ca_trust_overlay.html">
+ <include src="certificate_import_error_overlay.html">
+ </if>
+</div>
+<div id="main-content">
+ <div id="navbar-container">
+ <h1 id="navbar-content-title" i18n-content="title"></h1>
+ <ul id="navbar">
+ </ul>
+ </div>
+ <div id="mainview">
+ <div id="managed-prefs-banner" class="managed-prefs-banner" hidden>
+ <span id="managed-prefs-icon" class="managed-prefs-icon"></span>
+ <span id="managed-prefs-text" class="managed-prefs-text"></span>
+ </div>
+ <div id="subpage-backdrop" hidden></div>
+ <div id="mainview-content">
+ <div id="page-container">
+ <!-- Please keep the main pages in desired order of display. This will
+ allow search results to display in the desired order. -->
+ <include src="search_page.html">
+ <include src="browser_options.html">
+ <include src="personal_options.html">
+ <if expr="pp_ifdef('chromeos')">
+ <include src="chromeos/system_options.html">
+ <include src="chromeos/internet_options.html">
+ <include src="chromeos/accounts_options.html">
+ </if>
+ <include src="advanced_options.html">
+ <include src="extension_settings.html">
+ </div>
+ <div id="subpage-sheet-container-1"
+ class="subpage-sheet-container transparent" hidden>
+ <div id="subpage-sheet-1" class="subpage-sheet">
+ <button class="raw-button close-subpage custom-appearance"></button>
+ <div class="subpage-sheet-contents">
+ <if expr="pp_ifdef('chromeos')">
+ <include src="about_page.html">
+ <include src="chromeos/change_picture_options.html">
+ <include src="chromeos/proxy.html">
+ </if>
+ <if expr="not pp_ifdef('win32') and not pp_ifdef('darwin')">
+ <include src="certificate_manager.html">
+ </if>
+ <include src="autofill_options.html">
+ <include src="content_settings.html">
+ <include src="font_settings.html">
+ <include src="language_options.html">
+ <include src="password_manager.html">
+ <include src="search_engine_manager.html">
+ </div>
+ </div>
+ </div>
+ <div id="subpage-sheet-container-2"
+ class="subpage-sheet-container transparent" hidden>
+ <div id="subpage-sheet-2" class="subpage-sheet">
+ <button class="raw-button close-subpage custom-appearance"></button>
+ <div class="subpage-sheet-contents">
+ <if expr="pp_ifdef('chromeos')">
+ <include src="chromeos/language_chewing_options.html">
+ <include src="chromeos/language_hangul_options.html">
+ <include src="chromeos/language_mozc_options.html">
+ <include src="chromeos/language_pinyin_options.html">
+ </if>
+ <if expr="pp_ifdef('chromeos') and pp_ifdef('use_virtual_keyboard')">
+ <include src="chromeos/virtual_keyboard.html">
+ </if>
+ <include src="cookies_view.html">
+ <if expr="pp_ifdef('enable_register_protocol_handler')">
+ <include src="handler_options.html">
+ </if>
+ <if expr="pp_ifdef('enable_web_intents')">
+ <include src="intents_view.html">
+ </if>
+ <include src="content_settings_exceptions_area.html">
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+</div>
+<script src="chrome://settings-frame/strings.js"></script>
+<script src="chrome://resources/js/i18n_template.js"></script>
+<script src="chrome://resources/js/i18n_process.js"></script>
+</body>
+</html>
diff --git a/chrome/browser/resources/options2/options.js b/chrome/browser/resources/options2/options.js
new file mode 100644
index 0000000..5a744f3
--- /dev/null
+++ b/chrome/browser/resources/options2/options.js
@@ -0,0 +1,247 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+var AddLanguageOverlay = options.AddLanguageOverlay;
+var AdvancedOptions = options.AdvancedOptions;
+var AlertOverlay = options.AlertOverlay;
+var AutofillEditAddressOverlay = options.AutofillEditAddressOverlay;
+var AutofillEditCreditCardOverlay = options.AutofillEditCreditCardOverlay;
+var AutofillOptions = options.AutofillOptions;
+var BrowserOptions = options.BrowserOptions;
+var ClearBrowserDataOverlay = options.ClearBrowserDataOverlay;
+var ContentSettings = options.ContentSettings;
+var ContentSettingsExceptionsArea =
+ options.contentSettings.ContentSettingsExceptionsArea;
+var CookiesView = options.CookiesView;
+var ExtensionSettings = options.ExtensionSettings;
+var FontSettings = options.FontSettings;
+var HandlerOptions = options.HandlerOptions;
+var ImportDataOverlay = options.ImportDataOverlay;
+var IntentsView = options.IntentsView;
+var InstantConfirmOverlay = options.InstantConfirmOverlay;
+var LanguageOptions = options.LanguageOptions;
+var OptionsPage = options.OptionsPage;
+var PackExtensionOverlay = options.PackExtensionOverlay;
+var PasswordManager = options.PasswordManager;
+var PersonalOptions = options.PersonalOptions;
+var Preferences = options.Preferences;
+var ManageProfileOverlay = options.ManageProfileOverlay;
+var ProxyOptions = options.ProxyOptions;
+var SearchEngineManager = options.SearchEngineManager;
+var SearchPage = options.SearchPage;
+var SyncSetupOverlay = options.SyncSetupOverlay;
+var VirtualKeyboardManager = options.VirtualKeyboardManager;
+
+/**
+ * DOMContentLoaded handler, sets up the page.
+ */
+function load() {
+ // Decorate the existing elements in the document.
+ cr.ui.decorate('input[pref][type=checkbox]', options.PrefCheckbox);
+ cr.ui.decorate('input[pref][type=number]', options.PrefNumber);
+ cr.ui.decorate('input[pref][type=radio]', options.PrefRadio);
+ cr.ui.decorate('input[pref][type=range]', options.PrefRange);
+ cr.ui.decorate('select[pref]', options.PrefSelect);
+ cr.ui.decorate('input[pref][type=text]', options.PrefTextField);
+ cr.ui.decorate('input[pref][type=url]', options.PrefTextField);
+ cr.ui.decorate('button[pref]', options.PrefButton);
+ cr.ui.decorate('#content-settings-page input[type=radio]:not(.handler-radio)',
+ options.ContentSettingsRadio);
+ cr.ui.decorate('#content-settings-page input[type=radio].handler-radio',
+ options.HandlersEnabledRadio);
+ cr.ui.decorate('span.controlled-setting-indicator',
+ options.ControlledSettingIndicator);
+
+ var menuOffPattern = /(^\?|&)menu=off($|&)/;
+ var menuDisabled = menuOffPattern.test(window.location.search);
+ // document.documentElement.setAttribute('hide-menu', menuDisabled);
+ // We can't use an attribute on the html element because of webkit bug
+ // 12519. Instead, we add a class.
+ if (menuDisabled)
+ document.documentElement.classList.add('hide-menu');
+
+ localStrings = new LocalStrings();
+
+ OptionsPage.register(SearchPage.getInstance());
+
+ OptionsPage.register(BrowserOptions.getInstance());
+ OptionsPage.registerSubPage(SearchEngineManager.getInstance(),
+ BrowserOptions.getInstance(),
+ [$('defaultSearchManageEnginesButton')]);
+ OptionsPage.register(PersonalOptions.getInstance());
+ OptionsPage.registerSubPage(AutofillOptions.getInstance(),
+ PersonalOptions.getInstance(),
+ [$('autofill-settings')]);
+ OptionsPage.registerSubPage(PasswordManager.getInstance(),
+ PersonalOptions.getInstance(),
+ [$('manage-passwords')]);
+ if (cr.isChromeOS) {
+ OptionsPage.register(SystemOptions.getInstance());
+ OptionsPage.registerSubPage(AboutPage.getInstance(),
+ SystemOptions.getInstance());
+ OptionsPage.registerSubPage(LanguageOptions.getInstance(),
+ SystemOptions.getInstance(),
+ [$('language-button')]);
+ OptionsPage.registerSubPage(
+ new OptionsPage('languageChewing',
+ templateData.languageChewingPageTabTitle,
+ 'languageChewingPage'),
+ LanguageOptions.getInstance());
+ OptionsPage.registerSubPage(
+ new OptionsPage('languageHangul',
+ templateData.languageHangulPageTabTitle,
+ 'languageHangulPage'),
+ LanguageOptions.getInstance());
+ OptionsPage.registerSubPage(
+ new OptionsPage('languageMozc',
+ templateData.languageMozcPageTabTitle,
+ 'languageMozcPage'),
+ LanguageOptions.getInstance());
+ OptionsPage.registerSubPage(
+ new OptionsPage('languagePinyin',
+ templateData.languagePinyinPageTabTitle,
+ 'languagePinyinPage'),
+ LanguageOptions.getInstance());
+ // Only use the VirtualKeyboardManager if the keyboard DOM elements (which
+ // it will assume exists) are present (i.e. if we were built with
+ // USE_VIRTUAL_KEYBOARD).
+ if ($('language-options-virtual-keyboard')) {
+ OptionsPage.registerSubPage(VirtualKeyboardManager.getInstance(),
+ LanguageOptions.getInstance());
+ }
+ OptionsPage.register(InternetOptions.getInstance());
+ }
+ OptionsPage.register(AdvancedOptions.getInstance());
+ OptionsPage.registerSubPage(ContentSettings.getInstance(),
+ AdvancedOptions.getInstance(),
+ [$('privacyContentSettingsButton')]);
+ OptionsPage.registerSubPage(ContentSettingsExceptionsArea.getInstance(),
+ ContentSettings.getInstance());
+ OptionsPage.registerSubPage(CookiesView.getInstance(),
+ ContentSettings.getInstance(),
+ [$('privacyContentSettingsButton'),
+ $('show-cookies-button')]);
+ // If HandlerOptions is null it means it got compiled out.
+ if (HandlerOptions) {
+ OptionsPage.registerSubPage(HandlerOptions.getInstance(),
+ ContentSettings.getInstance(),
+ [$('manage-handlers-button')]);
+ }
+ if (IntentsView && $('manage-intents-button')) {
+ OptionsPage.registerSubPage(IntentsView.getInstance(),
+ ContentSettings.getInstance(),
+ [$('manage-intents-button')]);
+ }
+ OptionsPage.registerSubPage(FontSettings.getInstance(),
+ AdvancedOptions.getInstance(),
+ [$('fontSettingsCustomizeFontsButton')]);
+ if (!cr.isChromeOS) {
+ OptionsPage.registerSubPage(LanguageOptions.getInstance(),
+ AdvancedOptions.getInstance(),
+ [$('language-button')]);
+ }
+ if (!cr.isWindows && !cr.isMac) {
+ OptionsPage.registerSubPage(CertificateManager.getInstance(),
+ AdvancedOptions.getInstance(),
+ [$('certificatesManageButton')]);
+ OptionsPage.registerOverlay(CertificateRestoreOverlay.getInstance(),
+ CertificateManager.getInstance());
+ OptionsPage.registerOverlay(CertificateBackupOverlay.getInstance(),
+ CertificateManager.getInstance());
+ OptionsPage.registerOverlay(CertificateEditCaTrustOverlay.getInstance(),
+ CertificateManager.getInstance());
+ OptionsPage.registerOverlay(CertificateImportErrorOverlay.getInstance(),
+ CertificateManager.getInstance());
+ }
+ OptionsPage.registerOverlay(AddLanguageOverlay.getInstance(),
+ LanguageOptions.getInstance());
+ OptionsPage.registerOverlay(AlertOverlay.getInstance());
+ OptionsPage.registerOverlay(AutofillEditAddressOverlay.getInstance(),
+ AutofillOptions.getInstance());
+ OptionsPage.registerOverlay(AutofillEditCreditCardOverlay.getInstance(),
+ AutofillOptions.getInstance());
+ OptionsPage.registerOverlay(ClearBrowserDataOverlay.getInstance(),
+ AdvancedOptions.getInstance(),
+ [$('privacyClearDataButton')]);
+ OptionsPage.registerOverlay(ImportDataOverlay.getInstance(),
+ PersonalOptions.getInstance());
+ OptionsPage.registerOverlay(InstantConfirmOverlay.getInstance(),
+ BrowserOptions.getInstance());
+ OptionsPage.registerOverlay(SyncSetupOverlay.getInstance(),
+ PersonalOptions.getInstance());
+ OptionsPage.registerOverlay(ManageProfileOverlay.getInstance(),
+ PersonalOptions.getInstance());
+
+ OptionsPage.register(ExtensionSettings.getInstance());
+ OptionsPage.registerOverlay(PackExtensionOverlay.getInstance(),
+ ExtensionSettings.getInstance());
+
+ if (cr.isChromeOS) {
+ OptionsPage.register(AccountsOptions.getInstance());
+ OptionsPage.registerSubPage(ProxyOptions.getInstance(),
+ InternetOptions.getInstance());
+ OptionsPage.registerSubPage(ChangePictureOptions.getInstance(),
+ PersonalOptions.getInstance(),
+ [$('change-picture-button')]);
+ OptionsPage.registerOverlay(DetailsInternetPage.getInstance(),
+ InternetOptions.getInstance());
+
+ var languageModifierKeysOverlay = new OptionsPage(
+ 'languageCustomizeModifierKeysOverlay',
+ localStrings.getString('languageCustomizeModifierKeysOverlay'),
+ 'languageCustomizeModifierKeysOverlay')
+ $('languageCustomizeModifierKeysOverleyDismissButton').onclick =
+ function() {
+ OptionsPage.closeOverlay();
+ };
+ OptionsPage.registerOverlay(languageModifierKeysOverlay,
+ SystemOptions.getInstance(),
+ [$('modifier-keys-button')]);
+ }
+
+ Preferences.getInstance().initialize();
+ OptionsPage.initialize();
+
+ var path = document.location.pathname;
+
+ if (path.length > 1) {
+ // Skip starting slash and remove trailing slash (if any).
+ var pageName = path.slice(1).replace(/\/$/, '');
+ // Proxy page is now per network and only reachable from internet details.
+ if (pageName != 'proxy') {
+ // Show page, but don't update history (there's already an entry for it).
+ OptionsPage.showPageByName(pageName, false);
+ }
+ } else {
+ OptionsPage.showDefaultPage();
+ }
+
+ var subpagesNavTabs = document.querySelectorAll('.subpages-nav-tabs');
+ for(var i = 0; i < subpagesNavTabs.length; i++) {
+ subpagesNavTabs[i].onclick = function(event) {
+ OptionsPage.showTab(event.srcElement);
+ }
+ }
+
+ // Allow platform specific CSS rules.
+ cr.enablePlatformSpecificCSSRules();
+
+ if (navigator.plugins['Shockwave Flash'])
+ document.documentElement.setAttribute('hasFlashPlugin', '');
+
+ // Clicking on the Settings title brings up the 'Basics' page.
+ $('navbar-content-title').onclick = function() {
+ OptionsPage.navigateToPage(BrowserOptions.getInstance().name);
+ };
+}
+
+document.addEventListener('DOMContentLoaded', load);
+
+window.onpopstate = function(e) {
+ options.OptionsPage.setState(e.state);
+};
+
+window.onbeforeunload = function() {
+ options.OptionsPage.willClose();
+};
diff --git a/chrome/browser/resources/options2/options_bundle.js b/chrome/browser/resources/options2/options_bundle.js
new file mode 100644
index 0000000..f6e9939
--- /dev/null
+++ b/chrome/browser/resources/options2/options_bundle.js
@@ -0,0 +1,94 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// This file exists to aggregate all of the javascript used by the
+// settings page into a single file which will be flattened and served
+// as a single resource.
+<include src="preferences.js"></include>
+<include src="pref_ui.js"></include>
+<include src="deletable_item_list.js"></include>
+<include src="inline_editable_list.js"></include>
+<include src="controlled_setting.js"></include>
+<include src="options_page.js"></include>
+<if expr="pp_ifdef('chromeos')">
+ <include src="about_page.js"></include>
+ <include src="../chromeos/user_images_grid.js"></include>
+ <include src="chromeos/cellular_plan_element.js"></include>
+ <include src="chromeos/change_picture_options.js"></include>
+ <include src="chromeos/internet_detail_ip_config_list.js"></include>
+ <include src="chromeos/internet_network_element.js"></include>
+ <include src="chromeos/internet_options.js"></include>
+ <include src="chromeos/internet_detail.js"></include>
+ <include src="chromeos/system_options.js"></include>
+ <include src="chromeos/bluetooth_list_element.js"></include>
+ <include src="chromeos/accounts_options.js"></include>
+ <include src="chromeos/proxy_options.js"></include>
+ <include src="chromeos/proxy_rules_list.js"></include>
+ <include src="chromeos/accounts_user_list.js"></include>
+ <include src="chromeos/accounts_user_name_edit.js"></include>
+ <include src="chromeos/virtual_keyboard.js"></include>
+ <include src="chromeos/virtual_keyboard_list.js"></include>
+ var AboutPage = options.AboutPage;
+ var AccountsOptions = options.AccountsOptions;
+ var ChangePictureOptions = options.ChangePictureOptions;
+ var InternetOptions = options.InternetOptions;
+ var DetailsInternetPage = options.internet.DetailsInternetPage;
+ var SystemOptions = options.SystemOptions;
+</if>
+<if expr="not pp_ifdef('win32') and not pp_ifdef('darwin')">
+ <include src="certificate_tree.js"></include>
+ <include src="certificate_manager.js"></include>
+ <include src="certificate_restore_overlay.js"></include>
+ <include src="certificate_backup_overlay.js"></include>
+ <include src="certificate_edit_ca_trust_overlay.js"></include>
+ <include src="certificate_import_error_overlay.js"></include>
+ var CertificateManager = options.CertificateManager;
+ var CertificateRestoreOverlay = options.CertificateRestoreOverlay;
+ var CertificateBackupOverlay = options.CertificateBackupOverlay;
+ var CertificateEditCaTrustOverlay = options.CertificateEditCaTrustOverlay;
+ var CertificateImportErrorOverlay = options.CertificateImportErrorOverlay;
+</if>
+<include src="advanced_options.js"></include>
+<include src="alert_overlay.js"></include>
+<include src="autocomplete_list.js"></include>
+<include src="autofill_edit_address_overlay.js"></include>
+<include src="autofill_edit_creditcard_overlay.js"></include>
+<include src="autofill_options_list.js"></include>
+<include src="autofill_options.js"></include>
+<include src="browser_options.js"></include>
+<include src="browser_options_startup_page_list.js"></include>
+<include src="clear_browser_data_overlay.js"></include>
+<include src="content_settings.js"></include>
+<include src="content_settings_exceptions_area.js"></include>
+<include src="content_settings_ui.js"></include>
+<include src="cookies_list.js"></include>
+<include src="cookies_view.js"></include>
+<include src="extension_list.js"></include>
+<include src="extension_settings.js"></include>
+<include src="font_settings.js"></include>
+<if expr="pp_ifdef('enable_register_protocol_handler')">
+ <include src="handler_options.js"></script>
+ <include src="handler_options_list.js"></script>
+</if>
+<include src="import_data_overlay.js"></include>
+<include src="instant_confirm_overlay.js"></include>
+<if expr="pp_ifdef('enable_web_intents')">
+ <include src="intents_list.js"></include>
+ <include src="intents_view.js"></include>
+</if>
+<include src="language_add_language_overlay.js"></include>
+<include src="language_list.js"></include>
+<include src="language_options.js"></include>
+<include src="manage_profile_overlay.js"></include>
+<include src="pack_extension_overlay.js"></include>
+<include src="password_manager.js"></include>
+<include src="password_manager_list.js"></include>
+<include src="personal_options.js"></include>
+<include src="personal_options_profile_list.js"></include>
+<include src="profiles_icon_grid.js"></include>
+<include src="search_engine_manager.js"></include>
+<include src="search_engine_manager_engine_list.js"></include>
+<include src="search_page.js"></include>
+<include src="../sync_setup_overlay.js"></include>
+<include src="options.js"></include>
diff --git a/chrome/browser/resources/options2/options_page.css b/chrome/browser/resources/options2/options_page.css
new file mode 100644
index 0000000..ff45d67
--- /dev/null
+++ b/chrome/browser/resources/options2/options_page.css
@@ -0,0 +1,736 @@
+.hbox {
+ display: -webkit-box;
+ -webkit-box-orient: horizontal;
+}
+
+.vbox {
+ display: -webkit-box;
+ -webkit-box-orient: vertical;
+}
+
+.stretch {
+ -webkit-box-flex: 1;
+}
+
+.frozen,
+.subpage-sheet-container.frozen {
+ position: fixed;
+}
+
+#search-field {
+ font-size: inherit;
+ margin: 0;
+}
+
+/*
+ * Add padding to increase the touchable area of search box. Use original font
+ * size to avoid the width of search box exceeding the width of navbar.
+ */
+html[touch-optimized] #search-field {
+ font-size: 13px;
+ padding: 5px;
+}
+html[touch-optimized] #search-field::-webkit-search-cancel-button {
+ -webkit-transform: scale(1.5);
+}
+
+/*
+ * For touch-optimized UI, make the radio/checkbox input boxes in
+ * options/preference pages easier to touch.
+ * TODO(rbyers): We need to solve this more generally for all web pages
+ * (crbug.com/99981), and perhaps temporarily for all WebUI (crbug.com/102482).
+ */
+html[touch-optimized] div.radio > label > span,
+html[touch-optimized] div.checkbox > label > span {
+ -webkit-padding-start: 5px;
+}
+
+html[touch-optimized] label > input[type=checkbox],
+html[touch-optimized] label > input[type=radio] {
+ -webkit-transform: scale(1.4);
+}
+
+/*
+ * Override the font-size rule in shared_options.css file.
+ * 16 px font-size proved to be more touch friendly. It increases the touchable
+ * area for buttons and input boxes.
+ */
+html[touch-optimized] body {
+ font-size: 16px;
+}
+
+.overlay {
+ -webkit-box-align: center;
+ -webkit-box-orient: vertical;
+ -webkit-box-pack: center;
+ -webkit-transition: 250ms opacity;
+ background: -webkit-radial-gradient(rgba(127, 127, 127, 0.5),
+ rgba(127, 127, 127, 0.5) 35%,
+ rgba(0, 0, 0, 0.7));
+ bottom: 0;
+ display: -webkit-box;
+ left: 0;
+ overflow: auto;
+ padding: 20px;
+ padding-bottom: 130px;
+ position: fixed;
+ right: 0;
+ top: 0;
+ z-index: 10;
+}
+
+.raw-button,
+.raw-button:hover,
+.raw-button:active {
+ -webkit-box-shadow: none;
+ background-color: transparent;
+ background-repeat: no-repeat;
+ border: none;
+ min-width: 0;
+ padding: 1px 6px;
+}
+
+.close-subpage {
+ background-image: url('chrome://theme/IDR_CLOSE_BAR');
+ height: 16px;
+ min-width: 0;
+ position: relative;
+ top: 16px;
+ width: 16px;
+}
+
+/* In TOUCH_UI builds, the IDR_CLOSE_BAR resource is double-size. */
+<if expr="pp_ifdef('touchui')">
+.close-subpage {
+ height: 32px;
+ width: 32px;
+}
+</if>
+
+.close-subpage:hover {
+ background-image: url('chrome://theme/IDR_CLOSE_BAR_H');
+}
+
+.close-subpage:active {
+ background-image: url('chrome://theme/IDR_CLOSE_BAR_P');
+}
+
+html[dir='ltr'] .close-subpage {
+ float: right;
+ right: 20px;
+}
+
+html[dir='rtl'] .close-subpage {
+ float: left;
+ left: 20px;
+}
+
+html.hide-menu .close-subpage {
+ display: none
+}
+
+.content-area {
+ padding: 10px 15px 5px 15px;
+}
+
+.action-area {
+ -webkit-box-align: center;
+ -webkit-box-orient: horizontal;
+ -webkit-box-pack: end;
+ border-top: 1px solid rgba(188, 193, 208, .5);
+ display: -webkit-box;
+ padding: 12px;
+}
+
+html[dir='rtl'] .action-area {
+ left: 0;
+}
+
+.action-area-right {
+ display: -webkit-box;
+}
+
+.button-strip {
+ -webkit-box-orient: horizontal;
+ display: -webkit-box;
+}
+
+.button-strip > button {
+ -webkit-margin-start: 10px;
+ display: block;
+}
+
+.bottom-strip {
+ padding: 12px;
+ position: absolute;
+ right: 0px;
+ bottom: 0px;
+ border-top: none;
+}
+
+.overlay .page {
+ -webkit-box-shadow: 0px 5px 80px #505050;
+ background: white;
+ border: 1px solid rgb(188, 193, 208);
+ border-radius: 2px;
+ min-width: 400px;
+ padding: 0;
+ position: relative;
+}
+
+#subpage-backdrop {
+ -webkit-transition: 250ms opacity;
+ background-color: rgba(233, 238, 242, .5);
+ height: 100%;
+ left: 216px;
+ right: 216px;
+ position: fixed;
+ top: 0;
+ width: 100%;
+}
+
+.subpage-sheet-container {
+ -webkit-transition: 250ms opacity, 100ms padding-left, 100ms padding-right;
+ box-sizing: border-box;
+ min-height: 100%;
+ position: absolute;
+ /* We set both left and right for the sake of RTL. */
+ left: 0;
+ right: 0;
+ top: 0;
+ width: 100%;
+}
+
+#subpage-sheet-container-1 {
+ -webkit-padding-start: 40px;
+ z-index: 5;
+}
+
+#subpage-sheet-container-2 {
+ -webkit-padding-start: 80px;
+ z-index: 10;
+}
+
+.subpage-sheet {
+ -webkit-box-shadow: #666 0px 2px 5px;
+ background-color: white;
+ border-left: 1px solid #b8b8b8;
+ box-sizing: border-box;
+ min-height: 100%;
+ width: 100%;
+ min-width: 651px;
+}
+
+.subpage-sheet-contents {
+ box-sizing: border-box;
+ padding: 0px 20px 20px 20px;
+ width: 650px;
+}
+
+.managed-prefs-banner {
+ background: -webkit-linear-gradient(#fff2b7, #fae691 97%, #878787);
+ height: 31px;
+ width: 100%;
+ margin: 0;
+ padding: 0;
+ position: relative;
+ vertical-align: middle;
+ z-index: 11;
+}
+
+.managed-prefs-banner.clickable:active {
+ background: -webkit-linear-gradient(#878787, #fae691 3%, #fff2b7);
+}
+
+.managed-prefs-icon {
+ background-image: url("chrome://theme/IDR_WARNING");
+ background-repeat: no-repeat;
+ background-position:center;
+ display: inline-block;
+ padding: 5px;
+ height: 21px;
+ vertical-align: middle;
+ width: 24px;
+}
+
+.managed-prefs-text {
+ vertical-align: middle;
+}
+
+.subpage-sheet .page h1 {
+ margin-bottom: 10px;
+}
+
+.overlay .page h1 {
+ background: -webkit-linear-gradient(white, #F8F8F8);
+ border-bottom: 1px solid rgba(188, 193, 208, .5);
+ font-size: 105%;
+ font-weight: bold;
+ padding: 10px 15px 8px 15px;
+}
+
+.page list {
+ /* Min height is a multiple of the list item height (32) */
+ min-height: 192px;
+}
+
+/**
+ * TODO(kevers): Standardize formatting of sections to use display tables.
+ * For now, we require separate specialized rules for sections that are
+ * formatted as table rows.
+ */
+section {
+ -webkit-box-orient: horizontal;
+ border-bottom: 1px solid #eeeeee;
+ display: -webkit-box;
+ margin-top: 17px;
+ padding-bottom: 20px;
+}
+
+div.page section:last-child {
+ border-bottom: none;
+}
+
+h3 {
+ font-size: 105%;
+ font-weight: bold;
+ margin: 20px 0 10px 0;
+}
+
+section > h3 {
+ margin: 0;
+ vertical-align: middle;
+ width: 130px;
+ -webkit-padding-end: 10px;
+}
+
+section > div:only-of-type {
+ -webkit-box-flex: 1;
+}
+
+/* Don't allow edge margin on the first/last child of a section. */
+section > h3 + * > *:last-child {
+ margin-bottom: 0;
+}
+section > h3 + * > *:first-child {
+ margin-top: 0;
+}
+
+.option {
+ margin-top: 0;
+}
+
+/* [hidden] does display:none, but its priority is too low in some cases. */
+[hidden] {
+ display: none !important;
+}
+
+.transparent {
+ opacity: 0;
+}
+
+.touch-slider {
+ -webkit-appearance: slider-horizontal;
+}
+
+
+.settings-list,
+.settings-list-empty {
+ border: 1px solid #d9d9d9;
+ border-radius: 2px;
+}
+
+.settings-list-empty {
+ background-color: #f4f4f4;
+ box-sizing: border-box;
+ min-height: 125px;
+ padding-left: 20px;
+ padding-top: 20px;
+}
+
+list > * {
+ -webkit-box-align: center;
+ -webkit-transition: 150ms background-color;
+ box-sizing: border-box;
+ border-radius: 0;
+ display: -webkit-box;
+ height: 32px;
+ border: none;
+ margin: 0;
+}
+
+list:not([disabled]) > :hover {
+ background-color: #e4ecf7;
+}
+
+/* TODO(stuartmorgan): Once this becomes the list style for other WebUI pages
+ * these rules can be simplified (since they wont need to override other rules).
+ */
+
+list:not([hasElementFocus]) > [selected],
+list:not([hasElementFocus]) > [lead][selected] {
+ background-color: #d0d0d0;
+ background-image: none;
+}
+
+list[hasElementFocus] > [selected],
+list[hasElementFocus] > [lead][selected],
+list:not([hasElementFocus]) > [selected]:hover,
+list:not([hasElementFocus]) > [selected][lead]:hover {
+ background-color: #bbcee9;
+ background-image: none;
+}
+
+list[hasElementFocus] > [lead],
+list[hasElementFocus] > [lead][selected] {
+ border-top: 1px solid #7892b4;
+ border-bottom: 1px solid #7892b4;
+}
+
+list[hasElementFocus] > [lead]:nth-child(2),
+list[hasElementFocus] > [lead][selected]:nth-child(2) {
+ border-top: 1px solid transparent;
+}
+
+list[hasElementFocus] > [lead]:nth-last-child(2),
+list[hasElementFocus] > [lead][selected]:nth-last-child(2) {
+ border-bottom: 1px solid transparent;
+}
+
+list[disabled] > [lead][selected],
+list[disabled]:focus > [lead][selected] {
+ border: none;
+}
+
+list[disabled] {
+ opacity: 0.6;
+}
+
+list > .heading {
+ color: #666666;
+}
+
+list > .heading:hover {
+ background-color: transparent;
+ border-color: transparent;
+}
+
+list .deletable-item {
+ -webkit-box-align: center;
+}
+
+list .deletable-item > :first-child {
+ -webkit-box-align: center;
+ -webkit-box-flex: 1;
+ -webkit-padding-end: 5px;
+ display: -webkit-box;
+}
+
+list .close-button {
+ -webkit-transition: 150ms opacity;
+ background-color: transparent;
+ /* TODO(stuartmorgan): Replace with real images once they are available. */
+ background-image: url("../../../app/theme/close_bar.png");
+ border: none;
+ display: block;
+ height: 16px;
+ opacity: 1;
+ width: 16px;
+}
+
+list > *:not(:hover):not([lead]) .close-button,
+list > *:not(:hover):not([selected]) .close-button,
+list:not([hasElementFocus]) > *:not(:hover) .close-button,
+list[disabled] .close-button,
+list .close-button[disabled] {
+ opacity: 0;
+ pointer-events: none;
+}
+
+list .close-button:hover {
+ background-image: url("../../../app/theme/close_bar_h.png");
+}
+
+list .close-button:active {
+ background-image: url("../../../app/theme/close_bar_p.png");
+}
+
+list .static-text {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+list[inlineeditable] input {
+ box-sizing: border-box;
+ margin: 0;
+ width: 100%;
+}
+
+list > :not([editing]) [displaymode="edit"] {
+ display: none;
+}
+
+list > [editing] [displaymode="static"] {
+ display: none;
+}
+
+list > [editing] input:invalid {
+ /* TODO(stuartmorgan): Replace with validity badge */
+ background-color: pink;
+}
+
+.option-name {
+ padding-right: 5px;
+}
+
+html[dir=rtl].option-name {
+ padding-left: 5px;
+}
+
+.favicon-cell {
+ -webkit-padding-start: 20px;
+ background-position: left;
+ background-repeat: no-repeat;
+}
+
+input[type="url"].favicon-cell {
+ -webkit-padding-start: 22px;
+ background-position-x: 4px;
+}
+
+/* TODO(jhawkins): Use something better than 99.3% when CSS3 background
+ * positioning is available.
+ */
+html[dir=rtl] input.favicon-cell {
+ background-position-x: 99.3%;
+}
+
+list .favicon-cell {
+ -webkit-margin-start: 7px;
+ -webkit-padding-start: 26px;
+ display: block;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ white-space: nowrap;
+}
+
+html[dir=rtl] list .favicon-cell {
+ background-position: right;
+}
+
+html[enable-background-mode=false] #background-mode-section {
+ display: none;
+}
+
+/* UI Controls */
+
+/* LIST */
+html:not([os=mac]) list[hasElementFocus] {
+ outline: 1px solid rgba(0, 128, 256, 0.5);
+ outline-offset: -2px;
+}
+
+/* This matches the native list outline on Mac */
+html[os=mac] list[hasElementFocus] {
+ outline-color: #759ad9;
+ outline-offset: -1px;
+ outline-style: auto;
+ outline-width: 5px;
+}
+
+.suboption {
+ -webkit-margin-start: 23px;
+}
+
+.informational-text {
+ color: grey;
+}
+
+#main-content list.autocomplete-suggestions {
+ background-color: white;
+ border: 1px solid #aaa;
+ border-radius: 2px;
+ min-height: 0;
+ opacity: 0.9;
+ position: fixed;
+ z-index: 3;
+}
+
+list.autocomplete-suggestions > div {
+ height: auto;
+}
+
+list.autocomplete-suggestions:not([hasElementFocus]) > [selected],
+list.autocomplete-suggestions:not([hasElementFocus]) > [lead][selected] {
+ background-color: #bbcee9;
+}
+
+html:not([hasFlashPlugin]) .flash-plugin-area,
+/* If the Flash plug-in supports the NPP_ClearSiteData API, we don't need to
+ * show the link to the Flash storage settings manager:
+ */
+html[flashPluginSupportsClearSiteData] .flash-plugin-area,
+html:not([flashPluginSupportsClearSiteData]) .clear-plugin-lso-data-enabled,
+html[flashPluginSupportsClearSiteData] .clear-plugin-lso-data-disabled {
+ display: none;
+}
+
+
+/* Display a collection of sections as a table in order to display nicely
+ * in multiple locales.
+ */
+.displaytable {
+ display: table;
+ width: 100%;
+}
+
+.displaytable > section {
+ display: table-row;
+}
+
+/* right table column containing settable options */
+.displaytable > section > h3 + div,
+.displaytable > section > h3 + table {
+ padding-bottom: 20px;
+}
+
+/* Setting the padding on the header so the alignment doesn't depend on the
+ * contents of the right table column. */
+.displaytable > section > h3 {
+ padding-top: 17px;
+}
+
+.displaytable > section > * {
+ display: table-cell;
+ vertical-align: baseline;
+ border-bottom: 1px solid #eeeeee;
+}
+
+/* do not display a border after the last section in the table */
+.displaytable:not([searching='true']) > section:last-child > * {
+ border-bottom: none;
+}
+
+/* Controlled setting indicator and bubble. */
+.controlled-setting-indicator {
+ display: inline-block;
+ /* Establish a containing block for absolutely positioning the bubble. */
+ position: relative;
+ vertical-align: text-bottom;
+}
+
+.controlled-setting-indicator[controlled-by] summary {
+ background-size: contain;
+ height: 16px;
+ width: 16px;
+}
+
+.controlled-setting-indicator summary::-webkit-details-marker {
+ display: none;
+}
+
+.controlled-setting-indicator[controlled-by='policy'] summary {
+ background-image:
+ url('chrome://theme/IDR_CONTROLLED_SETTING_MANDATORY_GRAY');
+}
+
+.controlled-setting-indicator[controlled-by='policy'] summary:hover {
+ background-image:
+ url('chrome://theme/IDR_CONTROLLED_SETTING_MANDATORY');
+}
+
+.controlled-setting-indicator[controlled-by='extension'] summary {
+ background-image:
+ url('chrome://theme/IDR_CONTROLLED_SETTING_EXTENSION_GRAY');
+}
+
+.controlled-setting-indicator[controlled-by='extension'] summary:hover {
+ background-image:
+ url('chrome://theme/IDR_CONTROLLED_SETTING_EXTENSION');
+}
+
+.controlled-setting-indicator[controlled-by='recommended'] summary {
+ background-image:
+ url('chrome://theme/IDR_CONTROLLED_SETTING_RECOMMENDED_GRAY');
+}
+
+.controlled-setting-indicator[controlled-by='recommended'] summary:hover {
+ background-image:
+ url('chrome://theme/IDR_CONTROLLED_SETTING_RECOMMENDED');
+}
+
+.controlled-setting-bubble {
+ -webkit-margin-start: -20px;
+ background-color: white;
+ border-radius: 4px;
+ border: 1px solid #ccc;
+ box-shadow: 0 2px 2px #ddd;
+ margin-top: 10px;
+ padding: 10px;
+ position: absolute;
+ top: 50%;
+ z-index: 10;
+}
+
+html[dir='ltr'] .controlled-setting-bubble {
+ left: 50%;
+}
+
+html[dir='rtl'] .controlled-setting-bubble {
+ right: 50%;
+}
+
+.controlled-setting-bubble::before {
+ -webkit-margin-start: 4px;
+ border-color: #ccc transparent;
+ border-style: solid;
+ border-width: 0 5px 5px;
+ content: '';
+ position: absolute;
+ top: -5px;
+}
+
+.controlled-setting-bubble::after {
+ -webkit-margin-start: 5px;
+ border-color: white transparent;
+ border-style: solid;
+ border-width: 0 4px 4px;
+ content: '';
+ position: absolute;
+ top: -4px;
+}
+
+.controlled-setting-bubble-text {
+ -webkit-padding-start: 30px;
+ background-repeat: no-repeat;
+ margin: 0;
+ min-height: 32px;
+ min-width: 200px;
+}
+
+.controlled-setting-indicator[controlled-by='policy']
+ .controlled-setting-bubble-text {
+ background-image:
+ url('chrome://theme/IDR_CONTROLLED_SETTING_MANDATORY_LARGE');
+}
+
+.controlled-setting-indicator[controlled-by='extension']
+ .controlled-setting-bubble-text {
+ background-image:
+ url('chrome://theme/IDR_CONTROLLED_SETTING_EXTENSION_LARGE');
+}
+
+.controlled-setting-indicator[controlled-by='recommended']
+ .controlled-setting-bubble-text {
+ background-image:
+ url('chrome://theme/IDR_CONTROLLED_SETTING_RECOMMENDED_LARGE');
+}
+
+html[dir='rtl'] .controlled-setting-bubble-text {
+ background-position: right top;
+}
+
+.controlled-setting-bubble-action {
+ padding: 0 !important;
+}
diff --git a/chrome/browser/resources/options2/options_page.js b/chrome/browser/resources/options2/options_page.js
new file mode 100644
index 0000000..0599e0e
--- /dev/null
+++ b/chrome/browser/resources/options2/options_page.js
@@ -0,0 +1,1076 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+ /////////////////////////////////////////////////////////////////////////////
+ // OptionsPage class:
+
+ /**
+ * Base class for options page.
+ * @constructor
+ * @param {string} name Options page name, also defines id of the div element
+ * containing the options view and the name of options page navigation bar
+ * item as name+'PageNav'.
+ * @param {string} title Options page title, used for navigation bar
+ * @extends {EventTarget}
+ */
+ function OptionsPage(name, title, pageDivName) {
+ this.name = name;
+ this.title = title;
+ this.pageDivName = pageDivName;
+ this.pageDiv = $(this.pageDivName);
+ this.tab = null;
+ }
+
+ const SUBPAGE_SHEET_COUNT = 2;
+
+ /**
+ * Main level option pages. Maps lower-case page names to the respective page
+ * object.
+ * @protected
+ */
+ OptionsPage.registeredPages = {};
+
+ /**
+ * Pages which are meant to behave like modal dialogs. Maps lower-case overlay
+ * names to the respective overlay object.
+ * @protected
+ */
+ OptionsPage.registeredOverlayPages = {};
+
+ /**
+ * Whether or not |initialize| has been called.
+ * @private
+ */
+ OptionsPage.initialized_ = false;
+
+ /**
+ * Gets the default page (to be shown on initial load).
+ */
+ OptionsPage.getDefaultPage = function() {
+ return BrowserOptions.getInstance();
+ };
+
+ /**
+ * Shows the default page.
+ */
+ OptionsPage.showDefaultPage = function() {
+ this.navigateToPage(this.getDefaultPage().name);
+ };
+
+ /**
+ * "Navigates" to a page, meaning that the page will be shown and the
+ * appropriate entry is placed in the history.
+ * @param {string} pageName Page name.
+ */
+ OptionsPage.navigateToPage = function(pageName) {
+ this.showPageByName(pageName, true);
+ };
+
+ /**
+ * Shows a registered page. This handles both top-level pages and sub-pages.
+ * @param {string} pageName Page name.
+ * @param {boolean} updateHistory True if we should update the history after
+ * showing the page.
+ * @private
+ */
+ OptionsPage.showPageByName = function(pageName, updateHistory) {
+ // Find the currently visible root-level page.
+ var rootPage = null;
+ for (var name in this.registeredPages) {
+ var page = this.registeredPages[name];
+ if (page.visible && !page.parentPage) {
+ rootPage = page;
+ break;
+ }
+ }
+
+ // Find the target page.
+ var targetPage = this.registeredPages[pageName.toLowerCase()];
+ if (!targetPage || !targetPage.canShowPage()) {
+ // If it's not a page, try it as an overlay.
+ if (!targetPage && this.showOverlay_(pageName, rootPage)) {
+ if (updateHistory)
+ this.updateHistoryState_();
+ return;
+ } else {
+ targetPage = this.getDefaultPage();
+ }
+ }
+
+ pageName = targetPage.name.toLowerCase();
+ var targetPageWasVisible = targetPage.visible;
+
+ // Determine if the root page is 'sticky', meaning that it
+ // shouldn't change when showing a sub-page. This can happen for special
+ // pages like Search.
+ var isRootPageLocked =
+ rootPage && rootPage.sticky && targetPage.parentPage;
+
+ // Notify pages if they will be hidden.
+ for (var name in this.registeredPages) {
+ var page = this.registeredPages[name];
+ if (!page.parentPage && isRootPageLocked)
+ continue;
+ if (page.willHidePage && name != pageName &&
+ !page.isAncestorOfPage(targetPage))
+ page.willHidePage();
+ }
+
+ // Update visibilities to show only the hierarchy of the target page.
+ for (var name in this.registeredPages) {
+ var page = this.registeredPages[name];
+ if (!page.parentPage && isRootPageLocked)
+ continue;
+ page.visible = name == pageName ||
+ (!document.documentElement.classList.contains('hide-menu') &&
+ page.isAncestorOfPage(targetPage));
+ }
+
+ // Update the history and current location.
+ if (updateHistory)
+ this.updateHistoryState_();
+
+ // Always update the page title.
+ document.title = targetPage.title;
+
+ // Notify pages if they were shown.
+ for (var name in this.registeredPages) {
+ var page = this.registeredPages[name];
+ if (!page.parentPage && isRootPageLocked)
+ continue;
+ if (!targetPageWasVisible && page.didShowPage && (name == pageName ||
+ page.isAncestorOfPage(targetPage)))
+ page.didShowPage();
+ }
+ };
+
+ /**
+ * Updates the visibility and stacking order of the subpage backdrop
+ * according to which subpage is topmost and visible.
+ * @private
+ */
+ OptionsPage.updateSubpageBackdrop_ = function () {
+ var topmostPage = this.getTopmostVisibleNonOverlayPage_();
+ var nestingLevel = topmostPage ? topmostPage.nestingLevel : 0;
+
+ var subpageBackdrop = $('subpage-backdrop');
+ if (nestingLevel > 0) {
+ var container = $('subpage-sheet-container-' + nestingLevel);
+ subpageBackdrop.style.zIndex =
+ parseInt(window.getComputedStyle(container).zIndex) - 1;
+ subpageBackdrop.hidden = false;
+ } else {
+ subpageBackdrop.hidden = true;
+ }
+ };
+
+ /**
+ * Scrolls the page to the correct position (the top when opening a subpage,
+ * or the old scroll position a previously hidden subpage becomes visible).
+ * @private
+ */
+ OptionsPage.updateScrollPosition_ = function () {
+ var topmostPage = this.getTopmostVisibleNonOverlayPage_();
+ var nestingLevel = topmostPage ? topmostPage.nestingLevel : 0;
+
+ var container = (nestingLevel > 0) ?
+ $('subpage-sheet-container-' + nestingLevel) : $('page-container');
+
+ var scrollTop = container.oldScrollTop || 0;
+ container.oldScrollTop = undefined;
+ window.scroll(document.body.scrollLeft, scrollTop);
+ };
+
+ /**
+ * Pushes the current page onto the history stack, overriding the last page
+ * if it is the generic chrome://settings/.
+ * @private
+ */
+ OptionsPage.updateHistoryState_ = function() {
+ var page = this.getTopmostVisiblePage();
+ var path = location.pathname;
+ if (path)
+ path = path.slice(1).replace(/\/$/, ''); // Remove trailing slash.
+ // The page is already in history (the user may have clicked the same link
+ // twice). Do nothing.
+ if (path == page.name)
+ return;
+
+ // If there is no path, the current location is chrome://settings/.
+ // Override this with the new page.
+ var historyFunction = path ? window.history.pushState :
+ window.history.replaceState;
+ historyFunction.call(window.history,
+ {pageName: page.name},
+ page.title,
+ '/' + page.name);
+ // Update tab title.
+ document.title = page.title;
+ };
+
+ /**
+ * Shows a registered Overlay page. Does not update history.
+ * @param {string} overlayName Page name.
+ * @param {OptionPage} rootPage The currently visible root-level page.
+ * @return {boolean} whether we showed an overlay.
+ */
+ OptionsPage.showOverlay_ = function(overlayName, rootPage) {
+ var overlay = this.registeredOverlayPages[overlayName.toLowerCase()];
+ if (!overlay || !overlay.canShowPage())
+ return false;
+
+ if ((!rootPage || !rootPage.sticky) && overlay.parentPage)
+ this.showPageByName(overlay.parentPage.name, false);
+
+ if (!overlay.visible) {
+ overlay.visible = true;
+ if (overlay.didShowPage) overlay.didShowPage();
+ }
+
+ return true;
+ };
+
+ /**
+ * Returns whether or not an overlay is visible.
+ * @return {boolean} True if an overlay is visible.
+ * @private
+ */
+ OptionsPage.isOverlayVisible_ = function() {
+ return this.getVisibleOverlay_() != null;
+ };
+
+ /**
+ * Returns the currently visible overlay, or null if no page is visible.
+ * @return {OptionPage} The visible overlay.
+ */
+ OptionsPage.getVisibleOverlay_ = function() {
+ for (var name in this.registeredOverlayPages) {
+ var page = this.registeredOverlayPages[name];
+ if (page.visible)
+ return page;
+ }
+ return null;
+ };
+
+ /**
+ * Closes the visible overlay. Updates the history state after closing the
+ * overlay.
+ */
+ OptionsPage.closeOverlay = function() {
+ var overlay = this.getVisibleOverlay_();
+ if (!overlay)
+ return;
+
+ overlay.visible = false;
+ if (overlay.didClosePage) overlay.didClosePage();
+ this.updateHistoryState_();
+ };
+
+ /**
+ * Hides the visible overlay. Does not affect the history state.
+ * @private
+ */
+ OptionsPage.hideOverlay_ = function() {
+ var overlay = this.getVisibleOverlay_();
+ if (overlay)
+ overlay.visible = false;
+ };
+
+ /**
+ * Returns the topmost visible page (overlays excluded).
+ * @return {OptionPage} The topmost visible page aside any overlay.
+ * @private
+ */
+ OptionsPage.getTopmostVisibleNonOverlayPage_ = function() {
+ var topPage = null;
+ for (var name in this.registeredPages) {
+ var page = this.registeredPages[name];
+ if (page.visible &&
+ (!topPage || page.nestingLevel > topPage.nestingLevel))
+ topPage = page;
+ }
+
+ return topPage;
+ };
+
+ /**
+ * Returns the topmost visible page, or null if no page is visible.
+ * @return {OptionPage} The topmost visible page.
+ */
+ OptionsPage.getTopmostVisiblePage = function() {
+ // Check overlays first since they're top-most if visible.
+ return this.getVisibleOverlay_() || this.getTopmostVisibleNonOverlayPage_();
+ };
+
+ /**
+ * Closes the topmost open subpage, if any.
+ * @private
+ */
+ OptionsPage.closeTopSubPage_ = function() {
+ var topPage = this.getTopmostVisiblePage();
+ if (topPage && !topPage.isOverlay && topPage.parentPage) {
+ if (topPage.willHidePage)
+ topPage.willHidePage();
+ topPage.visible = false;
+ }
+
+ this.updateHistoryState_();
+ };
+
+ /**
+ * Closes all subpages below the given level.
+ * @param {number} level The nesting level to close below.
+ */
+ OptionsPage.closeSubPagesToLevel = function(level) {
+ var topPage = this.getTopmostVisiblePage();
+ while (topPage && topPage.nestingLevel > level) {
+ if (topPage.willHidePage)
+ topPage.willHidePage();
+ topPage.visible = false;
+ topPage = topPage.parentPage;
+ }
+
+ this.updateHistoryState_();
+ };
+
+ /**
+ * Updates managed banner visibility state based on the topmost page.
+ */
+ OptionsPage.updateManagedBannerVisibility = function() {
+ var topPage = this.getTopmostVisiblePage();
+ if (topPage)
+ topPage.updateManagedBannerVisibility();
+ };
+
+ /**
+ * Shows the tab contents for the given navigation tab.
+ * @param {!Element} tab The tab that the user clicked.
+ */
+ OptionsPage.showTab = function(tab) {
+ // Search parents until we find a tab, or the nav bar itself. This allows
+ // tabs to have child nodes, e.g. labels in separately-styled spans.
+ while (tab && !tab.classList.contains('subpages-nav-tabs') &&
+ !tab.classList.contains('tab')) {
+ tab = tab.parentNode;
+ }
+ if (!tab || !tab.classList.contains('tab'))
+ return;
+
+ // Find tab bar of the tab.
+ var tabBar = tab;
+ while (tabBar && !tabBar.classList.contains('subpages-nav-tabs')) {
+ tabBar = tabBar.parentNode;
+ }
+ if (!tabBar)
+ return;
+
+ if (tabBar.activeNavTab != null) {
+ tabBar.activeNavTab.classList.remove('active-tab');
+ $(tabBar.activeNavTab.getAttribute('tab-contents')).classList.
+ remove('active-tab-contents');
+ }
+
+ tab.classList.add('active-tab');
+ $(tab.getAttribute('tab-contents')).classList.add('active-tab-contents');
+ tabBar.activeNavTab = tab;
+ };
+
+ /**
+ * Registers new options page.
+ * @param {OptionsPage} page Page to register.
+ */
+ OptionsPage.register = function(page) {
+ this.registeredPages[page.name.toLowerCase()] = page;
+ // Create and add new page <li> element to navbar.
+ var pageNav = document.createElement('li');
+ pageNav.id = page.name + 'PageNav';
+ pageNav.className = 'navbar-item';
+ pageNav.setAttribute('pageName', page.name);
+ pageNav.setAttribute('role', 'tab');
+ pageNav.textContent = page.pageDiv.querySelector('h1').textContent;
+ pageNav.tabIndex = -1;
+ pageNav.onclick = function(event) {
+ OptionsPage.navigateToPage(this.getAttribute('pageName'));
+ };
+ pageNav.onkeydown = function(event) {
+ if ((event.keyCode == 37 || event.keyCode==38) &&
+ this.previousSibling && this.previousSibling.onkeydown) {
+ // Left and up arrow moves back one tab.
+ OptionsPage.navigateToPage(
+ this.previousSibling.getAttribute('pageName'));
+ this.previousSibling.focus();
+ } else if ((event.keyCode == 39 || event.keyCode == 40) &&
+ this.nextSibling) {
+ // Right and down arrows move forward one tab.
+ OptionsPage.navigateToPage(this.nextSibling.getAttribute('pageName'));
+ this.nextSibling.focus();
+ }
+ };
+ pageNav.onkeypress = function(event) {
+ // Enter or space
+ if (event.keyCode == 13 || event.keyCode == 32) {
+ OptionsPage.navigateToPage(this.getAttribute('pageName'));
+ }
+ };
+ var navbar = $('navbar');
+ navbar.appendChild(pageNav);
+ page.tab = pageNav;
+ page.initializePage();
+ };
+
+ /**
+ * Find an enclosing section for an element if it exists.
+ * @param {Element} element Element to search.
+ * @return {OptionPage} The section element, or null.
+ * @private
+ */
+ OptionsPage.findSectionForNode_ = function(node) {
+ while (node = node.parentNode) {
+ if (node.nodeName == 'SECTION')
+ return node;
+ }
+ return null;
+ };
+
+ /**
+ * Registers a new Sub-page.
+ * @param {OptionsPage} subPage Sub-page to register.
+ * @param {OptionsPage} parentPage Associated parent page for this page.
+ * @param {Array} associatedControls Array of control elements that lead to
+ * this sub-page. The first item is typically a button in a root-level
+ * page. There may be additional buttons for nested sub-pages.
+ */
+ OptionsPage.registerSubPage = function(subPage,
+ parentPage,
+ associatedControls) {
+ this.registeredPages[subPage.name.toLowerCase()] = subPage;
+ subPage.parentPage = parentPage;
+ if (associatedControls) {
+ subPage.associatedControls = associatedControls;
+ if (associatedControls.length) {
+ subPage.associatedSection =
+ this.findSectionForNode_(associatedControls[0]);
+ }
+ }
+ subPage.tab = undefined;
+ subPage.initializePage();
+ };
+
+ /**
+ * Registers a new Overlay page.
+ * @param {OptionsPage} overlay Overlay to register.
+ * @param {OptionsPage} parentPage Associated parent page for this overlay.
+ * @param {Array} associatedControls Array of control elements associated with
+ * this page.
+ */
+ OptionsPage.registerOverlay = function(overlay,
+ parentPage,
+ associatedControls) {
+ this.registeredOverlayPages[overlay.name.toLowerCase()] = overlay;
+ overlay.parentPage = parentPage;
+ if (associatedControls) {
+ overlay.associatedControls = associatedControls;
+ if (associatedControls.length) {
+ overlay.associatedSection =
+ this.findSectionForNode_(associatedControls[0]);
+ }
+ }
+
+ // Reverse the button strip for views. See the documentation of
+ // reverseButtonStrip_() for an explanation of why this is necessary.
+ if (cr.isViews)
+ this.reverseButtonStrip_(overlay);
+
+ overlay.tab = undefined;
+ overlay.isOverlay = true;
+ overlay.initializePage();
+ };
+
+ /**
+ * Reverses the child elements of a button strip. This is necessary because
+ * WebKit does not alter the tab order for elements that are visually reversed
+ * using -webkit-box-direction: reverse, and the button order is reversed for
+ * views. See https://bugs.webkit.org/show_bug.cgi?id=62664 for more
+ * information.
+ * @param {Object} overlay The overlay containing the button strip to reverse.
+ * @private
+ */
+ OptionsPage.reverseButtonStrip_ = function(overlay) {
+ var buttonStrips = overlay.pageDiv.querySelectorAll('.button-strip');
+
+ // Reverse all button-strips in the overlay.
+ for (var j = 0; j < buttonStrips.length; j++) {
+ var buttonStrip = buttonStrips[j];
+
+ var childNodes = buttonStrip.childNodes;
+ for (var i = childNodes.length - 1; i >= 0; i--)
+ buttonStrip.appendChild(childNodes[i]);
+ }
+ };
+
+ /**
+ * Callback for window.onpopstate.
+ * @param {Object} data State data pushed into history.
+ */
+ OptionsPage.setState = function(data) {
+ if (data && data.pageName) {
+ // It's possible an overlay may be the last top-level page shown.
+ if (this.isOverlayVisible_() &&
+ !this.registeredOverlayPages[data.pageName.toLowerCase()]) {
+ this.hideOverlay_();
+ }
+
+ this.showPageByName(data.pageName, false);
+ }
+ };
+
+ /**
+ * Callback for window.onbeforeunload. Used to notify overlays that they will
+ * be closed.
+ */
+ OptionsPage.willClose = function() {
+ var overlay = this.getVisibleOverlay_();
+ if (overlay && overlay.didClosePage)
+ overlay.didClosePage();
+ };
+
+ /**
+ * Freezes/unfreezes the scroll position of given level's page container.
+ * @param {boolean} freeze Whether the page should be frozen.
+ * @param {number} level The level to freeze/unfreeze.
+ * @private
+ */
+ OptionsPage.setPageFrozenAtLevel_ = function(freeze, level) {
+ var container = level == 0 ? $('page-container')
+ : $('subpage-sheet-container-' + level);
+
+ if (container.classList.contains('frozen') == freeze)
+ return;
+
+ if (freeze) {
+ // Lock the width, since auto width computation may change.
+ container.style.width = window.getComputedStyle(container).width;
+ container.oldScrollTop = document.body.scrollTop;
+ container.classList.add('frozen');
+ var verticalPosition =
+ container.getBoundingClientRect().top - container.oldScrollTop;
+ container.style.top = verticalPosition + 'px';
+ this.updateFrozenElementHorizontalPosition_(container);
+ } else {
+ container.classList.remove('frozen');
+ container.style.top = '';
+ container.style.left = '';
+ container.style.right = '';
+ container.style.width = '';
+ }
+ };
+
+ /**
+ * Freezes/unfreezes the scroll position of visible pages based on the current
+ * page stack.
+ */
+ OptionsPage.updatePageFreezeStates = function() {
+ var topPage = OptionsPage.getTopmostVisiblePage();
+ if (!topPage)
+ return;
+ var nestingLevel = topPage.isOverlay ? 100 : topPage.nestingLevel;
+ for (var i = 0; i <= SUBPAGE_SHEET_COUNT; i++) {
+ this.setPageFrozenAtLevel_(i < nestingLevel, i);
+ }
+ };
+
+ /**
+ * Initializes the complete options page. This will cause all C++ handlers to
+ * be invoked to do final setup.
+ */
+ OptionsPage.initialize = function() {
+ chrome.send('coreOptionsInitialize');
+ this.initialized_ = true;
+
+ document.addEventListener('scroll', this.handleScroll_.bind(this));
+ window.addEventListener('resize', this.handleResize_.bind(this));
+
+ if (!document.documentElement.classList.contains('hide-menu')) {
+ // Close subpages if the user clicks on the html body. Listen in the
+ // capturing phase so that we can stop the click from doing anything.
+ document.body.addEventListener('click',
+ this.bodyMouseEventHandler_.bind(this),
+ true);
+ // We also need to cancel mousedowns on non-subpage content.
+ document.body.addEventListener('mousedown',
+ this.bodyMouseEventHandler_.bind(this),
+ true);
+
+ var self = this;
+ // Hook up the close buttons.
+ subpageCloseButtons = document.querySelectorAll('.close-subpage');
+ for (var i = 0; i < subpageCloseButtons.length; i++) {
+ subpageCloseButtons[i].onclick = function() {
+ self.closeTopSubPage_();
+ };
+ };
+
+ // Install handler for key presses.
+ document.addEventListener('keydown',
+ this.keyDownEventHandler_.bind(this));
+
+ document.addEventListener('focus', this.manageFocusChange_.bind(this),
+ true);
+ }
+
+ // Calculate and store the horizontal locations of elements that may be
+ // frozen later.
+ var sidebarWidth =
+ parseInt(window.getComputedStyle($('mainview')).webkitPaddingStart, 10);
+ $('page-container').horizontalOffset = sidebarWidth +
+ parseInt(window.getComputedStyle(
+ $('mainview-content')).webkitPaddingStart, 10);
+ for (var level = 1; level <= SUBPAGE_SHEET_COUNT; level++) {
+ var containerId = 'subpage-sheet-container-' + level;
+ $(containerId).horizontalOffset = sidebarWidth;
+ }
+ $('subpage-backdrop').horizontalOffset = sidebarWidth;
+ // Trigger the resize handler manually to set the initial state.
+ this.handleResize_(null);
+ };
+
+ /**
+ * Does a bounds check for the element on the given x, y client coordinates.
+ * @param {Element} e The DOM element.
+ * @param {number} x The client X to check.
+ * @param {number} y The client Y to check.
+ * @return {boolean} True if the point falls within the element's bounds.
+ * @private
+ */
+ OptionsPage.elementContainsPoint_ = function(e, x, y) {
+ var clientRect = e.getBoundingClientRect();
+ return x >= clientRect.left && x <= clientRect.right &&
+ y >= clientRect.top && y <= clientRect.bottom;
+ };
+
+ /**
+ * Called when focus changes; ensures that focus doesn't move outside
+ * the topmost subpage/overlay.
+ * @param {Event} e The focus change event.
+ * @private
+ */
+ OptionsPage.manageFocusChange_ = function(e) {
+ var focusableItemsRoot;
+ var topPage = this.getTopmostVisiblePage();
+ if (!topPage)
+ return;
+
+ if (topPage.isOverlay) {
+ // If an overlay is visible, that defines the tab loop.
+ focusableItemsRoot = topPage.pageDiv;
+ } else {
+ // If a subpage is visible, use its parent as the tab loop constraint.
+ // (The parent is used because it contains the close button.)
+ if (topPage.nestingLevel > 0)
+ focusableItemsRoot = topPage.pageDiv.parentNode;
+ }
+
+ if (focusableItemsRoot && !focusableItemsRoot.contains(e.target))
+ topPage.focusFirstElement();
+ };
+
+ /**
+ * Called when the page is scrolled; moves elements that are position:fixed
+ * but should only behave as if they are fixed for vertical scrolling.
+ * @param {Event} e The scroll event.
+ * @private
+ */
+ OptionsPage.handleScroll_ = function(e) {
+ var scrollHorizontalOffset = document.body.scrollLeft;
+ // position:fixed doesn't seem to work for horizontal scrolling in RTL mode,
+ // so only adjust in LTR mode (where scroll values will be positive).
+ if (scrollHorizontalOffset >= 0) {
+ $('navbar-container').style.left = -scrollHorizontalOffset + 'px';
+ var subpageBackdrop = $('subpage-backdrop');
+ subpageBackdrop.style.left = subpageBackdrop.horizontalOffset -
+ scrollHorizontalOffset + 'px';
+ this.updateAllFrozenElementPositions_();
+ }
+ };
+
+ /**
+ * Updates all frozen pages to match the horizontal scroll position.
+ * @private
+ */
+ OptionsPage.updateAllFrozenElementPositions_ = function() {
+ var frozenElements = document.querySelectorAll('.frozen');
+ for (var i = 0; i < frozenElements.length; i++) {
+ this.updateFrozenElementHorizontalPosition_(frozenElements[i]);
+ }
+ };
+
+ /**
+ * Updates the given frozen element to match the horizontal scroll position.
+ * @param {HTMLElement} e The frozen element to update
+ * @private
+ */
+ OptionsPage.updateFrozenElementHorizontalPosition_ = function(e) {
+ if (document.documentElement.dir == 'rtl')
+ e.style.right = e.horizontalOffset + 'px';
+ else
+ e.style.left = e.horizontalOffset - document.body.scrollLeft + 'px';
+ };
+
+ /**
+ * Called when the page is resized; adjusts the size of elements that depend
+ * on the veiwport.
+ * @param {Event} e The resize event.
+ * @private
+ */
+ OptionsPage.handleResize_ = function(e) {
+ // Set an explicit height equal to the viewport on all the subpage
+ // containers shorter than the viewport. This is used instead of
+ // min-height: 100% so that there is an explicit height for the subpages'
+ // min-height: 100%.
+ var viewportHeight = document.documentElement.clientHeight;
+ var subpageContainers =
+ document.querySelectorAll('.subpage-sheet-container');
+ for (var i = 0; i < subpageContainers.length; i++) {
+ if (subpageContainers[i].scrollHeight > viewportHeight)
+ subpageContainers[i].style.removeProperty('height');
+ else
+ subpageContainers[i].style.height = viewportHeight + 'px';
+ }
+ };
+
+ /**
+ * A function to handle mouse events (mousedown or click) on the html body by
+ * closing subpages and/or stopping event propagation.
+ * @return {Event} a mousedown or click event.
+ * @private
+ */
+ OptionsPage.bodyMouseEventHandler_ = function(event) {
+ // Do nothing if a subpage isn't showing.
+ var topPage = this.getTopmostVisiblePage();
+ if (!topPage || topPage.isOverlay || !topPage.parentPage)
+ return;
+
+ // Don't close subpages if a user is clicking in a select element.
+ // This is necessary because WebKit sends click events with strange
+ // coordinates when a user selects a new entry in a select element.
+ // See: http://crbug.com/87199
+ if (event.srcElement.nodeName == 'SELECT')
+ return;
+
+ // Do nothing if the client coordinates are not within the source element.
+ // This occurs if the user toggles a checkbox by pressing spacebar.
+ // This is a workaround to prevent keyboard events from closing the window.
+ // See: crosbug.com/15678
+ if (event.clientX == -document.body.scrollLeft &&
+ event.clientY == -document.body.scrollTop) {
+ return;
+ }
+
+ // Don't interfere with navbar clicks.
+ if ($('navbar').contains(event.target))
+ return;
+
+ // Figure out which page the click happened in.
+ for (var level = topPage.nestingLevel; level >= 0; level--) {
+ var clickIsWithinLevel = level == 0 ? true :
+ OptionsPage.elementContainsPoint_(
+ $('subpage-sheet-' + level), event.clientX, event.clientY);
+
+ if (!clickIsWithinLevel)
+ continue;
+
+ // Event was within the topmost page; do nothing.
+ if (topPage.nestingLevel == level)
+ return;
+
+ // Block propgation of both clicks and mousedowns, but only close subpages
+ // on click.
+ if (event.type == 'click')
+ this.closeSubPagesToLevel(level);
+ event.stopPropagation();
+ event.preventDefault();
+ return;
+ }
+ };
+
+ /**
+ * A function to handle key press events.
+ * @return {Event} a keydown event.
+ * @private
+ */
+ OptionsPage.keyDownEventHandler_ = function(event) {
+ // Close the top overlay or sub-page on esc.
+ if (event.keyCode == 27) { // Esc
+ if (this.isOverlayVisible_())
+ this.closeOverlay();
+ else
+ this.closeTopSubPage_();
+ }
+ };
+
+ OptionsPage.setClearPluginLSODataEnabled = function(enabled) {
+ if (enabled) {
+ document.documentElement.setAttribute(
+ 'flashPluginSupportsClearSiteData', '');
+ } else {
+ document.documentElement.removeAttribute(
+ 'flashPluginSupportsClearSiteData');
+ }
+ };
+
+ /**
+ * Re-initializes the C++ handlers if necessary. This is called if the
+ * handlers are torn down and recreated but the DOM may not have been (in
+ * which case |initialize| won't be called again). If |initialize| hasn't been
+ * called, this does nothing (since it will be later, once the DOM has
+ * finished loading).
+ */
+ OptionsPage.reinitializeCore = function() {
+ if (this.initialized_)
+ chrome.send('coreOptionsInitialize');
+ }
+
+ OptionsPage.prototype = {
+ __proto__: cr.EventTarget.prototype,
+
+ /**
+ * The parent page of this option page, or null for top-level pages.
+ * @type {OptionsPage}
+ */
+ parentPage: null,
+
+ /**
+ * The section on the parent page that is associated with this page.
+ * Can be null.
+ * @type {Element}
+ */
+ associatedSection: null,
+
+ /**
+ * An array of controls that are associated with this page. The first
+ * control should be located on a top-level page.
+ * @type {OptionsPage}
+ */
+ associatedControls: null,
+
+ /**
+ * Initializes page content.
+ */
+ initializePage: function() {},
+
+ /**
+ * Updates managed banner visibility state. This function iterates over
+ * all input fields of a window and if any of these is marked as managed
+ * it triggers the managed banner to be visible. The banner can be enforced
+ * being on through the managed flag of this class but it can not be forced
+ * being off if managed items exist.
+ */
+ updateManagedBannerVisibility: function() {
+ var bannerDiv = $('managed-prefs-banner');
+
+ var controlledByPolicy = false;
+ var controlledByExtension = false;
+ var inputElements = this.pageDiv.querySelectorAll('input[controlled-by]');
+ for (var i = 0, len = inputElements.length; i < len; i++) {
+ if (inputElements[i].controlledBy == 'policy')
+ controlledByPolicy = true;
+ else if (inputElements[i].controlledBy == 'extension')
+ controlledByExtension = true;
+ }
+ if (!controlledByPolicy && !controlledByExtension) {
+ bannerDiv.hidden = true;
+ } else {
+ bannerDiv.hidden = false;
+ var height = window.getComputedStyle(bannerDiv).height;
+ if (controlledByPolicy && !controlledByExtension) {
+ $('managed-prefs-text').textContent =
+ templateData.policyManagedPrefsBannerText;
+ } else if (!controlledByPolicy && controlledByExtension) {
+ $('managed-prefs-text').textContent =
+ templateData.extensionManagedPrefsBannerText;
+ } else if (controlledByPolicy && controlledByExtension) {
+ $('managed-prefs-text').textContent =
+ templateData.policyAndExtensionManagedPrefsBannerText;
+ }
+ }
+ },
+
+ /**
+ * Gets page visibility state.
+ */
+ get visible() {
+ return !this.pageDiv.hidden;
+ },
+
+ /**
+ * Sets page visibility.
+ */
+ set visible(visible) {
+ if ((this.visible && visible) || (!this.visible && !visible))
+ return;
+
+ this.setContainerVisibility_(visible);
+ if (visible) {
+ this.pageDiv.hidden = false;
+
+ if (this.tab) {
+ this.tab.classList.add('navbar-item-selected');
+ this.tab.setAttribute('aria-selected', 'true');
+ this.tab.tabIndex = 0;
+ }
+ } else {
+ this.pageDiv.hidden = true;
+
+ if (this.tab) {
+ this.tab.classList.remove('navbar-item-selected');
+ this.tab.setAttribute('aria-selected', 'false');
+ this.tab.tabIndex = -1;
+ }
+ }
+
+ OptionsPage.updatePageFreezeStates();
+
+ // The managed prefs banner is global, so after any visibility change
+ // update it based on the topmost page, not necessarily this page
+ // (e.g., if an ancestor is made visible after a child).
+ OptionsPage.updateManagedBannerVisibility();
+
+ // A subpage was shown or hidden.
+ if (!this.isOverlay && this.nestingLevel > 0) {
+ OptionsPage.updateSubpageBackdrop_();
+ OptionsPage.updateScrollPosition_();
+ }
+
+ cr.dispatchPropertyChange(this, 'visible', visible, !visible);
+ },
+
+ /**
+ * Shows or hides this page's container.
+ * @param {boolean} visible Whether the container should be visible or not.
+ * @private
+ */
+ setContainerVisibility_: function(visible) {
+ var container = null;
+ if (this.isOverlay) {
+ container = $('overlay');
+ } else {
+ var nestingLevel = this.nestingLevel;
+ if (nestingLevel > 0)
+ container = $('subpage-sheet-container-' + nestingLevel);
+ }
+ var isSubpage = !this.isOverlay;
+
+ if (!container)
+ return;
+
+ if (container.hidden != visible) {
+ if (visible) {
+ // If the container is set hidden and then immediately set visible
+ // again, the fadeCompleted_ callback would cause it to be erroneously
+ // hidden again. Removing the transparent tag avoids that.
+ container.classList.remove('transparent');
+ }
+ return;
+ }
+
+ if (visible) {
+ container.hidden = false;
+ if (isSubpage) {
+ var computedStyle = window.getComputedStyle(container);
+ container.style.WebkitPaddingStart =
+ parseInt(computedStyle.WebkitPaddingStart, 10) + 100 + 'px';
+ }
+ // Separate animating changes from the removal of display:none.
+ window.setTimeout(function() {
+ container.classList.remove('transparent');
+ if (isSubpage)
+ container.style.WebkitPaddingStart = '';
+ });
+ } else {
+ var self = this;
+ container.addEventListener('webkitTransitionEnd', function f(e) {
+ if (e.propertyName != 'opacity')
+ return;
+ container.removeEventListener('webkitTransitionEnd', f);
+ self.fadeCompleted_(container);
+ });
+ container.classList.add('transparent');
+ }
+ },
+
+ /**
+ * Called when a container opacity transition finishes.
+ * @param {HTMLElement} container The container element.
+ * @private
+ */
+ fadeCompleted_: function(container) {
+ if (container.classList.contains('transparent'))
+ container.hidden = true;
+ },
+
+ /**
+ * Focuses the first control on the page.
+ */
+ focusFirstElement: function() {
+ // Sets focus on the first interactive element in the page.
+ var focusElement =
+ this.pageDiv.querySelector('button, input, list, select');
+ if (focusElement)
+ focusElement.focus();
+ },
+
+ /**
+ * The nesting level of this page.
+ * @type {number} The nesting level of this page (0 for top-level page)
+ */
+ get nestingLevel() {
+ var level = 0;
+ var parent = this.parentPage;
+ while (parent) {
+ level++;
+ parent = parent.parentPage;
+ }
+ return level;
+ },
+
+ /**
+ * Whether the page is considered 'sticky', such that it will
+ * remain a top-level page even if sub-pages change.
+ * @type {boolean} True if this page is sticky.
+ */
+ get sticky() {
+ return false;
+ },
+
+ /**
+ * Checks whether this page is an ancestor of the given page in terms of
+ * subpage nesting.
+ * @param {OptionsPage} page
+ * @return {boolean} True if this page is nested under |page|
+ */
+ isAncestorOfPage: function(page) {
+ var parent = page.parentPage;
+ while (parent) {
+ if (parent == this)
+ return true;
+ parent = parent.parentPage;
+ }
+ return false;
+ },
+
+ /**
+ * Whether it should be possible to show the page.
+ * @return {boolean} True if the page should be shown
+ */
+ canShowPage: function() {
+ return true;
+ },
+ };
+
+ // Export
+ return {
+ OptionsPage: OptionsPage
+ };
+});
diff --git a/chrome/browser/resources/options2/pack_extension_overlay.css b/chrome/browser/resources/options2/pack_extension_overlay.css
new file mode 100644
index 0000000..169750b
--- /dev/null
+++ b/chrome/browser/resources/options2/pack_extension_overlay.css
@@ -0,0 +1,18 @@
+/*
+Copyright (c) 2011 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+*/
+
+.packExtensionHeading {
+ width: 520px;
+ padding-bottom: 5px;
+}
+
+.packExtensionTextBoxes {
+ text-align: right;
+}
+
+.packExtensionTextArea {
+ width: 260px;
+}
diff --git a/chrome/browser/resources/options2/pack_extension_overlay.html b/chrome/browser/resources/options2/pack_extension_overlay.html
new file mode 100644
index 0000000..bd77789
--- /dev/null
+++ b/chrome/browser/resources/options2/pack_extension_overlay.html
@@ -0,0 +1,28 @@
+<div id="packExtensionOverlay" class="page" hidden>
+ <h1 i18n-content="packExtensionOverlay"></h1>
+ <div id="cbdContentArea" class="content-area">
+ <div class="packExtensionHeading" i18n-content="packExtensionHeading"></div>
+ <div class="packExtensionTextBoxes">
+ <label i18n-content="packExtensionRootDir"></label>
+ <input class="packExtensionTextArea" id="extensionRootDir" type="text" />
+ <button id="browseExtensionDir"
+ i18n-content="packExtensionBrowseButton"></button>
+ </div>
+ <div class="packExtensionTextBoxes">
+ <label i18n-content="packExtensionPrivateKey"></label>
+ <input class="packExtensionTextArea"
+ id="extensionPrivateKey" type="text" />
+ <button id="browsePrivateKey"
+ i18n-content="packExtensionBrowseButton"></button>
+ </div>
+ </div>
+ <div class="action-area">
+ <div class="action-area-right">
+ <div class="button-strip">
+ <button id="packExtensionDismiss" i18n-content="cancel"></button>
+ <button id="packExtensionCommit"
+ i18n-content="packExtensionCommit"></button>
+ </div>
+ </div>
+ </div>
+</div> \ No newline at end of file
diff --git a/chrome/browser/resources/options2/pack_extension_overlay.js b/chrome/browser/resources/options2/pack_extension_overlay.js
new file mode 100644
index 0000000..4154dcd
--- /dev/null
+++ b/chrome/browser/resources/options2/pack_extension_overlay.js
@@ -0,0 +1,90 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+ const OptionsPage = options.OptionsPage;
+
+ /**
+ * PackExtensionOverlay class
+ * Encapsulated handling of the 'Pack Extension' overlay page.
+ * @constructor
+ */
+ function PackExtensionOverlay() {
+ OptionsPage.call(this, 'packExtensionOverlay',
+ templateData.packExtensionOverlayTabTitle,
+ 'packExtensionOverlay');
+ }
+
+ cr.addSingletonGetter(PackExtensionOverlay);
+
+ PackExtensionOverlay.prototype = {
+ // Inherit PackExtensionOverlay from OptionsPage.
+ __proto__: OptionsPage.prototype,
+
+ /**
+ * Initialize the page.
+ */
+ initializePage: function() {
+ // Call base class implementation to starts preference initialization.
+ OptionsPage.prototype.initializePage.call(this);
+
+ $('packExtensionDismiss').onclick = function(event) {
+ OptionsPage.closeOverlay();
+ };
+ $('packExtensionCommit').onclick = function(event) {
+ var extensionPath = $('extensionRootDir').value;
+ var privateKeyPath = $('extensionPrivateKey').value;
+ chrome.send('pack', [extensionPath, privateKeyPath]);
+ };
+ $('browseExtensionDir').addEventListener('click',
+ this.handleBrowseExtensionDir_.bind(this));
+ $('browsePrivateKey').addEventListener('click',
+ this.handleBrowsePrivateKey_.bind(this));
+ },
+
+ /**
+ * Utility function which asks the C++ to show a platform-specific file
+ * select dialog, and fire |callback| with the |filePath| that resulted.
+ * |selectType| can be either 'file' or 'folder'. |operation| can be 'load',
+ * 'packRoot', or 'pem' which are signals to the C++ to do some
+ * operation-specific configuration.
+ * @private
+ */
+ showFileDialog_: function(selectType, operation, callback) {
+ handleFilePathSelected = function(filePath) {
+ callback(filePath);
+ handleFilePathSelected = function() {};
+ };
+
+ chrome.send('extensionSettingsSelectFilePath', [selectType, operation]);
+ },
+
+ /**
+ * Handles the showing of the extension directory browser.
+ * @param {Event} e Change event.
+ * @private
+ */
+ handleBrowseExtensionDir_: function(e) {
+ this.showFileDialog_('folder', 'load', function(filePath) {
+ $('extensionRootDir').value = filePath;
+ });
+ },
+
+ /**
+ * Handles the showing of the extension private key file.
+ * @param {Event} e Change event.
+ * @private
+ */
+ handleBrowsePrivateKey_: function(e) {
+ this.showFileDialog_('file', 'load', function(filePath) {
+ $('extensionPrivateKey').value = filePath;
+ });
+ },
+ };
+
+ // Export
+ return {
+ PackExtensionOverlay: PackExtensionOverlay
+ };
+});
diff --git a/chrome/browser/resources/options2/password_manager.css b/chrome/browser/resources/options2/password_manager.css
new file mode 100644
index 0000000..76ec562
--- /dev/null
+++ b/chrome/browser/resources/options2/password_manager.css
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2011 The Chromium Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#password-search-column {
+ bottom: 10px;
+ position: absolute;
+ right: 0;
+}
+
+html[dir=rtl] #password-search-column {
+ left: 0;
+ right: auto;
+}
+
+#password-list-headers {
+ position: relative;
+ width: 100%;
+}
+
+#password-list-headers h3 {
+ font-size: 105%;
+ font-weight: bold;
+ margin: 10px 0;
+}
+
+#passwords-title {
+ display: inline-block;
+}
diff --git a/chrome/browser/resources/options2/password_manager.html b/chrome/browser/resources/options2/password_manager.html
new file mode 100644
index 0000000..333042f
--- /dev/null
+++ b/chrome/browser/resources/options2/password_manager.html
@@ -0,0 +1,28 @@
+<div id="password-manager" class="page" hidden>
+ <h1 i18n-content="passwordsPage"></h1>
+ <div id="password-list-headers">
+ <div id="passwords-title">
+ <h3 i18n-content="savedPasswordsTitle"></h3>
+ </div>
+ <div id="password-search-column">
+ <input id="password-search-box" type="search"
+ i18n-values="placeholder:passwordSearchPlaceholder" incremental
+ results="10" autosave="org.chromium.options.passwords.search">
+ </div>
+ </div>
+ <list id="saved-passwords-list" class="settings-list"></list>
+ <div id="saved-passwords-list-empty-placeholder"
+ class="settings-list-empty" hidden>
+ <span i18n-content="passwordsNoPasswordsDescription"></span>
+ <a target="_blank" i18n-content="learnMore"
+ i18n-values="href:passwordManagerLearnMoreURL"></a>
+ </div>
+ <h3 i18n-content="passwordExceptionsTitle"></h3>
+ <list id="password-exceptions-list" class="settings-list"></list>
+ <div id="password-exceptions-list-empty-placeholder" hidden
+ class="settings-list-empty">
+ <span i18n-content="passwordsNoExceptionsDescription"></span>
+ <a target="_blank" i18n-content="learnMore"
+ i18n-values="href:passwordManagerLearnMoreURL"></a>
+ </div>
+</div>
diff --git a/chrome/browser/resources/options2/password_manager.js b/chrome/browser/resources/options2/password_manager.js
new file mode 100644
index 0000000..273d2c0
--- /dev/null
+++ b/chrome/browser/resources/options2/password_manager.js
@@ -0,0 +1,228 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+ const OptionsPage = options.OptionsPage;
+ const ArrayDataModel = cr.ui.ArrayDataModel;
+
+ /////////////////////////////////////////////////////////////////////////////
+ // PasswordManager class:
+
+ /**
+ * Encapsulated handling of password and exceptions page.
+ * @constructor
+ */
+ function PasswordManager() {
+ this.activeNavTab = null;
+ OptionsPage.call(this,
+ 'passwords',
+ templateData.passwordsPageTabTitle,
+ 'password-manager');
+ }
+
+ cr.addSingletonGetter(PasswordManager);
+
+ PasswordManager.prototype = {
+ __proto__: OptionsPage.prototype,
+
+ /**
+ * The saved passwords list.
+ * @type {DeletableItemList}
+ * @private
+ */
+ savedPasswordsList_: null,
+
+ /**
+ * The password exceptions list.
+ * @type {DeletableItemList}
+ * @private
+ */
+ passwordExceptionsList_: null,
+
+ /**
+ * The timer id of the timer set on search query change events.
+ * @type {number}
+ * @private
+ */
+ queryDelayTimerId_: 0,
+
+ /**
+ * The most recent search query, or null if the query is empty.
+ * @type {?string}
+ * @private
+ */
+ lastQuery_: null,
+
+ /** @inheritDoc */
+ initializePage: function() {
+ OptionsPage.prototype.initializePage.call(this);
+
+ $('password-search-box').addEventListener('search',
+ this.handleSearchQueryChange_.bind(this));
+
+ this.createSavedPasswordsList_();
+ this.createPasswordExceptionsList_();
+ },
+
+ /** @inheritDoc */
+ canShowPage: function() {
+ return !PersonalOptions.disablePasswordManagement();
+ },
+
+ /** @inheritDoc */
+ didShowPage: function() {
+ // Updating the password lists may cause a blocking platform dialog pop up
+ // (Mac, Linux), so we delay this operation until the page is shown.
+ chrome.send('updatePasswordLists');
+ $('password-search-box').focus();
+ },
+
+ /**
+ * Creates, decorates and initializes the saved passwords list.
+ * @private
+ */
+ createSavedPasswordsList_: function() {
+ this.savedPasswordsList_ = $('saved-passwords-list');
+ options.passwordManager.PasswordsList.decorate(this.savedPasswordsList_);
+ this.savedPasswordsList_.autoExpands = true;
+ },
+
+ /**
+ * Creates, decorates and initializes the password exceptions list.
+ * @private
+ */
+ createPasswordExceptionsList_: function() {
+ this.passwordExceptionsList_ = $('password-exceptions-list');
+ options.passwordManager.PasswordExceptionsList.decorate(
+ this.passwordExceptionsList_);
+ this.passwordExceptionsList_.autoExpands = true;
+ },
+
+ /**
+ * Handles search query changes.
+ * @param {!Event} e The event object.
+ * @private
+ */
+ handleSearchQueryChange_: function(e) {
+ if (this.queryDelayTimerId_)
+ window.clearTimeout(this.queryDelayTimerId_);
+
+ // Searching cookies uses a timeout of 500ms. We use a shorter timeout
+ // because there are probably fewer passwords and we want the UI to be
+ // snappier since users will expect that it's "less work."
+ this.queryDelayTimerId_ = window.setTimeout(
+ this.searchPasswords_.bind(this), 250);
+ },
+
+ /**
+ * Search passwords using text in |password-search-box|.
+ * @private
+ */
+ searchPasswords_: function() {
+ this.queryDelayTimerId_ = 0;
+ var filter = $('password-search-box').value;
+ filter = (filter == '') ? null : filter;
+ if (this.lastQuery_ != filter) {
+ this.lastQuery_ = filter;
+ // Searching for passwords has the side effect of requerying the
+ // underlying password store. This is done intentionally, as on OS X and
+ // Linux they can change from outside and we won't be notified of it.
+ chrome.send('updatePasswordLists');
+ }
+ },
+
+ /**
+ * Updates the visibility of the list and empty list placeholder.
+ * @param {!List} list The list to toggle visilibility for.
+ */
+ updateListVisibility_: function(list) {
+ var empty = list.dataModel.length == 0;
+ var listPlaceHolderID = list.id + '-empty-placeholder';
+ list.hidden = empty;
+ $(listPlaceHolderID).hidden = !empty;
+ },
+
+ /**
+ * Updates the data model for the saved passwords list with the values from
+ * |entries|.
+ * @param {Array} entries The list of saved password data.
+ */
+ setSavedPasswordsList_: function(entries) {
+ if (this.lastQuery_) {
+ // Implement password searching here in javascript, rather than in C++.
+ // The number of saved passwords shouldn't be too big for us to handle.
+ var query = this.lastQuery_;
+ var filter = function(entry, index, list) {
+ // Search both URL and username.
+ if (entry[0].indexOf(query) >= 0 || entry[1].indexOf(query) >= 0) {
+ // Keep the original index so we can delete correctly. See also
+ // deleteItemAtIndex() in password_manager_list.js that uses this.
+ entry[3] = index;
+ return true;
+ }
+ return false;
+ };
+ entries = entries.filter(filter);
+ }
+ this.savedPasswordsList_.dataModel = new ArrayDataModel(entries);
+ this.updateListVisibility_(this.savedPasswordsList_);
+ },
+
+ /**
+ * Updates the data model for the password exceptions list with the values
+ * from |entries|.
+ * @param {Array} entries The list of password exception data.
+ */
+ setPasswordExceptionsList_: function(entries) {
+ this.passwordExceptionsList_.dataModel = new ArrayDataModel(entries);
+ this.updateListVisibility_(this.passwordExceptionsList_);
+ },
+ };
+
+ /**
+ * Call to remove a saved password.
+ * @param rowIndex indicating the row to remove.
+ */
+ PasswordManager.removeSavedPassword = function(rowIndex) {
+ chrome.send('removeSavedPassword', [String(rowIndex)]);
+ };
+
+ /**
+ * Call to remove a password exception.
+ * @param rowIndex indicating the row to remove.
+ */
+ PasswordManager.removePasswordException = function(rowIndex) {
+ chrome.send('removePasswordException', [String(rowIndex)]);
+ };
+
+ /**
+ * Call to remove all saved passwords.
+ * @param tab contentType of the tab currently on.
+ */
+ PasswordManager.removeAllPasswords = function() {
+ chrome.send('removeAllSavedPasswords');
+ };
+
+ /**
+ * Call to remove all saved passwords.
+ * @param tab contentType of the tab currently on.
+ */
+ PasswordManager.removeAllPasswordExceptions = function() {
+ chrome.send('removeAllPasswordExceptions');
+ };
+
+ PasswordManager.setSavedPasswordsList = function(entries) {
+ PasswordManager.getInstance().setSavedPasswordsList_(entries);
+ };
+
+ PasswordManager.setPasswordExceptionsList = function(entries) {
+ PasswordManager.getInstance().setPasswordExceptionsList_(entries);
+ };
+
+ // Export
+ return {
+ PasswordManager: PasswordManager
+ };
+
+});
diff --git a/chrome/browser/resources/options2/password_manager_list.css b/chrome/browser/resources/options2/password_manager_list.css
new file mode 100644
index 0000000..576ecfd
--- /dev/null
+++ b/chrome/browser/resources/options2/password_manager_list.css
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2011 The Chromium Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+button.password-button {
+ -webkit-transition: opacity .15s;
+ background: #8aaaed;
+ color: #fff;
+ display: inline;
+ font-size: 90%;
+ font-weight: bold;
+ height: 18px;
+ opacity: 0.3;
+ padding: 0 2px;
+ position: absolute;
+ top: 5px;
+}
+
+button.password-button:hover {
+ -webkit-transition: opacity .15s;
+ opacity: 1;
+}
+
+html[dir='ltr'] button.password-button {
+ right: 2px;
+}
+
+html[dir='rtl'] button.password-button {
+ left: 2px;
+}
+
+input[type="password"].inactive-password {
+ background: transparent;
+ border: none;
+}
+
+#saved-passwords-list .url {
+ box-sizing: border-box;
+ width: 40%;
+}
+
+#saved-passwords-list .name {
+ -webkit-box-flex: 1;
+ width: 20%;
+}
+
+#saved-passwords-list .password {
+ -webkit-box-flex: 1;
+ position: relative;
+}
+
+#saved-passwords-list .password input[type="password"],
+#saved-passwords-list .password input[type="text"] {
+ box-sizing: border-box;
+ width: 100%;
+}
+
+#password-exceptions-list .url {
+ -webkit-box-flex: 1;
+}
+
+#saved-passwords-list .url,
+#saved-passwords-list .name,
+#password-exceptions-list .url {
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
diff --git a/chrome/browser/resources/options2/password_manager_list.js b/chrome/browser/resources/options2/password_manager_list.js
new file mode 100644
index 0000000..c3b822a
--- /dev/null
+++ b/chrome/browser/resources/options2/password_manager_list.js
@@ -0,0 +1,283 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options.passwordManager', function() {
+ const ArrayDataModel = cr.ui.ArrayDataModel;
+ const DeletableItemList = options.DeletableItemList;
+ const DeletableItem = options.DeletableItem;
+ const List = cr.ui.List;
+
+ /**
+ * Creates a new passwords list item.
+ * @param {Array} entry An array of the form [url, username, password]. When
+ * the list has been filtered, a fourth element [index] may be present.
+ * @constructor
+ * @extends {cr.ui.ListItem}
+ */
+ function PasswordListItem(entry, showPasswords) {
+ var el = cr.doc.createElement('div');
+ el.dataItem = entry;
+ el.__proto__ = PasswordListItem.prototype;
+ el.decorate(showPasswords);
+
+ return el;
+ }
+
+ PasswordListItem.prototype = {
+ __proto__: DeletableItem.prototype,
+
+ /** @inheritDoc */
+ decorate: function(showPasswords) {
+ DeletableItem.prototype.decorate.call(this);
+
+ // The URL of the site.
+ var urlLabel = this.ownerDocument.createElement('div');
+ urlLabel.classList.add('favicon-cell');
+ urlLabel.classList.add('weakrtl');
+ urlLabel.classList.add('url');
+ urlLabel.setAttribute('title', this.url);
+ urlLabel.textContent = this.url;
+ urlLabel.style.backgroundImage = url('chrome://favicon/' + this.url);
+ this.contentElement.appendChild(urlLabel);
+
+ // The stored username.
+ var usernameLabel = this.ownerDocument.createElement('div');
+ usernameLabel.className = 'name';
+ usernameLabel.textContent = this.username;
+ this.contentElement.appendChild(usernameLabel);
+
+ // The stored password.
+ var passwordInputDiv = this.ownerDocument.createElement('div');
+ passwordInputDiv.className = 'password';
+
+ // The password input field.
+ var passwordInput = this.ownerDocument.createElement('input');
+ passwordInput.type = 'password';
+ passwordInput.className = 'inactive-password';
+ passwordInput.readOnly = true;
+ passwordInput.value = showPasswords ? this.password : '********';
+ passwordInputDiv.appendChild(passwordInput);
+
+ // The show/hide button.
+ if (showPasswords) {
+ var button = this.ownerDocument.createElement('button');
+ button.hidden = true;
+ button.classList.add('password-button');
+ button.textContent = localStrings.getString('passwordShowButton');
+ button.addEventListener('click', this.onClick_, true);
+ passwordInputDiv.appendChild(button);
+ }
+
+ this.contentElement.appendChild(passwordInputDiv);
+ },
+
+ /** @inheritDoc */
+ selectionChanged: function() {
+ var passwordInput = this.querySelector('input[type=password]');
+ var textInput = this.querySelector('input[type=text]');
+ var input = passwordInput || textInput;
+ var button = input.nextSibling;
+ // |button| doesn't exist when passwords can't be shown.
+ if (!button)
+ return;
+ if (this.selected) {
+ input.classList.remove('inactive-password');
+ button.hidden = false;
+ } else {
+ input.classList.add('inactive-password');
+ button.hidden = true;
+ }
+ },
+
+ /**
+ * On-click event handler. Swaps the type of the input field from password
+ * to text and back.
+ * @private
+ */
+ onClick_: function(event) {
+ // The password is the input element previous to the button.
+ var button = event.currentTarget;
+ var passwordInput = button.previousSibling;
+ if (passwordInput.type == 'password') {
+ passwordInput.type = 'text';
+ button.textContent = localStrings.getString('passwordHideButton');
+ } else {
+ passwordInput.type = 'password';
+ button.textContent = localStrings.getString('passwordShowButton');
+ }
+ },
+
+ /**
+ * Get and set the URL for the entry.
+ * @type {string}
+ */
+ get url() {
+ return this.dataItem[0];
+ },
+ set url(url) {
+ this.dataItem[0] = url;
+ },
+
+ /**
+ * Get and set the username for the entry.
+ * @type {string}
+ */
+ get username() {
+ return this.dataItem[1];
+ },
+ set username(username) {
+ this.dataItem[1] = username;
+ },
+
+ /**
+ * Get and set the password for the entry.
+ * @type {string}
+ */
+ get password() {
+ return this.dataItem[2];
+ },
+ set password(password) {
+ this.dataItem[2] = password;
+ },
+ };
+
+ /**
+ * Creates a new PasswordExceptions list item.
+ * @param {Array} entry A pair of the form [url, username].
+ * @constructor
+ * @extends {Deletable.ListItem}
+ */
+ function PasswordExceptionsListItem(entry) {
+ var el = cr.doc.createElement('div');
+ el.dataItem = entry;
+ el.__proto__ = PasswordExceptionsListItem.prototype;
+ el.decorate();
+
+ return el;
+ }
+
+ PasswordExceptionsListItem.prototype = {
+ __proto__: DeletableItem.prototype,
+
+ /**
+ * Call when an element is decorated as a list item.
+ */
+ decorate: function() {
+ DeletableItem.prototype.decorate.call(this);
+
+ // The URL of the site.
+ var urlLabel = this.ownerDocument.createElement('div');
+ urlLabel.className = 'url';
+ urlLabel.classList.add('favicon-cell');
+ urlLabel.classList.add('weakrtl');
+ urlLabel.textContent = this.url;
+ urlLabel.style.backgroundImage = url('chrome://favicon/' + this.url);
+ this.contentElement.appendChild(urlLabel);
+ },
+
+ /**
+ * Get the url for the entry.
+ * @type {string}
+ */
+ get url() {
+ return this.dataItem;
+ },
+ set url(url) {
+ this.dataItem = url;
+ },
+ };
+
+ /**
+ * Create a new passwords list.
+ * @constructor
+ * @extends {cr.ui.List}
+ */
+ var PasswordsList = cr.ui.define('list');
+
+ PasswordsList.prototype = {
+ __proto__: DeletableItemList.prototype,
+
+ /**
+ * Whether passwords can be revealed or not.
+ * @type {boolean}
+ * @private
+ */
+ showPasswords_: true,
+
+ /** @inheritDoc */
+ decorate: function() {
+ DeletableItemList.prototype.decorate.call(this);
+ Preferences.getInstance().addEventListener(
+ "profile.password_manager_allow_show_passwords",
+ this.onPreferenceChanged_.bind(this));
+ },
+
+ /**
+ * Listener for changes on the preference.
+ * @param {Event} event The preference update event.
+ * @private
+ */
+ onPreferenceChanged_: function(event) {
+ this.showPasswords_ = event.value.value;
+ this.redraw();
+ },
+
+ /** @inheritDoc */
+ createItem: function(entry) {
+ return new PasswordListItem(entry, this.showPasswords_);
+ },
+
+ /** @inheritDoc */
+ deleteItemAtIndex: function(index) {
+ var item = this.dataModel.item(index);
+ if (item && item.length > 3) {
+ // The fourth element, if present, is the original index to delete.
+ index = item[3];
+ }
+ PasswordManager.removeSavedPassword(index);
+ },
+
+ /**
+ * The length of the list.
+ */
+ get length() {
+ return this.dataModel.length;
+ },
+ };
+
+ /**
+ * Create a new passwords list.
+ * @constructor
+ * @extends {cr.ui.List}
+ */
+ var PasswordExceptionsList = cr.ui.define('list');
+
+ PasswordExceptionsList.prototype = {
+ __proto__: DeletableItemList.prototype,
+
+ /** @inheritDoc */
+ createItem: function(entry) {
+ return new PasswordExceptionsListItem(entry);
+ },
+
+ /** @inheritDoc */
+ deleteItemAtIndex: function(index) {
+ PasswordManager.removePasswordException(index);
+ },
+
+ /**
+ * The length of the list.
+ */
+ get length() {
+ return this.dataModel.length;
+ },
+ };
+
+ return {
+ PasswordListItem: PasswordListItem,
+ PasswordExceptionsListItem: PasswordExceptionsListItem,
+ PasswordsList: PasswordsList,
+ PasswordExceptionsList: PasswordExceptionsList,
+ };
+});
diff --git a/chrome/browser/resources/options2/personal_options.css b/chrome/browser/resources/options2/personal_options.css
new file mode 100644
index 0000000..ee18893
--- /dev/null
+++ b/chrome/browser/resources/options2/personal_options.css
@@ -0,0 +1,66 @@
+#account-picture-wrapper {
+ border-radius: 4px;
+ border: 1px solid rgba(0, 0, 0, 0.3);
+ display: inline-block;
+ margin: 5px 10px 5px 2px;
+ padding: 3px;
+}
+
+#account-picture {
+ width: 70px;
+ height: 70px;
+ vertical-align: middle;
+}
+
+#sync-buttons, #profiles-buttons {
+ margin-top: 10px;
+}
+
+#start-stop-sync {
+ margin-left: 0;
+ margin-right: 5px;
+}
+
+#profiles-list {
+ min-height: 0;
+ margin-bottom: 10px;
+}
+
+#profiles-list > * {
+ height: 40px;
+}
+
+.profile-img {
+ height: 31px;
+ padding: 3px;
+ vertical-align: middle;
+ width: 38px;
+}
+
+.profile-item-current {
+ font-weight: bold;
+}
+
+#themes-gallery-div {
+ margin: 10px 0;
+}
+
+.sync-error {
+ background: #FFDBDB;
+ border: 1px solid #ce4c4c;
+ border-radius: 2px;
+ padding: 10px;
+}
+
+.sync-error .link-button {
+ margin: 0 1ex;
+ padding: 0;
+}
+
+#enable-auto-login-checkbox {
+ margin-top: 10px;
+}
+
+#mac-passwords-warning {
+ margin-top: 10px;
+}
diff --git a/chrome/browser/resources/options2/personal_options.html b/chrome/browser/resources/options2/personal_options.html
new file mode 100644
index 0000000..620ec06
--- /dev/null
+++ b/chrome/browser/resources/options2/personal_options.html
@@ -0,0 +1,162 @@
+<div id="personal-page" class="page" hidden>
+ <h1 i18n-content="personalPage"></h1>
+ <div class="displaytable">
+<if expr="pp_ifdef('chromeos')">
+ <section>
+ <h3 i18n-content="account"></h3>
+ <div>
+ <span id="account-picture-wrapper">
+ <img id="account-picture"
+ src="chrome://theme/IDR_PROFILE_PICTURE_LOADING">
+ </span>
+ <label>
+ <input id="enable-screen-lock" type="checkbox"
+ pref="settings.enable_screen_lock">
+ <span i18n-content="enableScreenlock"></span>
+ </label>
+ <br>
+ <button id="change-picture-button" i18n-content="changePicture">
+ </button>
+ </div>
+ </section>
+</if>
+ <section id="sync-section">
+ <h3 i18n-content="syncSection"></h3>
+ <div>
+ <div id="sync-overview" hidden>
+ <span i18n-content="syncOverview"></span>
+ <a i18n-values="href:syncLearnMoreURL" i18n-content="learnMore"></a>
+ </div>
+ <div id="sync-status" hidden>
+ <span id="sync-status-text"></span>
+ <button id="sync-action-link" class="link-button"></button>
+ </div>
+ <div id="sync-buttons">
+ <button id="start-stop-sync" hidden></button>
+ <button id="customize-sync" i18n-content="customizeSync" hidden>
+ </button>
+ <div id="enable-auto-login-checkbox" class="checkbox" hidden>
+ <label>
+ <input id="enable-auto-login" pref="autologin.enabled"
+ metric="Options_Autologin" type="checkbox">
+ <span i18n-content="autologinEnabled"></span>
+ </label>
+ </div>
+ </div>
+ </div>
+ </section>
+ <section id="profiles-section" hidden>
+ <h3 i18n-content="profiles"></h3>
+ <div>
+ <list id="profiles-list" class="settings-list" hidden></list>
+ <div id="profiles-single-message" i18n-content="profilesSingleUser">
+ </div>
+ <div id="profiles-buttons">
+ <button id="profiles-create" i18n-content="profilesCreate"></button>
+ <button id="profiles-manage" i18n-content="profilesManage" disabled>
+ </button>
+ <button id="profiles-delete" i18n-content="profilesDelete">
+ </button>
+ </div>
+ </div>
+ </section>
+ <section>
+ <h3 i18n-content="passwords"></h3>
+ <div>
+ <div class="radio">
+ <label>
+ <input id="passwords-offersave" type="radio" name="passwords_radio"
+ value="true" pref="profile.password_manager_enabled"
+ metric="Options_PasswordManager">
+ <span i18n-content="passwordsAskToSave"></span>
+ </label>
+ </div>
+ <div class="radio">
+ <label>
+ <input id="passwords-neversave" type="radio" name="passwords_radio"
+ value="false" pref="profile.password_manager_enabled"
+ metric="Options_PasswordManager">
+ <span i18n-content="passwordsNeverSave"></span>
+ </label>
+ </div>
+ <div><button id="manage-passwords" i18n-content="manage_passwords"
+ pref="profile.password_manager_enabled"></button></div>
+ <div id="mac-passwords-warning" i18n-content="macPasswordsWarning"
+ hidden></div>
+ <!-- This makes the managed-banner appear when the "pref" is
+ configured by the IT administrator. -->
+ <input name="password_allow_show_hidden" type="text"
+ pref="profile.password_manager_allow_show_passwords" hidden>
+ </div>
+ </section>
+ <section id="autofill-section">
+ <h3 i18n-content="autofill"></h3>
+ <div>
+ <div class="checkbox">
+ <label>
+ <input id="autofill-enabled" pref="autofill.enabled"
+ metric="Options_FormAutofill" type="checkbox">
+ <span i18n-content="autofillEnabled"></span>
+ </label>
+ </div>
+ <button id="autofill-settings" pref="autofill.enabled"
+ i18n-content="manageAutofillSettings"></button>
+ </div>
+ </section>
+<if expr="not pp_ifdef('chromeos')">
+ <section>
+ <h3 i18n-content="browsingData"></h3>
+ <div>
+ <button id="import-data" i18n-content="importData"></button>
+ </div>
+ </section>
+</if>
+<if expr="not pp_ifdef('toolkit_views') and is_posix and not is_macosx">
+ <section>
+ <h3 i18n-content="appearance"></h3>
+ <div>
+ <div>
+ <button id="themes-GTK-button"
+ i18n-content="themesGTKButton"></button>
+ <button id="themes-reset"
+ i18n-content="themesSetClassic"></button>
+ </div>
+ <div id="themes-gallery-div">
+ <a id="themes-gallery" i18n-content="themesGallery"
+ i18n-values="href:themesGalleryURL" target="_blank"></a>
+ </div>
+ <div class="radio">
+ <label>
+ <input name="decorations_radio"
+ pref="browser.custom_chrome_frame"
+ type="radio" value="false" metric="Options_CustomFrame">
+ <span i18n-content="showWindowDecorations"></span>
+ </label>
+ </div>
+ <div class="radio">
+ <label>
+ <input name="decorations_radio"
+ pref="browser.custom_chrome_frame"
+ type="radio" value="true" metric="Options_CustomFrame">
+ <span i18n-content="hideWindowDecorations"></span>
+ </label>
+ </div>
+ </div>
+ </section>
+</if>
+<if expr="pp_ifdef('toolkit_views') or os == 'win32' or os == 'darwin'">
+ <section>
+ <h3 i18n-content="themes"></h3>
+ <div>
+ <div>
+ <button id="themes-reset" i18n-content="themesReset"></button>
+ </div>
+ <div id="themes-gallery-div">
+ <a id="themes-gallery" i18n-content="themesGallery"
+ i18n-values="href:themesGalleryURL" target="_blank"></a>
+ </div>
+ </div>
+ </section>
+</if>
+ </div>
+</div>
diff --git a/chrome/browser/resources/options2/personal_options.js b/chrome/browser/resources/options2/personal_options.js
new file mode 100644
index 0000000..78d40dc
--- /dev/null
+++ b/chrome/browser/resources/options2/personal_options.js
@@ -0,0 +1,372 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+
+ var OptionsPage = options.OptionsPage;
+ var ArrayDataModel = cr.ui.ArrayDataModel;
+
+ /**
+ * Encapsulated handling of personal options page.
+ * @constructor
+ */
+ function PersonalOptions() {
+ OptionsPage.call(this, 'personal',
+ templateData.personalPageTabTitle,
+ 'personal-page');
+ if (cr.isChromeOS) {
+ // Username (canonical email) of the currently logged in user or
+ // |kGuestUser| if a guest session is active.
+ this.username_ = localStrings.getString('username');
+ }
+ }
+
+ cr.addSingletonGetter(PersonalOptions);
+
+ PersonalOptions.prototype = {
+ // Inherit PersonalOptions from OptionsPage.
+ __proto__: options.OptionsPage.prototype,
+
+ // State variables.
+ syncEnabled: false,
+ syncSetupCompleted: false,
+
+ // Initialize PersonalOptions page.
+ initializePage: function() {
+ // Call base class implementation to start preference initialization.
+ OptionsPage.prototype.initializePage.call(this);
+
+ var self = this;
+
+ // Sync.
+ $('sync-action-link').onclick = function(event) {
+ SyncSetupOverlay.showErrorUI();
+ };
+ $('start-stop-sync').onclick = function(event) {
+ if (self.syncSetupCompleted)
+ SyncSetupOverlay.showStopSyncingUI();
+ else
+ SyncSetupOverlay.showSetupUI();
+ };
+ $('customize-sync').onclick = function(event) {
+ SyncSetupOverlay.showSetupUI();
+ };
+
+ // Profiles.
+ var profilesList = $('profiles-list');
+ options.personal_options.ProfileList.decorate(profilesList);
+ profilesList.autoExpands = true;
+
+ profilesList.onchange = self.setProfileViewButtonsStatus_;
+ $('profiles-create').onclick = function(event) {
+ chrome.send('createProfile');
+ };
+ $('profiles-manage').onclick = function(event) {
+ var selectedProfile = self.getSelectedProfileItem_();
+ if (selectedProfile)
+ ManageProfileOverlay.showManageDialog(selectedProfile);
+ };
+ $('profiles-delete').onclick = function(event) {
+ var selectedProfile = self.getSelectedProfileItem_();
+ if (selectedProfile)
+ ManageProfileOverlay.showDeleteDialog(selectedProfile);
+ };
+
+ // Passwords.
+ $('manage-passwords').onclick = function(event) {
+ OptionsPage.navigateToPage('passwords');
+ OptionsPage.showTab($('passwords-nav-tab'));
+ chrome.send('coreOptionsUserMetricsAction',
+ ['Options_ShowPasswordManager']);
+ };
+
+ // Autofill.
+ $('autofill-settings').onclick = function(event) {
+ OptionsPage.navigateToPage('autofill');
+ chrome.send('coreOptionsUserMetricsAction',
+ ['Options_ShowAutofillSettings']);
+ };
+ if (cr.isChromeOS && cr.commandLine && cr.commandLine.options['--bwsi']) {
+ // Hide Autofill options for the guest user.
+ $('autofill-section').hidden = true;
+ }
+
+ // Appearance.
+ $('themes-reset').onclick = function(event) {
+ chrome.send('themesReset');
+ };
+
+ if (!cr.isChromeOS) {
+ $('import-data').onclick = function(event) {
+ // Make sure that any previous import success message is hidden, and
+ // we're showing the UI to import further data.
+ $('import-data-configure').hidden = false;
+ $('import-data-success').hidden = true;
+ OptionsPage.navigateToPage('importData');
+ chrome.send('coreOptionsUserMetricsAction', ['Import_ShowDlg']);
+ };
+
+ if ($('themes-GTK-button')) {
+ $('themes-GTK-button').onclick = function(event) {
+ chrome.send('themesSetGTK');
+ };
+ }
+ } else {
+ $('change-picture-button').onclick = function(event) {
+ OptionsPage.navigateToPage('changePicture');
+ };
+ this.updateAccountPicture_();
+
+ if (cr.commandLine && cr.commandLine.options['--bwsi']) {
+ // Disable the screen lock checkbox and change-picture-button in
+ // guest mode.
+ $('enable-screen-lock').disabled = true;
+ $('change-picture-button').disabled = true;
+ }
+ }
+
+ if (PersonalOptions.disablePasswordManagement()) {
+ // Disable the Password Manager in guest mode.
+ $('passwords-offersave').disabled = true;
+ $('passwords-neversave').disabled = true;
+ $('passwords-offersave').value = false;
+ $('passwords-neversave').value = true;
+ $('manage-passwords').disabled = true;
+ }
+
+ $('mac-passwords-warning').hidden =
+ !(localStrings.getString('macPasswordsWarning'));
+
+ if (PersonalOptions.disableAutofillManagement()) {
+ $('autofill-settings').disabled = true;
+
+ // Disable and turn off autofill.
+ var autofillEnabled = $('autofill-enabled');
+ autofillEnabled.disabled = true;
+ autofillEnabled.checked = false;
+ cr.dispatchSimpleEvent(autofillEnabled, 'change');
+ }
+ },
+
+ setSyncEnabled_: function(enabled) {
+ this.syncEnabled = enabled;
+ },
+
+ setAutoLoginVisible_ : function(visible) {
+ $('enable-auto-login-checkbox').hidden = !visible;
+ },
+
+ setSyncSetupCompleted_: function(completed) {
+ this.syncSetupCompleted = completed;
+ $('customize-sync').hidden = !completed;
+ },
+
+ setSyncStatus_: function(status) {
+ var statusSet = status != '';
+ $('sync-overview').hidden = statusSet;
+ $('sync-status').hidden = !statusSet;
+ $('sync-status-text').innerHTML = status;
+ },
+
+ setSyncStatusErrorVisible_: function(visible) {
+ visible ? $('sync-status').classList.add('sync-error') :
+ $('sync-status').classList.remove('sync-error');
+ },
+
+ setCustomizeSyncButtonEnabled_: function(enabled) {
+ $('customize-sync').disabled = !enabled;
+ },
+
+ setSyncActionLinkEnabled_: function(enabled) {
+ $('sync-action-link').disabled = !enabled;
+ },
+
+ setSyncActionLinkLabel_: function(status) {
+ $('sync-action-link').textContent = status;
+
+ // link-button does is not zero-area when the contents of the button are
+ // empty, so explicitly hide the element.
+ $('sync-action-link').hidden = !status.length;
+ },
+
+ /**
+ * Display or hide the profiles section of the page. This is used for
+ * multi-profile settings.
+ * @param {boolean} visible True to show the section.
+ * @private
+ */
+ setProfilesSectionVisible_: function(visible) {
+ $('profiles-section').hidden = !visible;
+ },
+
+ /**
+ * Get the selected profile item from the profile list. This also works
+ * correctly if the list is not displayed.
+ * @return {Object} the profile item object, or null if nothing is selected.
+ * @private
+ */
+ getSelectedProfileItem_: function() {
+ var profilesList = $('profiles-list');
+ if (profilesList.hidden) {
+ if (profilesList.dataModel.length > 0)
+ return profilesList.dataModel.item(0);
+ } else {
+ return profilesList.selectedItem;
+ }
+ return null;
+ },
+
+ /**
+ * Helper function to set the status of profile view buttons to disabled or
+ * enabled, depending on the number of profiles and selection status of the
+ * profiles list.
+ */
+ setProfileViewButtonsStatus_: function() {
+ var profilesList = $('profiles-list');
+ var selectedProfile = profilesList.selectedItem;
+ var hasSelection = selectedProfile != null;
+ var hasSingleProfile = profilesList.dataModel.length == 1;
+ $('profiles-manage').disabled = !hasSelection ||
+ !selectedProfile.isCurrentProfile;
+ $('profiles-delete').disabled = !hasSelection && !hasSingleProfile;
+ },
+
+ /**
+ * Display the correct dialog layout, depending on how many profiles are
+ * available.
+ * @param {number} numProfiles The number of profiles to display.
+ */
+ setProfileViewSingle_: function(numProfiles) {
+ var hasSingleProfile = numProfiles == 1;
+ $('profiles-list').hidden = hasSingleProfile;
+ $('profiles-single-message').hidden = !hasSingleProfile;
+ $('profiles-manage').hidden = hasSingleProfile;
+ $('profiles-delete').textContent = hasSingleProfile ?
+ templateData.profilesDeleteSingle :
+ templateData.profilesDelete;
+ },
+
+ /**
+ * Adds all |profiles| to the list.
+ * @param {Array.<Object>} An array of profile info objects.
+ * each object is of the form:
+ * profileInfo = {
+ * name: "Profile Name",
+ * iconURL: "chrome://path/to/icon/image",
+ * filePath: "/path/to/profile/data/on/disk",
+ * isCurrentProfile: false
+ * };
+ */
+ setProfilesInfo_: function(profiles) {
+ this.setProfileViewSingle_(profiles.length);
+ // add it to the list, even if the list is hidden so we can access it
+ // later.
+ $('profiles-list').dataModel = new ArrayDataModel(profiles);
+ this.setProfileViewButtonsStatus_();
+ },
+
+ setStartStopButtonVisible_: function(visible) {
+ $('start-stop-sync').hidden = !visible;
+ },
+
+ setStartStopButtonEnabled_: function(enabled) {
+ $('start-stop-sync').disabled = !enabled;
+ },
+
+ setStartStopButtonLabel_: function(label) {
+ $('start-stop-sync').textContent = label;
+ },
+
+ setGtkThemeButtonEnabled_: function(enabled) {
+ if (!cr.isChromeOS && navigator.platform.match(/linux|BSD/i)) {
+ $('themes-GTK-button').disabled = !enabled;
+ }
+ },
+
+ setThemesResetButtonEnabled_: function(enabled) {
+ $('themes-reset').disabled = !enabled;
+ },
+
+ hideSyncSection_: function() {
+ $('sync-section').hidden = true;
+ },
+
+ /**
+ * Get the start/stop sync button DOM element.
+ * @return {DOMElement} The start/stop sync button.
+ * @private
+ */
+ getStartStopSyncButton_: function() {
+ return $('start-stop-sync');
+ },
+
+ /**
+ * (Re)loads IMG element with current user account picture.
+ */
+ updateAccountPicture_: function() {
+ $('account-picture').src =
+ 'chrome://userimage/' + this.username_ +
+ '?id=' + (new Date()).getTime();
+ },
+ };
+
+ /**
+ * Returns whether the user should be able to manage (view and edit) their
+ * stored passwords. Password management is disabled in guest mode.
+ * @return {boolean} True if password management should be disabled.
+ */
+ PersonalOptions.disablePasswordManagement = function() {
+ return cr.commandLine && cr.commandLine.options['--bwsi'];
+ };
+
+ /**
+ * Returns whether the user should be able to manage autofill settings.
+ * @return {boolean} True if password management should be disabled.
+ */
+ PersonalOptions.disableAutofillManagement = function() {
+ return cr.commandLine && cr.commandLine.options['--bwsi'];
+ };
+
+ if (cr.isChromeOS) {
+ /**
+ * Returns username (canonical email) of the user logged in (ChromeOS only).
+ * @return {string} user email.
+ */
+ PersonalOptions.getLoggedInUsername = function() {
+ return PersonalOptions.getInstance().username_;
+ };
+ }
+
+ // Forward public APIs to private implementations.
+ [
+ 'getStartStopSyncButton',
+ 'hideSyncSection',
+ 'setAutoLoginVisible',
+ 'setCustomizeSyncButtonEnabled',
+ 'setGtkThemeButtonEnabled',
+ 'setProfilesInfo',
+ 'setProfilesSectionVisible',
+ 'setStartStopButtonEnabled',
+ 'setStartStopButtonLabel',
+ 'setStartStopButtonVisible',
+ 'setSyncActionLinkEnabled',
+ 'setSyncActionLinkLabel',
+ 'setSyncEnabled',
+ 'setSyncSetupCompleted',
+ 'setSyncStatus',
+ 'setSyncStatusErrorVisible',
+ 'setThemesResetButtonEnabled',
+ 'updateAccountPicture',
+ ].forEach(function(name) {
+ PersonalOptions[name] = function(value) {
+ return PersonalOptions.getInstance()[name + '_'](value);
+ };
+ });
+
+ // Export
+ return {
+ PersonalOptions: PersonalOptions
+ };
+
+});
diff --git a/chrome/browser/resources/options2/personal_options_profile_list.js b/chrome/browser/resources/options2/personal_options_profile_list.js
new file mode 100644
index 0000000..64436a3
--- /dev/null
+++ b/chrome/browser/resources/options2/personal_options_profile_list.js
@@ -0,0 +1,105 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options.personal_options', function() {
+ const DeletableItem = options.DeletableItem;
+ const DeletableItemList = options.DeletableItemList;
+ const ListSingleSelectionModel = cr.ui.ListSingleSelectionModel;
+
+ var localStrings = new LocalStrings();
+
+ /**
+ * Creates a new profile list item.
+ * @param {Object} profileInfo The profile this item respresents.
+ * @constructor
+ * @extends {cr.ui.DeletableItem}
+ */
+ function ProfileListItem(profileInfo) {
+ var el = cr.doc.createElement('div');
+ el.profileInfo_ = profileInfo;
+ ProfileListItem.decorate(el);
+ return el;
+ }
+
+ /**
+ * Decorates an element as a profile list item.
+ * @param {!HTMLElement} el The element to decorate.
+ */
+ ProfileListItem.decorate = function(el) {
+ el.__proto__ = ProfileListItem.prototype;
+ el.decorate();
+ };
+
+ ProfileListItem.prototype = {
+ __proto__: DeletableItem.prototype,
+
+ /**
+ * Get the filepath for this profile list item.
+ * @return the file path of this item.
+ */
+ get profilePath() {
+ return this.profileInfo_.filePath;
+ },
+
+ /** @inheritDoc */
+ decorate: function() {
+ DeletableItem.prototype.decorate.call(this);
+
+ var profileInfo = this.profileInfo_;
+
+ var iconEl = this.ownerDocument.createElement('img');
+ iconEl.className = 'profile-img';
+ iconEl.src = profileInfo.iconURL;
+ this.contentElement.appendChild(iconEl);
+
+ var nameEl = this.ownerDocument.createElement('div');
+ if (profileInfo.isCurrentProfile)
+ nameEl.classList.add('profile-item-current');
+ this.contentElement.appendChild(nameEl);
+
+ var displayName = profileInfo.name;
+ if (profileInfo.isCurrentProfile)
+ displayName = localStrings.getStringF(
+ 'profilesListItemCurrent',
+ profileInfo.name)
+ nameEl.textContent = displayName;
+ },
+ };
+
+ var ProfileList = cr.ui.define('list');
+
+ ProfileList.prototype = {
+ __proto__: DeletableItemList.prototype,
+
+ /** @inheritDoc */
+ decorate: function() {
+ DeletableItemList.prototype.decorate.call(this);
+ this.selectionModel = new ListSingleSelectionModel();
+ },
+
+ /** @inheritDoc */
+ createItem: function(pageInfo) {
+ var item = new ProfileListItem(pageInfo);
+ return item;
+ },
+
+ /** @inheritDoc */
+ deleteItemAtIndex: function(index) {
+ ManageProfileOverlay.showDeleteDialog(this.dataModel.item(index));
+ },
+
+ /** @inheritDoc */
+ activateItemAtIndex: function(index) {
+ // Don't allow the user to edit a profile that is not current.
+ var profileInfo = this.dataModel.item(index);
+ if (profileInfo.isCurrentProfile)
+ ManageProfileOverlay.showManageDialog(profileInfo);
+ },
+ };
+
+ return {
+ ProfileList: ProfileList
+ };
+});
+
diff --git a/chrome/browser/resources/options2/pref_ui.js b/chrome/browser/resources/options2/pref_ui.js
new file mode 100644
index 0000000..0468f04
--- /dev/null
+++ b/chrome/browser/resources/options2/pref_ui.js
@@ -0,0 +1,723 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+
+ var Preferences = options.Preferences;
+
+ /**
+ * Allows an element to be disabled for several reasons.
+ * The element is disabled if at least one reason is true, and the reasons
+ * can be set separately.
+ * @private
+ * @param {!HTMLElement} el The element to update.
+ * @param {string} reason The reason for disabling the element.
+ * @param {boolean} disabled Whether the element should be disabled or enabled
+ * for the given |reason|.
+ */
+ function updateDisabledState_(el, reason, disabled) {
+ if (!el.disabledReasons)
+ el.disabledReasons = {};
+ if (el.disabled && (Object.keys(el.disabledReasons).length == 0)) {
+ // The element has been previously disabled without a reason, so we add
+ // one to keep it disabled.
+ el.disabledReasons['other'] = true;
+ }
+ if (!el.disabled) {
+ // If the element is not disabled, there should be no reason, except for
+ // 'other'.
+ delete el.disabledReasons['other'];
+ if (Object.keys(el.disabledReasons).length > 0)
+ console.error("Element is not disabled but should be");
+ }
+ if (disabled) {
+ el.disabledReasons[reason] = true;
+ } else {
+ delete el.disabledReasons[reason];
+ }
+ el.disabled = Object.keys(el.disabledReasons).length > 0;
+ }
+
+ /**
+ * Helper function to update element's state from pref change event.
+ * @private
+ * @param {!HTMLElement} el The element to update.
+ * @param {!Event} event The pref change event.
+ */
+ function updateElementState_(el, event) {
+ el.controlledBy = null;
+
+ if (!event.value)
+ return;
+
+ updateDisabledState_(el, 'notUserModifiable', event.value.disabled);
+
+ el.controlledBy = event.value['controlledBy'];
+
+ OptionsPage.updateManagedBannerVisibility();
+ }
+
+ /////////////////////////////////////////////////////////////////////////////
+ // PrefCheckbox class:
+ // TODO(jhawkins): Refactor all this copy-pasted code!
+
+ // Define a constructor that uses an input element as its underlying element.
+ var PrefCheckbox = cr.ui.define('input');
+
+ PrefCheckbox.prototype = {
+ // Set up the prototype chain
+ __proto__: HTMLInputElement.prototype,
+
+ /**
+ * Initialization function for the cr.ui framework.
+ */
+ decorate: function() {
+ this.type = 'checkbox';
+ var self = this;
+
+ self.initializeValueType(self.getAttribute('value-type'));
+
+ // Listen to pref changes.
+ Preferences.getInstance().addEventListener(
+ this.pref,
+ function(event) {
+ var value = event.value && event.value['value'] != undefined ?
+ event.value['value'] : event.value;
+
+ // Invert pref value if inverted_pref == true.
+ if (self.inverted_pref)
+ self.checked = !Boolean(value);
+ else
+ self.checked = Boolean(value);
+
+ updateElementState_(self, event);
+ });
+
+ // Listen to user events.
+ this.addEventListener(
+ 'change',
+ function(e) {
+ if (self.customChangeHandler(e))
+ return;
+ var value = self.inverted_pref ? !self.checked : self.checked;
+ switch(self.valueType) {
+ case 'number':
+ Preferences.setIntegerPref(self.pref,
+ Number(value), self.metric);
+ break;
+ case 'boolean':
+ Preferences.setBooleanPref(self.pref,
+ value, self.metric);
+ break;
+ }
+ });
+ },
+
+ /**
+ * Sets up options in checkbox element.
+ * @param {String} valueType The preference type for this checkbox.
+ */
+ initializeValueType: function(valueType) {
+ this.valueType = valueType || 'boolean';
+ },
+
+ /**
+ * See |updateDisabledState_| above.
+ */
+ setDisabled: function(reason, disabled) {
+ updateDisabledState_(this, reason, disabled);
+ },
+
+ /**
+ * This method is called first while processing an onchange event. If it
+ * returns false, regular onchange processing continues (setting the
+ * associated pref, etc). If it returns true, the rest of the onchange is
+ * not performed. I.e., this works like stopPropagation or cancelBubble.
+ * @param {Event} event Change event.
+ */
+ customChangeHandler: function(event) {
+ return false;
+ },
+ };
+
+ /**
+ * The preference name.
+ * @type {string}
+ */
+ cr.defineProperty(PrefCheckbox, 'pref', cr.PropertyKind.ATTR);
+
+ /**
+ * Whether the preference is controlled by something else than the user's
+ * settings (either 'policy' or 'extension').
+ * @type {string}
+ */
+ cr.defineProperty(PrefCheckbox, 'controlledBy', cr.PropertyKind.ATTR);
+
+ /**
+ * The user metric string.
+ * @type {string}
+ */
+ cr.defineProperty(PrefCheckbox, 'metric', cr.PropertyKind.ATTR);
+
+ /**
+ * Whether to use inverted pref value.
+ * @type {boolean}
+ */
+ cr.defineProperty(PrefCheckbox, 'inverted_pref', cr.PropertyKind.BOOL_ATTR);
+
+ /////////////////////////////////////////////////////////////////////////////
+ // PrefRadio class:
+
+ //Define a constructor that uses an input element as its underlying element.
+ var PrefRadio = cr.ui.define('input');
+
+ PrefRadio.prototype = {
+ // Set up the prototype chain
+ __proto__: HTMLInputElement.prototype,
+
+ /**
+ * Initialization function for the cr.ui framework.
+ */
+ decorate: function() {
+ this.type = 'radio';
+ var self = this;
+
+ // Listen to pref changes.
+ Preferences.getInstance().addEventListener(this.pref,
+ function(event) {
+ var value = event.value && event.value['value'] != undefined ?
+ event.value['value'] : event.value;
+ self.checked = String(value) == self.value;
+
+ updateElementState_(self, event);
+ });
+
+ // Listen to user events.
+ this.addEventListener('change',
+ function(e) {
+ if(self.value == 'true' || self.value == 'false') {
+ Preferences.setBooleanPref(self.pref,
+ self.value == 'true', self.metric);
+ } else {
+ Preferences.setIntegerPref(self.pref,
+ parseInt(self.value, 10), self.metric);
+ }
+ });
+ },
+
+ /**
+ * See |updateDisabledState_| above.
+ */
+ setDisabled: function(reason, disabled) {
+ updateDisabledState_(this, reason, disabled);
+ },
+ };
+
+ /**
+ * The preference name.
+ * @type {string}
+ */
+ cr.defineProperty(PrefRadio, 'pref', cr.PropertyKind.ATTR);
+
+ /**
+ * Whether the preference is controlled by something else than the user's
+ * settings (either 'policy' or 'extension').
+ * @type {string}
+ */
+ cr.defineProperty(PrefRadio, 'controlledBy', cr.PropertyKind.ATTR);
+
+ /**
+ * The user metric string.
+ * @type {string}
+ */
+ cr.defineProperty(PrefRadio, 'metric', cr.PropertyKind.ATTR);
+
+ /////////////////////////////////////////////////////////////////////////////
+ // PrefNumeric class:
+
+ // Define a constructor that uses an input element as its underlying element.
+ var PrefNumeric = function() {};
+ PrefNumeric.prototype = {
+ // Set up the prototype chain
+ __proto__: HTMLInputElement.prototype,
+
+ /**
+ * Initialization function for the cr.ui framework.
+ */
+ decorate: function() {
+ var self = this;
+
+ // Listen to pref changes.
+ Preferences.getInstance().addEventListener(this.pref,
+ function(event) {
+ self.value = event.value && event.value['value'] != undefined ?
+ event.value['value'] : event.value;
+
+ updateElementState_(self, event);
+ });
+
+ // Listen to user events.
+ this.addEventListener('change',
+ function(e) {
+ if (this.validity.valid) {
+ Preferences.setIntegerPref(self.pref, self.value, self.metric);
+ }
+ });
+ },
+
+ /**
+ * See |updateDisabledState_| above.
+ */
+ setDisabled: function(reason, disabled) {
+ updateDisabledState_(this, reason, disabled);
+ },
+ };
+
+ /**
+ * The preference name.
+ * @type {string}
+ */
+ cr.defineProperty(PrefNumeric, 'pref', cr.PropertyKind.ATTR);
+
+ /**
+ * Whether the preference is controlled by something else than the user's
+ * settings (either 'policy' or 'extension').
+ * @type {string}
+ */
+ cr.defineProperty(PrefNumeric, 'controlledBy', cr.PropertyKind.ATTR);
+
+ /**
+ * The user metric string.
+ * @type {string}
+ */
+ cr.defineProperty(PrefNumeric, 'metric', cr.PropertyKind.ATTR);
+
+ /////////////////////////////////////////////////////////////////////////////
+ // PrefNumber class:
+
+ // Define a constructor that uses an input element as its underlying element.
+ var PrefNumber = cr.ui.define('input');
+
+ PrefNumber.prototype = {
+ // Set up the prototype chain
+ __proto__: PrefNumeric.prototype,
+
+ /**
+ * Initialization function for the cr.ui framework.
+ */
+ decorate: function() {
+ this.type = 'number';
+ PrefNumeric.prototype.decorate.call(this);
+
+ // Listen to user events.
+ this.addEventListener('input',
+ function(e) {
+ if (this.validity.valid) {
+ Preferences.setIntegerPref(self.pref, self.value, self.metric);
+ }
+ });
+ },
+
+ /**
+ * See |updateDisabledState_| above.
+ */
+ setDisabled: function(reason, disabled) {
+ updateDisabledState_(this, reason, disabled);
+ },
+ };
+
+ /////////////////////////////////////////////////////////////////////////////
+ // PrefRange class:
+
+ // Define a constructor that uses an input element as its underlying element.
+ var PrefRange = cr.ui.define('input');
+
+ PrefRange.prototype = {
+ // Set up the prototype chain
+ __proto__: HTMLInputElement.prototype,
+
+ /**
+ * The map from input range value to the corresponding preference value.
+ */
+ valueMap: undefined,
+
+ /**
+ * If true, the associated pref will be modified on each onchange event;
+ * otherwise, the pref will only be modified on the onmouseup event after
+ * the drag.
+ */
+ continuous: true,
+
+ /**
+ * Initialization function for the cr.ui framework.
+ */
+ decorate: function() {
+ this.type = 'range';
+
+ // Update the UI when the pref changes.
+ Preferences.getInstance().addEventListener(
+ this.pref, this.onPrefChange_.bind(this));
+
+ // Listen to user events.
+ // TODO(jhawkins): Add onmousewheel handling once the associated WK bug is
+ // fixed.
+ // https://bugs.webkit.org/show_bug.cgi?id=52256
+ this.onchange = this.onChange_.bind(this);
+ this.onkeyup = this.onmouseup = this.onInputUp_.bind(this);
+ },
+
+ /**
+ * Event listener that updates the UI when the underlying pref changes.
+ * @param {Event} event The event that details the pref change.
+ * @private
+ */
+ onPrefChange_: function(event) {
+ var value = event.value && event.value['value'] != undefined ?
+ event.value['value'] : event.value;
+ if (value != undefined)
+ this.value = this.valueMap ? this.valueMap.indexOf(value) : value;
+ },
+
+ /**
+ * onchange handler that sets the pref when the user changes the value of
+ * the input element.
+ * @private
+ */
+ onChange_: function(event) {
+ if (this.continuous)
+ this.setRangePref_();
+
+ if (this.notifyChange)
+ this.notifyChange(this, this.mapValueToRange_(this.value));
+ },
+
+ /**
+ * Sets the integer value of |pref| to the value of this element.
+ * @private
+ */
+ setRangePref_: function() {
+ Preferences.setIntegerPref(
+ this.pref, this.mapValueToRange_(this.value), this.metric);
+
+ if (this.notifyPrefChange)
+ this.notifyPrefChange(this, this.mapValueToRange_(this.value));
+ },
+
+ /**
+ * onkeyup/onmouseup handler that modifies the pref if |continuous| is
+ * false.
+ * @private
+ */
+ onInputUp_: function(event) {
+ if (!this.continuous)
+ this.setRangePref_();
+ },
+
+ /**
+ * Maps the value of this element into the range provided by the client,
+ * represented by |valueMap|.
+ * @param {number} value The value to map.
+ * @private
+ */
+ mapValueToRange_: function(value) {
+ return this.valueMap ? this.valueMap[value] : value;
+ },
+
+ /**
+ * Called when the client has specified non-continuous mode and the value of
+ * the range control changes.
+ * @param {Element} el This element.
+ * @param {number} value The value of this element.
+ */
+ notifyChange: function(el, value) {
+ },
+
+ /**
+ * See |updateDisabledState_| above.
+ */
+ setDisabled: function(reason, disabled) {
+ updateDisabledState_(this, reason, disabled);
+ },
+ };
+
+ /**
+ * The preference name.
+ * @type {string}
+ */
+ cr.defineProperty(PrefRange, 'pref', cr.PropertyKind.ATTR);
+
+ /**
+ * Whether the preference is controlled by something else than the user's
+ * settings (either 'policy' or 'extension').
+ * @type {string}
+ */
+ cr.defineProperty(PrefRange, 'controlledBy', cr.PropertyKind.ATTR);
+
+ /**
+ * The user metric string.
+ * @type {string}
+ */
+ cr.defineProperty(PrefRange, 'metric', cr.PropertyKind.ATTR);
+
+ /////////////////////////////////////////////////////////////////////////////
+ // PrefSelect class:
+
+ // Define a constructor that uses a select element as its underlying element.
+ var PrefSelect = cr.ui.define('select');
+
+ PrefSelect.prototype = {
+ // Set up the prototype chain
+ __proto__: HTMLSelectElement.prototype,
+
+ /**
+ * Initialization function for the cr.ui framework.
+ */
+ decorate: function() {
+ var self = this;
+
+ // Listen to pref changes.
+ Preferences.getInstance().addEventListener(this.pref,
+ function(event) {
+ var value = event.value && event.value['value'] != undefined ?
+ event.value['value'] : event.value;
+
+ // Make sure |value| is a string, because the value is stored as a
+ // string in the HTMLOptionElement.
+ value = value.toString();
+
+ updateElementState_(self, event);
+
+ var found = false;
+ for (var i = 0; i < self.options.length; i++) {
+ if (self.options[i].value == value) {
+ self.selectedIndex = i;
+ found = true;
+ }
+ }
+
+ // Item not found, select first item.
+ if (!found)
+ self.selectedIndex = 0;
+
+ if (self.onchange != undefined)
+ self.onchange(event);
+ });
+
+ // Listen to user events.
+ this.addEventListener('change',
+ function(e) {
+ if (!self.dataType) {
+ console.error('undefined data type for <select> pref');
+ return;
+ }
+
+ switch(self.dataType) {
+ case 'number':
+ Preferences.setIntegerPref(self.pref,
+ self.options[self.selectedIndex].value, self.metric);
+ break;
+ case 'double':
+ Preferences.setDoublePref(self.pref,
+ self.options[self.selectedIndex].value, self.metric);
+ break;
+ case 'boolean':
+ var option = self.options[self.selectedIndex];
+ var value = (option.value == 'true') ? true : false;
+ Preferences.setBooleanPref(self.pref, value, self.metric);
+ break;
+ case 'string':
+ Preferences.setStringPref(self.pref,
+ self.options[self.selectedIndex].value, self.metric);
+ break;
+ default:
+ console.error('unknown data type for <select> pref: ' +
+ self.dataType);
+ }
+ });
+ },
+
+ /**
+ * See |updateDisabledState_| above.
+ */
+ setDisabled: function(reason, disabled) {
+ updateDisabledState_(this, reason, disabled);
+ },
+ };
+
+ /**
+ * The preference name.
+ * @type {string}
+ */
+ cr.defineProperty(PrefSelect, 'pref', cr.PropertyKind.ATTR);
+
+ /**
+ * Whether the preference is controlled by something else than the user's
+ * settings (either 'policy' or 'extension').
+ * @type {string}
+ */
+ cr.defineProperty(PrefSelect, 'controlledBy', cr.PropertyKind.ATTR);
+
+ /**
+ * The user metric string.
+ * @type {string}
+ */
+ cr.defineProperty(PrefSelect, 'metric', cr.PropertyKind.ATTR);
+
+ /**
+ * The data type for the preference options.
+ * @type {string}
+ */
+ cr.defineProperty(PrefSelect, 'dataType', cr.PropertyKind.ATTR);
+
+ /////////////////////////////////////////////////////////////////////////////
+ // PrefTextField class:
+
+ // Define a constructor that uses an input element as its underlying element.
+ var PrefTextField = cr.ui.define('input');
+
+ PrefTextField.prototype = {
+ // Set up the prototype chain
+ __proto__: HTMLInputElement.prototype,
+
+ /**
+ * Initialization function for the cr.ui framework.
+ */
+ decorate: function() {
+ var self = this;
+
+ // Listen to pref changes.
+ Preferences.getInstance().addEventListener(this.pref,
+ function(event) {
+ self.value = event.value && event.value['value'] != undefined ?
+ event.value['value'] : event.value;
+
+ updateElementState_(self, event);
+ });
+
+ // Listen to user events.
+ this.addEventListener('change',
+ function(e) {
+ switch(self.dataType) {
+ case 'number':
+ Preferences.setIntegerPref(self.pref, self.value, self.metric);
+ break;
+ case 'double':
+ Preferences.setDoublePref(self.pref, self.value, self.metric);
+ break;
+ case 'url':
+ Preferences.setURLPref(self.pref, self.value, self.metric);
+ break;
+ default:
+ Preferences.setStringPref(self.pref, self.value, self.metric);
+ break;
+ }
+ });
+
+ window.addEventListener('unload',
+ function() {
+ if (document.activeElement == self)
+ self.blur();
+ });
+ },
+
+ /**
+ * See |updateDisabledState_| above.
+ */
+ setDisabled: function(reason, disabled) {
+ updateDisabledState_(this, reason, disabled);
+ },
+ };
+
+ /**
+ * The preference name.
+ * @type {string}
+ */
+ cr.defineProperty(PrefTextField, 'pref', cr.PropertyKind.ATTR);
+
+ /**
+ * Whether the preference is controlled by something else than the user's
+ * settings (either 'policy' or 'extension').
+ * @type {string}
+ */
+ cr.defineProperty(PrefTextField, 'controlledBy', cr.PropertyKind.ATTR);
+
+ /**
+ * The user metric string.
+ * @type {string}
+ */
+ cr.defineProperty(PrefTextField, 'metric', cr.PropertyKind.ATTR);
+
+ /**
+ * The data type for the preference options.
+ * @type {string}
+ */
+ cr.defineProperty(PrefTextField, 'dataType', cr.PropertyKind.ATTR);
+
+ /////////////////////////////////////////////////////////////////////////////
+ // PrefButton class:
+
+ // Define a constructor that uses a button element as its underlying element.
+ var PrefButton = cr.ui.define('button');
+
+ PrefButton.prototype = {
+ // Set up the prototype chain
+ __proto__: HTMLButtonElement.prototype,
+
+ /**
+ * Initialization function for the cr.ui framework.
+ */
+ decorate: function() {
+ var self = this;
+
+ // Listen to pref changes. This element behaves like a normal button and
+ // doesn't affect the underlying preference; it just becomes disabled
+ // when the preference is managed, and its value is false.
+ // This is useful for buttons that should be disabled when the underlying
+ // boolean preference is set to false by a policy or extension.
+ Preferences.getInstance().addEventListener(this.pref,
+ function(event) {
+ var e = {
+ value: {
+ 'disabled': event.value['disabled'] && !event.value['value'],
+ 'controlledBy': event.value['controlledBy']
+ }
+ };
+ updateElementState_(self, e);
+ });
+ },
+
+ /**
+ * See |updateDisabledState_| above.
+ */
+ setDisabled: function(reason, disabled) {
+ updateDisabledState_(this, reason, disabled);
+ },
+ };
+
+ /**
+ * The preference name.
+ * @type {string}
+ */
+ cr.defineProperty(PrefButton, 'pref', cr.PropertyKind.ATTR);
+
+ /**
+ * Whether the preference is controlled by something else than the user's
+ * settings (either 'policy' or 'extension').
+ * @type {string}
+ */
+ cr.defineProperty(PrefButton, 'controlledBy', cr.PropertyKind.ATTR);
+
+ // Export
+ return {
+ PrefCheckbox: PrefCheckbox,
+ PrefNumber: PrefNumber,
+ PrefNumeric: PrefNumeric,
+ PrefRadio: PrefRadio,
+ PrefRange: PrefRange,
+ PrefSelect: PrefSelect,
+ PrefTextField: PrefTextField,
+ PrefButton: PrefButton
+ };
+
+});
diff --git a/chrome/browser/resources/options2/preferences.js b/chrome/browser/resources/options2/preferences.js
new file mode 100644
index 0000000..807e45e
--- /dev/null
+++ b/chrome/browser/resources/options2/preferences.js
@@ -0,0 +1,185 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+
+ /////////////////////////////////////////////////////////////////////////////
+ // Preferences class:
+
+ /**
+ * Preferences class manages access to Chrome profile preferences.
+ * @constructor
+ */
+ function Preferences() {
+ }
+
+ cr.addSingletonGetter(Preferences);
+
+ /**
+ * Sets value of a boolean preference.
+ * and signals its changed value.
+ * @param {string} name Preference name.
+ * @param {boolean} value New preference value.
+ * @param {string} metric User metrics identifier.
+ */
+ Preferences.setBooleanPref = function(name, value, metric) {
+ var argumentList = [name, Boolean(value)];
+ if (metric != undefined) argumentList.push(metric);
+ chrome.send('setBooleanPref', argumentList);
+ };
+
+ /**
+ * Sets value of an integer preference.
+ * and signals its changed value.
+ * @param {string} name Preference name.
+ * @param {number} value New preference value.
+ * @param {string} metric User metrics identifier.
+ */
+ Preferences.setIntegerPref = function(name, value, metric) {
+ var argumentList = [name, Number(value)];
+ if (metric != undefined) argumentList.push(metric);
+ chrome.send('setIntegerPref', argumentList);
+ };
+
+ /**
+ * Sets value of a double-valued preference.
+ * and signals its changed value.
+ * @param {string} name Preference name.
+ * @param {number} value New preference value.
+ * @param {string} metric User metrics identifier.
+ */
+ Preferences.setDoublePref = function(name, value, metric) {
+ var argumentList = [name, Number(value)];
+ if (metric != undefined) argumentList.push(metric);
+ chrome.send('setDoublePref', argumentList);
+ };
+
+ /**
+ * Sets value of a string preference.
+ * and signals its changed value.
+ * @param {string} name Preference name.
+ * @param {string} value New preference value.
+ * @param {string} metric User metrics identifier.
+ */
+ Preferences.setStringPref = function(name, value, metric) {
+ var argumentList = [name, String(value)];
+ if (metric != undefined) argumentList.push(metric);
+ chrome.send('setStringPref', argumentList);
+ };
+
+ /**
+ * Sets value of a string preference that represents a URL
+ * and signals its changed value. The value will be fixed to be a valid URL.
+ * @param {string} name Preference name.
+ * @param {string} value New preference value.
+ * @param {string} metric User metrics identifier.
+ */
+ Preferences.setURLPref = function(name, value, metric) {
+ var argumentList = [name, String(value)];
+ if (metric != undefined) argumentList.push(metric);
+ chrome.send('setURLPref', argumentList);
+ };
+
+ /**
+ * Sets value of a JSON list preference.
+ * and signals its changed value.
+ * @param {string} name Preference name.
+ * @param {Array} value New preference value.
+ * @param {string} metric User metrics identifier.
+ */
+ Preferences.setListPref = function(name, value, metric) {
+ var argumentList = [name, JSON.stringify(value)];
+ if (metric != undefined) argumentList.push(metric);
+ chrome.send('setListPref', argumentList);
+ };
+
+ /**
+ * Clears value of a JSON preference.
+ * @param {string} name Preference name.
+ * @param {string} metric User metrics identifier.
+ */
+ Preferences.clearPref = function(name, metric) {
+ var argumentList = [name];
+ if (metric != undefined) argumentList.push(metric);
+ chrome.send('clearPref', argumentList);
+ };
+
+ Preferences.prototype = {
+ __proto__: cr.EventTarget.prototype,
+
+ // Map of registered preferences.
+ registeredPreferences_: {},
+
+ /**
+ * Adds an event listener to the target.
+ * @param {string} type The name of the event.
+ * @param {!Function|{handleEvent:Function}} handler The handler for the
+ * event. This is called when the event is dispatched.
+ */
+ addEventListener: function(type, handler) {
+ cr.EventTarget.prototype.addEventListener.call(this, type, handler);
+ this.registeredPreferences_[type] = true;
+ },
+
+ /**
+ * Initializes preference reading and change notifications.
+ */
+ initialize: function() {
+ var params1 = ['Preferences.prefsFetchedCallback'];
+ var params2 = ['Preferences.prefsChangedCallback'];
+ for (var prefName in this.registeredPreferences_) {
+ params1.push(prefName);
+ params2.push(prefName);
+ }
+ chrome.send('fetchPrefs', params1);
+ chrome.send('observePrefs', params2);
+ },
+
+ /**
+ * Helper function for flattening of dictionary passed via fetchPrefs
+ * callback.
+ * @param {string} prefix Preference name prefix.
+ * @param {object} dict Map with preference values.
+ */
+ flattenMapAndDispatchEvent_: function(prefix, dict) {
+ for (var prefName in dict) {
+ if (typeof dict[prefName] == 'object' &&
+ !this.registeredPreferences_[prefix + prefName]) {
+ this.flattenMapAndDispatchEvent_(prefix + prefName + '.',
+ dict[prefName]);
+ } else {
+ var event = new cr.Event(prefix + prefName);
+ event.value = dict[prefName];
+ this.dispatchEvent(event);
+ }
+ }
+ }
+ };
+
+ /**
+ * Callback for fetchPrefs method.
+ * @param {object} dict Map of fetched property values.
+ */
+ Preferences.prefsFetchedCallback = function(dict) {
+ Preferences.getInstance().flattenMapAndDispatchEvent_('', dict);
+ };
+
+ /**
+ * Callback for observePrefs method.
+ * @param {array} notification An array defining changed preference values.
+ * notification[0] contains name of the change preference while its new value
+ * is stored in notification[1].
+ */
+ Preferences.prefsChangedCallback = function(notification) {
+ var event = new cr.Event(notification[0]);
+ event.value = notification[1];
+ Preferences.getInstance().dispatchEvent(event);
+ };
+
+ // Export
+ return {
+ Preferences: Preferences
+ };
+
+});
diff --git a/chrome/browser/resources/options2/profiles_icon_grid.js b/chrome/browser/resources/options2/profiles_icon_grid.js
new file mode 100644
index 0000000..cdd9529
--- /dev/null
+++ b/chrome/browser/resources/options2/profiles_icon_grid.js
@@ -0,0 +1,68 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+ const ListItem = cr.ui.ListItem;
+ const Grid = cr.ui.Grid;
+ const ListSingleSelectionModel = cr.ui.ListSingleSelectionModel;
+
+ /**
+ * Creates a new profile icon grid item.
+ * @param {Object} iconURL The profile icon URL.
+ * @constructor
+ * @extends {cr.ui.GridItem}
+ */
+ function ProfilesIconGridItem(iconURL) {
+ var el = cr.doc.createElement('span');
+ el.iconURL_ = iconURL;
+ ProfilesIconGridItem.decorate(el);
+ return el;
+ }
+
+ /**
+ * Decorates an element as a profile grid item.
+ * @param {!HTMLElement} el The element to decorate.
+ */
+ ProfilesIconGridItem.decorate = function(el) {
+ el.__proto__ = ProfilesIconGridItem.prototype;
+ el.decorate();
+ };
+
+ ProfilesIconGridItem.prototype = {
+ __proto__: ListItem.prototype,
+
+ /** @inheritDoc */
+ decorate: function() {
+ ListItem.prototype.decorate.call(this);
+ var imageEl = cr.doc.createElement('img');
+ imageEl.className = 'profile-icon';
+ imageEl.src = this.iconURL_;
+ this.appendChild(imageEl);
+
+ this.className = 'profile-icon-grid-item';
+ },
+ };
+
+ var ProfilesIconGrid = cr.ui.define('grid');
+
+ ProfilesIconGrid.prototype = {
+ __proto__: Grid.prototype,
+
+ /** @inheritDoc */
+ decorate: function() {
+ Grid.prototype.decorate.call(this);
+ this.selectionModel = new ListSingleSelectionModel();
+ },
+
+ /** @inheritDoc */
+ createItem: function(iconURL) {
+ return new ProfilesIconGridItem(iconURL);
+ },
+ };
+
+ return {
+ ProfilesIconGrid: ProfilesIconGrid
+ };
+});
+
diff --git a/chrome/browser/resources/options2/search_engine_manager.css b/chrome/browser/resources/options2/search_engine_manager.css
new file mode 100644
index 0000000..50c2db2
--- /dev/null
+++ b/chrome/browser/resources/options2/search_engine_manager.css
@@ -0,0 +1,64 @@
+.search-engine-list > div {
+ display: -webkit-box;
+}
+
+.search-engine-list .favicon {
+ padding: 1px 7px 0px 7px;
+ height: 16px;
+}
+
+.search-engine-list .name-column {
+ -webkit-box-align: center;
+ -webkit-padding-end: 1ex;
+ box-sizing: border-box;
+ display: -webkit-box;
+ width: 37%;
+}
+
+.search-engine-list .name-column :last-child {
+ -webkit-box-flex: 1;
+}
+
+.search-engine-list .keyword-column {
+ -webkit-padding-end: 1ex;
+ box-sizing: border-box;
+ width: 26%;
+}
+
+.search-engine-list .url-column {
+ box-sizing: border-box;
+ width: 37%;
+}
+
+.search-engine-list .keyword-column,
+.search-engine-list .url-column {
+ color: #666666;
+}
+
+.search-engine-list .default .name-column,
+.search-engine-list .default .keyword-column {
+ font-weight: bold;
+}
+
+/* For temporary Make Default button */
+.search-engine-list .url-column {
+ display: -webkit-box;
+ -webkit-box-align: center;
+}
+
+.search-engine-list .url-column :first-child {
+ -webkit-box-flex: 1;
+}
+
+.search-engine-list .url-column button {
+ -webkit-margin-start: 3px;
+ background: #8aaaed;
+ color: #fff;
+ margin-top: 0;
+}
+
+.search-engine-list > :not(:hover):not([editing]) .url-column button {
+ display: none;
+}
+
+/* End temporary Make Default button styling */
diff --git a/chrome/browser/resources/options2/search_engine_manager.html b/chrome/browser/resources/options2/search_engine_manager.html
new file mode 100644
index 0000000..cc00a73
--- /dev/null
+++ b/chrome/browser/resources/options2/search_engine_manager.html
@@ -0,0 +1,19 @@
+<div id="search-engine-manager-page" class="page" hidden>
+ <h1 i18n-content="searchEngineManagerPage"></h1>
+ <h3 i18n-content="defaultSearchEngineListTitle"></h3>
+ <list id="default-search-engine-list"
+ class="search-engine-list settings-list"></list>
+ <h3 i18n-content="otherSearchEngineListTitle"></h3>
+ <list id="other-search-engine-list"
+ class="search-engine-list settings-list"></list>
+ <div id="extension-keyword-div" hidden>
+ <h3 id="extension-keyword-list-title"
+ i18n-content="extensionKeywordsListTitle"></h3>
+ <list id="extension-keyword-list"
+ class="search-engine-list settings-list"></list>
+ <div id="manage-extension-link">
+ <a href="chrome://settings/extensions/"
+ i18n-content="manageExtensionsLinkText"></a>
+ </div>
+ </div>
+</div>
diff --git a/chrome/browser/resources/options2/search_engine_manager.js b/chrome/browser/resources/options2/search_engine_manager.js
new file mode 100644
index 0000000..57826d8
--- /dev/null
+++ b/chrome/browser/resources/options2/search_engine_manager.js
@@ -0,0 +1,125 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+ const OptionsPage = options.OptionsPage;
+ const ArrayDataModel = cr.ui.ArrayDataModel;
+
+ /**
+ * Encapsulated handling of search engine management page.
+ * @constructor
+ */
+ function SearchEngineManager() {
+ this.activeNavTab = null;
+ OptionsPage.call(this, 'searchEngines',
+ templateData.searchEngineManagerPageTabTitle,
+ 'search-engine-manager-page');
+ }
+
+ cr.addSingletonGetter(SearchEngineManager);
+
+ SearchEngineManager.prototype = {
+ __proto__: OptionsPage.prototype,
+
+ /**
+ * List for default search engine options.
+ * @private
+ */
+ defaultsList_: null,
+
+ /**
+ * List for other search engine options.
+ * @private
+ */
+ othersList_: null,
+
+ /**
+ * List for extension keywords.
+ * @private
+ extensionList_ : null,
+
+ /** inheritDoc */
+ initializePage: function() {
+ OptionsPage.prototype.initializePage.call(this);
+
+ this.defaultsList_ = $('default-search-engine-list');
+ this.setUpList_(this.defaultsList_);
+
+ this.othersList_ = $('other-search-engine-list');
+ this.setUpList_(this.othersList_);
+
+ this.extensionList_ = $('extension-keyword-list');
+ this.setUpList_(this.extensionList_);
+ },
+
+ /**
+ * Sets up the given list as a search engine list
+ * @param {List} list The list to set up.
+ * @private
+ */
+ setUpList_: function(list) {
+ options.search_engines.SearchEngineList.decorate(list);
+ list.autoExpands = true;
+ },
+
+ /**
+ * Updates the search engine list with the given entries.
+ * @private
+ * @param {Array} defaultEngines List of possible default search engines.
+ * @param {Array} otherEngines List of other search engines.
+ * @param {Array} keywords List of keywords from extensions.
+ */
+ updateSearchEngineList_: function(defaultEngines, otherEngines, keywords) {
+ this.defaultsList_.dataModel = new ArrayDataModel(defaultEngines);
+
+ otherEngines = otherEngines.map(function(x) {
+ return [x, x['name'].toLocaleLowerCase()];
+ }).sort(function(a,b){
+ return a[1].localeCompare(b[1]);
+ }).map(function(x){
+ return x[0];
+ });
+
+ var othersModel = new ArrayDataModel(otherEngines);
+ // Add a "new engine" row.
+ othersModel.push({
+ 'modelIndex': '-1',
+ 'canBeEdited': true
+ });
+ this.othersList_.dataModel = othersModel;
+
+ if (keywords.length > 0) {
+ $('extension-keyword-div').hidden = false;
+ var extensionsModel = new ArrayDataModel(keywords);
+ this.extensionList_.dataModel = extensionsModel;
+ } else {
+ $('extension-keyword-div').hidden = true;
+ }
+ },
+ };
+
+ SearchEngineManager.updateSearchEngineList = function(defaultEngines,
+ otherEngines,
+ keywords) {
+ SearchEngineManager.getInstance().updateSearchEngineList_(defaultEngines,
+ otherEngines,
+ keywords);
+ };
+
+ SearchEngineManager.validityCheckCallback = function(validity, modelIndex) {
+ // Forward to both lists; the one without a matching modelIndex will ignore
+ // it.
+ SearchEngineManager.getInstance().defaultsList_.validationComplete(
+ validity, modelIndex);
+ SearchEngineManager.getInstance().othersList_.validationComplete(
+ validity, modelIndex);
+ };
+
+ // Export
+ return {
+ SearchEngineManager: SearchEngineManager
+ };
+
+});
+
diff --git a/chrome/browser/resources/options2/search_engine_manager_engine_list.js b/chrome/browser/resources/options2/search_engine_manager_engine_list.js
new file mode 100644
index 0000000..87ee2f5
--- /dev/null
+++ b/chrome/browser/resources/options2/search_engine_manager_engine_list.js
@@ -0,0 +1,316 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options.search_engines', function() {
+ const InlineEditableItemList = options.InlineEditableItemList;
+ const InlineEditableItem = options.InlineEditableItem;
+ const ListSelectionController = cr.ui.ListSelectionController;
+
+ /**
+ * Creates a new search engine list item.
+ * @param {Object} searchEnigne The search engine this represents.
+ * @constructor
+ * @extends {cr.ui.ListItem}
+ */
+ function SearchEngineListItem(searchEngine) {
+ var el = cr.doc.createElement('div');
+ el.searchEngine_ = searchEngine;
+ SearchEngineListItem.decorate(el);
+ return el;
+ }
+
+ /**
+ * Decorates an element as a search engine list item.
+ * @param {!HTMLElement} el The element to decorate.
+ */
+ SearchEngineListItem.decorate = function(el) {
+ el.__proto__ = SearchEngineListItem.prototype;
+ el.decorate();
+ };
+
+ SearchEngineListItem.prototype = {
+ __proto__: InlineEditableItem.prototype,
+
+ /**
+ * Input field for editing the engine name.
+ * @type {HTMLElement}
+ * @private
+ */
+ nameField_: null,
+
+ /**
+ * Input field for editing the engine keyword.
+ * @type {HTMLElement}
+ * @private
+ */
+ keywordField_: null,
+
+ /**
+ * Input field for editing the engine url.
+ * @type {HTMLElement}
+ * @private
+ */
+ urlField_: null,
+
+ /**
+ * Whether or not an input validation request is currently outstanding.
+ * @type {boolean}
+ * @private
+ */
+ waitingForValidation_: false,
+
+ /**
+ * Whether or not the current set of input is known to be valid.
+ * @type {boolean}
+ * @private
+ */
+ currentlyValid_: false,
+
+ /** @inheritDoc */
+ decorate: function() {
+ InlineEditableItem.prototype.decorate.call(this);
+
+ var engine = this.searchEngine_;
+
+ if (engine['modelIndex'] == '-1') {
+ this.isPlaceholder = true;
+ engine['name'] = '';
+ engine['keyword'] = '';
+ engine['url'] = '';
+ }
+
+ this.currentlyValid_ = !this.isPlaceholder;
+
+ if (engine['default'])
+ this.classList.add('default');
+
+ this.deletable = engine['canBeRemoved'];
+
+ // Construct the name column.
+ var nameColEl = this.ownerDocument.createElement('div');
+ nameColEl.className = 'name-column';
+ nameColEl.classList.add('weakrtl');
+ this.contentElement.appendChild(nameColEl);
+
+ // Add the favicon.
+ var faviconDivEl = this.ownerDocument.createElement('div');
+ faviconDivEl.className = 'favicon';
+ var imgEl = this.ownerDocument.createElement('img');
+ imgEl.src = 'chrome://favicon/iconurl/' + engine['iconURL'];
+ faviconDivEl.appendChild(imgEl);
+ nameColEl.appendChild(faviconDivEl);
+
+ var nameEl = this.createEditableTextCell(engine['displayName']);
+ nameEl.classList.add('weakrtl');
+ nameColEl.appendChild(nameEl);
+
+ // Then the keyword column.
+ var keywordEl = this.createEditableTextCell(engine['keyword']);
+ keywordEl.className = 'keyword-column';
+ keywordEl.classList.add('weakrtl');
+ this.contentElement.appendChild(keywordEl);
+
+ // And the URL column.
+ var urlEl = this.createEditableTextCell(engine['url']);
+ var urlWithButtonEl = this.ownerDocument.createElement('div');
+ urlWithButtonEl.appendChild(urlEl);
+ urlWithButtonEl.className = 'url-column';
+ urlWithButtonEl.classList.add('weakrtl');
+ this.contentElement.appendChild(urlWithButtonEl);
+ // Add the Make Default button. Temporary until drag-and-drop re-ordering
+ // is implemented. When this is removed, remove the extra div above.
+ if (engine['canBeDefault']) {
+ var makeDefaultButtonEl = this.ownerDocument.createElement('button');
+ makeDefaultButtonEl.className = 'raw-button custom-appearance';
+ makeDefaultButtonEl.textContent =
+ templateData.makeDefaultSearchEngineButton;
+ makeDefaultButtonEl.onclick = function(e) {
+ chrome.send('managerSetDefaultSearchEngine', [engine['modelIndex']]);
+ };
+ // Don't select the row when clicking the button.
+ makeDefaultButtonEl.onmousedown = function(e) {
+ e.stopPropagation();
+ };
+ urlWithButtonEl.appendChild(makeDefaultButtonEl);
+ }
+
+ // Do final adjustment to the input fields.
+ this.nameField_ = nameEl.querySelector('input');
+ // The editable field uses the raw name, not the display name.
+ this.nameField_.value = engine['name'];
+ this.keywordField_ = keywordEl.querySelector('input');
+ this.urlField_ = urlEl.querySelector('input');
+
+ if (engine['urlLocked'])
+ this.urlField_.disabled = true;
+
+ if (this.isPlaceholder) {
+ this.nameField_.placeholder =
+ localStrings.getString('searchEngineTableNamePlaceholder');
+ this.keywordField_.placeholder =
+ localStrings.getString('searchEngineTableKeywordPlaceholder');
+ this.urlField_.placeholder =
+ localStrings.getString('searchEngineTableURLPlaceholder');
+ }
+
+ var fields = [ this.nameField_, this.keywordField_, this.urlField_ ];
+ for (var i = 0; i < fields.length; i++) {
+ fields[i].oninput = this.startFieldValidation_.bind(this);
+ }
+
+ // Listen for edit events.
+ if (engine['canBeEdited']) {
+ this.addEventListener('edit', this.onEditStarted_.bind(this));
+ this.addEventListener('canceledit', this.onEditCancelled_.bind(this));
+ this.addEventListener('commitedit', this.onEditCommitted_.bind(this));
+ } else {
+ this.editable = false;
+ }
+ },
+
+ /** @inheritDoc */
+ get currentInputIsValid() {
+ return !this.waitingForValidation_ && this.currentlyValid_;
+ },
+
+ /** @inheritDoc */
+ get hasBeenEdited() {
+ var engine = this.searchEngine_;
+ return this.nameField_.value != engine['name'] ||
+ this.keywordField_.value != engine['keyword'] ||
+ this.urlField_.value != engine['url'];
+ },
+
+ /**
+ * Called when entering edit mode; starts an edit session in the model.
+ * @param {Event} e The edit event.
+ * @private
+ */
+ onEditStarted_: function(e) {
+ var editIndex = this.searchEngine_['modelIndex'];
+ chrome.send('editSearchEngine', [String(editIndex)]);
+ this.startFieldValidation_();
+ },
+
+ /**
+ * Called when committing an edit; updates the model.
+ * @param {Event} e The end event.
+ * @private
+ */
+ onEditCommitted_: function(e) {
+ chrome.send('searchEngineEditCompleted', this.getInputFieldValues_());
+ },
+
+ /**
+ * Called when cancelling an edit; informs the model and resets the control
+ * states.
+ * @param {Event} e The cancel event.
+ * @private
+ */
+ onEditCancelled_: function() {
+ chrome.send('searchEngineEditCancelled');
+
+ // The name field has been automatically set to match the display name,
+ // but it should use the raw name instead.
+ this.nameField_.value = this.searchEngine_['name'];
+ this.currentlyValid_ = !this.isPlaceholder;
+ },
+
+ /**
+ * Returns the input field values as an array suitable for passing to
+ * chrome.send. The order of the array is important.
+ * @private
+ * @return {array} The current input field values.
+ */
+ getInputFieldValues_: function() {
+ return [ this.nameField_.value,
+ this.keywordField_.value,
+ this.urlField_.value ];
+ },
+
+ /**
+ * Begins the process of asynchronously validing the input fields.
+ * @private
+ */
+ startFieldValidation_: function() {
+ this.waitingForValidation_ = true;
+ var args = this.getInputFieldValues_();
+ args.push(this.searchEngine_['modelIndex']);
+ chrome.send('checkSearchEngineInfoValidity', args);
+ },
+
+ /**
+ * Callback for the completion of an input validition check.
+ * @param {Object} validity A dictionary of validitation results.
+ */
+ validationComplete: function(validity) {
+ this.waitingForValidation_ = false;
+ // TODO(stuartmorgan): Implement the full validation UI with
+ // checkmark/exclamation mark icons and tooltips showing the errors.
+ if (validity['name']) {
+ this.nameField_.setCustomValidity('');
+ } else {
+ this.nameField_.setCustomValidity(
+ templateData.editSearchEngineInvalidTitleToolTip);
+ }
+
+ if (validity['keyword']) {
+ this.keywordField_.setCustomValidity('');
+ } else {
+ this.keywordField_.setCustomValidity(
+ templateData.editSearchEngineInvalidKeywordToolTip);
+ }
+
+ if (validity['url']) {
+ this.urlField_.setCustomValidity('');
+ } else {
+ this.urlField_.setCustomValidity(
+ templateData.editSearchEngineInvalidURLToolTip);
+ }
+
+ this.currentlyValid_ = validity['name'] && validity['keyword'] &&
+ validity['url'];
+ },
+ };
+
+ var SearchEngineList = cr.ui.define('list');
+
+ SearchEngineList.prototype = {
+ __proto__: InlineEditableItemList.prototype,
+
+ /** @inheritDoc */
+ createItem: function(searchEngine) {
+ return new SearchEngineListItem(searchEngine);
+ },
+
+ /** @inheritDoc */
+ deleteItemAtIndex: function(index) {
+ var modelIndex = this.dataModel.item(index)['modelIndex']
+ chrome.send('removeSearchEngine', [String(modelIndex)]);
+ },
+
+ /**
+ * Passes the results of an input validation check to the requesting row
+ * if it's still being edited.
+ * @param {number} modelIndex The model index of the item that was checked.
+ * @param {Object} validity A dictionary of validitation results.
+ */
+ validationComplete: function(validity, modelIndex) {
+ // If it's not still being edited, it no longer matters.
+ var currentSelection = this.selectedItem;
+ if (!currentSelection)
+ return;
+ var listItem = this.getListItem(currentSelection);
+ if (listItem.editing && currentSelection['modelIndex'] == modelIndex)
+ listItem.validationComplete(validity);
+ },
+ };
+
+ // Export
+ return {
+ SearchEngineList: SearchEngineList
+ };
+
+});
+
diff --git a/chrome/browser/resources/options2/search_page.css b/chrome/browser/resources/options2/search_page.css
new file mode 100644
index 0000000..942dd98
--- /dev/null
+++ b/chrome/browser/resources/options2/search_page.css
@@ -0,0 +1,36 @@
+.search-hidden {
+ display: none !important;
+}
+
+.search-highlighted {
+ background-color: rgba(255, 240, 120, 0.9);
+}
+
+.search-bubble {
+ -webkit-box-shadow: 0 2px 2px #888;
+ background-color: rgba(255, 240, 120, 0.8);
+ border-radius: 6px;
+ box-shadow: 0 2px 2px #888;
+ left: 0;
+ margin: 12px 0 0;
+ padding: 4px 10px;
+ pointer-events: none;
+ position: absolute;
+ text-align: center;
+ top: -1000px; /* minor hack: position off-screen by default */
+ width: 100px;
+}
+
+.search-bubble:after {
+ border-color: rgba(255, 240, 120, 0.9) transparent;
+ border-style: solid;
+ border-width: 0 10px 10px;
+ content: "";
+ left: 50px;
+ position: absolute;
+ top: -10px;
+}
+
+.search-bubble-wrapper {
+ position: relative;
+}
diff --git a/chrome/browser/resources/options2/search_page.html b/chrome/browser/resources/options2/search_page.html
new file mode 100644
index 0000000..5379ca5f
--- /dev/null
+++ b/chrome/browser/resources/options2/search_page.html
@@ -0,0 +1,10 @@
+<div id="searchPage" class="page" hidden>
+ <h1 i18n-content="searchPage"></h1>
+ <div id="searchPageNoMatches">
+ <p i18n-content="searchPageNoMatches"></p>
+ <p><span i18n-content="searchPageHelpLabel"></span>
+ <a target="_blank" i18n-content="searchPageHelpTitle"
+ i18n-values="href:searchPageHelpURL"></a>
+ </p>
+ </div>
+</div>
diff --git a/chrome/browser/resources/options2/search_page.js b/chrome/browser/resources/options2/search_page.js
new file mode 100644
index 0000000..8f0ca9f
--- /dev/null
+++ b/chrome/browser/resources/options2/search_page.js
@@ -0,0 +1,598 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+ const OptionsPage = options.OptionsPage;
+
+ /**
+ * Encapsulated handling of a search bubble.
+ * @constructor
+ */
+ function SearchBubble(text) {
+ var el = cr.doc.createElement('div');
+ SearchBubble.decorate(el);
+ el.textContent = text;
+ return el;
+ }
+
+ SearchBubble.decorate = function(el) {
+ el.__proto__ = SearchBubble.prototype;
+ el.decorate();
+ };
+
+ SearchBubble.prototype = {
+ __proto__: HTMLDivElement.prototype,
+
+ decorate: function() {
+ this.className = 'search-bubble';
+
+ // We create a timer to periodically update the position of the bubbles.
+ // While this isn't all that desirable, it's the only sure-fire way of
+ // making sure the bubbles stay in the correct location as sections
+ // may dynamically change size at any time.
+ var self = this;
+ this.intervalId = setInterval(this.updatePosition.bind(this), 250);
+ },
+
+ /**
+ * Attach the bubble to the element.
+ */
+ attachTo: function(element) {
+ var parent = element.parentElement;
+ if (!parent)
+ return;
+ if (parent.tagName == 'TD') {
+ // To make absolute positioning work inside a table cell we need
+ // to wrap the bubble div into another div with position:relative.
+ // This only works properly if the element is the first child of the
+ // table cell which is true for all options pages.
+ this.wrapper = cr.doc.createElement('div');
+ this.wrapper.className = 'search-bubble-wrapper';
+ this.wrapper.appendChild(this);
+ parent.insertBefore(this.wrapper, element);
+ } else {
+ parent.insertBefore(this, element);
+ }
+ },
+
+ /**
+ * Clear the interval timer and remove the element from the page.
+ */
+ dispose: function() {
+ clearInterval(this.intervalId);
+
+ var child = this.wrapper || this;
+ var parent = child.parentNode;
+ if (parent)
+ parent.removeChild(child);
+ },
+
+ /**
+ * Update the position of the bubble. Called at creation time and then
+ * periodically while the bubble remains visible.
+ */
+ updatePosition: function() {
+ // This bubble is 'owned' by the next sibling.
+ var owner = (this.wrapper || this).nextSibling;
+
+ // If there isn't an offset parent, we have nothing to do.
+ if (!owner.offsetParent)
+ return;
+
+ // Position the bubble below the location of the owner.
+ var left = owner.offsetLeft + owner.offsetWidth / 2 -
+ this.offsetWidth / 2;
+ var top = owner.offsetTop + owner.offsetHeight;
+
+ // Update the position in the CSS. Cache the last values for
+ // best performance.
+ if (left != this.lastLeft) {
+ this.style.left = left + 'px';
+ this.lastLeft = left;
+ }
+ if (top != this.lastTop) {
+ this.style.top = top + 'px';
+ this.lastTop = top;
+ }
+ }
+ }
+
+ /**
+ * Encapsulated handling of the search page.
+ * @constructor
+ */
+ function SearchPage() {
+ OptionsPage.call(this, 'search', templateData.searchPageTabTitle,
+ 'searchPage');
+ }
+
+ cr.addSingletonGetter(SearchPage);
+
+ SearchPage.prototype = {
+ // Inherit SearchPage from OptionsPage.
+ __proto__: OptionsPage.prototype,
+
+ /**
+ * A boolean to prevent recursion. Used by setSearchText_().
+ * @type {Boolean}
+ * @private
+ */
+ insideSetSearchText_: false,
+
+ /**
+ * Initialize the page.
+ */
+ initializePage: function() {
+ // Call base class implementation to start preference initialization.
+ OptionsPage.prototype.initializePage.call(this);
+
+ var self = this;
+
+ // Create a search field element.
+ var searchField = document.createElement('input');
+ searchField.id = 'search-field';
+ searchField.type = 'search';
+ searchField.incremental = true;
+ searchField.placeholder = localStrings.getString('searchPlaceholder');
+ searchField.setAttribute('aria-label', searchField.placeholder);
+ this.searchField = searchField;
+
+ // Replace the contents of the navigation tab with the search field.
+ self.tab.textContent = '';
+ self.tab.appendChild(searchField);
+ self.tab.onclick = self.tab.onkeydown = self.tab.onkeypress = undefined;
+ self.tab.tabIndex = -1;
+ self.tab.setAttribute('role', '');
+
+ // Don't allow the focus on the search navbar. http://crbug.com/77989
+ self.tab.onfocus = self.tab.blur;
+
+ // Handle search events. (No need to throttle, WebKit's search field
+ // will do that automatically.)
+ searchField.onsearch = function(e) {
+ self.setSearchText_(this.value);
+ };
+
+ // We update the history stack every time the search field blurs. This way
+ // we get a history entry for each search, roughly, but not each letter
+ // typed.
+ searchField.onblur = function(e) {
+ var query = SearchPage.canonicalizeQuery(searchField.value);
+ if (!query)
+ return;
+
+ // Don't push the same page onto the history stack more than once (if
+ // the user clicks in the search field and away several times).
+ var currentHash = location.hash;
+ var newHash = '#' + escape(query);
+ if (currentHash == newHash)
+ return;
+
+ // If there is no hash on the current URL, the history entry has no
+ // search query. Replace the history entry with no search with an entry
+ // that does have a search. Otherwise, add it onto the history stack.
+ var historyFunction = currentHash ? window.history.pushState :
+ window.history.replaceState;
+ historyFunction.call(
+ window.history,
+ {pageName: self.name},
+ self.title,
+ '/' + self.name + newHash);
+ };
+
+ // Install handler for key presses.
+ document.addEventListener('keydown',
+ this.keyDownEventHandler_.bind(this));
+
+ // Focus the search field by default.
+ searchField.focus();
+ },
+
+ /**
+ * @inheritDoc
+ */
+ get sticky() {
+ return true;
+ },
+
+ /**
+ * Called after this page has shown.
+ */
+ didShowPage: function() {
+ // This method is called by the Options page after all pages have
+ // had their visibilty attribute set. At this point we can perform the
+ // search specific DOM manipulation.
+ this.setSearchActive_(true);
+ },
+
+ /**
+ * Called before this page will be hidden.
+ */
+ willHidePage: function() {
+ // This method is called by the Options page before all pages have
+ // their visibilty attribute set. Before that happens, we need to
+ // undo the search specific DOM manipulation that was performed in
+ // didShowPage.
+ this.setSearchActive_(false);
+ },
+
+ /**
+ * Update the UI to reflect whether we are in a search state.
+ * @param {boolean} active True if we are on the search page.
+ * @private
+ */
+ setSearchActive_: function(active) {
+ // It's fine to exit if search wasn't active and we're not going to
+ // activate it now.
+ if (!this.searchActive_ && !active)
+ return;
+
+ this.searchActive_ = active;
+
+ if (active) {
+ var hash = location.hash;
+ if (hash)
+ this.searchField.value = unescape(hash.slice(1));
+ } else {
+ // Just wipe out any active search text since it's no longer relevant.
+ this.searchField.value = '';
+ }
+
+ var pagesToSearch = this.getSearchablePages_();
+ for (var key in pagesToSearch) {
+ var page = pagesToSearch[key];
+
+ if (!active)
+ page.visible = false;
+
+ // Update the visible state of all top-level elements that are not
+ // sections (ie titles, button strips). We do this before changing
+ // the page visibility to avoid excessive re-draw.
+ for (var i = 0, childDiv; childDiv = page.pageDiv.children[i]; i++) {
+ if (childDiv.classList.contains('displaytable')) {
+ childDiv.setAttribute('searching', active ? 'true' : 'false');
+ for (var j = 0, subDiv; subDiv = childDiv.children[j]; j++) {
+ if (active) {
+ if (subDiv.tagName != 'SECTION')
+ subDiv.classList.add('search-hidden');
+ } else {
+ subDiv.classList.remove('search-hidden');
+ }
+ }
+ } else {
+ if (active)
+ childDiv.classList.add('search-hidden');
+ else
+ childDiv.classList.remove('search-hidden');
+ }
+ }
+
+ if (active) {
+ // When search is active, remove the 'hidden' tag. This tag may have
+ // been added by the OptionsPage.
+ page.pageDiv.hidden = false;
+ }
+ }
+
+ if (active) {
+ this.setSearchText_(this.searchField.value);
+ } else {
+ // After hiding all page content, remove any search results.
+ this.unhighlightMatches_();
+ this.removeSearchBubbles_();
+ }
+ },
+
+ /**
+ * Set the current search criteria.
+ * @param {string} text Search text.
+ * @private
+ */
+ setSearchText_: function(text) {
+ // Prevent recursive execution of this method.
+ if (this.insideSetSearchText_) return;
+ this.insideSetSearchText_ = true;
+
+ // Cleanup the search query string.
+ text = SearchPage.canonicalizeQuery(text);
+
+ // Notify listeners about the new search query, some pages may wish to
+ // show/hide elements based on the query.
+ var event = new cr.Event('searchChanged');
+ event.searchText = text;
+ this.dispatchEvent(event);
+
+ // Toggle the search page if necessary.
+ if (text.length) {
+ if (!this.searchActive_)
+ OptionsPage.navigateToPage(this.name);
+ } else {
+ if (this.searchActive_)
+ OptionsPage.showDefaultPage();
+
+ this.insideSetSearchText_ = false;
+ return;
+ }
+
+ var foundMatches = false;
+ var bubbleControls = [];
+
+ // Remove any prior search results.
+ this.unhighlightMatches_();
+ this.removeSearchBubbles_();
+
+ // Generate search text by applying lowercase and escaping any characters
+ // that would be problematic for regular expressions.
+ var searchText =
+ text.toLowerCase().replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
+
+ // Generate a regular expression and replace string for hilighting
+ // search terms.
+ var regEx = new RegExp('(' + searchText + ')', 'ig');
+ var replaceString = '<span class="search-highlighted">$1</span>';
+
+ // Initialize all sections. If the search string matches a title page,
+ // show sections for that page.
+ var page, pageMatch, childDiv, length;
+ var pagesToSearch = this.getSearchablePages_();
+ for (var key in pagesToSearch) {
+ page = pagesToSearch[key];
+ pageMatch = false;
+ if (searchText.length) {
+ pageMatch = this.performReplace_(regEx, replaceString, page.tab);
+ }
+ if (pageMatch)
+ foundMatches = true;
+ var elements = page.pageDiv.querySelectorAll('.displaytable > section');
+ for (var i = 0, node; node = elements[i]; i++) {
+ if (pageMatch)
+ node.classList.remove('search-hidden');
+ else
+ node.classList.add('search-hidden');
+ }
+ }
+
+ if (searchText.length) {
+ // Search all top-level sections for anchored string matches.
+ for (var key in pagesToSearch) {
+ page = pagesToSearch[key];
+ var elements =
+ page.pageDiv.querySelectorAll('.displaytable > section');
+ for (var i = 0, node; node = elements[i]; i++) {
+ if (this.performReplace_(regEx, replaceString, node)) {
+ node.classList.remove('search-hidden');
+ foundMatches = true;
+ }
+ }
+ }
+
+ // Search all sub-pages, generating an array of top-level sections that
+ // we need to make visible.
+ var subPagesToSearch = this.getSearchableSubPages_();
+ var control, node;
+ for (var key in subPagesToSearch) {
+ page = subPagesToSearch[key];
+ if (this.performReplace_(regEx, replaceString, page.pageDiv)) {
+ // Reveal the section for this search result.
+ section = page.associatedSection;
+ if (section)
+ section.classList.remove('search-hidden');
+
+ // Identify any controls that should have bubbles.
+ var controls = page.associatedControls;
+ if (controls) {
+ length = controls.length;
+ for (var i = 0; i < length; i++)
+ bubbleControls.push(controls[i]);
+ }
+
+ foundMatches = true;
+ }
+ }
+ }
+
+ // Configure elements on the search results page based on search results.
+ if (foundMatches)
+ $('searchPageNoMatches').classList.add('search-hidden');
+ else
+ $('searchPageNoMatches').classList.remove('search-hidden');
+
+ // Create search balloons for sub-page results.
+ length = bubbleControls.length;
+ for (var i = 0; i < length; i++)
+ this.createSearchBubble_(bubbleControls[i], text);
+
+ // Cleanup the recursion-prevention variable.
+ this.insideSetSearchText_ = false;
+ },
+
+ /**
+ * Performs a string replacement based on a regex and replace string.
+ * @param {RegEx} regex A regular expression for finding search matches.
+ * @param {String} replace A string to apply the replace operation.
+ * @param {Element} element An HTML container element.
+ * @returns {Boolean} true if the element was changed.
+ * @private
+ */
+ performReplace_: function(regex, replace, element) {
+ var found = false;
+ var div, child, tmp;
+
+ // Walk the tree, searching each TEXT node.
+ var walker = document.createTreeWalker(element,
+ NodeFilter.SHOW_TEXT,
+ null,
+ false);
+ var node = walker.nextNode();
+ while (node) {
+ // Perform a search and replace on the text node value.
+ var newValue = node.nodeValue.replace(regex, replace);
+ if (newValue != node.nodeValue) {
+ // The text node has changed so that means we found at least one
+ // match.
+ found = true;
+
+ // Create a temporary div element and set the innerHTML to the new
+ // value.
+ div = document.createElement('div');
+ div.innerHTML = newValue;
+
+ // Insert all the child nodes of the temporary div element into the
+ // document, before the original node.
+ child = div.firstChild;
+ while (child = div.firstChild) {
+ node.parentNode.insertBefore(child, node);
+ };
+
+ // Delete the old text node and advance the walker to the next
+ // node.
+ tmp = node;
+ node = walker.nextNode();
+ tmp.parentNode.removeChild(tmp);
+ } else {
+ node = walker.nextNode();
+ }
+ }
+
+ return found;
+ },
+
+ /**
+ * Removes all search highlight tags from the document.
+ * @private
+ */
+ unhighlightMatches_: function() {
+ // Find all search highlight elements.
+ var elements = document.querySelectorAll('.search-highlighted');
+
+ // For each element, remove the highlighting.
+ var parent, i;
+ for (var i = 0, node; node = elements[i]; i++) {
+ parent = node.parentNode;
+
+ // Replace the highlight element with the first child (the text node).
+ parent.replaceChild(node.firstChild, node);
+
+ // Normalize the parent so that multiple text nodes will be combined.
+ parent.normalize();
+ }
+ },
+
+ /**
+ * Creates a search result bubble attached to an element.
+ * @param {Element} element An HTML element, usually a button.
+ * @param {string} text A string to show in the bubble.
+ * @private
+ */
+ createSearchBubble_: function(element, text) {
+ // avoid appending multiple bubbles to a button.
+ var sibling = element.previousElementSibling;
+ if (sibling && (sibling.classList.contains('search-bubble') ||
+ sibling.classList.contains('search-bubble-wrapper')))
+ return;
+
+ var parent = element.parentElement;
+ if (parent) {
+ var bubble = new SearchBubble(text);
+ bubble.attachTo(element);
+ bubble.updatePosition();
+ }
+ },
+
+ /**
+ * Removes all search match bubbles.
+ * @private
+ */
+ removeSearchBubbles_: function() {
+ var elements = document.querySelectorAll('.search-bubble');
+ var length = elements.length;
+ for (var i = 0; i < length; i++)
+ elements[i].dispose();
+ },
+
+ /**
+ * Builds a list of top-level pages to search. Omits the search page and
+ * all sub-pages.
+ * @returns {Array} An array of pages to search.
+ * @private
+ */
+ getSearchablePages_: function() {
+ var name, page, pages = [];
+ for (name in OptionsPage.registeredPages) {
+ if (name != this.name) {
+ page = OptionsPage.registeredPages[name];
+ if (!page.parentPage)
+ pages.push(page);
+ }
+ }
+ return pages;
+ },
+
+ /**
+ * Builds a list of sub-pages (and overlay pages) to search. Ignore pages
+ * that have no associated controls.
+ * @returns {Array} An array of pages to search.
+ * @private
+ */
+ getSearchableSubPages_: function() {
+ var name, pageInfo, page, pages = [];
+ for (name in OptionsPage.registeredPages) {
+ page = OptionsPage.registeredPages[name];
+ if (page.parentPage && page.associatedSection)
+ pages.push(page);
+ }
+ for (name in OptionsPage.registeredOverlayPages) {
+ page = OptionsPage.registeredOverlayPages[name];
+ if (page.associatedSection && page.pageDiv != undefined)
+ pages.push(page);
+ }
+ return pages;
+ },
+
+ /**
+ * A function to handle key press events.
+ * @return {Event} a keydown event.
+ * @private
+ */
+ keyDownEventHandler_: function(event) {
+ const ESCAPE_KEY_CODE = 27;
+ const FORWARD_SLASH_KEY_CODE = 191;
+
+ switch(event.keyCode) {
+ case ESCAPE_KEY_CODE:
+ if (event.target == this.searchField) {
+ this.setSearchText_('');
+ this.searchField.blur();
+ event.stopPropagation();
+ event.preventDefault();
+ }
+ break;
+ case FORWARD_SLASH_KEY_CODE:
+ if (!/INPUT|SELECT|BUTTON|TEXTAREA/.test(event.target.tagName) &&
+ !event.ctrlKey && !event.altKey) {
+ this.searchField.focus();
+ event.stopPropagation();
+ event.preventDefault();
+ }
+ break;
+ }
+ },
+ };
+
+ /**
+ * Standardizes a user-entered text query by removing extra whitespace.
+ * @param {string} The user-entered text.
+ * @return {string} The trimmed query.
+ */
+ SearchPage.canonicalizeQuery = function(text) {
+ // Trim beginning and ending whitespace.
+ return text.replace(/^\s+|\s+$/g, '');
+ };
+
+ // Export
+ return {
+ SearchPage: SearchPage
+ };
+
+});
diff --git a/chrome/browser/resources/options2/subpages_tab_controls.css b/chrome/browser/resources/options2/subpages_tab_controls.css
new file mode 100644
index 0000000..774520c
--- /dev/null
+++ b/chrome/browser/resources/options2/subpages_tab_controls.css
@@ -0,0 +1,66 @@
+/*
+Copyright (c) 2010 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+*/
+
+.subpages-nav-tabs .tab {
+ position: relative;
+ padding: 4px 8px;
+}
+
+.subpages-nav-tabs .active-tab {
+ position: relative;
+ background: white;
+ border: 1px solid #A0A0A0; /* light gray */
+ border-bottom: 2px solid white;
+ border-top-left-radius: 3px;
+ border-top-right-radius: 3px;
+}
+
+/* To avoid tabs changing size when they are clicked and their labels become
+ * bold, we actually put two labels inside each tab: an inactive label and an
+ * active label. Only one is visible at a time, but the bold label is used to
+ * size the tab even when it's not visible. This keeps the tab size constant.
+ */
+.subpages-nav-tabs .active-tab-label {
+ font-weight: bold;
+}
+
+.subpages-nav-tabs .tab-label {
+ position: absolute;
+ top: 5px;
+ left: 9px;
+}
+
+html[dir=rtl] .subpages-nav-tabs .tab-label {
+ right: 9px;
+}
+
+.subpages-nav-tabs .active-tab-label,
+.subpages-nav-tabs .active-tab .tab-label {
+ visibility: hidden;
+}
+
+/* .tab is not removed when .active-tab is added, so we must
+ * override the hidden visibility above in the active tab case.
+ */
+.subpages-nav-tabs .active-tab .active-tab-label {
+ visibility: visible;
+}
+
+.subpages-nav-tabs {
+ padding: 4px;
+ border-bottom: 1px solid #A0A0A0; /* light gray */
+ background: -webkit-linear-gradient(white, #F3F3F3); /* very light gray */
+ margin-bottom: 15px;
+}
+
+.subpages-tab-contents {
+ display: none;
+ -webkit-padding-start: 10px;
+}
+
+.active-tab-contents {
+ display: block;
+}
diff --git a/chrome/browser/resources/options2_resources.grd b/chrome/browser/resources/options2_resources.grd
new file mode 100644
index 0000000..f094fda
--- /dev/null
+++ b/chrome/browser/resources/options2_resources.grd
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<grit latest_public_release="0" current_release="1">
+ <outputs>
+ <output filename="grit/options2_resources.h" type="rc_header">
+ <emit emit_type='prepend'></emit>
+ </output>
+ <output filename="options2_resources.pak" type="data_package" />
+ </outputs>
+ <release seq="1">
+ <includes>
+ <include name="IDR_OPTIONS2_BUNDLE_JS" file="options2/options_bundle.js" flattenhtml="true" type="BINDATA" />
+ <include name="IDR_OPTIONS2_HTML" file="options2/options.html" flattenhtml="true" allowexternalscript="true" type="BINDATA" />
+ </includes>
+ </release>
+</grit>
diff --git a/chrome/browser/resources/uber/uber.html b/chrome/browser/resources/uber/uber.html
index 61469f8..d8f3bfd 100644
--- a/chrome/browser/resources/uber/uber.html
+++ b/chrome/browser/resources/uber/uber.html
@@ -13,7 +13,7 @@
<body>
-<iframe src="chrome://settings/"></iframe>
+<iframe src="chrome://settings-frame/"></iframe>
<iframe src="chrome://extensions-frame/"></iframe>
</body>
diff --git a/chrome/browser/ui/browser_navigator.cc b/chrome/browser/ui/browser_navigator.cc
index ab73fbc..234f2e9 100644
--- a/chrome/browser/ui/browser_navigator.cc
+++ b/chrome/browser/ui/browser_navigator.cc
@@ -641,6 +641,7 @@ bool IsURLAllowedInIncognito(const GURL& url) {
return !(url.scheme() == chrome::kChromeUIScheme &&
(url.host() == chrome::kChromeUISettingsHost ||
+ url.host() == chrome::kChromeUISettingsFrameHost ||
url.host() == chrome::kChromeUIExtensionsHost ||
url.host() == chrome::kChromeUIBookmarksHost ||
url.host() == chrome::kChromeUISyncPromoHost));
diff --git a/chrome/browser/ui/webui/chrome_web_ui_factory.cc b/chrome/browser/ui/webui/chrome_web_ui_factory.cc
index d7000e1..eabb387 100644
--- a/chrome/browser/ui/webui/chrome_web_ui_factory.cc
+++ b/chrome/browser/ui/webui/chrome_web_ui_factory.cc
@@ -33,6 +33,7 @@
#include "chrome/browser/ui/webui/net_internals_ui.h"
#include "chrome/browser/ui/webui/ntp/new_tab_ui.h"
#include "chrome/browser/ui/webui/options/options_ui.h"
+#include "chrome/browser/ui/webui/options2/options_ui2.h"
#include "chrome/browser/ui/webui/plugins_ui.h"
#include "chrome/browser/ui/webui/policy_ui.h"
#include "chrome/browser/ui/webui/print_preview_ui.h"
@@ -214,6 +215,8 @@ WebUIFactoryFunction GetWebUIFactoryFunction(TabContents* tab_contents,
return &NewWebUI<SessionsUI>;
if (url.host() == chrome::kChromeUISettingsHost)
return &NewWebUI<OptionsUI>;
+ if (url.host() == chrome::kChromeUISettingsFrameHost)
+ return &NewWebUI<Options2UI>;
if (url.host() == chrome::kChromeUISyncInternalsHost)
return &NewWebUI<SyncInternalsUI>;
if (url.host() == chrome::kChromeUITaskManagerHost)
@@ -449,6 +452,9 @@ RefCountedMemory* ChromeWebUIFactory::GetFaviconResourceBytes(
if (page_url.host() == chrome::kChromeUISettingsHost)
return OptionsUI::GetFaviconResourceBytes();
+ if (page_url.host() == chrome::kChromeUISettingsFrameHost)
+ return Options2UI::GetFaviconResourceBytes();
+
if (page_url.host() == chrome::kChromeUIPluginsHost)
return PluginsUI::GetFaviconResourceBytes();
diff --git a/chrome/browser/ui/webui/options/chromeos/bluetooth_options_handler.cc b/chrome/browser/ui/webui/options/chromeos/bluetooth_options_handler.cc
index 5dfe692..0e194a2 100644
--- a/chrome/browser/ui/webui/options/chromeos/bluetooth_options_handler.cc
+++ b/chrome/browser/ui/webui/options/chromeos/bluetooth_options_handler.cc
@@ -19,7 +19,7 @@
namespace {
-// |UpdateDeviceCallback| takes a variable length list as an arugment. The
+// |UpdateDeviceCallback| takes a variable length list as an argument. The
// value stored in each list element is indicated by the following constants.
const int kUpdateDeviceAddressIndex = 0;
const int kUpdateDeviceCommandIndex = 1;
diff --git a/chrome/browser/ui/webui/options/options_ui.h b/chrome/browser/ui/webui/options/options_ui.h
index 184af36..16237ff 100644
--- a/chrome/browser/ui/webui/options/options_ui.h
+++ b/chrome/browser/ui/webui/options/options_ui.h
@@ -72,6 +72,9 @@ class OptionsPageUIHandler : public WebUIMessageHandler,
class OptionsPageUIHandlerHost {
public:
virtual void InitializeHandlers() = 0;
+
+ protected:
+ virtual ~OptionsPageUIHandlerHost() {}
};
// The WebUI for chrome:settings.
diff --git a/chrome/browser/ui/webui/options2/OWNERS b/chrome/browser/ui/webui/options2/OWNERS
new file mode 100644
index 0000000..67257e6
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/OWNERS
@@ -0,0 +1,4 @@
+csilv@chromium.org
+estade@chromium.org
+jhawkins@chromium.org
+stuartmorgan@chromium.org
diff --git a/chrome/browser/ui/webui/options2/advanced_options_browsertest.js b/chrome/browser/ui/webui/options2/advanced_options_browsertest.js
new file mode 100644
index 0000000..a929bbb
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/advanced_options_browsertest.js
@@ -0,0 +1,63 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * TestFixture for advanced options WebUI testing.
+ * @extends {testing.Test}
+ * @constructor
+ **/
+function AdvancedOptionsWebUITest() {}
+
+AdvancedOptionsWebUITest.prototype = {
+ __proto__: testing.Test.prototype,
+
+ /**
+ * Browse to advanced options.
+ **/
+ browsePreload: 'chrome://settings/advanced',
+
+ /**
+ * Register a mock handler.
+ * @type {Function}
+ * @override
+ */
+ preLoad: function() {
+ this.makeAndRegisterMockHandler(['defaultZoomFactorAction']);
+ },
+};
+
+/**
+ * The expected minimum length of the |defaultZoomFactor| element.
+ * @type {number}
+ * @const
+ */
+var defaultZoomFactorMinimumLength = 10;
+
+/**
+ * Test opening the advanced options has correct location.
+ */
+TEST_F('AdvancedOptionsWebUITest', 'testOpenAdvancedOptions', function() {
+ assertEquals(this.browsePreload, document.location.href);
+});
+
+/**
+ * Test the default zoom factor select element.
+ */
+TEST_F('AdvancedOptionsWebUITest', 'testDefaultZoomFactor', function() {
+ // Verify that the zoom factor element exists.
+ var defaultZoomFactor = $('defaultZoomFactor');
+ assertNotEquals(defaultZoomFactor, null);
+
+ // Verify that the zoom factor element has a reasonable number of choices.
+ expectGE(defaultZoomFactor.options.length, defaultZoomFactorMinimumLength);
+
+ // Simulate a change event, selecting the highest zoom value. Verify that
+ // the javascript handler was invoked once.
+ this.mockHandler.expects(once()).defaultZoomFactorAction(NOT_NULL).
+ will(callFunction(function() { }));
+ defaultZoomFactor.selectedIndex = defaultZoomFactor.options.length - 1;
+ var event = { target: defaultZoomFactor };
+ if (defaultZoomFactor.onchange) defaultZoomFactor.onchange(event);
+});
+
diff --git a/chrome/browser/ui/webui/options2/advanced_options_handler.cc b/chrome/browser/ui/webui/options2/advanced_options_handler.cc
new file mode 100644
index 0000000..fbb8aed
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/advanced_options_handler.cc
@@ -0,0 +1,643 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/webui/options2/advanced_options_handler.h"
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/command_line.h"
+#include "base/utf_string_conversions.h"
+#include "base/values.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/chrome_page_zoom.h"
+#include "chrome/browser/download/download_prefs.h"
+#include "chrome/browser/google/google_util.h"
+#include "chrome/browser/prefs/pref_service.h"
+#include "chrome/browser/printing/cloud_print/cloud_print_proxy_service.h"
+#include "chrome/browser/printing/cloud_print/cloud_print_proxy_service_factory.h"
+#include "chrome/browser/printing/cloud_print/cloud_print_setup_flow.h"
+#include "chrome/browser/printing/cloud_print/cloud_print_url.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/service/service_process_control.h"
+#include "chrome/browser/ui/options/options_util.h"
+#include "chrome/common/chrome_notification_types.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/common/pref_names.h"
+#include "chrome/common/url_constants.h"
+#include "content/browser/download/download_manager.h"
+#include "content/browser/tab_contents/tab_contents.h"
+#include "content/browser/tab_contents/tab_contents_view.h"
+#include "content/public/browser/notification_details.h"
+#include "content/public/browser/notification_types.h"
+#include "content/public/browser/user_metrics.h"
+#include "content/public/common/page_zoom.h"
+#include "grit/chromium_strings.h"
+#include "grit/generated_resources.h"
+#include "grit/locale_settings.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebView.h"
+#include "ui/base/l10n/l10n_util.h"
+
+#if !defined(OS_CHROMEOS)
+#include "chrome/browser/printing/cloud_print/cloud_print_setup_handler.h"
+#include "chrome/browser/ui/webui/options2/advanced_options_utils.h"
+#endif
+
+using content::UserMetricsAction;
+
+AdvancedOptionsHandler::AdvancedOptionsHandler() {
+
+#if(!defined(GOOGLE_CHROME_BUILD) && defined(OS_WIN))
+ // On Windows, we need the PDF plugin which is only guaranteed to exist on
+ // Google Chrome builds. Use a command-line switch for Windows non-Google
+ // Chrome builds.
+ cloud_print_proxy_ui_enabled_ = CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kEnableCloudPrintProxy);
+#elif(!defined(OS_CHROMEOS))
+ // Always enabled for Mac, Linux and Google Chrome Windows builds.
+ // Never enabled for Chrome OS, we don't even need to indicate it.
+ cloud_print_proxy_ui_enabled_ = true;
+#endif
+}
+
+AdvancedOptionsHandler::~AdvancedOptionsHandler() {
+ // There may be pending file dialogs, we need to tell them that we've gone
+ // away so they don't try and call back to us.
+ if (select_folder_dialog_.get())
+ select_folder_dialog_->ListenerDestroyed();
+}
+
+void AdvancedOptionsHandler::GetLocalizedValues(
+ DictionaryValue* localized_strings) {
+ DCHECK(localized_strings);
+
+ static OptionsStringResource resources[] = {
+ { "downloadLocationGroupName",
+ IDS_OPTIONS_DOWNLOADLOCATION_GROUP_NAME },
+ { "downloadLocationChangeButton",
+ IDS_OPTIONS_DOWNLOADLOCATION_CHANGE_BUTTON },
+ { "downloadLocationBrowseTitle",
+ IDS_OPTIONS_DOWNLOADLOCATION_BROWSE_TITLE },
+ { "downloadLocationBrowseWindowTitle",
+ IDS_OPTIONS_DOWNLOADLOCATION_BROWSE_WINDOW_TITLE },
+ { "downloadLocationAskForSaveLocation",
+ IDS_OPTIONS_DOWNLOADLOCATION_ASKFORSAVELOCATION },
+ { "autoOpenFileTypesInfo",
+ IDS_OPTIONS_OPEN_FILE_TYPES_AUTOMATICALLY },
+ { "autoOpenFileTypesResetToDefault",
+ IDS_OPTIONS_AUTOOPENFILETYPES_RESETTODEFAULT },
+ { "translateEnableTranslate",
+ IDS_OPTIONS_TRANSLATE_ENABLE_TRANSLATE },
+ { "certificatesManageButton",
+ IDS_OPTIONS_CERTIFICATES_MANAGE_BUTTON },
+ { "proxiesLabel",
+ IDS_OPTIONS_PROXIES_LABEL },
+#if !defined(OS_CHROMEOS)
+ { "proxiesConfigureButton",
+ IDS_OPTIONS_PROXIES_CONFIGURE_BUTTON },
+#endif
+ { "safeBrowsingEnableProtection",
+ IDS_OPTIONS_SAFEBROWSING_ENABLEPROTECTION },
+ { "sslGroupDescription",
+ IDS_OPTIONS_SSL_GROUP_DESCRIPTION },
+ { "sslCheckRevocation",
+ IDS_OPTIONS_SSL_CHECKREVOCATION },
+ { "networkPredictionEnabledDescription",
+ IDS_NETWORK_PREDICTION_ENABLED_DESCRIPTION },
+ { "privacyContentSettingsButton",
+ IDS_OPTIONS_PRIVACY_CONTENT_SETTINGS_BUTTON },
+ { "privacyClearDataButton",
+ IDS_OPTIONS_PRIVACY_CLEAR_DATA_BUTTON },
+ { "linkDoctorPref",
+ IDS_OPTIONS_LINKDOCTOR_PREF },
+ { "suggestPref",
+ IDS_OPTIONS_SUGGEST_PREF },
+ { "tabsToLinksPref",
+ IDS_OPTIONS_TABS_TO_LINKS_PREF },
+ { "fontSettingsInfo",
+ IDS_OPTIONS_FONTSETTINGS_INFO },
+ { "defaultZoomFactorLabel",
+ IDS_OPTIONS_DEFAULT_ZOOM_LEVEL_LABEL },
+ { "defaultFontSizeLabel",
+ IDS_OPTIONS_DEFAULT_FONT_SIZE_LABEL },
+ { "fontSizeLabelVerySmall",
+ IDS_OPTIONS_FONT_SIZE_LABEL_VERY_SMALL },
+ { "fontSizeLabelSmall",
+ IDS_OPTIONS_FONT_SIZE_LABEL_SMALL },
+ { "fontSizeLabelMedium",
+ IDS_OPTIONS_FONT_SIZE_LABEL_MEDIUM },
+ { "fontSizeLabelLarge",
+ IDS_OPTIONS_FONT_SIZE_LABEL_LARGE },
+ { "fontSizeLabelVeryLarge",
+ IDS_OPTIONS_FONT_SIZE_LABEL_VERY_LARGE },
+ { "fontSizeLabelCustom",
+ IDS_OPTIONS_FONT_SIZE_LABEL_CUSTOM },
+ { "fontSettingsCustomizeFontsButton",
+ IDS_OPTIONS_FONTSETTINGS_CUSTOMIZE_FONTS_BUTTON },
+ { "languageAndSpellCheckSettingsButton",
+ IDS_OPTIONS_LANGUAGE_AND_SPELLCHECK_BUTTON },
+ { "advancedSectionTitlePrivacy",
+ IDS_OPTIONS_ADVANCED_SECTION_TITLE_PRIVACY },
+ { "advancedSectionTitleContent",
+ IDS_OPTIONS_ADVANCED_SECTION_TITLE_CONTENT },
+ { "advancedSectionTitleSecurity",
+ IDS_OPTIONS_ADVANCED_SECTION_TITLE_SECURITY },
+ { "advancedSectionTitleNetwork",
+ IDS_OPTIONS_ADVANCED_SECTION_TITLE_NETWORK },
+ { "advancedSectionTitleTranslate",
+ IDS_OPTIONS_ADVANCED_SECTION_TITLE_TRANSLATE },
+ { "translateEnableTranslate",
+ IDS_OPTIONS_TRANSLATE_ENABLE_TRANSLATE },
+ { "enableLogging",
+ IDS_OPTIONS_ENABLE_LOGGING },
+ { "improveBrowsingExperience",
+ IDS_OPTIONS_IMPROVE_BROWSING_EXPERIENCE },
+ { "disableWebServices",
+ IDS_OPTIONS_DISABLE_WEB_SERVICES },
+#if defined(OS_CHROMEOS)
+ { "cloudPrintChromeosOptionLabel",
+ IDS_CLOUD_PRINT_CHROMEOS_OPTION_LABEL },
+ { "cloudPrintChromeosOptionButton",
+ IDS_CLOUD_PRINT_CHROMEOS_OPTION_BUTTON },
+#endif
+ { "cloudPrintOptionsStaticLabel",
+ IDS_CLOUD_PRINT_SETUP_DIALOG_TITLE },
+ { "cloudPrintProxyEnabledManageButton",
+ IDS_OPTIONS_CLOUD_PRINT_PROXY_ENABLED_MANAGE_BUTTON },
+ { "advancedSectionTitleCloudPrint",
+ IDS_OPTIONS_ADVANCED_SECTION_TITLE_CLOUD_PRINT },
+#if !defined(OS_CHROMEOS)
+ { "cloudPrintProxyDisabledLabel",
+ IDS_OPTIONS_CLOUD_PRINT_PROXY_DISABLED_LABEL },
+ { "cloudPrintProxyDisabledButton",
+ IDS_OPTIONS_CLOUD_PRINT_PROXY_DISABLED_BUTTON },
+ { "cloudPrintProxyEnabledButton",
+ IDS_OPTIONS_CLOUD_PRINT_PROXY_ENABLED_BUTTON },
+ { "cloudPrintProxyEnablingButton",
+ IDS_OPTIONS_CLOUD_PRINT_PROXY_ENABLING_BUTTON },
+#endif
+#if !defined(OS_MACOSX) && !defined(OS_CHROMEOS)
+ { "advancedSectionTitleBackground",
+ IDS_OPTIONS_ADVANCED_SECTION_TITLE_BACKGROUND },
+ { "backgroundModeCheckbox",
+ IDS_OPTIONS_BACKGROUND_ENABLE_BACKGROUND_MODE },
+#endif
+ };
+
+ RegisterStrings(localized_strings, resources, arraysize(resources));
+ RegisterTitle(localized_strings, "advancedPage",
+ IDS_OPTIONS_ADVANCED_TAB_LABEL);
+
+ localized_strings->SetString("privacyLearnMoreURL",
+ google_util::AppendGoogleLocaleParam(
+ GURL(chrome::kPrivacyLearnMoreURL)).spec());
+
+#if defined(OS_CHROMEOS)
+ localized_strings->SetString("cloudPrintLearnMoreURL",
+ google_util::AppendGoogleLocaleParam(
+ GURL(chrome::kCloudPrintLearnMoreURL)).spec());
+#endif
+}
+
+void AdvancedOptionsHandler::Initialize() {
+ DCHECK(web_ui_);
+ SetupMetricsReportingCheckbox();
+ SetupMetricsReportingSettingVisibility();
+ SetupFontSizeSelector();
+ SetupPageZoomSelector();
+ SetupAutoOpenFileTypesDisabledAttribute();
+ SetupProxySettingsSection();
+ SetupSSLConfigSettings();
+#if !defined(OS_CHROMEOS)
+ if (cloud_print_proxy_ui_enabled_) {
+ SetupCloudPrintProxySection();
+ RefreshCloudPrintStatusFromService();
+ } else {
+ RemoveCloudPrintProxySection();
+ }
+#endif
+#if !defined(OS_MACOSX) && !defined(OS_CHROMEOS)
+ SetupBackgroundModeSettings();
+#endif
+
+}
+
+WebUIMessageHandler* AdvancedOptionsHandler::Attach(WebUI* web_ui) {
+ // Call through to superclass.
+ WebUIMessageHandler* handler = OptionsPage2UIHandler::Attach(web_ui);
+
+ // Register for preferences that we need to observe manually. These have
+ // special behaviors that aren't handled by the standard prefs UI.
+ DCHECK(web_ui_);
+ PrefService* prefs = Profile::FromWebUI(web_ui_)->GetPrefs();
+#if !defined(OS_CHROMEOS)
+ enable_metrics_recording_.Init(prefs::kMetricsReportingEnabled,
+ g_browser_process->local_state(), this);
+ cloud_print_proxy_email_.Init(prefs::kCloudPrintEmail, prefs, this);
+ cloud_print_proxy_enabled_.Init(prefs::kCloudPrintProxyEnabled, prefs, this);
+#endif
+
+ rev_checking_enabled_.Init(prefs::kCertRevocationCheckingEnabled,
+ g_browser_process->local_state(), this);
+
+#if !defined(OS_MACOSX) && !defined(OS_CHROMEOS)
+ background_mode_enabled_.Init(prefs::kBackgroundModeEnabled,
+ g_browser_process->local_state(),
+ this);
+#endif
+
+ auto_open_files_.Init(prefs::kDownloadExtensionsToOpen, prefs, this);
+ default_font_size_.Init(prefs::kWebKitDefaultFontSize, prefs, this);
+ default_zoom_level_.Init(prefs::kDefaultZoomLevel, prefs, this);
+#if !defined(OS_CHROMEOS)
+ proxy_prefs_.reset(
+ PrefSetObserver::CreateProxyPrefSetObserver(prefs, this));
+#endif // !defined(OS_CHROMEOS)
+
+ // Return result from the superclass.
+ return handler;
+}
+
+void AdvancedOptionsHandler::RegisterMessages() {
+ // Setup handlers specific to this panel.
+ DCHECK(web_ui_);
+ web_ui_->RegisterMessageCallback("selectDownloadLocation",
+ base::Bind(&AdvancedOptionsHandler::HandleSelectDownloadLocation,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("autoOpenFileTypesAction",
+ base::Bind(&AdvancedOptionsHandler::HandleAutoOpenButton,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("defaultFontSizeAction",
+ base::Bind(&AdvancedOptionsHandler::HandleDefaultFontSize,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("defaultZoomFactorAction",
+ base::Bind(&AdvancedOptionsHandler::HandleDefaultZoomFactor,
+ base::Unretained(this)));
+#if !defined(OS_CHROMEOS)
+ web_ui_->RegisterMessageCallback("metricsReportingCheckboxAction",
+ base::Bind(&AdvancedOptionsHandler::HandleMetricsReportingCheckbox,
+ base::Unretained(this)));
+#endif
+#if !defined(USE_NSS) && !defined(USE_OPENSSL)
+ web_ui_->RegisterMessageCallback("showManageSSLCertificates",
+ base::Bind(&AdvancedOptionsHandler::ShowManageSSLCertificates,
+ base::Unretained(this)));
+#endif
+ web_ui_->RegisterMessageCallback("showCloudPrintManagePage",
+ base::Bind(&AdvancedOptionsHandler::ShowCloudPrintManagePage,
+ base::Unretained(this)));
+#if !defined(OS_CHROMEOS)
+ if (cloud_print_proxy_ui_enabled_) {
+ web_ui_->RegisterMessageCallback("showCloudPrintSetupDialog",
+ base::Bind(&AdvancedOptionsHandler::ShowCloudPrintSetupDialog,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("disableCloudPrintProxy",
+ base::Bind(&AdvancedOptionsHandler::HandleDisableCloudPrintProxy,
+ base::Unretained(this)));
+ }
+ web_ui_->RegisterMessageCallback("showNetworkProxySettings",
+ base::Bind(&AdvancedOptionsHandler::ShowNetworkProxySettings,
+ base::Unretained(this)));
+#endif
+ web_ui_->RegisterMessageCallback("checkRevocationCheckboxAction",
+ base::Bind(&AdvancedOptionsHandler::HandleCheckRevocationCheckbox,
+ base::Unretained(this)));
+#if !defined(OS_MACOSX) && !defined(OS_CHROMEOS)
+ web_ui_->RegisterMessageCallback("backgroundModeAction",
+ base::Bind(&AdvancedOptionsHandler::HandleBackgroundModeCheckbox,
+ base::Unretained(this)));
+#endif
+}
+
+void AdvancedOptionsHandler::Observe(
+ int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) {
+ if (type == chrome::NOTIFICATION_PREF_CHANGED) {
+ std::string* pref_name = content::Details<std::string>(details).ptr();
+ if (*pref_name == prefs::kDownloadExtensionsToOpen) {
+ SetupAutoOpenFileTypesDisabledAttribute();
+#if !defined(OS_CHROMEOS)
+ } else if (proxy_prefs_->IsObserved(*pref_name)) {
+ SetupProxySettingsSection();
+#endif // !defined(OS_CHROMEOS)
+ } else if ((*pref_name == prefs::kCloudPrintEmail) ||
+ (*pref_name == prefs::kCloudPrintProxyEnabled)) {
+#if !defined(OS_CHROMEOS)
+ if (cloud_print_proxy_ui_enabled_)
+ SetupCloudPrintProxySection();
+#endif
+ } else if (*pref_name == prefs::kWebKitDefaultFontSize) {
+ SetupFontSizeSelector();
+ } else if (*pref_name == prefs::kDefaultZoomLevel) {
+ SetupPageZoomSelector();
+#if !defined(OS_MACOSX) && !defined(OS_CHROMEOS)
+ } else if (*pref_name == prefs::kBackgroundModeEnabled) {
+ SetupBackgroundModeSettings();
+#endif
+ }
+ }
+}
+
+void AdvancedOptionsHandler::HandleSelectDownloadLocation(
+ const ListValue* args) {
+ PrefService* pref_service = Profile::FromWebUI(web_ui_)->GetPrefs();
+ select_folder_dialog_ = SelectFileDialog::Create(this);
+ select_folder_dialog_->SelectFile(
+ SelectFileDialog::SELECT_FOLDER,
+ l10n_util::GetStringUTF16(IDS_OPTIONS_DOWNLOADLOCATION_BROWSE_TITLE),
+ pref_service->GetFilePath(prefs::kDownloadDefaultDirectory),
+ NULL, 0, FILE_PATH_LITERAL(""), web_ui_->tab_contents(),
+ web_ui_->tab_contents()->view()->GetTopLevelNativeWindow(), NULL);
+}
+
+void AdvancedOptionsHandler::FileSelected(const FilePath& path, int index,
+ void* params) {
+ content::RecordAction(UserMetricsAction("Options_SetDownloadDirectory"));
+ PrefService* pref_service = Profile::FromWebUI(web_ui_)->GetPrefs();
+ pref_service->SetFilePath(prefs::kDownloadDefaultDirectory, path);
+}
+
+void AdvancedOptionsHandler::OnCloudPrintSetupClosed() {
+#if !defined(OS_CHROMEOS)
+ if (cloud_print_proxy_ui_enabled_)
+ SetupCloudPrintProxySection();
+#endif
+}
+
+void AdvancedOptionsHandler::HandleAutoOpenButton(const ListValue* args) {
+ content::RecordAction(UserMetricsAction("Options_ResetAutoOpenFiles"));
+ DownloadManager* manager =
+ web_ui_->tab_contents()->browser_context()->GetDownloadManager();
+ if (manager)
+ DownloadPrefs::FromDownloadManager(manager)->ResetAutoOpen();
+}
+
+void AdvancedOptionsHandler::HandleMetricsReportingCheckbox(
+ const ListValue* args) {
+#if defined(GOOGLE_CHROME_BUILD) && !defined(OS_CHROMEOS)
+ std::string checked_str = UTF16ToUTF8(ExtractStringValue(args));
+ bool enabled = checked_str == "true";
+ content::RecordAction(
+ enabled ?
+ UserMetricsAction("Options_MetricsReportingCheckbox_Enable") :
+ UserMetricsAction("Options_MetricsReportingCheckbox_Disable"));
+ bool is_enabled = OptionsUtil::ResolveMetricsReportingEnabled(enabled);
+ enable_metrics_recording_.SetValue(is_enabled);
+ SetupMetricsReportingCheckbox();
+#endif
+}
+
+void AdvancedOptionsHandler::HandleDefaultFontSize(const ListValue* args) {
+ int font_size;
+ if (ExtractIntegerValue(args, &font_size)) {
+ if (font_size > 0) {
+ default_font_size_.SetValue(font_size);
+ SetupFontSizeSelector();
+ }
+ }
+}
+
+void AdvancedOptionsHandler::HandleDefaultZoomFactor(const ListValue* args) {
+ double zoom_factor;
+ if (ExtractDoubleValue(args, &zoom_factor)) {
+ default_zoom_level_.SetValue(
+ WebKit::WebView::zoomFactorToZoomLevel(zoom_factor));
+ }
+}
+
+void AdvancedOptionsHandler::HandleCheckRevocationCheckbox(
+ const ListValue* args) {
+ std::string checked_str = UTF16ToUTF8(ExtractStringValue(args));
+ bool enabled = checked_str == "true";
+ content::RecordAction(
+ enabled ?
+ UserMetricsAction("Options_CheckCertRevocation_Enable") :
+ UserMetricsAction("Options_CheckCertRevocation_Disable"));
+ rev_checking_enabled_.SetValue(enabled);
+}
+
+#if !defined(OS_MACOSX) && !defined(OS_CHROMEOS)
+void AdvancedOptionsHandler::HandleBackgroundModeCheckbox(
+ const ListValue* args) {
+ std::string checked_str = UTF16ToUTF8(ExtractStringValue(args));
+ bool enabled = checked_str == "true";
+ content::RecordAction(enabled ?
+ UserMetricsAction("Options_BackgroundMode_Enable") :
+ UserMetricsAction("Options_BackgroundMode_Disable"));
+ background_mode_enabled_.SetValue(enabled);
+}
+
+void AdvancedOptionsHandler::SetupBackgroundModeSettings() {
+ base::FundamentalValue checked(background_mode_enabled_.GetValue());
+ web_ui_->CallJavascriptFunction(
+ "options.AdvancedOptions.SetBackgroundModeCheckboxState", checked);
+}
+#endif
+
+#if !defined(OS_CHROMEOS)
+void AdvancedOptionsHandler::ShowNetworkProxySettings(const ListValue* args) {
+ content::RecordAction(UserMetricsAction("Options_ShowProxySettings"));
+ AdvancedOptionsUtilities::ShowNetworkProxySettings(web_ui_->tab_contents());
+}
+#endif
+
+#if !defined(USE_NSS) && !defined(USE_OPENSSL)
+void AdvancedOptionsHandler::ShowManageSSLCertificates(const ListValue* args) {
+ content::RecordAction(UserMetricsAction("Options_ManageSSLCertificates"));
+ AdvancedOptionsUtilities::ShowManageSSLCertificates(web_ui_->tab_contents());
+}
+#endif
+
+void AdvancedOptionsHandler::ShowCloudPrintManagePage(const ListValue* args) {
+ content::RecordAction(UserMetricsAction("Options_ManageCloudPrinters"));
+ // Open a new tab in the current window for the management page.
+ Profile* profile = Profile::FromWebUI(web_ui_);
+ web_ui_->tab_contents()->OpenURL(
+ CloudPrintURL(profile).GetCloudPrintServiceManageURL(),
+ GURL(), NEW_FOREGROUND_TAB, content::PAGE_TRANSITION_LINK);
+}
+
+#if !defined(OS_CHROMEOS)
+void AdvancedOptionsHandler::ShowCloudPrintSetupDialog(const ListValue* args) {
+ content::RecordAction(UserMetricsAction("Options_EnableCloudPrintProxy"));
+ // Open the connector enable page in the current tab.
+ Profile* profile = Profile::FromWebUI(web_ui_);
+ web_ui_->tab_contents()->OpenURL(
+ CloudPrintURL(profile).GetCloudPrintServiceEnableURL(
+ CloudPrintProxyServiceFactory::GetForProfile(profile)->proxy_id()),
+ GURL(), CURRENT_TAB, content::PAGE_TRANSITION_LINK);
+}
+
+void AdvancedOptionsHandler::HandleDisableCloudPrintProxy(
+ const ListValue* args) {
+ content::RecordAction(
+ UserMetricsAction("Options_DisableCloudPrintProxy"));
+ CloudPrintProxyServiceFactory::GetForProfile(Profile::FromWebUI(web_ui_))->
+ DisableForUser();
+}
+
+void AdvancedOptionsHandler::RefreshCloudPrintStatusFromService() {
+ if (cloud_print_proxy_ui_enabled_)
+ CloudPrintProxyServiceFactory::GetForProfile(Profile::FromWebUI(web_ui_))->
+ RefreshStatusFromService();
+}
+
+void AdvancedOptionsHandler::SetupCloudPrintProxySection() {
+ Profile* profile = Profile::FromWebUI(web_ui_);
+ if (!CloudPrintProxyServiceFactory::GetForProfile(profile)) {
+ cloud_print_proxy_ui_enabled_ = false;
+ RemoveCloudPrintProxySection();
+ return;
+ }
+
+ bool cloud_print_proxy_allowed =
+ !cloud_print_proxy_enabled_.IsManaged() ||
+ cloud_print_proxy_enabled_.GetValue();
+ base::FundamentalValue allowed(cloud_print_proxy_allowed);
+
+ std::string email;
+ if (profile->GetPrefs()->HasPrefPath(prefs::kCloudPrintEmail) &&
+ cloud_print_proxy_allowed) {
+ email = profile->GetPrefs()->GetString(prefs::kCloudPrintEmail);
+ }
+ base::FundamentalValue disabled(email.empty());
+
+ string16 label_str;
+ if (email.empty()) {
+ label_str = l10n_util::GetStringUTF16(
+ IDS_OPTIONS_CLOUD_PRINT_PROXY_DISABLED_LABEL);
+ } else {
+ label_str = l10n_util::GetStringFUTF16(
+ IDS_OPTIONS_CLOUD_PRINT_PROXY_ENABLED_LABEL, UTF8ToUTF16(email));
+ }
+ StringValue label(label_str);
+
+ web_ui_->CallJavascriptFunction(
+ "options.AdvancedOptions.SetupCloudPrintProxySection",
+ disabled, label, allowed);
+}
+
+void AdvancedOptionsHandler::RemoveCloudPrintProxySection() {
+ web_ui_->CallJavascriptFunction(
+ "options.AdvancedOptions.RemoveCloudPrintProxySection");
+}
+
+#endif
+
+void AdvancedOptionsHandler::SetupMetricsReportingCheckbox() {
+#if defined(GOOGLE_CHROME_BUILD) && !defined(OS_CHROMEOS)
+ base::FundamentalValue checked(enable_metrics_recording_.GetValue());
+ base::FundamentalValue disabled(enable_metrics_recording_.IsManaged());
+ web_ui_->CallJavascriptFunction(
+ "options.AdvancedOptions.SetMetricsReportingCheckboxState", checked,
+ disabled);
+#endif
+}
+
+void AdvancedOptionsHandler::SetupMetricsReportingSettingVisibility() {
+#if defined(GOOGLE_CHROME_BUILD) && defined(OS_CHROMEOS)
+ // Don't show the reporting setting if we are in the guest mode.
+ if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kGuestSession)) {
+ base::FundamentalValue visible(false);
+ web_ui_->CallJavascriptFunction(
+ "options.AdvancedOptions.SetMetricsReportingSettingVisibility",
+ visible);
+ }
+#endif
+}
+
+void AdvancedOptionsHandler::SetupFontSizeSelector() {
+ // We're only interested in integer values, so convert to int.
+ base::FundamentalValue font_size(default_font_size_.GetValue());
+ web_ui_->CallJavascriptFunction(
+ "options.AdvancedOptions.SetFontSize", font_size);
+}
+
+void AdvancedOptionsHandler::SetupPageZoomSelector() {
+ PrefService* pref_service = Profile::FromWebUI(web_ui_)->GetPrefs();
+ double default_zoom_level = pref_service->GetDouble(prefs::kDefaultZoomLevel);
+ double default_zoom_factor =
+ WebKit::WebView::zoomLevelToZoomFactor(default_zoom_level);
+
+ // Generate a vector of zoom factors from an array of known presets along with
+ // the default factor added if necessary.
+ std::vector<double> zoom_factors =
+ chrome_page_zoom::PresetZoomFactors(default_zoom_factor);
+
+ // Iterate through the zoom factors and and build the contents of the
+ // selector that will be sent to the javascript handler.
+ // Each item in the list has the following parameters:
+ // 1. Title (string).
+ // 2. Value (double).
+ // 3. Is selected? (bool).
+ ListValue zoom_factors_value;
+ for (std::vector<double>::const_iterator i = zoom_factors.begin();
+ i != zoom_factors.end(); ++i) {
+ ListValue* option = new ListValue();
+ double factor = *i;
+ int percent = static_cast<int>(factor * 100 + 0.5);
+ option->Append(Value::CreateStringValue(
+ l10n_util::GetStringFUTF16Int(IDS_ZOOM_PERCENT, percent)));
+ option->Append(Value::CreateDoubleValue(factor));
+ bool selected = content::ZoomValuesEqual(factor, default_zoom_factor);
+ option->Append(Value::CreateBooleanValue(selected));
+ zoom_factors_value.Append(option);
+ }
+
+ web_ui_->CallJavascriptFunction(
+ "options.AdvancedOptions.SetupPageZoomSelector", zoom_factors_value);
+}
+
+void AdvancedOptionsHandler::SetupAutoOpenFileTypesDisabledAttribute() {
+ // Set the enabled state for the AutoOpenFileTypesResetToDefault button.
+ // We enable the button if the user has any auto-open file types registered.
+ DownloadManager* manager =
+ web_ui_->tab_contents()->browser_context()->GetDownloadManager();
+ bool disabled = !(manager &&
+ DownloadPrefs::FromDownloadManager(manager)->IsAutoOpenUsed());
+ base::FundamentalValue value(disabled);
+ web_ui_->CallJavascriptFunction(
+ "options.AdvancedOptions.SetAutoOpenFileTypesDisabledAttribute", value);
+}
+
+void AdvancedOptionsHandler::SetupProxySettingsSection() {
+#if !defined(OS_CHROMEOS)
+ // Disable the button if proxy settings are managed by a sysadmin or
+ // overridden by an extension.
+ PrefService* pref_service = Profile::FromWebUI(web_ui_)->GetPrefs();
+ const PrefService::Preference* proxy_config =
+ pref_service->FindPreference(prefs::kProxy);
+ bool is_extension_controlled = (proxy_config &&
+ proxy_config->IsExtensionControlled());
+
+ base::FundamentalValue disabled(proxy_prefs_->IsManaged() ||
+ is_extension_controlled);
+
+ // Get the appropriate info string to describe the button.
+ string16 label_str;
+ if (is_extension_controlled) {
+ label_str = l10n_util::GetStringUTF16(IDS_OPTIONS_EXTENSION_PROXIES_LABEL);
+ } else {
+ label_str = l10n_util::GetStringFUTF16(IDS_OPTIONS_SYSTEM_PROXIES_LABEL,
+ l10n_util::GetStringUTF16(IDS_PRODUCT_NAME));
+ }
+ StringValue label(label_str);
+
+ web_ui_->CallJavascriptFunction(
+ "options.AdvancedOptions.SetupProxySettingsSection", disabled, label);
+#endif // !defined(OS_CHROMEOS)
+}
+
+void AdvancedOptionsHandler::SetupSSLConfigSettings() {
+ {
+ base::FundamentalValue checked(rev_checking_enabled_.GetValue());
+ base::FundamentalValue disabled(rev_checking_enabled_.IsManaged());
+ web_ui_->CallJavascriptFunction(
+ "options.AdvancedOptions.SetCheckRevocationCheckboxState", checked,
+ disabled);
+ }
+}
diff --git a/chrome/browser/ui/webui/options2/advanced_options_handler.h b/chrome/browser/ui/webui/options2/advanced_options_handler.h
new file mode 100644
index 0000000..3e404fa
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/advanced_options_handler.h
@@ -0,0 +1,182 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS2_ADVANCED_OPTIONS_HANDLER_H_
+#define CHROME_BROWSER_UI_WEBUI_OPTIONS2_ADVANCED_OPTIONS_HANDLER_H_
+#pragma once
+
+#include "chrome/browser/prefs/pref_member.h"
+#include "chrome/browser/printing/cloud_print/cloud_print_setup_handler.h"
+#include "chrome/browser/ui/select_file_dialog.h"
+#include "chrome/browser/ui/webui/options2/options_ui2.h"
+
+#if !defined(OS_CHROMEOS)
+#include "chrome/browser/prefs/pref_set_observer.h"
+#endif // !defined(OS_CHROMEOS)
+
+class CloudPrintSetupHandler;
+
+// Chrome advanced options page UI handler.
+class AdvancedOptionsHandler
+ : public OptionsPage2UIHandler,
+ public SelectFileDialog::Listener,
+ public CloudPrintSetupHandlerDelegate {
+ public:
+ AdvancedOptionsHandler();
+ virtual ~AdvancedOptionsHandler();
+
+ // OptionsPage2UIHandler implementation.
+ virtual void GetLocalizedValues(DictionaryValue* localized_strings) OVERRIDE;
+ virtual void Initialize() OVERRIDE;
+
+ // WebUIMessageHandler implementation.
+ virtual WebUIMessageHandler* Attach(WebUI* web_ui) OVERRIDE;
+ virtual void RegisterMessages() OVERRIDE;
+
+ // content::NotificationObserver implementation.
+ virtual void Observe(int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) OVERRIDE;
+
+ // SelectFileDialog::Listener implementation
+ virtual void FileSelected(const FilePath& path,
+ int index,
+ void* params) OVERRIDE;
+
+ // CloudPrintSetupHandler::Delegate implementation.
+ virtual void OnCloudPrintSetupClosed() OVERRIDE;
+
+ private:
+ // Callback for the "selectDownloadLocation" message. This will prompt the
+ // user for a destination folder using platform-specific APIs.
+ void HandleSelectDownloadLocation(const ListValue* args);
+
+ // Callback for the "autoOpenFileTypesResetToDefault" message. This will
+ // remove all auto-open file-type settings.
+ void HandleAutoOpenButton(const ListValue* args);
+
+ // Callback for the "metricsReportingCheckboxAction" message. This is called
+ // if the user toggles the metrics reporting checkbox.
+ void HandleMetricsReportingCheckbox(const ListValue* args);
+
+ // Callback for the "defaultFontSizeAction" message. This is called if the
+ // user changes the default font size. |args| is an array that contains
+ // one item, the font size as a numeric value.
+ void HandleDefaultFontSize(const ListValue* args);
+
+ // Callback for the "defaultZoomFactorAction" message. This is called if the
+ // user changes the default zoom factor. |args| is an array that contains
+ // one item, the zoom factor as a numeric value.
+ void HandleDefaultZoomFactor(const ListValue* args);
+
+ // Callback for the "Check for server certificate revocation" checkbox. This
+ // is called if the user toggles the "Check for server certificate revocation"
+ // checkbox.
+ void HandleCheckRevocationCheckbox(const ListValue* args);
+
+ // Callback for the "Use SSL 3.0" checkbox. This is called if the user toggles
+ // the "Use SSL 3.0" checkbox.
+ void HandleUseSSL3Checkbox(const ListValue* args);
+
+ // Callback for the "Use TLS 1.0" checkbox. This is called if the user toggles
+ // the "Use TLS 1.0" checkbox.
+ void HandleUseTLS1Checkbox(const ListValue* args);
+
+#if !defined(OS_CHROMEOS)
+ // Callback for the "showNetworkProxySettings" message. This will invoke
+ // an appropriate dialog for configuring proxy settings.
+ void ShowNetworkProxySettings(const ListValue* args);
+#endif
+
+#if !defined(USE_NSS)
+ // Callback for the "showManageSSLCertificates" message. This will invoke
+ // an appropriate certificate management action based on the platform.
+ void ShowManageSSLCertificates(const ListValue* args);
+#endif
+
+ // Callback for the Cloud Print manage button. This will open a new
+ // tab pointed at the management URL.
+ void ShowCloudPrintManagePage(const ListValue* args);
+
+#if !defined(OS_CHROMEOS)
+ // Callback for the Sign in to Cloud Print button. This will start
+ // the authentication process.
+ void ShowCloudPrintSetupDialog(const ListValue* args);
+
+ // Callback for the Disable Cloud Print button. This will sign out
+ // of cloud print.
+ void HandleDisableCloudPrintProxy(const ListValue* args);
+
+ // Pings the service to send us it's current notion of the enabled state.
+ void RefreshCloudPrintStatusFromService();
+
+ // Setup the enabled or disabled state of the cloud print proxy
+ // management UI.
+ void SetupCloudPrintProxySection();
+
+ // Remove cloud print proxy section if cloud print proxy management UI is
+ // disabled.
+ void RemoveCloudPrintProxySection();
+
+#endif
+
+#if !defined(OS_MACOSX) && !defined(OS_CHROMEOS)
+ // Sets up the checked state for the "Continue running background apps..."
+ // checkbox.
+ void SetupBackgroundModeSettings();
+
+ // Callback for the "Continue running background apps..." checkbox.
+ void HandleBackgroundModeCheckbox(const ListValue* args);
+#endif
+
+ // Setup the checked state for the metrics reporting checkbox.
+ void SetupMetricsReportingCheckbox();
+
+ // Setup the visibility for the metrics reporting setting.
+ void SetupMetricsReportingSettingVisibility();
+
+ // Setup the font size selector control.
+ void SetupFontSizeSelector();
+
+ // Setup the page zoom selector control.
+ void SetupPageZoomSelector();
+
+ // Setup the enabled state of the reset button.
+ void SetupAutoOpenFileTypesDisabledAttribute();
+
+ // Setup the proxy settings section UI.
+ void SetupProxySettingsSection();
+
+ // Setup the checked state for SSL related checkboxes.
+ void SetupSSLConfigSettings();
+
+ scoped_refptr<SelectFileDialog> select_folder_dialog_;
+
+#if !defined(OS_CHROMEOS)
+ BooleanPrefMember enable_metrics_recording_;
+ StringPrefMember cloud_print_proxy_email_;
+ BooleanPrefMember cloud_print_proxy_enabled_;
+ bool cloud_print_proxy_ui_enabled_;
+ scoped_ptr<CloudPrintSetupHandler> cloud_print_setup_handler_;
+#endif
+
+ // SSLConfigService prefs.
+ BooleanPrefMember rev_checking_enabled_;
+
+#if !defined(OS_MACOSX) && !defined(OS_CHROMEOS)
+ BooleanPrefMember background_mode_enabled_;
+#endif
+
+ StringPrefMember auto_open_files_;
+ IntegerPrefMember default_font_size_;
+ DoublePrefMember default_zoom_level_;
+
+#if !defined(OS_CHROMEOS)
+ scoped_ptr<PrefSetObserver> proxy_prefs_;
+#endif // !defined(OS_CHROMEOS)
+
+ DISALLOW_COPY_AND_ASSIGN(AdvancedOptionsHandler);
+};
+
+#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS2_ADVANCED_OPTIONS_HANDLER_H_
diff --git a/chrome/browser/ui/webui/options2/advanced_options_utils.h b/chrome/browser/ui/webui/options2/advanced_options_utils.h
new file mode 100644
index 0000000..edaacf5
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/advanced_options_utils.h
@@ -0,0 +1,25 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS2_ADVANCED_OPTIONS_UTILS_H_
+#define CHROME_BROWSER_UI_WEBUI_OPTIONS2_ADVANCED_OPTIONS_UTILS_H_
+
+#include "base/basictypes.h"
+
+class TabContents;
+
+// Chrome advanced options utility methods.
+class AdvancedOptionsUtilities {
+ public:
+ // Invoke UI for network proxy settings.
+ static void ShowNetworkProxySettings(TabContents*);
+
+ // Invoke UI for SSL certificates.
+ static void ShowManageSSLCertificates(TabContents*);
+
+ private:
+ DISALLOW_IMPLICIT_CONSTRUCTORS(AdvancedOptionsUtilities);
+};
+
+#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS2_ADVANCED_OPTIONS_UTILS_H_
diff --git a/chrome/browser/ui/webui/options2/advanced_options_utils_mac.mm b/chrome/browser/ui/webui/options2/advanced_options_utils_mac.mm
new file mode 100644
index 0000000..c1e5b52
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/advanced_options_utils_mac.mm
@@ -0,0 +1,41 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import <Cocoa/Cocoa.h>
+
+#include "chrome/browser/ui/webui/options2/advanced_options_utils.h"
+
+#include "base/logging.h"
+#include "base/mac/scoped_aedesc.h"
+
+void AdvancedOptionsUtilities::ShowNetworkProxySettings(
+ TabContents* tab_contents) {
+ NSArray* itemsToOpen = [NSArray arrayWithObject:[NSURL fileURLWithPath:
+ @"/System/Library/PreferencePanes/Network.prefPane"]];
+
+ const char* proxyPrefCommand = "Proxies";
+ base::mac::ScopedAEDesc<> openParams;
+ OSStatus status = AECreateDesc('ptru',
+ proxyPrefCommand,
+ strlen(proxyPrefCommand),
+ openParams.OutPointer());
+ LOG_IF(ERROR, status != noErr) << "Failed to create open params: " << status;
+
+ LSLaunchURLSpec launchSpec = { 0 };
+ launchSpec.itemURLs = (CFArrayRef)itemsToOpen;
+ launchSpec.passThruParams = openParams;
+ launchSpec.launchFlags = kLSLaunchAsync | kLSLaunchDontAddToRecents;
+ LSOpenFromURLSpec(&launchSpec, NULL);
+}
+
+void AdvancedOptionsUtilities::ShowManageSSLCertificates(
+ TabContents* tab_contents) {
+ NSString* const kKeychainBundleId = @"com.apple.keychainaccess";
+ [[NSWorkspace sharedWorkspace]
+ launchAppWithBundleIdentifier:kKeychainBundleId
+ options:0L
+ additionalEventParamDescriptor:nil
+ launchIdentifier:nil];
+}
+
diff --git a/chrome/browser/ui/webui/options2/advanced_options_utils_win.cc b/chrome/browser/ui/webui/options2/advanced_options_utils_win.cc
new file mode 100644
index 0000000..5942ff9
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/advanced_options_utils_win.cc
@@ -0,0 +1,68 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/webui/options2/advanced_options_utils.h"
+
+#include <windows.h>
+#include <cryptuiapi.h>
+#pragma comment(lib, "cryptui.lib")
+#include <shellapi.h>
+
+#include "base/bind.h"
+#include "base/file_util.h"
+#include "base/path_service.h"
+#include "base/threading/thread.h"
+#include "chrome/browser/browser_process.h"
+#include "content/browser/tab_contents/tab_contents.h"
+#include "content/browser/tab_contents/tab_contents_view.h"
+#include "content/public/browser/browser_thread.h"
+
+using content::BrowserThread;
+
+// Callback that opens the Internet Options control panel dialog with the
+// Connections tab selected.
+void OpenConnectionDialogCallback() {
+ // Using rundll32 seems better than LaunchConnectionDialog which causes a
+ // new dialog to be made for each call. rundll32 uses the same global
+ // dialog and it seems to share with the shortcut in control panel.
+ FilePath rundll32;
+ PathService::Get(base::DIR_SYSTEM, &rundll32);
+ rundll32 = rundll32.AppendASCII("rundll32.exe");
+
+ FilePath shell32dll;
+ PathService::Get(base::DIR_SYSTEM, &shell32dll);
+ shell32dll = shell32dll.AppendASCII("shell32.dll");
+
+ FilePath inetcpl;
+ PathService::Get(base::DIR_SYSTEM, &inetcpl);
+ inetcpl = inetcpl.AppendASCII("inetcpl.cpl,,4");
+
+ std::wstring args(shell32dll.value());
+ args.append(L",Control_RunDLL ");
+ args.append(inetcpl.value());
+
+ ShellExecute(NULL, L"open", rundll32.value().c_str(), args.c_str(), NULL,
+ SW_SHOWNORMAL);
+}
+
+void AdvancedOptionsUtilities::ShowNetworkProxySettings(
+ TabContents* tab_contents) {
+ DCHECK(BrowserThread::IsMessageLoopValid(BrowserThread::FILE));
+ BrowserThread::PostTask(BrowserThread::FILE,
+ FROM_HERE,
+ base::Bind(&OpenConnectionDialogCallback));
+}
+
+void AdvancedOptionsUtilities::ShowManageSSLCertificates(
+ TabContents* tab_contents) {
+ CRYPTUI_CERT_MGR_STRUCT cert_mgr = { 0 };
+ cert_mgr.dwSize = sizeof(CRYPTUI_CERT_MGR_STRUCT);
+ cert_mgr.hwndParent =
+#if defined(USE_AURA)
+ NULL;
+#else
+ tab_contents->view()->GetTopLevelNativeWindow();
+#endif
+ ::CryptUIDlgCertMgr(&cert_mgr);
+}
diff --git a/chrome/browser/ui/webui/options2/advanced_options_utils_x11.cc b/chrome/browser/ui/webui/options2/advanced_options_utils_x11.cc
new file mode 100644
index 0000000..20579b3
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/advanced_options_utils_x11.cc
@@ -0,0 +1,140 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#if !defined(OS_CHROMEOS)
+
+#include "chrome/browser/ui/webui/options2/advanced_options_utils.h"
+
+#include "base/bind.h"
+#include "base/environment.h"
+#include "base/file_path.h"
+#include "base/file_util.h"
+#include "base/nix/xdg_util.h"
+#include "base/process_util.h"
+#include "base/string_util.h"
+#include "content/browser/tab_contents/tab_contents.h"
+#include "content/public/browser/browser_thread.h"
+
+using content::BrowserThread;
+
+// Command used to configure GNOME 2 proxy settings.
+const char* kGNOME2ProxyConfigCommand[] = {"gnome-network-properties", NULL};
+// In GNOME 3, we might need to run gnome-control-center instead. We try this
+// only after gnome-network-properties is not found, because older GNOME also
+// has this but it doesn't do the same thing. See below where we use it.
+const char* kGNOME3ProxyConfigCommand[] = {"gnome-control-center", "network",
+ NULL};
+// KDE3 and KDE4 are only slightly different, but incompatible. Go figure.
+const char* kKDE3ProxyConfigCommand[] = {"kcmshell", "proxy", NULL};
+const char* kKDE4ProxyConfigCommand[] = {"kcmshell4", "proxy", NULL};
+
+// The URL for Linux proxy configuration help when not running under a
+// supported desktop environment.
+const char kLinuxProxyConfigUrl[] = "about:linux-proxy-config";
+
+namespace {
+
+// Show the proxy config URL in the given tab.
+void ShowLinuxProxyConfigUrl(TabContents* tab_contents) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ scoped_ptr<base::Environment> env(base::Environment::Create());
+ const char* name = base::nix::GetDesktopEnvironmentName(env.get());
+ if (name)
+ LOG(ERROR) << "Could not find " << name << " network settings in $PATH";
+ tab_contents->OpenURL(GURL(kLinuxProxyConfigUrl), GURL(),
+ NEW_FOREGROUND_TAB, content::PAGE_TRANSITION_LINK);
+}
+
+// Start the given proxy configuration utility.
+bool StartProxyConfigUtil(TabContents* tab_contents, const char* command[]) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+ // base::LaunchProcess() returns true ("success") if the fork()
+ // succeeds, but not necessarily the exec(). We'd like to be able to
+ // use StartProxyConfigUtil() to search possible options and stop on
+ // success, so we search $PATH first to predict whether the exec is
+ // expected to succeed.
+ // TODO(mdm): this is a useful check, and is very similar to some
+ // code in proxy_config_service_linux.cc. It should probably be in
+ // base:: somewhere.
+ scoped_ptr<base::Environment> env(base::Environment::Create());
+ std::string path;
+ if (!env->GetVar("PATH", &path)) {
+ LOG(ERROR) << "No $PATH variable. Assuming no " << command[0] << ".";
+ return false;
+ }
+ std::vector<std::string> paths;
+ Tokenize(path, ":", &paths);
+ bool found = false;
+ for (size_t i = 0; i < paths.size(); ++i) {
+ FilePath file(paths[i]);
+ if (file_util::PathExists(file.Append(command[0]))) {
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ return false;
+ std::vector<std::string> argv;
+ for (size_t i = 0; command[i]; ++i)
+ argv.push_back(command[i]);
+ base::ProcessHandle handle;
+ if (!base::LaunchProcess(argv, base::LaunchOptions(), &handle)) {
+ LOG(ERROR) << "StartProxyConfigUtil failed to start " << command[0];
+ return false;
+ }
+ base::EnsureProcessGetsReaped(handle);
+ return true;
+}
+
+// Detect, and if possible, start the appropriate proxy config utility. On
+// failure to do so, show the Linux proxy config URL in a new tab instead.
+void DetectAndStartProxyConfigUtil(TabContents* tab_contents) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+ scoped_ptr<base::Environment> env(base::Environment::Create());
+
+ bool launched = false;
+ switch (base::nix::GetDesktopEnvironment(env.get())) {
+ case base::nix::DESKTOP_ENVIRONMENT_GNOME: {
+ launched = StartProxyConfigUtil(tab_contents, kGNOME2ProxyConfigCommand);
+ if (!launched) {
+ // We try this second, even though it's the newer way, because this
+ // command existed in older versions of GNOME, but it didn't do the
+ // same thing. The older command is gone though, so this should do
+ // the right thing. (Also some distributions have blurred the lines
+ // between GNOME 2 and 3, so we can't necessarily detect what the
+ // right thing is based on indications of which version we have.)
+ launched = StartProxyConfigUtil(tab_contents,
+ kGNOME3ProxyConfigCommand);
+ }
+ break;
+ }
+
+ case base::nix::DESKTOP_ENVIRONMENT_KDE3:
+ launched = StartProxyConfigUtil(tab_contents, kKDE3ProxyConfigCommand);
+ break;
+
+ case base::nix::DESKTOP_ENVIRONMENT_KDE4:
+ launched = StartProxyConfigUtil(tab_contents, kKDE4ProxyConfigCommand);
+ break;
+
+ case base::nix::DESKTOP_ENVIRONMENT_XFCE:
+ case base::nix::DESKTOP_ENVIRONMENT_OTHER:
+ break;
+ }
+
+ if (launched)
+ return;
+ BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
+ base::Bind(&ShowLinuxProxyConfigUrl, tab_contents));
+}
+
+} // anonymous namespace
+
+void AdvancedOptionsUtilities::ShowNetworkProxySettings(
+ TabContents* tab_contents) {
+ BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
+ base::Bind(&DetectAndStartProxyConfigUtil, tab_contents));
+}
+
+#endif // !defined(OS_CHROMEOS)
diff --git a/chrome/browser/ui/webui/options2/autofill_options_browsertest.js b/chrome/browser/ui/webui/options2/autofill_options_browsertest.js
new file mode 100644
index 0000000..d9a5c9b
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/autofill_options_browsertest.js
@@ -0,0 +1,24 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * TestFixture for autofill options WebUI testing.
+ * @extends {testing.Test}
+ * @constructor
+ **/
+function AutofillOptionsWebUITest() {}
+
+AutofillOptionsWebUITest.prototype = {
+ __proto__: testing.Test.prototype,
+
+ /**
+ * Browse to autofill options.
+ **/
+ browsePreload: 'chrome://settings/autofill',
+};
+
+// Test opening the autofill options has correct location.
+TEST_F('AutofillOptionsWebUITest', 'testOpenAutofillOptions', function() {
+ assertEquals(this.browsePreload, document.location.href);
+});
diff --git a/chrome/browser/ui/webui/options2/autofill_options_handler.cc b/chrome/browser/ui/webui/options2/autofill_options_handler.cc
new file mode 100644
index 0000000..f751e635
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/autofill_options_handler.cc
@@ -0,0 +1,614 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/webui/options2/autofill_options_handler.h"
+
+#include <vector>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/logging.h"
+#include "base/string16.h"
+#include "base/string_number_conversions.h"
+#include "base/utf_string_conversions.h"
+#include "base/values.h"
+#include "chrome/browser/autofill/autofill_country.h"
+#include "chrome/browser/autofill/autofill_profile.h"
+#include "chrome/browser/autofill/credit_card.h"
+#include "chrome/browser/autofill/personal_data_manager.h"
+#include "chrome/browser/autofill/personal_data_manager_factory.h"
+#include "chrome/browser/autofill/phone_number_i18n.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/webui/web_ui_util.h"
+#include "chrome/common/guid.h"
+#include "grit/generated_resources.h"
+#include "grit/webkit_resources.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace {
+
+// Converts a credit card type to the appropriate resource ID of the CC icon.
+int CreditCardTypeToResourceID(const std::string& type) {
+ if (type == kAmericanExpressCard)
+ return IDR_AUTOFILL_CC_AMEX;
+ else if (type == kDinersCard)
+ return IDR_AUTOFILL_CC_DINERS;
+ else if (type == kDiscoverCard)
+ return IDR_AUTOFILL_CC_DISCOVER;
+ else if (type == kGenericCard)
+ return IDR_AUTOFILL_CC_GENERIC;
+ else if (type == kJCBCard)
+ return IDR_AUTOFILL_CC_JCB;
+ else if (type == kMasterCard)
+ return IDR_AUTOFILL_CC_MASTERCARD;
+ else if (type == kSoloCard)
+ return IDR_AUTOFILL_CC_SOLO;
+ else if (type == kVisaCard)
+ return IDR_AUTOFILL_CC_VISA;
+
+ NOTREACHED();
+ return 0;
+}
+
+// Converts a credit card type to the appropriate localized card type.
+string16 LocalizedCreditCardType(const std::string& type) {
+ if (type == kAmericanExpressCard)
+ return l10n_util::GetStringUTF16(IDS_AUTOFILL_CC_AMEX);
+ else if (type == kDinersCard)
+ return l10n_util::GetStringUTF16(IDS_AUTOFILL_CC_DINERS);
+ else if (type == kDiscoverCard)
+ return l10n_util::GetStringUTF16(IDS_AUTOFILL_CC_DISCOVER);
+ else if (type == kGenericCard)
+ return l10n_util::GetStringUTF16(IDS_AUTOFILL_CC_GENERIC);
+ else if (type == kJCBCard)
+ return l10n_util::GetStringUTF16(IDS_AUTOFILL_CC_JCB);
+ else if (type == kMasterCard)
+ return l10n_util::GetStringUTF16(IDS_AUTOFILL_CC_MASTERCARD);
+ else if (type == kSoloCard)
+ return l10n_util::GetStringUTF16(IDS_AUTOFILL_CC_SOLO);
+ else if (type == kVisaCard)
+ return l10n_util::GetStringUTF16(IDS_AUTOFILL_CC_VISA);
+
+ NOTREACHED();
+ return string16();
+}
+
+// Returns a dictionary that maps country codes to data for the country.
+DictionaryValue* GetCountryData() {
+ std::string app_locale = AutofillCountry::ApplicationLocale();
+ std::vector<std::string> country_codes;
+ AutofillCountry::GetAvailableCountries(&country_codes);
+
+ DictionaryValue* country_data = new DictionaryValue();
+ for (size_t i = 0; i < country_codes.size(); ++i) {
+ const AutofillCountry country(country_codes[i], app_locale);
+
+ DictionaryValue* details = new DictionaryValue();
+ details->SetString("name", country.name());
+ details->SetString("postalCodeLabel", country.postal_code_label());
+ details->SetString("stateLabel", country.state_label());
+
+ country_data->Set(country.country_code(), details);
+ }
+
+ return country_data;
+}
+
+// Get the multi-valued element for |type| and return it in |ListValue| form.
+void GetValueList(const AutofillProfile& profile,
+ AutofillFieldType type,
+ scoped_ptr<ListValue>* list) {
+ list->reset(new ListValue);
+
+ std::vector<string16> values;
+ profile.GetMultiInfo(type, &values);
+
+ // |GetMultiInfo()| always returns at least one, potentially empty, item.
+ if (values.size() == 1 && values.front().empty())
+ return;
+
+ for (size_t i = 0; i < values.size(); ++i) {
+ (*list)->Set(i, Value::CreateStringValue(values[i]));
+ }
+}
+
+// Set the multi-valued element for |type| from input |list| values.
+void SetValueList(const ListValue* list,
+ AutofillFieldType type,
+ AutofillProfile* profile) {
+ std::vector<string16> values(list->GetSize());
+ for (size_t i = 0; i < list->GetSize(); ++i) {
+ string16 value;
+ if (list->GetString(i, &value))
+ values[i] = value;
+ }
+ profile->SetMultiInfo(type, values);
+}
+
+// Get the multi-valued element for |type| and return it in |ListValue| form.
+void GetNameList(const AutofillProfile& profile,
+ scoped_ptr<ListValue>* names) {
+ names->reset(new ListValue);
+
+ std::vector<string16> first_names;
+ std::vector<string16> middle_names;
+ std::vector<string16> last_names;
+ profile.GetMultiInfo(NAME_FIRST, &first_names);
+ profile.GetMultiInfo(NAME_MIDDLE, &middle_names);
+ profile.GetMultiInfo(NAME_LAST, &last_names);
+ DCHECK_EQ(first_names.size(), middle_names.size());
+ DCHECK_EQ(first_names.size(), last_names.size());
+
+ // |GetMultiInfo()| always returns at least one, potentially empty, item.
+ if (first_names.size() == 1 && first_names.front().empty() &&
+ middle_names.front().empty() && last_names.front().empty()) {
+ return;
+ }
+
+ for (size_t i = 0; i < first_names.size(); ++i) {
+ ListValue* name = new ListValue; // owned by |list|
+ name->Set(0, Value::CreateStringValue(first_names[i]));
+ name->Set(1, Value::CreateStringValue(middle_names[i]));
+ name->Set(2, Value::CreateStringValue(last_names[i]));
+ (*names)->Set(i, name);
+ }
+}
+
+// Set the multi-valued element for |type| from input |list| values.
+void SetNameList(const ListValue* names,
+ AutofillProfile* profile) {
+ const size_t size = names->GetSize();
+ std::vector<string16> first_names(size);
+ std::vector<string16> middle_names(size);
+ std::vector<string16> last_names(size);
+
+ for (size_t i = 0; i < size; ++i) {
+ ListValue* name;
+ bool success = names->GetList(i, &name);
+ DCHECK(success);
+
+ string16 first_name;
+ success = name->GetString(0, &first_name);
+ DCHECK(success);
+ first_names[i] = first_name;
+
+ string16 middle_name;
+ success = name->GetString(1, &middle_name);
+ DCHECK(success);
+ middle_names[i] = middle_name;
+
+ string16 last_name;
+ success = name->GetString(2, &last_name);
+ DCHECK(success);
+ last_names[i] = last_name;
+ }
+
+ profile->SetMultiInfo(NAME_FIRST, first_names);
+ profile->SetMultiInfo(NAME_MIDDLE, middle_names);
+ profile->SetMultiInfo(NAME_LAST, last_names);
+}
+
+// Pulls the phone number |index|, |phone_number_list|, and |country_code| from
+// the |args| input.
+void ExtractPhoneNumberInformation(const ListValue* args,
+ size_t* index,
+ ListValue** phone_number_list,
+ std::string* country_code) {
+ // Retrieve index as a |double|, as that is how it comes across from
+ // JavaScript.
+ double number = 0.0;
+ if (!args->GetDouble(0, &number)) {
+ NOTREACHED();
+ return;
+ }
+ *index = number;
+
+ if (!args->GetList(1, phone_number_list)) {
+ NOTREACHED();
+ return;
+ }
+
+ if (!args->GetString(2, country_code)) {
+ NOTREACHED();
+ return;
+ }
+}
+
+// Searches the |list| for the value at |index|. If this value is present
+// in any of the rest of the list, then the item (at |index|) is removed.
+// The comparison of phone number values is done on normalized versions of the
+// phone number values.
+void RemoveDuplicatePhoneNumberAtIndex(size_t index,
+ const std::string& country_code,
+ ListValue* list) {
+ string16 new_value;
+ if (!list->GetString(index, &new_value)) {
+ NOTREACHED() << "List should have a value at index " << index;
+ return;
+ }
+
+ bool is_duplicate = false;
+ for (size_t i = 0; i < list->GetSize() && !is_duplicate; ++i) {
+ if (i == index)
+ continue;
+
+ string16 existing_value;
+ if (!list->GetString(i, &existing_value)) {
+ NOTREACHED() << "List should have a value at index " << i;
+ continue;
+ }
+ is_duplicate = autofill_i18n::PhoneNumbersMatch(new_value,
+ existing_value,
+ country_code);
+ }
+
+ if (is_duplicate)
+ list->Remove(index, NULL);
+}
+
+void ValidatePhoneArguments(const ListValue* args, ListValue** list) {
+ size_t index = 0;
+ std::string country_code;
+ ExtractPhoneNumberInformation(args, &index, list, &country_code);
+
+ RemoveDuplicatePhoneNumberAtIndex(index, country_code, *list);
+}
+
+} // namespace
+
+AutofillOptionsHandler::AutofillOptionsHandler()
+ : personal_data_(NULL) {
+}
+
+AutofillOptionsHandler::~AutofillOptionsHandler() {
+ if (personal_data_)
+ personal_data_->RemoveObserver(this);
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// OptionsPageUIHandler implementation:
+void AutofillOptionsHandler::GetLocalizedValues(
+ DictionaryValue* localized_strings) {
+ DCHECK(localized_strings);
+
+ static OptionsStringResource resources[] = {
+ { "autofillAddresses", IDS_AUTOFILL_ADDRESSES_GROUP_NAME },
+ { "autofillCreditCards", IDS_AUTOFILL_CREDITCARDS_GROUP_NAME },
+ { "autofillAddAddress", IDS_AUTOFILL_ADD_ADDRESS_BUTTON },
+ { "autofillAddCreditCard", IDS_AUTOFILL_ADD_CREDITCARD_BUTTON },
+ { "autofillEditProfileButton", IDS_AUTOFILL_EDIT_PROFILE_BUTTON },
+ { "helpButton", IDS_AUTOFILL_HELP_LABEL },
+ { "addAddressTitle", IDS_AUTOFILL_ADD_ADDRESS_CAPTION },
+ { "editAddressTitle", IDS_AUTOFILL_EDIT_ADDRESS_CAPTION },
+ { "addCreditCardTitle", IDS_AUTOFILL_ADD_CREDITCARD_CAPTION },
+ { "editCreditCardTitle", IDS_AUTOFILL_EDIT_CREDITCARD_CAPTION },
+#if defined(OS_MACOSX)
+ { "auxiliaryProfilesEnabled", IDS_AUTOFILL_USE_MAC_ADDRESS_BOOK },
+#endif // defined(OS_MACOSX)
+ };
+
+ RegisterStrings(localized_strings, resources, arraysize(resources));
+ RegisterTitle(localized_strings, "autofillOptionsPage",
+ IDS_AUTOFILL_OPTIONS_TITLE);
+
+ SetAddressOverlayStrings(localized_strings);
+ SetCreditCardOverlayStrings(localized_strings);
+}
+
+void AutofillOptionsHandler::Initialize() {
+ personal_data_ = PersonalDataManagerFactory::GetForProfile(
+ Profile::FromWebUI(web_ui_));
+ // personal_data_ is NULL in guest mode on Chrome OS.
+ if (personal_data_) {
+ personal_data_->SetObserver(this);
+ LoadAutofillData();
+ }
+}
+
+void AutofillOptionsHandler::RegisterMessages() {
+ web_ui_->RegisterMessageCallback(
+ "removeAddress",
+ base::Bind(&AutofillOptionsHandler::RemoveAddress,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback(
+ "removeCreditCard",
+ base::Bind(&AutofillOptionsHandler::RemoveCreditCard,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback(
+ "loadAddressEditor",
+ base::Bind(&AutofillOptionsHandler::LoadAddressEditor,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback(
+ "loadCreditCardEditor",
+ base::Bind(&AutofillOptionsHandler::LoadCreditCardEditor,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback(
+ "setAddress",
+ base::Bind(&AutofillOptionsHandler::SetAddress, base::Unretained(this)));
+ web_ui_->RegisterMessageCallback(
+ "setCreditCard",
+ base::Bind(&AutofillOptionsHandler::SetCreditCard,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback(
+ "validatePhoneNumbers",
+ base::Bind(&AutofillOptionsHandler::ValidatePhoneNumbers,
+ base::Unretained(this)));
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// PersonalDataManagerObserver implementation:
+void AutofillOptionsHandler::OnPersonalDataChanged() {
+ LoadAutofillData();
+}
+
+void AutofillOptionsHandler::SetAddressOverlayStrings(
+ DictionaryValue* localized_strings) {
+ localized_strings->SetString("autofillEditAddressTitle",
+ l10n_util::GetStringUTF16(IDS_AUTOFILL_EDIT_ADDRESS_CAPTION));
+ localized_strings->SetString("autofillFirstNameLabel",
+ l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_FIRST_NAME));
+ localized_strings->SetString("autofillMiddleNameLabel",
+ l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_MIDDLE_NAME));
+ localized_strings->SetString("autofillLastNameLabel",
+ l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_LAST_NAME));
+ localized_strings->SetString("autofillCompanyNameLabel",
+ l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_COMPANY_NAME));
+ localized_strings->SetString("autofillAddrLine1Label",
+ l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_ADDRESS_LINE_1));
+ localized_strings->SetString("autofillAddrLine2Label",
+ l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_ADDRESS_LINE_2));
+ localized_strings->SetString("autofillCityLabel",
+ l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_CITY));
+ localized_strings->SetString("autofillCountryLabel",
+ l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_COUNTRY));
+ localized_strings->SetString("autofillPhoneLabel",
+ l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_PHONE));
+ localized_strings->SetString("autofillEmailLabel",
+ l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_EMAIL));
+ localized_strings->SetString("autofillAddFirstNamePlaceholder",
+ l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_ADD_FIRST_NAME));
+ localized_strings->SetString("autofillAddMiddleNamePlaceholder",
+ l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_ADD_MIDDLE_NAME));
+ localized_strings->SetString("autofillAddLastNamePlaceholder",
+ l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_ADD_LAST_NAME));
+ localized_strings->SetString("autofillAddPhonePlaceholder",
+ l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_ADD_PHONE));
+ localized_strings->SetString("autofillAddEmailPlaceholder",
+ l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_ADD_EMAIL));
+
+ std::string app_locale = AutofillCountry::ApplicationLocale();
+ std::string default_country_code =
+ AutofillCountry::CountryCodeForLocale(app_locale);
+ localized_strings->SetString("defaultCountryCode", default_country_code);
+ localized_strings->Set("autofillCountryData", GetCountryData());
+}
+
+void AutofillOptionsHandler::SetCreditCardOverlayStrings(
+ DictionaryValue* localized_strings) {
+ localized_strings->SetString("autofillEditCreditCardTitle",
+ l10n_util::GetStringUTF16(IDS_AUTOFILL_EDIT_CREDITCARD_CAPTION));
+ localized_strings->SetString("nameOnCardLabel",
+ l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_NAME_ON_CARD));
+ localized_strings->SetString("creditCardNumberLabel",
+ l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_CREDIT_CARD_NUMBER));
+ localized_strings->SetString("creditCardExpirationDateLabel",
+ l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_EXPIRATION_DATE));
+}
+
+void AutofillOptionsHandler::LoadAutofillData() {
+ if (!personal_data_->IsDataLoaded())
+ return;
+
+ ListValue addresses;
+ for (std::vector<AutofillProfile*>::const_iterator i =
+ personal_data_->web_profiles().begin();
+ i != personal_data_->web_profiles().end(); ++i) {
+ ListValue* entry = new ListValue();
+ entry->Append(new StringValue((*i)->guid()));
+ entry->Append(new StringValue((*i)->Label()));
+ addresses.Append(entry);
+ }
+
+ web_ui_->CallJavascriptFunction("AutofillOptions.setAddressList", addresses);
+
+ ListValue credit_cards;
+ for (std::vector<CreditCard*>::const_iterator i =
+ personal_data_->credit_cards().begin();
+ i != personal_data_->credit_cards().end(); ++i) {
+ ListValue* entry = new ListValue();
+ entry->Append(new StringValue((*i)->guid()));
+ entry->Append(new StringValue((*i)->Label()));
+ int res = CreditCardTypeToResourceID((*i)->type());
+ entry->Append(
+ new StringValue(web_ui_util::GetImageDataUrlFromResource(res)));
+ entry->Append(new StringValue(LocalizedCreditCardType((*i)->type())));
+ credit_cards.Append(entry);
+ }
+
+ web_ui_->CallJavascriptFunction("AutofillOptions.setCreditCardList",
+ credit_cards);
+}
+
+void AutofillOptionsHandler::RemoveAddress(const ListValue* args) {
+ DCHECK(personal_data_->IsDataLoaded());
+
+ std::string guid;
+ if (!args->GetString(0, &guid)) {
+ NOTREACHED();
+ return;
+ }
+
+ personal_data_->RemoveProfile(guid);
+}
+
+void AutofillOptionsHandler::RemoveCreditCard(const ListValue* args) {
+ DCHECK(personal_data_->IsDataLoaded());
+
+ std::string guid;
+ if (!args->GetString(0, &guid)) {
+ NOTREACHED();
+ return;
+ }
+
+ personal_data_->RemoveCreditCard(guid);
+}
+
+void AutofillOptionsHandler::LoadAddressEditor(const ListValue* args) {
+ DCHECK(personal_data_->IsDataLoaded());
+
+ std::string guid;
+ if (!args->GetString(0, &guid)) {
+ NOTREACHED();
+ return;
+ }
+
+ AutofillProfile* profile = personal_data_->GetProfileByGUID(guid);
+ if (!profile) {
+ // There is a race where a user can click once on the close button and
+ // quickly click again on the list item before the item is removed (since
+ // the list is not updated until the model tells the list an item has been
+ // removed). This will activate the editor for a profile that has been
+ // removed. Do nothing in that case.
+ return;
+ }
+
+ DictionaryValue address;
+ address.SetString("guid", profile->guid());
+ scoped_ptr<ListValue> list;
+ GetNameList(*profile, &list);
+ address.Set("fullName", list.release());
+ address.SetString("companyName", profile->GetInfo(COMPANY_NAME));
+ address.SetString("addrLine1", profile->GetInfo(ADDRESS_HOME_LINE1));
+ address.SetString("addrLine2", profile->GetInfo(ADDRESS_HOME_LINE2));
+ address.SetString("city", profile->GetInfo(ADDRESS_HOME_CITY));
+ address.SetString("state", profile->GetInfo(ADDRESS_HOME_STATE));
+ address.SetString("postalCode", profile->GetInfo(ADDRESS_HOME_ZIP));
+ address.SetString("country", profile->CountryCode());
+ GetValueList(*profile, PHONE_HOME_WHOLE_NUMBER, &list);
+ address.Set("phone", list.release());
+ GetValueList(*profile, EMAIL_ADDRESS, &list);
+ address.Set("email", list.release());
+
+ web_ui_->CallJavascriptFunction("AutofillOptions.editAddress", address);
+}
+
+void AutofillOptionsHandler::LoadCreditCardEditor(const ListValue* args) {
+ DCHECK(personal_data_->IsDataLoaded());
+
+ std::string guid;
+ if (!args->GetString(0, &guid)) {
+ NOTREACHED();
+ return;
+ }
+
+ CreditCard* credit_card = personal_data_->GetCreditCardByGUID(guid);
+ if (!credit_card) {
+ // There is a race where a user can click once on the close button and
+ // quickly click again on the list item before the item is removed (since
+ // the list is not updated until the model tells the list an item has been
+ // removed). This will activate the editor for a profile that has been
+ // removed. Do nothing in that case.
+ return;
+ }
+
+ DictionaryValue credit_card_data;
+ credit_card_data.SetString("guid", credit_card->guid());
+ credit_card_data.SetString("nameOnCard",
+ credit_card->GetInfo(CREDIT_CARD_NAME));
+ credit_card_data.SetString("creditCardNumber",
+ credit_card->GetInfo(CREDIT_CARD_NUMBER));
+ credit_card_data.SetString("expirationMonth",
+ credit_card->GetInfo(CREDIT_CARD_EXP_MONTH));
+ credit_card_data.SetString(
+ "expirationYear",
+ credit_card->GetInfo(CREDIT_CARD_EXP_4_DIGIT_YEAR));
+
+ web_ui_->CallJavascriptFunction("AutofillOptions.editCreditCard",
+ credit_card_data);
+}
+
+void AutofillOptionsHandler::SetAddress(const ListValue* args) {
+ if (!personal_data_->IsDataLoaded())
+ return;
+
+ std::string guid;
+ if (!args->GetString(0, &guid)) {
+ NOTREACHED();
+ return;
+ }
+
+ AutofillProfile profile(guid);
+
+ std::string country_code;
+ string16 value;
+ ListValue* list_value;
+ if (args->GetList(1, &list_value))
+ SetNameList(list_value, &profile);
+ if (args->GetString(2, &value))
+ profile.SetInfo(COMPANY_NAME, value);
+ if (args->GetString(3, &value))
+ profile.SetInfo(ADDRESS_HOME_LINE1, value);
+ if (args->GetString(4, &value))
+ profile.SetInfo(ADDRESS_HOME_LINE2, value);
+ if (args->GetString(5, &value))
+ profile.SetInfo(ADDRESS_HOME_CITY, value);
+ if (args->GetString(6, &value))
+ profile.SetInfo(ADDRESS_HOME_STATE, value);
+ if (args->GetString(7, &value))
+ profile.SetInfo(ADDRESS_HOME_ZIP, value);
+ if (args->GetString(8, &country_code))
+ profile.SetCountryCode(country_code);
+ if (args->GetList(9, &list_value))
+ SetValueList(list_value, PHONE_HOME_WHOLE_NUMBER, &profile);
+ if (args->GetList(10, &list_value))
+ SetValueList(list_value, EMAIL_ADDRESS, &profile);
+
+ if (!guid::IsValidGUID(profile.guid())) {
+ profile.set_guid(guid::GenerateGUID());
+ personal_data_->AddProfile(profile);
+ } else {
+ personal_data_->UpdateProfile(profile);
+ }
+}
+
+void AutofillOptionsHandler::SetCreditCard(const ListValue* args) {
+ if (!personal_data_->IsDataLoaded())
+ return;
+
+ std::string guid;
+ if (!args->GetString(0, &guid)) {
+ NOTREACHED();
+ return;
+ }
+
+ CreditCard credit_card(guid);
+
+ string16 value;
+ if (args->GetString(1, &value))
+ credit_card.SetInfo(CREDIT_CARD_NAME, value);
+ if (args->GetString(2, &value))
+ credit_card.SetInfo(CREDIT_CARD_NUMBER, value);
+ if (args->GetString(3, &value))
+ credit_card.SetInfo(CREDIT_CARD_EXP_MONTH, value);
+ if (args->GetString(4, &value))
+ credit_card.SetInfo(CREDIT_CARD_EXP_4_DIGIT_YEAR, value);
+
+ if (!guid::IsValidGUID(credit_card.guid())) {
+ credit_card.set_guid(guid::GenerateGUID());
+ personal_data_->AddCreditCard(credit_card);
+ } else {
+ personal_data_->UpdateCreditCard(credit_card);
+ }
+}
+
+void AutofillOptionsHandler::ValidatePhoneNumbers(const ListValue* args) {
+ if (!personal_data_->IsDataLoaded())
+ return;
+
+ ListValue* list_value = NULL;
+ ValidatePhoneArguments(args, &list_value);
+
+ web_ui_->CallJavascriptFunction(
+ "AutofillEditAddressOverlay.setValidatedPhoneNumbers", *list_value);
+}
diff --git a/chrome/browser/ui/webui/options2/autofill_options_handler.h b/chrome/browser/ui/webui/options2/autofill_options_handler.h
new file mode 100644
index 0000000..4eeb90a
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/autofill_options_handler.h
@@ -0,0 +1,89 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS2_AUTOFILL_OPTIONS_HANDLER_H_
+#define CHROME_BROWSER_UI_WEBUI_OPTIONS2_AUTOFILL_OPTIONS_HANDLER_H_
+
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "chrome/browser/autofill/personal_data_manager_observer.h"
+#include "chrome/browser/ui/webui/options2/options_ui2.h"
+
+class PersonalDataManager;
+
+namespace base {
+class DictionaryValue;
+class ListValue;
+}
+
+class AutofillOptionsHandler : public OptionsPage2UIHandler,
+ public PersonalDataManagerObserver {
+ public:
+ AutofillOptionsHandler();
+ virtual ~AutofillOptionsHandler();
+
+ // OptionsPage2UIHandler implementation.
+ virtual void GetLocalizedValues(
+ base::DictionaryValue* localized_strings) OVERRIDE;
+ virtual void Initialize() OVERRIDE;
+ virtual void RegisterMessages() OVERRIDE;
+
+ // PersonalDataManagerObserver implementation.
+ virtual void OnPersonalDataChanged() OVERRIDE;
+
+ private:
+ // Loads the strings for the address and credit card overlays.
+ void SetAddressOverlayStrings(base::DictionaryValue* localized_strings);
+ void SetCreditCardOverlayStrings(base::DictionaryValue* localized_strings);
+
+ // Loads Autofill addresses and credit cards using the PersonalDataManager.
+ void LoadAutofillData();
+
+ // Removes an address from the PersonalDataManager.
+ // |args| - A string, the GUID of the address to remove.
+ void RemoveAddress(const base::ListValue* args);
+
+ // Removes a credit card from the PersonalDataManager.
+ // |args| - A string, the GUID of the credit card to remove.
+ void RemoveCreditCard(const base::ListValue* args);
+
+ // Requests profile data for a specific address. Calls into WebUI with the
+ // loaded profile data to open the address editor.
+ // |args| - A string, the GUID of the address to load.
+ void LoadAddressEditor(const base::ListValue* args);
+
+ // Requests profile data for a specific credit card. Calls into WebUI with the
+ // loaded profile data to open the credit card editor.
+ // |args| - A string, the GUID of the credit card to load.
+ void LoadCreditCardEditor(const base::ListValue* args);
+
+ // Adds or updates an address, depending on the GUID of the profile. If the
+ // GUID is empty, a new address is added to the WebDatabase; otherwise, the
+ // address with the matching GUID is updated. Called from WebUI.
+ // |args| - an array containing the GUID of the address followed by the
+ // address data.
+ void SetAddress(const base::ListValue* args);
+
+ // Adds or updates a credit card, depending on the GUID of the profile. If the
+ // GUID is empty, a new credit card is added to the WebDatabase; otherwise,
+ // the credit card with the matching GUID is updated. Called from WebUI.
+ // |args| - an array containing the GUID of the credit card followed by the
+ // credit card data.
+ void SetCreditCard(const base::ListValue* args);
+
+ // Validates a list of phone numbers. The resulting validated list of
+ // numbers is then sent back to the WebUI.
+ // |args| - an array containing the index of the modified or added number, the
+ // array of numbers, and the country code string set on the profile.
+ void ValidatePhoneNumbers(const base::ListValue* args);
+
+ // The personal data manager, used to load Autofill profiles and credit cards.
+ // Unowned pointer, may not be NULL.
+ PersonalDataManager* personal_data_;
+
+ DISALLOW_COPY_AND_ASSIGN(AutofillOptionsHandler);
+};
+
+#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS2_AUTOFILL_OPTIONS_HANDLER_H_
diff --git a/chrome/browser/ui/webui/options2/browser_options_browsertest.js b/chrome/browser/ui/webui/options2/browser_options_browsertest.js
new file mode 100644
index 0000000..e983b27
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/browser_options_browsertest.js
@@ -0,0 +1,24 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * TestFixture for browser options WebUI testing.
+ * @extends {testing.Test}
+ * @constructor
+ **/
+function BrowserOptionsWebUITest() {}
+
+BrowserOptionsWebUITest.prototype = {
+ __proto__: testing.Test.prototype,
+
+ /**
+ * Browse to browser options.
+ **/
+ browsePreload: 'chrome://settings/browser',
+};
+
+// Test opening the browser options has correct location.
+TEST_F('BrowserOptionsWebUITest', 'testOpenBrowserOptions', function() {
+ assertEquals(this.browsePreload, document.location.href);
+});
diff --git a/chrome/browser/ui/webui/options2/browser_options_handler.cc b/chrome/browser/ui/webui/options2/browser_options_handler.cc
new file mode 100644
index 0000000..205512c
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/browser_options_handler.cc
@@ -0,0 +1,496 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/webui/options2/browser_options_handler.h"
+
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/memory/singleton.h"
+#include "base/string_number_conversions.h"
+#include "base/utf_string_conversions.h"
+#include "base/values.h"
+#include "chrome/browser/autocomplete/autocomplete.h"
+#include "chrome/browser/autocomplete/autocomplete_match.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/custom_home_pages_table_model.h"
+#include "chrome/browser/instant/instant_confirm_dialog.h"
+#include "chrome/browser/instant/instant_controller.h"
+#include "chrome/browser/instant/instant_field_trial.h"
+#include "chrome/browser/net/url_fixer_upper.h"
+#include "chrome/browser/prefs/pref_service.h"
+#include "chrome/browser/prefs/session_startup_pref.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/search_engines/template_url.h"
+#include "chrome/browser/search_engines/template_url_service.h"
+#include "chrome/browser/search_engines/template_url_service_factory.h"
+#include "chrome/browser/ui/webui/favicon_source.h"
+#include "chrome/common/chrome_notification_types.h"
+#include "chrome/common/pref_names.h"
+#include "chrome/common/url_constants.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/notification_details.h"
+#include "content/public/browser/notification_source.h"
+#include "content/public/browser/user_metrics.h"
+#include "grit/chromium_strings.h"
+#include "grit/generated_resources.h"
+#include "ui/base/l10n/l10n_util.h"
+
+using content::UserMetricsAction;
+
+BrowserOptionsHandler::BrowserOptionsHandler()
+ : template_url_service_(NULL), startup_custom_pages_table_model_(NULL) {
+#if !defined(OS_MACOSX)
+ default_browser_worker_ = new ShellIntegration::DefaultBrowserWorker(this);
+#endif
+}
+
+BrowserOptionsHandler::~BrowserOptionsHandler() {
+ if (default_browser_worker_.get())
+ default_browser_worker_->ObserverDestroyed();
+ if (template_url_service_)
+ template_url_service_->RemoveObserver(this);
+}
+
+void BrowserOptionsHandler::GetLocalizedValues(
+ DictionaryValue* localized_strings) {
+ DCHECK(localized_strings);
+
+ static OptionsStringResource resources[] = {
+ { "startupGroupName", IDS_OPTIONS_STARTUP_GROUP_NAME },
+ { "startupShowDefaultAndNewTab",
+ IDS_OPTIONS_STARTUP_SHOW_DEFAULT_AND_NEWTAB},
+ { "startupShowLastSession", IDS_OPTIONS_STARTUP_SHOW_LAST_SESSION },
+ { "startupShowPages", IDS_OPTIONS_STARTUP_SHOW_PAGES },
+ { "startupAddLabel", IDS_OPTIONS_STARTUP_ADD_LABEL },
+ { "startupUseCurrent", IDS_OPTIONS_STARTUP_USE_CURRENT },
+ { "homepageGroupName", IDS_OPTIONS_HOMEPAGE_GROUP_NAME },
+ { "homepageUseNewTab", IDS_OPTIONS_HOMEPAGE_USE_NEWTAB },
+ { "homepageUseURL", IDS_OPTIONS_HOMEPAGE_USE_URL },
+ { "toolbarGroupName", IDS_OPTIONS_TOOLBAR_GROUP_NAME },
+ { "toolbarShowHomeButton", IDS_OPTIONS_TOOLBAR_SHOW_HOME_BUTTON },
+ { "toolbarShowBookmarksBar", IDS_OPTIONS_TOOLBAR_SHOW_BOOKMARKS_BAR },
+ { "defaultSearchGroupName", IDS_OPTIONS_DEFAULTSEARCH_GROUP_NAME },
+ { "defaultSearchManageEngines", IDS_OPTIONS_DEFAULTSEARCH_MANAGE_ENGINES },
+ { "instantName", IDS_INSTANT_PREF },
+ { "instantWarningText", IDS_INSTANT_PREF_WARNING },
+ { "instantConfirmTitle", IDS_INSTANT_OPT_IN_TITLE },
+ { "instantConfirmMessage", IDS_INSTANT_OPT_IN_MESSAGE },
+ { "defaultBrowserGroupName", IDS_OPTIONS_DEFAULTBROWSER_GROUP_NAME },
+ };
+
+ RegisterStrings(localized_strings, resources, arraysize(resources));
+ RegisterTitle(localized_strings, "browserPage",
+ IDS_OPTIONS_GENERAL_TAB_LABEL);
+
+ localized_strings->SetString("instantLearnMoreLink",
+ ASCIIToUTF16(browser::InstantLearnMoreURL().spec()));
+ localized_strings->SetString("defaultBrowserUnknown",
+ l10n_util::GetStringFUTF16(IDS_OPTIONS_DEFAULTBROWSER_UNKNOWN,
+ l10n_util::GetStringUTF16(IDS_PRODUCT_NAME)));
+ localized_strings->SetString("defaultBrowserUseAsDefault",
+ l10n_util::GetStringFUTF16(IDS_OPTIONS_DEFAULTBROWSER_USEASDEFAULT,
+ l10n_util::GetStringUTF16(IDS_PRODUCT_NAME)));
+}
+
+void BrowserOptionsHandler::RegisterMessages() {
+ web_ui_->RegisterMessageCallback("becomeDefaultBrowser",
+ base::Bind(&BrowserOptionsHandler::BecomeDefaultBrowser,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("setDefaultSearchEngine",
+ base::Bind(&BrowserOptionsHandler::SetDefaultSearchEngine,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("removeStartupPages",
+ base::Bind(&BrowserOptionsHandler::RemoveStartupPages,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("addStartupPage",
+ base::Bind(&BrowserOptionsHandler::AddStartupPage,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("editStartupPage",
+ base::Bind(&BrowserOptionsHandler::EditStartupPage,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("setStartupPagesToCurrentPages",
+ base::Bind(&BrowserOptionsHandler::SetStartupPagesToCurrentPages,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("dragDropStartupPage",
+ base::Bind(&BrowserOptionsHandler::DragDropStartupPage,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("requestAutocompleteSuggestions",
+ base::Bind(&BrowserOptionsHandler::RequestAutocompleteSuggestions,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("enableInstant",
+ base::Bind(&BrowserOptionsHandler::EnableInstant,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("disableInstant",
+ base::Bind(&BrowserOptionsHandler::DisableInstant,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("getInstantFieldTrialStatus",
+ base::Bind(&BrowserOptionsHandler::GetInstantFieldTrialStatus,
+ base::Unretained(this)));
+}
+
+void BrowserOptionsHandler::Initialize() {
+ Profile* profile = Profile::FromWebUI(web_ui_);
+
+ // Create our favicon data source.
+ profile->GetChromeURLDataManager()->AddDataSource(
+ new FaviconSource(profile, FaviconSource::FAVICON));
+
+ homepage_.Init(prefs::kHomePage, profile->GetPrefs(), NULL);
+ default_browser_policy_.Init(prefs::kDefaultBrowserSettingEnabled,
+ g_browser_process->local_state(),
+ this);
+ UpdateDefaultBrowserState();
+
+ startup_custom_pages_table_model_.reset(
+ new CustomHomePagesTableModel(profile));
+ startup_custom_pages_table_model_->SetObserver(this);
+ UpdateStartupPages();
+
+ pref_change_registrar_.Init(profile->GetPrefs());
+ pref_change_registrar_.Add(prefs::kURLsToRestoreOnStartup, this);
+
+ UpdateSearchEngines();
+
+ autocomplete_controller_.reset(new AutocompleteController(profile, this));
+}
+
+void BrowserOptionsHandler::UpdateDefaultBrowserState() {
+ // Check for side-by-side first.
+ if (!ShellIntegration::CanSetAsDefaultBrowser()) {
+ SetDefaultBrowserUIString(IDS_OPTIONS_DEFAULTBROWSER_SXS);
+ return;
+ }
+
+#if defined(OS_MACOSX)
+ ShellIntegration::DefaultWebClientState state =
+ ShellIntegration::IsDefaultBrowser();
+ int status_string_id;
+ if (state == ShellIntegration::IS_DEFAULT_WEB_CLIENT)
+ status_string_id = IDS_OPTIONS_DEFAULTBROWSER_DEFAULT;
+ else if (state == ShellIntegration::NOT_DEFAULT_WEB_CLIENT)
+ status_string_id = IDS_OPTIONS_DEFAULTBROWSER_NOTDEFAULT;
+ else
+ status_string_id = IDS_OPTIONS_DEFAULTBROWSER_UNKNOWN;
+
+ SetDefaultBrowserUIString(status_string_id);
+#else
+ default_browser_worker_->StartCheckIsDefault();
+#endif
+}
+
+void BrowserOptionsHandler::BecomeDefaultBrowser(const ListValue* args) {
+ // If the default browser setting is managed then we should not be able to
+ // call this function.
+ if (default_browser_policy_.IsManaged())
+ return;
+
+ content::RecordAction(UserMetricsAction("Options_SetAsDefaultBrowser"));
+#if defined(OS_MACOSX)
+ if (ShellIntegration::SetAsDefaultBrowser())
+ UpdateDefaultBrowserState();
+#else
+ default_browser_worker_->StartSetAsDefault();
+ // Callback takes care of updating UI.
+#endif
+
+ // If the user attempted to make Chrome the default browser, then he/she
+ // arguably wants to be notified when that changes.
+ PrefService* prefs = Profile::FromWebUI(web_ui_)->GetPrefs();
+ prefs->SetBoolean(prefs::kCheckDefaultBrowser, true);
+}
+
+int BrowserOptionsHandler::StatusStringIdForState(
+ ShellIntegration::DefaultWebClientState state) {
+ if (state == ShellIntegration::IS_DEFAULT_WEB_CLIENT)
+ return IDS_OPTIONS_DEFAULTBROWSER_DEFAULT;
+ if (state == ShellIntegration::NOT_DEFAULT_WEB_CLIENT)
+ return IDS_OPTIONS_DEFAULTBROWSER_NOTDEFAULT;
+ return IDS_OPTIONS_DEFAULTBROWSER_UNKNOWN;
+}
+
+void BrowserOptionsHandler::SetDefaultWebClientUIState(
+ ShellIntegration::DefaultWebClientUIState state) {
+ int status_string_id;
+ if (state == ShellIntegration::STATE_IS_DEFAULT)
+ status_string_id = IDS_OPTIONS_DEFAULTBROWSER_DEFAULT;
+ else if (state == ShellIntegration::STATE_NOT_DEFAULT)
+ status_string_id = IDS_OPTIONS_DEFAULTBROWSER_NOTDEFAULT;
+ else if (state == ShellIntegration::STATE_UNKNOWN)
+ status_string_id = IDS_OPTIONS_DEFAULTBROWSER_UNKNOWN;
+ else
+ return; // Still processing.
+
+ SetDefaultBrowserUIString(status_string_id);
+}
+
+void BrowserOptionsHandler::SetDefaultBrowserUIString(int status_string_id) {
+ scoped_ptr<Value> status_string(Value::CreateStringValue(
+ l10n_util::GetStringFUTF16(status_string_id,
+ l10n_util::GetStringUTF16(IDS_PRODUCT_NAME))));
+
+ scoped_ptr<Value> is_default(Value::CreateBooleanValue(
+ status_string_id == IDS_OPTIONS_DEFAULTBROWSER_DEFAULT));
+
+ scoped_ptr<Value> can_be_default(Value::CreateBooleanValue(
+ !default_browser_policy_.IsManaged() &&
+ (status_string_id == IDS_OPTIONS_DEFAULTBROWSER_DEFAULT ||
+ status_string_id == IDS_OPTIONS_DEFAULTBROWSER_NOTDEFAULT)));
+
+ web_ui_->CallJavascriptFunction("BrowserOptions.updateDefaultBrowserState",
+ *status_string, *is_default, *can_be_default);
+}
+
+void BrowserOptionsHandler::OnTemplateURLServiceChanged() {
+ if (!template_url_service_ || !template_url_service_->loaded())
+ return;
+
+ const TemplateURL* default_url =
+ template_url_service_->GetDefaultSearchProvider();
+
+ int default_index = 0;
+ ListValue search_engines;
+ std::vector<const TemplateURL*> model_urls =
+ template_url_service_->GetTemplateURLs();
+ for (size_t i = 0; i < model_urls.size(); ++i) {
+ if (!model_urls[i]->ShowInDefaultList())
+ continue;
+
+ DictionaryValue* entry = new DictionaryValue();
+ entry->SetString("name", model_urls[i]->short_name());
+ entry->SetInteger("index", i);
+ search_engines.Append(entry);
+ if (model_urls[i] == default_url)
+ default_index = i;
+ }
+
+ scoped_ptr<Value> default_value(Value::CreateIntegerValue(default_index));
+ scoped_ptr<Value> default_managed(Value::CreateBooleanValue(
+ template_url_service_->is_default_search_managed()));
+
+ web_ui_->CallJavascriptFunction("BrowserOptions.updateSearchEngines",
+ search_engines, *default_value,
+ *default_managed);
+}
+
+void BrowserOptionsHandler::SetDefaultSearchEngine(const ListValue* args) {
+ int selected_index = -1;
+ if (!ExtractIntegerValue(args, &selected_index)) {
+ NOTREACHED();
+ return;
+ }
+
+ std::vector<const TemplateURL*> model_urls =
+ template_url_service_->GetTemplateURLs();
+ if (selected_index >= 0 &&
+ selected_index < static_cast<int>(model_urls.size()))
+ template_url_service_->SetDefaultSearchProvider(model_urls[selected_index]);
+
+ content::RecordAction(UserMetricsAction("Options_SearchEngineChanged"));
+}
+
+void BrowserOptionsHandler::UpdateSearchEngines() {
+ template_url_service_ =
+ TemplateURLServiceFactory::GetForProfile(Profile::FromWebUI(web_ui_));
+ if (template_url_service_) {
+ template_url_service_->Load();
+ template_url_service_->AddObserver(this);
+ OnTemplateURLServiceChanged();
+ }
+}
+
+void BrowserOptionsHandler::UpdateStartupPages() {
+ Profile* profile = Profile::FromWebUI(web_ui_);
+ const SessionStartupPref startup_pref =
+ SessionStartupPref::GetStartupPref(profile->GetPrefs());
+ startup_custom_pages_table_model_->SetURLs(startup_pref.urls);
+}
+
+void BrowserOptionsHandler::OnModelChanged() {
+ ListValue startup_pages;
+ int page_count = startup_custom_pages_table_model_->RowCount();
+ std::vector<GURL> urls = startup_custom_pages_table_model_->GetURLs();
+ for (int i = 0; i < page_count; ++i) {
+ DictionaryValue* entry = new DictionaryValue();
+ entry->SetString("title", startup_custom_pages_table_model_->GetText(i, 0));
+ entry->SetString("url", urls[i].spec());
+ entry->SetString("tooltip",
+ startup_custom_pages_table_model_->GetTooltip(i));
+ entry->SetString("modelIndex", base::IntToString(i));
+ startup_pages.Append(entry);
+ }
+
+ web_ui_->CallJavascriptFunction("BrowserOptions.updateStartupPages",
+ startup_pages);
+}
+
+void BrowserOptionsHandler::OnItemsChanged(int start, int length) {
+ OnModelChanged();
+}
+
+void BrowserOptionsHandler::OnItemsAdded(int start, int length) {
+ OnModelChanged();
+}
+
+void BrowserOptionsHandler::OnItemsRemoved(int start, int length) {
+ OnModelChanged();
+}
+
+void BrowserOptionsHandler::Observe(
+ int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) {
+ if (type == chrome::NOTIFICATION_PREF_CHANGED) {
+ std::string* pref = content::Details<std::string>(details).ptr();
+ if (*pref == prefs::kDefaultBrowserSettingEnabled) {
+ UpdateDefaultBrowserState();
+ } else if (*pref == prefs::kURLsToRestoreOnStartup) {
+ UpdateStartupPages();
+ } else {
+ NOTREACHED();
+ }
+ } else {
+ NOTREACHED();
+ }
+}
+
+void BrowserOptionsHandler::SetStartupPagesToCurrentPages(
+ const ListValue* args) {
+ startup_custom_pages_table_model_->SetToCurrentlyOpenPages();
+ SaveStartupPagesPref();
+}
+
+void BrowserOptionsHandler::RemoveStartupPages(const ListValue* args) {
+ for (int i = args->GetSize() - 1; i >= 0; --i) {
+ std::string string_value;
+ CHECK(args->GetString(i, &string_value));
+
+ int selected_index;
+ base::StringToInt(string_value, &selected_index);
+ if (selected_index < 0 ||
+ selected_index >= startup_custom_pages_table_model_->RowCount()) {
+ NOTREACHED();
+ return;
+ }
+ startup_custom_pages_table_model_->Remove(selected_index);
+ }
+
+ SaveStartupPagesPref();
+}
+
+void BrowserOptionsHandler::AddStartupPage(const ListValue* args) {
+ std::string url_string;
+ CHECK_EQ(args->GetSize(), 1U);
+ CHECK(args->GetString(0, &url_string));
+
+ GURL url = URLFixerUpper::FixupURL(url_string, std::string());
+ if (!url.is_valid())
+ return;
+ int index = startup_custom_pages_table_model_->RowCount();
+ startup_custom_pages_table_model_->Add(index, url);
+ SaveStartupPagesPref();
+}
+
+void BrowserOptionsHandler::EditStartupPage(const ListValue* args) {
+ std::string url_string;
+ std::string index_string;
+ int index;
+ CHECK_EQ(args->GetSize(), 2U);
+ CHECK(args->GetString(0, &index_string));
+ CHECK(base::StringToInt(index_string, &index));
+ CHECK(args->GetString(1, &url_string));
+
+ if (index < 0 || index > startup_custom_pages_table_model_->RowCount()) {
+ NOTREACHED();
+ return;
+ }
+
+ std::vector<GURL> urls = startup_custom_pages_table_model_->GetURLs();
+ urls[index] = URLFixerUpper::FixupURL(url_string, std::string());
+ startup_custom_pages_table_model_->SetURLs(urls);
+ SaveStartupPagesPref();
+}
+
+void BrowserOptionsHandler::DragDropStartupPage(const ListValue* args) {
+ CHECK_EQ(args->GetSize(), 2U);
+
+ std::string value;
+ int to_index;
+
+ CHECK(args->GetString(0, &value));
+ base::StringToInt(value, &to_index);
+
+ ListValue* selected;
+ CHECK(args->GetList(1, &selected));
+
+ std::vector<int> index_list;
+ for (size_t i = 0; i < selected->GetSize(); ++i) {
+ int index;
+ CHECK(selected->GetString(i, &value));
+ base::StringToInt(value, &index);
+ index_list.push_back(index);
+ }
+
+ startup_custom_pages_table_model_->MoveURLs(to_index, index_list);
+ SaveStartupPagesPref();
+}
+
+void BrowserOptionsHandler::SaveStartupPagesPref() {
+ PrefService* prefs = Profile::FromWebUI(web_ui_)->GetPrefs();
+
+ SessionStartupPref pref = SessionStartupPref::GetStartupPref(prefs);
+ pref.urls = startup_custom_pages_table_model_->GetURLs();
+
+ SessionStartupPref::SetStartupPref(prefs, pref);
+}
+
+void BrowserOptionsHandler::RequestAutocompleteSuggestions(
+ const ListValue* args) {
+ string16 input;
+ CHECK_EQ(args->GetSize(), 1U);
+ CHECK(args->GetString(0, &input));
+
+ autocomplete_controller_->Start(input, string16(), true, false, false,
+ AutocompleteInput::ALL_MATCHES);
+}
+
+void BrowserOptionsHandler::EnableInstant(const ListValue* args) {
+ InstantController::Enable(Profile::FromWebUI(web_ui_));
+}
+
+void BrowserOptionsHandler::DisableInstant(const ListValue* args) {
+ InstantController::Disable(Profile::FromWebUI(web_ui_));
+}
+
+void BrowserOptionsHandler::GetInstantFieldTrialStatus(const ListValue* args) {
+ Profile* profile = Profile::FromWebUI(web_ui_);
+ base::FundamentalValue enabled(
+ InstantFieldTrial::IsInstantExperiment(profile) &&
+ !InstantFieldTrial::IsHiddenExperiment(profile));
+ web_ui_->CallJavascriptFunction("BrowserOptions.setInstantFieldTrialStatus",
+ enabled);
+}
+
+void BrowserOptionsHandler::OnResultChanged(bool default_match_changed) {
+ const AutocompleteResult& result = autocomplete_controller_->result();
+ ListValue suggestions;
+ for (size_t i = 0; i < result.size(); ++i) {
+ const AutocompleteMatch& match = result.match_at(i);
+ AutocompleteMatch::Type type = match.type;
+ if (type != AutocompleteMatch::HISTORY_URL &&
+ type != AutocompleteMatch::HISTORY_TITLE &&
+ type != AutocompleteMatch::HISTORY_BODY &&
+ type != AutocompleteMatch::HISTORY_KEYWORD &&
+ type != AutocompleteMatch::NAVSUGGEST)
+ continue;
+ DictionaryValue* entry = new DictionaryValue();
+ entry->SetString("title", match.description);
+ entry->SetString("displayURL", match.contents);
+ entry->SetString("url", match.destination_url.spec());
+ suggestions.Append(entry);
+ }
+
+ web_ui_->CallJavascriptFunction(
+ "BrowserOptions.updateAutocompleteSuggestions", suggestions);
+}
diff --git a/chrome/browser/ui/webui/options2/browser_options_handler.h b/chrome/browser/ui/webui/options2/browser_options_handler.h
new file mode 100644
index 0000000..894620a
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/browser_options_handler.h
@@ -0,0 +1,136 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS2_BROWSER_OPTIONS_HANDLER_H_
+#define CHROME_BROWSER_UI_WEBUI_OPTIONS2_BROWSER_OPTIONS_HANDLER_H_
+#pragma once
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "chrome/browser/autocomplete/autocomplete_controller_delegate.h"
+#include "chrome/browser/prefs/pref_change_registrar.h"
+#include "chrome/browser/prefs/pref_member.h"
+#include "chrome/browser/search_engines/template_url_service_observer.h"
+#include "chrome/browser/shell_integration.h"
+#include "chrome/browser/ui/webui/options2/options_ui2.h"
+#include "ui/base/models/table_model_observer.h"
+
+class AutocompleteController;
+class CustomHomePagesTableModel;
+class TemplateURLService;
+
+// Chrome browser options page UI handler.
+class BrowserOptionsHandler : public OptionsPage2UIHandler,
+ public AutocompleteControllerDelegate,
+ public ShellIntegration::DefaultWebClientObserver,
+ public TemplateURLServiceObserver,
+ public ui::TableModelObserver {
+ public:
+ BrowserOptionsHandler();
+ virtual ~BrowserOptionsHandler();
+
+ virtual void Initialize() OVERRIDE;
+
+ // OptionsPage2UIHandler implementation.
+ virtual void GetLocalizedValues(DictionaryValue* localized_strings) OVERRIDE;
+ virtual void RegisterMessages() OVERRIDE;
+
+ // AutocompleteControllerDelegate implementation.
+ virtual void OnResultChanged(bool default_match_changed) OVERRIDE;
+
+ // ShellIntegration::DefaultWebClientObserver implementation.
+ virtual void SetDefaultWebClientUIState(
+ ShellIntegration::DefaultWebClientUIState state) OVERRIDE;
+
+ // TemplateURLServiceObserver implementation.
+ virtual void OnTemplateURLServiceChanged() OVERRIDE;
+
+ // ui::TableModelObserver implementation.
+ virtual void OnModelChanged() OVERRIDE;
+ virtual void OnItemsChanged(int start, int length) OVERRIDE;
+ virtual void OnItemsAdded(int start, int length) OVERRIDE;
+ virtual void OnItemsRemoved(int start, int length) OVERRIDE;
+
+ private:
+ // content::NotificationObserver implementation.
+ virtual void Observe(int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) OVERRIDE;
+
+ // Makes this the default browser. Called from WebUI.
+ void BecomeDefaultBrowser(const ListValue* args);
+
+ // Sets the search engine at the given index to be default. Called from WebUI.
+ void SetDefaultSearchEngine(const ListValue* args);
+
+ // Removes the startup page at the given indexes. Called from WebUI.
+ void RemoveStartupPages(const ListValue* args);
+
+ // Adds a startup page with the given URL after the given index.
+ // Called from WebUI.
+ void AddStartupPage(const ListValue* args);
+
+ // Changes the startup page at the given index to the given URL.
+ // Called from WebUI.
+ void EditStartupPage(const ListValue* args);
+
+ // Sets the startup page set to the current pages. Called from WebUI.
+ void SetStartupPagesToCurrentPages(const ListValue* args);
+
+ // Writes the current set of startup pages to prefs. Called from WebUI.
+ void DragDropStartupPage(const ListValue* args);
+
+ // Gets autocomplete suggestions asychronously for the given string.
+ // Called from WebUI.
+ void RequestAutocompleteSuggestions(const ListValue* args);
+
+ // Enables/disables Instant.
+ void EnableInstant(const ListValue* args);
+ void DisableInstant(const ListValue* args);
+
+ // Called to request information about the Instant field trial.
+ void GetInstantFieldTrialStatus(const ListValue* args);
+
+ // Returns the string ID for the given default browser state.
+ int StatusStringIdForState(ShellIntegration::DefaultWebClientState state);
+
+ // Gets the current default browser state, and asynchronously reports it to
+ // the WebUI page.
+ void UpdateDefaultBrowserState();
+
+ // Updates the UI with the given state for the default browser.
+ void SetDefaultBrowserUIString(int status_string_id);
+
+ // Loads the current set of custom startup pages and reports it to the WebUI.
+ void UpdateStartupPages();
+
+ // Loads the possible default search engine list and reports it to the WebUI.
+ void UpdateSearchEngines();
+
+ // Writes the current set of startup pages to prefs.
+ void SaveStartupPagesPref();
+
+ scoped_refptr<ShellIntegration::DefaultBrowserWorker>
+ default_browser_worker_;
+
+ StringPrefMember homepage_;
+ BooleanPrefMember default_browser_policy_;
+
+ // Used to observe updates to the preference of the list of URLs to load
+ // on startup, which can be updated via sync.
+ PrefChangeRegistrar pref_change_registrar_;
+
+ TemplateURLService* template_url_service_; // Weak.
+
+ // TODO(stuartmorgan): Once there are no other clients of
+ // CustomHomePagesTableModel, consider changing it to something more like
+ // TemplateURLService.
+ scoped_ptr<CustomHomePagesTableModel> startup_custom_pages_table_model_;
+
+ scoped_ptr<AutocompleteController> autocomplete_controller_;
+
+ DISALLOW_COPY_AND_ASSIGN(BrowserOptionsHandler);
+};
+
+#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS2_BROWSER_OPTIONS_HANDLER_H_
diff --git a/chrome/browser/ui/webui/options2/certificate_manager_browsertest.js b/chrome/browser/ui/webui/options2/certificate_manager_browsertest.js
new file mode 100644
index 0000000..fdd2373
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/certificate_manager_browsertest.js
@@ -0,0 +1,33 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * TestFixture for certificate manager WebUI testing.
+ * @extends {testing.Test}
+ * @constructor
+ **/
+function CertificateManagerWebUITest() {}
+
+CertificateManagerWebUITest.prototype = {
+ __proto__: testing.Test.prototype,
+
+ /**
+ * Browse to the certificate manager.
+ **/
+ browsePreload: 'chrome://settings/certificates',
+};
+
+// Mac and Windows go to native certificate manager.
+GEN('#if defined(OS_MACOSX) || defined(OS_WIN)');
+GEN('#define MAYBE_testOpenCertificateManager ' +
+ 'DISABLED_testOpenCertificateManager');
+GEN('#else');
+GEN('#define MAYBE_testOpenCertificateManager ' +
+ 'testOpenCertificateManager');
+GEN('#endif // defined(OS_MACOSX) || defined(OS_WIN)');
+// Test opening the certificate manager has correct location.
+TEST_F('CertificateManagerWebUITest',
+ 'MAYBE_testOpenCertificateManager', function() {
+ assertEquals(this.browsePreload, document.location.href);
+ });
diff --git a/chrome/browser/ui/webui/options2/certificate_manager_handler.cc b/chrome/browser/ui/webui/options2/certificate_manager_handler.cc
new file mode 100644
index 0000000..00d52f6
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/certificate_manager_handler.cc
@@ -0,0 +1,1046 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/webui/options2/certificate_manager_handler.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/file_util.h" // for FileAccessProvider
+#include "base/memory/scoped_vector.h"
+#include "base/safe_strerror_posix.h"
+#include "base/string_number_conversions.h"
+#include "base/values.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/certificate_viewer.h"
+#include "chrome/browser/ui/certificate_dialogs.h"
+#include "chrome/browser/ui/crypto_module_password_dialog.h"
+#include "content/browser/tab_contents/tab_contents.h"
+#include "content/browser/tab_contents/tab_contents_view.h"
+#include "content/public/browser/browser_thread.h" // for FileAccessProvider
+#include "grit/generated_resources.h"
+#include "net/base/crypto_module.h"
+#include "net/base/x509_certificate.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/l10n/l10n_util_collator.h"
+
+#if defined(OS_CHROMEOS)
+#include "chrome/browser/chromeos/cros/cros_library.h"
+#include "chrome/browser/chromeos/cros/cryptohome_library.h"
+#endif
+
+using content::BrowserThread;
+
+namespace {
+
+static const char kKeyId[] = "id";
+static const char kSubNodesId[] = "subnodes";
+static const char kNameId[] = "name";
+static const char kReadOnlyId[] = "readonly";
+static const char kUntrustedId[] = "untrusted";
+static const char kSecurityDeviceId[] = "device";
+static const char kErrorId[] = "error";
+
+// Enumeration of different callers of SelectFile. (Start counting at 1 so
+// if SelectFile is accidentally called with params=NULL it won't match any.)
+enum {
+ EXPORT_PERSONAL_FILE_SELECTED = 1,
+ IMPORT_PERSONAL_FILE_SELECTED,
+ IMPORT_SERVER_FILE_SELECTED,
+ IMPORT_CA_FILE_SELECTED,
+};
+
+// TODO(mattm): These are duplicated from cookies_view_handler.cc
+// Encodes a pointer value into a hex string.
+std::string PointerToHexString(const void* pointer) {
+ return base::HexEncode(&pointer, sizeof(pointer));
+}
+
+// Decodes a pointer from a hex string.
+void* HexStringToPointer(const std::string& str) {
+ std::vector<uint8> buffer;
+ if (!base::HexStringToBytes(str, &buffer) ||
+ buffer.size() != sizeof(void*)) {
+ return NULL;
+ }
+
+ return *reinterpret_cast<void**>(&buffer[0]);
+}
+
+std::string OrgNameToId(const std::string& org) {
+ return "org-" + org;
+}
+
+std::string CertToId(const net::X509Certificate& cert) {
+ return "cert-" + PointerToHexString(&cert);
+}
+
+net::X509Certificate* IdToCert(const std::string& id) {
+ if (!StartsWithASCII(id, "cert-", true))
+ return NULL;
+ return reinterpret_cast<net::X509Certificate*>(
+ HexStringToPointer(id.substr(5)));
+}
+
+net::X509Certificate* CallbackArgsToCert(const ListValue* args) {
+ std::string node_id;
+ if (!args->GetString(0, &node_id)){
+ return NULL;
+ }
+ net::X509Certificate* cert = IdToCert(node_id);
+ if (!cert) {
+ NOTREACHED();
+ return NULL;
+ }
+ return cert;
+}
+
+bool CallbackArgsToBool(const ListValue* args, int index, bool* result) {
+ std::string string_value;
+ if (!args->GetString(index, &string_value))
+ return false;
+
+ *result = string_value[0] == 't';
+ return true;
+}
+
+struct DictionaryIdComparator {
+ explicit DictionaryIdComparator(icu::Collator* collator)
+ : collator_(collator) {
+ }
+
+ bool operator()(const Value* a,
+ const Value* b) const {
+ DCHECK(a->GetType() == Value::TYPE_DICTIONARY);
+ DCHECK(b->GetType() == Value::TYPE_DICTIONARY);
+ const DictionaryValue* a_dict = reinterpret_cast<const DictionaryValue*>(a);
+ const DictionaryValue* b_dict = reinterpret_cast<const DictionaryValue*>(b);
+ string16 a_str;
+ string16 b_str;
+ a_dict->GetString(kNameId, &a_str);
+ b_dict->GetString(kNameId, &b_str);
+ if (collator_ == NULL)
+ return a_str < b_str;
+ return l10n_util::CompareString16WithCollator(
+ collator_, a_str, b_str) == UCOL_LESS;
+ }
+
+ icu::Collator* collator_;
+};
+
+std::string NetErrorToString(int net_error) {
+ switch (net_error) {
+ // TODO(mattm): handle more cases.
+ case net::ERR_IMPORT_CA_CERT_NOT_CA:
+ return l10n_util::GetStringUTF8(IDS_CERT_MANAGER_ERROR_NOT_CA);
+ default:
+ return l10n_util::GetStringUTF8(IDS_CERT_MANAGER_UNKNOWN_ERROR);
+ }
+}
+
+} // namespace
+
+///////////////////////////////////////////////////////////////////////////////
+// FileAccessProvider
+
+// TODO(mattm): Move to some shared location?
+class FileAccessProvider
+ : public base::RefCountedThreadSafe<FileAccessProvider>,
+ public CancelableRequestProvider {
+ public:
+ // Reports 0 on success or errno on failure, and the data of the file upon
+ // success.
+ // TODO(mattm): don't pass std::string by value.. could use RefCountedBytes
+ // but it's a vector. Maybe do the derive from CancelableRequest thing
+ // described in cancelable_request.h?
+ typedef Callback2<int, std::string>::Type ReadCallback;
+
+ // Reports 0 on success or errno on failure, and the number of bytes written,
+ // on success.
+ typedef Callback2<int, int>::Type WriteCallback;
+
+ Handle StartRead(const FilePath& path,
+ CancelableRequestConsumerBase* consumer,
+ ReadCallback* callback);
+ Handle StartWrite(const FilePath& path,
+ const std::string& data,
+ CancelableRequestConsumerBase* consumer,
+ WriteCallback* callback);
+
+ private:
+ void DoRead(scoped_refptr<CancelableRequest<ReadCallback> > request,
+ FilePath path);
+ void DoWrite(scoped_refptr<CancelableRequest<WriteCallback> > request,
+ FilePath path,
+ std::string data);
+};
+
+CancelableRequestProvider::Handle FileAccessProvider::StartRead(
+ const FilePath& path,
+ CancelableRequestConsumerBase* consumer,
+ FileAccessProvider::ReadCallback* callback) {
+ scoped_refptr<CancelableRequest<ReadCallback> > request(
+ new CancelableRequest<ReadCallback>(callback));
+ AddRequest(request, consumer);
+
+ // Send the parameters and the request to the file thread.
+ BrowserThread::PostTask(
+ BrowserThread::FILE, FROM_HERE,
+ base::Bind(&FileAccessProvider::DoRead, this, request, path));
+
+ // The handle will have been set by AddRequest.
+ return request->handle();
+}
+
+CancelableRequestProvider::Handle FileAccessProvider::StartWrite(
+ const FilePath& path,
+ const std::string& data,
+ CancelableRequestConsumerBase* consumer,
+ WriteCallback* callback) {
+ scoped_refptr<CancelableRequest<WriteCallback> > request(
+ new CancelableRequest<WriteCallback>(callback));
+ AddRequest(request, consumer);
+
+ // Send the parameters and the request to the file thWrite.
+ BrowserThread::PostTask(
+ BrowserThread::FILE, FROM_HERE,
+ base::Bind(&FileAccessProvider::DoWrite, this, request, path, data));
+
+ // The handle will have been set by AddRequest.
+ return request->handle();
+}
+
+void FileAccessProvider::DoRead(
+ scoped_refptr<CancelableRequest<ReadCallback> > request,
+ FilePath path) {
+ if (request->canceled())
+ return;
+
+ std::string data;
+ VLOG(1) << "DoRead starting read";
+ bool success = file_util::ReadFileToString(path, &data);
+ int saved_errno = success ? 0 : errno;
+ VLOG(1) << "DoRead done read: " << success << " " << data.size();
+ request->ForwardResult(ReadCallback::TupleType(saved_errno, data));
+}
+
+void FileAccessProvider::DoWrite(
+ scoped_refptr<CancelableRequest<WriteCallback> > request,
+ FilePath path,
+ std::string data) {
+ VLOG(1) << "DoWrite starting write";
+ int bytes_written = file_util::WriteFile(path, data.data(), data.size());
+ int saved_errno = bytes_written >= 0 ? 0 : errno;
+ VLOG(1) << "DoWrite done write " << bytes_written;
+
+ if (request->canceled())
+ return;
+
+ request->ForwardResult(WriteCallback::TupleType(saved_errno, bytes_written));
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// CertificateManagerHandler
+
+CertificateManagerHandler::CertificateManagerHandler()
+ : file_access_provider_(new FileAccessProvider) {
+ certificate_manager_model_.reset(new CertificateManagerModel(this));
+}
+
+CertificateManagerHandler::~CertificateManagerHandler() {
+}
+
+void CertificateManagerHandler::GetLocalizedValues(
+ DictionaryValue* localized_strings) {
+ DCHECK(localized_strings);
+
+ RegisterTitle(localized_strings, "certificateManagerPage",
+ IDS_CERTIFICATE_MANAGER_TITLE);
+
+ // Tabs.
+ localized_strings->SetString("personalCertsTabTitle",
+ l10n_util::GetStringUTF16(IDS_CERT_MANAGER_PERSONAL_CERTS_TAB_LABEL));
+ localized_strings->SetString("serverCertsTabTitle",
+ l10n_util::GetStringUTF16(IDS_CERT_MANAGER_SERVER_CERTS_TAB_LABEL));
+ localized_strings->SetString("caCertsTabTitle",
+ l10n_util::GetStringUTF16(IDS_CERT_MANAGER_CERT_AUTHORITIES_TAB_LABEL));
+ localized_strings->SetString("unknownCertsTabTitle",
+ l10n_util::GetStringUTF16(IDS_CERT_MANAGER_UNKNOWN_TAB_LABEL));
+
+ // Tab descriptions.
+ localized_strings->SetString("personalCertsTabDescription",
+ l10n_util::GetStringUTF16(IDS_CERT_MANAGER_USER_TREE_DESCRIPTION));
+ localized_strings->SetString("serverCertsTabDescription",
+ l10n_util::GetStringUTF16(IDS_CERT_MANAGER_SERVER_TREE_DESCRIPTION));
+ localized_strings->SetString("caCertsTabDescription",
+ l10n_util::GetStringUTF16(IDS_CERT_MANAGER_AUTHORITIES_TREE_DESCRIPTION));
+ localized_strings->SetString("unknownCertsTabDescription",
+ l10n_util::GetStringUTF16(IDS_CERT_MANAGER_UNKNOWN_TREE_DESCRIPTION));
+
+ // Tree columns.
+ localized_strings->SetString("certNameColumn",
+ l10n_util::GetStringUTF16(IDS_CERT_MANAGER_NAME_COLUMN_LABEL));
+ localized_strings->SetString("certDeviceColumn",
+ l10n_util::GetStringUTF16(IDS_CERT_MANAGER_DEVICE_COLUMN_LABEL));
+ localized_strings->SetString("certSerialColumn",
+ l10n_util::GetStringUTF16(IDS_CERT_MANAGER_SERIAL_NUMBER_COLUMN_LABEL));
+ localized_strings->SetString("certExpiresColumn",
+ l10n_util::GetStringUTF16(IDS_CERT_MANAGER_EXPIRES_COLUMN_LABEL));
+
+ // Buttons.
+ localized_strings->SetString("view_certificate",
+ l10n_util::GetStringUTF16(IDS_CERT_MANAGER_VIEW_CERT_BUTTON));
+ localized_strings->SetString("import_certificate",
+ l10n_util::GetStringUTF16(IDS_CERT_MANAGER_IMPORT_BUTTON));
+ localized_strings->SetString("export_certificate",
+ l10n_util::GetStringUTF16(IDS_CERT_MANAGER_EXPORT_BUTTON));
+ localized_strings->SetString("export_all_certificates",
+ l10n_util::GetStringUTF16(IDS_CERT_MANAGER_EXPORT_ALL_BUTTON));
+ localized_strings->SetString("edit_certificate",
+ l10n_util::GetStringUTF16(IDS_CERT_MANAGER_EDIT_BUTTON));
+ localized_strings->SetString("delete_certificate",
+ l10n_util::GetStringUTF16(IDS_CERT_MANAGER_DELETE_BUTTON));
+
+ // Certificate Delete overlay strings.
+ localized_strings->SetString("personalCertsTabDeleteConfirm",
+ l10n_util::GetStringUTF16(IDS_CERT_MANAGER_DELETE_USER_FORMAT));
+ localized_strings->SetString("personalCertsTabDeleteImpact",
+ l10n_util::GetStringUTF16(IDS_CERT_MANAGER_DELETE_USER_DESCRIPTION));
+ localized_strings->SetString("serverCertsTabDeleteConfirm",
+ l10n_util::GetStringUTF16(IDS_CERT_MANAGER_DELETE_SERVER_FORMAT));
+ localized_strings->SetString("serverCertsTabDeleteImpact",
+ l10n_util::GetStringUTF16(IDS_CERT_MANAGER_DELETE_SERVER_DESCRIPTION));
+ localized_strings->SetString("caCertsTabDeleteConfirm",
+ l10n_util::GetStringUTF16(IDS_CERT_MANAGER_DELETE_CA_FORMAT));
+ localized_strings->SetString("caCertsTabDeleteImpact",
+ l10n_util::GetStringUTF16(IDS_CERT_MANAGER_DELETE_CA_DESCRIPTION));
+ localized_strings->SetString("unknownCertsTabDeleteConfirm",
+ l10n_util::GetStringUTF16(IDS_CERT_MANAGER_DELETE_UNKNOWN_FORMAT));
+ localized_strings->SetString("unknownCertsTabDeleteImpact", "");
+
+ // Certificate Restore overlay strings.
+ localized_strings->SetString("certificateRestorePasswordDescription",
+ l10n_util::GetStringUTF16(IDS_CERT_MANAGER_RESTORE_PASSWORD_DESC));
+ localized_strings->SetString("certificatePasswordLabel",
+ l10n_util::GetStringUTF16(IDS_CERT_MANAGER_PASSWORD_LABEL));
+
+ // Personal Certificate Export overlay strings.
+ localized_strings->SetString("certificateExportPasswordDescription",
+ l10n_util::GetStringUTF16(IDS_CERT_MANAGER_EXPORT_PASSWORD_DESC));
+ localized_strings->SetString("certificateExportPasswordHelp",
+ l10n_util::GetStringUTF16(IDS_CERT_MANAGER_EXPORT_PASSWORD_HELP));
+ localized_strings->SetString("certificateConfirmPasswordLabel",
+ l10n_util::GetStringUTF16(IDS_CERT_MANAGER_CONFIRM_PASSWORD_LABEL));
+
+ // Edit CA Trust & Import CA overlay strings.
+ localized_strings->SetString("certificateEditTrustLabel",
+ l10n_util::GetStringUTF16(IDS_CERT_MANAGER_EDIT_TRUST_LABEL));
+ localized_strings->SetString("certificateEditCaTrustDescriptionFormat",
+ l10n_util::GetStringUTF16(
+ IDS_CERT_MANAGER_EDIT_CA_TRUST_DESCRIPTION_FORMAT));
+ localized_strings->SetString("certificateImportCaDescriptionFormat",
+ l10n_util::GetStringUTF16(
+ IDS_CERT_MANAGER_IMPORT_CA_DESCRIPTION_FORMAT));
+ localized_strings->SetString("certificateCaTrustSSLLabel",
+ l10n_util::GetStringUTF16(IDS_CERT_MANAGER_EDIT_CA_TRUST_SSL_LABEL));
+ localized_strings->SetString("certificateCaTrustEmailLabel",
+ l10n_util::GetStringUTF16(IDS_CERT_MANAGER_EDIT_CA_TRUST_EMAIL_LABEL));
+ localized_strings->SetString("certificateCaTrustObjSignLabel",
+ l10n_util::GetStringUTF16(IDS_CERT_MANAGER_EDIT_CA_TRUST_OBJSIGN_LABEL));
+ localized_strings->SetString("certificateImportErrorFormat",
+ l10n_util::GetStringUTF16(IDS_CERT_MANAGER_IMPORT_ERROR_FORMAT));
+
+ // Badges next to certificates
+ localized_strings->SetString("badgeCertUntrusted",
+ l10n_util::GetStringUTF16(IDS_CERT_MANAGER_UNTRUSTED));
+
+#if defined(OS_CHROMEOS)
+ localized_strings->SetString("importAndBindCertificate",
+ l10n_util::GetStringUTF16(IDS_CERT_MANAGER_IMPORT_AND_BIND_BUTTON));
+ localized_strings->SetString("hardwareBackedKeyFormat",
+ l10n_util::GetStringUTF16(IDS_CERT_MANAGER_HARDWARE_BACKED_KEY_FORMAT));
+ localized_strings->SetString("chromeOSDeviceName",
+ l10n_util::GetStringUTF16(IDS_CERT_MANAGER_HARDWARE_BACKED));
+#endif // defined(OS_CHROMEOS)
+}
+
+void CertificateManagerHandler::RegisterMessages() {
+ web_ui_->RegisterMessageCallback(
+ "viewCertificate",
+ base::Bind(&CertificateManagerHandler::View, base::Unretained(this)));
+
+ web_ui_->RegisterMessageCallback(
+ "getCaCertificateTrust",
+ base::Bind(&CertificateManagerHandler::GetCATrust,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback(
+ "editCaCertificateTrust",
+ base::Bind(&CertificateManagerHandler::EditCATrust,
+ base::Unretained(this)));
+
+ web_ui_->RegisterMessageCallback(
+ "editServerCertificate",
+ base::Bind(&CertificateManagerHandler::EditServer,
+ base::Unretained(this)));
+
+ web_ui_->RegisterMessageCallback(
+ "cancelImportExportCertificate",
+ base::Bind(&CertificateManagerHandler::CancelImportExportProcess,
+ base::Unretained(this)));
+
+ web_ui_->RegisterMessageCallback(
+ "exportPersonalCertificate",
+ base::Bind(&CertificateManagerHandler::ExportPersonal,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback(
+ "exportAllPersonalCertificates",
+ base::Bind(&CertificateManagerHandler::ExportAllPersonal,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback(
+ "exportPersonalCertificatePasswordSelected",
+ base::Bind(&CertificateManagerHandler::ExportPersonalPasswordSelected,
+ base::Unretained(this)));
+
+ web_ui_->RegisterMessageCallback(
+ "importPersonalCertificate",
+ base::Bind(&CertificateManagerHandler::StartImportPersonal,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback(
+ "importPersonalCertificatePasswordSelected",
+ base::Bind(&CertificateManagerHandler::ImportPersonalPasswordSelected,
+ base::Unretained(this)));
+
+ web_ui_->RegisterMessageCallback(
+ "importCaCertificate",
+ base::Bind(&CertificateManagerHandler::ImportCA,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback(
+ "importCaCertificateTrustSelected",
+ base::Bind(&CertificateManagerHandler::ImportCATrustSelected,
+ base::Unretained(this)));
+
+ web_ui_->RegisterMessageCallback(
+ "importServerCertificate",
+ base::Bind(&CertificateManagerHandler::ImportServer,
+ base::Unretained(this)));
+
+ web_ui_->RegisterMessageCallback(
+ "exportCertificate",
+ base::Bind(&CertificateManagerHandler::Export,
+ base::Unretained(this)));
+
+ web_ui_->RegisterMessageCallback(
+ "deleteCertificate",
+ base::Bind(&CertificateManagerHandler::Delete,
+ base::Unretained(this)));
+
+ web_ui_->RegisterMessageCallback(
+ "populateCertificateManager",
+ base::Bind(&CertificateManagerHandler::Populate,
+ base::Unretained(this)));
+
+#if defined(OS_CHROMEOS)
+ web_ui_->RegisterMessageCallback(
+ "checkTpmTokenReady",
+ base::Bind(&CertificateManagerHandler::CheckTpmTokenReady,
+ base::Unretained(this)));
+#endif
+}
+
+void CertificateManagerHandler::CertificatesRefreshed() {
+ PopulateTree("personalCertsTab", net::USER_CERT);
+ PopulateTree("serverCertsTab", net::SERVER_CERT);
+ PopulateTree("caCertsTab", net::CA_CERT);
+ PopulateTree("otherCertsTab", net::UNKNOWN_CERT);
+ VLOG(1) << "populating finished";
+}
+
+void CertificateManagerHandler::FileSelected(const FilePath& path, int index,
+ void* params) {
+ switch (reinterpret_cast<intptr_t>(params)) {
+ case EXPORT_PERSONAL_FILE_SELECTED:
+ ExportPersonalFileSelected(path);
+ break;
+ case IMPORT_PERSONAL_FILE_SELECTED:
+ ImportPersonalFileSelected(path);
+ break;
+ case IMPORT_SERVER_FILE_SELECTED:
+ ImportServerFileSelected(path);
+ break;
+ case IMPORT_CA_FILE_SELECTED:
+ ImportCAFileSelected(path);
+ break;
+ default:
+ NOTREACHED();
+ }
+}
+
+void CertificateManagerHandler::FileSelectionCanceled(void* params) {
+ switch (reinterpret_cast<intptr_t>(params)) {
+ case EXPORT_PERSONAL_FILE_SELECTED:
+ case IMPORT_PERSONAL_FILE_SELECTED:
+ case IMPORT_SERVER_FILE_SELECTED:
+ case IMPORT_CA_FILE_SELECTED:
+ ImportExportCleanup();
+ break;
+ default:
+ NOTREACHED();
+ }
+}
+
+void CertificateManagerHandler::View(const ListValue* args) {
+ net::X509Certificate* cert = CallbackArgsToCert(args);
+ if (!cert)
+ return;
+ ShowCertificateViewer(GetParentWindow(), cert);
+}
+
+void CertificateManagerHandler::GetCATrust(const ListValue* args) {
+ net::X509Certificate* cert = CallbackArgsToCert(args);
+ if (!cert) {
+ web_ui_->CallJavascriptFunction("CertificateEditCaTrustOverlay.dismiss");
+ return;
+ }
+
+ net::CertDatabase::TrustBits trust_bits =
+ certificate_manager_model_->cert_db().GetCertTrust(cert, net::CA_CERT);
+ base::FundamentalValue ssl_value(
+ static_cast<bool>(trust_bits & net::CertDatabase::TRUSTED_SSL));
+ base::FundamentalValue email_value(
+ static_cast<bool>(trust_bits & net::CertDatabase::TRUSTED_EMAIL));
+ base::FundamentalValue obj_sign_value(
+ static_cast<bool>(trust_bits & net::CertDatabase::TRUSTED_OBJ_SIGN));
+ web_ui_->CallJavascriptFunction(
+ "CertificateEditCaTrustOverlay.populateTrust",
+ ssl_value, email_value, obj_sign_value);
+}
+
+void CertificateManagerHandler::EditCATrust(const ListValue* args) {
+ net::X509Certificate* cert = CallbackArgsToCert(args);
+ bool fail = !cert;
+ bool trust_ssl = false;
+ bool trust_email = false;
+ bool trust_obj_sign = false;
+ fail |= !CallbackArgsToBool(args, 1, &trust_ssl);
+ fail |= !CallbackArgsToBool(args, 2, &trust_email);
+ fail |= !CallbackArgsToBool(args, 3, &trust_obj_sign);
+ if (fail) {
+ LOG(ERROR) << "EditCATrust args fail";
+ web_ui_->CallJavascriptFunction("CertificateEditCaTrustOverlay.dismiss");
+ return;
+ }
+
+ bool result = certificate_manager_model_->SetCertTrust(
+ cert,
+ net::CA_CERT,
+ trust_ssl * net::CertDatabase::TRUSTED_SSL +
+ trust_email * net::CertDatabase::TRUSTED_EMAIL +
+ trust_obj_sign * net::CertDatabase::TRUSTED_OBJ_SIGN);
+ web_ui_->CallJavascriptFunction("CertificateEditCaTrustOverlay.dismiss");
+ if (!result) {
+ // TODO(mattm): better error messages?
+ ShowError(
+ l10n_util::GetStringUTF8(IDS_CERT_MANAGER_SET_TRUST_ERROR_TITLE),
+ l10n_util::GetStringUTF8(IDS_CERT_MANAGER_UNKNOWN_ERROR));
+ }
+}
+
+void CertificateManagerHandler::EditServer(const ListValue* args) {
+ NOTIMPLEMENTED();
+}
+
+void CertificateManagerHandler::ExportPersonal(const ListValue* args) {
+ net::X509Certificate* cert = CallbackArgsToCert(args);
+ if (!cert)
+ return;
+
+ selected_cert_list_.push_back(cert);
+
+ SelectFileDialog::FileTypeInfo file_type_info;
+ file_type_info.extensions.resize(1);
+ file_type_info.extensions[0].push_back(FILE_PATH_LITERAL("p12"));
+ file_type_info.extension_description_overrides.push_back(
+ l10n_util::GetStringUTF16(IDS_CERT_MANAGER_PKCS12_FILES));
+ file_type_info.include_all_files = true;
+ select_file_dialog_ = SelectFileDialog::Create(this);
+ select_file_dialog_->SelectFile(
+ SelectFileDialog::SELECT_SAVEAS_FILE, string16(),
+ FilePath(), &file_type_info, 1, FILE_PATH_LITERAL("p12"),
+ web_ui_->tab_contents(), GetParentWindow(),
+ reinterpret_cast<void*>(EXPORT_PERSONAL_FILE_SELECTED));
+}
+
+void CertificateManagerHandler::ExportAllPersonal(const ListValue* args) {
+ NOTIMPLEMENTED();
+}
+
+void CertificateManagerHandler::ExportPersonalFileSelected(
+ const FilePath& path) {
+ file_path_ = path;
+ web_ui_->CallJavascriptFunction(
+ "CertificateManager.exportPersonalAskPassword");
+}
+
+void CertificateManagerHandler::ExportPersonalPasswordSelected(
+ const ListValue* args) {
+ if (!args->GetString(0, &password_)){
+ web_ui_->CallJavascriptFunction("CertificateRestoreOverlay.dismiss");
+ ImportExportCleanup();
+ return;
+ }
+
+ // Currently, we don't support exporting more than one at a time. If we do,
+ // this would need to either change this to use UnlockSlotsIfNecessary or
+ // change UnlockCertSlotIfNecessary to take a CertificateList.
+ DCHECK_EQ(selected_cert_list_.size(), 1U);
+
+ // TODO(mattm): do something smarter about non-extractable keys
+ browser::UnlockCertSlotIfNecessary(
+ selected_cert_list_[0].get(),
+ browser::kCryptoModulePasswordCertExport,
+ "", // unused.
+ base::Bind(&CertificateManagerHandler::ExportPersonalSlotsUnlocked,
+ base::Unretained(this)));
+}
+
+void CertificateManagerHandler::ExportPersonalSlotsUnlocked() {
+ std::string output;
+ int num_exported = certificate_manager_model_->cert_db().ExportToPKCS12(
+ selected_cert_list_,
+ password_,
+ &output);
+ if (!num_exported) {
+ web_ui_->CallJavascriptFunction("CertificateRestoreOverlay.dismiss");
+ ShowError(
+ l10n_util::GetStringUTF8(IDS_CERT_MANAGER_PKCS12_EXPORT_ERROR_TITLE),
+ l10n_util::GetStringUTF8(IDS_CERT_MANAGER_UNKNOWN_ERROR));
+ ImportExportCleanup();
+ return;
+ }
+ file_access_provider_->StartWrite(
+ file_path_,
+ output,
+ &consumer_,
+ NewCallback(this, &CertificateManagerHandler::ExportPersonalFileWritten));
+}
+
+void CertificateManagerHandler::ExportPersonalFileWritten(int write_errno,
+ int bytes_written) {
+ web_ui_->CallJavascriptFunction("CertificateRestoreOverlay.dismiss");
+ ImportExportCleanup();
+ if (write_errno) {
+ ShowError(
+ l10n_util::GetStringUTF8(IDS_CERT_MANAGER_PKCS12_EXPORT_ERROR_TITLE),
+ l10n_util::GetStringFUTF8(IDS_CERT_MANAGER_WRITE_ERROR_FORMAT,
+ UTF8ToUTF16(safe_strerror(write_errno))));
+ }
+}
+
+void CertificateManagerHandler::StartImportPersonal(const ListValue* args) {
+ SelectFileDialog::FileTypeInfo file_type_info;
+ if (!args->GetBoolean(0, &use_hardware_backed_)){
+ // Unable to retrieve the hardware backed attribute from the args,
+ // so bail.
+ web_ui_->CallJavascriptFunction("CertificateRestoreOverlay.dismiss");
+ ImportExportCleanup();
+ return;
+ }
+ file_type_info.extensions.resize(1);
+ file_type_info.extensions[0].push_back(FILE_PATH_LITERAL("p12"));
+ file_type_info.extension_description_overrides.push_back(
+ l10n_util::GetStringUTF16(IDS_CERT_MANAGER_PKCS12_FILES));
+ file_type_info.include_all_files = true;
+ select_file_dialog_ = SelectFileDialog::Create(this);
+ select_file_dialog_->SelectFile(
+ SelectFileDialog::SELECT_OPEN_FILE, string16(),
+ FilePath(), &file_type_info, 1, FILE_PATH_LITERAL("p12"),
+ web_ui_->tab_contents(), GetParentWindow(),
+ reinterpret_cast<void*>(IMPORT_PERSONAL_FILE_SELECTED));
+}
+
+void CertificateManagerHandler::ImportPersonalFileSelected(
+ const FilePath& path) {
+ file_path_ = path;
+ web_ui_->CallJavascriptFunction(
+ "CertificateManager.importPersonalAskPassword");
+}
+
+void CertificateManagerHandler::ImportPersonalPasswordSelected(
+ const ListValue* args) {
+ if (!args->GetString(0, &password_)){
+ web_ui_->CallJavascriptFunction("CertificateRestoreOverlay.dismiss");
+ ImportExportCleanup();
+ return;
+ }
+ file_access_provider_->StartRead(
+ file_path_,
+ &consumer_,
+ NewCallback(this, &CertificateManagerHandler::ImportPersonalFileRead));
+}
+
+void CertificateManagerHandler::ImportPersonalFileRead(
+ int read_errno, std::string data) {
+ if (read_errno) {
+ ImportExportCleanup();
+ web_ui_->CallJavascriptFunction("CertificateRestoreOverlay.dismiss");
+ ShowError(
+ l10n_util::GetStringUTF8(IDS_CERT_MANAGER_PKCS12_IMPORT_ERROR_TITLE),
+ l10n_util::GetStringFUTF8(IDS_CERT_MANAGER_READ_ERROR_FORMAT,
+ UTF8ToUTF16(safe_strerror(read_errno))));
+ return;
+ }
+
+ file_data_ = data;
+
+ if (use_hardware_backed_) {
+ module_ = certificate_manager_model_->cert_db().GetPrivateModule();
+ } else {
+ module_ = certificate_manager_model_->cert_db().GetPublicModule();
+ }
+
+ net::CryptoModuleList modules;
+ modules.push_back(module_);
+ browser::UnlockSlotsIfNecessary(
+ modules,
+ browser::kCryptoModulePasswordCertImport,
+ "", // unused.
+ base::Bind(&CertificateManagerHandler::ImportPersonalSlotUnlocked,
+ base::Unretained(this)));
+}
+
+void CertificateManagerHandler::ImportPersonalSlotUnlocked() {
+ // Determine if the private key should be unextractable after the import.
+ // We do this by checking the value of |use_hardware_backed_| which is set
+ // to true if importing into a hardware module. Currently, this only happens
+ // for Chrome OS when the "Import and Bind" option is chosen.
+ bool is_extractable = !use_hardware_backed_;
+ int result = certificate_manager_model_->ImportFromPKCS12(
+ module_, file_data_, password_, is_extractable);
+ ImportExportCleanup();
+ web_ui_->CallJavascriptFunction("CertificateRestoreOverlay.dismiss");
+ int string_id;
+ switch (result) {
+ case net::OK:
+ return;
+ case net::ERR_PKCS12_IMPORT_BAD_PASSWORD:
+ // TODO(mattm): if the error was a bad password, we should reshow the
+ // password dialog after the user dismisses the error dialog.
+ string_id = IDS_CERT_MANAGER_BAD_PASSWORD;
+ break;
+ case net::ERR_PKCS12_IMPORT_INVALID_MAC:
+ string_id = IDS_CERT_MANAGER_PKCS12_IMPORT_INVALID_MAC;
+ break;
+ case net::ERR_PKCS12_IMPORT_INVALID_FILE:
+ string_id = IDS_CERT_MANAGER_PKCS12_IMPORT_INVALID_FILE;
+ break;
+ case net::ERR_PKCS12_IMPORT_UNSUPPORTED:
+ string_id = IDS_CERT_MANAGER_PKCS12_IMPORT_UNSUPPORTED;
+ break;
+ default:
+ string_id = IDS_CERT_MANAGER_UNKNOWN_ERROR;
+ break;
+ }
+ ShowError(
+ l10n_util::GetStringUTF8(IDS_CERT_MANAGER_PKCS12_IMPORT_ERROR_TITLE),
+ l10n_util::GetStringUTF8(string_id));
+}
+
+void CertificateManagerHandler::CancelImportExportProcess(
+ const ListValue* args) {
+ ImportExportCleanup();
+}
+
+void CertificateManagerHandler::ImportExportCleanup() {
+ file_path_.clear();
+ password_.clear();
+ file_data_.clear();
+ use_hardware_backed_ = false;
+ selected_cert_list_.clear();
+ module_ = NULL;
+
+ // There may be pending file dialogs, we need to tell them that we've gone
+ // away so they don't try and call back to us.
+ if (select_file_dialog_.get())
+ select_file_dialog_->ListenerDestroyed();
+ select_file_dialog_ = NULL;
+}
+
+void CertificateManagerHandler::ImportServer(const ListValue* args) {
+ select_file_dialog_ = SelectFileDialog::Create(this);
+ ShowCertSelectFileDialog(
+ select_file_dialog_.get(),
+ SelectFileDialog::SELECT_OPEN_FILE,
+ FilePath(),
+ web_ui_->tab_contents(),
+ GetParentWindow(),
+ reinterpret_cast<void*>(IMPORT_SERVER_FILE_SELECTED));
+}
+
+void CertificateManagerHandler::ImportServerFileSelected(const FilePath& path) {
+ file_path_ = path;
+ file_access_provider_->StartRead(
+ file_path_,
+ &consumer_,
+ NewCallback(this, &CertificateManagerHandler::ImportServerFileRead));
+}
+
+void CertificateManagerHandler::ImportServerFileRead(int read_errno,
+ std::string data) {
+ if (read_errno) {
+ ImportExportCleanup();
+ ShowError(
+ l10n_util::GetStringUTF8(IDS_CERT_MANAGER_SERVER_IMPORT_ERROR_TITLE),
+ l10n_util::GetStringFUTF8(IDS_CERT_MANAGER_READ_ERROR_FORMAT,
+ UTF8ToUTF16(safe_strerror(read_errno))));
+ return;
+ }
+
+ selected_cert_list_ = net::X509Certificate::CreateCertificateListFromBytes(
+ data.data(), data.size(), net::X509Certificate::FORMAT_AUTO);
+ if (selected_cert_list_.empty()) {
+ ImportExportCleanup();
+ ShowError(
+ l10n_util::GetStringUTF8(IDS_CERT_MANAGER_SERVER_IMPORT_ERROR_TITLE),
+ l10n_util::GetStringUTF8(IDS_CERT_MANAGER_CERT_PARSE_ERROR));
+ return;
+ }
+
+ net::CertDatabase::ImportCertFailureList not_imported;
+ bool result = certificate_manager_model_->ImportServerCert(
+ selected_cert_list_,
+ &not_imported);
+ if (!result) {
+ ShowError(
+ l10n_util::GetStringUTF8(IDS_CERT_MANAGER_SERVER_IMPORT_ERROR_TITLE),
+ l10n_util::GetStringUTF8(IDS_CERT_MANAGER_UNKNOWN_ERROR));
+ } else if (!not_imported.empty()) {
+ ShowImportErrors(
+ l10n_util::GetStringUTF8(IDS_CERT_MANAGER_SERVER_IMPORT_ERROR_TITLE),
+ not_imported);
+ }
+ ImportExportCleanup();
+}
+
+void CertificateManagerHandler::ImportCA(const ListValue* args) {
+ select_file_dialog_ = SelectFileDialog::Create(this);
+ ShowCertSelectFileDialog(select_file_dialog_.get(),
+ SelectFileDialog::SELECT_OPEN_FILE,
+ FilePath(),
+ web_ui_->tab_contents(),
+ GetParentWindow(),
+ reinterpret_cast<void*>(IMPORT_CA_FILE_SELECTED));
+}
+
+void CertificateManagerHandler::ImportCAFileSelected(const FilePath& path) {
+ file_path_ = path;
+ file_access_provider_->StartRead(
+ file_path_,
+ &consumer_,
+ NewCallback(this, &CertificateManagerHandler::ImportCAFileRead));
+}
+
+void CertificateManagerHandler::ImportCAFileRead(int read_errno,
+ std::string data) {
+ if (read_errno) {
+ ImportExportCleanup();
+ ShowError(
+ l10n_util::GetStringUTF8(IDS_CERT_MANAGER_CA_IMPORT_ERROR_TITLE),
+ l10n_util::GetStringFUTF8(IDS_CERT_MANAGER_READ_ERROR_FORMAT,
+ UTF8ToUTF16(safe_strerror(read_errno))));
+ return;
+ }
+
+ selected_cert_list_ = net::X509Certificate::CreateCertificateListFromBytes(
+ data.data(), data.size(), net::X509Certificate::FORMAT_AUTO);
+ if (selected_cert_list_.empty()) {
+ ImportExportCleanup();
+ ShowError(
+ l10n_util::GetStringUTF8(IDS_CERT_MANAGER_CA_IMPORT_ERROR_TITLE),
+ l10n_util::GetStringUTF8(IDS_CERT_MANAGER_CERT_PARSE_ERROR));
+ return;
+ }
+
+ scoped_refptr<net::X509Certificate> root_cert =
+ certificate_manager_model_->cert_db().FindRootInList(selected_cert_list_);
+
+ // TODO(mattm): check here if root_cert is not a CA cert and show error.
+
+ StringValue cert_name(root_cert->subject().GetDisplayName());
+ web_ui_->CallJavascriptFunction("CertificateEditCaTrustOverlay.showImport",
+ cert_name);
+}
+
+void CertificateManagerHandler::ImportCATrustSelected(const ListValue* args) {
+ bool fail = false;
+ bool trust_ssl = false;
+ bool trust_email = false;
+ bool trust_obj_sign = false;
+ fail |= !CallbackArgsToBool(args, 0, &trust_ssl);
+ fail |= !CallbackArgsToBool(args, 1, &trust_email);
+ fail |= !CallbackArgsToBool(args, 2, &trust_obj_sign);
+ if (fail) {
+ LOG(ERROR) << "ImportCATrustSelected args fail";
+ ImportExportCleanup();
+ web_ui_->CallJavascriptFunction("CertificateEditCaTrustOverlay.dismiss");
+ return;
+ }
+
+ net::CertDatabase::ImportCertFailureList not_imported;
+ bool result = certificate_manager_model_->ImportCACerts(
+ selected_cert_list_,
+ trust_ssl * net::CertDatabase::TRUSTED_SSL +
+ trust_email * net::CertDatabase::TRUSTED_EMAIL +
+ trust_obj_sign * net::CertDatabase::TRUSTED_OBJ_SIGN,
+ &not_imported);
+ web_ui_->CallJavascriptFunction("CertificateEditCaTrustOverlay.dismiss");
+ if (!result) {
+ ShowError(
+ l10n_util::GetStringUTF8(IDS_CERT_MANAGER_CA_IMPORT_ERROR_TITLE),
+ l10n_util::GetStringUTF8(IDS_CERT_MANAGER_UNKNOWN_ERROR));
+ } else if (!not_imported.empty()) {
+ ShowImportErrors(
+ l10n_util::GetStringUTF8(IDS_CERT_MANAGER_CA_IMPORT_ERROR_TITLE),
+ not_imported);
+ }
+ ImportExportCleanup();
+}
+
+void CertificateManagerHandler::Export(const ListValue* args) {
+ net::X509Certificate* cert = CallbackArgsToCert(args);
+ if (!cert)
+ return;
+ ShowCertExportDialog(web_ui_->tab_contents(), GetParentWindow(),
+ cert->os_cert_handle());
+}
+
+void CertificateManagerHandler::Delete(const ListValue* args) {
+ net::X509Certificate* cert = CallbackArgsToCert(args);
+ if (!cert)
+ return;
+ bool result = certificate_manager_model_->Delete(cert);
+ if (!result) {
+ // TODO(mattm): better error messages?
+ ShowError(
+ l10n_util::GetStringUTF8(IDS_CERT_MANAGER_DELETE_CERT_ERROR_TITLE),
+ l10n_util::GetStringUTF8(IDS_CERT_MANAGER_UNKNOWN_ERROR));
+ }
+}
+
+void CertificateManagerHandler::Populate(const ListValue* args) {
+ certificate_manager_model_->Refresh();
+}
+
+void CertificateManagerHandler::PopulateTree(const std::string& tab_name,
+ net::CertType type) {
+ const std::string tree_name = tab_name + "-tree";
+
+ scoped_ptr<icu::Collator> collator;
+ UErrorCode error = U_ZERO_ERROR;
+ collator.reset(
+ icu::Collator::createInstance(
+ icu::Locale(g_browser_process->GetApplicationLocale().c_str()),
+ error));
+ if (U_FAILURE(error))
+ collator.reset(NULL);
+ DictionaryIdComparator comparator(collator.get());
+ CertificateManagerModel::OrgGroupingMap map;
+
+ certificate_manager_model_->FilterAndBuildOrgGroupingMap(type, &map);
+
+ {
+ ListValue* nodes = new ListValue;
+ for (CertificateManagerModel::OrgGroupingMap::iterator i = map.begin();
+ i != map.end(); ++i) {
+ // Populate first level (org name).
+ DictionaryValue* dict = new DictionaryValue;
+ dict->SetString(kKeyId, OrgNameToId(i->first));
+ dict->SetString(kNameId, i->first);
+
+ // Populate second level (certs).
+ ListValue* subnodes = new ListValue;
+ for (net::CertificateList::const_iterator org_cert_it = i->second.begin();
+ org_cert_it != i->second.end(); ++org_cert_it) {
+ DictionaryValue* cert_dict = new DictionaryValue;
+ net::X509Certificate* cert = org_cert_it->get();
+ cert_dict->SetString(kKeyId, CertToId(*cert));
+ cert_dict->SetString(kNameId, certificate_manager_model_->GetColumnText(
+ *cert, CertificateManagerModel::COL_SUBJECT_NAME));
+ cert_dict->SetBoolean(
+ kReadOnlyId,
+ certificate_manager_model_->cert_db().IsReadOnly(cert));
+ cert_dict->SetBoolean(
+ kUntrustedId,
+ certificate_manager_model_->cert_db().IsUntrusted(cert));
+ // TODO(mattm): Other columns.
+ subnodes->Append(cert_dict);
+ }
+ std::sort(subnodes->begin(), subnodes->end(), comparator);
+
+ dict->Set(kSubNodesId, subnodes);
+ nodes->Append(dict);
+ }
+ std::sort(nodes->begin(), nodes->end(), comparator);
+
+ ListValue args;
+ args.Append(Value::CreateStringValue(tree_name));
+ args.Append(nodes);
+ web_ui_->CallJavascriptFunction("CertificateManager.onPopulateTree", args);
+ }
+}
+
+void CertificateManagerHandler::ShowError(const std::string& title,
+ const std::string& error) const {
+ ScopedVector<const Value> args;
+ args.push_back(Value::CreateStringValue(title));
+ args.push_back(Value::CreateStringValue(error));
+ args.push_back(Value::CreateStringValue(l10n_util::GetStringUTF8(IDS_OK)));
+ args.push_back(Value::CreateNullValue()); // cancelTitle
+ args.push_back(Value::CreateNullValue()); // okCallback
+ args.push_back(Value::CreateNullValue()); // cancelCallback
+ web_ui_->CallJavascriptFunction("AlertOverlay.show", args.get());
+}
+
+void CertificateManagerHandler::ShowImportErrors(
+ const std::string& title,
+ const net::CertDatabase::ImportCertFailureList& not_imported) const {
+ std::string error;
+ if (selected_cert_list_.size() == 1)
+ error = l10n_util::GetStringUTF8(
+ IDS_CERT_MANAGER_IMPORT_SINGLE_NOT_IMPORTED);
+ else if (not_imported.size() == selected_cert_list_.size())
+ error = l10n_util::GetStringUTF8(IDS_CERT_MANAGER_IMPORT_ALL_NOT_IMPORTED);
+ else
+ error = l10n_util::GetStringUTF8(IDS_CERT_MANAGER_IMPORT_SOME_NOT_IMPORTED);
+
+ ListValue cert_error_list;
+ for (size_t i = 0; i < not_imported.size(); ++i) {
+ const net::CertDatabase::ImportCertFailure& failure = not_imported[i];
+ DictionaryValue* dict = new DictionaryValue;
+ dict->SetString(kNameId, failure.certificate->subject().GetDisplayName());
+ dict->SetString(kErrorId, NetErrorToString(failure.net_error));
+ cert_error_list.Append(dict);
+ }
+
+ StringValue title_value(title);
+ StringValue error_value(error);
+ web_ui_->CallJavascriptFunction("CertificateImportErrorOverlay.show",
+ title_value,
+ error_value,
+ cert_error_list);
+}
+
+#if defined(OS_CHROMEOS)
+void CertificateManagerHandler::CheckTpmTokenReady(const ListValue* args) {
+ chromeos::CryptohomeLibrary* cryptohome =
+ chromeos::CrosLibrary::Get()->GetCryptohomeLibrary();
+
+ // TODO(xiyuan): Use async way when underlying supports it.
+ base::FundamentalValue ready(cryptohome->Pkcs11IsTpmTokenReady());
+ web_ui_->CallJavascriptFunction("CertificateManager.onCheckTpmTokenReady",
+ ready);
+}
+#endif
+
+gfx::NativeWindow CertificateManagerHandler::GetParentWindow() const {
+ return web_ui_->tab_contents()->view()->GetTopLevelNativeWindow();
+}
diff --git a/chrome/browser/ui/webui/options2/certificate_manager_handler.h b/chrome/browser/ui/webui/options2/certificate_manager_handler.h
new file mode 100644
index 0000000..9844a19
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/certificate_manager_handler.h
@@ -0,0 +1,171 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS2_CERTIFICATE_MANAGER_HANDLER_H_
+#define CHROME_BROWSER_UI_WEBUI_OPTIONS2_CERTIFICATE_MANAGER_HANDLER_H_
+#pragma once
+
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "chrome/browser/certificate_manager_model.h"
+#include "chrome/browser/ui/select_file_dialog.h"
+#include "chrome/browser/ui/webui/options2/options_ui2.h"
+#include "content/browser/cancelable_request.h"
+#include "net/base/cert_database.h"
+#include "ui/gfx/native_widget_types.h"
+
+class FileAccessProvider;
+
+class CertificateManagerHandler : public OptionsPage2UIHandler,
+ public CertificateManagerModel::Observer,
+ public SelectFileDialog::Listener {
+ public:
+ CertificateManagerHandler();
+ virtual ~CertificateManagerHandler();
+
+ // OptionsPage2UIHandler implementation.
+ virtual void GetLocalizedValues(
+ base::DictionaryValue* localized_strings) OVERRIDE;
+ virtual void RegisterMessages() OVERRIDE;
+
+ // CertificateManagerModel::Observer implementation.
+ virtual void CertificatesRefreshed() OVERRIDE;
+
+ // SelectFileDialog::Listener implementation.
+ virtual void FileSelected(const FilePath& path,
+ int index,
+ void* params) OVERRIDE;
+ virtual void FileSelectionCanceled(void* params) OVERRIDE;
+
+ private:
+ // View certificate.
+ void View(const base::ListValue* args);
+
+ // Edit server certificate trust values.
+ void EditServer(const base::ListValue* args);
+
+ // Edit certificate authority trust values. The sequence goes like:
+ // 1. user clicks edit button -> CertificateEditCaTrustOverlay.show ->
+ // GetCATrust -> CertificateEditCaTrustOverlay.populateTrust
+ // 2. user clicks ok -> EditCATrust -> CertificateEditCaTrustOverlay.dismiss
+ void GetCATrust(const base::ListValue* args);
+ void EditCATrust(const base::ListValue* args);
+
+ // Cleanup state stored during import or export process.
+ void CancelImportExportProcess(const base::ListValue* args);
+ void ImportExportCleanup();
+
+ // Export to PKCS #12 file. The sequence goes like:
+ // 1a. user click on export button -> ExportPersonal -> launches file
+ // selector
+ // 1b. user click on export all button -> ExportAllPersonal -> launches file
+ // selector
+ // 2. user selects file -> ExportPersonalFileSelected -> launches password
+ // dialog
+ // 3. user enters password -> ExportPersonalPasswordSelected -> unlock slots
+ // 4. slots unlocked -> ExportPersonalSlotsUnlocked -> exports to memory
+ // buffer -> starts async write operation
+ // 5. write finishes (or fails) -> ExportPersonalFileWritten
+ void ExportPersonal(const base::ListValue* args);
+ void ExportAllPersonal(const base::ListValue* args);
+ void ExportPersonalFileSelected(const FilePath& path);
+ void ExportPersonalPasswordSelected(const base::ListValue* args);
+ void ExportPersonalSlotsUnlocked();
+ void ExportPersonalFileWritten(int write_errno, int bytes_written);
+
+ // Import from PKCS #12 file. The sequence goes like:
+ // 1. user click on import button -> StartImportPersonal -> launches file
+ // selector
+ // 2. user selects file -> ImportPersonalFileSelected -> launches password
+ // dialog
+ // 3. user enters password -> ImportPersonalPasswordSelected -> starts async
+ // read operation
+ // 4. read operation completes -> ImportPersonalFileRead -> unlock slot
+ // 5. slot unlocked -> ImportPersonalSlotUnlocked attempts to
+ // import with previously entered password
+ // 6a. if import succeeds -> ImportExportCleanup
+ // 6b. if import fails -> show error, ImportExportCleanup
+ // TODO(mattm): allow retrying with different password
+ void StartImportPersonal(const base::ListValue* args);
+ void ImportPersonalFileSelected(const FilePath& path);
+ void ImportPersonalPasswordSelected(const base::ListValue* args);
+ void ImportPersonalFileRead(int read_errno, std::string data);
+ void ImportPersonalSlotUnlocked();
+
+ // Import Server certificates from file. Sequence goes like:
+ // 1. user clicks on import button -> ImportServer -> launches file selector
+ // 2. user selects file -> ImportServerFileSelected -> starts async read
+ // 3. read completes -> ImportServerFileRead -> parse certs -> attempt import
+ // 4a. if import succeeds -> ImportExportCleanup
+ // 4b. if import fails -> show error, ImportExportCleanup
+ void ImportServer(const base::ListValue* args);
+ void ImportServerFileSelected(const FilePath& path);
+ void ImportServerFileRead(int read_errno, std::string data);
+
+ // Import Certificate Authorities from file. Sequence goes like:
+ // 1. user clicks on import button -> ImportCA -> launches file selector
+ // 2. user selects file -> ImportCAFileSelected -> starts async read
+ // 3. read completes -> ImportCAFileRead -> parse certs ->
+ // CertificateEditCaTrustOverlay.showImport
+ // 4. user clicks ok -> ImportCATrustSelected -> attempt import
+ // 5a. if import succeeds -> ImportExportCleanup
+ // 5b. if import fails -> show error, ImportExportCleanup
+ void ImportCA(const base::ListValue* args);
+ void ImportCAFileSelected(const FilePath& path);
+ void ImportCAFileRead(int read_errno, std::string data);
+ void ImportCATrustSelected(const base::ListValue* args);
+
+ // Export a certificate.
+ void Export(const base::ListValue* args);
+
+ // Delete certificate and private key (if any).
+ void Delete(const base::ListValue* args);
+
+ // Populate the trees in all the tabs.
+ void Populate(const base::ListValue* args);
+
+ // Populate the given tab's tree.
+ void PopulateTree(const std::string& tab_name, net::CertType type);
+
+ // Display a WebUI error message box.
+ void ShowError(const std::string& title, const std::string& error) const;
+
+ // Display a WebUI error message box for import failures.
+ // Depends on |selected_cert_list_| being set to the imports that we
+ // attempted to import.
+ void ShowImportErrors(
+ const std::string& title,
+ const net::CertDatabase::ImportCertFailureList& not_imported) const;
+
+#if defined(OS_CHROMEOS)
+ // Check whether Tpm token is ready and notifiy JS side.
+ void CheckTpmTokenReady(const base::ListValue* args);
+#endif
+
+ gfx::NativeWindow GetParentWindow() const;
+
+ // The Certificates Manager model
+ scoped_ptr<CertificateManagerModel> certificate_manager_model_;
+
+ // For multi-step import or export processes, we need to store the path,
+ // password, etc the user chose while we wait for them to enter a password,
+ // wait for file to be read, etc.
+ FilePath file_path_;
+ string16 password_;
+ bool use_hardware_backed_;
+ std::string file_data_;
+ net::CertificateList selected_cert_list_;
+ scoped_refptr<SelectFileDialog> select_file_dialog_;
+ scoped_refptr<net::CryptoModule> module_;
+
+ // Used in reading and writing certificate files.
+ CancelableRequestConsumer consumer_;
+ scoped_refptr<FileAccessProvider> file_access_provider_;
+
+ DISALLOW_COPY_AND_ASSIGN(CertificateManagerHandler);
+};
+
+#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS2_CERTIFICATE_MANAGER_HANDLER_H_
diff --git a/chrome/browser/ui/webui/options2/chromeos/about_page_handler.cc b/chrome/browser/ui/webui/options2/chromeos/about_page_handler.cc
new file mode 100644
index 0000000..31d960e
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/chromeos/about_page_handler.cc
@@ -0,0 +1,419 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/webui/options2/chromeos/about_page_handler.h"
+
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/command_line.h"
+#include "base/i18n/time_formatting.h"
+#include "base/string16.h"
+#include "base/string_number_conversions.h"
+#include "base/time.h"
+#include "base/utf_string_conversions.h"
+#include "base/values.h"
+#include "chrome/browser/chromeos/dbus/dbus_thread_manager.h"
+#include "chrome/browser/chromeos/dbus/power_manager_client.h"
+#include "chrome/browser/chromeos/dbus/update_engine_client.h"
+#include "chrome/browser/chromeos/login/user_manager.h"
+#include "chrome/browser/chromeos/login/wizard_controller.h"
+#include "chrome/browser/google/google_util.h"
+#include "chrome/common/chrome_version_info.h"
+#include "chrome/common/url_constants.h"
+#include "content/public/common/content_client.h"
+#include "googleurl/src/gurl.h"
+#include "grit/chromium_strings.h"
+#include "grit/generated_resources.h"
+#include "grit/locale_settings.h"
+#include "grit/theme_resources.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "v8/include/v8.h"
+#include "webkit/glue/user_agent.h"
+#include "webkit/glue/webkit_glue.h"
+#include "webkit/glue/user_agent.h"
+
+namespace {
+
+// These are used as placeholder text around the links in the text in the
+// license.
+const char kBeginLink[] = "BEGIN_LINK";
+const char kEndLink[] = "END_LINK";
+const char kBeginLinkChr[] = "BEGIN_LINK_CHR";
+const char kBeginLinkOss[] = "BEGIN_LINK_OSS";
+const char kEndLinkChr[] = "END_LINK_CHR";
+const char kEndLinkOss[] = "END_LINK_OSS";
+const char kBeginLinkCrosOss[] = "BEGIN_LINK_CROS_OSS";
+const char kEndLinkCrosOss[] = "END_LINK_CROS_OSS";
+
+// Returns a substring [start, end) from |text|.
+std::string StringSubRange(const std::string& text, size_t start,
+ size_t end) {
+ DCHECK(end > start);
+ return text.substr(start, end - start);
+}
+
+} // namespace
+
+namespace chromeos {
+
+class AboutPageHandler::UpdateObserver
+ : public UpdateEngineClient::Observer {
+ public:
+ explicit UpdateObserver(AboutPageHandler* handler) : page_handler_(handler) {}
+ virtual ~UpdateObserver() {}
+
+ AboutPageHandler* page_handler() const { return page_handler_; }
+
+ private:
+ virtual void UpdateStatusChanged(
+ const UpdateEngineClient::Status& status) OVERRIDE {
+ page_handler_->UpdateStatus(status);
+ }
+
+ AboutPageHandler* page_handler_;
+
+ DISALLOW_COPY_AND_ASSIGN(UpdateObserver);
+};
+
+AboutPageHandler::AboutPageHandler()
+ : progress_(-1),
+ sticky_(false),
+ started_(false)
+{}
+
+AboutPageHandler::~AboutPageHandler() {
+ if (update_observer_.get()) {
+ DBusThreadManager::Get()->GetUpdateEngineClient()->
+ RemoveObserver(update_observer_.get());
+ }
+}
+
+void AboutPageHandler::GetLocalizedValues(DictionaryValue* localized_strings) {
+ DCHECK(localized_strings);
+
+ static OptionsStringResource resources[] = {
+ { "firmware", IDS_ABOUT_PAGE_FIRMWARE },
+ { "product", IDS_PRODUCT_OS_NAME },
+ { "os", IDS_PRODUCT_OS_NAME },
+ { "platform", IDS_PLATFORM_LABEL },
+ { "loading", IDS_ABOUT_PAGE_LOADING },
+ { "check_now", IDS_ABOUT_PAGE_CHECK_NOW },
+ { "update_status", IDS_UPGRADE_CHECK_STARTED },
+ { "restart_now", IDS_RELAUNCH_AND_UPDATE },
+ { "browser", IDS_PRODUCT_NAME },
+ { "more_info", IDS_ABOUT_PAGE_MORE_INFO },
+ { "copyright", IDS_ABOUT_VERSION_COPYRIGHT },
+ { "channel", IDS_ABOUT_PAGE_CHANNEL },
+ { "stable", IDS_ABOUT_PAGE_CHANNEL_STABLE },
+ { "beta", IDS_ABOUT_PAGE_CHANNEL_BETA },
+ { "dev", IDS_ABOUT_PAGE_CHANNEL_DEVELOPMENT },
+ { "canary", IDS_ABOUT_PAGE_CHANNEL_CANARY },
+ { "channel_warning_header", IDS_ABOUT_PAGE_CHANNEL_WARNING_HEADER },
+ { "channel_warning_text", IDS_ABOUT_PAGE_CHANNEL_WARNING_TEXT },
+ { "user_agent", IDS_ABOUT_VERSION_USER_AGENT },
+ { "command_line", IDS_ABOUT_VERSION_COMMAND_LINE },
+ };
+
+ RegisterStrings(localized_strings, resources, arraysize(resources));
+ RegisterTitle(localized_strings, "aboutPage", IDS_ABOUT_TAB_TITLE);
+
+ // browser version
+
+ chrome::VersionInfo version_info;
+ DCHECK(version_info.is_valid());
+
+ std::string browser_version = version_info.Version();
+ std::string version_modifier =
+ chrome::VersionInfo::GetVersionStringModifier();
+ if (!version_modifier.empty())
+ browser_version += " " + version_modifier;
+
+#if !defined(GOOGLE_CHROME_BUILD)
+ browser_version += " (";
+ browser_version += version_info.LastChange();
+ browser_version += ")";
+#endif
+
+ localized_strings->SetString("browser_version", browser_version);
+
+ // license
+
+ std::string text = l10n_util::GetStringUTF8(IDS_ABOUT_VERSION_LICENSE);
+
+ bool chromium_url_appears_first =
+ text.find(kBeginLinkChr) < text.find(kBeginLinkOss);
+
+ size_t link1 = text.find(kBeginLink);
+ DCHECK(link1 != std::string::npos);
+ size_t link1_end = text.find(kEndLink, link1);
+ DCHECK(link1_end != std::string::npos);
+ size_t link2 = text.find(kBeginLink, link1_end);
+ DCHECK(link2 != std::string::npos);
+ size_t link2_end = text.find(kEndLink, link2);
+ DCHECK(link2_end != std::string::npos);
+
+ localized_strings->SetString("license_content_0", text.substr(0, link1));
+ localized_strings->SetString("license_content_1",
+ StringSubRange(text, link1_end + strlen(kEndLinkOss), link2));
+ localized_strings->SetString("license_content_2",
+ text.substr(link2_end + strlen(kEndLinkOss)));
+
+ // The Chromium link within the main text of the dialog.
+ localized_strings->SetString(chromium_url_appears_first ?
+ "license_link_content_0" : "license_link_content_1",
+ StringSubRange(text,
+ text.find(kBeginLinkChr) + strlen(kBeginLinkChr),
+ text.find(kEndLinkChr)));
+ GURL url = google_util::AppendGoogleLocaleParam(
+ GURL(chrome::kChromiumProjectURL));
+ localized_strings->SetString(chromium_url_appears_first ?
+ "license_link_0" : "license_link_1", url.spec());
+
+ // The Open Source link within the main text of the dialog.
+ localized_strings->SetString(chromium_url_appears_first ?
+ "license_link_content_1" : "license_link_content_0",
+ StringSubRange(text,
+ text.find(kBeginLinkOss) + strlen(kBeginLinkOss),
+ text.find(kEndLinkOss)));
+ localized_strings->SetString(chromium_url_appears_first ?
+ "license_link_1" : "license_link_0", chrome::kChromeUICreditsURL);
+
+ std::string cros_text =
+ l10n_util::GetStringUTF8(IDS_ABOUT_CROS_VERSION_LICENSE);
+
+ size_t cros_link = cros_text.find(kBeginLinkCrosOss);
+ DCHECK(cros_link != std::string::npos);
+ size_t cros_link_end = cros_text.find(kEndLinkCrosOss, cros_link);
+ DCHECK(cros_link_end != std::string::npos);
+
+ localized_strings->SetString("cros_license_content_0",
+ cros_text.substr(0, cros_link));
+ localized_strings->SetString("cros_license_content_1",
+ cros_text.substr(cros_link_end + strlen(kEndLinkCrosOss)));
+ localized_strings->SetString("cros_license_link_content_0",
+ StringSubRange(cros_text, cros_link + strlen(kBeginLinkCrosOss),
+ cros_link_end));
+ localized_strings->SetString("cros_license_link_0",
+ chrome::kChromeUIOSCreditsURL);
+
+ // webkit
+
+ localized_strings->SetString("webkit_version",
+ webkit_glue::GetWebKitVersion());
+
+ // javascript
+
+ localized_strings->SetString("js_engine", "V8");
+ localized_strings->SetString("js_engine_version", v8::V8::GetVersion());
+
+ // user agent
+
+ localized_strings->SetString("user_agent_info",
+ content::GetUserAgent(GURL()));
+
+ // command line
+
+#if defined(OS_WIN)
+ localized_strings->SetString("command_line_info",
+ WideToUTF16(CommandLine::ForCurrentProcess()->GetCommandLineString()));
+#elif defined(OS_POSIX)
+ // TODO(viettrungluu): something horrible might happen if there are non-UTF-8
+ // arguments (since |SetString()| requires Unicode).
+ std::string command_line = "";
+ typedef std::vector<std::string> ArgvList;
+ const ArgvList& argv = CommandLine::ForCurrentProcess()->argv();
+ for (ArgvList::const_iterator iter = argv.begin(); iter != argv.end(); iter++)
+ command_line += " " + *iter;
+ localized_strings->SetString("command_line_info", command_line);
+#endif
+}
+
+void AboutPageHandler::RegisterMessages() {
+ web_ui_->RegisterMessageCallback("PageReady",
+ base::Bind(&AboutPageHandler::PageReady, base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("SetReleaseTrack",
+ base::Bind(&AboutPageHandler::SetReleaseTrack, base::Unretained(this)));
+
+ web_ui_->RegisterMessageCallback("CheckNow",
+ base::Bind(&AboutPageHandler::CheckNow, base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("RestartNow",
+ base::Bind(&AboutPageHandler::RestartNow, base::Unretained(this)));
+}
+
+void AboutPageHandler::PageReady(const ListValue* args) {
+ // Version information is loaded from a callback
+ loader_.GetVersion(&consumer_,
+ base::Bind(&AboutPageHandler::OnOSVersion,
+ base::Unretained(this)),
+ VersionLoader::VERSION_FULL);
+ loader_.GetFirmware(&consumer_,
+ base::Bind(&AboutPageHandler::OnOSFirmware,
+ base::Unretained(this)));
+
+ UpdateEngineClient* update_engine_client =
+ DBusThreadManager::Get()->GetUpdateEngineClient();
+
+ update_observer_.reset(new UpdateObserver(this));
+ update_engine_client->AddObserver(update_observer_.get());
+
+ // Update the WebUI page with the current status. See comments below.
+ UpdateStatus(update_engine_client->GetLastStatus());
+
+ // Initiate update check. UpdateStatus() below will be called when we
+ // get update status via update_observer_. If the update has been
+ // already complete, update_observer_ won't receive a notification.
+ // This is why we manually update the WebUI page above.
+ CheckNow(NULL);
+
+ // Request the channel information. Use the observer to track the about
+ // page handler and ensure it does not get deleted before the callback.
+ update_engine_client->GetReleaseTrack(
+ base::Bind(UpdateSelectedChannel, update_observer_.get()));
+}
+
+void AboutPageHandler::SetReleaseTrack(const ListValue* args) {
+ if (!UserManager::Get()->current_user_is_owner()) {
+ LOG(WARNING) << "Non-owner tried to change release track.";
+ return;
+ }
+ const std::string channel = UTF16ToUTF8(ExtractStringValue(args));
+ DBusThreadManager::Get()->GetUpdateEngineClient()->SetReleaseTrack(channel);
+}
+
+void AboutPageHandler::CheckNow(const ListValue* args) {
+ // Make sure that libcros is loaded and OOBE is complete.
+ if (!WizardController::default_controller() ||
+ WizardController::IsDeviceRegistered()) {
+ DBusThreadManager::Get()->GetUpdateEngineClient()->
+ RequestUpdateCheck(UpdateEngineClient::EmptyUpdateCheckCallback());
+ }
+}
+
+void AboutPageHandler::RestartNow(const ListValue* args) {
+ DBusThreadManager::Get()->GetPowerManagerClient()->RequestRestart();
+}
+
+void AboutPageHandler::UpdateStatus(
+ const UpdateEngineClient::Status& status) {
+ string16 message;
+ std::string image = "up-to-date";
+ bool enabled = false;
+
+ switch (status.status) {
+ case UpdateEngineClient::UPDATE_STATUS_IDLE:
+ if (!sticky_) {
+ message = l10n_util::GetStringFUTF16(IDS_UPGRADE_ALREADY_UP_TO_DATE,
+ l10n_util::GetStringUTF16(IDS_PRODUCT_OS_NAME));
+ enabled = true;
+ }
+ break;
+ case UpdateEngineClient::UPDATE_STATUS_CHECKING_FOR_UPDATE:
+ message = l10n_util::GetStringUTF16(IDS_UPGRADE_CHECK_STARTED);
+ sticky_ = false;
+ break;
+ case UpdateEngineClient::UPDATE_STATUS_UPDATE_AVAILABLE:
+ message = l10n_util::GetStringUTF16(IDS_UPDATE_AVAILABLE);
+ started_ = true;
+ break;
+ case UpdateEngineClient::UPDATE_STATUS_DOWNLOADING:
+ {
+ int progress = static_cast<int>(status.download_progress * 100.0);
+ if (progress != progress_) {
+ progress_ = progress;
+ message = l10n_util::GetStringFUTF16Int(IDS_UPDATE_DOWNLOADING,
+ progress_);
+ }
+ started_ = true;
+ }
+ break;
+ case UpdateEngineClient::UPDATE_STATUS_VERIFYING:
+ message = l10n_util::GetStringUTF16(IDS_UPDATE_VERIFYING);
+ started_ = true;
+ break;
+ case UpdateEngineClient::UPDATE_STATUS_FINALIZING:
+ message = l10n_util::GetStringUTF16(IDS_UPDATE_FINALIZING);
+ started_ = true;
+ break;
+ case UpdateEngineClient::UPDATE_STATUS_UPDATED_NEED_REBOOT:
+ message = l10n_util::GetStringUTF16(IDS_UPDATE_COMPLETED);
+ image = "available";
+ sticky_ = true;
+ break;
+ default:
+ // case UpdateEngineClient::UPDATE_STATUS_ERROR:
+ // case UpdateEngineClient::UPDATE_STATUS_REPORTING_ERROR_EVENT:
+
+ // The error is only displayed if we were able to determine an
+ // update was available.
+ if (started_) {
+ message = l10n_util::GetStringUTF16(IDS_UPDATE_ERROR);
+ image = "fail";
+ enabled = true;
+ sticky_ = true;
+ started_ = false;
+ }
+ break;
+ }
+ if (message.size()) {
+ scoped_ptr<Value> update_message(Value::CreateStringValue(message));
+ // "Checking for update..." needs to be shown for a while, so users
+ // can read it, hence insert delay for this.
+ scoped_ptr<Value> insert_delay(Value::CreateBooleanValue(
+ status.status ==
+ UpdateEngineClient::UPDATE_STATUS_CHECKING_FOR_UPDATE));
+ web_ui_->CallJavascriptFunction("AboutPage.updateStatusCallback",
+ *update_message, *insert_delay);
+
+ scoped_ptr<Value> enabled_value(Value::CreateBooleanValue(enabled));
+ web_ui_->CallJavascriptFunction("AboutPage.updateEnableCallback",
+ *enabled_value);
+
+ scoped_ptr<Value> image_string(Value::CreateStringValue(image));
+ web_ui_->CallJavascriptFunction("AboutPage.setUpdateImage",
+ *image_string);
+ }
+ // We'll change the "Check For Update" button to "Restart" button.
+ if (status.status == UpdateEngineClient::UPDATE_STATUS_UPDATED_NEED_REBOOT) {
+ web_ui_->CallJavascriptFunction("AboutPage.changeToRestartButton");
+ }
+}
+
+void AboutPageHandler::OnOSVersion(VersionLoader::Handle handle,
+ std::string version) {
+ if (version.size()) {
+ scoped_ptr<Value> version_string(Value::CreateStringValue(version));
+ web_ui_->CallJavascriptFunction("AboutPage.updateOSVersionCallback",
+ *version_string);
+ }
+}
+
+void AboutPageHandler::OnOSFirmware(VersionLoader::Handle handle,
+ std::string firmware) {
+ if (firmware.size()) {
+ scoped_ptr<Value> firmware_string(Value::CreateStringValue(firmware));
+ web_ui_->CallJavascriptFunction("AboutPage.updateOSFirmwareCallback",
+ *firmware_string);
+ }
+}
+
+// Callback from UpdateEngine with channel information.
+// static
+void AboutPageHandler::UpdateSelectedChannel(UpdateObserver* observer,
+ const std::string& channel) {
+ if (DBusThreadManager::Get()->GetUpdateEngineClient()
+ ->HasObserver(observer)) {
+ // If UpdateEngineClient still has the observer, then the page handler
+ // is valid.
+ AboutPageHandler* handler = observer->page_handler();
+ scoped_ptr<Value> channel_string(Value::CreateStringValue(channel));
+ handler->web_ui_->CallJavascriptFunction(
+ "AboutPage.updateSelectedOptionCallback", *channel_string);
+ }
+}
+
+} // namespace chromeos
diff --git a/chrome/browser/ui/webui/options2/chromeos/about_page_handler.h b/chrome/browser/ui/webui/options2/chromeos/about_page_handler.h
new file mode 100644
index 0000000..cfd78b0
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/chromeos/about_page_handler.h
@@ -0,0 +1,74 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_ABOUT_PAGE_HANDLER_H_
+#define CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_ABOUT_PAGE_HANDLER_H_
+
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "chrome/browser/ui/webui/options2/options_ui2.h"
+#include "chrome/browser/chromeos/dbus/update_engine_client.h"
+#include "chrome/browser/chromeos/version_loader.h"
+
+namespace chromeos {
+
+// ChromeOS about page UI handler.
+class AboutPageHandler : public OptionsPage2UIHandler {
+
+ public:
+ AboutPageHandler();
+ virtual ~AboutPageHandler();
+
+ // OptionsPage2UIHandler implementation.
+ virtual void GetLocalizedValues(
+ base::DictionaryValue* localized_strings) OVERRIDE;
+ virtual void RegisterMessages() OVERRIDE;
+
+ private:
+ class UpdateObserver;
+
+ // The function is called from JavaScript when the about page is ready.
+ void PageReady(const base::ListValue* args);
+
+ // The function is called from JavaScript to set the release track like
+ // "beta-channel" and "dev-channel".
+ void SetReleaseTrack(const base::ListValue* args);
+
+ // Initiates update check.
+ void CheckNow(const base::ListValue* args);
+
+ // Restarts the system.
+ void RestartNow(const base::ListValue* args);
+
+ // Callback from VersionLoader giving the version.
+ void OnOSVersion(VersionLoader::Handle handle,
+ std::string version);
+ void OnOSFirmware(VersionLoader::Handle handle,
+ std::string firmware);
+ void UpdateStatus(const UpdateEngineClient::Status& status);
+
+ // UpdateEngine Callback handler.
+ static void UpdateSelectedChannel(UpdateObserver* observer,
+ const std::string& channel);
+
+ // Handles asynchronously loading the version.
+ VersionLoader loader_;
+
+ // Used to request the version.
+ CancelableRequestConsumer consumer_;
+
+ // Update Observer
+ scoped_ptr<UpdateObserver> update_observer_;
+
+ int progress_;
+ bool sticky_;
+ bool started_;
+
+ DISALLOW_COPY_AND_ASSIGN(AboutPageHandler);
+};
+
+} // namespace chromeos
+
+#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_ABOUT_PAGE_HANDLER_H_
diff --git a/chrome/browser/ui/webui/options2/chromeos/accounts_options_handler.cc b/chrome/browser/ui/webui/options2/chromeos/accounts_options_handler.cc
new file mode 100644
index 0000000..396dcec
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/chromeos/accounts_options_handler.cc
@@ -0,0 +1,132 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/webui/options2/chromeos/accounts_options_handler.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/json/json_reader.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/utf_string_conversions.h"
+#include "base/values.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/chromeos/cros_settings.h"
+#include "chrome/browser/chromeos/cros_settings_names.h"
+#include "chrome/browser/chromeos/login/authenticator.h"
+#include "chrome/browser/chromeos/login/user_manager.h"
+#include "chrome/browser/prefs/pref_service.h"
+#include "chrome/browser/policy/browser_policy_connector.h"
+#include "grit/generated_resources.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace chromeos {
+
+namespace {
+
+// Adds specified user to the whitelist. Returns false if that user is already
+// in the whitelist.
+bool WhitelistUser(const std::string& username) {
+ CrosSettings* cros_settings = CrosSettings::Get();
+ if (cros_settings->FindEmailInList(kAccountsPrefUsers, username))
+ return false;
+ base::StringValue username_value(username);
+ cros_settings->AppendToList(kAccountsPrefUsers, &username_value);
+ return true;
+}
+
+} // namespace
+
+AccountsOptionsHandler::AccountsOptionsHandler() {
+}
+
+AccountsOptionsHandler::~AccountsOptionsHandler() {
+}
+
+void AccountsOptionsHandler::RegisterMessages() {
+ DCHECK(web_ui_);
+ web_ui_->RegisterMessageCallback("whitelistUser",
+ base::Bind(&AccountsOptionsHandler::HandleWhitelistUser,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("unwhitelistUser",
+ base::Bind(&AccountsOptionsHandler::HandleUnwhitelistUser,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("whitelistExistingUsers",
+ base::Bind(&AccountsOptionsHandler::HandleWhitelistExistingUsers,
+ base::Unretained(this)));
+}
+
+void AccountsOptionsHandler::GetLocalizedValues(
+ base::DictionaryValue* localized_strings) {
+ DCHECK(localized_strings);
+
+ RegisterTitle(localized_strings, "accountsPage",
+ IDS_OPTIONS_ACCOUNTS_TAB_LABEL);
+
+ localized_strings->SetString("allow_BWSI", l10n_util::GetStringUTF16(
+ IDS_OPTIONS_ACCOUNTS_ALLOW_BWSI_DESCRIPTION));
+ localized_strings->SetString("use_whitelist",l10n_util::GetStringUTF16(
+ IDS_OPTIONS_ACCOUNTS_USE_WHITELIST_DESCRIPTION));
+ localized_strings->SetString("show_user_on_signin",l10n_util::GetStringUTF16(
+ IDS_OPTIONS_ACCOUNTS_SHOW_USER_NAMES_ON_SINGIN_DESCRIPTION));
+ localized_strings->SetString("username_edit_hint",l10n_util::GetStringUTF16(
+ IDS_OPTIONS_ACCOUNTS_USERNAME_EDIT_HINT));
+ localized_strings->SetString("username_format",l10n_util::GetStringUTF16(
+ IDS_OPTIONS_ACCOUNTS_USERNAME_FORMAT));
+ localized_strings->SetString("add_users",l10n_util::GetStringUTF16(
+ IDS_OPTIONS_ACCOUNTS_ADD_USERS));
+ localized_strings->SetString("owner_only", l10n_util::GetStringUTF16(
+ IDS_OPTIONS_ACCOUNTS_OWNER_ONLY));
+
+ std::string owner_email;
+ CrosSettings::Get()->GetString(kDeviceOwner, &owner_email);
+ // Translate owner's email to the display email.
+ std::string display_email =
+ UserManager::Get()->GetUserDisplayEmail(owner_email);
+ localized_strings->SetString("owner_user_id", UTF8ToUTF16(display_email));
+
+ localized_strings->SetString("current_user_is_owner",
+ UserManager::Get()->current_user_is_owner() ?
+ ASCIIToUTF16("true") : ASCIIToUTF16("false"));
+ localized_strings->SetString("logged_in_as_guest",
+ UserManager::Get()->IsLoggedInAsGuest() ?
+ ASCIIToUTF16("true") : ASCIIToUTF16("false"));
+ localized_strings->SetString("whitelist_is_managed",
+ g_browser_process->browser_policy_connector()->IsEnterpriseManaged() ?
+ ASCIIToUTF16("true") : ASCIIToUTF16("false"));
+}
+
+void AccountsOptionsHandler::HandleWhitelistUser(const base::ListValue* args) {
+ std::string typed_email;
+ std::string name;
+ if (!args->GetString(0, &typed_email) ||
+ !args->GetString(1, &name)) {
+ return;
+ }
+
+ WhitelistUser(Authenticator::Canonicalize(typed_email));
+}
+
+void AccountsOptionsHandler::HandleUnwhitelistUser(
+ const base::ListValue* args) {
+ std::string email;
+ if (!args->GetString(0, &email)) {
+ return;
+ }
+
+ base::StringValue canonical_email(Authenticator::Canonicalize(email));
+ CrosSettings::Get()->RemoveFromList(kAccountsPrefUsers, &canonical_email);
+ UserManager::Get()->RemoveUser(email, NULL);
+}
+
+void AccountsOptionsHandler::HandleWhitelistExistingUsers(
+ const base::ListValue* args) {
+ DCHECK(args && args->empty());
+
+ const UserList& users = UserManager::Get()->GetUsers();
+ for (UserList::const_iterator it = users.begin(); it < users.end(); ++it) {
+ WhitelistUser((*it)->email());
+ }
+}
+
+} // namespace chromeos
diff --git a/chrome/browser/ui/webui/options2/chromeos/accounts_options_handler.h b/chrome/browser/ui/webui/options2/chromeos/accounts_options_handler.h
new file mode 100644
index 0000000..f16c5ba
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/chromeos/accounts_options_handler.h
@@ -0,0 +1,40 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_ACCOUNTS_OPTIONS_HANDLER_H_
+#define CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_ACCOUNTS_OPTIONS_HANDLER_H_
+#pragma once
+
+#include "base/compiler_specific.h"
+#include "chrome/browser/ui/webui/options2/options_ui2.h"
+
+namespace chromeos {
+
+// ChromeOS accounts options page handler.
+class AccountsOptionsHandler : public OptionsPage2UIHandler {
+ public:
+ AccountsOptionsHandler();
+ virtual ~AccountsOptionsHandler();
+
+ // WebUIMessageHandler implementation.
+ virtual void RegisterMessages() OVERRIDE;
+
+ // OptionsPage2UIHandler implementation.
+ virtual void GetLocalizedValues(
+ base::DictionaryValue* localized_strings) OVERRIDE;
+
+ private:
+ // Javascript callbacks to whitelist/unwhitelist user.
+ void HandleWhitelistUser(const base::ListValue* args);
+ void HandleUnwhitelistUser(const base::ListValue* args);
+
+ // Javascript callback to auto add existing users to white list.
+ void HandleWhitelistExistingUsers(const base::ListValue* args);
+
+ DISALLOW_COPY_AND_ASSIGN(AccountsOptionsHandler);
+};
+
+} // namespace chromeos
+
+#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_ACCOUNTS_OPTIONS_HANDLER_H_
diff --git a/chrome/browser/ui/webui/options2/chromeos/bluetooth_options_handler.cc b/chrome/browser/ui/webui/options2/chromeos/bluetooth_options_handler.cc
new file mode 100644
index 0000000..f8f6a28
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/chromeos/bluetooth_options_handler.cc
@@ -0,0 +1,411 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/webui/options2/chromeos/bluetooth_options_handler.h"
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/utf_string_conversions.h"
+#include "base/values.h"
+#include "chrome/browser/chromeos/bluetooth/bluetooth_device.h"
+#include "chrome/browser/chromeos/system/runtime_environment.h"
+#include "chrome/browser/ui/webui/options2/chromeos/system_settings_provider.h"
+#include "chrome/common/chrome_switches.h"
+#include "grit/chromium_strings.h"
+#include "grit/generated_resources.h"
+#include "third_party/cros_system_api/dbus/service_constants.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace {
+
+// |UpdateDeviceCallback| takes a variable length list as an argument. The
+// value stored in each list element is indicated by the following constants.
+const int kUpdateDeviceAddressIndex = 0;
+const int kUpdateDeviceCommandIndex = 1;
+const int kUpdateDevicePasskeyIndex = 2;
+
+} // namespace
+
+namespace chromeos {
+
+BluetoothOptionsHandler::BluetoothOptionsHandler() {
+}
+
+BluetoothOptionsHandler::~BluetoothOptionsHandler() {
+ if (!CommandLine::ForCurrentProcess()
+ ->HasSwitch(switches::kEnableBluetooth)) {
+ return;
+ }
+
+ chromeos::BluetoothManager* bluetooth_manager =
+ chromeos::BluetoothManager::GetInstance();
+ DCHECK(bluetooth_manager);
+
+ chromeos::BluetoothAdapter* default_adapter =
+ bluetooth_manager->DefaultAdapter();
+
+ if (default_adapter != NULL) {
+ default_adapter->RemoveObserver(this);
+ }
+
+ bluetooth_manager->RemoveObserver(this);
+}
+
+void BluetoothOptionsHandler::GetLocalizedValues(
+ DictionaryValue* localized_strings) {
+ DCHECK(localized_strings);
+
+ localized_strings->SetString("bluetooth",
+ l10n_util::GetStringUTF16(IDS_OPTIONS_SETTINGS_SECTION_TITLE_BLUETOOTH));
+ localized_strings->SetString("disableBluetooth",
+ l10n_util::GetStringUTF16(IDS_OPTIONS_SETTINGS_BLUETOOTH_DISABLE));
+ localized_strings->SetString("enableBluetooth",
+ l10n_util::GetStringUTF16(IDS_OPTIONS_SETTINGS_BLUETOOTH_ENABLE));
+ localized_strings->SetString("noBluetoothDevicesFound",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_NO_BLUETOOTH_DEVICES_FOUND));
+ localized_strings->SetString("findBluetoothDevices",
+ l10n_util::GetStringUTF16(IDS_OPTIONS_SETTINGS_FIND_BLUETOOTH_DEVICES));
+ localized_strings->SetString("bluetoothScanning",
+ l10n_util::GetStringUTF16(IDS_OPTIONS_SETTINGS_BLUETOOTH_SCANNING));
+ localized_strings->SetString("bluetoothDeviceConnected",
+ l10n_util::GetStringUTF16(IDS_OPTIONS_SETTINGS_BLUETOOTH_CONNECTED));
+ localized_strings->SetString("bluetoothDeviceConnecting",
+ l10n_util::GetStringUTF16(IDS_OPTIONS_SETTINGS_BLUETOOTH_CONNECTING));
+ localized_strings->SetString("bluetoothDeviceNotPaired",
+ l10n_util::GetStringUTF16(IDS_OPTIONS_SETTINGS_BLUETOOTH_NOT_PAIRED));
+ localized_strings->SetString("bluetoothDevicePaired",
+ l10n_util::GetStringUTF16(IDS_OPTIONS_SETTINGS_BLUETOOTH_PAIRED));
+ localized_strings->SetString("bluetoothDeviceFailedPairing",
+ l10n_util::GetStringUTF16(IDS_OPTIONS_SETTINGS_BLUETOOTH_FAILED_PAIRING));
+ localized_strings->SetString("bluetoothConnectDevice",
+ l10n_util::GetStringUTF16(IDS_OPTIONS_SETTINGS_BLUETOOTH_CONNECT));
+ localized_strings->SetString("bluetoothDisconnectDevice",
+ l10n_util::GetStringUTF16(IDS_OPTIONS_SETTINGS_BLUETOOTH_DISCONNECT));
+ localized_strings->SetString("bluetoothForgetDevice",
+ l10n_util::GetStringUTF16(IDS_OPTIONS_SETTINGS_BLUETOOTH_FORGET));
+ localized_strings->SetString("bluetoothCancel",
+ l10n_util::GetStringUTF16(IDS_OPTIONS_SETTINGS_BLUETOOTH_CANCEL));
+ localized_strings->SetString("bluetoothAcceptPasskey",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_BLUETOOTH_ACCEPT_PASSKEY));
+ localized_strings->SetString("bluetoothRejectPasskey",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_BLUETOOTH_REJECT_PASSKEY));
+ localized_strings->SetString("bluetoothConfirmPasskey",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_BLUETOOTH_CONFIRM_PASSKEY_REQUEST));
+ localized_strings->SetString("bluetoothEnterPasskey",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_BLUETOOTH_ENTER_PASSKEY_REQUEST));
+ localized_strings->SetString("bluetoothRemotePasskey",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_BLUETOOTH_REMOTE_PASSKEY_REQUEST));
+ localized_strings->SetString("bluetoothFailedPairingInstructions",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_BLUETOOTH_FAILED_PAIRING_INSTRUCTIONS));
+}
+
+void BluetoothOptionsHandler::Initialize() {
+ DCHECK(web_ui_);
+ // Bluetooth support is a work in progress. Supress the feature unless
+ // explicitly enabled via a command line flag.
+ if (!CommandLine::ForCurrentProcess()
+ ->HasSwitch(switches::kEnableBluetooth)) {
+ return;
+ }
+
+ web_ui_->CallJavascriptFunction(
+ "options.SystemOptions.showBluetoothSettings");
+
+ // TODO(kevers): Determine whether bluetooth adapter is powered.
+ bool bluetooth_on = false;
+ base::FundamentalValue checked(bluetooth_on);
+ web_ui_->CallJavascriptFunction(
+ "options.SystemOptions.setBluetoothState", checked);
+
+ chromeos::BluetoothManager* bluetooth_manager =
+ chromeos::BluetoothManager::GetInstance();
+ DCHECK(bluetooth_manager);
+ bluetooth_manager->AddObserver(this);
+
+ chromeos::BluetoothAdapter* default_adapter =
+ bluetooth_manager->DefaultAdapter();
+ DefaultAdapterChanged(default_adapter);
+}
+
+void BluetoothOptionsHandler::RegisterMessages() {
+ DCHECK(web_ui_);
+ web_ui_->RegisterMessageCallback("bluetoothEnableChange",
+ base::Bind(&BluetoothOptionsHandler::EnableChangeCallback,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("findBluetoothDevices",
+ base::Bind(&BluetoothOptionsHandler::FindDevicesCallback,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("updateBluetoothDevice",
+ base::Bind(&BluetoothOptionsHandler::UpdateDeviceCallback,
+ base::Unretained(this)));
+}
+
+void BluetoothOptionsHandler::EnableChangeCallback(
+ const ListValue* args) {
+ bool bluetooth_enabled;
+ args->GetBoolean(0, &bluetooth_enabled);
+ // TODO(kevers): Call Bluetooth API to enable or disable.
+ base::FundamentalValue checked(bluetooth_enabled);
+ web_ui_->CallJavascriptFunction(
+ "options.SystemOptions.setBluetoothState", checked);
+}
+
+void BluetoothOptionsHandler::FindDevicesCallback(
+ const ListValue* args) {
+ // We only initiate a scan if we're running on Chrome OS. Otherwise, we
+ // generate a fake device list.
+ if (!chromeos::system::runtime_environment::IsRunningOnChromeOS()) {
+ GenerateFakeDeviceList();
+ return;
+ }
+
+ chromeos::BluetoothManager* bluetooth_manager =
+ chromeos::BluetoothManager::GetInstance();
+ DCHECK(bluetooth_manager);
+
+ chromeos::BluetoothAdapter* default_adapter =
+ bluetooth_manager->DefaultAdapter();
+
+ ValidateDefaultAdapter(default_adapter);
+
+ if (default_adapter == NULL) {
+ VLOG(1) << "FindDevicesCallback: no default adapter";
+ return;
+ }
+
+ default_adapter->StartDiscovery();
+}
+
+void BluetoothOptionsHandler::UpdateDeviceCallback(
+ const ListValue* args) {
+ // TODO(kevers): Trigger connect/disconnect.
+ int size = args->GetSize();
+ std::string address;
+ std::string command;
+ args->GetString(kUpdateDeviceAddressIndex, &address);
+ args->GetString(kUpdateDeviceCommandIndex, &command);
+ if (size > kUpdateDevicePasskeyIndex) {
+ // Passkey confirmation as part of the pairing process.
+ std::string passkey;
+ args->GetString(kUpdateDevicePasskeyIndex, &passkey);
+ DVLOG(1) << "UpdateDeviceCallback: " << address << ": " << command
+ << " [" << passkey << "]";
+ } else {
+ // Initiating a device connection or disconnecting
+ DVLOG(1) << "UpdateDeviceCallback: " << address << ": " << command;
+ }
+}
+
+void BluetoothOptionsHandler::SendDeviceNotification(
+ chromeos::BluetoothDevice* device,
+ base::DictionaryValue* params) {
+ // Retrieve properties of the bluetooth device. The properties names are
+ // in title case. Convert to camel case in accordance with our Javascript
+ // naming convention.
+ const DictionaryValue& properties = device->AsDictionary();
+ base::DictionaryValue js_properties;
+ for (DictionaryValue::key_iterator it = properties.begin_keys();
+ it != properties.end_keys(); ++it) {
+ base::Value* child = NULL;
+ properties.GetWithoutPathExpansion(*it, &child);
+ if (child) {
+ std::string js_key = *it;
+ js_key[0] = tolower(js_key[0]);
+ js_properties.SetWithoutPathExpansion(js_key, child->DeepCopy());
+ }
+ }
+ if (params) {
+ js_properties.MergeDictionary(params);
+ }
+ web_ui_->CallJavascriptFunction(
+ "options.SystemOptions.addBluetoothDevice",
+ js_properties);
+}
+
+void BluetoothOptionsHandler::RequestConfirmation(
+ chromeos::BluetoothDevice* device,
+ int passkey) {
+ DictionaryValue params;
+ params.SetString("pairing", "bluetoothConfirmPasskey");
+ params.SetInteger("passkey", passkey);
+ SendDeviceNotification(device, &params);
+}
+
+void BluetoothOptionsHandler::DisplayPasskey(
+ chromeos::BluetoothDevice* device,
+ int passkey,
+ int entered) {
+ DictionaryValue params;
+ params.SetString("pairing", "bluetoothRemotePasskey");
+ params.SetInteger("passkey", passkey);
+ params.SetInteger("entered", entered);
+ SendDeviceNotification(device, &params);
+}
+
+void BluetoothOptionsHandler::RequestPasskey(
+ chromeos::BluetoothDevice* device) {
+ DictionaryValue params;
+ params.SetString("pairing", "bluetoothEnterPasskey");
+ SendDeviceNotification(device, &params);
+}
+
+void BluetoothOptionsHandler::ValidatePasskeyCallback(
+ const base::ListValue* args) {
+ // TODO(kevers): Implement me.
+}
+
+void BluetoothOptionsHandler::DefaultAdapterChanged(
+ chromeos::BluetoothAdapter* adapter) {
+ std::string old_default_adapter_id = default_adapter_id_;
+
+ if (adapter == NULL) {
+ default_adapter_id_.clear();
+ VLOG(2) << "DefaultAdapterChanged: no default bluetooth adapter";
+ } else {
+ default_adapter_id_ = adapter->Id();
+ VLOG(2) << "DefaultAdapterChanged: " << default_adapter_id_;
+ }
+
+ if (default_adapter_id_ == old_default_adapter_id) {
+ return;
+ }
+
+ if (adapter != NULL) {
+ adapter->AddObserver(this);
+ }
+
+ // TODO(vlaviano): Respond to adapter change.
+}
+
+void BluetoothOptionsHandler::DiscoveryStarted(const std::string& adapter_id) {
+ VLOG(2) << "Discovery started on " << adapter_id;
+}
+
+void BluetoothOptionsHandler::DiscoveryEnded(const std::string& adapter_id) {
+ VLOG(2) << "Discovery ended on " << adapter_id;
+ web_ui_->CallJavascriptFunction(
+ "options.SystemOptions.notifyBluetoothSearchComplete");
+
+ // Stop the discovery session.
+ // TODO(vlaviano): We may want to expose DeviceDisappeared, remove the
+ // "Find devices" button, and let the discovery session continue throughout
+ // the time that the page is visible rather than just doing a single discovery
+ // cycle in response to a button click.
+ chromeos::BluetoothManager* bluetooth_manager =
+ chromeos::BluetoothManager::GetInstance();
+ DCHECK(bluetooth_manager);
+
+ chromeos::BluetoothAdapter* default_adapter =
+ bluetooth_manager->DefaultAdapter();
+
+ ValidateDefaultAdapter(default_adapter);
+
+ if (default_adapter == NULL) {
+ VLOG(1) << "DiscoveryEnded: no default adapter";
+ return;
+ }
+
+ default_adapter->StopDiscovery();
+}
+
+void BluetoothOptionsHandler::DeviceFound(const std::string& adapter_id,
+ chromeos::BluetoothDevice* device) {
+ VLOG(2) << "Device found on " << adapter_id;
+ DCHECK(device);
+ SendDeviceNotification(device, NULL);
+}
+
+void BluetoothOptionsHandler::ValidateDefaultAdapter(
+ chromeos::BluetoothAdapter* adapter) {
+ if ((adapter == NULL && !default_adapter_id_.empty()) ||
+ (adapter != NULL && default_adapter_id_ != adapter->Id())) {
+ VLOG(1) << "unexpected default adapter change from \""
+ << default_adapter_id_ << "\" to \"" << adapter->Id() << "\"";
+ DefaultAdapterChanged(adapter);
+ }
+}
+
+void BluetoothOptionsHandler::GenerateFakeDeviceList() {
+ GenerateFakeDevice(
+ "Fake Wireless Keyboard",
+ "01-02-03-04-05-06",
+ "input-keyboard",
+ true,
+ true,
+ "");
+ GenerateFakeDevice(
+ "Fake Wireless Mouse",
+ "02-03-04-05-06-01",
+ "input-mouse",
+ true,
+ false,
+ "");
+ GenerateFakeDevice(
+ "Fake Wireless Headset",
+ "03-04-05-06-01-02",
+ "headset",
+ false,
+ false,
+ "");
+ GenerateFakeDevice(
+ "Fake Connecting Keyboard",
+ "04-05-06-01-02-03",
+ "input-keyboard",
+ false,
+ false,
+ "bluetoothRemotePasskey");
+ GenerateFakeDevice(
+ "Fake Connecting Phone",
+ "05-06-01-02-03-04",
+ "phone",
+ false,
+ false,
+ "bluetoothConfirmPasskey");
+ GenerateFakeDevice(
+ "Fake Connecting Headset",
+ "06-01-02-03-04-05",
+ "headset",
+ false,
+ false,
+ "bluetoothEnterPasskey");
+ web_ui_->CallJavascriptFunction(
+ "options.SystemOptions.notifyBluetoothSearchComplete");
+}
+
+void BluetoothOptionsHandler::GenerateFakeDevice(
+ const std::string& name,
+ const std::string& address,
+ const std::string& icon,
+ bool paired,
+ bool connected,
+ const std::string& pairing) {
+ DictionaryValue properties;
+ properties.SetString(bluetooth_device::kNameProperty, name);
+ properties.SetString(bluetooth_device::kAddressProperty, address);
+ properties.SetString(bluetooth_device::kIconProperty, icon);
+ properties.SetBoolean(bluetooth_device::kPairedProperty, paired);
+ properties.SetBoolean(bluetooth_device::kConnectedProperty, connected);
+ properties.SetInteger(bluetooth_device::kClassProperty, 0);
+ chromeos::BluetoothDevice* device =
+ chromeos::BluetoothDevice::Create(properties);
+ DeviceFound("FakeAdapter", device);
+ if (pairing.compare("bluetoothRemotePasskey") == 0) {
+ DisplayPasskey(device, 12345, 2);
+ } else if (pairing.compare("bluetoothConfirmPasskey") == 0) {
+ RequestConfirmation(device, 12345);
+ } else if (pairing.compare("bluetoothEnterPasskey") == 0) {
+ RequestPasskey(device);
+ }
+ delete device;
+}
+
+} // namespace chromeos
+
diff --git a/chrome/browser/ui/webui/options2/chromeos/bluetooth_options_handler.h b/chrome/browser/ui/webui/options2/chromeos/bluetooth_options_handler.h
new file mode 100644
index 0000000..f719734
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/chromeos/bluetooth_options_handler.h
@@ -0,0 +1,138 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_BLUETOOTH_OPTIONS_HANDLER_H_
+#define CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_BLUETOOTH_OPTIONS_HANDLER_H_
+#pragma once
+
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "chrome/browser/chromeos/bluetooth/bluetooth_adapter.h"
+#include "chrome/browser/chromeos/bluetooth/bluetooth_manager.h"
+#include "chrome/browser/ui/webui/options2/options_ui2.h"
+
+namespace base {
+class DictionaryValue;
+}
+
+namespace chromeos {
+
+// Handler for Bluetooth options on the system options page.
+class BluetoothOptionsHandler : public OptionsPage2UIHandler,
+ public chromeos::BluetoothManager::Observer,
+ public chromeos::BluetoothAdapter::Observer {
+ public:
+ BluetoothOptionsHandler();
+ virtual ~BluetoothOptionsHandler();
+
+ // OptionsPage2UIHandler implementation.
+ virtual void GetLocalizedValues(
+ base::DictionaryValue* localized_strings) OVERRIDE;
+ virtual void Initialize() OVERRIDE;
+ virtual void RegisterMessages() OVERRIDE;
+
+ // Called when the 'Enable bluetooth' checkbox value is changed.
+ // |args| will contain the checkbox checked state as a string
+ // ("true" or "false").
+ void EnableChangeCallback(const base::ListValue* args);
+
+ // Called when the 'Find Devices' button is pressed from the Bluetooth
+ // ssettings.
+ // |args| will be an empty list.
+ void FindDevicesCallback(const base::ListValue* args);
+
+ // Called when the user requests to connect to or disconnect from a Bluetooth
+ // device.
+ // |args| will be a list containing two or three arguments, the first argument
+ // is the device ID and the second is the requested action. If a third
+ // argument is present, it is the passkey for pairing confirmation.
+ void UpdateDeviceCallback(const base::ListValue* args);
+
+ // Sends a notification to the Web UI of the status of a Bluetooth device.
+ // |device| is the Bluetooth device.
+ // |params| is an optional set of parameters.
+ void SendDeviceNotification(chromeos::BluetoothDevice* device,
+ base::DictionaryValue* params);
+
+ // Displays a passkey for a device, requesting user confirmation that the
+ // key matches an expected value (value displayed on a smartphone for
+ // example).
+ // |device| is the Bluetooth device being paired.
+ // |passkey| is the passkey to display for confirmation.
+ void RequestConfirmation(chromeos::BluetoothDevice* device,
+ int passkey);
+
+ // Displays a passkey for a device, which is being typed remotely. During
+ // the pairing process, this method may be called repeatedly to track the
+ // number of characters entered. This method is commonly used for pairing
+ // keyboards.
+ // |device| is the Bluetooth device being paired.
+ // |passkey| is the required passkey.
+ // |entered| is the number of characters that have already been entered on
+ // the remote device.
+ void DisplayPasskey(chromeos::BluetoothDevice* device,
+ int passkey,
+ int entered);
+
+ // Displays a blank field for entering a passkey. The passkey may be
+ // a set value specified by the manufacturer of the Bluetooth device, or
+ // on a remote display. The validation is asychronous, and a call is made
+ // to |ValidatePasskeyCallback| when the passkey entry is complete.
+ // |device| is the Bluetooth device being paired.
+ void RequestPasskey(chromeos::BluetoothDevice* device);
+
+ // Callback to validate a user entered passkey.
+ // |args| is a list containing the device address and entered passkey.
+ void ValidatePasskeyCallback(const base::ListValue* args);
+
+ // chromeos::BluetoothManager::Observer override.
+ virtual void DefaultAdapterChanged(
+ chromeos::BluetoothAdapter* adapter) OVERRIDE;
+
+ // chromeos::BluetoothAdapter::Observer override.
+ virtual void DiscoveryStarted(const std::string& adapter_id) OVERRIDE;
+
+ // chromeos::BluetoothAdapter::Observer override.
+ virtual void DiscoveryEnded(const std::string& adapter_id) OVERRIDE;
+
+ // chromeos::BluetoothAdapter::Observer override.
+ virtual void DeviceFound(const std::string& adapter_id,
+ chromeos::BluetoothDevice* device) OVERRIDE;
+
+ private:
+ // Compares |adapter| with our cached default adapter ID and calls
+ // DefaultAdapterChanged if there has been an unexpected change.
+ void ValidateDefaultAdapter(chromeos::BluetoothAdapter* adapter);
+
+ // Simulates extracting a list of available bluetooth devices.
+ // Called when emulating ChromeOS from a desktop environment.
+ void GenerateFakeDeviceList();
+
+ // Simulates the discovery or pairing of a Bluetooth device. Used when
+ // emulating ChromeOS from a desktop environment.
+ // |name| is the display name for the device.
+ // |address| is the unique Mac address of the device.
+ // |icon| is the base name of the icon to use for the device and corresponds
+ // to the general device category (e.g. mouse or keyboard).
+ // |paired| indicates if the device is paired.
+ // |connected| indicates if the device is connected.
+ // |pairing| indicates the type of pairing operation.
+ void GenerateFakeDevice(const std::string& name,
+ const std::string& address,
+ const std::string& icon,
+ bool paired,
+ bool connected,
+ const std::string& pairing);
+
+ // The id of the current default bluetooth adapter.
+ // The empty string represents "none".
+ std::string default_adapter_id_;
+
+ DISALLOW_COPY_AND_ASSIGN(BluetoothOptionsHandler);
+};
+
+} // namespace chromeos
+
+#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_BLUETOOTH_OPTIONS_HANDLER_H_
diff --git a/chrome/browser/ui/webui/options2/chromeos/change_picture_options_handler.cc b/chrome/browser/ui/webui/options2/chromeos/change_picture_options_handler.cc
new file mode 100644
index 0000000..c3ac974
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/chromeos/change_picture_options_handler.cc
@@ -0,0 +1,344 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/webui/options2/chromeos/change_picture_options_handler.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/metrics/histogram.h"
+#include "base/path_service.h"
+#include "base/string_util.h"
+#include "base/values.h"
+#include "chrome/browser/chromeos/login/camera_detector.h"
+#include "chrome/browser/chromeos/login/default_user_images.h"
+#include "chrome/browser/chromeos/login/user_manager.h"
+#include "chrome/browser/chromeos/options/take_photo_dialog.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/browser_list.h"
+#include "chrome/browser/ui/browser_window.h"
+#include "chrome/browser/ui/dialog_style.h"
+#include "chrome/browser/ui/views/window.h"
+#include "chrome/browser/ui/webui/web_ui_util.h"
+#include "chrome/common/chrome_notification_types.h"
+#include "chrome/common/chrome_paths.h"
+#include "chrome/common/url_constants.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/common/url_constants.h"
+#include "grit/generated_resources.h"
+#include "grit/theme_resources.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/views/widget/widget.h"
+
+namespace chromeos {
+
+namespace {
+
+// Returns info about extensions for files we support as user images.
+SelectFileDialog::FileTypeInfo GetUserImageFileTypeInfo() {
+ SelectFileDialog::FileTypeInfo file_type_info;
+ file_type_info.extensions.resize(5);
+
+ file_type_info.extensions[0].push_back(FILE_PATH_LITERAL("bmp"));
+
+ file_type_info.extensions[1].push_back(FILE_PATH_LITERAL("gif"));
+
+ file_type_info.extensions[2].push_back(FILE_PATH_LITERAL("jpg"));
+ file_type_info.extensions[2].push_back(FILE_PATH_LITERAL("jpeg"));
+
+ file_type_info.extensions[3].push_back(FILE_PATH_LITERAL("png"));
+
+ file_type_info.extensions[4].push_back(FILE_PATH_LITERAL("tif"));
+ file_type_info.extensions[4].push_back(FILE_PATH_LITERAL("tiff"));
+
+ return file_type_info;
+}
+
+} // namespace
+
+ChangePictureOptionsHandler::ChangePictureOptionsHandler()
+ : previous_image_data_url_(chrome::kAboutBlankURL),
+ previous_image_index_(User::kInvalidImageIndex),
+ ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)) {
+ registrar_.Add(this, chrome::NOTIFICATION_PROFILE_IMAGE_UPDATED,
+ content::NotificationService::AllSources());
+ registrar_.Add(this, chrome::NOTIFICATION_PROFILE_IMAGE_UPDATE_FAILED,
+ content::NotificationService::AllSources());
+}
+
+ChangePictureOptionsHandler::~ChangePictureOptionsHandler() {
+ if (select_file_dialog_.get())
+ select_file_dialog_->ListenerDestroyed();
+}
+
+void ChangePictureOptionsHandler::GetLocalizedValues(
+ DictionaryValue* localized_strings) {
+ DCHECK(localized_strings);
+ localized_strings->SetString("changePicturePage",
+ l10n_util::GetStringUTF16(IDS_OPTIONS_CHANGE_PICTURE_DIALOG_TITLE));
+ localized_strings->SetString("changePicturePageDescription",
+ l10n_util::GetStringUTF16(IDS_OPTIONS_CHANGE_PICTURE_DIALOG_TEXT));
+ localized_strings->SetString("takePhoto",
+ l10n_util::GetStringUTF16(IDS_OPTIONS_CHANGE_PICTURE_TAKE_PHOTO));
+ localized_strings->SetString("chooseFile",
+ l10n_util::GetStringUTF16(IDS_OPTIONS_CHANGE_PICTURE_CHOOSE_FILE));
+ localized_strings->SetString("profilePhoto",
+ l10n_util::GetStringUTF16(IDS_OPTIONS_CHANGE_PICTURE_PROFILE_PHOTO));
+ localized_strings->SetString("profilePhotoLoading",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_CHANGE_PICTURE_PROFILE_LOADING_PHOTO));
+}
+
+void ChangePictureOptionsHandler::RegisterMessages() {
+ DCHECK(web_ui_);
+ web_ui_->RegisterMessageCallback("chooseFile",
+ base::Bind(&ChangePictureOptionsHandler::HandleChooseFile,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("takePhoto",
+ base::Bind(&ChangePictureOptionsHandler::HandleTakePhoto,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("onChangePicturePageShown",
+ base::Bind(&ChangePictureOptionsHandler::HandlePageShown,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("onChangePicturePageInitialized",
+ base::Bind(&ChangePictureOptionsHandler::HandlePageInitialized,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("selectImage",
+ base::Bind(&ChangePictureOptionsHandler::HandleSelectImage,
+ base::Unretained(this)));
+}
+
+void ChangePictureOptionsHandler::SendDefaultImages() {
+ ListValue image_urls;
+ for (int i = 0; i < kDefaultImagesCount; ++i) {
+ image_urls.Append(new StringValue(GetDefaultImageUrl(i)));
+ }
+ web_ui_->CallJavascriptFunction("ChangePictureOptions.setDefaultImages",
+ image_urls);
+}
+
+void ChangePictureOptionsHandler::HandleChooseFile(const ListValue* args) {
+ DCHECK(args && args->empty());
+ if (!select_file_dialog_.get())
+ select_file_dialog_ = SelectFileDialog::Create(this);
+
+ FilePath downloads_path;
+ if (!PathService::Get(chrome::DIR_DEFAULT_DOWNLOADS, &downloads_path)) {
+ NOTREACHED();
+ return;
+ }
+
+ // Static so we initialize it only once.
+ CR_DEFINE_STATIC_LOCAL(SelectFileDialog::FileTypeInfo, file_type_info,
+ (GetUserImageFileTypeInfo()));
+
+ select_file_dialog_->SelectFile(
+ SelectFileDialog::SELECT_OPEN_FILE,
+ l10n_util::GetStringUTF16(IDS_DOWNLOAD_TITLE),
+ downloads_path,
+ &file_type_info,
+ 0,
+ FILE_PATH_LITERAL(""),
+ web_ui_->tab_contents(),
+ GetBrowserWindow(),
+ NULL);
+}
+
+void ChangePictureOptionsHandler::HandleTakePhoto(const ListValue* args) {
+ DCHECK(args && args->empty());
+ views::Widget* window = browser::CreateViewsWindow(
+ GetBrowserWindow(),
+ new TakePhotoDialog(this),
+ STYLE_GENERIC);
+ window->SetAlwaysOnTop(true);
+ window->Show();
+}
+
+void ChangePictureOptionsHandler::HandlePageInitialized(
+ const base::ListValue* args) {
+ DCHECK(args && args->empty());
+ // If no camera presence check has been performed in this session,
+ // start one now.
+ if (CameraDetector::camera_presence() ==
+ CameraDetector::kCameraPresenceUnknown) {
+ CheckCameraPresence();
+ }
+
+ // While the check is in progress, use previous camera presence state and
+ // presume it is present if no check has been performed yet.
+ SetCameraPresent(CameraDetector::camera_presence() !=
+ CameraDetector::kCameraAbsent);
+
+ SendDefaultImages();
+}
+
+void ChangePictureOptionsHandler::HandlePageShown(const base::ListValue* args) {
+ DCHECK(args && args->empty());
+ // TODO(ivankr): If user opens settings and goes to Change Picture page right
+ // after the check started |HandlePageInitialized| has been completed,
+ // |CheckCameraPresence| will be called twice, should be throttled.
+ CheckCameraPresence();
+ SendSelectedImage();
+ UpdateProfileImage();
+}
+
+void ChangePictureOptionsHandler::SendSelectedImage() {
+ const User& user = UserManager::Get()->logged_in_user();
+ DCHECK(!user.email().empty());
+
+ previous_image_index_ = user.image_index();
+ switch (previous_image_index_) {
+ case User::kExternalImageIndex: {
+ // User has image from camera/file, record it and add to the image list.
+ previous_image_ = user.image();
+ previous_image_data_url_ = web_ui_util::GetImageDataUrl(previous_image_);
+ web_ui_->CallJavascriptFunction("ChangePictureOptions.setOldImage");
+ break;
+ }
+ case User::kProfileImageIndex: {
+ // User has his/her Profile image as the current image.
+ SendProfileImage(user.image(), true);
+ break;
+ }
+ default: {
+ DCHECK(previous_image_index_ >= 0 &&
+ previous_image_index_ < kDefaultImagesCount);
+ // User has image from the set of default images.
+ base::StringValue image_url(GetDefaultImageUrl(previous_image_index_));
+ web_ui_->CallJavascriptFunction("ChangePictureOptions.setSelectedImage",
+ image_url);
+ }
+ }
+}
+
+void ChangePictureOptionsHandler::SendProfileImage(const SkBitmap& image,
+ bool should_select) {
+ base::StringValue data_url(web_ui_util::GetImageDataUrl(image));
+ base::FundamentalValue select(should_select);
+ web_ui_->CallJavascriptFunction("ChangePictureOptions.setProfileImage",
+ data_url, select);
+}
+
+void ChangePictureOptionsHandler::UpdateProfileImage() {
+ UserManager* user_manager = UserManager::Get();
+
+ // If we have a downloaded profile image and haven't sent it in
+ // |SendSelectedImage|, send it now (without selecting).
+ if (previous_image_index_ != User::kProfileImageIndex &&
+ !user_manager->downloaded_profile_image().empty())
+ SendProfileImage(user_manager->downloaded_profile_image(), false);
+
+ user_manager->DownloadProfileImage();
+}
+
+void ChangePictureOptionsHandler::HandleSelectImage(const ListValue* args) {
+ std::string image_url;
+ if (!args ||
+ args->GetSize() != 1 ||
+ !args->GetString(0, &image_url)) {
+ NOTREACHED();
+ return;
+ }
+ DCHECK(!image_url.empty());
+
+ UserManager* user_manager = UserManager::Get();
+ const User& user = user_manager->logged_in_user();
+ int image_index = User::kInvalidImageIndex;
+
+ if (StartsWithASCII(image_url, chrome::kChromeUIUserImageURL, false)) {
+ // Image from file/camera uses kChromeUIUserImageURL as URL while
+ // current profile image always has a full data URL.
+ // This way transition from (current profile image) to
+ // (profile image, current image from file) is easier.
+
+ DCHECK(!previous_image_.empty());
+ user_manager->SaveUserImage(user.email(), previous_image_);
+
+ UMA_HISTOGRAM_ENUMERATION("UserImage.ChangeChoice",
+ kHistogramImageOld,
+ kHistogramImagesCount);
+ VLOG(1) << "Selected old user image";
+ } else if (IsDefaultImageUrl(image_url, &image_index)) {
+ // One of the default user images.
+ user_manager->SaveUserDefaultImageIndex(user.email(), image_index);
+
+ UMA_HISTOGRAM_ENUMERATION("UserImage.ChangeChoice",
+ image_index,
+ kHistogramImagesCount);
+ VLOG(1) << "Selected default user image: " << image_index;
+ } else {
+ // Profile image selected. Could be previous (old) user image.
+ user_manager->SaveUserImageFromProfileImage(user.email());
+
+ if (previous_image_index_ == User::kProfileImageIndex) {
+ UMA_HISTOGRAM_ENUMERATION("UserImage.ChangeChoice",
+ kHistogramImageOld,
+ kHistogramImagesCount);
+ VLOG(1) << "Selected old (profile) user image";
+ } else {
+ UMA_HISTOGRAM_ENUMERATION("UserImage.ChangeChoice",
+ kHistogramImageFromProfile,
+ kHistogramImagesCount);
+ VLOG(1) << "Selected profile image";
+ }
+ }
+}
+
+void ChangePictureOptionsHandler::FileSelected(const FilePath& path,
+ int index,
+ void* params) {
+ UserManager* user_manager = UserManager::Get();
+ user_manager->SaveUserImageFromFile(user_manager->logged_in_user().email(),
+ path);
+ UMA_HISTOGRAM_ENUMERATION("UserImage.ChangeChoice",
+ kHistogramImageFromFile,
+ kHistogramImagesCount);
+}
+
+void ChangePictureOptionsHandler::OnPhotoAccepted(const SkBitmap& photo) {
+ UserManager* user_manager = UserManager::Get();
+ user_manager->SaveUserImage(user_manager->logged_in_user().email(), photo);
+ UMA_HISTOGRAM_ENUMERATION("UserImage.ChangeChoice",
+ kHistogramImageFromCamera,
+ kHistogramImagesCount);
+}
+
+void ChangePictureOptionsHandler::CheckCameraPresence() {
+ CameraDetector::StartPresenceCheck(
+ base::Bind(&ChangePictureOptionsHandler::OnCameraPresenceCheckDone,
+ weak_factory_.GetWeakPtr()));
+}
+
+void ChangePictureOptionsHandler::SetCameraPresent(bool present) {
+ base::FundamentalValue present_value(present);
+ web_ui_->CallJavascriptFunction("ChangePictureOptions.setCameraPresent",
+ present_value);
+}
+
+void ChangePictureOptionsHandler::OnCameraPresenceCheckDone() {
+ SetCameraPresent(CameraDetector::camera_presence() ==
+ CameraDetector::kCameraPresent);
+}
+
+void ChangePictureOptionsHandler::Observe(
+ int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) {
+ OptionsPage2UIHandler::Observe(type, source, details);
+ if (type == chrome::NOTIFICATION_PROFILE_IMAGE_UPDATED) {
+ // User profile image has been updated.
+ SendProfileImage(*content::Details<const SkBitmap>(details).ptr(), false);
+ }
+}
+
+gfx::NativeWindow ChangePictureOptionsHandler::GetBrowserWindow() const {
+ Browser* browser =
+ BrowserList::FindBrowserWithProfile(Profile::FromWebUI(web_ui_));
+ if (!browser)
+ return NULL;
+ return browser->window()->GetNativeHandle();
+}
+
+} // namespace chromeos
diff --git a/chrome/browser/ui/webui/options2/chromeos/change_picture_options_handler.h b/chrome/browser/ui/webui/options2/chromeos/change_picture_options_handler.h
new file mode 100644
index 0000000..8db67f4
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/chromeos/change_picture_options_handler.h
@@ -0,0 +1,113 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_CHANGE_PICTURE_OPTIONS_HANDLER_H_
+#define CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_CHANGE_PICTURE_OPTIONS_HANDLER_H_
+
+#include "base/memory/weak_ptr.h"
+#include "chrome/browser/chromeos/options/take_photo_dialog.h"
+#include "chrome/browser/ui/select_file_dialog.h"
+#include "chrome/browser/ui/webui/options2/options_ui2.h"
+#include "content/public/browser/notification_registrar.h"
+#include "ui/gfx/native_widget_types.h"
+
+namespace base {
+class DictionaryValue;
+class ListValue;
+}
+
+namespace chromeos {
+
+// ChromeOS user image options page UI handler.
+class ChangePictureOptionsHandler : public OptionsPage2UIHandler,
+ public SelectFileDialog::Listener,
+ public TakePhotoDialog::Delegate {
+ public:
+ ChangePictureOptionsHandler();
+ virtual ~ChangePictureOptionsHandler();
+
+ // OptionsPage2UIHandler implementation.
+ virtual void GetLocalizedValues(
+ base::DictionaryValue* localized_strings) OVERRIDE;
+
+ // WebUIMessageHandler implementation.
+ virtual void RegisterMessages() OVERRIDE;
+
+ private:
+ // Sends list of available default images to the page.
+ void SendDefaultImages();
+
+ // Sends current selection to the page.
+ void SendSelectedImage();
+
+ // Sends the profile image to the page. If |should_select| is true then
+ // the profile image element is selected.
+ void SendProfileImage(const SkBitmap& image, bool should_select);
+
+ // Starts profile image update and shows the last downloaded profile image,
+ // if any, on the page. Shouldn't be called before |SendProfileImage|.
+ void UpdateProfileImage();
+
+ // Starts camera presence check.
+ void CheckCameraPresence();
+
+ // Updates UI with camera presence state.
+ void SetCameraPresent(bool present);
+
+ // Opens a file selection dialog to choose user image from file.
+ void HandleChooseFile(const base::ListValue* args);
+
+ // Opens the camera capture dialog.
+ void HandleTakePhoto(const base::ListValue* args);
+
+ // Gets the list of available user images and sends it to the page.
+ void HandleGetAvailableImages(const base::ListValue* args);
+
+ // Handles page initialized event.
+ void HandlePageInitialized(const base::ListValue* args);
+
+ // Handles page shown event.
+ void HandlePageShown(const base::ListValue* args);
+
+ // Selects one of the available images as user's.
+ void HandleSelectImage(const base::ListValue* args);
+
+ // SelectFileDialog::Delegate implementation.
+ virtual void FileSelected(
+ const FilePath& path,
+ int index, void* params) OVERRIDE;
+
+ // TakePhotoDialog::Delegate implementation.
+ virtual void OnPhotoAccepted(const SkBitmap& photo) OVERRIDE;
+
+ // content::NotificationObserver implementation.
+ virtual void Observe(int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) OVERRIDE;
+
+ // Called when the camera presence check has been completed.
+ void OnCameraPresenceCheckDone();
+
+ // Returns handle to browser window or NULL if it can't be found.
+ gfx::NativeWindow GetBrowserWindow() const;
+
+ scoped_refptr<SelectFileDialog> select_file_dialog_;
+
+ // Previous user image from camera/file and its data URL.
+ SkBitmap previous_image_;
+ std::string previous_image_data_url_;
+
+ // Index of the previous user image.
+ int previous_image_index_;
+
+ content::NotificationRegistrar registrar_;
+
+ base::WeakPtrFactory<ChangePictureOptionsHandler> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(ChangePictureOptionsHandler);
+};
+
+} // namespace chromeos
+
+#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_CHANGE_PICTURE_OPTIONS_HANDLER_H_
diff --git a/chrome/browser/ui/webui/options2/chromeos/core_chromeos_options_handler.cc b/chrome/browser/ui/webui/options2/chromeos/core_chromeos_options_handler.cc
new file mode 100644
index 0000000..91fbc226
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/chromeos/core_chromeos_options_handler.cc
@@ -0,0 +1,268 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/webui/options2/chromeos/core_chromeos_options_handler.h"
+
+#include <string>
+
+#include "base/bind.h"
+#include "base/string_number_conversions.h"
+#include "base/string_util.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/chromeos/cros_settings.h"
+#include "chrome/browser/chromeos/login/user_manager.h"
+#include "chrome/browser/chromeos/proxy_config_service_impl.h"
+#include "chrome/browser/chromeos/proxy_cros_settings_parser.h"
+#include "chrome/browser/policy/browser_policy_connector.h"
+#include "chrome/browser/prefs/pref_set_observer.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/webui/options2/chromeos/accounts_options_handler.h"
+#include "chrome/common/chrome_notification_types.h"
+#include "chrome/common/pref_names.h"
+#include "content/public/browser/notification_details.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/notification_source.h"
+#include "content/public/browser/user_metrics.h"
+
+namespace chromeos {
+
+namespace {
+
+// Create a settings value with "managed" and "disabled" property.
+// "managed" property is true if the setting is managed by administrator.
+// "disabled" property is true if the UI for the setting should be disabled.
+base::Value* CreateSettingsValue(base::Value *value,
+ bool managed,
+ bool disabled) {
+ DictionaryValue* dict = new DictionaryValue;
+ dict->Set("value", value);
+ dict->Set("managed", base::Value::CreateBooleanValue(managed));
+ dict->Set("disabled", base::Value::CreateBooleanValue(disabled));
+ return dict;
+}
+
+// Returns true if |username| is the logged-in owner.
+bool IsLoggedInOwner(const std::string& username) {
+ UserManager* user_manager = UserManager::Get();
+ return user_manager->current_user_is_owner() &&
+ user_manager->logged_in_user().email() == username;
+}
+
+// Creates a user info dictionary to be stored in the |ListValue| that is
+// passed to Javascript for the |kAccountsPrefUsers| preference.
+base::DictionaryValue* CreateUserInfo(const std::string& username,
+ const std::string& display_email,
+ const std::string& display_name) {
+ base::DictionaryValue* user_dict = new DictionaryValue;
+ user_dict->SetString("username", username);
+ user_dict->SetString("name", display_email);
+ user_dict->SetString("email", display_name);
+ user_dict->SetBoolean("owner", IsLoggedInOwner(username));
+ return user_dict;
+}
+
+// This function decorates the bare list of emails with some more information
+// needed by the UI to properly display the Accounts page.
+base::Value* CreateUsersWhitelist(const base::Value *pref_value) {
+ const base::ListValue* list_value =
+ static_cast<const base::ListValue*>(pref_value);
+ base::ListValue* user_list = new base::ListValue();
+ UserManager* user_manager = UserManager::Get();
+
+ for (base::ListValue::const_iterator i = list_value->begin();
+ i != list_value->end(); ++i) {
+ std::string email;
+ if ((*i)->GetAsString(&email)) {
+ // Translate email to the display email.
+ std::string display_email = user_manager->GetUserDisplayEmail(email);
+ // TODO(ivankr): fetch display name for existing users.
+ user_list->Append(CreateUserInfo(email, display_email, std::string()));
+ }
+ }
+ return user_list;
+}
+
+} // namespace
+
+CoreChromeOSOptionsHandler::CoreChromeOSOptionsHandler()
+ : handling_change_(false),
+ pointer_factory_(this) {
+}
+
+CoreChromeOSOptionsHandler::~CoreChromeOSOptionsHandler() {
+ PrefProxyConfigTracker* proxy_tracker =
+ Profile::FromWebUI(web_ui_)->GetProxyConfigTracker();
+ proxy_tracker->RemoveNotificationCallback(
+ base::Bind(&CoreChromeOSOptionsHandler::NotifyProxyPrefsChanged,
+ pointer_factory_.GetWeakPtr()));
+}
+
+void CoreChromeOSOptionsHandler::Initialize() {
+ proxy_prefs_.reset(PrefSetObserver::CreateProxyPrefSetObserver(
+ Profile::FromWebUI(web_ui_)->GetPrefs(), this));
+ // Observe the chromeos::ProxyConfigServiceImpl for changes from the UI.
+ PrefProxyConfigTracker* proxy_tracker =
+ Profile::FromWebUI(web_ui_)->GetProxyConfigTracker();
+ proxy_tracker->AddNotificationCallback(
+ base::Bind(&CoreChromeOSOptionsHandler::NotifyProxyPrefsChanged,
+ pointer_factory_.GetWeakPtr()));
+}
+
+base::Value* CoreChromeOSOptionsHandler::FetchPref(
+ const std::string& pref_name) {
+ if (proxy_cros_settings_parser::IsProxyPref(pref_name)) {
+ base::Value *value = NULL;
+ proxy_cros_settings_parser::GetProxyPrefValue(Profile::FromWebUI(web_ui_),
+ pref_name, &value);
+ if (!value)
+ return base::Value::CreateNullValue();
+
+ return value;
+ }
+ if (!CrosSettings::IsCrosSettings(pref_name)) {
+ // Specially handle kUseSharedProxies because kProxy controls it to
+ // determine if it's managed by policy/extension.
+ if (pref_name == prefs::kUseSharedProxies) {
+ PrefService* pref_service = Profile::FromWebUI(web_ui_)->GetPrefs();
+ const PrefService::Preference* pref =
+ pref_service->FindPreference(prefs::kUseSharedProxies);
+ if (!pref)
+ return base::Value::CreateNullValue();
+ const PrefService::Preference* controlling_pref =
+ pref_service->FindPreference(prefs::kProxy);
+ return CreateValueForPref(pref, controlling_pref);
+ }
+ return ::CoreOptionsHandler::FetchPref(pref_name);
+ }
+
+ const base::Value* pref_value = CrosSettings::Get()->GetPref(pref_name);
+ if (!pref_value)
+ return base::Value::CreateNullValue();
+
+ // Lists don't get the standard pref decoration.
+ if (pref_value->GetType() == base::Value::TYPE_LIST) {
+ if (pref_name == kAccountsPrefUsers)
+ return CreateUsersWhitelist(pref_value);
+ // Return a copy because the UI will take ownership of this object.
+ return pref_value->DeepCopy();
+ }
+ // All other prefs are decorated the same way.
+ return CreateSettingsValue(
+ pref_value->DeepCopy(), // The copy will be owned by the dictionary.
+ g_browser_process->browser_policy_connector()->IsEnterpriseManaged(),
+ !UserManager::Get()->current_user_is_owner());
+}
+
+void CoreChromeOSOptionsHandler::ObservePref(const std::string& pref_name) {
+ if (proxy_cros_settings_parser::IsProxyPref(pref_name)) {
+ // We observe those all the time.
+ return;
+ }
+ if (!CrosSettings::IsCrosSettings(pref_name))
+ return ::CoreOptionsHandler::ObservePref(pref_name);
+ CrosSettings::Get()->AddSettingsObserver(pref_name.c_str(), this);
+}
+
+void CoreChromeOSOptionsHandler::SetPref(const std::string& pref_name,
+ const base::Value* value,
+ const std::string& metric) {
+ if (proxy_cros_settings_parser::IsProxyPref(pref_name)) {
+ proxy_cros_settings_parser::SetProxyPrefValue(Profile::FromWebUI(web_ui_),
+ pref_name, value);
+ ProcessUserMetric(value, metric);
+ return;
+ }
+ if (!CrosSettings::IsCrosSettings(pref_name))
+ return ::CoreOptionsHandler::SetPref(pref_name, value, metric);
+ handling_change_ = true;
+ CrosSettings::Get()->Set(pref_name, *value);
+ handling_change_ = false;
+
+ ProcessUserMetric(value, metric);
+}
+
+void CoreChromeOSOptionsHandler::StopObservingPref(const std::string& path) {
+ if (proxy_cros_settings_parser::IsProxyPref(path))
+ return; // We unregister those in the destructor.
+ // Unregister this instance from observing prefs of chrome os settings.
+ if (CrosSettings::IsCrosSettings(path))
+ CrosSettings::Get()->RemoveSettingsObserver(path.c_str(), this);
+ else // Call base class to handle regular preferences.
+ ::CoreOptionsHandler::StopObservingPref(path);
+}
+
+void CoreChromeOSOptionsHandler::Observe(
+ int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) {
+ // Ignore the notification if this instance had caused it.
+ if (handling_change_)
+ return;
+ if (type == chrome::NOTIFICATION_SYSTEM_SETTING_CHANGED) {
+ NotifySettingsChanged(content::Details<std::string>(details).ptr());
+ return;
+ }
+ // Special handling for preferences kUseSharedProxies and kProxy, the latter
+ // controls the former and decides if it's managed by policy/extension.
+ if (type == chrome::NOTIFICATION_PREF_CHANGED) {
+ const PrefService* pref_service = Profile::FromWebUI(web_ui_)->GetPrefs();
+ std::string* pref_name = content::Details<std::string>(details).ptr();
+ if (content::Source<PrefService>(source).ptr() == pref_service &&
+ (proxy_prefs_->IsObserved(*pref_name) ||
+ *pref_name == prefs::kUseSharedProxies)) {
+ NotifyPrefChanged(prefs::kUseSharedProxies, prefs::kProxy);
+ return;
+ }
+ }
+ ::CoreOptionsHandler::Observe(type, source, details);
+}
+
+void CoreChromeOSOptionsHandler::NotifySettingsChanged(
+ const std::string* setting_name) {
+ DCHECK(web_ui_);
+ DCHECK(CrosSettings::Get()->IsCrosSettings(*setting_name));
+ const base::Value* value = FetchPref(*setting_name);
+ if (!value) {
+ NOTREACHED();
+ return;
+ }
+ std::pair<PreferenceCallbackMap::const_iterator,
+ PreferenceCallbackMap::const_iterator> range =
+ pref_callback_map_.equal_range(*setting_name);
+ for (PreferenceCallbackMap::const_iterator iter = range.first;
+ iter != range.second; ++iter) {
+ const std::wstring& callback_function = iter->second;
+ ListValue result_value;
+ result_value.Append(base::Value::CreateStringValue(setting_name->c_str()));
+ result_value.Append(value->DeepCopy());
+ web_ui_->CallJavascriptFunction(WideToASCII(callback_function),
+ result_value);
+ }
+ if (value)
+ delete value;
+}
+
+void CoreChromeOSOptionsHandler::NotifyProxyPrefsChanged() {
+ DCHECK(web_ui_);
+ for (size_t i = 0; i < kProxySettingsCount; ++i) {
+ base::Value* value = NULL;
+ proxy_cros_settings_parser::GetProxyPrefValue(
+ Profile::FromWebUI(web_ui_), kProxySettings[i], &value);
+ DCHECK(value);
+ PreferenceCallbackMap::const_iterator iter =
+ pref_callback_map_.find(kProxySettings[i]);
+ for (; iter != pref_callback_map_.end(); ++iter) {
+ const std::wstring& callback_function = iter->second;
+ ListValue result_value;
+ result_value.Append(base::Value::CreateStringValue(kProxySettings[i]));
+ result_value.Append(value->DeepCopy());
+ web_ui_->CallJavascriptFunction(WideToASCII(callback_function),
+ result_value);
+ }
+ if (value)
+ delete value;
+ }
+}
+
+} // namespace chromeos
diff --git a/chrome/browser/ui/webui/options2/chromeos/core_chromeos_options_handler.h b/chrome/browser/ui/webui/options2/chromeos/core_chromeos_options_handler.h
new file mode 100644
index 0000000..48aa029
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/chromeos/core_chromeos_options_handler.h
@@ -0,0 +1,53 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_CORE_CHROMEOS_OPTIONS_HANDLER_H_
+#define CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_CORE_CHROMEOS_OPTIONS_HANDLER_H_
+#pragma once
+
+#include "base/compiler_specific.h"
+#include "base/memory/weak_ptr.h"
+#include "chrome/browser/ui/webui/options2/core_options_handler.h"
+
+class PrefSetObserver;
+
+namespace chromeos {
+
+// CoreChromeOSOptionsHandler handles ChromeOS settings.
+class CoreChromeOSOptionsHandler : public CoreOptionsHandler {
+ public:
+ CoreChromeOSOptionsHandler();
+ virtual ~CoreChromeOSOptionsHandler();
+
+ protected:
+ // ::CoreOptionsHandler overrides
+ virtual void Initialize() OVERRIDE;
+ virtual base::Value* FetchPref(const std::string& pref_name) OVERRIDE;
+ virtual void ObservePref(const std::string& pref_name) OVERRIDE;
+ virtual void SetPref(const std::string& pref_name,
+ const base::Value* value,
+ const std::string& metric) OVERRIDE;
+ virtual void StopObservingPref(const std::string& path) OVERRIDE;
+
+ // content::NotificationObserver implementation.
+ virtual void Observe(int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) OVERRIDE;
+
+ private:
+ // Notifies registered JS callbacks on ChromeOS setting change.
+ void NotifySettingsChanged(const std::string* setting_name);
+ void NotifyProxyPrefsChanged();
+
+ // Keeps the track of change caused by the handler to make sure
+ // it does not signal itself again.
+ bool handling_change_;
+
+ scoped_ptr<PrefSetObserver> proxy_prefs_;
+ base::WeakPtrFactory<CoreChromeOSOptionsHandler> pointer_factory_;
+};
+
+} // namespace chromeos
+
+#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_CORE_CHROMEOS_OPTIONS_HANDLER_H_
diff --git a/chrome/browser/ui/webui/options2/chromeos/cros_language_options_handler.cc b/chrome/browser/ui/webui/options2/chromeos/cros_language_options_handler.cc
new file mode 100644
index 0000000..6fc2dce
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/chromeos/cros_language_options_handler.cc
@@ -0,0 +1,242 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/webui/options2/chromeos/cros_language_options_handler.h"
+
+#include <map>
+#include <set>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/stringprintf.h"
+#include "base/utf_string_conversions.h"
+#include "base/values.h"
+#include "chrome/app/chrome_command_ids.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/chromeos/input_method/input_method_manager.h"
+#include "chrome/browser/chromeos/input_method/input_method_util.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/browser.h"
+#include "content/browser/tab_contents/tab_contents.h"
+#include "content/public/browser/user_metrics.h"
+#include "grit/chromium_strings.h"
+#include "grit/generated_resources.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace chromeos {
+
+CrosLanguageOptionsHandler::CrosLanguageOptionsHandler() {
+}
+
+CrosLanguageOptionsHandler::~CrosLanguageOptionsHandler() {
+}
+
+void CrosLanguageOptionsHandler::GetLocalizedValues(
+ DictionaryValue* localized_strings) {
+ LanguageOptionsHandlerCommon::GetLocalizedValues(localized_strings);
+
+ RegisterTitle(localized_strings, "languagePage",
+ IDS_OPTIONS_SETTINGS_LANGUAGES_AND_INPUT_DIALOG_TITLE);
+ localized_strings->SetString("ok_button", l10n_util::GetStringUTF16(IDS_OK));
+ localized_strings->SetString("configure",
+ l10n_util::GetStringUTF16(IDS_OPTIONS_SETTINGS_LANGUAGES_CONFIGURE));
+ localized_strings->SetString("input_method",
+ l10n_util::GetStringUTF16(IDS_OPTIONS_SETTINGS_LANGUAGES_INPUT_METHOD));
+ localized_strings->SetString("please_add_another_input_method",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_LANGUAGES_PLEASE_ADD_ANOTHER_INPUT_METHOD));
+ localized_strings->SetString("input_method_instructions",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_LANGUAGES_INPUT_METHOD_INSTRUCTIONS));
+ localized_strings->SetString("switch_input_methods_hint",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_LANGUAGES_SWITCH_INPUT_METHODS_HINT));
+ localized_strings->SetString("select_previous_input_method_hint",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_LANGUAGES_SELECT_PREVIOUS_INPUT_METHOD_HINT));
+ localized_strings->SetString("restart_button",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_LANGUAGES_SIGN_OUT_BUTTON));
+ localized_strings->SetString("virtual_keyboard_button",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_LANGUAGES_VIRTUAL_KEYBOARD_BUTTON));
+
+ input_method::InputMethodManager* manager =
+ input_method::InputMethodManager::GetInstance();
+ // GetSupportedInputMethods() never return NULL.
+ scoped_ptr<input_method::InputMethodDescriptors> descriptors(
+ manager->GetSupportedInputMethods());
+ localized_strings->Set("languageList", GetLanguageList(*descriptors));
+ localized_strings->Set("inputMethodList", GetInputMethodList(*descriptors));
+}
+
+void CrosLanguageOptionsHandler::RegisterMessages() {
+ LanguageOptionsHandlerCommon::RegisterMessages();
+
+ web_ui_->RegisterMessageCallback("inputMethodDisable",
+ base::Bind(&CrosLanguageOptionsHandler::InputMethodDisableCallback,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("inputMethodEnable",
+ base::Bind(&CrosLanguageOptionsHandler::InputMethodEnableCallback,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("inputMethodOptionsOpen",
+ base::Bind(&CrosLanguageOptionsHandler::InputMethodOptionsOpenCallback,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("uiLanguageRestart",
+ base::Bind(&CrosLanguageOptionsHandler::RestartCallback,
+ base::Unretained(this)));
+}
+
+ListValue* CrosLanguageOptionsHandler::GetInputMethodList(
+ const input_method::InputMethodDescriptors& descriptors) {
+ input_method::InputMethodManager* manager =
+ input_method::InputMethodManager::GetInstance();
+
+ ListValue* input_method_list = new ListValue();
+
+ for (size_t i = 0; i < descriptors.size(); ++i) {
+ const input_method::InputMethodDescriptor& descriptor =
+ descriptors[i];
+ const std::string language_code =
+ manager->GetInputMethodUtil()->GetLanguageCodeFromDescriptor(
+ descriptor);
+ const std::string display_name =
+ manager->GetInputMethodUtil()->GetInputMethodDisplayNameFromId(
+ descriptor.id());
+
+ DictionaryValue* dictionary = new DictionaryValue();
+ dictionary->SetString("id", descriptor.id());
+ dictionary->SetString("displayName", display_name);
+
+ // One input method can be associated with multiple languages, hence
+ // we use a dictionary here.
+ DictionaryValue* language_codes = new DictionaryValue();
+ language_codes->SetBoolean(language_code, true);
+ // Check kExtraLanguages to see if there are languages associated with
+ // this input method. If these are present, add these.
+ for (size_t j = 0; j < input_method::kExtraLanguagesLength; ++j) {
+ const std::string extra_input_method_id =
+ input_method::kExtraLanguages[j].input_method_id;
+ const std::string extra_language_code =
+ input_method::kExtraLanguages[j].language_code;
+ if (extra_input_method_id == descriptor.id()) {
+ language_codes->SetBoolean(extra_language_code, true);
+ }
+ }
+ dictionary->Set("languageCodeSet", language_codes);
+
+ input_method_list->Append(dictionary);
+ }
+
+ return input_method_list;
+}
+
+ListValue* CrosLanguageOptionsHandler::GetLanguageList(
+ const input_method::InputMethodDescriptors& descriptors) {
+ input_method::InputMethodManager* manager =
+ input_method::InputMethodManager::GetInstance();
+
+ std::set<std::string> language_codes;
+ // Collect the language codes from the supported input methods.
+ for (size_t i = 0; i < descriptors.size(); ++i) {
+ const input_method::InputMethodDescriptor& descriptor = descriptors[i];
+ const std::string language_code =
+ manager->GetInputMethodUtil()->GetLanguageCodeFromDescriptor(
+ descriptor);
+ language_codes.insert(language_code);
+ }
+ // Collect the language codes from kExtraLanguages.
+ for (size_t i = 0; i < input_method::kExtraLanguagesLength; ++i) {
+ const char* language_code =
+ input_method::kExtraLanguages[i].language_code;
+ language_codes.insert(language_code);
+ }
+
+ // Map of display name -> {language code, native_display_name}.
+ // In theory, we should be able to create a map that is sorted by
+ // display names using ICU comparator, but doing it is hard, thus we'll
+ // use an auxiliary vector to achieve the same result.
+ typedef std::pair<std::string, string16> LanguagePair;
+ typedef std::map<string16, LanguagePair> LanguageMap;
+ LanguageMap language_map;
+ // The auxiliary vector mentioned above.
+ std::vector<string16> display_names;
+
+ // Build the list of display names, and build the language map.
+ for (std::set<std::string>::const_iterator iter = language_codes.begin();
+ iter != language_codes.end(); ++iter) {
+ const string16 display_name =
+ input_method::InputMethodUtil::GetLanguageDisplayNameFromCode(*iter);
+ const string16 native_display_name =
+ input_method::InputMethodUtil::GetLanguageNativeDisplayNameFromCode(
+ *iter);
+ display_names.push_back(display_name);
+ language_map[display_name] =
+ std::make_pair(*iter, native_display_name);
+ }
+ DCHECK_EQ(display_names.size(), language_map.size());
+
+ // Sort display names using locale specific sorter.
+ l10n_util::SortStrings16(g_browser_process->GetApplicationLocale(),
+ &display_names);
+
+ // Build the language list from the language map.
+ ListValue* language_list = new ListValue();
+ for (size_t i = 0; i < display_names.size(); ++i) {
+ const LanguagePair& pair = language_map[display_names[i]];
+ DictionaryValue* dictionary = new DictionaryValue();
+ dictionary->SetString("code", pair.first);
+ dictionary->SetString("displayName", display_names[i]);
+ dictionary->SetString("nativeDisplayName", pair.second);
+ language_list->Append(dictionary);
+ }
+
+ return language_list;
+}
+
+string16 CrosLanguageOptionsHandler::GetProductName() {
+ return l10n_util::GetStringUTF16(IDS_PRODUCT_OS_NAME);
+}
+
+void CrosLanguageOptionsHandler::SetApplicationLocale(
+ const std::string& language_code) {
+ Profile::FromWebUI(web_ui_)->ChangeAppLocale(
+ language_code, Profile::APP_LOCALE_CHANGED_VIA_SETTINGS);
+}
+
+void CrosLanguageOptionsHandler::RestartCallback(const ListValue* args) {
+ UserMetrics::RecordAction(UserMetricsAction("LanguageOptions_SignOut"));
+
+ Browser* browser = Browser::GetBrowserForController(
+ &web_ui_->tab_contents()->controller(), NULL);
+ if (browser)
+ browser->ExecuteCommand(IDC_EXIT);
+}
+
+void CrosLanguageOptionsHandler::InputMethodDisableCallback(
+ const ListValue* args) {
+ const std::string input_method_id = UTF16ToASCII(ExtractStringValue(args));
+ const std::string action = base::StringPrintf(
+ "LanguageOptions_DisableInputMethod_%s", input_method_id.c_str());
+ UserMetrics::RecordComputedAction(action);
+}
+
+void CrosLanguageOptionsHandler::InputMethodEnableCallback(
+ const ListValue* args) {
+ const std::string input_method_id = UTF16ToASCII(ExtractStringValue(args));
+ const std::string action = base::StringPrintf(
+ "LanguageOptions_EnableInputMethod_%s", input_method_id.c_str());
+ UserMetrics::RecordComputedAction(action);
+}
+
+void CrosLanguageOptionsHandler::InputMethodOptionsOpenCallback(
+ const ListValue* args) {
+ const std::string input_method_id = UTF16ToASCII(ExtractStringValue(args));
+ const std::string action = base::StringPrintf(
+ "InputMethodOptions_Open_%s", input_method_id.c_str());
+ UserMetrics::RecordComputedAction(action);
+}
+
+} // namespace chromeos
diff --git a/chrome/browser/ui/webui/options2/chromeos/cros_language_options_handler.h b/chrome/browser/ui/webui/options2/chromeos/cros_language_options_handler.h
new file mode 100644
index 0000000..aa0464b
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/chromeos/cros_language_options_handler.h
@@ -0,0 +1,73 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_CROS_LANGUAGE_OPTIONS_HANDLER_H_
+#define CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_CROS_LANGUAGE_OPTIONS_HANDLER_H_
+#pragma once
+
+#include "base/compiler_specific.h"
+#include "chrome/browser/chromeos/input_method/ibus_controller.h"
+#include "chrome/browser/ui/webui/options2/language_options_handler.h"
+
+namespace chromeos {
+
+// Language options page UI handler for Chrome OS. For non-Chrome OS,
+// see LanguageOptionsHnadler.
+class CrosLanguageOptionsHandler : public LanguageOptionsHandlerCommon {
+ public:
+ CrosLanguageOptionsHandler();
+ virtual ~CrosLanguageOptionsHandler();
+
+ // OptionsPageUIHandler implementation.
+ virtual void GetLocalizedValues(
+ base::DictionaryValue* localized_strings) OVERRIDE;
+
+ // DOMMessageHandler implementation.
+ virtual void RegisterMessages() OVERRIDE;
+
+ // The following static methods are public for ease of testing.
+
+ // Gets the list of input methods from the given input descriptors.
+ // The return value will look like:
+ // [{'id': 'pinyin', 'displayName': 'Pinyin',
+ // 'languageCodeSet': {'zh-CW': true}}, ...]
+ //
+ // Note that true in languageCodeSet does not mean anything. We just use
+ // the dictionary as a set.
+ static base::ListValue* GetInputMethodList(
+ const input_method::InputMethodDescriptors& descriptors);
+
+ // Gets the list of languages from the given input descriptors.
+ // The return value will look like:
+ // [{'code': 'fi', 'displayName': 'Finnish', 'nativeDisplayName': 'suomi'},
+ // ...]
+ static base::ListValue* GetLanguageList(
+ const input_method::InputMethodDescriptors& descriptors);
+
+ private:
+ // LanguageOptionsHandlerCommon implementation.
+ virtual string16 GetProductName() OVERRIDE;
+ virtual void SetApplicationLocale(const std::string& language_code) OVERRIDE;
+
+ // Called when the sign-out button is clicked.
+ void RestartCallback(const base::ListValue* args);
+
+ // Called when the input method is disabled.
+ // |args| will contain the input method ID as string (ex. "mozc").
+ void InputMethodDisableCallback(const base::ListValue* args);
+
+ // Called when the input method is enabled.
+ // |args| will contain the input method ID as string (ex. "mozc").
+ void InputMethodEnableCallback(const base::ListValue* args);
+
+ // Called when the input method options page is opened.
+ // |args| will contain the input method ID as string (ex. "mozc").
+ void InputMethodOptionsOpenCallback(const base::ListValue* args);
+
+ DISALLOW_COPY_AND_ASSIGN(CrosLanguageOptionsHandler);
+};
+
+} // namespace chromeos
+
+#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_CROS_LANGUAGE_OPTIONS_HANDLER_H_
diff --git a/chrome/browser/ui/webui/options2/chromeos/guest_mode_options_ui_uitest.cc b/chrome/browser/ui/webui/options2/chromeos/guest_mode_options_ui_uitest.cc
new file mode 100644
index 0000000..e001d63
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/chromeos/guest_mode_options_ui_uitest.cc
@@ -0,0 +1,43 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/webui/options2/options_ui_uitest.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/test/automation/automation_proxy.h"
+#include "chrome/test/automation/browser_proxy.h"
+#include "chrome/test/automation/tab_proxy.h"
+#include "chrome/test/ui/ui_test.h"
+
+namespace {
+
+// Same as OptionsUITest but launches with Guest mode command line switches.
+class GuestModeOptionsUITest : public OptionsUITest {
+ public:
+ GuestModeOptionsUITest() : OptionsUITest() {
+ launch_arguments_.AppendSwitch(switches::kGuestSession);
+ launch_arguments_.AppendSwitch(switches::kIncognito);
+ }
+};
+
+// See bug 104393.
+#if defined(USE_AURA)
+#define MAYBE_LoadOptionsByURL FAILS_LoadOptionsByURL
+#else
+#define MAYBE_LoadOptionsByURL LoadOptionsByURL
+#endif
+
+TEST_F(GuestModeOptionsUITest, MAYBE_LoadOptionsByURL) {
+ scoped_refptr<BrowserProxy> browser(automation()->GetBrowserWindow(0));
+ ASSERT_TRUE(browser.get());
+
+ scoped_refptr<TabProxy> tab = browser->GetActiveTab();
+ ASSERT_TRUE(tab.get());
+
+ NavigateToSettings(tab);
+ VerifyTitle(tab);
+ VerifyNavbar(tab);
+ VerifySections(tab);
+}
+
+} // namespace
diff --git a/chrome/browser/ui/webui/options2/chromeos/internet_options_handler.cc b/chrome/browser/ui/webui/options2/chromeos/internet_options_handler.cc
new file mode 100644
index 0000000..7af2d1b
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/chromeos/internet_options_handler.cc
@@ -0,0 +1,1372 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/webui/options2/chromeos/internet_options_handler.h"
+
+#include <ctype.h>
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/base64.h"
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/command_line.h"
+#include "base/i18n/time_formatting.h"
+#include "base/string16.h"
+#include "base/string_number_conversions.h"
+#include "base/stringprintf.h"
+#include "base/time.h"
+#include "base/utf_string_conversions.h"
+#include "base/values.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/chromeos/choose_mobile_network_dialog.h"
+#include "chrome/browser/chromeos/cros/cros_library.h"
+#include "chrome/browser/chromeos/cros/network_library.h"
+#include "chrome/browser/chromeos/cros_settings.h"
+#include "chrome/browser/chromeos/mobile_config.h"
+#include "chrome/browser/chromeos/options/network_config_view.h"
+#include "chrome/browser/chromeos/proxy_config_service_impl.h"
+#include "chrome/browser/chromeos/sim_dialog_delegate.h"
+#include "chrome/browser/chromeos/status/network_menu_icon.h"
+#include "chrome/browser/net/pref_proxy_config_tracker.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_list.h"
+#include "chrome/browser/ui/browser_window.h"
+#include "chrome/browser/ui/dialog_style.h"
+#include "chrome/browser/ui/views/window.h"
+#include "chrome/browser/ui/webui/web_ui_util.h"
+#include "chrome/common/chrome_notification_types.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/common/time_format.h"
+#include "content/public/browser/notification_service.h"
+#include "grit/chromium_strings.h"
+#include "grit/generated_resources.h"
+#include "grit/locale_settings.h"
+#include "grit/theme_resources.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/views/widget/widget.h"
+
+namespace {
+
+static const char kOtherNetworksFakePath[] = "?";
+
+// Keys for the network description dictionary passed to the web ui. Make sure
+// to keep the strings in sync with what the Javascript side uses.
+const char kNetworkInfoKeyActivationState[] = "activation_state";
+const char kNetworkInfoKeyConnectable[] = "connectable";
+const char kNetworkInfoKeyConnected[] = "connected";
+const char kNetworkInfoKeyConnecting[] = "connecting";
+const char kNetworkInfoKeyIconURL[] = "iconURL";
+const char kNetworkInfoKeyNeedsNewPlan[] = "needs_new_plan";
+const char kNetworkInfoKeyNetworkName[] = "networkName";
+const char kNetworkInfoKeyNetworkStatus[] = "networkStatus";
+const char kNetworkInfoKeyNetworkType[] = "networkType";
+const char kNetworkInfoKeyRemembered[] = "remembered";
+const char kNetworkInfoKeyServicePath[] = "servicePath";
+const char kNetworkInfoKeyPolicyManaged[] = "policyManaged";
+
+// A helper class for building network information dictionaries to be sent to
+// the webui code.
+class NetworkInfoDictionary {
+ public:
+ // Initializes the dictionary with default values.
+ NetworkInfoDictionary();
+
+ // Copies in service path, connect{ing|ed|able} flags and connection type from
+ // the provided network object. Also chooses an appropriate icon based on the
+ // network type.
+ explicit NetworkInfoDictionary(const chromeos::Network* network);
+
+ // Initializes a remembered network entry, pulling information from the passed
+ // network object and the corresponding remembered network object. |network|
+ // may be NULL.
+ NetworkInfoDictionary(const chromeos::Network* network,
+ const chromeos::Network* remembered);
+
+ // Setters for filling in information.
+ void set_service_path(const std::string& service_path) {
+ service_path_ = service_path;
+ }
+ void set_icon(const SkBitmap& icon) {
+ icon_url_ = icon.isNull() ? "" : web_ui_util::GetImageDataUrl(icon);
+ }
+ void set_name(const std::string& name) {
+ name_ = name;
+ }
+ void set_connecting(bool connecting) {
+ connecting_ = connecting;
+ }
+ void set_connected(bool connected) {
+ connected_ = connected;
+ }
+ void set_connectable(bool connectable) {
+ connectable_ = connectable;
+ }
+ void set_connection_type(chromeos::ConnectionType connection_type) {
+ connection_type_ = connection_type;
+ }
+ void set_remembered(bool remembered) {
+ remembered_ = remembered;
+ }
+ void set_shared(bool shared) {
+ shared_ = shared;
+ }
+ void set_activation_state(chromeos::ActivationState activation_state) {
+ activation_state_ = activation_state;
+ }
+ void set_needs_new_plan(bool needs_new_plan) {
+ needs_new_plan_ = needs_new_plan;
+ }
+ void set_policy_managed(bool policy_managed) {
+ policy_managed_ = policy_managed;
+ }
+
+ // Builds the DictionaryValue representation from the previously set
+ // parameters. Ownership of the returned pointer is transferred to the caller.
+ DictionaryValue* BuildDictionary();
+
+ private:
+ // Values to be filled into the dictionary.
+ std::string service_path_;
+ std::string icon_url_;
+ std::string name_;
+ bool connecting_;
+ bool connected_;
+ bool connectable_;
+ chromeos::ConnectionType connection_type_;
+ bool remembered_;
+ bool shared_;
+ chromeos::ActivationState activation_state_;
+ bool needs_new_plan_;
+ bool policy_managed_;
+
+ DISALLOW_COPY_AND_ASSIGN(NetworkInfoDictionary);
+};
+
+NetworkInfoDictionary::NetworkInfoDictionary() {
+ set_connecting(false);
+ set_connected(false);
+ set_connectable(false);
+ set_remembered(false);
+ set_shared(false);
+ set_activation_state(chromeos::ACTIVATION_STATE_UNKNOWN);
+ set_needs_new_plan(false);
+ set_policy_managed(false);
+}
+
+NetworkInfoDictionary::NetworkInfoDictionary(const chromeos::Network* network) {
+ set_service_path(network->service_path());
+ set_icon(chromeos::NetworkMenuIcon::GetBitmap(network));
+ set_name(network->name());
+ set_connecting(network->connecting());
+ set_connected(network->connected());
+ set_connectable(network->connectable());
+ set_connection_type(network->type());
+ set_remembered(false);
+ set_shared(false);
+ set_needs_new_plan(false);
+ set_policy_managed(chromeos::NetworkUIData::IsManaged(network));
+}
+
+NetworkInfoDictionary::NetworkInfoDictionary(
+ const chromeos::Network* network,
+ const chromeos::Network* remembered) {
+ set_service_path(remembered->service_path());
+ set_icon(
+ chromeos::NetworkMenuIcon::GetBitmap(network ? network : remembered));
+ set_name(remembered->name());
+ set_connecting(network ? network->connecting() : false);
+ set_connected(network ? network->connected() : false);
+ set_connectable(true);
+ set_connection_type(remembered->type());
+ set_remembered(true);
+ set_shared(remembered->profile_type() == chromeos::PROFILE_SHARED);
+ set_needs_new_plan(false);
+ set_policy_managed(
+ network ? chromeos::NetworkUIData::IsManaged(network) : false);
+}
+
+DictionaryValue* NetworkInfoDictionary::BuildDictionary() {
+ std::string status;
+
+ if (remembered_) {
+ if (shared_)
+ status = l10n_util::GetStringUTF8(IDS_OPTIONS_SETTINGS_SHARED_NETWORK);
+ } else {
+ // 802.1X networks can be connected but not have saved credentials, and
+ // hence be "not configured". Give preference to the "connected" and
+ // "connecting" states. http://crosbug.com/14459
+ int connection_state = IDS_STATUSBAR_NETWORK_DEVICE_DISCONNECTED;
+ if (connected_)
+ connection_state = IDS_STATUSBAR_NETWORK_DEVICE_CONNECTED;
+ else if (connecting_)
+ connection_state = IDS_STATUSBAR_NETWORK_DEVICE_CONNECTING;
+ else if (!connectable_)
+ connection_state = IDS_STATUSBAR_NETWORK_DEVICE_NOT_CONFIGURED;
+ status = l10n_util::GetStringUTF8(connection_state);
+ if (connection_type_ == chromeos::TYPE_CELLULAR) {
+ if (needs_new_plan_) {
+ status = l10n_util::GetStringUTF8(IDS_OPTIONS_SETTINGS_NO_PLAN_LABEL);
+ } else if (activation_state_ != chromeos::ACTIVATION_STATE_ACTIVATED) {
+ status.append(" / ");
+ status.append(chromeos::CellularNetwork::ActivationStateToString(
+ activation_state_));
+ }
+ }
+ }
+
+ scoped_ptr<DictionaryValue> network_info(new DictionaryValue());
+ network_info->SetInteger(kNetworkInfoKeyActivationState,
+ static_cast<int>(activation_state_));
+ network_info->SetBoolean(kNetworkInfoKeyConnectable, connectable_);
+ network_info->SetBoolean(kNetworkInfoKeyConnected, connected_);
+ network_info->SetBoolean(kNetworkInfoKeyConnecting, connecting_);
+ network_info->SetString(kNetworkInfoKeyIconURL, icon_url_);
+ network_info->SetBoolean(kNetworkInfoKeyNeedsNewPlan, needs_new_plan_);
+ network_info->SetString(kNetworkInfoKeyNetworkName, name_);
+ network_info->SetString(kNetworkInfoKeyNetworkStatus, status);
+ network_info->SetInteger(kNetworkInfoKeyNetworkType,
+ static_cast<int>(connection_type_));
+ network_info->SetBoolean(kNetworkInfoKeyRemembered, remembered_);
+ network_info->SetString(kNetworkInfoKeyServicePath, service_path_);
+ network_info->SetBoolean(kNetworkInfoKeyPolicyManaged, policy_managed_);
+
+ return network_info.release();
+}
+
+} // namespace
+
+InternetOptionsHandler::InternetOptionsHandler() {
+ registrar_.Add(this, chrome::NOTIFICATION_REQUIRE_PIN_SETTING_CHANGE_ENDED,
+ content::NotificationService::AllSources());
+ registrar_.Add(this, chrome::NOTIFICATION_ENTER_PIN_ENDED,
+ content::NotificationService::AllSources());
+ cros_ = chromeos::CrosLibrary::Get()->GetNetworkLibrary();
+ if (cros_) {
+ cros_->AddNetworkManagerObserver(this);
+ cros_->AddCellularDataPlanObserver(this);
+ MonitorNetworks();
+ }
+}
+
+InternetOptionsHandler::~InternetOptionsHandler() {
+ if (cros_) {
+ cros_->RemoveNetworkManagerObserver(this);
+ cros_->RemoveCellularDataPlanObserver(this);
+ cros_->RemoveObserverForAllNetworks(this);
+ }
+}
+
+void InternetOptionsHandler::GetLocalizedValues(
+ DictionaryValue* localized_strings) {
+ DCHECK(localized_strings);
+
+ RegisterTitle(localized_strings, "internetPage",
+ IDS_OPTIONS_INTERNET_TAB_LABEL);
+
+ localized_strings->SetString("wired_title",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_SECTION_TITLE_WIRED_NETWORK));
+ localized_strings->SetString("wireless_title",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_SECTION_TITLE_WIRELESS_NETWORK));
+ localized_strings->SetString("vpn_title",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_SECTION_TITLE_VIRTUAL_NETWORK));
+ localized_strings->SetString("remembered_title",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_SECTION_TITLE_REMEMBERED_NETWORK));
+
+ localized_strings->SetString("connect_button",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_CONNECT));
+ localized_strings->SetString("disconnect_button",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_DISCONNECT));
+ localized_strings->SetString("options_button",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_OPTIONS));
+ localized_strings->SetString("forget_button",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_FORGET));
+ localized_strings->SetString("activate_button",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_ACTIVATE));
+ localized_strings->SetString("buyplan_button",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_BUY_PLAN));
+
+ localized_strings->SetString("changeProxyButton",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_INTERNET_OPTIONS_CHANGE_PROXY_BUTTON));
+
+ localized_strings->SetString("managedNetwork",
+ l10n_util::GetStringUTF16(IDS_OPTIONS_SETTINGS_MANAGED_NETWORK));
+
+ localized_strings->SetString("wifiNetworkTabLabel",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_INTERNET_TAB_WIFI));
+ localized_strings->SetString("vpnTabLabel",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_INTERNET_TAB_VPN));
+ localized_strings->SetString("cellularPlanTabLabel",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_INTERNET_TAB_PLAN));
+ localized_strings->SetString("cellularConnTabLabel",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_INTERNET_TAB_CONNECTION));
+ localized_strings->SetString("cellularDeviceTabLabel",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_INTERNET_TAB_DEVICE));
+ localized_strings->SetString("networkTabLabel",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_INTERNET_TAB_NETWORK));
+ localized_strings->SetString("securityTabLabel",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_INTERNET_TAB_SECURITY));
+
+ localized_strings->SetString("useDHCP",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_INTERNET_OPTIONS_USE_DHCP));
+ localized_strings->SetString("useStaticIP",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_INTERNET_OPTIONS_USE_STATIC_IP));
+ localized_strings->SetString("connectionState",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_INTERNET_CONNECTION_STATE));
+ localized_strings->SetString("inetAddress",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_INTERNET_OPTIONS_ADDRESS));
+ localized_strings->SetString("inetSubnetAddress",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_INTERNET_OPTIONS_SUBNETMASK));
+ localized_strings->SetString("inetGateway",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_INTERNET_OPTIONS_GATEWAY));
+ localized_strings->SetString("inetDns",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_INTERNET_OPTIONS_DNSSERVER));
+ localized_strings->SetString("hardwareAddress",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_INTERNET_OPTIONS_HARDWARE_ADDRESS));
+
+ // Wifi Tab.
+ localized_strings->SetString("accessLockedMsg",
+ l10n_util::GetStringUTF16(
+ IDS_STATUSBAR_NETWORK_LOCKED));
+ localized_strings->SetString("inetSsid",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_INTERNET_OPTIONS_NETWORK_ID));
+ localized_strings->SetString("inetPassProtected",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_INTERNET_OPTIONS_NET_PROTECTED));
+ localized_strings->SetString("inetNetworkShared",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_INTERNET_OPTIONS_NETWORK_SHARED));
+ localized_strings->SetString("inetPreferredNetwork",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_INTERNET_OPTIONS_PREFER_NETWORK));
+ localized_strings->SetString("inetAutoConnectNetwork",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_INTERNET_OPTIONS_AUTO_CONNECT));
+ localized_strings->SetString("inetLogin",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_INTERNET_OPTIONS_LOGIN));
+ localized_strings->SetString("inetShowPass",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_INTERNET_OPTIONS_SHOWPASSWORD));
+ localized_strings->SetString("inetPassPrompt",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_INTERNET_OPTIONS_PASSWORD));
+ localized_strings->SetString("inetSsidPrompt",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_INTERNET_OPTIONS_SSID));
+ localized_strings->SetString("inetStatus",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_INTERNET_OPTIONS_STATUS_TITLE));
+ localized_strings->SetString("inetConnect",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_INTERNET_OPTIONS_CONNECT_TITLE));
+
+ // VPN Tab.
+ localized_strings->SetString("inetServiceName",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_INTERNET_OPTIONS_VPN_SERVICE_NAME));
+ localized_strings->SetString("inetServerHostname",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_INTERNET_OPTIONS_VPN_SERVER_HOSTNAME));
+ localized_strings->SetString("inetProviderType",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_INTERNET_OPTIONS_VPN_PROVIDER_TYPE));
+ localized_strings->SetString("inetUsername",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_INTERNET_OPTIONS_VPN_USERNAME));
+
+ // Cellular Tab.
+ localized_strings->SetString("serviceName",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_INTERNET_CELLULAR_SERVICE_NAME));
+ localized_strings->SetString("networkTechnology",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_INTERNET_CELLULAR_NETWORK_TECHNOLOGY));
+ localized_strings->SetString("operatorName",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_INTERNET_CELLULAR_OPERATOR));
+ localized_strings->SetString("operatorCode",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_INTERNET_CELLULAR_OPERATOR_CODE));
+ localized_strings->SetString("activationState",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_INTERNET_CELLULAR_ACTIVATION_STATE));
+ localized_strings->SetString("roamingState",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_INTERNET_CELLULAR_ROAMING_STATE));
+ localized_strings->SetString("restrictedPool",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_INTERNET_CELLULAR_RESTRICTED_POOL));
+ localized_strings->SetString("errorState",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_INTERNET_CELLULAR_ERROR_STATE));
+ localized_strings->SetString("manufacturer",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_INTERNET_CELLULAR_MANUFACTURER));
+ localized_strings->SetString("modelId",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_INTERNET_CELLULAR_MODEL_ID));
+ localized_strings->SetString("firmwareRevision",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_INTERNET_CELLULAR_FIRMWARE_REVISION));
+ localized_strings->SetString("hardwareRevision",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_INTERNET_CELLULAR_HARDWARE_REVISION));
+ localized_strings->SetString("prlVersion",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_INTERNET_CELLULAR_PRL_VERSION));
+ localized_strings->SetString("cellularApnLabel",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_INTERNET_CELLULAR_APN));
+ localized_strings->SetString("cellularApnOther",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_INTERNET_CELLULAR_APN_OTHER));
+ localized_strings->SetString("cellularApnUsername",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_INTERNET_CELLULAR_APN_USERNAME));
+ localized_strings->SetString("cellularApnPassword",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_INTERNET_CELLULAR_APN_PASSWORD));
+ localized_strings->SetString("cellularApnUseDefault",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_INTERNET_CELLULAR_APN_CLEAR));
+ localized_strings->SetString("cellularApnSet",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_INTERNET_CELLULAR_APN_SET));
+ localized_strings->SetString("cellularApnCancel",
+ l10n_util::GetStringUTF16(
+ IDS_CANCEL));
+
+ localized_strings->SetString("accessSecurityTabLink",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_INTERNET_CELLULAR_ACCESS_SECURITY_TAB));
+ localized_strings->SetString("lockSimCard",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_INTERNET_CELLULAR_LOCK_SIM_CARD));
+ localized_strings->SetString("changePinButton",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_INTERNET_CELLULAR_CHANGE_PIN_BUTTON));
+
+ localized_strings->SetString("planName",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_INTERNET_CELL_PLAN_NAME));
+ localized_strings->SetString("planLoading",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_INTERNET_OPTIONS_LOADING_PLAN));
+ localized_strings->SetString("noPlansFound",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_INTERNET_OPTIONS_NO_PLANS_FOUND));
+ localized_strings->SetString("purchaseMore",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_INTERNET_OPTIONS_PURCHASE_MORE));
+ localized_strings->SetString("dataRemaining",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_INTERNET_OPTIONS_DATA_REMAINING));
+ localized_strings->SetString("planExpires",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_INTERNET_OPTIONS_EXPIRES));
+ localized_strings->SetString("showPlanNotifications",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_INTERNET_OPTIONS_SHOW_MOBILE_NOTIFICATION));
+ localized_strings->SetString("autoconnectCellular",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_INTERNET_OPTIONS_AUTO_CONNECT));
+ localized_strings->SetString("customerSupport",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_INTERNET_OPTIONS_CUSTOMER_SUPPORT));
+
+ localized_strings->SetString("enableWifi",
+ l10n_util::GetStringFUTF16(
+ IDS_STATUSBAR_NETWORK_DEVICE_ENABLE,
+ l10n_util::GetStringUTF16(IDS_STATUSBAR_NETWORK_DEVICE_WIFI)));
+ localized_strings->SetString("disableWifi",
+ l10n_util::GetStringFUTF16(
+ IDS_STATUSBAR_NETWORK_DEVICE_DISABLE,
+ l10n_util::GetStringUTF16(IDS_STATUSBAR_NETWORK_DEVICE_WIFI)));
+ localized_strings->SetString("enableCellular",
+ l10n_util::GetStringFUTF16(
+ IDS_STATUSBAR_NETWORK_DEVICE_ENABLE,
+ l10n_util::GetStringUTF16(IDS_STATUSBAR_NETWORK_DEVICE_CELLULAR)));
+ localized_strings->SetString("disableCellular",
+ l10n_util::GetStringFUTF16(
+ IDS_STATUSBAR_NETWORK_DEVICE_DISABLE,
+ l10n_util::GetStringUTF16(IDS_STATUSBAR_NETWORK_DEVICE_CELLULAR)));
+ localized_strings->SetString("useSharedProxies",
+ l10n_util::GetStringUTF16(IDS_OPTIONS_SETTINGS_USE_SHARED_PROXIES));
+ localized_strings->SetString("enableDataRoaming",
+ l10n_util::GetStringUTF16(IDS_OPTIONS_SETTINGS_ENABLE_DATA_ROAMING));
+ localized_strings->SetString("generalNetworkingTitle",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_INTERNET_CONTROL_TITLE));
+ localized_strings->SetString("detailsInternetDismiss",
+ l10n_util::GetStringUTF16(IDS_CLOSE));
+ localized_strings->SetString("ownerOnly", l10n_util::GetStringUTF16(
+ IDS_OPTIONS_ACCOUNTS_OWNER_ONLY));
+ std::string owner;
+ chromeos::CrosSettings::Get()->GetString(chromeos::kDeviceOwner, &owner);
+ localized_strings->SetString("ownerUserId", UTF8ToUTF16(owner));
+
+ FillNetworkInfo(localized_strings);
+}
+
+void InternetOptionsHandler::Initialize() {
+ cros_->RequestNetworkScan();
+}
+
+void InternetOptionsHandler::RegisterMessages() {
+ // Setup handlers specific to this panel.
+ DCHECK(web_ui_);
+ web_ui_->RegisterMessageCallback("buttonClickCallback",
+ base::Bind(&InternetOptionsHandler::ButtonClickCallback,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("refreshCellularPlan",
+ base::Bind(&InternetOptionsHandler::RefreshCellularPlanCallback,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("setPreferNetwork",
+ base::Bind(&InternetOptionsHandler::SetPreferNetworkCallback,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("setAutoConnect",
+ base::Bind(&InternetOptionsHandler::SetAutoConnectCallback,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("setIPConfig",
+ base::Bind(&InternetOptionsHandler::SetIPConfigCallback,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("enableWifi",
+ base::Bind(&InternetOptionsHandler::EnableWifiCallback,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("disableWifi",
+ base::Bind(&InternetOptionsHandler::DisableWifiCallback,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("enableCellular",
+ base::Bind(&InternetOptionsHandler::EnableCellularCallback,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("disableCellular",
+ base::Bind(&InternetOptionsHandler::DisableCellularCallback,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("buyDataPlan",
+ base::Bind(&InternetOptionsHandler::BuyDataPlanCallback,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("showMorePlanInfo",
+ base::Bind(&InternetOptionsHandler::BuyDataPlanCallback,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("setApn",
+ base::Bind(&InternetOptionsHandler::SetApnCallback,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("setSimCardLock",
+ base::Bind(&InternetOptionsHandler::SetSimCardLockCallback,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("changePin",
+ base::Bind(&InternetOptionsHandler::ChangePinCallback,
+ base::Unretained(this)));
+}
+
+void InternetOptionsHandler::EnableWifiCallback(const ListValue* args) {
+ cros_->EnableWifiNetworkDevice(true);
+}
+
+void InternetOptionsHandler::DisableWifiCallback(const ListValue* args) {
+ cros_->EnableWifiNetworkDevice(false);
+}
+
+void InternetOptionsHandler::EnableCellularCallback(const ListValue* args) {
+ const chromeos::NetworkDevice* cellular = cros_->FindCellularDevice();
+ if (!cellular) {
+ LOG(ERROR) << "Didn't find cellular device, it should have been available.";
+ cros_->EnableCellularNetworkDevice(true);
+ } else if (cellular->sim_lock_state() == chromeos::SIM_UNLOCKED ||
+ cellular->sim_lock_state() == chromeos::SIM_UNKNOWN) {
+ cros_->EnableCellularNetworkDevice(true);
+ } else {
+ chromeos::SimDialogDelegate::ShowDialog(GetNativeWindow(),
+ chromeos::SimDialogDelegate::SIM_DIALOG_UNLOCK);
+ }
+}
+
+void InternetOptionsHandler::DisableCellularCallback(const ListValue* args) {
+ cros_->EnableCellularNetworkDevice(false);
+}
+
+void InternetOptionsHandler::BuyDataPlanCallback(const ListValue* args) {
+ if (!web_ui_)
+ return;
+ Browser* browser = BrowserList::FindBrowserWithFeature(
+ Profile::FromWebUI(web_ui_), Browser::FEATURE_TABSTRIP);
+ if (browser)
+ browser->OpenMobilePlanTabAndActivate();
+}
+
+void InternetOptionsHandler::SetApnCallback(const ListValue* args) {
+ std::string service_path;
+ std::string apn;
+ std::string username;
+ std::string password;
+ if (args->GetSize() != 4 ||
+ !args->GetString(0, &service_path) ||
+ !args->GetString(1, &apn) ||
+ !args->GetString(2, &username) ||
+ !args->GetString(3, &password)) {
+ NOTREACHED();
+ return;
+ }
+
+ chromeos::CellularNetwork* network =
+ cros_->FindCellularNetworkByPath(service_path);
+ if (network) {
+ network->SetApn(chromeos::CellularApn(
+ apn, network->apn().network_id, username, password));
+ }
+}
+
+void InternetOptionsHandler::SetSimCardLockCallback(const ListValue* args) {
+ bool require_pin_new_value;
+ if (!args->GetBoolean(0, &require_pin_new_value)) {
+ NOTREACHED();
+ return;
+ }
+ // 1. Bring up SIM unlock dialog, pass new RequirePin setting in URL.
+ // 2. Dialog will ask for current PIN in any case.
+ // 3. If card is locked it will first call PIN unlock operation
+ // 4. Then it will call Set RequirePin, passing the same PIN.
+ // 5. We'll get notified by REQUIRE_PIN_SETTING_CHANGE_ENDED notification.
+ chromeos::SimDialogDelegate::SimDialogMode mode;
+ if (require_pin_new_value)
+ mode = chromeos::SimDialogDelegate::SIM_DIALOG_SET_LOCK_ON;
+ else
+ mode = chromeos::SimDialogDelegate::SIM_DIALOG_SET_LOCK_OFF;
+ chromeos::SimDialogDelegate::ShowDialog(GetNativeWindow(), mode);
+}
+
+void InternetOptionsHandler::ChangePinCallback(const ListValue* args) {
+ chromeos::SimDialogDelegate::ShowDialog(GetNativeWindow(),
+ chromeos::SimDialogDelegate::SIM_DIALOG_CHANGE_PIN);
+}
+
+void InternetOptionsHandler::RefreshNetworkData() {
+ DictionaryValue dictionary;
+ FillNetworkInfo(&dictionary);
+ web_ui_->CallJavascriptFunction(
+ "options.InternetOptions.refreshNetworkData", dictionary);
+}
+
+void InternetOptionsHandler::OnNetworkManagerChanged(
+ chromeos::NetworkLibrary* cros) {
+ if (!web_ui_)
+ return;
+ MonitorNetworks();
+ RefreshNetworkData();
+}
+
+void InternetOptionsHandler::OnNetworkChanged(
+ chromeos::NetworkLibrary* cros,
+ const chromeos::Network* network) {
+ if (web_ui_)
+ RefreshNetworkData();
+}
+
+// Monitor wireless networks for changes. It is only necessary
+// to set up individual observers for the cellular networks
+// (if any) and for the connected Wi-Fi network (if any). The
+// only change we are interested in for Wi-Fi networks is signal
+// strength. For non-connected Wi-Fi networks, all information is
+// reported via scan results, which trigger network manager
+// updates. Only the connected Wi-Fi network has changes reported
+// via service property updates.
+void InternetOptionsHandler::MonitorNetworks() {
+ cros_->RemoveObserverForAllNetworks(this);
+ const chromeos::WifiNetwork* wifi_network = cros_->wifi_network();
+ if (wifi_network)
+ cros_->AddNetworkObserver(wifi_network->service_path(), this);
+ // Always monitor the cellular networks, if any, so that changes
+ // in network technology, roaming status, and signal strength
+ // will be shown.
+ const chromeos::CellularNetworkVector& cell_networks =
+ cros_->cellular_networks();
+ for (size_t i = 0; i < cell_networks.size(); ++i) {
+ chromeos::CellularNetwork* cell_network = cell_networks[i];
+ cros_->AddNetworkObserver(cell_network->service_path(), this);
+ }
+ const chromeos::VirtualNetwork* virtual_network = cros_->virtual_network();
+ if (virtual_network)
+ cros_->AddNetworkObserver(virtual_network->service_path(), this);
+}
+
+void InternetOptionsHandler::OnCellularDataPlanChanged(
+ chromeos::NetworkLibrary* cros) {
+ if (!web_ui_)
+ return;
+ const chromeos::CellularNetwork* cellular = cros_->cellular_network();
+ if (!cellular)
+ return;
+ const chromeos::CellularDataPlanVector* plans =
+ cros_->GetDataPlans(cellular->service_path());
+ DictionaryValue connection_plans;
+ ListValue* plan_list = new ListValue();
+ if (plans) {
+ for (chromeos::CellularDataPlanVector::const_iterator iter = plans->begin();
+ iter != plans->end(); ++iter) {
+ plan_list->Append(CellularDataPlanToDictionary(*iter));
+ }
+ }
+ connection_plans.SetString("servicePath", cellular->service_path());
+ connection_plans.SetBoolean("needsPlan", cellular->needs_new_plan());
+ connection_plans.SetBoolean("activated",
+ cellular->activation_state() == chromeos::ACTIVATION_STATE_ACTIVATED);
+ connection_plans.Set("plans", plan_list);
+ SetActivationButtonVisibility(cellular, &connection_plans);
+ web_ui_->CallJavascriptFunction(
+ "options.InternetOptions.updateCellularPlans", connection_plans);
+}
+
+
+void InternetOptionsHandler::Observe(
+ int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) {
+ OptionsPage2UIHandler::Observe(type, source, details);
+ if (type == chrome::NOTIFICATION_REQUIRE_PIN_SETTING_CHANGE_ENDED) {
+ base::FundamentalValue require_pin(*content::Details<bool>(details).ptr());
+ web_ui_->CallJavascriptFunction(
+ "options.InternetOptions.updateSecurityTab", require_pin);
+ } else if (type == chrome::NOTIFICATION_ENTER_PIN_ENDED) {
+ // We make an assumption (which is valid for now) that the SIM
+ // unlock dialog is put up only when the user is trying to enable
+ // mobile data.
+ bool cancelled = *content::Details<bool>(details).ptr();
+ if (cancelled) {
+ base::DictionaryValue dictionary;
+ FillNetworkInfo(&dictionary);
+ web_ui_->CallJavascriptFunction(
+ "options.InternetOptions.setupAttributes", dictionary);
+ }
+ // The case in which the correct PIN was entered and the SIM is
+ // now unlocked is handled in NetworkMenuButton.
+ }
+}
+
+DictionaryValue* InternetOptionsHandler::CellularDataPlanToDictionary(
+ const chromeos::CellularDataPlan* plan) {
+ DictionaryValue* plan_dict = new DictionaryValue();
+ plan_dict->SetInteger("planType", plan->plan_type);
+ plan_dict->SetString("name", plan->plan_name);
+ plan_dict->SetString("planSummary", plan->GetPlanDesciption());
+ plan_dict->SetString("dataRemaining", plan->GetDataRemainingDesciption());
+ plan_dict->SetString("planExpires", plan->GetPlanExpiration());
+ plan_dict->SetString("warning", plan->GetRemainingWarning());
+ return plan_dict;
+}
+
+void InternetOptionsHandler::SetPreferNetworkCallback(const ListValue* args) {
+ std::string service_path;
+ std::string prefer_network_str;
+
+ if (args->GetSize() < 2 ||
+ !args->GetString(0, &service_path) ||
+ !args->GetString(1, &prefer_network_str)) {
+ NOTREACHED();
+ return;
+ }
+
+ chromeos::Network* network = cros_->FindNetworkByPath(service_path);
+ if (!network)
+ return;
+
+ bool prefer_network = prefer_network_str == "true";
+ if (prefer_network != network->preferred())
+ network->SetPreferred(prefer_network);
+}
+
+void InternetOptionsHandler::SetAutoConnectCallback(const ListValue* args) {
+ std::string service_path;
+ std::string auto_connect_str;
+
+ if (args->GetSize() < 2 ||
+ !args->GetString(0, &service_path) ||
+ !args->GetString(1, &auto_connect_str)) {
+ NOTREACHED();
+ return;
+ }
+
+ chromeos::Network* network = cros_->FindNetworkByPath(service_path);
+ if (!network)
+ return;
+
+ bool auto_connect = auto_connect_str == "true";
+ if (auto_connect != network->auto_connect())
+ network->SetAutoConnect(auto_connect);
+}
+
+void InternetOptionsHandler::SetIPConfigCallback(const ListValue* args) {
+ std::string service_path;
+ std::string dhcp_str;
+ std::string address;
+ std::string netmask;
+ std::string gateway;
+ std::string name_servers;
+
+ if (args->GetSize() < 6 ||
+ !args->GetString(0, &service_path) ||
+ !args->GetString(1, &dhcp_str) ||
+ !args->GetString(2, &address) ||
+ !args->GetString(3, &netmask) ||
+ !args->GetString(4, &gateway) ||
+ !args->GetString(5, &name_servers)) {
+ NOTREACHED();
+ return;
+ }
+
+ chromeos::Network* network = cros_->FindNetworkByPath(service_path);
+ if (!network)
+ return;
+
+ cros_->SetIPConfig(chromeos::NetworkIPConfig(network->device_path(),
+ dhcp_str == "true" ? chromeos::IPCONFIG_TYPE_DHCP :
+ chromeos::IPCONFIG_TYPE_IPV4,
+ address, netmask, gateway, name_servers));
+}
+
+void InternetOptionsHandler::PopulateDictionaryDetails(
+ const chromeos::Network* network) {
+ DCHECK(network);
+
+ if (web_ui_) {
+ Profile::FromWebUI(web_ui_)->GetProxyConfigTracker()->UISetCurrentNetwork(
+ network->service_path());
+ }
+
+ DictionaryValue dictionary;
+ std::string hardware_address;
+ chromeos::NetworkIPConfigVector ipconfigs = cros_->GetIPConfigs(
+ network->device_path(), &hardware_address,
+ chromeos::NetworkLibrary::FORMAT_COLON_SEPARATED_HEX);
+ if (!hardware_address.empty())
+ dictionary.SetString("hardwareAddress", hardware_address);
+
+ scoped_ptr<DictionaryValue> ipconfig_dhcp;
+ scoped_ptr<DictionaryValue> ipconfig_static;
+ for (chromeos::NetworkIPConfigVector::const_iterator it = ipconfigs.begin();
+ it != ipconfigs.end(); ++it) {
+ const chromeos::NetworkIPConfig& ipconfig = *it;
+ scoped_ptr<DictionaryValue> ipconfig_dict(new DictionaryValue());
+ ipconfig_dict->SetString("address", ipconfig.address);
+ ipconfig_dict->SetString("subnetAddress", ipconfig.netmask);
+ ipconfig_dict->SetString("gateway", ipconfig.gateway);
+ ipconfig_dict->SetString("dns", ipconfig.name_servers);
+ if (ipconfig.type == chromeos::IPCONFIG_TYPE_DHCP)
+ ipconfig_dhcp.reset(ipconfig_dict.release());
+ else if (ipconfig.type == chromeos::IPCONFIG_TYPE_IPV4)
+ ipconfig_static.reset(ipconfig_dict.release());
+ }
+
+ chromeos::NetworkPropertyUIData ipconfig_dhcp_ui_data(network, NULL);
+ SetValueDictionary(&dictionary, "ipconfigDHCP", ipconfig_dhcp.release(),
+ ipconfig_dhcp_ui_data);
+ chromeos::NetworkPropertyUIData ipconfig_static_ui_data(network, NULL);
+ SetValueDictionary(&dictionary, "ipconfigStatic", ipconfig_static.release(),
+ ipconfig_static_ui_data);
+
+ chromeos::ConnectionType type = network->type();
+ dictionary.SetInteger("type", type);
+ dictionary.SetString("servicePath", network->service_path());
+ dictionary.SetBoolean("connecting", network->connecting());
+ dictionary.SetBoolean("connected", network->connected());
+ dictionary.SetString("connectionState", network->GetStateString());
+
+ // Only show proxy for remembered networks.
+ chromeos::NetworkProfileType network_profile = network->profile_type();
+ dictionary.SetBoolean("showProxy", network_profile != chromeos::PROFILE_NONE);
+
+ // Hide the dhcp/static radio if not ethernet or wifi (or if not enabled)
+ bool staticIPConfig = CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kEnableStaticIPConfig);
+ dictionary.SetBoolean("showStaticIPConfig", staticIPConfig &&
+ (type == chromeos::TYPE_WIFI || type == chromeos::TYPE_ETHERNET));
+
+ chromeos::NetworkPropertyUIData preferred_ui_data(
+ network, chromeos::NetworkUIData::kPropertyPreferred);
+ if (network_profile == chromeos::PROFILE_USER) {
+ dictionary.SetBoolean("showPreferred", true);
+ SetValueDictionary(&dictionary, "preferred",
+ Value::CreateBooleanValue(network->preferred()),
+ preferred_ui_data);
+ } else {
+ dictionary.SetBoolean("showPreferred", false);
+ SetValueDictionary(&dictionary, "preferred",
+ Value::CreateBooleanValue(network->preferred()),
+ preferred_ui_data);
+ }
+ chromeos::NetworkPropertyUIData auto_connect_ui_data(
+ network, chromeos::NetworkUIData::kPropertyAutoConnect);
+ SetValueDictionary(&dictionary, "autoConnect",
+ Value::CreateBooleanValue(network->auto_connect()),
+ auto_connect_ui_data);
+
+ if (type == chromeos::TYPE_WIFI) {
+ dictionary.SetBoolean("deviceConnected", cros_->wifi_connected());
+ const chromeos::WifiNetwork* wifi =
+ cros_->FindWifiNetworkByPath(network->service_path());
+ if (!wifi) {
+ LOG(WARNING) << "Cannot find network " << network->service_path();
+ } else {
+ PopulateWifiDetails(wifi, &dictionary);
+ }
+ } else if (type == chromeos::TYPE_CELLULAR) {
+ dictionary.SetBoolean("deviceConnected", cros_->cellular_connected());
+ const chromeos::CellularNetwork* cellular =
+ cros_->FindCellularNetworkByPath(network->service_path());
+ if (!cellular) {
+ LOG(WARNING) << "Cannot find network " << network->service_path();
+ } else {
+ PopulateCellularDetails(cellular, &dictionary);
+ }
+ } else if (type == chromeos::TYPE_VPN) {
+ dictionary.SetBoolean("deviceConnected",
+ cros_->virtual_network_connected());
+ const chromeos::VirtualNetwork* vpn =
+ cros_->FindVirtualNetworkByPath(network->service_path());
+ if (!vpn) {
+ LOG(WARNING) << "Cannot find network " << network->service_path();
+ } else {
+ PopulateVPNDetails(vpn, &dictionary);
+ }
+ } else if (type == chromeos::TYPE_ETHERNET) {
+ dictionary.SetBoolean("deviceConnected", cros_->ethernet_connected());
+ }
+
+ web_ui_->CallJavascriptFunction(
+ "options.InternetOptions.showDetailedInfo", dictionary);
+}
+
+void InternetOptionsHandler::PopulateWifiDetails(
+ const chromeos::WifiNetwork* wifi,
+ DictionaryValue* dictionary) {
+ dictionary->SetString("ssid", wifi->name());
+ bool remembered = (wifi->profile_type() != chromeos::PROFILE_NONE);
+ dictionary->SetBoolean("remembered", remembered);
+ dictionary->SetBoolean("encrypted", wifi->encrypted());
+ bool shared = wifi->profile_type() == chromeos::PROFILE_SHARED;
+ dictionary->SetBoolean("shared", shared);
+}
+
+DictionaryValue* InternetOptionsHandler::CreateDictionaryFromCellularApn(
+ const chromeos::CellularApn& apn) {
+ DictionaryValue* dictionary = new DictionaryValue();
+ dictionary->SetString("apn", apn.apn);
+ dictionary->SetString("networkId", apn.network_id);
+ dictionary->SetString("username", apn.username);
+ dictionary->SetString("password", apn.password);
+ dictionary->SetString("name", apn.name);
+ dictionary->SetString("localizedName", apn.localized_name);
+ dictionary->SetString("language", apn.language);
+ return dictionary;
+}
+
+void InternetOptionsHandler::PopulateCellularDetails(
+ const chromeos::CellularNetwork* cellular,
+ DictionaryValue* dictionary) {
+ // Cellular network / connection settings.
+ dictionary->SetString("serviceName", cellular->name());
+ dictionary->SetString("networkTechnology",
+ cellular->GetNetworkTechnologyString());
+ dictionary->SetString("operatorName", cellular->operator_name());
+ dictionary->SetString("operatorCode", cellular->operator_code());
+ dictionary->SetString("activationState",
+ cellular->GetActivationStateString());
+ dictionary->SetString("roamingState",
+ cellular->GetRoamingStateString());
+ dictionary->SetString("restrictedPool",
+ cellular->restricted_pool() ?
+ l10n_util::GetStringUTF8(
+ IDS_CONFIRM_MESSAGEBOX_YES_BUTTON_LABEL) :
+ l10n_util::GetStringUTF8(
+ IDS_CONFIRM_MESSAGEBOX_NO_BUTTON_LABEL));
+ dictionary->SetString("errorState", cellular->GetErrorString());
+ dictionary->SetString("supportUrl", cellular->payment_url());
+ dictionary->SetBoolean("needsPlan", cellular->needs_new_plan());
+
+ dictionary->Set("apn", CreateDictionaryFromCellularApn(cellular->apn()));
+ dictionary->Set("lastGoodApn",
+ CreateDictionaryFromCellularApn(cellular->last_good_apn()));
+
+ // Device settings.
+ const chromeos::NetworkDevice* device =
+ cros_->FindNetworkDeviceByPath(cellular->device_path());
+ if (device) {
+ chromeos::NetworkPropertyUIData cellular_propety_ui_data(cellular, NULL);
+ dictionary->SetString("manufacturer", device->manufacturer());
+ dictionary->SetString("modelId", device->model_id());
+ dictionary->SetString("firmwareRevision", device->firmware_revision());
+ dictionary->SetString("hardwareRevision", device->hardware_revision());
+ dictionary->SetString("prlVersion",
+ base::StringPrintf("%u", device->prl_version()));
+ dictionary->SetString("meid", device->meid());
+ dictionary->SetString("imei", device->imei());
+ dictionary->SetString("mdn", device->mdn());
+ dictionary->SetString("imsi", device->imsi());
+ dictionary->SetString("esn", device->esn());
+ dictionary->SetString("min", device->min());
+ dictionary->SetBoolean("gsm",
+ device->technology_family() == chromeos::TECHNOLOGY_FAMILY_GSM);
+ SetValueDictionary(
+ dictionary, "simCardLockEnabled",
+ Value::CreateBooleanValue(
+ device->sim_pin_required() == chromeos::SIM_PIN_REQUIRED),
+ cellular_propety_ui_data);
+
+ chromeos::MobileConfig* config = chromeos::MobileConfig::GetInstance();
+ if (config->IsReady()) {
+ std::string carrier_id = cros_->GetCellularHomeCarrierId();
+ const chromeos::MobileConfig::Carrier* carrier =
+ config->GetCarrier(carrier_id);
+ if (carrier && !carrier->top_up_url().empty())
+ dictionary->SetString("carrierUrl", carrier->top_up_url());
+ }
+
+ const chromeos::CellularApnList& apn_list = device->provider_apn_list();
+ ListValue* apn_list_value = new ListValue();
+ for (chromeos::CellularApnList::const_iterator it = apn_list.begin();
+ it != apn_list.end(); ++it) {
+ apn_list_value->Append(CreateDictionaryFromCellularApn(*it));
+ }
+ SetValueDictionary(dictionary, "providerApnList", apn_list_value,
+ cellular_propety_ui_data);
+ }
+
+ SetActivationButtonVisibility(cellular, dictionary);
+}
+
+void InternetOptionsHandler::PopulateVPNDetails(
+ const chromeos::VirtualNetwork* vpn,
+ DictionaryValue* dictionary) {
+ dictionary->SetString("service_name", vpn->name());
+ bool remembered = (vpn->profile_type() != chromeos::PROFILE_NONE);
+ dictionary->SetBoolean("remembered", remembered);
+ dictionary->SetString("server_hostname", vpn->server_hostname());
+ dictionary->SetString("provider_type", vpn->GetProviderTypeString());
+ dictionary->SetString("username", vpn->username());
+}
+
+void InternetOptionsHandler::SetActivationButtonVisibility(
+ const chromeos::CellularNetwork* cellular,
+ DictionaryValue* dictionary) {
+ if (cellular->needs_new_plan()) {
+ dictionary->SetBoolean("showBuyButton", true);
+ } else if (cellular->activation_state() !=
+ chromeos::ACTIVATION_STATE_ACTIVATING &&
+ cellular->activation_state() !=
+ chromeos::ACTIVATION_STATE_ACTIVATED) {
+ dictionary->SetBoolean("showActivateButton", true);
+ }
+}
+
+void InternetOptionsHandler::CreateModalPopup(views::WidgetDelegate* view) {
+ views::Widget* window = browser::CreateViewsWindow(GetNativeWindow(),
+ view,
+ STYLE_GENERIC);
+ window->SetAlwaysOnTop(true);
+ window->Show();
+}
+
+gfx::NativeWindow InternetOptionsHandler::GetNativeWindow() const {
+ // TODO(beng): This is an improper direct dependency on Browser. Route this
+ // through some sort of delegate.
+ Browser* browser =
+ BrowserList::FindBrowserWithProfile(Profile::FromWebUI(web_ui_));
+ return browser->window()->GetNativeHandle();
+}
+
+void InternetOptionsHandler::ButtonClickCallback(const ListValue* args) {
+ std::string str_type;
+ std::string service_path;
+ std::string command;
+ if (args->GetSize() != 3 ||
+ !args->GetString(0, &str_type) ||
+ !args->GetString(1, &service_path) ||
+ !args->GetString(2, &command)) {
+ NOTREACHED();
+ return;
+ }
+
+ int type = atoi(str_type.c_str());
+ if (type == chromeos::TYPE_ETHERNET) {
+ const chromeos::EthernetNetwork* ether = cros_->ethernet_network();
+ if (ether)
+ PopulateDictionaryDetails(ether);
+ } else if (type == chromeos::TYPE_WIFI) {
+ HandleWifiButtonClick(service_path, command);
+ } else if (type == chromeos::TYPE_CELLULAR) {
+ HandleCellularButtonClick(service_path, command);
+ } else if (type == chromeos::TYPE_VPN) {
+ HandleVPNButtonClick(service_path, command);
+ } else {
+ NOTREACHED();
+ }
+}
+
+void InternetOptionsHandler::HandleWifiButtonClick(
+ const std::string& service_path,
+ const std::string& command) {
+ chromeos::WifiNetwork* wifi = NULL;
+ if (command == "forget") {
+ cros_->ForgetNetwork(service_path);
+ } else if (service_path == kOtherNetworksFakePath) {
+ // Other wifi networks.
+ CreateModalPopup(new chromeos::NetworkConfigView(chromeos::TYPE_WIFI));
+ } else if ((wifi = cros_->FindWifiNetworkByPath(service_path))) {
+ if (command == "connect") {
+ // Connect to wifi here. Open password page if appropriate.
+ if (wifi->IsPassphraseRequired()) {
+ CreateModalPopup(new chromeos::NetworkConfigView(wifi));
+ } else {
+ cros_->ConnectToWifiNetwork(wifi);
+ }
+ } else if (command == "disconnect") {
+ cros_->DisconnectFromNetwork(wifi);
+ } else if (command == "options") {
+ PopulateDictionaryDetails(wifi);
+ }
+ }
+}
+
+void InternetOptionsHandler::HandleCellularButtonClick(
+ const std::string& service_path,
+ const std::string& command) {
+ chromeos::CellularNetwork* cellular = NULL;
+ if (service_path == kOtherNetworksFakePath) {
+ chromeos::ChooseMobileNetworkDialog::ShowDialog(GetNativeWindow());
+ } else if ((cellular = cros_->FindCellularNetworkByPath(service_path))) {
+ if (command == "connect") {
+ cros_->ConnectToCellularNetwork(cellular);
+ } else if (command == "disconnect") {
+ cros_->DisconnectFromNetwork(cellular);
+ } else if (command == "activate") {
+ Browser* browser = BrowserList::GetLastActive();
+ if (browser)
+ browser->OpenMobilePlanTabAndActivate();
+ } else if (command == "options") {
+ PopulateDictionaryDetails(cellular);
+ }
+ }
+}
+
+void InternetOptionsHandler::HandleVPNButtonClick(
+ const std::string& service_path,
+ const std::string& command) {
+ chromeos::VirtualNetwork* network = NULL;
+ if (command == "forget") {
+ cros_->ForgetNetwork(service_path);
+ } else if (service_path == kOtherNetworksFakePath) {
+ // TODO(altimofeev): verify if service_path in condition is correct.
+ // Other VPN networks.
+ CreateModalPopup(new chromeos::NetworkConfigView(chromeos::TYPE_VPN));
+ } else if ((network = cros_->FindVirtualNetworkByPath(service_path))) {
+ if (command == "connect") {
+ // Connect to VPN here. Open password page if appropriate.
+ if (network->NeedMoreInfoToConnect()) {
+ CreateModalPopup(new chromeos::NetworkConfigView(network));
+ } else {
+ cros_->ConnectToVirtualNetwork(network);
+ }
+ } else if (command == "disconnect") {
+ cros_->DisconnectFromNetwork(network);
+ } else if (command == "options") {
+ PopulateDictionaryDetails(network);
+ }
+ }
+}
+
+void InternetOptionsHandler::RefreshCellularPlanCallback(
+ const ListValue* args) {
+ std::string service_path;
+ if (args->GetSize() != 1 ||
+ !args->GetString(0, &service_path)) {
+ NOTREACHED();
+ return;
+ }
+ const chromeos::CellularNetwork* cellular =
+ cros_->FindCellularNetworkByPath(service_path);
+ if (cellular)
+ cellular->RefreshDataPlansIfNeeded();
+}
+
+ListValue* InternetOptionsHandler::GetWiredList() {
+ ListValue* list = new ListValue();
+
+ // If ethernet is not enabled, then don't add anything.
+ if (cros_->ethernet_enabled()) {
+ const chromeos::EthernetNetwork* ethernet_network =
+ cros_->ethernet_network();
+ if (ethernet_network) {
+ NetworkInfoDictionary network_dict(ethernet_network);
+ network_dict.set_name(
+ l10n_util::GetStringUTF8(IDS_STATUSBAR_NETWORK_DEVICE_ETHERNET)),
+ list->Append(network_dict.BuildDictionary());
+ }
+ }
+ return list;
+}
+
+ListValue* InternetOptionsHandler::GetWirelessList() {
+ ListValue* list = new ListValue();
+
+ const chromeos::WifiNetworkVector& wifi_networks = cros_->wifi_networks();
+ for (chromeos::WifiNetworkVector::const_iterator it =
+ wifi_networks.begin(); it != wifi_networks.end(); ++it) {
+ NetworkInfoDictionary network_dict(*it);
+ network_dict.set_connectable(cros_->CanConnectToNetwork(*it));
+ list->Append(network_dict.BuildDictionary());
+ }
+
+ // Add "Other WiFi network..." if wifi is enabled.
+ if (cros_->wifi_enabled()) {
+ NetworkInfoDictionary network_dict;
+ network_dict.set_service_path(kOtherNetworksFakePath);
+ network_dict.set_icon(
+ chromeos::NetworkMenuIcon::GetConnectedBitmap(
+ chromeos::NetworkMenuIcon::ARCS));
+ network_dict.set_name(
+ l10n_util::GetStringUTF8(IDS_OPTIONS_SETTINGS_OTHER_WIFI_NETWORKS));
+ network_dict.set_connectable(true);
+ network_dict.set_connection_type(chromeos::TYPE_WIFI);
+ list->Append(network_dict.BuildDictionary());
+ }
+
+ const chromeos::CellularNetworkVector cellular_networks =
+ cros_->cellular_networks();
+ for (chromeos::CellularNetworkVector::const_iterator it =
+ cellular_networks.begin(); it != cellular_networks.end(); ++it) {
+ NetworkInfoDictionary network_dict(*it);
+ network_dict.set_connectable(cros_->CanConnectToNetwork(*it));
+ network_dict.set_activation_state((*it)->activation_state());
+ network_dict.set_needs_new_plan(
+ (*it)->SupportsDataPlan() && (*it)->restricted_pool());
+ list->Append(network_dict.BuildDictionary());
+ }
+
+ const chromeos::NetworkDevice* cellular_device = cros_->FindCellularDevice();
+ if (cellular_device && cellular_device->support_network_scan() &&
+ cros_->cellular_enabled()) {
+ NetworkInfoDictionary network_dict;
+ network_dict.set_service_path(kOtherNetworksFakePath);
+ network_dict.set_icon(
+ chromeos::NetworkMenuIcon::GetDisconnectedBitmap(
+ chromeos::NetworkMenuIcon::BARS));
+ network_dict.set_name(
+ l10n_util::GetStringUTF8(IDS_OPTIONS_SETTINGS_OTHER_CELLULAR_NETWORKS));
+ network_dict.set_connectable(true);
+ network_dict.set_connection_type(chromeos::TYPE_CELLULAR);
+ network_dict.set_activation_state(chromeos::ACTIVATION_STATE_ACTIVATED);
+ list->Append(network_dict.BuildDictionary());
+ }
+
+ return list;
+}
+
+ListValue* InternetOptionsHandler::GetVPNList() {
+ ListValue* list = new ListValue();
+
+ const chromeos::VirtualNetworkVector& virtual_networks =
+ cros_->virtual_networks();
+ for (chromeos::VirtualNetworkVector::const_iterator it =
+ virtual_networks.begin(); it != virtual_networks.end(); ++it) {
+ NetworkInfoDictionary network_dict(*it);
+ network_dict.set_connectable(cros_->CanConnectToNetwork(*it));
+ list->Append(network_dict.BuildDictionary());
+ }
+
+ return list;
+}
+
+ListValue* InternetOptionsHandler::GetRememberedList() {
+ ListValue* list = new ListValue();
+
+ for (chromeos::WifiNetworkVector::const_iterator rit =
+ cros_->remembered_wifi_networks().begin();
+ rit != cros_->remembered_wifi_networks().end(); ++rit) {
+ chromeos::WifiNetwork* remembered = *rit;
+ chromeos::WifiNetwork* wifi = static_cast<chromeos::WifiNetwork*>(
+ cros_->FindNetworkByUniqueId(remembered->unique_id()));
+
+ NetworkInfoDictionary network_dict(wifi, remembered);
+ list->Append(network_dict.BuildDictionary());
+ }
+
+ for (chromeos::VirtualNetworkVector::const_iterator rit =
+ cros_->remembered_virtual_networks().begin();
+ rit != cros_->remembered_virtual_networks().end(); ++rit) {
+ chromeos::VirtualNetwork* remembered = *rit;
+ chromeos::VirtualNetwork* vpn = static_cast<chromeos::VirtualNetwork*>(
+ cros_->FindNetworkByUniqueId(remembered->unique_id()));
+
+ NetworkInfoDictionary network_dict(vpn, remembered);
+ list->Append(network_dict.BuildDictionary());
+ }
+
+ return list;
+}
+
+void InternetOptionsHandler::FillNetworkInfo(DictionaryValue* dictionary) {
+ dictionary->SetBoolean("accessLocked", cros_->IsLocked());
+ dictionary->Set("wiredList", GetWiredList());
+ dictionary->Set("wirelessList", GetWirelessList());
+ dictionary->Set("vpnList", GetVPNList());
+ dictionary->Set("rememberedList", GetRememberedList());
+ dictionary->SetBoolean("wifiAvailable", cros_->wifi_available());
+ dictionary->SetBoolean("wifiBusy", cros_->wifi_busy());
+ dictionary->SetBoolean("wifiEnabled", cros_->wifi_enabled());
+ dictionary->SetBoolean("cellularAvailable", cros_->cellular_available());
+ dictionary->SetBoolean("cellularBusy", cros_->cellular_busy());
+ dictionary->SetBoolean("cellularEnabled", cros_->cellular_enabled());
+}
+
+void InternetOptionsHandler::SetValueDictionary(
+ DictionaryValue* settings,
+ const char* key,
+ base::Value* value,
+ const chromeos::NetworkPropertyUIData& ui_data) {
+ DictionaryValue* value_dict = new DictionaryValue();
+ // DictionaryValue::Set() takes ownership of |value|.
+ if (value)
+ value_dict->Set("value", value);
+ const base::Value* default_value = ui_data.default_value();
+ if (default_value)
+ value_dict->Set("default", default_value->DeepCopy());
+ if (ui_data.managed())
+ value_dict->SetString("controlledBy", "policy");
+ else if (ui_data.recommended())
+ value_dict->SetString("controlledBy", "recommended");
+ settings->Set(key, value_dict);
+}
diff --git a/chrome/browser/ui/webui/options2/chromeos/internet_options_handler.h b/chrome/browser/ui/webui/options2/chromeos/internet_options_handler.h
new file mode 100644
index 0000000..568f097
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/chromeos/internet_options_handler.h
@@ -0,0 +1,148 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_INTERNET_OPTIONS_HANDLER_H_
+#define CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_INTERNET_OPTIONS_HANDLER_H_
+
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "chrome/browser/chromeos/cros/network_library.h"
+#include "chrome/browser/chromeos/cros/network_ui_data.h"
+#include "chrome/browser/ui/webui/options2/options_ui2.h"
+#include "content/public/browser/notification_registrar.h"
+#include "ui/gfx/native_widget_types.h"
+
+class SkBitmap;
+namespace views {
+class WidgetDelegate;
+}
+
+// ChromeOS internet options page UI handler.
+class InternetOptionsHandler
+ : public OptionsPage2UIHandler,
+ public chromeos::NetworkLibrary::NetworkManagerObserver,
+ public chromeos::NetworkLibrary::NetworkObserver,
+ public chromeos::NetworkLibrary::CellularDataPlanObserver {
+ public:
+ InternetOptionsHandler();
+ virtual ~InternetOptionsHandler();
+
+ // OptionsPage2UIHandler implementation.
+ virtual void GetLocalizedValues(
+ base::DictionaryValue* localized_strings) OVERRIDE;
+ virtual void Initialize() OVERRIDE;
+
+ // WebUIMessageHandler implementation.
+ virtual void RegisterMessages() OVERRIDE;
+
+ // NetworkLibrary::NetworkManagerObserver implementation.
+ virtual void OnNetworkManagerChanged(
+ chromeos::NetworkLibrary* network_lib) OVERRIDE;
+ // NetworkLibrary::NetworkObserver implementation.
+ virtual void OnNetworkChanged(chromeos::NetworkLibrary* network_lib,
+ const chromeos::Network* network) OVERRIDE;
+ // NetworkLibrary::CellularDataPlanObserver implementation.
+ virtual void OnCellularDataPlanChanged(
+ chromeos::NetworkLibrary* network_lib) OVERRIDE;
+
+ // content::NotificationObserver implementation.
+ virtual void Observe(int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) OVERRIDE;
+
+ private:
+ // Opens a modal popup dialog.
+ void CreateModalPopup(views::WidgetDelegate* view);
+ gfx::NativeWindow GetNativeWindow() const;
+
+ // Passes data needed to show details overlay for network.
+ // |args| will be [ network_type, service_path, command ]
+ // And command is one of 'options', 'connect', disconnect', 'activate' or
+ // 'forget'
+ // Handle{Wifi,Cellular}ButtonClick handles button click on a wireless
+ // network item and a cellular network item respectively.
+ void ButtonClickCallback(const base::ListValue* args);
+ void HandleWifiButtonClick(const std::string& service_path,
+ const std::string& command);
+ void HandleCellularButtonClick(const std::string& service_path,
+ const std::string& command);
+ void HandleVPNButtonClick(const std::string& service_path,
+ const std::string& command);
+
+ // Initiates cellular plan data refresh. The results from libcros will be
+ // passed through CellularDataPlanChanged() callback method.
+ // |args| will be [ service_path ]
+ void RefreshCellularPlanCallback(const base::ListValue* args);
+ void SetActivationButtonVisibility(
+ const chromeos::CellularNetwork* cellular,
+ base::DictionaryValue* dictionary);
+
+ void SetPreferNetworkCallback(const base::ListValue* args);
+ void SetAutoConnectCallback(const base::ListValue* args);
+ void SetSharedCallback(const base::ListValue* args);
+ void SetIPConfigCallback(const base::ListValue* args);
+ void EnableWifiCallback(const base::ListValue* args);
+ void DisableWifiCallback(const base::ListValue* args);
+ void EnableCellularCallback(const base::ListValue* args);
+ void DisableCellularCallback(const base::ListValue* args);
+ void BuyDataPlanCallback(const base::ListValue* args);
+ void SetApnCallback(const base::ListValue* args);
+ void SetSimCardLockCallback(const base::ListValue* args);
+ void ChangePinCallback(const base::ListValue* args);
+ void ShareNetworkCallback(const base::ListValue* args);
+
+ // Populates the ui with the details of the given device path. This forces
+ // an overlay to be displayed in the UI.
+ void PopulateDictionaryDetails(const chromeos::Network* network);
+ void PopulateWifiDetails(const chromeos::WifiNetwork* wifi,
+ base::DictionaryValue* dictionary);
+ void PopulateCellularDetails(const chromeos::CellularNetwork* cellular,
+ base::DictionaryValue* dictionary);
+ void PopulateVPNDetails(const chromeos::VirtualNetwork* vpn,
+ base::DictionaryValue* dictionary);
+
+ // Converts CellularDataPlan structure into dictionary for JS. Formats plan
+ // settings into human readable texts.
+ base::DictionaryValue* CellularDataPlanToDictionary(
+ const chromeos::CellularDataPlan* plan);
+
+ // Converts CellularApn stuct into dictionary for JS.
+ base::DictionaryValue* CreateDictionaryFromCellularApn(
+ const chromeos::CellularApn& apn);
+
+ // Creates the map of wired networks.
+ base::ListValue* GetWiredList();
+ // Creates the map of wireless networks.
+ base::ListValue* GetWirelessList();
+ // Creates the map of virtual networks.
+ base::ListValue* GetVPNList();
+ // Creates the map of remembered networks.
+ base::ListValue* GetRememberedList();
+ // Fills network information into JS dictionary for displaying network lists.
+ void FillNetworkInfo(base::DictionaryValue* dictionary);
+ // Refreshes the display of network information.
+ void RefreshNetworkData();
+ // Adds observers for wireless networks, if any, so that we can dynamically
+ // display the correct icon for that network's signal strength and, in the
+ // case of cellular networks, network technology and roaming status.
+ void MonitorNetworks();
+
+ // Stores a dictionary under |key| in |settings| that is suitable to be sent
+ // to the webui that contains the actual value of a setting and whether it's
+ // controlled by policy. Takes ownership of |value|.
+ void SetValueDictionary(DictionaryValue* settings,
+ const char* key,
+ base::Value* value,
+ const chromeos::NetworkPropertyUIData& ui_data);
+
+ // Convenience pointer to netwrok library (will not change).
+ chromeos::NetworkLibrary* cros_;
+
+ content::NotificationRegistrar registrar_;
+
+ DISALLOW_COPY_AND_ASSIGN(InternetOptionsHandler);
+};
+
+#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_INTERNET_OPTIONS_HANDLER_H_
diff --git a/chrome/browser/ui/webui/options2/chromeos/language_chewing_handler.cc b/chrome/browser/ui/webui/options2/chromeos/language_chewing_handler.cc
new file mode 100644
index 0000000..c3c3096
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/chromeos/language_chewing_handler.cc
@@ -0,0 +1,103 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/webui/options2/chromeos/language_chewing_handler.h"
+
+#include <limits>
+
+#include "base/logging.h"
+#include "base/string_number_conversions.h"
+#include "base/utf_string_conversions.h"
+#include "base/values.h"
+#include "chrome/browser/chromeos/language_preferences.h"
+#include "chrome/browser/ui/webui/options2/chromeos/language_options_util.h"
+#include "grit/generated_resources.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace {
+const char kI18nPrefix[] = "Chewing_";
+} // namespace
+
+namespace chromeos {
+
+LanguageChewingHandler::LanguageChewingHandler() {
+}
+
+LanguageChewingHandler::~LanguageChewingHandler() {
+}
+
+void LanguageChewingHandler::GetLocalizedValues(
+ DictionaryValue* localized_strings) {
+ DCHECK(localized_strings);
+
+ RegisterTitle(localized_strings, "languageChewingPage",
+ IDS_OPTIONS_SETTINGS_LANGUAGES_CHEWING_SETTINGS_TITLE);
+
+ for (size_t i = 0; i < language_prefs::kNumChewingBooleanPrefs; ++i) {
+ localized_strings->SetString(
+ GetI18nContentValue(language_prefs::kChewingBooleanPrefs[i],
+ kI18nPrefix),
+ l10n_util::GetStringUTF16(
+ language_prefs::kChewingBooleanPrefs[i].message_id));
+ }
+
+ // For maximum Chinese characters in pre-edit buffer, we use slider UI.
+ {
+ const language_prefs::LanguageIntegerRangePreference& preference =
+ language_prefs::kChewingIntegerPrefs[
+ language_prefs::kChewingMaxChiSymbolLenIndex];
+ localized_strings->SetString(
+ GetI18nContentValue(preference, kI18nPrefix),
+ l10n_util::GetStringUTF16(preference.message_id));
+ localized_strings->SetString(
+ GetTemplateDataMinName(preference, kI18nPrefix),
+ base::IntToString(preference.min_pref_value));
+ localized_strings->SetString(
+ GetTemplateDataMaxName(preference, kI18nPrefix),
+ base::IntToString(preference.max_pref_value));
+ }
+
+ // For number of candidates per page, we use select-option UI.
+ {
+ const language_prefs::LanguageIntegerRangePreference& preference =
+ language_prefs::kChewingIntegerPrefs[
+ language_prefs::kChewingCandPerPageIndex];
+ localized_strings->SetString(
+ GetI18nContentValue(preference, kI18nPrefix),
+ l10n_util::GetStringUTF16(preference.message_id));
+ ListValue* list_value = new ListValue();
+ for (int i = preference.min_pref_value; i <= preference.max_pref_value;
+ ++i) {
+ ListValue* option = new ListValue();
+ option->Append(CreateValue(i));
+ option->Append(CreateValue(i));
+ list_value->Append(option);
+ }
+ localized_strings->Set(GetTemplateDataPropertyName(preference, kI18nPrefix),
+ list_value);
+ }
+
+ for (size_t i = 0; i < language_prefs::kNumChewingMultipleChoicePrefs;
+ ++i) {
+ const language_prefs::LanguageMultipleChoicePreference<const char*>&
+ preference = language_prefs::kChewingMultipleChoicePrefs[i];
+ localized_strings->SetString(
+ GetI18nContentValue(preference, kI18nPrefix),
+ l10n_util::GetStringUTF16(preference.label_message_id));
+ localized_strings->Set(
+ GetTemplateDataPropertyName(preference, kI18nPrefix),
+ CreateMultipleChoiceList(preference));
+ }
+
+ localized_strings->SetString(
+ GetI18nContentValue(language_prefs::kChewingHsuSelKeyType, kI18nPrefix),
+ l10n_util::GetStringUTF16(
+ language_prefs::kChewingHsuSelKeyType.label_message_id));
+ localized_strings->Set(
+ GetTemplateDataPropertyName(language_prefs::kChewingHsuSelKeyType,
+ kI18nPrefix),
+ CreateMultipleChoiceList(language_prefs::kChewingHsuSelKeyType));
+}
+
+} // namespace chromeos
diff --git a/chrome/browser/ui/webui/options2/chromeos/language_chewing_handler.h b/chrome/browser/ui/webui/options2/chromeos/language_chewing_handler.h
new file mode 100644
index 0000000..5a37249
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/chromeos/language_chewing_handler.h
@@ -0,0 +1,35 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_LANGUAGE_CHEWING_HANDLER_H_
+#define CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_LANGUAGE_CHEWING_HANDLER_H_
+#pragma once
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "chrome/browser/ui/webui/options2/options_ui2.h"
+
+namespace base {
+class DictionaryValue;
+}
+
+namespace chromeos {
+
+// Chewing options page UI handler.
+class LanguageChewingHandler : public OptionsPage2UIHandler {
+ public:
+ LanguageChewingHandler();
+ virtual ~LanguageChewingHandler();
+
+ // OptionsPage2UIHandler implementation.
+ virtual void GetLocalizedValues(
+ base::DictionaryValue* localized_strings) OVERRIDE;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(LanguageChewingHandler);
+};
+
+} // namespace chromeos
+
+#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_LANGUAGE_CHEWING_HANDLER_H_
diff --git a/chrome/browser/ui/webui/options2/chromeos/language_customize_modifier_keys_handler.cc b/chrome/browser/ui/webui/options2/chromeos/language_customize_modifier_keys_handler.cc
new file mode 100644
index 0000000..d95b2ace
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/chromeos/language_customize_modifier_keys_handler.cc
@@ -0,0 +1,79 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/webui/options2/chromeos/language_customize_modifier_keys_handler.h"
+
+#include "base/values.h"
+#include "chrome/browser/chromeos/input_method/xkeyboard.h"
+#include "grit/generated_resources.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace {
+const struct ModifierKeysSelectItem {
+ int message_id;
+ chromeos::input_method::ModifierKey value;
+} kModifierKeysSelectItems[] = {
+ { IDS_OPTIONS_SETTINGS_LANGUAGES_XKB_KEY_SEARCH,
+ chromeos::input_method::kSearchKey },
+ { IDS_OPTIONS_SETTINGS_LANGUAGES_XKB_KEY_LEFT_CTRL,
+ chromeos::input_method::kLeftControlKey },
+ { IDS_OPTIONS_SETTINGS_LANGUAGES_XKB_KEY_LEFT_ALT,
+ chromeos::input_method::kLeftAltKey },
+ { IDS_OPTIONS_SETTINGS_LANGUAGES_XKB_KEY_VOID,
+ chromeos::input_method::kVoidKey },
+ { IDS_OPTIONS_SETTINGS_LANGUAGES_XKB_KEY_CAPS_LOCK,
+ chromeos::input_method::kCapsLockKey },
+};
+
+const char* kDataValuesNames[] = {
+ "xkbRemapSearchKeyToValue",
+ "xkbRemapControlKeyToValue",
+ "xkbRemapAltKeyToValue",
+};
+} // namespace
+
+namespace chromeos {
+
+LanguageCustomizeModifierKeysHandler::LanguageCustomizeModifierKeysHandler() {
+}
+
+LanguageCustomizeModifierKeysHandler::~LanguageCustomizeModifierKeysHandler() {
+}
+
+void LanguageCustomizeModifierKeysHandler::GetLocalizedValues(
+ DictionaryValue* localized_strings) {
+ DCHECK(localized_strings);
+
+ localized_strings->SetString("xkbRemapSearchKeyToContent",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_LANGUAGES_XKB_KEY_SEARCH_LABEL));
+ localized_strings->SetString("xkbRemapControlKeyToContent",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_LANGUAGES_XKB_KEY_LEFT_CTRL_LABEL));
+ localized_strings->SetString("xkbRemapAltKeyToContent",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_LANGUAGES_XKB_KEY_LEFT_ALT_LABEL));
+
+ for (size_t i = 0; i < arraysize(kDataValuesNames); ++i) {
+ ListValue* list_value = new ListValue();
+ for (size_t j = 0; j < arraysize(kModifierKeysSelectItems); ++j) {
+ const input_method::ModifierKey value =
+ kModifierKeysSelectItems[j].value;
+ const int message_id = kModifierKeysSelectItems[j].message_id;
+ // Only the seach key can be remapped to the caps lock key.
+ if (kDataValuesNames[i] != std::string("xkbRemapSearchKeyToValue") &&
+ value == input_method::kCapsLockKey) {
+ continue;
+ }
+ ListValue* option = new ListValue();
+ option->Append(Value::CreateIntegerValue(value));
+ option->Append(Value::CreateStringValue(l10n_util::GetStringUTF16(
+ message_id)));
+ list_value->Append(option);
+ }
+ localized_strings->Set(kDataValuesNames[i], list_value);
+ }
+}
+
+} // namespace chromeos
diff --git a/chrome/browser/ui/webui/options2/chromeos/language_customize_modifier_keys_handler.h b/chrome/browser/ui/webui/options2/chromeos/language_customize_modifier_keys_handler.h
new file mode 100644
index 0000000..67ea8bd
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/chromeos/language_customize_modifier_keys_handler.h
@@ -0,0 +1,30 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_LANGUAGE_CUSTOMIZE_MODIFIER_KEYS_HANDLER_H_ // NOLINT
+#define CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_LANGUAGE_CUSTOMIZE_MODIFIER_KEYS_HANDLER_H_ // NOLINT
+#pragma once
+
+#include "base/compiler_specific.h"
+#include "chrome/browser/prefs/pref_member.h"
+#include "chrome/browser/ui/webui/options2/options_ui2.h"
+
+namespace chromeos {
+
+// Customize modifier keys overlay page UI handler.
+class LanguageCustomizeModifierKeysHandler : public OptionsPage2UIHandler {
+ public:
+ LanguageCustomizeModifierKeysHandler();
+ virtual ~LanguageCustomizeModifierKeysHandler();
+
+ // OptionsPage2UIHandler implementation.
+ virtual void GetLocalizedValues(DictionaryValue* localized_strings) OVERRIDE;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(LanguageCustomizeModifierKeysHandler);
+};
+
+} // namespace chromeos
+
+#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_LANGUAGE_CUSTOMIZE_MODIFIER_KEYS_HANDLER_H_ // NOLINT
diff --git a/chrome/browser/ui/webui/options2/chromeos/language_hangul_handler.cc b/chrome/browser/ui/webui/options2/chromeos/language_hangul_handler.cc
new file mode 100644
index 0000000..5362ccf
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/chromeos/language_hangul_handler.cc
@@ -0,0 +1,47 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/webui/options2/chromeos/language_hangul_handler.h"
+
+#include "base/utf_string_conversions.h"
+#include "base/values.h"
+#include "chrome/browser/chromeos/language_preferences.h"
+#include "grit/generated_resources.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace chromeos {
+
+LanguageHangulHandler::LanguageHangulHandler() {
+}
+
+LanguageHangulHandler::~LanguageHangulHandler() {
+}
+
+void LanguageHangulHandler::GetLocalizedValues(
+ DictionaryValue* localized_strings) {
+ DCHECK(localized_strings);
+
+ RegisterTitle(localized_strings, "languageHangulPage",
+ IDS_OPTIONS_SETTINGS_LANGUAGES_HANGUL_SETTINGS_TITLE);
+
+ localized_strings->SetString("hangul_keyboard_layout",
+ l10n_util::GetStringUTF16(IDS_OPTIONS_SETTINGS_KEYBOARD_LAYOUT_TEXT));
+
+ localized_strings->Set("HangulkeyboardLayoutList", GetKeyboardLayoutList());
+}
+
+ListValue* LanguageHangulHandler::GetKeyboardLayoutList() {
+ ListValue* keyboard_layout_list = new ListValue();
+ for (size_t i = 0; i < language_prefs::kNumHangulKeyboardNameIDPairs; ++i) {
+ ListValue* option = new ListValue();
+ option->Append(Value::CreateStringValue(
+ language_prefs::kHangulKeyboardNameIDPairs[i].keyboard_id));
+ option->Append(Value::CreateStringValue(l10n_util::GetStringUTF16(
+ language_prefs::kHangulKeyboardNameIDPairs[i].message_id)));
+ keyboard_layout_list->Append(option);
+ }
+ return keyboard_layout_list;
+}
+
+} // namespace chromeos
diff --git a/chrome/browser/ui/webui/options2/chromeos/language_hangul_handler.h b/chrome/browser/ui/webui/options2/chromeos/language_hangul_handler.h
new file mode 100644
index 0000000..8b1c9de
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/chromeos/language_hangul_handler.h
@@ -0,0 +1,38 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_LANGUAGE_HANGUL_HANDLER_H_
+#define CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_LANGUAGE_HANGUL_HANDLER_H_
+#pragma once
+
+#include "base/compiler_specific.h"
+#include "chrome/browser/ui/webui/options2/options_ui2.h"
+
+namespace base {
+class DictionaryValue;
+class ListValue;
+}
+
+namespace chromeos {
+
+// Hangul options page UI handler.
+class LanguageHangulHandler : public OptionsPage2UIHandler {
+ public:
+ LanguageHangulHandler();
+ virtual ~LanguageHangulHandler();
+
+ // OptionsPage2UIHandler implementation.
+ virtual void GetLocalizedValues(
+ base::DictionaryValue* localized_strings) OVERRIDE;
+
+ private:
+ // Returns the list of hangul keyboards.
+ static base::ListValue* GetKeyboardLayoutList();
+
+ DISALLOW_COPY_AND_ASSIGN(LanguageHangulHandler);
+};
+
+} // namespace chromeos
+
+#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_LANGUAGE_HANGUL_HANDLER_H_
diff --git a/chrome/browser/ui/webui/options2/chromeos/language_mozc_handler.cc b/chrome/browser/ui/webui/options2/chromeos/language_mozc_handler.cc
new file mode 100644
index 0000000..ce8afe9
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/chromeos/language_mozc_handler.cc
@@ -0,0 +1,70 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/webui/options2/chromeos/language_mozc_handler.h"
+
+#include "base/string_number_conversions.h"
+#include "base/utf_string_conversions.h"
+#include "base/values.h"
+#include "chrome/browser/chromeos/language_preferences.h"
+#include "chrome/browser/ui/webui/options2/chromeos/language_options_util.h"
+#include "grit/generated_resources.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace {
+const char kI18nPrefix[] = "mozc_";
+} // namespace
+
+namespace chromeos {
+
+LanguageMozcHandler::LanguageMozcHandler() {
+}
+
+LanguageMozcHandler::~LanguageMozcHandler() {
+}
+
+void LanguageMozcHandler::GetLocalizedValues(
+ DictionaryValue* localized_strings) {
+ DCHECK(localized_strings);
+
+ RegisterTitle(localized_strings, "languageMozcPage",
+ IDS_OPTIONS_SETTINGS_LANGUAGES_MOZC_SETTINGS_TITLE);
+
+ for (size_t i = 0; i < language_prefs::kNumMozcBooleanPrefs; ++i) {
+ localized_strings->SetString(
+ GetI18nContentValue(language_prefs::kMozcBooleanPrefs[i], kI18nPrefix),
+ l10n_util::GetStringUTF16(
+ language_prefs::kMozcBooleanPrefs[i].message_id));
+ }
+
+ for (size_t i = 0; i < language_prefs::kNumMozcMultipleChoicePrefs; ++i) {
+ const language_prefs::LanguageMultipleChoicePreference<const char*>&
+ preference = language_prefs::kMozcMultipleChoicePrefs[i];
+ localized_strings->SetString(
+ GetI18nContentValue(preference, kI18nPrefix),
+ l10n_util::GetStringUTF16(preference.label_message_id));
+ localized_strings->Set(GetTemplateDataPropertyName(preference, kI18nPrefix),
+ CreateMultipleChoiceList(preference));
+ }
+
+ for (size_t i = 0; i < language_prefs::kNumMozcIntegerPrefs; ++i) {
+ const language_prefs::LanguageIntegerRangePreference& preference =
+ language_prefs::kMozcIntegerPrefs[i];
+ localized_strings->SetString(
+ GetI18nContentValue(preference, kI18nPrefix),
+ l10n_util::GetStringUTF16(preference.message_id));
+ ListValue* list_value = new ListValue();
+ for (int j = preference.min_pref_value; j <= preference.max_pref_value;
+ ++j) {
+ ListValue* option = new ListValue();
+ option->Append(CreateValue(j));
+ option->Append(CreateValue(j));
+ list_value->Append(option);
+ }
+ localized_strings->Set(GetTemplateDataPropertyName(preference, kI18nPrefix),
+ list_value);
+ }
+}
+
+} // namespace chromeos
diff --git a/chrome/browser/ui/webui/options2/chromeos/language_mozc_handler.h b/chrome/browser/ui/webui/options2/chromeos/language_mozc_handler.h
new file mode 100644
index 0000000..df1410e
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/chromeos/language_mozc_handler.h
@@ -0,0 +1,34 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_LANGUAGE_MOZC_HANDLER_H_
+#define CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_LANGUAGE_MOZC_HANDLER_H_
+#pragma once
+
+#include "base/compiler_specific.h"
+#include "chrome/browser/ui/webui/options2/options_ui2.h"
+
+namespace base {
+class DictionaryValue;
+}
+
+namespace chromeos {
+
+// Mozc options page UI handler.
+class LanguageMozcHandler : public OptionsPage2UIHandler {
+ public:
+ LanguageMozcHandler();
+ virtual ~LanguageMozcHandler();
+
+ // OptionsPage2UIHandler implementation.
+ virtual void GetLocalizedValues(
+ base::DictionaryValue* localized_strings) OVERRIDE;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(LanguageMozcHandler);
+};
+
+} // namespace chromeos
+
+#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_LANGUAGE_MOZC_HANDLER_H_
diff --git a/chrome/browser/ui/webui/options2/chromeos/language_options_util.cc b/chrome/browser/ui/webui/options2/chromeos/language_options_util.cc
new file mode 100644
index 0000000..6108070
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/chromeos/language_options_util.cc
@@ -0,0 +1,18 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/webui/options2/chromeos/language_options_util.h"
+
+namespace chromeos {
+
+// See comments in .h.
+Value* CreateValue(const char* in_value) {
+ return Value::CreateStringValue(in_value);
+}
+
+Value* CreateValue(int in_value) {
+ return Value::CreateIntegerValue(in_value);
+}
+
+} // namespace chromeos
diff --git a/chrome/browser/ui/webui/options2/chromeos/language_options_util.h b/chrome/browser/ui/webui/options2/chromeos/language_options_util.h
new file mode 100644
index 0000000..70f7307
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/chromeos/language_options_util.h
@@ -0,0 +1,83 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_LANGUAGE_OPTIONS_UTIL_H_
+#define CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_LANGUAGE_OPTIONS_UTIL_H_
+#pragma once
+
+#include <string>
+
+#include "base/utf_string_conversions.h"
+#include "base/values.h"
+#include "chrome/browser/chromeos/language_preferences.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace base {
+class ListValue;
+}
+
+namespace chromeos {
+
+// Returns an i18n-content value corresponding to |preference|.
+template <typename T>
+std::string GetI18nContentValue(const T& preference, const char* prefix) {
+ return std::string(prefix) + preference.ibus_config_name;
+}
+
+// Returns a property name of templateData corresponding to |preference|.
+template <typename T>
+std::string GetTemplateDataPropertyName(const T& preference,
+ const char* prefix) {
+ return std::string(prefix) + preference.ibus_config_name + "Value";
+}
+
+// Returns an property name of templateData corresponding the value of the min
+// attribute.
+template <typename T>
+std::string GetTemplateDataMinName(const T& preference, const char* prefix) {
+ return std::string(prefix) + preference.ibus_config_name + "Min";
+}
+
+// Returns an property name of templateData corresponding the value of the max
+// attribute.
+template <typename T>
+std::string GetTemplateDataMaxName(const T& preference, const char* prefix) {
+ return std::string(prefix) + preference.ibus_config_name + "Max";
+}
+
+// Creates a Value object from the given value. Here we use function
+// overloading to handle string and integer preferences in
+// CreateMultipleChoiceList.
+Value* CreateValue(const char* in_value);
+Value* CreateValue(int in_value);
+
+// Creates a multiple choice list from the given preference.
+template <typename T>
+base::ListValue* CreateMultipleChoiceList(
+ const language_prefs::LanguageMultipleChoicePreference<T>& preference) {
+ int list_length = 0;
+ for (size_t i = 0;
+ i < language_prefs::LanguageMultipleChoicePreference<T>::kMaxItems;
+ ++i) {
+ if (preference.values_and_ids[i].item_message_id == 0)
+ break;
+ ++list_length;
+ }
+ DCHECK_GT(list_length, 0);
+
+ base::ListValue* list_value = new base::ListValue();
+ for (int i = 0; i < list_length; ++i) {
+ base::ListValue* option = new base::ListValue();
+ option->Append(CreateValue(
+ preference.values_and_ids[i].ibus_config_value));
+ option->Append(base::Value::CreateStringValue(l10n_util::GetStringUTF16(
+ preference.values_and_ids[i].item_message_id)));
+ list_value->Append(option);
+ }
+ return list_value;
+}
+
+} // namespace chromeos
+
+#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_LANGUAGE_OPTIONS_UTIL_H_
diff --git a/chrome/browser/ui/webui/options2/chromeos/language_pinyin_handler.cc b/chrome/browser/ui/webui/options2/chromeos/language_pinyin_handler.cc
new file mode 100644
index 0000000..8e2c19c
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/chromeos/language_pinyin_handler.cc
@@ -0,0 +1,67 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/webui/options2/chromeos/language_pinyin_handler.h"
+
+#include "base/values.h"
+#include "chrome/browser/chromeos/language_preferences.h"
+#include "chrome/browser/ui/webui/options2/chromeos/language_options_util.h"
+#include "grit/generated_resources.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace {
+const char kI18nPrefix[] = "Pinyin";
+} // namespace
+
+namespace chromeos {
+
+LanguagePinyinHandler::LanguagePinyinHandler() {
+}
+
+LanguagePinyinHandler::~LanguagePinyinHandler() {
+}
+
+void LanguagePinyinHandler::GetLocalizedValues(
+ DictionaryValue* localized_strings) {
+ DCHECK(localized_strings);
+
+ RegisterTitle(localized_strings, "languagePinyinPage",
+ IDS_OPTIONS_SETTINGS_LANGUAGES_PINYIN_SETTINGS_TITLE);
+
+ for (size_t i = 0; i < language_prefs::kNumPinyinBooleanPrefs; ++i) {
+ localized_strings->SetString(
+ GetI18nContentValue(language_prefs::kPinyinBooleanPrefs[i],
+ kI18nPrefix),
+ l10n_util::GetStringUTF16(
+ language_prefs::kPinyinBooleanPrefs[i].message_id));
+ }
+
+ localized_strings->SetString(
+ GetI18nContentValue(language_prefs::kPinyinDoublePinyinSchema,
+ kI18nPrefix),
+ l10n_util::GetStringUTF16(
+ language_prefs::kPinyinDoublePinyinSchema.label_message_id));
+ ListValue* list_value = new ListValue();
+ for (size_t i = 0;
+ i < language_prefs::LanguageMultipleChoicePreference<int>::kMaxItems;
+ ++i) {
+ if (language_prefs::kPinyinDoublePinyinSchema.values_and_ids[i].
+ item_message_id == 0)
+ break;
+ ListValue* option = new ListValue();
+ option->Append(Value::CreateIntegerValue(
+ language_prefs::kPinyinDoublePinyinSchema.values_and_ids[i].
+ ibus_config_value));
+ option->Append(Value::CreateStringValue(l10n_util::GetStringUTF16(
+ language_prefs::kPinyinDoublePinyinSchema.values_and_ids[i].
+ item_message_id)));
+ list_value->Append(option);
+ }
+ localized_strings->Set(
+ GetTemplateDataPropertyName(language_prefs::kPinyinDoublePinyinSchema,
+ kI18nPrefix),
+ list_value);
+}
+
+} // namespace chromeos
diff --git a/chrome/browser/ui/webui/options2/chromeos/language_pinyin_handler.h b/chrome/browser/ui/webui/options2/chromeos/language_pinyin_handler.h
new file mode 100644
index 0000000..7e0d922
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/chromeos/language_pinyin_handler.h
@@ -0,0 +1,34 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_LANGUAGE_PINYIN_HANDLER_H_
+#define CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_LANGUAGE_PINYIN_HANDLER_H_
+#pragma once
+
+#include "base/compiler_specific.h"
+#include "chrome/browser/ui/webui/options2/options_ui2.h"
+
+namespace base {
+class DictionaryValue;
+}
+
+namespace chromeos {
+
+// Pinyin options page UI handler.
+class LanguagePinyinHandler : public OptionsPage2UIHandler {
+ public:
+ LanguagePinyinHandler();
+ virtual ~LanguagePinyinHandler();
+
+ // OptionsPage2UIHandler implementation.
+ virtual void GetLocalizedValues(
+ base::DictionaryValue* localized_strings) OVERRIDE;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(LanguagePinyinHandler);
+};
+
+} // namespace chromeos
+
+#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_LANGUAGE_PINYIN_HANDLER_H_
diff --git a/chrome/browser/ui/webui/options2/chromeos/proxy_handler.cc b/chrome/browser/ui/webui/options2/chromeos/proxy_handler.cc
new file mode 100644
index 0000000..773fe51
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/chromeos/proxy_handler.cc
@@ -0,0 +1,86 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/webui/options2/chromeos/proxy_handler.h"
+
+#include "base/basictypes.h"
+#include "base/callback.h"
+#include "base/stl_util.h"
+#include "base/time.h"
+#include "base/utf_string_conversions.h"
+#include "base/values.h"
+#include "grit/chromium_strings.h"
+#include "grit/generated_resources.h"
+#include "grit/locale_settings.h"
+#include "grit/theme_resources.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/resource/resource_bundle.h"
+
+namespace chromeos {
+
+ProxyHandler::ProxyHandler() {
+}
+
+ProxyHandler::~ProxyHandler() {
+}
+
+void ProxyHandler::GetLocalizedValues(
+ DictionaryValue* localized_strings) {
+ DCHECK(localized_strings);
+ // Proxy page - ChromeOS
+ localized_strings->SetString("proxyPage",
+ l10n_util::GetStringUTF16(IDS_OPTIONS_PROXY_TAB_LABEL));
+ localized_strings->SetString("proxyPageTitleFormat",
+ l10n_util::GetStringUTF16(IDS_PROXY_PAGE_TITLE_FORMAT));
+ localized_strings->SetString("proxy_config_title",
+ l10n_util::GetStringUTF16(IDS_PROXY_CONFIG_TITLE));
+ localized_strings->SetString("proxyDirectInternetConnection",
+ l10n_util::GetStringUTF16(IDS_PROXY_DIRECT_CONNECTION));
+
+ localized_strings->SetString("proxyManual",
+ l10n_util::GetStringUTF16(IDS_PROXY_MANUAL_CONFIG));
+ localized_strings->SetString("sameProxyProtocols",
+ l10n_util::GetStringUTF16(IDS_PROXY_SAME_FORALL));
+
+ localized_strings->SetString("httpProxy",
+ l10n_util::GetStringUTF16(IDS_PROXY_HTTP_PROXY));
+ localized_strings->SetString("secureHttpProxy",
+ l10n_util::GetStringUTF16(IDS_PROXY_HTTP_SECURE_HTTP_PROXY));
+ localized_strings->SetString("ftpProxy",
+ l10n_util::GetStringUTF16(IDS_PROXY_FTP_PROXY));
+ localized_strings->SetString("socksHost",
+ l10n_util::GetStringUTF16(IDS_PROXY_SOCKS_HOST));
+ localized_strings->SetString("proxyAutomatic",
+ l10n_util::GetStringUTF16(IDS_PROXY_AUTOMATIC));
+ localized_strings->SetString("proxyConfigUrl",
+ l10n_util::GetStringUTF16(IDS_PROXY_CONFIG_URL));
+ localized_strings->SetString("advanced_proxy_config",
+ l10n_util::GetStringUTF16(IDS_PROXY_ADVANCED_CONFIG));
+ localized_strings->SetString("addHost",
+ l10n_util::GetStringUTF16(IDS_PROXY_ADD_HOST));
+ localized_strings->SetString("removeHost",
+ l10n_util::GetStringUTF16(IDS_PROXY_REMOVE_HOST));
+ localized_strings->SetString("proxyPort",
+ l10n_util::GetStringUTF16(IDS_PROXY_PORT));
+ localized_strings->SetString("proxyBypass",
+ l10n_util::GetStringUTF16(IDS_PROXY_BYPASS));
+ localized_strings->SetString("policyManagedPrefsBannerText",
+ l10n_util::GetStringUTF16(IDS_OPTIONS_POLICY_MANAGED_PREFS));
+ localized_strings->SetString("extensionManagedPrefsBannerText",
+ l10n_util::GetStringUTF16(IDS_OPTIONS_EXTENSION_MANAGED_PREFS));
+ localized_strings->SetString("unmodifiablePrefsBannerText",
+ l10n_util::GetStringUTF16(IDS_OPTIONS_UNMODIFIABLE_PREFS));
+ localized_strings->SetString("enableSharedProxiesBannerText",
+ l10n_util::GetStringFUTF16(
+ IDS_OPTIONS_SETTINGS_INTERNET_OPTIONS_ENABLE_SHARED_PROXIES_HINT,
+ l10n_util::GetStringUTF16(IDS_OPTIONS_SETTINGS_USE_SHARED_PROXIES)));
+}
+
+void ProxyHandler::SetNetworkName(const std::string& name) {
+ StringValue network(name);
+ web_ui_->CallJavascriptFunction("options.ProxyOptions.setNetworkName",
+ network);
+}
+
+} // namespace chromeos
diff --git a/chrome/browser/ui/webui/options2/chromeos/proxy_handler.h b/chrome/browser/ui/webui/options2/chromeos/proxy_handler.h
new file mode 100644
index 0000000..d170786
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/chromeos/proxy_handler.h
@@ -0,0 +1,33 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_PROXY_HANDLER_H_
+#define CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_PROXY_HANDLER_H_
+
+#include "base/compiler_specific.h"
+#include "chrome/browser/ui/webui/options2/options_ui2.h"
+
+namespace chromeos {
+
+// ChromeOS proxy options page UI handler.
+class ProxyHandler : public OptionsPage2UIHandler {
+ public:
+ explicit ProxyHandler();
+ virtual ~ProxyHandler();
+
+ // OptionsPage2UIHandler implementation.
+ virtual void GetLocalizedValues(
+ base::DictionaryValue* localized_strings) OVERRIDE;
+
+ // Set network name for proxy page title.
+ void SetNetworkName(const std::string& name);
+
+ private:
+
+ DISALLOW_COPY_AND_ASSIGN(ProxyHandler);
+};
+
+} // namespace chromeos
+
+#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_PROXY_HANDLER_H_
diff --git a/chrome/browser/ui/webui/options2/chromeos/stats_options_handler.cc b/chrome/browser/ui/webui/options2/chromeos/stats_options_handler.cc
new file mode 100644
index 0000000..4fc40d9
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/chromeos/stats_options_handler.cc
@@ -0,0 +1,45 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/webui/options2/chromeos/stats_options_handler.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/utf_string_conversions.h"
+#include "base/values.h"
+#include "content/public/browser/user_metrics.h"
+
+namespace chromeos {
+
+StatsOptionsHandler::StatsOptionsHandler() {
+}
+
+// OptionsPageUIHandler implementation.
+void StatsOptionsHandler::GetLocalizedValues(
+ DictionaryValue* localized_strings) {
+}
+
+void StatsOptionsHandler::Initialize() {
+}
+
+// WebUIMessageHandler implementation.
+void StatsOptionsHandler::RegisterMessages() {
+ web_ui_->RegisterMessageCallback("metricsReportingCheckboxAction",
+ base::Bind(&StatsOptionsHandler::HandleMetricsReportingCheckbox,
+ base::Unretained(this)));
+}
+
+void StatsOptionsHandler::HandleMetricsReportingCheckbox(
+ const ListValue* args) {
+#if defined(GOOGLE_CHROME_BUILD)
+ const std::string checked_str = UTF16ToUTF8(ExtractStringValue(args));
+ const bool enabled = (checked_str == "true");
+ UserMetrics::RecordAction(
+ enabled ?
+ UserMetricsAction("Options_MetricsReportingCheckbox_Enable") :
+ UserMetricsAction("Options_MetricsReportingCheckbox_Disable"));
+#endif
+}
+
+} // namespace chromeos
diff --git a/chrome/browser/ui/webui/options2/chromeos/stats_options_handler.h b/chrome/browser/ui/webui/options2/chromeos/stats_options_handler.h
new file mode 100644
index 0000000..2579cb30
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/chromeos/stats_options_handler.h
@@ -0,0 +1,38 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_STATS_OPTIONS_HANDLER_H_
+#define CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_STATS_OPTIONS_HANDLER_H_
+#pragma once
+
+#include "base/compiler_specific.h"
+#include "chrome/browser/ui/webui/options2/options_ui2.h"
+
+namespace chromeos {
+
+// ChromeOS handler for "Stats/crash reporting to Google" option of the Advanced
+// settings page. This handler does only ChromeOS-specific actions while default
+// code is in Chrome's AdvancedOptionsHandler
+// (chrome/browser/webui/advanced_options_handler.cc).
+class StatsOptionsHandler : public OptionsPage2UIHandler {
+ public:
+ StatsOptionsHandler();
+
+ // OptionsPage2UIHandler implementation.
+ virtual void GetLocalizedValues(
+ base::DictionaryValue* localized_strings) OVERRIDE;
+ virtual void Initialize() OVERRIDE;
+
+ // WebUIMessageHandler implementation.
+ virtual void RegisterMessages() OVERRIDE;
+
+ private:
+ void HandleMetricsReportingCheckbox(const base::ListValue* args);
+
+ DISALLOW_COPY_AND_ASSIGN(StatsOptionsHandler);
+};
+
+} // namespace chromeos
+
+#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_STATS_OPTIONS_HANDLER_H_
diff --git a/chrome/browser/ui/webui/options2/chromeos/system_options_handler.cc b/chrome/browser/ui/webui/options2/chromeos/system_options_handler.cc
new file mode 100644
index 0000000..62261ee
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/chromeos/system_options_handler.cc
@@ -0,0 +1,170 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/webui/options2/chromeos/system_options_handler.h"
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/command_line.h"
+#include "base/json/json_value_serializer.h"
+#include "base/string_number_conversions.h"
+#include "base/utf_string_conversions.h"
+#include "base/values.h"
+#include "content/browser/tab_contents/tab_contents.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/chromeos/accessibility/accessibility_util.h"
+#include "chrome/browser/chromeos/cros_settings.h"
+#include "chrome/browser/chromeos/dbus/dbus_thread_manager.h"
+#include "chrome/browser/chromeos/dbus/power_manager_client.h"
+#include "chrome/browser/chromeos/language_preferences.h"
+#include "chrome/browser/chromeos/system/touchpad_settings.h"
+#include "chrome/browser/extensions/extension_service.h"
+#include "chrome/browser/prefs/pref_service.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/webui/options2/chromeos/system_settings_provider.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/common/pref_names.h"
+#include "chrome/common/extensions/extension.h"
+#include "grit/browser_resources.h"
+#include "grit/chromium_strings.h"
+#include "grit/generated_resources.h"
+#include "grit/locale_settings.h"
+#include "grit/theme_resources.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/resource/resource_bundle.h"
+
+using content::BrowserThread;
+
+namespace {
+
+void TouchpadExistsFileThread(bool* exists) {
+ *exists = chromeos::system::touchpad_settings::TouchpadExists();
+}
+
+}
+
+SystemOptionsHandler::SystemOptionsHandler() {
+}
+
+SystemOptionsHandler::~SystemOptionsHandler() {
+}
+
+void SystemOptionsHandler::GetLocalizedValues(
+ DictionaryValue* localized_strings) {
+ DCHECK(localized_strings);
+
+ RegisterTitle(localized_strings, "systemPage", IDS_OPTIONS_SYSTEM_TAB_LABEL);
+ localized_strings->SetString("datetimeTitle",
+ l10n_util::GetStringUTF16(IDS_OPTIONS_SETTINGS_SECTION_TITLE_DATETIME));
+ localized_strings->SetString("timezone",
+ l10n_util::GetStringUTF16(IDS_OPTIONS_SETTINGS_TIMEZONE_DESCRIPTION));
+ localized_strings->SetString("use24HourClock",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_USE_24HOUR_CLOCK_DESCRIPTION));
+
+ localized_strings->SetString("screen",
+ l10n_util::GetStringUTF16(IDS_OPTIONS_SETTINGS_SECTION_TITLE_SCREEN));
+ localized_strings->SetString("brightness",
+ l10n_util::GetStringUTF16(IDS_OPTIONS_SETTINGS_BRIGHTNESS_DESCRIPTION));
+ localized_strings->SetString("brightnessDecrease",
+ l10n_util::GetStringUTF16(IDS_OPTIONS_SETTINGS_BRIGHTNESS_DECREASE));
+ localized_strings->SetString("brightnessIncrease",
+ l10n_util::GetStringUTF16(IDS_OPTIONS_SETTINGS_BRIGHTNESS_INCREASE));
+
+ localized_strings->SetString("touchpad",
+ l10n_util::GetStringUTF16(IDS_OPTIONS_SETTINGS_SECTION_TITLE_TOUCHPAD));
+ localized_strings->SetString("enableTapToClick",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_TAP_TO_CLICK_ENABLED_DESCRIPTION));
+ localized_strings->SetString("sensitivity",
+ l10n_util::GetStringUTF16(IDS_OPTIONS_SETTINGS_SENSITIVITY_DESCRIPTION));
+ localized_strings->SetString("sensitivityLess",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_SENSITIVITY_LESS_DESCRIPTION));
+ localized_strings->SetString("sensitivityMore",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_SENSITIVITY_MORE_DESCRIPTION));
+
+ localized_strings->SetString("language",
+ l10n_util::GetStringUTF16(IDS_OPTIONS_SETTINGS_SECTION_TITLE_LANGUAGE));
+ localized_strings->SetString("languageCustomize",
+ l10n_util::GetStringUTF16(IDS_OPTIONS_SETTINGS_LANGUAGES_CUSTOMIZE));
+ localized_strings->SetString("modifierKeysCustomize",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_LANGUAGES_MODIFIER_KEYS_CUSTOMIZE));
+
+ localized_strings->SetString("accessibilityTitle",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_SECTION_TITLE_ACCESSIBILITY));
+ localized_strings->SetString("accessibility",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_ACCESSIBILITY_DESCRIPTION));
+
+ // TODO(pastarmovj): replace this with a call to the CrosSettings list
+ // handling functionality to come.
+ localized_strings->Set("timezoneList",
+ static_cast<chromeos::SystemSettingsProvider*>(
+ chromeos::CrosSettings::Get()->GetProvider(
+ chromeos::kSystemTimezone))->GetTimezoneList());
+}
+
+void SystemOptionsHandler::Initialize() {
+ DCHECK(web_ui_);
+ PrefService* pref_service = g_browser_process->local_state();
+ bool acc_enabled = pref_service->GetBoolean(prefs::kSpokenFeedbackEnabled);
+ base::FundamentalValue checked(acc_enabled);
+ web_ui_->CallJavascriptFunction(
+ "options.SystemOptions.SetAccessibilityCheckboxState", checked);
+
+ bool* exists = new bool;
+ BrowserThread::PostTaskAndReply(BrowserThread::FILE, FROM_HERE,
+ base::Bind(&TouchpadExistsFileThread, exists),
+ base::Bind(&SystemOptionsHandler::TouchpadExists, AsWeakPtr(), exists));
+}
+
+void SystemOptionsHandler::TouchpadExists(bool* exists) {
+ if (*exists)
+ web_ui_->CallJavascriptFunction(
+ "options.SystemOptions.showTouchpadControls");
+ delete exists;
+}
+
+void SystemOptionsHandler::RegisterMessages() {
+ DCHECK(web_ui_);
+ web_ui_->RegisterMessageCallback("accessibilityChange",
+ base::Bind(&SystemOptionsHandler::AccessibilityChangeCallback,
+ base::Unretained(this)));
+
+ web_ui_->RegisterMessageCallback("decreaseScreenBrightness",
+ base::Bind(&SystemOptionsHandler::DecreaseScreenBrightnessCallback,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("increaseScreenBrightness",
+ base::Bind(&SystemOptionsHandler::IncreaseScreenBrightnessCallback,
+ base::Unretained(this)));
+}
+
+void SystemOptionsHandler::AccessibilityChangeCallback(const ListValue* args) {
+ std::string checked_str;
+ args->GetString(0, &checked_str);
+ bool accessibility_enabled = (checked_str == "true");
+
+ chromeos::accessibility::EnableAccessibility(accessibility_enabled, NULL);
+}
+
+void SystemOptionsHandler::DecreaseScreenBrightnessCallback(
+ const ListValue* args) {
+ // Do not allow the options button to turn off the backlight, as that
+ // can make it very difficult to see the increase brightness button.
+ chromeos::DBusThreadManager::Get()->GetPowerManagerClient()->
+ DecreaseScreenBrightness(false);
+}
+
+void SystemOptionsHandler::IncreaseScreenBrightnessCallback(
+ const ListValue* args) {
+ chromeos::DBusThreadManager::Get()->GetPowerManagerClient()->
+ IncreaseScreenBrightness();
+}
diff --git a/chrome/browser/ui/webui/options2/chromeos/system_options_handler.h b/chrome/browser/ui/webui/options2/chromeos/system_options_handler.h
new file mode 100644
index 0000000..e3e333b
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/chromeos/system_options_handler.h
@@ -0,0 +1,50 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_SYSTEM_OPTIONS_HANDLER_H_
+#define CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_SYSTEM_OPTIONS_HANDLER_H_
+#pragma once
+
+#include "base/memory/weak_ptr.h"
+#include "base/compiler_specific.h"
+#include "chrome/browser/ui/webui/options2/options_ui2.h"
+
+namespace base {
+class DictionaryValue;
+}
+
+// ChromeOS system options page UI handler.
+class SystemOptionsHandler
+ : public OptionsPage2UIHandler,
+ public base::SupportsWeakPtr<SystemOptionsHandler> {
+ public:
+ SystemOptionsHandler();
+ virtual ~SystemOptionsHandler();
+
+ // OptionsPage2UIHandler implementation.
+ virtual void GetLocalizedValues(
+ base::DictionaryValue* localized_strings) OVERRIDE;
+ virtual void Initialize() OVERRIDE;
+
+ virtual void RegisterMessages() OVERRIDE;
+
+ // Called when the accessibility checkbox value is changed.
+ // |args| will contain the checkbox checked state as a string
+ // ("true" or "false").
+ void AccessibilityChangeCallback(const base::ListValue* args);
+
+ // Called when the System configuration screen is used to adjust
+ // the screen brightness.
+ // |args| will be an empty list.
+ void DecreaseScreenBrightnessCallback(const base::ListValue* args);
+ void IncreaseScreenBrightnessCallback(const base::ListValue* args);
+
+ private:
+ // Callback for TouchpadHelper.
+ void TouchpadExists(bool* exists);
+
+ DISALLOW_COPY_AND_ASSIGN(SystemOptionsHandler);
+};
+
+#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_SYSTEM_OPTIONS_HANDLER_H_
diff --git a/chrome/browser/ui/webui/options2/chromeos/system_settings_provider.cc b/chrome/browser/ui/webui/options2/chromeos/system_settings_provider.cc
new file mode 100644
index 0000000..c951164
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/chromeos/system_settings_provider.cc
@@ -0,0 +1,333 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/webui/options2/chromeos/system_settings_provider.h"
+
+#include <string>
+
+#include "base/i18n/rtl.h"
+#include "base/lazy_instance.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/stl_util.h"
+#include "base/string_util.h"
+#include "base/stringprintf.h"
+#include "base/synchronization/lock.h"
+#include "base/time.h"
+#include "base/utf_string_conversions.h"
+#include "base/values.h"
+#include "chrome/browser/chromeos/cros/cros_library.h"
+#include "chrome/browser/chromeos/cros_settings.h"
+#include "chrome/browser/chromeos/cros_settings_names.h"
+#include "chrome/browser/chromeos/login/user_manager.h"
+#include "grit/generated_resources.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "unicode/calendar.h"
+#include "unicode/timezone.h"
+#include "unicode/ures.h"
+
+namespace {
+
+// TODO(jungshik): Using Enumerate method in ICU gives 600+ timezones.
+// Even after filtering out duplicate entries with a strict identity check,
+// we still have 400+ zones. Relaxing the criteria for the timezone
+// identity is likely to cut down the number to < 100. Until we
+// come up with a better list, we hard-code the following list as used by
+// Android.
+static const char* kTimeZones[] = {
+ "Pacific/Majuro",
+ "Pacific/Midway",
+ "Pacific/Honolulu",
+ "America/Anchorage",
+ "America/Los_Angeles",
+ "America/Tijuana",
+ "America/Denver",
+ "America/Phoenix",
+ "America/Chihuahua",
+ "America/Chicago",
+ "America/Mexico_City",
+ "America/Costa_Rica",
+ "America/Regina",
+ "America/New_York",
+ "America/Bogota",
+ "America/Caracas",
+ "America/Barbados",
+ "America/Manaus",
+ "America/Santiago",
+ "America/St_Johns",
+ "America/Sao_Paulo",
+ "America/Araguaina",
+ "America/Argentina/Buenos_Aires",
+ "America/Godthab",
+ "America/Montevideo",
+ "Atlantic/South_Georgia",
+ "Atlantic/Azores",
+ "Atlantic/Cape_Verde",
+ "Africa/Casablanca",
+ "Europe/London",
+ "Europe/Amsterdam",
+ "Europe/Belgrade",
+ "Europe/Brussels",
+ "Europe/Sarajevo",
+ "Africa/Windhoek",
+ "Africa/Brazzaville",
+ "Asia/Amman",
+ "Europe/Athens",
+ "Asia/Beirut",
+ "Africa/Cairo",
+ "Europe/Helsinki",
+ "Asia/Jerusalem",
+ "Europe/Minsk",
+ "Africa/Harare",
+ "Asia/Baghdad",
+ "Europe/Moscow",
+ "Asia/Kuwait",
+ "Africa/Nairobi",
+ "Asia/Tehran",
+ "Asia/Baku",
+ "Asia/Tbilisi",
+ "Asia/Yerevan",
+ "Asia/Dubai",
+ "Asia/Kabul",
+ "Asia/Karachi",
+ "Asia/Oral",
+ "Asia/Yekaterinburg",
+ "Asia/Calcutta",
+ "Asia/Colombo",
+ "Asia/Katmandu",
+ "Asia/Almaty",
+ "Asia/Rangoon",
+ "Asia/Krasnoyarsk",
+ "Asia/Bangkok",
+ "Asia/Shanghai",
+ "Asia/Hong_Kong",
+ "Asia/Irkutsk",
+ "Asia/Kuala_Lumpur",
+ "Australia/Perth",
+ "Asia/Taipei",
+ "Asia/Seoul",
+ "Asia/Tokyo",
+ "Asia/Yakutsk",
+ "Australia/Adelaide",
+ "Australia/Darwin",
+ "Australia/Brisbane",
+ "Australia/Hobart",
+ "Australia/Sydney",
+ "Asia/Vladivostok",
+ "Pacific/Guam",
+ "Asia/Magadan",
+ "Pacific/Auckland",
+ "Pacific/Fiji",
+ "Pacific/Tongatapu",
+};
+
+static base::LazyInstance<base::Lock,
+ base::LeakyLazyInstanceTraits<base::Lock> >
+ g_timezone_bundle_lock = LAZY_INSTANCE_INITIALIZER;
+
+struct UResClose {
+ inline void operator() (UResourceBundle* b) const {
+ ures_close(b);
+ }
+};
+
+string16 GetExemplarCity(const icu::TimeZone& zone) {
+ // TODO(jungshik): After upgrading to ICU 4.6, use U_ICUDATA_ZONE
+ static const char* zone_bundle_name = NULL;
+
+ // These will be leaked at the end.
+ static UResourceBundle *zone_bundle = NULL;
+ static UResourceBundle *zone_strings = NULL;
+
+ UErrorCode status = U_ZERO_ERROR;
+ {
+ base::AutoLock lock(g_timezone_bundle_lock.Get());
+ if (zone_bundle == NULL)
+ zone_bundle = ures_open(zone_bundle_name, uloc_getDefault(), &status);
+
+ if (zone_strings == NULL)
+ zone_strings = ures_getByKey(zone_bundle, "zone_strings", NULL, &status);
+ }
+
+ icu::UnicodeString zone_id;
+ zone.getID(zone_id);
+ std::string zone_id_str;
+ zone_id.toUTF8String(zone_id_str);
+
+ // resource keys for timezones use ':' in place of '/'.
+ ReplaceSubstringsAfterOffset(&zone_id_str, 0, "/", ":");
+ scoped_ptr_malloc<UResourceBundle, UResClose> zone_item(
+ ures_getByKey(zone_strings, zone_id_str.c_str(), NULL, &status));
+ icu::UnicodeString city;
+ if (!U_FAILURE(status)) {
+ city = icu::ures_getUnicodeStringByKey(zone_item.get(), "ec", &status);
+ if (U_SUCCESS(status))
+ return string16(city.getBuffer(), city.length());
+ }
+
+ // Fallback case in case of failure.
+ ReplaceSubstringsAfterOffset(&zone_id_str, 0, ":", "/");
+ // Take the last component of a timezone id (e.g. 'Baz' in 'Foo/Bar/Baz').
+ // Depending on timezones, keeping all but the 1st component
+ // (e.g. Bar/Baz) may be better, but our current list does not have
+ // any timezone for which that's the case.
+ std::string::size_type slash_pos = zone_id_str.rfind('/');
+ if (slash_pos != std::string::npos && slash_pos < zone_id_str.size())
+ zone_id_str.erase(0, slash_pos + 1);
+ // zone id has '_' in place of ' '.
+ ReplaceSubstringsAfterOffset(&zone_id_str, 0, "_", " ");
+ return ASCIIToUTF16(zone_id_str);
+}
+
+} // namespace anonymous
+
+namespace chromeos {
+
+SystemSettingsProvider::SystemSettingsProvider(
+ const NotifyObserversCallback& notify_cb)
+ : CrosSettingsProvider(notify_cb) {
+ for (size_t i = 0; i < arraysize(kTimeZones); i++) {
+ timezones_.push_back(icu::TimeZone::createTimeZone(
+ icu::UnicodeString(kTimeZones[i], -1, US_INV)));
+ }
+ system::TimezoneSettings::GetInstance()->AddObserver(this);
+ timezone_value_.reset(base::Value::CreateStringValue(GetKnownTimezoneID(
+ system::TimezoneSettings::GetInstance()->GetTimezone())));
+}
+
+SystemSettingsProvider::~SystemSettingsProvider() {
+ system::TimezoneSettings::GetInstance()->RemoveObserver(this);
+ STLDeleteElements(&timezones_);
+}
+
+void SystemSettingsProvider::DoSet(const std::string& path,
+ const base::Value& in_value) {
+ // Non-guest users can change the time zone.
+ if (UserManager::Get()->IsLoggedInAsGuest())
+ return;
+
+ if (path == kSystemTimezone) {
+ string16 value;
+ if (!in_value.IsType(Value::TYPE_STRING) || !in_value.GetAsString(&value))
+ return;
+ const icu::TimeZone* timezone = GetTimezone(value);
+ if (!timezone)
+ return;
+ system::TimezoneSettings::GetInstance()->SetTimezone(*timezone);
+ timezone_value_.reset(
+ base::Value::CreateStringValue(GetKnownTimezoneID(*timezone)));
+ }
+}
+
+const base::Value* SystemSettingsProvider::Get(const std::string& path) const {
+ if (path == kSystemTimezone)
+ return timezone_value_.get();
+ return NULL;
+}
+
+// The timezone is always trusted.
+bool SystemSettingsProvider::GetTrusted(const std::string& path,
+ const base::Closure& callback) {
+ return true;
+}
+
+bool SystemSettingsProvider::HandlesSetting(const std::string& path) const {
+ return path == kSystemTimezone;
+}
+
+void SystemSettingsProvider::Reload() {
+ // TODO(pastarmovj): We can actually cache the timezone here to make returning
+ // it faster.
+}
+
+void SystemSettingsProvider::TimezoneChanged(const icu::TimeZone& timezone) {
+ // Fires system setting change notification.
+ timezone_value_.reset(
+ base::Value::CreateStringValue(GetKnownTimezoneID(timezone)));
+ NotifyObservers(kSystemTimezone);
+}
+
+ListValue* SystemSettingsProvider::GetTimezoneList() {
+ ListValue* timezoneList = new ListValue();
+ for (std::vector<icu::TimeZone*>::iterator iter = timezones_.begin();
+ iter != timezones_.end(); ++iter) {
+ const icu::TimeZone* timezone = *iter;
+ ListValue* option = new ListValue();
+ option->Append(Value::CreateStringValue(GetTimezoneID(*timezone)));
+ option->Append(Value::CreateStringValue(GetTimezoneName(*timezone)));
+ timezoneList->Append(option);
+ }
+ return timezoneList;
+}
+
+string16 SystemSettingsProvider::GetTimezoneName(
+ const icu::TimeZone& timezone) {
+ // Instead of using the raw_offset, use the offset in effect now.
+ // For instance, US Pacific Time, the offset shown will be -7 in summer
+ // while it'll be -8 in winter.
+ int raw_offset, dst_offset;
+ UDate now = icu::Calendar::getNow();
+ UErrorCode status = U_ZERO_ERROR;
+ timezone.getOffset(now, false, raw_offset, dst_offset, status);
+ DCHECK(U_SUCCESS(status));
+ int offset = raw_offset + dst_offset;
+ // offset is in msec.
+ int minute_offset = std::abs(offset) / 60000;
+ int hour_offset = minute_offset / 60;
+ int min_remainder = minute_offset % 60;
+ // Some timezones have a non-integral hour offset. So, we need to
+ // use hh:mm form.
+ std::string offset_str = base::StringPrintf(offset >= 0 ?
+ "UTC+%d:%02d" : "UTC-%d:%02d", hour_offset, min_remainder);
+
+ // TODO(jungshik): When coming up with a better list of timezones, we also
+ // have to come up with better 'display' names. One possibility is to list
+ // multiple cities (e.g. "Los Angeles, Vancouver .." in the order of
+ // the population of a country the city belongs to.).
+ // We can also think of using LONG_GENERIC or LOCATION once we upgrade
+ // to ICU 4.6.
+ // In the meantime, we use "LONG" name with "Exemplar City" to distinguish
+ // multiple timezones with the same "LONG" name but with different
+ // rules (e.g. US Mountain Time in Denver vs Phoenix).
+ icu::UnicodeString name;
+ timezone.getDisplayName(dst_offset != 0, icu::TimeZone::LONG, name);
+ string16 result(l10n_util::GetStringFUTF16(
+ IDS_OPTIONS_SETTINGS_TIMEZONE_DISPLAY_TEMPLATE, ASCIIToUTF16(offset_str),
+ string16(name.getBuffer(), name.length()), GetExemplarCity(timezone)));
+ base::i18n::AdjustStringForLocaleDirection(&result);
+ return result;
+}
+
+string16 SystemSettingsProvider::GetTimezoneID(
+ const icu::TimeZone& timezone) {
+ icu::UnicodeString id;
+ timezone.getID(id);
+ return string16(id.getBuffer(), id.length());
+}
+
+const icu::TimeZone* SystemSettingsProvider::GetTimezone(
+ const string16& timezone_id) {
+ for (std::vector<icu::TimeZone*>::iterator iter = timezones_.begin();
+ iter != timezones_.end(); ++iter) {
+ const icu::TimeZone* timezone = *iter;
+ if (GetTimezoneID(*timezone) == timezone_id) {
+ return timezone;
+ }
+ }
+ return NULL;
+}
+
+string16 SystemSettingsProvider::GetKnownTimezoneID(
+ const icu::TimeZone& timezone) const {
+ for (std::vector<icu::TimeZone*>::const_iterator iter = timezones_.begin();
+ iter != timezones_.end(); ++iter) {
+ const icu::TimeZone* known_timezone = *iter;
+ if (known_timezone->hasSameRules(timezone))
+ return GetTimezoneID(*known_timezone);
+ }
+
+ // Not able to find a matching timezone in our list.
+ return string16();
+}
+
+} // namespace chromeos
diff --git a/chrome/browser/ui/webui/options2/chromeos/system_settings_provider.h b/chrome/browser/ui/webui/options2/chromeos/system_settings_provider.h
new file mode 100644
index 0000000..33399ed
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/chromeos/system_settings_provider.h
@@ -0,0 +1,76 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_SYSTEM_SETTINGS_PROVIDER_H_
+#define CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_SYSTEM_SETTINGS_PROVIDER_H_
+
+#include <vector>
+
+#include "base/callback.h"
+#include "base/string16.h"
+#include "chrome/browser/chromeos/cros_settings_provider.h"
+#include "chrome/browser/chromeos/system/timezone_settings.h"
+#include "third_party/icu/public/i18n/unicode/timezone.h"
+
+namespace base {
+class Value;
+class ListValue;
+class StringValue;
+}
+
+namespace chromeos {
+
+class SystemSettingsProvider : public CrosSettingsProvider,
+ public system::TimezoneSettings::Observer {
+ public:
+ explicit SystemSettingsProvider(const NotifyObserversCallback& notify_cb);
+ virtual ~SystemSettingsProvider();
+
+ // CrosSettingsProvider overrides.
+ virtual const base::Value* Get(const std::string& path) const OVERRIDE;
+ virtual bool GetTrusted(const std::string& path,
+ const base::Closure& callback) OVERRIDE;
+ virtual bool HandlesSetting(const std::string& path) const OVERRIDE;
+ virtual void Reload() OVERRIDE;
+
+ // Overridden from TimezoneSettings::Observer:
+ virtual void TimezoneChanged(const icu::TimeZone& timezone) OVERRIDE;
+
+ // Creates the map of timezones used by the options page.
+ base::ListValue* GetTimezoneList();
+
+ private:
+ // CrosSettingsProvider overrides.
+ virtual void DoSet(const std::string& path,
+ const base::Value& in_value) OVERRIDE;
+
+ // Gets timezone name.
+ static string16 GetTimezoneName(const icu::TimeZone& timezone);
+
+ // Gets timezone ID which is also used as timezone pref value.
+ static string16 GetTimezoneID(const icu::TimeZone& timezone);
+
+ // Gets timezone object from its id.
+ const icu::TimeZone* GetTimezone(const string16& timezone_id);
+
+ // Gets a timezone id from a timezone in |timezones_| that has the same
+ // rule of given |timezone|.
+ // One timezone could have multiple timezones,
+ // e.g.
+ // US/Pacific == America/Los_Angeles
+ // We should always use the known timezone id when passing back as
+ // pref values.
+ string16 GetKnownTimezoneID(const icu::TimeZone& timezone) const;
+
+ // Timezones.
+ std::vector<icu::TimeZone*> timezones_;
+
+ scoped_ptr<base::Value> timezone_value_;
+
+ DISALLOW_COPY_AND_ASSIGN(SystemSettingsProvider);
+};
+
+} // namespace chromeos
+
+#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_SYSTEM_SETTINGS_PROVIDER_H_
diff --git a/chrome/browser/ui/webui/options2/chromeos/user_image_source.cc b/chrome/browser/ui/webui/options2/chromeos/user_image_source.cc
new file mode 100644
index 0000000..f259c03
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/chromeos/user_image_source.cc
@@ -0,0 +1,53 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/webui/options2/chromeos/user_image_source.h"
+
+#include "base/memory/ref_counted_memory.h"
+#include "chrome/browser/chromeos/login/user_manager.h"
+#include "chrome/common/url_constants.h"
+#include "grit/theme_resources.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/gfx/codec/png_codec.h"
+
+namespace chromeos {
+
+std::vector<unsigned char> UserImageSource::GetUserImage(
+ const std::string& email) const {
+ std::vector<unsigned char> user_image;
+ const chromeos::User* user = chromeos::UserManager::Get()->FindUser(email);
+ if (user) {
+ gfx::PNGCodec::EncodeBGRASkBitmap(user->image(), false, &user_image);
+ return user_image;
+ }
+ gfx::PNGCodec::EncodeBGRASkBitmap(
+ *ResourceBundle::GetSharedInstance().GetBitmapNamed(
+ IDR_LOGIN_DEFAULT_USER),
+ false,
+ &user_image);
+ return user_image;
+}
+
+UserImageSource::UserImageSource()
+ : DataSource(chrome::kChromeUIUserImageHost, MessageLoop::current()) {
+}
+
+UserImageSource::~UserImageSource() {}
+
+void UserImageSource::StartDataRequest(const std::string& path,
+ bool is_incognito,
+ int request_id) {
+ // Strip the query param value - we only use it as a hack to ensure our
+ // image gets reloaded instead of being pulled from the browser cache
+ std::string email = path.substr(0, path.find_first_of("?"));
+ SendResponse(request_id, new RefCountedBytes(GetUserImage(email)));
+}
+
+std::string UserImageSource::GetMimeType(const std::string&) const {
+ // We need to explicitly return a mime type, otherwise if the user tries to
+ // drag the image they get no extension.
+ return "image/png";
+}
+
+} // namespace chromeos
diff --git a/chrome/browser/ui/webui/options2/chromeos/user_image_source.h b/chrome/browser/ui/webui/options2/chromeos/user_image_source.h
new file mode 100644
index 0000000..ea38b49
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/chromeos/user_image_source.h
@@ -0,0 +1,44 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_USER_IMAGE_SOURCE_H_
+#define CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_USER_IMAGE_SOURCE_H_
+#pragma once
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "chrome/browser/ui/webui/chrome_url_data_manager.h"
+
+namespace chromeos {
+
+// UserImageSource is the data source that serves user images for users that
+// have it.
+class UserImageSource : public ChromeURLDataManager::DataSource {
+ public:
+ UserImageSource();
+
+ // Called when the network layer has requested a resource underneath
+ // the path we registered.
+ virtual void StartDataRequest(const std::string& path,
+ bool is_incognito,
+ int request_id) OVERRIDE;
+
+ virtual std::string GetMimeType(const std::string&) const OVERRIDE;
+
+ // Returns PNG encoded image for user with specified email.
+ // If there's no user with such email, returns the default image.
+ std::vector<unsigned char> GetUserImage(const std::string& email) const;
+
+ private:
+ virtual ~UserImageSource();
+
+ DISALLOW_COPY_AND_ASSIGN(UserImageSource);
+};
+
+} // namespace chromeos
+
+#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_USER_IMAGE_SOURCE_H_
diff --git a/chrome/browser/ui/webui/options2/chromeos/virtual_keyboard_manager_handler.cc b/chrome/browser/ui/webui/options2/chromeos/virtual_keyboard_manager_handler.cc
new file mode 100644
index 0000000..22c0fe6
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/chromeos/virtual_keyboard_manager_handler.cc
@@ -0,0 +1,237 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/webui/options2/chromeos/virtual_keyboard_manager_handler.h"
+
+#include <map>
+#include <set>
+#include <string>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/string_number_conversions.h"
+#include "base/utf_string_conversions.h"
+#include "base/values.h"
+#include "chrome/browser/chromeos/input_method/input_method_manager.h"
+#include "chrome/browser/chromeos/input_method/input_method_util.h"
+#include "chrome/browser/chromeos/input_method/virtual_keyboard_selector.h"
+#include "chrome/browser/chromeos/preferences.h"
+#include "chrome/browser/prefs/pref_service.h"
+#include "chrome/browser/prefs/scoped_user_pref_update.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/common/chrome_notification_types.h"
+#include "chrome/common/pref_names.h"
+#include "content/public/browser/notification_details.h"
+#include "content/public/browser/notification_source.h"
+#include "grit/generated_resources.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace ime = ::chromeos::input_method;
+
+namespace chromeos {
+
+VirtualKeyboardManagerHandler::VirtualKeyboardManagerHandler() {
+}
+
+VirtualKeyboardManagerHandler::~VirtualKeyboardManagerHandler() {
+}
+
+void VirtualKeyboardManagerHandler::GetLocalizedValues(
+ DictionaryValue* localized_strings) {
+ DCHECK(localized_strings);
+
+ static const OptionsStringResource resources[] = {
+ { "virtualKeyboardLayoutColumnTitle",
+ IDS_OPTIONS_SETTINGS_LANGUAGES_VIRTUAL_KEYBOARD_LAYOUT_COLUMN_TITLE },
+ { "virtualKeyboardKeyboardColumnTitle",
+ IDS_OPTIONS_SETTINGS_LANGUAGES_VIRTUAL_KEYBOARD_KEYBOARD_COLUMN_TITLE },
+ { "defaultVirtualKeyboard",
+ IDS_OPTIONS_SETTINGS_LANGUAGES_DEFAULT_VIRTUAL_KEYBOARD },
+ };
+ RegisterStrings(localized_strings, resources, arraysize(resources));
+
+ RegisterTitle(localized_strings, "virtualKeyboardPage",
+ IDS_OPTIONS_SETTINGS_LANGUAGES_VIRTUAL_KEYBOARD_SETTINGS_TITLE);
+
+ // Do not call GetVirtualKeyboardList() here since |web_ui_| is not ready yet.
+}
+
+void VirtualKeyboardManagerHandler::Initialize() {
+}
+
+void VirtualKeyboardManagerHandler::RegisterMessages() {
+ DCHECK(web_ui_);
+ // Register handler functions for chrome.send().
+ web_ui_->RegisterMessageCallback("updateVirtualKeyboardList",
+ base::Bind(&VirtualKeyboardManagerHandler::UpdateVirtualKeyboardList,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("setVirtualKeyboardPreference",
+ base::Bind(&VirtualKeyboardManagerHandler::SetVirtualKeyboardPreference,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("clearVirtualKeyboardPreference",
+ base::Bind(&VirtualKeyboardManagerHandler::ClearVirtualKeyboardPreference,
+ base::Unretained(this)));
+}
+
+ListValue* VirtualKeyboardManagerHandler::GetVirtualKeyboardList() {
+ DCHECK(web_ui_);
+ ime::InputMethodManager* input_method =
+ ime::InputMethodManager::GetInstance();
+
+ // Get a multi map from layout name (e.g. "us(dvorak)"), to virtual keyboard
+ // extension.
+ const LayoutToKeyboard& layout_to_keyboard =
+ input_method->GetLayoutNameToKeyboardMapping();
+ const UrlToKeyboard& url_to_keyboard =
+ input_method->GetUrlToKeyboardMapping();
+
+ // Get the current pref values.
+ PrefService* prefs = Profile::FromWebUI(web_ui_)->GetPrefs();
+ DCHECK(prefs);
+ const DictionaryValue* virtual_keyboard_pref =
+ prefs->GetDictionary(prefs::kLanguagePreferredVirtualKeyboard);
+
+ return CreateVirtualKeyboardList(
+ layout_to_keyboard, url_to_keyboard, virtual_keyboard_pref);
+}
+
+void VirtualKeyboardManagerHandler::UpdateVirtualKeyboardList(
+ const ListValue* args) {
+ scoped_ptr<Value> virtual_keyboards(GetVirtualKeyboardList());
+ DCHECK(virtual_keyboards.get());
+ web_ui_->CallJavascriptFunction(
+ "VirtualKeyboardManager.updateVirtualKeyboardList", *virtual_keyboards);
+}
+
+void VirtualKeyboardManagerHandler::SetVirtualKeyboardPreference(
+ const ListValue* args) {
+ DCHECK(web_ui_);
+ std::string layout, url;
+ if (!args || !args->GetString(0, &layout) || !args->GetString(1, &url)) {
+ LOG(ERROR) << "SetVirtualKeyboardPreference: Invalid argument";
+ return;
+ }
+
+ // Validate args.
+ ime::InputMethodManager* input_method =
+ ime::InputMethodManager::GetInstance();
+ if (!ValidateUrl(input_method->GetUrlToKeyboardMapping(), layout, url)) {
+ LOG(ERROR) << "SetVirtualKeyboardPreference: Invalid args: "
+ << "layout=" << layout << ", url=" << url;
+ return;
+ }
+
+ PrefService* prefs = Profile::FromWebUI(web_ui_)->GetPrefs();
+ DCHECK(prefs);
+ {
+ DictionaryPrefUpdate updater(
+ prefs, prefs::kLanguagePreferredVirtualKeyboard);
+ DictionaryValue* pref_value = updater.Get();
+ pref_value->SetWithoutPathExpansion(layout, new StringValue(url));
+ }
+ Preferences::UpdateVirturalKeyboardPreference(prefs);
+}
+
+void VirtualKeyboardManagerHandler::ClearVirtualKeyboardPreference(
+ const ListValue* args) {
+ DCHECK(web_ui_);
+ std::string layout;
+ if (!args || !args->GetString(0, &layout)) {
+ LOG(ERROR) << "ClearVirtualKeyboardPreference: Invalid argument";
+ return;
+ }
+
+ // Validate |layout|.
+ ime::InputMethodManager* input_method =
+ ime::InputMethodManager::GetInstance();
+ if (!input_method->GetLayoutNameToKeyboardMapping().count(layout)) {
+ LOG(ERROR) << "ClearVirtualKeyboardPreference: Invalid layout: " << layout;
+ return;
+ }
+
+ PrefService* prefs = Profile::FromWebUI(web_ui_)->GetPrefs();
+ DCHECK(prefs);
+ {
+ DictionaryPrefUpdate updater(
+ prefs, prefs::kLanguagePreferredVirtualKeyboard);
+ DictionaryValue* pref_value = updater.Get();
+ pref_value->RemoveWithoutPathExpansion(layout, NULL);
+ }
+ Preferences::UpdateVirturalKeyboardPreference(prefs);
+}
+
+// static
+bool VirtualKeyboardManagerHandler::ValidateUrl(
+ const UrlToKeyboard& url_to_keyboard,
+ const std::string& layout,
+ const std::string& url) {
+ UrlToKeyboard::const_iterator iter = url_to_keyboard.find(GURL(url));
+ if (iter == url_to_keyboard.end() ||
+ !iter->second->supported_layouts().count(layout)) {
+ return false;
+ }
+ return true;
+}
+
+// static
+ListValue* VirtualKeyboardManagerHandler::CreateVirtualKeyboardList(
+ const LayoutToKeyboard& layout_to_keyboard,
+ const UrlToKeyboard& url_to_keyboard,
+ const DictionaryValue* virtual_keyboard_pref) {
+ ListValue* layout_list = new ListValue;
+
+ // |dictionary| points to an element in the |layout_list|. One dictionary
+ // element is created for one layout.
+ DictionaryValue* dictionary = NULL;
+
+ LayoutToKeyboard::const_iterator i;
+ for (i = layout_to_keyboard.begin(); i != layout_to_keyboard.end(); ++i) {
+ const std::string& layout_id = i->first;
+
+ std::string string_value;
+ // Check the "layout" value in the current dictionary.
+ if (dictionary) {
+ dictionary->GetString("layout", &string_value);
+ }
+
+ if (string_value != layout_id) {
+ // New layout is found. Add the layout to |layout_list|.
+ dictionary = new DictionaryValue;
+ layout_list->Append(dictionary);
+
+ // Set layout id as well as its human readable form.
+ ime::InputMethodManager* manager = ime::InputMethodManager::GetInstance();
+ const ime::InputMethodDescriptor* desc =
+ manager->GetInputMethodUtil()->GetInputMethodDescriptorFromXkbId(
+ layout_id);
+ const std::string layout_name = desc ?
+ manager->GetInputMethodUtil()->GetInputMethodDisplayNameFromId(
+ desc->id()) : layout_id;
+ dictionary->SetString("layout", layout_id);
+ dictionary->SetString("layoutName", layout_name);
+
+ // Check if the layout is in user pref.
+ if (virtual_keyboard_pref &&
+ virtual_keyboard_pref->GetString(layout_id, &string_value) &&
+ ValidateUrl(url_to_keyboard, layout_id, string_value)) {
+ dictionary->SetString("preferredKeyboard", string_value);
+ }
+ dictionary->Set("supportedKeyboards", new ListValue);
+ }
+
+ ListValue* supported_keyboards = NULL;
+ dictionary->GetList("supportedKeyboards", &supported_keyboards);
+ DCHECK(supported_keyboards);
+
+ DictionaryValue* virtual_keyboard = new DictionaryValue;
+ virtual_keyboard->SetString("name", i->second->name());
+ virtual_keyboard->SetBoolean("isSystem", i->second->is_system());
+ virtual_keyboard->SetString("url", i->second->url().spec());
+ supported_keyboards->Append(virtual_keyboard);
+ }
+
+ return layout_list;
+}
+
+} // namespace chromeos
diff --git a/chrome/browser/ui/webui/options2/chromeos/virtual_keyboard_manager_handler.h b/chrome/browser/ui/webui/options2/chromeos/virtual_keyboard_manager_handler.h
new file mode 100644
index 0000000..6e720f7
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/chromeos/virtual_keyboard_manager_handler.h
@@ -0,0 +1,78 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_VIRTUAL_KEYBOARD_MANAGER_HANDLER_H_
+#define CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_VIRTUAL_KEYBOARD_MANAGER_HANDLER_H_
+#pragma once
+
+#include <map>
+#include <set>
+#include <string>
+
+#include "chrome/browser/ui/webui/options2/options_ui2.h"
+#include "googleurl/src/gurl.h"
+
+namespace base {
+class DictionaryValue;
+class ListValue;
+} // namespace base
+
+namespace chromeos {
+
+namespace input_method {
+class VirtualKeyboard;
+} // namespace input_method;
+
+// A class which provides information to virtual_keyboard.js.
+class VirtualKeyboardManagerHandler : public OptionsPage2UIHandler {
+ public:
+ VirtualKeyboardManagerHandler();
+ virtual ~VirtualKeyboardManagerHandler();
+
+ // OptionsPage2UIHandler implementation.
+ virtual void GetLocalizedValues(
+ base::DictionaryValue* localized_strings) OVERRIDE;
+ virtual void Initialize() OVERRIDE;
+ virtual void RegisterMessages() OVERRIDE;
+
+ protected:
+ typedef std::multimap<
+ std::string, const input_method::VirtualKeyboard*> LayoutToKeyboard;
+ typedef std::map<GURL, const input_method::VirtualKeyboard*> UrlToKeyboard;
+
+ // Returns true if |layout_to_keyboard| contains |layout| as a key, and the
+ // value for |layout| contains |url|. This function is protected for
+ // testability.
+ static bool ValidateUrl(const UrlToKeyboard& url_to_keyboard,
+ const std::string& layout,
+ const std::string& url);
+
+ // Builds a list from |layout_to_keyboard| and |virtual_keyboard_user_pref|.
+ // See virtual_keyboard_list.js for an example of the format the list should
+ // take. This function is protected for testability.
+ static base::ListValue* CreateVirtualKeyboardList(
+ const LayoutToKeyboard& layout_to_keyboard,
+ const UrlToKeyboard& url_to_keyboard,
+ const base::DictionaryValue* virtual_keyboard_user_pref);
+
+ private:
+ // Reads user pref and create a list using CreateVirtualKeyboardList().
+ base::ListValue* GetVirtualKeyboardList();
+
+ // Handles chrome.send("updateVirtualKeyboardList") JS call.
+ // TODO(yusukes): This function should also be called when user pref is
+ // updated by chrome://settings page in other tab.
+ void UpdateVirtualKeyboardList(const base::ListValue* args);
+
+ // Handles chrome.send("setVirtualKeyboardPreference") JS call.
+ void SetVirtualKeyboardPreference(const base::ListValue* args);
+ // Handles chrome.send("clearVirtualKeyboardPreference") JS call.
+ void ClearVirtualKeyboardPreference(const base::ListValue* args);
+
+ DISALLOW_COPY_AND_ASSIGN(VirtualKeyboardManagerHandler);
+};
+
+} // namespace chromeos
+
+#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS2_CHROMEOS_VIRTUAL_KEYBOARD_MANAGER_HANDLER_H_
diff --git a/chrome/browser/ui/webui/options2/chromeos/virtual_keyboard_manager_handler_unittest.cc b/chrome/browser/ui/webui/options2/chromeos/virtual_keyboard_manager_handler_unittest.cc
new file mode 100644
index 0000000..5783401
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/chromeos/virtual_keyboard_manager_handler_unittest.cc
@@ -0,0 +1,550 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/webui/options2/chromeos/virtual_keyboard_manager_handler.h"
+
+#include <map>
+#include <set>
+#include <string>
+
+#include "base/logging.h"
+#include "base/values.h"
+#include "chrome/browser/chromeos/input_method/virtual_keyboard_selector.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+typedef std::multimap<
+ std::string, const chromeos::input_method::VirtualKeyboard*> LayoutToKeyboard;
+typedef std::map<
+ GURL, const chromeos::input_method::VirtualKeyboard*> UrlToKeyboard;
+
+template <size_t L>
+std::set<std::string> CreateLayoutSet(const char* (&layouts)[L]) {
+ return std::set<std::string>(layouts, layouts + L);
+}
+
+} // namespace
+
+namespace chromeos {
+
+class Testee : public VirtualKeyboardManagerHandler {
+ public:
+ // Change access rights.
+ using VirtualKeyboardManagerHandler::ValidateUrl;
+ using VirtualKeyboardManagerHandler::CreateVirtualKeyboardList;
+};
+
+TEST(VirtualKeyboardManagerHandler, TestValidateUrl) {
+ static const char* layouts1[] = { "a", "b" };
+ static const char* layouts2[] = { "b" };
+ input_method::VirtualKeyboard virtual_keyboard_1(
+ GURL("http://url1/"), "name 1", CreateLayoutSet(layouts1), true);
+ input_method::VirtualKeyboard virtual_keyboard_2(
+ GURL("http://url2/"), "name 2", CreateLayoutSet(layouts2), true);
+
+ input_method::VirtualKeyboardSelector selector;
+ ASSERT_TRUE(selector.AddVirtualKeyboard(
+ virtual_keyboard_1.url(),
+ virtual_keyboard_1.name(),
+ virtual_keyboard_1.supported_layouts(),
+ virtual_keyboard_1.is_system()));
+ ASSERT_TRUE(selector.AddVirtualKeyboard(
+ virtual_keyboard_2.url(),
+ virtual_keyboard_2.name(),
+ virtual_keyboard_2.supported_layouts(),
+ virtual_keyboard_2.is_system()));
+
+ const UrlToKeyboard& url_to_keyboard = selector.url_to_keyboard();
+ ASSERT_EQ(2U, url_to_keyboard.size());
+
+ EXPECT_TRUE(Testee::ValidateUrl(url_to_keyboard, "a", "http://url1/"));
+ EXPECT_TRUE(Testee::ValidateUrl(url_to_keyboard, "b", "http://url1/"));
+ EXPECT_TRUE(Testee::ValidateUrl(url_to_keyboard, "b", "http://url2/"));
+
+ EXPECT_FALSE(Testee::ValidateUrl(url_to_keyboard, "a", "http://url3/"));
+ EXPECT_FALSE(Testee::ValidateUrl(url_to_keyboard, "b", "http://url3/"));
+ EXPECT_FALSE(Testee::ValidateUrl(url_to_keyboard, "c", "http://url1/"));
+ EXPECT_FALSE(Testee::ValidateUrl(url_to_keyboard, "c", "http://url2/"));
+}
+
+TEST(VirtualKeyboardManagerHandler, TestSingleKeyboard) {
+ static const char* layouts[] = { "a", "b" };
+ input_method::VirtualKeyboard virtual_keyboard_1(
+ GURL("http://url1/"), "name 1", CreateLayoutSet(layouts), true);
+
+ input_method::VirtualKeyboardSelector selector;
+ ASSERT_TRUE(selector.AddVirtualKeyboard(
+ virtual_keyboard_1.url(),
+ virtual_keyboard_1.name(),
+ virtual_keyboard_1.supported_layouts(),
+ virtual_keyboard_1.is_system()));
+
+ const LayoutToKeyboard& layout_to_keyboard = selector.layout_to_keyboard();
+ ASSERT_EQ(arraysize(layouts), layout_to_keyboard.size());
+ const UrlToKeyboard& url_to_keyboard = selector.url_to_keyboard();
+ ASSERT_EQ(1U, url_to_keyboard.size());
+
+ scoped_ptr<ListValue> keyboards(Testee::CreateVirtualKeyboardList(
+ layout_to_keyboard, url_to_keyboard, NULL));
+ ASSERT_TRUE(keyboards.get());
+ ASSERT_EQ(arraysize(layouts), keyboards->GetSize());
+
+ DictionaryValue* dictionary_value;
+ std::string string_value;
+ ListValue* list_value;
+
+ // Check the first element (for the layout "a").
+ ASSERT_TRUE(keyboards->GetDictionary(0, &dictionary_value));
+ EXPECT_TRUE(dictionary_value->GetString("layout", &string_value));
+ EXPECT_EQ("a", string_value);
+ EXPECT_TRUE(dictionary_value->GetString("layoutName", &string_value));
+ EXPECT_FALSE(dictionary_value->GetString("preferredKeyboard", &string_value));
+ ASSERT_TRUE(dictionary_value->GetList("supportedKeyboards", &list_value));
+ ASSERT_EQ(1U, list_value->GetSize());
+ ASSERT_TRUE(list_value->GetDictionary(0, &dictionary_value));
+ EXPECT_TRUE(dictionary_value->GetString("url", &string_value));
+ EXPECT_EQ("http://url1/", string_value);
+ EXPECT_TRUE(dictionary_value->GetString("name", &string_value));
+ EXPECT_EQ("name 1", string_value);
+
+ // Check the second element (for the layout "b").
+ ASSERT_TRUE(keyboards->GetDictionary(1, &dictionary_value));
+ EXPECT_TRUE(dictionary_value->GetString("layout", &string_value));
+ EXPECT_EQ("b", string_value);
+ EXPECT_TRUE(dictionary_value->GetString("layoutName", &string_value));
+ EXPECT_FALSE(dictionary_value->GetString("preferredKeyboard", &string_value));
+ ASSERT_TRUE(dictionary_value->GetList("supportedKeyboards", &list_value));
+ ASSERT_EQ(1U, list_value->GetSize());
+ ASSERT_TRUE(list_value->GetDictionary(0, &dictionary_value));
+ EXPECT_TRUE(dictionary_value->GetString("url", &string_value));
+ EXPECT_EQ("http://url1/", string_value);
+ EXPECT_TRUE(dictionary_value->GetString("name", &string_value));
+ EXPECT_EQ("name 1", string_value);
+}
+
+TEST(VirtualKeyboardManagerHandler, TestSingleKeyboardWithPref) {
+ static const char* layouts[] = { "a", "b" };
+ input_method::VirtualKeyboard virtual_keyboard_1(
+ GURL("http://url1/"), "name 1", CreateLayoutSet(layouts), true);
+
+ input_method::VirtualKeyboardSelector selector;
+ ASSERT_TRUE(selector.AddVirtualKeyboard(
+ virtual_keyboard_1.url(),
+ virtual_keyboard_1.name(),
+ virtual_keyboard_1.supported_layouts(),
+ virtual_keyboard_1.is_system()));
+
+ const LayoutToKeyboard& layout_to_keyboard = selector.layout_to_keyboard();
+ ASSERT_EQ(arraysize(layouts), layout_to_keyboard.size());
+ const UrlToKeyboard& url_to_keyboard = selector.url_to_keyboard();
+ ASSERT_EQ(1U, url_to_keyboard.size());
+
+ // create pref object.
+ scoped_ptr<DictionaryValue> pref(new DictionaryValue);
+ pref->SetString("b", "http://url1/");
+
+ scoped_ptr<ListValue> keyboards(Testee::CreateVirtualKeyboardList(
+ layout_to_keyboard, url_to_keyboard, pref.get()));
+ ASSERT_TRUE(keyboards.get());
+ ASSERT_EQ(arraysize(layouts), keyboards->GetSize());
+
+ DictionaryValue* dictionary_value;
+ std::string string_value;
+ ListValue* list_value;
+
+ // Check the first element (for the layout "a").
+ ASSERT_TRUE(keyboards->GetDictionary(0, &dictionary_value));
+ EXPECT_TRUE(dictionary_value->GetString("layout", &string_value));
+ EXPECT_EQ("a", string_value);
+ EXPECT_TRUE(dictionary_value->GetString("layoutName", &string_value));
+ EXPECT_FALSE(dictionary_value->GetString("preferredKeyboard", &string_value));
+ ASSERT_TRUE(dictionary_value->GetList("supportedKeyboards", &list_value));
+ ASSERT_EQ(1U, list_value->GetSize());
+ ASSERT_TRUE(list_value->GetDictionary(0, &dictionary_value));
+ EXPECT_TRUE(dictionary_value->GetString("url", &string_value));
+ EXPECT_EQ("http://url1/", string_value);
+ EXPECT_TRUE(dictionary_value->GetString("name", &string_value));
+ EXPECT_EQ("name 1", string_value);
+
+ // Check the second element (for the layout "b").
+ ASSERT_TRUE(keyboards->GetDictionary(1, &dictionary_value));
+ EXPECT_TRUE(dictionary_value->GetString("layout", &string_value));
+ EXPECT_EQ("b", string_value);
+ EXPECT_TRUE(dictionary_value->GetString("layoutName", &string_value));
+ EXPECT_TRUE(dictionary_value->GetString("preferredKeyboard", &string_value));
+ EXPECT_EQ("http://url1/", string_value);
+ ASSERT_TRUE(dictionary_value->GetList("supportedKeyboards", &list_value));
+ ASSERT_EQ(1U, list_value->GetSize());
+ ASSERT_TRUE(list_value->GetDictionary(0, &dictionary_value));
+ EXPECT_TRUE(dictionary_value->GetString("url", &string_value));
+ EXPECT_EQ("http://url1/", string_value);
+ EXPECT_TRUE(dictionary_value->GetString("name", &string_value));
+ EXPECT_EQ("name 1", string_value);
+}
+
+TEST(VirtualKeyboardManagerHandler, TestSingleKeyboardWithTwoPrefs) {
+ static const char* layouts[] = { "a", "b" };
+ input_method::VirtualKeyboard virtual_keyboard_1(
+ GURL("http://url1/"), "name 1", CreateLayoutSet(layouts), true);
+
+ input_method::VirtualKeyboardSelector selector;
+ ASSERT_TRUE(selector.AddVirtualKeyboard(
+ virtual_keyboard_1.url(),
+ virtual_keyboard_1.name(),
+ virtual_keyboard_1.supported_layouts(),
+ virtual_keyboard_1.is_system()));
+
+ const LayoutToKeyboard& layout_to_keyboard = selector.layout_to_keyboard();
+ ASSERT_EQ(arraysize(layouts), layout_to_keyboard.size());
+ const UrlToKeyboard& url_to_keyboard = selector.url_to_keyboard();
+ ASSERT_EQ(1U, url_to_keyboard.size());
+
+ // create pref object.
+ scoped_ptr<DictionaryValue> pref(new DictionaryValue);
+ pref->SetString("a", "http://url1/");
+ pref->SetString("b", "http://url1/");
+
+ scoped_ptr<ListValue> keyboards(Testee::CreateVirtualKeyboardList(
+ layout_to_keyboard, url_to_keyboard, pref.get()));
+ ASSERT_TRUE(keyboards.get());
+ ASSERT_EQ(arraysize(layouts), keyboards->GetSize());
+
+ DictionaryValue* dictionary_value;
+ std::string string_value;
+ ListValue* list_value;
+
+ // Check the first element (for the layout "a").
+ ASSERT_TRUE(keyboards->GetDictionary(0, &dictionary_value));
+ EXPECT_TRUE(dictionary_value->GetString("layout", &string_value));
+ EXPECT_EQ("a", string_value);
+ EXPECT_TRUE(dictionary_value->GetString("layoutName", &string_value));
+ EXPECT_TRUE(dictionary_value->GetString("preferredKeyboard", &string_value));
+ EXPECT_EQ("http://url1/", string_value);
+ ASSERT_TRUE(dictionary_value->GetList("supportedKeyboards", &list_value));
+ ASSERT_EQ(1U, list_value->GetSize());
+ ASSERT_TRUE(list_value->GetDictionary(0, &dictionary_value));
+ EXPECT_TRUE(dictionary_value->GetString("url", &string_value));
+ EXPECT_EQ("http://url1/", string_value);
+ EXPECT_TRUE(dictionary_value->GetString("name", &string_value));
+ EXPECT_EQ("name 1", string_value);
+
+ // Check the second element (for the layout "b").
+ ASSERT_TRUE(keyboards->GetDictionary(1, &dictionary_value));
+ EXPECT_TRUE(dictionary_value->GetString("layout", &string_value));
+ EXPECT_EQ("b", string_value);
+ EXPECT_TRUE(dictionary_value->GetString("layoutName", &string_value));
+ EXPECT_TRUE(dictionary_value->GetString("preferredKeyboard", &string_value));
+ EXPECT_EQ("http://url1/", string_value);
+ ASSERT_TRUE(dictionary_value->GetList("supportedKeyboards", &list_value));
+ ASSERT_EQ(1U, list_value->GetSize());
+ ASSERT_TRUE(list_value->GetDictionary(0, &dictionary_value));
+ EXPECT_TRUE(dictionary_value->GetString("url", &string_value));
+ EXPECT_EQ("http://url1/", string_value);
+ EXPECT_TRUE(dictionary_value->GetString("name", &string_value));
+ EXPECT_EQ("name 1", string_value);
+}
+
+TEST(VirtualKeyboardManagerHandler, TestSingleKeyboardWithBadPref1) {
+ static const char* layouts[] = { "a", "b" };
+ input_method::VirtualKeyboard virtual_keyboard_1(
+ GURL("http://url1/"), "name 1", CreateLayoutSet(layouts), true);
+
+ input_method::VirtualKeyboardSelector selector;
+ ASSERT_TRUE(selector.AddVirtualKeyboard(
+ virtual_keyboard_1.url(),
+ virtual_keyboard_1.name(),
+ virtual_keyboard_1.supported_layouts(),
+ virtual_keyboard_1.is_system()));
+
+ const LayoutToKeyboard& layout_to_keyboard = selector.layout_to_keyboard();
+ ASSERT_EQ(arraysize(layouts), layout_to_keyboard.size());
+ const UrlToKeyboard& url_to_keyboard = selector.url_to_keyboard();
+ ASSERT_EQ(1U, url_to_keyboard.size());
+
+ // create pref object.
+ scoped_ptr<DictionaryValue> pref(new DictionaryValue);
+ pref->SetString("unknownlayout", "http://url1/");
+
+ scoped_ptr<ListValue> keyboards(Testee::CreateVirtualKeyboardList(
+ layout_to_keyboard, url_to_keyboard, pref.get()));
+ ASSERT_TRUE(keyboards.get());
+ ASSERT_EQ(arraysize(layouts), keyboards->GetSize());
+
+ DictionaryValue* dictionary_value;
+ std::string string_value;
+ ListValue* list_value;
+
+ // Check the first element (for the layout "a").
+ ASSERT_TRUE(keyboards->GetDictionary(0, &dictionary_value));
+ EXPECT_TRUE(dictionary_value->GetString("layout", &string_value));
+ EXPECT_EQ("a", string_value);
+ EXPECT_TRUE(dictionary_value->GetString("layoutName", &string_value));
+ EXPECT_FALSE(dictionary_value->GetString("preferredKeyboard", &string_value));
+ ASSERT_TRUE(dictionary_value->GetList("supportedKeyboards", &list_value));
+ ASSERT_EQ(1U, list_value->GetSize());
+ ASSERT_TRUE(list_value->GetDictionary(0, &dictionary_value));
+ EXPECT_TRUE(dictionary_value->GetString("url", &string_value));
+ EXPECT_EQ("http://url1/", string_value);
+ EXPECT_TRUE(dictionary_value->GetString("name", &string_value));
+ EXPECT_EQ("name 1", string_value);
+
+ // Check the second element (for the layout "b").
+ ASSERT_TRUE(keyboards->GetDictionary(1, &dictionary_value));
+ EXPECT_TRUE(dictionary_value->GetString("layout", &string_value));
+ EXPECT_EQ("b", string_value);
+ EXPECT_TRUE(dictionary_value->GetString("layoutName", &string_value));
+ EXPECT_FALSE(dictionary_value->GetString("preferredKeyboard", &string_value));
+ ASSERT_TRUE(dictionary_value->GetList("supportedKeyboards", &list_value));
+ ASSERT_EQ(1U, list_value->GetSize());
+ ASSERT_TRUE(list_value->GetDictionary(0, &dictionary_value));
+ EXPECT_TRUE(dictionary_value->GetString("url", &string_value));
+ EXPECT_EQ("http://url1/", string_value);
+ EXPECT_TRUE(dictionary_value->GetString("name", &string_value));
+ EXPECT_EQ("name 1", string_value);
+}
+
+TEST(VirtualKeyboardManagerHandler, TestSingleKeyboardWithBadPref2) {
+ static const char* layouts[] = { "a", "b" };
+ input_method::VirtualKeyboard virtual_keyboard_1(
+ GURL("http://url1/"), "name 1", CreateLayoutSet(layouts), true);
+
+ input_method::VirtualKeyboardSelector selector;
+ ASSERT_TRUE(selector.AddVirtualKeyboard(
+ virtual_keyboard_1.url(),
+ virtual_keyboard_1.name(),
+ virtual_keyboard_1.supported_layouts(),
+ virtual_keyboard_1.is_system()));
+
+ const LayoutToKeyboard& layout_to_keyboard = selector.layout_to_keyboard();
+ ASSERT_EQ(arraysize(layouts), layout_to_keyboard.size());
+ const UrlToKeyboard& url_to_keyboard = selector.url_to_keyboard();
+ ASSERT_EQ(1U, url_to_keyboard.size());
+
+ // create pref object.
+ scoped_ptr<DictionaryValue> pref(new DictionaryValue);
+ pref->SetString("a", "http://unknownurl/");
+
+ scoped_ptr<ListValue> keyboards(Testee::CreateVirtualKeyboardList(
+ layout_to_keyboard, url_to_keyboard, pref.get()));
+ ASSERT_TRUE(keyboards.get());
+ ASSERT_EQ(arraysize(layouts), keyboards->GetSize());
+
+ DictionaryValue* dictionary_value;
+ std::string string_value;
+ ListValue* list_value;
+
+ // Check the first element (for the layout "a").
+ ASSERT_TRUE(keyboards->GetDictionary(0, &dictionary_value));
+ EXPECT_TRUE(dictionary_value->GetString("layout", &string_value));
+ EXPECT_EQ("a", string_value);
+ EXPECT_TRUE(dictionary_value->GetString("layoutName", &string_value));
+ EXPECT_FALSE(dictionary_value->GetString("preferredKeyboard", &string_value));
+ ASSERT_TRUE(dictionary_value->GetList("supportedKeyboards", &list_value));
+ ASSERT_EQ(1U, list_value->GetSize());
+ ASSERT_TRUE(list_value->GetDictionary(0, &dictionary_value));
+ EXPECT_TRUE(dictionary_value->GetString("url", &string_value));
+ EXPECT_EQ("http://url1/", string_value);
+ EXPECT_TRUE(dictionary_value->GetString("name", &string_value));
+ EXPECT_EQ("name 1", string_value);
+
+ // Check the second element (for the layout "b").
+ ASSERT_TRUE(keyboards->GetDictionary(1, &dictionary_value));
+ EXPECT_TRUE(dictionary_value->GetString("layout", &string_value));
+ EXPECT_EQ("b", string_value);
+ EXPECT_TRUE(dictionary_value->GetString("layoutName", &string_value));
+ EXPECT_FALSE(dictionary_value->GetString("preferredKeyboard", &string_value));
+ ASSERT_TRUE(dictionary_value->GetList("supportedKeyboards", &list_value));
+ ASSERT_EQ(1U, list_value->GetSize());
+ ASSERT_TRUE(list_value->GetDictionary(0, &dictionary_value));
+ EXPECT_TRUE(dictionary_value->GetString("url", &string_value));
+ EXPECT_EQ("http://url1/", string_value);
+ EXPECT_TRUE(dictionary_value->GetString("name", &string_value));
+ EXPECT_EQ("name 1", string_value);
+}
+
+TEST(VirtualKeyboardManagerHandler, TestSingleKeyboardWithBadPref3) {
+ static const char* layout1[] = { "a" };
+ static const char* layout2[] = { "b" };
+ input_method::VirtualKeyboard virtual_keyboard_1(
+ GURL("http://url1/"), "name 1", CreateLayoutSet(layout1), true);
+ input_method::VirtualKeyboard virtual_keyboard_2(
+ GURL("http://url2/"), "name 2", CreateLayoutSet(layout2), true);
+
+ input_method::VirtualKeyboardSelector selector;
+ ASSERT_TRUE(selector.AddVirtualKeyboard(
+ virtual_keyboard_1.url(),
+ virtual_keyboard_1.name(),
+ virtual_keyboard_1.supported_layouts(),
+ virtual_keyboard_1.is_system()));
+ ASSERT_TRUE(selector.AddVirtualKeyboard(
+ virtual_keyboard_2.url(),
+ virtual_keyboard_2.name(),
+ virtual_keyboard_2.supported_layouts(),
+ virtual_keyboard_2.is_system()));
+
+ const LayoutToKeyboard& layout_to_keyboard = selector.layout_to_keyboard();
+ ASSERT_EQ(2U, layout_to_keyboard.size());
+ const UrlToKeyboard& url_to_keyboard = selector.url_to_keyboard();
+ ASSERT_EQ(2U, url_to_keyboard.size());
+
+ // create pref object.
+ scoped_ptr<DictionaryValue> pref(new DictionaryValue);
+ pref->SetString("a", "http://url2/"); // url2 does not support "a".
+
+ scoped_ptr<ListValue> keyboards(Testee::CreateVirtualKeyboardList(
+ layout_to_keyboard, url_to_keyboard, pref.get()));
+ ASSERT_TRUE(keyboards.get());
+ ASSERT_EQ(2U, keyboards->GetSize());
+
+ DictionaryValue* dictionary_value;
+ std::string string_value;
+ ListValue* list_value;
+
+ // Check the first element (for the layout "a").
+ ASSERT_TRUE(keyboards->GetDictionary(0, &dictionary_value));
+ EXPECT_TRUE(dictionary_value->GetString("layout", &string_value));
+ EXPECT_EQ("a", string_value);
+ EXPECT_TRUE(dictionary_value->GetString("layoutName", &string_value));
+ EXPECT_FALSE(dictionary_value->GetString("preferredKeyboard", &string_value));
+ ASSERT_TRUE(dictionary_value->GetList("supportedKeyboards", &list_value));
+ ASSERT_EQ(1U, list_value->GetSize());
+ ASSERT_TRUE(list_value->GetDictionary(0, &dictionary_value));
+ EXPECT_TRUE(dictionary_value->GetString("url", &string_value));
+ EXPECT_EQ("http://url1/", string_value);
+ EXPECT_TRUE(dictionary_value->GetString("name", &string_value));
+ EXPECT_EQ("name 1", string_value);
+
+ // Check the second element (for the layout "b").
+ ASSERT_TRUE(keyboards->GetDictionary(1, &dictionary_value));
+ EXPECT_TRUE(dictionary_value->GetString("layout", &string_value));
+ EXPECT_EQ("b", string_value);
+ EXPECT_TRUE(dictionary_value->GetString("layoutName", &string_value));
+ EXPECT_FALSE(dictionary_value->GetString("preferredKeyboard", &string_value));
+ ASSERT_TRUE(dictionary_value->GetList("supportedKeyboards", &list_value));
+ ASSERT_EQ(1U, list_value->GetSize());
+ ASSERT_TRUE(list_value->GetDictionary(0, &dictionary_value));
+ EXPECT_TRUE(dictionary_value->GetString("url", &string_value));
+ EXPECT_EQ("http://url2/", string_value);
+ EXPECT_TRUE(dictionary_value->GetString("name", &string_value));
+ EXPECT_EQ("name 2", string_value);
+}
+
+TEST(VirtualKeyboardManagerHandler, TestMultipleKeyboards) {
+ static const char* layouts1[] = { "a", "b" };
+ static const char* layouts2[] = { "c" };
+ static const char* layouts3[] = { "b", "d" };
+ input_method::VirtualKeyboard virtual_keyboard_1(
+ GURL("http://url1/"), "name 1", CreateLayoutSet(layouts1), true);
+ input_method::VirtualKeyboard virtual_keyboard_2(
+ GURL("http://url2/"), "name 2", CreateLayoutSet(layouts2), false);
+ input_method::VirtualKeyboard virtual_keyboard_3(
+ GURL("http://url3/"), "name 3", CreateLayoutSet(layouts3), true);
+
+ input_method::VirtualKeyboardSelector selector;
+ ASSERT_TRUE(selector.AddVirtualKeyboard(
+ virtual_keyboard_1.url(),
+ virtual_keyboard_1.name(),
+ virtual_keyboard_1.supported_layouts(),
+ virtual_keyboard_1.is_system()));
+ ASSERT_TRUE(selector.AddVirtualKeyboard(
+ virtual_keyboard_2.url(),
+ virtual_keyboard_2.name(),
+ virtual_keyboard_2.supported_layouts(),
+ virtual_keyboard_2.is_system()));
+ ASSERT_TRUE(selector.AddVirtualKeyboard(
+ virtual_keyboard_3.url(),
+ virtual_keyboard_3.name(),
+ virtual_keyboard_3.supported_layouts(),
+ virtual_keyboard_3.is_system()));
+
+ const LayoutToKeyboard& layout_to_keyboard = selector.layout_to_keyboard();
+ ASSERT_EQ(arraysize(layouts1) + arraysize(layouts2) + arraysize(layouts3),
+ layout_to_keyboard.size());
+ const UrlToKeyboard& url_to_keyboard = selector.url_to_keyboard();
+ ASSERT_EQ(3U, url_to_keyboard.size());
+
+ scoped_ptr<ListValue> keyboards(Testee::CreateVirtualKeyboardList(
+ layout_to_keyboard, url_to_keyboard, NULL));
+ ASSERT_TRUE(keyboards.get());
+ ASSERT_EQ(4U /* a, b, c, and d */, keyboards->GetSize());
+
+ DictionaryValue* dictionary_value;
+ std::string string_value;
+ ListValue* list_value;
+ bool boolean_value = false;
+
+ // Check the first element (for the layout "a").
+ ASSERT_TRUE(keyboards->GetDictionary(0, &dictionary_value));
+ EXPECT_TRUE(dictionary_value->GetString("layout", &string_value));
+ EXPECT_EQ("a", string_value);
+ EXPECT_TRUE(dictionary_value->GetString("layoutName", &string_value));
+ EXPECT_FALSE(dictionary_value->GetString("preferredKeyboard", &string_value));
+ ASSERT_TRUE(dictionary_value->GetList("supportedKeyboards", &list_value));
+ ASSERT_EQ(1U, list_value->GetSize());
+ ASSERT_TRUE(list_value->GetDictionary(0, &dictionary_value));
+ EXPECT_TRUE(dictionary_value->GetString("url", &string_value));
+ EXPECT_EQ("http://url1/", string_value);
+ EXPECT_TRUE(dictionary_value->GetBoolean("isSystem", &boolean_value));
+ EXPECT_TRUE(boolean_value);
+ EXPECT_TRUE(dictionary_value->GetString("name", &string_value));
+ EXPECT_EQ("name 1", string_value);
+
+ // Check the second element (for the layout "b").
+ ASSERT_TRUE(keyboards->GetDictionary(1, &dictionary_value));
+ EXPECT_TRUE(dictionary_value->GetString("layout", &string_value));
+ EXPECT_EQ("b", string_value);
+ EXPECT_TRUE(dictionary_value->GetString("layoutName", &string_value));
+ EXPECT_FALSE(dictionary_value->GetString("preferredKeyboard", &string_value));
+ ASSERT_TRUE(dictionary_value->GetList("supportedKeyboards", &list_value));
+ ASSERT_EQ(2U, list_value->GetSize()); // keyboard1 and 3.
+ ASSERT_TRUE(list_value->GetDictionary(0, &dictionary_value));
+ EXPECT_TRUE(dictionary_value->GetString("url", &string_value));
+ EXPECT_EQ("http://url1/", string_value);
+ EXPECT_TRUE(dictionary_value->GetBoolean("isSystem", &boolean_value));
+ EXPECT_TRUE(boolean_value);
+ EXPECT_TRUE(dictionary_value->GetString("name", &string_value));
+ EXPECT_EQ("name 1", string_value);
+ ASSERT_TRUE(list_value->GetDictionary(1, &dictionary_value));
+ EXPECT_TRUE(dictionary_value->GetString("url", &string_value));
+ EXPECT_EQ("http://url3/", string_value);
+ EXPECT_TRUE(dictionary_value->GetBoolean("isSystem", &boolean_value));
+ EXPECT_TRUE(boolean_value);
+ EXPECT_TRUE(dictionary_value->GetString("name", &string_value));
+ EXPECT_EQ("name 3", string_value);
+
+ // 3rd.
+ ASSERT_TRUE(keyboards->GetDictionary(2, &dictionary_value));
+ EXPECT_TRUE(dictionary_value->GetString("layout", &string_value));
+ EXPECT_EQ("c", string_value);
+ EXPECT_TRUE(dictionary_value->GetString("layoutName", &string_value));
+ EXPECT_FALSE(dictionary_value->GetString("preferredKeyboard", &string_value));
+ ASSERT_TRUE(dictionary_value->GetList("supportedKeyboards", &list_value));
+ ASSERT_EQ(1U, list_value->GetSize());
+ ASSERT_TRUE(list_value->GetDictionary(0, &dictionary_value));
+ EXPECT_TRUE(dictionary_value->GetString("url", &string_value));
+ EXPECT_EQ("http://url2/", string_value);
+ EXPECT_TRUE(dictionary_value->GetBoolean("isSystem", &boolean_value));
+ EXPECT_FALSE(boolean_value);
+ EXPECT_TRUE(dictionary_value->GetString("name", &string_value));
+ EXPECT_EQ("name 2", string_value);
+
+ // 4th.
+ ASSERT_TRUE(keyboards->GetDictionary(3, &dictionary_value));
+ EXPECT_TRUE(dictionary_value->GetString("layout", &string_value));
+ EXPECT_EQ("d", string_value);
+ EXPECT_TRUE(dictionary_value->GetString("layoutName", &string_value));
+ EXPECT_FALSE(dictionary_value->GetString("preferredKeyboard", &string_value));
+ ASSERT_TRUE(dictionary_value->GetList("supportedKeyboards", &list_value));
+ ASSERT_EQ(1U, list_value->GetSize());
+ ASSERT_TRUE(list_value->GetDictionary(0, &dictionary_value));
+ EXPECT_TRUE(dictionary_value->GetString("url", &string_value));
+ EXPECT_EQ("http://url3/", string_value);
+ EXPECT_TRUE(dictionary_value->GetBoolean("isSystem", &boolean_value));
+ EXPECT_TRUE(boolean_value);
+ EXPECT_TRUE(dictionary_value->GetString("name", &string_value));
+ EXPECT_EQ("name 3", string_value);
+}
+
+} // namespace chromeos
diff --git a/chrome/browser/ui/webui/options2/clear_browser_data_handler.cc b/chrome/browser/ui/webui/options2/clear_browser_data_handler.cc
new file mode 100644
index 0000000..a861780
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/clear_browser_data_handler.cc
@@ -0,0 +1,143 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/webui/options2/clear_browser_data_handler.h"
+
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/string16.h"
+#include "base/values.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/prefs/pref_service.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/common/pref_names.h"
+#include "content/public/browser/notification_details.h"
+#include "grit/chromium_strings.h"
+#include "grit/generated_resources.h"
+#include "grit/locale_settings.h"
+#include "ui/base/l10n/l10n_util.h"
+
+ClearBrowserDataHandler::ClearBrowserDataHandler() : remover_(NULL) {
+}
+
+ClearBrowserDataHandler::~ClearBrowserDataHandler() {
+ if (remover_)
+ remover_->RemoveObserver(this);
+}
+
+void ClearBrowserDataHandler::Initialize() {
+ clear_plugin_lso_data_enabled_.Init(prefs::kClearPluginLSODataEnabled,
+ Profile::FromWebUI(web_ui_)->GetPrefs(),
+ NULL);
+}
+
+void ClearBrowserDataHandler::GetLocalizedValues(
+ DictionaryValue* localized_strings) {
+ DCHECK(localized_strings);
+
+ static OptionsStringResource resources[] = {
+ { "clearBrowserDataLabel", IDS_CLEAR_BROWSING_DATA_LABEL },
+ { "deleteBrowsingHistoryCheckbox", IDS_DEL_BROWSING_HISTORY_CHKBOX },
+ { "deleteDownloadHistoryCheckbox", IDS_DEL_DOWNLOAD_HISTORY_CHKBOX },
+ { "deleteCacheCheckbox", IDS_DEL_CACHE_CHKBOX },
+ { "deleteCookiesCheckbox", IDS_DEL_COOKIES_CHKBOX },
+ { "deleteCookiesFlashCheckbox", IDS_DEL_COOKIES_FLASH_CHKBOX },
+ { "deletePasswordsCheckbox", IDS_DEL_PASSWORDS_CHKBOX },
+ { "deleteFormDataCheckbox", IDS_DEL_FORM_DATA_CHKBOX },
+ { "clearBrowserDataCommit", IDS_CLEAR_BROWSING_DATA_COMMIT },
+ { "flashStorageSettings", IDS_FLASH_STORAGE_SETTINGS },
+ { "flash_storage_url", IDS_FLASH_STORAGE_URL },
+ { "clearDataDeleting", IDS_CLEAR_DATA_DELETING },
+ };
+
+ RegisterStrings(localized_strings, resources, arraysize(resources));
+ RegisterTitle(localized_strings, "clearBrowserDataOverlay",
+ IDS_CLEAR_BROWSING_DATA_TITLE);
+
+ ListValue* time_list = new ListValue;
+ for (int i = 0; i < 5; i++) {
+ string16 label_string;
+ switch (i) {
+ case 0:
+ label_string = l10n_util::GetStringUTF16(IDS_CLEAR_DATA_HOUR);
+ break;
+ case 1:
+ label_string = l10n_util::GetStringUTF16(IDS_CLEAR_DATA_DAY);
+ break;
+ case 2:
+ label_string = l10n_util::GetStringUTF16(IDS_CLEAR_DATA_WEEK);
+ break;
+ case 3:
+ label_string = l10n_util::GetStringUTF16(IDS_CLEAR_DATA_4WEEKS);
+ break;
+ case 4:
+ label_string = l10n_util::GetStringUTF16(IDS_CLEAR_DATA_EVERYTHING);
+ break;
+ }
+ ListValue* option = new ListValue();
+ option->Append(Value::CreateIntegerValue(i));
+ option->Append(Value::CreateStringValue(label_string));
+ time_list->Append(option);
+ }
+ localized_strings->Set("clearBrowserDataTimeList", time_list);
+}
+
+void ClearBrowserDataHandler::RegisterMessages() {
+ // Setup handlers specific to this panel.
+ DCHECK(web_ui_);
+ web_ui_->RegisterMessageCallback("performClearBrowserData",
+ base::Bind(&ClearBrowserDataHandler::HandleClearBrowserData,
+ base::Unretained(this)));
+}
+
+void ClearBrowserDataHandler::HandleClearBrowserData(const ListValue* value) {
+ Profile* profile = Profile::FromWebUI(web_ui_);
+ PrefService* prefs = profile->GetPrefs();
+
+ int remove_mask = 0;
+ if (prefs->GetBoolean(prefs::kDeleteBrowsingHistory))
+ remove_mask |= BrowsingDataRemover::REMOVE_HISTORY;
+ if (prefs->GetBoolean(prefs::kDeleteDownloadHistory))
+ remove_mask |= BrowsingDataRemover::REMOVE_DOWNLOADS;
+ if (prefs->GetBoolean(prefs::kDeleteCache))
+ remove_mask |= BrowsingDataRemover::REMOVE_CACHE;
+ if (prefs->GetBoolean(prefs::kDeleteCookies)) {
+ int site_data_mask = BrowsingDataRemover::REMOVE_SITE_DATA;
+ // Don't try to clear LSO data if it's not supported.
+ if (!*clear_plugin_lso_data_enabled_)
+ site_data_mask &= ~BrowsingDataRemover::REMOVE_PLUGIN_DATA;
+ remove_mask |= site_data_mask;
+ }
+ if (prefs->GetBoolean(prefs::kDeletePasswords))
+ remove_mask |= BrowsingDataRemover::REMOVE_PASSWORDS;
+ if (prefs->GetBoolean(prefs::kDeleteFormData))
+ remove_mask |= BrowsingDataRemover::REMOVE_FORM_DATA;
+
+ int period_selected = prefs->GetInteger(prefs::kDeleteTimePeriod);
+
+ base::FundamentalValue state(true);
+ web_ui_->CallJavascriptFunction("ClearBrowserDataOverlay.setClearingState",
+ state);
+
+ // If we are still observing a previous data remover, we need to stop
+ // observing.
+ if (remover_)
+ remover_->RemoveObserver(this);
+
+ // BrowsingDataRemover deletes itself when done.
+ remover_ = new BrowsingDataRemover(profile,
+ static_cast<BrowsingDataRemover::TimePeriod>(period_selected),
+ base::Time());
+ remover_->AddObserver(this);
+ remover_->Remove(remove_mask);
+}
+
+void ClearBrowserDataHandler::OnBrowsingDataRemoverDone() {
+ // No need to remove ourselves as an observer as BrowsingDataRemover deletes
+ // itself after we return.
+ remover_ = NULL;
+ DCHECK(web_ui_);
+ web_ui_->CallJavascriptFunction("ClearBrowserDataOverlay.doneClearing");
+}
diff --git a/chrome/browser/ui/webui/options2/clear_browser_data_handler.h b/chrome/browser/ui/webui/options2/clear_browser_data_handler.h
new file mode 100644
index 0000000..0823158
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/clear_browser_data_handler.h
@@ -0,0 +1,45 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS2_CLEAR_BROWSER_DATA_HANDLER_H_
+#define CHROME_BROWSER_UI_WEBUI_OPTIONS2_CLEAR_BROWSER_DATA_HANDLER_H_
+#pragma once
+
+#include "chrome/browser/browsing_data_remover.h"
+#include "chrome/browser/prefs/pref_member.h"
+#include "chrome/browser/ui/webui/options2/options_ui2.h"
+
+// Clear browser data handler page UI handler.
+class ClearBrowserDataHandler : public OptionsPage2UIHandler,
+ public BrowsingDataRemover::Observer {
+ public:
+ ClearBrowserDataHandler();
+ virtual ~ClearBrowserDataHandler();
+
+ // OptionsPage2UIHandler implementation.
+ virtual void Initialize() OVERRIDE;
+
+ virtual void GetLocalizedValues(DictionaryValue* localized_strings) OVERRIDE;
+
+ // WebUIMessageHandler implementation.
+ virtual void RegisterMessages() OVERRIDE;
+
+ private:
+ // Javascript callback to start clearing data.
+ void HandleClearBrowserData(const ListValue* value);
+
+ // Callback from BrowsingDataRemover. Closes the dialog.
+ virtual void OnBrowsingDataRemoverDone() OVERRIDE;
+
+ // If non-null it means removal is in progress. BrowsingDataRemover takes care
+ // of deleting itself when done.
+ BrowsingDataRemover* remover_;
+
+ // Keeps track of whether clearing LSO data is supported.
+ BooleanPrefMember clear_plugin_lso_data_enabled_;
+
+ DISALLOW_COPY_AND_ASSIGN(ClearBrowserDataHandler);
+};
+
+#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS2_CLEAR_BROWSER_DATA_HANDLER_H_
diff --git a/chrome/browser/ui/webui/options2/content_options_browsertest.js b/chrome/browser/ui/webui/options2/content_options_browsertest.js
new file mode 100644
index 0000000..605660a
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/content_options_browsertest.js
@@ -0,0 +1,24 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * TestFixture for content options WebUI testing.
+ * @extends {testing.Test}
+ * @constructor
+ **/
+function ContentOptionsWebUITest() {}
+
+ContentOptionsWebUITest.prototype = {
+ __proto__: testing.Test.prototype,
+
+ /**
+ * Browse to content options.
+ **/
+ browsePreload: 'chrome://settings/content',
+};
+
+// Test opening the content options has correct location.
+TEST_F('ContentOptionsWebUITest', 'testOpenContentOptions', function() {
+ assertEquals(this.browsePreload, document.location.href);
+});
diff --git a/chrome/browser/ui/webui/options2/content_settings_exception_area_browsertest.js b/chrome/browser/ui/webui/options2/content_settings_exception_area_browsertest.js
new file mode 100644
index 0000000..33b3fd2
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/content_settings_exception_area_browsertest.js
@@ -0,0 +1,26 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * TestFixture for content settings exception area WebUI testing.
+ * @extends {testing.Test}
+ * @constructor
+ **/
+function ContentSettingsExceptionAreaWebUITest() {}
+
+ContentSettingsExceptionAreaWebUITest.prototype = {
+ __proto__: testing.Test.prototype,
+
+ /**
+ * Browse to the content settings exception area.
+ **/
+ browsePreload: 'chrome://settings/contentExceptions',
+};
+
+// Test opening the content settings exception area has correct location.
+TEST_F('ContentSettingsExceptionAreaWebUITest',
+ 'testOpenContentSettingsExceptionArea',
+ function() {
+ assertEquals(this.browsePreload, document.location.href);
+ });
diff --git a/chrome/browser/ui/webui/options2/content_settings_handler.cc b/chrome/browser/ui/webui/options2/content_settings_handler.cc
new file mode 100644
index 0000000..67fb223
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/content_settings_handler.cc
@@ -0,0 +1,879 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/webui/options2/content_settings_handler.h"
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/command_line.h"
+#include "base/utf_string_conversions.h"
+#include "base/values.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/content_settings/content_settings_details.h"
+#include "chrome/browser/content_settings/content_settings_utils.h"
+#include "chrome/browser/content_settings/host_content_settings_map.h"
+#include "chrome/browser/custom_handlers/protocol_handler_registry.h"
+#include "chrome/browser/notifications/desktop_notification_service.h"
+#include "chrome/browser/notifications/desktop_notification_service_factory.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/browser_list.h"
+#include "chrome/common/chrome_notification_types.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/common/content_settings.h"
+#include "chrome/common/content_settings_pattern.h"
+#include "chrome/common/pref_names.h"
+#include "chrome/common/url_constants.h"
+#include "content/browser/tab_contents/tab_contents.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/notification_source.h"
+#include "content/public/browser/notification_types.h"
+#include "content/public/browser/user_metrics.h"
+#include "content/public/common/content_switches.h"
+#include "grit/generated_resources.h"
+#include "grit/locale_settings.h"
+#include "ui/base/l10n/l10n_util.h"
+
+using content::UserMetricsAction;
+
+namespace {
+
+struct ContentSettingsTypeNameEntry {
+ ContentSettingsType type;
+ const char* name;
+};
+
+typedef std::map<ContentSettingsPattern, ContentSetting> OnePatternSettings;
+typedef std::map<ContentSettingsPattern, OnePatternSettings>
+ AllPatternsSettings;
+
+const char* kDisplayPattern = "displayPattern";
+const char* kSetting = "setting";
+const char* kOrigin = "origin";
+const char* kSource = "source";
+const char* kEmbeddingOrigin = "embeddingOrigin";
+
+const ContentSettingsTypeNameEntry kContentSettingsTypeGroupNames[] = {
+ {CONTENT_SETTINGS_TYPE_COOKIES, "cookies"},
+ {CONTENT_SETTINGS_TYPE_IMAGES, "images"},
+ {CONTENT_SETTINGS_TYPE_JAVASCRIPT, "javascript"},
+ {CONTENT_SETTINGS_TYPE_PLUGINS, "plugins"},
+ {CONTENT_SETTINGS_TYPE_POPUPS, "popups"},
+ {CONTENT_SETTINGS_TYPE_GEOLOCATION, "location"},
+ {CONTENT_SETTINGS_TYPE_NOTIFICATIONS, "notifications"},
+ {CONTENT_SETTINGS_TYPE_INTENTS, "intents"},
+ {CONTENT_SETTINGS_TYPE_AUTO_SELECT_CERTIFICATE, "auto-select-certificate"},
+ {CONTENT_SETTINGS_TYPE_FULLSCREEN, "fullscreen"},
+ {CONTENT_SETTINGS_TYPE_MOUSELOCK, "mouselock"},
+};
+COMPILE_ASSERT(arraysize(kContentSettingsTypeGroupNames) ==
+ CONTENT_SETTINGS_NUM_TYPES,
+ MISSING_CONTENT_SETTINGS_TYPE);
+
+ContentSettingsType ContentSettingsTypeFromGroupName(const std::string& name) {
+ for (size_t i = 0; i < arraysize(kContentSettingsTypeGroupNames); ++i) {
+ if (name == kContentSettingsTypeGroupNames[i].name)
+ return kContentSettingsTypeGroupNames[i].type;
+ }
+
+ NOTREACHED() << name << " is not a recognized content settings type.";
+ return CONTENT_SETTINGS_TYPE_DEFAULT;
+}
+
+std::string ContentSettingToString(ContentSetting setting) {
+ switch (setting) {
+ case CONTENT_SETTING_ALLOW:
+ return "allow";
+ case CONTENT_SETTING_ASK:
+ return "ask";
+ case CONTENT_SETTING_BLOCK:
+ return "block";
+ case CONTENT_SETTING_SESSION_ONLY:
+ return "session";
+ case CONTENT_SETTING_DEFAULT:
+ return "default";
+ case CONTENT_SETTING_NUM_SETTINGS:
+ NOTREACHED();
+ }
+
+ return "";
+}
+
+ContentSetting ContentSettingFromString(const std::string& name) {
+ if (name == "allow")
+ return CONTENT_SETTING_ALLOW;
+ if (name == "ask")
+ return CONTENT_SETTING_ASK;
+ if (name == "block")
+ return CONTENT_SETTING_BLOCK;
+ if (name == "session")
+ return CONTENT_SETTING_SESSION_ONLY;
+
+ NOTREACHED() << name << " is not a recognized content setting.";
+ return CONTENT_SETTING_DEFAULT;
+}
+
+std::string GeolocationExceptionToString(
+ const ContentSettingsPattern& origin,
+ const ContentSettingsPattern& embedding_origin) {
+ if (origin == embedding_origin)
+ return origin.ToString();
+
+ // TODO(estade): the page needs to use CSS to indent the string.
+ std::string indent(" ");
+
+ if (embedding_origin == ContentSettingsPattern::Wildcard()) {
+ // NOTE: As long as the user cannot add/edit entries from the exceptions
+ // dialog, it's impossible to actually have a non-default setting for some
+ // origin "embedded on any other site", so this row will never appear. If
+ // we add the ability to add/edit exceptions, we'll need to decide when to
+ // display this and how "removing" it will function.
+ return indent +
+ l10n_util::GetStringUTF8(IDS_EXCEPTIONS_GEOLOCATION_EMBEDDED_ANY_OTHER);
+ }
+
+ return indent + l10n_util::GetStringFUTF8(
+ IDS_EXCEPTIONS_GEOLOCATION_EMBEDDED_ON_HOST,
+ UTF8ToUTF16(embedding_origin.ToString()));
+}
+
+// Create a DictionaryValue* that will act as a data source for a single row
+// in a HostContentSettingsMap-controlled exceptions table (e.g., cookies).
+// Ownership of the pointer is passed to the caller.
+DictionaryValue* GetExceptionForPage(
+ const ContentSettingsPattern& pattern,
+ ContentSetting setting,
+ std::string provider_name) {
+ DictionaryValue* exception = new DictionaryValue();
+ exception->SetString(kDisplayPattern, pattern.ToString());
+ exception->SetString(kSetting, ContentSettingToString(setting));
+ exception->SetString(kSource, provider_name);
+ return exception;
+}
+
+// Create a DictionaryValue* that will act as a data source for a single row
+// in the Geolocation exceptions table. Ownership of the pointer is passed to
+// the caller.
+DictionaryValue* GetGeolocationExceptionForPage(
+ const ContentSettingsPattern& origin,
+ const ContentSettingsPattern& embedding_origin,
+ ContentSetting setting) {
+ DictionaryValue* exception = new DictionaryValue();
+ exception->SetString(kDisplayPattern,
+ GeolocationExceptionToString(origin, embedding_origin));
+ exception->SetString(kSetting, ContentSettingToString(setting));
+ exception->SetString(kOrigin, origin.ToString());
+ exception->SetString(kEmbeddingOrigin, embedding_origin.ToString());
+ return exception;
+}
+
+// Create a DictionaryValue* that will act as a data source for a single row
+// in the desktop notifications exceptions table. Ownership of the pointer is
+// passed to the caller.
+DictionaryValue* GetNotificationExceptionForPage(
+ const ContentSettingsPattern& pattern,
+ ContentSetting setting,
+ const std::string& provider_name) {
+ DictionaryValue* exception = new DictionaryValue();
+ exception->SetString(kDisplayPattern, pattern.ToString());
+ exception->SetString(kSetting, ContentSettingToString(setting));
+ exception->SetString(kOrigin, pattern.ToString());
+ exception->SetString(kSource, provider_name);
+ return exception;
+}
+
+} // namespace
+
+ContentSettingsHandler::ContentSettingsHandler() {
+}
+
+ContentSettingsHandler::~ContentSettingsHandler() {
+}
+
+void ContentSettingsHandler::GetLocalizedValues(
+ DictionaryValue* localized_strings) {
+ DCHECK(localized_strings);
+
+ static OptionsStringResource resources[] = {
+ { "content_exceptions", IDS_COOKIES_EXCEPTIONS_BUTTON },
+ { "allowException", IDS_EXCEPTIONS_ALLOW_BUTTON },
+ { "blockException", IDS_EXCEPTIONS_BLOCK_BUTTON },
+ { "sessionException", IDS_EXCEPTIONS_SESSION_ONLY_BUTTON },
+ { "askException", IDS_EXCEPTIONS_ASK_BUTTON },
+ { "addExceptionRow", IDS_EXCEPTIONS_ADD_BUTTON },
+ { "removeExceptionRow", IDS_EXCEPTIONS_REMOVE_BUTTON },
+ { "editExceptionRow", IDS_EXCEPTIONS_EDIT_BUTTON },
+ { "otr_exceptions_explanation", IDS_EXCEPTIONS_OTR_LABEL },
+ { "examplePattern", IDS_EXCEPTIONS_PATTERN_EXAMPLE },
+ { "addNewExceptionInstructions", IDS_EXCEPTIONS_ADD_NEW_INSTRUCTIONS },
+ { "manage_exceptions", IDS_EXCEPTIONS_MANAGE },
+ { "manage_handlers", IDS_HANDLERS_MANAGE },
+ { "exceptionPatternHeader", IDS_EXCEPTIONS_PATTERN_HEADER },
+ { "exceptionBehaviorHeader", IDS_EXCEPTIONS_ACTION_HEADER },
+ // Cookies filter.
+ { "cookies_tab_label", IDS_COOKIES_TAB_LABEL },
+ { "cookies_header", IDS_COOKIES_HEADER },
+ { "cookies_allow", IDS_COOKIES_ALLOW_RADIO },
+ { "cookies_block", IDS_COOKIES_BLOCK_RADIO },
+ { "cookies_session_only", IDS_COOKIES_SESSION_ONLY_RADIO },
+ { "cookies_block_3rd_party", IDS_COOKIES_BLOCK_3RDPARTY_CHKBOX },
+ { "cookies_clear_when_close", IDS_COOKIES_CLEAR_WHEN_CLOSE_CHKBOX },
+ { "cookies_lso_clear_when_close", IDS_COOKIES_LSO_CLEAR_WHEN_CLOSE_CHKBOX },
+ { "cookies_show_cookies", IDS_COOKIES_SHOW_COOKIES_BUTTON },
+ { "flash_storage_settings", IDS_FLASH_STORAGE_SETTINGS },
+ { "flash_storage_url", IDS_FLASH_STORAGE_URL },
+ // Image filter.
+ { "images_tab_label", IDS_IMAGES_TAB_LABEL },
+ { "images_header", IDS_IMAGES_HEADER },
+ { "images_allow", IDS_IMAGES_LOAD_RADIO },
+ { "images_block", IDS_IMAGES_NOLOAD_RADIO },
+ // JavaScript filter.
+ { "javascript_tab_label", IDS_JAVASCRIPT_TAB_LABEL },
+ { "javascript_header", IDS_JAVASCRIPT_HEADER },
+ { "javascript_allow", IDS_JS_ALLOW_RADIO },
+ { "javascript_block", IDS_JS_DONOTALLOW_RADIO },
+ // Plug-ins filter.
+ { "plugins_tab_label", IDS_PLUGIN_TAB_LABEL },
+ { "plugins_header", IDS_PLUGIN_HEADER },
+ { "plugins_ask", IDS_PLUGIN_ASK_RADIO },
+ { "plugins_allow", IDS_PLUGIN_LOAD_RADIO },
+ { "plugins_block", IDS_PLUGIN_NOLOAD_RADIO },
+ { "disableIndividualPlugins", IDS_PLUGIN_SELECTIVE_DISABLE },
+ // Pop-ups filter.
+ { "popups_tab_label", IDS_POPUP_TAB_LABEL },
+ { "popups_header", IDS_POPUP_HEADER },
+ { "popups_allow", IDS_POPUP_ALLOW_RADIO },
+ { "popups_block", IDS_POPUP_BLOCK_RADIO },
+ // Location filter.
+ { "location_tab_label", IDS_GEOLOCATION_TAB_LABEL },
+ { "location_header", IDS_GEOLOCATION_HEADER },
+ { "location_allow", IDS_GEOLOCATION_ALLOW_RADIO },
+ { "location_ask", IDS_GEOLOCATION_ASK_RADIO },
+ { "location_block", IDS_GEOLOCATION_BLOCK_RADIO },
+ // Notifications filter.
+ { "notifications_tab_label", IDS_NOTIFICATIONS_TAB_LABEL },
+ { "notifications_header", IDS_NOTIFICATIONS_HEADER },
+ { "notifications_allow", IDS_NOTIFICATIONS_ALLOW_RADIO },
+ { "notifications_ask", IDS_NOTIFICATIONS_ASK_RADIO },
+ { "notifications_block", IDS_NOTIFICATIONS_BLOCK_RADIO },
+ // Intents filter.
+ { "intentsTabLabel", IDS_INTENTS_TAB_LABEL },
+ { "intentsAllow", IDS_INTENTS_ALLOW_RADIO },
+ { "intentsAsk", IDS_INTENTS_ASK_RADIO },
+ { "intentsBlock", IDS_INTENTS_BLOCK_RADIO },
+ { "intents_header", IDS_INTENTS_HEADER },
+ // Fullscreen filter.
+ { "fullscreen_tab_label", IDS_FULLSCREEN_TAB_LABEL },
+ { "fullscreen_header", IDS_FULLSCREEN_HEADER },
+ // Mouse Lock filter.
+ { "mouselock_tab_label", IDS_MOUSE_LOCK_TAB_LABEL },
+ { "mouselock_header", IDS_MOUSE_LOCK_HEADER },
+ { "mouselock_allow", IDS_MOUSE_LOCK_ALLOW_RADIO },
+ { "mouselock_ask", IDS_MOUSE_LOCK_ASK_RADIO },
+ { "mouselock_block", IDS_MOUSE_LOCK_BLOCK_RADIO },
+ };
+
+ RegisterStrings(localized_strings, resources, arraysize(resources));
+ RegisterTitle(localized_strings, "contentSettingsPage",
+ IDS_CONTENT_SETTINGS_TITLE);
+ localized_strings->SetBoolean("enable_click_to_play",
+ CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kEnableClickToPlay));
+ localized_strings->SetBoolean("enable_web_intents",
+ CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kEnableWebIntents));
+}
+
+void ContentSettingsHandler::Initialize() {
+ notification_registrar_.Add(
+ this, chrome::NOTIFICATION_PROFILE_CREATED,
+ content::NotificationService::AllSources());
+ notification_registrar_.Add(
+ this, chrome::NOTIFICATION_PROFILE_DESTROYED,
+ content::NotificationService::AllSources());
+
+ UpdateHandlersEnabledRadios();
+ UpdateAllExceptionsViewsFromModel();
+ notification_registrar_.Add(
+ this, chrome::NOTIFICATION_CONTENT_SETTINGS_CHANGED,
+ content::NotificationService::AllSources());
+ notification_registrar_.Add(
+ this, chrome::NOTIFICATION_DESKTOP_NOTIFICATION_SETTINGS_CHANGED,
+ content::NotificationService::AllSources());
+ Profile* profile = Profile::FromWebUI(web_ui_);
+ notification_registrar_.Add(
+ this, chrome::NOTIFICATION_PROTOCOL_HANDLER_REGISTRY_CHANGED,
+ content::Source<Profile>(profile));
+
+ PrefService* prefs = profile->GetPrefs();
+ pref_change_registrar_.Init(prefs);
+ pref_change_registrar_.Add(prefs::kGeolocationContentSettings, this);
+}
+
+void ContentSettingsHandler::Observe(
+ int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) {
+ switch (type) {
+ case chrome::NOTIFICATION_PROFILE_DESTROYED: {
+ if (content::Source<Profile>(source).ptr()->IsOffTheRecord()) {
+ web_ui_->CallJavascriptFunction(
+ "ContentSettingsExceptionsArea.OTRProfileDestroyed");
+ }
+ break;
+ }
+
+ case chrome::NOTIFICATION_PROFILE_CREATED: {
+ if (content::Source<Profile>(source).ptr()->IsOffTheRecord())
+ UpdateAllOTRExceptionsViewsFromModel();
+ break;
+ }
+
+ case chrome::NOTIFICATION_CONTENT_SETTINGS_CHANGED: {
+ // Filter out notifications from other profiles.
+ HostContentSettingsMap* map =
+ content::Source<HostContentSettingsMap>(source).ptr();
+ if (map != GetContentSettingsMap() &&
+ map != GetOTRContentSettingsMap())
+ break;
+
+ const ContentSettingsDetails* settings_details =
+ content::Details<const ContentSettingsDetails>(details).ptr();
+
+ // TODO(estade): we pretend update_all() is always true.
+ if (settings_details->update_all_types())
+ UpdateAllExceptionsViewsFromModel();
+ else
+ UpdateExceptionsViewFromModel(settings_details->type());
+ break;
+ }
+
+ case chrome::NOTIFICATION_PREF_CHANGED: {
+ const std::string& pref_name =
+ *content::Details<std::string>(details).ptr();
+ if (pref_name == prefs::kGeolocationContentSettings)
+ UpdateGeolocationExceptionsView();
+ break;
+ }
+
+ case chrome::NOTIFICATION_DESKTOP_NOTIFICATION_SETTINGS_CHANGED: {
+ UpdateNotificationExceptionsView();
+ break;
+ }
+
+ case chrome::NOTIFICATION_PROTOCOL_HANDLER_REGISTRY_CHANGED: {
+ UpdateHandlersEnabledRadios();
+ break;
+ }
+
+ default:
+ OptionsPage2UIHandler::Observe(type, source, details);
+ }
+}
+
+void ContentSettingsHandler::UpdateSettingDefaultFromModel(
+ ContentSettingsType type) {
+ DictionaryValue filter_settings;
+ std::string provider_id;
+ filter_settings.SetString(ContentSettingsTypeToGroupName(type) + ".value",
+ GetSettingDefaultFromModel(type, &provider_id));
+ filter_settings.SetString(
+ ContentSettingsTypeToGroupName(type) + ".managedBy",
+ provider_id);
+
+ web_ui_->CallJavascriptFunction(
+ "ContentSettings.setContentFilterSettingsValue", filter_settings);
+}
+
+std::string ContentSettingsHandler::GetSettingDefaultFromModel(
+ ContentSettingsType type, std::string* provider_id) {
+ Profile* profile = Profile::FromWebUI(web_ui_);
+ ContentSetting default_setting;
+ if (type == CONTENT_SETTINGS_TYPE_NOTIFICATIONS) {
+ default_setting =
+ DesktopNotificationServiceFactory::GetForProfile(profile)->
+ GetDefaultContentSetting(provider_id);
+ } else {
+ default_setting =
+ profile->GetHostContentSettingsMap()->
+ GetDefaultContentSetting(type, provider_id);
+ }
+
+ return ContentSettingToString(default_setting);
+}
+
+void ContentSettingsHandler::UpdateHandlersEnabledRadios() {
+#if defined(ENABLE_REGISTER_PROTOCOL_HANDLER)
+ DCHECK(web_ui_);
+ base::FundamentalValue handlers_enabled(
+ GetProtocolHandlerRegistry()->enabled());
+
+ web_ui_->CallJavascriptFunction("ContentSettings.updateHandlersEnabledRadios",
+ handlers_enabled);
+#endif // defined(ENABLE_REGISTER_PROTOCOL_HANDLER)
+}
+
+void ContentSettingsHandler::UpdateAllExceptionsViewsFromModel() {
+ for (int type = CONTENT_SETTINGS_TYPE_DEFAULT + 1;
+ type < CONTENT_SETTINGS_NUM_TYPES; ++type) {
+ // The content settings type CONTENT_SETTINGS_TYPE_AUTO_SELECT_CERTIFICATE
+ // is supposed to be set by policy only. Hence there is no user facing UI
+ // for this content type and we skip it here.
+ if (type == CONTENT_SETTINGS_TYPE_AUTO_SELECT_CERTIFICATE)
+ continue;
+ UpdateExceptionsViewFromModel(static_cast<ContentSettingsType>(type));
+ }
+}
+
+void ContentSettingsHandler::UpdateAllOTRExceptionsViewsFromModel() {
+ for (int type = CONTENT_SETTINGS_TYPE_DEFAULT + 1;
+ type < CONTENT_SETTINGS_NUM_TYPES; ++type) {
+ UpdateOTRExceptionsViewFromModel(static_cast<ContentSettingsType>(type));
+ }
+}
+
+void ContentSettingsHandler::UpdateExceptionsViewFromModel(
+ ContentSettingsType type) {
+ // Skip updating intents unless it's enabled from the command line.
+ if (type == CONTENT_SETTINGS_TYPE_INTENTS &&
+ !CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kEnableWebIntents))
+ return;
+
+ switch (type) {
+ case CONTENT_SETTINGS_TYPE_GEOLOCATION:
+ UpdateGeolocationExceptionsView();
+ break;
+ case CONTENT_SETTINGS_TYPE_NOTIFICATIONS:
+ UpdateNotificationExceptionsView();
+ break;
+ default:
+ UpdateExceptionsViewFromHostContentSettingsMap(type);
+ break;
+ }
+}
+
+void ContentSettingsHandler::UpdateOTRExceptionsViewFromModel(
+ ContentSettingsType type) {
+ switch (type) {
+ case CONTENT_SETTINGS_TYPE_GEOLOCATION:
+ case CONTENT_SETTINGS_TYPE_NOTIFICATIONS:
+ case CONTENT_SETTINGS_TYPE_INTENTS:
+ case CONTENT_SETTINGS_TYPE_AUTO_SELECT_CERTIFICATE:
+ break;
+ default:
+ UpdateExceptionsViewFromOTRHostContentSettingsMap(type);
+ break;
+ }
+}
+
+void ContentSettingsHandler::UpdateGeolocationExceptionsView() {
+ Profile* profile = Profile::FromWebUI(web_ui_);
+ HostContentSettingsMap* map = profile->GetHostContentSettingsMap();
+
+ ContentSettingsForOneType all_settings;
+ map->GetSettingsForOneType(
+ CONTENT_SETTINGS_TYPE_GEOLOCATION,
+ std::string(),
+ &all_settings);
+
+ // Group geolocation settings by primary_pattern.
+ AllPatternsSettings all_patterns_settings;
+ for (ContentSettingsForOneType::iterator i =
+ all_settings.begin();
+ i != all_settings.end();
+ ++i) {
+ // Don't add default settings.
+ if (i->primary_pattern == ContentSettingsPattern::Wildcard() &&
+ i->secondary_pattern == ContentSettingsPattern::Wildcard() &&
+ i->source != "preferences") {
+ continue;
+ }
+ all_patterns_settings[i->primary_pattern][i->secondary_pattern] =
+ i->setting;
+ }
+
+ ListValue exceptions;
+ for (AllPatternsSettings::iterator i = all_patterns_settings.begin();
+ i != all_patterns_settings.end();
+ ++i) {
+ const ContentSettingsPattern& primary_pattern = i->first;
+ const OnePatternSettings& one_settings = i->second;
+
+ OnePatternSettings::const_iterator parent =
+ one_settings.find(primary_pattern);
+
+ // Add the "parent" entry for the non-embedded setting.
+ ContentSetting parent_setting =
+ parent == one_settings.end() ? CONTENT_SETTING_DEFAULT : parent->second;
+ exceptions.Append(GetGeolocationExceptionForPage(primary_pattern,
+ primary_pattern,
+ parent_setting));
+
+ // Add the "children" for any embedded settings.
+ for (OnePatternSettings::const_iterator j = one_settings.begin();
+ j != one_settings.end();
+ ++j) {
+ // Skip the non-embedded setting which we already added above.
+ if (j == parent)
+ continue;
+
+ exceptions.Append(
+ GetGeolocationExceptionForPage(primary_pattern, j->first, j->second));
+ }
+ }
+
+ StringValue type_string(
+ ContentSettingsTypeToGroupName(CONTENT_SETTINGS_TYPE_GEOLOCATION));
+ web_ui_->CallJavascriptFunction("ContentSettings.setExceptions",
+ type_string, exceptions);
+
+ // This is mainly here to keep this function ideologically parallel to
+ // UpdateExceptionsViewFromHostContentSettingsMap().
+ UpdateSettingDefaultFromModel(CONTENT_SETTINGS_TYPE_GEOLOCATION);
+}
+
+void ContentSettingsHandler::UpdateNotificationExceptionsView() {
+ Profile* profile = Profile::FromWebUI(web_ui_);
+ DesktopNotificationService* service =
+ DesktopNotificationServiceFactory::GetForProfile(profile);
+
+ ContentSettingsForOneType settings;
+ service->GetNotificationsSettings(&settings);
+
+ ListValue exceptions;
+ for (ContentSettingsForOneType::const_iterator i =
+ settings.begin();
+ i != settings.end();
+ ++i) {
+ // Don't add default settings.
+ if (i->primary_pattern == ContentSettingsPattern::Wildcard() &&
+ i->secondary_pattern == ContentSettingsPattern::Wildcard() &&
+ i->source != "preferences") {
+ continue;
+ }
+
+ exceptions.Append(
+ GetNotificationExceptionForPage(i->primary_pattern, i->setting,
+ i->source));
+ }
+
+ StringValue type_string(
+ ContentSettingsTypeToGroupName(CONTENT_SETTINGS_TYPE_NOTIFICATIONS));
+ web_ui_->CallJavascriptFunction("ContentSettings.setExceptions",
+ type_string, exceptions);
+
+ // This is mainly here to keep this function ideologically parallel to
+ // UpdateExceptionsViewFromHostContentSettingsMap().
+ UpdateSettingDefaultFromModel(CONTENT_SETTINGS_TYPE_NOTIFICATIONS);
+}
+
+void ContentSettingsHandler::UpdateExceptionsViewFromHostContentSettingsMap(
+ ContentSettingsType type) {
+ ContentSettingsForOneType entries;
+ GetContentSettingsMap()->GetSettingsForOneType(type, "", &entries);
+
+ ListValue exceptions;
+ for (size_t i = 0; i < entries.size(); ++i) {
+ // Skip default settings from extensions and policy, and the default content
+ // settings; all of them will affect the default setting UI.
+ if (entries[i].primary_pattern == ContentSettingsPattern::Wildcard() &&
+ entries[i].secondary_pattern == ContentSettingsPattern::Wildcard() &&
+ entries[i].source != "preference") {
+ continue;
+ }
+ // The content settings UI does not support secondary content settings
+ // pattern yet. For content settings set through the content settings UI the
+ // secondary pattern is by default a wildcard pattern. Hence users are not
+ // able to modify content settings with a secondary pattern other than the
+ // wildcard pattern. So only show settings that the user is able to modify.
+ // TODO(bauerb): Support a read-only view for those patterns.
+ if (entries[i].secondary_pattern == ContentSettingsPattern::Wildcard()) {
+ exceptions.Append(
+ GetExceptionForPage(entries[i].primary_pattern, entries[i].setting,
+ entries[i].source));
+ } else {
+ LOG(ERROR) << "Secondary content settings patterns are not "
+ << "supported by the content settings UI";
+ }
+ }
+
+ StringValue type_string(ContentSettingsTypeToGroupName(type));
+ web_ui_->CallJavascriptFunction("ContentSettings.setExceptions", type_string,
+ exceptions);
+
+ UpdateExceptionsViewFromOTRHostContentSettingsMap(type);
+
+ // TODO(koz): The default for fullscreen is always 'ask'.
+ // http://crbug.com/104683
+ if (type == CONTENT_SETTINGS_TYPE_FULLSCREEN)
+ return;
+
+ // The default may also have changed (we won't get a separate notification).
+ // If it hasn't changed, this call will be harmless.
+ UpdateSettingDefaultFromModel(type);
+}
+
+void ContentSettingsHandler::UpdateExceptionsViewFromOTRHostContentSettingsMap(
+ ContentSettingsType type) {
+ const HostContentSettingsMap* otr_settings_map = GetOTRContentSettingsMap();
+ if (!otr_settings_map)
+ return;
+
+ ContentSettingsForOneType otr_entries;
+ otr_settings_map->GetSettingsForOneType(type, "", &otr_entries);
+
+ ListValue otr_exceptions;
+ for (size_t i = 0; i < otr_entries.size(); ++i) {
+ // Off-the-record HostContentSettingsMap contains incognito content settings
+ // as well as normal content settings. Here, we use the incongnito settings
+ // only.
+ if (!otr_entries[i].incognito)
+ continue;
+ // The content settings UI does not support secondary content settings
+ // pattern yet. For content settings set through the content settings UI the
+ // secondary pattern is by default a wildcard pattern. Hence users are not
+ // able to modify content settings with a secondary pattern other than the
+ // wildcard pattern. So only show settings that the user is able to modify.
+ // TODO(bauerb): Support a read-only view for those patterns.
+ if (otr_entries[i].secondary_pattern ==
+ ContentSettingsPattern::Wildcard()) {
+ otr_exceptions.Append(
+ GetExceptionForPage(otr_entries[i].primary_pattern,
+ otr_entries[i].setting,
+ otr_entries[i].source));
+ } else {
+ LOG(ERROR) << "Secondary content settings patterns are not "
+ << "supported by the content settings UI";
+ }
+ }
+
+ StringValue type_string(ContentSettingsTypeToGroupName(type));
+ web_ui_->CallJavascriptFunction("ContentSettings.setOTRExceptions",
+ type_string, otr_exceptions);
+}
+
+void ContentSettingsHandler::RegisterMessages() {
+ web_ui_->RegisterMessageCallback("setContentFilter",
+ base::Bind(&ContentSettingsHandler::SetContentFilter,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("removeException",
+ base::Bind(&ContentSettingsHandler::RemoveException,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("setException",
+ base::Bind(&ContentSettingsHandler::SetException,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("checkExceptionPatternValidity",
+ base::Bind(&ContentSettingsHandler::CheckExceptionPatternValidity,
+ base::Unretained(this)));
+}
+
+void ContentSettingsHandler::SetContentFilter(const ListValue* args) {
+ DCHECK_EQ(2U, args->GetSize());
+ std::string group, setting;
+ if (!(args->GetString(0, &group) &&
+ args->GetString(1, &setting))) {
+ NOTREACHED();
+ return;
+ }
+
+ ContentSetting default_setting = ContentSettingFromString(setting);
+ ContentSettingsType content_type = ContentSettingsTypeFromGroupName(group);
+ if (content_type == CONTENT_SETTINGS_TYPE_NOTIFICATIONS) {
+ Profile* profile = Profile::FromWebUI(web_ui_);
+ DesktopNotificationServiceFactory::GetForProfile(profile)->
+ SetDefaultContentSetting(default_setting);
+ } else {
+ GetContentSettingsMap()->
+ SetDefaultContentSetting(content_type, default_setting);
+ }
+ switch (content_type) {
+ case CONTENT_SETTINGS_TYPE_COOKIES:
+ content::RecordAction(
+ UserMetricsAction("Options_DefaultCookieSettingChanged"));
+ break;
+ case CONTENT_SETTINGS_TYPE_IMAGES:
+ content::RecordAction(
+ UserMetricsAction("Options_DefaultImagesSettingChanged"));
+ break;
+ case CONTENT_SETTINGS_TYPE_JAVASCRIPT:
+ content::RecordAction(
+ UserMetricsAction("Options_DefaultJavaScriptSettingChanged"));
+ break;
+ case CONTENT_SETTINGS_TYPE_PLUGINS:
+ content::RecordAction(
+ UserMetricsAction("Options_DefaultPluginsSettingChanged"));
+ break;
+ case CONTENT_SETTINGS_TYPE_POPUPS:
+ content::RecordAction(
+ UserMetricsAction("Options_DefaultPopupsSettingChanged"));
+ break;
+ case CONTENT_SETTINGS_TYPE_NOTIFICATIONS:
+ content::RecordAction(
+ UserMetricsAction("Options_DefaultNotificationsSettingChanged"));
+ break;
+ case CONTENT_SETTINGS_TYPE_GEOLOCATION:
+ content::RecordAction(
+ UserMetricsAction("Options_DefaultGeolocationSettingChanged"));
+ break;
+ case CONTENT_SETTINGS_TYPE_INTENTS:
+ content::RecordAction(
+ UserMetricsAction("Options_DefaultHandlersSettingChanged"));
+ break;
+ case CONTENT_SETTINGS_TYPE_MOUSELOCK:
+ content::RecordAction(
+ UserMetricsAction("Options_DefaultMouseLockSettingChanged"));
+ break;
+ default:
+ break;
+ }
+}
+
+void ContentSettingsHandler::RemoveException(const ListValue* args) {
+ size_t arg_i = 0;
+ std::string type_string;
+ CHECK(args->GetString(arg_i++, &type_string));
+
+ Profile* profile = Profile::FromWebUI(web_ui_);
+ ContentSettingsType type = ContentSettingsTypeFromGroupName(type_string);
+ if (type == CONTENT_SETTINGS_TYPE_GEOLOCATION) {
+ std::string origin;
+ std::string embedding_origin;
+ bool rv = args->GetString(arg_i++, &origin);
+ DCHECK(rv);
+ rv = args->GetString(arg_i++, &embedding_origin);
+ DCHECK(rv);
+
+ profile->GetHostContentSettingsMap()->
+ SetContentSetting(ContentSettingsPattern::FromString(origin),
+ ContentSettingsPattern::FromString(embedding_origin),
+ CONTENT_SETTINGS_TYPE_GEOLOCATION,
+ std::string(),
+ CONTENT_SETTING_DEFAULT);
+ } else if (type == CONTENT_SETTINGS_TYPE_NOTIFICATIONS) {
+ std::string origin;
+ std::string setting;
+ bool rv = args->GetString(arg_i++, &origin);
+ DCHECK(rv);
+ rv = args->GetString(arg_i++, &setting);
+ DCHECK(rv);
+ ContentSetting content_setting = ContentSettingFromString(setting);
+
+ DCHECK(content_setting == CONTENT_SETTING_ALLOW ||
+ content_setting == CONTENT_SETTING_BLOCK);
+ DesktopNotificationServiceFactory::GetForProfile(profile)->
+ ClearSetting(ContentSettingsPattern::FromString(origin));
+ } else {
+ std::string mode;
+ bool rv = args->GetString(arg_i++, &mode);
+ DCHECK(rv);
+
+ std::string pattern;
+ rv = args->GetString(arg_i++, &pattern);
+ DCHECK(rv);
+
+ HostContentSettingsMap* settings_map =
+ mode == "normal" ? GetContentSettingsMap() :
+ GetOTRContentSettingsMap();
+ // The settings map could be null if the mode was OTR but the OTR profile
+ // got destroyed before we received this message.
+ if (settings_map) {
+ settings_map->SetContentSetting(
+ ContentSettingsPattern::FromString(pattern),
+ ContentSettingsPattern::Wildcard(),
+ ContentSettingsTypeFromGroupName(type_string),
+ "",
+ CONTENT_SETTING_DEFAULT);
+ }
+ }
+}
+
+void ContentSettingsHandler::SetException(const ListValue* args) {
+ size_t arg_i = 0;
+ std::string type_string;
+ CHECK(args->GetString(arg_i++, &type_string));
+ std::string mode;
+ CHECK(args->GetString(arg_i++, &mode));
+ std::string pattern;
+ CHECK(args->GetString(arg_i++, &pattern));
+ std::string setting;
+ CHECK(args->GetString(arg_i++, &setting));
+
+ ContentSettingsType type = ContentSettingsTypeFromGroupName(type_string);
+ if (type == CONTENT_SETTINGS_TYPE_GEOLOCATION ||
+ type == CONTENT_SETTINGS_TYPE_NOTIFICATIONS) {
+ NOTREACHED();
+ return;
+ }
+
+ HostContentSettingsMap* settings_map =
+ mode == "normal" ? GetContentSettingsMap() :
+ GetOTRContentSettingsMap();
+
+ // The settings map could be null if the mode was OTR but the OTR profile
+ // got destroyed before we received this message.
+ if (!settings_map)
+ return;
+ settings_map->SetContentSetting(ContentSettingsPattern::FromString(pattern),
+ ContentSettingsPattern::Wildcard(),
+ type,
+ "",
+ ContentSettingFromString(setting));
+}
+
+void ContentSettingsHandler::CheckExceptionPatternValidity(
+ const ListValue* args) {
+ size_t arg_i = 0;
+ Value* type;
+ CHECK(args->Get(arg_i++, &type));
+ std::string mode_string;
+ CHECK(args->GetString(arg_i++, &mode_string));
+ std::string pattern_string;
+ CHECK(args->GetString(arg_i++, &pattern_string));
+
+ ContentSettingsPattern pattern =
+ ContentSettingsPattern::FromString(pattern_string);
+
+ scoped_ptr<Value> mode_value(Value::CreateStringValue(mode_string));
+ scoped_ptr<Value> pattern_value(Value::CreateStringValue(pattern_string));
+ scoped_ptr<Value> valid_value(Value::CreateBooleanValue(pattern.IsValid()));
+
+ web_ui_->CallJavascriptFunction(
+ "ContentSettings.patternValidityCheckComplete",
+ *type,
+ *mode_value.get(),
+ *pattern_value.get(),
+ *valid_value.get());
+}
+
+// static
+std::string ContentSettingsHandler::ContentSettingsTypeToGroupName(
+ ContentSettingsType type) {
+ for (size_t i = 0; i < arraysize(kContentSettingsTypeGroupNames); ++i) {
+ if (type == kContentSettingsTypeGroupNames[i].type)
+ return kContentSettingsTypeGroupNames[i].name;
+ }
+
+ NOTREACHED();
+ return std::string();
+}
+
+HostContentSettingsMap* ContentSettingsHandler::GetContentSettingsMap() {
+ return Profile::FromWebUI(web_ui_)->GetHostContentSettingsMap();
+}
+
+ProtocolHandlerRegistry* ContentSettingsHandler::GetProtocolHandlerRegistry() {
+ return Profile::FromWebUI(web_ui_)->GetProtocolHandlerRegistry();
+}
+
+HostContentSettingsMap*
+ ContentSettingsHandler::GetOTRContentSettingsMap() {
+ Profile* profile = Profile::FromWebUI(web_ui_);
+ if (profile->HasOffTheRecordProfile())
+ return profile->GetOffTheRecordProfile()->GetHostContentSettingsMap();
+ return NULL;
+}
diff --git a/chrome/browser/ui/webui/options2/content_settings_handler.h b/chrome/browser/ui/webui/options2/content_settings_handler.h
new file mode 100644
index 0000000..665a3ef
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/content_settings_handler.h
@@ -0,0 +1,113 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS2_CONTENT_SETTINGS_HANDLER_H_
+#define CHROME_BROWSER_UI_WEBUI_OPTIONS2_CONTENT_SETTINGS_HANDLER_H_
+#pragma once
+
+#include "chrome/browser/plugin_data_remover_helper.h"
+#include "chrome/browser/prefs/pref_change_registrar.h"
+#include "chrome/browser/ui/webui/options2/options_ui2.h"
+#include "chrome/common/content_settings_types.h"
+#include "content/public/browser/notification_observer.h"
+#include "content/public/browser/notification_registrar.h"
+
+class HostContentSettingsMap;
+class ProtocolHandlerRegistry;
+
+class ContentSettingsHandler : public OptionsPage2UIHandler {
+ public:
+ ContentSettingsHandler();
+ virtual ~ContentSettingsHandler();
+
+ // OptionsPage2UIHandler implementation.
+ virtual void GetLocalizedValues(DictionaryValue* localized_strings) OVERRIDE;
+
+ virtual void Initialize() OVERRIDE;
+
+ virtual void RegisterMessages() OVERRIDE;
+
+ // content::NotificationObserver implementation.
+ virtual void Observe(int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) OVERRIDE;
+
+ // Gets a string identifier for the group name, for use in HTML.
+ static std::string ContentSettingsTypeToGroupName(ContentSettingsType type);
+
+ private:
+ // Functions that call into the page -----------------------------------------
+
+ // Updates the page with the default settings (allow, ask, block, etc.)
+ void UpdateSettingDefaultFromModel(ContentSettingsType type);
+
+ // Clobbers and rebuilds the specific content setting type exceptions table.
+ void UpdateExceptionsViewFromModel(ContentSettingsType type);
+ // Clobbers and rebuilds the specific content setting type exceptions
+ // OTR table.
+ void UpdateOTRExceptionsViewFromModel(ContentSettingsType type);
+ // Clobbers and rebuilds all the exceptions tables in the page (both normal
+ // and OTR tables).
+ void UpdateAllExceptionsViewsFromModel();
+ // As above, but only OTR tables.
+ void UpdateAllOTRExceptionsViewsFromModel();
+ // Clobbers and rebuilds just the geolocation exception table.
+ void UpdateGeolocationExceptionsView();
+ // Clobbers and rebuilds just the desktop notification exception table.
+ void UpdateNotificationExceptionsView();
+ // Clobbers and rebuilds an exception table that's managed by the host content
+ // settings map.
+ void UpdateExceptionsViewFromHostContentSettingsMap(ContentSettingsType type);
+ // As above, but acts on the OTR table for the content setting type.
+ void UpdateExceptionsViewFromOTRHostContentSettingsMap(
+ ContentSettingsType type);
+ // Updates the radio buttons for enabling / disabling handlers.
+ void UpdateHandlersEnabledRadios();
+
+ // Callbacks used by the page ------------------------------------------------
+
+ // Sets the default value for a specific content type. |args| includes the
+ // content type and a string describing the new default the user has
+ // chosen.
+ void SetContentFilter(const ListValue* args);
+
+ // Removes the given row from the table. The first entry in |args| is the
+ // content type, and the rest of the arguments depend on the content type
+ // to be removed.
+ void RemoveException(const ListValue* args);
+
+ // Changes the value of an exception. Called after the user is done editing an
+ // exception.
+ void SetException(const ListValue* args);
+
+ // Called to decide whether a given pattern is valid, or if it should be
+ // rejected. Called while the user is editing an exception pattern.
+ void CheckExceptionPatternValidity(const ListValue* args);
+
+ // Utility functions ---------------------------------------------------------
+
+ // Gets the HostContentSettingsMap for the normal profile.
+ HostContentSettingsMap* GetContentSettingsMap();
+
+ // Gets the HostContentSettingsMap for the incognito profile, or NULL if there
+ // is no active incognito session.
+ HostContentSettingsMap* GetOTRContentSettingsMap();
+
+ // Gets the default setting in string form. If |provider_id| is not NULL, the
+ // id of the provider which provided the default setting is assigned to it.
+ std::string GetSettingDefaultFromModel(ContentSettingsType type,
+ std::string* provider_id);
+
+ // Gets the ProtocolHandlerRegistry for the normal profile.
+ ProtocolHandlerRegistry* GetProtocolHandlerRegistry();
+
+ // Member variables ---------------------------------------------------------
+
+ content::NotificationRegistrar notification_registrar_;
+ PrefChangeRegistrar pref_change_registrar_;
+
+ DISALLOW_COPY_AND_ASSIGN(ContentSettingsHandler);
+};
+
+#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS2_CONTENT_SETTINGS_HANDLER_H_
diff --git a/chrome/browser/ui/webui/options2/cookies_view_browsertest.js b/chrome/browser/ui/webui/options2/cookies_view_browsertest.js
new file mode 100644
index 0000000..43213cb
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/cookies_view_browsertest.js
@@ -0,0 +1,24 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * TestFixture for cookies view WebUI testing.
+ * @extends {testing.Test}
+ * @constructor
+ **/
+function CookiesViewWebUITest() {}
+
+CookiesViewWebUITest.prototype = {
+ __proto__: testing.Test.prototype,
+
+ /**
+ * Browse to the cookies view.
+ **/
+ browsePreload: 'chrome://settings/cookies',
+};
+
+// Test opening the cookies view has correct location.
+TEST_F('CookiesViewWebUITest', 'testOpenCookiesView', function() {
+ assertEquals(this.browsePreload, document.location.href);
+});
diff --git a/chrome/browser/ui/webui/options2/cookies_view_handler.cc b/chrome/browser/ui/webui/options2/cookies_view_handler.cc
new file mode 100644
index 0000000..6d86eb2
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/cookies_view_handler.cc
@@ -0,0 +1,222 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/webui/options2/cookies_view_handler.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/utf_string_conversions.h"
+#include "base/values.h"
+#include "chrome/browser/browsing_data_appcache_helper.h"
+#include "chrome/browser/browsing_data_cookie_helper.h"
+#include "chrome/browser/browsing_data_database_helper.h"
+#include "chrome/browser/browsing_data_file_system_helper.h"
+#include "chrome/browser/browsing_data_indexed_db_helper.h"
+#include "chrome/browser/browsing_data_quota_helper.h"
+#include "chrome/browser/browsing_data_local_storage_helper.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/webui/cookies_tree_model_util.h"
+#include "grit/generated_resources.h"
+#include "ui/base/l10n/l10n_util.h"
+
+CookiesViewHandler::CookiesViewHandler() : batch_update_(false) {
+}
+
+CookiesViewHandler::~CookiesViewHandler() {
+}
+
+void CookiesViewHandler::GetLocalizedValues(
+ DictionaryValue* localized_strings) {
+ DCHECK(localized_strings);
+
+ static OptionsStringResource resources[] = {
+ { "label_cookie_name", IDS_COOKIES_COOKIE_NAME_LABEL },
+ { "label_cookie_content", IDS_COOKIES_COOKIE_CONTENT_LABEL },
+ { "label_cookie_domain", IDS_COOKIES_COOKIE_DOMAIN_LABEL },
+ { "label_cookie_path", IDS_COOKIES_COOKIE_PATH_LABEL },
+ { "label_cookie_send_for", IDS_COOKIES_COOKIE_SENDFOR_LABEL },
+ { "label_cookie_accessible_to_script",
+ IDS_COOKIES_COOKIE_ACCESSIBLE_TO_SCRIPT_LABEL },
+ { "label_cookie_created", IDS_COOKIES_COOKIE_CREATED_LABEL },
+ { "label_cookie_expires", IDS_COOKIES_COOKIE_EXPIRES_LABEL },
+ { "label_webdb_desc", IDS_COOKIES_WEB_DATABASE_DESCRIPTION_LABEL },
+ { "label_local_storage_size",
+ IDS_COOKIES_LOCAL_STORAGE_SIZE_ON_DISK_LABEL },
+ { "label_local_storage_last_modified",
+ IDS_COOKIES_LOCAL_STORAGE_LAST_MODIFIED_LABEL },
+ { "label_local_storage_origin", IDS_COOKIES_LOCAL_STORAGE_ORIGIN_LABEL },
+ { "label_indexed_db_size", IDS_COOKIES_LOCAL_STORAGE_SIZE_ON_DISK_LABEL },
+ { "label_indexed_db_last_modified",
+ IDS_COOKIES_LOCAL_STORAGE_LAST_MODIFIED_LABEL },
+ { "label_indexed_db_origin", IDS_COOKIES_LOCAL_STORAGE_ORIGIN_LABEL },
+ { "label_app_cache_manifest",
+ IDS_COOKIES_APPLICATION_CACHE_MANIFEST_LABEL },
+ { "label_cookie_last_accessed", IDS_COOKIES_LAST_ACCESSED_LABEL },
+ { "cookie_domain", IDS_COOKIES_DOMAIN_COLUMN_HEADER },
+ { "cookie_local_data", IDS_COOKIES_DATA_COLUMN_HEADER },
+ { "cookie_singular", IDS_COOKIES_SINGLE_COOKIE },
+ { "cookie_plural", IDS_COOKIES_PLURAL_COOKIES },
+ { "cookie_database_storage", IDS_COOKIES_DATABASE_STORAGE },
+ { "cookie_indexed_db", IDS_COOKIES_INDEXED_DB },
+ { "cookie_local_storage", IDS_COOKIES_LOCAL_STORAGE },
+ { "cookie_app_cache", IDS_COOKIES_APPLICATION_CACHE },
+ { "search_cookies", IDS_COOKIES_SEARCH_COOKIES },
+ { "remove_cookie", IDS_COOKIES_REMOVE_LABEL },
+ { "remove_all_cookie", IDS_COOKIES_REMOVE_ALL_LABEL },
+ { "cookie_file_system", IDS_COOKIES_FILE_SYSTEM },
+ { "label_file_system_origin", IDS_COOKIES_LOCAL_STORAGE_ORIGIN_LABEL },
+ { "label_file_system_temporary_usage",
+ IDS_COOKIES_FILE_SYSTEM_TEMPORARY_USAGE_LABEL },
+ { "label_file_system_persistent_usage",
+ IDS_COOKIES_FILE_SYSTEM_PERSISTENT_USAGE_LABEL },
+ };
+
+ RegisterStrings(localized_strings, resources, arraysize(resources));
+ RegisterTitle(localized_strings, "cookiesViewPage",
+ IDS_COOKIES_WEBSITE_PERMISSIONS_WINDOW_TITLE);
+}
+
+void CookiesViewHandler::RegisterMessages() {
+ web_ui_->RegisterMessageCallback("updateCookieSearchResults",
+ base::Bind(&CookiesViewHandler::UpdateSearchResults,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("removeAllCookies",
+ base::Bind(&CookiesViewHandler::RemoveAll,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("removeCookie",
+ base::Bind(&CookiesViewHandler::Remove,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("loadCookie",
+ base::Bind(&CookiesViewHandler::LoadChildren,
+ base::Unretained(this)));
+}
+
+void CookiesViewHandler::TreeNodesAdded(ui::TreeModel* model,
+ ui::TreeModelNode* parent,
+ int start,
+ int count) {
+ // Skip if there is a batch update in progress.
+ if (batch_update_)
+ return;
+
+ CookieTreeNode* parent_node = cookies_tree_model_->AsNode(parent);
+
+ ListValue* children = new ListValue;
+ cookies_tree_model_util::GetChildNodeList(parent_node, start, count,
+ children);
+
+ ListValue args;
+ args.Append(parent == cookies_tree_model_->GetRoot() ?
+ Value::CreateNullValue() :
+ Value::CreateStringValue(
+ cookies_tree_model_util::GetTreeNodeId(parent_node)));
+ args.Append(Value::CreateIntegerValue(start));
+ args.Append(children);
+ web_ui_->CallJavascriptFunction("CookiesView.onTreeItemAdded", args);
+}
+
+void CookiesViewHandler::TreeNodesRemoved(ui::TreeModel* model,
+ ui::TreeModelNode* parent,
+ int start,
+ int count) {
+ // Skip if there is a batch update in progress.
+ if (batch_update_)
+ return;
+
+ ListValue args;
+ args.Append(parent == cookies_tree_model_->GetRoot() ?
+ Value::CreateNullValue() :
+ Value::CreateStringValue(cookies_tree_model_util::GetTreeNodeId(
+ cookies_tree_model_->AsNode(parent))));
+ args.Append(Value::CreateIntegerValue(start));
+ args.Append(Value::CreateIntegerValue(count));
+ web_ui_->CallJavascriptFunction("CookiesView.onTreeItemRemoved", args);
+}
+
+void CookiesViewHandler::TreeModelBeginBatch(CookiesTreeModel* model) {
+ DCHECK(!batch_update_); // There should be no nested batch begin.
+ batch_update_ = true;
+}
+
+void CookiesViewHandler::TreeModelEndBatch(CookiesTreeModel* model) {
+ DCHECK(batch_update_);
+ batch_update_ = false;
+
+ SendChildren(cookies_tree_model_->GetRoot());
+}
+
+void CookiesViewHandler::EnsureCookiesTreeModelCreated() {
+ if (!cookies_tree_model_.get()) {
+ Profile* profile = Profile::FromWebUI(web_ui_);
+ cookies_tree_model_.reset(new CookiesTreeModel(
+ new BrowsingDataCookieHelper(profile),
+ new BrowsingDataDatabaseHelper(profile),
+ new BrowsingDataLocalStorageHelper(profile),
+ NULL,
+ new BrowsingDataAppCacheHelper(profile),
+ BrowsingDataIndexedDBHelper::Create(profile),
+ BrowsingDataFileSystemHelper::Create(profile),
+ BrowsingDataQuotaHelper::Create(profile),
+ false));
+ cookies_tree_model_->AddCookiesTreeObserver(this);
+ }
+}
+
+void CookiesViewHandler::UpdateSearchResults(const ListValue* args) {
+ std::string query;
+ if (!args->GetString(0, &query)) {
+ return;
+ }
+
+ EnsureCookiesTreeModelCreated();
+
+ cookies_tree_model_->UpdateSearchResults(UTF8ToWide(query));
+}
+
+void CookiesViewHandler::RemoveAll(const ListValue* args) {
+ EnsureCookiesTreeModelCreated();
+ cookies_tree_model_->DeleteAllStoredObjects();
+}
+
+void CookiesViewHandler::Remove(const ListValue* args) {
+ std::string node_path;
+ if (!args->GetString(0, &node_path)) {
+ return;
+ }
+
+ EnsureCookiesTreeModelCreated();
+
+ CookieTreeNode* node = cookies_tree_model_util::GetTreeNodeFromPath(
+ cookies_tree_model_->GetRoot(), node_path);
+ if (node)
+ cookies_tree_model_->DeleteCookieNode(node);
+}
+
+void CookiesViewHandler::LoadChildren(const ListValue* args) {
+ std::string node_path;
+ if (!args->GetString(0, &node_path)) {
+ return;
+ }
+
+ EnsureCookiesTreeModelCreated();
+
+ CookieTreeNode* node = cookies_tree_model_util::GetTreeNodeFromPath(
+ cookies_tree_model_->GetRoot(), node_path);
+ if (node)
+ SendChildren(node);
+}
+
+void CookiesViewHandler::SendChildren(CookieTreeNode* parent) {
+ ListValue* children = new ListValue;
+ cookies_tree_model_util::GetChildNodeList(parent, 0, parent->child_count(),
+ children);
+
+ ListValue args;
+ args.Append(parent == cookies_tree_model_->GetRoot() ?
+ Value::CreateNullValue() :
+ Value::CreateStringValue(cookies_tree_model_util::GetTreeNodeId(parent)));
+ args.Append(children);
+
+ web_ui_->CallJavascriptFunction("CookiesView.loadChildren", args);
+}
diff --git a/chrome/browser/ui/webui/options2/cookies_view_handler.h b/chrome/browser/ui/webui/options2/cookies_view_handler.h
new file mode 100644
index 0000000..5167cf6
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/cookies_view_handler.h
@@ -0,0 +1,69 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS2_COOKIES_VIEW_HANDLER_H_
+#define CHROME_BROWSER_UI_WEBUI_OPTIONS2_COOKIES_VIEW_HANDLER_H_
+#pragma once
+
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "chrome/browser/cookies_tree_model.h"
+#include "chrome/browser/ui/webui/options2/options_ui2.h"
+
+class CookiesViewHandler : public OptionsPage2UIHandler,
+ public CookiesTreeModel::Observer {
+ public:
+ CookiesViewHandler();
+ virtual ~CookiesViewHandler();
+
+ // OptionsPage2UIHandler implementation.
+ virtual void GetLocalizedValues(
+ base::DictionaryValue* localized_strings) OVERRIDE;
+ virtual void RegisterMessages() OVERRIDE;
+
+ // CookiesTreeModel::Observer implementation.
+ virtual void TreeNodesAdded(ui::TreeModel* model,
+ ui::TreeModelNode* parent,
+ int start,
+ int count) OVERRIDE;
+ virtual void TreeNodesRemoved(ui::TreeModel* model,
+ ui::TreeModelNode* parent,
+ int start,
+ int count) OVERRIDE;
+ virtual void TreeNodeChanged(ui::TreeModel* model,
+ ui::TreeModelNode* node) OVERRIDE {}
+ virtual void TreeModelBeginBatch(CookiesTreeModel* model) OVERRIDE;
+ virtual void TreeModelEndBatch(CookiesTreeModel* model) OVERRIDE;
+
+ private:
+ // Creates the CookiesTreeModel if neccessary.
+ void EnsureCookiesTreeModelCreated();
+
+ // Updates search filter for cookies tree model.
+ void UpdateSearchResults(const base::ListValue* args);
+
+ // Remove all sites data.
+ void RemoveAll(const base::ListValue* args);
+
+ // Remove selected sites data.
+ void Remove(const base::ListValue* args);
+
+ // Get the tree node using the tree path info in |args| and call
+ // SendChildren to pass back children nodes data to WebUI.
+ void LoadChildren(const base::ListValue* args);
+
+ // Get children nodes data and pass it to 'CookiesView.loadChildren' to
+ // update the WebUI.
+ void SendChildren(CookieTreeNode* parent);
+
+ // The Cookies Tree model
+ scoped_ptr<CookiesTreeModel> cookies_tree_model_;
+
+ // Flag to indicate whether there is a batch update in progress.
+ bool batch_update_;
+
+ DISALLOW_COPY_AND_ASSIGN(CookiesViewHandler);
+};
+
+#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS2_COOKIES_VIEW_HANDLER_H_
diff --git a/chrome/browser/ui/webui/options2/core_options_handler.cc b/chrome/browser/ui/webui/options2/core_options_handler.cc
new file mode 100644
index 0000000..4140233
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/core_options_handler.cc
@@ -0,0 +1,466 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/webui/options2/core_options_handler.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/json/json_reader.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/string16.h"
+#include "base/string_number_conversions.h"
+#include "base/utf_string_conversions.h"
+#include "base/values.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/google/google_util.h"
+#include "chrome/browser/net/url_fixer_upper.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/common/chrome_notification_types.h"
+#include "chrome/common/pref_names.h"
+#include "chrome/common/url_constants.h"
+#include "content/public/browser/notification_details.h"
+#include "content/public/browser/notification_types.h"
+#include "content/public/browser/user_metrics.h"
+#include "googleurl/src/gurl.h"
+#include "grit/chromium_strings.h"
+#include "grit/generated_resources.h"
+#include "grit/locale_settings.h"
+#include "grit/theme_resources.h"
+#include "ui/base/l10n/l10n_util.h"
+
+using content::UserMetricsAction;
+
+CoreOptionsHandler::CoreOptionsHandler()
+ : handlers_host_(NULL) {
+}
+
+CoreOptionsHandler::~CoreOptionsHandler() {}
+
+void CoreOptionsHandler::Initialize() {
+ clear_plugin_lso_data_enabled_.Init(prefs::kClearPluginLSODataEnabled,
+ Profile::FromWebUI(web_ui_),
+ this);
+ UpdateClearPluginLSOData();
+}
+
+void CoreOptionsHandler::GetLocalizedValues(
+ DictionaryValue* localized_strings) {
+ GetStaticLocalizedValues(localized_strings);
+}
+
+void CoreOptionsHandler::GetStaticLocalizedValues(
+ base::DictionaryValue* localized_strings) {
+ DCHECK(localized_strings);
+ // Main
+ localized_strings->SetString("title",
+ l10n_util::GetStringUTF16(IDS_SETTINGS_TITLE));
+
+ // Managed prefs
+ localized_strings->SetString("policyManagedPrefsBannerText",
+ l10n_util::GetStringUTF16(IDS_OPTIONS_POLICY_MANAGED_PREFS));
+ localized_strings->SetString("extensionManagedPrefsBannerText",
+ l10n_util::GetStringUTF16(IDS_OPTIONS_EXTENSION_MANAGED_PREFS));
+ localized_strings->SetString("policyAndExtensionManagedPrefsBannerText",
+ l10n_util::GetStringUTF16(IDS_OPTIONS_POLICY_EXTENSION_MANAGED_PREFS));
+
+ // Controlled settings bubble.
+ localized_strings->SetString("controlledSettingPolicy",
+ l10n_util::GetStringUTF16(IDS_OPTIONS_CONTROLLED_SETTING_POLICY));
+ localized_strings->SetString("controlledSettingExtension",
+ l10n_util::GetStringUTF16(IDS_OPTIONS_CONTROLLED_SETTING_EXTENSION));
+ localized_strings->SetString("controlledSettingRecommended",
+ l10n_util::GetStringUTF16(IDS_OPTIONS_CONTROLLED_SETTING_RECOMMENDED));
+ localized_strings->SetString("controlledSettingApplyRecommendation",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_CONTROLLED_SETTING_APPLY_RECOMMENDATION));
+
+ // Search
+ RegisterTitle(localized_strings, "searchPage", IDS_OPTIONS_SEARCH_PAGE_TITLE);
+ localized_strings->SetString("searchPlaceholder",
+ l10n_util::GetStringUTF16(IDS_OPTIONS_SEARCH_PLACEHOLDER));
+ localized_strings->SetString("searchPageNoMatches",
+ l10n_util::GetStringUTF16(IDS_OPTIONS_SEARCH_PAGE_NO_MATCHES));
+ localized_strings->SetString("searchPageHelpLabel",
+ l10n_util::GetStringUTF16(IDS_OPTIONS_SEARCH_PAGE_HELP_LABEL));
+ localized_strings->SetString("searchPageHelpTitle",
+ l10n_util::GetStringFUTF16(IDS_OPTIONS_SEARCH_PAGE_HELP_TITLE,
+ l10n_util::GetStringUTF16(IDS_PRODUCT_NAME)));
+ localized_strings->SetString("searchPageHelpURL",
+ google_util::AppendGoogleLocaleParam(
+ GURL(chrome::kChromeHelpURL)).spec());
+
+ // Common
+ localized_strings->SetString("ok",
+ l10n_util::GetStringUTF16(IDS_OK));
+ localized_strings->SetString("cancel",
+ l10n_util::GetStringUTF16(IDS_CANCEL));
+ localized_strings->SetString("learnMore",
+ l10n_util::GetStringUTF16(IDS_LEARN_MORE));
+ localized_strings->SetString("close",
+ l10n_util::GetStringUTF16(IDS_CLOSE));
+}
+
+void CoreOptionsHandler::Uninitialize() {
+ std::string last_pref;
+ for (PreferenceCallbackMap::const_iterator iter = pref_callback_map_.begin();
+ iter != pref_callback_map_.end();
+ ++iter) {
+ if (last_pref != iter->first) {
+ StopObservingPref(iter->first);
+ last_pref = iter->first;
+ }
+ }
+}
+
+WebUIMessageHandler* CoreOptionsHandler::Attach(WebUI* web_ui) {
+ WebUIMessageHandler* result = WebUIMessageHandler::Attach(web_ui);
+ DCHECK(web_ui_);
+ registrar_.Init(Profile::FromWebUI(web_ui_)->GetPrefs());
+ return result;
+}
+
+void CoreOptionsHandler::Observe(int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) {
+ if (type == chrome::NOTIFICATION_PREF_CHANGED) {
+ std::string* pref_name = content::Details<std::string>(details).ptr();
+ if (*pref_name == prefs::kClearPluginLSODataEnabled) {
+ // This preference is stored in Local State, not in the user preferences.
+ UpdateClearPluginLSOData();
+ return;
+ }
+ NotifyPrefChanged(*pref_name, std::string());
+ }
+}
+
+void CoreOptionsHandler::RegisterMessages() {
+ web_ui_->RegisterMessageCallback("coreOptionsInitialize",
+ base::Bind(&CoreOptionsHandler::HandleInitialize,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("fetchPrefs",
+ base::Bind(&CoreOptionsHandler::HandleFetchPrefs,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("observePrefs",
+ base::Bind(&CoreOptionsHandler::HandleObservePrefs,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("setBooleanPref",
+ base::Bind(&CoreOptionsHandler::HandleSetBooleanPref,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("setIntegerPref",
+ base::Bind(&CoreOptionsHandler::HandleSetIntegerPref,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("setDoublePref",
+ base::Bind(&CoreOptionsHandler::HandleSetDoublePref,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("setStringPref",
+ base::Bind(&CoreOptionsHandler::HandleSetStringPref,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("setURLPref",
+ base::Bind(&CoreOptionsHandler::HandleSetURLPref,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("setListPref",
+ base::Bind(&CoreOptionsHandler::HandleSetListPref,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("clearPref",
+ base::Bind(&CoreOptionsHandler::HandleClearPref,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("coreOptionsUserMetricsAction",
+ base::Bind(&CoreOptionsHandler::HandleUserMetricsAction,
+ base::Unretained(this)));
+}
+
+void CoreOptionsHandler::HandleInitialize(const ListValue* args) {
+ DCHECK(handlers_host_);
+ handlers_host_->InitializeHandlers();
+}
+
+base::Value* CoreOptionsHandler::FetchPref(const std::string& pref_name) {
+ PrefService* pref_service = Profile::FromWebUI(web_ui_)->GetPrefs();
+
+ const PrefService::Preference* pref =
+ pref_service->FindPreference(pref_name.c_str());
+ if (!pref)
+ return base::Value::CreateNullValue();
+
+ return CreateValueForPref(pref, NULL);
+}
+
+void CoreOptionsHandler::ObservePref(const std::string& pref_name) {
+ registrar_.Add(pref_name.c_str(), this);
+}
+
+void CoreOptionsHandler::SetPref(const std::string& pref_name,
+ const base::Value* value,
+ const std::string& metric) {
+ PrefService* pref_service = Profile::FromWebUI(web_ui_)->GetPrefs();
+
+ switch (value->GetType()) {
+ case base::Value::TYPE_BOOLEAN:
+ case base::Value::TYPE_INTEGER:
+ case base::Value::TYPE_DOUBLE:
+ case base::Value::TYPE_STRING:
+ pref_service->Set(pref_name.c_str(), *value);
+ break;
+
+ default:
+ NOTREACHED();
+ return;
+ }
+
+ pref_service->ScheduleSavePersistentPrefs();
+
+ ProcessUserMetric(value, metric);
+}
+
+void CoreOptionsHandler::ClearPref(const std::string& pref_name,
+ const std::string& metric) {
+ PrefService* pref_service = Profile::FromWebUI(web_ui_)->GetPrefs();
+ pref_service->ClearPref(pref_name.c_str());
+ pref_service->ScheduleSavePersistentPrefs();
+
+ if (!metric.empty())
+ content::RecordComputedAction(metric);
+}
+
+void CoreOptionsHandler::ProcessUserMetric(const base::Value* value,
+ const std::string& metric) {
+ if (metric.empty())
+ return;
+
+ std::string metric_string = metric;
+ if (value->IsType(base::Value::TYPE_BOOLEAN)) {
+ bool bool_value;
+ CHECK(value->GetAsBoolean(&bool_value));
+ metric_string += bool_value ? "_Enable" : "_Disable";
+ }
+
+ content::RecordComputedAction(metric_string);
+}
+
+void CoreOptionsHandler::NotifyPrefChanged(
+ const std::string& pref_name,
+ const std::string& controlling_pref_name) {
+ const PrefService* pref_service = Profile::FromWebUI(web_ui_)->GetPrefs();
+ const PrefService::Preference* pref =
+ pref_service->FindPreference(pref_name.c_str());
+ if (!pref)
+ return;
+ const PrefService::Preference* controlling_pref =
+ !controlling_pref_name.empty() ?
+ pref_service->FindPreference(controlling_pref_name.c_str()) : NULL;
+ std::pair<PreferenceCallbackMap::const_iterator,
+ PreferenceCallbackMap::const_iterator> range;
+ range = pref_callback_map_.equal_range(pref_name);
+ for (PreferenceCallbackMap::const_iterator iter = range.first;
+ iter != range.second; ++iter) {
+ const std::wstring& callback_function = iter->second;
+ ListValue result_value;
+ result_value.Append(base::Value::CreateStringValue(pref_name.c_str()));
+ result_value.Append(CreateValueForPref(pref, controlling_pref));
+ web_ui_->CallJavascriptFunction(WideToASCII(callback_function),
+ result_value);
+ }
+}
+
+DictionaryValue* CoreOptionsHandler::CreateValueForPref(
+ const PrefService::Preference* pref,
+ const PrefService::Preference* controlling_pref) {
+ DictionaryValue* dict = new DictionaryValue;
+ dict->Set("value", pref->GetValue()->DeepCopy());
+ if (!controlling_pref) // No controlling pref is managing actual pref.
+ controlling_pref = pref; // This means pref is controlling itself.
+ if (controlling_pref->IsManaged()) {
+ dict->SetString("controlledBy", "policy");
+ } else if (controlling_pref->IsExtensionControlled()) {
+ dict->SetString("controlledBy", "extension");
+ } else if (controlling_pref->IsRecommended()) {
+ dict->SetString("controlledBy", "recommended");
+ }
+ dict->SetBoolean("disabled", !controlling_pref->IsUserModifiable());
+ return dict;
+}
+
+void CoreOptionsHandler::StopObservingPref(const std::string& path) {
+ registrar_.Remove(path.c_str(), this);
+}
+
+void CoreOptionsHandler::HandleFetchPrefs(const ListValue* args) {
+ // First param is name of callback function, so, there needs to be at least
+ // one more element for the actual preference identifier.
+ DCHECK_GE(static_cast<int>(args->GetSize()), 2);
+
+ // Get callback JS function name.
+ base::Value* callback;
+ if (!args->Get(0, &callback) || !callback->IsType(base::Value::TYPE_STRING))
+ return;
+
+ string16 callback_function;
+ if (!callback->GetAsString(&callback_function))
+ return;
+
+ // Get the list of name for prefs to build the response dictionary.
+ DictionaryValue result_value;
+ base::Value* list_member;
+
+ for (size_t i = 1; i < args->GetSize(); i++) {
+ if (!args->Get(i, &list_member))
+ break;
+
+ if (!list_member->IsType(base::Value::TYPE_STRING))
+ continue;
+
+ std::string pref_name;
+ if (!list_member->GetAsString(&pref_name))
+ continue;
+
+ result_value.Set(pref_name.c_str(), FetchPref(pref_name));
+ }
+ web_ui_->CallJavascriptFunction(UTF16ToASCII(callback_function),
+ result_value);
+}
+
+void CoreOptionsHandler::HandleObservePrefs(const ListValue* args) {
+ // First param is name is JS callback function name, the rest are pref
+ // identifiers that we are observing.
+ DCHECK_GE(static_cast<int>(args->GetSize()), 2);
+
+ // Get preference change callback function name.
+ string16 callback_func_name;
+ if (!args->GetString(0, &callback_func_name))
+ return;
+
+ // Get all other parameters - pref identifiers.
+ for (size_t i = 1; i < args->GetSize(); i++) {
+ base::Value* list_member;
+ if (!args->Get(i, &list_member))
+ break;
+
+ // Just ignore bad pref identifiers for now.
+ std::string pref_name;
+ if (!list_member->IsType(base::Value::TYPE_STRING) ||
+ !list_member->GetAsString(&pref_name))
+ continue;
+
+ if (pref_callback_map_.find(pref_name) == pref_callback_map_.end())
+ ObservePref(pref_name);
+
+ pref_callback_map_.insert(
+ PreferenceCallbackMap::value_type(pref_name,
+ UTF16ToWideHack(callback_func_name)));
+ }
+}
+
+void CoreOptionsHandler::HandleSetBooleanPref(const ListValue* args) {
+ HandleSetPref(args, TYPE_BOOLEAN);
+}
+
+void CoreOptionsHandler::HandleSetIntegerPref(const ListValue* args) {
+ HandleSetPref(args, TYPE_INTEGER);
+}
+
+void CoreOptionsHandler::HandleSetDoublePref(const ListValue* args) {
+ HandleSetPref(args, TYPE_DOUBLE);
+}
+
+void CoreOptionsHandler::HandleSetStringPref(const ListValue* args) {
+ HandleSetPref(args, TYPE_STRING);
+}
+
+void CoreOptionsHandler::HandleSetURLPref(const ListValue* args) {
+ HandleSetPref(args, TYPE_URL);
+}
+
+void CoreOptionsHandler::HandleSetListPref(const ListValue* args) {
+ HandleSetPref(args, TYPE_LIST);
+}
+
+void CoreOptionsHandler::HandleSetPref(const ListValue* args, PrefType type) {
+ DCHECK_GT(static_cast<int>(args->GetSize()), 1);
+
+ std::string pref_name;
+ if (!args->GetString(0, &pref_name))
+ return;
+
+ base::Value* value;
+ if (!args->Get(1, &value))
+ return;
+
+ scoped_ptr<base::Value> temp_value;
+
+ switch (type) {
+ case TYPE_BOOLEAN:
+ CHECK_EQ(base::Value::TYPE_BOOLEAN, value->GetType());
+ break;
+ case TYPE_INTEGER: {
+ // In JS all numbers are doubles.
+ double double_value;
+ CHECK(value->GetAsDouble(&double_value));
+ int int_value = static_cast<int>(double_value);
+ temp_value.reset(base::Value::CreateIntegerValue(int_value));
+ value = temp_value.get();
+ break;
+ }
+ case TYPE_DOUBLE:
+ CHECK_EQ(base::Value::TYPE_DOUBLE, value->GetType());
+ break;
+ case TYPE_STRING:
+ CHECK_EQ(base::Value::TYPE_STRING, value->GetType());
+ break;
+ case TYPE_URL: {
+ std::string original;
+ CHECK(value->GetAsString(&original));
+ GURL fixed = URLFixerUpper::FixupURL(original, std::string());
+ temp_value.reset(base::Value::CreateStringValue(fixed.spec()));
+ value = temp_value.get();
+ break;
+ }
+ case TYPE_LIST: {
+ // In case we have a List pref we got a JSON string.
+ std::string json_string;
+ CHECK(value->GetAsString(&json_string));
+ temp_value.reset(
+ base::JSONReader().JsonToValue(json_string,
+ false, // no check_root
+ false)); // no trailing comma
+ value = temp_value.get();
+ CHECK_EQ(base::Value::TYPE_LIST, value->GetType());
+ break;
+ }
+ default:
+ NOTREACHED();
+ }
+
+ std::string metric;
+ if (args->GetSize() > 2 && !args->GetString(2, &metric))
+ LOG(WARNING) << "Invalid metric parameter: " << pref_name;
+ SetPref(pref_name, value, metric);
+}
+
+void CoreOptionsHandler::HandleClearPref(const ListValue* args) {
+ DCHECK_GT(static_cast<int>(args->GetSize()), 0);
+
+ std::string pref_name;
+ if (!args->GetString(0, &pref_name))
+ return;
+
+ std::string metric;
+ if (args->GetSize() > 1)
+ args->GetString(1, &metric);
+
+ ClearPref(pref_name, metric);
+}
+
+void CoreOptionsHandler::HandleUserMetricsAction(const ListValue* args) {
+ std::string metric = UTF16ToUTF8(ExtractStringValue(args));
+ if (!metric.empty())
+ content::RecordComputedAction(metric);
+}
+
+void CoreOptionsHandler::UpdateClearPluginLSOData() {
+ scoped_ptr<base::Value> enabled(
+ base::Value::CreateBooleanValue(
+ clear_plugin_lso_data_enabled_.GetValue()));
+ web_ui_->CallJavascriptFunction(
+ "OptionsPage.setClearPluginLSODataEnabled", *enabled);
+}
diff --git a/chrome/browser/ui/webui/options2/core_options_handler.h b/chrome/browser/ui/webui/options2/core_options_handler.h
new file mode 100644
index 0000000..e1adb21
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/core_options_handler.h
@@ -0,0 +1,151 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS2_CORE_OPTIONS_HANDLER_H_
+#define CHROME_BROWSER_UI_WEBUI_OPTIONS2_CORE_OPTIONS_HANDLER_H_
+#pragma once
+
+#include <map>
+#include <string>
+
+#include "base/values.h"
+#include "chrome/browser/plugin_data_remover_helper.h"
+#include "chrome/browser/prefs/pref_change_registrar.h"
+#include "chrome/browser/prefs/pref_service.h"
+#include "chrome/browser/ui/webui/options2/options_ui2.h"
+
+// Core options UI handler.
+// Handles resource and JS calls common to all options sub-pages.
+class CoreOptionsHandler : public OptionsPage2UIHandler {
+ public:
+ CoreOptionsHandler();
+ virtual ~CoreOptionsHandler();
+
+ // OptionsPage2UIHandler implementation.
+ virtual void Initialize() OVERRIDE;
+ virtual void GetLocalizedValues(DictionaryValue* localized_strings) OVERRIDE;
+ virtual void Uninitialize() OVERRIDE;
+
+ // content::NotificationObserver implementation.
+ virtual void Observe(int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) OVERRIDE;
+
+ // WebUIMessageHandler implementation.
+ virtual void RegisterMessages() OVERRIDE;
+ virtual WebUIMessageHandler* Attach(WebUI* web_ui) OVERRIDE;
+
+ void set_handlers_host(OptionsPage2UIHandlerHost* handlers_host) {
+ handlers_host_ = handlers_host;
+ }
+
+ // Adds localized strings to |localized_strings|.
+ static void GetStaticLocalizedValues(
+ base::DictionaryValue* localized_strings);
+
+ protected:
+ // Fetches a pref value of given |pref_name|.
+ // Note that caller owns the returned Value.
+ virtual base::Value* FetchPref(const std::string& pref_name);
+
+ // Observes a pref of given |pref_name|.
+ virtual void ObservePref(const std::string& pref_name);
+
+ // Sets a pref |value| to given |pref_name|.
+ virtual void SetPref(const std::string& pref_name,
+ const base::Value* value,
+ const std::string& metric);
+
+ // Clears pref value for given |pref_name|.
+ void ClearPref(const std::string& pref_name, const std::string& metric);
+
+ // Stops observing given preference identified by |path|.
+ virtual void StopObservingPref(const std::string& path);
+
+ // Records a user metric action for the given value.
+ void ProcessUserMetric(const base::Value* value,
+ const std::string& metric);
+
+ // Notifies registered JS callbacks on change in |pref_name| preference.
+ // |controlling_pref_name| controls if |pref_name| is managed by
+ // policy/extension; empty |controlling_pref_name| indicates no other pref is
+ // controlling |pref_name|.
+ void NotifyPrefChanged(const std::string& pref_name,
+ const std::string& controlling_pref_name);
+
+ // Creates dictionary value for |pref|, |controlling_pref| controls if |pref|
+ // is managed by policy/extension; NULL indicates no other pref is controlling
+ // |pref|.
+ DictionaryValue* CreateValueForPref(
+ const PrefService::Preference* pref,
+ const PrefService::Preference* controlling_pref);
+
+ typedef std::multimap<std::string, std::wstring> PreferenceCallbackMap;
+ PreferenceCallbackMap pref_callback_map_;
+
+ private:
+ // Type of preference value received from the page. This doesn't map 1:1 to
+ // Value::Type, since a TYPE_STRING can require custom processing.
+ enum PrefType {
+ TYPE_BOOLEAN = 0,
+ TYPE_INTEGER,
+ TYPE_DOUBLE,
+ TYPE_STRING,
+ TYPE_URL,
+ TYPE_LIST,
+ };
+
+ // Callback for the "coreOptionsInitialize" message. This message will
+ // trigger the Initialize() method of all other handlers so that final
+ // setup can be performed before the page is shown.
+ void HandleInitialize(const ListValue* args);
+
+ // Callback for the "fetchPrefs" message. This message accepts the list of
+ // preference names passed as the |args| parameter (ListValue). It passes
+ // results dictionary of preference values by calling prefsFetched() JS method
+ // on the page.
+ void HandleFetchPrefs(const ListValue* args);
+
+ // Callback for the "observePrefs" message. This message initiates
+ // notification observing for given array of preference names.
+ void HandleObservePrefs(const ListValue* args);
+
+ // Callbacks for the "set<type>Pref" message. This message saves the new
+ // preference value. |args| is an array of parameters as follows:
+ // item 0 - name of the preference.
+ // item 1 - the value of the preference in string form.
+ // item 2 - name of the metric identifier (optional).
+ void HandleSetBooleanPref(const ListValue* args);
+ void HandleSetIntegerPref(const ListValue* args);
+ void HandleSetDoublePref(const ListValue* args);
+ void HandleSetStringPref(const ListValue* args);
+ void HandleSetURLPref(const ListValue* args);
+ void HandleSetListPref(const ListValue* args);
+
+ void HandleSetPref(const ListValue* args, PrefType type);
+
+ // Callback for the "clearPref" message. This message clears a preference
+ // value. |args| is an array of parameters as follows:
+ // item 0 - name of the preference.
+ // item 1 - name of the metric identifier (optional).
+ void HandleClearPref(const ListValue* args);
+
+ // Callback for the "coreOptionsUserMetricsAction" message. This records
+ // an action that should be tracked if metrics recording is enabled. |args|
+ // is an array that contains a single item, the name of the metric identifier.
+ void HandleUserMetricsAction(const ListValue* args);
+
+ void UpdateClearPluginLSOData();
+
+ OptionsPage2UIHandlerHost* handlers_host_;
+ PrefChangeRegistrar registrar_;
+
+ // Used for asynchronously updating the preference stating whether clearing
+ // LSO data is supported.
+ PluginDataRemoverHelper clear_plugin_lso_data_enabled_;
+
+ DISALLOW_COPY_AND_ASSIGN(CoreOptionsHandler);
+};
+
+#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS2_CORE_OPTIONS_HANDLER_H_
diff --git a/chrome/browser/ui/webui/options2/extension_settings_handler.cc b/chrome/browser/ui/webui/options2/extension_settings_handler.cc
new file mode 100644
index 0000000..fddf7f1
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/extension_settings_handler.cc
@@ -0,0 +1,785 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/webui/options2/extension_settings_handler.h"
+
+#include "base/auto_reset.h"
+#include "base/base64.h"
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/command_line.h"
+#include "base/file_util.h"
+#include "base/string_number_conversions.h"
+#include "base/utf_string_conversions.h"
+#include "base/values.h"
+#include "base/version.h"
+#include "chrome/browser/debugger/devtools_window.h"
+#include "chrome/browser/extensions/crx_installer.h"
+#include "chrome/browser/extensions/extension_disabled_infobar_delegate.h"
+#include "chrome/browser/extensions/extension_service.h"
+#include "chrome/browser/extensions/extension_updater.h"
+#include "chrome/browser/extensions/extension_warning_set.h"
+#include "chrome/browser/extensions/unpacked_installer.h"
+#include "chrome/browser/google/google_util.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/tab_contents/background_contents.h"
+#include "chrome/browser/ui/webui/extensions/extension_icon_source.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/common/chrome_view_type.h"
+#include "chrome/common/extensions/extension.h"
+#include "chrome/common/pref_names.h"
+#include "chrome/common/url_constants.h"
+#include "content/browser/browsing_instance.h"
+#include "content/browser/renderer_host/render_view_host.h"
+#include "content/browser/tab_contents/tab_contents.h"
+#include "content/browser/tab_contents/tab_contents_view.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/notification_types.h"
+#include "grit/browser_resources.h"
+#include "grit/chromium_strings.h"
+#include "grit/generated_resources.h"
+#include "grit/theme_resources.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/resource/resource_bundle.h"
+
+namespace {
+
+bool ShouldShowExtension(const Extension* extension) {
+ // Don't show themes since this page's UI isn't really useful for themes.
+ if (extension->is_theme())
+ return false;
+
+ // Don't show component extensions because they are only extensions as an
+ // implementation detail of Chrome.
+ if (extension->location() == Extension::COMPONENT &&
+ !CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kShowComponentExtensionOptions))
+ return false;
+
+ // Always show unpacked extensions and apps.
+ if (extension->location() == Extension::LOAD)
+ return true;
+
+ // Unless they are unpacked, never show hosted apps.
+ if (extension->is_hosted_app())
+ return false;
+
+ return true;
+}
+
+} // namespace
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// ExtensionSettingsHandler
+//
+///////////////////////////////////////////////////////////////////////////////
+
+ExtensionSettingsHandler::ExtensionSettingsHandler()
+ : extension_service_(NULL),
+ ignore_notifications_(false),
+ deleting_rvh_(NULL),
+ registered_for_notifications_(false) {
+}
+
+ExtensionSettingsHandler::~ExtensionSettingsHandler() {
+ // There may be pending file dialogs, we need to tell them that we've gone
+ // away so they don't try and call back to us.
+ if (load_extension_dialog_.get())
+ load_extension_dialog_->ListenerDestroyed();
+
+ registrar_.RemoveAll();
+}
+
+// static
+void ExtensionSettingsHandler::RegisterUserPrefs(PrefService* prefs) {
+ prefs->RegisterBooleanPref(prefs::kExtensionsUIDeveloperMode,
+ false,
+ PrefService::SYNCABLE_PREF);
+}
+
+void ExtensionSettingsHandler::RegisterMessages() {
+ web_ui_->RegisterMessageCallback("extensionSettingsRequestExtensionsData",
+ base::Bind(&ExtensionSettingsHandler::HandleRequestExtensionsData,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("extensionSettingsToggleDeveloperMode",
+ base::Bind(&ExtensionSettingsHandler::HandleToggleDeveloperMode,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("extensionSettingsInspect",
+ base::Bind(&ExtensionSettingsHandler::HandleInspectMessage,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("extensionSettingsReload",
+ base::Bind(&ExtensionSettingsHandler::HandleReloadMessage,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("extensionSettingsEnable",
+ base::Bind(&ExtensionSettingsHandler::HandleEnableMessage,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("extensionSettingsEnableIncognito",
+ base::Bind(&ExtensionSettingsHandler::HandleEnableIncognitoMessage,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("extensionSettingsAllowFileAccess",
+ base::Bind(&ExtensionSettingsHandler::HandleAllowFileAccessMessage,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("extensionSettingsUninstall",
+ base::Bind(&ExtensionSettingsHandler::HandleUninstallMessage,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("extensionSettingsOptions",
+ base::Bind(&ExtensionSettingsHandler::HandleOptionsMessage,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("extensionSettingsShowButton",
+ base::Bind(&ExtensionSettingsHandler::HandleShowButtonMessage,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("extensionSettingsLoad",
+ base::Bind(&ExtensionSettingsHandler::HandleLoadMessage,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("extensionSettingsAutoupdate",
+ base::Bind(&ExtensionSettingsHandler::HandleAutoUpdateMessage,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("extensionSettingsSelectFilePath",
+ base::Bind(&ExtensionSettingsHandler::HandleSelectFilePathMessage,
+ base::Unretained(this)));
+}
+
+void ExtensionSettingsHandler::HandleRequestExtensionsData(
+ const ListValue* args) {
+ DictionaryValue results;
+
+ // Add the extensions to the results structure.
+ ListValue *extensions_list = new ListValue();
+
+ ExtensionWarningSet* warnings = extension_service_->extension_warnings();
+
+ const ExtensionSet* extensions = extension_service_->extensions();
+ for (ExtensionSet::const_iterator extension = extensions->begin();
+ extension != extensions->end(); ++extension) {
+ if (ShouldShowExtension(*extension)) {
+ extensions_list->Append(CreateExtensionDetailValue(
+ extension_service_,
+ *extension,
+ GetActivePagesForExtension(*extension),
+ warnings,
+ true, false)); // enabled, terminated
+ }
+ }
+ extensions = extension_service_->disabled_extensions();
+ for (ExtensionSet::const_iterator extension = extensions->begin();
+ extension != extensions->end(); ++extension) {
+ if (ShouldShowExtension(*extension)) {
+ extensions_list->Append(CreateExtensionDetailValue(
+ extension_service_,
+ *extension,
+ GetActivePagesForExtension(*extension),
+ warnings,
+ false, false)); // enabled, terminated
+ }
+ }
+ extensions = extension_service_->terminated_extensions();
+ std::vector<ExtensionPage> empty_pages;
+ for (ExtensionSet::const_iterator extension = extensions->begin();
+ extension != extensions->end(); ++extension) {
+ if (ShouldShowExtension(*extension)) {
+ extensions_list->Append(CreateExtensionDetailValue(
+ extension_service_,
+ *extension,
+ empty_pages, // Terminated process has no active pages.
+ warnings,
+ false, true)); // enabled, terminated
+ }
+ }
+ results.Set("extensions", extensions_list);
+
+ Profile* profile = Profile::FromWebUI(web_ui_);
+ bool developer_mode =
+ profile->GetPrefs()->GetBoolean(prefs::kExtensionsUIDeveloperMode);
+ results.SetBoolean("developerMode", developer_mode);
+
+ web_ui_->CallJavascriptFunction("ExtensionSettings.returnExtensionsData",
+ results);
+
+ MaybeRegisterForNotifications();
+}
+
+void ExtensionSettingsHandler::MaybeRegisterForNotifications() {
+ if (registered_for_notifications_)
+ return;
+
+ registered_for_notifications_ = true;
+ Profile* profile = Profile::FromWebUI(web_ui_);
+
+ // Register for notifications that we need to reload the page.
+ registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_LOADED,
+ content::Source<Profile>(profile));
+ registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED,
+ content::Source<Profile>(profile));
+ registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UPDATE_DISABLED,
+ content::Source<Profile>(profile));
+ registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_WARNING_CHANGED,
+ content::Source<Profile>(profile));
+ registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_HOST_CREATED,
+ content::NotificationService::AllBrowserContextsAndSources());
+ registrar_.Add(this,
+ content::NOTIFICATION_RENDER_VIEW_HOST_CREATED,
+ content::NotificationService::AllBrowserContextsAndSources());
+ registrar_.Add(this,
+ content::NOTIFICATION_RENDER_VIEW_HOST_DELETED,
+ content::NotificationService::AllBrowserContextsAndSources());
+ registrar_.Add(this,
+ chrome::NOTIFICATION_BACKGROUND_CONTENTS_NAVIGATED,
+ content::NotificationService::AllBrowserContextsAndSources());
+ registrar_.Add(this,
+ chrome::NOTIFICATION_BACKGROUND_CONTENTS_DELETED,
+ content::NotificationService::AllBrowserContextsAndSources());
+ registrar_.Add(
+ this,
+ chrome::NOTIFICATION_EXTENSION_BROWSER_ACTION_VISIBILITY_CHANGED,
+ content::Source<ExtensionPrefs>(profile->GetExtensionService()->
+ extension_prefs()));
+}
+
+ExtensionUninstallDialog*
+ExtensionSettingsHandler::GetExtensionUninstallDialog() {
+ if (!extension_uninstall_dialog_.get()) {
+ extension_uninstall_dialog_.reset(
+ ExtensionUninstallDialog::Create(Profile::FromWebUI(web_ui_), this));
+ }
+ return extension_uninstall_dialog_.get();
+}
+
+void ExtensionSettingsHandler::HandleToggleDeveloperMode(
+ const ListValue* args) {
+ Profile* profile = Profile::FromWebUI(web_ui_);
+ bool developer_mode =
+ profile->GetPrefs()->GetBoolean(prefs::kExtensionsUIDeveloperMode);
+ profile->GetPrefs()->SetBoolean(
+ prefs::kExtensionsUIDeveloperMode, !developer_mode);
+ HandleRequestExtensionsData(NULL);
+}
+
+void ExtensionSettingsHandler::HandleInspectMessage(const ListValue* args) {
+ std::string render_process_id_str;
+ std::string render_view_id_str;
+ int render_process_id;
+ int render_view_id;
+ CHECK_EQ(2U, args->GetSize());
+ CHECK(args->GetString(0, &render_process_id_str));
+ CHECK(args->GetString(1, &render_view_id_str));
+ CHECK(base::StringToInt(render_process_id_str, &render_process_id));
+ CHECK(base::StringToInt(render_view_id_str, &render_view_id));
+ RenderViewHost* host = RenderViewHost::FromID(render_process_id,
+ render_view_id);
+ if (!host) {
+ // This can happen if the host has gone away since the page was displayed.
+ return;
+ }
+
+ DevToolsWindow::OpenDevToolsWindow(host);
+}
+
+void ExtensionSettingsHandler::HandleReloadMessage(const ListValue* args) {
+ std::string extension_id = UTF16ToUTF8(ExtractStringValue(args));
+ CHECK(!extension_id.empty());
+ extension_service_->ReloadExtension(extension_id);
+}
+
+void ExtensionSettingsHandler::HandleEnableMessage(const ListValue* args) {
+ CHECK_EQ(2U, args->GetSize());
+ std::string extension_id, enable_str;
+ CHECK(args->GetString(0, &extension_id));
+ CHECK(args->GetString(1, &enable_str));
+
+ const Extension* extension =
+ extension_service_->GetExtensionById(extension_id, true);
+ if (!Extension::UserMayDisable(extension->location())) {
+ LOG(ERROR) << "Attempt to enable an extension that is non-usermanagable was"
+ << "made. Extension id: " << extension->id();
+ return;
+ }
+
+ if (enable_str == "true") {
+ ExtensionPrefs* prefs = extension_service_->extension_prefs();
+ if (prefs->DidExtensionEscalatePermissions(extension_id)) {
+ ShowExtensionDisabledDialog(extension_service_,
+ Profile::FromWebUI(web_ui_), extension);
+ } else {
+ extension_service_->EnableExtension(extension_id);
+ }
+ } else {
+ extension_service_->DisableExtension(extension_id);
+ }
+}
+
+void ExtensionSettingsHandler::HandleEnableIncognitoMessage(
+ const ListValue* args) {
+ CHECK_EQ(2U, args->GetSize());
+ std::string extension_id, enable_str;
+ CHECK(args->GetString(0, &extension_id));
+ CHECK(args->GetString(1, &enable_str));
+ const Extension* extension =
+ extension_service_->GetExtensionById(extension_id, true);
+ DCHECK(extension);
+
+ // Flipping the incognito bit will generate unload/load notifications for the
+ // extension, but we don't want to reload the page, because a) we've already
+ // updated the UI to reflect the change, and b) we want the yellow warning
+ // text to stay until the user has left the page.
+ //
+ // TODO(aa): This creates crapiness in some cases. For example, in a main
+ // window, when toggling this, the browser action will flicker because it gets
+ // unloaded, then reloaded. It would be better to have a dedicated
+ // notification for this case.
+ //
+ // Bug: http://crbug.com/41384
+ AutoReset<bool> auto_reset_ignore_notifications(&ignore_notifications_, true);
+ extension_service_->SetIsIncognitoEnabled(extension->id(),
+ enable_str == "true");
+}
+
+void ExtensionSettingsHandler::HandleAllowFileAccessMessage(
+ const ListValue* args) {
+ CHECK_EQ(2U, args->GetSize());
+ std::string extension_id, allow_str;
+ CHECK(args->GetString(0, &extension_id));
+ CHECK(args->GetString(1, &allow_str));
+ const Extension* extension =
+ extension_service_->GetExtensionById(extension_id, true);
+ DCHECK(extension);
+
+ if (!Extension::UserMayDisable(extension->location())) {
+ LOG(ERROR) << "Attempt to change allow file access of an extension that is "
+ << "non-usermanagable was made. Extension id : "
+ << extension->id();
+ return;
+ }
+
+ extension_service_->SetAllowFileAccess(extension, allow_str == "true");
+}
+
+void ExtensionSettingsHandler::HandleUninstallMessage(const ListValue* args) {
+ std::string extension_id = UTF16ToUTF8(ExtractStringValue(args));
+ CHECK(!extension_id.empty());
+ const Extension* extension =
+ extension_service_->GetExtensionById(extension_id, true);
+ if (!extension)
+ extension = extension_service_->GetTerminatedExtension(extension_id);
+ if (!extension)
+ return;
+
+ if (!Extension::UserMayDisable(extension->location())) {
+ LOG(ERROR) << "Attempt to uninstall an extension that is non-usermanagable "
+ << "was made. Extension id : " << extension->id();
+ return;
+ }
+
+ if (!extension_id_prompting_.empty())
+ return; // Only one prompt at a time.
+
+ extension_id_prompting_ = extension_id;
+
+ GetExtensionUninstallDialog()->ConfirmUninstall(extension);
+}
+
+void ExtensionSettingsHandler::ExtensionUninstallAccepted() {
+ DCHECK(!extension_id_prompting_.empty());
+
+ bool was_terminated = false;
+
+ // The extension can be uninstalled in another window while the UI was
+ // showing. Do nothing in that case.
+ const Extension* extension =
+ extension_service_->GetExtensionById(extension_id_prompting_, true);
+ if (!extension) {
+ extension = extension_service_->GetTerminatedExtension(
+ extension_id_prompting_);
+ was_terminated = true;
+ }
+ if (!extension)
+ return;
+
+ extension_service_->UninstallExtension(extension_id_prompting_,
+ false, // External uninstall.
+ NULL); // Error.
+ extension_id_prompting_ = "";
+
+ // There will be no EXTENSION_UNLOADED notification for terminated
+ // extensions as they were already unloaded.
+ if (was_terminated)
+ HandleRequestExtensionsData(NULL);
+}
+
+void ExtensionSettingsHandler::ExtensionUninstallCanceled() {
+ extension_id_prompting_ = "";
+}
+
+void ExtensionSettingsHandler::HandleOptionsMessage(const ListValue* args) {
+ const Extension* extension = GetExtension(args);
+ if (!extension || extension->options_url().is_empty())
+ return;
+ Profile::FromWebUI(web_ui_)->GetExtensionProcessManager()->OpenOptionsPage(
+ extension, NULL);
+}
+
+void ExtensionSettingsHandler::HandleShowButtonMessage(const ListValue* args) {
+ const Extension* extension = GetExtension(args);
+ extension_service_->SetBrowserActionVisibility(extension, true);
+}
+
+void ExtensionSettingsHandler::HandleLoadMessage(const ListValue* args) {
+ FilePath::StringType string_path;
+ CHECK_EQ(1U, args->GetSize()) << args->GetSize();
+ CHECK(args->GetString(0, &string_path));
+ extensions::UnpackedInstaller::Create(extension_service_)->
+ Load(FilePath(string_path));
+}
+
+void ExtensionSettingsHandler::ShowAlert(const std::string& message) {
+ ListValue arguments;
+ arguments.Append(Value::CreateStringValue(message));
+ web_ui_->CallJavascriptFunction("alert", arguments);
+}
+
+void ExtensionSettingsHandler::HandleAutoUpdateMessage(const ListValue* args) {
+ ExtensionUpdater* updater = extension_service_->updater();
+ if (updater)
+ updater->CheckNow();
+}
+
+void ExtensionSettingsHandler::HandleSelectFilePathMessage(
+ const ListValue* args) {
+ std::string select_type;
+ std::string operation;
+ CHECK_EQ(2U, args->GetSize());
+ CHECK(args->GetString(0, &select_type));
+ CHECK(args->GetString(1, &operation));
+
+ SelectFileDialog::Type type = SelectFileDialog::SELECT_FOLDER;
+ SelectFileDialog::FileTypeInfo info;
+ int file_type_index = 0;
+ if (select_type == "file")
+ type = SelectFileDialog::SELECT_OPEN_FILE;
+
+ string16 select_title;
+ if (operation == "load") {
+ select_title = l10n_util::GetStringUTF16(IDS_EXTENSION_LOAD_FROM_DIRECTORY);
+ } else if (operation == "packRoot") {
+ select_title = l10n_util::GetStringUTF16(
+ IDS_EXTENSION_PACK_DIALOG_SELECT_ROOT);
+ } else if (operation == "pem") {
+ select_title = l10n_util::GetStringUTF16(
+ IDS_EXTENSION_PACK_DIALOG_SELECT_KEY);
+ info.extensions.push_back(std::vector<FilePath::StringType>());
+ info.extensions.front().push_back(FILE_PATH_LITERAL("pem"));
+ info.extension_description_overrides.push_back(
+ l10n_util::GetStringUTF16(
+ IDS_EXTENSION_PACK_DIALOG_KEY_FILE_TYPE_DESCRIPTION));
+ info.include_all_files = true;
+ file_type_index = 1;
+ } else {
+ NOTREACHED();
+ return;
+ }
+
+ load_extension_dialog_ = SelectFileDialog::Create(this);
+ load_extension_dialog_->SelectFile(type, select_title, FilePath(), &info,
+ file_type_index, FILE_PATH_LITERAL(""), web_ui_->tab_contents(),
+ web_ui_->tab_contents()->view()->GetTopLevelNativeWindow(), NULL);
+}
+
+
+void ExtensionSettingsHandler::FileSelected(const FilePath& path, int index,
+ void* params) {
+ // Add the extensions to the results structure.
+ ListValue results;
+ results.Append(Value::CreateStringValue(path.value()));
+ web_ui_->CallJavascriptFunction("window.handleFilePathSelected", results);
+}
+
+void ExtensionSettingsHandler::MultiFilesSelected(
+ const std::vector<FilePath>& files, void* params) {
+ NOTREACHED();
+}
+
+void ExtensionSettingsHandler::GetLocalizedValues(
+ DictionaryValue* localized_strings) {
+ DCHECK(localized_strings);
+
+ RegisterTitle(localized_strings, "extensionSettings",
+ IDS_MANAGE_EXTENSIONS_SETTING_WINDOWS_TITLE);
+
+ localized_strings->SetString("extensionSettingsVisitWebsite",
+ l10n_util::GetStringUTF16(IDS_EXTENSIONS_VISIT_WEBSITE));
+
+ localized_strings->SetString("extensionSettingsDeveloperMode",
+ l10n_util::GetStringUTF16(IDS_EXTENSIONS_DEVELOPER_MODE_LINK));
+ localized_strings->SetString("extensionSettingsNoExtensions",
+ l10n_util::GetStringUTF16(IDS_EXTENSIONS_NONE_INSTALLED));
+ localized_strings->SetString("extensionSettingsSuggestGallery",
+ l10n_util::GetStringFUTF16(IDS_EXTENSIONS_NONE_INSTALLED_SUGGEST_GALLERY,
+ ASCIIToUTF16("<a href='") +
+ ASCIIToUTF16(google_util::AppendGoogleLocaleParam(
+ GURL(extension_urls::GetWebstoreLaunchURL())).spec()) +
+ ASCIIToUTF16("'>"),
+ ASCIIToUTF16("</a>")));
+ localized_strings->SetString("extensionSettingsGetMoreExtensions",
+ ASCIIToUTF16("<a href='") +
+ ASCIIToUTF16(google_util::AppendGoogleLocaleParam(
+ GURL(extension_urls::GetWebstoreLaunchURL())).spec()) +
+ ASCIIToUTF16("'>") +
+ l10n_util::GetStringUTF16(IDS_GET_MORE_EXTENSIONS) +
+ ASCIIToUTF16("</a>"));
+ localized_strings->SetString("extensionSettingsExtensionId",
+ l10n_util::GetStringUTF16(IDS_EXTENSIONS_ID));
+ localized_strings->SetString("extensionSettingsExtensionPath",
+ l10n_util::GetStringUTF16(IDS_EXTENSIONS_PATH));
+ localized_strings->SetString("extensionSettingsInspectViews",
+ l10n_util::GetStringUTF16(IDS_EXTENSIONS_INSPECT_VIEWS));
+ localized_strings->SetString("viewIncognito",
+ l10n_util::GetStringUTF16(IDS_EXTENSIONS_VIEW_INCOGNITO));
+ localized_strings->SetString("extensionSettingsEnable",
+ l10n_util::GetStringUTF16(IDS_EXTENSIONS_ENABLE));
+ localized_strings->SetString("extensionSettingsEnabled",
+ l10n_util::GetStringUTF16(IDS_EXTENSIONS_ENABLED));
+ localized_strings->SetString("extensionSettingsRemove",
+ l10n_util::GetStringUTF16(IDS_EXTENSIONS_REMOVE));
+ localized_strings->SetString("extensionSettingsEnableIncognito",
+ l10n_util::GetStringUTF16(IDS_EXTENSIONS_ENABLE_INCOGNITO));
+ localized_strings->SetString("extensionSettingsAllowFileAccess",
+ l10n_util::GetStringUTF16(IDS_EXTENSIONS_ALLOW_FILE_ACCESS));
+ localized_strings->SetString("extensionSettingsIncognitoWarning",
+ l10n_util::GetStringFUTF16(IDS_EXTENSIONS_INCOGNITO_WARNING,
+ l10n_util::GetStringUTF16(IDS_PRODUCT_NAME)));
+ localized_strings->SetString("extensionSettingsReload",
+ l10n_util::GetStringUTF16(IDS_EXTENSIONS_RELOAD));
+ localized_strings->SetString("extensionSettingsOptions",
+ l10n_util::GetStringUTF16(IDS_EXTENSIONS_OPTIONS));
+ localized_strings->SetString("extensionSettingsPolicyControlled",
+ l10n_util::GetStringUTF16(IDS_EXTENSIONS_POLICY_CONTROLLED));
+ localized_strings->SetString("extensionSettingsShowButton",
+ l10n_util::GetStringUTF16(IDS_EXTENSIONS_SHOW_BUTTON));
+ localized_strings->SetString("extensionSettingsLoadUnpackedButton",
+ l10n_util::GetStringUTF16(IDS_EXTENSIONS_LOAD_UNPACKED_BUTTON));
+ localized_strings->SetString("extensionSettingsPackButton",
+ l10n_util::GetStringUTF16(IDS_EXTENSIONS_PACK_BUTTON));
+ localized_strings->SetString("extensionSettingsUpdateButton",
+ l10n_util::GetStringUTF16(IDS_EXTENSIONS_UPDATE_BUTTON));
+ localized_strings->SetString("extensionSettingsCrashMessage",
+ l10n_util::GetStringUTF16(IDS_EXTENSIONS_CRASHED_EXTENSION));
+ localized_strings->SetString("extensionSettingsInDevelopment",
+ l10n_util::GetStringUTF16(IDS_EXTENSIONS_IN_DEVELOPMENT));
+ localized_strings->SetString("extensionSettingsWarningsTitle",
+ l10n_util::GetStringUTF16(IDS_EXTENSION_WARNINGS_TITLE));
+ localized_strings->SetString("extensionSettingsShowDetails",
+ l10n_util::GetStringUTF16(IDS_EXTENSIONS_SHOW_DETAILS));
+ localized_strings->SetString("extensionSettingsHideDetails",
+ l10n_util::GetStringUTF16(IDS_EXTENSIONS_HIDE_DETAILS));
+}
+
+void ExtensionSettingsHandler::Initialize() {
+}
+
+WebUIMessageHandler* ExtensionSettingsHandler::Attach(WebUI* web_ui) {
+ // Call through to superclass.
+ WebUIMessageHandler* handler = OptionsPage2UIHandler::Attach(web_ui);
+
+ extension_service_ = Profile::FromWebUI(web_ui_)
+ ->GetOriginalProfile()->GetExtensionService();
+
+ // Return result from the superclass.
+ return handler;
+}
+
+void ExtensionSettingsHandler::Observe(
+ int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) {
+ Profile* profile = Profile::FromWebUI(web_ui_);
+ Profile* source_profile = NULL;
+ switch (type) {
+ // We listen for notifications that will result in the page being
+ // repopulated with data twice for the same event in certain cases.
+ // For instance, EXTENSION_LOADED & EXTENSION_HOST_CREATED because
+ // we don't know about the views for an extension at EXTENSION_LOADED, but
+ // if we only listen to EXTENSION_HOST_CREATED, we'll miss extensions
+ // that don't have a process at startup.
+ //
+ // Doing it this way gets everything but causes the page to be rendered
+ // more than we need. It doesn't seem to result in any noticeable flicker.
+ case content::NOTIFICATION_RENDER_VIEW_HOST_DELETED:
+ deleting_rvh_ = content::Source<RenderViewHost>(source).ptr();
+ // Fall through.
+ case content::NOTIFICATION_RENDER_VIEW_HOST_CREATED:
+ source_profile = Profile::FromBrowserContext(
+ content::Source<RenderViewHost>(source)->site_instance()->
+ browsing_instance()->browser_context());
+ if (!profile->IsSameProfile(source_profile))
+ return;
+ MaybeUpdateAfterNotification();
+ break;
+ case chrome::NOTIFICATION_BACKGROUND_CONTENTS_DELETED:
+ deleting_rvh_ = content::Details<BackgroundContents>(details)->
+ tab_contents()->render_view_host();
+ // Fall through.
+ case chrome::NOTIFICATION_BACKGROUND_CONTENTS_NAVIGATED:
+ case chrome::NOTIFICATION_EXTENSION_HOST_CREATED:
+ source_profile = content::Source<Profile>(source).ptr();
+ if (!profile->IsSameProfile(source_profile))
+ return;
+ MaybeUpdateAfterNotification();
+ break;
+ case chrome::NOTIFICATION_EXTENSION_LOADED:
+ case chrome::NOTIFICATION_EXTENSION_UNLOADED:
+ case chrome::NOTIFICATION_EXTENSION_UPDATE_DISABLED:
+ case chrome::NOTIFICATION_EXTENSION_WARNING_CHANGED:
+ case chrome::NOTIFICATION_EXTENSION_BROWSER_ACTION_VISIBILITY_CHANGED:
+ MaybeUpdateAfterNotification();
+ break;
+ default:
+ NOTREACHED();
+ }
+}
+
+const Extension* ExtensionSettingsHandler::GetExtension(const ListValue* args) {
+ std::string extension_id = UTF16ToUTF8(ExtractStringValue(args));
+ CHECK(!extension_id.empty());
+ return extension_service_->GetExtensionById(extension_id, true);
+}
+
+void ExtensionSettingsHandler::MaybeUpdateAfterNotification() {
+ TabContents* contents = web_ui_->tab_contents();
+ if (!ignore_notifications_ && contents && contents->render_view_host())
+ HandleRequestExtensionsData(NULL);
+ deleting_rvh_ = NULL;
+}
+
+// Static
+DictionaryValue* ExtensionSettingsHandler::CreateExtensionDetailValue(
+ ExtensionService* service, const Extension* extension,
+ const std::vector<ExtensionPage>& pages,
+ const ExtensionWarningSet* warnings_set,
+ bool enabled, bool terminated) {
+ DictionaryValue* extension_data = new DictionaryValue();
+ GURL icon =
+ ExtensionIconSource::GetIconURL(extension,
+ Extension::EXTENSION_ICON_MEDIUM,
+ ExtensionIconSet::MATCH_BIGGER,
+ !enabled, NULL);
+ extension_data->SetString("id", extension->id());
+ extension_data->SetString("name", extension->name());
+ extension_data->SetString("description", extension->description());
+ if (extension->location() == Extension::LOAD)
+ extension_data->SetString("path", extension->path().value());
+ extension_data->SetString("version", extension->version()->GetString());
+ extension_data->SetString("icon", icon.spec());
+ extension_data->SetBoolean("isUnpacked",
+ extension->location() == Extension::LOAD);
+ extension_data->SetBoolean("mayDisable",
+ Extension::UserMayDisable(extension->location()));
+ extension_data->SetBoolean("enabled", enabled);
+ extension_data->SetBoolean("terminated", terminated);
+ extension_data->SetBoolean("enabledIncognito",
+ service ? service->IsIncognitoEnabled(extension->id()) : false);
+ extension_data->SetBoolean("wantsFileAccess", extension->wants_file_access());
+ extension_data->SetBoolean("allowFileAccess",
+ service ? service->AllowFileAccess(extension) : false);
+ extension_data->SetBoolean("allow_reload",
+ extension->location() == Extension::LOAD);
+ extension_data->SetBoolean("is_hosted_app", extension->is_hosted_app());
+
+ // Determine the sort order: Extensions loaded through --load-extensions show
+ // up at the top. Disabled extensions show up at the bottom.
+ if (extension->location() == Extension::LOAD)
+ extension_data->SetInteger("order", 1);
+ else
+ extension_data->SetInteger("order", 2);
+
+ if (!extension->options_url().is_empty() && enabled)
+ extension_data->SetString("options_url", extension->options_url().spec());
+
+ if (service && !service->GetBrowserActionVisibility(extension))
+ extension_data->SetBoolean("enable_show_button", true);
+
+ // Add views
+ ListValue* views = new ListValue;
+ for (std::vector<ExtensionPage>::const_iterator iter = pages.begin();
+ iter != pages.end(); ++iter) {
+ DictionaryValue* view_value = new DictionaryValue;
+ if (iter->url.scheme() == chrome::kExtensionScheme) {
+ // No leading slash.
+ view_value->SetString("path", iter->url.path().substr(1));
+ } else {
+ // For live pages, use the full URL.
+ view_value->SetString("path", iter->url.spec());
+ }
+ view_value->SetInteger("renderViewId", iter->render_view_id);
+ view_value->SetInteger("renderProcessId", iter->render_process_id);
+ view_value->SetBoolean("incognito", iter->incognito);
+ views->Append(view_value);
+ }
+ extension_data->Set("views", views);
+ extension_data->SetBoolean("hasPopupAction",
+ extension->browser_action() || extension->page_action());
+ extension_data->SetString("homepageUrl", extension->GetHomepageURL().spec());
+
+ // Add warnings.
+ ListValue* warnings_list = new ListValue;
+ if (warnings_set) {
+ std::set<ExtensionWarningSet::WarningType> warnings;
+ warnings_set->GetWarningsAffectingExtension(extension->id(), &warnings);
+
+ for (std::set<ExtensionWarningSet::WarningType>::const_iterator iter =
+ warnings.begin();
+ iter != warnings.end();
+ ++iter) {
+ string16 warning_string(ExtensionWarningSet::GetLocalizedWarning(*iter));
+ warnings_list->Append(Value::CreateStringValue(warning_string));
+ }
+ }
+ extension_data->Set("warnings", warnings_list);
+
+ return extension_data;
+}
+
+std::vector<ExtensionPage> ExtensionSettingsHandler::GetActivePagesForExtension(
+ const Extension* extension) {
+ std::vector<ExtensionPage> result;
+
+ // Get the extension process's active views.
+ ExtensionProcessManager* process_manager =
+ extension_service_->profile()->GetExtensionProcessManager();
+ GetActivePagesForExtensionProcess(
+ process_manager->GetRenderViewHostsForExtension(
+ extension->id()), &result);
+
+ // Repeat for the incognito process, if applicable.
+ if (extension_service_->profile()->HasOffTheRecordProfile() &&
+ extension->incognito_split_mode()) {
+ ExtensionProcessManager* process_manager =
+ extension_service_->profile()->GetOffTheRecordProfile()->
+ GetExtensionProcessManager();
+ GetActivePagesForExtensionProcess(
+ process_manager->GetRenderViewHostsForExtension(
+ extension->id()), &result);
+ }
+
+ return result;
+}
+
+void ExtensionSettingsHandler::GetActivePagesForExtensionProcess(
+ const std::set<RenderViewHost*>& views,
+ std::vector<ExtensionPage> *result) {
+ for (std::set<RenderViewHost*>::const_iterator iter = views.begin();
+ iter != views.end(); ++iter) {
+ RenderViewHost* host = *iter;
+ int host_type = host->delegate()->GetRenderViewType();
+ if (host == deleting_rvh_ ||
+ chrome::VIEW_TYPE_EXTENSION_POPUP == host_type ||
+ chrome::VIEW_TYPE_EXTENSION_DIALOG == host_type)
+ continue;
+
+ GURL url = host->delegate()->GetURL();
+ content::RenderProcessHost* process = host->process();
+ result->push_back(
+ ExtensionPage(url, process->GetID(), host->routing_id(),
+ process->GetBrowserContext()->IsOffTheRecord()));
+ }
+}
diff --git a/chrome/browser/ui/webui/options2/extension_settings_handler.h b/chrome/browser/ui/webui/options2/extension_settings_handler.h
new file mode 100644
index 0000000..9ac9713
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/extension_settings_handler.h
@@ -0,0 +1,200 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS2_EXTENSION_SETTINGS_HANDLER_H_
+#define CHROME_BROWSER_UI_WEBUI_OPTIONS2_EXTENSION_SETTINGS_HANDLER_H_
+#pragma once
+
+#include <set>
+#include <string>
+#include <vector>
+
+#include "chrome/browser/extensions/extension_install_ui.h"
+#include "chrome/browser/extensions/extension_uninstall_dialog.h"
+#include "chrome/browser/extensions/extension_warning_set.h"
+#include "chrome/browser/ui/select_file_dialog.h"
+#include "chrome/browser/ui/webui/options2/options_ui2.h"
+#include "chrome/browser/ui/webui/chrome_web_ui.h"
+#include "chrome/common/extensions/extension_resource.h"
+#include "content/public/browser/notification_observer.h"
+#include "content/public/browser/notification_registrar.h"
+#include "googleurl/src/gurl.h"
+
+class Extension;
+class ExtensionService;
+class FilePath;
+class PrefService;
+class UserScript;
+
+namespace base {
+class DictionaryValue;
+class ListValue;
+}
+
+// Information about a page running in an extension, for example a popup bubble,
+// a background page, or a tab contents.
+struct ExtensionPage {
+ ExtensionPage(const GURL& url, int render_process_id, int render_view_id,
+ bool incognito)
+ : url(url),
+ render_process_id(render_process_id),
+ render_view_id(render_view_id),
+ incognito(incognito) {}
+ GURL url;
+ int render_process_id;
+ int render_view_id;
+ bool incognito;
+};
+
+// Extension Settings UI handler.
+class ExtensionSettingsHandler : public OptionsPage2UIHandler,
+ public SelectFileDialog::Listener,
+ public ExtensionUninstallDialog::Delegate {
+ public:
+ ExtensionSettingsHandler();
+ virtual ~ExtensionSettingsHandler();
+
+ static void RegisterUserPrefs(PrefService* prefs);
+
+ // Extension Detail JSON Struct for page. (static for ease of testing).
+ // Note: |service| and |warnings| can be NULL in unit tests.
+ static base::DictionaryValue* CreateExtensionDetailValue(
+ ExtensionService* service,
+ const Extension* extension,
+ const std::vector<ExtensionPage>& pages,
+ const ExtensionWarningSet* warnings,
+ bool enabled,
+ bool terminated);
+
+ // ContentScript JSON Struct for page. (static for ease of testing).
+ static base::DictionaryValue* CreateContentScriptDetailValue(
+ const UserScript& script,
+ const FilePath& extension_path);
+
+ // Callback for "requestExtensionsData" message.
+ void HandleRequestExtensionsData(const base::ListValue* args);
+
+ // Callback for "toggleDeveloperMode" message.
+ void HandleToggleDeveloperMode(const base::ListValue* args);
+
+ // Callback for "inspect" message.
+ void HandleInspectMessage(const base::ListValue* args);
+
+ // Callback for "reload" message.
+ void HandleReloadMessage(const base::ListValue* args);
+
+ // Callback for "enable" message.
+ void HandleEnableMessage(const base::ListValue* args);
+
+ // Callback for "enableIncognito" message.
+ void HandleEnableIncognitoMessage(const base::ListValue* args);
+
+ // Callback for "allowFileAcces" message.
+ void HandleAllowFileAccessMessage(const base::ListValue* args);
+
+ // Callback for "uninstall" message.
+ void HandleUninstallMessage(const base::ListValue* args);
+
+ // Callback for "options" message.
+ void HandleOptionsMessage(const base::ListValue* args);
+
+ // Callback for "showButton" message.
+ void HandleShowButtonMessage(const base::ListValue* args);
+
+ // Callback for "load" message.
+ void HandleLoadMessage(const base::ListValue* args);
+
+ // Callback for "pack" message.
+ void HandlePackMessage(const base::ListValue* args);
+
+ // Callback for "autoupdate" message.
+ void HandleAutoUpdateMessage(const base::ListValue* args);
+
+ // Utility for calling javascript window.alert in the page.
+ void ShowAlert(const std::string& message);
+
+ // Callback for "selectFilePath" message.
+ void HandleSelectFilePathMessage(const base::ListValue* args);
+
+ // Utility for callbacks that get an extension ID as the sole argument.
+ const Extension* GetExtension(const base::ListValue* args);
+
+ // Forces a UI update if appropriate after a notification is received.
+ void MaybeUpdateAfterNotification();
+
+ // Register for notifications that we need to reload the page.
+ void MaybeRegisterForNotifications();
+
+ // SelectFileDialog::Listener
+ virtual void FileSelected(const FilePath& path,
+ int index, void* params) OVERRIDE;
+ virtual void MultiFilesSelected(
+ const std::vector<FilePath>& files, void* params) OVERRIDE;
+ virtual void FileSelectionCanceled(void* params) OVERRIDE {}
+
+ // WebUIMessageHandler implementation.
+ virtual void RegisterMessages() OVERRIDE;
+ virtual WebUIMessageHandler* Attach(WebUI* web_ui) OVERRIDE;
+
+ // OptionsUIHandler implementation.
+ virtual void GetLocalizedValues(
+ base::DictionaryValue* localized_strings) OVERRIDE;
+ virtual void Initialize() OVERRIDE;
+
+ // content::NotificationObserver implementation.
+ virtual void Observe(int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) OVERRIDE;
+
+ // ExtensionUninstallDialog::Delegate implementation, used for receiving
+ // notification about uninstall confirmation dialog selections.
+ virtual void ExtensionUninstallAccepted() OVERRIDE;
+ virtual void ExtensionUninstallCanceled() OVERRIDE;
+
+ private:
+ // Helper that lists the current active html pages for an extension.
+ std::vector<ExtensionPage> GetActivePagesForExtension(
+ const Extension* extension);
+ void GetActivePagesForExtensionProcess(
+ const std::set<RenderViewHost*>& views,
+ std::vector<ExtensionPage> *result);
+
+ // Returns the ExtensionUninstallDialog object for this class, creating it if
+ // needed.
+ ExtensionUninstallDialog* GetExtensionUninstallDialog();
+
+ // Our model. Outlives us since it's owned by our containing profile.
+ ExtensionService* extension_service_;
+
+ // Used to pick the directory when loading an extension.
+ scoped_refptr<SelectFileDialog> load_extension_dialog_;
+
+ // Used to show confirmation UI for uninstalling extensions in incognito mode.
+ scoped_ptr<ExtensionUninstallDialog> extension_uninstall_dialog_;
+
+ // The id of the extension we are prompting the user about.
+ std::string extension_id_prompting_;
+
+ // If true, we will ignore notifications in ::Observe(). This is needed
+ // to prevent reloading the page when we were the cause of the
+ // notification.
+ bool ignore_notifications_;
+
+ // The page may be refreshed in response to a RENDER_VIEW_HOST_DELETED,
+ // but the iteration over RenderViewHosts will include the host because the
+ // notification is sent when it is in the process of being deleted (and before
+ // it is removed from the process). Keep a pointer to it so we can exclude
+ // it from the active views.
+ RenderViewHost* deleting_rvh_;
+
+ // We want to register for notifications only after we've responded at least
+ // once to the page, otherwise we'd be calling javacsript functions on objects
+ // that don't exist yet when notifications come in. This variable makes sure
+ // we do so only once.
+ bool registered_for_notifications_;
+
+ DISALLOW_COPY_AND_ASSIGN(ExtensionSettingsHandler);
+};
+
+#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS2_EXTENSION_SETTINGS_HANDLER_H_
diff --git a/chrome/browser/ui/webui/options2/font_settings_browsertest.js b/chrome/browser/ui/webui/options2/font_settings_browsertest.js
new file mode 100644
index 0000000..9800b41
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/font_settings_browsertest.js
@@ -0,0 +1,24 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * TestFixture for font settings WebUI testing.
+ * @extends {testing.Test}
+ * @constructor
+ **/
+function FontSettingsWebUITest() {}
+
+FontSettingsWebUITest.prototype = {
+ __proto__: testing.Test.prototype,
+
+ /**
+ * Browse to the font settings page.
+ **/
+ browsePreload: 'chrome://settings/fonts',
+};
+
+// Test opening font settings has correct location.
+TEST_F('FontSettingsWebUITest', 'testOpenFontSettings', function() {
+ assertEquals(this.browsePreload, document.location.href);
+});
diff --git a/chrome/browser/ui/webui/options2/font_settings_handler.cc b/chrome/browser/ui/webui/options2/font_settings_handler.cc
new file mode 100644
index 0000000..2ffef10b
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/font_settings_handler.cc
@@ -0,0 +1,211 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/webui/options2/font_settings_handler.h"
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/i18n/rtl.h"
+#include "base/string_number_conversions.h"
+#include "base/string_util.h"
+#include "base/values.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/character_encoding.h"
+#include "chrome/browser/prefs/pref_service.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/webui/options2/font_settings_utils.h"
+#include "chrome/common/chrome_notification_types.h"
+#include "chrome/common/pref_names.h"
+#include "content/public/browser/notification_details.h"
+#include "grit/chromium_strings.h"
+#include "grit/generated_resources.h"
+#include "ui/base/l10n/l10n_util.h"
+
+FontSettingsHandler::FontSettingsHandler() {
+}
+
+FontSettingsHandler::~FontSettingsHandler() {
+}
+
+void FontSettingsHandler::GetLocalizedValues(
+ DictionaryValue* localized_strings) {
+ DCHECK(localized_strings);
+
+ static OptionsStringResource resources[] = {
+ { "fontSettingsStandard",
+ IDS_FONT_LANGUAGE_SETTING_FONT_SELECTOR_STANDARD_LABEL },
+ { "fontSettingsSerif",
+ IDS_FONT_LANGUAGE_SETTING_FONT_SELECTOR_SERIF_LABEL },
+ { "fontSettingsSansSerif",
+ IDS_FONT_LANGUAGE_SETTING_FONT_SELECTOR_SANS_SERIF_LABEL },
+ { "fontSettingsFixedWidth",
+ IDS_FONT_LANGUAGE_SETTING_FONT_SELECTOR_FIXED_WIDTH_LABEL },
+ { "fontSettingsMinimumSize",
+ IDS_FONT_LANGUAGE_SETTING_MINIMUM_FONT_SIZE_TITLE },
+ { "fontSettingsEncoding",
+ IDS_FONT_LANGUAGE_SETTING_FONT_SUB_DIALOG_ENCODING_TITLE },
+ { "fontSettingsSizeTiny",
+ IDS_FONT_LANGUAGE_SETTING_FONT_SIZE_TINY },
+ { "fontSettingsSizeHuge",
+ IDS_FONT_LANGUAGE_SETTING_FONT_SIZE_HUGE },
+ { "fontSettingsLoremIpsum",
+ IDS_FONT_LANGUAGE_SETTING_LOREM_IPSUM },
+ };
+
+ RegisterStrings(localized_strings, resources, arraysize(resources));
+ RegisterTitle(localized_strings, "fontSettingsPage",
+ IDS_FONT_LANGUAGE_SETTING_FONT_TAB_TITLE);
+ localized_strings->SetString("fontSettingsPlaceholder",
+ l10n_util::GetStringUTF16(
+ IDS_FONT_LANGUAGE_SETTING_PLACEHOLDER));
+}
+
+void FontSettingsHandler::Initialize() {
+ DCHECK(web_ui_);
+ SetUpStandardFontSample();
+ SetUpSerifFontSample();
+ SetUpSansSerifFontSample();
+ SetUpFixedFontSample();
+ SetUpMinimumFontSample();
+}
+
+WebUIMessageHandler* FontSettingsHandler::Attach(WebUI* web_ui) {
+ // Call through to superclass.
+ WebUIMessageHandler* handler = OptionsPage2UIHandler::Attach(web_ui);
+
+ // Perform validation for saved fonts.
+ DCHECK(web_ui_);
+ PrefService* pref_service = Profile::FromWebUI(web_ui_)->GetPrefs();
+ FontSettingsUtilities::ValidateSavedFonts(pref_service);
+
+ // Register for preferences that we need to observe manually.
+ standard_font_.Init(prefs::kWebKitStandardFontFamily, pref_service, this);
+ serif_font_.Init(prefs::kWebKitSerifFontFamily, pref_service, this);
+ sans_serif_font_.Init(prefs::kWebKitSansSerifFontFamily, pref_service, this);
+ fixed_font_.Init(prefs::kWebKitFixedFontFamily, pref_service, this);
+ font_encoding_.Init(prefs::kDefaultCharset, pref_service, this);
+ default_font_size_.Init(prefs::kWebKitDefaultFontSize, pref_service, this);
+ default_fixed_font_size_.Init(prefs::kWebKitDefaultFixedFontSize,
+ pref_service, this);
+ minimum_font_size_.Init(prefs::kWebKitMinimumFontSize, pref_service, this);
+
+ // Return result from the superclass.
+ return handler;
+}
+
+void FontSettingsHandler::RegisterMessages() {
+ web_ui_->RegisterMessageCallback("fetchFontsData",
+ base::Bind(&FontSettingsHandler::HandleFetchFontsData,
+ base::Unretained(this)));
+}
+
+void FontSettingsHandler::HandleFetchFontsData(const ListValue* args) {
+ content::GetFontListAsync(
+ base::Bind(&FontSettingsHandler::FontsListHasLoaded,
+ base::Unretained(this)));
+}
+
+void FontSettingsHandler::FontsListHasLoaded(
+ scoped_refptr<content::FontListResult> list) {
+ ListValue encoding_list;
+ const std::vector<CharacterEncoding::EncodingInfo>* encodings;
+ PrefService* pref_service = Profile::FromWebUI(web_ui_)->GetPrefs();
+ encodings = CharacterEncoding::GetCurrentDisplayEncodings(
+ g_browser_process->GetApplicationLocale(),
+ pref_service->GetString(prefs::kStaticEncodings),
+ pref_service->GetString(prefs::kRecentlySelectedEncoding));
+ DCHECK(encodings);
+ DCHECK(!encodings->empty());
+
+ std::vector<CharacterEncoding::EncodingInfo>::const_iterator it;
+ for (it = encodings->begin(); it != encodings->end(); ++it) {
+ ListValue* option = new ListValue();
+ if (it->encoding_id) {
+ int cmd_id = it->encoding_id;
+ std::string encoding =
+ CharacterEncoding::GetCanonicalEncodingNameByCommandId(cmd_id);
+ string16 name = it->encoding_display_name;
+ base::i18n::AdjustStringForLocaleDirection(&name);
+ option->Append(Value::CreateStringValue(encoding));
+ option->Append(Value::CreateStringValue(name));
+ } else {
+ // Add empty name/value to indicate a separator item.
+ option->Append(Value::CreateStringValue(""));
+ option->Append(Value::CreateStringValue(""));
+ }
+ encoding_list.Append(option);
+ }
+
+ ListValue selected_values;
+ selected_values.Append(Value::CreateStringValue(standard_font_.GetValue()));
+ selected_values.Append(Value::CreateStringValue(serif_font_.GetValue()));
+ selected_values.Append(Value::CreateStringValue(sans_serif_font_.GetValue()));
+ selected_values.Append(Value::CreateStringValue(fixed_font_.GetValue()));
+ selected_values.Append(Value::CreateStringValue(font_encoding_.GetValue()));
+
+ web_ui_->CallJavascriptFunction("FontSettings.setFontsData",
+ *list->list.get(), encoding_list,
+ selected_values);
+}
+
+void FontSettingsHandler::Observe(int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) {
+ if (type == chrome::NOTIFICATION_PREF_CHANGED) {
+ std::string* pref_name = content::Details<std::string>(details).ptr();
+ if (*pref_name == prefs::kWebKitStandardFontFamily) {
+ SetUpStandardFontSample();
+ } else if (*pref_name == prefs::kWebKitSerifFontFamily) {
+ SetUpSerifFontSample();
+ } else if (*pref_name == prefs::kWebKitSansSerifFontFamily) {
+ SetUpSansSerifFontSample();
+ } else if (*pref_name == prefs::kWebKitFixedFontFamily ||
+ *pref_name == prefs::kWebKitDefaultFixedFontSize) {
+ SetUpFixedFontSample();
+ } else if (*pref_name == prefs::kWebKitDefaultFontSize) {
+ SetUpStandardFontSample();
+ SetUpSerifFontSample();
+ SetUpSansSerifFontSample();
+ } else if (*pref_name == prefs::kWebKitMinimumFontSize) {
+ SetUpMinimumFontSample();
+ }
+ }
+}
+
+void FontSettingsHandler::SetUpStandardFontSample() {
+ base::StringValue font_value(standard_font_.GetValue());
+ base::FundamentalValue size_value(default_font_size_.GetValue());
+ web_ui_->CallJavascriptFunction(
+ "FontSettings.setUpStandardFontSample", font_value, size_value);
+}
+
+void FontSettingsHandler::SetUpSerifFontSample() {
+ base::StringValue font_value(serif_font_.GetValue());
+ base::FundamentalValue size_value(default_font_size_.GetValue());
+ web_ui_->CallJavascriptFunction(
+ "FontSettings.setUpSerifFontSample", font_value, size_value);
+}
+
+void FontSettingsHandler::SetUpSansSerifFontSample() {
+ base::StringValue font_value(sans_serif_font_.GetValue());
+ base::FundamentalValue size_value(default_font_size_.GetValue());
+ web_ui_->CallJavascriptFunction(
+ "FontSettings.setUpSansSerifFontSample", font_value, size_value);
+}
+
+void FontSettingsHandler::SetUpFixedFontSample() {
+ base::StringValue font_value(fixed_font_.GetValue());
+ base::FundamentalValue size_value(default_fixed_font_size_.GetValue());
+ web_ui_->CallJavascriptFunction(
+ "FontSettings.setUpFixedFontSample", font_value, size_value);
+}
+
+void FontSettingsHandler::SetUpMinimumFontSample() {
+ base::FundamentalValue size_value(minimum_font_size_.GetValue());
+ web_ui_->CallJavascriptFunction("FontSettings.setUpMinimumFontSample",
+ size_value);
+}
diff --git a/chrome/browser/ui/webui/options2/font_settings_handler.h b/chrome/browser/ui/webui/options2/font_settings_handler.h
new file mode 100644
index 0000000..baedd3b
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/font_settings_handler.h
@@ -0,0 +1,55 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS2_FONT_SETTINGS_HANDLER_H_
+#define CHROME_BROWSER_UI_WEBUI_OPTIONS2_FONT_SETTINGS_HANDLER_H_
+#pragma once
+
+#include "chrome/browser/prefs/pref_member.h"
+#include "chrome/browser/ui/webui/options2/options_ui2.h"
+#include "content/browser/font_list_async.h"
+
+// Font settings overlay page UI handler.
+class FontSettingsHandler : public OptionsPage2UIHandler {
+ public:
+ FontSettingsHandler();
+ virtual ~FontSettingsHandler();
+
+ // OptionsPage2UIHandler implementation.
+ virtual void GetLocalizedValues(DictionaryValue* localized_strings) OVERRIDE;
+ virtual void Initialize() OVERRIDE;
+
+ // WebUIMessageHandler implementation.
+ virtual WebUIMessageHandler* Attach(WebUI* web_ui) OVERRIDE;
+ virtual void RegisterMessages() OVERRIDE;
+
+ // content::NotificationObserver implementation.
+ virtual void Observe(int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) OVERRIDE;
+
+ private:
+ void HandleFetchFontsData(const ListValue* args);
+
+ void FontsListHasLoaded(scoped_refptr<content::FontListResult> list);
+
+ void SetUpStandardFontSample();
+ void SetUpSerifFontSample();
+ void SetUpSansSerifFontSample();
+ void SetUpFixedFontSample();
+ void SetUpMinimumFontSample();
+
+ StringPrefMember standard_font_;
+ StringPrefMember serif_font_;
+ StringPrefMember sans_serif_font_;
+ StringPrefMember fixed_font_;
+ StringPrefMember font_encoding_;
+ IntegerPrefMember default_font_size_;
+ IntegerPrefMember default_fixed_font_size_;
+ IntegerPrefMember minimum_font_size_;
+
+ DISALLOW_COPY_AND_ASSIGN(FontSettingsHandler);
+};
+
+#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS2_FONT_SETTINGS_HANDLER_H_
diff --git a/chrome/browser/ui/webui/options2/font_settings_utils.h b/chrome/browser/ui/webui/options2/font_settings_utils.h
new file mode 100644
index 0000000..9fb435c
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/font_settings_utils.h
@@ -0,0 +1,22 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS2_FONT_SETTINGS_UTILS_H_
+#define CHROME_BROWSER_UI_WEBUI_OPTIONS2_FONT_SETTINGS_UTILS_H_
+#pragma once
+
+#include "base/basictypes.h"
+
+class PrefService;
+
+// Chrome advanced options utility methods.
+class FontSettingsUtilities {
+ public:
+ static void ValidateSavedFonts(PrefService* prefs);
+
+ private:
+ DISALLOW_IMPLICIT_CONSTRUCTORS(FontSettingsUtilities);
+};
+
+#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS2_FONT_SETTINGS_UTILS_H_
diff --git a/chrome/browser/ui/webui/options2/font_settings_utils_mac.mm b/chrome/browser/ui/webui/options2/font_settings_utils_mac.mm
new file mode 100644
index 0000000..0d26d83
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/font_settings_utils_mac.mm
@@ -0,0 +1,38 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/webui/options2/font_settings_utils.h"
+
+#import <Cocoa/Cocoa.h>
+
+#include "base/mac/scoped_nsautorelease_pool.h"
+#include "base/sys_string_conversions.h"
+#include "base/values.h"
+#include "chrome/browser/prefs/pref_service.h"
+#include "chrome/common/pref_names.h"
+
+static void ValidateFontFamily(PrefService* prefs,
+ const char* family_pref_name) {
+ // The native font settings dialog saved fonts by the font name, rather
+ // than the family name. This worked for the old dialog since
+ // -[NSFont fontWithName:size] accepted a font or family name, but the
+ // behavior was technically wrong. Since we really need the family name for
+ // the dom-ui options window, we will fix the saved preference if necessary.
+ NSString *family_name =
+ base::SysUTF8ToNSString(prefs->GetString(family_pref_name));
+ NSFont *font = [NSFont fontWithName:family_name
+ size:[NSFont systemFontSize]];
+ if (font &&
+ [[font familyName] caseInsensitiveCompare:family_name] != NSOrderedSame) {
+ std::string new_family_name = base::SysNSStringToUTF8([font familyName]);
+ prefs->SetString(family_pref_name, new_family_name);
+ }
+}
+
+// static
+void FontSettingsUtilities::ValidateSavedFonts(PrefService* prefs) {
+ ValidateFontFamily(prefs, prefs::kWebKitSerifFontFamily);
+ ValidateFontFamily(prefs, prefs::kWebKitSansSerifFontFamily);
+ ValidateFontFamily(prefs, prefs::kWebKitFixedFontFamily);
+}
diff --git a/chrome/browser/ui/webui/options2/font_settings_utils_win.cc b/chrome/browser/ui/webui/options2/font_settings_utils_win.cc
new file mode 100644
index 0000000..3c5ff5e
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/font_settings_utils_win.cc
@@ -0,0 +1,11 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/webui/options2/font_settings_utils.h"
+
+// static
+void FontSettingsUtilities::ValidateSavedFonts(PrefService* prefs) {
+ // Nothing to do for Windows.
+}
+
diff --git a/chrome/browser/ui/webui/options2/font_settings_utils_x11.cc b/chrome/browser/ui/webui/options2/font_settings_utils_x11.cc
new file mode 100644
index 0000000..61c2c6e
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/font_settings_utils_x11.cc
@@ -0,0 +1,10 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/webui/options2/font_settings_utils.h"
+
+// static
+void FontSettingsUtilities::ValidateSavedFonts(PrefService* prefs) {
+ // Nothing to do for X11.
+}
diff --git a/chrome/browser/ui/webui/options2/handler_options_handler.cc b/chrome/browser/ui/webui/options2/handler_options_handler.cc
new file mode 100644
index 0000000..4c4c30f
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/handler_options_handler.cc
@@ -0,0 +1,207 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/webui/options2/handler_options_handler.h"
+
+#include <vector>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/utf_string_conversions.h"
+#include "base/values.h"
+#include "chrome/browser/prefs/pref_service.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/common/chrome_notification_types.h"
+#include "grit/generated_resources.h"
+#include "ui/base/l10n/l10n_util.h"
+
+
+HandlerOptionsHandler::HandlerOptionsHandler() {
+}
+
+HandlerOptionsHandler::~HandlerOptionsHandler() {
+}
+
+void HandlerOptionsHandler::GetLocalizedValues(
+ DictionaryValue* localized_strings) {
+ DCHECK(localized_strings);
+
+ static OptionsStringResource resources[] = {
+ { "handlers_tab_label", IDS_HANDLERS_TAB_LABEL },
+ { "handlers_allow", IDS_HANDLERS_ALLOW_RADIO },
+ { "handlers_block", IDS_HANDLERS_DONOTALLOW_RADIO },
+ { "handlers_type_column_header", IDS_HANDLERS_TYPE_COLUMN_HEADER },
+ { "handlers_site_column_header", IDS_HANDLERS_SITE_COLUMN_HEADER },
+ { "handlers_remove_link", IDS_HANDLERS_REMOVE_HANDLER_LINK },
+ { "handlers_none_handler", IDS_HANDLERS_NONE_HANDLER },
+ { "handlers_active_heading", IDS_HANDLERS_ACTIVE_HEADING },
+ { "handlers_ignored_heading", IDS_HANDLERS_IGNORED_HEADING },
+ };
+ RegisterTitle(localized_strings, "handlersPage",
+ IDS_HANDLER_OPTIONS_WINDOW_TITLE);
+ RegisterStrings(localized_strings, resources, arraysize(resources));
+}
+
+void HandlerOptionsHandler::Initialize() {
+ UpdateHandlerList();
+ notification_registrar_.Add(
+ this, chrome::NOTIFICATION_PROTOCOL_HANDLER_REGISTRY_CHANGED,
+ content::Source<Profile>(Profile::FromWebUI(web_ui_)));
+}
+
+void HandlerOptionsHandler::RegisterMessages() {
+ DCHECK(web_ui_);
+ web_ui_->RegisterMessageCallback("clearDefault",
+ base::Bind(&HandlerOptionsHandler::ClearDefault,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("removeHandler",
+ base::Bind(&HandlerOptionsHandler::RemoveHandler,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("setHandlersEnabled",
+ base::Bind(&HandlerOptionsHandler::SetHandlersEnabled,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("setDefault",
+ base::Bind(&HandlerOptionsHandler::SetDefault,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("removeIgnoredHandler",
+ base::Bind(&HandlerOptionsHandler::RemoveIgnoredHandler,
+ base::Unretained(this)));
+}
+
+ProtocolHandlerRegistry* HandlerOptionsHandler::GetProtocolHandlerRegistry() {
+ DCHECK(web_ui_);
+ return Profile::FromWebUI(web_ui_)->GetProtocolHandlerRegistry();
+}
+
+static void GetHandlersAsListValue(
+ const ProtocolHandlerRegistry::ProtocolHandlerList& handlers,
+ ListValue* handler_list) {
+ ProtocolHandlerRegistry::ProtocolHandlerList::const_iterator handler;
+ for (handler = handlers.begin(); handler != handlers.end(); ++handler) {
+ ListValue* handlerValue = new ListValue();
+ handlerValue->Append(Value::CreateStringValue(handler->protocol()));
+ handlerValue->Append(Value::CreateStringValue(handler->url().spec()));
+ handlerValue->Append(Value::CreateStringValue(handler->title()));
+ handler_list->Append(handlerValue);
+ }
+}
+
+void HandlerOptionsHandler::GetHandlersForProtocol(
+ const std::string& protocol,
+ DictionaryValue* handlers_value) {
+ ProtocolHandlerRegistry* registry = GetProtocolHandlerRegistry();
+ handlers_value->SetString("protocol", protocol);
+ handlers_value->SetInteger("default_handler",
+ registry->GetHandlerIndex(protocol));
+
+ ListValue* handlers_list = new ListValue();
+ GetHandlersAsListValue(registry->GetHandlersFor(protocol), handlers_list);
+ handlers_value->Set("handlers", handlers_list);
+}
+
+void HandlerOptionsHandler::GetIgnoredHandlers(ListValue* handlers) {
+ ProtocolHandlerRegistry* registry = GetProtocolHandlerRegistry();
+ ProtocolHandlerRegistry::ProtocolHandlerList ignored_handlers =
+ registry->GetIgnoredHandlers();
+ return GetHandlersAsListValue(ignored_handlers, handlers);
+}
+
+void HandlerOptionsHandler::UpdateHandlerList() {
+#if defined(ENABLE_REGISTER_PROTOCOL_HANDLER)
+ ProtocolHandlerRegistry* registry = GetProtocolHandlerRegistry();
+ std::vector<std::string> protocols;
+ registry->GetRegisteredProtocols(&protocols);
+
+ ListValue handlers;
+ for (std::vector<std::string>::iterator protocol = protocols.begin();
+ protocol != protocols.end(); protocol++) {
+ DictionaryValue* handler_value = new DictionaryValue();
+ GetHandlersForProtocol(*protocol, handler_value);
+ handlers.Append(handler_value);
+ }
+
+ scoped_ptr<ListValue> ignored_handlers(new ListValue());
+ GetIgnoredHandlers(ignored_handlers.get());
+ web_ui_->CallJavascriptFunction("HandlerOptions.setHandlers", handlers);
+ web_ui_->CallJavascriptFunction("HandlerOptions.setIgnoredHandlers",
+ *ignored_handlers);
+#endif // defined(ENABLE_REGISTER_PROTOCOL_HANDLER)
+}
+
+void HandlerOptionsHandler::RemoveHandler(const ListValue* args) {
+ ListValue* list;
+ if (!args->GetList(0, &list)) {
+ NOTREACHED();
+ return;
+ }
+
+ ProtocolHandler handler(ParseHandlerFromArgs(list));
+ GetProtocolHandlerRegistry()->RemoveHandler(handler);
+
+ // No need to call UpdateHandlerList() - we should receive a notification
+ // that the ProtocolHandlerRegistry has changed and we will update the view
+ // then.
+}
+
+void HandlerOptionsHandler::RemoveIgnoredHandler(const ListValue* args) {
+ ListValue* list;
+ if (!args->GetList(0, &list)) {
+ NOTREACHED();
+ return;
+ }
+
+ ProtocolHandler handler(ParseHandlerFromArgs(list));
+ GetProtocolHandlerRegistry()->RemoveIgnoredHandler(handler);
+}
+
+void HandlerOptionsHandler::SetHandlersEnabled(const ListValue* args) {
+ bool enabled = true;
+ CHECK(args->GetBoolean(0, &enabled));
+ if (enabled)
+ GetProtocolHandlerRegistry()->Enable();
+ else
+ GetProtocolHandlerRegistry()->Disable();
+}
+
+void HandlerOptionsHandler::ClearDefault(const ListValue* args) {
+ Value* value;
+ CHECK(args->Get(0, &value));
+ std::string protocol_to_clear;
+ CHECK(value->GetAsString(&protocol_to_clear));
+ GetProtocolHandlerRegistry()->ClearDefault(protocol_to_clear);
+}
+
+void HandlerOptionsHandler::SetDefault(const ListValue* args) {
+ Value* value;
+ CHECK(args->Get(0, &value));
+ ListValue* list;
+ CHECK(args->GetList(0, &list));
+ const ProtocolHandler& handler(ParseHandlerFromArgs(list));
+ CHECK(!handler.IsEmpty());
+ GetProtocolHandlerRegistry()->OnAcceptRegisterProtocolHandler(handler);
+}
+
+ProtocolHandler HandlerOptionsHandler::ParseHandlerFromArgs(
+ const ListValue* args) const {
+ string16 protocol;
+ string16 url;
+ string16 title;
+ bool ok = args->GetString(0, &protocol) && args->GetString(1, &url) &&
+ args->GetString(2, &title);
+ if (!ok)
+ return ProtocolHandler::EmptyProtocolHandler();
+ return ProtocolHandler::CreateProtocolHandler(UTF16ToUTF8(protocol),
+ GURL(UTF16ToUTF8(url)),
+ title);
+}
+
+void HandlerOptionsHandler::Observe(
+ int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) {
+ if (type == chrome::NOTIFICATION_PROTOCOL_HANDLER_REGISTRY_CHANGED)
+ UpdateHandlerList();
+ else
+ NOTREACHED();
+}
diff --git a/chrome/browser/ui/webui/options2/handler_options_handler.h b/chrome/browser/ui/webui/options2/handler_options_handler.h
new file mode 100644
index 0000000..4699f82
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/handler_options_handler.h
@@ -0,0 +1,76 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS2_HANDLER_OPTIONS_HANDLER_H_
+#define CHROME_BROWSER_UI_WEBUI_OPTIONS2_HANDLER_OPTIONS_HANDLER_H_
+
+#include <string>
+
+#include "chrome/browser/custom_handlers/protocol_handler_registry.h"
+#include "chrome/browser/ui/webui/options2/options_ui2.h"
+#include "chrome/common/custom_handlers/protocol_handler.h"
+#include "content/public/browser/notification_registrar.h"
+
+namespace base {
+class DictionaryValue;
+}
+
+class HandlerOptionsHandler : public OptionsPage2UIHandler {
+ public:
+ HandlerOptionsHandler();
+ virtual ~HandlerOptionsHandler();
+
+ // OptionsPage2UIHandler implementation.
+ virtual void GetLocalizedValues(
+ base::DictionaryValue* localized_strings) OVERRIDE;
+ virtual void Initialize() OVERRIDE;
+ virtual void RegisterMessages() OVERRIDE;
+
+ // content::NotificationObserver implementation.
+ virtual void Observe(int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) OVERRIDE;
+
+ private:
+ // Called when the user toggles whether custom handlers are enabled.
+ void SetHandlersEnabled(const ListValue* args);
+
+ // Called when the user sets a new default handler for a protocol.
+ void SetDefault(const ListValue* args);
+
+ // Called when the user clears the default handler for a protocol.
+ // |args| is the string name of the protocol to clear.
+ void ClearDefault(const ListValue* args);
+
+ // Parses a ProtocolHandler out of the arguments passed back from the view.
+ // |args| is a list of [protocol, url, title].
+ ProtocolHandler ParseHandlerFromArgs(const ListValue* args) const;
+
+ // Returns a JSON object describing the set of protocol handlers for the
+ // given protocol.
+ void GetHandlersForProtocol(const std::string& protocol,
+ base::DictionaryValue* value);
+
+ // Returns a JSON list of the ignored protocol handlers.
+ void GetIgnoredHandlers(ListValue* handlers);
+
+ // Called when the JS PasswordManager object is initialized.
+ void UpdateHandlerList();
+
+ // Remove a handler.
+ // |args| is a list of [protocol, url, title].
+ void RemoveHandler(const ListValue* args);
+
+ // Remove an ignored handler.
+ // |args| is a list of [protocol, url, title].
+ void RemoveIgnoredHandler(const ListValue* args);
+
+ ProtocolHandlerRegistry* GetProtocolHandlerRegistry();
+
+ content::NotificationRegistrar notification_registrar_;
+
+ DISALLOW_COPY_AND_ASSIGN(HandlerOptionsHandler);
+};
+
+#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS2_HANDLER_OPTIONS_HANDLER_H_
diff --git a/chrome/browser/ui/webui/options2/import_data_handler.cc b/chrome/browser/ui/webui/options2/import_data_handler.cc
new file mode 100644
index 0000000..76e5b83
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/import_data_handler.cc
@@ -0,0 +1,177 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/webui/options2/import_data_handler.h"
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/string16.h"
+#include "base/string_number_conversions.h"
+#include "base/string_util.h"
+#include "base/threading/thread_restrictions.h"
+#include "base/utf_string_conversions.h"
+#include "base/values.h"
+#include "chrome/browser/importer/external_process_importer_host.h"
+#include "chrome/browser/importer/importer_host.h"
+#include "chrome/browser/importer/importer_list.h"
+#include "chrome/browser/profiles/profile.h"
+#include "grit/chromium_strings.h"
+#include "grit/generated_resources.h"
+#include "ui/base/l10n/l10n_util.h"
+
+ImportDataHandler::ImportDataHandler() : importer_host_(NULL),
+ import_did_succeed_(false) {
+}
+
+ImportDataHandler::~ImportDataHandler() {
+ if (importer_list_)
+ importer_list_->SetObserver(NULL);
+
+ if (importer_host_)
+ importer_host_->SetObserver(NULL);
+}
+
+void ImportDataHandler::GetLocalizedValues(DictionaryValue* localized_strings) {
+ DCHECK(localized_strings);
+
+ static OptionsStringResource resources[] = {
+ { "importFromLabel", IDS_IMPORT_FROM_LABEL },
+ { "importLoading", IDS_IMPORT_LOADING_PROFILES },
+ { "importDescription", IDS_IMPORT_ITEMS_LABEL },
+ { "importHistory", IDS_IMPORT_HISTORY_CHKBOX },
+ { "importFavorites", IDS_IMPORT_FAVORITES_CHKBOX },
+ { "importSearch", IDS_IMPORT_SEARCH_ENGINES_CHKBOX },
+ { "importPasswords", IDS_IMPORT_PASSWORDS_CHKBOX },
+ { "importCommit", IDS_IMPORT_COMMIT },
+ { "noProfileFound", IDS_IMPORT_NO_PROFILE_FOUND },
+ { "importSucceeded", IDS_IMPORT_SUCCEEDED },
+ { "findYourImportedBookmarks", IDS_IMPORT_FIND_YOUR_BOOKMARKS },
+ };
+
+ RegisterStrings(localized_strings, resources, arraysize(resources));
+ RegisterTitle(localized_strings, "importDataOverlay",
+ IDS_IMPORT_SETTINGS_TITLE);
+}
+
+void ImportDataHandler::Initialize() {
+ Profile* profile = Profile::FromWebUI(web_ui_);
+ importer_list_ = new ImporterList(profile->GetRequestContext());
+ importer_list_->DetectSourceProfiles(this);
+}
+
+void ImportDataHandler::RegisterMessages() {
+ web_ui_->RegisterMessageCallback("importData",
+ base::Bind(&ImportDataHandler::ImportData, base::Unretained(this)));
+}
+
+void ImportDataHandler::ImportData(const ListValue* args) {
+ std::string string_value;
+
+ int browser_index;
+ if (!args->GetString(0, &string_value) ||
+ !base::StringToInt(string_value, &browser_index)) {
+ NOTREACHED();
+ return;
+ }
+
+ uint16 selected_items = importer::NONE;
+ if (args->GetString(1, &string_value) && string_value == "true") {
+ selected_items |= importer::HISTORY;
+ }
+ if (args->GetString(2, &string_value) && string_value == "true") {
+ selected_items |= importer::FAVORITES;
+ }
+ if (args->GetString(3, &string_value) && string_value == "true") {
+ selected_items |= importer::PASSWORDS;
+ }
+ if (args->GetString(4, &string_value) && string_value == "true") {
+ selected_items |= importer::SEARCH_ENGINES;
+ }
+
+ const importer::SourceProfile& source_profile =
+ importer_list_->GetSourceProfileAt(browser_index);
+ uint16 supported_items = source_profile.services_supported;
+
+ uint16 import_services = (selected_items & supported_items);
+ if (import_services) {
+ base::FundamentalValue state(true);
+ web_ui_->CallJavascriptFunction("ImportDataOverlay.setImportingState",
+ state);
+ import_did_succeed_ = false;
+
+ // TODO(csilv): Out-of-process import has only been qualified on MacOS X,
+ // so we will only use it on that platform since it is required. Remove this
+ // conditional logic once oop import is qualified for Linux/Windows.
+ // http://crbug.com/22142
+#if defined(OS_MACOSX)
+ importer_host_ = new ExternalProcessImporterHost;
+#else
+ importer_host_ = new ImporterHost;
+#endif
+ importer_host_->SetObserver(this);
+ Profile* profile = Profile::FromWebUI(web_ui_);
+ importer_host_->StartImportSettings(source_profile, profile,
+ import_services,
+ new ProfileWriter(profile), false);
+ } else {
+ LOG(WARNING) << "There were no settings to import from '"
+ << source_profile.importer_name << "'.";
+ }
+}
+
+void ImportDataHandler::OnSourceProfilesLoaded() {
+ ListValue browser_profiles;
+ for (size_t i = 0; i < importer_list_->count(); ++i) {
+ const importer::SourceProfile& source_profile =
+ importer_list_->GetSourceProfileAt(i);
+ uint16 browser_services = source_profile.services_supported;
+
+ DictionaryValue* browser_profile = new DictionaryValue();
+ browser_profile->SetString("name", source_profile.importer_name);
+ browser_profile->SetInteger("index", i);
+ browser_profile->SetBoolean("history",
+ (browser_services & importer::HISTORY) != 0);
+ browser_profile->SetBoolean("favorites",
+ (browser_services & importer::FAVORITES) != 0);
+ browser_profile->SetBoolean("passwords",
+ (browser_services & importer::PASSWORDS) != 0);
+ browser_profile->SetBoolean("search",
+ (browser_services & importer::SEARCH_ENGINES) != 0);
+
+ browser_profiles.Append(browser_profile);
+ }
+
+ web_ui_->CallJavascriptFunction("ImportDataOverlay.updateSupportedBrowsers",
+ browser_profiles);
+}
+
+void ImportDataHandler::ImportStarted() {
+}
+
+void ImportDataHandler::ImportItemStarted(importer::ImportItem item) {
+ // TODO(csilv): show progress detail in the web view.
+}
+
+void ImportDataHandler::ImportItemEnded(importer::ImportItem item) {
+ // TODO(csilv): show progress detail in the web view.
+ import_did_succeed_ = true;
+}
+
+void ImportDataHandler::ImportEnded() {
+ importer_host_->SetObserver(NULL);
+ importer_host_ = NULL;
+
+ if (import_did_succeed_) {
+ web_ui_->CallJavascriptFunction("ImportDataOverlay.confirmSuccess");
+ } else {
+ base::FundamentalValue state(false);
+ web_ui_->CallJavascriptFunction("ImportDataOverlay.setImportingState",
+ state);
+ web_ui_->CallJavascriptFunction("ImportDataOverlay.dismiss");
+ }
+}
diff --git a/chrome/browser/ui/webui/options2/import_data_handler.h b/chrome/browser/ui/webui/options2/import_data_handler.h
new file mode 100644
index 0000000..f537c54
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/import_data_handler.h
@@ -0,0 +1,59 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS2_IMPORT_DATA_HANDLER_H_
+#define CHROME_BROWSER_UI_WEBUI_OPTIONS2_IMPORT_DATA_HANDLER_H_
+#pragma once
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/memory/ref_counted.h"
+#include "chrome/browser/importer/importer_data_types.h"
+#include "chrome/browser/importer/importer_list_observer.h"
+#include "chrome/browser/importer/importer_progress_observer.h"
+#include "chrome/browser/ui/webui/options2/options_ui2.h"
+
+class ImporterHost;
+class ImporterList;
+
+// Chrome personal stuff import data overlay UI handler.
+class ImportDataHandler : public OptionsPage2UIHandler,
+ public importer::ImporterListObserver,
+ public importer::ImporterProgressObserver {
+ public:
+ ImportDataHandler();
+ virtual ~ImportDataHandler();
+
+ // OptionsPage2UIHandler:
+ virtual void GetLocalizedValues(
+ base::DictionaryValue* localized_strings) OVERRIDE;
+ virtual void Initialize() OVERRIDE;
+
+ // WebUIMessageHandler:
+ virtual void RegisterMessages() OVERRIDE;
+
+ private:
+ void ImportData(const base::ListValue* args);
+
+ // importer::ImporterListObserver:
+ virtual void OnSourceProfilesLoaded() OVERRIDE;
+
+ // importer::ImporterProgressObserver:
+ virtual void ImportStarted() OVERRIDE;
+ virtual void ImportItemStarted(importer::ImportItem item) OVERRIDE;
+ virtual void ImportItemEnded(importer::ImportItem item) OVERRIDE;
+ virtual void ImportEnded() OVERRIDE;
+
+ scoped_refptr<ImporterList> importer_list_;
+
+ // If non-null it means importing is in progress. ImporterHost takes care
+ // of deleting itself when import is complete.
+ ImporterHost* importer_host_; // weak
+
+ bool import_did_succeed_;
+
+ DISALLOW_COPY_AND_ASSIGN(ImportDataHandler);
+};
+
+#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS2_IMPORT_DATA_HANDLER_H_
diff --git a/chrome/browser/ui/webui/options2/language_options_browsertest.js b/chrome/browser/ui/webui/options2/language_options_browsertest.js
new file mode 100644
index 0000000..4f1b87c
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/language_options_browsertest.js
@@ -0,0 +1,24 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * TestFixture for languages options WebUI testing.
+ * @extends {testing.Test}
+ * @constructor
+ **/
+function LanguagesOptionsWebUITest() {}
+
+LanguagesOptionsWebUITest.prototype = {
+ __proto__: testing.Test.prototype,
+
+ /**
+ * Browse to languages options.
+ **/
+ browsePreload: 'chrome://settings/languages',
+};
+
+// Test opening languages options has correct location.
+TEST_F('LanguagesOptionsWebUITest', 'testOpenLanguagesOptions', function() {
+ assertEquals(this.browsePreload, document.location.href);
+});
diff --git a/chrome/browser/ui/webui/options2/language_options_handler.cc b/chrome/browser/ui/webui/options2/language_options_handler.cc
new file mode 100644
index 0000000..64e4d8c
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/language_options_handler.cc
@@ -0,0 +1,118 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/webui/options2/language_options_handler.h"
+
+#include <map>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/i18n/rtl.h"
+#include "base/utf_string_conversions.h"
+#include "base/values.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/prefs/pref_service.h"
+#include "chrome/browser/ui/browser_list.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/common/pref_names.h"
+#include "content/public/browser/user_metrics.h"
+#include "grit/chromium_strings.h"
+#include "grit/generated_resources.h"
+#include "ui/base/l10n/l10n_util.h"
+
+using content::UserMetricsAction;
+
+LanguageOptionsHandler::LanguageOptionsHandler() {
+}
+
+LanguageOptionsHandler::~LanguageOptionsHandler() {
+}
+
+void LanguageOptionsHandler::GetLocalizedValues(
+ DictionaryValue* localized_strings) {
+ LanguageOptionsHandlerCommon::GetLocalizedValues(localized_strings);
+
+ RegisterTitle(localized_strings, "languagePage",
+ IDS_OPTIONS_SETTINGS_LANGUAGES_DIALOG_TITLE);
+ localized_strings->SetString("restart_button",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_LANGUAGES_RELAUNCH_BUTTON));
+ localized_strings->Set("languageList", GetLanguageList());
+}
+
+void LanguageOptionsHandler::RegisterMessages() {
+ LanguageOptionsHandlerCommon::RegisterMessages();
+
+ web_ui_->RegisterMessageCallback("uiLanguageRestart",
+ base::Bind(&LanguageOptionsHandler::RestartCallback,
+ base::Unretained(this)));
+}
+
+ListValue* LanguageOptionsHandler::GetLanguageList() {
+ // Collect the language codes from the supported accept-languages.
+ const std::string app_locale = g_browser_process->GetApplicationLocale();
+ std::vector<std::string> language_codes;
+ l10n_util::GetAcceptLanguagesForLocale(app_locale, &language_codes);
+
+ // Map of display name -> {language code, native_display_name}.
+ // In theory, we should be able to create a map that is sorted by
+ // display names using ICU comparator, but doing it is hard, thus we'll
+ // use an auxiliary vector to achieve the same result.
+ typedef std::pair<std::string, string16> LanguagePair;
+ typedef std::map<string16, LanguagePair> LanguageMap;
+ LanguageMap language_map;
+ // The auxiliary vector mentioned above.
+ std::vector<string16> display_names;
+
+ // Build the list of display names, and build the language map.
+ for (size_t i = 0; i < language_codes.size(); ++i) {
+ string16 display_name =
+ l10n_util::GetDisplayNameForLocale(language_codes[i], app_locale,
+ false);
+ base::i18n::AdjustStringForLocaleDirection(&display_name);
+ string16 native_display_name =
+ l10n_util::GetDisplayNameForLocale(language_codes[i], language_codes[i],
+ false);
+ base::i18n::AdjustStringForLocaleDirection(&native_display_name);
+ display_names.push_back(display_name);
+ language_map[display_name] =
+ std::make_pair(language_codes[i], native_display_name);
+ }
+ DCHECK_EQ(display_names.size(), language_map.size());
+
+ // Sort display names using locale specific sorter.
+ l10n_util::SortStrings16(app_locale, &display_names);
+
+ // Build the language list from the language map.
+ ListValue* language_list = new ListValue();
+ for (size_t i = 0; i < display_names.size(); ++i) {
+ const LanguagePair& pair = language_map[display_names[i]];
+ DictionaryValue* dictionary = new DictionaryValue();
+ dictionary->SetString("code", pair.first);
+ dictionary->SetString("displayName", display_names[i]);
+ dictionary->SetString("nativeDisplayName", pair.second);
+ language_list->Append(dictionary);
+ }
+
+ return language_list;
+}
+
+string16 LanguageOptionsHandler::GetProductName() {
+ return l10n_util::GetStringUTF16(IDS_PRODUCT_NAME);
+}
+
+void LanguageOptionsHandler::SetApplicationLocale(
+ const std::string& language_code) {
+ PrefService* pref_service = g_browser_process->local_state();
+ pref_service->SetString(prefs::kApplicationLocale, language_code);
+}
+
+void LanguageOptionsHandler::RestartCallback(const ListValue* args) {
+ content::RecordAction(UserMetricsAction("LanguageOptions_Restart"));
+ BrowserList::AttemptRestart();
+}
diff --git a/chrome/browser/ui/webui/options2/language_options_handler.h b/chrome/browser/ui/webui/options2/language_options_handler.h
new file mode 100644
index 0000000..1131b32
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/language_options_handler.h
@@ -0,0 +1,44 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS2_LANGUAGE_OPTIONS_HANDLER_H_
+#define CHROME_BROWSER_UI_WEBUI_OPTIONS2_LANGUAGE_OPTIONS_HANDLER_H_
+#pragma once
+
+#include "chrome/browser/ui/webui/options2/language_options_handler_common.h"
+
+// Language options UI page handler for non-Chrome OS platforms. For Chrome OS,
+// see chromeos::CrosLanguageOptionsHandler.
+class LanguageOptionsHandler : public LanguageOptionsHandlerCommon {
+ public:
+ LanguageOptionsHandler();
+ virtual ~LanguageOptionsHandler();
+
+ // OptionsPageUIHandler implementation.
+ virtual void GetLocalizedValues(
+ base::DictionaryValue* localized_strings) OVERRIDE;
+
+ // WebUIMessageHandler implementation.
+ virtual void RegisterMessages() OVERRIDE;
+
+ // The following static method is public for ease of testing.
+
+ // Gets the list of languages from the given input descriptors.
+ // The return value will look like:
+ // [{'code': 'fi', 'displayName': 'Finnish', 'nativeDisplayName': 'suomi'},
+ // ...]
+ static base::ListValue* GetLanguageList();
+
+ private:
+ // LanguageOptionsHandlerCommon implementation.
+ virtual string16 GetProductName() OVERRIDE;
+ virtual void SetApplicationLocale(const std::string& language_code) OVERRIDE;
+
+ // Called when the restart button is clicked.
+ void RestartCallback(const base::ListValue* args);
+
+ DISALLOW_COPY_AND_ASSIGN(LanguageOptionsHandler);
+};
+
+#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS2_LANGUAGE_OPTIONS_HANDLER_H_
diff --git a/chrome/browser/ui/webui/options2/language_options_handler_common.cc b/chrome/browser/ui/webui/options2/language_options_handler_common.cc
new file mode 100644
index 0000000..cff8ee7
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/language_options_handler_common.cc
@@ -0,0 +1,168 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/webui/options2/language_options_handler_common.h"
+
+#include <map>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/stringprintf.h"
+#include "base/utf_string_conversions.h"
+#include "base/values.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/prefs/pref_service.h"
+#include "chrome/browser/ui/browser_list.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/common/pref_names.h"
+#include "chrome/common/spellcheck_common.h"
+#include "content/public/browser/user_metrics.h"
+#include "grit/chromium_strings.h"
+#include "grit/generated_resources.h"
+#include "ui/base/l10n/l10n_util.h"
+
+using content::UserMetricsAction;
+
+LanguageOptionsHandlerCommon::LanguageOptionsHandlerCommon() {
+}
+
+LanguageOptionsHandlerCommon::~LanguageOptionsHandlerCommon() {
+}
+
+void LanguageOptionsHandlerCommon::GetLocalizedValues(
+ DictionaryValue* localized_strings) {
+ DCHECK(localized_strings);
+ string16 product_name = GetProductName();
+ localized_strings->SetString("add_button",
+ l10n_util::GetStringUTF16(IDS_OPTIONS_SETTINGS_LANGUAGES_ADD_BUTTON));
+ localized_strings->SetString("languages",
+ l10n_util::GetStringUTF16(IDS_OPTIONS_SETTINGS_LANGUAGES_LANGUAGES));
+ localized_strings->SetString("please_add_another_language",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_LANGUAGES_PLEASE_ADD_ANOTHER_LANGUAGE));
+ localized_strings->SetString("remove_button",
+ l10n_util::GetStringUTF16(IDS_OPTIONS_SETTINGS_LANGUAGES_REMOVE_BUTTON));
+ localized_strings->SetString("add_language_instructions",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_LANGUAGES_ADD_LANGUAGE_INSTRUCTIONS));
+ localized_strings->SetString("cannot_be_displayed_in_this_language",
+ l10n_util::GetStringFUTF16(
+ IDS_OPTIONS_SETTINGS_LANGUAGES_CANNOT_BE_DISPLAYED_IN_THIS_LANGUAGE,
+ product_name));
+ localized_strings->SetString("is_displayed_in_this_language",
+ l10n_util::GetStringFUTF16(
+ IDS_OPTIONS_SETTINGS_LANGUAGES_IS_DISPLAYED_IN_THIS_LANGUAGE,
+ product_name));
+ localized_strings->SetString("display_in_this_language",
+ l10n_util::GetStringFUTF16(
+ IDS_OPTIONS_SETTINGS_LANGUAGES_DISPLAY_IN_THIS_LANGUAGE,
+ product_name));
+ localized_strings->SetString("this_language_is_currently_in_use",
+ l10n_util::GetStringFUTF16(
+ IDS_OPTIONS_SETTINGS_LANGUAGES_THIS_LANGUAGE_IS_CURRENTLY_IN_USE,
+ product_name));
+ localized_strings->SetString("restart_required",
+ l10n_util::GetStringUTF16(IDS_OPTIONS_RELAUNCH_REQUIRED));
+ // OS X uses the OS native spellchecker so no need for these strings.
+#if !defined(OS_MACOSX)
+ localized_strings->SetString("use_this_for_spell_checking",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_USE_THIS_FOR_SPELL_CHECKING));
+ localized_strings->SetString("cannot_be_used_for_spell_checking",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_CANNOT_BE_USED_FOR_SPELL_CHECKING));
+ localized_strings->SetString("is_used_for_spell_checking",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_IS_USED_FOR_SPELL_CHECKING));
+ localized_strings->SetString("enable_spell_check",
+ l10n_util::GetStringUTF16(IDS_OPTIONS_ENABLE_SPELLCHECK));
+ localized_strings->SetString("enable_auto_spell_correction",
+ l10n_util::GetStringUTF16(IDS_OPTIONS_ENABLE_AUTO_SPELL_CORRECTION));
+#endif // !OS_MACOSX
+ localized_strings->SetString("add_language_title",
+ l10n_util::GetStringUTF16(IDS_OPTIONS_LANGUAGES_ADD_TITLE));
+ localized_strings->SetString("add_language_select_label",
+ l10n_util::GetStringUTF16(IDS_OPTIONS_LANGUAGES_ADD_SELECT_LABEL));
+ localized_strings->SetString("restart_button",
+ l10n_util::GetStringUTF16(
+ IDS_OPTIONS_SETTINGS_LANGUAGES_RELAUNCH_BUTTON));
+
+ // The following are resources, rather than local strings.
+ localized_strings->SetString("currentUiLanguageCode",
+ g_browser_process->GetApplicationLocale());
+ localized_strings->Set("spellCheckLanguageCodeSet",
+ GetSpellCheckLanguageCodeSet());
+ localized_strings->Set("uiLanguageCodeSet", GetUILanguageCodeSet());
+
+ const CommandLine& command_line = *CommandLine::ForCurrentProcess();
+ bool experimental_spell_check_features =
+ command_line.HasSwitch(switches::kExperimentalSpellcheckerFeatures);
+ localized_strings->SetBoolean("experimentalSpellCheckFeatures",
+ experimental_spell_check_features);
+}
+
+void LanguageOptionsHandlerCommon::RegisterMessages() {
+ DCHECK(web_ui_);
+ web_ui_->RegisterMessageCallback("languageOptionsOpen",
+ base::Bind(
+ &LanguageOptionsHandlerCommon::LanguageOptionsOpenCallback,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("spellCheckLanguageChange",
+ base::Bind(
+ &LanguageOptionsHandlerCommon::SpellCheckLanguageChangeCallback,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("uiLanguageChange",
+ base::Bind(
+ &LanguageOptionsHandlerCommon::UiLanguageChangeCallback,
+ base::Unretained(this)));
+}
+
+DictionaryValue* LanguageOptionsHandlerCommon::GetUILanguageCodeSet() {
+ DictionaryValue* dictionary = new DictionaryValue();
+ const std::vector<std::string>& available_locales =
+ l10n_util::GetAvailableLocales();
+ for (size_t i = 0; i < available_locales.size(); ++i) {
+ dictionary->SetBoolean(available_locales[i], true);
+ }
+ return dictionary;
+}
+
+DictionaryValue* LanguageOptionsHandlerCommon::GetSpellCheckLanguageCodeSet() {
+ DictionaryValue* dictionary = new DictionaryValue();
+ std::vector<std::string> spell_check_languages;
+ SpellCheckCommon::SpellCheckLanguages(&spell_check_languages);
+ for (size_t i = 0; i < spell_check_languages.size(); ++i) {
+ dictionary->SetBoolean(spell_check_languages[i], true);
+ }
+ return dictionary;
+}
+
+void LanguageOptionsHandlerCommon::LanguageOptionsOpenCallback(
+ const ListValue* args) {
+ content::RecordAction(UserMetricsAction("LanguageOptions_Open"));
+}
+
+void LanguageOptionsHandlerCommon::UiLanguageChangeCallback(
+ const ListValue* args) {
+ const std::string language_code = UTF16ToASCII(ExtractStringValue(args));
+ CHECK(!language_code.empty());
+ const std::string action = base::StringPrintf(
+ "LanguageOptions_UiLanguageChange_%s", language_code.c_str());
+ content::RecordComputedAction(action);
+ SetApplicationLocale(language_code);
+ web_ui_->CallJavascriptFunction("options.LanguageOptions.uiLanguageSaved");
+}
+
+void LanguageOptionsHandlerCommon::SpellCheckLanguageChangeCallback(
+ const ListValue* args) {
+ const std::string language_code = UTF16ToASCII(ExtractStringValue(args));
+ CHECK(!language_code.empty());
+ const std::string action = base::StringPrintf(
+ "LanguageOptions_SpellCheckLanguageChange_%s", language_code.c_str());
+ content::RecordComputedAction(action);
+}
diff --git a/chrome/browser/ui/webui/options2/language_options_handler_common.h b/chrome/browser/ui/webui/options2/language_options_handler_common.h
new file mode 100644
index 0000000..562a722
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/language_options_handler_common.h
@@ -0,0 +1,69 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS2_LANGUAGE_OPTIONS_HANDLER_COMMON_H_
+#define CHROME_BROWSER_UI_WEBUI_OPTIONS2_LANGUAGE_OPTIONS_HANDLER_COMMON_H_
+#pragma once
+
+#include "chrome/browser/ui/webui/options2/options_ui2.h"
+
+namespace base {
+class DictionaryValue;
+class ListValue;
+}
+
+// The base class for language options page UI handlers. This class has code
+// common to the Chrome OS and non-Chrome OS implementation of the handler.
+class LanguageOptionsHandlerCommon : public OptionsPage2UIHandler {
+ public:
+ LanguageOptionsHandlerCommon();
+ virtual ~LanguageOptionsHandlerCommon();
+
+ // OptionsPage2UIHandler implementation.
+ virtual void GetLocalizedValues(
+ base::DictionaryValue* localized_strings) OVERRIDE;
+
+ // DOMMessageHandler implementation.
+ virtual void RegisterMessages() OVERRIDE;
+
+ // The following static methods are public for ease of testing.
+
+ // Gets the set of language codes that can be used as UI language.
+ // The return value will look like:
+ // {'en-US': true, 'fi': true, 'fr': true, ...}
+ //
+ // Note that true in values does not mean anything. We just use the
+ // dictionary as a set.
+ static base::DictionaryValue* GetUILanguageCodeSet();
+
+ // Gets the set of language codes that can be used for spellchecking.
+ // The return value will look like:
+ // {'en-US': true, 'fi': true, 'fr': true, ...}
+ //
+ // Note that true in values does not mean anything. We just use the
+ // dictionary as a set.
+ static base::DictionaryValue* GetSpellCheckLanguageCodeSet();
+
+ private:
+ // Returns the name of the product (ex. "Chrome" or "Chrome OS").
+ virtual string16 GetProductName() = 0;
+
+ // Sets the application locale.
+ virtual void SetApplicationLocale(const std::string& language_code) = 0;
+
+ // Called when the language options is opened.
+ void LanguageOptionsOpenCallback(const base::ListValue* args);
+
+ // Called when the UI language is changed.
+ // |args| will contain the language code as string (ex. "fr").
+ void UiLanguageChangeCallback(const base::ListValue* args);
+
+ // Called when the spell check language is changed.
+ // |args| will contain the language code as string (ex. "fr").
+ void SpellCheckLanguageChangeCallback(const base::ListValue* args);
+
+ DISALLOW_COPY_AND_ASSIGN(LanguageOptionsHandlerCommon);
+};
+
+#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS2_LANGUAGE_OPTIONS_HANDLER_COMMON_H_
diff --git a/chrome/browser/ui/webui/options2/language_options_handler_unittest.cc b/chrome/browser/ui/webui/options2/language_options_handler_unittest.cc
new file mode 100644
index 0000000..c889818
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/language_options_handler_unittest.cc
@@ -0,0 +1,198 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/webui/options2/language_options_handler.h"
+
+#include <string>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/values.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#if defined(OS_CHROMEOS)
+#include "chrome/browser/chromeos/input_method/ibus_controller.h"
+#include "chrome/browser/chromeos/input_method/input_method_manager.h"
+#include "chrome/browser/ui/webui/options2/chromeos/cros_language_options_handler.h"
+#endif // defined(OS_CHROMEOS)
+
+#if defined(OS_CHROMEOS)
+
+using chromeos::input_method::IBusController;
+using chromeos::input_method::InputMethodDescriptor;
+using chromeos::input_method::InputMethodDescriptors;
+
+static InputMethodDescriptor GetDesc(IBusController* controller,
+ const std::string& id,
+ const std::string& raw_layout,
+ const std::string& language_code) {
+ return controller->CreateInputMethodDescriptor(id, "", raw_layout,
+ language_code);
+}
+
+static InputMethodDescriptors CreateInputMethodDescriptors() {
+ scoped_ptr<IBusController> controller(IBusController::Create());
+
+ InputMethodDescriptors descriptors;
+ descriptors.push_back(GetDesc(controller.get(), "xkb:us::eng", "us", "eng"));
+ descriptors.push_back(GetDesc(controller.get(), "xkb:fr::fra", "fr", "fra"));
+ descriptors.push_back(GetDesc(controller.get(), "xkb:be::fra", "be", "fr"));
+ descriptors.push_back(GetDesc(controller.get(), "mozc", "us", "ja"));
+ return descriptors;
+}
+
+TEST(LanguageOptionsHandlerTest, GetInputMethodList) {
+ InputMethodDescriptors descriptors = CreateInputMethodDescriptors();
+ scoped_ptr<ListValue> list(
+ chromeos::CrosLanguageOptionsHandler::GetInputMethodList(descriptors));
+ ASSERT_EQ(4U, list->GetSize());
+
+ DictionaryValue* entry = NULL;
+ DictionaryValue *language_code_set = NULL;
+ std::string input_method_id;
+ std::string display_name;
+ std::string language_code;
+
+ // As shown below, the list should be input method ids should appear in
+ // the same order of the descriptors.
+ ASSERT_TRUE(list->GetDictionary(0, &entry));
+ ASSERT_TRUE(entry->GetString("id", &input_method_id));
+ ASSERT_TRUE(entry->GetString("displayName", &display_name));
+ ASSERT_TRUE(entry->GetDictionary("languageCodeSet", &language_code_set));
+ EXPECT_EQ("xkb:us::eng", input_method_id);
+ // Commented out as it depends on translation in generated_resources.grd
+ // (i.e. makes the test fragile).
+ // EXPECT_EQ("English (USA) keyboard layout", display_name);
+ ASSERT_TRUE(language_code_set->HasKey("en-US"));
+ ASSERT_TRUE(language_code_set->HasKey("id")); // From kExtraLanguages.
+ ASSERT_TRUE(language_code_set->HasKey("fil")); // From kExtraLanguages.
+
+ ASSERT_TRUE(list->GetDictionary(1, &entry));
+ ASSERT_TRUE(entry->GetString("id", &input_method_id));
+ ASSERT_TRUE(entry->GetString("displayName", &display_name));
+ ASSERT_TRUE(entry->GetDictionary("languageCodeSet", &language_code_set));
+ EXPECT_EQ("xkb:fr::fra", input_method_id);
+ // Commented out. See above.
+ // EXPECT_EQ("French keyboard layout", display_name);
+ ASSERT_TRUE(language_code_set->HasKey("fr"));
+
+ ASSERT_TRUE(list->GetDictionary(2, &entry));
+ ASSERT_TRUE(entry->GetString("id", &input_method_id));
+ ASSERT_TRUE(entry->GetString("displayName", &display_name));
+ ASSERT_TRUE(entry->GetDictionary("languageCodeSet", &language_code_set));
+ EXPECT_EQ("xkb:be::fra", input_method_id);
+ // Commented out. See above.
+ // EXPECT_EQ("Belgian keyboard layout", display_name);
+ ASSERT_TRUE(language_code_set->HasKey("fr"));
+
+ ASSERT_TRUE(list->GetDictionary(3, &entry));
+ ASSERT_TRUE(entry->GetString("id", &input_method_id));
+ ASSERT_TRUE(entry->GetString("displayName", &display_name));
+ ASSERT_TRUE(entry->GetDictionary("languageCodeSet", &language_code_set));
+ EXPECT_EQ("mozc", input_method_id);
+ // Commented out. See above.
+ // EXPECT_EQ("Japanese input method (for US keyboard)", display_name);
+ ASSERT_TRUE(language_code_set->HasKey("ja"));
+}
+
+TEST(LanguageOptionsHandlerTest, GetLanguageList) {
+ InputMethodDescriptors descriptors = CreateInputMethodDescriptors();
+ scoped_ptr<ListValue> list(
+ chromeos::CrosLanguageOptionsHandler::GetLanguageList(descriptors));
+ ASSERT_EQ(8U, list->GetSize());
+
+ DictionaryValue* entry = NULL;
+ std::string language_code;
+ std::string display_name;
+ std::string native_display_name;
+
+ // As shown below, the list should be sorted by the display names,
+ // and these names should not have duplicates.
+
+ // This comes from kExtraLanguages.
+ ASSERT_TRUE(list->GetDictionary(0, &entry));
+ ASSERT_TRUE(entry->GetString("code", &language_code));
+ ASSERT_TRUE(entry->GetString("displayName", &display_name));
+ ASSERT_TRUE(entry->GetString("nativeDisplayName", &native_display_name));
+ EXPECT_EQ("nl", language_code);
+ EXPECT_EQ("Dutch", display_name);
+ EXPECT_EQ("Nederlands", native_display_name);
+
+ // This comes from kExtraLanguages.
+ ASSERT_TRUE(list->GetDictionary(1, &entry));
+ ASSERT_TRUE(entry->GetString("code", &language_code));
+ ASSERT_TRUE(entry->GetString("displayName", &display_name));
+ ASSERT_TRUE(entry->GetString("nativeDisplayName", &native_display_name));
+ EXPECT_EQ("en-AU", language_code);
+ EXPECT_EQ("English (Australia)", display_name);
+ EXPECT_EQ("English (Australia)", native_display_name);
+
+ ASSERT_TRUE(list->GetDictionary(2, &entry));
+ ASSERT_TRUE(entry->GetString("code", &language_code));
+ ASSERT_TRUE(entry->GetString("displayName", &display_name));
+ ASSERT_TRUE(entry->GetString("nativeDisplayName", &native_display_name));
+ EXPECT_EQ("en-US", language_code);
+ EXPECT_EQ("English (United States)", display_name);
+ EXPECT_EQ("English (United States)", native_display_name);
+
+ // This comes from kExtraLanguages.
+ ASSERT_TRUE(list->GetDictionary(3, &entry));
+ ASSERT_TRUE(entry->GetString("code", &language_code));
+ ASSERT_TRUE(entry->GetString("displayName", &display_name));
+ ASSERT_TRUE(entry->GetString("nativeDisplayName", &native_display_name));
+ EXPECT_EQ("fil", language_code);
+ EXPECT_EQ("Filipino", display_name);
+ EXPECT_EQ("Filipino", native_display_name);
+
+ ASSERT_TRUE(list->GetDictionary(4, &entry));
+ ASSERT_TRUE(entry->GetString("code", &language_code));
+ ASSERT_TRUE(entry->GetString("displayName", &display_name));
+ ASSERT_TRUE(entry->GetString("nativeDisplayName", &native_display_name));
+ EXPECT_EQ("fr", language_code);
+ EXPECT_EQ("French", display_name);
+ EXPECT_EQ("fran\u00E7ais", native_display_name);
+
+ // This comes from kExtraLanguages.
+ ASSERT_TRUE(list->GetDictionary(5, &entry));
+ ASSERT_TRUE(entry->GetString("code", &language_code));
+ ASSERT_TRUE(entry->GetString("displayName", &display_name));
+ ASSERT_TRUE(entry->GetString("nativeDisplayName", &native_display_name));
+ EXPECT_EQ("id", language_code);
+ EXPECT_EQ("Indonesian", display_name);
+ EXPECT_EQ("Bahasa Indonesia", native_display_name);
+
+ ASSERT_TRUE(list->GetDictionary(6, &entry));
+ ASSERT_TRUE(entry->GetString("code", &language_code));
+ ASSERT_TRUE(entry->GetString("displayName", &display_name));
+ ASSERT_TRUE(entry->GetString("nativeDisplayName", &native_display_name));
+ EXPECT_EQ("ja", language_code);
+ EXPECT_EQ("Japanese", display_name);
+ EXPECT_EQ("\u65E5\u672C\u8A9E", native_display_name);
+
+ // This comes from kExtraLanguages.
+ ASSERT_TRUE(list->GetDictionary(7, &entry));
+ ASSERT_TRUE(entry->GetString("code", &language_code));
+ ASSERT_TRUE(entry->GetString("displayName", &display_name));
+ ASSERT_TRUE(entry->GetString("nativeDisplayName", &native_display_name));
+ EXPECT_EQ("es-419", language_code);
+ EXPECT_EQ("Spanish (Latin America)", display_name);
+ EXPECT_EQ("espa\u00F1ol (Latinoam\u00E9rica)", native_display_name);
+}
+#endif // defined(OS_CHROMEOS)
+
+#if !defined(OS_MACOSX)
+TEST(LanguageOptionsHandlerTest, GetUILanguageCodeSet) {
+ scoped_ptr<DictionaryValue> dictionary(
+ LanguageOptionsHandler::GetUILanguageCodeSet());
+ EXPECT_TRUE(dictionary->HasKey("en-US"));
+ // Note that we don't test a false case, as such an expectation will
+ // fail when we add support for the language.
+ // EXPECT_FALSE(dictionary->HasKey("no"));
+}
+#endif // !defined(OS_MACOSX)
+
+TEST(LanguageOptionsHandlerTest, GetSpellCheckLanguageCodeSet) {
+ scoped_ptr<DictionaryValue> dictionary(
+ LanguageOptionsHandler::GetSpellCheckLanguageCodeSet());
+ EXPECT_TRUE(dictionary->HasKey("en-US"));
+}
diff --git a/chrome/browser/ui/webui/options2/manage_profile_handler.cc b/chrome/browser/ui/webui/options2/manage_profile_handler.cc
new file mode 100644
index 0000000..4c29788
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/manage_profile_handler.cc
@@ -0,0 +1,300 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/webui/options2/manage_profile_handler.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/string_number_conversions.h"
+#include "base/utf_string_conversions.h"
+#include "base/value_conversions.h"
+#include "base/values.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/profiles/gaia_info_update_service.h"
+#include "chrome/browser/profiles/profile_info_cache.h"
+#include "chrome/browser/profiles/profile_info_util.h"
+#include "chrome/browser/profiles/profile_manager.h"
+#include "chrome/browser/profiles/profile_metrics.h"
+#include "chrome/browser/ui/webui/web_ui_util.h"
+#include "chrome/common/chrome_notification_types.h"
+#include "content/browser/tab_contents/tab_contents.h"
+#include "content/public/browser/notification_service.h"
+#include "grit/generated_resources.h"
+
+ManageProfileHandler::ManageProfileHandler() {
+}
+
+ManageProfileHandler::~ManageProfileHandler() {
+}
+
+void ManageProfileHandler::GetLocalizedValues(
+ DictionaryValue* localized_strings) {
+ DCHECK(localized_strings);
+
+ static OptionsStringResource resources[] = {
+ { "manageProfilesTitle", IDS_PROFILES_MANAGE_TITLE },
+ { "manageProfilesNameLabel", IDS_PROFILES_MANAGE_NAME_LABEL },
+ { "manageProfilesDuplicateNameError",
+ IDS_PROFILES_MANAGE_DUPLICATE_NAME_ERROR },
+ { "manageProfilesIconLabel", IDS_PROFILES_MANAGE_ICON_LABEL },
+ { "deleteProfileTitle", IDS_PROFILES_DELETE_TITLE },
+ { "deleteProfileOK", IDS_PROFILES_DELETE_OK_BUTTON_LABEL },
+ { "deleteProfileMessage", IDS_PROFILES_DELETE_MESSAGE },
+ };
+
+ RegisterStrings(localized_strings, resources, arraysize(resources));
+}
+
+void ManageProfileHandler::Initialize() {
+ registrar_.Add(this, chrome::NOTIFICATION_PROFILE_CACHED_INFO_CHANGED,
+ content::NotificationService::AllSources());
+ SendProfileNames();
+}
+
+void ManageProfileHandler::RegisterMessages() {
+ web_ui_->RegisterMessageCallback("setProfileNameAndIcon",
+ base::Bind(&ManageProfileHandler::SetProfileNameAndIcon,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("deleteProfile",
+ base::Bind(&ManageProfileHandler::DeleteProfile,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("requestDefaultProfileIcons",
+ base::Bind(&ManageProfileHandler::RequestDefaultProfileIcons,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("requestProfileInfo",
+ base::Bind(&ManageProfileHandler::RequestProfileInfo,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("profileIconSelectionChanged",
+ base::Bind(&ManageProfileHandler::ProfileIconSelectionChanged,
+ base::Unretained(this)));
+}
+
+void ManageProfileHandler::Observe(
+ int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) {
+ if (type == chrome::NOTIFICATION_PROFILE_CACHED_INFO_CHANGED) {
+ SendProfileNames();
+ SendProfileIcons();
+ } else {
+ OptionsPage2UIHandler::Observe(type, source, details);
+ }
+}
+
+void ManageProfileHandler::RequestDefaultProfileIcons(const ListValue* args) {
+ SendProfileIcons();
+}
+
+void ManageProfileHandler::SendProfileIcons() {
+ ListValue image_url_list;
+
+ // First add the GAIA picture if it's available.
+ ProfileInfoCache& cache =
+ g_browser_process->profile_manager()->GetProfileInfoCache();
+ Profile* profile = Profile::FromWebUI(web_ui_);
+ size_t profile_index = cache.GetIndexOfProfileWithPath(profile->GetPath());
+ if (profile_index != std::string::npos) {
+ const gfx::Image* icon =
+ cache.GetGAIAPictureOfProfileAtIndex(profile_index);
+ if (icon) {
+ gfx::Image icon2 = profiles::GetAvatarIconForWebUI(*icon, true);
+ gaia_picture_url_ = web_ui_util::GetImageDataUrl(icon2);
+ image_url_list.Append(Value::CreateStringValue(gaia_picture_url_));
+ }
+ }
+
+ // Next add the default avatar icons.
+ for (size_t i = 0; i < ProfileInfoCache::GetDefaultAvatarIconCount(); i++) {
+ std::string url = ProfileInfoCache::GetDefaultAvatarIconUrl(i);
+ image_url_list.Append(Value::CreateStringValue(url));
+ }
+
+ web_ui_->CallJavascriptFunction(
+ "ManageProfileOverlay.receiveDefaultProfileIcons",
+ image_url_list);
+}
+
+void ManageProfileHandler::SendProfileNames() {
+ ProfileInfoCache& cache =
+ g_browser_process->profile_manager()->GetProfileInfoCache();
+ DictionaryValue profile_name_dict;
+ for (size_t i = 0, e = cache.GetNumberOfProfiles(); i < e; ++i)
+ profile_name_dict.SetBoolean(UTF16ToUTF8(cache.GetNameOfProfileAtIndex(i)),
+ true);
+
+ web_ui_->CallJavascriptFunction("ManageProfileOverlay.receiveProfileNames",
+ profile_name_dict);
+}
+
+void ManageProfileHandler::SetProfileNameAndIcon(const ListValue* args) {
+ DCHECK(args);
+
+ Value* file_path_value;
+ FilePath profile_file_path;
+ if (!args->Get(0, &file_path_value) ||
+ !base::GetValueAsFilePath(*file_path_value, &profile_file_path))
+ return;
+
+ ProfileInfoCache& cache =
+ g_browser_process->profile_manager()->GetProfileInfoCache();
+ size_t profile_index = cache.GetIndexOfProfileWithPath(profile_file_path);
+ if (profile_index == std::string::npos)
+ return;
+
+ string16 new_profile_name;
+ if (!args->GetString(1, &new_profile_name))
+ return;
+
+ if (new_profile_name == cache.GetGAIANameOfProfileAtIndex(profile_index)) {
+ // Set the profile to use the GAIA name as the profile name. Note, this
+ // is a little weird if the user typed their GAIA name manually but
+ // it's not a big deal.
+ cache.SetIsUsingGAIANameOfProfileAtIndex(profile_index, true);
+ // Using the GAIA name as the profile name can invalidate the profile index.
+ profile_index = cache.GetIndexOfProfileWithPath(profile_file_path);
+ if (profile_index == std::string::npos)
+ return;
+ } else {
+ cache.SetNameOfProfileAtIndex(profile_index, new_profile_name);
+ // Changing the profile name can invalidate the profile index.
+ profile_index = cache.GetIndexOfProfileWithPath(profile_file_path);
+ if (profile_index == std::string::npos)
+ return;
+
+ cache.SetIsUsingGAIANameOfProfileAtIndex(profile_index, false);
+ // Unsetting the GAIA name as the profile name can invalidate the profile
+ // index.
+ profile_index = cache.GetIndexOfProfileWithPath(profile_file_path);
+ if (profile_index == std::string::npos)
+ return;
+ }
+
+ std::string icon_url;
+ if (!args->GetString(2, &icon_url))
+ return;
+
+ // Metrics logging variable.
+ bool previously_using_gaia_icon =
+ cache.IsUsingGAIANameOfProfileAtIndex(profile_index);
+
+ size_t new_icon_index;
+ if (icon_url == gaia_picture_url_) {
+ cache.SetIsUsingGAIAPictureOfProfileAtIndex(profile_index, true);
+ if (!previously_using_gaia_icon) {
+ // Only log if they changed to the GAIA photo.
+ // Selection of GAIA photo as avatar is logged as part of the function
+ // below.
+ ProfileMetrics::LogProfileSwitchGaia(ProfileMetrics::GAIA_OPT_IN);
+ }
+ } else if (cache.IsDefaultAvatarIconUrl(icon_url, &new_icon_index)) {
+ ProfileMetrics::LogProfileAvatarSelection(new_icon_index);
+ cache.SetAvatarIconOfProfileAtIndex(profile_index, new_icon_index);
+ cache.SetIsUsingGAIAPictureOfProfileAtIndex(profile_index, false);
+ }
+
+ ProfileMetrics::LogProfileUpdate(profile_file_path);
+}
+
+void ManageProfileHandler::DeleteProfile(const ListValue* args) {
+ DCHECK(args);
+
+ ProfileMetrics::LogProfileDeleteUser(ProfileMetrics::PROFILE_DELETED);
+
+ Value* file_path_value;
+ FilePath profile_file_path;
+ if (!args->Get(0, &file_path_value) ||
+ !base::GetValueAsFilePath(*file_path_value, &profile_file_path))
+ return;
+
+ g_browser_process->profile_manager()->ScheduleProfileForDeletion(
+ profile_file_path);
+}
+
+void ManageProfileHandler::RequestProfileInfo(const ListValue* args) {
+ DCHECK(args);
+
+ Value* index_value;
+ double index_double;
+ if (!args->Get(0, &index_value) || !index_value->GetAsDouble(&index_double))
+ return;
+
+ int index = static_cast<int>(index_double);
+ ProfileInfoCache& cache =
+ g_browser_process->profile_manager()->GetProfileInfoCache();
+ int profile_count = cache.GetNumberOfProfiles();
+ if (index < 0 && index >= profile_count)
+ return;
+
+ FilePath profile_path = cache.GetPathOfProfileAtIndex(index);
+ FilePath current_profile_path = Profile::FromWebUI(web_ui_)->GetPath();
+ bool is_current_profile =
+ profile_path == Profile::FromWebUI(web_ui_)->GetPath();
+
+ DictionaryValue profile_value;
+ profile_value.SetString("name", cache.GetNameOfProfileAtIndex(index));
+ profile_value.Set("filePath", base::CreateFilePathValue(profile_path));
+ profile_value.SetBoolean("isCurrentProfile", is_current_profile);
+
+ bool is_gaia_picture =
+ cache.IsUsingGAIAPictureOfProfileAtIndex(index) &&
+ cache.GetGAIAPictureOfProfileAtIndex(index);
+ if (is_gaia_picture) {
+ gfx::Image icon = profiles::GetAvatarIconForWebUI(
+ cache.GetAvatarIconOfProfileAtIndex(index), true);
+ profile_value.SetString("iconURL", web_ui_util::GetImageDataUrl(icon));
+ } else {
+ size_t icon_index = cache.GetAvatarIconIndexOfProfileAtIndex(index);
+ profile_value.SetString("iconURL",
+ cache.GetDefaultAvatarIconUrl(icon_index));
+ }
+
+ web_ui_->CallJavascriptFunction("ManageProfileOverlay.setProfileInfo",
+ profile_value);
+
+ // Ensure that we have the most up to date GAIA picture.
+ if (is_current_profile) {
+ GAIAInfoUpdateService* service =
+ Profile::FromWebUI(web_ui_)->GetGAIAInfoUpdateService();
+ if (service)
+ service->Update();
+ }
+}
+
+void ManageProfileHandler::ProfileIconSelectionChanged(
+ const base::ListValue* args) {
+ DCHECK(args);
+
+ Value* file_path_value;
+ FilePath file_path;
+ if (!args->Get(0, &file_path_value) ||
+ !base::GetValueAsFilePath(*file_path_value, &file_path)) {
+ return;
+ }
+
+ // Currently this only supports editing the current profile's info.
+ if (file_path != Profile::FromWebUI(web_ui_)->GetPath())
+ return;
+
+ std::string icon_url;
+ if (!args->GetString(1, &icon_url))
+ return;
+
+ if (icon_url != gaia_picture_url_)
+ return;
+
+ // If the selection is the GAIA picture then also show the GAIA name in the
+ // text field.
+ ProfileInfoCache& cache =
+ g_browser_process->profile_manager()->GetProfileInfoCache();
+ size_t i = cache.GetIndexOfProfileWithPath(file_path);
+ if (i == std::string::npos)
+ return;
+ string16 gaia_name = cache.GetGAIANameOfProfileAtIndex(i);
+ if (gaia_name.empty())
+ return;
+
+ StringValue gaia_name_value(gaia_name);
+ web_ui_->CallJavascriptFunction("ManageProfileOverlay.setProfileName",
+ gaia_name_value);
+}
diff --git a/chrome/browser/ui/webui/options2/manage_profile_handler.h b/chrome/browser/ui/webui/options2/manage_profile_handler.h
new file mode 100644
index 0000000..8e79f6a
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/manage_profile_handler.h
@@ -0,0 +1,82 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS2_MANAGE_PROFILE_HANDLER_H_
+#define CHROME_BROWSER_UI_WEBUI_OPTIONS2_MANAGE_PROFILE_HANDLER_H_
+#pragma once
+
+#include "chrome/browser/ui/webui/options2/options_ui2.h"
+
+// Chrome personal stuff profiles manage overlay UI handler.
+class ManageProfileHandler : public OptionsPage2UIHandler {
+ public:
+ ManageProfileHandler();
+ virtual ~ManageProfileHandler();
+
+ // OptionsPage2UIHandler:
+ virtual void GetLocalizedValues(
+ base::DictionaryValue* localized_strings) OVERRIDE;
+ virtual void Initialize() OVERRIDE;
+
+ // WebUIMessageHandler:
+ virtual void RegisterMessages() OVERRIDE;
+
+ // content::NotificationObserver:
+ virtual void Observe(int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) OVERRIDE;
+
+ private:
+ // Callback for the "requestDefaultProfileIcons" message.
+ // Sends the array of default profile icon URLs to WebUI.
+ // |args| is of the form: [ {string} iconURL ]
+ void RequestDefaultProfileIcons(const base::ListValue* args);
+
+ // Sends an object to WebUI of the form:
+ // profileNames = {
+ // "Profile Name 1": true,
+ // "Profile Name 2": true,
+ // ...
+ // };
+ // This is used to detect duplicate profile names.
+ void SendProfileNames();
+
+ // Callback for the "setProfileNameAndIcon" message. Sets the name and icon
+ // of a given profile.
+ // |args| is of the form: [
+ // /*string*/ profileFilePath,
+ // /*string*/ newProfileName,
+ // /*string*/ newProfileIconURL
+ // ]
+ void SetProfileNameAndIcon(const base::ListValue* args);
+
+ // Callback for the "deleteProfile" message. Deletes the given profile.
+ // |args| is of the form: [ {string} profileFilePath ]
+ void DeleteProfile(const base::ListValue* args);
+
+ // Callback for the "requestProfileInfo" message.
+ // Given |args| of the form: [ {number} profileIndex ]
+ // Sends an object to WebUI of the form:
+ // profileInfo = {
+ // name: "Profile Name",
+ // iconURL: "chrome://path/to/icon/image",
+ // filePath: "/path/to/profile/data/on/disk"
+ // isCurrentProfile: false,
+ // };
+ void RequestProfileInfo(const base::ListValue* args);
+
+ // Callback for the 'profileIconSelectionChanged' message. Used to update the
+ // name in the manager profile dialog based on the selected icon.
+ void ProfileIconSelectionChanged(const base::ListValue* args);
+
+ // Send all profile icons to the overlay.
+ void SendProfileIcons();
+
+ // URL for the current profile's GAIA picture.
+ std::string gaia_picture_url_;
+
+ DISALLOW_COPY_AND_ASSIGN(ManageProfileHandler);
+};
+
+#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS2_MANAGE_PROFILE_HANDLER_H_
diff --git a/chrome/browser/ui/webui/options2/options_browsertest.js b/chrome/browser/ui/webui/options2/options_browsertest.js
new file mode 100644
index 0000000..9be9041
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/options_browsertest.js
@@ -0,0 +1,93 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * TestFixture for OptionsPage WebUI testing.
+ * @extends {testing.Test}
+ * @constructor
+ */
+function OptionsWebUITest() {}
+
+OptionsWebUITest.prototype = {
+ __proto__: testing.Test.prototype,
+
+ /**
+ * Browse to the options page & call our preLoad().
+ */
+ browsePreload: 'chrome://settings',
+
+ /**
+ * Register a mock handler to ensure expectations are met and options pages
+ * behave correctly.
+ */
+ preLoad: function() {
+ this.makeAndRegisterMockHandler(
+ ['coreOptionsInitialize',
+ 'fetchPrefs',
+ 'observePrefs',
+ 'setBooleanPref',
+ 'setIntegerPref',
+ 'setDoublePref',
+ 'setStringPref',
+ 'setObjectPref',
+ 'clearPref',
+ 'coreOptionsUserMetricsAction',
+ // TODO(scr): Handle this new message:
+ // getInstantFieldTrialStatus: function() {},
+ ]);
+
+ // Register stubs for methods expected to be called before our tests run.
+ // Specific expectations can be made in the tests themselves.
+ this.mockHandler.stubs().fetchPrefs(ANYTHING);
+ this.mockHandler.stubs().observePrefs(ANYTHING);
+ this.mockHandler.stubs().coreOptionsInitialize();
+ },
+};
+
+// Crashes on Mac only. See http://crbug.com/79181
+GEN('#if defined(OS_MACOSX)');
+GEN('#define MAYBE_testSetBooleanPrefTriggers ' +
+ 'DISABLED_testSetBooleanPrefTriggers');
+GEN('#else');
+GEN('#define MAYBE_testSetBooleanPrefTriggers testSetBooleanPrefTriggers');
+GEN('#endif // defined(OS_MACOSX)');
+
+TEST_F('OptionsWebUITest', 'MAYBE_testSetBooleanPrefTriggers', function() {
+ // TODO(dtseng): make generic to click all buttons.
+ var showHomeButton = $('toolbarShowHomeButton');
+ var trueListValue = [
+ 'browser.show_home_button',
+ true,
+ 'Options_Homepage_HomeButton',
+ ];
+ // Note: this expectation is checked in testing::Test::tearDown.
+ this.mockHandler.expects(once()).setBooleanPref(trueListValue);
+
+ // Cause the handler to be called.
+ showHomeButton.click();
+ showHomeButton.blur();
+});
+
+// Not meant to run on ChromeOS at this time.
+// Not finishing in windows. http://crbug.com/81723
+GEN('#if defined(OS_CHROMEOS) || defined(OS_MACOSX) || defined(OS_WIN)');
+GEN('#define MAYBE_testRefreshStaysOnCurrentPage \\');
+GEN(' DISABLED_testRefreshStaysOnCurrentPage');
+GEN('#else');
+GEN('#define MAYBE_testRefreshStaysOnCurrentPage ' +
+ 'testRefreshStaysOnCurrentPage');
+GEN('#endif');
+
+TEST_F('OptionsWebUITest', 'MAYBE_testRefreshStaysOnCurrentPage', function() {
+ var item = $('advancedPageNav');
+ item.onclick();
+ window.location.reload();
+ var pageInstance = AdvancedOptions.getInstance();
+ var topPage = OptionsPage.getTopmostVisiblePage();
+ var expectedTitle = pageInstance.title;
+ var actualTitle = document.title;
+ assertEquals("chrome://settings/advanced", document.location.href);
+ assertEquals(expectedTitle, actualTitle);
+ assertEquals(pageInstance, topPage);
+});
diff --git a/chrome/browser/ui/webui/options2/options_sync_setup_handler.cc b/chrome/browser/ui/webui/options2/options_sync_setup_handler.cc
new file mode 100644
index 0000000..81e114e
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/options_sync_setup_handler.cc
@@ -0,0 +1,55 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/webui/options2/options_sync_setup_handler.h"
+
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/sync/profile_sync_service.h"
+
+OptionsSyncSetupHandler::OptionsSyncSetupHandler(
+ ProfileManager* profile_manager) : SyncSetupHandler2(profile_manager) {
+}
+
+OptionsSyncSetupHandler::~OptionsSyncSetupHandler() {
+}
+
+void OptionsSyncSetupHandler::StepWizardForShowSetupUI() {
+ ProfileSyncService* service =
+ Profile::FromWebUI(web_ui_)->GetProfileSyncService();
+ DCHECK(service);
+
+ // We should bring up either a login or a configure flow based on the state of
+ // sync.
+ if (service->HasSyncSetupCompleted()) {
+ if (service->IsPassphraseRequiredForDecryption()) {
+ service->get_wizard().Step(SyncSetupWizard::ENTER_PASSPHRASE);
+ } else {
+ service->get_wizard().Step(SyncSetupWizard::CONFIGURE);
+ }
+ } else {
+ service->get_wizard().Step(SyncSetupWizard::GetLoginState());
+ }
+}
+
+void OptionsSyncSetupHandler::ShowSetupUI() {
+ ProfileSyncService* service =
+ Profile::FromWebUI(web_ui_)->GetProfileSyncService();
+ DCHECK(service);
+
+ // The user is trying to manually load a syncSetup URL. We should bring up
+ // either a login or a configure flow based on the state of sync.
+ if (service->HasSyncSetupCompleted()) {
+ if (service->IsPassphraseRequiredForDecryption()) {
+ service->get_wizard().Step(SyncSetupWizard::ENTER_PASSPHRASE);
+ } else {
+ service->get_wizard().Step(SyncSetupWizard::CONFIGURE);
+ }
+ } else {
+ service->get_wizard().Step(SyncSetupWizard::GetLoginState());
+ }
+
+ // Show the Sync Setup page.
+ scoped_ptr<Value> page(Value::CreateStringValue("syncSetup"));
+ web_ui_->CallJavascriptFunction("OptionsPage.navigateToPage", *page);
+}
diff --git a/chrome/browser/ui/webui/options2/options_sync_setup_handler.h b/chrome/browser/ui/webui/options2/options_sync_setup_handler.h
new file mode 100644
index 0000000..a61df17
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/options_sync_setup_handler.h
@@ -0,0 +1,22 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS2_OPTIONS_SYNC_SETUP_HANDLER_H_
+#define CHROME_BROWSER_UI_WEBUI_OPTIONS2_OPTIONS_SYNC_SETUP_HANDLER_H_
+
+#include "chrome/browser/ui/webui/sync_setup_handler2.h"
+
+// The handler for Javascript messages related to sync setup UI in the options
+// page.
+class OptionsSyncSetupHandler : public SyncSetupHandler2 {
+ public:
+ explicit OptionsSyncSetupHandler(ProfileManager* profile_manager);
+ virtual ~OptionsSyncSetupHandler();
+
+ protected:
+ virtual void StepWizardForShowSetupUI() OVERRIDE;
+ virtual void ShowSetupUI() OVERRIDE;
+};
+
+#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS2_OPTIONS_SYNC_SETUP_HANDLER_H_
diff --git a/chrome/browser/ui/webui/options2/options_ui2.cc b/chrome/browser/ui/webui/options2/options_ui2.cc
new file mode 100644
index 0000000..879361a
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/options_ui2.cc
@@ -0,0 +1,368 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/webui/options2/options_ui2.h"
+
+#include <algorithm>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/command_line.h"
+#include "base/memory/singleton.h"
+#include "base/message_loop.h"
+#include "base/string_piece.h"
+#include "base/string_util.h"
+#include "base/threading/thread.h"
+#include "base/time.h"
+#include "base/values.h"
+#include "chrome/browser/browser_about_handler.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/webui/options2/advanced_options_handler.h"
+#include "chrome/browser/ui/webui/options2/autofill_options_handler.h"
+#include "chrome/browser/ui/webui/options2/browser_options_handler.h"
+#include "chrome/browser/ui/webui/options2/clear_browser_data_handler.h"
+#include "chrome/browser/ui/webui/options2/content_settings_handler.h"
+#include "chrome/browser/ui/webui/options2/cookies_view_handler.h"
+#include "chrome/browser/ui/webui/options2/core_options_handler.h"
+#include "chrome/browser/ui/webui/options2/extension_settings_handler.h"
+#include "chrome/browser/ui/webui/options2/font_settings_handler.h"
+#include "chrome/browser/ui/webui/options2/handler_options_handler.h"
+#include "chrome/browser/ui/webui/options2/import_data_handler.h"
+#include "chrome/browser/ui/webui/options2/language_options_handler.h"
+#include "chrome/browser/ui/webui/options2/manage_profile_handler.h"
+#include "chrome/browser/ui/webui/options2/options_sync_setup_handler.h"
+#include "chrome/browser/ui/webui/options2/pack_extension_handler.h"
+#include "chrome/browser/ui/webui/options2/password_manager_handler.h"
+#include "chrome/browser/ui/webui/options2/personal_options_handler.h"
+#include "chrome/browser/ui/webui/options2/search_engine_manager_handler.h"
+#include "chrome/browser/ui/webui/options2/stop_syncing_handler.h"
+#include "chrome/browser/ui/webui/options2/web_intents_settings_handler.h"
+#include "chrome/browser/ui/webui/theme_source.h"
+#include "chrome/common/jstemplate_builder.h"
+#include "chrome/common/time_format.h"
+#include "chrome/common/url_constants.h"
+#include "content/browser/renderer_host/render_view_host.h"
+#include "content/browser/tab_contents/tab_contents.h"
+#include "content/browser/tab_contents/tab_contents_delegate.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/notification_types.h"
+#include "grit/chromium_strings.h"
+#include "grit/generated_resources.h"
+#include "grit/locale_settings.h"
+#include "grit/options2_resources.h"
+#include "grit/theme_resources.h"
+#include "grit/theme_resources_standard.h"
+#include "net/base/escape.h"
+#include "ui/base/resource/resource_bundle.h"
+
+#if defined(OS_CHROMEOS)
+#include "chrome/browser/ui/webui/options2/chromeos/about_page_handler.h"
+#include "chrome/browser/ui/webui/options2/chromeos/accounts_options_handler.h"
+#include "chrome/browser/ui/webui/options2/chromeos/bluetooth_options_handler.h"
+#include "chrome/browser/ui/webui/options2/chromeos/change_picture_options_handler.h"
+#include "chrome/browser/ui/webui/options2/chromeos/core_chromeos_options_handler.h"
+#include "chrome/browser/ui/webui/options2/chromeos/cros_language_options_handler.h"
+#include "chrome/browser/ui/webui/options2/chromeos/internet_options_handler.h"
+#include "chrome/browser/ui/webui/options2/chromeos/language_chewing_handler.h"
+#include "chrome/browser/ui/webui/options2/chromeos/language_customize_modifier_keys_handler.h"
+#include "chrome/browser/ui/webui/options2/chromeos/language_hangul_handler.h"
+#include "chrome/browser/ui/webui/options2/chromeos/language_mozc_handler.h"
+#include "chrome/browser/ui/webui/options2/chromeos/language_pinyin_handler.h"
+#include "chrome/browser/ui/webui/options2/chromeos/proxy_handler.h"
+#include "chrome/browser/ui/webui/options2/chromeos/stats_options_handler.h"
+#include "chrome/browser/ui/webui/options2/chromeos/system_options_handler.h"
+#include "chrome/browser/ui/webui/options2/chromeos/user_image_source.h"
+#include "chrome/browser/ui/webui/options2/chromeos/virtual_keyboard_manager_handler.h"
+#endif
+
+#if defined(USE_NSS)
+#include "chrome/browser/ui/webui/options2/certificate_manager_handler.h"
+#endif
+
+static const char kLocalizedStringsFile[] = "strings.js";
+static const char kOptionsBundleJsFile[] = "options_bundle.js";
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Options2UIHTMLSource
+//
+////////////////////////////////////////////////////////////////////////////////
+
+class Options2UIHTMLSource : public ChromeURLDataManager::DataSource {
+ public:
+ // The constructor takes over ownership of |localized_strings|.
+ explicit Options2UIHTMLSource(DictionaryValue* localized_strings);
+ virtual ~Options2UIHTMLSource();
+
+ // Called when the network layer has requested a resource underneath
+ // the path we registered.
+ virtual void StartDataRequest(const std::string& path,
+ bool is_incognito,
+ int request_id);
+ virtual std::string GetMimeType(const std::string&) const;
+
+ private:
+ // Localized strings collection.
+ scoped_ptr<DictionaryValue> localized_strings_;
+
+ DISALLOW_COPY_AND_ASSIGN(Options2UIHTMLSource);
+};
+
+Options2UIHTMLSource::Options2UIHTMLSource(DictionaryValue* localized_strings)
+ : DataSource(chrome::kChromeUISettingsFrameHost, MessageLoop::current()) {
+ DCHECK(localized_strings);
+ localized_strings_.reset(localized_strings);
+}
+
+Options2UIHTMLSource::~Options2UIHTMLSource() {}
+
+void Options2UIHTMLSource::StartDataRequest(const std::string& path,
+ bool is_incognito,
+ int request_id) {
+ scoped_refptr<RefCountedMemory> response_bytes;
+ SetFontAndTextDirection(localized_strings_.get());
+
+ if (path == kLocalizedStringsFile) {
+ // Return dynamically-generated strings from memory.
+ std::string strings_js;
+ jstemplate_builder::AppendJsonJS(localized_strings_.get(), &strings_js);
+ response_bytes = base::RefCountedString::TakeString(&strings_js);
+ } else if (path == kOptionsBundleJsFile) {
+ // Return (and cache) the options javascript code.
+ response_bytes = ResourceBundle::GetSharedInstance().LoadDataResourceBytes(
+ IDR_OPTIONS2_BUNDLE_JS);
+ } else {
+ // Return (and cache) the main options html page as the default.
+ response_bytes = ResourceBundle::GetSharedInstance().LoadDataResourceBytes(
+ IDR_OPTIONS2_HTML);
+ }
+
+ SendResponse(request_id, response_bytes);
+}
+
+std::string Options2UIHTMLSource::GetMimeType(const std::string& path) const {
+ if (path == kLocalizedStringsFile || path == kOptionsBundleJsFile)
+ return "application/javascript";
+
+ return "text/html";
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// OptionsPage2UIHandler
+//
+////////////////////////////////////////////////////////////////////////////////
+
+OptionsPage2UIHandler::OptionsPage2UIHandler() {
+}
+
+OptionsPage2UIHandler::~OptionsPage2UIHandler() {
+}
+
+bool OptionsPage2UIHandler::IsEnabled() {
+ return true;
+}
+
+// static
+void OptionsPage2UIHandler::RegisterStrings(
+ DictionaryValue* localized_strings,
+ const OptionsStringResource* resources,
+ size_t length) {
+ for (size_t i = 0; i < length; ++i) {
+ localized_strings->SetString(
+ resources[i].name, l10n_util::GetStringUTF16(resources[i].id));
+ }
+}
+
+void OptionsPage2UIHandler::RegisterTitle(DictionaryValue* localized_strings,
+ const std::string& variable_name,
+ int title_id) {
+ localized_strings->SetString(variable_name,
+ l10n_util::GetStringUTF16(title_id));
+ localized_strings->SetString(variable_name + "TabTitle",
+ l10n_util::GetStringFUTF16(IDS_OPTIONS_TAB_TITLE,
+ l10n_util::GetStringUTF16(IDS_SETTINGS_TITLE),
+ l10n_util::GetStringUTF16(title_id)));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Options2UI
+//
+////////////////////////////////////////////////////////////////////////////////
+
+Options2UI::Options2UI(TabContents* contents)
+ : ChromeWebUI(contents),
+ initialized_handlers_(false) {
+ DictionaryValue* localized_strings = new DictionaryValue();
+
+ CoreOptionsHandler* core_handler;
+#if defined(OS_CHROMEOS)
+ core_handler = new chromeos::CoreChromeOSOptionsHandler();
+#else
+ core_handler = new CoreOptionsHandler();
+#endif
+ core_handler->set_handlers_host(this);
+ AddOptionsPageUIHandler(localized_strings, core_handler);
+
+ AddOptionsPageUIHandler(localized_strings, new AdvancedOptionsHandler());
+ AddOptionsPageUIHandler(localized_strings, new AutofillOptionsHandler());
+ AddOptionsPageUIHandler(localized_strings, new BrowserOptionsHandler());
+ AddOptionsPageUIHandler(localized_strings, new ClearBrowserDataHandler());
+ AddOptionsPageUIHandler(localized_strings, new ContentSettingsHandler());
+ AddOptionsPageUIHandler(localized_strings, new CookiesViewHandler());
+ AddOptionsPageUIHandler(localized_strings, new ExtensionSettingsHandler());
+ AddOptionsPageUIHandler(localized_strings, new FontSettingsHandler());
+ AddOptionsPageUIHandler(localized_strings, new WebIntentsSettingsHandler());
+#if defined(OS_CHROMEOS)
+ AddOptionsPageUIHandler(localized_strings,
+ new chromeos::CrosLanguageOptionsHandler());
+#else
+ AddOptionsPageUIHandler(localized_strings, new LanguageOptionsHandler());
+#endif
+ AddOptionsPageUIHandler(localized_strings, new ManageProfileHandler());
+ AddOptionsPageUIHandler(localized_strings, new PackExtensionHandler());
+ AddOptionsPageUIHandler(localized_strings, new PasswordManagerHandler());
+ AddOptionsPageUIHandler(localized_strings, new PersonalOptionsHandler());
+ AddOptionsPageUIHandler(localized_strings, new SearchEngineManagerHandler());
+ AddOptionsPageUIHandler(localized_strings, new ImportDataHandler());
+ AddOptionsPageUIHandler(localized_strings, new StopSyncingHandler());
+ AddOptionsPageUIHandler(localized_strings, new OptionsSyncSetupHandler(
+ g_browser_process->profile_manager()));
+#if defined(OS_CHROMEOS)
+ AddOptionsPageUIHandler(localized_strings,
+ new chromeos::AboutPageHandler());
+ AddOptionsPageUIHandler(localized_strings,
+ new chromeos::AccountsOptionsHandler());
+ AddOptionsPageUIHandler(localized_strings,
+ new chromeos::BluetoothOptionsHandler());
+ AddOptionsPageUIHandler(localized_strings, new InternetOptionsHandler());
+ AddOptionsPageUIHandler(localized_strings,
+ new chromeos::LanguageChewingHandler());
+ AddOptionsPageUIHandler(localized_strings,
+ new chromeos::LanguageCustomizeModifierKeysHandler());
+ AddOptionsPageUIHandler(localized_strings,
+ new chromeos::LanguageHangulHandler());
+ AddOptionsPageUIHandler(localized_strings,
+ new chromeos::LanguageMozcHandler());
+ AddOptionsPageUIHandler(localized_strings,
+ new chromeos::LanguagePinyinHandler());
+ AddOptionsPageUIHandler(localized_strings,
+ new chromeos::VirtualKeyboardManagerHandler());
+ AddOptionsPageUIHandler(localized_strings,
+ new chromeos::ProxyHandler());
+ AddOptionsPageUIHandler(localized_strings,
+ new chromeos::ChangePictureOptionsHandler());
+ AddOptionsPageUIHandler(localized_strings,
+ new chromeos::StatsOptionsHandler());
+ AddOptionsPageUIHandler(localized_strings, new SystemOptionsHandler());
+#endif
+#if defined(USE_NSS)
+ AddOptionsPageUIHandler(localized_strings, new CertificateManagerHandler());
+#endif
+ AddOptionsPageUIHandler(localized_strings, new HandlerOptionsHandler());
+
+ // |localized_strings| ownership is taken over by this constructor.
+ Options2UIHTMLSource* html_source =
+ new Options2UIHTMLSource(localized_strings);
+
+ // Set up the chrome://settings-frame/ source.
+ Profile* profile = Profile::FromBrowserContext(contents->browser_context());
+ profile->GetChromeURLDataManager()->AddDataSource(html_source);
+
+ // Set up the chrome://theme/ source.
+ ThemeSource* theme = new ThemeSource(profile);
+ profile->GetChromeURLDataManager()->AddDataSource(theme);
+
+#if defined(OS_CHROMEOS)
+ // Set up the chrome://userimage/ source.
+ chromeos::UserImageSource* user_image_source =
+ new chromeos::UserImageSource();
+ profile->GetChromeURLDataManager()->AddDataSource(user_image_source);
+#endif
+}
+
+Options2UI::~Options2UI() {
+ // Uninitialize all registered handlers. The base class owns them and it will
+ // eventually delete them. Skip over the generic handler.
+ for (std::vector<WebUIMessageHandler*>::iterator iter = handlers_.begin() + 1;
+ iter != handlers_.end();
+ ++iter) {
+ static_cast<OptionsPage2UIHandler*>(*iter)->Uninitialize();
+ }
+}
+
+// Override.
+void Options2UI::RenderViewCreated(RenderViewHost* render_view_host) {
+ SetCommandLineString(render_view_host);
+ ChromeWebUI::RenderViewCreated(render_view_host);
+}
+
+void Options2UI::RenderViewReused(RenderViewHost* render_view_host) {
+ SetCommandLineString(render_view_host);
+ ChromeWebUI::RenderViewReused(render_view_host);
+}
+
+void Options2UI::DidBecomeActiveForReusedRenderView() {
+ // When the renderer is re-used (e.g., for back/forward navigation within
+ // options), the handlers are torn down and rebuilt, so are no longer
+ // initialized, but the web page's DOM may remain intact, in which case onload
+ // won't fire to initilize the handlers. To make sure initialization always
+ // happens, call reinitializeCore (which is a no-op unless the DOM was already
+ // initialized).
+ CallJavascriptFunction("OptionsPage.reinitializeCore");
+
+ ChromeWebUI::DidBecomeActiveForReusedRenderView();
+}
+
+// static
+RefCountedMemory* Options2UI::GetFaviconResourceBytes() {
+ return ResourceBundle::GetSharedInstance().
+ LoadDataResourceBytes(IDR_SETTINGS_FAVICON);
+}
+
+void Options2UI::InitializeHandlers() {
+ DCHECK(!GetProfile()->IsOffTheRecord() || Profile::IsGuestSession());
+
+ // The reinitialize call from DidBecomeActiveForReusedRenderView end up being
+ // delivered after a new web page DOM has been brought up in an existing
+ // renderer (due to IPC delays), causing this method to be called twice. If
+ // that happens, ignore the second call.
+ if (initialized_handlers_)
+ return;
+ initialized_handlers_ = true;
+
+ std::vector<WebUIMessageHandler*>::iterator iter;
+ // Skip over the generic handler.
+ for (iter = handlers_.begin() + 1; iter != handlers_.end(); ++iter) {
+ (static_cast<OptionsPage2UIHandler*>(*iter))->Initialize();
+ }
+}
+
+void Options2UI::AddOptionsPageUIHandler(DictionaryValue* localized_strings,
+ OptionsPage2UIHandler* handler_raw) {
+ scoped_ptr<OptionsPage2UIHandler> handler(handler_raw);
+ DCHECK(handler.get());
+ // Add only if handler's service is enabled.
+ if (handler->IsEnabled()) {
+ handler->GetLocalizedValues(localized_strings);
+ // Add handler to the list and also pass the ownership.
+ AddMessageHandler(handler.release()->Attach(this));
+ }
+}
+
+void Options2UI::SetCommandLineString(RenderViewHost* render_view_host) {
+ std::string command_line_string;
+
+#if defined(OS_WIN)
+ command_line_string =
+ WideToASCII(CommandLine::ForCurrentProcess()->GetCommandLineString());
+#else
+ command_line_string =
+ CommandLine::ForCurrentProcess()->GetCommandLineString();
+#endif
+
+ render_view_host->SetWebUIProperty("commandLineString", command_line_string);
+}
diff --git a/chrome/browser/ui/webui/options2/options_ui2.h b/chrome/browser/ui/webui/options2/options_ui2.h
new file mode 100644
index 0000000..9a7a759
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/options_ui2.h
@@ -0,0 +1,111 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS2_OPTIONS_UI_H_
+#define CHROME_BROWSER_UI_WEBUI_OPTIONS2_OPTIONS_UI_H_
+#pragma once
+
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "chrome/browser/ui/webui/chrome_url_data_manager.h"
+#include "chrome/browser/ui/webui/chrome_web_ui.h"
+#include "content/public/browser/notification_observer.h"
+#include "content/public/browser/notification_registrar.h"
+#include "content/public/browser/notification_types.h"
+
+// The base class handler of Javascript messages of options pages.
+class OptionsPage2UIHandler : public WebUIMessageHandler,
+ public content::NotificationObserver {
+ public:
+ OptionsPage2UIHandler();
+ virtual ~OptionsPage2UIHandler();
+
+ // Is this handler enabled?
+ virtual bool IsEnabled();
+
+ // Collects localized strings for options page.
+ virtual void GetLocalizedValues(base::DictionaryValue* localized_strings) = 0;
+
+ // Initialize the page. Called once the DOM is available for manipulation.
+ // This will be called only once.
+ virtual void Initialize() {}
+
+ // Uninitializes the page. Called just before the object is destructed.
+ virtual void Uninitialize() {}
+
+ // WebUIMessageHandler implementation.
+ virtual void RegisterMessages() OVERRIDE {}
+
+ // content::NotificationObserver implementation.
+ virtual void Observe(int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) OVERRIDE {}
+
+ protected:
+ struct OptionsStringResource {
+ // The name of the resource in templateData.
+ const char* name;
+ // The .grd ID for the resource (IDS_*).
+ int id;
+ };
+ // A helper for simplifying the process of registering strings in WebUI.
+ static void RegisterStrings(base::DictionaryValue* localized_strings,
+ const OptionsStringResource* resources,
+ size_t length);
+
+ // Registers string resources for a page's header and tab title.
+ static void RegisterTitle(base::DictionaryValue* localized_strings,
+ const std::string& variable_name,
+ int title_id);
+
+ content::NotificationRegistrar registrar_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(OptionsPage2UIHandler);
+};
+
+// An interface for common operations that a host of OptionsPage2UIHandlers
+// should provide.
+class OptionsPage2UIHandlerHost {
+ public:
+ virtual void InitializeHandlers() = 0;
+
+ protected:
+ virtual ~OptionsPage2UIHandlerHost() {}
+};
+
+// The WebUI for chrome:settings-frame.
+class Options2UI : public ChromeWebUI,
+ public OptionsPage2UIHandlerHost {
+ public:
+ explicit Options2UI(TabContents* contents);
+ virtual ~Options2UI();
+
+ static RefCountedMemory* GetFaviconResourceBytes();
+
+ // WebUI implementation.
+ virtual void RenderViewCreated(RenderViewHost* render_view_host) OVERRIDE;
+ virtual void RenderViewReused(RenderViewHost* render_view_host) OVERRIDE;
+ virtual void DidBecomeActiveForReusedRenderView() OVERRIDE;
+
+ // Overridden from OptionsPage2UIHandlerHost:
+ virtual void InitializeHandlers() OVERRIDE;
+
+ private:
+ // Adds OptionsPageUiHandler to the handlers list if handler is enabled.
+ void AddOptionsPageUIHandler(base::DictionaryValue* localized_strings,
+ OptionsPage2UIHandler* handler);
+
+ // Sets the WebUI CommandLineString property with arguments passed while
+ // launching chrome.
+ void SetCommandLineString(RenderViewHost* render_view_host);
+
+ bool initialized_handlers_;
+
+ DISALLOW_COPY_AND_ASSIGN(Options2UI);
+};
+
+#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS2_OPTIONS_UI_H_
diff --git a/chrome/browser/ui/webui/options2/options_ui_uitest.cc b/chrome/browser/ui/webui/options2/options_ui_uitest.cc
new file mode 100644
index 0000000..b292213
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/options_ui_uitest.cc
@@ -0,0 +1,65 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/webui/options2/options_ui_uitest.h"
+
+#include "base/string16.h"
+#include "base/utf_string_conversions.h"
+#include "chrome/common/url_constants.h"
+#include "chrome/test/automation/automation_proxy.h"
+#include "chrome/test/automation/browser_proxy.h"
+#include "chrome/test/automation/tab_proxy.h"
+#include "grit/generated_resources.h"
+#include "ui/base/l10n/l10n_util.h"
+
+OptionsUITest::OptionsUITest() {
+ dom_automation_enabled_ = true;
+}
+
+void OptionsUITest::NavigateToSettings(scoped_refptr<TabProxy> tab) {
+ const GURL& url = GURL(chrome::kChromeUISettingsURL);
+ ASSERT_EQ(AUTOMATION_MSG_NAVIGATION_SUCCESS,
+ tab->NavigateToURLBlockUntilNavigationsComplete(url, 1)) << url.spec();
+}
+
+void OptionsUITest::VerifyNavbar(scoped_refptr<TabProxy> tab) {
+ bool navbar_exist = false;
+ EXPECT_TRUE(tab->ExecuteAndExtractBool(L"",
+ L"domAutomationController.send("
+ L"!!document.getElementById('navbar'))", &navbar_exist));
+ EXPECT_EQ(true, navbar_exist);
+}
+
+void OptionsUITest::VerifyTitle(scoped_refptr<TabProxy> tab) {
+ std::wstring title;
+ EXPECT_TRUE(tab->GetTabTitle(&title));
+ string16 expected_title = l10n_util::GetStringUTF16(IDS_SETTINGS_TITLE);
+ EXPECT_NE(WideToUTF16Hack(title).find(expected_title), string16::npos);
+}
+
+void OptionsUITest::VerifySections(scoped_refptr<TabProxy> tab) {
+#if defined(OS_CHROMEOS)
+ const int kExpectedSections = 1 + 7;
+#else
+ const int kExpectedSections = 1 + 4;
+#endif
+ int num_of_sections = 0;
+ EXPECT_TRUE(tab->ExecuteAndExtractInt(L"",
+ L"domAutomationController.send("
+ L"document.getElementById('navbar').children.length)", &num_of_sections));
+ EXPECT_EQ(kExpectedSections, num_of_sections);
+}
+
+TEST_F(OptionsUITest, LoadOptionsByURL) {
+ scoped_refptr<BrowserProxy> browser(automation()->GetBrowserWindow(0));
+ ASSERT_TRUE(browser.get());
+
+ scoped_refptr<TabProxy> tab = browser->GetActiveTab();
+ ASSERT_TRUE(tab.get());
+
+ NavigateToSettings(tab);
+ VerifyTitle(tab);
+ VerifyNavbar(tab);
+ VerifySections(tab);
+}
diff --git a/chrome/browser/ui/webui/options2/options_ui_uitest.h b/chrome/browser/ui/webui/options2/options_ui_uitest.h
new file mode 100644
index 0000000..3cc6806
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/options_ui_uitest.h
@@ -0,0 +1,38 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS2_OPTIONS_UI_UITEST_H_
+#define CHROME_BROWSER_UI_WEBUI_OPTIONS2_OPTIONS_UI_UITEST_H_
+#pragma once
+
+#include "base/memory/scoped_ptr.h"
+#include "chrome/test/ui/ui_test.h"
+
+class TabProxy;
+
+class OptionsUITest : public UITest {
+ public:
+ OptionsUITest();
+
+ // Navigate to the settings tab and block until complete.
+ void NavigateToSettings(scoped_refptr<TabProxy> tab);
+
+ // Check navbar's existence.
+ void VerifyNavbar(scoped_refptr<TabProxy> tab);
+
+ // Verify that the page title is correct.
+ // The only guarantee we can make about the title of a settings tab is that
+ // it should contain IDS_SETTINGS_TITLE somewhere.
+ void VerifyTitle(scoped_refptr<TabProxy> tab);
+
+ // Check section headers in navbar.
+ // For ChromeOS, there should be 1 + 7:
+ // Search, Basics, Personal, System, Internet, Under the Hood,
+ // Users and Extensions.
+ // For other platforms, there should 1 + 4:
+ // Search, Basics, Personal, Under the Hood and Extensions.
+ void VerifySections(scoped_refptr<TabProxy> tab);
+};
+
+#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS2_OPTIONS_UI_UITEST_H_
diff --git a/chrome/browser/ui/webui/options2/pack_extension_handler.cc b/chrome/browser/ui/webui/options2/pack_extension_handler.cc
new file mode 100644
index 0000000..0790482
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/pack_extension_handler.cc
@@ -0,0 +1,101 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/webui/options2/pack_extension_handler.h"
+
+#include "base/bind.h"
+#include "base/utf_string_conversions.h"
+#include "grit/generated_resources.h"
+#include "ui/base/l10n/l10n_util.h"
+
+PackExtensionHandler::PackExtensionHandler() {
+}
+
+PackExtensionHandler::~PackExtensionHandler() {
+ if (pack_job_.get())
+ pack_job_->ClearClient();
+}
+
+void PackExtensionHandler::Initialize() {
+}
+
+void PackExtensionHandler::GetLocalizedValues(
+ DictionaryValue* localized_strings) {
+ DCHECK(localized_strings);
+ RegisterTitle(localized_strings, "clearBrowserDataOverlay",
+ IDS_CLEAR_BROWSING_DATA_TITLE);
+
+ localized_strings->SetString("packExtensionOverlay",
+ l10n_util::GetStringUTF16(IDS_EXTENSION_PACK_DIALOG_TITLE));
+ localized_strings->SetString("packExtensionHeading",
+ l10n_util::GetStringUTF16(IDS_EXTENSION_PACK_DIALOG_HEADING));
+ localized_strings->SetString("packExtensionCommit",
+ l10n_util::GetStringUTF16(IDS_EXTENSION_PACK_BUTTON));
+ localized_strings->SetString("packExtensionRootDir",
+ l10n_util::GetStringUTF16(
+ IDS_EXTENSION_PACK_DIALOG_ROOT_DIRECTORY_LABEL));
+ localized_strings->SetString("packExtensionPrivateKey",
+ l10n_util::GetStringUTF16(IDS_EXTENSION_PACK_DIALOG_PRIVATE_KEY_LABEL));
+ localized_strings->SetString("packExtensionBrowseButton",
+ l10n_util::GetStringUTF16(IDS_EXTENSION_PACK_DIALOG_BROWSE));
+}
+
+void PackExtensionHandler::RegisterMessages() {
+ // Setup handlers specific to this panel.
+ web_ui_->RegisterMessageCallback("pack",
+ base::Bind(&PackExtensionHandler::HandlePackMessage,
+ base::Unretained(this)));
+}
+
+void PackExtensionHandler::OnPackSuccess(const FilePath& crx_file,
+ const FilePath& pem_file) {
+ ListValue results;
+ web_ui_->CallJavascriptFunction("OptionsPage.closeOverlay", results);
+
+ ShowAlert(UTF16ToUTF8(PackExtensionJob::StandardSuccessMessage(crx_file,
+ pem_file)));
+}
+
+void PackExtensionHandler::OnPackFailure(const std::string& error) {
+ ShowAlert(error);
+}
+
+void PackExtensionHandler::HandlePackMessage(const ListValue* args) {
+ std::string extension_path;
+ std::string private_key_path;
+ CHECK_EQ(2U, args->GetSize());
+ CHECK(args->GetString(0, &extension_path));
+ CHECK(args->GetString(1, &private_key_path));
+
+ FilePath root_directory =
+ FilePath::FromWStringHack(UTF8ToWide(extension_path));
+ FilePath key_file = FilePath::FromWStringHack(UTF8ToWide(private_key_path));
+
+ if (root_directory.empty()) {
+ if (extension_path.empty()) {
+ ShowAlert(l10n_util::GetStringUTF8(
+ IDS_EXTENSION_PACK_DIALOG_ERROR_ROOT_REQUIRED));
+ } else {
+ ShowAlert(l10n_util::GetStringUTF8(
+ IDS_EXTENSION_PACK_DIALOG_ERROR_ROOT_INVALID));
+ }
+
+ return;
+ }
+
+ if (!private_key_path.empty() && key_file.empty()) {
+ ShowAlert(l10n_util::GetStringUTF8(
+ IDS_EXTENSION_PACK_DIALOG_ERROR_KEY_INVALID));
+ return;
+ }
+
+ pack_job_ = new PackExtensionJob(this, root_directory, key_file);
+ pack_job_->Start();
+}
+
+void PackExtensionHandler::ShowAlert(const std::string& message) {
+ ListValue arguments;
+ arguments.Append(Value::CreateStringValue(message));
+ web_ui_->CallJavascriptFunction("alert", arguments);
+}
diff --git a/chrome/browser/ui/webui/options2/pack_extension_handler.h b/chrome/browser/ui/webui/options2/pack_extension_handler.h
new file mode 100644
index 0000000..47b6f0a
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/pack_extension_handler.h
@@ -0,0 +1,49 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS2_PACK_EXTENSION_HANDLER_H_
+#define CHROME_BROWSER_UI_WEBUI_OPTIONS2_PACK_EXTENSION_HANDLER_H_
+#pragma once
+
+#include <string>
+
+#include "chrome/browser/browsing_data_remover.h"
+#include "chrome/browser/extensions/pack_extension_job.h"
+#include "chrome/browser/plugin_data_remover_helper.h"
+#include "chrome/browser/ui/webui/options2/options_ui2.h"
+
+// Clear browser data handler page UI handler.
+class PackExtensionHandler : public OptionsPage2UIHandler,
+ public PackExtensionJob::Client {
+ public:
+ PackExtensionHandler();
+ virtual ~PackExtensionHandler();
+
+ // OptionsPage2UIHandler implementation.
+ virtual void Initialize() OVERRIDE;
+ virtual void GetLocalizedValues(DictionaryValue* localized_strings) OVERRIDE;
+
+ // WebUIMessageHandler implementation.
+ virtual void RegisterMessages() OVERRIDE;
+
+ // ExtensionPackJob::Client
+ virtual void OnPackSuccess(const FilePath& crx_file,
+ const FilePath& key_file) OVERRIDE;
+
+ virtual void OnPackFailure(const std::string& error) OVERRIDE;
+
+ private:
+ // Javascript callback to start packing an extension.
+ void HandlePackMessage(const ListValue* args);
+
+ // A function to ask the webpage to show an alert.
+ void ShowAlert(const std::string& message);
+
+ // Used to package the extension.
+ scoped_refptr<PackExtensionJob> pack_job_;
+
+ DISALLOW_COPY_AND_ASSIGN(PackExtensionHandler);
+};
+
+#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS2_PACK_EXTENSION_HANDLER_H_
diff --git a/chrome/browser/ui/webui/options2/password_manager_browsertest.js b/chrome/browser/ui/webui/options2/password_manager_browsertest.js
new file mode 100644
index 0000000..98c632d
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/password_manager_browsertest.js
@@ -0,0 +1,25 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * TestFixture for password manager WebUI testing.
+ * @extends {testing.Test}
+ * @constructor
+ **/
+function PasswordManagerWebUITest() {}
+
+PasswordManagerWebUITest.prototype = {
+ __proto__: testing.Test.prototype,
+
+ /**
+ * Browse to the password manager.
+ **/
+ browsePreload: 'chrome://settings/passwords',
+};
+
+// Test opening the password manager has correct location.
+TEST_F('PasswordManagerWebUITest', 'testOpenPasswordManager',
+ function() {
+ assertEquals(this.browsePreload, document.location.href);
+ });
diff --git a/chrome/browser/ui/webui/options2/password_manager_handler.cc b/chrome/browser/ui/webui/options2/password_manager_handler.cc
new file mode 100644
index 0000000..b0990bb
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/password_manager_handler.cc
@@ -0,0 +1,291 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/webui/options2/password_manager_handler.h"
+
+#include "base/bind.h"
+#include "base/string_number_conversions.h"
+#include "base/utf_string_conversions.h"
+#include "base/values.h"
+#include "chrome/browser/google/google_util.h"
+#include "chrome/browser/prefs/pref_service.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/common/chrome_notification_types.h"
+#include "chrome/common/pref_names.h"
+#include "chrome/common/url_constants.h"
+#include "content/public/browser/notification_details.h"
+#include "content/public/browser/notification_source.h"
+#include "grit/chromium_strings.h"
+#include "grit/generated_resources.h"
+#include "net/base/net_util.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "webkit/glue/password_form.h"
+
+PasswordManagerHandler::PasswordManagerHandler()
+ : ALLOW_THIS_IN_INITIALIZER_LIST(populater_(this)),
+ ALLOW_THIS_IN_INITIALIZER_LIST(exception_populater_(this)) {
+}
+
+PasswordManagerHandler::~PasswordManagerHandler() {
+ PasswordStore* store = GetPasswordStore();
+ if (store)
+ store->RemoveObserver(this);
+}
+
+void PasswordManagerHandler::GetLocalizedValues(
+ DictionaryValue* localized_strings) {
+ DCHECK(localized_strings);
+
+ static const OptionsStringResource resources[] = {
+ { "savedPasswordsTitle",
+ IDS_PASSWORDS_SHOW_PASSWORDS_TAB_TITLE },
+ { "passwordExceptionsTitle",
+ IDS_PASSWORDS_EXCEPTIONS_TAB_TITLE },
+ { "passwordSearchPlaceholder",
+ IDS_PASSWORDS_PAGE_SEARCH_PASSWORDS },
+ { "passwordShowButton",
+ IDS_PASSWORDS_PAGE_VIEW_SHOW_BUTTON },
+ { "passwordHideButton",
+ IDS_PASSWORDS_PAGE_VIEW_HIDE_BUTTON },
+ { "passwordsSiteColumn",
+ IDS_PASSWORDS_PAGE_VIEW_SITE_COLUMN },
+ { "passwordsUsernameColumn",
+ IDS_PASSWORDS_PAGE_VIEW_USERNAME_COLUMN },
+ { "passwordsRemoveButton",
+ IDS_PASSWORDS_PAGE_VIEW_REMOVE_BUTTON },
+ { "passwordsNoPasswordsDescription",
+ IDS_PASSWORDS_PAGE_VIEW_NO_PASSWORDS_DESCRIPTION },
+ { "passwordsNoExceptionsDescription",
+ IDS_PASSWORDS_PAGE_VIEW_NO_EXCEPTIONS_DESCRIPTION },
+ { "passwordsRemoveAllButton",
+ IDS_PASSWORDS_PAGE_VIEW_REMOVE_ALL_BUTTON },
+ { "passwordsRemoveAllTitle",
+ IDS_PASSWORDS_PAGE_VIEW_CAPTION_DELETE_ALL_PASSWORDS },
+ { "passwordsRemoveAllWarning",
+ IDS_PASSWORDS_PAGE_VIEW_TEXT_DELETE_ALL_PASSWORDS },
+ };
+
+ RegisterStrings(localized_strings, resources, arraysize(resources));
+ RegisterTitle(localized_strings, "passwordsPage",
+ IDS_PASSWORDS_EXCEPTIONS_WINDOW_TITLE);
+
+ localized_strings->SetString("passwordManagerLearnMoreURL",
+ google_util::AppendGoogleLocaleParam(
+ GURL(chrome::kPasswordManagerLearnMoreURL)).spec());
+}
+
+void PasswordManagerHandler::Initialize() {
+ // Due to the way that handlers are (re)initialized under certain types of
+ // navigation, we may already be initialized. (See bugs 88986 and 86448.)
+ // If this is the case, return immediately. This is a hack.
+ // TODO(mdm): remove this hack once it is no longer necessary.
+ if (!show_passwords_.GetPrefName().empty())
+ return;
+
+ show_passwords_.Init(prefs::kPasswordManagerAllowShowPasswords,
+ Profile::FromWebUI(web_ui_)->GetPrefs(), this);
+ // We should not cache web_ui_->GetProfile(). See crosbug.com/6304.
+ PasswordStore* store = GetPasswordStore();
+ if (store)
+ store->AddObserver(this);
+}
+
+void PasswordManagerHandler::RegisterMessages() {
+ DCHECK(web_ui_);
+
+ web_ui_->RegisterMessageCallback("updatePasswordLists",
+ base::Bind(&PasswordManagerHandler::UpdatePasswordLists,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("removeSavedPassword",
+ base::Bind(&PasswordManagerHandler::RemoveSavedPassword,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("removePasswordException",
+ base::Bind(&PasswordManagerHandler::RemovePasswordException,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("removeAllSavedPasswords",
+ base::Bind(&PasswordManagerHandler::RemoveAllSavedPasswords,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("removeAllPasswordExceptions",
+ base::Bind(&PasswordManagerHandler::RemoveAllPasswordExceptions,
+ base::Unretained(this)));
+}
+
+void PasswordManagerHandler::OnLoginsChanged() {
+ UpdatePasswordLists(NULL);
+}
+
+PasswordStore* PasswordManagerHandler::GetPasswordStore() {
+ return Profile::FromWebUI(web_ui_)->
+ GetPasswordStore(Profile::EXPLICIT_ACCESS);
+}
+
+void PasswordManagerHandler::Observe(
+ int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) {
+ if (type == chrome::NOTIFICATION_PREF_CHANGED) {
+ std::string* pref_name = content::Details<std::string>(details).ptr();
+ if (*pref_name == prefs::kPasswordManagerAllowShowPasswords) {
+ UpdatePasswordLists(NULL);
+ }
+ }
+
+ OptionsPage2UIHandler::Observe(type, source, details);
+}
+
+void PasswordManagerHandler::UpdatePasswordLists(const ListValue* args) {
+ // Reset the current lists.
+ password_list_.reset();
+ password_exception_list_.reset();
+
+ languages_ = Profile::FromWebUI(web_ui_)->GetPrefs()->
+ GetString(prefs::kAcceptLanguages);
+ populater_.Populate();
+ exception_populater_.Populate();
+}
+
+void PasswordManagerHandler::RemoveSavedPassword(const ListValue* args) {
+ PasswordStore* store = GetPasswordStore();
+ if (!store)
+ return;
+ std::string string_value = UTF16ToUTF8(ExtractStringValue(args));
+ int index;
+ if (base::StringToInt(string_value, &index) && index >= 0 &&
+ static_cast<size_t>(index) < password_list_.size())
+ store->RemoveLogin(*password_list_[index]);
+}
+
+void PasswordManagerHandler::RemovePasswordException(
+ const ListValue* args) {
+ PasswordStore* store = GetPasswordStore();
+ if (!store)
+ return;
+ std::string string_value = UTF16ToUTF8(ExtractStringValue(args));
+ int index;
+ if (base::StringToInt(string_value, &index) && index >= 0 &&
+ static_cast<size_t>(index) < password_exception_list_.size())
+ store->RemoveLogin(*password_exception_list_[index]);
+}
+
+void PasswordManagerHandler::RemoveAllSavedPasswords(
+ const ListValue* args) {
+ // TODO(jhawkins): This will cause a list refresh for every password in the
+ // list. Add PasswordStore::RemoveAllLogins().
+ PasswordStore* store = GetPasswordStore();
+ if (!store)
+ return;
+ for (size_t i = 0; i < password_list_.size(); ++i)
+ store->RemoveLogin(*password_list_[i]);
+}
+
+void PasswordManagerHandler::RemoveAllPasswordExceptions(
+ const ListValue* args) {
+ PasswordStore* store = GetPasswordStore();
+ if (!store)
+ return;
+ for (size_t i = 0; i < password_exception_list_.size(); ++i)
+ store->RemoveLogin(*password_exception_list_[i]);
+}
+
+void PasswordManagerHandler::SetPasswordList() {
+ // Due to the way that handlers are (re)initialized under certain types of
+ // navigation, we may not be initialized yet. (See bugs 88986 and 86448.)
+ // If this is the case, initialize on demand. This is a hack.
+ // TODO(mdm): remove this hack once it is no longer necessary.
+ if (show_passwords_.GetPrefName().empty())
+ Initialize();
+
+ ListValue entries;
+ bool show_passwords = *show_passwords_;
+ string16 empty;
+ for (size_t i = 0; i < password_list_.size(); ++i) {
+ ListValue* entry = new ListValue();
+ entry->Append(new StringValue(net::FormatUrl(password_list_[i]->origin,
+ languages_)));
+ entry->Append(new StringValue(password_list_[i]->username_value));
+ entry->Append(new StringValue(
+ show_passwords ? password_list_[i]->password_value : empty));
+ entries.Append(entry);
+ }
+
+ web_ui_->CallJavascriptFunction("PasswordManager.setSavedPasswordsList",
+ entries);
+}
+
+void PasswordManagerHandler::SetPasswordExceptionList() {
+ ListValue entries;
+ for (size_t i = 0; i < password_exception_list_.size(); ++i) {
+ entries.Append(new StringValue(
+ net::FormatUrl(password_exception_list_[i]->origin, languages_)));
+ }
+
+ web_ui_->CallJavascriptFunction("PasswordManager.setPasswordExceptionsList",
+ entries);
+}
+
+PasswordManagerHandler::ListPopulater::ListPopulater(
+ PasswordManagerHandler* page)
+ : page_(page),
+ pending_login_query_(0) {
+}
+
+PasswordManagerHandler::ListPopulater::~ListPopulater() {
+}
+
+PasswordManagerHandler::PasswordListPopulater::PasswordListPopulater(
+ PasswordManagerHandler* page) : ListPopulater(page) {
+}
+
+void PasswordManagerHandler::PasswordListPopulater::Populate() {
+ PasswordStore* store = page_->GetPasswordStore();
+ if (store != NULL) {
+ if (pending_login_query_)
+ store->CancelRequest(pending_login_query_);
+
+ pending_login_query_ = store->GetAutofillableLogins(this);
+ } else {
+ LOG(ERROR) << "No password store! Cannot display passwords.";
+ }
+}
+
+void PasswordManagerHandler::PasswordListPopulater::
+ OnPasswordStoreRequestDone(
+ CancelableRequestProvider::Handle handle,
+ const std::vector<webkit_glue::PasswordForm*>& result) {
+ DCHECK_EQ(pending_login_query_, handle);
+ pending_login_query_ = 0;
+ page_->password_list_.reset();
+ page_->password_list_.insert(page_->password_list_.end(),
+ result.begin(), result.end());
+ page_->SetPasswordList();
+}
+
+PasswordManagerHandler::PasswordExceptionListPopulater::
+ PasswordExceptionListPopulater(PasswordManagerHandler* page)
+ : ListPopulater(page) {
+}
+
+void PasswordManagerHandler::PasswordExceptionListPopulater::Populate() {
+ PasswordStore* store = page_->GetPasswordStore();
+ if (store != NULL) {
+ if (pending_login_query_)
+ store->CancelRequest(pending_login_query_);
+
+ pending_login_query_ = store->GetBlacklistLogins(this);
+ } else {
+ LOG(ERROR) << "No password store! Cannot display exceptions.";
+ }
+}
+
+void PasswordManagerHandler::PasswordExceptionListPopulater::
+ OnPasswordStoreRequestDone(
+ CancelableRequestProvider::Handle handle,
+ const std::vector<webkit_glue::PasswordForm*>& result) {
+ DCHECK_EQ(pending_login_query_, handle);
+ pending_login_query_ = 0;
+ page_->password_exception_list_.reset();
+ page_->password_exception_list_.insert(page_->password_exception_list_.end(),
+ result.begin(), result.end());
+ page_->SetPasswordExceptionList();
+}
diff --git a/chrome/browser/ui/webui/options2/password_manager_handler.h b/chrome/browser/ui/webui/options2/password_manager_handler.h
new file mode 100644
index 0000000..389f1af
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/password_manager_handler.h
@@ -0,0 +1,129 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS2_PASSWORD_MANAGER_HANDLER_H_
+#define CHROME_BROWSER_UI_WEBUI_OPTIONS2_PASSWORD_MANAGER_HANDLER_H_
+
+#include <string>
+#include <vector>
+
+#include "base/memory/scoped_vector.h"
+#include "chrome/browser/password_manager/password_store.h"
+#include "chrome/browser/password_manager/password_store_consumer.h"
+#include "chrome/browser/prefs/pref_member.h"
+#include "chrome/browser/ui/webui/options2/options_ui2.h"
+
+class PasswordManagerHandler : public OptionsPage2UIHandler,
+ public PasswordStore::Observer {
+ public:
+ PasswordManagerHandler();
+ virtual ~PasswordManagerHandler();
+
+ // OptionsPage2UIHandler implementation.
+ virtual void GetLocalizedValues(DictionaryValue* localized_strings) OVERRIDE;
+ virtual void Initialize() OVERRIDE;
+ virtual void RegisterMessages() OVERRIDE;
+
+ // PasswordStore::Observer implementation.
+ virtual void OnLoginsChanged() OVERRIDE;
+
+ // content::NotificationObserver implementation.
+ virtual void Observe(int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) OVERRIDE;
+
+ private:
+ // The password store associated with the currently active profile.
+ PasswordStore* GetPasswordStore();
+
+ // Called when the JS PasswordManager object is initialized.
+ void UpdatePasswordLists(const ListValue* args);
+
+ // Remove an entry.
+ // @param value the entry index to be removed.
+ void RemoveSavedPassword(const ListValue* args);
+
+ // Remove an password exception.
+ // @param value the entry index to be removed.
+ void RemovePasswordException(const ListValue* args);
+
+ // Remove all saved passwords
+ void RemoveAllSavedPasswords(const ListValue* args);
+
+ // Remove All password exceptions
+ void RemoveAllPasswordExceptions(const ListValue* args);
+
+ // Get password value for the selected entry.
+ // @param value the selected entry index.
+ void ShowSelectedPassword(const ListValue* args);
+
+ // Sets the password and exception list contents to the given data.
+ // We take ownership of the PasswordForms in the vector.
+ void SetPasswordList();
+ void SetPasswordExceptionList();
+
+ // A short class to mediate requests to the password store.
+ class ListPopulater : public PasswordStoreConsumer {
+ public:
+ explicit ListPopulater(PasswordManagerHandler* page);
+ virtual ~ListPopulater();
+
+ // Send a query to the password store to populate a list.
+ virtual void Populate() = 0;
+
+ // Send the password store's reply back to the handler.
+ virtual void OnPasswordStoreRequestDone(
+ CancelableRequestProvider::Handle handle,
+ const std::vector<webkit_glue::PasswordForm*>& result) = 0;
+
+ protected:
+ PasswordManagerHandler* page_;
+ CancelableRequestProvider::Handle pending_login_query_;
+ };
+
+ // A short class to mediate requests to the password store for passwordlist.
+ class PasswordListPopulater : public ListPopulater {
+ public:
+ explicit PasswordListPopulater(PasswordManagerHandler* page);
+
+ // Send a query to the password store to populate a password list.
+ virtual void Populate() OVERRIDE;
+
+ // Send the password store's reply back to the handler.
+ virtual void OnPasswordStoreRequestDone(
+ CancelableRequestProvider::Handle handle,
+ const std::vector<webkit_glue::PasswordForm*>& result) OVERRIDE;
+ };
+
+ // A short class to mediate requests to the password store for exceptions.
+ class PasswordExceptionListPopulater : public ListPopulater {
+ public:
+ explicit PasswordExceptionListPopulater(PasswordManagerHandler* page);
+
+ // Send a query to the password store to populate a passwordException list.
+ virtual void Populate() OVERRIDE;
+
+ // Send the password store's reply back to the handler.
+ virtual void OnPasswordStoreRequestDone(
+ CancelableRequestProvider::Handle handle,
+ const std::vector<webkit_glue::PasswordForm*>& result) OVERRIDE;
+ };
+
+ // Password store consumer for populating the password list and exceptions.
+ PasswordListPopulater populater_;
+ PasswordExceptionListPopulater exception_populater_;
+
+ ScopedVector<webkit_glue::PasswordForm> password_list_;
+ ScopedVector<webkit_glue::PasswordForm> password_exception_list_;
+
+ // User's pref
+ std::string languages_;
+
+ // Whether to show stored passwords or not.
+ BooleanPrefMember show_passwords_;
+
+ DISALLOW_COPY_AND_ASSIGN(PasswordManagerHandler);
+};
+
+#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS2_PASSWORD_MANAGER_HANDLER_H_
diff --git a/chrome/browser/ui/webui/options2/personal_options_browsertest.js b/chrome/browser/ui/webui/options2/personal_options_browsertest.js
new file mode 100644
index 0000000..222adfa
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/personal_options_browsertest.js
@@ -0,0 +1,24 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * TestFixture for personal options WebUI testing.
+ * @extends {testing.Test}
+ * @constructor
+ **/
+function PersonalOptionsWebUITest() {}
+
+PersonalOptionsWebUITest.prototype = {
+ __proto__: testing.Test.prototype,
+
+ /**
+ * Browse to personal options.
+ **/
+ browsePreload: 'chrome://settings/personal',
+};
+
+// Test opening personal options has correct location.
+TEST_F('PersonalOptionsWebUITest', 'testOpenPersonalOptions', function() {
+ assertEquals(this.browsePreload, document.location.href);
+});
diff --git a/chrome/browser/ui/webui/options2/personal_options_handler.cc b/chrome/browser/ui/webui/options2/personal_options_handler.cc
new file mode 100644
index 0000000..0a2a367
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/personal_options_handler.cc
@@ -0,0 +1,444 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/webui/options2/personal_options_handler.h"
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/path_service.h"
+#include "base/stl_util.h"
+#include "base/string_number_conversions.h"
+#include "base/utf_string_conversions.h"
+#include "base/value_conversions.h"
+#include "base/values.h"
+#include "build/build_config.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/google/google_util.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/profiles/profile_info_cache.h"
+#include "chrome/browser/profiles/profile_info_util.h"
+#include "chrome/browser/profiles/profile_manager.h"
+#include "chrome/browser/sync/profile_sync_service.h"
+#include "chrome/browser/sync/sync_setup_flow.h"
+#include "chrome/browser/sync/sync_ui_util.h"
+#include "chrome/browser/themes/theme_service.h"
+#include "chrome/browser/themes/theme_service_factory.h"
+#include "chrome/browser/ui/webui/web_ui_util.h"
+#include "chrome/common/chrome_notification_types.h"
+#include "chrome/common/chrome_paths.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/common/net/gaia/google_service_auth_error.h"
+#include "chrome/common/url_constants.h"
+#include "content/browser/tab_contents/tab_contents.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/user_metrics.h"
+#include "grit/chromium_strings.h"
+#include "grit/generated_resources.h"
+#include "grit/locale_settings.h"
+#include "grit/theme_resources.h"
+#include "ui/base/l10n/l10n_util.h"
+
+#if defined(OS_CHROMEOS)
+#include "chrome/browser/chromeos/login/user_manager.h"
+#include "chrome/browser/chromeos/options/take_photo_dialog.h"
+#include "chrome/browser/ui/browser_window.h"
+#include "chrome/browser/ui/views/window.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#endif // defined(OS_CHROMEOS)
+#if defined(TOOLKIT_GTK)
+#include "chrome/browser/ui/gtk/gtk_theme_service.h"
+#endif // defined(TOOLKIT_GTK)
+
+using content::UserMetricsAction;
+
+PersonalOptionsHandler::PersonalOptionsHandler() {
+ multiprofile_ = ProfileManager::IsMultipleProfilesEnabled();
+#if defined(OS_CHROMEOS)
+ registrar_.Add(this,
+ chrome::NOTIFICATION_LOGIN_USER_IMAGE_CHANGED,
+ content::NotificationService::AllSources());
+#endif
+}
+
+PersonalOptionsHandler::~PersonalOptionsHandler() {
+ ProfileSyncService* sync_service =
+ Profile::FromWebUI(web_ui_)->GetProfileSyncService();
+ if (sync_service)
+ sync_service->RemoveObserver(this);
+}
+
+void PersonalOptionsHandler::GetLocalizedValues(
+ DictionaryValue* localized_strings) {
+ DCHECK(localized_strings);
+
+ RegisterTitle(localized_strings, "personalPage",
+ IDS_OPTIONS_CONTENT_TAB_LABEL);
+
+
+ localized_strings->SetString(
+ "syncOverview",
+ l10n_util::GetStringFUTF16(IDS_SYNC_OVERVIEW,
+ l10n_util::GetStringUTF16(IDS_PRODUCT_NAME)));
+ localized_strings->SetString("syncSection",
+ l10n_util::GetStringUTF16(IDS_SYNC_OPTIONS_GROUP_NAME));
+ localized_strings->SetString("customizeSync",
+ l10n_util::GetStringUTF16(IDS_SYNC_CUSTOMIZE_BUTTON_LABEL));
+ localized_strings->SetString("syncLearnMoreURL",
+ google_util::StringAppendGoogleLocaleParam(chrome::kSyncLearnMoreURL));
+
+ localized_strings->SetString("profiles",
+ l10n_util::GetStringUTF16(IDS_PROFILES_OPTIONS_GROUP_NAME));
+ localized_strings->SetString("profilesCreate",
+ l10n_util::GetStringUTF16(IDS_PROFILES_CREATE_BUTTON_LABEL));
+ localized_strings->SetString("profilesManage",
+ l10n_util::GetStringUTF16(IDS_PROFILES_MANAGE_BUTTON_LABEL));
+ localized_strings->SetString("profilesDelete",
+ l10n_util::GetStringUTF16(IDS_PROFILES_DELETE_BUTTON_LABEL));
+ localized_strings->SetString("profilesDeleteSingle",
+ l10n_util::GetStringUTF16(IDS_PROFILES_DELETE_SINGLE_BUTTON_LABEL));
+ localized_strings->SetString("profilesListItemCurrent",
+ l10n_util::GetStringUTF16(IDS_PROFILES_LIST_ITEM_CURRENT));
+ localized_strings->SetString("profilesSingleUser",
+ l10n_util::GetStringFUTF16(IDS_PROFILES_SINGLE_USER_MESSAGE,
+ l10n_util::GetStringUTF16(IDS_PRODUCT_NAME)));
+
+ localized_strings->SetString("passwords",
+ l10n_util::GetStringUTF16(IDS_OPTIONS_PASSWORDS_GROUP_NAME));
+ localized_strings->SetString("passwordsAskToSave",
+ l10n_util::GetStringUTF16(IDS_OPTIONS_PASSWORDS_ASKTOSAVE));
+ localized_strings->SetString("passwordsNeverSave",
+ l10n_util::GetStringUTF16(IDS_OPTIONS_PASSWORDS_NEVERSAVE));
+ localized_strings->SetString("manage_passwords",
+ l10n_util::GetStringUTF16(IDS_OPTIONS_PASSWORDS_MANAGE_PASSWORDS));
+#if defined(OS_MACOSX)
+ ProfileManager* profile_manager = g_browser_process->profile_manager();
+ if (profile_manager->GetNumberOfProfiles() > 1) {
+ localized_strings->SetString("macPasswordsWarning",
+ l10n_util::GetStringUTF16(IDS_OPTIONS_PASSWORDS_MAC_WARNING));
+ }
+#endif
+ localized_strings->SetString("autologinEnabled",
+ l10n_util::GetStringUTF16(IDS_OPTIONS_PASSWORDS_AUTOLOGIN));
+
+ localized_strings->SetString("autofill",
+ l10n_util::GetStringUTF16(IDS_AUTOFILL_SETTING_WINDOWS_GROUP_NAME));
+ localized_strings->SetString("autofillEnabled",
+ l10n_util::GetStringUTF16(IDS_OPTIONS_AUTOFILL_ENABLE));
+ localized_strings->SetString("manageAutofillSettings",
+ l10n_util::GetStringUTF16(IDS_OPTIONS_MANAGE_AUTOFILL_SETTINGS));
+
+ localized_strings->SetString("browsingData",
+ l10n_util::GetStringUTF16(IDS_OPTIONS_BROWSING_DATA_GROUP_NAME));
+ localized_strings->SetString("importData",
+ l10n_util::GetStringUTF16(IDS_OPTIONS_IMPORT_DATA_BUTTON));
+
+ localized_strings->SetString("themesGallery",
+ l10n_util::GetStringUTF16(IDS_THEMES_GALLERY_BUTTON));
+ localized_strings->SetString("themesGalleryURL",
+ l10n_util::GetStringUTF16(IDS_THEMES_GALLERY_URL));
+
+#if defined(TOOLKIT_GTK)
+ localized_strings->SetString("appearance",
+ l10n_util::GetStringUTF16(IDS_APPEARANCE_GROUP_NAME));
+ localized_strings->SetString("themesGTKButton",
+ l10n_util::GetStringUTF16(IDS_THEMES_GTK_BUTTON));
+ localized_strings->SetString("themesSetClassic",
+ l10n_util::GetStringUTF16(IDS_THEMES_SET_CLASSIC));
+ localized_strings->SetString("showWindowDecorations",
+ l10n_util::GetStringUTF16(IDS_SHOW_WINDOW_DECORATIONS_RADIO));
+ localized_strings->SetString("hideWindowDecorations",
+ l10n_util::GetStringUTF16(IDS_HIDE_WINDOW_DECORATIONS_RADIO));
+#else
+ localized_strings->SetString("themes",
+ l10n_util::GetStringUTF16(IDS_THEMES_GROUP_NAME));
+ localized_strings->SetString("themesReset",
+ l10n_util::GetStringUTF16(IDS_THEMES_RESET_BUTTON));
+#endif
+
+ // Sync select control.
+ ListValue* sync_select_list = new ListValue;
+ ListValue* datatypes = new ListValue;
+ datatypes->Append(Value::CreateBooleanValue(false));
+ datatypes->Append(
+ Value::CreateStringValue(
+ l10n_util::GetStringUTF8(IDS_SYNC_OPTIONS_SELECT_DATATYPES)));
+ sync_select_list->Append(datatypes);
+ ListValue* everything = new ListValue;
+ everything->Append(Value::CreateBooleanValue(true));
+ everything->Append(
+ Value::CreateStringValue(
+ l10n_util::GetStringUTF8(IDS_SYNC_OPTIONS_SELECT_EVERYTHING)));
+ sync_select_list->Append(everything);
+ localized_strings->Set("syncSelectList", sync_select_list);
+
+ // Sync page.
+ localized_strings->SetString("syncPage",
+ l10n_util::GetStringUTF16(IDS_SYNC_NTP_SYNC_SECTION_TITLE));
+ localized_strings->SetString("sync_title",
+ l10n_util::GetStringUTF16(IDS_CUSTOMIZE_SYNC_DESCRIPTION));
+ localized_strings->SetString("syncsettings",
+ l10n_util::GetStringUTF16(IDS_SYNC_DATATYPE_PREFERENCES));
+ localized_strings->SetString("syncbookmarks",
+ l10n_util::GetStringUTF16(IDS_SYNC_DATATYPE_BOOKMARKS));
+ localized_strings->SetString("synctypedurls",
+ l10n_util::GetStringUTF16(IDS_SYNC_DATATYPE_TYPED_URLS));
+ localized_strings->SetString("syncpasswords",
+ l10n_util::GetStringUTF16(IDS_SYNC_DATATYPE_PASSWORDS));
+ localized_strings->SetString("syncextensions",
+ l10n_util::GetStringUTF16(IDS_SYNC_DATATYPE_EXTENSIONS));
+ localized_strings->SetString("syncautofill",
+ l10n_util::GetStringUTF16(IDS_SYNC_DATATYPE_AUTOFILL));
+ localized_strings->SetString("syncthemes",
+ l10n_util::GetStringUTF16(IDS_SYNC_DATATYPE_THEMES));
+ localized_strings->SetString("syncapps",
+ l10n_util::GetStringUTF16(IDS_SYNC_DATATYPE_APPS));
+ localized_strings->SetString("syncsessions",
+ l10n_util::GetStringUTF16(IDS_SYNC_DATATYPE_TABS));
+
+#if defined(OS_CHROMEOS)
+ localized_strings->SetString("account",
+ l10n_util::GetStringUTF16(IDS_OPTIONS_PERSONAL_ACCOUNT_GROUP_NAME));
+ localized_strings->SetString("enableScreenlock",
+ l10n_util::GetStringUTF16(IDS_OPTIONS_ENABLE_SCREENLOCKER_CHECKBOX));
+ localized_strings->SetString("changePicture",
+ l10n_util::GetStringUTF16(IDS_OPTIONS_CHANGE_PICTURE));
+ if (chromeos::UserManager::Get()->user_is_logged_in()) {
+ localized_strings->SetString("username",
+ chromeos::UserManager::Get()->logged_in_user().email());
+ }
+#endif
+}
+
+void PersonalOptionsHandler::RegisterMessages() {
+ DCHECK(web_ui_);
+ web_ui_->RegisterMessageCallback(
+ "themesReset",
+ base::Bind(&PersonalOptionsHandler::ThemesReset,
+ base::Unretained(this)));
+#if defined(TOOLKIT_GTK)
+ web_ui_->RegisterMessageCallback(
+ "themesSetGTK",
+ base::Bind(&PersonalOptionsHandler::ThemesSetGTK,
+ base::Unretained(this)));
+#endif
+ web_ui_->RegisterMessageCallback(
+ "createProfile",
+ base::Bind(&PersonalOptionsHandler::CreateProfile,
+ base::Unretained(this)));
+}
+
+void PersonalOptionsHandler::Observe(
+ int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) {
+ if (type == chrome::NOTIFICATION_BROWSER_THEME_CHANGED) {
+ ObserveThemeChanged();
+ } else if (multiprofile_ &&
+ type == chrome::NOTIFICATION_PROFILE_CACHED_INFO_CHANGED) {
+ SendProfilesInfo();
+#if defined(OS_CHROMEOS)
+ } else if (type == chrome::NOTIFICATION_LOGIN_USER_IMAGE_CHANGED) {
+ UpdateAccountPicture();
+#endif
+ } else {
+ OptionsPage2UIHandler::Observe(type, source, details);
+ }
+}
+
+void PersonalOptionsHandler::OnStateChanged() {
+ string16 status_label;
+ string16 link_label;
+ ProfileSyncService* service =
+ Profile::FromWebUI(web_ui_)->GetProfileSyncService();
+ DCHECK(service);
+ bool managed = service->IsManaged();
+ bool sync_setup_completed = service->HasSyncSetupCompleted();
+ bool status_has_error = sync_ui_util::GetStatusLabels(
+ service, sync_ui_util::WITH_HTML, &status_label, &link_label) ==
+ sync_ui_util::SYNC_ERROR;
+
+ string16 start_stop_button_label;
+ bool is_start_stop_button_visible = false;
+ bool is_start_stop_button_enabled = false;
+ if (sync_setup_completed) {
+ start_stop_button_label =
+ l10n_util::GetStringUTF16(IDS_SYNC_STOP_SYNCING_BUTTON_LABEL);
+#if defined(OS_CHROMEOS)
+ is_start_stop_button_visible = false;
+#else
+ is_start_stop_button_visible = true;
+#endif // defined(OS_CHROMEOS)
+ is_start_stop_button_enabled = !managed;
+ } else if (service->SetupInProgress()) {
+ start_stop_button_label =
+ l10n_util::GetStringUTF16(IDS_SYNC_NTP_SETUP_IN_PROGRESS);
+ is_start_stop_button_visible = true;
+ is_start_stop_button_enabled = false;
+ } else {
+ start_stop_button_label =
+ l10n_util::GetStringFUTF16(IDS_SYNC_START_SYNC_BUTTON_LABEL,
+ l10n_util::GetStringUTF16(IDS_SHORT_PRODUCT_NAME));
+ is_start_stop_button_visible = true;
+ is_start_stop_button_enabled = !managed;
+ }
+
+ scoped_ptr<Value> completed(Value::CreateBooleanValue(sync_setup_completed));
+ web_ui_->CallJavascriptFunction("PersonalOptions.setSyncSetupCompleted",
+ *completed);
+
+ scoped_ptr<Value> label(Value::CreateStringValue(status_label));
+ web_ui_->CallJavascriptFunction("PersonalOptions.setSyncStatus", *label);
+
+ scoped_ptr<Value> enabled(
+ Value::CreateBooleanValue(is_start_stop_button_enabled));
+ web_ui_->CallJavascriptFunction("PersonalOptions.setStartStopButtonEnabled",
+ *enabled);
+
+ scoped_ptr<Value> visible(
+ Value::CreateBooleanValue(is_start_stop_button_visible));
+ web_ui_->CallJavascriptFunction("PersonalOptions.setStartStopButtonVisible",
+ *visible);
+
+ label.reset(Value::CreateStringValue(start_stop_button_label));
+ web_ui_->CallJavascriptFunction("PersonalOptions.setStartStopButtonLabel",
+ *label);
+
+ label.reset(Value::CreateStringValue(link_label));
+ web_ui_->CallJavascriptFunction("PersonalOptions.setSyncActionLinkLabel",
+ *label);
+
+ enabled.reset(Value::CreateBooleanValue(!managed));
+ web_ui_->CallJavascriptFunction("PersonalOptions.setSyncActionLinkEnabled",
+ *enabled);
+
+ visible.reset(Value::CreateBooleanValue(status_has_error));
+ web_ui_->CallJavascriptFunction("PersonalOptions.setSyncStatusErrorVisible",
+ *visible);
+
+ enabled.reset(Value::CreateBooleanValue(
+ !service->unrecoverable_error_detected()));
+ web_ui_->CallJavascriptFunction(
+ "PersonalOptions.setCustomizeSyncButtonEnabled",
+ *enabled);
+
+ if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kEnableAutologin)) {
+ visible.reset(Value::CreateBooleanValue(
+ service->AreCredentialsAvailable()));
+ web_ui_->CallJavascriptFunction("PersonalOptions.setAutoLoginVisible",
+ *visible);
+ }
+
+ // Set profile creation text and button if multi-profiles switch is on.
+ visible.reset(Value::CreateBooleanValue(multiprofile_));
+ web_ui_->CallJavascriptFunction("PersonalOptions.setProfilesSectionVisible",
+ *visible);
+ if (multiprofile_)
+ SendProfilesInfo();
+}
+
+void PersonalOptionsHandler::ObserveThemeChanged() {
+ Profile* profile = Profile::FromWebUI(web_ui_);
+#if defined(TOOLKIT_GTK)
+ GtkThemeService* theme_service = GtkThemeService::GetFrom(profile);
+ bool is_gtk_theme = theme_service->UsingNativeTheme();
+ base::FundamentalValue gtk_enabled(!is_gtk_theme);
+ web_ui_->CallJavascriptFunction(
+ "options.PersonalOptions.setGtkThemeButtonEnabled", gtk_enabled);
+#else
+ ThemeService* theme_service = ThemeServiceFactory::GetForProfile(profile);
+ bool is_gtk_theme = false;
+#endif
+
+ bool is_classic_theme = !is_gtk_theme && theme_service->UsingDefaultTheme();
+ base::FundamentalValue enabled(!is_classic_theme);
+ web_ui_->CallJavascriptFunction(
+ "options.PersonalOptions.setThemesResetButtonEnabled", enabled);
+}
+
+void PersonalOptionsHandler::Initialize() {
+ Profile* profile = Profile::FromWebUI(web_ui_);
+
+ // Listen for theme installation.
+ registrar_.Add(this, chrome::NOTIFICATION_BROWSER_THEME_CHANGED,
+ content::Source<ThemeService>(
+ ThemeServiceFactory::GetForProfile(profile)));
+ registrar_.Add(this, chrome::NOTIFICATION_PROFILE_CACHED_INFO_CHANGED,
+ content::NotificationService::AllSources());
+ ObserveThemeChanged();
+
+ ProfileSyncService* sync_service = profile->GetProfileSyncService();
+ if (sync_service) {
+ sync_service->AddObserver(this);
+ OnStateChanged();
+ } else {
+ web_ui_->CallJavascriptFunction("options.PersonalOptions.hideSyncSection");
+ }
+}
+
+void PersonalOptionsHandler::ThemesReset(const ListValue* args) {
+ content::RecordAction(UserMetricsAction("Options_ThemesReset"));
+ Profile* profile = Profile::FromWebUI(web_ui_);
+ ThemeServiceFactory::GetForProfile(profile)->UseDefaultTheme();
+}
+
+#if defined(TOOLKIT_GTK)
+void PersonalOptionsHandler::ThemesSetGTK(const ListValue* args) {
+ content::RecordAction(UserMetricsAction("Options_GtkThemeSet"));
+ Profile* profile = Profile::FromWebUI(web_ui_);
+ ThemeServiceFactory::GetForProfile(profile)->SetNativeTheme();
+}
+#endif
+
+#if defined(OS_CHROMEOS)
+void PersonalOptionsHandler::UpdateAccountPicture() {
+ std::string email = chromeos::UserManager::Get()->logged_in_user().email();
+ if (!email.empty()) {
+ web_ui_->CallJavascriptFunction("PersonalOptions.updateAccountPicture");
+ base::StringValue email_value(email);
+ web_ui_->CallJavascriptFunction("AccountsOptions.updateAccountPicture",
+ email_value);
+ }
+}
+#endif
+
+void PersonalOptionsHandler::SendProfilesInfo() {
+ ProfileInfoCache& cache =
+ g_browser_process->profile_manager()->GetProfileInfoCache();
+ ListValue profile_info_list;
+ FilePath current_profile_path =
+ web_ui_->tab_contents()->browser_context()->GetPath();
+ for (size_t i = 0, e = cache.GetNumberOfProfiles(); i < e; ++i) {
+ DictionaryValue* profile_value = new DictionaryValue();
+ FilePath profile_path = cache.GetPathOfProfileAtIndex(i);
+ profile_value->SetString("name", cache.GetNameOfProfileAtIndex(i));
+ profile_value->Set("filePath", base::CreateFilePathValue(profile_path));
+ profile_value->SetBoolean("isCurrentProfile",
+ profile_path == current_profile_path);
+
+ bool is_gaia_picture =
+ cache.IsUsingGAIAPictureOfProfileAtIndex(i) &&
+ cache.GetGAIAPictureOfProfileAtIndex(i);
+ if (is_gaia_picture) {
+ gfx::Image icon = profiles::GetAvatarIconForWebUI(
+ cache.GetAvatarIconOfProfileAtIndex(i), true);
+ profile_value->SetString("iconURL", web_ui_util::GetImageDataUrl(icon));
+ } else {
+ size_t icon_index = cache.GetAvatarIconIndexOfProfileAtIndex(i);
+ profile_value->SetString("iconURL",
+ cache.GetDefaultAvatarIconUrl(icon_index));
+ }
+
+ profile_info_list.Append(profile_value);
+ }
+
+ web_ui_->CallJavascriptFunction("PersonalOptions.setProfilesInfo",
+ profile_info_list);
+}
+
+void PersonalOptionsHandler::CreateProfile(const ListValue* args) {
+ ProfileManager::CreateMultiProfileAsync();
+}
diff --git a/chrome/browser/ui/webui/options2/personal_options_handler.h b/chrome/browser/ui/webui/options2/personal_options_handler.h
new file mode 100644
index 0000000..4ffe0b6
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/personal_options_handler.h
@@ -0,0 +1,70 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS2_PERSONAL_OPTIONS_HANDLER_H_
+#define CHROME_BROWSER_UI_WEBUI_OPTIONS2_PERSONAL_OPTIONS_HANDLER_H_
+#pragma once
+
+#include "base/basictypes.h"
+#include "chrome/browser/sync/profile_sync_service.h"
+#include "chrome/browser/ui/webui/options2/options_ui2.h"
+#if defined(OS_CHROMEOS)
+#include "content/public/browser/notification_registrar.h"
+#endif
+
+// Chrome personal options page UI handler.
+class PersonalOptionsHandler : public OptionsPage2UIHandler,
+ public ProfileSyncServiceObserver {
+ public:
+ PersonalOptionsHandler();
+ virtual ~PersonalOptionsHandler();
+
+ // OptionsPage2UIHandler implementation.
+ virtual void GetLocalizedValues(DictionaryValue* localized_strings) OVERRIDE;
+ virtual void Initialize() OVERRIDE;
+
+ // WebUIMessageHandler implementation.
+ virtual void RegisterMessages() OVERRIDE;
+
+ // content::NotificationObserver implementation.
+ virtual void Observe(int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) OVERRIDE;
+
+ // ProfileSyncServiceObserver implementation.
+ virtual void OnStateChanged() OVERRIDE;
+
+ private:
+ void ObserveThemeChanged();
+ void ThemesReset(const ListValue* args);
+#if defined(TOOLKIT_GTK)
+ void ThemesSetGTK(const ListValue* args);
+#endif
+
+#if defined(OS_CHROMEOS)
+ void UpdateAccountPicture();
+ content::NotificationRegistrar registrar_;
+#endif
+
+ // Sends an array of Profile objects to javascript.
+ // Each object is of the form:
+ // profileInfo = {
+ // name: "Profile Name",
+ // iconURL: "chrome://path/to/icon/image",
+ // filePath: "/path/to/profile/data/on/disk",
+ // isCurrentProfile: false
+ // };
+ void SendProfilesInfo();
+
+ // Asynchronously opens a new browser window to create a new profile.
+ // |args| is not used.
+ void CreateProfile(const ListValue* args);
+
+ // True if the multiprofiles switch is enabled.
+ bool multiprofile_;
+
+ DISALLOW_COPY_AND_ASSIGN(PersonalOptionsHandler);
+};
+
+#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS2_PERSONAL_OPTIONS_HANDLER_H_
diff --git a/chrome/browser/ui/webui/options2/search_engine_manager_browsertest.js b/chrome/browser/ui/webui/options2/search_engine_manager_browsertest.js
new file mode 100644
index 0000000..9fe2165
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/search_engine_manager_browsertest.js
@@ -0,0 +1,33 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * TestFixture for search engine manager WebUI testing.
+ * @extends {testing.Test}
+ * @constructor
+ **/
+function SearchEngineManagerWebUITest() {}
+
+SearchEngineManagerWebUITest.prototype = {
+ __proto__: testing.Test.prototype,
+
+ /**
+ * Browse to the search engine manager.
+ **/
+ browsePreload: 'chrome://settings/searchEngines',
+};
+
+// See crosbug.com/22673
+GEN('#if defined(OS_CHROMEOS)');
+GEN('#define MAYBE_testOpenSearchEngineManager ' +
+ 'DISABLED_testOpenSearchEngineManager');
+GEN('#else');
+GEN('#define MAYBE_testOpenSearchEngineManager testOpenSearchEngineManager');
+GEN('#endif // defined(OS_CHROMEOS)');
+
+// Test opening the search engine manager has correct location.
+TEST_F('SearchEngineManagerWebUITest', 'MAYBE_testOpenSearchEngineManager',
+ function() {
+ assertEquals(this.browsePreload, document.location.href);
+ });
diff --git a/chrome/browser/ui/webui/options2/search_engine_manager_handler.cc b/chrome/browser/ui/webui/options2/search_engine_manager_handler.cc
new file mode 100644
index 0000000..1e289d8
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/search_engine_manager_handler.cc
@@ -0,0 +1,313 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/webui/options2/search_engine_manager_handler.h"
+
+#include "base/bind.h"
+#include "base/string_number_conversions.h"
+#include "base/utf_string_conversions.h"
+#include "base/values.h"
+#include "chrome/browser/extensions/extension_service.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/search_engines/template_url.h"
+#include "chrome/browser/search_engines/template_url_service.h"
+#include "chrome/browser/ui/search_engines/keyword_editor_controller.h"
+#include "chrome/browser/ui/search_engines/template_url_table_model.h"
+#include "chrome/common/extensions/extension.h"
+#include "chrome/common/url_constants.h"
+#include "grit/generated_resources.h"
+#include "grit/locale_settings.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace {
+
+enum EngineInfoIndexes {
+ ENGINE_NAME,
+ ENGINE_KEYWORD,
+ ENGINE_URL,
+};
+
+}; // namespace
+
+SearchEngineManagerHandler::SearchEngineManagerHandler() {
+}
+
+SearchEngineManagerHandler::~SearchEngineManagerHandler() {
+ if (list_controller_.get() && list_controller_->table_model())
+ list_controller_->table_model()->SetObserver(NULL);
+}
+
+void SearchEngineManagerHandler::Initialize() {
+ list_controller_.reset(
+ new KeywordEditorController(Profile::FromWebUI(web_ui_)));
+ if (list_controller_.get()) {
+ list_controller_->table_model()->SetObserver(this);
+ OnModelChanged();
+ }
+}
+
+void SearchEngineManagerHandler::GetLocalizedValues(
+ base::DictionaryValue* localized_strings) {
+ DCHECK(localized_strings);
+
+ RegisterTitle(localized_strings, "searchEngineManagerPage",
+ IDS_SEARCH_ENGINES_EDITOR_WINDOW_TITLE);
+ localized_strings->SetString("defaultSearchEngineListTitle",
+ l10n_util::GetStringUTF16(IDS_SEARCH_ENGINES_EDITOR_MAIN_SEPARATOR));
+ localized_strings->SetString("otherSearchEngineListTitle",
+ l10n_util::GetStringUTF16(IDS_SEARCH_ENGINES_EDITOR_OTHER_SEPARATOR));
+ localized_strings->SetString("extensionKeywordsListTitle",
+ l10n_util::GetStringUTF16(
+ IDS_SEARCH_ENGINES_EDITOR_EXTENSIONS_SEPARATOR));
+ localized_strings->SetString("manageExtensionsLinkText",
+ l10n_util::GetStringUTF16(IDS_MANAGE_EXTENSIONS));
+ localized_strings->SetString("searchEngineTableNameHeader",
+ l10n_util::GetStringUTF16(IDS_SEARCH_ENGINES_EDITOR_DESCRIPTION_COLUMN));
+ localized_strings->SetString("searchEngineTableKeywordHeader",
+ l10n_util::GetStringUTF16(IDS_SEARCH_ENGINES_EDITOR_KEYWORD_COLUMN));
+ localized_strings->SetString("searchEngineTableURLHeader",
+ l10n_util::GetStringUTF16(IDS_SEARCH_ENGINES_EDITOR_EDIT_BUTTON));
+ localized_strings->SetString("makeDefaultSearchEngineButton",
+ l10n_util::GetStringUTF16(IDS_SEARCH_ENGINES_EDITOR_MAKE_DEFAULT_BUTTON));
+ localized_strings->SetString("searchEngineTableNamePlaceholder",
+ l10n_util::GetStringUTF16(IDS_SEARCH_ENGINE_ADD_NEW_NAME_PLACEHOLDER));
+ localized_strings->SetString("searchEngineTableKeywordPlaceholder",
+ l10n_util::GetStringUTF16(IDS_SEARCH_ENGINE_ADD_NEW_KEYWORD_PLACEHOLDER));
+ localized_strings->SetString("searchEngineTableURLPlaceholder",
+ l10n_util::GetStringUTF16(IDS_SEARCH_ENGINE_ADD_NEW_URL_PLACEHOLDER));
+ localized_strings->SetString("editSearchEngineInvalidTitleToolTip",
+ l10n_util::GetStringUTF16(IDS_SEARCH_ENGINES_INVALID_TITLE_TT));
+ localized_strings->SetString("editSearchEngineInvalidKeywordToolTip",
+ l10n_util::GetStringUTF16(IDS_SEARCH_ENGINES_INVALID_KEYWORD_TT));
+ localized_strings->SetString("editSearchEngineInvalidURLToolTip",
+ l10n_util::GetStringUTF16(IDS_SEARCH_ENGINES_INVALID_URL_TT));
+}
+
+void SearchEngineManagerHandler::RegisterMessages() {
+ web_ui_->RegisterMessageCallback(
+ "managerSetDefaultSearchEngine",
+ base::Bind(&SearchEngineManagerHandler::SetDefaultSearchEngine,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback(
+ "removeSearchEngine",
+ base::Bind(&SearchEngineManagerHandler::RemoveSearchEngine,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback(
+ "editSearchEngine",
+ base::Bind(&SearchEngineManagerHandler::EditSearchEngine,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback(
+ "checkSearchEngineInfoValidity",
+ base::Bind(&SearchEngineManagerHandler::CheckSearchEngineInfoValidity,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback(
+ "searchEngineEditCancelled",
+ base::Bind(&SearchEngineManagerHandler::EditCancelled,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback(
+ "searchEngineEditCompleted",
+ base::Bind(&SearchEngineManagerHandler::EditCompleted,
+ base::Unretained(this)));
+}
+
+void SearchEngineManagerHandler::OnModelChanged() {
+ if (!list_controller_->loaded())
+ return;
+
+ // Find the default engine.
+ const TemplateURL* default_engine =
+ list_controller_->url_model()->GetDefaultSearchProvider();
+ int default_index = list_controller_->table_model()->IndexOfTemplateURL(
+ default_engine);
+
+ // Build the first list (default search engine options).
+ ListValue defaults_list;
+ int last_default_engine_index =
+ list_controller_->table_model()->last_search_engine_index();
+ for (int i = 0; i < last_default_engine_index; ++i) {
+ defaults_list.Append(CreateDictionaryForEngine(i, i == default_index));
+ }
+
+ // Build the second list (other search templates).
+ ListValue others_list;
+ if (last_default_engine_index < 0)
+ last_default_engine_index = 0;
+ int engine_count = list_controller_->table_model()->RowCount();
+ for (int i = last_default_engine_index; i < engine_count; ++i) {
+ others_list.Append(CreateDictionaryForEngine(i, i == default_index));
+ }
+
+ // Build the extension keywords list.
+ ListValue keyword_list;
+ ExtensionService* extension_service =
+ Profile::FromWebUI(web_ui_)->GetExtensionService();
+ if (extension_service) {
+ const ExtensionSet* extensions = extension_service->extensions();
+ for (ExtensionSet::const_iterator it = extensions->begin();
+ it != extensions->end(); ++it) {
+ if ((*it)->omnibox_keyword().size() > 0)
+ keyword_list.Append(CreateDictionaryForExtension(*(*it)));
+ }
+ }
+
+ web_ui_->CallJavascriptFunction("SearchEngineManager.updateSearchEngineList",
+ defaults_list, others_list, keyword_list);
+}
+
+void SearchEngineManagerHandler::OnItemsChanged(int start, int length) {
+ OnModelChanged();
+}
+
+void SearchEngineManagerHandler::OnItemsAdded(int start, int length) {
+ OnModelChanged();
+}
+
+void SearchEngineManagerHandler::OnItemsRemoved(int start, int length) {
+ OnModelChanged();
+}
+
+base::DictionaryValue* SearchEngineManagerHandler::CreateDictionaryForExtension(
+ const Extension& extension) {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ dict->SetString("name", extension.name());
+ dict->SetString("displayName", extension.name());
+ dict->SetString("keyword", extension.omnibox_keyword());
+ GURL icon = extension.GetIconURL(16, ExtensionIconSet::MATCH_BIGGER);
+ dict->SetString("iconURL", icon.spec());
+ dict->SetString("url", string16());
+ return dict;
+}
+
+base::DictionaryValue* SearchEngineManagerHandler::CreateDictionaryForEngine(
+ int index, bool is_default) {
+ TemplateURLTableModel* table_model = list_controller_->table_model();
+ const TemplateURL* template_url = list_controller_->GetTemplateURL(index);
+
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ dict->SetString("name", template_url->short_name());
+ dict->SetString("displayName", table_model->GetText(
+ index, IDS_SEARCH_ENGINES_EDITOR_DESCRIPTION_COLUMN));
+ dict->SetString("keyword", table_model->GetText(
+ index, IDS_SEARCH_ENGINES_EDITOR_KEYWORD_COLUMN));
+ dict->SetString("url", template_url->url()->DisplayURL());
+ dict->SetBoolean("urlLocked", template_url->prepopulate_id() > 0);
+ GURL icon_url = template_url->GetFaviconURL();
+ if (icon_url.is_valid())
+ dict->SetString("iconURL", icon_url.spec());
+ dict->SetString("modelIndex", base::IntToString(index));
+
+ if (list_controller_->CanRemove(template_url))
+ dict->SetString("canBeRemoved", "1");
+ if (list_controller_->CanMakeDefault(template_url))
+ dict->SetString("canBeDefault", "1");
+ if (is_default)
+ dict->SetString("default", "1");
+ if (list_controller_->CanEdit(template_url))
+ dict->SetString("canBeEdited", "1");
+
+ return dict;
+}
+
+void SearchEngineManagerHandler::SetDefaultSearchEngine(const ListValue* args) {
+ int index;
+ if (!ExtractIntegerValue(args, &index)) {
+ NOTREACHED();
+ return;
+ }
+ if (index < 0 || index >= list_controller_->table_model()->RowCount())
+ return;
+
+ list_controller_->MakeDefaultTemplateURL(index);
+}
+
+void SearchEngineManagerHandler::RemoveSearchEngine(const ListValue* args) {
+ int index;
+ if (!ExtractIntegerValue(args, &index)) {
+ NOTREACHED();
+ return;
+ }
+ if (index < 0 || index >= list_controller_->table_model()->RowCount())
+ return;
+
+ if (list_controller_->CanRemove(list_controller_->GetTemplateURL(index)))
+ list_controller_->RemoveTemplateURL(index);
+}
+
+void SearchEngineManagerHandler::EditSearchEngine(const ListValue* args) {
+ int index;
+ if (!ExtractIntegerValue(args, &index)) {
+ NOTREACHED();
+ return;
+ }
+ // Allow -1, which means we are adding a new engine.
+ if (index < -1 || index >= list_controller_->table_model()->RowCount())
+ return;
+
+ const TemplateURL* edit_url = NULL;
+ if (index != -1)
+ edit_url = list_controller_->GetTemplateURL(index);
+ edit_controller_.reset(new EditSearchEngineController(
+ edit_url, this, Profile::FromWebUI(web_ui_)));
+}
+
+void SearchEngineManagerHandler::OnEditedKeyword(
+ const TemplateURL* template_url,
+ const string16& title,
+ const string16& keyword,
+ const std::string& url) {
+ if (template_url) {
+ list_controller_->ModifyTemplateURL(template_url, title, keyword, url);
+ } else {
+ list_controller_->AddTemplateURL(title, keyword, url);
+ }
+ edit_controller_.reset();
+}
+
+void SearchEngineManagerHandler::CheckSearchEngineInfoValidity(
+ const ListValue* args)
+{
+ if (!edit_controller_.get())
+ return;
+ string16 name;
+ string16 keyword;
+ std::string url;
+ std::string modelIndex;
+ if (!args->GetString(ENGINE_NAME, &name) ||
+ !args->GetString(ENGINE_KEYWORD, &keyword) ||
+ !args->GetString(ENGINE_URL, &url) ||
+ !args->GetString(3, &modelIndex)) {
+ NOTREACHED();
+ return;
+ }
+
+ base::DictionaryValue validity;
+ validity.SetBoolean("name", edit_controller_->IsTitleValid(name));
+ validity.SetBoolean("keyword", edit_controller_->IsKeywordValid(keyword));
+ validity.SetBoolean("url", edit_controller_->IsURLValid(url));
+ StringValue indexValue(modelIndex);
+ web_ui_->CallJavascriptFunction("SearchEngineManager.validityCheckCallback",
+ validity, indexValue);
+}
+
+void SearchEngineManagerHandler::EditCancelled(const ListValue* args) {
+ if (!edit_controller_.get())
+ return;
+ edit_controller_->CleanUpCancelledAdd();
+ edit_controller_.reset();
+}
+
+void SearchEngineManagerHandler::EditCompleted(const ListValue* args) {
+ if (!edit_controller_.get())
+ return;
+ string16 name;
+ string16 keyword;
+ std::string url;
+ if (!args->GetString(ENGINE_NAME, &name) ||
+ !args->GetString(ENGINE_KEYWORD, &keyword) ||
+ !args->GetString(ENGINE_URL, &url)) {
+ NOTREACHED();
+ return;
+ }
+ edit_controller_->AcceptAddOrEdit(name, keyword, url);
+}
diff --git a/chrome/browser/ui/webui/options2/search_engine_manager_handler.h b/chrome/browser/ui/webui/options2/search_engine_manager_handler.h
new file mode 100644
index 0000000..aa6815d
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/search_engine_manager_handler.h
@@ -0,0 +1,79 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS2_SEARCH_ENGINE_MANAGER_HANDLER_H_
+#define CHROME_BROWSER_UI_WEBUI_OPTIONS2_SEARCH_ENGINE_MANAGER_HANDLER_H_
+
+#include "chrome/browser/ui/search_engines/edit_search_engine_controller.h"
+#include "chrome/browser/ui/webui/options2/options_ui2.h"
+#include "ui/base/models/table_model_observer.h"
+
+class Extension;
+class KeywordEditorController;
+
+class SearchEngineManagerHandler : public OptionsPage2UIHandler,
+ public ui::TableModelObserver,
+ public EditSearchEngineControllerDelegate {
+ public:
+ SearchEngineManagerHandler();
+ virtual ~SearchEngineManagerHandler();
+
+ virtual void Initialize() OVERRIDE;
+
+ // OptionsPage2UIHandler implementation.
+ virtual void GetLocalizedValues(
+ base::DictionaryValue* localized_strings) OVERRIDE;
+
+ // ui::TableModelObserver implementation.
+ virtual void OnModelChanged() OVERRIDE;
+ virtual void OnItemsChanged(int start, int length) OVERRIDE;
+ virtual void OnItemsAdded(int start, int length) OVERRIDE;
+ virtual void OnItemsRemoved(int start, int length) OVERRIDE;
+
+ // EditSearchEngineControllerDelegate implementation.
+ virtual void OnEditedKeyword(const TemplateURL* template_url,
+ const string16& title,
+ const string16& keyword,
+ const std::string& url) OVERRIDE;
+
+ virtual void RegisterMessages() OVERRIDE;
+
+ private:
+ scoped_ptr<KeywordEditorController> list_controller_;
+ scoped_ptr<EditSearchEngineController> edit_controller_;
+
+ // Removes the search engine at the given index. Called from WebUI.
+ void RemoveSearchEngine(const base::ListValue* args);
+
+ // Sets the search engine at the given index to be default. Called from WebUI.
+ void SetDefaultSearchEngine(const base::ListValue* args);
+
+ // Starts an edit session for the search engine at the given index. If the
+ // index is -1, starts editing a new search engine instead of an existing one.
+ // Called from WebUI.
+ void EditSearchEngine(const base::ListValue* args);
+
+ // Validates the given search engine values, and reports the results back
+ // to WebUI. Called from WebUI.
+ void CheckSearchEngineInfoValidity(const base::ListValue* args);
+
+ // Called when an edit is cancelled.
+ // Called from WebUI.
+ void EditCancelled(const base::ListValue* args);
+
+ // Called when an edit is finished and should be saved.
+ // Called from WebUI.
+ void EditCompleted(const base::ListValue* args);
+
+ // Returns a dictionary to pass to WebUI representing the given search engine.
+ base::DictionaryValue* CreateDictionaryForEngine(int index, bool is_default);
+
+ // Returns a dictionary to pass to WebUI representing the extension.
+ base::DictionaryValue* CreateDictionaryForExtension(
+ const Extension& extension);
+
+ DISALLOW_COPY_AND_ASSIGN(SearchEngineManagerHandler);
+};
+
+#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS2_SEARCH_ENGINE_MANAGER_HANDLER_H_
diff --git a/chrome/browser/ui/webui/options2/stop_syncing_handler.cc b/chrome/browser/ui/webui/options2/stop_syncing_handler.cc
new file mode 100644
index 0000000..550cc4c
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/stop_syncing_handler.cc
@@ -0,0 +1,54 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/webui/options2/stop_syncing_handler.h"
+
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/utf_string_conversions.h"
+#include "base/values.h"
+#include "chrome/browser/google/google_util.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/profiles/profile_manager.h"
+#include "chrome/browser/sync/profile_sync_service.h"
+#include "chrome/common/url_constants.h"
+#include "grit/chromium_strings.h"
+#include "grit/generated_resources.h"
+#include "ui/base/l10n/l10n_util.h"
+
+StopSyncingHandler::StopSyncingHandler() {
+}
+
+StopSyncingHandler::~StopSyncingHandler() {
+}
+
+void StopSyncingHandler::GetLocalizedValues(
+ DictionaryValue* localized_strings) {
+ DCHECK(localized_strings);
+ localized_strings->SetString("stopSyncingExplanation",
+ l10n_util::GetStringFUTF16(
+ IDS_SYNC_STOP_SYNCING_EXPLANATION_LABEL,
+ l10n_util::GetStringUTF16(IDS_PRODUCT_NAME),
+ ASCIIToUTF16(google_util::StringAppendGoogleLocaleParam(
+ chrome::kSyncGoogleDashboardURL))));
+ localized_strings->SetString("stopSyncingTitle",
+ l10n_util::GetStringUTF16(IDS_SYNC_STOP_SYNCING_DIALOG_TITLE));
+ localized_strings->SetString("stopSyncingConfirm",
+ l10n_util::GetStringUTF16(IDS_SYNC_STOP_SYNCING_CONFIRM_BUTTON_LABEL));
+}
+
+void StopSyncingHandler::RegisterMessages() {
+ DCHECK(web_ui_);
+ web_ui_->RegisterMessageCallback("stopSyncing",
+ base::Bind(&StopSyncingHandler::StopSyncing, base::Unretained(this)));
+}
+
+void StopSyncingHandler::StopSyncing(const ListValue* args){
+ ProfileSyncService* service =
+ Profile::FromWebUI(web_ui_)->GetProfileSyncService();
+ if (service != NULL && ProfileSyncService::IsSyncEnabled()) {
+ service->DisableForUser();
+ ProfileSyncService::SyncEvent(ProfileSyncService::STOP_FROM_OPTIONS);
+ }
+}
diff --git a/chrome/browser/ui/webui/options2/stop_syncing_handler.h b/chrome/browser/ui/webui/options2/stop_syncing_handler.h
new file mode 100644
index 0000000..2c8b626
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/stop_syncing_handler.h
@@ -0,0 +1,29 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS2_STOP_SYNCING_HANDLER_H_
+#define CHROME_BROWSER_UI_WEBUI_OPTIONS2_STOP_SYNCING_HANDLER_H_
+
+#include "chrome/browser/ui/webui/options2/options_ui2.h"
+
+// Chrome personal stuff stop syncing overlay UI handler.
+class StopSyncingHandler : public OptionsPage2UIHandler {
+ public:
+ StopSyncingHandler();
+ virtual ~StopSyncingHandler();
+
+ // OptionsPage2UIHandler implementation.
+ virtual void GetLocalizedValues(
+ base::DictionaryValue* localized_strings) OVERRIDE;
+
+ // WebUIMessageHandler implementation.
+ virtual void RegisterMessages() OVERRIDE;
+
+ private:
+ void StopSyncing(const base::ListValue* args);
+
+ DISALLOW_COPY_AND_ASSIGN(StopSyncingHandler);
+};
+
+#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS2_STOP_SYNCING_HANDLER_H_
diff --git a/chrome/browser/ui/webui/options2/web_intents_settings_handler.cc b/chrome/browser/ui/webui/options2/web_intents_settings_handler.cc
new file mode 100644
index 0000000..874cf834
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/web_intents_settings_handler.cc
@@ -0,0 +1,160 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/webui/options2/web_intents_settings_handler.h"
+
+#include "base/bind.h"
+#include "base/utf_string_conversions.h"
+#include "base/values.h"
+#include "chrome/browser/browsing_data_appcache_helper.h"
+#include "chrome/browser/browsing_data_database_helper.h"
+#include "chrome/browser/browsing_data_file_system_helper.h"
+#include "chrome/browser/browsing_data_indexed_db_helper.h"
+#include "chrome/browser/browsing_data_local_storage_helper.h"
+#include "chrome/browser/intents/web_intents_registry.h"
+#include "chrome/browser/intents/web_intents_registry_factory.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/webdata/web_data_service.h"
+#include "content/browser/webui/web_ui.h"
+#include "grit/generated_resources.h"
+#include "net/url_request/url_request_context_getter.h"
+#include "ui/base/l10n/l10n_util.h"
+
+WebIntentsSettingsHandler::WebIntentsSettingsHandler()
+ : web_intents_registry_(NULL),
+ batch_update_(false) {
+}
+
+WebIntentsSettingsHandler::~WebIntentsSettingsHandler() {
+}
+
+void WebIntentsSettingsHandler::GetLocalizedValues(
+ DictionaryValue* localized_strings) {
+ DCHECK(localized_strings);
+
+ static OptionsStringResource resources[] = {
+ { "intentsDomain", IDS_INTENTS_DOMAIN_COLUMN_HEADER },
+ { "intentsServiceData", IDS_INTENTS_SERVICE_DATA_COLUMN_HEADER },
+ { "manageIntents", IDS_INTENTS_MANAGE_BUTTON },
+ { "removeIntent", IDS_INTENTS_REMOVE_INTENT_BUTTON },
+ };
+
+ RegisterStrings(localized_strings, resources, arraysize(resources));
+ RegisterTitle(localized_strings, "intentsViewPage",
+ IDS_INTENTS_MANAGER_WINDOW_TITLE);
+}
+
+void WebIntentsSettingsHandler::RegisterMessages() {
+ web_ui_->RegisterMessageCallback("removeIntent",
+ base::Bind(&WebIntentsSettingsHandler::RemoveIntent,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("loadIntents",
+ base::Bind(&WebIntentsSettingsHandler::LoadChildren,
+ base::Unretained(this)));
+}
+
+void WebIntentsSettingsHandler::TreeNodesAdded(ui::TreeModel* model,
+ ui::TreeModelNode* parent,
+ int start,
+ int count) {
+ SendChildren(intents_tree_model_->GetRoot());
+}
+
+void WebIntentsSettingsHandler::TreeNodesRemoved(ui::TreeModel* model,
+ ui::TreeModelNode* parent,
+ int start,
+ int count) {
+ SendChildren(intents_tree_model_->GetRoot());
+}
+
+void WebIntentsSettingsHandler::TreeModelBeginBatch(WebIntentsModel* model) {
+ batch_update_ = true;
+}
+
+void WebIntentsSettingsHandler::TreeModelEndBatch(WebIntentsModel* model) {
+ batch_update_ = false;
+
+ SendChildren(intents_tree_model_->GetRoot());
+}
+
+void WebIntentsSettingsHandler::EnsureWebIntentsModelCreated() {
+ if (intents_tree_model_.get()) return;
+
+ Profile* profile = Profile::FromWebUI(web_ui_);
+ web_intents_registry_ = WebIntentsRegistryFactory::GetForProfile(profile);
+ intents_tree_model_.reset(new WebIntentsModel(web_intents_registry_));
+ intents_tree_model_->AddWebIntentsTreeObserver(this);
+}
+
+void WebIntentsSettingsHandler::RemoveIntent(const base::ListValue* args) {
+ std::string node_path;
+ if (!args->GetString(0, &node_path)) {
+ return;
+ }
+
+ EnsureWebIntentsModelCreated();
+
+ WebIntentsTreeNode* node = intents_tree_model_->GetTreeNode(node_path);
+ if (node->Type() == WebIntentsTreeNode::TYPE_ORIGIN) {
+ RemoveOrigin(node);
+ } else if (node->Type() == WebIntentsTreeNode::TYPE_SERVICE) {
+ ServiceTreeNode* snode = static_cast<ServiceTreeNode*>(node);
+ RemoveService(snode);
+ }
+}
+
+void WebIntentsSettingsHandler::RemoveOrigin(WebIntentsTreeNode* node) {
+ // TODO(gbillock): This is a known batch update. Worth optimizing?
+ while (!node->empty()) {
+ WebIntentsTreeNode* cnode = node->GetChild(0);
+ CHECK(cnode->Type() == WebIntentsTreeNode::TYPE_SERVICE);
+ ServiceTreeNode* snode = static_cast<ServiceTreeNode*>(cnode);
+ RemoveService(snode);
+ }
+ delete intents_tree_model_->Remove(node->parent(), node);
+}
+
+void WebIntentsSettingsHandler::RemoveService(ServiceTreeNode* snode) {
+ webkit_glue::WebIntentServiceData service;
+ service.service_url = GURL(snode->ServiceUrl());
+ service.action = snode->Action();
+ string16 stype;
+ if (snode->Types().GetString(0, &stype)) {
+ service.type = stype; // Really need to iterate here.
+ }
+ service.title = snode->ServiceName();
+ web_intents_registry_->UnregisterIntentProvider(service);
+ delete intents_tree_model_->Remove(snode->parent(), snode);
+}
+
+void WebIntentsSettingsHandler::LoadChildren(const base::ListValue* args) {
+ EnsureWebIntentsModelCreated();
+
+ std::string node_path;
+ if (!args->GetString(0, &node_path)) {
+ SendChildren(intents_tree_model_->GetRoot());
+ return;
+ }
+
+ WebIntentsTreeNode* node = intents_tree_model_->GetTreeNode(node_path);
+ SendChildren(node);
+}
+
+void WebIntentsSettingsHandler::SendChildren(WebIntentsTreeNode* parent) {
+ // Early bailout during batch updates. We'll get one after the batch concludes
+ // with batch_update_ set false.
+ if (batch_update_) return;
+
+ ListValue* children = new ListValue;
+ intents_tree_model_->GetChildNodeList(parent, 0, parent->child_count(),
+ children);
+
+ ListValue args;
+ args.Append(parent == intents_tree_model_->GetRoot() ?
+ Value::CreateNullValue() :
+ Value::CreateStringValue(intents_tree_model_->GetTreeNodeId(parent)));
+ args.Append(children);
+
+ web_ui_->CallJavascriptFunction("IntentsView.loadChildren", args);
+}
diff --git a/chrome/browser/ui/webui/options2/web_intents_settings_handler.h b/chrome/browser/ui/webui/options2/web_intents_settings_handler.h
new file mode 100644
index 0000000..186b295
--- /dev/null
+++ b/chrome/browser/ui/webui/options2/web_intents_settings_handler.h
@@ -0,0 +1,77 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS2_WEB_INTENTS_SETTINGS_HANDLER_H_
+#define CHROME_BROWSER_UI_WEBUI_OPTIONS2_WEB_INTENTS_SETTINGS_HANDLER_H_
+#pragma once
+
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "chrome/browser/ui/intents/web_intents_model.h"
+#include "chrome/browser/ui/webui/options2/options_ui2.h"
+
+class WebIntentsRegistry;
+
+// Manage setting up the backing data for the web intents options page.
+class WebIntentsSettingsHandler : public OptionsPage2UIHandler,
+ public WebIntentsModel::Observer {
+ public:
+ WebIntentsSettingsHandler();
+ virtual ~WebIntentsSettingsHandler();
+
+ // OptionsPage2UIHandler implementation.
+ virtual void GetLocalizedValues(
+ base::DictionaryValue* localized_strings) OVERRIDE;
+ virtual void RegisterMessages() OVERRIDE;
+
+ // WebIntentsModel::Observer implementation.
+ virtual void TreeNodesAdded(ui::TreeModel* model,
+ ui::TreeModelNode* parent,
+ int start,
+ int count) OVERRIDE;
+ virtual void TreeNodesRemoved(ui::TreeModel* model,
+ ui::TreeModelNode* parent,
+ int start,
+ int count) OVERRIDE;
+ virtual void TreeNodeChanged(ui::TreeModel* model,
+ ui::TreeModelNode* node) OVERRIDE {}
+ virtual void TreeModelBeginBatch(WebIntentsModel* model) OVERRIDE;
+ virtual void TreeModelEndBatch(WebIntentsModel* model) OVERRIDE;
+
+ private:
+ // Creates the WebIntentsModel if neccessary.
+ void EnsureWebIntentsModelCreated();
+
+ // Updates search filter for cookies tree model.
+ void UpdateSearchResults(const base::ListValue* args);
+
+ // Remove all sites data.
+ void RemoveAll(const base::ListValue* args);
+
+ // Remove selected sites data.
+ void RemoveIntent(const base::ListValue* args);
+
+ // Helper functions for removals.
+ void RemoveOrigin(WebIntentsTreeNode* node);
+ void RemoveService(ServiceTreeNode* snode);
+
+ // Trigger for SendChildren to load the JS model.
+ void LoadChildren(const base::ListValue* args);
+
+ // Get children nodes data and pass it to 'IntentsView.loadChildren' to
+ // update the WebUI.
+ void SendChildren(WebIntentsTreeNode* parent);
+
+ WebIntentsRegistry* web_intents_registry_; // Weak pointer.
+
+ // Backing data model for the intents list.
+ scoped_ptr<WebIntentsModel> intents_tree_model_;
+
+ // Flag to indicate whether there is a batch update in progress.
+ bool batch_update_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebIntentsSettingsHandler);
+};
+
+#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS2_WEB_INTENTS_SETTINGS_HANDLER_H_
diff --git a/chrome/browser/ui/webui/sync_promo_handler2.cc b/chrome/browser/ui/webui/sync_promo_handler2.cc
new file mode 100644
index 0000000..9788d45
--- /dev/null
+++ b/chrome/browser/ui/webui/sync_promo_handler2.cc
@@ -0,0 +1,288 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/webui/sync_promo_handler2.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/metrics/histogram.h"
+#include "base/time.h"
+#include "chrome/browser/prefs/pref_service.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/sync/profile_sync_service.h"
+#include "chrome/browser/sync/sync_setup_flow.h"
+#include "chrome/browser/tabs/tab_strip_model.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_list.h"
+#include "chrome/browser/ui/webui/sync_promo_trial.h"
+#include "chrome/browser/ui/webui/sync_promo_ui.h"
+#include "chrome/common/chrome_notification_types.h"
+#include "chrome/common/extensions/extension_constants.h"
+#include "chrome/common/pref_names.h"
+#include "chrome/common/url_constants.h"
+#include "content/browser/tab_contents/tab_contents.h"
+#include "content/public/browser/notification_details.h"
+#include "content/public/browser/notification_service.h"
+
+namespace {
+
+// User actions on the sync promo (aka "Sign in to Chrome").
+enum SyncPromoUserFlowActionEnums {
+ SYNC_PROMO_VIEWED,
+ SYNC_PROMO_LEARN_MORE_CLICKED,
+ SYNC_PROMO_ACCOUNT_HELP_CLICKED,
+ SYNC_PROMO_CREATE_ACCOUNT_CLICKED,
+ SYNC_PROMO_SKIP_CLICKED,
+ SYNC_PROMO_SIGN_IN_ATTEMPTED,
+ SYNC_PROMO_SIGNED_IN_SUCCESSFULLY,
+ SYNC_PROMO_ADVANCED_CLICKED,
+ SYNC_PROMO_ENCRYPTION_HELP_CLICKED,
+ SYNC_PROMO_CANCELLED_AFTER_SIGN_IN,
+ SYNC_PROMO_CONFIRMED_AFTER_SIGN_IN,
+ SYNC_PROMO_CLOSED_TAB,
+ SYNC_PROMO_CLOSED_WINDOW,
+ SYNC_PROMO_LEFT_DURING_THROBBER,
+ SYNC_PROMO_BUCKET_BOUNDARY,
+ SYNC_PROMO_FIRST_VALID_JS_ACTION = SYNC_PROMO_LEARN_MORE_CLICKED,
+ SYNC_PROMO_LAST_VALID_JS_ACTION = SYNC_PROMO_CONFIRMED_AFTER_SIGN_IN,
+};
+
+// This was added because of the need to change the existing UMA enum for the
+// sync promo mid-flight. Ideally these values would be contiguous, but the
+// real world is not always ideal.
+static bool IsValidUserFlowAction(int action) {
+ return (action >= SYNC_PROMO_FIRST_VALID_JS_ACTION &&
+ action <= SYNC_PROMO_LAST_VALID_JS_ACTION) ||
+ action == SYNC_PROMO_LEFT_DURING_THROBBER;
+}
+
+} // namespace
+
+SyncPromoHandler2::SyncPromoHandler2(ProfileManager* profile_manager)
+ : SyncSetupHandler2(profile_manager),
+ window_already_closed_(false) {
+}
+
+SyncPromoHandler2::~SyncPromoHandler2() {
+}
+
+// static
+void SyncPromoHandler2::RegisterUserPrefs(PrefService* prefs) {
+ prefs->RegisterIntegerPref(prefs::kSyncPromoViewCount, 0,
+ PrefService::UNSYNCABLE_PREF);
+ prefs->RegisterBooleanPref(prefs::kSyncPromoShowNTPBubble, false,
+ PrefService::UNSYNCABLE_PREF);
+}
+
+WebUIMessageHandler* SyncPromoHandler2::Attach(WebUI* web_ui) {
+ DCHECK(web_ui);
+ // Keep a reference to the preferences service for convenience and it's
+ // probably a little faster that getting it via Profile::FromWebUI() every
+ // time we need to interact with preferences.
+ prefs_ = Profile::FromWebUI(web_ui)->GetPrefs();
+ DCHECK(prefs_);
+ // Ignore events from view-source:chrome://syncpromo.
+ if (!web_ui->tab_contents()->controller().GetActiveEntry()->
+ IsViewSourceMode()) {
+ // Listen to see if the tab we're in gets closed.
+ registrar_.Add(this, content::NOTIFICATION_TAB_CLOSING,
+ content::Source<NavigationController>(
+ &web_ui->tab_contents()->controller()));
+ // Listen to see if the window we're in gets closed.
+ registrar_.Add(this, chrome::NOTIFICATION_BROWSER_CLOSING,
+ content::NotificationService::AllSources());
+ }
+ return SyncSetupHandler2::Attach(web_ui);
+}
+
+void SyncPromoHandler2::RegisterMessages() {
+ web_ui_->RegisterMessageCallback("SyncPromo:Close",
+ base::Bind(&SyncPromoHandler2::HandleCloseSyncPromo,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("SyncPromo:Initialize",
+ base::Bind(&SyncPromoHandler2::HandleInitializeSyncPromo,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("SyncPromo:RecordSignInAttempts",
+ base::Bind(&SyncPromoHandler2::HandleRecordSignInAttempts,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("SyncPromo:RecordThrobberTime",
+ base::Bind(&SyncPromoHandler2::HandleRecordThrobberTime,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("SyncPromo:ShowAdvancedSettings",
+ base::Bind(&SyncPromoHandler2::HandleShowAdvancedSettings,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("SyncPromo:UserFlowAction",
+ base::Bind(&SyncPromoHandler2::HandleUserFlowAction,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("SyncPromo:UserSkipped",
+ base::Bind(&SyncPromoHandler2::HandleUserSkipped,
+ base::Unretained(this)));
+ SyncSetupHandler2::RegisterMessages();
+}
+
+void SyncPromoHandler2::ShowGaiaSuccessAndClose() {
+ if (sync_promo_trial::IsExperimentActive())
+ sync_promo_trial::RecordUserSignedIn();
+
+ SyncSetupHandler2::ShowGaiaSuccessAndClose();
+}
+
+void SyncPromoHandler2::ShowGaiaSuccessAndSettingUp() {
+ if (sync_promo_trial::IsExperimentActive())
+ sync_promo_trial::RecordUserSignedIn();
+
+ SyncSetupHandler2::ShowGaiaSuccessAndSettingUp();
+}
+
+void SyncPromoHandler2::ShowConfigure(const base::DictionaryValue& args) {
+ bool usePassphrase = false;
+ args.GetBoolean("usePassphrase", &usePassphrase);
+
+ if (usePassphrase) {
+ // If a passphrase is required then we must show the configure pane.
+ SyncSetupHandler2::ShowConfigure(args);
+ } else {
+ // If no passphrase is required then skip the configure pane and sync
+ // everything by default. This makes the first run experience simpler.
+ // Note, there's an advanced link in the sync promo that takes users
+ // to Settings where the configure pane is not skipped.
+ SyncConfiguration configuration;
+ configuration.sync_everything = true;
+ DCHECK(flow());
+ flow()->OnUserConfigured(configuration);
+ }
+}
+
+void SyncPromoHandler2::Observe(int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) {
+ switch (type) {
+ case content::NOTIFICATION_TAB_CLOSING: {
+ if (!window_already_closed_)
+ RecordUserFlowAction(SYNC_PROMO_CLOSED_TAB);
+ break;
+ }
+ case chrome::NOTIFICATION_BROWSER_CLOSING: {
+ // Make sure we're in the tab strip of the closing window.
+ Browser* browser = content::Source<Browser>(source).ptr();
+ if (browser->tabstrip_model()->GetWrapperIndex(
+ web_ui_->tab_contents()) != TabStripModel::kNoTab) {
+ RecordUserFlowAction(SYNC_PROMO_CLOSED_WINDOW);
+ window_already_closed_ = true;
+ }
+ break;
+ }
+ default: {
+ NOTREACHED();
+ }
+ }
+}
+
+void SyncPromoHandler2::StepWizardForShowSetupUI() {
+ ProfileSyncService* service =
+ Profile::FromWebUI(web_ui_)->GetProfileSyncService();
+ service->get_wizard().Step(SyncSetupWizard::GetLoginState());
+}
+
+void SyncPromoHandler2::ShowSetupUI() {
+ // We don't need to do anything here; The UI for the sync promo is already
+ // displayed.
+}
+
+void SyncPromoHandler2::HandleCloseSyncPromo(const base::ListValue* args) {
+ CloseSyncSetup();
+
+ // If the user has signed in then set the pref to show them NTP bubble
+ // confirming that they're signed in.
+ std::string username = prefs_->GetString(prefs::kGoogleServicesUsername);
+ if (!username.empty())
+ prefs_->SetBoolean(prefs::kSyncPromoShowNTPBubble, true);
+
+ GURL url = SyncPromoUI::GetNextPageURLForSyncPromoURL(
+ web_ui_->tab_contents()->GetURL());
+ web_ui_->tab_contents()->OpenURL(url, GURL(), CURRENT_TAB,
+ content::PAGE_TRANSITION_LINK);
+}
+
+void SyncPromoHandler2::HandleInitializeSyncPromo(const base::ListValue* args) {
+ // If the promo is also the Chrome launch page, we want to show the title and
+ // log an event if we are running an experiment.
+ bool is_launch_page = SyncPromoUI::GetIsLaunchPageForSyncPromoURL(
+ web_ui_->tab_contents()->GetURL());
+ if (is_launch_page && sync_promo_trial::IsExperimentActive())
+ sync_promo_trial::RecordUserSawMessage();
+ base::FundamentalValue visible(is_launch_page);
+ web_ui_->CallJavascriptFunction("SyncSetupOverlay.setPromoTitleVisible",
+ visible);
+
+ OpenSyncSetup();
+ // We don't need to compute anything for this, just do this every time.
+ RecordUserFlowAction(SYNC_PROMO_VIEWED);
+ // Increment view count first and show natural numbers in stats rather than 0
+ // based starting point (if it happened to be our first time showing this).
+ IncrementViewCountBy(1);
+ // Record +1 for every view. This is the only thing we record that's not part
+ // of the user flow histogram.
+ UMA_HISTOGRAM_COUNTS("SyncPromo.NumTimesViewed", GetViewCount());
+}
+
+void SyncPromoHandler2::HandleShowAdvancedSettings(
+ const base::ListValue* args) {
+ CloseSyncSetup();
+ std::string url(chrome::kChromeUISettingsURL);
+ url += chrome::kSyncSetupSubPage;
+ web_ui_->tab_contents()->OpenURL(GURL(url), GURL(), CURRENT_TAB,
+ content::PAGE_TRANSITION_LINK);
+ RecordUserFlowAction(SYNC_PROMO_ADVANCED_CLICKED);
+}
+
+// TODO(dbeam): Replace with metricsHandler:recordHistogramTime when it exists.
+void SyncPromoHandler2::HandleRecordThrobberTime(const base::ListValue* args) {
+ double time_double;
+ CHECK(args->GetDouble(0, &time_double));
+ UMA_HISTOGRAM_TIMES("SyncPromo.ThrobberTime",
+ base::TimeDelta::FromMilliseconds(time_double));
+}
+
+// TODO(dbeam): Replace with metricsHandler:recordHistogramCount when it exists.
+void SyncPromoHandler2::HandleRecordSignInAttempts(const base::ListValue* args) {
+ double count_double;
+ CHECK(args->GetDouble(0, &count_double));
+ UMA_HISTOGRAM_COUNTS("SyncPromo.SignInAttempts", count_double);
+}
+
+void SyncPromoHandler2::HandleUserFlowAction(const base::ListValue* args) {
+ double action_double;
+ CHECK(args->GetDouble(0, &action_double));
+ int action = static_cast<int>(action_double);
+
+ if (IsValidUserFlowAction(action))
+ RecordUserFlowAction(action);
+ else
+ NOTREACHED() << "Attempt to record invalid user flow action on sync promo.";
+}
+
+void SyncPromoHandler2::HandleUserSkipped(const base::ListValue* args) {
+ SyncPromoUI::SetUserSkippedSyncPromo(Profile::FromWebUI(web_ui_));
+ RecordUserFlowAction(SYNC_PROMO_SKIP_CLICKED);
+}
+
+int SyncPromoHandler2::GetViewCount() const {
+ // The locally persistent number of times the user has seen the sync promo.
+ return prefs_->GetInteger(prefs::kSyncPromoViewCount);
+}
+
+int SyncPromoHandler2::IncrementViewCountBy(unsigned int amount) {
+ // Let the user increment by 0 if they really want. It might be useful for a
+ // weird way of sending preference change notifications...
+ int adjusted = GetViewCount() + amount;
+ prefs_->SetInteger(prefs::kSyncPromoViewCount, adjusted);
+ return adjusted;
+}
+
+void SyncPromoHandler2::RecordUserFlowAction(int action) {
+ // Send an enumeration to our single user flow histogram.
+ UMA_HISTOGRAM_ENUMERATION("SyncPromo.UserFlow", action,
+ SYNC_PROMO_BUCKET_BOUNDARY);
+}
diff --git a/chrome/browser/ui/webui/sync_promo_handler2.h b/chrome/browser/ui/webui/sync_promo_handler2.h
new file mode 100644
index 0000000..c2d3c8f
--- /dev/null
+++ b/chrome/browser/ui/webui/sync_promo_handler2.h
@@ -0,0 +1,97 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_WEBUI_SYNC_PROMO_HANDLER_H_
+#define CHROME_BROWSER_UI_WEBUI_SYNC_PROMO_HANDLER_H_
+#pragma once
+
+#include "chrome/browser/ui/webui/sync_setup_handler2.h"
+
+class PrefService;
+
+// The handler for JavaScript messages related to the "sync promo" page.
+class SyncPromoHandler2 : public SyncSetupHandler2 {
+ public:
+ explicit SyncPromoHandler2(ProfileManager* profile_manager);
+ virtual ~SyncPromoHandler2();
+
+ // Called to register our preferences before we use them (so there will be a
+ // default if not present yet).
+ static void RegisterUserPrefs(PrefService* prefs);
+
+ // WebUIMessageHandler implementation.
+ virtual WebUIMessageHandler* Attach(WebUI* web_ui) OVERRIDE;
+ virtual void RegisterMessages() OVERRIDE;
+
+ // SyncSetupFlowHandler implementation.
+ virtual void ShowGaiaSuccessAndClose() OVERRIDE;
+ virtual void ShowGaiaSuccessAndSettingUp() OVERRIDE;
+ virtual void ShowConfigure(const base::DictionaryValue& args) OVERRIDE;
+
+ // content::NotificationObserver implementation.
+ virtual void Observe(int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) OVERRIDE;
+
+ protected:
+ virtual void StepWizardForShowSetupUI() OVERRIDE;
+
+ virtual void ShowSetupUI() OVERRIDE;
+
+ private:
+ // JavaScript callback handler to close the sync promo.
+ void HandleCloseSyncPromo(const base::ListValue* args);
+
+ // JavaScript callback handler to initialize the sync promo.
+ void HandleInitializeSyncPromo(const base::ListValue* args);
+
+ // JavaScript handler to record the duration for which the throbber was
+ // visible during an attempted sign-in flow.
+ void HandleRecordThrobberTime(const base::ListValue* args);
+
+ // JavaScript handler to record the number of times a user attempted to sign
+ // in to chrome while they were on the sync promo page.
+ void HandleRecordSignInAttempts(const base::ListValue* args);
+
+ // JavaScript callback handler to switch the advanced sync settings. |args| is
+ // the list of arguments passed from JS and should be an empty list.
+ void HandleShowAdvancedSettings(const base::ListValue* args);
+
+ // JavaScript callback handler to record user actions on the sync promo.
+ void HandleUserFlowAction(const base::ListValue* args);
+
+ // JavaScript callback handler for when a user clicks skip.
+ void HandleUserSkipped(const base::ListValue* args);
+
+ // Return the number of times the user with the current profile has seen the
+ // sync promo.
+ int GetViewCount() const;
+
+ // Increment the local view count by the specified non-negative integer
+ // amount. Returns the new total view count.
+ int IncrementViewCountBy(unsigned int amount);
+
+ // Record a user's flow through the promo to our histogram in UMA.
+ void RecordUserFlowAction(int action);
+
+ // Load any experiments that run on the promo page.
+ void LoadPromoExperiments();
+
+ // Use this to register for certain notifications (currently when tabs or
+ // windows close).
+ content::NotificationRegistrar registrar_;
+
+ // Weak reference that's initialized and checked in Attach() (after that
+ // guaranteed to be non-NULL).
+ PrefService* prefs_;
+
+ // If the user closes the whole window we'll get a close notification from the
+ // tab as well, so this bool acts as a small mutex to only report the close
+ // method once.
+ bool window_already_closed_;
+
+ DISALLOW_COPY_AND_ASSIGN(SyncPromoHandler2);
+};
+
+#endif // CHROME_BROWSER_UI_WEBUI_SYNC_PROMO_HANDLER_H_
diff --git a/chrome/browser/ui/webui/sync_setup_handler2.cc b/chrome/browser/ui/webui/sync_setup_handler2.cc
new file mode 100644
index 0000000..d47e6a2
--- /dev/null
+++ b/chrome/browser/ui/webui/sync_setup_handler2.cc
@@ -0,0 +1,772 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/webui/sync_setup_handler2.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/json/json_reader.h"
+#include "base/json/json_writer.h"
+#include "base/metrics/histogram.h"
+#include "base/utf_string_conversions.h"
+#include "base/values.h"
+#include "chrome/browser/google/google_util.h"
+#include "chrome/browser/prefs/pref_service.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/profiles/profile_info_cache.h"
+#include "chrome/browser/profiles/profile_manager.h"
+#include "chrome/browser/profiles/profile_metrics.h"
+#include "chrome/browser/sync/profile_sync_service.h"
+#include "chrome/browser/sync/protocol/service_constants.h"
+#include "chrome/browser/sync/signin_manager.h"
+#include "chrome/browser/sync/syncable/model_type.h"
+#include "chrome/browser/sync/sync_setup_flow.h"
+#include "chrome/browser/sync/util/oauth.h"
+#include "chrome/browser/ui/browser_list.h"
+#include "chrome/browser/ui/webui/sync_promo_trial.h"
+#include "chrome/browser/ui/webui/sync_promo_ui.h"
+#include "chrome/browser/ui/webui/user_selectable_sync_type.h"
+#include "chrome/common/net/gaia/gaia_constants.h"
+#include "chrome/common/pref_names.h"
+#include "chrome/common/url_constants.h"
+#include "content/browser/tab_contents/tab_contents.h"
+#include "grit/chromium_strings.h"
+#include "grit/generated_resources.h"
+#include "grit/locale_settings.h"
+#include "ui/base/l10n/l10n_util.h"
+
+using l10n_util::GetStringFUTF16;
+using l10n_util::GetStringUTF16;
+
+namespace {
+
+bool GetAuthData(const std::string& json,
+ std::string* username,
+ std::string* password,
+ std::string* captcha,
+ std::string* access_code) {
+ scoped_ptr<Value> parsed_value(base::JSONReader::Read(json, false));
+ if (!parsed_value.get() || !parsed_value->IsType(Value::TYPE_DICTIONARY))
+ return false;
+
+ DictionaryValue* result = static_cast<DictionaryValue*>(parsed_value.get());
+ if (!result->GetString("user", username) ||
+ !result->GetString("pass", password) ||
+ !result->GetString("captcha", captcha) ||
+ !result->GetString("access_code", access_code)) {
+ return false;
+ }
+ return true;
+}
+
+bool GetConfiguration(const std::string& json, SyncConfiguration* config) {
+ scoped_ptr<Value> parsed_value(base::JSONReader::Read(json, false));
+ if (!parsed_value.get() || !parsed_value->IsType(Value::TYPE_DICTIONARY))
+ return false;
+
+ DictionaryValue* result = static_cast<DictionaryValue*>(parsed_value.get());
+ if (!result->GetBoolean("syncAllDataTypes", &config->sync_everything))
+ return false;
+
+ // These values need to be kept in sync with where they are written in
+ // choose_datatypes.html.
+ bool sync_bookmarks;
+ if (!result->GetBoolean("syncBookmarks", &sync_bookmarks))
+ return false;
+ if (sync_bookmarks)
+ config->data_types.Put(syncable::BOOKMARKS);
+
+ bool sync_preferences;
+ if (!result->GetBoolean("syncPreferences", &sync_preferences))
+ return false;
+ if (sync_preferences)
+ config->data_types.Put(syncable::PREFERENCES);
+
+ bool sync_themes;
+ if (!result->GetBoolean("syncThemes", &sync_themes))
+ return false;
+ if (sync_themes)
+ config->data_types.Put(syncable::THEMES);
+
+ bool sync_passwords;
+ if (!result->GetBoolean("syncPasswords", &sync_passwords))
+ return false;
+ if (sync_passwords)
+ config->data_types.Put(syncable::PASSWORDS);
+
+ bool sync_autofill;
+ if (!result->GetBoolean("syncAutofill", &sync_autofill))
+ return false;
+ if (sync_autofill)
+ config->data_types.Put(syncable::AUTOFILL);
+
+ bool sync_extensions;
+ if (!result->GetBoolean("syncExtensions", &sync_extensions))
+ return false;
+ if (sync_extensions) {
+ config->data_types.Put(syncable::EXTENSIONS);
+ config->data_types.Put(syncable::EXTENSION_SETTINGS);
+ }
+
+ bool sync_typed_urls;
+ if (!result->GetBoolean("syncTypedUrls", &sync_typed_urls))
+ return false;
+ if (sync_typed_urls)
+ config->data_types.Put(syncable::TYPED_URLS);
+
+ bool sync_sessions;
+ if (!result->GetBoolean("syncSessions", &sync_sessions))
+ return false;
+ if (sync_sessions)
+ config->data_types.Put(syncable::SESSIONS);
+
+ bool sync_apps;
+ if (!result->GetBoolean("syncApps", &sync_apps))
+ return false;
+ if (sync_apps) {
+ config->data_types.Put(syncable::APPS);
+ config->data_types.Put(syncable::APP_SETTINGS);
+ }
+
+ // Encryption settings.
+ if (!result->GetBoolean("encryptAllData", &config->encrypt_all))
+ return false;
+
+ // Passphrase settings.
+ bool have_passphrase;
+ if (!result->GetBoolean("usePassphrase", &have_passphrase))
+ return false;
+
+ if (have_passphrase) {
+ bool is_gaia;
+ if (!result->GetBoolean("isGooglePassphrase", &is_gaia))
+ return false;
+ std::string passphrase;
+ if (!result->GetString("passphrase", &passphrase))
+ return false;
+ // The user provided a passphrase - pass it off to SyncSetupFlow as either
+ // the secondary or GAIA passphrase as appropriate.
+ if (is_gaia) {
+ config->set_gaia_passphrase = true;
+ config->gaia_passphrase = passphrase;
+ } else {
+ config->set_secondary_passphrase = true;
+ config->secondary_passphrase = passphrase;
+ }
+ }
+ return true;
+}
+
+bool HasConfigurationChanged(const SyncConfiguration& config,
+ Profile* profile) {
+ CHECK(profile);
+
+ // This function must be updated every time a new sync datatype is added to
+ // the sync preferences page.
+ COMPILE_ASSERT(17 == syncable::MODEL_TYPE_COUNT,
+ UpdateCustomConfigHistogram);
+
+ // If service is null or if this is a first time configuration, return true.
+ ProfileSyncService* service = profile->GetProfileSyncService();
+ if (!service || !service->HasSyncSetupCompleted())
+ return true;
+
+ if ((config.set_secondary_passphrase || config.set_gaia_passphrase) &&
+ !service->IsUsingSecondaryPassphrase())
+ return true;
+
+ if (config.encrypt_all != service->EncryptEverythingEnabled())
+ return true;
+
+ PrefService* pref_service = profile->GetPrefs();
+ CHECK(pref_service);
+
+ if (config.sync_everything !=
+ pref_service->GetBoolean(prefs::kSyncKeepEverythingSynced))
+ return true;
+
+ // Only check the data types that are explicitly listed on the sync
+ // preferences page.
+ const syncable::ModelTypeSet types = config.data_types;
+ if (((types.Has(syncable::BOOKMARKS)) !=
+ pref_service->GetBoolean(prefs::kSyncBookmarks)) ||
+ ((types.Has(syncable::PREFERENCES)) !=
+ pref_service->GetBoolean(prefs::kSyncPreferences)) ||
+ ((types.Has(syncable::THEMES)) !=
+ pref_service->GetBoolean(prefs::kSyncThemes)) ||
+ ((types.Has(syncable::PASSWORDS)) !=
+ pref_service->GetBoolean(prefs::kSyncPasswords)) ||
+ ((types.Has(syncable::AUTOFILL)) !=
+ pref_service->GetBoolean(prefs::kSyncAutofill)) ||
+ ((types.Has(syncable::EXTENSIONS)) !=
+ pref_service->GetBoolean(prefs::kSyncExtensions)) ||
+ ((types.Has(syncable::TYPED_URLS)) !=
+ pref_service->GetBoolean(prefs::kSyncTypedUrls)) ||
+ ((types.Has(syncable::SESSIONS)) !=
+ pref_service->GetBoolean(prefs::kSyncSessions)) ||
+ ((types.Has(syncable::APPS)) !=
+ pref_service->GetBoolean(prefs::kSyncApps)))
+ return true;
+
+ return false;
+}
+
+bool GetPassphrase(const std::string& json, std::string* passphrase) {
+ scoped_ptr<Value> parsed_value(base::JSONReader::Read(json, false));
+ if (!parsed_value.get() || !parsed_value->IsType(Value::TYPE_DICTIONARY))
+ return false;
+
+ DictionaryValue* result = static_cast<DictionaryValue*>(parsed_value.get());
+ return result->GetString("passphrase", passphrase);
+}
+
+string16 NormalizeUserName(const string16& user) {
+ if (user.find_first_of(ASCIIToUTF16("@")) != string16::npos)
+ return user;
+ return user + ASCIIToUTF16("@") + ASCIIToUTF16(DEFAULT_SIGNIN_DOMAIN);
+}
+
+bool AreUserNamesEqual(const string16& user1, const string16& user2) {
+ return NormalizeUserName(user1) == NormalizeUserName(user2);
+}
+
+} // namespace
+
+SyncSetupHandler2::SyncSetupHandler2(ProfileManager* profile_manager)
+ : flow_(NULL),
+ profile_manager_(profile_manager) {
+}
+
+SyncSetupHandler2::~SyncSetupHandler2() {
+ // This case is hit when the user performs a back navigation.
+ if (flow_)
+ flow_->OnDialogClosed("");
+}
+
+void SyncSetupHandler2::GetLocalizedValues(DictionaryValue* localized_strings) {
+ GetStaticLocalizedValues(localized_strings, web_ui_);
+}
+
+void SyncSetupHandler2::GetStaticLocalizedValues(
+ DictionaryValue* localized_strings,
+ WebUI* web_ui) {
+ DCHECK(localized_strings);
+
+ localized_strings->SetString(
+ "invalidPasswordHelpURL",
+ google_util::StringAppendGoogleLocaleParam(
+ chrome::kInvalidPasswordHelpURL));
+ localized_strings->SetString(
+ "cannotAccessAccountURL",
+ google_util::StringAppendGoogleLocaleParam(
+ chrome::kCanNotAccessAccountURL));
+ localized_strings->SetString(
+ "introduction",
+ GetStringFUTF16(IDS_SYNC_LOGIN_INTRODUCTION,
+ GetStringUTF16(IDS_PRODUCT_NAME)));
+ localized_strings->SetString(
+ "chooseDataTypesInstructions",
+ GetStringFUTF16(IDS_SYNC_CHOOSE_DATATYPES_INSTRUCTIONS,
+ GetStringUTF16(IDS_PRODUCT_NAME)));
+ localized_strings->SetString(
+ "encryptionInstructions",
+ GetStringFUTF16(IDS_SYNC_ENCRYPTION_INSTRUCTIONS,
+ GetStringUTF16(IDS_PRODUCT_NAME)));
+ localized_strings->SetString(
+ "encryptionHelpURL",
+ google_util::StringAppendGoogleLocaleParam(
+ chrome::kSyncEncryptionHelpURL));
+ localized_strings->SetString(
+ "passphraseEncryptionMessage",
+ GetStringFUTF16(IDS_SYNC_PASSPHRASE_ENCRYPTION_MESSAGE,
+ GetStringUTF16(IDS_PRODUCT_NAME)));
+ localized_strings->SetString(
+ "passphraseRecover",
+ GetStringFUTF16(IDS_SYNC_PASSPHRASE_RECOVER,
+ ASCIIToUTF16(google_util::StringAppendGoogleLocaleParam(
+ chrome::kSyncGoogleDashboardURL))));
+ localized_strings->SetString(
+ "promoTitle",
+ GetStringFUTF16(IDS_SYNC_PROMO_TITLE,
+ GetStringUTF16(IDS_PRODUCT_NAME)));
+ localized_strings->SetString(
+ "promoMessageTitle",
+ GetStringFUTF16(IDS_SYNC_PROMO_MESSAGE_TITLE,
+ GetStringUTF16(IDS_SHORT_PRODUCT_NAME)));
+ localized_strings->SetString(
+ "syncEverythingHelpURL",
+ google_util::StringAppendGoogleLocaleParam(
+ chrome::kSyncEverythingLearnMoreURL));
+
+ // The experimental body string only appears if we are on the launch page
+ // version of the Sync Promo.
+ int message_body_resource_id = IDS_SYNC_PROMO_MESSAGE_BODY_A;
+ if (web_ui && SyncPromoUI::GetIsLaunchPageForSyncPromoURL(
+ web_ui->tab_contents()->GetURL())) {
+ message_body_resource_id = sync_promo_trial::GetMessageBodyResID();
+ }
+ localized_strings->SetString(
+ "promoMessageBody",
+ GetStringUTF16(message_body_resource_id));
+
+ std::string create_account_url = google_util::StringAppendGoogleLocaleParam(
+ chrome::kSyncCreateNewAccountURL);
+ string16 create_account = GetStringUTF16(IDS_SYNC_CREATE_ACCOUNT);
+ create_account= UTF8ToUTF16("<a id='create-account-link' target='_blank' "
+ "class='account-link' href='" + create_account_url + "'>") +
+ create_account + UTF8ToUTF16("</a>");
+ localized_strings->SetString("createAccountLinkHTML",
+ GetStringFUTF16(IDS_SYNC_CREATE_ACCOUNT_PREFIX, create_account));
+
+ static OptionsStringResource resources[] = {
+ { "syncSetupOverlayTitle", IDS_SYNC_SETUP_TITLE },
+ { "syncSetupConfigureTitle", IDS_SYNC_SETUP_CONFIGURE_TITLE },
+ { "cannotBeBlank", IDS_SYNC_CANNOT_BE_BLANK },
+ { "emailLabel", IDS_SYNC_LOGIN_EMAIL_NEW_LINE },
+ { "passwordLabel", IDS_SYNC_LOGIN_PASSWORD_NEW_LINE },
+ { "invalidCredentials", IDS_SYNC_INVALID_USER_CREDENTIALS },
+ { "signin", IDS_SYNC_SIGNIN },
+ { "couldNotConnect", IDS_SYNC_LOGIN_COULD_NOT_CONNECT },
+ { "unrecoverableError", IDS_SYNC_UNRECOVERABLE_ERROR },
+ { "errorLearnMore", IDS_LEARN_MORE },
+ { "unrecoverableErrorHelpURL", IDS_SYNC_UNRECOVERABLE_ERROR_HELP_URL },
+ { "cannotAccessAccount", IDS_SYNC_CANNOT_ACCESS_ACCOUNT },
+ { "cancel", IDS_CANCEL },
+ { "settingUp", IDS_SYNC_LOGIN_SETTING_UP },
+ { "errorSigningIn", IDS_SYNC_ERROR_SIGNING_IN },
+ { "signinHeader", IDS_SYNC_PROMO_SIGNIN_HEADER},
+ { "captchaInstructions", IDS_SYNC_GAIA_CAPTCHA_INSTRUCTIONS },
+ { "invalidAccessCode", IDS_SYNC_INVALID_ACCESS_CODE_LABEL },
+ { "enterAccessCode", IDS_SYNC_ENTER_ACCESS_CODE_LABEL },
+ { "getAccessCodeHelp", IDS_SYNC_ACCESS_CODE_HELP_LABEL },
+ { "getAccessCodeURL", IDS_SYNC_GET_ACCESS_CODE_URL },
+ { "syncAllDataTypes", IDS_SYNC_EVERYTHING },
+ { "chooseDataTypes", IDS_SYNC_CHOOSE_DATATYPES },
+ { "bookmarks", IDS_SYNC_DATATYPE_BOOKMARKS },
+ { "preferences", IDS_SYNC_DATATYPE_PREFERENCES },
+ { "autofill", IDS_SYNC_DATATYPE_AUTOFILL },
+ { "themes", IDS_SYNC_DATATYPE_THEMES },
+ { "passwords", IDS_SYNC_DATATYPE_PASSWORDS },
+ { "extensions", IDS_SYNC_DATATYPE_EXTENSIONS },
+ { "typedURLs", IDS_SYNC_DATATYPE_TYPED_URLS },
+ { "apps", IDS_SYNC_DATATYPE_APPS },
+ { "openTabs", IDS_SYNC_DATATYPE_TABS },
+ { "syncZeroDataTypesError", IDS_SYNC_ZERO_DATA_TYPES_ERROR },
+ { "serviceUnavailableError", IDS_SYNC_SETUP_ABORTED_BY_PENDING_CLEAR },
+ { "encryptAllLabel", IDS_SYNC_ENCRYPT_ALL_LABEL },
+ { "googleOption", IDS_SYNC_PASSPHRASE_OPT_GOOGLE },
+ { "explicitOption", IDS_SYNC_PASSPHRASE_OPT_EXPLICIT },
+ { "sectionGoogleMessage", IDS_SYNC_PASSPHRASE_MSG_GOOGLE },
+ { "sectionExplicitMessage", IDS_SYNC_PASSPHRASE_MSG_EXPLICIT },
+ { "passphraseLabel", IDS_SYNC_PASSPHRASE_LABEL },
+ { "confirmLabel", IDS_SYNC_CONFIRM_PASSPHRASE_LABEL },
+ { "emptyErrorMessage", IDS_SYNC_EMPTY_PASSPHRASE_ERROR },
+ { "mismatchErrorMessage", IDS_SYNC_PASSPHRASE_MISMATCH_ERROR },
+ { "passphraseWarning", IDS_SYNC_PASSPHRASE_WARNING },
+ { "customizeLinkLabel", IDS_SYNC_CUSTOMIZE_LINK_LABEL },
+ { "confirmSyncPreferences", IDS_SYNC_CONFIRM_SYNC_PREFERENCES },
+ { "syncEverything", IDS_SYNC_SYNC_EVERYTHING },
+ { "useDefaultSettings", IDS_SYNC_USE_DEFAULT_SETTINGS },
+ { "passphraseSectionTitle", IDS_SYNC_PASSPHRASE_SECTION_TITLE },
+ { "privacyDashboardLink", IDS_SYNC_PRIVACY_DASHBOARD_LINK_LABEL },
+ { "enterPassphraseTitle", IDS_SYNC_ENTER_PASSPHRASE_TITLE },
+ { "enterPassphraseBody", IDS_SYNC_ENTER_PASSPHRASE_BODY },
+ { "enterOtherPassphraseBody", IDS_SYNC_ENTER_OTHER_PASSPHRASE_BODY },
+ { "enterGooglePassphraseBody", IDS_SYNC_ENTER_GOOGLE_PASSPHRASE_BODY },
+ { "passphraseLabel", IDS_SYNC_PASSPHRASE_LABEL },
+ { "incorrectPassphrase", IDS_SYNC_INCORRECT_PASSPHRASE },
+ { "passphraseWarning", IDS_SYNC_PASSPHRASE_WARNING },
+ { "cancelWarningHeader", IDS_SYNC_PASSPHRASE_CANCEL_WARNING_HEADER },
+ { "cancelWarning", IDS_SYNC_PASSPHRASE_CANCEL_WARNING },
+ { "yes", IDS_SYNC_PASSPHRASE_CANCEL_YES },
+ { "no", IDS_SYNC_PASSPHRASE_CANCEL_NO },
+ { "sectionExplicitMessagePrefix", IDS_SYNC_PASSPHRASE_MSG_EXPLICIT_PREFIX },
+ { "sectionExplicitMessagePostfix",
+ IDS_SYNC_PASSPHRASE_MSG_EXPLICIT_POSTFIX },
+ { "encryptedDataTypesTitle", IDS_SYNC_ENCRYPTION_DATA_TYPES_TITLE },
+ { "encryptSensitiveOption", IDS_SYNC_ENCRYPT_SENSITIVE_DATA },
+ { "encryptAllOption", IDS_SYNC_ENCRYPT_ALL_DATA },
+ { "encryptAllOption", IDS_SYNC_ENCRYPT_ALL_DATA },
+ { "aspWarningText", IDS_SYNC_ASP_PASSWORD_WARNING_TEXT },
+ { "promoPageTitle", IDS_SYNC_PROMO_TAB_TITLE},
+ { "promoSkipButton", IDS_SYNC_PROMO_SKIP_BUTTON},
+ { "promoAdvanced", IDS_SYNC_PROMO_ADVANCED},
+ { "promoLearnMoreShow", IDS_SYNC_PROMO_LEARN_MORE_SHOW},
+ { "promoLearnMoreHide", IDS_SYNC_PROMO_LEARN_MORE_HIDE},
+ { "promoInformation", IDS_SYNC_PROMO_INFORMATION},
+ };
+
+ RegisterStrings(localized_strings, resources, arraysize(resources));
+}
+
+void SyncSetupHandler2::Initialize() {
+}
+
+void SyncSetupHandler2::OnGetOAuthTokenSuccess(const std::string& oauth_token) {
+ flow_->OnUserSubmittedOAuth(oauth_token);
+}
+
+void SyncSetupHandler2::OnGetOAuthTokenFailure(
+ const GoogleServiceAuthError& error) {
+ CloseSyncSetup();
+}
+
+void SyncSetupHandler2::RegisterMessages() {
+ web_ui_->RegisterMessageCallback("SyncSetupDidClosePage",
+ base::Bind(&SyncSetupHandler2::OnDidClosePage,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("SyncSetupSubmitAuth",
+ base::Bind(&SyncSetupHandler2::HandleSubmitAuth,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("SyncSetupConfigure",
+ base::Bind(&SyncSetupHandler2::HandleConfigure,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("SyncSetupPassphrase",
+ base::Bind(&SyncSetupHandler2::HandlePassphraseEntry,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("SyncSetupPassphraseCancel",
+ base::Bind(&SyncSetupHandler2::HandlePassphraseCancel,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("SyncSetupAttachHandler",
+ base::Bind(&SyncSetupHandler2::HandleAttachHandler,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("SyncSetupShowErrorUI",
+ base::Bind(&SyncSetupHandler2::HandleShowErrorUI,
+ base::Unretained(this)));
+ web_ui_->RegisterMessageCallback("SyncSetupShowSetupUI",
+ base::Bind(&SyncSetupHandler2::HandleShowSetupUI,
+ base::Unretained(this)));
+}
+
+// Ideal(?) solution here would be to mimic the ClientLogin overlay. Since
+// this UI must render an external URL, that overlay cannot be used directly.
+// The current implementation is functional, but fails asthetically.
+// TODO(rickcam): Bug 90711: Update UI for OAuth sign-in flow
+void SyncSetupHandler2::ShowOAuthLogin() {
+ DCHECK(browser_sync::IsUsingOAuth());
+
+ Profile* profile = Profile::FromWebUI(web_ui_);
+ oauth_login_.reset(new GaiaOAuthFetcher(this,
+ profile->GetRequestContext(),
+ profile,
+ GaiaConstants::kSyncServiceOAuth));
+ oauth_login_->SetAutoFetchLimit(GaiaOAuthFetcher::OAUTH1_REQUEST_TOKEN);
+ oauth_login_->StartGetOAuthToken();
+}
+
+void SyncSetupHandler2::ShowGaiaLogin(const DictionaryValue& args) {
+ DCHECK(!browser_sync::IsUsingOAuth());
+ StringValue page("login");
+ web_ui_->CallJavascriptFunction(
+ "SyncSetupOverlay.showSyncSetupPage", page, args);
+}
+
+void SyncSetupHandler2::ShowGaiaSuccessAndClose() {
+ web_ui_->CallJavascriptFunction("SyncSetupOverlay.showSuccessAndClose");
+}
+
+void SyncSetupHandler2::ShowGaiaSuccessAndSettingUp() {
+ web_ui_->CallJavascriptFunction("SyncSetupOverlay.showSuccessAndSettingUp");
+}
+
+void SyncSetupHandler2::ShowConfigure(const DictionaryValue& args) {
+ StringValue page("configure");
+ web_ui_->CallJavascriptFunction(
+ "SyncSetupOverlay.showSyncSetupPage", page, args);
+}
+
+void SyncSetupHandler2::ShowPassphraseEntry(const DictionaryValue& args) {
+ StringValue page("passphrase");
+ web_ui_->CallJavascriptFunction(
+ "SyncSetupOverlay.showSyncSetupPage", page, args);
+}
+
+void SyncSetupHandler2::ShowSettingUp() {
+ StringValue page("settingUp");
+ web_ui_->CallJavascriptFunction(
+ "SyncSetupOverlay.showSyncSetupPage", page);
+}
+
+void SyncSetupHandler2::ShowSetupDone(const string16& user) {
+ StringValue page("done");
+ web_ui_->CallJavascriptFunction(
+ "SyncSetupOverlay.showSyncSetupPage", page);
+
+ // Suppress the sync promo once the user signs into sync. This way the user
+ // doesn't see the sync promo even if they sign out of sync later on.
+ SyncPromoUI::SetUserSkippedSyncPromo(Profile::FromWebUI(web_ui_));
+
+ Profile* profile = Profile::FromWebUI(web_ui_);
+ ProfileSyncService* service = profile->GetProfileSyncService();
+ if (!service->HasSyncSetupCompleted()) {
+ FilePath profile_file_path = profile->GetPath();
+ ProfileMetrics::LogProfileSyncSignIn(profile_file_path);
+ }
+}
+
+void SyncSetupHandler2::SetFlow(SyncSetupFlow* flow) {
+ flow_ = flow;
+}
+
+void SyncSetupHandler2::Focus() {
+ static_cast<RenderViewHostDelegate*>(web_ui_->tab_contents())->Activate();
+}
+
+void SyncSetupHandler2::OnDidClosePage(const ListValue* args) {
+ CloseSyncSetup();
+}
+
+void SyncSetupHandler2::HandleSubmitAuth(const ListValue* args) {
+ std::string json;
+ if (!args->GetString(0, &json)) {
+ NOTREACHED() << "Could not read JSON argument";
+ return;
+ }
+
+ if (json.empty())
+ return;
+
+ std::string username, password, captcha, access_code;
+ if (!GetAuthData(json, &username, &password, &captcha, &access_code)) {
+ // The page sent us something that we didn't understand.
+ // This probably indicates a programming error.
+ NOTREACHED();
+ return;
+ }
+
+ string16 error_message;
+ if (!IsLoginAuthDataValid(username, &error_message)) {
+ ShowLoginErrorMessage(error_message);
+ return;
+ }
+
+ if (flow_)
+ flow_->OnUserSubmittedAuth(username, password, captcha, access_code);
+}
+
+void SyncSetupHandler2::HandleConfigure(const ListValue* args) {
+ std::string json;
+ if (!args->GetString(0, &json)) {
+ NOTREACHED() << "Could not read JSON argument";
+ return;
+ }
+ if (json.empty()) {
+ NOTREACHED();
+ return;
+ }
+
+ SyncConfiguration configuration;
+ if (!GetConfiguration(json, &configuration)) {
+ // The page sent us something that we didn't understand.
+ // This probably indicates a programming error.
+ NOTREACHED();
+ return;
+ }
+
+ // We do not do UMA logging during unit tests.
+ if (web_ui_) {
+ Profile* profile = Profile::FromWebUI(web_ui_);
+ if (HasConfigurationChanged(configuration, profile)) {
+ UMA_HISTOGRAM_BOOLEAN("Sync.SyncEverything",
+ configuration.sync_everything);
+ if (!configuration.sync_everything) {
+ // Only log the data types that are explicitly listed on the sync
+ // preferences page.
+ const syncable::ModelTypeSet types = configuration.data_types;
+ if (types.Has(syncable::BOOKMARKS))
+ UMA_HISTOGRAM_ENUMERATION(
+ "Sync.CustomSync", BOOKMARKS, SELECTABLE_DATATYPE_COUNT + 1);
+ if (types.Has(syncable::PREFERENCES))
+ UMA_HISTOGRAM_ENUMERATION(
+ "Sync.CustomSync", PREFERENCES, SELECTABLE_DATATYPE_COUNT + 1);
+ if (types.Has(syncable::PASSWORDS))
+ UMA_HISTOGRAM_ENUMERATION(
+ "Sync.CustomSync", PASSWORDS, SELECTABLE_DATATYPE_COUNT + 1);
+ if (types.Has(syncable::AUTOFILL))
+ UMA_HISTOGRAM_ENUMERATION(
+ "Sync.CustomSync", AUTOFILL, SELECTABLE_DATATYPE_COUNT + 1);
+ if (types.Has(syncable::THEMES))
+ UMA_HISTOGRAM_ENUMERATION(
+ "Sync.CustomSync", THEMES, SELECTABLE_DATATYPE_COUNT + 1);
+ if (types.Has(syncable::TYPED_URLS))
+ UMA_HISTOGRAM_ENUMERATION(
+ "Sync.CustomSync", TYPED_URLS, SELECTABLE_DATATYPE_COUNT + 1);
+ if (types.Has(syncable::EXTENSIONS))
+ UMA_HISTOGRAM_ENUMERATION(
+ "Sync.CustomSync", EXTENSIONS, SELECTABLE_DATATYPE_COUNT + 1);
+ if (types.Has(syncable::SESSIONS))
+ UMA_HISTOGRAM_ENUMERATION(
+ "Sync.CustomSync", SESSIONS, SELECTABLE_DATATYPE_COUNT + 1);
+ if (types.Has(syncable::APPS))
+ UMA_HISTOGRAM_ENUMERATION(
+ "Sync.CustomSync", APPS, SELECTABLE_DATATYPE_COUNT + 1);
+ COMPILE_ASSERT(17 == syncable::MODEL_TYPE_COUNT,
+ UpdateCustomConfigHistogram);
+ COMPILE_ASSERT(9 == SELECTABLE_DATATYPE_COUNT,
+ UpdateCustomConfigHistogram);
+ }
+ UMA_HISTOGRAM_BOOLEAN("Sync.EncryptAllData", configuration.encrypt_all);
+ UMA_HISTOGRAM_BOOLEAN("Sync.CustomPassphrase",
+ configuration.set_gaia_passphrase ||
+ configuration.set_secondary_passphrase);
+ }
+ }
+
+ DCHECK(flow_);
+ flow_->OnUserConfigured(configuration);
+
+ ProfileMetrics::LogProfileSyncInfo(ProfileMetrics::SYNC_CUSTOMIZE);
+ if (configuration.encrypt_all) {
+ ProfileMetrics::LogProfileSyncInfo(ProfileMetrics::SYNC_ENCRYPT);
+ }
+ if (configuration.set_secondary_passphrase) {
+ ProfileMetrics::LogProfileSyncInfo(ProfileMetrics::SYNC_PASSPHRASE);
+ }
+ if (!configuration.sync_everything) {
+ ProfileMetrics::LogProfileSyncInfo(ProfileMetrics::SYNC_CHOOSE);
+ }
+}
+
+void SyncSetupHandler2::HandlePassphraseEntry(const ListValue* args) {
+ std::string json;
+ if (!args->GetString(0, &json)) {
+ NOTREACHED() << "Could not read JSON argument";
+ return;
+ }
+
+ if (json.empty())
+ return;
+
+ std::string passphrase;
+ if (!GetPassphrase(json, &passphrase)) {
+ // Couldn't understand what the page sent. Indicates a programming error.
+ NOTREACHED();
+ return;
+ }
+
+ DCHECK(flow_);
+ flow_->OnPassphraseEntry(passphrase);
+}
+
+void SyncSetupHandler2::HandlePassphraseCancel(const ListValue* args) {
+ DCHECK(flow_);
+ flow_->OnPassphraseCancel();
+}
+
+void SyncSetupHandler2::HandleAttachHandler(const ListValue* args) {
+ OpenSyncSetup();
+}
+
+void SyncSetupHandler2::HandleShowErrorUI(const ListValue* args) {
+ DCHECK(!flow_);
+
+ Profile* profile = Profile::FromWebUI(web_ui_);
+ ProfileSyncService* service = profile->GetProfileSyncService();
+ DCHECK(service);
+
+ service->ShowErrorUI();
+}
+
+void SyncSetupHandler2::HandleShowSetupUI(const ListValue* args) {
+ DCHECK(!flow_);
+ if (FocusExistingWizard()) {
+ CloseOverlay();
+ return;
+ }
+ ShowSetupUI();
+}
+
+void SyncSetupHandler2::CloseSyncSetup() {
+ if (flow_) {
+ flow_->OnDialogClosed(std::string());
+ flow_ = NULL;
+ }
+}
+
+void SyncSetupHandler2::OpenSyncSetup() {
+ DCHECK(web_ui_);
+ DCHECK(!flow_);
+
+ Profile* profile = Profile::FromWebUI(web_ui_);
+ ProfileSyncService* service = profile->GetProfileSyncService();
+ if (!service) {
+ // If there's no sync service, the user tried to manually invoke a syncSetup
+ // URL, but sync features are disabled. We need to close the overlay for
+ // this (rare) case.
+ CloseOverlay();
+ return;
+ }
+
+ // If the wizard is already visible, it must be attached to another flow
+ // handler.
+ if (FocusExistingWizard()) {
+ CloseOverlay();
+ return;
+ }
+
+ // Attach this as the sync setup handler, before calling ShowSetupUI().
+ if (!service->get_wizard().AttachSyncSetupHandler(this)) {
+ LOG(ERROR) << "SyncSetupHandler attach failed!";
+ CloseOverlay();
+ return;
+ }
+
+ ShowSetupUI();
+}
+
+// Private member functions.
+
+bool SyncSetupHandler2::FocusExistingWizard() {
+ Profile* profile = Profile::FromWebUI(web_ui_);
+ ProfileSyncService* service = profile->GetProfileSyncService();
+ if (!service)
+ return false;
+
+ // If the wizard is already visible, focus it.
+ if (service->get_wizard().IsVisible()) {
+ service->get_wizard().Focus();
+ return true;
+ }
+ return false;
+}
+
+void SyncSetupHandler2::CloseOverlay() {
+ web_ui_->CallJavascriptFunction("OptionsPage.closeOverlay");
+}
+
+bool SyncSetupHandler2::IsLoginAuthDataValid(const std::string& username,
+ string16* error_message) {
+ // Happens during unit tests.
+ if (!web_ui_ || !profile_manager_)
+ return true;
+
+ if (username.empty())
+ return true;
+
+ // Check if the username is already in use by another profile.
+ const ProfileInfoCache& cache = profile_manager_->GetProfileInfoCache();
+ size_t current_profile_index = cache.GetIndexOfProfileWithPath(
+ Profile::FromWebUI(web_ui_)->GetPath());
+ string16 username_utf16 = UTF8ToUTF16(username);
+
+ for (size_t i = 0; i < cache.GetNumberOfProfiles(); ++i) {
+ if (i != current_profile_index && AreUserNamesEqual(
+ cache.GetUserNameOfProfileAtIndex(i), username_utf16)) {
+ *error_message = l10n_util::GetStringUTF16(
+ IDS_SYNC_USER_NAME_IN_USE_ERROR);
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void SyncSetupHandler2::ShowLoginErrorMessage(const string16& error_message) {
+ DictionaryValue args;
+ Profile* profile = Profile::FromWebUI(web_ui_);
+ ProfileSyncService* service = profile->GetProfileSyncService();
+ SyncSetupFlow::GetArgsForGaiaLogin(service, &args);
+ args.SetString("error_message", error_message);
+ ShowGaiaLogin(args);
+}
diff --git a/chrome/browser/ui/webui/sync_setup_handler2.h b/chrome/browser/ui/webui/sync_setup_handler2.h
new file mode 100644
index 0000000..a5bc2e5
--- /dev/null
+++ b/chrome/browser/ui/webui/sync_setup_handler2.h
@@ -0,0 +1,117 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_WEBUI_SYNC_SETUP_HANDLER2_H_
+#define CHROME_BROWSER_UI_WEBUI_SYNC_SETUP_HANDLER2_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "chrome/browser/net/gaia/gaia_oauth_consumer.h"
+#include "chrome/browser/net/gaia/gaia_oauth_fetcher.h"
+#include "chrome/browser/sync/sync_setup_flow_handler.h"
+#include "chrome/browser/ui/webui/options2/options_ui2.h"
+
+class SyncSetupFlow;
+class ProfileManager;
+
+class SyncSetupHandler2 : public GaiaOAuthConsumer,
+ public OptionsPage2UIHandler,
+ public SyncSetupFlowHandler {
+ public:
+ // Constructs a new SyncSetupHandler. |profile_manager| may be NULL.
+ explicit SyncSetupHandler2(ProfileManager* profile_manager);
+ virtual ~SyncSetupHandler2();
+
+ // OptionsPageUIHandler implementation.
+ virtual void GetLocalizedValues(base::DictionaryValue* localized_strings)
+ OVERRIDE;
+ virtual void Initialize() OVERRIDE;
+ virtual void RegisterMessages() OVERRIDE;
+
+ // SyncSetupFlowHandler implementation.
+ virtual void ShowOAuthLogin() OVERRIDE;
+ virtual void ShowGaiaLogin(const base::DictionaryValue& args) OVERRIDE;
+ virtual void ShowGaiaSuccessAndClose() OVERRIDE;
+ virtual void ShowGaiaSuccessAndSettingUp() OVERRIDE;
+ virtual void ShowConfigure(const base::DictionaryValue& args) OVERRIDE;
+ virtual void ShowPassphraseEntry(const base::DictionaryValue& args) OVERRIDE;
+ virtual void ShowSettingUp() OVERRIDE;
+ virtual void ShowSetupDone(const string16& user) OVERRIDE;
+ virtual void SetFlow(SyncSetupFlow* flow) OVERRIDE;
+ virtual void Focus() OVERRIDE;
+
+ // GaiaOAuthConsumer implementation.
+ virtual void OnGetOAuthTokenSuccess(const std::string& oauth_token) OVERRIDE;
+ virtual void OnGetOAuthTokenFailure(
+ const GoogleServiceAuthError& error) OVERRIDE;
+
+ static void GetStaticLocalizedValues(
+ base::DictionaryValue* localized_strings,
+ WebUI* web_ui);
+
+ // Initializes the sync setup flow and shows the setup UI.
+ void OpenSyncSetup();
+
+ // Terminates the sync setup flow.
+ void CloseSyncSetup();
+
+ protected:
+ FRIEND_TEST_ALL_PREFIXES(SyncSetupWizardTest, InitialStepLogin);
+ FRIEND_TEST_ALL_PREFIXES(SyncSetupWizardTest, ChooseDataTypesSetsPrefs);
+ FRIEND_TEST_ALL_PREFIXES(SyncSetupWizardTest, DialogCancelled);
+ FRIEND_TEST_ALL_PREFIXES(SyncSetupWizardTest, InvalidTransitions);
+ FRIEND_TEST_ALL_PREFIXES(SyncSetupWizardTest, FullSuccessfulRunSetsPref);
+ FRIEND_TEST_ALL_PREFIXES(SyncSetupWizardTest, AbortedByPendingClear);
+ FRIEND_TEST_ALL_PREFIXES(SyncSetupWizardTest, DiscreteRunGaiaLogin);
+ FRIEND_TEST_ALL_PREFIXES(SyncSetupWizardTest, DiscreteRunChooseDataTypes);
+ FRIEND_TEST_ALL_PREFIXES(SyncSetupWizardTest,
+ DiscreteRunChooseDataTypesAbortedByPendingClear);
+ FRIEND_TEST_ALL_PREFIXES(SyncSetupWizardTest, EnterPassphraseRequired);
+
+ // Callbacks from the page. Protected in order to be called by the
+ // SyncSetupWizardTest.
+ void OnDidClosePage(const base::ListValue* args);
+ void HandleSubmitAuth(const base::ListValue* args);
+ void HandleConfigure(const base::ListValue* args);
+ void HandlePassphraseEntry(const base::ListValue* args);
+ void HandlePassphraseCancel(const base::ListValue* args);
+ void HandleAttachHandler(const base::ListValue* args);
+ void HandleShowErrorUI(const base::ListValue* args);
+ void HandleShowSetupUI(const base::ListValue* args);
+
+ SyncSetupFlow* flow() { return flow_; }
+
+ // Subclasses must implement to step the SyncSetupWizard to the correct state
+ // before showing the Setup UI.
+ virtual void StepWizardForShowSetupUI() = 0;
+
+ // Subclasses must implement this to show the setup UI that's appropriate
+ // for the page this is contained in.
+ virtual void ShowSetupUI() = 0;
+
+ private:
+ // If a wizard already exists, focus it and return true.
+ bool FocusExistingWizard();
+
+ // Invokes the javascript call to close the setup overlay.
+ void CloseOverlay();
+
+ // Returns true if the given login data is valid, false otherwise. If the
+ // login data is not valid then on return |error_message| will be set to a
+ // localized error message. Note, |error_message| must not be NULL.
+ bool IsLoginAuthDataValid(const std::string& username,
+ string16* error_message);
+
+ // Displays the given error message in the login UI.
+ void ShowLoginErrorMessage(const string16& error_message);
+
+ // Weak reference.
+ SyncSetupFlow* flow_;
+ scoped_ptr<GaiaOAuthFetcher> oauth_login_;
+ // Weak reference to the profile manager.
+ ProfileManager* const profile_manager_;
+
+ DISALLOW_COPY_AND_ASSIGN(SyncSetupHandler2);
+};
+
+#endif // CHROME_BROWSER_UI_WEBUI_SYNC_SETUP_HANDLER2_H_
diff --git a/chrome/browser/ui/webui/uber/uber_ui.cc b/chrome/browser/ui/webui/uber/uber_ui.cc
index 47a6ea2..ded7554 100644
--- a/chrome/browser/ui/webui/uber/uber_ui.cc
+++ b/chrome/browser/ui/webui/uber/uber_ui.cc
@@ -10,7 +10,7 @@
#include "chrome/browser/ui/webui/chrome_web_ui_data_source.h"
#include "chrome/browser/ui/webui/chrome_web_ui_factory.h"
#include "chrome/browser/ui/webui/extensions/extensions_ui.h"
-#include "chrome/browser/ui/webui/options/options_ui.h"
+#include "chrome/browser/ui/webui/options2/options_ui2.h"
#include "chrome/common/url_constants.h"
#include "content/browser/tab_contents/tab_contents.h"
#include "grit/browser_resources.h"
@@ -33,7 +33,7 @@ UberUI::UberUI(TabContents* contents) : ChromeWebUI(contents) {
Profile* profile = Profile::FromBrowserContext(contents->browser_context());
profile->GetChromeURLDataManager()->AddDataSource(CreateUberHTMLSource());
- RegisterSubpage(chrome::kChromeUISettingsURL);
+ RegisterSubpage(chrome::kChromeUISettingsFrameURL);
RegisterSubpage(chrome::kChromeUIExtensionsFrameURL);
}
diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi
index c9c54a6..2ab9bd9 100644
--- a/chrome/chrome_browser.gypi
+++ b/chrome/chrome_browser.gypi
@@ -3920,6 +3920,98 @@
'browser/ui/webui/options/stop_syncing_handler.h',
'browser/ui/webui/options/web_intents_settings_handler.cc',
'browser/ui/webui/options/web_intents_settings_handler.h',
+ 'browser/ui/webui/options2/advanced_options_handler.cc',
+ 'browser/ui/webui/options2/advanced_options_handler.h',
+ 'browser/ui/webui/options2/advanced_options_utils.h',
+ 'browser/ui/webui/options2/advanced_options_utils_mac.mm',
+ 'browser/ui/webui/options2/advanced_options_utils_win.cc',
+ 'browser/ui/webui/options2/advanced_options_utils_x11.cc',
+ 'browser/ui/webui/options2/autofill_options_handler.cc',
+ 'browser/ui/webui/options2/autofill_options_handler.h',
+ 'browser/ui/webui/options2/browser_options_handler.cc',
+ 'browser/ui/webui/options2/browser_options_handler.h',
+ 'browser/ui/webui/options2/certificate_manager_handler.cc',
+ 'browser/ui/webui/options2/certificate_manager_handler.h',
+ 'browser/ui/webui/options2/chromeos/about_page_handler.cc',
+ 'browser/ui/webui/options2/chromeos/about_page_handler.h',
+ 'browser/ui/webui/options2/chromeos/accounts_options_handler.cc',
+ 'browser/ui/webui/options2/chromeos/accounts_options_handler.h',
+ 'browser/ui/webui/options2/chromeos/bluetooth_options_handler.cc',
+ 'browser/ui/webui/options2/chromeos/bluetooth_options_handler.h',
+ 'browser/ui/webui/options2/chromeos/change_picture_options_handler.cc',
+ 'browser/ui/webui/options2/chromeos/change_picture_options_handler.h',
+ 'browser/ui/webui/options2/chromeos/core_chromeos_options_handler.cc',
+ 'browser/ui/webui/options2/chromeos/core_chromeos_options_handler.h',
+ 'browser/ui/webui/options2/chromeos/cros_language_options_handler.cc',
+ 'browser/ui/webui/options2/chromeos/cros_language_options_handler.h',
+ 'browser/ui/webui/options2/chromeos/internet_options_handler.cc',
+ 'browser/ui/webui/options2/chromeos/internet_options_handler.h',
+ 'browser/ui/webui/options2/chromeos/language_chewing_handler.cc',
+ 'browser/ui/webui/options2/chromeos/language_chewing_handler.h',
+ 'browser/ui/webui/options2/chromeos/language_customize_modifier_keys_handler.cc',
+ 'browser/ui/webui/options2/chromeos/language_customize_modifier_keys_handler.h',
+ 'browser/ui/webui/options2/chromeos/language_hangul_handler.cc',
+ 'browser/ui/webui/options2/chromeos/language_hangul_handler.h',
+ 'browser/ui/webui/options2/chromeos/language_mozc_handler.cc',
+ 'browser/ui/webui/options2/chromeos/language_mozc_handler.h',
+ 'browser/ui/webui/options2/chromeos/language_options_util.cc',
+ 'browser/ui/webui/options2/chromeos/language_options_util.h',
+ 'browser/ui/webui/options2/chromeos/language_pinyin_handler.cc',
+ 'browser/ui/webui/options2/chromeos/language_pinyin_handler.h',
+ 'browser/ui/webui/options2/chromeos/proxy_handler.cc',
+ 'browser/ui/webui/options2/chromeos/proxy_handler.h',
+ 'browser/ui/webui/options2/chromeos/stats_options_handler.cc',
+ 'browser/ui/webui/options2/chromeos/stats_options_handler.h',
+ 'browser/ui/webui/options2/chromeos/system_options_handler.cc',
+ 'browser/ui/webui/options2/chromeos/system_options_handler.h',
+ 'browser/ui/webui/options2/chromeos/system_settings_provider.cc',
+ 'browser/ui/webui/options2/chromeos/system_settings_provider.h',
+ 'browser/ui/webui/options2/chromeos/user_image_source.cc',
+ 'browser/ui/webui/options2/chromeos/user_image_source.h',
+ 'browser/ui/webui/options2/chromeos/virtual_keyboard_manager_handler.cc',
+ 'browser/ui/webui/options2/chromeos/virtual_keyboard_manager_handler.h',
+ 'browser/ui/webui/options2/clear_browser_data_handler.cc',
+ 'browser/ui/webui/options2/clear_browser_data_handler.h',
+ 'browser/ui/webui/options2/content_settings_handler.cc',
+ 'browser/ui/webui/options2/content_settings_handler.h',
+ 'browser/ui/webui/options2/cookies_view_handler.cc',
+ 'browser/ui/webui/options2/cookies_view_handler.h',
+ 'browser/ui/webui/options2/core_options_handler.cc',
+ 'browser/ui/webui/options2/core_options_handler.h',
+ 'browser/ui/webui/options2/extension_settings_handler.h',
+ 'browser/ui/webui/options2/extension_settings_handler.cc',
+ 'browser/ui/webui/options2/font_settings_handler.cc',
+ 'browser/ui/webui/options2/font_settings_handler.h',
+ 'browser/ui/webui/options2/font_settings_utils.h',
+ 'browser/ui/webui/options2/font_settings_utils_mac.mm',
+ 'browser/ui/webui/options2/font_settings_utils_win.cc',
+ 'browser/ui/webui/options2/font_settings_utils_x11.cc',
+ 'browser/ui/webui/options2/handler_options_handler.cc',
+ 'browser/ui/webui/options2/handler_options_handler.h',
+ 'browser/ui/webui/options2/import_data_handler.cc',
+ 'browser/ui/webui/options2/import_data_handler.h',
+ 'browser/ui/webui/options2/language_options_handler.cc',
+ 'browser/ui/webui/options2/language_options_handler.h',
+ 'browser/ui/webui/options2/language_options_handler_common.cc',
+ 'browser/ui/webui/options2/language_options_handler_common.h',
+ 'browser/ui/webui/options2/manage_profile_handler.cc',
+ 'browser/ui/webui/options2/manage_profile_handler.h',
+ 'browser/ui/webui/options2/options_sync_setup_handler.cc',
+ 'browser/ui/webui/options2/options_sync_setup_handler.h',
+ 'browser/ui/webui/options2/options_ui2.cc',
+ 'browser/ui/webui/options2/options_ui2.h',
+ 'browser/ui/webui/options2/password_manager_handler.cc',
+ 'browser/ui/webui/options2/password_manager_handler.h',
+ 'browser/ui/webui/options2/pack_extension_handler.h',
+ 'browser/ui/webui/options2/pack_extension_handler.cc',
+ 'browser/ui/webui/options2/personal_options_handler.cc',
+ 'browser/ui/webui/options2/personal_options_handler.h',
+ 'browser/ui/webui/options2/search_engine_manager_handler.cc',
+ 'browser/ui/webui/options2/search_engine_manager_handler.h',
+ 'browser/ui/webui/options2/stop_syncing_handler.cc',
+ 'browser/ui/webui/options2/stop_syncing_handler.h',
+ 'browser/ui/webui/options2/web_intents_settings_handler.cc',
+ 'browser/ui/webui/options2/web_intents_settings_handler.h',
'browser/ui/webui/plugins_ui.cc',
'browser/ui/webui/plugins_ui.h',
'browser/ui/webui/policy_ui.cc',
@@ -3954,12 +4046,16 @@
'browser/ui/webui/sync_internals_ui.h',
'browser/ui/webui/sync_promo_handler.cc',
'browser/ui/webui/sync_promo_handler.h',
+ 'browser/ui/webui/sync_promo_handler2.cc',
+ 'browser/ui/webui/sync_promo_handler2.h',
'browser/ui/webui/sync_promo_trial.cc',
'browser/ui/webui/sync_promo_trial.h',
'browser/ui/webui/sync_promo_ui.cc',
'browser/ui/webui/sync_promo_ui.h',
'browser/ui/webui/sync_setup_handler.cc',
'browser/ui/webui/sync_setup_handler.h',
+ 'browser/ui/webui/sync_setup_handler2.cc',
+ 'browser/ui/webui/sync_setup_handler2.h',
'browser/ui/webui/task_manager_dialog.cc',
'browser/ui/webui/task_manager_dialog.h',
'browser/ui/webui/task_manager_handler.cc',
@@ -4101,6 +4197,7 @@
['exclude', '^browser/chromeos'],
['exclude', '^browser/ui/webui/chromeos'],
['exclude', '^browser/ui/webui/options/chromeos'],
+ ['exclude', '^browser/ui/webui/options2/chromeos'],
['exclude', 'browser/extensions/api/terminal/terminal_private_api.cc'],
['exclude', 'browser/extensions/api/terminal/terminal_private_api.h'],
['exclude', 'browser/extensions/extension_input_ime_api.cc'],
@@ -4367,6 +4464,8 @@
'browser/certificate_manager_model.h',
'browser/ui/webui/options/certificate_manager_handler.cc',
'browser/ui/webui/options/certificate_manager_handler.h',
+ 'browser/ui/webui/options2/certificate_manager_handler.cc',
+ 'browser/ui/webui/options2/certificate_manager_handler.h',
'browser/ui/webui/ssl_client_certificate_selector_webui.cc',
'browser/ui/webui/ssl_client_certificate_selector_webui.h',
],
diff --git a/chrome/chrome_repack_resources.gypi b/chrome/chrome_repack_resources.gypi
index d8d2a03..0a0c1b2 100644
--- a/chrome/chrome_repack_resources.gypi
+++ b/chrome/chrome_repack_resources.gypi
@@ -10,6 +10,7 @@
'<(grit_out_dir)/devtools_resources.pak',
'<(grit_out_dir)/net_internals_resources.pak',
'<(grit_out_dir)/options_resources.pak',
+ '<(grit_out_dir)/options2_resources.pak',
'<(grit_out_dir)/quota_internals_resources.pak',
'<(grit_out_dir)/shared_resources.pak',
'<(grit_out_dir)/sync_internals_resources.pak',
diff --git a/chrome/chrome_resources.gyp b/chrome/chrome_resources.gyp
index 3eccfa0..59b6143 100644
--- a/chrome/chrome_resources.gyp
+++ b/chrome/chrome_resources.gyp
@@ -39,6 +39,13 @@
'includes': [ '../build/grit_action.gypi' ],
},
{
+ 'action_name': 'options2_resources',
+ 'variables': {
+ 'grit_grd_file': 'browser/resources/options2_resources.grd',
+ },
+ 'includes': [ '../build/grit_action.gypi' ],
+ },
+ {
'action_name': 'quota_internals_resources',
'variables': {
'grit_grd_file': 'browser/resources/quota_internals_resources.grd',
diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi
index 5ece9ee..c393186 100644
--- a/chrome/chrome_tests.gypi
+++ b/chrome/chrome_tests.gypi
@@ -917,6 +917,7 @@
['chromeos==0', {
'sources/': [
['exclude', '^browser/ui/webui/options/chromeos/'],
+ ['exclude', '^browser/ui/webui/options2/chromeos/'],
],
}],
],
@@ -2132,6 +2133,7 @@
['exclude', '^browser/ui/webui/chromeos/imageburner/'],
['exclude', '^browser/ui/webui/chromeos/login'],
['exclude', '^browser/ui/webui/options/chromeos/'],
+ ['exclude', '^browser/ui/webui/options2/chromeos/'],
],
}],
['toolkit_uses_gtk == 1', {
diff --git a/chrome/common/url_constants.cc b/chrome/common/url_constants.cc
index 5d313e95c..8ef0d66 100644
--- a/chrome/common/url_constants.cc
+++ b/chrome/common/url_constants.cc
@@ -65,6 +65,9 @@ const char kChromeUIPolicyURL[] = "chrome://policy/";
const char kChromeUIPrintURL[] = "chrome://print/";
const char kChromeUISessionsURL[] = "chrome://sessions/";
const char kChromeUISettingsURL[] = "chrome://settings/";
+// settings-frame is the URL used to directly access the new settings page in
+// the UberPage, AKA options2.
+const char kChromeUISettingsFrameURL[] = "chrome://settings-frame/";
const char kChromeUIShorthangURL[] = "chrome://shorthang/";
const char kChromeUISSLClientCertificateSelectorURL[] = "chrome://select-cert/";
const char kChromeUISyncPromoURL[] = "chrome://syncpromo/";
@@ -169,6 +172,7 @@ const char kChromeUIQuotaInternalsHost[] = "quota-internals";
const char kChromeUIResourcesHost[] = "resources";
const char kChromeUISessionsHost[] = "sessions";
const char kChromeUISettingsHost[] = "settings";
+const char kChromeUISettingsFrameHost[] = "settings-frame";
const char kChromeUIShorthangHost[] = "shorthang";
const char kChromeUISSLClientCertificateSelectorHost[] = "select-cert";
const char kChromeUIStatsHost[] = "stats";
diff --git a/chrome/common/url_constants.h b/chrome/common/url_constants.h
index 15f5051..9b710c2 100644
--- a/chrome/common/url_constants.h
+++ b/chrome/common/url_constants.h
@@ -60,6 +60,7 @@ extern const char kChromeUIPolicyURL[];
extern const char kChromeUIPrintURL[];
extern const char kChromeUISessionsURL[];
extern const char kChromeUISettingsURL[];
+extern const char kChromeUISettingsFrameURL[];
extern const char kChromeUIShorthangURL[];
extern const char kChromeUISSLClientCertificateSelectorURL[];
extern const char kChromeUISyncPromoURL[];
@@ -161,6 +162,7 @@ extern const char kChromeUIQuotaInternalsHost[];
extern const char kChromeUIResourcesHost[];
extern const char kChromeUISessionsHost[];
extern const char kChromeUISettingsHost[];
+extern const char kChromeUISettingsFrameHost[];
extern const char kChromeUIShorthangHost[];
extern const char kChromeUISSLClientCertificateSelectorHost[];
extern const char kChromeUIStatsHost[];
diff --git a/tools/gritsettings/resource_ids b/tools/gritsettings/resource_ids
index c7bdb8f..fe4e3e5 100644
--- a/tools/gritsettings/resource_ids
+++ b/tools/gritsettings/resource_ids
@@ -156,6 +156,9 @@
"chrome/browser/resources/options_resources.grd": {
"includes": [22000],
},
+ "chrome/browser/resources/options2_resources.grd": {
+ "includes": [22200],
+ },
"cloud_print/virtual_driver/win/install/virtual_driver_setup_resources.grd": {
"messages": [22500],
},