diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2020-03-11 11:32:04 +0100 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2020-03-18 13:40:17 +0000 |
commit | 31ccca0778db85c159634478b4ec7997f6704860 (patch) | |
tree | 3d33fc3afd9d5ec95541e1bbe074a9cf8da12a0e /chromium/chrome/browser/resources/extensions | |
parent | 248b70b82a40964d5594eb04feca0fa36716185d (diff) |
BASELINE: Update Chromium to 80.0.3987.136
Change-Id: I98e1649aafae85ba3a83e67af00bb27ef301db7b
Reviewed-by: Jüri Valdmann <juri.valdmann@qt.io>
Diffstat (limited to 'chromium/chrome/browser/resources/extensions')
83 files changed, 8817 insertions, 9001 deletions
diff --git a/chromium/chrome/browser/resources/extensions/BUILD.gn b/chromium/chrome/browser/resources/extensions/BUILD.gn index 734470fdaf4..49c26aa9c38 100644 --- a/chromium/chrome/browser/resources/extensions/BUILD.gn +++ b/chromium/chrome/browser/resources/extensions/BUILD.gn @@ -5,6 +5,7 @@ import("//chrome/common/features.gni") import("//third_party/closure_compiler/compile_js.gni") import("//tools/grit/grit_rule.gni") +import("//tools/polymer/polymer.gni") import("../optimize_webui.gni") if (optimize_webui) { @@ -13,16 +14,15 @@ if (optimize_webui) { optimize_webui("build") { host = "extensions" - html_in_files = [ "extensions.html" ] - html_out_files = [ "vulcanized.html" ] - insert_in_head = "<base href=\"chrome://extensions\">" input = rebase_path("$target_gen_dir/$unpak_folder", root_build_dir) - js_out_files = [ "crisper.js" ] - replace_for_html_imports_polyfill = "crisper.js" + js_out_files = [ "extensions.rollup.js" ] + js_module_in_files = [ "extensions.js" ] deps = [ ":unpak", + "../../../../ui/webui/resources:modulize", ] + excludes = [ "chrome://resources/js/cr.m.js" ] } unpak("unpak") { @@ -39,7 +39,14 @@ if (optimize_webui) { # The .grd contains references to generated files. source_is_generated = true + grit_flags = [ + "-E", + "root_gen_dir=" + rebase_path(root_gen_dir, root_build_dir), + ] + deps = [ + ":polymer3_elements", + ] defines = chrome_grit_defines outputs = [ "grit/extensions_resources.h", @@ -53,12 +60,195 @@ if (optimize_webui) { group("closure_compile") { deps = [ - ":extensions_resources", - "activity_log:closure_compile", + ":extensions_module_resources", + "activity_log:closure_compile_module", ] } -js_type_check("extensions_resources") { +group("polymer3_elements") { + deps = [ + ":code_section_module", + ":detail_view_module", + ":drop_overlay_module", + ":error_page_module", + ":host_permissions_toggle_list_module", + ":icons_module", + ":install_warnings_dialog_module", + ":item_list_module", + ":item_module", + ":keyboard_shortcuts_module", + ":load_error_module", + ":manager_module", + ":options_dialog_module", + ":pack_dialog_alert_module", + ":pack_dialog_module", + ":runtime_host_permissions_module", + ":runtime_hosts_dialog_module", + ":shared_style_module", + ":shared_vars_module", + ":shortcut_input_module", + ":sidebar_module", + ":toggle_row_module", + ":toolbar_module", + "activity_log:polymer3_elements", + ] + if (is_chromeos) { + deps += [ ":kiosk_dialog_module" ] + } +} + +polymer_modulizer("code_section") { + js_file = "code_section.js" + html_file = "code_section.html" + html_type = "v3-ready" +} + +polymer_modulizer("detail_view") { + js_file = "detail_view.js" + html_file = "detail_view.html" + html_type = "v3-ready" +} + +polymer_modulizer("drop_overlay") { + js_file = "drop_overlay.js" + html_file = "drop_overlay.html" + html_type = "v3-ready" +} + +polymer_modulizer("error_page") { + js_file = "error_page.js" + html_file = "error_page.html" + html_type = "v3-ready" +} + +polymer_modulizer("host_permissions_toggle_list") { + js_file = "host_permissions_toggle_list.js" + html_file = "host_permissions_toggle_list.html" + html_type = "v3-ready" +} + +polymer_modulizer("icons") { + js_file = "icons.js" + html_file = "icons.html" + html_type = "v3-ready" +} + +polymer_modulizer("install_warnings_dialog") { + js_file = "install_warnings_dialog.js" + html_file = "install_warnings_dialog.html" + html_type = "v3-ready" +} + +polymer_modulizer("item") { + js_file = "item.js" + html_file = "item.html" + html_type = "v3-ready" +} + +polymer_modulizer("item_list") { + js_file = "item_list.js" + html_file = "item_list.html" + html_type = "v3-ready" +} + +polymer_modulizer("keyboard_shortcuts") { + js_file = "keyboard_shortcuts.js" + html_file = "keyboard_shortcuts.html" + html_type = "v3-ready" +} + +if (is_chromeos) { + polymer_modulizer("kiosk_dialog") { + js_file = "kiosk_dialog.js" + html_file = "kiosk_dialog.html" + html_type = "v3-ready" + } +} + +polymer_modulizer("load_error") { + js_file = "load_error.js" + html_file = "load_error.html" + html_type = "v3-ready" +} + +polymer_modulizer("manager") { + js_file = "manager.js" + html_file = "manager.html" + html_type = "v3-ready" +} + +polymer_modulizer("options_dialog") { + js_file = "options_dialog.js" + html_file = "options_dialog.html" + html_type = "v3-ready" +} + +polymer_modulizer("pack_dialog") { + js_file = "pack_dialog.js" + html_file = "pack_dialog.html" + html_type = "v3-ready" +} + +polymer_modulizer("pack_dialog_alert") { + js_file = "pack_dialog_alert.js" + html_file = "pack_dialog_alert.html" + html_type = "v3-ready" +} + +polymer_modulizer("runtime_host_permissions") { + js_file = "runtime_host_permissions.js" + html_file = "runtime_host_permissions.html" + html_type = "v3-ready" +} + +polymer_modulizer("runtime_hosts_dialog") { + js_file = "runtime_hosts_dialog.js" + html_file = "runtime_hosts_dialog.html" + html_type = "v3-ready" +} + +polymer_modulizer("shared_style") { + js_file = "shared_style.js" + html_file = "shared_style.html" + html_type = "v3-ready" +} + +polymer_modulizer("shared_vars") { + js_file = "shared_vars.js" + html_file = "shared_vars.html" + html_type = "v3-ready" +} + +polymer_modulizer("shortcut_input") { + js_file = "shortcut_input.js" + html_file = "shortcut_input.html" + html_type = "v3-ready" +} + +polymer_modulizer("sidebar") { + js_file = "sidebar.js" + html_file = "sidebar.html" + html_type = "v3-ready" +} + +polymer_modulizer("toggle_row") { + js_file = "toggle_row.js" + html_file = "toggle_row.html" + html_type = "v3-ready" +} + +polymer_modulizer("toolbar") { + js_file = "toolbar.js" + html_file = "toolbar.html" + html_type = "v3-ready" +} + +js_type_check("extensions_module_resources") { + closure_flags = default_closure_args + [ + "js_module_root=../../chrome/browser/resources/extensions/", + "js_module_root=./gen/chrome/browser/resources/extensions/", + ] + is_polymer3 = true deps = [ ":code_section", ":detail_view", @@ -74,7 +264,6 @@ js_type_check("extensions_resources") { ":keyboard_shortcut_delegate", ":keyboard_shortcuts", ":kiosk_browser_proxy", - ":kiosk_dialog", ":load_error", ":manager", ":navigation_helper", @@ -87,15 +276,19 @@ js_type_check("extensions_resources") { ":shortcut_input", ":shortcut_util", ":sidebar", + ":toggle_row", ":toolbar", ] + if (is_chromeos) { + deps += [ ":kiosk_dialog" ] + } } js_library("code_section") { deps = [ - "//ui/webui/resources/js:cr", - "//ui/webui/resources/js:i18n_behavior", - "//ui/webui/resources/js:load_time_data", + "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled", + "//ui/webui/resources/js:i18n_behavior.m", + "//ui/webui/resources/js:load_time_data.m", ] externs_list = [ "$externs_path/developer_private.js" ] } @@ -106,31 +299,26 @@ js_library("detail_view") { ":item_behavior", ":item_util", ":navigation_helper", - "//ui/webui/resources/cr_elements:cr_container_shadow_behavior", - "//ui/webui/resources/cr_elements/cr_action_menu:cr_action_menu", - "//ui/webui/resources/js:assert", - "//ui/webui/resources/js:cr", - "//ui/webui/resources/js:load_time_data", - "//ui/webui/resources/js/cr/ui:focus_without_ink", + "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled", + "//ui/webui/resources/cr_elements:cr_container_shadow_behavior.m", + "//ui/webui/resources/cr_elements/cr_action_menu:cr_action_menu.m", + "//ui/webui/resources/js:load_time_data.m", + "//ui/webui/resources/js/cr/ui:focus_without_ink.m", ] externs_list = [ "$externs_path/developer_private.js" ] } -js_library("drag_and_drop_handler") { +js_library("drop_overlay") { deps = [ - ":service", - "//ui/webui/resources/js:assert", - "//ui/webui/resources/js:cr", - "//ui/webui/resources/js/cr/ui:drag_wrapper", + ":drag_and_drop_handler", + "//ui/webui/resources/js/cr/ui:drag_wrapper.m", ] } -js_library("drop_overlay") { +js_library("drag_and_drop_handler") { deps = [ - ":drag_and_drop_handler", - "//ui/webui/resources/js:cr", - "//ui/webui/resources/js:load_time_data", - "//ui/webui/resources/js/cr/ui:drag_wrapper", + ":service", + "//ui/webui/resources/js/cr/ui:drag_wrapper.m", ] } @@ -139,10 +327,9 @@ js_library("error_page") { ":code_section", ":item_util", ":navigation_helper", - "//ui/webui/resources/cr_elements:cr_container_shadow_behavior", - "//ui/webui/resources/js:cr", - "//ui/webui/resources/js/cr/ui:focus_outline_manager", - "//ui/webui/resources/js/cr/ui:focus_without_ink", + "//ui/webui/resources/cr_elements:cr_container_shadow_behavior.m", + "//ui/webui/resources/js/cr/ui:focus_outline_manager.m", + "//ui/webui/resources/js/cr/ui:focus_without_ink.m", ] externs_list = [ "$externs_path/developer_private.js", @@ -152,15 +339,16 @@ js_library("error_page") { js_library("host_permissions_toggle_list") { deps = [ - "//ui/webui/resources/js:cr", + ":item", ] externs_list = [ "$externs_path/developer_private.js" ] } js_library("install_warnings_dialog") { deps = [ - "//ui/webui/resources/js:cr", + "//ui/webui/resources/cr_elements/cr_dialog:cr_dialog.m", ] + externs_list = [ "$externs_path/developer_private.js" ] } js_library("item") { @@ -168,19 +356,18 @@ js_library("item") { ":item_behavior", ":item_util", ":navigation_helper", - "//ui/webui/resources/cr_elements/cr_toast:cr_toast_manager", - "//ui/webui/resources/js:assert", - "//ui/webui/resources/js:cr", - "//ui/webui/resources/js:i18n_behavior", - "//ui/webui/resources/js:load_time_data", + "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled", + "//ui/webui/resources/cr_elements/cr_toast:cr_toast_manager.m", + "//ui/webui/resources/js:assert.m", + "//ui/webui/resources/js:i18n_behavior.m", + "//ui/webui/resources/js:load_time_data.m", ] externs_list = [ "$externs_path/developer_private.js" ] } js_library("item_behavior") { deps = [ - "//ui/webui/resources/js:cr", - "//ui/webui/resources/js:load_time_data", + "//ui/webui/resources/js:load_time_data.m", ] externs_list = [ "$externs_path/developer_private.js" ] } @@ -188,11 +375,9 @@ js_library("item_behavior") { js_library("item_list") { deps = [ ":item", - "//third_party/polymer/v1_0/components-chromium/iron-a11y-announcer:iron-a11y-announcer-extracted", - "//ui/webui/resources/cr_elements:cr_container_shadow_behavior", - "//ui/webui/resources/js:assert", - "//ui/webui/resources/js:cr", - "//ui/webui/resources/js:i18n_behavior", + "//third_party/polymer/v3_0/components-chromium/iron-a11y-announcer:iron-a11y-announcer", + "//ui/webui/resources/cr_elements:cr_container_shadow_behavior.m", + "//ui/webui/resources/js:i18n_behavior.m", ] externs_list = [ "$externs_path/developer_private.js", @@ -203,17 +388,14 @@ js_library("item_list") { js_library("item_util") { deps = [ ":navigation_helper", - "//ui/webui/resources/js:assert", - "//ui/webui/resources/js:cr", - "//ui/webui/resources/js:load_time_data", + "//ui/webui/resources/js:assert.m", + "//ui/webui/resources/js:load_time_data.m", ] externs_list = [ "$externs_path/developer_private.js" ] } js_library("keyboard_shortcut_delegate") { - deps = [ - "//ui/webui/resources/js:cr", - ] + deps = [] externs_list = [ "$externs_path/developer_private.js" ] } @@ -221,9 +403,7 @@ js_library("keyboard_shortcuts") { deps = [ ":item_behavior", ":keyboard_shortcut_delegate", - "//ui/webui/resources/cr_elements:cr_container_shadow_behavior", - "//ui/webui/resources/js:assert", - "//ui/webui/resources/js:cr", + "//ui/webui/resources/cr_elements:cr_container_shadow_behavior.m", ] externs_list = [ "$externs_path/developer_private.js", @@ -233,24 +413,24 @@ js_library("keyboard_shortcuts") { js_library("kiosk_browser_proxy") { deps = [ - ":item_behavior", - "//ui/webui/resources/js:cr", + "//ui/webui/resources/js:cr.m", ] } -js_library("kiosk_dialog") { - deps = [ - ":kiosk_browser_proxy", - "//ui/webui/resources/js:cr", - "//ui/webui/resources/js:web_ui_listener_behavior", - ] +if (is_chromeos) { + js_library("kiosk_dialog") { + deps = [ + ":item_behavior", + ":kiosk_browser_proxy", + "//ui/webui/resources/js:web_ui_listener_behavior.m", + ] + } } js_library("load_error") { deps = [ - "//ui/webui/resources/cr_elements/cr_dialog:cr_dialog", - "//ui/webui/resources/js:assert", - "//ui/webui/resources/js:cr", + "//ui/webui/resources/cr_elements/cr_dialog:cr_dialog.m", + "//ui/webui/resources/js:assert.m", ] externs_list = [ "$externs_path/developer_private.js" ] } @@ -269,46 +449,39 @@ js_library("manager") { ":sidebar", ":toolbar", "activity_log:activity_log", - "//ui/webui/resources/cr_elements/cr_drawer:cr_drawer", - "//ui/webui/resources/cr_elements/cr_view_manager:cr_view_manager", - "//ui/webui/resources/js:assert", - "//ui/webui/resources/js:cr", - "//ui/webui/resources/js:load_time_data", + "//ui/webui/resources/cr_elements/cr_drawer:cr_drawer.m", + "//ui/webui/resources/cr_elements/cr_view_manager:cr_view_manager.m", + "//ui/webui/resources/js:assert.m", + "//ui/webui/resources/js:load_time_data.m", ] externs_list = [ "$externs_path/developer_private.js" ] } js_library("navigation_helper") { deps = [ - "//ui/webui/resources/js:assert", - "//ui/webui/resources/js:cr", - "//ui/webui/resources/js:load_time_data", + "//ui/webui/resources/js:assert.m", + "//ui/webui/resources/js:load_time_data.m", ] + externs_list = [ "$externs_path/developer_private.js" ] } js_library("options_dialog") { deps = [ ":item_behavior", ":navigation_helper", - "//ui/webui/resources/cr_elements/cr_dialog:cr_dialog", - "//ui/webui/resources/js:assert", - "//ui/webui/resources/js:cr", + "//ui/webui/resources/cr_elements/cr_dialog:cr_dialog.m", ] externs_list = [ "$externs_path/developer_private.js" ] } js_library("pack_dialog") { - deps = [ - "//ui/webui/resources/js:cr", - ] externs_list = [ "$externs_path/developer_private.js" ] } js_library("pack_dialog_alert") { deps = [ - "//ui/webui/resources/cr_elements/cr_dialog:cr_dialog", - "//ui/webui/resources/js:cr", - "//ui/webui/resources/js:load_time_data", + "//ui/webui/resources/cr_elements/cr_dialog:cr_dialog.m", + "//ui/webui/resources/js:load_time_data.m", ] externs_list = [ "$externs_path/developer_private.js" ] } @@ -317,17 +490,19 @@ js_library("runtime_host_permissions") { deps = [ ":item", ":runtime_hosts_dialog", - "//ui/webui/resources/cr_elements/cr_radio_button:cr_radio_button", - "//ui/webui/resources/cr_elements/cr_radio_group:cr_radio_group", + "//ui/webui/resources/cr_elements/cr_action_menu:cr_action_menu.m", + "//ui/webui/resources/cr_elements/cr_radio_button:cr_radio_button.m", + "//ui/webui/resources/cr_elements/cr_radio_group:cr_radio_group.m", + "//ui/webui/resources/js/cr/ui:focus_without_ink.m", ] externs_list = [ "$externs_path/developer_private.js" ] } js_library("runtime_hosts_dialog") { deps = [ - "//ui/webui/resources/cr_elements/cr_dialog:cr_dialog", - "//ui/webui/resources/cr_elements/cr_input:cr_input", - "//ui/webui/resources/js:load_time_data", + "//ui/webui/resources/cr_elements/cr_dialog:cr_dialog.m", + "//ui/webui/resources/cr_elements/cr_input:cr_input.m", + "//ui/webui/resources/js:load_time_data.m", ] } @@ -343,9 +518,8 @@ js_library("service") { "activity_log:activity_log", "activity_log:activity_log_history", "activity_log:activity_log_stream", - "//ui/webui/resources/js:assert", - "//ui/webui/resources/js:cr", - "//ui/webui/resources/js:load_time_data", + "//ui/webui/resources/js:assert.m", + "//ui/webui/resources/js:cr.m", ] externs_list = [ "$externs_path/activity_log_private.js", @@ -359,35 +533,35 @@ js_library("shortcut_input") { deps = [ ":keyboard_shortcut_delegate", ":shortcut_util", - "//ui/webui/resources/js:assert", - "//ui/webui/resources/js:cr", + "//ui/webui/resources/js:assert.m", ] externs_list = [ "$externs_path/developer_private.js" ] } js_library("shortcut_util") { deps = [ - "//ui/webui/resources/js:assert", - "//ui/webui/resources/js:cr", + "//ui/webui/resources/js:assert.m", + "//ui/webui/resources/js:cr.m", ] } js_library("sidebar") { deps = [ ":navigation_helper", - "//ui/webui/resources/js:assert", - "//ui/webui/resources/js:cr", + "//ui/webui/resources/js:assert.m", ] externs_list = [ "$externs_path/metrics_private.js" ] } +js_library("toggle_row") { +} + js_library("toolbar") { deps = [ - "//third_party/polymer/v1_0/components-chromium/iron-a11y-announcer:iron-a11y-announcer-extracted", - "//ui/webui/resources/cr_elements/cr_toast:cr_toast_manager", - "//ui/webui/resources/js:cr", - "//ui/webui/resources/js:i18n_behavior", - "//ui/webui/resources/js:util", + "//third_party/polymer/v3_0/components-chromium/iron-a11y-announcer:iron-a11y-announcer", + "//ui/webui/resources/cr_elements/cr_toast:cr_toast_manager.m", + "//ui/webui/resources/js:i18n_behavior.m", + "//ui/webui/resources/js:util.m", ] externs_list = [ "$externs_path/metrics_private.js" ] } diff --git a/chromium/chrome/browser/resources/extensions/activity_log/BUILD.gn b/chromium/chrome/browser/resources/extensions/activity_log/BUILD.gn index be1754ad8ff..d562aa8ca3d 100644 --- a/chromium/chrome/browser/resources/extensions/activity_log/BUILD.gn +++ b/chromium/chrome/browser/resources/extensions/activity_log/BUILD.gn @@ -1,10 +1,56 @@ -# Copyright 2019 The Chromium Authors. All rights reserved. -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. +#Copyright 2019 The Chromium 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("//third_party/closure_compiler/compile_js.gni") +import("//tools/polymer/polymer.gni") -js_type_check("closure_compile") { +polymer_modulizer("activity_log") { + js_file = "activity_log.js" + html_file = "activity_log.html" + html_type = "v3-ready" +} + +polymer_modulizer("activity_log_history") { + js_file = "activity_log_history.js" + html_file = "activity_log_history.html" + html_type = "v3-ready" +} + +polymer_modulizer("activity_log_history_item") { + js_file = "activity_log_history_item.js" + html_file = "activity_log_history_item.html" + html_type = "v3-ready" +} + +polymer_modulizer("activity_log_stream") { + js_file = "activity_log_stream.js" + html_file = "activity_log_stream.html" + html_type = "v3-ready" +} + +polymer_modulizer("activity_log_stream_item") { + js_file = "activity_log_stream_item.js" + html_file = "activity_log_stream_item.html" + html_type = "v3-ready" +} + +group("polymer3_elements") { + deps = [ + ":activity_log_history_item_module", + ":activity_log_history_module", + ":activity_log_module", + ":activity_log_stream_item_module", + ":activity_log_stream_module", + ] +} + +js_type_check("closure_compile_module") { + closure_flags = default_closure_args + [ + "js_module_root=../../chrome/browser/resources/extensions/", + "js_module_root=./gen/chrome/browser/resources/extensions/", + ] + is_polymer3 = true deps = [ ":activity_log", ":activity_log_history", @@ -14,53 +60,46 @@ js_type_check("closure_compile") { ] } -js_library("activity_log_history_item") { +js_library("activity_log") { deps = [ - "//ui/webui/resources/js:cr", + ":activity_log_history", + ":activity_log_stream", + "..:item_behavior", + "..:navigation_helper", + "//ui/webui/resources/cr_elements:cr_container_shadow_behavior.m", + "//ui/webui/resources/js:i18n_behavior.m", + "//ui/webui/resources/js/cr/ui:focus_without_ink.m", + ] + externs_list = [ + "$externs_path/activity_log_private.js", + "$externs_path/developer_private.js", ] - externs_list = [ "$externs_path/activity_log_private.js" ] } js_library("activity_log_history") { deps = [ ":activity_log_history_item", - "//ui/webui/resources/cr_elements/cr_action_menu:cr_action_menu", - "//ui/webui/resources/cr_elements/cr_search_field:cr_search_field", - "//ui/webui/resources/js:cr", + "//ui/webui/resources/cr_elements/cr_action_menu:cr_action_menu.m", + "//ui/webui/resources/cr_elements/cr_search_field:cr_search_field.m", ] externs_list = [ "$externs_path/activity_log_private.js" ] } +js_library("activity_log_history_item") { + deps = [] + externs_list = [ "$externs_path/activity_log_private.js" ] +} + js_library("activity_log_stream") { deps = [ ":activity_log_stream_item", - "//third_party/polymer/v1_0/components-chromium/iron-list:iron-list-extracted", - "//ui/webui/resources/cr_elements/cr_search_field:cr_search_field", - "//ui/webui/resources/js:cr", + "//third_party/polymer/v3_0/components-chromium/iron-list:iron-list", + "//ui/webui/resources/cr_elements/cr_search_field:cr_search_field.m", ] externs_list = [ "$externs_path/activity_log_private.js" ] } js_library("activity_log_stream_item") { - deps = [ - "//ui/webui/resources/js:cr", - ] + deps = [] externs_list = [ "$externs_path/activity_log_private.js" ] } - -js_library("activity_log") { - deps = [ - ":activity_log_history", - ":activity_log_stream", - "..:item_behavior", - "..:navigation_helper", - "//ui/webui/resources/cr_elements:cr_container_shadow_behavior", - "//ui/webui/resources/js:cr", - "//ui/webui/resources/js:i18n_behavior", - "//ui/webui/resources/js/cr/ui:focus_without_ink", - ] - externs_list = [ - "$externs_path/activity_log_private.js", - "$externs_path/developer_private.js", - ] -} diff --git a/chromium/chrome/browser/resources/extensions/activity_log/activity_log.html b/chromium/chrome/browser/resources/extensions/activity_log/activity_log.html index 6a10765807b..c3167d20755 100644 --- a/chromium/chrome/browser/resources/extensions/activity_log/activity_log.html +++ b/chromium/chrome/browser/resources/extensions/activity_log/activity_log.html @@ -1,103 +1,79 @@ -<link rel="import" href="chrome://resources/html/polymer.html"> +<style include="cr-icons cr-shared-style shared-style"> + #clear-activities-button { + margin-inline-start: 8px; + } -<link rel="import" href="chrome://resources/cr_elements/cr_container_shadow_behavior.html"> -<link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html"> -<link rel="import" href="chrome://resources/cr_elements/cr_icons_css.html"> -<link rel="import" href="chrome://resources/cr_elements/cr_tabs/cr_tabs.html"> -<link rel="import" href="chrome://resources/cr_elements/shared_style_css.html"> -<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html"> -<link rel="import" href="chrome://resources/html/cr.html"> -<link rel="import" href="chrome://resources/html/cr/ui/focus_without_ink.html"> -<link rel="import" href="chrome://resources/html/i18n_behavior.html"> -<link rel="import" href="chrome://resources/polymer/v1_0/iron-pages/iron-pages.html"> -<link rel="import" href="activity_log_history.html"> -<link rel="import" href="activity_log_stream.html"> -<link rel="import" href="../item_behavior.html"> -<link rel="import" href="../navigation_helper.html"> -<link rel="import" href="../shared_style.html"> -<link rel="import" href="../shared_vars.html"> + #closeButton { + margin-inline-end: 16px; + } -<dom-module id="extensions-activity-log"> - <template> - <style include="cr-icons cr-shared-style shared-style"> - #clear-activities-button { - margin-inline-start: 8px; - } + #icon { + height: 24px; + margin-inline-end: 12px; + width: 24px; + } - #closeButton { - margin-inline-end: 16px; - } + cr-tabs { + --cr-tabs-font-size: inherit; + --cr-tabs-height: 40px; + border-bottom: 1px solid var(--google-grey-refresh-300); + } - #icon { - height: 24px; - margin-inline-end: 12px; - width: 24px; - } + .page-content { + display: flex; + flex-direction: column; + padding-bottom: 0; + } - cr-tabs { - --cr-tabs-font-size: inherit; - --cr-tabs-height: 40px; - border-bottom: 1px solid var(--google-grey-refresh-300); - } + iron-pages { + flex: 1; + position: relative; + } - .page-content { - display: flex; - flex-direction: column; - padding-bottom: 0; - } - - iron-pages { - flex: 1; - position: relative; - } - - activity-log-history, - activity-log-stream { - bottom: 0; - position: absolute; - top: 0; - width: 100%; - } - </style> - <div class="page-container" id="container"> - <div class="page-content"> - <div class="page-header"> - <cr-icon-button class="icon-arrow-back no-overlap" id="closeButton" - aria-label="$i18n{back}" on-click="onCloseButtonTap_"> - </cr-icon-button> - <template is="dom-if" if="[[!extensionInfo.isPlaceholder]]"> - <img id="icon" src="[[extensionInfo.iconUrl]]" - alt$="[[appOrExtension( - extensionInfo.type, - '$i18nPolymer{appIcon}', - '$i18nPolymer{extensionIcon}')]]"> - </template> - <div class="cr-title-text"> - [[getActivityLogHeading_(extensionInfo)]] - </div> - </div> - <cr-tabs selected="{{selectedSubpage_}}" tab-names="[[tabNames_]]"> - </cr-tabs> - <iron-pages selected="[[selectedSubpage_]]"> - <div> - <template is="dom-if" - if="[[isHistoryTabSelected_(selectedSubpage_)]]" restamp> - <activity-log-history extension-id="[[extensionInfo.id]]" - delegate="[[delegate]]"> - </activity-log-history> - </template> - </div> - <div> - <template is="dom-if" - if="[[isStreamTabSelected_(selectedSubpage_)]]"> - <activity-log-stream extension-id="[[extensionInfo.id]]" - delegate="[[delegate]]"> - </activity-log-stream> - </template> - </div> - </iron-pages> + activity-log-history, + activity-log-stream { + bottom: 0; + position: absolute; + top: 0; + width: 100%; + } +</style> +<div class="page-container" id="container"> + <div class="page-content"> + <div class="page-header"> + <cr-icon-button class="icon-arrow-back no-overlap" id="closeButton" + aria-label="$i18n{back}" on-click="onCloseButtonTap_"> + </cr-icon-button> + <template is="dom-if" if="[[!extensionInfo.isPlaceholder]]"> + <img id="icon" src="[[extensionInfo.iconUrl]]" + alt$="[[appOrExtension( + extensionInfo.type, + '$i18nPolymer{appIcon}', + '$i18nPolymer{extensionIcon}')]]"> + </template> + <div class="cr-title-text"> + [[getActivityLogHeading_(extensionInfo)]] </div> </div> - </template> - <script src="activity_log.js"></script> -</dom-module> + <cr-tabs selected="{{selectedSubpage_}}" tab-names="[[tabNames_]]"> + </cr-tabs> + <iron-pages selected="[[selectedSubpage_]]"> + <div> + <template is="dom-if" + if="[[isHistoryTabSelected_(selectedSubpage_)]]" restamp> + <activity-log-history extension-id="[[extensionInfo.id]]" + delegate="[[delegate]]"> + </activity-log-history> + </template> + </div> + <div> + <template is="dom-if" + if="[[isStreamTabSelected_(selectedSubpage_)]]"> + <activity-log-stream extension-id="[[extensionInfo.id]]" + delegate="[[delegate]]"> + </activity-log-stream> + </template> + </div> + </iron-pages> + </div> +</div> diff --git a/chromium/chrome/browser/resources/extensions/activity_log/activity_log.js b/chromium/chrome/browser/resources/extensions/activity_log/activity_log.js index 3f3d33e0990..6bf480f9927 100644 --- a/chromium/chrome/browser/resources/extensions/activity_log/activity_log.js +++ b/chromium/chrome/browser/resources/extensions/activity_log/activity_log.js @@ -2,6 +2,28 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'chrome://resources/cr_elements/cr_icon_button/cr_icon_button.m.js'; +import 'chrome://resources/cr_elements/cr_icons_css.m.js'; +import 'chrome://resources/cr_elements/cr_tabs/cr_tabs.m.js'; +import 'chrome://resources/cr_elements/shared_style_css.m.js'; +import 'chrome://resources/cr_elements/shared_vars_css.m.js'; +import 'chrome://resources/polymer/v3_0/iron-pages/iron-pages.js'; +import './activity_log_stream.js'; +import '../strings.m.js'; +import '../shared_style.js'; +import '../shared_vars.js'; + +import {CrContainerShadowBehavior} from 'chrome://resources/cr_elements/cr_container_shadow_behavior.m.js'; +import {focusWithoutInk} from 'chrome://resources/js/cr/ui/focus_without_ink.m.js'; +import {I18nBehavior} from 'chrome://resources/js/i18n_behavior.m.js'; +import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js'; +import {afterNextRender, html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; + +import {ItemBehavior} from '../item_behavior.js'; +import {navigation, Page} from '../navigation_helper.js'; + +import {ActivityLogDelegate} from './activity_log_history.js'; + /** * Subpages/views for the activity log. HISTORY shows extension activities * fetched from the activity log database with some fields such as args @@ -15,150 +37,140 @@ const ActivityLogSubpage = { STREAM: 1 }; -cr.define('extensions', function() { - 'use strict'; +/** + * A struct used as a placeholder for chrome.developerPrivate.ExtensionInfo + * for this component if the extensionId from the URL does not correspond to + * installed extension. + * @typedef {{ + * id: string, + * isPlaceholder: boolean, + * }} + */ +export let ActivityLogExtensionPlaceholder; - /** - * A struct used as a placeholder for chrome.developerPrivate.ExtensionInfo - * for this component if the extensionId from the URL does not correspond to - * installed extension. - * @typedef {{ - * id: string, - * isPlaceholder: boolean, - * }} - */ - let ActivityLogExtensionPlaceholder; - - const ActivityLog = Polymer({ - is: 'extensions-activity-log', - - behaviors: [ - CrContainerShadowBehavior, - I18nBehavior, - extensions.ItemBehavior, - ], - - properties: { - /** - * The underlying ExtensionInfo for the details being displayed. - * @type {!chrome.developerPrivate.ExtensionInfo| - * !extensions.ActivityLogExtensionPlaceholder} - */ - extensionInfo: Object, - - /** @type {!extensions.ActivityLogDelegate} */ - delegate: Object, - - /** @private {!ActivityLogSubpage} */ - selectedSubpage_: { - type: Number, - value: ActivityLogSubpage.NONE, - observer: 'onSelectedSubpageChanged_', - }, - - /** @private {Array<string>} */ - tabNames_: { - type: Array, - value: () => ([ - loadTimeData.getString('activityLogHistoryTabHeading'), - loadTimeData.getString('activityLogStreamTabHeading'), - ]), - } - }, +Polymer({ + is: 'extensions-activity-log', - listeners: { - 'view-enter-start': 'onViewEnterStart_', - 'view-exit-finish': 'onViewExitFinish_', - }, + _template: html`{__html_template__}`, - /** - * Focuses the back button when page is loaded and set the activie view to - * be HISTORY when we navigate to the page. - * @private - */ - onViewEnterStart_: function() { - this.selectedSubpage_ = ActivityLogSubpage.HISTORY; - Polymer.RenderStatus.afterNextRender( - this, () => cr.ui.focusWithoutInk(this.$.closeButton)); - }, + behaviors: [ + CrContainerShadowBehavior, + I18nBehavior, + ItemBehavior, + ], + properties: { /** - * Set |selectedSubpage_| to NONE to remove the active view from the DOM. - * @private + * The underlying ExtensionInfo for the details being displayed. + * @type {!chrome.developerPrivate.ExtensionInfo| + * !ActivityLogExtensionPlaceholder} */ - onViewExitFinish_: function() { - this.selectedSubpage_ = ActivityLogSubpage.NONE; - // clear the stream if the user is exiting the activity log page. - const activityLogStream = this.$$('activity-log-stream'); - if (activityLogStream) { - activityLogStream.clearStream(); - } - }, + extensionInfo: Object, - /** - * @private - * @return {string} - */ - getActivityLogHeading_: function() { - const headingName = this.extensionInfo.isPlaceholder ? - this.i18n('missingOrUninstalledExtension') : - this.extensionInfo.name; - return this.i18n('activityLogPageHeading', headingName); - }, + /** @type {!ActivityLogDelegate} */ + delegate: Object, - /** - * @private - * @return {boolean} - */ - isHistoryTabSelected_: function() { - return this.selectedSubpage_ === ActivityLogSubpage.HISTORY; + /** @private {!ActivityLogSubpage} */ + selectedSubpage_: { + type: Number, + value: ActivityLogSubpage.NONE, + observer: 'onSelectedSubpageChanged_', }, - /** - * @private - * @return {boolean} - */ - isStreamTabSelected_: function() { - return this.selectedSubpage_ === ActivityLogSubpage.STREAM; - }, + /** @private {Array<string>} */ + tabNames_: { + type: Array, + value: () => ([ + loadTimeData.getString('activityLogHistoryTabHeading'), + loadTimeData.getString('activityLogStreamTabHeading'), + ]), + } + }, + + listeners: { + 'view-enter-start': 'onViewEnterStart_', + 'view-exit-finish': 'onViewExitFinish_', + }, - /** - * @private - * @param {!ActivityLogSubpage} newTab - * @param {!ActivityLogSubpage} oldTab - */ - onSelectedSubpageChanged_: function(newTab, oldTab) { - const activityLogStream = this.$$('activity-log-stream'); - if (activityLogStream) { - if (newTab === ActivityLogSubpage.STREAM) { - // Start the stream if the user is switching to the real-time tab. - // This will not handle the first tab switch to the real-time tab as - // the stream has not been attached to the DOM yet, and is handled - // instead by the stream's |attached| method. - activityLogStream.startStream(); - } else if (oldTab === ActivityLogSubpage.STREAM) { - // Pause the stream if the user is navigating away from the real-time - // tab. - activityLogStream.pauseStream(); - } - } - }, + /** + * Focuses the back button when page is loaded and set the activie view to + * be HISTORY when we navigate to the page. + * @private + */ + onViewEnterStart_: function() { + this.selectedSubpage_ = ActivityLogSubpage.HISTORY; + afterNextRender(this, () => focusWithoutInk(this.$.closeButton)); + }, - /** @private */ - onCloseButtonTap_: function() { - if (this.extensionInfo.isPlaceholder) { - extensions.navigation.navigateTo({page: extensions.Page.LIST}); - } else { - extensions.navigation.navigateTo({ - page: extensions.Page.DETAILS, - extensionId: this.extensionInfo.id - }); - } - }, - }); + /** + * Set |selectedSubpage_| to NONE to remove the active view from the DOM. + * @private + */ + onViewExitFinish_: function() { + this.selectedSubpage_ = ActivityLogSubpage.NONE; + // clear the stream if the user is exiting the activity log page. + const activityLogStream = this.$$('activity-log-stream'); + if (activityLogStream) { + activityLogStream.clearStream(); + } + }, - return { - ActivityLog: ActivityLog, - ActivityLogExtensionPlaceholder: ActivityLogExtensionPlaceholder, - }; + /** + * @private + * @return {string} + */ + getActivityLogHeading_: function() { + const headingName = this.extensionInfo.isPlaceholder ? + this.i18n('missingOrUninstalledExtension') : + this.extensionInfo.name; + return this.i18n('activityLogPageHeading', headingName); + }, + + /** + * @private + * @return {boolean} + */ + isHistoryTabSelected_: function() { + return this.selectedSubpage_ === ActivityLogSubpage.HISTORY; + }, + + /** + * @private + * @return {boolean} + */ + isStreamTabSelected_: function() { + return this.selectedSubpage_ === ActivityLogSubpage.STREAM; + }, + + /** + * @private + * @param {!ActivityLogSubpage} newTab + * @param {!ActivityLogSubpage} oldTab + */ + onSelectedSubpageChanged_: function(newTab, oldTab) { + const activityLogStream = this.$$('activity-log-stream'); + if (activityLogStream) { + if (newTab === ActivityLogSubpage.STREAM) { + // Start the stream if the user is switching to the real-time tab. + // This will not handle the first tab switch to the real-time tab as + // the stream has not been attached to the DOM yet, and is handled + // instead by the stream's |attached| method. + activityLogStream.startStream(); + } else if (oldTab === ActivityLogSubpage.STREAM) { + // Pause the stream if the user is navigating away from the real-time + // tab. + activityLogStream.pauseStream(); + } + } + }, + + /** @private */ + onCloseButtonTap_: function() { + if (this.extensionInfo.isPlaceholder) { + navigation.navigateTo({page: Page.LIST}); + } else { + navigation.navigateTo( + {page: Page.DETAILS, extensionId: this.extensionInfo.id}); + } + }, }); diff --git a/chromium/chrome/browser/resources/extensions/activity_log/activity_log_history.html b/chromium/chrome/browser/resources/extensions/activity_log/activity_log_history.html index 41cb1feabd4..1e614d5acdf 100644 --- a/chromium/chrome/browser/resources/extensions/activity_log/activity_log_history.html +++ b/chromium/chrome/browser/resources/extensions/activity_log/activity_log_history.html @@ -1,106 +1,90 @@ -<link rel="import" href="chrome://resources/html/polymer.html"> +<style include="shared-style"> + :host { + --activity-log-call-and-count-width: 514px; + --activity-type-width: 85px; + --activity-count-width: 100px; -<link rel="import" href="chrome://resources/html/cr.html"> -<link rel="import" href="chrome://resources/html/promise_resolver.html"> -<link rel="import" href="chrome://resources/cr_elements/cr_action_menu/cr_action_menu.html"> -<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html"> -<link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html"> -<link rel="import" href="chrome://resources/cr_elements/cr_search_field/cr_search_field.html"> -<link rel="import" href="activity_log_history_item.html"> -<link rel="import" href="../shared_style.html"> + display: flex; + flex-direction: column; + } -<dom-module id="activity-log-history"> - <template> - <style include="shared-style"> - :host { - --activity-log-call-and-count-width: 514px; - --activity-type-width: 85px; - --activity-count-width: 100px; + cr-search-field { + align-self: center; + margin-inline-end: auto; + } - display: flex; - flex-direction: column; - } + cr-icon-button { + margin: 0; + } - cr-search-field { - align-self: center; - margin-inline-end: auto; - } + .activity-table-headings { + width: var(--activity-log-call-and-count-width); + } - cr-icon-button { - margin: 0; - } + #activity-list { + overflow-y: auto; + } - .activity-table-headings { - width: var(--activity-log-call-and-count-width); - } + #activity-type { + flex: 0 var(--activity-type-width); + } - #activity-list { - overflow-y: auto; - } + #activity-key { + flex: 1; + margin-inline-start: 10px; + } - #activity-type { - flex: 0 var(--activity-type-width); - } - - #activity-key { - flex: 1; - margin-inline-start: 10px; - } - - #activity-count { - flex: 0 var(--activity-count-width); - text-align: end; - } - </style> - <div class="activity-subpage-header"> - <cr-search-field label="$i18n{activityLogSearchLabel}" - on-search-changed="onSearchChanged_"> - </cr-search-field > - <cr-button class="clear-activities-button" - on-click="onClearActivitiesClick_"> - $i18n{clearActivities} - </cr-button> - <cr-icon-button id="more-actions" iron-icon="cr:more-vert" - title="$i18n{activityLogMoreActionsLabel}" - on-click="onMoreActionsClick_"></cr-icon-button> - <cr-action-menu> - <button id="expand-all-button" class="dropdown-item" - on-click="onExpandAllClick_"> - $i18n{activityLogExpandAll} - </button> - <button id="collapse-all-button" class="dropdown-item" - on-click="onCollapseAllClick_"> - $i18n{activityLogCollapseAll} - </button> - <button id="export-button" class="dropdown-item" - on-click="onExportClick_"> - $i18n{activityLogExportHistory} - </button> - </cr-action-menu> - </div> - <div id="loading-activities" class="activity-message" - hidden$="[[!shouldShowLoadingMessage_( - pageState_)]]"> - <span>$i18n{loadingActivities}</span> - </div> - <div id="no-activities" class="activity-message" - hidden$="[[!shouldShowEmptyActivityLogMessage_( - pageState_, activityData_)]]"> - <span>$i18n{noActivities}</span> - </div> - <div class="activity-table-headings" - hidden$="[[!shouldShowActivities_(pageState_, activityData_)]]"> - <span id="activity-type">$i18n{activityLogTypeColumn}</span> - <span id="activity-key">$i18n{activityLogNameColumn}</span> - <span id="activity-count">$i18n{activityLogCountColumn}</span> - </div> - <div id="activity-list" - hidden$="[[!shouldShowActivities_(pageState_, activityData_)]]"> - <template is="dom-repeat" items="[[activityData_]]"> - <activity-log-history-item data="[[item]]"> - </activity-log-history-item> - </template> - </div> + #activity-count { + flex: 0 var(--activity-count-width); + text-align: end; + } +</style> +<div class="activity-subpage-header"> + <cr-search-field label="$i18n{activityLogSearchLabel}" + on-search-changed="onSearchChanged_"> + </cr-search-field > + <cr-button class="clear-activities-button" + on-click="onClearActivitiesClick_"> + $i18n{clearActivities} + </cr-button> + <cr-icon-button id="more-actions" iron-icon="cr:more-vert" + title="$i18n{activityLogMoreActionsLabel}" + on-click="onMoreActionsClick_"></cr-icon-button> + <cr-action-menu role-description="$i18n{menu}"> + <button id="expand-all-button" class="dropdown-item" + on-click="onExpandAllClick_"> + $i18n{activityLogExpandAll} + </button> + <button id="collapse-all-button" class="dropdown-item" + on-click="onCollapseAllClick_"> + $i18n{activityLogCollapseAll} + </button> + <button id="export-button" class="dropdown-item" + on-click="onExportClick_"> + $i18n{activityLogExportHistory} + </button> + </cr-action-menu> +</div> +<div id="loading-activities" class="activity-message" + hidden$="[[!shouldShowLoadingMessage_( + pageState_)]]"> + <span>$i18n{loadingActivities}</span> +</div> +<div id="no-activities" class="activity-message" + hidden$="[[!shouldShowEmptyActivityLogMessage_( + pageState_, activityData_)]]"> + <span>$i18n{noActivities}</span> +</div> +<div class="activity-table-headings" + hidden$="[[!shouldShowActivities_(pageState_, activityData_)]]"> + <span id="activity-type">$i18n{activityLogTypeColumn}</span> + <span id="activity-key">$i18n{activityLogNameColumn}</span> + <span id="activity-count">$i18n{activityLogCountColumn}</span> +</div> +<div id="activity-list" + hidden$="[[!shouldShowActivities_(pageState_, activityData_)]]"> + <template is="dom-repeat" items="[[activityData_]]"> + <activity-log-history-item data="[[item]]"> + </activity-log-history-item> </template> - <script src="activity_log_history.js"></script> -</dom-module> +</div> diff --git a/chromium/chrome/browser/resources/extensions/activity_log/activity_log_history.js b/chromium/chrome/browser/resources/extensions/activity_log/activity_log_history.js index fda97f585e2..a28281eaa9d 100644 --- a/chromium/chrome/browser/resources/extensions/activity_log/activity_log_history.js +++ b/chromium/chrome/browser/resources/extensions/activity_log/activity_log_history.js @@ -2,415 +2,422 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -cr.define('extensions', function() { - 'use strict'; - +import 'chrome://resources/cr_elements/cr_action_menu/cr_action_menu.m.js'; +import 'chrome://resources/cr_elements/cr_button/cr_button.m.js'; +import 'chrome://resources/cr_elements/cr_icon_button/cr_icon_button.m.js'; +import 'chrome://resources/cr_elements/cr_search_field/cr_search_field.m.js'; +import '../shared_style.js'; + +import {assert} from 'chrome://resources/js/assert.m.js'; +import {PromiseResolver} from 'chrome://resources/js/promise_resolver.m.js'; +import {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; + +import {ActivityGroup} from './activity_log_history_item.js'; + +/** + * The different states the activity log page can be in. Initial state is + * LOADING because we call the activity log API whenever a user navigates to + * the page. LOADED is the state where the API call has returned a successful + * result. + * @enum {string} + */ +export const ActivityLogPageState = { + LOADING: 'loading', + LOADED: 'loaded' +}; + +/** @interface */ +export class ActivityLogDelegate { /** - * The different states the activity log page can be in. Initial state is - * LOADING because we call the activity log API whenever a user navigates to - * the page. LOADED is the state where the API call has returned a successful - * result. - * @enum {string} + * @param {string} extensionId + * @return {!Promise<!chrome.activityLogPrivate.ActivityResultSet>} */ - const ActivityLogPageState = {LOADING: 'loading', LOADED: 'loaded'}; - - /** @interface */ - class ActivityLogDelegate { - /** - * @param {string} extensionId - * @return {!Promise<!chrome.activityLogPrivate.ActivityResultSet>} - */ - getExtensionActivityLog(extensionId) {} - - /** - * @param {string} extensionId - * @param {string} searchTerm - * @return {!Promise<!chrome.activityLogPrivate.ActivityResultSet>} - */ - getFilteredExtensionActivityLog(extensionId, searchTerm) {} - - /** - * @param {!Array<string>} activityIds - * @return {!Promise<void>} - */ - deleteActivitiesById(activityIds) {} - - /** - * @param {string} extensionId - * @return {!Promise<void>} - */ - deleteActivitiesFromExtension(extensionId) {} - - /** - * @param {string} rawActivityData - * @param {string} fileName - */ - downloadActivities(rawActivityData, fileName) {} - } + getExtensionActivityLog(extensionId) {} /** - * Content scripts activities do not have an API call, so we use the names of - * the scripts executed (specified as a stringified JSON array in the args - * field) as the keys for an activity group instead. - * @private - * @param {!chrome.activityLogPrivate.ExtensionActivity} activity - * @return {!Array<string>} + * @param {string} extensionId + * @param {string} searchTerm + * @return {!Promise<!chrome.activityLogPrivate.ActivityResultSet>} */ - function getActivityGroupKeysForContentScript_(activity) { - assert( - activity.activityType === - chrome.activityLogPrivate.ExtensionActivityType.CONTENT_SCRIPT); - - if (!activity.args) { - return []; - } - - const parsedArgs = JSON.parse(activity.args); - assert(Array.isArray(parsedArgs), 'Invalid API data.'); - return /** @type {!Array<string>} */ (parsedArgs); - } + getFilteredExtensionActivityLog(extensionId, searchTerm) {} /** - * Web request activities can have extra information which describes what the - * web request does in more detail than just the api_call. This information - * is in activity.other.webRequest and we use this to generate more activity - * group keys if possible. - * @private - * @param {!chrome.activityLogPrivate.ExtensionActivity} activity - * @return {!Array<string>} + * @param {!Array<string>} activityIds + * @return {!Promise<void>} */ - function getActivityGroupKeysForWebRequest_(activity) { - assert( - activity.activityType === - chrome.activityLogPrivate.ExtensionActivityType.WEB_REQUEST); - - const apiCall = activity.apiCall; - const other = activity.other; + deleteActivitiesById(activityIds) {} - if (!other || !other.webRequest) { - return [apiCall]; - } + /** + * @param {string} extensionId + * @return {!Promise<void>} + */ + deleteActivitiesFromExtension(extensionId) {} - const webRequest = /** @type {!Object} */ (JSON.parse(other.webRequest)); - assert(typeof webRequest === 'object', 'Invalid API data'); + /** + * @param {string} rawActivityData + * @param {string} fileName + */ + downloadActivities(rawActivityData, fileName) {} +} + +/** + * Content scripts activities do not have an API call, so we use the names of + * the scripts executed (specified as a stringified JSON array in the args + * field) as the keys for an activity group instead. + * @private + * @param {!chrome.activityLogPrivate.ExtensionActivity} activity + * @return {!Array<string>} + */ +function getActivityGroupKeysForContentScript_(activity) { + assert( + activity.activityType === + chrome.activityLogPrivate.ExtensionActivityType.CONTENT_SCRIPT); + + if (!activity.args) { + return []; + } - // If there is extra information in the other.webRequest object, - // construct a group for each consisting of the API call and each object key - // in other.webRequest. Otherwise we default to just the API call. - return Object.keys(webRequest).length === 0 ? - [apiCall] : - Object.keys(webRequest).map(field => `${apiCall} (${field})`); + const parsedArgs = JSON.parse(activity.args); + assert(Array.isArray(parsedArgs), 'Invalid API data.'); + return /** @type {!Array<string>} */ (parsedArgs); +} + +/** + * Web request activities can have extra information which describes what the + * web request does in more detail than just the api_call. This information + * is in activity.other.webRequest and we use this to generate more activity + * group keys if possible. + * @private + * @param {!chrome.activityLogPrivate.ExtensionActivity} activity + * @return {!Array<string>} + */ +function getActivityGroupKeysForWebRequest_(activity) { + assert( + activity.activityType === + chrome.activityLogPrivate.ExtensionActivityType.WEB_REQUEST); + + const apiCall = activity.apiCall; + const other = activity.other; + + if (!other || !other.webRequest) { + return [apiCall]; } - /** - * Group activity log entries by a key determined from each entry. Usually - * this would be the activity's API call though content script and web - * requests have different keys. We currently assume that every API call - * matches to one activity type. - * @param {!Array<!chrome.activityLogPrivate.ExtensionActivity>} - * activityData - * @return {!Map<string, !extensions.ActivityGroup>} - */ - function groupActivities(activityData) { - const groupedActivities = new Map(); - - for (const activity of activityData) { - const activityId = activity.activityId; - const activityType = activity.activityType; - const count = activity.count; - const pageUrl = activity.pageUrl; - - const isContentScript = activityType === - chrome.activityLogPrivate.ExtensionActivityType.CONTENT_SCRIPT; - const isWebRequest = activityType === - chrome.activityLogPrivate.ExtensionActivityType.WEB_REQUEST; - - let activityGroupKeys = [activity.apiCall]; - if (isContentScript) { - activityGroupKeys = getActivityGroupKeysForContentScript_(activity); - } else if (isWebRequest) { - activityGroupKeys = getActivityGroupKeysForWebRequest_(activity); - } + const webRequest = /** @type {!Object} */ (JSON.parse(other.webRequest)); + assert(typeof webRequest === 'object', 'Invalid API data'); + + // If there is extra information in the other.webRequest object, + // construct a group for each consisting of the API call and each object key + // in other.webRequest. Otherwise we default to just the API call. + return Object.keys(webRequest).length === 0 ? + [apiCall] : + Object.keys(webRequest).map(field => `${apiCall} (${field})`); +} + +/** + * Group activity log entries by a key determined from each entry. Usually + * this would be the activity's API call though content script and web + * requests have different keys. We currently assume that every API call + * matches to one activity type. + * @param {!Array<!chrome.activityLogPrivate.ExtensionActivity>} + * activityData + * @return {!Map<string, !ActivityGroup>} + */ +function groupActivities(activityData) { + const groupedActivities = new Map(); + + for (const activity of activityData) { + const activityId = activity.activityId; + const activityType = activity.activityType; + const count = activity.count; + const pageUrl = activity.pageUrl; + + const isContentScript = activityType === + chrome.activityLogPrivate.ExtensionActivityType.CONTENT_SCRIPT; + const isWebRequest = activityType === + chrome.activityLogPrivate.ExtensionActivityType.WEB_REQUEST; + + let activityGroupKeys = [activity.apiCall]; + if (isContentScript) { + activityGroupKeys = getActivityGroupKeysForContentScript_(activity); + } else if (isWebRequest) { + activityGroupKeys = getActivityGroupKeysForWebRequest_(activity); + } - for (const key of activityGroupKeys) { - if (!groupedActivities.has(key)) { - const activityGroup = { - activityIds: new Set([activityId]), - key, - count, - activityType, - countsByUrl: pageUrl ? new Map([[pageUrl, count]]) : new Map(), - expanded: false, - }; - groupedActivities.set(key, activityGroup); - } else { - const activityGroup = groupedActivities.get(key); - activityGroup.activityIds.add(activityId); - activityGroup.count += count; - - if (pageUrl) { - const currentCount = activityGroup.countsByUrl.get(pageUrl) || 0; - activityGroup.countsByUrl.set(pageUrl, currentCount + count); - } + for (const key of activityGroupKeys) { + if (!groupedActivities.has(key)) { + const activityGroup = { + activityIds: new Set([activityId]), + key, + count, + activityType, + countsByUrl: pageUrl ? new Map([[pageUrl, count]]) : new Map(), + expanded: false, + }; + groupedActivities.set(key, activityGroup); + } else { + const activityGroup = groupedActivities.get(key); + activityGroup.activityIds.add(activityId); + activityGroup.count += count; + + if (pageUrl) { + const currentCount = activityGroup.countsByUrl.get(pageUrl) || 0; + activityGroup.countsByUrl.set(pageUrl, currentCount + count); } } } - - return groupedActivities; } - /** - * Sort activities by the total count for each activity group key. Resolve - * ties by the alphabetical order of the key. - * @param {!Map<string, !extensions.ActivityGroup>} groupedActivities - * @return {!Array<!extensions.ActivityGroup>} - */ - function sortActivitiesByCallCount(groupedActivities) { - return Array.from(groupedActivities.values()).sort((a, b) => { - if (a.count != b.count) { - return b.count - a.count; - } - if (a.key < b.key) { - return -1; - } - if (a.key > b.key) { - return 1; - } - return 0; - }); - } + return groupedActivities; +} + +/** + * Sort activities by the total count for each activity group key. Resolve + * ties by the alphabetical order of the key. + * @param {!Map<string, !ActivityGroup>} groupedActivities + * @return {!Array<!ActivityGroup>} + */ +function sortActivitiesByCallCount(groupedActivities) { + return Array.from(groupedActivities.values()).sort((a, b) => { + if (a.count != b.count) { + return b.count - a.count; + } + if (a.key < b.key) { + return -1; + } + if (a.key > b.key) { + return 1; + } + return 0; + }); +} - const ActivityLogHistory = Polymer({ - is: 'activity-log-history', - - properties: { - /** @type {!string} */ - extensionId: String, - - /** @type {!extensions.ActivityLogDelegate} */ - delegate: Object, - - /** - * An array representing the activity log. Stores activities grouped by - * API call or content script name sorted in descending order of the call - * count. - * @private {!Array<!extensions.ActivityGroup>} - */ - activityData_: { - type: Array, - value: () => [], - }, - - /** @private {extensions.ActivityLogPageState} */ - pageState_: { - type: String, - value: ActivityLogPageState.LOADING, - }, - - /** @private */ - lastSearch_: { - type: String, - value: '', - }, - }, +Polymer({ + is: 'activity-log-history', - listeners: { - 'delete-activity-log-item': 'deleteItem_', - }, + _template: html`{__html_template__}`, - /** - * A promise resolver for any external files waiting for the - * GetExtensionActivity API call to finish. - * Currently only used for extension_settings_browsertest.cc - * @private {PromiseResolver} - */ - dataFetchedResolver_: null, + properties: { + /** @type {!string} */ + extensionId: String, - /** - * The stringified API response from the activityLogPrivate API with - * individual activities sorted in ascending order by timestamp; used for - * exporting the activity log. - * @private {string} - */ - rawActivities_: '', + /** @type {!ActivityLogDelegate} */ + delegate: Object, /** - * Expose only the promise of dataFetchedResolver_. - * @return {!Promise<void>} + * An array representing the activity log. Stores activities grouped by + * API call or content script name sorted in descending order of the call + * count. + * @private {!Array<!ActivityGroup>} */ - whenDataFetched: function() { - return this.dataFetchedResolver_.promise; + activityData_: { + type: Array, + value: () => [], }, - /** @override */ - attached: function() { - this.dataFetchedResolver_ = new PromiseResolver(); - this.refreshActivities_(); + /** @private {ActivityLogPageState} */ + pageState_: { + type: String, + value: ActivityLogPageState.LOADING, }, - /** - * @private - * @return {boolean} - */ - shouldShowEmptyActivityLogMessage_: function() { - return this.pageState_ === ActivityLogPageState.LOADED && - this.activityData_.length === 0; + /** @private */ + lastSearch_: { + type: String, + value: '', }, + }, - /** - * @private - * @return {boolean} - */ - shouldShowLoadingMessage_: function() { - return this.pageState_ === ActivityLogPageState.LOADING; - }, + listeners: { + 'delete-activity-log-item': 'deleteItem_', + }, - /** - * @private - * @return {boolean} - */ - shouldShowActivities_: function() { - return this.pageState_ === ActivityLogPageState.LOADED && - this.activityData_.length > 0; - }, + /** + * A promise resolver for any external files waiting for the + * GetExtensionActivity API call to finish. + * Currently only used for extension_settings_browsertest.cc + * @private {PromiseResolver} + */ + dataFetchedResolver_: null, - /** @private */ - onClearActivitiesClick_: function() { - this.delegate.deleteActivitiesFromExtension(this.extensionId).then(() => { - this.processActivities_([]); - }); - }, + /** + * The stringified API response from the activityLogPrivate API with + * individual activities sorted in ascending order by timestamp; used for + * exporting the activity log. + * @private {string} + */ + rawActivities_: '', - /** @private */ - onMoreActionsClick_: function() { - this.$$('cr-action-menu').showAt(assert(this.$$('cr-icon-button'))); - }, + /** + * Expose only the promise of dataFetchedResolver_. + * @return {!Promise<void>} + */ + whenDataFetched: function() { + return this.dataFetchedResolver_.promise; + }, - /** - * @private - * @param {boolean} expanded - */ - expandItems_: function(expanded) { - // Do not use .filter here as we need the original index of the item - // in |activityData_|. - this.activityData_.forEach((item, index) => { - if (item.countsByUrl.size > 0) { - this.set(`activityData_.${index}.expanded`, expanded); - } - }); - this.$$('cr-action-menu').close(); - }, + /** @override */ + attached: function() { + this.dataFetchedResolver_ = new PromiseResolver(); + this.refreshActivities_(); + }, - /** @private */ - onExpandAllClick_: function() { - this.expandItems_(true); - }, + /** + * @private + * @return {boolean} + */ + shouldShowEmptyActivityLogMessage_: function() { + return this.pageState_ === ActivityLogPageState.LOADED && + this.activityData_.length === 0; + }, - /** @private */ - onCollapseAllClick_: function() { - this.expandItems_(false); - }, + /** + * @private + * @return {boolean} + */ + shouldShowLoadingMessage_: function() { + return this.pageState_ === ActivityLogPageState.LOADING; + }, - /** @private */ - onExportClick_: function() { - const fileName = `exported_activity_log_${this.extensionId}.json`; - this.delegate.downloadActivities(this.rawActivities_, fileName); - }, + /** + * @private + * @return {boolean} + */ + shouldShowActivities_: function() { + return this.pageState_ === ActivityLogPageState.LOADED && + this.activityData_.length > 0; + }, + + /** @private */ + onClearActivitiesClick_: function() { + this.delegate.deleteActivitiesFromExtension(this.extensionId).then(() => { + this.processActivities_([]); + }); + }, - /** - * @private - * @param {!CustomEvent<!Array<string>>} e - */ - deleteItem_: function(e) { - const activityIds = e.detail; - this.delegate.deleteActivitiesById(activityIds).then(() => { - // It is possible for multiple activities displayed to have the same - // underlying activity ID. This happens when we split content script and - // web request activities by fields other than their API call. For - // consistency, we will re-fetch the activity log. - this.refreshActivities_(); - }); - }, + /** @private */ + onMoreActionsClick_: function() { + this.$$('cr-action-menu').showAt(assert(this.$$('cr-icon-button'))); + }, - /** - * @private - * @param {!Array<!chrome.activityLogPrivate.ExtensionActivity>} - * activityData - */ - processActivities_: function(activityData) { - this.pageState_ = ActivityLogPageState.LOADED; - - // Sort |activityData| in ascending order based on the activity's - // timestamp; Used for |this.encodedRawActivities|. - activityData.sort((a, b) => a.time - b.time); - this.rawActivities_ = JSON.stringify(activityData); - - this.activityData_ = - sortActivitiesByCallCount(groupActivities(activityData)); - if (!this.dataFetchedResolver_.isFulfilled) { - this.dataFetchedResolver_.resolve(); + /** + * @private + * @param {boolean} expanded + */ + expandItems_: function(expanded) { + // Do not use .filter here as we need the original index of the item + // in |activityData_|. + this.activityData_.forEach((item, index) => { + if (item.countsByUrl.size > 0) { + this.set(`activityData_.${index}.expanded`, expanded); } - }, + }); + this.$$('cr-action-menu').close(); + }, - /** - * @private - * @return {!Promise<void>} - */ - refreshActivities_: function() { - if (this.lastSearch_ === '') { - return this.getActivityLog_(); - } + /** @private */ + onExpandAllClick_: function() { + this.expandItems_(true); + }, - return this.getFilteredActivityLog_(this.lastSearch_); - }, + /** @private */ + onCollapseAllClick_: function() { + this.expandItems_(false); + }, - /** - * @private - * @return {!Promise<void>} - */ - getActivityLog_: function() { - this.pageState_ = ActivityLogPageState.LOADING; - return this.delegate.getExtensionActivityLog(this.extensionId) - .then(result => { - this.processActivities_(result.activities); - }); - }, + /** @private */ + onExportClick_: function() { + const fileName = `exported_activity_log_${this.extensionId}.json`; + this.delegate.downloadActivities(this.rawActivities_, fileName); + }, - /** - * @private - * @param {string} searchTerm - * @return {!Promise<void>} - */ - getFilteredActivityLog_: function(searchTerm) { - this.pageState_ = ActivityLogPageState.LOADING; - return this.delegate - .getFilteredExtensionActivityLog(this.extensionId, searchTerm) - .then(result => { - this.processActivities_(result.activities); - }); - }, + /** + * @private + * @param {!CustomEvent<!Array<string>>} e + */ + deleteItem_: function(e) { + const activityIds = e.detail; + this.delegate.deleteActivitiesById(activityIds).then(() => { + // It is possible for multiple activities displayed to have the same + // underlying activity ID. This happens when we split content script and + // web request activities by fields other than their API call. For + // consistency, we will re-fetch the activity log. + this.refreshActivities_(); + }); + }, - /** - * @private - * @param {!CustomEvent<string>} e - */ - onSearchChanged_: function(e) { - // Remove all whitespaces from the search term, as API call names and - // urls should not contain any whitespace. As of now, only single term - // search queries are allowed. - const searchTerm = e.detail.replace(/\s+/g, ''); - if (searchTerm === this.lastSearch_) { - return; - } + /** + * @private + * @param {!Array<!chrome.activityLogPrivate.ExtensionActivity>} + * activityData + */ + processActivities_: function(activityData) { + this.pageState_ = ActivityLogPageState.LOADED; + + // Sort |activityData| in ascending order based on the activity's + // timestamp; Used for |this.encodedRawActivities|. + activityData.sort((a, b) => a.time - b.time); + this.rawActivities_ = JSON.stringify(activityData); + + this.activityData_ = + sortActivitiesByCallCount(groupActivities(activityData)); + if (!this.dataFetchedResolver_.isFulfilled) { + this.dataFetchedResolver_.resolve(); + } + }, - this.lastSearch_ = searchTerm; - this.refreshActivities_(); - }, - }); + /** + * @private + * @return {!Promise<void>} + */ + refreshActivities_: function() { + if (this.lastSearch_ === '') { + return this.getActivityLog_(); + } + + return this.getFilteredActivityLog_(this.lastSearch_); + }, + + /** + * @private + * @return {!Promise<void>} + */ + getActivityLog_: function() { + this.pageState_ = ActivityLogPageState.LOADING; + return this.delegate.getExtensionActivityLog(this.extensionId) + .then(result => { + this.processActivities_(result.activities); + }); + }, + + /** + * @private + * @param {string} searchTerm + * @return {!Promise<void>} + */ + getFilteredActivityLog_: function(searchTerm) { + this.pageState_ = ActivityLogPageState.LOADING; + return this.delegate + .getFilteredExtensionActivityLog(this.extensionId, searchTerm) + .then(result => { + this.processActivities_(result.activities); + }); + }, + + /** + * @private + * @param {!CustomEvent<string>} e + */ + onSearchChanged_: function(e) { + // Remove all whitespaces from the search term, as API call names and + // urls should not contain any whitespace. As of now, only single term + // search queries are allowed. + const searchTerm = e.detail.replace(/\s+/g, ''); + if (searchTerm === this.lastSearch_) { + return; + } - return { - ActivityLogDelegate: ActivityLogDelegate, - ActivityLogHistory: ActivityLogHistory, - ActivityLogPageState: ActivityLogPageState, - }; + this.lastSearch_ = searchTerm; + this.refreshActivities_(); + }, }); diff --git a/chromium/chrome/browser/resources/extensions/activity_log/activity_log_history_item.html b/chromium/chrome/browser/resources/extensions/activity_log/activity_log_history_item.html index 5ed47ab1996..2315557edd3 100644 --- a/chromium/chrome/browser/resources/extensions/activity_log/activity_log_history_item.html +++ b/chromium/chrome/browser/resources/extensions/activity_log/activity_log_history_item.html @@ -1,117 +1,102 @@ -<link rel="import" href="chrome://resources/html/polymer.html"> +<style include="cr-icons cr-shared-style shared-style"> + :host { + border-top: var(--cr-separator-line); + display: block; + /* Unequal padding on left/right side as the cr-icon-button's width is + * greater than the delete icon's width. */ + padding-bottom: 8px; + padding-inline-end: 8px; + padding-inline-start: var(--cr-section-padding); + padding-top: 8px; + } -<link rel="import" href="chrome://resources/cr_elements/cr_expand_button/cr_expand_button.html"> -<link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html"> -<link rel="import" href="chrome://resources/cr_elements/cr_icons_css.html"> -<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html"> -<link rel="import" href="chrome://resources/html/cr.html"> -<link rel="import" href="../shared_style.html"> -<link rel="import" href="../shared_vars.html"> + #activity-item-main-row { + align-items: center; + display: flex; + flex-direction: row; + /* Match separator height. */ + min-height: calc(var(--cr-section-min-height) - var(--separator-gap)); + } -<dom-module id="activity-log-history-item"> - <template> - <style include="cr-icons cr-shared-style shared-style"> - :host { - border-top: var(--cr-separator-line); - display: block; - /* Unequal padding on left/right side as the cr-icon-button's width is - * greater than the delete icon's width. */ - padding-bottom: 8px; - padding-inline-end: 8px; - padding-inline-start: var(--cr-section-padding); - padding-top: 8px; - } + #activity-item-main-row .separator { + margin: 0 calc( + var(--cr-section-padding) + var(--cr-icon-ripple-margin)); + } - #activity-item-main-row { - align-items: center; - display: flex; - flex-direction: row; - /* Match separator height. */ - min-height: calc(var(--cr-section-min-height) - var(--separator-gap)); - } + #activity-item-main-row cr-expand-button { + margin-inline-end: 6px; + } - #activity-item-main-row .separator { - margin: 0 calc( - var(--cr-section-padding) + var(--cr-icon-ripple-margin)); - } + #activity-call-and-count { + display: flex; + flex: 1; + flex-direction: row; + margin-inline-end: auto; + max-width: var(--activity-log-call-and-count-width); + } - #activity-item-main-row cr-expand-button { - margin-inline-end: 6px; - } + #activity-delete { + margin: 0; + } - #activity-call-and-count { - display: flex; - flex: 1; - flex-direction: row; - margin-inline-end: auto; - max-width: var(--activity-log-call-and-count-width); - } + #activity-type { + flex: 0 var(--activity-type-width); + } - #activity-delete { - margin: 0; - } + #activity-key { + flex: 1; + margin-inline-start: 10px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } - #activity-type { - flex: 0 var(--activity-type-width); - } + #activity-count { + flex: 0 var(--activity-count-width); + text-align: end; + } - #activity-key { - flex: 1; - margin-inline-start: 10px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } + .page-url { + display: flex; + flex-direction: row; + margin-bottom: 10px; + max-width: var(--activity-log-call-and-count-width); + } - #activity-count { - flex: 0 var(--activity-count-width); - text-align: end; - } - - .page-url { - display: flex; - flex-direction: row; - margin-bottom: 10px; - max-width: var(--activity-log-call-and-count-width); - } - - .page-url-link { - flex-grow: 1; - margin-inline-end: 20px; - margin-inline-start: 16px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } - </style> - <div actionable$="[[isExpandable_]]" - id="activity-item-main-row" - on-click="onExpandTap_"> - <div id="activity-call-and-count"> - <span id="activity-type">[[data.activityType]]</span> - <span id="activity-key" title="[[data.key]]">[[data.key]]</span> - <span id="activity-count">[[data.count]]</span> - </div> - <cr-expand-button expanded="{{data.expanded}}" - hidden$="[[!isExpandable_]]"> - </cr-expand-button> - <div class="separator" hidden$="[[!isExpandable_]]"></div> - <cr-icon-button id="activity-delete" class="icon-delete-gray" - aria-describedby="api-call" aria-label="$i18n{clearEntry}" - on-click="onDeleteTap_"></cr-icon-button> - </div> - <div id="page-url-list" hidden$="[[!data.expanded]]"> - <template is="dom-repeat" items="[[getPageUrls_(data)]]"> - <div class="page-url"> - <a class="page-url-link" href="[[item.page]]" target="_blank" - title="[[item.page]]">[[item.page]]</a> - <span class="page-url-count" - hidden$="[[!shouldShowPageUrlCount_(data)]]"> - [[item.count]] - </span> - </div> - </template> + .page-url-link { + flex-grow: 1; + margin-inline-end: 20px; + margin-inline-start: 16px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } +</style> +<div actionable$="[[isExpandable_]]" + id="activity-item-main-row" + on-click="onExpandTap_"> + <div id="activity-call-and-count"> + <span id="activity-type">[[data.activityType]]</span> + <span id="activity-key" title="[[data.key]]">[[data.key]]</span> + <span id="activity-count">[[data.count]]</span> + </div> + <cr-expand-button expanded="{{data.expanded}}" + hidden$="[[!isExpandable_]]"> + </cr-expand-button> + <div class="separator" hidden$="[[!isExpandable_]]"></div> + <cr-icon-button id="activity-delete" class="icon-delete-gray" + aria-describedby="api-call" aria-label="$i18n{clearEntry}" + on-click="onDeleteTap_"></cr-icon-button> +</div> +<div id="page-url-list" hidden$="[[!data.expanded]]"> + <template is="dom-repeat" items="[[getPageUrls_(data)]]"> + <div class="page-url"> + <a class="page-url-link" href="[[item.page]]" target="_blank" + title="[[item.page]]">[[item.page]]</a> + <span class="page-url-count" + hidden$="[[!shouldShowPageUrlCount_(data)]]"> + [[item.count]] + </span> </div> </template> - <script src="activity_log_history_item.js"></script> -</dom-module> +</div> diff --git a/chromium/chrome/browser/resources/extensions/activity_log/activity_log_history_item.js b/chromium/chrome/browser/resources/extensions/activity_log/activity_log_history_item.js index f91b27f95e4..1ed2d728fcd 100644 --- a/chromium/chrome/browser/resources/extensions/activity_log/activity_log_history_item.js +++ b/chromium/chrome/browser/resources/extensions/activity_log/activity_log_history_item.js @@ -2,103 +2,104 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -cr.define('extensions', function() { - 'use strict'; +import 'chrome://resources/cr_elements/cr_expand_button/cr_expand_button.m.js'; +import 'chrome://resources/cr_elements/cr_icon_button/cr_icon_button.m.js'; +import 'chrome://resources/cr_elements/cr_icons_css.m.js'; +import 'chrome://resources/cr_elements/shared_vars_css.m.js'; +import '../shared_style.js'; +import '../shared_vars.js'; - /** - * @typedef {{ - * activityIds: !Set<string>, - * key: string, - * count: number, - * activityType: !chrome.activityLogPrivate.ExtensionActivityFilter, - * countsByUrl: !Map<string, number>, - * expanded: boolean - * }} - */ - let ActivityGroup; - - /** - * A struct used to describe each url and its associated counts. The id is - * unique for each item in the list of URLs and is used for the tooltip. - * @typedef {{ - * page: string, - * count: number - * }} - */ - let PageUrlItem; +import {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; - const ActivityLogHistoryItem = Polymer({ - is: 'activity-log-history-item', +/** + * @typedef {{ + * activityIds: !Set<string>, + * key: string, + * count: number, + * activityType: !chrome.activityLogPrivate.ExtensionActivityFilter, + * countsByUrl: !Map<string, number>, + * expanded: boolean + * }} + */ +export let ActivityGroup; - properties: { - /** - * The underlying ActivityGroup that provides data for the - * ActivityLogItem displayed. - * @type {!extensions.ActivityGroup} - */ - data: Object, +/** + * A struct used to describe each url and its associated counts. The id is + * unique for each item in the list of URLs and is used for the tooltip. + * @typedef {{ + * page: string, + * count: number + * }} + */ +let PageUrlItem; - /** @private */ - isExpandable_: { - type: Boolean, - computed: 'computeIsExpandable_(data.countsByUrl)', - }, - }, +Polymer({ + is: 'activity-log-history-item', - /** - * @private - * @return {boolean} - */ - computeIsExpandable_: function() { - return this.data.countsByUrl.size > 0; - }, + _template: html`{__html_template__}`, + properties: { /** - * Sort the page URLs by the number of times it was associated with the key - * for this ActivityGroup (API call or content script invocation.) Resolve - * ties by the alphabetical order of the page URL. - * @private - * @return {!Array<PageUrlItem>} + * The underlying ActivityGroup that provides data for the + * ActivityLogItem displayed. + * @type {!ActivityGroup} */ - getPageUrls_: function() { - return Array.from(this.data.countsByUrl.entries()) - .map(e => ({page: e[0], count: e[1]})) - .sort(function(a, b) { - if (a.count != b.count) { - return b.count - a.count; - } - return a.page < b.page ? -1 : (a.page > b.page ? 1 : 0); - }); - }, + data: Object, /** @private */ - onDeleteTap_: function(e) { - e.stopPropagation(); - this.fire( - 'delete-activity-log-item', - Array.from(this.data.activityIds.values())); + isExpandable_: { + type: Boolean, + computed: 'computeIsExpandable_(data.countsByUrl)', }, + }, - /** @private */ - onExpandTap_: function() { - if (this.isExpandable_) { - this.set('data.expanded', !this.data.expanded); - } - }, + /** + * @private + * @return {boolean} + */ + computeIsExpandable_: function() { + return this.data.countsByUrl.size > 0; + }, - /** - * Show the call count for a particular page URL if more than one page - * URL is associated with the key for this ActivityGroup. - * @private - * @return {boolean} - */ - shouldShowPageUrlCount_: function() { - return this.data.countsByUrl.size > 1; - }, - }); + /** + * Sort the page URLs by the number of times it was associated with the key + * for this ActivityGroup (API call or content script invocation.) Resolve + * ties by the alphabetical order of the page URL. + * @private + * @return {!Array<PageUrlItem>} + */ + getPageUrls_: function() { + return Array.from(this.data.countsByUrl.entries()) + .map(e => ({page: e[0], count: e[1]})) + .sort(function(a, b) { + if (a.count != b.count) { + return b.count - a.count; + } + return a.page < b.page ? -1 : (a.page > b.page ? 1 : 0); + }); + }, + + /** @private */ + onDeleteTap_: function(e) { + e.stopPropagation(); + this.fire( + 'delete-activity-log-item', Array.from(this.data.activityIds.values())); + }, + + /** @private */ + onExpandTap_: function() { + if (this.isExpandable_) { + this.set('data.expanded', !this.data.expanded); + } + }, - return { - ActivityLogHistoryItem: ActivityLogHistoryItem, - ActivityGroup: ActivityGroup, - }; + /** + * Show the call count for a particular page URL if more than one page + * URL is associated with the key for this ActivityGroup. + * @private + * @return {boolean} + */ + shouldShowPageUrlCount_: function() { + return this.data.countsByUrl.size > 1; + }, }); diff --git a/chromium/chrome/browser/resources/extensions/activity_log/activity_log_stream.html b/chromium/chrome/browser/resources/extensions/activity_log/activity_log_stream.html index 0d78032b425..fdeef54f4de 100644 --- a/chromium/chrome/browser/resources/extensions/activity_log/activity_log_stream.html +++ b/chromium/chrome/browser/resources/extensions/activity_log/activity_log_stream.html @@ -1,92 +1,78 @@ -<link rel="import" href="chrome://resources/html/polymer.html"> +<style include="shared-style"> + :host { + --activity-log-call-and-time-width: 575px; + --activity-type-width: 85px; + --activity-time-width: 100px; -<link rel="import" href="chrome://resources/html/cr.html"> -<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html"> -<link rel="import" href="chrome://resources/cr_elements/cr_search_field/cr_search_field.html"> -<link rel="import" href="chrome://resources/polymer/v1_0/iron-list/iron-list.html"> -<link rel="import" href="activity_log_stream_item.html"> -<link rel="import" href="../shared_style.html"> + display: flex; + flex-direction: column; + } -<dom-module id="activity-log-stream"> - <template> - <style include="shared-style"> - :host { - --activity-log-call-and-time-width: 575px; - --activity-type-width: 85px; - --activity-time-width: 100px; - - display: flex; - flex-direction: column; - } + cr-search-field { + align-self: center; + margin-inline-end: auto; + } - cr-search-field { - align-self: center; - margin-inline-end: auto; - } + .activity-table-headings { + width: var(--activity-log-call-and-time-width); + } - .activity-table-headings { - width: var(--activity-log-call-and-time-width); - } + #activity-type { + flex: 0 var(--activity-type-width); + } - #activity-type { - flex: 0 var(--activity-type-width); - } + #activity-key { + flex: 1; + margin-inline-start: 10px; + } - #activity-key { - flex: 1; - margin-inline-start: 10px; - } + #activity-time { + flex: 0 var(--activity-time-width); + text-align: end; + } - #activity-time { - flex: 0 var(--activity-time-width); - text-align: end; - } - - iron-list { - flex: 1; - } - </style> - <div class="activity-subpage-header"> - <cr-search-field label="$i18n{activityLogSearchLabel}" - on-search-changed="onSearchChanged_"> - </cr-search-field > - <cr-button id="toggle-stream-button" on-click="onToggleButtonClick_"> - <span hidden$="[[isStreamOn_]]"> - $i18n{startActivityStream} - </span> - <span hidden$="[[!isStreamOn_]]"> - $i18n{stopActivityStream} - </span> - </cr-button> - <cr-button class="clear-activities-button" on-click="clearStream"> - $i18n{clearActivities} - </cr-button> - </div> - <div id="empty-stream-message" class="activity-message" - hidden$="[[!isStreamEmpty_(activityStream_.length)]]"> - <span id="stream-stopped-message" hidden$="[[isStreamOn_]]"> - $i18n{emptyStreamStopped} - </span> - <span id="stream-started-message" hidden$="[[!isStreamOn_]]"> - $i18n{emptyStreamStarted} - </span> - </div> - <div id="empty-search-message" class="activity-message" - hidden$="[[!shouldShowEmptySearchMessage_( - activityStream_.length, filteredActivityStream_.length)]]"> - $i18n{noSearchResults} - </div> - <div class="activity-table-headings" - hidden$="[[isFilteredStreamEmpty_(filteredActivityStream_.length)]]"> - <span id="activity-type">$i18n{activityLogTypeColumn}</span> - <span id="activity-key">$i18n{activityLogNameColumn}</span> - <span id="activity-time">$i18n{activityLogTimeColumn}</span> - </div> - <iron-list items="[[filteredActivityStream_]]"> - <template> - <activity-log-stream-item data="[[item]]"></activity-log-stream-item> - </template> - </iron-list> + iron-list { + flex: 1; + } +</style> +<div class="activity-subpage-header"> + <cr-search-field label="$i18n{activityLogSearchLabel}" + on-search-changed="onSearchChanged_"> + </cr-search-field > + <cr-button id="toggle-stream-button" on-click="onToggleButtonClick_"> + <span hidden$="[[isStreamOn_]]"> + $i18n{startActivityStream} + </span> + <span hidden$="[[!isStreamOn_]]"> + $i18n{stopActivityStream} + </span> + </cr-button> + <cr-button class="clear-activities-button" on-click="clearStream"> + $i18n{clearActivities} + </cr-button> +</div> +<div id="empty-stream-message" class="activity-message" + hidden$="[[!isStreamEmpty_(activityStream_.length)]]"> + <span id="stream-stopped-message" hidden$="[[isStreamOn_]]"> + $i18n{emptyStreamStopped} + </span> + <span id="stream-started-message" hidden$="[[!isStreamOn_]]"> + $i18n{emptyStreamStarted} + </span> +</div> +<div id="empty-search-message" class="activity-message" + hidden$="[[!shouldShowEmptySearchMessage_( + activityStream_.length, filteredActivityStream_.length)]]"> + $i18n{noSearchResults} +</div> +<div class="activity-table-headings" + hidden$="[[isFilteredStreamEmpty_(filteredActivityStream_.length)]]"> + <span id="activity-type">$i18n{activityLogTypeColumn}</span> + <span id="activity-key">$i18n{activityLogNameColumn}</span> + <span id="activity-time">$i18n{activityLogTimeColumn}</span> +</div> +<iron-list items="[[filteredActivityStream_]]"> + <template> + <activity-log-stream-item data="[[item]]"></activity-log-stream-item> </template> - <script src="activity_log_stream.js"></script> -</dom-module> +</iron-list> diff --git a/chromium/chrome/browser/resources/extensions/activity_log/activity_log_stream.js b/chromium/chrome/browser/resources/extensions/activity_log/activity_log_stream.js index 6d96a757375..7b74faf5714 100644 --- a/chromium/chrome/browser/resources/extensions/activity_log/activity_log_stream.js +++ b/chromium/chrome/browser/resources/extensions/activity_log/activity_log_stream.js @@ -2,230 +2,232 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -cr.define('extensions', function() { - 'use strict'; - - /** @interface */ - class ActivityLogEventDelegate { - /** @return {!ChromeEvent} */ - getOnExtensionActivity() {} +import 'chrome://resources/cr_elements/cr_button/cr_button.m.js'; +import 'chrome://resources/cr_elements/cr_search_field/cr_search_field.m.js'; +import 'chrome://resources/polymer/v3_0/iron-list/iron-list.js'; +import '../shared_style.js'; + +import {assert} from 'chrome://resources/js/assert.m.js'; +import {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; + +import {StreamArgItem, StreamItem} from './activity_log_stream_item.js'; + + +/** @interface */ +export class ActivityLogEventDelegate { + /** @return {!ChromeEvent} */ + getOnExtensionActivity() {} +} + +/** + * Process activity for the stream. In the case of content scripts, we split + * the activity for every script invoked. + * @param {!chrome.activityLogPrivate.ExtensionActivity} + * activity + * @return {!Array<!StreamItem>} + */ +function processActivityForStream(activity) { + const activityType = activity.activityType; + const timestamp = activity.time; + const isContentScript = activityType === + chrome.activityLogPrivate.ExtensionActivityType.CONTENT_SCRIPT; + + const args = isContentScript ? JSON.stringify([]) : activity.args; + + let streamItemNames = [activity.apiCall]; + + // TODO(kelvinjiang): Reuse logic from activity_log_history and refactor + // some of the processing code into a separate file in a follow up CL. + if (isContentScript) { + streamItemNames = activity.args ? JSON.parse(activity.args) : []; + assert(Array.isArray(streamItemNames), 'Invalid data for script names.'); } - /** - * Process activity for the stream. In the case of content scripts, we split - * the activity for every script invoked. - * @param {!chrome.activityLogPrivate.ExtensionActivity} - * activity - * @return {!Array<!extensions.StreamItem>} - */ - function processActivityForStream(activity) { - const activityType = activity.activityType; - const timestamp = activity.time; - const isContentScript = activityType === - chrome.activityLogPrivate.ExtensionActivityType.CONTENT_SCRIPT; + const other = activity.other; + const webRequestInfo = other && other.webRequest; - const args = isContentScript ? JSON.stringify([]) : activity.args; + return streamItemNames.map(name => ({ + args, + argUrl: activity.argUrl, + activityType, + name, + pageUrl: activity.pageUrl, + timestamp, + webRequestInfo, + expanded: false, + })); +} - let streamItemNames = [activity.apiCall]; +Polymer({ + is: 'activity-log-stream', - // TODO(kelvinjiang): Reuse logic from activity_log_history and refactor - // some of the processing code into a separate file in a follow up CL. - if (isContentScript) { - streamItemNames = activity.args ? JSON.parse(activity.args) : []; - assert(Array.isArray(streamItemNames), 'Invalid data for script names.'); - } + _template: html`{__html_template__}`, - const other = activity.other; - const webRequestInfo = other && other.webRequest; - - return streamItemNames.map(name => ({ - args, - argUrl: activity.argUrl, - activityType, - name, - pageUrl: activity.pageUrl, - timestamp, - webRequestInfo, - expanded: false, - })); - } + properties: { + /** @type {string} */ + extensionId: String, + + /** @type {!ActivityLogEventDelegate} */ + delegate: Object, - const ActivityLogStream = Polymer({ - is: 'activity-log-stream', - - properties: { - /** @type {string} */ - extensionId: String, - - /** @type {!extensions.ActivityLogEventDelegate} */ - delegate: Object, - - /** @private */ - isStreamOn_: { - type: Boolean, - value: false, - }, - - /** @private {!Array<!extensions.StreamItem>} */ - activityStream_: { - type: Array, - value: () => [], - }, - - /** @private {!Array<!extensions.StreamItem>} */ - filteredActivityStream_: { - type: Array, - computed: - 'computeFilteredActivityStream_(activityStream_.*, lastSearch_)', - }, - - /** @private */ - lastSearch_: { - type: String, - value: '', - }, + /** @private */ + isStreamOn_: { + type: Boolean, + value: false, }, - listeners: { - 'resize-stream': 'onResizeStream_', + /** @private {!Array<!StreamItem>} */ + activityStream_: { + type: Array, + value: () => [], }, - /** - * Instance of |extensionActivityListener_| bound to |this|. - * @private {!Function} - */ - listenerInstance_: () => {}, - - /** @override */ - attached: function() { - // Since this component is not restamped, this will only be called once - // in its lifecycle. - this.listenerInstance_ = this.extensionActivityListener_.bind(this); - this.startStream(); + /** @private {!Array<!StreamItem>} */ + filteredActivityStream_: { + type: Array, + computed: + 'computeFilteredActivityStream_(activityStream_.*, lastSearch_)', }, /** @private */ - onResizeStream_: function(e) { - this.$$('iron-list').notifyResize(); + lastSearch_: { + type: String, + value: '', }, + }, - clearStream: function() { - this.splice('activityStream_', 0, this.activityStream_.length); - }, + listeners: { + 'resize-stream': 'onResizeStream_', + }, - startStream: function() { - if (this.isStreamOn_) { - return; - } + /** + * Instance of |extensionActivityListener_| bound to |this|. + * @private {!Function} + */ + listenerInstance_: () => {}, + + /** @override */ + attached: function() { + // Since this component is not restamped, this will only be called once + // in its lifecycle. + this.listenerInstance_ = this.extensionActivityListener_.bind(this); + this.startStream(); + }, + + /** @private */ + onResizeStream_: function(e) { + this.$$('iron-list').notifyResize(); + }, + + clearStream: function() { + this.splice('activityStream_', 0, this.activityStream_.length); + }, + + startStream: function() { + if (this.isStreamOn_) { + return; + } - this.isStreamOn_ = true; - this.delegate.getOnExtensionActivity().addListener( - this.listenerInstance_); - }, + this.isStreamOn_ = true; + this.delegate.getOnExtensionActivity().addListener(this.listenerInstance_); + }, - pauseStream: function() { - if (!this.isStreamOn_) { - return; - } + pauseStream: function() { + if (!this.isStreamOn_) { + return; + } - this.delegate.getOnExtensionActivity().removeListener( - this.listenerInstance_); - this.isStreamOn_ = false; - }, + this.delegate.getOnExtensionActivity().removeListener( + this.listenerInstance_); + this.isStreamOn_ = false; + }, - /** @private */ - onToggleButtonClick_: function() { - if (this.isStreamOn_) { - this.pauseStream(); - } else { - this.startStream(); - } - }, + /** @private */ + onToggleButtonClick_: function() { + if (this.isStreamOn_) { + this.pauseStream(); + } else { + this.startStream(); + } + }, - /** - * @private - * @return {boolean} - */ - isStreamEmpty_: function() { - return this.activityStream_.length == 0; - }, + /** + * @private + * @return {boolean} + */ + isStreamEmpty_: function() { + return this.activityStream_.length == 0; + }, - /** - * @private - * @return {boolean} - */ - isFilteredStreamEmpty_: function() { - return this.filteredActivityStream_.length == 0; - }, + /** + * @private + * @return {boolean} + */ + isFilteredStreamEmpty_: function() { + return this.filteredActivityStream_.length == 0; + }, - /** - * @private - * @return {boolean} - */ - shouldShowEmptySearchMessage_: function() { - return !this.isStreamEmpty_() && this.isFilteredStreamEmpty_(); - }, + /** + * @private + * @return {boolean} + */ + shouldShowEmptySearchMessage_: function() { + return !this.isStreamEmpty_() && this.isFilteredStreamEmpty_(); + }, - /** - * @private - * @param {!chrome.activityLogPrivate.ExtensionActivity} activity - */ - extensionActivityListener_: function(activity) { - if (activity.extensionId != this.extensionId) { - return; - } - - this.splice( - 'activityStream_', this.activityStream_.length, 0, - ...processActivityForStream(activity)); - - // Used to update the scrollbar. - this.$$('iron-list').notifyResize(); - }, + /** + * @private + * @param {!chrome.activityLogPrivate.ExtensionActivity} activity + */ + extensionActivityListener_: function(activity) { + if (activity.extensionId != this.extensionId) { + return; + } - /** - * @private - * @param {!CustomEvent<string>} e - */ - onSearchChanged_: function(e) { - // Remove all whitespaces from the search term, as API call names and - // URLs should not contain any whitespace. As of now, only single term - // search queries are allowed. - const searchTerm = e.detail.replace(/\s+/g, '').toLowerCase(); - if (searchTerm === this.lastSearch_) { - return; - } - - this.lastSearch_ = searchTerm; - }, + this.splice( + 'activityStream_', this.activityStream_.length, 0, + ...processActivityForStream(activity)); - /** - * @private - * @return {!Array<!extensions.StreamItem>} - */ - computeFilteredActivityStream_: function() { - if (!this.lastSearch_) { - return this.activityStream_.slice(); - } - - // Match on these properties for each activity. - const propNames = [ - 'name', - 'pageUrl', - 'activityType', - ]; - - return this.activityStream_.filter(act => { - return propNames.some(prop => { - return act[prop] && - act[prop].toLowerCase().includes(this.lastSearch_); - }); - }); - }, - }); + // Used to update the scrollbar. + this.$$('iron-list').notifyResize(); + }, - return { - ActivityLogStream: ActivityLogStream, - ActivityLogEventDelegate: ActivityLogEventDelegate, - }; + /** + * @private + * @param {!CustomEvent<string>} e + */ + onSearchChanged_: function(e) { + // Remove all whitespaces from the search term, as API call names and + // URLs should not contain any whitespace. As of now, only single term + // search queries are allowed. + const searchTerm = e.detail.replace(/\s+/g, '').toLowerCase(); + if (searchTerm === this.lastSearch_) { + return; + } + + this.lastSearch_ = searchTerm; + }, + + /** + * @private + * @return {!Array<!StreamItem>} + */ + computeFilteredActivityStream_: function() { + if (!this.lastSearch_) { + return this.activityStream_.slice(); + } + + // Match on these properties for each activity. + const propNames = [ + 'name', + 'pageUrl', + 'activityType', + ]; + + return this.activityStream_.filter(act => { + return propNames.some(prop => { + return act[prop] && act[prop].toLowerCase().includes(this.lastSearch_); + }); + }); + }, }); diff --git a/chromium/chrome/browser/resources/extensions/activity_log/activity_log_stream_item.html b/chromium/chrome/browser/resources/extensions/activity_log/activity_log_stream_item.html index eca08d5f538..9fbffdac333 100644 --- a/chromium/chrome/browser/resources/extensions/activity_log/activity_log_stream_item.html +++ b/chromium/chrome/browser/resources/extensions/activity_log/activity_log_stream_item.html @@ -1,132 +1,118 @@ -<link rel="import" href="chrome://resources/html/polymer.html"> - -<link rel="import" href="chrome://resources/cr_elements/cr_expand_button/cr_expand_button.html"> -<link rel="import" href="chrome://resources/cr_elements/cr_icons_css.html"> -<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html"> -<link rel="import" href="chrome://resources/html/cr.html"> -<link rel="import" href="../shared_style.html"> -<link rel="import" href="../shared_vars.html"> - -<dom-module id="activity-log-stream-item"> - <template> - <style include="cr-icons cr-shared-style shared-style"> - :host { - border-top: var(--cr-separator-line); - display: block; - padding: 8px var(--cr-section-padding); - } - - cr-expand-button { - --cr-expand-button-disabled-display: none; - - /* Match separator height. */ - height: calc(var(--cr-section-min-height) - var(--separator-gap)); - } - - cr-expand-button[disabled] { - opacity: 1; - } - - #activity-call-and-time { - display: flex; - flex: 1; - flex-direction: row; - margin-inline-end: auto; - max-width: var(--activity-log-call-and-time-width); - } - - #activity-type { - min-width: var(--activity-type-width); - } - - #activity-name { - flex: 1; - margin-inline-start: 10px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } - - #activity-time { - min-width: var(--activity-time-width); - text-align: end; - } - - #expanded-data { - display: flex; - flex-direction: column; - margin-inline-start: 16px; - max-width: var(--activity-log-call-and-time-width); - } - - #page-url-link { - margin-bottom: 10px; - margin-inline-end: auto; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - width: 100%; - } - - #args-list, - #web-request-section { - display: flex; - flex-direction: column; - margin-bottom: 10px; - } - - .expanded-data-heading { - font-weight: 500; - } - - .list-item { - display: flex; - margin-top: 10px; - } - - .index { - min-width: 3em; /* Allow 3 digits of space */ - } - - .arg, - #web-request-details { - overflow: hidden; - overflow-wrap: break-word; - } - - #web-request-details { - margin-top: 10px; - } - </style> - <cr-expand-button expanded="[[data.expanded]]" - disabled="[[!isExpandable_]]" on-click="onExpandClick_"> - <div id="activity-call-and-time"> - <span id="activity-type">[[data.activityType]]</span> - <span id="activity-name" title="[[data.name]]">[[data.name]]</span> - <span id="activity-time">[[getFormattedTime_(data.timeStamp)]]</span> - </div> - </cr-expand-button> - <div id="expanded-data" hidden$="[[!data.expanded]]"> - <a id="page-url-link" href="[[data.pageUrl]]" target="_blank" - hidden$="[[!hasPageUrl_(data.pageUrl)]]" - title="[[data.pageUrl]]">[[data.pageUrl]]</a> - <div id="args-list" hidden$="[[!hasArgs_(argsList_)]]"> - <span class="expanded-data-heading"> - $i18n{activityArgumentsHeading} - </span> - <template is="dom-repeat" items="[[argsList_]]"> - <div class="list-item"> - <span class="index">[[item.index]]</span> - <span class="arg">[[item.arg]]</span> - </div> - </template> - </div> - <div id="web-request-section" - hidden$="[[!hasWebRequestInfo_(data.webRequestInfo)]]"> - <span class="expanded-data-heading">$i18n{webRequestInfoHeading}</span> - <span id="web-request-details">[[data.webRequestInfo]]</span> +<style include="cr-icons cr-shared-style shared-style"> + :host { + border-top: var(--cr-separator-line); + display: block; + padding: 8px var(--cr-section-padding); + } + + cr-expand-button { + --cr-expand-button-disabled-display: none; + + /* Match separator height. */ + height: calc(var(--cr-section-min-height) - var(--separator-gap)); + } + + cr-expand-button[disabled] { + opacity: 1; + } + + #activity-call-and-time { + display: flex; + flex: 1; + flex-direction: row; + margin-inline-end: auto; + max-width: var(--activity-log-call-and-time-width); + } + + #activity-type { + min-width: var(--activity-type-width); + } + + #activity-name { + flex: 1; + margin-inline-start: 10px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + #activity-time { + min-width: var(--activity-time-width); + text-align: end; + } + + #expanded-data { + display: flex; + flex-direction: column; + margin-inline-start: 16px; + max-width: var(--activity-log-call-and-time-width); + } + + #page-url-link { + margin-bottom: 10px; + margin-inline-end: auto; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + width: 100%; + } + + #args-list, + #web-request-section { + display: flex; + flex-direction: column; + margin-bottom: 10px; + } + + .expanded-data-heading { + font-weight: 500; + } + + .list-item { + display: flex; + margin-top: 10px; + } + + .index { + min-width: 3em; /* Allow 3 digits of space */ + } + + .arg, + #web-request-details { + overflow: hidden; + overflow-wrap: break-word; + } + + #web-request-details { + margin-top: 10px; + } +</style> +<cr-expand-button expanded="[[data.expanded]]" + disabled="[[!isExpandable_]]" on-click="onExpandClick_"> + <div id="activity-call-and-time"> + <span id="activity-type">[[data.activityType]]</span> + <span id="activity-name" title="[[data.name]]">[[data.name]]</span> + <span id="activity-time">[[getFormattedTime_(data.timeStamp)]]</span> + </div> +</cr-expand-button> +<div id="expanded-data" hidden$="[[!data.expanded]]"> + <a id="page-url-link" href="[[data.pageUrl]]" target="_blank" + hidden$="[[!hasPageUrl_(data.pageUrl)]]" + title="[[data.pageUrl]]">[[data.pageUrl]]</a> + <div id="args-list" hidden$="[[!hasArgs_(argsList_)]]"> + <span class="expanded-data-heading"> + $i18n{activityArgumentsHeading} + </span> + <template is="dom-repeat" items="[[argsList_]]"> + <div class="list-item"> + <span class="index">[[item.index]]</span> + <span class="arg">[[item.arg]]</span> </div> - </div> - </template> - <script src="activity_log_stream_item.js"></script> -</dom-module> + </template> + </div> + <div id="web-request-section" + hidden$="[[!hasWebRequestInfo_(data.webRequestInfo)]]"> + <span class="expanded-data-heading">$i18n{webRequestInfoHeading}</span> + <span id="web-request-details">[[data.webRequestInfo]]</span> + </div> +</div> diff --git a/chromium/chrome/browser/resources/extensions/activity_log/activity_log_stream_item.js b/chromium/chrome/browser/resources/extensions/activity_log/activity_log_stream_item.js index 9a2e477ee0d..e7025078fbc 100644 --- a/chromium/chrome/browser/resources/extensions/activity_log/activity_log_stream_item.js +++ b/chromium/chrome/browser/resources/extensions/activity_log/activity_log_stream_item.js @@ -2,161 +2,160 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -cr.define('extensions', function() { - 'use strict'; +import 'chrome://resources/cr_elements/cr_expand_button/cr_expand_button.m.js'; +import 'chrome://resources/cr_elements/cr_icons_css.m.js'; +import 'chrome://resources/cr_elements/shared_vars_css.m.js'; +import '../shared_style.js'; +import '../shared_vars.js'; + +import {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; + +/** + * @typedef {{ + * name: string, + * timestamp: number, + * activityType: !chrome.activityLogPrivate.ExtensionActivityFilter, + * pageUrl: string, + * argUrl: string, + * args: string, + * webRequestInfo: (string|undefined), + * expanded: boolean + * }} + */ +export let StreamItem; + +/** + * A struct used to describe each argument for an activity (each item in + * the parsed version of |data.args|). Contains the argument's value itself + * and its index. + * @typedef {{ + * arg: string, + * index: number + * }} + */ +export let StreamArgItem; + +/** + * Placeholder for arg_url that can occur in |StreamItem.args|. Sometimes we + * see this as '\u003Carg_url>' (opening arrow is unicode converted) but + * string comparison with the non-unicode value still returns true so we + * don't need to convert. + * @type {string} + */ +export const ARG_URL_PLACEHOLDER = '<arg_url>'; + +/** + * Regex pattern for |ARG_URL_PLACEHOLDER| for String.replace. A regex of the + * exact string with a global search flag is needed to replace all + * occurrences. + * @type {!RegExp} + */ +const ARG_URL_PLACEHOLDER_REGEX = /"<arg_url>"/g; + +Polymer({ + is: 'activity-log-stream-item', + + _template: html`{__html_template__}`, + + properties: { + /** + * The underlying ActivityGroup that provides data for the + * ActivityLogItem displayed. + * @type {!StreamItem} + */ + data: Object, + + /** @private {!Array<!StreamArgItem>} */ + argsList_: { + type: Array, + computed: 'computeArgsList_(data.args)', + }, + + /** @private */ + isExpandable_: { + type: Boolean, + computed: 'computeIsExpandable_(data)', + }, + }, /** - * @typedef {{ - * name: string, - * timestamp: number, - * activityType: !chrome.activityLogPrivate.ExtensionActivityFilter, - * pageUrl: string, - * argUrl: string, - * args: string, - * webRequestInfo: (string|undefined), - * expanded: boolean - * }} + * @private + * @return {boolean} */ - let StreamItem; + computeIsExpandable_: function() { + return this.hasPageUrl_() || this.hasArgs_() || this.hasWebRequestInfo_(); + }, /** - * A struct used to describe each argument for an activity (each item in - * the parsed version of |data.args|). Contains the argument's value itself - * and its index. - * @typedef {{ - * arg: string, - * index: number - * }} + * @private + * @return {string} */ - let StreamArgItem; + getFormattedTime_: function() { + // Format the activity's time to HH:MM:SS.mmm format. Use ToLocaleString + // for HH:MM:SS and padLeft for milliseconds. + const activityDate = new Date(this.data.timestamp); + const timeString = activityDate.toLocaleTimeString(undefined, { + hour12: false, + hour: '2-digit', + minute: '2-digit', + second: '2-digit', + }); + + const ms = activityDate.getMilliseconds().toString().padStart(3, '0'); + return `${timeString}.${ms}`; + }, /** - * Placeholder for arg_url that can occur in |StreamItem.args|. Sometimes we - * see this as '\u003Carg_url>' (opening arrow is unicode converted) but - * string comparison with the non-unicode value still returns true so we - * don't need to convert. - * @type {string} + * @private + * @return {boolean} */ - const ARG_URL_PLACEHOLDER = '<arg_url>'; + hasPageUrl_: function() { + return !!this.data.pageUrl; + }, /** - * Regex pattern for |ARG_URL_PLACEHOLDER| for String.replace. A regex of the - * exact string with a global search flag is needed to replace all - * occurrences. - * @type {!RegExp} + * @private + * @return {boolean} */ - const ARG_URL_PLACEHOLDER_REGEX = /"<arg_url>"/g; - - const ActivityLogStreamItem = Polymer({ - is: 'activity-log-stream-item', - - properties: { - /** - * The underlying ActivityGroup that provides data for the - * ActivityLogItem displayed. - * @type {!extensions.StreamItem} - */ - data: Object, - - /** @private {!Array<!extensions.StreamArgItem>} */ - argsList_: { - type: Array, - computed: 'computeArgsList_(data.args)', - }, - - /** @private */ - isExpandable_: { - type: Boolean, - computed: 'computeIsExpandable_(data)', - }, - }, + hasArgs_: function() { + return this.argsList_.length > 0; + }, - /** - * @private - * @return {boolean} - */ - computeIsExpandable_: function() { - return this.hasPageUrl_() || this.hasArgs_() || this.hasWebRequestInfo_(); - }, - - /** - * @private - * @return {string} - */ - getFormattedTime_: function() { - // Format the activity's time to HH:MM:SS.mmm format. Use ToLocaleString - // for HH:MM:SS and padLeft for milliseconds. - const activityDate = new Date(this.data.timestamp); - const timeString = activityDate.toLocaleTimeString(undefined, { - hour12: false, - hour: '2-digit', - minute: '2-digit', - second: '2-digit', - }); - - const ms = activityDate.getMilliseconds().toString().padStart(3, '0'); - return `${timeString}.${ms}`; - }, - - /** - * @private - * @return {boolean} - */ - hasPageUrl_: function() { - return !!this.data.pageUrl; - }, - - /** - * @private - * @return {boolean} - */ - hasArgs_: function() { - return this.argsList_.length > 0; - }, - - /** - * @private - * @return {boolean} - */ - hasWebRequestInfo_: function() { - return !!this.data.webRequestInfo && this.data.webRequestInfo != '{}'; - }, - - /** - * @private - * @return {!Array<!extensions.StreamArgItem>} - */ - computeArgsList_: function() { - const parsedArgs = JSON.parse(this.data.args); - if (!Array.isArray(parsedArgs)) { - return []; - } - - // Replace occurrences AFTER parsing then stringifying as the JSON - // serializer on the C++ side escapes certain characters such as '<' and - // parsing un-escapes these characters. - // See EscapeSpecialCodePoint in base/json/string_escape.cc. - return parsedArgs.map( - (arg, i) => ({ - arg: JSON.stringify(arg).replace( - ARG_URL_PLACEHOLDER_REGEX, `"${this.data.argUrl}"`), - index: i + 1, - })); - }, + /** + * @private + * @return {boolean} + */ + hasWebRequestInfo_: function() { + return !!this.data.webRequestInfo && this.data.webRequestInfo != '{}'; + }, - /** @private */ - onExpandClick_: function() { - if (this.isExpandable_) { - this.set('data.expanded', !this.data.expanded); - this.fire('resize-stream'); - } - }, - }); - - return { - ActivityLogStreamItem: ActivityLogStreamItem, - StreamItem: StreamItem, - StreamArgItem: StreamArgItem, - ARG_URL_PLACEHOLDER: ARG_URL_PLACEHOLDER, - }; + /** + * @private + * @return {!Array<!StreamArgItem>} + */ + computeArgsList_: function() { + const parsedArgs = JSON.parse(this.data.args); + if (!Array.isArray(parsedArgs)) { + return []; + } + + // Replace occurrences AFTER parsing then stringifying as the JSON + // serializer on the C++ side escapes certain characters such as '<' and + // parsing un-escapes these characters. + // See EscapeSpecialCodePoint in base/json/string_escape.cc. + return parsedArgs.map( + (arg, i) => ({ + arg: JSON.stringify(arg).replace( + ARG_URL_PLACEHOLDER_REGEX, `"${this.data.argUrl}"`), + index: i + 1, + })); + }, + + /** @private */ + onExpandClick_: function() { + if (this.isExpandable_) { + this.set('data.expanded', !this.data.expanded); + this.fire('resize-stream'); + } + }, }); diff --git a/chromium/chrome/browser/resources/extensions/checkup_image.svg b/chromium/chrome/browser/resources/extensions/checkup_image.svg new file mode 100644 index 00000000000..2965cae3980 --- /dev/null +++ b/chromium/chrome/browser/resources/extensions/checkup_image.svg @@ -0,0 +1 @@ +<svg width="270" height="117" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><linearGradient x1="22.739%" y1="50%" x2="50%" y2="50%" id="a"><stop stop-color="#E8EAED" offset="0%"/><stop stop-color="#FFF" offset="100%"/></linearGradient><path id="b" d="M0 .195h94.65v14.023H0z"/><path id="d" d="M.062.195h136.716v14.023H.062z"/><path id="f" d="M0 116.114h217.344V.426H0z"/><path d="M35.238 19.048h-2.857v-7.62a3.808 3.808 0 0 0-3.81-3.809h-7.619V4.762A4.764 4.764 0 0 0 16.19 0a4.764 4.764 0 0 0-4.761 4.762v2.857h-7.62a3.792 3.792 0 0 0-3.79 3.81l-.01 7.238h2.848A5.146 5.146 0 0 1 8 23.81a5.146 5.146 0 0 1-5.143 5.142H.01L0 36.19A3.808 3.808 0 0 0 3.81 40h7.238v-2.857A5.146 5.146 0 0 1 16.19 32a5.146 5.146 0 0 1 5.143 5.143V40h7.238a3.808 3.808 0 0 0 3.81-3.81v-7.619h2.857A4.764 4.764 0 0 0 40 23.81a4.764 4.764 0 0 0-4.762-4.762z" id="h"/><path id="j" d="M0 0h12v12H0z"/></defs><g fill="none" fill-rule="evenodd"><path fill="url(#a)" transform="matrix(-1 0 0 1 57 0)" d="M0 109h57v7H0z"/><g transform="translate(51)"><path d="M.79 104.807V5.166a3.954 3.954 0 0 1 3.95-3.95h15.503v103.59H.79z" fill="#F8F9FA"/><path d="M21.033.426H4.74A4.74 4.74 0 0 0 0 5.166v100.43h21.033V.426zm-1.58 1.58v102.01H1.58V5.167a3.164 3.164 0 0 1 3.16-3.16h14.713z" fill="#E8EAED"/><path d="M7.801 104.807V5.166a3.954 3.954 0 0 1 3.95-3.95h123.73a3.954 3.954 0 0 1 3.95 3.95v99.64H7.801z" fill="#FFF"/><path d="M135.482.426H11.751a4.74 4.74 0 0 0-4.74 4.74v100.43h133.21V5.166a4.74 4.74 0 0 0-4.739-4.74m0 1.58a3.163 3.163 0 0 1 3.16 3.16v98.85H8.592V5.167a3.163 3.163 0 0 1 3.159-3.16h123.731" fill="#E8EAED"/><path d="M4.74 115.324a3.954 3.954 0 0 1-3.95-3.95v-8.493h93.07v12.443H4.74z" fill="#F8F9FA"/><g transform="translate(0 101.896)"><mask id="c" fill="#fff"><use xlink:href="#b"/></mask><path d="M94.65.195H0V9.48a4.74 4.74 0 0 0 4.739 4.74h89.91V.194zm-1.58 1.58v10.863H4.739a3.164 3.164 0 0 1-3.16-3.16V1.776H93.07z" fill="#E8EAED" mask="url(#c)"/></g><path d="M85.367 115.324a3.954 3.954 0 0 1-3.95-3.95v-8.493h135.137v8.493a3.954 3.954 0 0 1-3.95 3.95H85.367z" fill="#FFF"/><g transform="translate(80.565 101.896)"><mask id="e" fill="#fff"><use xlink:href="#d"/></mask><path d="M136.778.195H.062V9.48a4.74 4.74 0 0 0 4.74 4.74h127.237a4.74 4.74 0 0 0 4.74-4.74V.195zm-1.58 1.58v7.704a3.163 3.163 0 0 1-3.159 3.16H4.801a3.164 3.164 0 0 1-3.16-3.16V1.775h133.557z" fill="#E8EAED" mask="url(#e)"/></g><path d="M163.629 109.102h-29.287a2.884 2.884 0 0 1-2.884-2.884v-4.127h35.055v4.127a2.884 2.884 0 0 1-2.884 2.884" fill="#E8EAED"/><mask id="g" fill="#fff"><use xlink:href="#f"/></mask><path fill="#F8F9FA" mask="url(#g)" d="M18.318 94.29h110.597V11.734H18.318z"/><path d="M17.528 95.08h112.177V10.943H17.528V95.08zm1.58-1.58h109.018V12.522H19.108V93.5z" fill="#E8EAED" mask="url(#g)"/></g><g transform="translate(106 32)"><mask id="i" fill="#fff"><use xlink:href="#h"/></mask><use fill="#3C4043" fill-rule="nonzero" xlink:href="#h"/><g mask="url(#i)" fill="#1A73E8"><path d="M-3.81-1.905h46v46h-46z"/></g></g><path d="M162.83 24.905L166 24l-3.17-.906a5.714 5.714 0 0 1-3.924-3.924L158 16l-.906 3.17a5.714 5.714 0 0 1-3.924 3.924L150 24l3.17.905a5.714 5.714 0 0 1 3.924 3.925L158 32l.906-3.17a5.714 5.714 0 0 1 3.924-3.925m9.585 43.548L174 68l-1.585-.453a2.856 2.856 0 0 1-1.962-1.962L170 64l-.453 1.585a2.856 2.856 0 0 1-1.962 1.962L166 68l1.585.453a2.856 2.856 0 0 1 1.962 1.962L170 72l.453-1.585a2.856 2.856 0 0 1 1.962-1.962" fill="#FBBC04"/><g transform="translate(74 80)"><mask id="k" fill="#fff"><use xlink:href="#j"/></mask><path d="M9.623 6.68L12 6l-2.377-.68A4.284 4.284 0 0 1 6.68 2.378L6 0l-.68 2.377A4.284 4.284 0 0 1 2.379 5.32L0 6l2.378.68A4.283 4.283 0 0 1 5.32 9.623L6 12l.68-2.377A4.283 4.283 0 0 1 9.623 6.68" fill="#FBBC04" mask="url(#k)"/></g></g></svg>
\ No newline at end of file diff --git a/chromium/chrome/browser/resources/extensions/checkup_image_dark.svg b/chromium/chrome/browser/resources/extensions/checkup_image_dark.svg new file mode 100644 index 00000000000..b68b1840157 --- /dev/null +++ b/chromium/chrome/browser/resources/extensions/checkup_image_dark.svg @@ -0,0 +1 @@ +<svg width="270" height="117" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><linearGradient x1="23.421%" y1="50%" x2="64.093%" y2="50%" id="a"><stop offset="0%"/><stop stop-color="#28292C" offset="100%"/></linearGradient><path id="b" d="M0 116h218V0H0z"/><path d="M35.238 19.048h-2.857v-7.62a3.808 3.808 0 0 0-3.81-3.809h-7.619V4.762A4.764 4.764 0 0 0 16.19 0a4.764 4.764 0 0 0-4.761 4.762v2.857h-7.62a3.792 3.792 0 0 0-3.79 3.81l-.01 7.238h2.848A5.146 5.146 0 0 1 8 23.81a5.146 5.146 0 0 1-5.143 5.142H.01L0 36.19A3.808 3.808 0 0 0 3.81 40h7.238v-2.857A5.146 5.146 0 0 1 16.19 32a5.146 5.146 0 0 1 5.143 5.143V40h7.238a3.808 3.808 0 0 0 3.81-3.81v-7.619h2.857A4.764 4.764 0 0 0 40 23.81a4.764 4.764 0 0 0-4.762-4.762z" id="d"/><path id="f" d="M0 0h12v12H0z"/></defs><g fill="none" fill-rule="evenodd"><path fill="url(#a)" transform="matrix(-1 0 0 1 57 0)" d="M0 109h57v7H0z"/><g transform="translate(50)"><path d="M2 104V10.793C2 5.945 6.037 2 11 2h9v102H2z" fill="#2A2B2E"/><path d="M21 0H10.5C4.702 0 0 4.702 0 10.5V105h21V0zm-3.5 3.5v98h-14v-91c0-3.859 3.14-7 7-7h7z" fill="#3B3F42"/><path d="M9 104V10.793C9 5.945 12.94 2 17.784 2h112.432C135.06 2 139 5.945 139 10.793V104H9z" fill="#1F2123"/><path d="M130.421 0H17.58C11.738 0 7 4.702 7 10.5V105h134V10.5C141 4.702 136.262 0 130.421 0m0 3.5c3.89 0 7.053 3.141 7.053 7v91H10.526v-91c0-3.859 3.163-7 7.053-7H130.42" fill="#3B3F42"/><path d="M10.75 114C5.925 114 2 109.888 2 104.833V103h91v11H10.75z" fill="#2A2B2E"/><path d="M95 102H0v3.5c0 5.798 4.727 10.5 10.556 10.5H95v-14zm-3.519 3.5v7H10.556c-3.881 0-7.037-3.141-7.037-7H91.48z" fill="#3B3F42"/><path d="M90.816 114c-4.861 0-8.816-4.112-8.816-9.167V103h134v1.833c0 5.055-3.955 9.167-8.816 9.167H90.816z" fill="#1F2123"/><path d="M218 102H81v3.5c0 5.798 4.72 10.5 10.538 10.5h115.924c5.819 0 10.538-4.702 10.538-10.5V102zm-3.513 3.5c0 3.859-3.15 7-7.025 7H91.538c-3.874 0-7.025-3.141-7.025-7h129.974z" fill="#3B3F42"/><path d="M160.61 109h-22.219a6.39 6.39 0 0 1-6.391-6.39V102h35v.61a6.39 6.39 0 0 1-6.39 6.39" fill="#3B3F42"/><mask id="c" fill="#fff"><use xlink:href="#b"/></mask><path fill="#2A2B2E" mask="url(#c)" d="M19 93h109V12H19z"/><path d="M17 95h113V11H17v84zm3.531-3.5H126.47v-77H20.53v77z" fill="#3B3F42" mask="url(#c)"/></g><g transform="translate(106 32)"><mask id="e" fill="#fff"><use xlink:href="#d"/></mask><use fill="#3C4043" fill-rule="nonzero" xlink:href="#d"/><g mask="url(#e)" fill="#1A73E8"><path d="M-3.81-1.905h46v46h-46z"/></g></g><path d="M162.83 24.905L166 24l-3.17-.906a5.714 5.714 0 0 1-3.924-3.924L158 16l-.906 3.17a5.714 5.714 0 0 1-3.924 3.924L150 24l3.17.905a5.714 5.714 0 0 1 3.924 3.925L158 32l.906-3.17a5.714 5.714 0 0 1 3.924-3.925m9.585 43.548L174 68l-1.585-.453a2.856 2.856 0 0 1-1.962-1.962L170 64l-.453 1.585a2.856 2.856 0 0 1-1.962 1.962L166 68l1.585.453a2.856 2.856 0 0 1 1.962 1.962L170 72l.453-1.585a2.856 2.856 0 0 1 1.962-1.962" fill="#FBBC04"/><g transform="translate(74 80)"><mask id="g" fill="#fff"><use xlink:href="#f"/></mask><path d="M9.623 6.68L12 6l-2.377-.68A4.284 4.284 0 0 1 6.68 2.378L6 0l-.68 2.377A4.284 4.284 0 0 1 2.379 5.32L0 6l2.378.68A4.283 4.283 0 0 1 5.32 9.623L6 12l.68-2.377A4.283 4.283 0 0 1 9.623 6.68" fill="#FBBC04" mask="url(#g)"/></g></g></svg>
\ No newline at end of file diff --git a/chromium/chrome/browser/resources/extensions/code_section.html b/chromium/chrome/browser/resources/extensions/code_section.html index b4721b10309..7fc90e97518 100644 --- a/chromium/chrome/browser/resources/extensions/code_section.html +++ b/chromium/chrome/browser/resources/extensions/code_section.html @@ -1,141 +1,127 @@ -<link rel="import" href="chrome://resources/html/polymer.html"> +<style include="cr-hidden-style"> + :host { + --container-bg: white; + --line-bg: var(--paper-grey-300); + --main-color: var(--paper-grey-800); + display: block; + } -<link rel="import" href="chrome://resources/cr_elements/hidden_style_css.html"> -<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html"> -<link rel="import" href="chrome://resources/html/cr.html"> -<link rel="import" href="chrome://resources/html/i18n_behavior.html"> -<link rel="import" href="chrome://resources/html/load_time_data.html"> -<link rel="import" href="chrome://resources/polymer/v1_0/paper-styles/color.html"> + @media (prefers-color-scheme: dark) { + :host { + --container-bg: rgba(0, 0, 0, .4); + --line-bg: var(--google-grey-800); + --main-color: var(--cr-primary-text-color); + } + } -<dom-module id="extensions-code-section"> - <template> - <style include="cr-hidden-style"> - :host { - --container-bg: white; - --line-bg: var(--paper-grey-300); - --main-color: var(--paper-grey-800); - display: block; - } + #scroll-container { + background: var(--container-bg); + height: 100%; + overflow: auto; + position: relative; + } - @media (prefers-color-scheme: dark) { - :host { - --container-bg: rgba(0, 0, 0, .4); - --line-bg: var(--google-grey-800); - --main-color: var(--cr-primary-text-color); - } - } + @media (prefers-color-scheme: light) { + #scroll-container { + border: 1px solid var(--paper-grey-500); + } + } - #scroll-container { - background: var(--container-bg); - height: 100%; - overflow: auto; - position: relative; - } + #main { + color: var(--main-color); + display: flex; + font-family: monospace; + min-height: 100%; + } - @media (prefers-color-scheme: light) { - #scroll-container { - border: 1px solid var(--paper-grey-500); - } - } + #line-numbers { + background: var(--line-bg); + display: flex; + flex-direction: column; + padding: 0 8px; + text-align: end; + } - #main { - color: var(--main-color); - display: flex; - font-family: monospace; - min-height: 100%; - } + @media (prefers-color-scheme: light) { + #line-numbers { + border-inline-end: 1px solid var(--paper-grey-500); + } + } - #line-numbers { - background: var(--line-bg); - display: flex; - flex-direction: column; - padding: 0 8px; - text-align: end; - } + #source { + display: flex; + flex-direction: column; + margin-inline-start: 4px; + } - @media (prefers-color-scheme: light) { - #line-numbers { - border-inline-end: 1px solid var(--paper-grey-500); - } - } + #line-numbers span, + #source span { + white-space: pre; + } - #source { - display: flex; - flex-direction: column; - margin-inline-start: 4px; - } + #no-code { + text-align: center; + } - #line-numbers span, - #source span { - white-space: pre; - } + @media (prefers-color-scheme: light) { + #no-code { + color: var(--paper-grey-800); + } - #no-code { - text-align: center; - } + .more-code { + color: var(--paper-grey-500); + } + } - @media (prefers-color-scheme: light) { - #no-code { - color: var(--paper-grey-800); - } + #highlight-description { + height: 0; + overflow: hidden; + } - .more-code { - color: var(--paper-grey-500); - } - } - - #highlight-description { - height: 0; - overflow: hidden; - } - - @media (prefers-color-scheme: dark) { - mark { - background-color: var(--google-yellow-refresh-300); - color: var(--google-grey-900); - } - } - </style> - <div id="scroll-container" hidden="[[!highlighted_]]" dir="ltr"> - <div id="main"> - <!-- Line numbers are not useful to a screenreader --> - <div id="line-numbers" aria-hidden="true"> - <div class="more-code before" hidden="[[!truncatedBefore_]]"> - ... - </div> - <span>[[lineNumbers_]]</span> - <div class="more-code after" hidden="[[!truncatedAfter_]]"> - ... - </div> - </div> - <div id="source"> - <div class="more-code before" hidden="[[!truncatedBefore_]]"> - [[getLinesNotShownLabel_( - truncatedBefore_, - '$i18nPolymer{errorLinesNotShownSingular}', - '$i18nPolymer{errorLinesNotShownPlural}')]] - </div> - <span><!-- Whitespace is preserved in this span. Ignore new lines. - --><span>[[before_]]</span><!-- - --><mark aria-label$="[[highlighted_]]" - aria-describedby="highlight-description"><!-- - --><span aria-hidden="true">[[highlighted_]]</span><!-- - --></mark><!-- - --><span>[[after_]]</span><!-- - --></span> - <div class="more-code after" hidden="[[!truncatedAfter_]]"> - [[getLinesNotShownLabel_( - truncatedAfter_, - '$i18nPolymer{errorLinesNotShownSingular}', - '$i18nPolymer{errorLinesNotShownPlural}')]] - </div> - </div> + @media (prefers-color-scheme: dark) { + mark { + background-color: var(--google-yellow-refresh-300); + color: var(--google-grey-900); + } + } +</style> +<div id="scroll-container" hidden="[[!highlighted_]]" dir="ltr"> + <div id="main"> + <!-- Line numbers are not useful to a screenreader --> + <div id="line-numbers" aria-hidden="true"> + <div class="more-code before" hidden="[[!truncatedBefore_]]"> + ... + </div> + <span>[[lineNumbers_]]</span> + <div class="more-code after" hidden="[[!truncatedAfter_]]"> + ... </div> </div> - <div id="no-code" hidden="[[!showNoCode_]]">[[couldNotDisplayCode]]</div> - <div id="highlight-description" aria-hidden="true"> - [[highlightDescription_]] + <div id="source"> + <div class="more-code before" hidden="[[!truncatedBefore_]]"> + [[getLinesNotShownLabel_( + truncatedBefore_, + '$i18nPolymer{errorLinesNotShownSingular}', + '$i18nPolymer{errorLinesNotShownPlural}')]] + </div> + <span><!-- Whitespace is preserved in this span. Ignore new lines. + --><span>[[before_]]</span><!-- + --><mark aria-label$="[[highlighted_]]" + aria-describedby="highlight-description"><!-- + --><span aria-hidden="true">[[highlighted_]]</span><!-- + --></mark><!-- + --><span>[[after_]]</span><!-- + --></span> + <div class="more-code after" hidden="[[!truncatedAfter_]]"> + [[getLinesNotShownLabel_( + truncatedAfter_, + '$i18nPolymer{errorLinesNotShownSingular}', + '$i18nPolymer{errorLinesNotShownPlural}')]] + </div> </div> - </template> - <script src="code_section.js"></script> -</dom-module> + </div> +</div> +<div id="no-code" hidden="[[!showNoCode_]]">[[couldNotDisplayCode]]</div> +<div id="highlight-description" aria-hidden="true"> + [[highlightDescription_]] +</div> diff --git a/chromium/chrome/browser/resources/extensions/code_section.js b/chromium/chrome/browser/resources/extensions/code_section.js index 3577af2687a..9a24dd366bb 100644 --- a/chromium/chrome/browser/resources/extensions/code_section.js +++ b/chromium/chrome/browser/resources/extensions/code_section.js @@ -2,196 +2,202 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -cr.define('extensions', function() { - 'use strict'; +import 'chrome://resources/cr_elements/hidden_style_css.m.js'; +import 'chrome://resources/cr_elements/shared_vars_css.m.js'; +import 'chrome://resources/polymer/v3_0/paper-styles/color.js'; +import './strings.m.js'; - /** - * @param {number} totalCount - * @param {number} oppositeCount - * @return {number} - */ - function visibleLineCount(totalCount, oppositeCount) { - // We limit the number of lines shown for DOM performance. - const MAX_VISIBLE_LINES = 1000; - const max = - Math.max(MAX_VISIBLE_LINES / 2, MAX_VISIBLE_LINES - oppositeCount); - return Math.min(max, totalCount); - } - - const CodeSection = Polymer({ - is: 'extensions-code-section', - - behaviors: [I18nBehavior], - - properties: { - /** - * The code this object is displaying. - * @type {?chrome.developerPrivate.RequestFileSourceResponse} - */ - code: { - type: Object, - value: null, - }, - - isActive: Boolean, - - /** @private Highlighted code. */ - highlighted_: String, - - /** @private Code before the highlighted section. */ - before_: String, - - /** @private Code after the highlighted section. */ - after_: String, - - /** @private */ - showNoCode_: { - type: Boolean, - computed: 'computeShowNoCode_(isActive, highlighted_)', - }, - - /** @private Description for the highlighted section. */ - highlightDescription_: String, - - /** @private */ - lineNumbers_: String, - - /** @private */ - truncatedBefore_: Number, - - /** @private */ - truncatedAfter_: Number, - - /** - * The string to display if no |code| is set (e.g. because we couldn't - * load the relevant source file). - * @type {string} - */ - couldNotDisplayCode: String, - }, +import {I18nBehavior} from 'chrome://resources/js/i18n_behavior.m.js'; +import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js'; +import {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; - observers: [ - 'onCodeChanged_(code.*)', - ], - /** - * @private - */ - onCodeChanged_: function() { - if (!this.code || - (!this.code.beforeHighlight && !this.code.highlight && - !this.code.afterHighlight)) { - this.highlighted_ = ''; - this.highlightDescription_ = ''; - this.before_ = ''; - this.after_ = ''; - this.lineNumbers_ = ''; - return; - } - - const before = this.code.beforeHighlight; - const highlight = this.code.highlight; - const after = this.code.afterHighlight; - - const linesBefore = before ? before.split('\n') : []; - const linesAfter = after ? after.split('\n') : []; - const visibleLineCountBefore = - visibleLineCount(linesBefore.length, linesAfter.length); - const visibleLineCountAfter = - visibleLineCount(linesAfter.length, linesBefore.length); - - const visibleBefore = - linesBefore.slice(linesBefore.length - visibleLineCountBefore) - .join('\n'); - let visibleAfter = linesAfter.slice(0, visibleLineCountAfter).join('\n'); - // If the last character is a \n, force it to be rendered. - if (visibleAfter.charAt(visibleAfter.length - 1) == '\n') { - visibleAfter += ' '; - } - - this.highlighted_ = highlight; - this.highlightDescription_ = this.getAccessibilityHighlightDescription_( - linesBefore.length, highlight.split('\n').length); - this.before_ = visibleBefore; - this.after_ = visibleAfter; - this.truncatedBefore_ = linesBefore.length - visibleLineCountBefore; - this.truncatedAfter_ = linesAfter.length - visibleLineCountAfter; - - const visibleCode = visibleBefore + highlight + visibleAfter; - - this.setLineNumbers_( - this.truncatedBefore_ + 1, - this.truncatedBefore_ + visibleCode.split('\n').length); - this.scrollToHighlight_(visibleLineCountBefore); - }, +/** + * @param {number} totalCount + * @param {number} oppositeCount + * @return {number} + */ +function visibleLineCount(totalCount, oppositeCount) { + // We limit the number of lines shown for DOM performance. + const MAX_VISIBLE_LINES = 1000; + const max = + Math.max(MAX_VISIBLE_LINES / 2, MAX_VISIBLE_LINES - oppositeCount); + return Math.min(max, totalCount); +} +Polymer({ + is: 'extensions-code-section', + + _template: html`{__html_template__}`, + + behaviors: [I18nBehavior], + + properties: { /** - * @param {number} lineCount - * @param {string} stringSingular - * @param {string} stringPluralTemplate - * @return {string} - * @private + * The code this object is displaying. + * @type {?chrome.developerPrivate.RequestFileSourceResponse} */ - getLinesNotShownLabel_(lineCount, stringSingular, stringPluralTemplate) { - return lineCount == 1 ? - stringSingular : - loadTimeData.substituteString(stringPluralTemplate, lineCount); + code: { + type: Object, + value: null, }, - /** - * @param {number} start - * @param {number} end - * @private - */ - setLineNumbers_: function(start, end) { - let lineNumbers = ''; - for (let i = start; i <= end; ++i) { - lineNumbers += i + '\n'; - } + isActive: Boolean, - this.lineNumbers_ = lineNumbers; + /** @private Highlighted code. */ + highlighted_: String, + + /** @private Code before the highlighted section. */ + before_: String, + + /** @private Code after the highlighted section. */ + after_: String, + + /** @private */ + showNoCode_: { + type: Boolean, + computed: 'computeShowNoCode_(isActive, highlighted_)', }, - /** - * @param {number} linesBeforeHighlight - * @private - */ - scrollToHighlight_: function(linesBeforeHighlight) { - const CSS_LINE_HEIGHT = 20; + /** @private Description for the highlighted section. */ + highlightDescription_: String, - // Count how many pixels is above the highlighted code. - const highlightTop = linesBeforeHighlight * CSS_LINE_HEIGHT; + /** @private */ + lineNumbers_: String, - // Find the position to show the highlight roughly in the middle. - const targetTop = highlightTop - this.clientHeight * 0.5; + /** @private */ + truncatedBefore_: Number, - this.$['scroll-container'].scrollTo({top: targetTop}); - }, + /** @private */ + truncatedAfter_: Number, /** - * @param {number} lineStart - * @param {number} numLines - * @return {string} - * @private + * The string to display if no |code| is set (e.g. because we couldn't + * load the relevant source file). + * @type {string} */ - getAccessibilityHighlightDescription_: function(lineStart, numLines) { - if (numLines > 1) { - return this.i18n( - 'accessibilityErrorMultiLine', lineStart.toString(), - (lineStart + numLines - 1).toString()); - } else { - return this.i18n('accessibilityErrorLine', lineStart.toString()); - } - }, + couldNotDisplayCode: String, + }, - /** - * @private - * @return {boolean} - */ - computeShowNoCode_: function() { - return this.isActive && !this.highlighted_; - }, - }); + observers: [ + 'onCodeChanged_(code.*)', + ], - return {CodeSection: CodeSection}; + /** + * @private + */ + onCodeChanged_: function() { + if (!this.code || + (!this.code.beforeHighlight && !this.code.highlight && + !this.code.afterHighlight)) { + this.highlighted_ = ''; + this.highlightDescription_ = ''; + this.before_ = ''; + this.after_ = ''; + this.lineNumbers_ = ''; + return; + } + + const before = this.code.beforeHighlight; + const highlight = this.code.highlight; + const after = this.code.afterHighlight; + + const linesBefore = before ? before.split('\n') : []; + const linesAfter = after ? after.split('\n') : []; + const visibleLineCountBefore = + visibleLineCount(linesBefore.length, linesAfter.length); + const visibleLineCountAfter = + visibleLineCount(linesAfter.length, linesBefore.length); + + const visibleBefore = + linesBefore.slice(linesBefore.length - visibleLineCountBefore) + .join('\n'); + let visibleAfter = linesAfter.slice(0, visibleLineCountAfter).join('\n'); + // If the last character is a \n, force it to be rendered. + if (visibleAfter.charAt(visibleAfter.length - 1) == '\n') { + visibleAfter += ' '; + } + + this.highlighted_ = highlight; + this.highlightDescription_ = this.getAccessibilityHighlightDescription_( + linesBefore.length, highlight.split('\n').length); + this.before_ = visibleBefore; + this.after_ = visibleAfter; + this.truncatedBefore_ = linesBefore.length - visibleLineCountBefore; + this.truncatedAfter_ = linesAfter.length - visibleLineCountAfter; + + const visibleCode = visibleBefore + highlight + visibleAfter; + + this.setLineNumbers_( + this.truncatedBefore_ + 1, + this.truncatedBefore_ + visibleCode.split('\n').length); + this.scrollToHighlight_(visibleLineCountBefore); + }, + + /** + * @param {number} lineCount + * @param {string} stringSingular + * @param {string} stringPluralTemplate + * @return {string} + * @private + */ + getLinesNotShownLabel_(lineCount, stringSingular, stringPluralTemplate) { + return lineCount == 1 ? + stringSingular : + loadTimeData.substituteString(stringPluralTemplate, lineCount); + }, + + /** + * @param {number} start + * @param {number} end + * @private + */ + setLineNumbers_: function(start, end) { + let lineNumbers = ''; + for (let i = start; i <= end; ++i) { + lineNumbers += i + '\n'; + } + + this.lineNumbers_ = lineNumbers; + }, + + /** + * @param {number} linesBeforeHighlight + * @private + */ + scrollToHighlight_: function(linesBeforeHighlight) { + const CSS_LINE_HEIGHT = 20; + + // Count how many pixels is above the highlighted code. + const highlightTop = linesBeforeHighlight * CSS_LINE_HEIGHT; + + // Find the position to show the highlight roughly in the middle. + const targetTop = highlightTop - this.clientHeight * 0.5; + + this.$['scroll-container'].scrollTo({top: targetTop}); + }, + + /** + * @param {number} lineStart + * @param {number} numLines + * @return {string} + * @private + */ + getAccessibilityHighlightDescription_: function(lineStart, numLines) { + if (numLines > 1) { + return this.i18n( + 'accessibilityErrorMultiLine', lineStart.toString(), + (lineStart + numLines - 1).toString()); + } else { + return this.i18n('accessibilityErrorLine', lineStart.toString()); + } + }, + + /** + * @private + * @return {boolean} + */ + computeShowNoCode_: function() { + return this.isActive && !this.highlighted_; + }, }); diff --git a/chromium/chrome/browser/resources/extensions/detail_view.html b/chromium/chrome/browser/resources/extensions/detail_view.html index 23cdc7bc32e..6f63a2a978f 100644 --- a/chromium/chrome/browser/resources/extensions/detail_view.html +++ b/chromium/chrome/browser/resources/extensions/detail_view.html @@ -1,448 +1,413 @@ -<link rel="import" href="chrome://resources/html/polymer.html"> +<style include="iron-flex cr-shared-style cr-icons action-link + shared-style"> + :host { + --iron-icon-fill-color: var(--cr-secondary-text-color); + display: block; + height: 100%; + } -<link rel="import" href="chrome://resources/cr_elements/cr_container_shadow_behavior.html"> -<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html"> -<link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html"> -<link rel="import" href="chrome://resources/cr_elements/cr_icons_css.html"> -<link rel="import" href="chrome://resources/cr_elements/cr_link_row/cr_link_row.html"> -<link rel="import" href="chrome://resources/cr_elements/cr_toggle/cr_toggle.html"> -<link rel="import" href="chrome://resources/cr_elements/icons.html"> -<link rel="import" href="chrome://resources/cr_elements/policy/cr_tooltip_icon.html"> -<link rel="import" href="chrome://resources/cr_elements/shared_style_css.html"> -<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html"> -<link rel="import" href="chrome://resources/html/action_link.html"> -<link rel="import" href="chrome://resources/cr_elements/action_link_css.html"> -<link rel="import" href="chrome://resources/html/cr.html"> -<link rel="import" href="chrome://resources/html/cr/ui/focus_without_ink.html"> -<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/iron-flex-layout-classes.html"> -<link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html"> -<link rel="import" href="chrome://resources/polymer/v1_0/paper-spinner/paper-spinner-lite.html"> -<link rel="import" href="chrome://resources/polymer/v1_0/paper-styles/color.html"> -<link rel="import" href="item_behavior.html"> -<link rel="import" href="item_util.html"> -<link rel="import" href="host_permissions_toggle_list.html"> -<link rel="import" href="navigation_helper.html"> -<link rel="import" href="runtime_host_permissions.html"> -<link rel="import" href="shared_style.html"> -<link rel="import" href="shared_vars.html"> -<link rel="import" href="strings.html"> -<link rel="import" href="toggle_row.html"> + #enable-section { + margin-bottom: 8px; + } -<dom-module id="extensions-detail-view"> - <template> - <style include="iron-flex cr-shared-style cr-icons action-link - shared-style"> - :host { - --iron-icon-fill-color: var(--cr-secondary-text-color); - display: block; - height: 100%; - } + #enable-section cr-tooltip-icon { + margin-inline-end: 20px; + } - #enable-section { - margin-bottom: 8px; - } + #enable-section span { + color: var(--cr-secondary-text-color); + font-weight: 500; + } - #enable-section cr-tooltip-icon { - margin-inline-end: 20px; - } + #enable-section .enabled-text { + color: var(--google-blue-500); + } - #enable-section span { - color: var(--cr-secondary-text-color); - font-weight: 500; - } + @media (prefers-color-scheme: dark) { + #enable-section .enabled-text { + color: var(--google-blue-refresh-300); + } + } - #enable-section .enabled-text { - color: var(--google-blue-500); - } + #icon { + height: 24px; + margin-inline-end: 12px; + margin-inline-start: 16px; + width: 24px; + } - @media (prefers-color-scheme: dark) { - #enable-section .enabled-text { - color: var(--google-blue-refresh-300); - } - } + #name { + flex-grow: 1; + } - #icon { - height: 24px; - margin-inline-end: 12px; - margin-inline-start: 16px; - width: 24px; - } + .section { + @apply --cr-section; + } - #name { - flex-grow: 1; - } + .section.block { + box-sizing: border-box; + display: block; + padding-bottom: var(--cr-section-vertical-padding); + padding-top: var(--cr-section-vertical-padding); + } - .section { - @apply --cr-section; - } + .section.continuation { + border-top: none; + } - .section.block { - box-sizing: border-box; - display: block; - padding-bottom: var(--cr-section-vertical-padding); - padding-top: var(--cr-section-vertical-padding); - } + .section.control-line { + justify-content: space-between; + } - .section.continuation { - border-top: none; - } + .section:first-child { + border: none; + } - .section.control-line { - justify-content: space-between; - } + .section-content { + color: var(--cr-secondary-text-color); + } - .section:first-child { - border: none; - } + .actionable { + cursor: pointer; + } - .section-content { - color: var(--cr-secondary-text-color); - } + .inspectable-view { + height: 20px; + width: auto; /* override the default button size of 24x24 */ + } - .actionable { - cursor: pointer; - } + @media (prefers-color-scheme: light) { + .warning .action-button { + background: white; + color: var(--google-blue-500); + } - .inspectable-view { - height: 20px; - width: auto; /* override the default button size of 24x24 */ - } + #reload-button { + color: var(--google-blue-500); + } + } - @media (prefers-color-scheme: light) { - .warning .action-button { - background: white; - color: var(--google-blue-500); - } + .warning span { + color: var(--error-color); + flex: 1; + } - #reload-button { - color: var(--google-blue-500); - } - } + .warning-icon { + --iron-icon-fill-color: var(--error-color); + flex-shrink: 0; + height: 18px; + margin-inline-end: 8px; + width: 18px; + } - .warning span { - color: var(--error-color); - flex: 1; - } + ul { + margin: 0; + padding-inline-start: 20px; + } - .warning-icon { - --iron-icon-fill-color: var(--error-color); - flex-shrink: 0; - height: 18px; - margin-inline-end: 8px; - width: 18px; - } + #options-section .control-line:first-child { + border-top: var(--cr-separator-line); + } - ul { - margin: 0; - padding-inline-start: 20px; - } + extensions-toggle-row { + @apply --cr-section; + box-sizing: border-box; + padding-inline-end: 0; + padding-inline-start: 0; + --toggle-row-label-horizontal-padding: var(--cr-section-padding); + --toggle-row-label-vertical-padding: var(--cr-section-vertical-padding); + } - #options-section .control-line:first-child { - border-top: var(--cr-separator-line); - } + #load-path { + word-break: break-all; + } - extensions-toggle-row { - @apply --cr-section; - box-sizing: border-box; - padding-inline-end: 0; - padding-inline-start: 0; - --toggle-row-label-horizontal-padding: var(--cr-section-padding); - --toggle-row-label-vertical-padding: var(--cr-section-vertical-padding); - } + #load-path > a[is='action-link'] { + display: inline; + } - #load-path { - word-break: break-all; - } + #size { + align-items: center; + display: flex; + } - #load-path > a[is='action-link'] { - display: inline; - } - - #size { - align-items: center; - display: flex; - } - - paper-spinner-lite { - @apply --cr-icon-height-width; - } - </style> - <div class="page-container" id="container"> - <div class="page-content"> - <div class="page-header"> - <cr-icon-button class="icon-arrow-back no-overlap" id="closeButton" - aria-label="$i18n{back}" on-click="onCloseButtonTap_"> - </cr-icon-button> - <img id="icon" src="[[data.iconUrl]]" - alt$="[[appOrExtension( - data.type, - '$i18nPolymer{appIcon}', - '$i18nPolymer{extensionIcon}')]]"> - <span id="name" class="cr-title-text" role="heading" aria-level="1"> - [[data.name]] - </span> - </div> - <div class="section continuation control-line" id="enable-section"> - <span class$="[[computeEnabledStyle_(data.state)]]"> - [[computeEnabledText_(data.state, '$i18nPolymer{itemOn}', - '$i18nPolymer{itemOff}')]] - </span> - <div class="layout horizontal"> - <cr-tooltip-icon hidden$="[[!data.controlledInfo]]" - tooltip-text="[[data.controlledInfo.text]]" - icon-class="[[getIndicatorIcon_(data.controlledInfo.type)]]" - icon-aria-label="[[data.controlledInfo.type]]"> - </cr-tooltip-icon> - <template is="dom-if" if="[[isTerminated_(data.state)]]"> - <cr-button id="terminated-reload-button" class="action-button" - on-click="onReloadTap_"> - $i18n{itemReload} - </cr-button> - </template> - <cr-toggle id="enable-toggle" - aria-label$="[[appOrExtension( - data.type, - '$i18nPolymer{appEnabled}', - '$i18nPolymer{extensionEnabled}')]]" - aria-describedby="name" - checked="[[isEnabled_(data.state)]]" - on-change="onEnableChange_" - disabled="[[!isEnableToggleEnabled_(data.*)]]" - hidden$="[[isTerminated_(data.state)]]"> - </cr-toggle> - </div> + paper-spinner-lite { + @apply --cr-icon-height-width; + } +</style> +<div class="page-container" id="container"> + <div class="page-content"> + <div class="page-header"> + <cr-icon-button class="icon-arrow-back no-overlap" id="closeButton" + aria-label="$i18n{back}" on-click="onCloseButtonTap_"> + </cr-icon-button> + <img id="icon" src="[[data.iconUrl]]" + alt$="[[appOrExtension( + data.type, + '$i18nPolymer{appIcon}', + '$i18nPolymer{extensionIcon}')]]"> + <span id="name" class="cr-title-text" role="heading" aria-level="1"> + [[data.name]] + </span> + </div> + <div class="section continuation control-line" id="enable-section"> + <span class$="[[computeEnabledStyle_(data.state)]]"> + [[computeEnabledText_(data.state, '$i18nPolymer{itemOn}', + '$i18nPolymer{itemOff}')]] + </span> + <div class="layout horizontal"> + <cr-tooltip-icon hidden$="[[!data.controlledInfo]]" + tooltip-text="[[data.controlledInfo.text]]" + icon-class="[[getIndicatorIcon_(data.controlledInfo.type)]]" + icon-aria-label="[[data.controlledInfo.type]]"> + </cr-tooltip-icon> + <template is="dom-if" if="[[isTerminated_(data.state)]]"> + <cr-button id="terminated-reload-button" class="action-button" + on-click="onReloadTap_"> + $i18n{itemReload} + </cr-button> + </template> + <cr-toggle id="enable-toggle" + aria-label$="[[appOrExtension( + data.type, + '$i18nPolymer{appEnabled}', + '$i18nPolymer{extensionEnabled}')]]" + aria-describedby="name" + checked="[[isEnabled_(data.state)]]" + on-change="onEnableChange_" + disabled="[[!isEnableToggleEnabled_(data.*)]]" + hidden$="[[isTerminated_(data.state)]]"> + </cr-toggle> + </div> + </div> + <div id="warnings" hidden$="[[!hasWarnings_(data.*)]]"> + <div id="runtime-warnings" hidden$="[[!data.runtimeWarnings.length]]" + class="section continuation warning control-line"> + <iron-icon class="warning-icon" icon="cr:error"></iron-icon> + <span> + <template is="dom-repeat" items="[[data.runtimeWarnings]]"> + [[item]] + </template> + </span> + <template is="dom-if" if="[[!isTerminated_(data.state)]]"> + <cr-button id="warnings-reload-button" class="action-button" + on-click="onReloadTap_"> + $i18n{itemReload} + </cr-button> + </template> + </div> + <div class="section continuation warning" id="suspicious-warning" + hidden$="[[!data.disableReasons.suspiciousInstall]]"> + <iron-icon class="warning-icon" icon="cr:warning"></iron-icon> + <span> + $i18n{itemSuspiciousInstall} + <a target="_blank" href="$i18n{suspiciousInstallHelpUrl}"> + $i18n{learnMore} + </a> + </span> + </div> + <div class="section continuation warning control-line" + id="corrupted-warning" + hidden$="[[!data.disableReasons.corruptInstall]]"> + <iron-icon class="warning-icon" icon="cr:warning"></iron-icon> + <span>$i18n{itemCorruptInstall}</span> + <cr-button id="repair-button" class="action-button" + on-click="onRepairTap_"> + $i18n{itemRepair} + </cr-button> + </div> + <div class="section continuation warning" id="blacklisted-warning" + hidden$="[[!data.blacklistText]]"> + <iron-icon class="warning-icon" icon="cr:warning"></iron-icon> + <span>[[data.blacklistText]]</span> + </div> + <div class="section continuation warning" id="update-required-warning" + hidden$="[[!data.disableReasons.updateRequired]]"> + <iron-icon class="warning-icon" icon="cr:warning"></iron-icon> + <span>$i18n{updateRequiredByPolicy}</span> + </div> + </div> + <div class="section continuation block"> + <div class="section-title" role="heading" aria-level="2"> + $i18n{itemDescriptionLabel} + </div> + <div class="section-content" id="description"> + [[getDescription_(data.description, '$i18nPolymer{noDescription}')]] + </div> + </div> + <div class="section block"> + <div class="section-title" role="heading" aria-level="2"> + $i18n{itemVersion} + </div> + <div class="section-content">[[data.version]]</div> + </div> + <div class="section block"> + <div class="section-title" role="heading" aria-level="2"> + $i18n{itemSize} + </div> + <div class="section-content" id="size"> + <span>[[size_]]</span> + <paper-spinner-lite active="[[!size_]]" hidden="[[size_]]"> + </paper-spinner-lite> + </div> + </div> + <div class="section block" id="id-section" hidden$="[[!inDevMode]]"> + <div class="section-title" role="heading" aria-level="2"> + $i18n{itemIdHeading} + </div> + <div class="section-content">[[data.id]]</div> + </div> + <template is="dom-if" if="[[inDevMode]]"> + <div class="section block" id="inspectable-views"> + <div class="section-title" role="heading" aria-level="2"> + $i18n{itemInspectViews} </div> - <div id="warnings" hidden$="[[!hasWarnings_(data.*)]]"> - <div id="runtime-warnings" hidden$="[[!data.runtimeWarnings.length]]" - class="section continuation warning control-line"> - <iron-icon class="warning-icon" icon="cr:error"></iron-icon> - <span> - <template is="dom-repeat" items="[[data.runtimeWarnings]]"> - [[item]] - </template> - </span> - <template is="dom-if" if="[[!isTerminated_(data.state)]]"> - <cr-button id="warnings-reload-button" class="action-button" - on-click="onReloadTap_"> - $i18n{itemReload} - </cr-button> + <div class="section-content"> + <ul id="inspect-views"> + <li hidden="[[data.views.length]]"> + $i18n{noActiveViews} + </li> + <template is="dom-repeat" items="[[data.views]]"> + <li> + <a is="action-link" class="inspectable-view" + on-click="onInspectTap_"> + [[computeInspectLabel_(item)]] + </a> + </li> </template> - </div> - <div class="section continuation warning" id="suspicious-warning" - hidden$="[[!data.disableReasons.suspiciousInstall]]"> - <iron-icon class="warning-icon" icon="cr:warning"></iron-icon> - <span> - $i18n{itemSuspiciousInstall} - <a target="_blank" href="$i18n{suspiciousInstallHelpUrl}"> - $i18n{learnMore} - </a> - </span> - </div> - <div class="section continuation warning control-line" - id="corrupted-warning" - hidden$="[[!data.disableReasons.corruptInstall]]"> - <iron-icon class="warning-icon" icon="cr:warning"></iron-icon> - <span>$i18n{itemCorruptInstall}</span> - <cr-button id="repair-button" class="action-button" - on-click="onRepairTap_"> - $i18n{itemRepair} - </cr-button> - </div> - <div class="section continuation warning" id="blacklisted-warning" - hidden$="[[!data.blacklistText]]"> - <iron-icon class="warning-icon" icon="cr:warning"></iron-icon> - <span>[[data.blacklistText]]</span> - </div> - <div class="section continuation warning" id="update-required-warning" - hidden$="[[!data.disableReasons.updateRequired]]"> - <iron-icon class="warning-icon" icon="cr:warning"></iron-icon> - <span>$i18n{updateRequiredByPolicy}</span> - </div> - </div> - <div class="section continuation block"> - <div class="section-title" role="heading" aria-level="2"> - $i18n{itemDescriptionLabel} - </div> - <div class="section-content" id="description"> - [[getDescription_(data.description, '$i18nPolymer{noDescription}')]] - </div> - </div> - <div class="section block"> - <div class="section-title" role="heading" aria-level="2"> - $i18n{itemVersion} - </div> - <div class="section-content">[[data.version]]</div> + </ul> </div> - <div class="section block"> - <div class="section-title" role="heading" aria-level="2"> - $i18n{itemSize} - </div> - <div class="section-content" id="size"> - <span>[[size_]]</span> - <paper-spinner-lite active="[[!size_]]" hidden="[[size_]]"> - </paper-spinner-lite> - </div> - </div> - <div class="section block" id="id-section" hidden$="[[!inDevMode]]"> - <div class="section-title" role="heading" aria-level="2"> - $i18n{itemIdHeading} - </div> - <div class="section-content">[[data.id]]</div> - </div> - <template is="dom-if" if="[[inDevMode]]"> - <div class="section block" id="inspectable-views"> - <div class="section-title" role="heading" aria-level="2"> - $i18n{itemInspectViews} - </div> - <div class="section-content"> - <ul id="inspect-views"> - <li hidden="[[data.views.length]]"> - $i18n{noActiveViews} - </li> - <template is="dom-repeat" items="[[data.views]]"> - <li> - <a is="action-link" class="inspectable-view" - on-click="onInspectTap_"> - [[computeInspectLabel_(item)]] - </a> - </li> + </div> + </template> + <div class="section block"> + <div class="section-title" role="heading" aria-level="2"> + $i18n{itemPermissions} + </div> + <div class="section-content"> + <span id="no-permissions" hidden$="[[hasPermissions_(data.*)]]"> + $i18n{itemPermissionsEmpty} + </span> + <ul id="permissions-list" + hidden$="[[!data.permissions.simplePermissions.length]]"> + <template is="dom-repeat" + items="[[data.permissions.simplePermissions]]"> + <li> + [[item.message]] + <ul hidden="[[!item.submessages.length]]"> + <template is="dom-repeat" items="[[item.submessages]]"> + <li>[[item]]</li> </template> </ul> - </div> - </div> + </li> + </template> + </ul> + </div> + </div> + <div class="section block"> + <div class="section-title" role="heading" aria-level="2"> + $i18n{itemSiteAccess} + </div> + <div class="section-content"> + <span id="no-site-access" + hidden$="[[showSiteAccessContent_(data.*)]]"> + $i18n{itemSiteAccessEmpty} + </span> + <template is="dom-if" + if="[[showFreeformRuntimeHostPermissions_(data.*)]]"> + <extensions-runtime-host-permissions + permissions="[[data.permissions.runtimeHostPermissions]]" + delegate="[[delegate]]" + item-id="[[data.id]]"> + </extensions-runtime-host-permissions> </template> - <div class="section block"> - <div class="section-title" role="heading" aria-level="2"> - $i18n{itemPermissions} - </div> - <div class="section-content"> - <span id="no-permissions" hidden$="[[hasPermissions_(data.*)]]"> - $i18n{itemPermissionsEmpty} - </span> - <ul id="permissions-list" - hidden$="[[!data.permissions.simplePermissions.length]]"> - <template is="dom-repeat" - items="[[data.permissions.simplePermissions]]"> - <li> - [[item.message]] - <ul hidden="[[!item.submessages.length]]"> - <template is="dom-repeat" items="[[item.submessages]]"> - <li>[[item]]</li> - </template> - </ul> - </li> - </template> - </ul> - </div> + <template is="dom-if" + if="[[showHostPermissionsToggleList_(data.*)]]"> + <extensions-host-permissions-toggle-list + permissions="[[data.permissions.runtimeHostPermissions]]" + delegate="[[delegate]]" + item-id="[[data.id]]"> + </extensions-host-permissions-toggle-list> + </template> + </div> + </div> + <template is="dom-if" + if="[[hasDependentExtensions_(data.dependentExtensions.splices)]]"> + <div class="section block"> + <div class="section-title" role="heading" aria-level="2"> + $i18n{itemDependencies} </div> - <div class="section block"> - <div class="section-title" role="heading" aria-level="2"> - $i18n{itemSiteAccess} - </div> - <div class="section-content"> - <span id="no-site-access" - hidden$="[[showSiteAccessContent_(data.*)]]"> - $i18n{itemSiteAccessEmpty} - </span> - <template is="dom-if" - if="[[showFreeformRuntimeHostPermissions_(data.*)]]"> - <extensions-runtime-host-permissions - permissions="[[data.permissions.runtimeHostPermissions]]" - delegate="[[delegate]]" - item-id="[[data.id]]"> - </extensions-runtime-host-permissions> + <div class="section-content"> + <ul id="dependent-extensions-list"> + <template is="dom-repeat" items="[[data.dependentExtensions]]"> + <li>[[computeDependentEntry_(item)]]</li> </template> - <template is="dom-if" - if="[[showHostPermissionsToggleList_(data.*)]]"> - <extensions-host-permissions-toggle-list - permissions="[[data.permissions.runtimeHostPermissions]]" - delegate="[[delegate]]" - item-id="[[data.id]]"> - </extensions-host-permissions-toggle-list> - </template> - </div> + </ul> </div> + </div> + </template> + <template is="dom-if" if="[[shouldShowOptionsSection_(data.*)]]"> + <div id="options-section"> <template is="dom-if" - if="[[hasDependentExtensions_(data.dependentExtensions.splices)]]"> - <div class="section block"> - <div class="section-title" role="heading" aria-level="2"> - $i18n{itemDependencies} + if="[[shouldShowIncognitoOption_( + data.incognitoAccess.isEnabled, incognitoAvailable)]]"> + <extensions-toggle-row id="allow-incognito" + checked="[[data.incognitoAccess.isActive]]" + on-change="onAllowIncognitoChange_"> + <div> + <div>$i18n{itemAllowIncognito}</div> + <div class="section-content">$i18n{incognitoInfoWarning}</div> </div> - <div class="section-content"> - <ul id="dependent-extensions-list"> - <template is="dom-repeat" items="[[data.dependentExtensions]]"> - <li>[[computeDependentEntry_(item)]]</li> - </template> - </ul> - </div> - </div> + </extensions-toggle-row> </template> - <template is="dom-if" if="[[shouldShowOptionsSection_(data.*)]]"> - <div id="options-section"> - <template is="dom-if" - if="[[shouldShowIncognitoOption_( - data.incognitoAccess.isEnabled, incognitoAvailable)]]"> - <extensions-toggle-row id="allow-incognito" - checked="[[data.incognitoAccess.isActive]]" - on-change="onAllowIncognitoChange_"> - <div> - <div>$i18n{itemAllowIncognito}</div> - <div class="section-content">$i18n{incognitoInfoWarning}</div> - </div> - </extensions-toggle-row> - </template> - <template is="dom-if" if="[[data.fileAccess.isEnabled]]"> - <extensions-toggle-row id="allow-on-file-urls" - checked="[[data.fileAccess.isActive]]" - on-change="onAllowOnFileUrlsChange_"> - <span>$i18n{itemAllowOnFileUrls}</span> - </extensions-toggle-row> - </template> - <template is="dom-if" if="[[data.errorCollection.isEnabled]]"> - <extensions-toggle-row id="collect-errors" - checked="[[data.errorCollection.isActive]]" - on-change="onCollectErrorsChange_"> - <span>$i18n{itemCollectErrors}</span> - </extensions-toggle-row> - </template> - </div> + <template is="dom-if" if="[[data.fileAccess.isEnabled]]"> + <extensions-toggle-row id="allow-on-file-urls" + checked="[[data.fileAccess.isActive]]" + on-change="onAllowOnFileUrlsChange_"> + <span>$i18n{itemAllowOnFileUrls}</span> + </extensions-toggle-row> </template> - <cr-link-row class="hr" id="extensions-options" - disabled="[[!isEnabled_(data.state)]]" - hidden="[[!shouldShowOptionsLink_(data.*)]]" - label="$i18n{itemOptions}" on-click="onExtensionOptionsTap_" - external></cr-link-row> - <cr-link-row class="hr" - id="extensionsActivityLogLink" hidden$="[[!showActivityLog]]" - label="$i18n{viewActivityLog}" on-click="onActivityLogTap_"> - </cr-link-row> - <cr-link-row class="hr" hidden="[[!data.manifestHomePageUrl.length]]" - id="extensionWebsite" label="$i18n{extensionWebsite}" - on-click="onExtensionWebSiteTap_" external></cr-link-row> - <cr-link-row class="hr" hidden="[[!data.webStoreUrl.length]]" - id="viewInStore" label="$i18n{viewInStore}" - on-click="onViewInStoreTap_" external></cr-link-row> - <div class="section block"> - <div class="section-title" role="heading" aria-level="2"> - $i18n{itemSource} - </div> - <div id="source" class="section-content"> - [[computeSourceString_(data.*)]] - </div> - <div id="load-path" class="section-content" - hidden$="[[!data.prettifiedPath]]"> - <span>$i18n{itemExtensionPath}</span> - <a is="action-link" on-click="onLoadPathTap_"> - [[data.prettifiedPath]] - </a> - </div> - </div> - <cr-link-row class="hr" id="remove-extension" - hidden="[[isControlled_(data.controlledInfo)]]" - label="$i18n{itemRemoveExtension}" on-click="onRemoveTap_"> - </cr-link-row> + <template is="dom-if" if="[[data.errorCollection.isEnabled]]"> + <extensions-toggle-row id="collect-errors" + checked="[[data.errorCollection.isActive]]" + on-change="onCollectErrorsChange_"> + <span>$i18n{itemCollectErrors}</span> + </extensions-toggle-row> + </template> + </div> + </template> + <cr-link-row class="hr" id="extensions-options" + disabled="[[!isEnabled_(data.state)]]" + hidden="[[!shouldShowOptionsLink_(data.*)]]" + label="$i18n{itemOptions}" on-click="onExtensionOptionsTap_" + external></cr-link-row> + <cr-link-row class="hr" + id="extensionsActivityLogLink" hidden$="[[!showActivityLog]]" + label="$i18n{viewActivityLog}" on-click="onActivityLogTap_"> + </cr-link-row> + <cr-link-row class="hr" hidden="[[!data.manifestHomePageUrl.length]]" + id="extensionWebsite" label="$i18n{extensionWebsite}" + on-click="onExtensionWebSiteTap_" external></cr-link-row> + <cr-link-row class="hr" hidden="[[!data.webStoreUrl.length]]" + id="viewInStore" label="$i18n{viewInStore}" + on-click="onViewInStoreTap_" external></cr-link-row> + <div class="section block"> + <div class="section-title" role="heading" aria-level="2"> + $i18n{itemSource} + </div> + <div id="source" class="section-content"> + [[computeSourceString_(data.*)]] + </div> + <div id="load-path" class="section-content" + hidden$="[[!data.prettifiedPath]]"> + <span>$i18n{itemExtensionPath}</span> + <a is="action-link" on-click="onLoadPathTap_"> + [[data.prettifiedPath]] + </a> </div> </div> - </template> - <script src="detail_view.js"></script> -</dom-module> + <cr-link-row class="hr" id="remove-extension" + hidden="[[isControlled_(data.controlledInfo)]]" + label="$i18n{itemRemoveExtension}" on-click="onRemoveTap_"> + </cr-link-row> + </div> +</div> diff --git a/chromium/chrome/browser/resources/extensions/detail_view.js b/chromium/chrome/browser/resources/extensions/detail_view.js index 53d33c481d0..562c6ba0fb4 100644 --- a/chromium/chrome/browser/resources/extensions/detail_view.js +++ b/chromium/chrome/browser/resources/extensions/detail_view.js @@ -2,359 +2,381 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -cr.define('extensions', function() { - 'use strict'; - - const DetailView = Polymer({ - is: 'extensions-detail-view', - - behaviors: [ - CrContainerShadowBehavior, - extensions.ItemBehavior, - ], - - properties: { - /** - * The underlying ExtensionInfo for the details being displayed. - * @type {!chrome.developerPrivate.ExtensionInfo} - */ - data: Object, - - /** @private */ - size_: String, - - /** @type {!extensions.ItemDelegate} */ - delegate: Object, - - /** Whether the user has enabled the UI's developer mode. */ - inDevMode: Boolean, - - /** Whether "allow in incognito" option should be shown. */ - incognitoAvailable: Boolean, - - /** Whether "View Activity Log" link should be shown. */ - showActivityLog: Boolean, - - /** Whether the user navigated to this page from the activity log page. */ - fromActivityLog: Boolean, - }, - - observers: [ - 'onItemIdChanged_(data.id, delegate)', - ], - - listeners: { - 'view-enter-start': 'onViewEnterStart_', - }, - - /** - * Focuses the extensions options button. This should be used after the - * dialog closes. - */ - focusOptionsButton: function() { - this.$$('#extensions-options').focus(); - }, - - /** - * Focuses the back button when page is loaded. - * @private - */ - onViewEnterStart_: function() { - const elementToFocus = this.fromActivityLog ? - this.$.extensionsActivityLogLink : - this.$.closeButton; - - Polymer.RenderStatus.afterNextRender( - this, () => cr.ui.focusWithoutInk(elementToFocus)); - }, - - /** @private */ - onItemIdChanged_: function() { - // Clear the size, since this view is reused, such that no obsolete size - // is displayed.: - this.size_ = ''; - this.delegate.getExtensionSize(this.data.id).then(size => { - this.size_ = size; - }); - }, - - /** @private */ - onActivityLogTap_: function() { - extensions.navigation.navigateTo( - {page: extensions.Page.ACTIVITY_LOG, extensionId: this.data.id}); - }, - +import 'chrome://resources/cr_elements/cr_button/cr_button.m.js'; +import 'chrome://resources/cr_elements/cr_icon_button/cr_icon_button.m.js'; +import 'chrome://resources/cr_elements/cr_icons_css.m.js'; +import 'chrome://resources/cr_elements/cr_link_row/cr_link_row.m.js'; +import 'chrome://resources/cr_elements/cr_toggle/cr_toggle.m.js'; +import 'chrome://resources/cr_elements/icons.m.js'; +import 'chrome://resources/cr_elements/policy/cr_tooltip_icon.m.js'; +import 'chrome://resources/cr_elements/shared_style_css.m.js'; +import 'chrome://resources/cr_elements/shared_vars_css.m.js'; +import 'chrome://resources/js/action_link.js'; +import 'chrome://resources/cr_elements/action_link_css.m.js'; +import 'chrome://resources/polymer/v3_0/iron-flex-layout/iron-flex-layout-classes.js'; +import 'chrome://resources/polymer/v3_0/iron-icon/iron-icon.js'; +import 'chrome://resources/polymer/v3_0/paper-spinner/paper-spinner-lite.js'; +import 'chrome://resources/polymer/v3_0/paper-styles/color.js'; +import './host_permissions_toggle_list.js'; +import './runtime_host_permissions.js'; +import './shared_style.js'; +import './shared_vars.js'; +import './strings.m.js'; +import './toggle_row.js'; + +import {CrContainerShadowBehavior} from 'chrome://resources/cr_elements/cr_container_shadow_behavior.m.js'; +import {focusWithoutInk} from 'chrome://resources/js/cr/ui/focus_without_ink.m.js'; +import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js'; +import {afterNextRender, html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; + +import {ItemDelegate} from './item.js'; +import {ItemBehavior} from './item_behavior.js'; +import {computeInspectableViewLabel, getItemSource, getItemSourceString, isControlled, isEnabled, userCanChangeEnablement} from './item_util.js'; +import {navigation, Page} from './navigation_helper.js'; + +Polymer({ + is: 'extensions-detail-view', + + _template: html`{__html_template__}`, + + behaviors: [ + CrContainerShadowBehavior, + ItemBehavior, + ], + + properties: { /** - * @param {string} description - * @param {string} fallback - * @return {string} - * @private + * The underlying ExtensionInfo for the details being displayed. + * @type {!chrome.developerPrivate.ExtensionInfo} */ - getDescription_: function(description, fallback) { - return description || fallback; - }, + data: Object, /** @private */ - onCloseButtonTap_: function() { - extensions.navigation.navigateTo({page: extensions.Page.LIST}); - }, - - /** - * @return {boolean} - * @private - */ - isControlled_: function() { - return extensions.isControlled(this.data); - }, - - /** - * @return {boolean} - * @private - */ - isEnabled_: function() { - return extensions.isEnabled(this.data.state); - }, - - /** - * @return {boolean} - * @private - */ - isEnableToggleEnabled_: function() { - return extensions.userCanChangeEnablement(this.data); - }, - - /** - * Returns true if the extension is in the terminated state. - * @return {boolean} - * @private - */ - isTerminated_: function() { - return this.data.state == - chrome.developerPrivate.ExtensionState.TERMINATED; - }, - - /** - * @return {boolean} - * @private - */ - hasDependentExtensions_: function() { - return this.data.dependentExtensions.length > 0; - }, - - /** - * @return {boolean} - * @private - */ - hasWarnings_: function() { - return this.data.disableReasons.corruptInstall || - this.data.disableReasons.suspiciousInstall || - this.data.disableReasons.updateRequired || - !!this.data.blacklistText || this.data.runtimeWarnings.length > 0; - }, - - /** - * @return {string} - * @private - */ - computeEnabledStyle_: function() { - return this.isEnabled_() ? 'enabled-text' : ''; - }, - - /** - * @param {!chrome.developerPrivate.ExtensionState} state - * @param {string} onText - * @param {string} offText - * @return {string} - * @private - */ - computeEnabledText_: function(state, onText, offText) { - // TODO(devlin): Get the full spectrum of these strings from bettes. - return extensions.isEnabled(state) ? onText : offText; - }, - - /** - * @param {!chrome.developerPrivate.ExtensionView} view - * @return {string} - * @private - */ - computeInspectLabel_: function(view) { - return extensions.computeInspectableViewLabel(view); - }, - - /** - * @return {boolean} - * @private - */ - shouldShowOptionsLink_: function() { - return !!this.data.optionsPage; - }, - - /** - * @return {boolean} - * @private - */ - shouldShowOptionsSection_: function() { - return this.data.incognitoAccess.isEnabled || - this.data.fileAccess.isEnabled || this.data.errorCollection.isEnabled; - }, - - /** - * @return {boolean} - * @private - */ - shouldShowIncognitoOption_: function() { - return this.data.incognitoAccess.isEnabled && this.incognitoAvailable; - }, - - /** @private */ - onEnableChange_: function() { - this.delegate.setItemEnabled( - this.data.id, this.$['enable-toggle'].checked); - }, - - /** - * @param {!{model: !{item: !chrome.developerPrivate.ExtensionView}}} e - * @private - */ - onInspectTap_: function(e) { - this.delegate.inspectItemView(this.data.id, e.model.item); - }, - - /** @private */ - onExtensionOptionsTap_: function() { - this.delegate.showItemOptionsPage(this.data); - }, - - /** @private */ - onReloadTap_: function() { - this.delegate.reloadItem(this.data.id).catch(loadError => { - this.fire('load-error', loadError); - }); - }, - - /** @private */ - onRemoveTap_: function() { - this.delegate.deleteItem(this.data.id); - }, - - /** @private */ - onRepairTap_: function() { - this.delegate.repairItem(this.data.id); - }, - - /** @private */ - onLoadPathTap_: function() { - this.delegate.showInFolder(this.data.id); - }, - - /** @private */ - onAllowIncognitoChange_: function() { - this.delegate.setItemAllowedIncognito( - this.data.id, this.$$('#allow-incognito').checked); - }, - - /** @private */ - onAllowOnFileUrlsChange_: function() { - this.delegate.setItemAllowedOnFileUrls( - this.data.id, this.$$('#allow-on-file-urls').checked); - }, - - /** @private */ - onCollectErrorsChange_: function() { - this.delegate.setItemCollectsErrors( - this.data.id, this.$$('#collect-errors').checked); - }, - - /** @private */ - onExtensionWebSiteTap_: function() { - this.delegate.openUrl(this.data.manifestHomePageUrl); - }, - - /** @private */ - onViewInStoreTap_: function() { - this.delegate.openUrl(this.data.webStoreUrl); - }, - - /** - * @param {!chrome.developerPrivate.DependentExtension} item - * @return {string} - * @private - */ - computeDependentEntry_: function(item) { - return loadTimeData.getStringF('itemDependentEntry', item.name, item.id); - }, - - /** - * @return {string} - * @private - */ - computeSourceString_: function() { - return this.data.locationText || - extensions.getItemSourceString(extensions.getItemSource(this.data)); - }, - - /** - * @param {chrome.developerPrivate.ControllerType} type - * @return {string} - * @private - */ - getIndicatorIcon_: function(type) { - switch (type) { - case 'POLICY': - return 'cr20:domain'; - case 'CHILD_CUSTODIAN': - return 'cr:account-child-invert'; - case 'SUPERVISED_USER_CUSTODIAN': - return 'cr:supervisor-account'; - default: - return ''; - } - }, - - /** - * @return {boolean} - * @private - */ - hasPermissions_: function() { - return this.data.permissions.simplePermissions.length > 0 || - this.hasRuntimeHostPermissions_(); - }, - - /** - * @return {boolean} - * @private - */ - hasRuntimeHostPermissions_: function() { - return !!this.data.permissions.runtimeHostPermissions; - }, - - /** - * @return {boolean} - * @private - */ - showSiteAccessContent_: function() { - return this.showFreeformRuntimeHostPermissions_() || - this.showHostPermissionsToggleList_(); - }, - - /** - * @return {boolean} - * @private - */ - showFreeformRuntimeHostPermissions_: function() { - return this.hasRuntimeHostPermissions_() && - this.data.permissions.runtimeHostPermissions.hasAllHosts; - }, - - /** - * @return {boolean} - * @private - */ - showHostPermissionsToggleList_: function() { - return this.hasRuntimeHostPermissions_() && - !this.data.permissions.runtimeHostPermissions.hasAllHosts; - }, - }); - - return {DetailView: DetailView}; + size_: String, + + /** @type {!ItemDelegate} */ + delegate: Object, + + /** Whether the user has enabled the UI's developer mode. */ + inDevMode: Boolean, + + /** Whether "allow in incognito" option should be shown. */ + incognitoAvailable: Boolean, + + /** Whether "View Activity Log" link should be shown. */ + showActivityLog: Boolean, + + /** Whether the user navigated to this page from the activity log page. */ + fromActivityLog: Boolean, + }, + + observers: [ + 'onItemIdChanged_(data.id, delegate)', + ], + + listeners: { + 'view-enter-start': 'onViewEnterStart_', + }, + + /** + * Focuses the extensions options button. This should be used after the + * dialog closes. + */ + focusOptionsButton: function() { + this.$$('#extensions-options').focus(); + }, + + /** + * Focuses the back button when page is loaded. + * @private + */ + onViewEnterStart_: function() { + const elementToFocus = this.fromActivityLog ? + this.$.extensionsActivityLogLink : + this.$.closeButton; + + afterNextRender(this, () => focusWithoutInk(elementToFocus)); + }, + + /** @private */ + onItemIdChanged_: function() { + // Clear the size, since this view is reused, such that no obsolete size + // is displayed.: + this.size_ = ''; + this.delegate.getExtensionSize(this.data.id).then(size => { + this.size_ = size; + }); + }, + + /** @private */ + onActivityLogTap_: function() { + navigation.navigateTo({page: Page.ACTIVITY_LOG, extensionId: this.data.id}); + }, + + /** + * @param {string} description + * @param {string} fallback + * @return {string} + * @private + */ + getDescription_: function(description, fallback) { + return description || fallback; + }, + + /** @private */ + onCloseButtonTap_: function() { + navigation.navigateTo({page: Page.LIST}); + }, + + /** + * @return {boolean} + * @private + */ + isControlled_: function() { + return isControlled(this.data); + }, + + /** + * @return {boolean} + * @private + */ + isEnabled_: function() { + return isEnabled(this.data.state); + }, + + /** + * @return {boolean} + * @private + */ + isEnableToggleEnabled_: function() { + return userCanChangeEnablement(this.data); + }, + + /** + * Returns true if the extension is in the terminated state. + * @return {boolean} + * @private + */ + isTerminated_: function() { + return this.data.state == chrome.developerPrivate.ExtensionState.TERMINATED; + }, + + /** + * @return {boolean} + * @private + */ + hasDependentExtensions_: function() { + return this.data.dependentExtensions.length > 0; + }, + + /** + * @return {boolean} + * @private + */ + hasWarnings_: function() { + return this.data.disableReasons.corruptInstall || + this.data.disableReasons.suspiciousInstall || + this.data.disableReasons.updateRequired || !!this.data.blacklistText || + this.data.runtimeWarnings.length > 0; + }, + + /** + * @return {string} + * @private + */ + computeEnabledStyle_: function() { + return this.isEnabled_() ? 'enabled-text' : ''; + }, + + /** + * @param {!chrome.developerPrivate.ExtensionState} state + * @param {string} onText + * @param {string} offText + * @return {string} + * @private + */ + computeEnabledText_: function(state, onText, offText) { + // TODO(devlin): Get the full spectrum of these strings from bettes. + return isEnabled(state) ? onText : offText; + }, + + /** + * @param {!chrome.developerPrivate.ExtensionView} view + * @return {string} + * @private + */ + computeInspectLabel_: function(view) { + return computeInspectableViewLabel(view); + }, + + /** + * @return {boolean} + * @private + */ + shouldShowOptionsLink_: function() { + return !!this.data.optionsPage; + }, + + /** + * @return {boolean} + * @private + */ + shouldShowOptionsSection_: function() { + return this.data.incognitoAccess.isEnabled || + this.data.fileAccess.isEnabled || this.data.errorCollection.isEnabled; + }, + + /** + * @return {boolean} + * @private + */ + shouldShowIncognitoOption_: function() { + return this.data.incognitoAccess.isEnabled && this.incognitoAvailable; + }, + + /** @private */ + onEnableChange_: function() { + this.delegate.setItemEnabled(this.data.id, this.$['enable-toggle'].checked); + }, + + /** + * @param {!{model: !{item: !chrome.developerPrivate.ExtensionView}}} e + * @private + */ + onInspectTap_: function(e) { + this.delegate.inspectItemView(this.data.id, e.model.item); + }, + + /** @private */ + onExtensionOptionsTap_: function() { + this.delegate.showItemOptionsPage(this.data); + }, + + /** @private */ + onReloadTap_: function() { + this.delegate.reloadItem(this.data.id).catch(loadError => { + this.fire('load-error', loadError); + }); + }, + + /** @private */ + onRemoveTap_: function() { + this.delegate.deleteItem(this.data.id); + }, + + /** @private */ + onRepairTap_: function() { + this.delegate.repairItem(this.data.id); + }, + + /** @private */ + onLoadPathTap_: function() { + this.delegate.showInFolder(this.data.id); + }, + + /** @private */ + onAllowIncognitoChange_: function() { + this.delegate.setItemAllowedIncognito( + this.data.id, this.$$('#allow-incognito').checked); + }, + + /** @private */ + onAllowOnFileUrlsChange_: function() { + this.delegate.setItemAllowedOnFileUrls( + this.data.id, this.$$('#allow-on-file-urls').checked); + }, + + /** @private */ + onCollectErrorsChange_: function() { + this.delegate.setItemCollectsErrors( + this.data.id, this.$$('#collect-errors').checked); + }, + + /** @private */ + onExtensionWebSiteTap_: function() { + this.delegate.openUrl(this.data.manifestHomePageUrl); + }, + + /** @private */ + onViewInStoreTap_: function() { + this.delegate.openUrl(this.data.webStoreUrl); + }, + + /** + * @param {!chrome.developerPrivate.DependentExtension} item + * @return {string} + * @private + */ + computeDependentEntry_: function(item) { + return loadTimeData.getStringF('itemDependentEntry', item.name, item.id); + }, + + /** + * @return {string} + * @private + */ + computeSourceString_: function() { + return this.data.locationText || + getItemSourceString(getItemSource(this.data)); + }, + + /** + * @param {chrome.developerPrivate.ControllerType} type + * @return {string} + * @private + */ + getIndicatorIcon_: function(type) { + switch (type) { + case 'POLICY': + return 'cr20:domain'; + case 'SUPERVISED_USER_CUSTODIAN': + return 'cr:supervisor-account'; + default: + return ''; + } + }, + + /** + * @return {boolean} + * @private + */ + hasPermissions_: function() { + return this.data.permissions.simplePermissions.length > 0 || + this.hasRuntimeHostPermissions_(); + }, + + /** + * @return {boolean} + * @private + */ + hasRuntimeHostPermissions_: function() { + return !!this.data.permissions.runtimeHostPermissions; + }, + + /** + * @return {boolean} + * @private + */ + showSiteAccessContent_: function() { + return this.showFreeformRuntimeHostPermissions_() || + this.showHostPermissionsToggleList_(); + }, + + /** + * @return {boolean} + * @private + */ + showFreeformRuntimeHostPermissions_: function() { + return this.hasRuntimeHostPermissions_() && + this.data.permissions.runtimeHostPermissions.hasAllHosts; + }, + + /** + * @return {boolean} + * @private + */ + showHostPermissionsToggleList_: function() { + return this.hasRuntimeHostPermissions_() && + !this.data.permissions.runtimeHostPermissions.hasAllHosts; + }, }); diff --git a/chromium/chrome/browser/resources/extensions/drag_and_drop_handler.html b/chromium/chrome/browser/resources/extensions/drag_and_drop_handler.html deleted file mode 100644 index 5e959f2d095..00000000000 --- a/chromium/chrome/browser/resources/extensions/drag_and_drop_handler.html +++ /dev/null @@ -1,3 +0,0 @@ -<link rel="import" href="chrome://resources/html/cr.html"> -<link rel="import" href="service.html"> -<script src="drag_and_drop_handler.js"></script> diff --git a/chromium/chrome/browser/resources/extensions/drag_and_drop_handler.js b/chromium/chrome/browser/resources/extensions/drag_and_drop_handler.js index 15324adb0ef..a6bfab31897 100644 --- a/chromium/chrome/browser/resources/extensions/drag_and_drop_handler.js +++ b/chromium/chrome/browser/resources/extensions/drag_and_drop_handler.js @@ -2,109 +2,103 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -cr.define('extensions', function() { - 'use strict'; - - /** @implements cr.ui.DragWrapperDelegate */ - class DragAndDropHandler { - /** - * @param {boolean} dragEnabled - * @param {!EventTarget} target - */ - constructor(dragEnabled, target) { - this.dragEnabled = dragEnabled; - - /** @private {!EventTarget} */ - this.eventTarget_ = target; - } +import {DragWrapperDelegate} from 'chrome://resources/js/cr/ui/drag_wrapper.m.js'; - /** @override */ - shouldAcceptDrag(e) { - // External Extension installation can be disabled globally, e.g. while a - // different overlay is already showing. - if (!this.dragEnabled) { - return false; - } - - // We can't access filenames during the 'dragenter' event, so we have to - // wait until 'drop' to decide whether to do something with the file or - // not. - // See: http://www.w3.org/TR/2011/WD-html5-20110113/dnd.html#concept-dnd-p - return !!e.dataTransfer.types && - e.dataTransfer.types.indexOf('Files') > -1; - } +import {Service} from './service.js'; - /** @override */ - doDragEnter() { - extensions.Service.getInstance().notifyDragInstallInProgress(); - this.eventTarget_.dispatchEvent( - new CustomEvent('extension-drag-started')); - } - /** @override */ - doDragLeave() { - this.fireDragEnded_(); - } +/** @implements DragWrapperDelegate */ +export class DragAndDropHandler { + /** + * @param {boolean} dragEnabled + * @param {!EventTarget} target + */ + constructor(dragEnabled, target) { + this.dragEnabled = dragEnabled; - /** @override */ - doDragOver(e) { - e.preventDefault(); - } + /** @private {!EventTarget} */ + this.eventTarget_ = target; + } - /** @override */ - doDrop(e) { - this.fireDragEnded_(); - if (e.dataTransfer.files.length != 1) { - return; - } - - let handled = false; - - // Files lack a check if they're a directory, but we can find out through - // its item entry. - const item = e.dataTransfer.items[0]; - if (item.kind === 'file' && item.webkitGetAsEntry().isDirectory) { - handled = true; - this.handleDirectoryDrop_(); - } else if (/\.(crx|user\.js|zip)$/i.test(e.dataTransfer.files[0].name)) { - // Only process files that look like extensions. Other files should - // navigate the browser normally. - handled = true; - this.handleFileDrop_(); - } - - if (handled) { - e.preventDefault(); - } + /** @override */ + shouldAcceptDrag(e) { + // External Extension installation can be disabled globally, e.g. while a + // different overlay is already showing. + if (!this.dragEnabled) { + return false; } - /** - * Handles a dropped file. - * @private - */ - handleFileDrop_() { - extensions.Service.getInstance().installDroppedFile(); + // We can't access filenames during the 'dragenter' event, so we have to + // wait until 'drop' to decide whether to do something with the file or + // not. + // See: http://www.w3.org/TR/2011/WD-html5-20110113/dnd.html#concept-dnd-p + return !!e.dataTransfer.types && e.dataTransfer.types.indexOf('Files') > -1; + } + + /** @override */ + doDragEnter() { + Service.getInstance().notifyDragInstallInProgress(); + this.eventTarget_.dispatchEvent(new CustomEvent('extension-drag-started')); + } + + /** @override */ + doDragLeave() { + this.fireDragEnded_(); + } + + /** @override */ + doDragOver(e) { + e.preventDefault(); + } + + /** @override */ + doDrop(e) { + this.fireDragEnded_(); + if (e.dataTransfer.files.length != 1) { + return; } - /** - * Handles a dropped directory. - * @private - */ - handleDirectoryDrop_() { - extensions.Service.getInstance().loadUnpackedFromDrag().catch( - loadError => { - this.eventTarget_.dispatchEvent(new CustomEvent( - 'drag-and-drop-load-error', {detail: loadError})); - }); + let handled = false; + + // Files lack a check if they're a directory, but we can find out through + // its item entry. + const item = e.dataTransfer.items[0]; + if (item.kind === 'file' && item.webkitGetAsEntry().isDirectory) { + handled = true; + this.handleDirectoryDrop_(); + } else if (/\.(crx|user\.js|zip)$/i.test(e.dataTransfer.files[0].name)) { + // Only process files that look like extensions. Other files should + // navigate the browser normally. + handled = true; + this.handleFileDrop_(); } - /** @private */ - fireDragEnded_() { - this.eventTarget_.dispatchEvent(new CustomEvent('extension-drag-ended')); + if (handled) { + e.preventDefault(); } } - return { - DragAndDropHandler: DragAndDropHandler, - }; -}); + /** + * Handles a dropped file. + * @private + */ + handleFileDrop_() { + Service.getInstance().installDroppedFile(); + } + + /** + * Handles a dropped directory. + * @private + */ + handleDirectoryDrop_() { + Service.getInstance().loadUnpackedFromDrag().catch(loadError => { + this.eventTarget_.dispatchEvent( + new CustomEvent('drag-and-drop-load-error', {detail: loadError})); + }); + } + + /** @private */ + fireDragEnded_() { + this.eventTarget_.dispatchEvent(new CustomEvent('extension-drag-ended')); + } +} diff --git a/chromium/chrome/browser/resources/extensions/drop_overlay.html b/chromium/chrome/browser/resources/extensions/drop_overlay.html index b3209817520..beedbf81ab2 100644 --- a/chromium/chrome/browser/resources/extensions/drop_overlay.html +++ b/chromium/chrome/browser/resources/extensions/drop_overlay.html @@ -1,57 +1,42 @@ -<link rel="import" href="chrome://resources/html/polymer.html"> +<style include="cr-hidden-style"> + :host { + align-items: center; + background-color: rgba(241, 241, 241, .9); + color: var(--cr-secondary-text-color); + display: flex; + height: 100%; + justify-content: center; + position: absolute; + width: 100%; + z-index: 10; + } -<link rel="import" href="chrome://resources/cr_elements/hidden_style_css.html"> -<link rel="import" href="chrome://resources/cr_elements/icons.html"> -<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html"> -<link rel="import" href="chrome://resources/html/cr.html"> -<link rel="import" href="chrome://resources/html/cr/ui/drag_wrapper.html"> -<link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html"> -<link rel="import" href="drag_and_drop_handler.html"> + @media (prefers-color-scheme: dark) { + :host { + /* TODO(dbeam): share with cr-dialog dialog::backdrop? */ + background-color: rgba(0, 0, 0, .6); + } + } -<dom-module id="extensions-drop-overlay"> - <template> - <style include="cr-hidden-style"> - :host { - align-items: center; - background-color: rgba(241, 241, 241, .9); - color: var(--cr-secondary-text-color); - display: flex; - height: 100%; - justify-content: center; - position: absolute; - width: 100%; - z-index: 10; - } + #container { + align-items: center; + display: flex; + flex-direction: column; + } - @media (prefers-color-scheme: dark) { - :host { - /* TODO(dbeam): share with cr-dialog dialog::backdrop? */ - background-color: rgba(0, 0, 0, .6); - } - } + iron-icon { + height: 64px; + margin-bottom: 16px; + width: 64px; + } - #container { - align-items: center; - display: flex; - flex-direction: column; - } - - iron-icon { - height: 64px; - margin-bottom: 16px; - width: 64px; - } - - #text { - color: #6e6e6e; - font-size: 123.1%; - font-weight: 500; - } - </style> - <div id="container"> - <iron-icon icon="cr:extension"></iron-icon> - <div id="text">$i18n{dropToInstall}</div> - </div> - </template> - <script src="drop_overlay.js"></script> -</dom-module> + #text { + color: #6e6e6e; + font-size: 123.1%; + font-weight: 500; + } +</style> +<div id="container"> + <iron-icon icon="cr:extension"></iron-icon> + <div id="text">$i18n{dropToInstall}</div> +</div> diff --git a/chromium/chrome/browser/resources/extensions/drop_overlay.js b/chromium/chrome/browser/resources/extensions/drop_overlay.js index 913d7186a84..2a627f85c7a 100644 --- a/chromium/chrome/browser/resources/extensions/drop_overlay.js +++ b/chromium/chrome/browser/resources/extensions/drop_overlay.js @@ -2,12 +2,21 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -(function() { -'use strict'; +import 'chrome://resources/cr_elements/hidden_style_css.m.js'; +import 'chrome://resources/cr_elements/icons.m.js'; +import 'chrome://resources/cr_elements/shared_vars_css.m.js'; +import 'chrome://resources/polymer/v3_0/iron-icon/iron-icon.js'; + +import {DragWrapper} from 'chrome://resources/js/cr/ui/drag_wrapper.m.js'; +import {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; + +import {DragAndDropHandler} from './drag_and_drop_handler.js'; Polymer({ is: 'extensions-drop-overlay', + _template: html`{__html_template__}`, + properties: { /** @private {boolean} */ dragEnabled: { @@ -20,8 +29,7 @@ Polymer({ created: function() { this.hidden = true; const dragTarget = document.documentElement; - this.dragWrapperHandler_ = - new extensions.DragAndDropHandler(true, dragTarget); + this.dragWrapperHandler_ = new DragAndDropHandler(true, dragTarget); // TODO(devlin): All these dragTarget listeners leak (they aren't removed // when the element is). This only matters in tests at the moment, but would // be good to fix. @@ -34,8 +42,7 @@ Polymer({ dragTarget.addEventListener('drag-and-drop-load-error', (e) => { this.fire('load-error', e.detail); }); - this.dragWrapper_ = - new cr.ui.DragWrapper(dragTarget, this.dragWrapperHandler_); + this.dragWrapper_ = new DragWrapper(dragTarget, this.dragWrapperHandler_); }, /** @@ -46,4 +53,3 @@ Polymer({ this.dragWrapperHandler_.dragEnabled = dragEnabled; }, }); -})(); diff --git a/chromium/chrome/browser/resources/extensions/error_page.html b/chromium/chrome/browser/resources/extensions/error_page.html index 4e0e4784985..5f67a455dd8 100644 --- a/chromium/chrome/browser/resources/extensions/error_page.html +++ b/chromium/chrome/browser/resources/extensions/error_page.html @@ -1,215 +1,192 @@ -<link rel="import" href="chrome://resources/html/polymer.html"> - -<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html"> -<link rel="import" href="chrome://resources/cr_elements/cr_container_shadow_behavior.html"> -<link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html"> -<link rel="import" href="chrome://resources/cr_elements/cr_icons_css.html"> -<link rel="import" href="chrome://resources/cr_elements/icons.html"> -<link rel="import" href="chrome://resources/cr_elements/shared_style_css.html"> -<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html"> -<link rel="import" href="chrome://resources/html/cr.html"> -<link rel="import" href="chrome://resources/html/cr/ui/focus_outline_manager.html"> -<link rel="import" href="chrome://resources/html/cr/ui/focus_without_ink.html"> -<link rel="import" href="chrome://resources/polymer/v1_0/iron-collapse/iron-collapse.html"> -<link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html"> -<link rel="import" href="chrome://resources/polymer/v1_0/paper-styles/color.html"> -<link rel="import" href="code_section.html"> -<link rel="import" href="item_util.html"> -<link rel="import" href="navigation_helper.html"> -<link rel="import" href="shared_style.html"> - -<dom-module id="extensions-error-page"> - <template> - <style include="cr-icons cr-shared-style shared-style"> - :host { - display: block; - height: 100%; - } - - iron-icon { - --iron-icon-fill-color: var(--google-grey-refresh-700); - @apply --cr-icon-height-width; - flex-shrink: 0; - } - - iron-icon[icon='cr:warning'] { - /* TODO(dbeam): find dark mode equivalent for this orange. */ - --iron-icon-fill-color: var(--paper-orange-500); - } - - iron-icon[icon='cr:error'] { - --iron-icon-fill-color: var(--error-color); - } - - .section { - padding: 0 var(--cr-section-padding); - } - - #heading { - align-items: center; - display: flex; - height: 40px; - margin-bottom: 30px; - padding: 8px 12px 0; - } - - #heading span { - flex: 1; - margin: 0 10px; - } - - #errorsList { - min-height: 100px; - } - - .error-item { - @apply --cr-section; - padding-inline-start: 0; - } - - .error-item cr-icon-button { - margin: 0; - } - - .error-item.selected { - background-color: rgba(0, 0, 0, 0.08); - } - - .error-item .start { - align-items: center; - align-self: stretch; /* Makes the tappable area fill its parent. */ - display: flex; - flex: 1; - padding: 0 var(--cr-section-padding); - } - - .error-message { - flex-grow: 1; - margin-inline-start: 10px; - word-break: break-word; - } - - .devtools-controls { - padding: 0 var(--cr-section-padding); - } - - .details-heading { - align-items: center; - display: flex; - height: var(--cr-section-min-height); - } - - .stack-trace-container { - list-style: none; - margin-top: 0; - padding: 0; - } - - .stack-trace-container li { - cursor: pointer; - font-family: monospace; - padding: 4px; - } - - .stack-trace-container li.selected, - .stack-trace-container li:hover { - background: var(--google-blue-100); - color: var(--google-grey-900); /* Same in light & dark modes. */ - } - - extensions-code-section { - height: 200px; - margin-bottom: 20px; - } - - :host-context(.focus-outline-visible) .start:focus { - outline: -webkit-focus-ring-color auto 5px; - } - - .start:focus { - outline: none; - } - - .context-url { - word-wrap: break-word; - } - </style> - <div class="page-container" id="container"> - <div class="page-content"> - <div id="heading" class="cr-title-text"> - <cr-icon-button class="icon-arrow-back no-overlap" id="closeButton" - aria-label="$i18n{back}" on-click="onCloseButtonTap_"> - </cr-icon-button> - <span>$i18n{errorsPageHeading}</span> - <cr-button on-click="onClearAllTap_" hidden="[[!entries_.length]]"> - $i18n{clearAll} - </cr-button> - </div> - <div class="section"> - <div id="errorsList"> - <template is="dom-repeat" items="[[entries_]]"> - <div class="item-container"> - <div class$="error-item - [[computeErrorClass_(item, selectedEntry_)]]"> - <div actionable class="start" on-click="onErrorItemAction_" - on-keydown="onErrorItemAction_" tabindex="0" - role="button" aria-expanded$="[[isAriaExpanded_( - index, selectedEntry_)]]"> - <iron-icon icon$="cr:[[computeErrorIcon_(item)]]" - title$="[[computeErrorTypeLabel_(item)]]"> - </iron-icon> - <div id$="[[item.id]]" class="error-message"> - [[item.message]] - </div> - <div class$="cr-icon [[iconName_(index, selectedEntry_)]]"> - </div> - </div> - <div class="separator"></div> - <cr-icon-button class="icon-delete-gray" - on-click="onDeleteErrorAction_" - aria-describedby$="[[item.id]]" - aria-label="$i18n{clearEntry}"></cr-icon-button> +<style include="cr-icons cr-shared-style shared-style"> + :host { + display: block; + height: 100%; + } + + iron-icon { + --iron-icon-fill-color: var(--google-grey-refresh-700); + @apply --cr-icon-height-width; + flex-shrink: 0; + } + + iron-icon[icon='cr:warning'] { + /* TODO(dbeam): find dark mode equivalent for this orange. */ + --iron-icon-fill-color: var(--paper-orange-500); + } + + iron-icon[icon='cr:error'] { + --iron-icon-fill-color: var(--error-color); + } + + .section { + padding: 0 var(--cr-section-padding); + } + + #heading { + align-items: center; + display: flex; + height: 40px; + margin-bottom: 30px; + padding: 8px 12px 0; + } + + #heading span { + flex: 1; + margin: 0 10px; + } + + #errorsList { + min-height: 100px; + } + + .error-item { + @apply --cr-section; + padding-inline-start: 0; + } + + .error-item cr-icon-button { + margin: 0; + } + + .error-item.selected { + background-color: rgba(0, 0, 0, 0.08); + } + + .error-item .start { + align-items: center; + align-self: stretch; /* Makes the tappable area fill its parent. */ + display: flex; + flex: 1; + padding: 0 var(--cr-section-padding); + } + + .error-message { + flex-grow: 1; + margin-inline-start: 10px; + word-break: break-word; + } + + .devtools-controls { + padding: 0 var(--cr-section-padding); + } + + .details-heading { + align-items: center; + display: flex; + height: var(--cr-section-min-height); + } + + .stack-trace-container { + list-style: none; + margin-top: 0; + padding: 0; + } + + .stack-trace-container li { + cursor: pointer; + font-family: monospace; + padding: 4px; + } + + .stack-trace-container li.selected, + .stack-trace-container li:hover { + background: var(--google-blue-100); + color: var(--google-grey-900); /* Same in light & dark modes. */ + } + + extensions-code-section { + height: 200px; + margin-bottom: 20px; + } + + :host-context(.focus-outline-visible) .start:focus { + outline: -webkit-focus-ring-color auto 5px; + } + + .start:focus { + outline: none; + } + + .context-url { + word-wrap: break-word; + } +</style> +<div class="page-container" id="container"> + <div class="page-content"> + <div id="heading" class="cr-title-text"> + <cr-icon-button class="icon-arrow-back no-overlap" id="closeButton" + aria-label="$i18n{back}" on-click="onCloseButtonTap_"> + </cr-icon-button> + <span role="heading" aria-level="2">$i18n{errorsPageHeading}</span> + <cr-button on-click="onClearAllTap_" hidden="[[!entries_.length]]"> + $i18n{clearAll} + </cr-button> + </div> + <div class="section"> + <div id="errorsList"> + <template is="dom-repeat" items="[[entries_]]"> + <div class="item-container"> + <div class$="error-item + [[computeErrorClass_(item, selectedEntry_)]]"> + <div actionable class="start" on-click="onErrorItemAction_" + on-keydown="onErrorItemAction_" tabindex="0" + role="button" aria-expanded$="[[isAriaExpanded_( + index, selectedEntry_)]]"> + <iron-icon icon$="cr:[[computeErrorIcon_(item)]]" + title$="[[computeErrorTypeLabel_(item)]]"> + </iron-icon> + <div id$="[[item.id]]" class="error-message"> + [[item.message]] </div> - <iron-collapse opened="[[isOpened_(index, selectedEntry_)]]"> - <div class="devtools-controls"> - <template is="dom-if" - if="[[computeIsRuntimeError_(item)]]"> - <div class="details-heading cr-title-text"> - $i18n{errorContext} - </div> - <span class="context-url"> - [[getContextUrl_( - item, '$i18nPolymer{errorContextUnknown}')]] - </span> - <div class="details-heading cr-title-text"> - $i18n{stackTrace} - </div> - <ul class="stack-trace-container" - on-keydown="onStackKeydown_"> - <template is="dom-repeat" items="[[item.stackTrace]]"> - <li on-click="onStackFrameTap_" - tabindex$="[[getStackFrameTabIndex_(item, - selectedStackFrame_)]]" - hidden="[[!shouldDisplayFrame_(item.url)]]" - class$="[[getStackFrameClass_(item, - selectedStackFrame_)]]"> - [[getStackTraceLabel_(item)]] - </li> - </template> - </ul> - </template> - <extensions-code-section code="[[code_]]" - is-active="[[isOpened_(index, selectedEntry_)]]" - could-not-display-code="$i18n{noErrorsToShow}"> - </extensions-code-section> + <div class$="cr-icon [[iconName_(index, selectedEntry_)]]"> + </div> + </div> + <div class="separator"></div> + <cr-icon-button class="icon-delete-gray" + on-click="onDeleteErrorAction_" + aria-describedby$="[[item.id]]" + aria-label="$i18n{clearEntry}"></cr-icon-button> + </div> + <iron-collapse opened="[[isOpened_(index, selectedEntry_)]]"> + <div class="devtools-controls"> + <template is="dom-if" + if="[[computeIsRuntimeError_(item)]]"> + <div class="details-heading cr-title-text" role="heading" + aria-level="3"> + $i18n{errorContext} + </div> + <span class="context-url"> + [[getContextUrl_( + item, '$i18nPolymer{errorContextUnknown}')]] + </span> + <div class="details-heading cr-title-text" role="heading" + aria-level="3"> + $i18n{stackTrace} </div> - </iron-collapse> + <ul class="stack-trace-container" + on-keydown="onStackKeydown_"> + <template is="dom-repeat" items="[[item.stackTrace]]"> + <li on-click="onStackFrameTap_" + tabindex$="[[getStackFrameTabIndex_(item, + selectedStackFrame_)]]" + hidden="[[!shouldDisplayFrame_(item.url)]]" + class$="[[getStackFrameClass_(item, + selectedStackFrame_)]]"> + [[getStackTraceLabel_(item)]] + </li> + </template> + </ul> + </template> + <extensions-code-section code="[[code_]]" + is-active="[[isOpened_(index, selectedEntry_)]]" + could-not-display-code="$i18n{noErrorsToShow}"> + </extensions-code-section> </div> - </template> + </iron-collapse> </div> - </div> + </template> </div> </div> - </template> - <script src="error_page.js"></script> -</dom-module> + </div> +</div> diff --git a/chromium/chrome/browser/resources/extensions/error_page.js b/chromium/chrome/browser/resources/extensions/error_page.js index fea88babb4a..1da980e9a85 100644 --- a/chromium/chrome/browser/resources/extensions/error_page.js +++ b/chromium/chrome/browser/resources/extensions/error_page.js @@ -2,428 +2,440 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'chrome://resources/cr_elements/cr_button/cr_button.m.js'; +import 'chrome://resources/cr_elements/cr_icon_button/cr_icon_button.m.js'; +import 'chrome://resources/cr_elements/cr_icons_css.m.js'; +import 'chrome://resources/cr_elements/icons.m.js'; +import 'chrome://resources/cr_elements/shared_style_css.m.js'; +import 'chrome://resources/cr_elements/shared_vars_css.m.js'; +import 'chrome://resources/polymer/v3_0/iron-collapse/iron-collapse.js'; +import 'chrome://resources/polymer/v3_0/iron-icon/iron-icon.js'; +import 'chrome://resources/polymer/v3_0/paper-styles/color.js'; +import './code_section.js'; +import './shared_style.js'; + +import {CrContainerShadowBehavior} from 'chrome://resources/cr_elements/cr_container_shadow_behavior.m.js'; +import {assert, assertNotReached} from 'chrome://resources/js/assert.m.js'; +import {FocusOutlineManager} from 'chrome://resources/js/cr/ui/focus_outline_manager.m.js'; +import {focusWithoutInk} from 'chrome://resources/js/cr/ui/focus_without_ink.m.js'; +import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js'; +import {afterNextRender, html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; + +import {navigation, Page} from './navigation_helper.js'; + /** @typedef {chrome.developerPrivate.ManifestError} */ let ManifestError; /** @typedef {chrome.developerPrivate.RuntimeError} */ let RuntimeError; -cr.define('extensions', function() { - 'use strict'; - - /** @interface */ - class ErrorPageDelegate { - /** - * @param {string} extensionId - * @param {!Array<number>=} errorIds - * @param {chrome.developerPrivate.ErrorType=} type - */ - deleteErrors(extensionId, errorIds, type) {} - - /** - * @param {chrome.developerPrivate.RequestFileSourceProperties} args - * @return {!Promise<!chrome.developerPrivate.RequestFileSourceResponse>} - */ - requestFileSource(args) {} - } - +/** @interface */ +export class ErrorPageDelegate { /** - * Get the URL relative to the main extension url. If the url is - * unassociated with the extension, this will be the full url. - * @param {string} url - * @param {?(ManifestError|RuntimeError)} error - * @return {string} + * @param {string} extensionId + * @param {!Array<number>=} errorIds + * @param {chrome.developerPrivate.ErrorType=} type */ - function getRelativeUrl(url, error) { - const fullUrl = 'chrome-extension://' + error.extensionId + '/'; - return url.startsWith(fullUrl) ? url.substring(fullUrl.length) : url; - } + deleteErrors(extensionId, errorIds, type) {} /** - * Given 3 strings, this function returns the correct one for the type of - * error that |item| is. - * @param {!ManifestError|!RuntimeError} item - * @param {string} log - * @param {string} warn - * @param {string} error - * @return {string} - * @private + * @param {chrome.developerPrivate.RequestFileSourceProperties} args + * @return {!Promise<!chrome.developerPrivate.RequestFileSourceResponse>} */ - function getErrorSeverityText_(item, log, warn, error) { - if (item.type == chrome.developerPrivate.ErrorType.RUNTIME) { - switch (item.severity) { - case chrome.developerPrivate.ErrorLevel.LOG: - return log; - case chrome.developerPrivate.ErrorLevel.WARN: - return warn; - case chrome.developerPrivate.ErrorLevel.ERROR: - return error; - } - assertNotReached(); + requestFileSource(args) {} +} + +/** + * Get the URL relative to the main extension url. If the url is + * unassociated with the extension, this will be the full url. + * @param {string} url + * @param {?(ManifestError|RuntimeError)} error + * @return {string} + */ +function getRelativeUrl(url, error) { + const fullUrl = 'chrome-extension://' + error.extensionId + '/'; + return url.startsWith(fullUrl) ? url.substring(fullUrl.length) : url; +} + +/** + * Given 3 strings, this function returns the correct one for the type of + * error that |item| is. + * @param {!ManifestError|!RuntimeError} item + * @param {string} log + * @param {string} warn + * @param {string} error + * @return {string} + * @private + */ +function getErrorSeverityText_(item, log, warn, error) { + if (item.type == chrome.developerPrivate.ErrorType.RUNTIME) { + switch (item.severity) { + case chrome.developerPrivate.ErrorLevel.LOG: + return log; + case chrome.developerPrivate.ErrorLevel.WARN: + return warn; + case chrome.developerPrivate.ErrorLevel.ERROR: + return error; } - assert(item.type == chrome.developerPrivate.ErrorType.MANIFEST); - return warn; + assertNotReached(); } + assert(item.type == chrome.developerPrivate.ErrorType.MANIFEST); + return warn; +} - const ErrorPage = Polymer({ - is: 'extensions-error-page', - - behaviors: [CrContainerShadowBehavior], - - properties: { - /** @type {!chrome.developerPrivate.ExtensionInfo|undefined} */ - data: Object, - - /** @type {!extensions.ErrorPageDelegate|undefined} */ - delegate: Object, +Polymer({ + is: 'extensions-error-page', - // Whether or not dev mode is enabled. - inDevMode: { - type: Boolean, - value: false, - observer: 'onInDevModeChanged_', - }, - - /** @private {!Array<!(ManifestError|RuntimeError)>} */ - entries_: Array, - - /** @private {?chrome.developerPrivate.RequestFileSourceResponse} */ - code_: Object, + _template: html`{__html_template__}`, - /** - * Index into |entries_|. - * @private - */ - selectedEntry_: { - type: Number, - observer: 'onSelectedErrorChanged_', - }, - - /** @private {?chrome.developerPrivate.StackFrame}*/ - selectedStackFrame_: { - type: Object, - value: function() { - return null; - }, - }, - }, + behaviors: [CrContainerShadowBehavior], - observers: [ - 'observeDataChanges_(data.*)', - ], + properties: { + /** @type {!chrome.developerPrivate.ExtensionInfo|undefined} */ + data: Object, - listeners: { - 'view-enter-start': 'onViewEnterStart_', - }, + /** @type {!ErrorPageDelegate|undefined} */ + delegate: Object, - /** @override */ - ready: function() { - cr.ui.FocusOutlineManager.forDocument(document); + // Whether or not dev mode is enabled. + inDevMode: { + type: Boolean, + value: false, + observer: 'onInDevModeChanged_', }, - /** @return {!ManifestError|!RuntimeError} */ - getSelectedError: function() { - return this.entries_[this.selectedEntry_]; - }, + /** @private {!Array<!(ManifestError|RuntimeError)>} */ + entries_: Array, - /** - * Focuses the back button when page is loaded. - * @private - */ - onViewEnterStart_: function() { - Polymer.RenderStatus.afterNextRender( - this, () => cr.ui.focusWithoutInk(this.$.closeButton)); - chrome.metricsPrivate.recordUserAction('Options_ViewExtensionErrors'); - }, + /** @private {?chrome.developerPrivate.RequestFileSourceResponse} */ + code_: Object, /** - * @param {!ManifestError|!RuntimeError} error - * @param {string} unknown - * @return {string} + * Index into |entries_|. * @private */ - getContextUrl_: function(error, unknown) { - return error.contextUrl ? getRelativeUrl(error.contextUrl, error) : - unknown; + selectedEntry_: { + type: Number, + observer: 'onSelectedErrorChanged_', }, - /** - * Watches for changes to |data| in order to fetch the corresponding - * file source. - * @private - */ - observeDataChanges_: function() { - const errors = this.data.manifestErrors.concat(this.data.runtimeErrors); - this.entries_ = errors; - this.selectedEntry_ = -1; // This also help reset code-section content. - if (this.entries_.length) { - this.selectedEntry_ = 0; - } + /** @private {?chrome.developerPrivate.StackFrame}*/ + selectedStackFrame_: { + type: Object, + value: function() { + return null; + }, }, + }, - /** @private */ - onCloseButtonTap_: function() { - extensions.navigation.navigateTo({page: extensions.Page.LIST}); - }, + observers: [ + 'observeDataChanges_(data.*)', + ], - /** @private */ - onClearAllTap_: function() { - const ids = this.entries_.map(entry => entry.id); - this.delegate.deleteErrors(this.data.id, ids); - }, + listeners: { + 'view-enter-start': 'onViewEnterStart_', + }, - /** - * @param {!ManifestError|!RuntimeError} error - * @return {string} - * @private - */ - computeErrorIcon_: function(error) { - // Do not i18n these strings, they're CSS classes. - return getErrorSeverityText_(error, 'info', 'warning', 'error'); - }, + /** @override */ + ready: function() { + FocusOutlineManager.forDocument(document); + }, - /** - * @param {!ManifestError|!RuntimeError} error - * @return {string} - * @private - */ - computeErrorTypeLabel_: function(error) { - return getErrorSeverityText_( - error, loadTimeData.getString('logLevel'), - loadTimeData.getString('warnLevel'), - loadTimeData.getString('errorLevel')); - }, + /** @return {!ManifestError|!RuntimeError} */ + getSelectedError: function() { + return this.entries_[this.selectedEntry_]; + }, - /** - * @param {!Event} e - * @private - */ - onDeleteErrorAction_: function(e) { - this.delegate.deleteErrors( - this.data.id, [(/** @type {!{model:Object}} */ (e)).model.item.id]); - e.stopPropagation(); - }, + /** + * Focuses the back button when page is loaded. + * @private + */ + onViewEnterStart_: function() { + afterNextRender(this, () => focusWithoutInk(this.$.closeButton)); + chrome.metricsPrivate.recordUserAction('Options_ViewExtensionErrors'); + }, - /** private */ - onInDevModeChanged_: function() { - if (!this.inDevMode) { - // Wait until next render cycle in case error page is loading. - this.async(() => { - this.onCloseButtonTap_(); - }); - } - }, + /** + * @param {!ManifestError|!RuntimeError} error + * @param {string} unknown + * @return {string} + * @private + */ + getContextUrl_: function(error, unknown) { + return error.contextUrl ? getRelativeUrl(error.contextUrl, error) : unknown; + }, - /** - * Fetches the source for the selected error and populates the code section. - * @private - */ - onSelectedErrorChanged_: function() { - this.code_ = null; + /** + * Watches for changes to |data| in order to fetch the corresponding + * file source. + * @private + */ + observeDataChanges_: function() { + const errors = this.data.manifestErrors.concat(this.data.runtimeErrors); + this.entries_ = errors; + this.selectedEntry_ = -1; // This also help reset code-section content. + if (this.entries_.length) { + this.selectedEntry_ = 0; + } + }, - if (this.selectedEntry_ < 0) { - return; - } + /** @private */ + onCloseButtonTap_: function() { + navigation.navigateTo({page: Page.LIST}); + }, - const error = this.getSelectedError(); - const args = { - extensionId: error.extensionId, - message: error.message, - }; - switch (error.type) { - case chrome.developerPrivate.ErrorType.MANIFEST: - args.pathSuffix = error.source; - args.manifestKey = error.manifestKey; - args.manifestSpecific = error.manifestSpecific; - break; - case chrome.developerPrivate.ErrorType.RUNTIME: - // slice(1) because pathname starts with a /. - args.pathSuffix = new URL(error.source).pathname.slice(1); - args.lineNumber = error.stackTrace && error.stackTrace[0] ? - error.stackTrace[0].lineNumber : - 0; - this.selectedStackFrame_ = error.stackTrace && error.stackTrace[0] ? - error.stackTrace[0] : - null; - break; - } - this.delegate.requestFileSource(args).then(code => this.code_ = code); - }, + /** @private */ + onClearAllTap_: function() { + const ids = this.entries_.map(entry => entry.id); + this.delegate.deleteErrors(this.data.id, ids); + }, - /** - * @return {boolean} - * @private - */ - computeIsRuntimeError_: function(item) { - return item.type == chrome.developerPrivate.ErrorType.RUNTIME; - }, + /** + * @param {!ManifestError|!RuntimeError} error + * @return {string} + * @private + */ + computeErrorIcon_: function(error) { + // Do not i18n these strings, they're CSS classes. + return getErrorSeverityText_(error, 'info', 'warning', 'error'); + }, - /** - * The description is a human-readable summation of the frame, in the - * form "<relative_url>:<line_number> (function)", e.g. - * "myfile.js:25 (myFunction)". - * @param {!chrome.developerPrivate.StackFrame} frame - * @return {string} - * @private - */ - getStackTraceLabel_: function(frame) { - let description = getRelativeUrl(frame.url, this.getSelectedError()) + - ':' + frame.lineNumber; - - if (frame.functionName) { - const functionName = frame.functionName == '(anonymous function)' ? - loadTimeData.getString('anonymousFunction') : - frame.functionName; - description += ' (' + functionName + ')'; - } + /** + * @param {!ManifestError|!RuntimeError} error + * @return {string} + * @private + */ + computeErrorTypeLabel_: function(error) { + return getErrorSeverityText_( + error, loadTimeData.getString('logLevel'), + loadTimeData.getString('warnLevel'), + loadTimeData.getString('errorLevel')); + }, - return description; - }, + /** + * @param {!Event} e + * @private + */ + onDeleteErrorAction_: function(e) { + this.delegate.deleteErrors( + this.data.id, [(/** @type {!{model:Object}} */ (e)).model.item.id]); + e.stopPropagation(); + }, + + /** private */ + onInDevModeChanged_: function() { + if (!this.inDevMode) { + // Wait until next render cycle in case error page is loading. + this.async(() => { + this.onCloseButtonTap_(); + }); + } + }, - /** - * @param {chrome.developerPrivate.StackFrame} frame - * @return {string} - * @private - */ - getStackFrameClass_: function(frame) { - return frame == this.selectedStackFrame_ ? 'selected' : ''; - }, + /** + * Fetches the source for the selected error and populates the code section. + * @private + */ + onSelectedErrorChanged_: function() { + this.code_ = null; - /** - * @param {!chrome.developerPrivate.StackFrame} frame - * @return {number} - * @private - */ - getStackFrameTabIndex_: function(frame) { - return frame == this.selectedStackFrame_ ? 0 : -1; - }, + if (this.selectedEntry_ < 0) { + return; + } - /** - * This function is used to determine whether or not we want to show a - * stack frame. We don't want to show code from internal scripts. - * @param {string} url - * @return {boolean} - * @private - */ - shouldDisplayFrame_: function(url) { - // All our internal scripts are in the 'extensions::' namespace. - return !/^extensions::/.test(url); - }, + const error = this.getSelectedError(); + const args = { + extensionId: error.extensionId, + message: error.message, + }; + switch (error.type) { + case chrome.developerPrivate.ErrorType.MANIFEST: + args.pathSuffix = error.source; + args.manifestKey = error.manifestKey; + args.manifestSpecific = error.manifestSpecific; + break; + case chrome.developerPrivate.ErrorType.RUNTIME: + // slice(1) because pathname starts with a /. + args.pathSuffix = new URL(error.source).pathname.slice(1); + args.lineNumber = error.stackTrace && error.stackTrace[0] ? + error.stackTrace[0].lineNumber : + 0; + this.selectedStackFrame_ = error.stackTrace && error.stackTrace[0] ? + error.stackTrace[0] : + null; + break; + } + this.delegate.requestFileSource(args).then(code => this.code_ = code); + }, - /** - * @param {!chrome.developerPrivate.StackFrame} frame - * @private - */ - updateSelected_: function(frame) { - this.selectedStackFrame_ = assert(frame); - - const selectedError = this.getSelectedError(); - this.delegate - .requestFileSource({ - extensionId: selectedError.extensionId, - message: selectedError.message, - pathSuffix: getRelativeUrl(frame.url, selectedError), - lineNumber: frame.lineNumber, - }) - .then(code => this.code_ = code); - }, + /** + * @return {boolean} + * @private + */ + computeIsRuntimeError_: function(item) { + return item.type == chrome.developerPrivate.ErrorType.RUNTIME; + }, - /** - * @param {!Event} e - * @private - */ - onStackFrameTap_: function(e) { - const frame = /** @type {!{model:Object}} */ (e).model.item; - this.updateSelected_(frame); - }, + /** + * The description is a human-readable summation of the frame, in the + * form "<relative_url>:<line_number> (function)", e.g. + * "myfile.js:25 (myFunction)". + * @param {!chrome.developerPrivate.StackFrame} frame + * @return {string} + * @private + */ + getStackTraceLabel_: function(frame) { + let description = getRelativeUrl(frame.url, this.getSelectedError()) + ':' + + frame.lineNumber; + + if (frame.functionName) { + const functionName = frame.functionName == '(anonymous function)' ? + loadTimeData.getString('anonymousFunction') : + frame.functionName; + description += ' (' + functionName + ')'; + } - /** - * @param {!Event} e - * @private - */ - onStackKeydown_: function(e) { - let direction = 0; - - if (e.key == 'ArrowDown') { - direction = 1; - } else if (e.key == 'ArrowUp') { - direction = -1; - } else { - return; - } + return description; + }, - e.preventDefault(); + /** + * @param {chrome.developerPrivate.StackFrame} frame + * @return {string} + * @private + */ + getStackFrameClass_: function(frame) { + return frame == this.selectedStackFrame_ ? 'selected' : ''; + }, - const list = e.target.parentElement.querySelectorAll('li'); + /** + * @param {!chrome.developerPrivate.StackFrame} frame + * @return {number} + * @private + */ + getStackFrameTabIndex_: function(frame) { + return frame == this.selectedStackFrame_ ? 0 : -1; + }, - for (let i = 0; i < list.length; ++i) { - if (list[i].classList.contains('selected')) { - const polymerEvent = /** @type {!{model: !Object}} */ (e); - const frame = polymerEvent.model.item.stackTrace[i + direction]; - if (frame) { - this.updateSelected_(frame); - list[i + direction].focus(); // Preserve focus. - } - return; - } - } - }, + /** + * This function is used to determine whether or not we want to show a + * stack frame. We don't want to show code from internal scripts. + * @param {string} url + * @return {boolean} + * @private + */ + shouldDisplayFrame_: function(url) { + // All our internal scripts are in the 'extensions::' namespace. + return !/^extensions::/.test(url); + }, - /** - * Computes the class name for the error item depending on whether its - * the currently selected error. - * @param {number} index - * @return {string} - * @private - */ - computeErrorClass_: function(index) { - return index == this.selectedEntry_ ? 'selected' : ''; - }, + /** + * @param {!chrome.developerPrivate.StackFrame} frame + * @private + */ + updateSelected_: function(frame) { + this.selectedStackFrame_ = assert(frame); + + const selectedError = this.getSelectedError(); + this.delegate + .requestFileSource({ + extensionId: selectedError.extensionId, + message: selectedError.message, + pathSuffix: getRelativeUrl(frame.url, selectedError), + lineNumber: frame.lineNumber, + }) + .then(code => this.code_ = code); + }, - /** @private */ - iconName_: function(index) { - return index == this.selectedEntry_ ? 'icon-expand-less' : - 'icon-expand-more'; - }, + /** + * @param {!Event} e + * @private + */ + onStackFrameTap_: function(e) { + const frame = /** @type {!{model:Object}} */ (e).model.item; + this.updateSelected_(frame); + }, - /** - * Determine if the iron-collapse should be opened (expanded). - * @param {number} index - * @return {boolean} - * @private - */ - isOpened_: function(index) { - return index == this.selectedEntry_; - }, + /** + * @param {!Event} e + * @private + */ + onStackKeydown_: function(e) { + let direction = 0; + + if (e.key == 'ArrowDown') { + direction = 1; + } else if (e.key == 'ArrowUp') { + direction = -1; + } else { + return; + } + e.preventDefault(); - /** - * @param {number} index - * @return {string} The aria-expanded value as a string. - * @private - */ - isAriaExpanded_: function(index) { - return this.isOpened_(index).toString(); - }, + const list = e.target.parentElement.querySelectorAll('li'); - /** - * @param {!{type: string, code: string, model: !{index: number}}} e - * @private - */ - onErrorItemAction_: function(e) { - if (e.type == 'keydown' && !((e.code == 'Space' || e.code == 'Enter'))) { + for (let i = 0; i < list.length; ++i) { + if (list[i].classList.contains('selected')) { + const polymerEvent = /** @type {!{model: !Object}} */ (e); + const frame = polymerEvent.model.item.stackTrace[i + direction]; + if (frame) { + this.updateSelected_(frame); + list[i + direction].focus(); // Preserve focus. + } return; } + } + }, - // Call preventDefault() to avoid the browser scrolling when the space key - // is pressed. - e.preventDefault(); - this.selectedEntry_ = - this.selectedEntry_ == e.model.index ? -1 : e.model.index; - }, - }); + /** + * Computes the class name for the error item depending on whether its + * the currently selected error. + * @param {number} index + * @return {string} + * @private + */ + computeErrorClass_: function(index) { + return index == this.selectedEntry_ ? 'selected' : ''; + }, + + /** @private */ + iconName_: function(index) { + return index == this.selectedEntry_ ? 'icon-expand-less' : + 'icon-expand-more'; + }, + + /** + * Determine if the iron-collapse should be opened (expanded). + * @param {number} index + * @return {boolean} + * @private + */ + isOpened_: function(index) { + return index == this.selectedEntry_; + }, + + + /** + * @param {number} index + * @return {string} The aria-expanded value as a string. + * @private + */ + isAriaExpanded_: function(index) { + return this.isOpened_(index).toString(); + }, + + /** + * @param {!{type: string, code: string, model: !{index: number}}} e + * @private + */ + onErrorItemAction_: function(e) { + if (e.type == 'keydown' && !((e.code == 'Space' || e.code == 'Enter'))) { + return; + } - return { - ErrorPage: ErrorPage, - ErrorPageDelegate: ErrorPageDelegate, - }; + // Call preventDefault() to avoid the browser scrolling when the space key + // is pressed. + e.preventDefault(); + this.selectedEntry_ = + this.selectedEntry_ == e.model.index ? -1 : e.model.index; + }, }); diff --git a/chromium/chrome/browser/resources/extensions/extensions.html b/chromium/chrome/browser/resources/extensions/extensions.html index 832434023d9..087a92fd887 100644 --- a/chromium/chrome/browser/resources/extensions/extensions.html +++ b/chromium/chrome/browser/resources/extensions/extensions.html @@ -1,12 +1,10 @@ <!doctype html> <html dir="$i18n{textdirection}" lang="$i18n{language}" - class="loading $i18n{loadTimeClasses}"> + class="loading $i18n{loadTimeClasses}" $i18n{a11yenhanced}> <head> <meta charset="utf8"> <title>$i18n{title}</title> -<if expr="not optimize_webui"> <base href="chrome://extensions"> -</if> <link rel="stylesheet" href="chrome://resources/css/md_colors.css"> <style> html { @@ -55,12 +53,10 @@ width: 100%; } </style> + <script type="module" src="extensions.js"></script> </head> <body> - <script src="chrome://resources/polymer/v1_0/html-imports/html-imports.min.js"> - </script> <extensions-manager></extensions-manager> <link rel="stylesheet" href="chrome://resources/css/text_defaults_md.css"> - <link rel="import" href="manager.html"> </body> </html> diff --git a/chromium/chrome/browser/resources/extensions/extensions.js b/chromium/chrome/browser/resources/extensions/extensions.js new file mode 100644 index 00000000000..289f30d6bb9 --- /dev/null +++ b/chromium/chrome/browser/resources/extensions/extensions.js @@ -0,0 +1,17 @@ +// Copyright 2019 The Chromium 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 './manager.js'; + +export {getToastManager} from 'chrome://resources/cr_elements/cr_toast/cr_toast_manager.m.js'; +export {ActivityLogPageState} from './activity_log/activity_log_history.js'; +export {ARG_URL_PLACEHOLDER} from './activity_log/activity_log_stream_item.js'; +// <if expr="chromeos"> +export {KioskBrowserProxyImpl} from './kiosk_browser_proxy.js'; +// </if> +export {Dialog, navigation, NavigationHelper, Page} from './navigation_helper.js'; +export {OptionsDialogMaxHeight, OptionsDialogMinWidth} from './options_dialog.js'; +export {getPatternFromSite} from './runtime_hosts_dialog.js'; +export {Service} from './service.js'; +export {isValidKeyCode, Key, keystrokeToString} from './shortcut_util.js'; diff --git a/chromium/chrome/browser/resources/extensions/extensions_resources.grd b/chromium/chrome/browser/resources/extensions/extensions_resources.grd index 9201c338087..a98ad371e92 100644 --- a/chromium/chrome/browser/resources/extensions/extensions_resources.grd +++ b/chromium/chrome/browser/resources/extensions/extensions_resources.grd @@ -11,238 +11,136 @@ <output filename="extensions_resources.pak" type="data_package" /> </outputs> <release seq="1"> + <includes> + <include name="IDR_EXTENSIONS_CODE_SECTION_JS" + file="${root_gen_dir}/chrome/browser/resources/extensions/code_section.js" + use_base_dir="false" type ="BINDATA" /> + <include name="IDR_EXTENSIONS_ACTIVITY_LOG_ACTIVITY_LOG_JS" + file="${root_gen_dir}/chrome/browser/resources/extensions/activity_log/activity_log.js" + use_base_dir="false" type ="BINDATA" /> + <include name="IDR_EXTENSIONS_ACTIVITY_LOG_ACTIVITY_LOG_HISTORY_JS" + file="${root_gen_dir}/chrome/browser/resources/extensions/activity_log/activity_log_history.js" + use_base_dir="false" type ="BINDATA" /> + <include name="IDR_EXTENSIONS_ACTIVITY_LOG_ACTIVITY_LOG_HISTORY_ITEM_JS" + file="${root_gen_dir}/chrome/browser/resources/extensions/activity_log/activity_log_history_item.js" + use_base_dir="false" type ="BINDATA" /> + <include name="IDR_EXTENSIONS_ACTIVITY_LOG_ACTIVITY_LOG_STREAM_JS" + file="${root_gen_dir}/chrome/browser/resources/extensions/activity_log/activity_log_stream.js" + use_base_dir="false" type ="BINDATA" /> + <include name="IDR_EXTENSIONS_ACTIVITY_LOG_ACTIVITY_LOG_STREAM_ITEM_JS" + file="${root_gen_dir}/chrome/browser/resources/extensions/activity_log/activity_log_stream_item.js" + use_base_dir="false" type ="BINDATA" /> + <include name="IDR_EXTENSIONS_DETAIL_VIEW_JS" + file="${root_gen_dir}/chrome/browser/resources/extensions/detail_view.js" + use_base_dir="false" type ="BINDATA" /> + <include name="IDR_EXTENSIONS_DROP_OVERLAY_JS" + file="${root_gen_dir}/chrome/browser/resources/extensions/drop_overlay.js" + use_base_dir="false" type ="BINDATA" /> + <include name="IDR_EXTENSIONS_ERROR_PAGE_JS" + file="${root_gen_dir}/chrome/browser/resources/extensions/error_page.js" + use_base_dir="false" type ="BINDATA" flattenhtml="true" /> + <include name="IDR_EXTENSIONS_KEYBOARD_SHORTCUTS_JS" + file="${root_gen_dir}/chrome/browser/resources/extensions/keyboard_shortcuts.js" + use_base_dir="false" type ="BINDATA" /> + <if expr="chromeos"> + <include name="IDR_EXTENSIONS_KIOSK_DIALOG_JS" + file="${root_gen_dir}/chrome/browser/resources/extensions/kiosk_dialog.js" + use_base_dir="false" type ="BINDATA" /> + </if> + <include name="IDR_EXTENSIONS_MANAGER_JS" + file="${root_gen_dir}/chrome/browser/resources/extensions/manager.js" + use_base_dir="false" type ="BINDATA" flattenhtml="true" /> + <include name="IDR_EXTENSIONS_ICONS_JS" + file="${root_gen_dir}/chrome/browser/resources/extensions/icons.js" + use_base_dir="false" type ="BINDATA" /> + <include name="IDR_EXTENSIONS_INSTALL_WARNINGS_DIALOG_JS" + file="${root_gen_dir}/chrome/browser/resources/extensions/install_warnings_dialog.js" + use_base_dir="false" type ="BINDATA" /> + <include name="IDR_EXTENSIONS_ITEM_JS" + file="${root_gen_dir}/chrome/browser/resources/extensions/item.js" + use_base_dir="false" type ="BINDATA" /> + <include name="IDR_EXTENSIONS_ITEM_LIST_JS" + file="${root_gen_dir}/chrome/browser/resources/extensions/item_list.js" + use_base_dir="false" type ="BINDATA" /> + <include name="IDR_EXTENSIONS_LOAD_ERROR_JS" + file="${root_gen_dir}/chrome/browser/resources/extensions/load_error.js" + use_base_dir="false" type ="BINDATA" /> + <include name="IDR_EXTENSIONS_HOST_PERMISSIONS_TOGGLE_LIST_JS" + file="${root_gen_dir}/chrome/browser/resources/extensions/host_permissions_toggle_list.js" + use_base_dir="false" type ="BINDATA" /> + <include name="IDR_EXTENSIONS_OPTIONS_DIALOG_JS" + file="${root_gen_dir}/chrome/browser/resources/extensions/options_dialog.js" + use_base_dir="false" type ="BINDATA" /> + <include name="IDR_EXTENSIONS_PACK_DIALOG_JS" + file="${root_gen_dir}/chrome/browser/resources/extensions/pack_dialog.js" + use_base_dir="false" type ="BINDATA" /> + <include name="IDR_EXTENSIONS_PACK_DIALOG_ALERT_JS" + file="${root_gen_dir}/chrome/browser/resources/extensions/pack_dialog_alert.js" + use_base_dir="false" type ="BINDATA" /> + <include name="IDR_EXTENSIONS_RUNTIME_HOST_PERMISSIONS_JS" + file="${root_gen_dir}/chrome/browser/resources/extensions/runtime_host_permissions.js" + use_base_dir="false" type ="BINDATA" /> + <include name="IDR_EXTENSIONS_RUNTIME_HOSTS_DIALOG_JS" + file="${root_gen_dir}/chrome/browser/resources/extensions/runtime_hosts_dialog.js" + use_base_dir="false" type ="BINDATA" /> + <include name="IDR_EXTENSIONS_SHARED_STYLE_JS" + file="${root_gen_dir}/chrome/browser/resources/extensions/shared_style.js" + use_base_dir="false" type ="BINDATA" /> + <include name="IDR_EXTENSIONS_SHARED_VARS_JS" + file="${root_gen_dir}/chrome/browser/resources/extensions/shared_vars.js" + use_base_dir="false" type ="BINDATA" /> + <include name="IDR_EXTENSIONS_SHORTCUT_INPUT_JS" + file="${root_gen_dir}/chrome/browser/resources/extensions/shortcut_input.js" + use_base_dir="false" type ="BINDATA" /> + <include name="IDR_EXTENSIONS_SIDEBAR_JS" + file="${root_gen_dir}/chrome/browser/resources/extensions/sidebar.js" + use_base_dir="false" type ="BINDATA" /> + <include name="IDR_EXTENSIONS_TOGGLE_ROW_JS" + file="${root_gen_dir}/chrome/browser/resources/extensions/toggle_row.js" + use_base_dir="false" type ="BINDATA" /> + <include name="IDR_EXTENSIONS_TOOLBAR_JS" + file="${root_gen_dir}/chrome/browser/resources/extensions/toolbar.js" + use_base_dir="false" type ="BINDATA" flattenhtml="true" /> + <include name="IDR_WEBUI_IMAGES_CHECKUP_IMAGE" + file="checkup_image.svg" type="BINDATA" compress="gzip" /> + <include name="IDR_WEBUI_IMAGES_CHECKUP_IMAGE_DARKMODE" + file="checkup_image_dark.svg" type="BINDATA" + compress="gzip" /> + </includes> <structures> - <structure name="IDR_EXTENSIONS_ITEM_BEHAVIOR_HTML" - file="item_behavior.html" - type="chrome_html" /> <structure name="IDR_EXTENSIONS_ITEM_BEHAVIOR_JS" file="item_behavior.js" type="chrome_html" /> <structure name="IDR_EXTENSIONS_EXTENSIONS_HTML" file="extensions.html" - flattenhtml="true" - allowexternalscript="true" - type="chrome_html" /> - <structure name="IDR_EXTENSIONS_CODE_SECTION_HTML" - file="code_section.html" - type="chrome_html" /> - <structure name="IDR_EXTENSIONS_CODE_SECTION_JS" - file="code_section.js" - type="chrome_html" /> - <structure name="IDR_EXTENSIONS_ACTIVITY_LOG_ACTIVITY_LOG_HTML" - file="activity_log/activity_log.html" - type="chrome_html" /> - <structure name="IDR_EXTENSIONS_ACTIVITY_LOG_ACTIVITY_LOG_JS" - file="activity_log/activity_log.js" - type="chrome_html" /> - <structure name="IDR_EXTENSIONS_ACTIVITY_LOG_ACTIVITY_LOG_HISTORY_HTML" - file="activity_log/activity_log_history.html" - type="chrome_html" /> - <structure name="IDR_EXTENSIONS_ACTIVITY_LOG_ACTIVITY_LOG_HISTORY_JS" - file="activity_log/activity_log_history.js" - type="chrome_html" /> - <structure name="IDR_EXTENSIONS_ACTIVITY_LOG_ACTIVITY_LOG_HISTORY_ITEM_HTML" - file="activity_log/activity_log_history_item.html" - type="chrome_html" /> - <structure name="IDR_EXTENSIONS_ACTIVITY_LOG_ACTIVITY_LOG_HISTORY_ITEM_JS" - file="activity_log/activity_log_history_item.js" - type="chrome_html" /> - <structure name="IDR_EXTENSIONS_ACTIVITY_LOG_ACTIVITY_LOG_STREAM_HTML" - file="activity_log/activity_log_stream.html" - type="chrome_html" /> - <structure name="IDR_EXTENSIONS_ACTIVITY_LOG_ACTIVITY_LOG_STREAM_JS" - file="activity_log/activity_log_stream.js" - type="chrome_html" /> - <structure name="IDR_EXTENSIONS_ACTIVITY_LOG_ACTIVITY_LOG_STREAM_ITEM_HTML" - file="activity_log/activity_log_stream_item.html" - type="chrome_html" /> - <structure name="IDR_EXTENSIONS_ACTIVITY_LOG_ACTIVITY_LOG_STREAM_ITEM_JS" - file="activity_log/activity_log_stream_item.js" - type="chrome_html" /> - <structure name="IDR_EXTENSIONS_DETAIL_VIEW_HTML" - file="detail_view.html" - type="chrome_html" /> - <structure name="IDR_EXTENSIONS_DETAIL_VIEW_JS" - file="detail_view.js" - type="chrome_html" /> - <structure name="IDR_EXTENSIONS_DRAG_AND_DROP_HANDLER_HTML" - file="drag_and_drop_handler.html" type="chrome_html" /> <structure name="IDR_EXTENSIONS_DRAG_AND_DROP_HANDLER_JS" file="drag_and_drop_handler.js" type="chrome_html" /> - <structure name="IDR_EXTENSIONS_DROP_OVERLAY_HTML" - file="drop_overlay.html" - type="chrome_html" /> - <structure name="IDR_EXTENSIONS_DROP_OVERLAY_JS" - file="drop_overlay.js" - type="chrome_html" /> - <structure name="IDR_EXTENSIONS_ERROR_PAGE_HTML" - file="error_page.html" - flattenhtml="true" - allowexternalscript="true" - type="chrome_html" /> - <structure name="IDR_EXTENSIONS_ERROR_PAGE_JS" - file="error_page.js" - type="chrome_html" /> - <structure name="IDR_EXTENSIONS_KEYBOARD_SHORTCUT_DELEGATE_HTML" - file="keyboard_shortcut_delegate.html" + <structure name="IDR_EXTENSIONS_EXTENSIONS_JS" + file="extensions.js" + preprocess="true" type="chrome_html" /> <structure name="IDR_EXTENSIONS_KEYBOARD_SHORTCUT_DELEGATE_JS" file="keyboard_shortcut_delegate.js" type="chrome_html" /> - <structure name="IDR_EXTENSIONS_KEYBOARD_SHORTCUTS_HTML" - file="keyboard_shortcuts.html" - type="chrome_html" /> - <structure name="IDR_EXTENSIONS_KEYBOARD_SHORTCUTS_JS" - file="keyboard_shortcuts.js" - type="chrome_html" /> <if expr="chromeos"> - <structure name="IDR_EXTENSIONS_KIOSK_BROWSER_PROXY_HTML" - file="kiosk_browser_proxy.html" - type="chrome_html" /> <structure name="IDR_EXTENSIONS_KIOSK_BROWSER_PROXY_JS" file="kiosk_browser_proxy.js" type="chrome_html" /> - <structure name="IDR_EXTENSIONS_KIOSK_DIALOG_HTML" - file="kiosk_dialog.html" - type="chrome_html" /> - <structure name="IDR_EXTENSIONS_KIOSK_DIALOG_JS" - file="kiosk_dialog.js" - type="chrome_html" /> </if> - <structure name="IDR_EXTENSIONS_MANAGER_HTML" - file="manager.html" - type="chrome_html" - flattenhtml="true" - allowexternalscript="true" /> - <structure name="IDR_EXTENSIONS_MANAGER_JS" - file="manager.js" - type="chrome_html" - flattenhtml="true" /> - <structure name="IDR_EXTENSIONS_ICONS_HTML" - file="icons.html" - type="chrome_html" /> - <structure name="IDR_EXTENSIONS_INSTALL_WARNINGS_DIALOG_HTML" - file="install_warnings_dialog.html" - type="chrome_html" /> - <structure name="IDR_EXTENSIONS_INSTALL_WARNINGS_DIALOG_JS" - file="install_warnings_dialog.js" - type="chrome_html" /> - <structure name="IDR_EXTENSIONS_ITEM_HTML" - file="item.html" - type="chrome_html" /> - <structure name="IDR_EXTENSIONS_ITEM_JS" - file="item.js" - type="chrome_html" /> - <structure name="IDR_EXTENSIONS_ITEM_LIST_HTML" - file="item_list.html" - type="chrome_html" /> - <structure name="IDR_EXTENSIONS_ITEM_LIST_JS" - file="item_list.js" - type="chrome_html" /> - <structure name="IDR_EXTENSIONS_ITEM_UTIL_HTML" - file="item_util.html" - type="chrome_html" /> <structure name="IDR_EXTENSIONS_ITEM_UTIL_JS" file="item_util.js" type="chrome_html" /> - <structure name="IDR_EXTENSIONS_LOAD_ERROR_HTML" - file="load_error.html" - type="chrome_html" /> - <structure name="IDR_EXTENSIONS_LOAD_ERROR_JS" - file="load_error.js" - type="chrome_html" /> - <structure name="IDR_EXTENSIONS_HOST_PERMISSIONS_TOGGLE_LIST_HMTL" - file="host_permissions_toggle_list.html" - type="chrome_html" /> - <structure name="IDR_EXTENSIONS_HOST_PERMISSIONS_TOGGLE_LIST_JS" - file="host_permissions_toggle_list.js" - type="chrome_html" /> - <structure name="IDR_EXTENSIONS_NAVIGATION_HELPER_HTML" - file="navigation_helper.html" - type="chrome_html" /> <structure name="IDR_EXTENSIONS_NAVIGATION_HELPER_JS" file="navigation_helper.js" type="chrome_html" /> - <structure name="IDR_EXTENSIONS_OPTIONS_DIALOG_HTML" - file="options_dialog.html" - type="chrome_html" /> - <structure name="IDR_EXTENSIONS_OPTIONS_DIALOG_JS" - file="options_dialog.js" - type="chrome_html" /> - <structure name="IDR_EXTENSIONS_PACK_DIALOG_HTML" - file="pack_dialog.html" - type="chrome_html" /> - <structure name="IDR_EXTENSIONS_PACK_DIALOG_JS" - file="pack_dialog.js" - type="chrome_html" /> - <structure name="IDR_EXTENSIONS_PACK_DIALOG_ALERT_HTML" - file="pack_dialog_alert.html" - type="chrome_html" /> - <structure name="IDR_EXTENSIONS_PACK_DIALOG_ALERT_JS" - file="pack_dialog_alert.js" - type="chrome_html" /> - <structure name="IDR_EXTENSIONS_RUNTIME_HOST_PERMISSIONS_HMTL" - file="runtime_host_permissions.html" - type="chrome_html" /> - <structure name="IDR_EXTENSIONS_RUNTIME_HOST_PERMISSIONS_JS" - file="runtime_host_permissions.js" - type="chrome_html" /> - <structure name="IDR_EXTENSIONS_RUNTIME_HOSTS_DIALOG_HTML" - file="runtime_hosts_dialog.html" - type="chrome_html" /> - <structure name="IDR_EXTENSIONS_RUNTIME_HOSTS_DIALOG_JS" - file="runtime_hosts_dialog.js" - type="chrome_html" /> - <structure name="IDR_EXTENSIONS_SERVICE_HTML" - file="service.html" - type="chrome_html" /> <structure name="IDR_EXTENSIONS_SERVICE_JS" file="service.js" type="chrome_html" /> - <structure name="IDR_EXTENSIONS_SHARED_STYLE_HTML" - file="shared_style.html" - type="chrome_html" /> - <structure name="IDR_EXTENSIONS_SHARED_VARS_HTML" - file="shared_vars.html" - type="chrome_html" /> - <structure name="IDR_EXTENSIONS_SHORTCUT_INPUT_HTML" - file="shortcut_input.html" - type="chrome_html" /> - <structure name="IDR_EXTENSIONS_SHORTCUT_INPUT_JS" - file="shortcut_input.js" - type="chrome_html" /> - <structure name="IDR_EXTENSIONS_SHORTCUT_UTIL_HTML" - file="shortcut_util.html" - type="chrome_html" /> <structure name="IDR_EXTENSIONS_SHORTCUT_UTIL_JS" file="shortcut_util.js" type="chrome_html" /> - <structure name="IDR_EXTENSIONS_SIDEBAR_HTML" - file="sidebar.html" - type="chrome_html" /> - <structure name="IDR_EXTENSIONS_SIDEBAR_JS" - file="sidebar.js" - type="chrome_html" /> - <structure name="IDR_EXTENSIONS_TOGGLE_ROW_HTML" - file="toggle_row.html" - type="chrome_html" /> - <structure name="IDR_EXTENSIONS_TOGGLE_ROW_JS" - file="toggle_row.js" - type="chrome_html" /> - <structure name="IDR_EXTENSIONS_TOOLBAR_HTML" - file="toolbar.html" - type="chrome_html" - flattenhtml="true" - allowexternalscript="true" /> - <structure name="IDR_EXTENSIONS_TOOLBAR_JS" - file="toolbar.js" - type="chrome_html" - flattenhtml="true" /> - <structure name="IDR_EXTENSIONS_STRINGS_HTML" - file="strings.html" - type="chrome_html" /> </structures> </release> </grit> diff --git a/chromium/chrome/browser/resources/extensions/extensions_resources_vulcanized.grd b/chromium/chrome/browser/resources/extensions/extensions_resources_vulcanized.grd index 2a3c0c6c403..d8aec253a44 100644 --- a/chromium/chrome/browser/resources/extensions/extensions_resources_vulcanized.grd +++ b/chromium/chrome/browser/resources/extensions/extensions_resources_vulcanized.grd @@ -12,17 +12,14 @@ </outputs> <release seq="1"> <includes> - <include name="IDR_EXTENSIONS_VULCANIZED_HTML" - file="${root_gen_dir}\chrome\browser\resources\extensions\vulcanized.html" - use_base_dir="false" - flattenhtml="true" - allowexternalscript="true" - type="BINDATA" + <include name="IDR_EXTENSIONS_EXTENSIONS_HTML" + file="extensions.html" + type="chrome_html" compress="gzip" /> - <include name="IDR_EXTENSIONS_CRISPER_JS" - file="${root_gen_dir}\chrome\browser\resources\extensions\crisper.js" + <include name="IDR_EXTENSIONS_EXTENSIONS_ROLLUP_JS" + file="${root_gen_dir}\chrome\browser\resources\extensions\extensions.rollup.js" use_base_dir="false" - flattenhtml="true" + preprocess="true" type="BINDATA" compress="gzip" /> </includes> diff --git a/chromium/chrome/browser/resources/extensions/host_permissions_toggle_list.html b/chromium/chrome/browser/resources/extensions/host_permissions_toggle_list.html index 4094ed0a0cc..385b9297aad 100644 --- a/chromium/chrome/browser/resources/extensions/host_permissions_toggle_list.html +++ b/chromium/chrome/browser/resources/extensions/host_permissions_toggle_list.html @@ -1,67 +1,51 @@ -<link rel="import" href="chrome://resources/html/polymer.html"> +<style include="cr-shared-style shared-style"> + iron-icon { + --iron-icon-height: var(--cr-icon-size); + --iron-icon-width: var(--cr-icon-size); + } -<link rel="import" href="chrome://resources/cr_elements/shared_style_css.html"> -<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html"> -<link rel="import" href="chrome://resources/cr_elements/icons.html"> -<link rel="import" href="chrome://resources/html/cr.html"> -<link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html"> -<link rel="import" href="toggle_row.html"> -<link rel="import" href="shared_style.html"> -<link rel="import" href="strings.html"> + #section-heading { + align-items: center; + color: var(--cr-primary-text-color); + display: flex; + justify-content: space-between; + margin-top: 12px; + } -<dom-module id="extensions-host-permissions-toggle-list"> - <template> - <style include="cr-shared-style shared-style"> - iron-icon { - --iron-icon-height: var(--cr-icon-size); - --iron-icon-width: var(--cr-icon-size); - } + .toggle-section { + display: flex; + flex-direction: column; + justify-content: center; + min-height: var(--cr-section-min-height); + } - #section-heading { - align-items: center; - color: var(--cr-primary-text-color); - display: flex; - justify-content: space-between; - margin-top: 12px; - } - - .toggle-section { - display: flex; - flex-direction: column; - justify-content: center; - min-height: var(--cr-section-min-height); - } - - .site-toggle { - border-top: var(--cr-separator-line); - margin-inline-start: var(--cr-section-indent-width); - } - </style> - <div id="section-heading"> - <span>$i18n{hostPermissionsDescription}</span> - <a id="link-icon-button" aria-label="$i18n{learnMore}" - href="$i18n{hostPermissionsLearnMoreLink}" target="_blank"> - <iron-icon icon="cr:help-outline"></iron-icon> - </a> - </div> - <div class="toggle-section"> - <extensions-toggle-row checked="[[allowedOnAllHosts_(permissions.*)]]" - id="allHostsToggle" - on-change="onAllHostsToggleChanged_"> - <span>$i18n{itemAllowOnFollowingSites}</span> - </extensions-toggle-row> - </div> - <template is="dom-repeat" items="[[getSortedHosts_(permissions.*)]]"> - <div class="toggle-section site-toggle"> - <extensions-toggle-row checked="[[item.granted]]" - class="host-toggle no-end-padding" - disabled="[[allowedOnAllHosts_(permissions.*)]]" - host="[[item.host]]" - on-change="onHostAccessChanged_"> - <span>[[item.host]]</span> - </extensions-toggle-row> - </div> - </template> - </template> - <script src="host_permissions_toggle_list.js"></script> -</dom-module> + .site-toggle { + border-top: var(--cr-separator-line); + margin-inline-start: var(--cr-section-indent-width); + } +</style> +<div id="section-heading"> + <span>$i18n{hostPermissionsDescription}</span> + <a id="link-icon-button" aria-label="$i18n{learnMore}" + href="$i18n{hostPermissionsLearnMoreLink}" target="_blank"> + <iron-icon icon="cr:help-outline"></iron-icon> + </a> +</div> +<div class="toggle-section"> + <extensions-toggle-row checked="[[allowedOnAllHosts_(permissions.*)]]" + id="allHostsToggle" + on-change="onAllHostsToggleChanged_"> + <span>$i18n{itemAllowOnFollowingSites}</span> + </extensions-toggle-row> +</div> +<template is="dom-repeat" items="[[getSortedHosts_(permissions.*)]]"> + <div class="toggle-section site-toggle"> + <extensions-toggle-row checked="[[item.granted]]" + class="host-toggle no-end-padding" + disabled="[[allowedOnAllHosts_(permissions.*)]]" + host="[[item.host]]" + on-change="onHostAccessChanged_"> + <span>[[item.host]]</span> + </extensions-toggle-row> + </div> +</template> diff --git a/chromium/chrome/browser/resources/extensions/host_permissions_toggle_list.js b/chromium/chrome/browser/resources/extensions/host_permissions_toggle_list.js index 6ca93aeab16..b8a3e492161 100644 --- a/chromium/chrome/browser/resources/extensions/host_permissions_toggle_list.js +++ b/chromium/chrome/browser/resources/extensions/host_permissions_toggle_list.js @@ -2,82 +2,90 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -cr.define('extensions', function() { - 'use strict'; +import 'chrome://resources/cr_elements/shared_style_css.m.js'; +import 'chrome://resources/cr_elements/shared_vars_css.m.js'; +import 'chrome://resources/cr_elements/icons.m.js'; +import 'chrome://resources/polymer/v3_0/iron-icon/iron-icon.js'; +import './toggle_row.js'; +import './shared_style.js'; +import './strings.m.js'; - const HostPermissionsToggleList = Polymer({ - is: 'extensions-host-permissions-toggle-list', +import {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; - properties: { - /** - * The underlying permissions data. - * @type {chrome.developerPrivate.RuntimeHostPermissions} - */ - permissions: Object, +import {ItemDelegate} from './item.js'; - /** @private */ - itemId: String, +Polymer({ + is: 'extensions-host-permissions-toggle-list', - /** @type {!extensions.ItemDelegate} */ - delegate: Object, - }, + _template: html`{__html_template__}`, + properties: { /** - * @return {boolean} Whether the item is allowed to execute on all of its - * requested sites. - * @private + * The underlying permissions data. + * @type {chrome.developerPrivate.RuntimeHostPermissions} */ - allowedOnAllHosts_: function() { - return this.permissions.hostAccess == - chrome.developerPrivate.HostAccess.ON_ALL_SITES; - }, - - /** - * Returns a lexicographically-sorted list of the hosts associated with this - * item. - * @return {!Array<!chrome.developerPrivate.SiteControl>} - * @private - */ - getSortedHosts_: function() { - return this.permissions.hosts.sort((a, b) => { - if (a.host < b.host) { - return -1; - } - if (a.host > b.host) { - return 1; - } - return 0; - }); - }, + permissions: Object, /** @private */ - onAllHostsToggleChanged_: function() { - // TODO(devlin): In the case of going from all sites to specific sites, - // we'll withhold all sites (i.e., all specific site toggles will move to - // unchecked, and the user can check them individually). This is slightly - // different than the sync page, where disabling the "sync everything" - // switch leaves everything synced, and user can uncheck them - // individually. It could be nice to align on behavior, but probably not - // super high priority. - this.delegate.setItemHostAccess( - this.itemId, - this.$.allHostsToggle.checked ? - chrome.developerPrivate.HostAccess.ON_ALL_SITES : - chrome.developerPrivate.HostAccess.ON_SPECIFIC_SITES); - }, + itemId: String, - /** @private */ - onHostAccessChanged_: function(e) { - const host = e.target.host; - const checked = e.target.checked; + /** @type {!ItemDelegate} */ + delegate: Object, + }, - if (checked) { - this.delegate.addRuntimeHostPermission(this.itemId, host); - } else { - this.delegate.removeRuntimeHostPermission(this.itemId, host); + /** + * @return {boolean} Whether the item is allowed to execute on all of its + * requested sites. + * @private + */ + allowedOnAllHosts_: function() { + return this.permissions.hostAccess == + chrome.developerPrivate.HostAccess.ON_ALL_SITES; + }, + + /** + * Returns a lexicographically-sorted list of the hosts associated with this + * item. + * @return {!Array<!chrome.developerPrivate.SiteControl>} + * @private + */ + getSortedHosts_: function() { + return this.permissions.hosts.sort((a, b) => { + if (a.host < b.host) { + return -1; + } + if (a.host > b.host) { + return 1; } - }, - }); + return 0; + }); + }, + + /** @private */ + onAllHostsToggleChanged_: function() { + // TODO(devlin): In the case of going from all sites to specific sites, + // we'll withhold all sites (i.e., all specific site toggles will move to + // unchecked, and the user can check them individually). This is slightly + // different than the sync page, where disabling the "sync everything" + // switch leaves everything synced, and user can uncheck them + // individually. It could be nice to align on behavior, but probably not + // super high priority. + this.delegate.setItemHostAccess( + this.itemId, + this.$.allHostsToggle.checked ? + chrome.developerPrivate.HostAccess.ON_ALL_SITES : + chrome.developerPrivate.HostAccess.ON_SPECIFIC_SITES); + }, + + /** @private */ + onHostAccessChanged_: function(e) { + const host = e.target.host; + const checked = e.target.checked; - return {HostPermissionsToggleList: HostPermissionsToggleList}; + if (checked) { + this.delegate.addRuntimeHostPermission(this.itemId, host); + } else { + this.delegate.removeRuntimeHostPermission(this.itemId, host); + } + }, }); diff --git a/chromium/chrome/browser/resources/extensions/icons.html b/chromium/chrome/browser/resources/extensions/icons.html index 765f9bf400a..925e4108ec3 100644 --- a/chromium/chrome/browser/resources/extensions/icons.html +++ b/chromium/chrome/browser/resources/extensions/icons.html @@ -1,6 +1,3 @@ -<link rel="import" href="chrome://resources/html/polymer.html"> -<link rel="import" href="chrome://resources/polymer/v1_0/iron-iconset-svg/iron-iconset-svg.html"> - <iron-iconset-svg name="extensions-icons" size="24"> <svg> <defs> diff --git a/chromium/chrome/browser/resources/extensions/icons.js b/chromium/chrome/browser/resources/extensions/icons.js new file mode 100644 index 00000000000..531c5e73112 --- /dev/null +++ b/chromium/chrome/browser/resources/extensions/icons.js @@ -0,0 +1,9 @@ +// Copyright 2019 The Chromium 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 'chrome://resources/polymer/v3_0/iron-iconset-svg/iron-iconset-svg.js'; +import {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; + +const template = html`{__html_template__}`; +document.head.appendChild(template.content); diff --git a/chromium/chrome/browser/resources/extensions/install_warnings_dialog.html b/chromium/chrome/browser/resources/extensions/install_warnings_dialog.html index 760a8a40ae6..abd531cd53d 100644 --- a/chromium/chrome/browser/resources/extensions/install_warnings_dialog.html +++ b/chromium/chrome/browser/resources/extensions/install_warnings_dialog.html @@ -1,47 +1,32 @@ -<link rel="import" href="chrome://resources/html/polymer.html"> +<style include="cr-shared-style"> + div[slot='body'] ul { + background-color: var(--paper-red-50); + margin: 0; + padding-bottom: 10px; + padding-inline-end: 10px; + padding-top: 10px; + } -<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html"> -<link rel="import" href="chrome://resources/cr_elements/cr_dialog/cr_dialog.html"> -<link rel="import" href="chrome://resources/cr_elements/shared_style_css.html"> -<link rel="import" href="chrome://resources/html/assert.html"> -<link rel="import" href="chrome://resources/html/cr.html"> -<link rel="import" href="chrome://resources/polymer/v1_0/paper-styles/color.html"> -<link rel="import" href="code_section.html"> - -<dom-module id="extensions-install-warnings-dialog"> - <template> - <style include="cr-shared-style"> - div[slot='body'] ul { - background-color: var(--paper-red-50); - margin: 0; - padding-bottom: 10px; - padding-inline-end: 10px; - padding-top: 10px; - } - - @media (prefers-color-scheme: dark) { - div[slot='body'] ul { - /* TODO(dbeam): merge with --cr-input-background-color? */ - background-color: rgba(0, 0, 0, .3); - color: var(--error-color); - } - } - </style> - <cr-dialog id="dialog" close-text="$i18n{close}"> - <div slot="title">$i18n{installWarnings}</div> - <div slot="body"> - <ul> - <template is="dom-repeat" items="[[installWarnings]]"> - <li>[[item]]</li> - </template> - </ul> - </div> - <div slot="button-container"> - <cr-button class="action-button" on-click="onOkTap_"> - $i18n{ok} - </cr-button> - </div> - </cr-dialog> - </template> - <script src="install_warnings_dialog.js"></script> -</dom-module> + @media (prefers-color-scheme: dark) { + div[slot='body'] ul { + /* TODO(dbeam): merge with --cr-input-background-color? */ + background-color: rgba(0, 0, 0, .3); + color: var(--error-color); + } + } +</style> +<cr-dialog id="dialog" close-text="$i18n{close}"> + <div slot="title">$i18n{installWarnings}</div> + <div slot="body"> + <ul> + <template is="dom-repeat" items="[[installWarnings]]"> + <li>[[item]]</li> + </template> + </ul> + </div> + <div slot="button-container"> + <cr-button class="action-button" on-click="onOkTap_"> + $i18n{ok} + </cr-button> + </div> +</cr-dialog> diff --git a/chromium/chrome/browser/resources/extensions/install_warnings_dialog.js b/chromium/chrome/browser/resources/extensions/install_warnings_dialog.js index 5417428c937..b333dca3db5 100644 --- a/chromium/chrome/browser/resources/extensions/install_warnings_dialog.js +++ b/chromium/chrome/browser/resources/extensions/install_warnings_dialog.js @@ -2,27 +2,31 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -cr.define('extensions', function() { - 'use strict'; +import 'chrome://resources/cr_elements/cr_button/cr_button.m.js'; +import 'chrome://resources/cr_elements/cr_dialog/cr_dialog.m.js'; +import 'chrome://resources/cr_elements/shared_style_css.m.js'; +import 'chrome://resources/polymer/v3_0/paper-styles/color.js'; +import './code_section.js'; - const InstallWarningsDialog = Polymer({ - is: 'extensions-install-warnings-dialog', +import {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; - properties: { - /** @type {!Array<string>} */ - installWarnings: Array, - }, +Polymer({ + is: 'extensions-install-warnings-dialog', - /** @override */ - attached: function() { - this.$.dialog.showModal(); - }, + _template: html`{__html_template__}`, - /** @private */ - onOkTap_: function() { - this.$.dialog.close(); - }, - }); + properties: { + /** @type {!Array<string>} */ + installWarnings: Array, + }, - return {InstallWarningsDialog: InstallWarningsDialog}; + /** @override */ + attached: function() { + this.$.dialog.showModal(); + }, + + /** @private */ + onOkTap_: function() { + this.$.dialog.close(); + }, }); diff --git a/chromium/chrome/browser/resources/extensions/item.html b/chromium/chrome/browser/resources/extensions/item.html index a69c6b4c656..b70ec925b38 100644 --- a/chromium/chrome/browser/resources/extensions/item.html +++ b/chromium/chrome/browser/resources/extensions/item.html @@ -1,356 +1,325 @@ -<link rel="import" href="chrome://resources/html/polymer.html"> +<style include="iron-flex cr-hidden-style cr-icons action-link + shared-style"> + .bounded-text, + .multiline-clippable-text, + .clippable-flex-text { + /** Ensure that the text does not overflow its container. */ + overflow: hidden; + text-overflow: ellipsis; + } -<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html"> -<link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html"> -<link rel="import" href="chrome://resources/cr_elements/cr_icons_css.html"> -<link rel="import" href="chrome://resources/cr_elements/cr_toast/cr_toast_manager.html"> -<link rel="import" href="chrome://resources/cr_elements/cr_toggle/cr_toggle.html"> -<link rel="import" href="chrome://resources/cr_elements/hidden_style_css.html"> -<link rel="import" href="chrome://resources/cr_elements/icons.html"> -<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html"> -<link rel="import" href="chrome://resources/html/action_link.html"> -<link rel="import" href="chrome://resources/cr_elements/action_link_css.html"> -<link rel="import" href="chrome://resources/html/assert.html"> -<link rel="import" href="chrome://resources/html/cr.html"> -<link rel="import" href="chrome://resources/html/i18n_behavior.html"> -<link rel="import" href="icons.html"> -<link rel="import" href="item_behavior.html"> -<link rel="import" href="item_util.html"> -<link rel="import" href="shared_style.html"> -<link rel="import" href="shared_vars.html"> -<link rel="import" href="strings.html"> -<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/iron-flex-layout-classes.html"> -<link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html"> -<link rel="import" href="chrome://resources/polymer/v1_0/paper-tooltip/paper-tooltip.html"> -<link rel="import" href="navigation_helper.html"> + .bounded-text, + .clippable-flex-text { + white-space: nowrap; + } -<dom-module id="extensions-item"> - <template> - <style include="iron-flex cr-hidden-style cr-icons action-link - shared-style"> - .bounded-text, - .multiline-clippable-text, - .clippable-flex-text { - /** Ensure that the text does not overflow its container. */ - overflow: hidden; - text-overflow: ellipsis; - } + .clippable-flex-text { + /** + * These labels can be arbitrarily long. We want to ensure that these + * shrink, rather than the neighboring content. + */ + flex-shrink: 1; + } - .bounded-text, - .clippable-flex-text { - white-space: nowrap; - } + #icon-wrapper { + align-self: flex-start; + display: flex; + padding: 6px; + position: relative; + } - .clippable-flex-text { - /** - * These labels can be arbitrarily long. We want to ensure that these - * shrink, rather than the neighboring content. - */ - flex-shrink: 1; - } + #icon { + height: 36px; + width: 36px; + } - #icon-wrapper { - align-self: flex-start; - display: flex; - padding: 6px; - position: relative; - } + #card { + @apply --cr-card-elevation; + background-color: var(--cr-card-background-color); + border-radius: var(--cr-card-border-radius); + display: flex; + flex-direction: column; + height: 160px; + /* Duration matches --drawer-transition from toolbar.html. */ + transition: height 300ms cubic-bezier(.25, .1, .25, 1); + } - #icon { - height: 36px; - width: 36px; - } + #card.dev-mode { + height: 208px; + } - #card { - @apply --cr-card-elevation; - background-color: var(--cr-card-background-color); - border-radius: var(--cr-card-border-radius); - display: flex; - flex-direction: column; - height: 160px; - /* Duration matches --drawer-transition from toolbar.html. */ - transition: height 300ms cubic-bezier(.25, .1, .25, 1); - } + #main { + display: flex; + flex: 1; + min-height: 0; + padding: 16px 20px; + } - #card.dev-mode { - height: 208px; - } + #content { + display: flex; + flex: 1; + flex-direction: column; + margin-inline-start: 24px; + overflow: hidden; + } - #main { - display: flex; - flex: 1; - min-height: 0; - padding: 16px 20px; - } + #name-and-version { + color: var(--cr-primary-text-color); + margin-bottom: 4px; + } - #content { - display: flex; - flex: 1; - flex-direction: column; - margin-inline-start: 24px; - overflow: hidden; - } + #name { + margin-inline-end: 8px; + } - #name-and-version { - color: var(--cr-primary-text-color); - margin-bottom: 4px; - } + #description { + flex: 1; + } - #name { - margin-inline-end: 8px; - } + #warnings { + color: var(--error-color); + flex: 1; + margin-bottom: 8px; + } - #description { - flex: 1; - } + #error-icon { + --iron-icon-fill-color: var(--error-color); + height: 18px; + margin-inline-end: 4px; + width: 18px; + } - #warnings { - color: var(--error-color); - flex: 1; - margin-bottom: 8px; - } + #description, + #version, + #extension-id, + #inspect-views, + #button-strip { + @apply --cr-secondary-text; + } - #error-icon { - --iron-icon-fill-color: var(--error-color); - height: 18px; - margin-inline-end: 4px; - width: 18px; - } + #extension-id { + flex-shrink: 0; + } - #description, - #version, - #extension-id, - #inspect-views, - #button-strip { - @apply --cr-secondary-text; - } + #inspect-views { + display: flex; + white-space: nowrap; + } - #extension-id { - flex-shrink: 0; - } + #inspect-views > span { + margin-inline-end: 4px; + } - #inspect-views { - display: flex; - white-space: nowrap; - } + #button-strip { + box-sizing: border-box; + flex-shrink: 0; + height: var(--cr-section-min-height); + padding-bottom: 8px; + padding-inline-end: 20px; + padding-top: 8px; + } - #inspect-views > span { - margin-inline-end: 4px; - } + #button-strip cr-button { + margin-inline-start: 8px; + } - #button-strip { - box-sizing: border-box; - flex-shrink: 0; - height: var(--cr-section-min-height); - padding-bottom: 8px; - padding-inline-end: 20px; - padding-top: 8px; - } + #source-indicator { + margin-inline-start: 24px; + margin-top: 24px; + position: absolute; + } - #button-strip cr-button { - margin-inline-start: 8px; - } + .source-icon-wrapper { + align-items: center; + background: rgb(241, 89, 43); /* Same in light & dark modes. */ + border-radius: 50%; /* 50% border radius == a circle */ + box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.22), + 0 2px 2px 0 rgba(0, 0, 0, 0.12); + display: flex; + height: 22px; + justify-content: center; + width: 22px; + } - #source-indicator { - margin-inline-start: 24px; - margin-top: 24px; - position: absolute; - } + #source-indicator iron-icon { + color: white; + height: 16px; + width: 16px; + } - .source-icon-wrapper { - align-items: center; - background: rgb(241, 89, 43); /* Same in light & dark modes. */ - border-radius: 50%; /* 50% border radius == a circle */ - box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.22), - 0 2px 2px 0 rgba(0, 0, 0, 0.12); - display: flex; - height: 22px; - justify-content: center; - width: 22px; - } + paper-tooltip { + --paper-tooltip: { + @apply --cr-tooltip; + min-width: 0; + }; + } - #source-indicator iron-icon { - color: white; - height: 16px; - width: 16px; - } + #errors-button { + color: var(--error-color); + } - paper-tooltip { - --paper-tooltip: { - @apply --cr-tooltip; - min-width: 0; - }; - } + #dev-reload-button { + margin-inline-end: 12px; + } - #errors-button { - color: var(--error-color); - } + #blacklisted-warning:empty { + display: none; + } - #dev-reload-button { - margin-inline-end: 12px; - } - - #blacklisted-warning:empty { - display: none; - } - - #a11yAssociation { - height: 0; - overflow: hidden; - } - </style> - <!-- Invisible instead of hidden because VoiceOver refuses to read text of - element that's hidden when referenced by an aria label. Unfortunately, - this text can be found by Ctrl + F because it isn't hidden. --> - <div id="a11yAssociation" aria-hidden="true"> - [[a11yAssociation_(data.name)]] - </div> - <div id="card" class$="[[computeClasses_(data.state, inDevMode)]]"> - <div id="main"> - <div id="icon-wrapper"> - <img id="icon" src="[[data.iconUrl]]" + #a11yAssociation { + height: 0; + overflow: hidden; + } +</style> +<!-- Invisible instead of hidden because VoiceOver refuses to read text of + element that's hidden when referenced by an aria label. Unfortunately, + this text can be found by Ctrl + F because it isn't hidden. --> +<div id="a11yAssociation" aria-hidden="true"> + [[a11yAssociation_(data.name)]] +</div> +<div id="card" class$="[[computeClasses_(data.state, inDevMode)]]"> + <div id="main"> + <div id="icon-wrapper"> + <img id="icon" src="[[data.iconUrl]]" + aria-describedby="a11yAssociation" + alt$="[[appOrExtension( + data.type, + '$i18nPolymer{appIcon}', + '$i18nPolymer{extensionIcon}')]]"> + <template is="dom-if" + if="[[computeSourceIndicatorIcon_(data.*)]]"> + <div id="source-indicator"> + <div class="source-icon-wrapper" role="img" aria-describedby="a11yAssociation" - alt$="[[appOrExtension( - data.type, - '$i18nPolymer{appIcon}', - '$i18nPolymer{extensionIcon}')]]"> - <template is="dom-if" - if="[[computeSourceIndicatorIcon_(data.*)]]"> - <div id="source-indicator"> - <div class="source-icon-wrapper" role="img" - aria-describedby="a11yAssociation" - aria-label$="[[computeSourceIndicatorText_(data.*)]]"> - <iron-icon icon="[[computeSourceIndicatorIcon_(data.*)]]"> - </iron-icon> - </div> - </div> - </template> + aria-label$="[[computeSourceIndicatorText_(data.*)]]"> + <iron-icon icon="[[computeSourceIndicatorIcon_(data.*)]]"> + </iron-icon> + </div> + </div> + </template> + </div> + <!-- This needs to be separate from the source-indicator since it can't + be contained inside of a position:relative parent element. --> + <template is="dom-if" + if="[[computeSourceIndicatorIcon_(data.*)]]"> + <paper-tooltip id="source-indicator-text" for="source-indicator" + position="top" fit-to-visible-bounds aria-hidden="true"> + [[computeSourceIndicatorText_(data.*)]] + </paper-tooltip> + </template> + <div id="content"> + <!--Note: We wrap inspect-views in a div so that the outer div + doesn't shrink (because it's not display: flex).--> + <div> + <div id="name-and-version" class="layout horizontal center"> + <div id="name" role="heading" aria-level="3" + class="clippable-flex-text">[[data.name]]</div> + <span id="version" hidden$="[[!inDevMode]]"> + [[data.version]] + </span> </div> - <!-- This needs to be separate from the source-indicator since it can't - be contained inside of a position:relative parent element. --> + </div> + <div id="description" class="multiline-clippable-text" + hidden$="[[hasWarnings_(data.disableReasons.*, data.*)]]"> + [[data.description]] + </div> + <template is="dom-if" + if="[[hasWarnings_(data.disableReasons.*, data.*)]]"> + <div id="warnings"> + <iron-icon id="error-icon" icon="cr:error"></iron-icon> + <span id="runtime-warnings" aria-describedby="a11yAssociation" + hidden$="[[!data.runtimeWarnings.length]]"> + <template is="dom-repeat" items="[[data.runtimeWarnings]]"> + [[item]] + </template> + </span> + <span id="suspicious-warning" aria-describedby="a11yAssociation" + hidden$="[[!data.disableReasons.suspiciousInstall]]"> + $i18n{itemSuspiciousInstall} + <a target="_blank" href="$i18n{suspiciousInstallHelpUrl}"> + $i18n{learnMore} + </a> + </span> + <span id="corrupted-warning" aria-describedby="a11yAssociation" + hidden$="[[!data.disableReasons.corruptInstall]]"> + $i18n{itemCorruptInstall} + </span> + <span id="blacklisted-warning"><!-- No whitespace + -->[[data.blacklistText]]<!-- so we can use :empty in css. + --></span> + </div> + </template> + <template is="dom-if" if="[[inDevMode]]"> + <div id="extension-id" class="bounded-text">[[data.id]]</div> <template is="dom-if" - if="[[computeSourceIndicatorIcon_(data.*)]]"> - <paper-tooltip id="source-indicator-text" for="source-indicator" - position="top" fit-to-visible-bounds aria-hidden="true"> - [[computeSourceIndicatorText_(data.*)]] - </paper-tooltip> - </template> - <div id="content"> + if="[[!computeInspectViewsHidden_(data.views)]]"> <!--Note: We wrap inspect-views in a div so that the outer div doesn't shrink (because it's not display: flex).--> <div> - <div id="name-and-version" class="layout horizontal center"> - <div id="name" role="heading" aria-level="3" - class="clippable-flex-text">[[data.name]]</div> - <span id="version" hidden$="[[!inDevMode]]"> - [[data.version]] + <div id="inspect-views"> + <span aria-describedby="a11yAssociation"> + $i18n{itemInspectViews} </span> + <a class="clippable-flex-text" is="action-link" + title="[[computeFirstInspectTitle_(data.views)]]" + on-click="onInspectTap_"> + [[computeFirstInspectLabel_(data.views)]] + </a> + <a is="action-link" + hidden$="[[computeExtraViewsHidden_(data.views)]]" + on-click="onExtraInspectTap_"> + [[computeExtraInspectLabel_(data.views)]] + </a> </div> </div> - <div id="description" class="multiline-clippable-text" - hidden$="[[hasWarnings_(data.disableReasons.*, data.*)]]"> - [[data.description]] - </div> - <template is="dom-if" - if="[[hasWarnings_(data.disableReasons.*, data.*)]]"> - <div id="warnings"> - <iron-icon id="error-icon" icon="cr:error"></iron-icon> - <span id="runtime-warnings" aria-describedby="a11yAssociation" - hidden$="[[!data.runtimeWarnings.length]]"> - <template is="dom-repeat" items="[[data.runtimeWarnings]]"> - [[item]] - </template> - </span> - <span id="suspicious-warning" aria-describedby="a11yAssociation" - hidden$="[[!data.disableReasons.suspiciousInstall]]"> - $i18n{itemSuspiciousInstall} - <a target="_blank" href="$i18n{suspiciousInstallHelpUrl}"> - $i18n{learnMore} - </a> - </span> - <span id="corrupted-warning" aria-describedby="a11yAssociation" - hidden$="[[!data.disableReasons.corruptInstall]]"> - $i18n{itemCorruptInstall} - </span> - <span id="blacklisted-warning"><!-- No whitespace - -->[[data.blacklistText]]<!-- so we can use :empty in css. - --></span> - </div> - </template> - <template is="dom-if" if="[[inDevMode]]"> - <div id="extension-id" class="bounded-text">[[data.id]]</div> - <template is="dom-if" - if="[[!computeInspectViewsHidden_(data.views)]]"> - <!--Note: We wrap inspect-views in a div so that the outer div - doesn't shrink (because it's not display: flex).--> - <div> - <div id="inspect-views"> - <span aria-describedby="a11yAssociation"> - $i18n{itemInspectViews} - </span> - <a class="clippable-flex-text" is="action-link" - title="[[computeFirstInspectTitle_(data.views)]]" - on-click="onInspectTap_"> - [[computeFirstInspectLabel_(data.views)]] - </a> - <a is="action-link" - hidden$="[[computeExtraViewsHidden_(data.views)]]" - on-click="onExtraInspectTap_"> - [[computeExtraInspectLabel_(data.views)]] - </a> - </div> - </div> - </template> - </template> - </div> - </div> - <div id="button-strip" class="layout horizontal center"> - <div class="layout flex horizontal center"> - <cr-button id="detailsButton" on-click="onDetailsTap_" - aria-describedby="a11yAssociation"> - $i18n{itemDetails} - </cr-button> - <cr-button id="remove-button" on-click="onRemoveTap_" - aria-describedby="a11yAssociation" - hidden="[[isControlled_(data.controlledInfo)]]"> - $i18n{remove} - </cr-button> - <template is="dom-if" if="[[shouldShowErrorsButton_(data.*)]]"> - <cr-button id="errors-button" on-click="onErrorsTap_" - aria-describedby="a11yAssociation"> - $i18n{itemErrors} - </cr-button> - </template> - </div> - <template is="dom-if" if="[[!computeDevReloadButtonHidden_(data.*)]]"> - <cr-icon-button id="dev-reload-button" class="icon-refresh no-overlap" - aria-label="$i18n{itemReload}" aria-describedby="a11yAssociation" - on-click="onReloadTap_"></cr-icon-button> - </template> - <template is="dom-if" if="[[data.disableReasons.corruptInstall]]"> - <cr-button id="repair-button" class="action-button" - aria-describedby="a11yAssociation" on-click="onRepairTap_"> - $i18n{itemRepair} - </cr-button> </template> - <template is="dom-if" if="[[isTerminated_(data.state)]]"> - <cr-button id="terminated-reload-button" on-click="onReloadTap_" - aria-describedby="a11yAssociation" class="action-button"> - $i18n{itemReload} - </cr-button> - </template> - <cr-toggle id="enable-toggle" - aria-label$="[[appOrExtension( - data.type, - '$i18nPolymer{appEnabled}', - '$i18nPolymer{extensionEnabled}')]]" - aria-describedby="a11yAssociation" - checked="[[isEnabled_(data.state)]]" on-change="onEnableChange_" - disabled="[[!isEnableToggleEnabled_(data.*)]]" - hidden$="[[!showEnableToggle_(data.*)]]"> - </cr-toggle> - </div> + </template> + </div> + </div> + <div id="button-strip" class="layout horizontal center"> + <div class="layout flex horizontal center"> + <cr-button id="detailsButton" on-click="onDetailsTap_" + aria-describedby="a11yAssociation"> + $i18n{itemDetails} + </cr-button> + <cr-button id="remove-button" on-click="onRemoveTap_" + aria-describedby="a11yAssociation" + hidden="[[isControlled_(data.controlledInfo)]]"> + $i18n{remove} + </cr-button> + <template is="dom-if" if="[[shouldShowErrorsButton_(data.*)]]"> + <cr-button id="errors-button" on-click="onErrorsTap_" + aria-describedby="a11yAssociation"> + $i18n{itemErrors} + </cr-button> + </template> </div> - </template> - <script src="item.js"></script> -</dom-module> + <template is="dom-if" if="[[!computeDevReloadButtonHidden_(data.*)]]"> + <cr-icon-button id="dev-reload-button" class="icon-refresh no-overlap" + aria-label="$i18n{itemReload}" aria-describedby="a11yAssociation" + on-click="onReloadTap_"></cr-icon-button> + </template> + <template is="dom-if" if="[[data.disableReasons.corruptInstall]]"> + <cr-button id="repair-button" class="action-button" + aria-describedby="a11yAssociation" on-click="onRepairTap_"> + $i18n{itemRepair} + </cr-button> + </template> + <template is="dom-if" if="[[isTerminated_(data.state)]]"> + <cr-button id="terminated-reload-button" on-click="onReloadTap_" + aria-describedby="a11yAssociation" class="action-button"> + $i18n{itemReload} + </cr-button> + </template> + <cr-toggle id="enable-toggle" + aria-label$="[[appOrExtension( + data.type, + '$i18nPolymer{appEnabled}', + '$i18nPolymer{extensionEnabled}')]]" + aria-describedby="a11yAssociation" + checked="[[isEnabled_(data.state)]]" on-change="onEnableChange_" + disabled="[[!isEnableToggleEnabled_(data.*)]]" + hidden$="[[!showEnableToggle_(data.*)]]"> + </cr-toggle> + </div> +</div> diff --git a/chromium/chrome/browser/resources/extensions/item.js b/chromium/chrome/browser/resources/extensions/item.js index d1fc8e973ab..7973811c58e 100644 --- a/chromium/chrome/browser/resources/extensions/item.js +++ b/chromium/chrome/browser/resources/extensions/item.js @@ -2,426 +2,441 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -cr.define('extensions', function() { - /** @interface */ - class ItemDelegate { - /** @param {string} id */ - deleteItem(id) {} - - /** - * @param {string} id - * @param {boolean} isEnabled - */ - setItemEnabled(id, isEnabled) {} - - /** - * @param {string} id - * @param {boolean} isAllowedIncognito - */ - setItemAllowedIncognito(id, isAllowedIncognito) {} - - /** - * @param {string} id - * @param {boolean} isAllowedOnFileUrls - */ - setItemAllowedOnFileUrls(id, isAllowedOnFileUrls) {} - - /** - * @param {string} id - * @param {!chrome.developerPrivate.HostAccess} hostAccess - */ - setItemHostAccess(id, hostAccess) {} - - /** - * @param {string} id - * @param {boolean} collectsErrors - */ - setItemCollectsErrors(id, collectsErrors) {} - - /** - * @param {string} id - * @param {chrome.developerPrivate.ExtensionView} view - */ - inspectItemView(id, view) {} - - /** - * @param {string} url - */ - openUrl(url) {} - - /** - * @param {string} id - * @return {!Promise} - */ - reloadItem(id) {} - - /** @param {string} id */ - repairItem(id) {} - - /** @param {!chrome.developerPrivate.ExtensionInfo} extension */ - showItemOptionsPage(extension) {} - - /** @param {string} id */ - showInFolder(id) {} - - /** - * @param {string} id - * @return {!Promise<string>} - */ - getExtensionSize(id) {} - - /** - * @param {string} id - * @param {string} host - * @return {!Promise<void>} - */ - addRuntimeHostPermission(id, host) {} - - /** - * @param {string} id - * @param {string} host - * @return {!Promise<void>} - */ - removeRuntimeHostPermission(id, host) {} - } - - const Item = Polymer({ - is: 'extensions-item', - - behaviors: [I18nBehavior, extensions.ItemBehavior], - - properties: { - // The item's delegate, or null. - delegate: { - type: Object, - }, - - // Whether or not dev mode is enabled. - inDevMode: { - type: Boolean, - value: false, - }, - - // The underlying ExtensionInfo itself. Public for use in declarative - // bindings. - /** @type {chrome.developerPrivate.ExtensionInfo} */ - data: { - type: Object, - }, - - // Whether or not the expanded view of the item is shown. - /** @private */ - showingDetails_: { - type: Boolean, - value: false, - }, +import 'chrome://resources/cr_elements/cr_button/cr_button.m.js'; +import 'chrome://resources/cr_elements/cr_icon_button/cr_icon_button.m.js'; +import 'chrome://resources/cr_elements/cr_icons_css.m.js'; +import 'chrome://resources/cr_elements/cr_toggle/cr_toggle.m.js'; +import 'chrome://resources/cr_elements/hidden_style_css.m.js'; +import 'chrome://resources/cr_elements/icons.m.js'; +import 'chrome://resources/cr_elements/shared_vars_css.m.js'; +import 'chrome://resources/js/action_link.js'; +import 'chrome://resources/cr_elements/action_link_css.m.js'; +import './icons.js'; +import './shared_style.js'; +import './shared_vars.js'; +import './strings.m.js'; +import 'chrome://resources/polymer/v3_0/iron-flex-layout/iron-flex-layout-classes.js'; +import 'chrome://resources/polymer/v3_0/iron-icon/iron-icon.js'; +import 'chrome://resources/polymer/v3_0/paper-tooltip/paper-tooltip.js'; + +import {getToastManager} from 'chrome://resources/cr_elements/cr_toast/cr_toast_manager.m.js'; +import {assert, assertNotReached} from 'chrome://resources/js/assert.m.js'; +import {I18nBehavior} from 'chrome://resources/js/i18n_behavior.m.js'; +import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js'; +import {flush, html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; + +import {ItemBehavior} from './item_behavior.js'; +import {computeInspectableViewLabel, getItemSource, getItemSourceString, isControlled, isEnabled, SourceType, userCanChangeEnablement} from './item_util.js'; +import {navigation, Page} from './navigation_helper.js'; + +/** @interface */ +export class ItemDelegate { + /** @param {string} id */ + deleteItem(id) {} + + /** + * @param {string} id + * @param {boolean} isEnabled + */ + setItemEnabled(id, isEnabled) {} + + /** + * @param {string} id + * @param {boolean} isAllowedIncognito + */ + setItemAllowedIncognito(id, isAllowedIncognito) {} + + /** + * @param {string} id + * @param {boolean} isAllowedOnFileUrls + */ + setItemAllowedOnFileUrls(id, isAllowedOnFileUrls) {} + + /** + * @param {string} id + * @param {!chrome.developerPrivate.HostAccess} hostAccess + */ + setItemHostAccess(id, hostAccess) {} + + /** + * @param {string} id + * @param {boolean} collectsErrors + */ + setItemCollectsErrors(id, collectsErrors) {} + + /** + * @param {string} id + * @param {chrome.developerPrivate.ExtensionView} view + */ + inspectItemView(id, view) {} + + /** + * @param {string} url + */ + openUrl(url) {} + + /** + * @param {string} id + * @return {!Promise} + */ + reloadItem(id) {} + + /** @param {string} id */ + repairItem(id) {} + + /** @param {!chrome.developerPrivate.ExtensionInfo} extension */ + showItemOptionsPage(extension) {} + + /** @param {string} id */ + showInFolder(id) {} + + /** + * @param {string} id + * @return {!Promise<string>} + */ + getExtensionSize(id) {} + + /** + * @param {string} id + * @param {string} host + * @return {!Promise<void>} + */ + addRuntimeHostPermission(id, host) {} + + /** + * @param {string} id + * @param {string} host + * @return {!Promise<void>} + */ + removeRuntimeHostPermission(id, host) {} +} + +Polymer({ + is: 'extensions-item', + + _template: html`{__html_template__}`, + + behaviors: [I18nBehavior, ItemBehavior], + + properties: { + // The item's delegate, or null. + delegate: { + type: Object, }, - /** Prevents reloading the same item while it's already being reloaded. */ - isReloading_: false, - - observers: [ - 'observeIdVisibility_(inDevMode, showingDetails_, data.id)', - ], - - /** @return {!HTMLElement} The "Details" button. */ - getDetailsButton: function() { - return this.$.detailsButton; - }, - - /** @return {?HTMLElement} The "Errors" button, if it exists. */ - getErrorsButton: function() { - return /** @type {?HTMLElement} */ (this.$$('#errors-button')); - }, - - /** @private string */ - a11yAssociation_: function() { - // Don't use I18nBehavior.i18n because of additional checks it performs. - // Polymer ensures that this string is not stamped into arbitrary HTML. - // |this.data.name| can contain any data including html tags. - // ex: "My <video> download extension!" - return loadTimeData.getStringF( - 'extensionA11yAssociation', this.data.name); - }, - - /** @private */ - observeIdVisibility_: function(inDevMode, showingDetails, id) { - Polymer.dom.flush(); - const idElement = this.$$('#extension-id'); - if (idElement) { - assert(this.data); - idElement.innerHTML = this.i18n('itemId', this.data.id); - } - }, - - /** - * @return {boolean} - * @private - */ - shouldShowErrorsButton_: function() { - // When the error console is disabled (happens when - // --disable-error-console command line flag is used or when in the - // Stable/Beta channel), |installWarnings| is populated. - if (this.data.installWarnings && this.data.installWarnings.length > 0) { - return true; - } - - // When error console is enabled |installedWarnings| is not populated. - // Instead |manifestErrors| and |runtimeErrors| are used. - return this.data.manifestErrors.length > 0 || - this.data.runtimeErrors.length > 0; - }, - - /** @private */ - onRemoveTap_: function() { - this.delegate.deleteItem(this.data.id); + // Whether or not dev mode is enabled. + inDevMode: { + type: Boolean, + value: false, }, - /** @private */ - onEnableChange_: function() { - this.delegate.setItemEnabled( - this.data.id, this.$['enable-toggle'].checked); + // The underlying ExtensionInfo itself. Public for use in declarative + // bindings. + /** @type {chrome.developerPrivate.ExtensionInfo} */ + data: { + type: Object, }, + // Whether or not the expanded view of the item is shown. /** @private */ - onErrorsTap_: function() { - if (this.data.installWarnings && this.data.installWarnings.length > 0) { - this.fire('show-install-warnings', this.data.installWarnings); - return; - } - - extensions.navigation.navigateTo( - {page: extensions.Page.ERRORS, extensionId: this.data.id}); - }, - - /** @private */ - onDetailsTap_: function() { - extensions.navigation.navigateTo( - {page: extensions.Page.DETAILS, extensionId: this.data.id}); - }, - - /** - * @param {!{model: !{item: !chrome.developerPrivate.ExtensionView}}} e - * @private - */ - onInspectTap_: function(e) { - this.delegate.inspectItemView(this.data.id, this.data.views[0]); - }, - - /** @private */ - onExtraInspectTap_: function() { - extensions.navigation.navigateTo( - {page: extensions.Page.DETAILS, extensionId: this.data.id}); - }, - - /** @private */ - onReloadTap_: function() { - // Don't reload if in the middle of an update. - if (this.isReloading_) { - return; - } - - this.isReloading_ = true; - - const toastManager = cr.toastManager.getInstance(); - // Keep the toast open indefinitely. - toastManager.duration = 0; - toastManager.show(this.i18n('itemReloading'), false); - this.delegate.reloadItem(this.data.id) - .then( - () => { - toastManager.hide(); - toastManager.duration = 3000; - toastManager.show(this.i18n('itemReloaded'), false); - this.isReloading_ = false; - }, - loadError => { - this.fire('load-error', loadError); - toastManager.hide(); - this.isReloading_ = false; - }); - }, - - /** @private */ - onRepairTap_: function() { - this.delegate.repairItem(this.data.id); - }, - - /** - * @return {boolean} - * @private - */ - isControlled_: function() { - return extensions.isControlled(this.data); + showingDetails_: { + type: Boolean, + value: false, }, - - /** - * @return {boolean} - * @private - */ - isEnabled_: function() { - return extensions.isEnabled(this.data.state); - }, - - /** - * @return {boolean} - * @private - */ - isEnableToggleEnabled_: function() { - return extensions.userCanChangeEnablement(this.data); - }, - - /** - * Returns true if the enable toggle should be shown. - * @return {boolean} - * @private - */ - showEnableToggle_: function() { - return !this.isTerminated_() && !this.data.disableReasons.corruptInstall; - }, - - /** - * Returns true if the extension is in the terminated state. - * @return {boolean} - * @private - */ - isTerminated_: function() { - return this.data.state == - chrome.developerPrivate.ExtensionState.TERMINATED; - }, - - /** - * return {string} - * @private - */ - computeClasses_: function() { - let classes = this.isEnabled_() ? 'enabled' : 'disabled'; - if (this.inDevMode) { - classes += ' dev-mode'; - } - return classes; - }, - - /** - * @return {string} - * @private - */ - computeSourceIndicatorIcon_: function() { - switch (extensions.getItemSource(this.data)) { - case SourceType.POLICY: - return 'extensions-icons:business'; - case SourceType.SIDELOADED: - return 'extensions-icons:input'; - case SourceType.UNKNOWN: - // TODO(dpapad): Ask UX for a better icon for this case. - return 'extensions-icons:input'; - case SourceType.UNPACKED: - return 'extensions-icons:unpacked'; - case SourceType.WEBSTORE: - return ''; - } - assertNotReached(); - }, - - /** - * @return {string} - * @private - */ - computeSourceIndicatorText_: function() { - if (this.data.locationText) { - return this.data.locationText; - } - - const sourceType = extensions.getItemSource(this.data); - return sourceType == SourceType.WEBSTORE ? - '' : - extensions.getItemSourceString(sourceType); - }, - - /** - * @return {boolean} - * @private - */ - computeInspectViewsHidden_: function() { - return !this.data.views || this.data.views.length == 0; - }, - - /** - * @return {string} - * @private - */ - computeFirstInspectTitle_: function() { - // Note: theoretically, this wouldn't be called without any inspectable - // views (because it's in a dom-if="!computeInspectViewsHidden_()"). - // However, due to the recycling behavior of iron list, it seems that - // sometimes it can. Even when it is, the UI behaves properly, but we - // need to handle the case gracefully. - return this.data.views.length > 0 ? - extensions.computeInspectableViewLabel(this.data.views[0]) : - ''; - }, - - /** - * @return {string} - * @private - */ - computeFirstInspectLabel_: function() { - const label = this.computeFirstInspectTitle_(); - return label && this.data.views.length > 1 ? label + ',' : label; - }, - - /** - * @return {boolean} - * @private - */ - computeExtraViewsHidden_: function() { - return this.data.views.length <= 1; - }, - - /** - * @return {boolean} - * @private - */ - computeDevReloadButtonHidden_: function() { - // Only display the reload spinner if the extension is unpacked and - // enabled. There's no point in reloading a disabled extension, and we'll - // show a crashed reload buton if it's terminated. - const showIcon = - this.data.location == chrome.developerPrivate.Location.UNPACKED && - this.data.state == chrome.developerPrivate.ExtensionState.ENABLED; - return !showIcon; - }, - - /** - * @return {string} - * @private - */ - computeExtraInspectLabel_: function() { - return this.i18n( - 'itemInspectViewsExtra', (this.data.views.length - 1).toString()); - }, - - /** - * @return {boolean} - * @private - */ - hasWarnings_: function() { - return this.data.disableReasons.corruptInstall || - this.data.disableReasons.suspiciousInstall || - this.data.runtimeWarnings.length > 0 || !!this.data.blacklistText; - }, - - /** - * @return {string} - * @private - */ - computeWarningsClasses_: function() { - return this.data.blacklistText ? 'severe' : 'mild'; - }, - }); - - return { - Item: Item, - ItemDelegate: ItemDelegate, - }; + }, + + /** Prevents reloading the same item while it's already being reloaded. */ + isReloading_: false, + + observers: [ + 'observeIdVisibility_(inDevMode, showingDetails_, data.id)', + ], + + /** @return {!HTMLElement} The "Details" button. */ + getDetailsButton: function() { + return /** @type {!HTMLElement} */ (this.$.detailsButton); + }, + + /** @return {?HTMLElement} The "Errors" button, if it exists. */ + getErrorsButton: function() { + return /** @type {?HTMLElement} */ (this.$$('#errors-button')); + }, + + /** @private string */ + a11yAssociation_: function() { + // Don't use I18nBehavior.i18n because of additional checks it performs. + // Polymer ensures that this string is not stamped into arbitrary HTML. + // |this.data.name| can contain any data including html tags. + // ex: "My <video> download extension!" + return loadTimeData.getStringF('extensionA11yAssociation', this.data.name); + }, + + /** @private */ + observeIdVisibility_: function(inDevMode, showingDetails, id) { + flush(); + const idElement = this.$$('#extension-id'); + if (idElement) { + assert(this.data); + idElement.innerHTML = this.i18n('itemId', this.data.id); + } + }, + + /** + * @return {boolean} + * @private + */ + shouldShowErrorsButton_: function() { + // When the error console is disabled (happens when + // --disable-error-console command line flag is used or when in the + // Stable/Beta channel), |installWarnings| is populated. + if (this.data.installWarnings && this.data.installWarnings.length > 0) { + return true; + } + + // When error console is enabled |installedWarnings| is not populated. + // Instead |manifestErrors| and |runtimeErrors| are used. + return this.data.manifestErrors.length > 0 || + this.data.runtimeErrors.length > 0; + }, + + /** @private */ + onRemoveTap_: function() { + this.delegate.deleteItem(this.data.id); + }, + + /** @private */ + onEnableChange_: function() { + this.delegate.setItemEnabled(this.data.id, this.$['enable-toggle'].checked); + }, + + /** @private */ + onErrorsTap_: function() { + if (this.data.installWarnings && this.data.installWarnings.length > 0) { + this.fire('show-install-warnings', this.data.installWarnings); + return; + } + + navigation.navigateTo({page: Page.ERRORS, extensionId: this.data.id}); + }, + + /** @private */ + onDetailsTap_: function() { + navigation.navigateTo({page: Page.DETAILS, extensionId: this.data.id}); + }, + + /** + * @param {!{model: !{item: !chrome.developerPrivate.ExtensionView}}} e + * @private + */ + onInspectTap_: function(e) { + this.delegate.inspectItemView(this.data.id, this.data.views[0]); + }, + + /** @private */ + onExtraInspectTap_: function() { + navigation.navigateTo({page: Page.DETAILS, extensionId: this.data.id}); + }, + + /** @private */ + onReloadTap_: function() { + // Don't reload if in the middle of an update. + if (this.isReloading_) { + return; + } + + this.isReloading_ = true; + + const toastManager = getToastManager(); + // Keep the toast open indefinitely. + toastManager.duration = 0; + toastManager.show(this.i18n('itemReloading')); + this.delegate.reloadItem(this.data.id) + .then( + () => { + toastManager.hide(); + toastManager.duration = 3000; + toastManager.show(this.i18n('itemReloaded')); + this.isReloading_ = false; + }, + loadError => { + this.fire('load-error', loadError); + toastManager.hide(); + this.isReloading_ = false; + }); + }, + + /** @private */ + onRepairTap_: function() { + this.delegate.repairItem(this.data.id); + }, + + /** + * @return {boolean} + * @private + */ + isControlled_: function() { + return isControlled(this.data); + }, + + /** + * @return {boolean} + * @private + */ + isEnabled_: function() { + return isEnabled(this.data.state); + }, + + /** + * @return {boolean} + * @private + */ + isEnableToggleEnabled_: function() { + return userCanChangeEnablement(this.data); + }, + + /** + * Returns true if the enable toggle should be shown. + * @return {boolean} + * @private + */ + showEnableToggle_: function() { + return !this.isTerminated_() && !this.data.disableReasons.corruptInstall; + }, + + /** + * Returns true if the extension is in the terminated state. + * @return {boolean} + * @private + */ + isTerminated_: function() { + return this.data.state == chrome.developerPrivate.ExtensionState.TERMINATED; + }, + + /** + * return {string} + * @private + */ + computeClasses_: function() { + let classes = this.isEnabled_() ? 'enabled' : 'disabled'; + if (this.inDevMode) { + classes += ' dev-mode'; + } + return classes; + }, + + /** + * @return {string} + * @private + */ + computeSourceIndicatorIcon_: function() { + switch (getItemSource(this.data)) { + case SourceType.POLICY: + return 'extensions-icons:business'; + case SourceType.SIDELOADED: + return 'extensions-icons:input'; + case SourceType.UNKNOWN: + // TODO(dpapad): Ask UX for a better icon for this case. + return 'extensions-icons:input'; + case SourceType.UNPACKED: + return 'extensions-icons:unpacked'; + case SourceType.WEBSTORE: + return ''; + } + assertNotReached(); + }, + + /** + * @return {string} + * @private + */ + computeSourceIndicatorText_: function() { + if (this.data.locationText) { + return this.data.locationText; + } + + const sourceType = getItemSource(this.data); + return sourceType == SourceType.WEBSTORE ? '' : + getItemSourceString(sourceType); + }, + + /** + * @return {boolean} + * @private + */ + computeInspectViewsHidden_: function() { + return !this.data.views || this.data.views.length == 0; + }, + + /** + * @return {string} + * @private + */ + computeFirstInspectTitle_: function() { + // Note: theoretically, this wouldn't be called without any inspectable + // views (because it's in a dom-if="!computeInspectViewsHidden_()"). + // However, due to the recycling behavior of iron list, it seems that + // sometimes it can. Even when it is, the UI behaves properly, but we + // need to handle the case gracefully. + return this.data.views.length > 0 ? + computeInspectableViewLabel(this.data.views[0]) : + ''; + }, + + /** + * @return {string} + * @private + */ + computeFirstInspectLabel_: function() { + const label = this.computeFirstInspectTitle_(); + return label && this.data.views.length > 1 ? label + ',' : label; + }, + + /** + * @return {boolean} + * @private + */ + computeExtraViewsHidden_: function() { + return this.data.views.length <= 1; + }, + + /** + * @return {boolean} + * @private + */ + computeDevReloadButtonHidden_: function() { + // Only display the reload spinner if the extension is unpacked and + // enabled. There's no point in reloading a disabled extension, and we'll + // show a crashed reload button if it's terminated. + const showIcon = + this.data.location == chrome.developerPrivate.Location.UNPACKED && + this.data.state == chrome.developerPrivate.ExtensionState.ENABLED; + return !showIcon; + }, + + /** + * @return {string} + * @private + */ + computeExtraInspectLabel_: function() { + return this.i18n( + 'itemInspectViewsExtra', (this.data.views.length - 1).toString()); + }, + + /** + * @return {boolean} + * @private + */ + hasWarnings_: function() { + return this.data.disableReasons.corruptInstall || + this.data.disableReasons.suspiciousInstall || + this.data.runtimeWarnings.length > 0 || !!this.data.blacklistText; + }, + + /** + * @return {string} + * @private + */ + computeWarningsClasses_: function() { + return this.data.blacklistText ? 'severe' : 'mild'; + }, }); diff --git a/chromium/chrome/browser/resources/extensions/item_behavior.html b/chromium/chrome/browser/resources/extensions/item_behavior.html deleted file mode 100644 index b0369e6dc6d..00000000000 --- a/chromium/chrome/browser/resources/extensions/item_behavior.html +++ /dev/null @@ -1 +0,0 @@ -<script src="item_behavior.js"></script> diff --git a/chromium/chrome/browser/resources/extensions/item_behavior.js b/chromium/chrome/browser/resources/extensions/item_behavior.js index 40241f5eb2d..c1e368a4368 100644 --- a/chromium/chrome/browser/resources/extensions/item_behavior.js +++ b/chromium/chrome/browser/resources/extensions/item_behavior.js @@ -2,29 +2,27 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -cr.define('extensions', function() { - /** @polymerBehavior */ - const ItemBehavior = { - /** - * @param {chrome.developerPrivate.ExtensionType} type - * @param {string} appLabel - * @param {string} extensionLabel - * @return {string} The app or extension label depending on |type|. - */ - appOrExtension: function(type, appLabel, extensionLabel) { - const ExtensionType = chrome.developerPrivate.ExtensionType; - switch (type) { - case ExtensionType.HOSTED_APP: - case ExtensionType.LEGACY_PACKAGED_APP: - case ExtensionType.PLATFORM_APP: - return appLabel; - case ExtensionType.EXTENSION: - case ExtensionType.SHARED_MODULE: - return extensionLabel; - } - assertNotReached('Item type is not App or Extension.'); - }, - }; +import {assertNotReached} from 'chrome://resources/js/assert.m.js'; - return {ItemBehavior: ItemBehavior}; -}); +/** @polymerBehavior */ +export const ItemBehavior = { + /** + * @param {chrome.developerPrivate.ExtensionType} type + * @param {string} appLabel + * @param {string} extensionLabel + * @return {string} The app or extension label depending on |type|. + */ + appOrExtension: function(type, appLabel, extensionLabel) { + const ExtensionType = chrome.developerPrivate.ExtensionType; + switch (type) { + case ExtensionType.HOSTED_APP: + case ExtensionType.LEGACY_PACKAGED_APP: + case ExtensionType.PLATFORM_APP: + return appLabel; + case ExtensionType.EXTENSION: + case ExtensionType.SHARED_MODULE: + return extensionLabel; + } + assertNotReached('Item type is not App or Extension.'); + }, +}; diff --git a/chromium/chrome/browser/resources/extensions/item_list.html b/chromium/chrome/browser/resources/extensions/item_list.html index 339e774de80..2ea1c0f87ae 100644 --- a/chromium/chrome/browser/resources/extensions/item_list.html +++ b/chromium/chrome/browser/resources/extensions/item_list.html @@ -1,133 +1,118 @@ -<link rel="import" href="chrome://resources/html/polymer.html"> +<style include="shared-style"> + .items-container, + #content-wrapper { + --extensions-card-width: 400px; + } -<link rel="import" href="chrome://resources/cr_components/managed_footnote/managed_footnote.html"> -<link rel="import" href="chrome://resources/cr_elements/cr_container_shadow_behavior.html"> -<link rel="import" href="chrome://resources/html/cr.html"> -<link rel="import" href="chrome://resources/html/i18n_behavior.html"> -<link rel="import" href="chrome://resources/polymer/v1_0/iron-a11y-announcer/iron-a11y-announcer.html"> -<link rel="import" href="item.html"> -<link rel="import" href="shared_style.html"> + #container { + box-sizing: border-box; + height: 100%; + overflow: overlay; + } -<dom-module id="extensions-item-list"> - <template> - <style include="shared-style"> - .items-container, - #content-wrapper { - --extensions-card-width: 400px; - } + #content-wrapper { + min-width: var(--extensions-card-width); + padding: 24px 60px 64px; + } - #container { - box-sizing: border-box; - height: 100%; - overflow: overlay; - } + .empty-list-message { + color: #6e6e6e; + font-size: 123%; /* Should be 16px when 100% is 13px. */ + font-weight: 500; + margin-top: 80px; + text-align: center; + } - #content-wrapper { - min-width: var(--extensions-card-width); - padding: 24px 60px 64px; - } + @media (prefers-color-scheme: dark) { + .empty-list-message { + color: var(--cr-secondary-text-color); + } + } - .empty-list-message { - color: #6e6e6e; - font-size: 123%; /* Should be 16px when 100% is 13px. */ - font-weight: 500; - margin-top: 80px; - text-align: center; - } + .items-container { + --grid-gutter: 12px; + --max-columns: 3; + display: grid; + grid-column-gap: var(--grid-gutter); + grid-row-gap: var(--grid-gutter); + grid-template-columns: repeat(auto-fill, var(--extensions-card-width)); + justify-content: center; + margin: auto; + max-width: calc(var(--extensions-card-width) * var(--max-columns) + + var(--grid-gutter) * var(--max-columns)); + } - @media (prefers-color-scheme: dark) { - .empty-list-message { - color: var(--cr-secondary-text-color); - } - } + extensions-item { + grid-column-start: auto; + grid-row-start: auto; + } - .items-container { - --grid-gutter: 12px; - --max-columns: 3; - display: grid; - grid-column-gap: var(--grid-gutter); - grid-row-gap: var(--grid-gutter); - grid-template-columns: repeat(auto-fill, var(--extensions-card-width)); - justify-content: center; - margin: auto; - max-width: calc(var(--extensions-card-width) * var(--max-columns) + - var(--grid-gutter) * var(--max-columns)); - } + #app-title { + color: var(--cr-primary-text-color); + font-size: 123%; + font-weight: 400; + letter-spacing: .25px; + margin-bottom: 12px; + margin-top: 21px; + padding-bottom: 4px; + padding-top: 8px; + } - extensions-item { - grid-column-start: auto; - grid-row-start: auto; - } - - #app-title { - color: var(--cr-primary-text-color); - font-size: 123%; - font-weight: 400; - letter-spacing: .25px; - margin-bottom: 12px; - margin-top: 21px; - padding-bottom: 4px; - padding-top: 8px; - } - - managed-footnote { - border-top: none; - /* #content-wrapper has a 24px margin-top. This overrides that - * margin-top, so the only space left is this element's 12px - * padding-bottom. - */ - margin-bottom: -24px; - padding-bottom: 12px; - padding-top: 12px; - /* The next element spills over this element. This ensures the link - * is clickable. */ - z-index: 1; - } - </style> - <div id="container"> - <managed-footnote hidden="[[filter]]"></managed-footnote> - <div id="content-wrapper"> - <div id="no-items" class="empty-list-message" - hidden$="[[!shouldShowEmptyItemsMessage_( - apps.length, extensions.length)]]"> - <span on-click="onNoExtensionsTap_"> - $i18nRaw{noExtensionsOrApps} - </span> - </div> - <div id="no-search-results" class="empty-list-message" - hidden$="[[!shouldShowEmptySearchMessage_( - shownAppsCount_, shownExtensionsCount_, apps, extensions)]]"> - <span>$i18n{noSearchResults}</span> - </div> - <div class="items-container" hidden="[[!shownExtensionsCount_]]"> - <!-- Render only a few items first, to improve initial render time, - then render the remaining items on a different frame. Value of 3 - was chosen by experimentation, and it is a good trade-off between - initial render time and total render time. --> - <template is="dom-repeat" items="[[extensions]]" initial-count="3" - filter="[[computedFilter_]]" - rendered-item-count="{{shownExtensionsCount_::dom-change}}"> - <extensions-item id="[[item.id]]" data="[[item]]" - delegate="[[delegate]]" in-dev-mode="[[inDevMode]]"> - </extensions-item> - </template> - </div> - <div hidden="[[!shownAppsCount_]]"> - <!-- app-title needs to left-align with the grid content below, and - the easiest way to achieve this is to make it a grid as well. --> - <h2 id="app-title" class="items-container">$i18n{appsTitle}</h2> - <div class="items-container"> - <template is="dom-repeat" items="[[apps]]" initial-count="3" - filter="[[computedFilter_]]" - rendered-item-count="{{shownAppsCount_::dom-change}}"> - <extensions-item id="[[item.id]]" data="[[item]]" - delegate="[[delegate]]" in-dev-mode="[[inDevMode]]"> - </extensions-item> - </template> - </div> - </div> + managed-footnote { + border-top: none; + /* #content-wrapper has a 24px margin-top. This overrides that + * margin-top, so the only space left is this element's 12px + * padding-bottom. + */ + margin-bottom: -24px; + padding-bottom: 12px; + padding-top: 12px; + /* The next element spills over this element. This ensures the link + * is clickable. */ + z-index: 1; + } +</style> +<div id="container"> + <managed-footnote hidden="[[filter]]"></managed-footnote> + <div id="content-wrapper"> + <div id="no-items" class="empty-list-message" + hidden$="[[!shouldShowEmptyItemsMessage_( + apps.length, extensions.length)]]"> + <span on-click="onNoExtensionsTap_"> + $i18nRaw{noExtensionsOrApps} + </span> + </div> + <div id="no-search-results" class="empty-list-message" + hidden$="[[!shouldShowEmptySearchMessage_( + shownAppsCount_, shownExtensionsCount_, apps, extensions)]]"> + <span>$i18n{noSearchResults}</span> + </div> + <div class="items-container" hidden="[[!shownExtensionsCount_]]"> + <!-- Render only a few items first, to improve initial render time, + then render the remaining items on a different frame. Value of 3 + was chosen by experimentation, and it is a good trade-off between + initial render time and total render time. --> + <template is="dom-repeat" items="[[extensions]]" initial-count="3" + filter="[[computedFilter_]]" + rendered-item-count="{{shownExtensionsCount_::dom-change}}"> + <extensions-item id="[[item.id]]" data="[[item]]" + delegate="[[delegate]]" in-dev-mode="[[inDevMode]]"> + </extensions-item> + </template> + </div> + <div hidden="[[!shownAppsCount_]]"> + <!-- app-title needs to left-align with the grid content below, and + the easiest way to achieve this is to make it a grid as well. --> + <h2 id="app-title" class="items-container">$i18n{appsTitle}</h2> + <div class="items-container"> + <template is="dom-repeat" items="[[apps]]" initial-count="3" + filter="[[computedFilter_]]" + rendered-item-count="{{shownAppsCount_::dom-change}}"> + <extensions-item id="[[item.id]]" data="[[item]]" + delegate="[[delegate]]" in-dev-mode="[[inDevMode]]"> + </extensions-item> + </template> </div> </div> - </template> - <script src="item_list.js"></script> -</dom-module> + </div> +</div> diff --git a/chromium/chrome/browser/resources/extensions/item_list.js b/chromium/chrome/browser/resources/extensions/item_list.js index 04f8df0dfd5..f24ce184d71 100644 --- a/chromium/chrome/browser/resources/extensions/item_list.js +++ b/chromium/chrome/browser/resources/extensions/item_list.js @@ -2,125 +2,131 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -cr.define('extensions', function() { - const ItemList = Polymer({ - is: 'extensions-item-list', - - behaviors: [CrContainerShadowBehavior, I18nBehavior], - - properties: { - /** @type {!Array<!chrome.developerPrivate.ExtensionInfo>} */ - apps: Array, - - /** @type {!Array<!chrome.developerPrivate.ExtensionInfo>} */ - extensions: Array, - - /** @type {extensions.ItemDelegate} */ - delegate: Object, - - inDevMode: { - type: Boolean, - value: false, - }, - - filter: { - type: String, - }, - - /** @private */ - computedFilter_: { - type: String, - computed: 'computeFilter_(filter)', - observer: 'announceSearchResults_', - }, - - /** @private */ - shownExtensionsCount_: { - type: Number, - value: 0, - }, - - /** @private */ - shownAppsCount_: { - type: Number, - value: 0, - }, - }, +import 'chrome://resources/cr_components/managed_footnote/managed_footnote.m.js'; +import './shared_style.js'; - /** - * @param {string} id - * @return {?Element} - */ - getDetailsButton: function(id) { - const item = this.$$(`#${id}`); - return item && item.getDetailsButton(); - }, +import {CrContainerShadowBehavior} from 'chrome://resources/cr_elements/cr_container_shadow_behavior.m.js'; +import {I18nBehavior} from 'chrome://resources/js/i18n_behavior.m.js'; +import {IronA11yAnnouncer} from 'chrome://resources/polymer/v3_0/iron-a11y-announcer/iron-a11y-announcer.js'; +import {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; - /** - * @param {string} id - * @return {?Element} - */ - getErrorsButton: function(id) { - const item = this.$$(`#${id}`); - return item && item.getErrorsButton(); - }, +import {ItemDelegate} from './item.js'; - /** - * Computes the filter function to be used for determining which items - * should be shown. A |null| value indicates that everything should be - * shown. - * return {?Function} - * @private - */ - computeFilter_: function() { - const formattedFilter = this.filter.trim().toLowerCase(); - return formattedFilter ? - i => i.name.toLowerCase().includes(formattedFilter) : - null; - }, +Polymer({ + is: 'extensions-item-list', - /** @private */ - shouldShowEmptyItemsMessage_: function() { - if (!this.apps || !this.extensions) { - return; - } + _template: html`{__html_template__}`, + + behaviors: [CrContainerShadowBehavior, I18nBehavior], + + properties: { + /** @type {!Array<!chrome.developerPrivate.ExtensionInfo>} */ + apps: Array, - return this.apps.length === 0 && this.extensions.length === 0; + /** @type {!Array<!chrome.developerPrivate.ExtensionInfo>} */ + extensions: Array, + + /** @type {ItemDelegate} */ + delegate: Object, + + inDevMode: { + type: Boolean, + value: false, }, - /** @private */ - shouldShowEmptySearchMessage_: function() { - return !this.shouldShowEmptyItemsMessage_() && - this.shownAppsCount_ === 0 && this.shownExtensionsCount_ === 0; + filter: { + type: String, }, /** @private */ - onNoExtensionsTap_: function(e) { - if (e.target.tagName == 'A') { - chrome.metricsPrivate.recordUserAction('Options_GetMoreExtensions'); - } + computedFilter_: { + type: String, + computed: 'computeFilter_(filter)', + observer: 'announceSearchResults_', }, /** @private */ - announceSearchResults_: function() { - if (this.computedFilter_) { - Polymer.IronA11yAnnouncer.requestAvailability(); - this.async(() => { // Async to allow list to update. - const total = this.shownAppsCount_ + this.shownExtensionsCount_; - this.fire('iron-announce', { - text: this.shouldShowEmptySearchMessage_() ? - this.i18n('noSearchResults') : - (total == 1 ? - this.i18n('searchResultsSingular', this.filter) : - this.i18n( - 'searchResultsPlural', total.toString(), this.filter)), - }); - }); - } + shownExtensionsCount_: { + type: Number, + value: 0, }, - }); - return { - ItemList: ItemList, - }; + /** @private */ + shownAppsCount_: { + type: Number, + value: 0, + }, + }, + + /** + * @param {string} id + * @return {?Element} + */ + getDetailsButton: function(id) { + const item = this.$$(`#${id}`); + return item && item.getDetailsButton(); + }, + + /** + * @param {string} id + * @return {?Element} + */ + getErrorsButton: function(id) { + const item = this.$$(`#${id}`); + return item && item.getErrorsButton(); + }, + + /** + * Computes the filter function to be used for determining which items + * should be shown. A |null| value indicates that everything should be + * shown. + * return {?Function} + * @private + */ + computeFilter_: function() { + const formattedFilter = this.filter.trim().toLowerCase(); + return formattedFilter ? + i => i.name.toLowerCase().includes(formattedFilter) : + null; + }, + + /** @private */ + shouldShowEmptyItemsMessage_: function() { + if (!this.apps || !this.extensions) { + return; + } + + return this.apps.length === 0 && this.extensions.length === 0; + }, + + /** @private */ + shouldShowEmptySearchMessage_: function() { + return !this.shouldShowEmptyItemsMessage_() && this.shownAppsCount_ === 0 && + this.shownExtensionsCount_ === 0; + }, + + /** @private */ + onNoExtensionsTap_: function(e) { + if (e.target.tagName == 'A') { + chrome.metricsPrivate.recordUserAction('Options_GetMoreExtensions'); + } + }, + + /** @private */ + announceSearchResults_: function() { + if (this.computedFilter_) { + IronA11yAnnouncer.requestAvailability(); + this.async(() => { // Async to allow list to update. + const total = this.shownAppsCount_ + this.shownExtensionsCount_; + this.fire('iron-announce', { + text: this.shouldShowEmptySearchMessage_() ? + this.i18n('noSearchResults') : + (total == 1 ? + this.i18n('searchResultsSingular', this.filter) : + this.i18n( + 'searchResultsPlural', total.toString(), this.filter)), + }); + }); + } + }, }); diff --git a/chromium/chrome/browser/resources/extensions/item_util.html b/chromium/chrome/browser/resources/extensions/item_util.html deleted file mode 100644 index 8836ce076d2..00000000000 --- a/chromium/chrome/browser/resources/extensions/item_util.html +++ /dev/null @@ -1,4 +0,0 @@ -<link rel="import" href="chrome://resources/html/assert.html"> -<link rel="import" href="chrome://resources/html/cr.html"> -<link rel="import" href="strings.html"> -<script src="item_util.js"></script> diff --git a/chromium/chrome/browser/resources/extensions/item_util.js b/chromium/chrome/browser/resources/extensions/item_util.js index b619d2cdb8e..b137f5d8438 100644 --- a/chromium/chrome/browser/resources/extensions/item_util.js +++ b/chromium/chrome/browser/resources/extensions/item_util.js @@ -2,9 +2,15 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import './strings.m.js'; + +import {assertNotReached} from 'chrome://resources/js/assert.m.js'; +import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js'; + + // Closure compiler won't let this be declared inside cr.define(). /** @enum {string} */ -const SourceType = { +export const SourceType = { WEBSTORE: 'webstore', POLICY: 'policy', SIDELOADED: 'sideloaded', @@ -12,145 +18,134 @@ const SourceType = { UNKNOWN: 'unknown', }; -cr.define('extensions', function() { - /** - * Returns true if the extension is enabled, including terminated - * extensions. - * @param {!chrome.developerPrivate.ExtensionState} state - * @return {boolean} - */ - function isEnabled(state) { - switch (state) { - case chrome.developerPrivate.ExtensionState.ENABLED: - case chrome.developerPrivate.ExtensionState.TERMINATED: - return true; - case chrome.developerPrivate.ExtensionState.BLACKLISTED: - case chrome.developerPrivate.ExtensionState.DISABLED: - return false; - } - assertNotReached(); +/** + * Returns true if the extension is enabled, including terminated + * extensions. + * @param {!chrome.developerPrivate.ExtensionState} state + * @return {boolean} + */ +export function isEnabled(state) { + switch (state) { + case chrome.developerPrivate.ExtensionState.ENABLED: + case chrome.developerPrivate.ExtensionState.TERMINATED: + return true; + case chrome.developerPrivate.ExtensionState.BLACKLISTED: + case chrome.developerPrivate.ExtensionState.DISABLED: + return false; } + assertNotReached(); +} + +/** + * @param {!chrome.developerPrivate.ExtensionInfo} extensionInfo + * @return {boolean} Whether the extension is controlled. + */ +export function isControlled(extensionInfo) { + return !!extensionInfo.controlledInfo; +} - /** - * @param {!chrome.developerPrivate.ExtensionInfo} extensionInfo - * @return {boolean} Whether the extension is controlled. - */ - function isControlled(extensionInfo) { - return !!extensionInfo.controlledInfo; +/** + * Returns true if the user can change whether or not the extension is + * enabled. + * @param {!chrome.developerPrivate.ExtensionInfo} item + * @return {boolean} + */ +export function userCanChangeEnablement(item) { + // User doesn't have permission. + if (!item.userMayModify) { + return false; + } + // Item is forcefully disabled. + if (item.disableReasons.corruptInstall || + item.disableReasons.suspiciousInstall || + item.disableReasons.updateRequired) { + return false; + } + // An item with dependent extensions can't be disabled (it would bork the + // dependents). + if (item.dependentExtensions.length > 0) { + return false; + } + // Blacklisted can't be enabled, either. + if (item.state == chrome.developerPrivate.ExtensionState.BLACKLISTED) { + return false; } - /** - * Returns true if the user can change whether or not the extension is - * enabled. - * @param {!chrome.developerPrivate.ExtensionInfo} item - * @return {boolean} - */ - function userCanChangeEnablement(item) { - // User doesn't have permission. - if (!item.userMayModify) { - return false; - } - // Item is forcefully disabled. - if (item.disableReasons.corruptInstall || - item.disableReasons.suspiciousInstall || - item.disableReasons.updateRequired) { - return false; - } - // An item with dependent extensions can't be disabled (it would bork the - // dependents). - if (item.dependentExtensions.length > 0) { - return false; - } - // Blacklisted can't be enabled, either. - if (item.state == chrome.developerPrivate.ExtensionState.BLACKLISTED) { - return false; - } + return true; +} - return true; +/** + * @param {!chrome.developerPrivate.ExtensionInfo} item + * @return {SourceType} + */ +export function getItemSource(item) { + if (item.controlledInfo && + item.controlledInfo.type == + chrome.developerPrivate.ControllerType.POLICY) { + return SourceType.POLICY; } - /** - * @param {!chrome.developerPrivate.ExtensionInfo} item - * @return {SourceType} - */ - function getItemSource(item) { - if (item.controlledInfo && - item.controlledInfo.type == - chrome.developerPrivate.ControllerType.POLICY) { - return SourceType.POLICY; - } + switch (item.location) { + case chrome.developerPrivate.Location.THIRD_PARTY: + return SourceType.SIDELOADED; + case chrome.developerPrivate.Location.UNPACKED: + return SourceType.UNPACKED; + case chrome.developerPrivate.Location.UNKNOWN: + return SourceType.UNKNOWN; + case chrome.developerPrivate.Location.FROM_STORE: + return SourceType.WEBSTORE; + } - switch (item.location) { - case chrome.developerPrivate.Location.THIRD_PARTY: - return SourceType.SIDELOADED; - case chrome.developerPrivate.Location.UNPACKED: - return SourceType.UNPACKED; - case chrome.developerPrivate.Location.UNKNOWN: - return SourceType.UNKNOWN; - case chrome.developerPrivate.Location.FROM_STORE: - return SourceType.WEBSTORE; - } + assertNotReached(item.location); +} - assertNotReached(item.location); +/** + * @param {SourceType} source + * @return {string} + */ +export function getItemSourceString(source) { + switch (source) { + case SourceType.POLICY: + return loadTimeData.getString('itemSourcePolicy'); + case SourceType.SIDELOADED: + return loadTimeData.getString('itemSourceSideloaded'); + case SourceType.UNPACKED: + return loadTimeData.getString('itemSourceUnpacked'); + case SourceType.WEBSTORE: + return loadTimeData.getString('itemSourceWebstore'); + case SourceType.UNKNOWN: + // Nothing to return. Calling code should use + // chrome.developerPrivate.ExtensionInfo's |locationText| instead. + return ''; } + assertNotReached(); +} - /** - * @param {SourceType} source - * @return {string} - */ - function getItemSourceString(source) { - switch (source) { - case SourceType.POLICY: - return loadTimeData.getString('itemSourcePolicy'); - case SourceType.SIDELOADED: - return loadTimeData.getString('itemSourceSideloaded'); - case SourceType.UNPACKED: - return loadTimeData.getString('itemSourceUnpacked'); - case SourceType.WEBSTORE: - return loadTimeData.getString('itemSourceWebstore'); - case SourceType.UNKNOWN: - // Nothing to return. Calling code should use - // chrome.developerPrivate.ExtensionInfo's |locationText| instead. - return ''; - } - assertNotReached(); +/** + * Computes the human-facing label for the given inspectable view. + * @param {!chrome.developerPrivate.ExtensionView} view + * @return {string} + */ +export function computeInspectableViewLabel(view) { + // Trim the "chrome-extension://<id>/". + const url = new URL(view.url); + let label = view.url; + if (url.protocol == 'chrome-extension:') { + label = url.pathname.substring(1); } - - /** - * Computes the human-facing label for the given inspectable view. - * @param {!chrome.developerPrivate.ExtensionView} view - * @return {string} - */ - function computeInspectableViewLabel(view) { - // Trim the "chrome-extension://<id>/". - const url = new URL(view.url); - let label = view.url; - if (url.protocol == 'chrome-extension:') { - label = url.pathname.substring(1); - } - if (label == '_generated_background_page.html') { - label = loadTimeData.getString('viewBackgroundPage'); - } - // Add any qualifiers. - if (view.incognito) { - label += ' ' + loadTimeData.getString('viewIncognito'); - } - if (view.renderProcessId == -1) { - label += ' ' + loadTimeData.getString('viewInactive'); - } - if (view.isIframe) { - label += ' ' + loadTimeData.getString('viewIframe'); - } - - return label; + if (label == '_generated_background_page.html') { + label = loadTimeData.getString('viewBackgroundPage'); + } + // Add any qualifiers. + if (view.incognito) { + label += ' ' + loadTimeData.getString('viewIncognito'); + } + if (view.renderProcessId == -1) { + label += ' ' + loadTimeData.getString('viewInactive'); + } + if (view.isIframe) { + label += ' ' + loadTimeData.getString('viewIframe'); } - return { - isControlled: isControlled, - isEnabled: isEnabled, - userCanChangeEnablement: userCanChangeEnablement, - getItemSource: getItemSource, - getItemSourceString: getItemSourceString, - computeInspectableViewLabel: computeInspectableViewLabel - }; -}); + return label; +} diff --git a/chromium/chrome/browser/resources/extensions/keyboard_shortcut_delegate.html b/chromium/chrome/browser/resources/extensions/keyboard_shortcut_delegate.html deleted file mode 100644 index bb851cf9c59..00000000000 --- a/chromium/chrome/browser/resources/extensions/keyboard_shortcut_delegate.html +++ /dev/null @@ -1 +0,0 @@ -<script src="keyboard_shortcut_delegate.js"></script> diff --git a/chromium/chrome/browser/resources/extensions/keyboard_shortcut_delegate.js b/chromium/chrome/browser/resources/extensions/keyboard_shortcut_delegate.js index d83e47926d3..4281304ea03 100644 --- a/chromium/chrome/browser/resources/extensions/keyboard_shortcut_delegate.js +++ b/chromium/chrome/browser/resources/extensions/keyboard_shortcut_delegate.js @@ -1,41 +1,35 @@ -// Copyright 2016 The Chromium Authors. All rights reserved. +// Copyright 2019 The Chromium 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('extensions', function() { - 'use strict'; +'use strict'; - /** @interface */ - class KeyboardShortcutDelegate { - /** - * Called when shortcut capturing changes in order to suspend or re-enable - * global shortcut handling. This is important so that the shortcuts aren't - * processed normally as the user types them. - * TODO(devlin): From very brief experimentation, it looks like preventing - * the default handling on the event also does this. Investigate more in the - * future. - * @param {boolean} isCapturing - */ - setShortcutHandlingSuspended(isCapturing) {} +/** @interface */ +export class KeyboardShortcutDelegate { + /** + * Called when shortcut capturing changes in order to suspend or re-enable + * global shortcut handling. This is important so that the shortcuts aren't + * processed normally as the user types them. + * TODO(devlin): From very brief experimentation, it looks like preventing + * the default handling on the event also does this. Investigate more in the + * future. + * @param {boolean} isCapturing + */ + setShortcutHandlingSuspended(isCapturing) {} - /** - * Updates an extension command's keybinding. - * @param {string} extensionId - * @param {string} commandName - * @param {string} keybinding - */ - updateExtensionCommandKeybinding(extensionId, commandName, keybinding) {} + /** + * Updates an extension command's keybinding. + * @param {string} extensionId + * @param {string} commandName + * @param {string} keybinding + */ + updateExtensionCommandKeybinding(extensionId, commandName, keybinding) {} - /** - * Updates an extension command's scope. - * @param {string} extensionId - * @param {string} commandName - * @param {chrome.developerPrivate.CommandScope} scope - */ - updateExtensionCommandScope(extensionId, commandName, scope) {} - } - - return { - KeyboardShortcutDelegate: KeyboardShortcutDelegate, - }; -}); + /** + * Updates an extension command's scope. + * @param {string} extensionId + * @param {string} commandName + * @param {chrome.developerPrivate.CommandScope} scope + */ + updateExtensionCommandScope(extensionId, commandName, scope) {} +} diff --git a/chromium/chrome/browser/resources/extensions/keyboard_shortcuts.html b/chromium/chrome/browser/resources/extensions/keyboard_shortcuts.html index cc985093d80..9c8a8638fb3 100644 --- a/chromium/chrome/browser/resources/extensions/keyboard_shortcuts.html +++ b/chromium/chrome/browser/resources/extensions/keyboard_shortcuts.html @@ -1,123 +1,106 @@ -<link rel="import" href="chrome://resources/html/polymer.html"> +<style include="md-select cr-shared-style"> + :host { + height: 100%; + } -<link rel="import" href="chrome://resources/cr_elements/cr_container_shadow_behavior.html"> -<link rel="import" href="chrome://resources/cr_elements/shared_style_css.html"> -<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html"> -<link rel="import" href="chrome://resources/html/cr.html"> -<link rel="import" href="chrome://resources/cr_elements/md_select_css.html"> -<link rel="import" href="chrome://resources/polymer/v1_0/paper-styles/color.html"> -<link rel="import" href="item_behavior.html"> -<link rel="import" href="keyboard_shortcut_delegate.html"> -<link rel="import" href="shortcut_input.html"> + .shortcut-card { + @apply --cr-card-elevation; + background-color: var(--cr-card-background-color); + border-radius: var(--cr-card-border-radius); + color: var(--cr-primary-text-color); + margin: 0 auto 16px auto; + padding-bottom: 8px; + width: var(--cr-toolbar-field-width); + } -<dom-module id="extensions-keyboard-shortcuts"> - <template> - <style include="md-select cr-shared-style"> - :host { - height: 100%; - } + .shortcut-card:last-of-type { + margin-bottom: 64px; + } - .shortcut-card { - @apply --cr-card-elevation; - background-color: var(--cr-card-background-color); - border-radius: var(--cr-card-border-radius); - color: var(--cr-primary-text-color); - margin: 0 auto 16px auto; - padding-bottom: 8px; - width: var(--cr-toolbar-field-width); - } + #container { + box-sizing: border-box; + height: 100%; + overflow: overlay; + padding-top: 24px; + } - .shortcut-card:last-of-type { - margin-bottom: 64px; - } + .command-entry { + align-items: start; + display: flex; + /* Makes top/bottom spacing of each row more even, while leaving + space for cr-input error message in between rows. */ + margin-bottom: -8px; + padding-top: 16px; + } - #container { - box-sizing: border-box; - height: 100%; - overflow: overlay; - padding-top: 24px; - } + .command-name { + /* Align with cr-input by matching the field's top padding. */ + flex: 1; + margin-top: 6px; + } - .command-entry { - align-items: start; - display: flex; - /* Makes top/bottom spacing of each row more even, while leaving - space for cr-input error message in between rows. */ - margin-bottom: -8px; - padding-top: 16px; - } + .command-entry .md-select { + /* TODO(johntlee): line-height needs adjustment to fix large fonts. */ + line-height: 22px; + margin-inline-start: var(--cr-section-padding); + } - .command-name { - /* Align with cr-input by matching the field's top padding. */ - flex: 1; - margin-top: 6px; - } + .card-title { + align-items: center; + border-bottom: var(--cr-separator-line); + display: flex; + margin-bottom: 9px; + padding: 16px var(--cr-section-padding); + } - .command-entry .md-select { - /* TODO(johntlee): line-height needs adjustment to fix large fonts. */ - line-height: 22px; - margin-inline-start: var(--cr-section-padding); - } + .icon { + height: 20px; + margin-inline-end: 20px; + width: 20px; + } - .card-title { - align-items: center; - border-bottom: var(--cr-separator-line); - display: flex; - margin-bottom: 9px; - padding: 16px var(--cr-section-padding); - } - - .icon { - height: 20px; - margin-inline-end: 20px; - width: 20px; - } - - .card-controls { - /* We line up the controls with the name, which is after the - 20px left padding + 20px icon + 20px margin on the icon. */ - margin-inline-end: 20px; - margin-inline-start: 60px; - } - </style> - <div id="container"> - <template is="dom-repeat" items="[[calculateShownItems_(items.*)]]"> - <div class="shortcut-card"> - <div class="card-title cr-title-text"> - <img class="icon" src="[[item.iconUrl]]" - alt$="[[appOrExtension( - item.type, - '$i18nPolymer{appIcon}', - '$i18nPolymer{extensionIcon}')]]"> - <span>[[item.name]]</span> - </div> - <div class="card-controls"> - <template is="dom-repeat" items="[[item.commands]]" as="command"> - <div class="command-entry" command="[[command]]"> - <span class="command-name">[[command.description]]</span> - <extensions-shortcut-input delegate="[[delegate]]" - item="[[item.id]]" shortcut="[[command.keybinding]]" - command-name="[[command.name]]"> - </extensions-shortcut-input> - <!-- Binding "value" to triggerScopeChange_ to trigger update - only after CommandScope_ becomes available. --> - <select class="md-select" on-change="onScopeChanged_" - disabled$="[[computeScopeDisabled_(command)]]" - value="[[ - triggerScopeChange_(command.scope, CommandScope_)]]"> - <option value$="[[CommandScope_.CHROME]]"> - $i18n{shortcutScopeInChrome} - </option> - <option value$="[[CommandScope_.GLOBAL]]"> - $i18n{shortcutScopeGlobal} - </option> - </select> - </div> - </template> + .card-controls { + /* We line up the controls with the name, which is after the + 20px left padding + 20px icon + 20px margin on the icon. */ + margin-inline-end: 20px; + margin-inline-start: 60px; + } +</style> +<div id="container"> + <template is="dom-repeat" items="[[calculateShownItems_(items.*)]]"> + <div class="shortcut-card"> + <div class="card-title cr-title-text"> + <img class="icon" src="[[item.iconUrl]]" + alt$="[[appOrExtension( + item.type, + '$i18nPolymer{appIcon}', + '$i18nPolymer{extensionIcon}')]]"> + <span role="heading" aria-level="2">[[item.name]]</span> + </div> + <div class="card-controls"> + <template is="dom-repeat" items="[[item.commands]]" as="command"> + <div class="command-entry" command="[[command]]"> + <span class="command-name">[[command.description]]</span> + <extensions-shortcut-input delegate="[[delegate]]" + item="[[item.id]]" shortcut="[[command.keybinding]]" + command-name="[[command.name]]"> + </extensions-shortcut-input> + <!-- Binding "value" to triggerScopeChange_ to trigger update + only after CommandScope_ becomes available. --> + <select class="md-select" on-change="onScopeChanged_" + disabled$="[[computeScopeDisabled_(command)]]" + value="[[ + triggerScopeChange_(command.scope, CommandScope_)]]"> + <option value$="[[CommandScope_.CHROME]]"> + $i18n{shortcutScopeInChrome} + </option> + <option value$="[[CommandScope_.GLOBAL]]"> + $i18n{shortcutScopeGlobal} + </option> + </select> </div> - </div> - </template> + </template> + </div> </div> </template> - <script src="keyboard_shortcuts.js"></script> -</dom-module> +</div> diff --git a/chromium/chrome/browser/resources/extensions/keyboard_shortcuts.js b/chromium/chrome/browser/resources/extensions/keyboard_shortcuts.js index f0716526957..92d8663ffee 100644 --- a/chromium/chrome/browser/resources/extensions/keyboard_shortcuts.js +++ b/chromium/chrome/browser/resources/extensions/keyboard_shortcuts.js @@ -2,101 +2,107 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -cr.define('extensions', function() { - 'use strict'; - - // The UI to display and manage keyboard shortcuts set for extension commands. - const KeyboardShortcuts = Polymer({ - is: 'extensions-keyboard-shortcuts', - - behaviors: [CrContainerShadowBehavior, extensions.ItemBehavior], - - properties: { - /** @type {!extensions.KeyboardShortcutDelegate} */ - delegate: Object, - - /** @type {Array<!chrome.developerPrivate.ExtensionInfo>} */ - items: Array, - - /** - * Proxying the enum to be used easily by the html template. - * @private - */ - CommandScope_: { - type: Object, - value: chrome.developerPrivate.CommandScope, - }, - }, +import 'chrome://resources/cr_elements/shared_style_css.m.js'; +import 'chrome://resources/cr_elements/shared_vars_css.m.js'; +import 'chrome://resources/cr_elements/md_select_css.m.js'; +import 'chrome://resources/polymer/v3_0/paper-styles/color.js'; +import './shortcut_input.js'; - listeners: { - 'view-enter-start': 'onViewEnter_', - }, +import {CrContainerShadowBehavior} from 'chrome://resources/cr_elements/cr_container_shadow_behavior.m.js'; +import {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; - /** @private */ - onViewEnter_: function() { - chrome.metricsPrivate.recordUserAction('Options_ExtensionCommands'); - }, +import {ItemBehavior} from './item_behavior.js'; +import {KeyboardShortcutDelegate} from './keyboard_shortcut_delegate.js'; - /** - * @return {!Array<!chrome.developerPrivate.ExtensionInfo>} - * @private - */ - calculateShownItems_: function() { - return this.items.filter(function(item) { - return item.commands.length > 0; - }); - }, +// The UI to display and manage keyboard shortcuts set for extension commands. +Polymer({ + is: 'extensions-keyboard-shortcuts', - /** - * A polymer bug doesn't allow for databinding of a string property as a - * boolean, but it is correctly interpreted from a function. - * Bug: https://github.com/Polymer/polymer/issues/3669 - * @param {string} keybinding - * @return {boolean} - * @private - */ - hasKeybinding_: function(keybinding) { - return !!keybinding; - }, + _template: html`{__html_template__}`, - /** - * Determines whether to disable the dropdown menu for the command's scope. - * @param {!chrome.developerPrivate.Command} command - * @return {boolean} - * @private - */ - computeScopeDisabled_: function(command) { - return command.isExtensionAction || !command.isActive; - }, + behaviors: [CrContainerShadowBehavior, ItemBehavior], - /** - * This function exists to force trigger an update when CommandScope_ - * becomes available. - * @param {string} scope - * @return {string} - */ - triggerScopeChange_: function(scope) { - return scope; - }, + properties: { + /** @type {!KeyboardShortcutDelegate} */ + delegate: Object, - /** @private */ - onCloseButtonClick_: function() { - this.fire('close'); - }, + /** @type {Array<!chrome.developerPrivate.ExtensionInfo>} */ + items: Array, /** - * @param {!{target: HTMLSelectElement, model: Object}} event + * Proxying the enum to be used easily by the html template. * @private */ - onScopeChanged_: function(event) { - this.delegate.updateExtensionCommandScope( - event.model.get('item.id'), event.model.get('command.name'), - /** @type {chrome.developerPrivate.CommandScope} */ - (event.target.value)); + CommandScope_: { + type: Object, + value: chrome.developerPrivate.CommandScope, }, - }); + }, + + listeners: { + 'view-enter-start': 'onViewEnter_', + }, + + /** @private */ + onViewEnter_: function() { + chrome.metricsPrivate.recordUserAction('Options_ExtensionCommands'); + }, + + /** + * @return {!Array<!chrome.developerPrivate.ExtensionInfo>} + * @private + */ + calculateShownItems_: function() { + return this.items.filter(function(item) { + return item.commands.length > 0; + }); + }, + + /** + * A polymer bug doesn't allow for databinding of a string property as a + * boolean, but it is correctly interpreted from a function. + * Bug: https://github.com/Polymer/polymer/issues/3669 + * @param {string} keybinding + * @return {boolean} + * @private + */ + hasKeybinding_: function(keybinding) { + return !!keybinding; + }, + + /** + * Determines whether to disable the dropdown menu for the command's scope. + * @param {!chrome.developerPrivate.Command} command + * @return {boolean} + * @private + */ + computeScopeDisabled_: function(command) { + return command.isExtensionAction || !command.isActive; + }, + + /** + * This function exists to force trigger an update when CommandScope_ + * becomes available. + * @param {string} scope + * @return {string} + */ + triggerScopeChange_: function(scope) { + return scope; + }, + + /** @private */ + onCloseButtonClick_: function() { + this.fire('close'); + }, - return { - KeyboardShortcuts: KeyboardShortcuts, - }; + /** + * @param {!{target: HTMLSelectElement, model: Object}} event + * @private + */ + onScopeChanged_: function(event) { + this.delegate.updateExtensionCommandScope( + event.model.get('item.id'), event.model.get('command.name'), + /** @type {chrome.developerPrivate.CommandScope} */ + (event.target.value)); + }, }); diff --git a/chromium/chrome/browser/resources/extensions/kiosk_browser_proxy.html b/chromium/chrome/browser/resources/extensions/kiosk_browser_proxy.html deleted file mode 100644 index 58f9bb8fe27..00000000000 --- a/chromium/chrome/browser/resources/extensions/kiosk_browser_proxy.html +++ /dev/null @@ -1,2 +0,0 @@ -<link rel="import" href="chrome://resources/html/cr.html"> -<script src="kiosk_browser_proxy.js"></script> diff --git a/chromium/chrome/browser/resources/extensions/kiosk_browser_proxy.js b/chromium/chrome/browser/resources/extensions/kiosk_browser_proxy.js index 1d0539898b8..2a00b9e63e9 100644 --- a/chromium/chrome/browser/resources/extensions/kiosk_browser_proxy.js +++ b/chromium/chrome/browser/resources/extensions/kiosk_browser_proxy.js @@ -7,6 +7,8 @@ * the browser. */ +import {addSingletonGetter, sendWithPromise} from 'chrome://resources/js/cr.m.js'; + /** * @typedef {{ * kioskEnabled: boolean, @@ -24,7 +26,7 @@ let KioskSettings; * isLoading: boolean * }} */ -let KioskApp; +export let KioskApp; /** * @typedef {{ @@ -33,75 +35,68 @@ let KioskApp; * hasAutoLaunchApp: boolean * }} */ -let KioskAppSettings; +export let KioskAppSettings; + +/** @interface */ +export class KioskBrowserProxy { + /** @param {string} appId */ + addKioskApp(appId) {} + + /** @param {string} appId */ + disableKioskAutoLaunch(appId) {} -cr.define('extensions', function() { - /** @interface */ - class KioskBrowserProxy { - /** @param {string} appId */ - addKioskApp(appId) {} + /** @param {string} appId */ + enableKioskAutoLaunch(appId) {} - /** @param {string} appId */ - disableKioskAutoLaunch(appId) {} + /** @return {!Promise<!KioskAppSettings>} */ + getKioskAppSettings() {} - /** @param {string} appId */ - enableKioskAutoLaunch(appId) {} + /** @return {!Promise<!KioskSettings>} */ + initializeKioskAppSettings() {} - /** @return {!Promise<!KioskAppSettings>} */ - getKioskAppSettings() {} + /** @param {string} appId */ + removeKioskApp(appId) {} - /** @return {!Promise<!KioskSettings>} */ - initializeKioskAppSettings() {} + /** @param {boolean} disableBailout */ + setDisableBailoutShortcut(disableBailout) {} +} - /** @param {string} appId */ - removeKioskApp(appId) {} +/** @implements {KioskBrowserProxy} */ +export class KioskBrowserProxyImpl { + /** @override */ + initializeKioskAppSettings() { + return sendWithPromise('initializeKioskAppSettings'); + } + + /** @override */ + getKioskAppSettings() { + return sendWithPromise('getKioskAppSettings'); + } - /** @param {boolean} disableBailout */ - setDisableBailoutShortcut(disableBailout) {} + /** @override */ + addKioskApp(appId) { + chrome.send('addKioskApp', [appId]); } - /** @implements {extensions.KioskBrowserProxy} */ - class KioskBrowserProxyImpl { - /** @override */ - initializeKioskAppSettings() { - return cr.sendWithPromise('initializeKioskAppSettings'); - } - - /** @override */ - getKioskAppSettings() { - return cr.sendWithPromise('getKioskAppSettings'); - } - - /** @override */ - addKioskApp(appId) { - chrome.send('addKioskApp', [appId]); - } - - /** @override */ - disableKioskAutoLaunch(appId) { - chrome.send('disableKioskAutoLaunch', [appId]); - } - - /** @override */ - enableKioskAutoLaunch(appId) { - chrome.send('enableKioskAutoLaunch', [appId]); - } - - /** @override */ - removeKioskApp(appId) { - chrome.send('removeKioskApp', [appId]); - } - - /** @override */ - setDisableBailoutShortcut(disableBailout) { - chrome.send('setDisableBailoutShortcut', [disableBailout]); - } + /** @override */ + disableKioskAutoLaunch(appId) { + chrome.send('disableKioskAutoLaunch', [appId]); } - cr.addSingletonGetter(KioskBrowserProxyImpl); + /** @override */ + enableKioskAutoLaunch(appId) { + chrome.send('enableKioskAutoLaunch', [appId]); + } + + /** @override */ + removeKioskApp(appId) { + chrome.send('removeKioskApp', [appId]); + } + + /** @override */ + setDisableBailoutShortcut(disableBailout) { + chrome.send('setDisableBailoutShortcut', [disableBailout]); + } +} - return { - KioskBrowserProxy: KioskBrowserProxy, - KioskBrowserProxyImpl: KioskBrowserProxyImpl, - }; -}); +addSingletonGetter(KioskBrowserProxyImpl); diff --git a/chromium/chrome/browser/resources/extensions/kiosk_dialog.html b/chromium/chrome/browser/resources/extensions/kiosk_dialog.html index 7210150053e..df122a2eca4 100644 --- a/chromium/chrome/browser/resources/extensions/kiosk_dialog.html +++ b/chromium/chrome/browser/resources/extensions/kiosk_dialog.html @@ -1,141 +1,120 @@ -<link rel="import" href="chrome://resources/html/polymer.html"> +<style include="cr-shared-style cr-icons"> + #add-kiosk-app { + margin-bottom: 10px; + margin-top: 20px; + } -<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html"> -<link rel="import" href="chrome://resources/cr_elements/cr_checkbox/cr_checkbox.html"> -<link rel="import" href="chrome://resources/cr_elements/cr_dialog/cr_dialog.html"> -<link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html"> -<link rel="import" href="chrome://resources/cr_elements/cr_icons_css.html"> -<link rel="import" href="chrome://resources/cr_elements/cr_input/cr_input.html"> -<link rel="import" href="chrome://resources/cr_elements/shared_style_css.html"> -<link rel="import" href="chrome://resources/html/assert.html"> -<link rel="import" href="chrome://resources/html/cr.html"> -<link rel="import" href="chrome://resources/html/util.html"> -<link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html"> -<link rel="import" href="item_behavior.html"> -<link rel="import" href="kiosk_browser_proxy.html"> + #add-kiosk-app cr-input { + width: 350px; + } -<dom-module id="extensions-kiosk-dialog"> - <template> - <style include="cr-shared-style cr-icons"> - #add-kiosk-app { - margin-bottom: 10px; - margin-top: 20px; - } + #add-kiosk-app cr-button { + margin-inline-start: 10px; + } - #add-kiosk-app cr-input { - width: 350px; - } + #kiosk-apps-list { + border: 1px solid var(--paper-grey-300); + padding: 10px; + } - #add-kiosk-app cr-button { - margin-inline-start: 10px; - } + .list-item { + align-items: center; + border-bottom: 1px solid var(--paper-grey-300); + display: flex; + justify-content: space-between; + padding: 5px; + } - #kiosk-apps-list { - border: 1px solid var(--paper-grey-300); - padding: 10px; - } + .list-item:last-of-type { + border-bottom: none; + } - .list-item { - align-items: center; - border-bottom: 1px solid var(--paper-grey-300); - display: flex; - justify-content: space-between; - padding: 5px; - } + .list-item:hover { + background-color: var(--paper-grey-300); + } - .list-item:last-of-type { - border-bottom: none; - } + .item-icon { + vertical-align: middle; + width: 25px; + } - .list-item:hover { - background-color: var(--paper-grey-300); - } + .item-controls { + visibility: hidden; + } - .item-icon { - vertical-align: middle; - width: 25px; - } + .list-item:hover .item-controls { + visibility: visible; + } - .item-controls { - visibility: hidden; - } - - .list-item:hover .item-controls { - visibility: visible; - } - - cr-icon-button { - margin: 0; - } - </style> - <cr-dialog id="dialog" close-text="$i18n{close}" - ignore-enter-key> - <div slot="title">$i18n{manageKioskApp}</div> - <div slot="body"> - <div id="kiosk-apps-list"> - <template is="dom-repeat" items="[[apps_]]"> - <div class="list-item"> - <div class="item-name"> - <img class="item-icon" src="[[item.iconURL]]" - alt$="[[appOrExtension( - data.type, - '$i18nPolymer{appIcon}', - '$i18nPolymer{extensionIcon}')]]"> - [[item.name]] - <span hidden="[[!item.autoLaunch]]"> - $i18n{kioskAutoLaunch} - </span> - </div> - <div class="item-controls"> - <cr-button hidden="[[!canEditAutoLaunch_]]" - on-click="onAutoLaunchButtonTap_"> - [[getAutoLaunchButtonLabel_(item.autoLaunch, - '$i18nPolymer{kioskDisableAutoLaunch}', - '$i18nPolymer{kioskEnableAutoLaunch}')]] - </cr-button> - <cr-icon-button class="icon-delete-gray" - on-click="onDeleteAppTap_"></cr-icon-button> - </div> - </div> - </template> - </div> - <div id="add-kiosk-app"> - <cr-input id="add-input" label="$i18n{kioskAddApp}" - placeholder="$i18n{kioskAddAppHint}" value="{{addAppInput_}}" - invalid="[[errorAppId_]]" on-keydown="clearInputInvalid_" - error-message="[[getErrorMessage_( - '$i18nPolymer{kioskInvalidApp}', errorAppId_)]]"> - <cr-button id="add-button" on-click="onAddAppTap_" - disabled="[[!addAppInput_]]" slot="suffix"> - $i18n{add} + cr-icon-button { + margin: 0; + } +</style> +<cr-dialog id="dialog" close-text="$i18n{close}" + ignore-enter-key> + <div slot="title">$i18n{manageKioskApp}</div> + <div slot="body"> + <div id="kiosk-apps-list"> + <template is="dom-repeat" items="[[apps_]]"> + <div class="list-item"> + <div class="item-name"> + <img class="item-icon" src="[[item.iconURL]]" + alt$="[[appOrExtension( + data.type, + '$i18nPolymer{appIcon}', + '$i18nPolymer{extensionIcon}')]]"> + [[item.name]] + <span hidden="[[!item.autoLaunch]]"> + $i18n{kioskAutoLaunch} + </span> + </div> + <div class="item-controls"> + <cr-button hidden="[[!canEditAutoLaunch_]]" + on-click="onAutoLaunchButtonTap_"> + [[getAutoLaunchButtonLabel_(item.autoLaunch, + '$i18nPolymer{kioskDisableAutoLaunch}', + '$i18nPolymer{kioskEnableAutoLaunch}')]] </cr-button> - </cr-input> + <cr-icon-button class="icon-delete-gray" + on-click="onDeleteAppTap_"></cr-icon-button> + </div> </div> - <cr-checkbox disabled="[[!canEditBailout_]]" id="bailout" - on-change="onBailoutChanged_" checked="[[bailoutDisabled_]]" - hidden="[[!canEditAutoLaunch_]]"> - $i18n{kioskDisableBailout} - </cr-checkbox> - </div> - <div slot="button-container"> - <cr-button class="action-button" on-click="onDoneTap_"> - $i18n{done} - </cr-button> - </div> - </cr-dialog> - <cr-dialog id="confirm-dialog" close-text="$i18n{close}" - ignore-enter-key on-close="stopPropagation_"> - <div slot="title">$i18n{kioskDisableBailoutWarningTitle}</div> - <div slot="body">$i18n{kioskDisableBailoutWarningBody}</div> - <div slot="button-container"> - <cr-button class="cancel-button" on-click="onBailoutDialogCancelTap_"> - $i18n{cancel} - </cr-button> - <cr-button class="action-button" on-click="onBailoutDialogConfirmTap_"> - $i18n{confirm} + </template> + </div> + <div id="add-kiosk-app"> + <cr-input id="add-input" label="$i18n{kioskAddApp}" + placeholder="$i18n{kioskAddAppHint}" value="{{addAppInput_}}" + invalid="[[errorAppId_]]" on-keydown="clearInputInvalid_" + error-message="[[getErrorMessage_( + '$i18nPolymer{kioskInvalidApp}', errorAppId_)]]"> + <cr-button id="add-button" on-click="onAddAppTap_" + disabled="[[!addAppInput_]]" slot="suffix"> + $i18n{add} </cr-button> - </div> - </cr-dialog> - </template> - <script src="kiosk_dialog.js"></script> -</dom-module> + </cr-input> + </div> + <cr-checkbox disabled="[[!canEditBailout_]]" id="bailout" + on-change="onBailoutChanged_" checked="[[bailoutDisabled_]]" + hidden="[[!canEditAutoLaunch_]]"> + $i18n{kioskDisableBailout} + </cr-checkbox> + </div> + <div slot="button-container"> + <cr-button class="action-button" on-click="onDoneTap_"> + $i18n{done} + </cr-button> + </div> +</cr-dialog> +<cr-dialog id="confirm-dialog" close-text="$i18n{close}" + ignore-enter-key on-close="stopPropagation_"> + <div slot="title">$i18n{kioskDisableBailoutWarningTitle}</div> + <div slot="body">$i18n{kioskDisableBailoutWarningBody}</div> + <div slot="button-container"> + <cr-button class="cancel-button" on-click="onBailoutDialogCancelTap_"> + $i18n{cancel} + </cr-button> + <cr-button class="action-button" on-click="onBailoutDialogConfirmTap_"> + $i18n{confirm} + </cr-button> + </div> +</cr-dialog> diff --git a/chromium/chrome/browser/resources/extensions/kiosk_dialog.js b/chromium/chrome/browser/resources/extensions/kiosk_dialog.js index 49e5056c0c1..04bc4cb1e9b 100644 --- a/chromium/chrome/browser/resources/extensions/kiosk_dialog.js +++ b/chromium/chrome/browser/resources/extensions/kiosk_dialog.js @@ -2,183 +2,195 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -cr.define('extensions', function() { - 'use strict'; +import 'chrome://resources/cr_elements/cr_button/cr_button.m.js'; +import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.m.js'; +import 'chrome://resources/cr_elements/cr_dialog/cr_dialog.m.js'; +import 'chrome://resources/cr_elements/cr_icon_button/cr_icon_button.m.js'; +import 'chrome://resources/cr_elements/cr_icons_css.m.js'; +import 'chrome://resources/cr_elements/cr_input/cr_input.m.js'; +import 'chrome://resources/cr_elements/shared_style_css.m.js'; - const KioskDialog = Polymer({ - is: 'extensions-kiosk-dialog', - behaviors: [WebUIListenerBehavior, extensions.ItemBehavior], - properties: { - /** @private {?string} */ - addAppInput_: { - type: String, - value: null, - }, +import {assert} from 'chrome://resources/js/assert.m.js'; +import {WebUIListenerBehavior} from 'chrome://resources/js/web_ui_listener_behavior.m.js'; +import {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; - /** @private {!Array<!KioskApp>} */ - apps_: Array, +import {ItemBehavior} from './item_behavior.js'; +import {KioskApp, KioskAppSettings, KioskBrowserProxy, KioskBrowserProxyImpl} from './kiosk_browser_proxy.js'; - /** @private */ - bailoutDisabled_: Boolean, - /** @private */ - canEditAutoLaunch_: Boolean, +Polymer({ + is: 'extensions-kiosk-dialog', - /** @private */ - canEditBailout_: Boolean, + _template: html`{__html_template__}`, - /** @private {?string} */ - errorAppId_: String, - }, - - /** @private {?extensions.KioskBrowserProxy} */ - kioskBrowserProxy_: null, - - /** @override */ - ready: function() { - this.kioskBrowserProxy_ = extensions.KioskBrowserProxyImpl.getInstance(); - }, - - /** @override */ - attached: function() { - this.kioskBrowserProxy_.initializeKioskAppSettings() - .then(params => { - this.canEditAutoLaunch_ = params.autoLaunchEnabled; - return this.kioskBrowserProxy_.getKioskAppSettings(); - }) - .then(this.setSettings_.bind(this)); - - this.addWebUIListener( - 'kiosk-app-settings-changed', this.setSettings_.bind(this)); - this.addWebUIListener('kiosk-app-updated', this.updateApp_.bind(this)); - this.addWebUIListener('kiosk-app-error', this.showError_.bind(this)); - - this.$.dialog.showModal(); - }, - - /** - * @param {!KioskAppSettings} settings - * @private - */ - setSettings_: function(settings) { - this.apps_ = settings.apps; - this.bailoutDisabled_ = settings.disableBailout; - this.canEditBailout_ = settings.hasAutoLaunchApp; - }, + behaviors: [WebUIListenerBehavior, ItemBehavior], - /** - * @param {!KioskApp} app - * @private - */ - updateApp_: function(app) { - const index = this.apps_.findIndex(a => a.id == app.id); - assert(index < this.apps_.length); - this.set('apps_.' + index, app); + properties: { + /** @private {?string} */ + addAppInput_: { + type: String, + value: null, }, - /** - * @param {string} appId - * @private - */ - showError_: function(appId) { - this.errorAppId_ = appId; - }, - - /** - * @param {string} errorMessage - * @return {string} - * @private - */ - getErrorMessage_: function(errorMessage) { - return this.errorAppId_ + ' ' + errorMessage; - }, + /** @private {!Array<!KioskApp>} */ + apps_: Array, /** @private */ - onAddAppTap_: function() { - assert(this.addAppInput_); - this.kioskBrowserProxy_.addKioskApp(this.addAppInput_); - this.addAppInput_ = null; - }, + bailoutDisabled_: Boolean, /** @private */ - clearInputInvalid_: function() { - this.errorAppId_ = null; - }, - - /** - * @param {{model: {item: !KioskApp}}} event - * @private - */ - onAutoLaunchButtonTap_: function(event) { - const app = event.model.item; - if (app.autoLaunch) { // If the app is originally set to - // auto-launch. - this.kioskBrowserProxy_.disableKioskAutoLaunch(app.id); - } else { - this.kioskBrowserProxy_.enableKioskAutoLaunch(app.id); - } - }, - - /** - * @param {!Event} event - * @private - */ - onBailoutChanged_: function(event) { - event.preventDefault(); - if (this.$.bailout.checked) { - this.$['confirm-dialog'].showModal(); - } else { - this.kioskBrowserProxy_.setDisableBailoutShortcut(false); - this.$['confirm-dialog'].close(); - } - }, - - /** @private */ - onBailoutDialogCancelTap_: function() { - this.$.bailout.checked = false; - this.$['confirm-dialog'].cancel(); - }, + canEditAutoLaunch_: Boolean, /** @private */ - onBailoutDialogConfirmTap_: function() { - this.kioskBrowserProxy_.setDisableBailoutShortcut(true); + canEditBailout_: Boolean, + + /** @private {?string} */ + errorAppId_: String, + }, + + /** @private {?KioskBrowserProxy} */ + kioskBrowserProxy_: null, + + /** @override */ + ready: function() { + this.kioskBrowserProxy_ = KioskBrowserProxyImpl.getInstance(); + }, + + /** @override */ + attached: function() { + this.kioskBrowserProxy_.initializeKioskAppSettings() + .then(params => { + this.canEditAutoLaunch_ = params.autoLaunchEnabled; + return this.kioskBrowserProxy_.getKioskAppSettings(); + }) + .then(this.setSettings_.bind(this)); + + this.addWebUIListener( + 'kiosk-app-settings-changed', this.setSettings_.bind(this)); + this.addWebUIListener('kiosk-app-updated', this.updateApp_.bind(this)); + this.addWebUIListener('kiosk-app-error', this.showError_.bind(this)); + + this.$.dialog.showModal(); + }, + + /** + * @param {!KioskAppSettings} settings + * @private + */ + setSettings_: function(settings) { + this.apps_ = settings.apps; + this.bailoutDisabled_ = settings.disableBailout; + this.canEditBailout_ = settings.hasAutoLaunchApp; + }, + + /** + * @param {!KioskApp} app + * @private + */ + updateApp_: function(app) { + const index = this.apps_.findIndex(a => a.id == app.id); + assert(index < this.apps_.length); + this.set('apps_.' + index, app); + }, + + /** + * @param {string} appId + * @private + */ + showError_: function(appId) { + this.errorAppId_ = appId; + }, + + /** + * @param {string} errorMessage + * @return {string} + * @private + */ + getErrorMessage_: function(errorMessage) { + return this.errorAppId_ + ' ' + errorMessage; + }, + + /** @private */ + onAddAppTap_: function() { + assert(this.addAppInput_); + this.kioskBrowserProxy_.addKioskApp(this.addAppInput_); + this.addAppInput_ = null; + }, + + /** @private */ + clearInputInvalid_: function() { + this.errorAppId_ = null; + }, + + /** + * @param {{model: {item: !KioskApp}}} event + * @private + */ + onAutoLaunchButtonTap_: function(event) { + const app = event.model.item; + if (app.autoLaunch) { // If the app is originally set to + // auto-launch. + this.kioskBrowserProxy_.disableKioskAutoLaunch(app.id); + } else { + this.kioskBrowserProxy_.enableKioskAutoLaunch(app.id); + } + }, + + /** + * @param {!Event} event + * @private + */ + onBailoutChanged_: function(event) { + event.preventDefault(); + if (this.$.bailout.checked) { + this.$['confirm-dialog'].showModal(); + } else { + this.kioskBrowserProxy_.setDisableBailoutShortcut(false); this.$['confirm-dialog'].close(); - }, - - /** @private */ - onDoneTap_: function() { - this.$.dialog.close(); - }, - - /** - * @param {{model: {item: !KioskApp}}} event - * @private - */ - onDeleteAppTap_: function(event) { - this.kioskBrowserProxy_.removeKioskApp(event.model.item.id); - }, - - /** - * @param {boolean} autoLaunched - * @param {string} disableStr - * @param {string} enableStr - * @return {string} - * @private - */ - getAutoLaunchButtonLabel_: function(autoLaunched, disableStr, enableStr) { - return autoLaunched ? disableStr : enableStr; - }, - - /** - * @param {!Event} e - * @private - */ - stopPropagation_: function(e) { - e.stopPropagation(); - }, - }); - - return { - KioskDialog: KioskDialog, - }; + } + }, + + /** @private */ + onBailoutDialogCancelTap_: function() { + this.$.bailout.checked = false; + this.$['confirm-dialog'].cancel(); + }, + + /** @private */ + onBailoutDialogConfirmTap_: function() { + this.kioskBrowserProxy_.setDisableBailoutShortcut(true); + this.$['confirm-dialog'].close(); + }, + + /** @private */ + onDoneTap_: function() { + this.$.dialog.close(); + }, + + /** + * @param {{model: {item: !KioskApp}}} event + * @private + */ + onDeleteAppTap_: function(event) { + this.kioskBrowserProxy_.removeKioskApp(event.model.item.id); + }, + + /** + * @param {boolean} autoLaunched + * @param {string} disableStr + * @param {string} enableStr + * @return {string} + * @private + */ + getAutoLaunchButtonLabel_: function(autoLaunched, disableStr, enableStr) { + return autoLaunched ? disableStr : enableStr; + }, + + /** + * @param {!Event} e + * @private + */ + stopPropagation_: function(e) { + e.stopPropagation(); + }, }); diff --git a/chromium/chrome/browser/resources/extensions/load_error.html b/chromium/chrome/browser/resources/extensions/load_error.html index 682b4fbcdf9..804fc40a5b6 100644 --- a/chromium/chrome/browser/resources/extensions/load_error.html +++ b/chromium/chrome/browser/resources/extensions/load_error.html @@ -1,57 +1,42 @@ -<link rel="import" href="chrome://resources/html/polymer.html"> +<style include="cr-shared-style"> + .description-row { + display: flex; + } -<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html"> -<link rel="import" href="chrome://resources/cr_elements/cr_dialog/cr_dialog.html"> -<link rel="import" href="chrome://resources/cr_elements/shared_style_css.html"> -<link rel="import" href="chrome://resources/html/assert.html"> -<link rel="import" href="chrome://resources/html/cr.html"> -<link rel="import" href="chrome://resources/polymer/v1_0/paper-spinner/paper-spinner-lite.html"> -<link rel="import" href="code_section.html"> + .row-label { + display: block; + width: 104px; /* Magic number from the specs. */ + } -<dom-module id="extensions-load-error"> - <template> - <style include="cr-shared-style"> - .description-row { - display: flex; - } - - .row-label { - display: block; - width: 104px; /* Magic number from the specs. */ - } - - paper-spinner-lite { - margin-inline-end: 8px; - } - </style> - <cr-dialog id="dialog" close-text="$i18n{close}"> - <div slot="title">$i18n{loadErrorHeading}</div> - <div slot="body"> - <div id="info"> - <div id="file" class="description-row"> - <span class="row-label">$i18n{loadErrorFileLabel}</span> - <span class="row-value">[[loadError.path]]</span> - </div> - <div id="error" class="description-row"> - <span class="row-label">$i18n{loadErrorErrorLabel}</span> - <span class="row-value">[[loadError.error]]</span> - </div> - </div> - <extensions-code-section id="code" - could-not-display-code="$i18n{loadErrorCouldNotLoadManifest}"> - </extensions-code-section> + paper-spinner-lite { + margin-inline-end: 8px; + } +</style> +<cr-dialog id="dialog" close-text="$i18n{close}"> + <div slot="title">$i18n{loadErrorHeading}</div> + <div slot="body"> + <div id="info"> + <div id="file" class="description-row"> + <span class="row-label">$i18n{loadErrorFileLabel}</span> + <span class="row-value">[[loadError.path]]</span> </div> - <div slot="button-container"> - <paper-spinner-lite active="[[retrying_]]"></paper-spinner-lite> - <cr-button class="cancel-button" on-click="close"> - $i18n{cancel} - </cr-button> - <cr-button class="action-button" disabled="[[retrying_]]" - on-click="onRetryTap_"> - $i18n{loadErrorRetry} - </cr-button> + <div id="error" class="description-row"> + <span class="row-label">$i18n{loadErrorErrorLabel}</span> + <span class="row-value">[[loadError.error]]</span> </div> - </cr-dialog> - </template> - <script src="load_error.js"></script> -</dom-module> + </div> + <extensions-code-section id="code" + could-not-display-code="$i18n{loadErrorCouldNotLoadManifest}"> + </extensions-code-section> + </div> + <div slot="button-container"> + <paper-spinner-lite active="[[retrying_]]"></paper-spinner-lite> + <cr-button class="cancel-button" on-click="close"> + $i18n{cancel} + </cr-button> + <cr-button class="action-button" disabled="[[retrying_]]" + on-click="onRetryTap_"> + $i18n{loadErrorRetry} + </cr-button> + </div> +</cr-dialog> diff --git a/chromium/chrome/browser/resources/extensions/load_error.js b/chromium/chrome/browser/resources/extensions/load_error.js index be8d9022554..ff678ac6176 100644 --- a/chromium/chrome/browser/resources/extensions/load_error.js +++ b/chromium/chrome/browser/resources/extensions/load_error.js @@ -2,79 +2,85 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -cr.define('extensions', function() { - 'use strict'; +import 'chrome://resources/cr_elements/cr_button/cr_button.m.js'; +import 'chrome://resources/cr_elements/cr_dialog/cr_dialog.m.js'; +import 'chrome://resources/cr_elements/shared_style_css.m.js'; +import 'chrome://resources/polymer/v3_0/paper-spinner/paper-spinner-lite.js'; +import './code_section.js'; +import './strings.m.js'; - /** @interface */ - class LoadErrorDelegate { - /** - * Attempts to load the previously-attempted unpacked extension. - * @param {string} retryId - * @return {!Promise} - */ - retryLoadUnpacked(retryId) {} - } +import {assert} from 'chrome://resources/js/assert.m.js'; +import {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; - const LoadError = Polymer({ - is: 'extensions-load-error', - properties: { - /** @type {extensions.LoadErrorDelegate} */ - delegate: Object, +/** @interface */ +export class LoadErrorDelegate { + /** + * Attempts to load the previously-attempted unpacked extension. + * @param {string} retryId + * @return {!Promise} + */ + retryLoadUnpacked(retryId) {} +} - /** @type {chrome.developerPrivate.LoadError} */ - loadError: Object, +Polymer({ + is: 'extensions-load-error', - /** @private */ - retrying_: Boolean, - }, + _template: html`{__html_template__}`, - observers: [ - 'observeLoadErrorChanges_(loadError)', - ], + properties: { + /** @type {LoadErrorDelegate} */ + delegate: Object, - show: function() { - /** @type {!CrDialogElement} */ (this.$.dialog).showModal(); - }, - - close: function() { - /** @type {!CrDialogElement} */ (this.$.dialog).close(); - }, + /** @type {chrome.developerPrivate.LoadError} */ + loadError: Object, /** @private */ - onRetryTap_: function() { - this.retrying_ = true; - this.delegate.retryLoadUnpacked(this.loadError.retryGuid) - .then( - () => { - this.close(); - }, - loadError => { - this.loadError = - /** @type {chrome.developerPrivate.LoadError} */ ( - loadError); - this.retrying_ = false; - }); - }, + retrying_: Boolean, + }, - /** @private */ - observeLoadErrorChanges_: function() { - assert(this.loadError); - const source = this.loadError.source; - // CodeSection expects a RequestFileSourceResponse, rather than an - // ErrorFileSource. Massage into place. - // TODO(devlin): Make RequestFileSourceResponse use ErrorFileSource. - /** @type {!chrome.developerPrivate.RequestFileSourceResponse} */ - const codeSectionProperties = { - beforeHighlight: source ? source.beforeHighlight : '', - highlight: source ? source.highlight : '', - afterHighlight: source ? source.afterHighlight : '', - title: '', - message: this.loadError.error, - }; + observers: [ + 'observeLoadErrorChanges_(loadError)', + ], + + show: function() { + /** @type {!CrDialogElement} */ (this.$.dialog).showModal(); + }, + + close: function() { + /** @type {!CrDialogElement} */ (this.$.dialog).close(); + }, + + /** @private */ + onRetryTap_: function() { + this.retrying_ = true; + this.delegate.retryLoadUnpacked(this.loadError.retryGuid) + .then( + () => { + this.close(); + }, + loadError => { + this.loadError = + /** @type {chrome.developerPrivate.LoadError} */ (loadError); + this.retrying_ = false; + }); + }, - this.$.code.code = codeSectionProperties; - }, - }); + /** @private */ + observeLoadErrorChanges_: function() { + assert(this.loadError); + const source = this.loadError.source; + // CodeSection expects a RequestFileSourceResponse, rather than an + // ErrorFileSource. Massage into place. + // TODO(devlin): Make RequestFileSourceResponse use ErrorFileSource. + /** @type {!chrome.developerPrivate.RequestFileSourceResponse} */ + const codeSectionProperties = { + beforeHighlight: source ? source.beforeHighlight : '', + highlight: source ? source.highlight : '', + afterHighlight: source ? source.afterHighlight : '', + title: '', + message: this.loadError.error, + }; - return {LoadError: LoadError, LoadErrorDelegate: LoadErrorDelegate}; + this.$.code.code = codeSectionProperties; + }, }); diff --git a/chromium/chrome/browser/resources/extensions/manager.html b/chromium/chrome/browser/resources/extensions/manager.html index 1c35c285068..87457fba166 100644 --- a/chromium/chrome/browser/resources/extensions/manager.html +++ b/chromium/chrome/browser/resources/extensions/manager.html @@ -1,145 +1,109 @@ -<link rel="import" href="chrome://resources/html/polymer.html"> +<style include="cr-hidden-style"> + :host { + color: var(--cr-primary-text-color); + display: flex; + flex-direction: column; + height: 100%; + --cr-toolbar-field-width: 680px; + } -<link rel="import" href="chrome://resources/cr_elements/cr_drawer/cr_drawer.html"> -<link rel="import" href="chrome://resources/cr_elements/cr_lazy_render/cr_lazy_render.html"> -<link rel="import" href="chrome://resources/cr_elements/cr_toast/cr_toast_manager.html"> -<link rel="import" href="chrome://resources/cr_elements/cr_toolbar/cr_toolbar.html"> -<link rel="import" href="chrome://resources/cr_elements/cr_view_manager/cr_view_manager.html"> -<link rel="import" href="chrome://resources/cr_elements/hidden_style_css.html"> -<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html"> -<link rel="import" href="chrome://resources/html/assert.html"> -<link rel="import" href="chrome://resources/html/cr.html"> -<link rel="import" href="activity_log/activity_log.html"> -<link rel="import" href="detail_view.html"> -<link rel="import" href="drop_overlay.html"> -<link rel="import" href="error_page.html"> -<link rel="import" href="install_warnings_dialog.html"> -<link rel="import" href="item_list.html"> -<link rel="import" href="item_util.html"> -<link rel="import" href="keyboard_shortcuts.html"> -<link rel="import" href="load_error.html"> -<link rel="import" href="navigation_helper.html"> -<link rel="import" href="options_dialog.html"> -<link rel="import" href="service.html"> -<link rel="import" href="sidebar.html"> -<link rel="import" href="toolbar.html"> + extensions-sidebar { + flex-basis: 256px; + } -<if expr="chromeos"> -<link rel="import" href="kiosk_browser_proxy.html"> -<link rel="import" href="kiosk_dialog.html"> -</if> - -<dom-module id="extensions-manager"> - <template> - <style include="cr-hidden-style"> - :host { - color: var(--cr-primary-text-color); - display: flex; - flex-direction: column; - height: 100%; - --cr-toolbar-field-width: 680px; - } - - extensions-sidebar { - flex-basis: 256px; - } + #viewManager { + flex: 1; + position: relative; + } - #viewManager { - flex: 1; - position: relative; - } - - extensions-item { - display: inline-block; - } - </style> - <extensions-drop-overlay drag-enabled="[[inDevMode]]"> - </extensions-drop-overlay> - <extensions-toolbar in-dev-mode="[[inDevMode]]" - can-load-unpacked="[[canLoadUnpacked]]" - is-supervised="[[isSupervised_]]" - dev-mode-controlled-by-policy="[[devModeControlledByPolicy]]" - delegate="[[delegate]]" on-cr-toolbar-menu-tap="onMenuButtonTap_" - on-search-changed="onFilterChanged_" + extensions-item { + display: inline-block; + } +</style> +<extensions-drop-overlay drag-enabled="[[inDevMode]]"> +</extensions-drop-overlay> +<extensions-toolbar in-dev-mode="[[inDevMode]]" + can-load-unpacked="[[canLoadUnpacked]]" + is-supervised="[[isSupervised_]]" + dev-mode-controlled-by-policy="[[devModeControlledByPolicy]]" + delegate="[[delegate]]" on-cr-toolbar-menu-tap="onMenuButtonTap_" + on-search-changed="onFilterChanged_" <if expr="chromeos"> - on-kiosk-tap="onKioskTap_" - kiosk-enabled="[[kioskEnabled_]]" + on-kiosk-tap="onKioskTap_" + kiosk-enabled="[[kioskEnabled_]]" </if> - > - </extensions-toolbar> - <template is="dom-if" if="[[showDrawer_]]" restamp> - <cr-drawer id="drawer" heading="$i18n{toolbarTitle}" - align="$i18n{textdirection}" on-close="onDrawerClose_"> - <div class="drawer-content"> - <extensions-sidebar id="sidebar" is-supervised="[[isSupervised_]]" - on-close-drawer="onCloseDrawer_"> - </extensions-sidebar> - </div> - </cr-drawer> + > +</extensions-toolbar> +<template is="dom-if" if="[[showDrawer_]]" restamp> + <cr-drawer id="drawer" heading="$i18n{toolbarTitle}" + align="$i18n{textdirection}" on-close="onDrawerClose_"> + <div class="drawer-content"> + <extensions-sidebar id="sidebar" is-supervised="[[isSupervised_]]" + on-close-drawer="onCloseDrawer_"> + </extensions-sidebar> + </div> + </cr-drawer> +</template> +<cr-view-manager id="viewManager" role="main"> + <extensions-item-list id="items-list" delegate="[[delegate]]" + in-dev-mode="[[inDevMode]]" filter="[[filter]]" + hidden$="[[!didInitPage_]]" slot="view" apps="[[apps_]]" + extensions="[[extensions_]]" + on-show-install-warnings="onShowInstallWarnings_"> + </extensions-item-list> + <cr-lazy-render id="details-view"> + <template> + <extensions-detail-view delegate="[[delegate]]" slot="view" + in-dev-mode="[[inDevMode]]" + from-activity-log="[[fromActivityLog_]]" + show-activity-log="[[showActivityLog]]" + incognito-available="[[incognitoAvailable_]]" + data="[[detailViewItem_]]"> + </extensions-detail-view> </template> - <cr-view-manager id="viewManager" role="main"> - <extensions-item-list id="items-list" delegate="[[delegate]]" - in-dev-mode="[[inDevMode]]" filter="[[filter]]" - hidden$="[[!didInitPage_]]" slot="view" apps="[[apps_]]" - extensions="[[extensions_]]" - on-show-install-warnings="onShowInstallWarnings_"> - </extensions-item-list> - <cr-lazy-render id="details-view"> - <template> - <extensions-detail-view delegate="[[delegate]]" slot="view" - in-dev-mode="[[inDevMode]]" - from-activity-log="[[fromActivityLog_]]" - show-activity-log="[[showActivityLog]]" - incognito-available="[[incognitoAvailable_]]" - data="[[detailViewItem_]]"> - </extensions-detail-view> - </template> - </cr-lazy-render> - <cr-lazy-render id="activity-log"> - <template> - <extensions-activity-log delegate="[[delegate]]" slot="view" - extension-info="[[activityLogItem_]]"> - </extensions-activity-log> - </template> - </cr-lazy-render> - <cr-lazy-render id="keyboard-shortcuts"> - <template> - <extensions-keyboard-shortcuts delegate="[[delegate]]" slot="view" - items="[[extensions_]]"> - </extensions-keyboard-shortcuts> - </template> - </cr-lazy-render> - <cr-lazy-render id="error-page"> - <template> - <extensions-error-page data="[[errorPageItem_]]" slot="view" - delegate="[[delegate]]" in-dev-mode="[[inDevMode]]"> - </extensions-error-page> - </template> - </cr-lazy-render> - </cr-view-manager> - <template is="dom-if" if="[[showOptionsDialog_]]" restamp> - <extensions-options-dialog id="options-dialog" - on-close="onOptionsDialogClose_"> - </extensions-options-dialog> + </cr-lazy-render> + <cr-lazy-render id="activity-log"> + <template> + <extensions-activity-log delegate="[[delegate]]" slot="view" + extension-info="[[activityLogItem_]]"> + </extensions-activity-log> </template> - <template is="dom-if" if="[[showLoadErrorDialog_]]" restamp> - <extensions-load-error id="load-error" delegate="[[delegate]]" - on-close="onLoadErrorDialogClose_"> - </extensions-load-error> + </cr-lazy-render> + <cr-lazy-render id="keyboard-shortcuts"> + <template> + <extensions-keyboard-shortcuts delegate="[[delegate]]" slot="view" + items="[[extensions_]]"> + </extensions-keyboard-shortcuts> </template> -<if expr="chromeos"> - <template is="dom-if" if="[[showKioskDialog_]]" restamp> - <extensions-kiosk-dialog id="kiosk-dialog" on-close="onKioskDialogClose_"> - </extensions-kiosk-dialog> + </cr-lazy-render> + <cr-lazy-render id="error-page"> + <template> + <extensions-error-page data="[[errorPageItem_]]" slot="view" + delegate="[[delegate]]" in-dev-mode="[[inDevMode]]"> + </extensions-error-page> </template> + </cr-lazy-render> +</cr-view-manager> +<template is="dom-if" if="[[showOptionsDialog_]]" restamp> + <extensions-options-dialog id="options-dialog" + on-close="onOptionsDialogClose_"> + </extensions-options-dialog> +</template> +<template is="dom-if" if="[[showLoadErrorDialog_]]" restamp> + <extensions-load-error id="load-error" delegate="[[delegate]]" + on-close="onLoadErrorDialogClose_"> + </extensions-load-error> +</template> +<if expr="chromeos"> +<template is="dom-if" if="[[showKioskDialog_]]" restamp> + <extensions-kiosk-dialog id="kiosk-dialog" on-close="onKioskDialogClose_"> + </extensions-kiosk-dialog> +</template> </if> - <template is="dom-if" if="[[showInstallWarningsDialog_]]" restamp> - <extensions-install-warnings-dialog - on-close="onInstallWarningsDialogClose_" - install-warnings="[[installWarnings_]]"> - </extensions-install-warnings-dialog> - </template> - <cr-toast-manager></cr-toast-manager> - </template> - <script src="manager.js"></script> -</dom-module> +<template is="dom-if" if="[[showInstallWarningsDialog_]]" restamp> + <extensions-install-warnings-dialog + on-close="onInstallWarningsDialogClose_" + install-warnings="[[installWarnings_]]"> + </extensions-install-warnings-dialog> +</template> +<cr-toast-manager></cr-toast-manager> diff --git a/chromium/chrome/browser/resources/extensions/manager.js b/chromium/chrome/browser/resources/extensions/manager.js index 28e8b0fdece..b1df5c6fef5 100644 --- a/chromium/chrome/browser/resources/extensions/manager.js +++ b/chromium/chrome/browser/resources/extensions/manager.js @@ -2,632 +2,658 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -cr.define('extensions', function() { - 'use strict'; - - /** - * Compares two extensions to determine which should come first in the list. - * @param {chrome.developerPrivate.ExtensionInfo} a - * @param {chrome.developerPrivate.ExtensionInfo} b - * @return {number} - */ - const compareExtensions = function(a, b) { - function compare(x, y) { - return x < y ? -1 : (x > y ? 1 : 0); - } - function compareLocation(x, y) { - if (x.location == y.location) { - return 0; - } - if (x.location == chrome.developerPrivate.Location.UNPACKED) { - return -1; - } - if (y.location == chrome.developerPrivate.Location.UNPACKED) { - return 1; - } +import 'chrome://resources/cr_elements/cr_drawer/cr_drawer.m.js'; +import 'chrome://resources/cr_elements/cr_lazy_render/cr_lazy_render.m.js'; +import 'chrome://resources/cr_elements/cr_toast/cr_toast_manager.m.js'; +import 'chrome://resources/cr_elements/cr_toolbar/cr_toolbar.m.js'; +import 'chrome://resources/cr_elements/cr_view_manager/cr_view_manager.m.js'; +import 'chrome://resources/cr_elements/hidden_style_css.m.js'; +import 'chrome://resources/cr_elements/shared_vars_css.m.js'; +import './detail_view.js'; +import './drop_overlay.js'; +import './error_page.js'; +import './install_warnings_dialog.js'; +import './item_list.js'; +import './item_util.js'; +import './keyboard_shortcuts.js'; +import './load_error.js'; +import './options_dialog.js'; +import './sidebar.js'; +import './toolbar.js'; +// <if expr="chromeos"> +import './kiosk_dialog.js'; +// </if> + +import {assert, assertNotReached} from 'chrome://resources/js/assert.m.js'; +import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js'; +import {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; + +import {ActivityLogExtensionPlaceholder} from './activity_log/activity_log.js'; +// <if expr="chromeos"> +import {KioskBrowserProxyImpl} from './kiosk_browser_proxy.js'; +// </if> +import {Dialog, navigation, Page, PageState} from './navigation_helper.js'; +import {Service} from './service.js'; + +/** + * Compares two extensions to determine which should come first in the list. + * @param {chrome.developerPrivate.ExtensionInfo} a + * @param {chrome.developerPrivate.ExtensionInfo} b + * @return {number} + */ +const compareExtensions = function(a, b) { + function compare(x, y) { + return x < y ? -1 : (x > y ? 1 : 0); + } + function compareLocation(x, y) { + if (x.location == y.location) { return 0; } - return compareLocation(a, b) || - compare(a.name.toLowerCase(), b.name.toLowerCase()) || - compare(a.id, b.id); - }; - - const Manager = Polymer({ - is: 'extensions-manager', - - properties: { - canLoadUnpacked: { - type: Boolean, - value: false, - }, - - /** @type {!extensions.Service} */ - delegate: { - type: Object, - value: function() { - return extensions.Service.getInstance(); - }, - }, - - inDevMode: { - type: Boolean, - value: () => loadTimeData.getBoolean('inDevMode'), - }, - - showActivityLog: { - type: Boolean, - value: () => loadTimeData.getBoolean('showActivityLog'), - }, - - devModeControlledByPolicy: { - type: Boolean, - value: false, - }, - - /** @private */ - isSupervised_: { - type: Boolean, - value: false, - }, - - incognitoAvailable_: { - type: Boolean, - value: false, - }, - - filter: { - type: String, - value: '', - }, + if (x.location == chrome.developerPrivate.Location.UNPACKED) { + return -1; + } + if (y.location == chrome.developerPrivate.Location.UNPACKED) { + return 1; + } + return 0; + } + return compareLocation(a, b) || + compare(a.name.toLowerCase(), b.name.toLowerCase()) || + compare(a.id, b.id); +}; + +Polymer({ + is: 'extensions-manager', + + _template: html`{__html_template__}`, + + properties: { + canLoadUnpacked: { + type: Boolean, + value: false, + }, - /** - * The item currently displayed in the error subpage. We use a separate - * item for different pages (rather than a single subpageItem_ property) - * so that hidden subpages don't update when an item updates. That is, we - * don't want the details view subpage to update when the item shown in - * the errors page updates, and vice versa. - * @private {!chrome.developerPrivate.ExtensionInfo|undefined} - */ - errorPageItem_: Object, - - /** - * The item currently displayed in the details view subpage. See also - * errorPageItem_. - * @private {!chrome.developerPrivate.ExtensionInfo|undefined} - */ - detailViewItem_: Object, - - /** - * The item that provides some information about the current extension - * for the activity log view subpage. See also errorPageItem_. - * @private {!chrome.developerPrivate.ExtensionInfo|undefined| - * !extensions.ActivityLogExtensionPlaceholder} - */ - activityLogItem_: Object, - - /** @private {!Array<!chrome.developerPrivate.ExtensionInfo>} */ - extensions_: Array, - - /** @private {!Array<!chrome.developerPrivate.ExtensionInfo>} */ - apps_: Array, - - /** - * Prevents page content from showing before data is first loaded. - * @private - */ - didInitPage_: { - type: Boolean, - value: false, + /** @type {!Service} */ + delegate: { + type: Object, + value: function() { + return Service.getInstance(); }, + }, - /** @private */ - showDrawer_: Boolean, - - /** @private */ - showLoadErrorDialog_: Boolean, - - /** @private */ - showInstallWarningsDialog_: Boolean, - - /** @private {?Array<string>} */ - installWarnings_: Array, + inDevMode: { + type: Boolean, + value: () => loadTimeData.getBoolean('inDevMode'), + }, - /** @private */ - showOptionsDialog_: Boolean, + showActivityLog: { + type: Boolean, + value: () => loadTimeData.getBoolean('showActivityLog'), + }, - /** - * Whether the last page the user navigated from was the activity log - * page. - * @private - */ - fromActivityLog_: Boolean, + devModeControlledByPolicy: { + type: Boolean, + value: false, + }, - // <if expr="chromeos"> - /** @private */ - kioskEnabled_: { - type: Boolean, - value: false, - }, + /** @private */ + isSupervised_: { + type: Boolean, + value: false, + }, - /** @private */ - showKioskDialog_: { - type: Boolean, - value: false, - }, - // </if> + incognitoAvailable_: { + type: Boolean, + value: false, }, - listeners: { - 'load-error': 'onLoadError_', - 'view-enter-start': 'onViewEnterStart_', - 'view-exit-start': 'onViewExitStart_', - 'view-exit-finish': 'onViewExitFinish_', + filter: { + type: String, + value: '', }, /** - * The current page being shown. Default to null, and initPage_ will figure - * out the initial page based on url. - * @private {?extensions.PageState} + * The item currently displayed in the error subpage. We use a separate + * item for different pages (rather than a single subpageItem_ property) + * so that hidden subpages don't update when an item updates. That is, we + * don't want the details view subpage to update when the item shown in + * the errors page updates, and vice versa. + * @private {!chrome.developerPrivate.ExtensionInfo|undefined} */ - currentPage_: null, + errorPageItem_: Object, /** - * The ID of the listener on |extensions.navigation|. Stored so that the - * listener can be removed when this element is detached (happens in tests). - * @private {?number} + * The item currently displayed in the details view subpage. See also + * errorPageItem_. + * @private {!chrome.developerPrivate.ExtensionInfo|undefined} */ - navigationListener_: null, - - /** @override */ - ready: function() { - const service = extensions.Service.getInstance(); - - const onProfileStateChanged = profileInfo => { - this.isSupervised_ = profileInfo.isSupervised; - this.incognitoAvailable_ = profileInfo.isIncognitoAvailable; - this.devModeControlledByPolicy = - profileInfo.isDeveloperModeControlledByPolicy; - this.inDevMode = profileInfo.inDeveloperMode; - this.canLoadUnpacked = profileInfo.canLoadUnpacked; - }; - service.getProfileStateChangedTarget().addListener(onProfileStateChanged); - service.getProfileConfiguration().then(onProfileStateChanged); - - service.getExtensionsInfo().then(extensionsAndApps => { - this.initExtensionsAndApps_(extensionsAndApps); - this.initPage_(); - - service.getItemStateChangedTarget().addListener( - this.onItemStateChanged_.bind(this)); - }); + detailViewItem_: Object, - // <if expr="chromeos"> - extensions.KioskBrowserProxyImpl.getInstance() - .initializeKioskAppSettings() - .then(params => { - this.kioskEnabled_ = params.kioskEnabled; - }); - // </if> - }, + /** + * The item that provides some information about the current extension + * for the activity log view subpage. See also errorPageItem_. + * @private {!chrome.developerPrivate.ExtensionInfo|undefined| + * !ActivityLogExtensionPlaceholder} + */ + activityLogItem_: Object, - /** @override */ - attached: function() { - document.documentElement.classList.remove('loading'); - document.fonts.load('bold 12px Roboto'); + /** @private {!Array<!chrome.developerPrivate.ExtensionInfo>} */ + extensions_: Array, - this.navigationListener_ = extensions.navigation.addListener(newPage => { - this.changePage_(newPage); - }); - }, - - /** @override */ - detached: function() { - assert(extensions.navigation.removeListener(this.navigationListener_)); - this.navigationListener_ = null; - }, + /** @private {!Array<!chrome.developerPrivate.ExtensionInfo>} */ + apps_: Array, /** - * Initializes the page to reflect what's specified in the url so that if - * the user visits chrome://extensions/?id=..., we land on the proper page. + * Prevents page content from showing before data is first loaded. * @private */ - initPage_: function() { - this.didInitPage_ = true; - this.changePage_(extensions.navigation.getCurrentPage()); + didInitPage_: { + type: Boolean, + value: false, }, - /** - * @param {!chrome.developerPrivate.EventData} eventData - * @private - */ - onItemStateChanged_: function(eventData) { - const EventType = chrome.developerPrivate.EventType; - switch (eventData.event_type) { - case EventType.VIEW_REGISTERED: - case EventType.VIEW_UNREGISTERED: - case EventType.INSTALLED: - case EventType.LOADED: - case EventType.UNLOADED: - case EventType.ERROR_ADDED: - case EventType.ERRORS_REMOVED: - case EventType.PREFS_CHANGED: - case EventType.WARNINGS_CHANGED: - case EventType.COMMAND_ADDED: - case EventType.COMMAND_REMOVED: - case EventType.PERMISSIONS_CHANGED: - // |extensionInfo| can be undefined in the case of an extension - // being unloaded right before uninstallation. There's nothing to do - // here. - if (!eventData.extensionInfo) { - break; - } - - if (this.delegate.shouldIgnoreUpdate( - eventData.extensionInfo.id, eventData.event_type)) { - break; - } - - const listId = this.getListId_(eventData.extensionInfo); - const currentIndex = this[listId].findIndex( - item => item.id == eventData.extensionInfo.id); - - if (currentIndex >= 0) { - this.updateItem_(listId, currentIndex, eventData.extensionInfo); - } else { - this.addItem_(listId, eventData.extensionInfo); - } - break; - case EventType.UNINSTALLED: - this.removeItem_(eventData.item_id); - break; - default: - assertNotReached(); - } - }, + /** @private */ + showDrawer_: Boolean, - /** - * @param {!CustomEvent<string>} event - * @private - */ - onFilterChanged_: function(event) { - if (this.currentPage_.page !== extensions.Page.LIST) { - extensions.navigation.navigateTo({page: extensions.Page.LIST}); - } - this.filter = event.detail; - }, + /** @private */ + showLoadErrorDialog_: Boolean, /** @private */ - onMenuButtonTap_: function() { - this.showDrawer_ = true; - this.async(() => { - this.$$('#drawer').openDrawer(); - }); - }, + showInstallWarningsDialog_: Boolean, - /** - * @param {!chrome.developerPrivate.ExtensionInfo} item - * @return {string} The ID of the list that the item belongs in. - * @private - */ - getListId_: function(item) { - const ExtensionType = chrome.developerPrivate.ExtensionType; - switch (item.type) { - case ExtensionType.HOSTED_APP: - case ExtensionType.LEGACY_PACKAGED_APP: - case ExtensionType.PLATFORM_APP: - return 'apps_'; - case ExtensionType.EXTENSION: - case ExtensionType.SHARED_MODULE: - return 'extensions_'; - case ExtensionType.THEME: - assertNotReached( - 'Don\'t send themes to the chrome://extensions page'); - break; - } - assertNotReached(); - }, + /** @private {?Array<string>} */ + installWarnings_: Array, + + /** @private */ + showOptionsDialog_: Boolean, /** - * @param {string} listId The list to look for the item in. - * @param {string} itemId The id of the item to look for. - * @return {number} The index of the item in the list, or -1 if not found. + * Whether the last page the user navigated from was the activity log + * page. * @private */ - getIndexInList_: function(listId, itemId) { - return this[listId].findIndex(function(item) { - return item.id == itemId; - }); + fromActivityLog_: Boolean, + + // <if expr="chromeos"> + /** @private */ + kioskEnabled_: { + type: Boolean, + value: false, }, - /** - * @return {?chrome.developerPrivate.ExtensionInfo} - * @private - */ - getData_: function(id) { - return this.extensions_[this.getIndexInList_('extensions_', id)] || - this.apps_[this.getIndexInList_('apps_', id)]; + /** @private */ + showKioskDialog_: { + type: Boolean, + value: false, }, + // </if> + }, - /** - * Categorizes |extensionsAndApps| to apps and extensions and initializes - * those lists. - * @param {!Array<!chrome.developerPrivate.ExtensionInfo>} extensionsAndApps - * @private - */ - initExtensionsAndApps_: function(extensionsAndApps) { - extensionsAndApps.sort(compareExtensions); - const apps = []; - const extensions = []; - for (const i of extensionsAndApps) { - const list = this.getListId_(i) === 'apps_' ? apps : extensions; - list.push(i); - } + listeners: { + 'load-error': 'onLoadError_', + 'view-enter-start': 'onViewEnterStart_', + 'view-exit-start': 'onViewExitStart_', + 'view-exit-finish': 'onViewExitFinish_', + }, - this.apps_ = apps; - this.extensions_ = extensions; - }, + /** + * The current page being shown. Default to null, and initPage_ will figure + * out the initial page based on url. + * @private {?PageState} + */ + currentPage_: null, - /** - * Creates and adds a new extensions-item element to the list, inserting it - * into its sorted position in the relevant section. - * @param {!chrome.developerPrivate.ExtensionInfo} item The extension - * the new element is representing. - * @private - */ - addItem_: function(listId, item) { - // We should never try and add an existing item. - assert(this.getIndexInList_(listId, item.id) == -1); - let insertBeforeChild = this[listId].findIndex(function(listEl) { - return compareExtensions(listEl, item) > 0; - }); - if (insertBeforeChild == -1) { - insertBeforeChild = this[listId].length; - } - this.splice(listId, insertBeforeChild, 0, item); - }, + /** + * The ID of the listener on |navigation|. Stored so that the + * listener can be removed when this element is detached (happens in tests). + * @private {?number} + */ + navigationListener_: null, + + /** @override */ + ready: function() { + const service = Service.getInstance(); + + const onProfileStateChanged = profileInfo => { + this.isSupervised_ = profileInfo.isSupervised; + this.incognitoAvailable_ = profileInfo.isIncognitoAvailable; + this.devModeControlledByPolicy = + profileInfo.isDeveloperModeControlledByPolicy; + this.inDevMode = profileInfo.inDeveloperMode; + this.canLoadUnpacked = profileInfo.canLoadUnpacked; + }; + service.getProfileStateChangedTarget().addListener(onProfileStateChanged); + service.getProfileConfiguration().then(onProfileStateChanged); + + service.getExtensionsInfo().then(extensionsAndApps => { + this.initExtensionsAndApps_(extensionsAndApps); + this.initPage_(); + + service.getItemStateChangedTarget().addListener( + this.onItemStateChanged_.bind(this)); + }); - /** - * @param {!chrome.developerPrivate.ExtensionInfo} item The data for the - * item to update. - * @private - */ - updateItem_: function(listId, index, item) { - // We should never try and update a non-existent item. - assert(index >= 0); - this.set([listId, index], item); - - // Update the subpage if it is open and displaying the item. If it's not - // open, we don't update the data even if it's displaying that item. We'll - // set the item correctly before opening the page. It's a little weird - // that the DOM will have stale data, but there's no point in causing the - // extra work. - if (this.detailViewItem_ && this.detailViewItem_.id == item.id && - this.currentPage_.page == extensions.Page.DETAILS) { - this.detailViewItem_ = item; - } else if ( - this.errorPageItem_ && this.errorPageItem_.id == item.id && - this.currentPage_.page == extensions.Page.ERRORS) { - this.errorPageItem_ = item; - } else if ( - this.activityLogItem_ && this.activityLogItem_.id == item.id && - this.currentPage_.page == extensions.Page.ACTIVITY_LOG) { - this.activityLogItem_ = item; - } - }, + // <if expr="chromeos"> + KioskBrowserProxyImpl.getInstance().initializeKioskAppSettings().then( + params => { + this.kioskEnabled_ = params.kioskEnabled; + }); + // </if> + }, - /** - * @param {string} itemId The id of item to remove. - * @private - */ - removeItem_: function(itemId) { - // Search for the item to be deleted in |extensions_|. - let listId = 'extensions_'; - let index = this.getIndexInList_(listId, itemId); - if (index == -1) { - // If not in |extensions_| it must be in |apps_|. - listId = 'apps_'; - index = this.getIndexInList_(listId, itemId); - } + /** @override */ + attached: function() { + document.documentElement.classList.remove('loading'); + document.fonts.load('bold 12px Roboto'); - // We should never try and remove a non-existent item. - assert(index >= 0); - this.splice(listId, index, 1); - if ((this.currentPage_.page == extensions.Page.ACTIVITY_LOG || - this.currentPage_.page == extensions.Page.DETAILS || - this.currentPage_.page == extensions.Page.ERRORS) && - this.currentPage_.extensionId == itemId) { - // Leave the details page (the 'list' page is a fine choice). - extensions.navigation.replaceWith({page: extensions.Page.LIST}); - } - }, + this.navigationListener_ = navigation.addListener(newPage => { + this.changePage_(newPage); + }); + }, - /** - * @param {!CustomEvent<!chrome.developerPrivate.LoadError>} e - * @private - */ - onLoadError_: function(e) { - this.showLoadErrorDialog_ = true; - this.async(() => { - const dialog = this.$$('#load-error'); - dialog.loadError = e.detail; - dialog.show(); - }); - }, + /** @override */ + detached: function() { + assert(navigation.removeListener( + /** @type {number} */ (this.navigationListener_))); + this.navigationListener_ = null; + }, - /** - * Changes the active page selection. - * @param {extensions.PageState} newPage - * @private - */ - changePage_: function(newPage) { - this.onCloseDrawer_(); + /** + * Initializes the page to reflect what's specified in the url so that if + * the user visits chrome://extensions/?id=..., we land on the proper page. + * @private + */ + initPage_: function() { + this.didInitPage_ = true; + this.changePage_(navigation.getCurrentPage()); + }, - const optionsDialog = this.$$('#options-dialog'); - if (optionsDialog && optionsDialog.open) { - this.showOptionsDialog_ = false; - } + /** + * @param {!chrome.developerPrivate.EventData} eventData + * @private + */ + onItemStateChanged_: function(eventData) { + const EventType = chrome.developerPrivate.EventType; + switch (eventData.event_type) { + case EventType.VIEW_REGISTERED: + case EventType.VIEW_UNREGISTERED: + case EventType.INSTALLED: + case EventType.LOADED: + case EventType.UNLOADED: + case EventType.ERROR_ADDED: + case EventType.ERRORS_REMOVED: + case EventType.PREFS_CHANGED: + case EventType.WARNINGS_CHANGED: + case EventType.COMMAND_ADDED: + case EventType.COMMAND_REMOVED: + case EventType.PERMISSIONS_CHANGED: + // |extensionInfo| can be undefined in the case of an extension + // being unloaded right before uninstallation. There's nothing to do + // here. + if (!eventData.extensionInfo) { + break; + } - const fromPage = this.currentPage_ ? this.currentPage_.page : null; - const toPage = newPage.page; - let data; - let activityLogPlaceholder; - if (newPage.extensionId) { - data = this.getData_(newPage.extensionId); - if (!data) { - // Allow the user to navigate to the activity log page even if the - // extension ID is not valid. This enables the use case of seeing an - // extension's install-time activities by navigating to an extension's - // activity log page, then installing the extension. - if (this.showActivityLog && toPage == extensions.Page.ACTIVITY_LOG) { - activityLogPlaceholder = { - id: newPage.extensionId, - isPlaceholder: true, - }; - } else { - // Attempting to view an invalid (removed?) app or extension ID. - extensions.navigation.replaceWith({page: extensions.Page.LIST}); - return; - } + if (this.delegate.shouldIgnoreUpdate( + eventData.extensionInfo.id, eventData.event_type)) { + break; } - } - if (toPage == extensions.Page.DETAILS) { - this.detailViewItem_ = assert(data); - } else if (toPage == extensions.Page.ERRORS) { - this.errorPageItem_ = assert(data); - } else if (toPage == extensions.Page.ACTIVITY_LOG) { - if (!this.showActivityLog) { - // Redirect back to the details page if we try to view the - // activity log of an extension but the flag is not set. - extensions.navigation.replaceWith({ - page: extensions.Page.DETAILS, - extensionId: newPage.extensionId - }); - return; + const listId = this.getListId_(eventData.extensionInfo); + const currentIndex = this[listId].findIndex( + item => item.id == eventData.extensionInfo.id); + + if (currentIndex >= 0) { + this.updateItem_(listId, currentIndex, eventData.extensionInfo); + } else { + this.addItem_(listId, eventData.extensionInfo); } + break; + case EventType.UNINSTALLED: + this.removeItem_(eventData.item_id); + break; + default: + assertNotReached(); + } + }, - this.activityLogItem_ = data ? assert(data) : activityLogPlaceholder; - } + /** + * @param {!CustomEvent<string>} event + * @private + */ + onFilterChanged_: function(event) { + if (this.currentPage_.page !== Page.LIST) { + navigation.navigateTo({page: Page.LIST}); + } + this.filter = event.detail; + }, - if (fromPage != toPage) { - /** @type {CrViewManagerElement} */ (this.$.viewManager) - .switchView(/** @type {string} */ (toPage)); - } + /** @private */ + onMenuButtonTap_: function() { + this.showDrawer_ = true; + this.async(() => { + this.$$('#drawer').openDrawer(); + }); + }, - if (newPage.subpage) { - assert(newPage.subpage == extensions.Dialog.OPTIONS); - assert(newPage.extensionId); - this.showOptionsDialog_ = true; - this.async(() => { - this.$$('#options-dialog').show(data); - }); - } + /** + * @param {!chrome.developerPrivate.ExtensionInfo} item + * @return {string} The ID of the list that the item belongs in. + * @private + */ + getListId_: function(item) { + const ExtensionType = chrome.developerPrivate.ExtensionType; + switch (item.type) { + case ExtensionType.HOSTED_APP: + case ExtensionType.LEGACY_PACKAGED_APP: + case ExtensionType.PLATFORM_APP: + return 'apps_'; + case ExtensionType.EXTENSION: + case ExtensionType.SHARED_MODULE: + return 'extensions_'; + case ExtensionType.THEME: + assertNotReached('Don\'t send themes to the chrome://extensions page'); + break; + } + assertNotReached(); + }, - document.title = toPage == extensions.Page.DETAILS ? - `${loadTimeData.getString('title')} - ${this.detailViewItem_.name}` : - loadTimeData.getString('title'); - this.currentPage_ = newPage; - }, + /** + * @param {string} listId The list to look for the item in. + * @param {string} itemId The id of the item to look for. + * @return {number} The index of the item in the list, or -1 if not found. + * @private + */ + getIndexInList_: function(listId, itemId) { + return this[listId].findIndex(function(item) { + return item.id == itemId; + }); + }, - /** - * This method detaches the drawer dialog completely. Should only be - * triggered by the dialog's 'close' event. - * @private - */ - onDrawerClose_: function() { - this.showDrawer_ = false; - }, + /** + * @return {?chrome.developerPrivate.ExtensionInfo} + * @private + */ + getData_: function(id) { + return this.extensions_[this.getIndexInList_('extensions_', id)] || + this.apps_[this.getIndexInList_('apps_', id)]; + }, - /** - * This method animates the closing of the drawer. - * @private - */ - onCloseDrawer_: function() { - const drawer = this.$$('#drawer'); - if (drawer && drawer.open) { - drawer.close(); - } - }, + /** + * Categorizes |extensionsAndApps| to apps and extensions and initializes + * those lists. + * @param {!Array<!chrome.developerPrivate.ExtensionInfo>} extensionsAndApps + * @private + */ + initExtensionsAndApps_: function(extensionsAndApps) { + extensionsAndApps.sort(compareExtensions); + const apps = []; + const extensions = []; + for (const i of extensionsAndApps) { + const list = this.getListId_(i) === 'apps_' ? apps : extensions; + list.push(i); + } - /** @private */ - onLoadErrorDialogClose_: function() { - this.showLoadErrorDialog_ = false; - }, + this.apps_ = apps; + this.extensions_ = extensions; + }, - /** @private */ - onOptionsDialogClose_: function() { - this.showOptionsDialog_ = false; - this.$$('extensions-detail-view').focusOptionsButton(); - }, + /** + * Creates and adds a new extensions-item element to the list, inserting it + * into its sorted position in the relevant section. + * @param {!chrome.developerPrivate.ExtensionInfo} item The extension + * the new element is representing. + * @private + */ + addItem_: function(listId, item) { + // We should never try and add an existing item. + assert(this.getIndexInList_(listId, item.id) == -1); + let insertBeforeChild = this[listId].findIndex(function(listEl) { + return compareExtensions(listEl, item) > 0; + }); + if (insertBeforeChild == -1) { + insertBeforeChild = this[listId].length; + } + this.splice(listId, insertBeforeChild, 0, item); + }, - /** @private */ - onViewEnterStart_: function() { - this.fromActivityLog_ = false; - }, + /** + * @param {!chrome.developerPrivate.ExtensionInfo} item The data for the + * item to update. + * @private + */ + updateItem_: function(listId, index, item) { + // We should never try and update a non-existent item. + assert(index >= 0); + this.set([listId, index], item); + + // Update the subpage if it is open and displaying the item. If it's not + // open, we don't update the data even if it's displaying that item. We'll + // set the item correctly before opening the page. It's a little weird + // that the DOM will have stale data, but there's no point in causing the + // extra work. + if (this.detailViewItem_ && this.detailViewItem_.id == item.id && + this.currentPage_.page == Page.DETAILS) { + this.detailViewItem_ = item; + } else if ( + this.errorPageItem_ && this.errorPageItem_.id == item.id && + this.currentPage_.page == Page.ERRORS) { + this.errorPageItem_ = item; + } else if ( + this.activityLogItem_ && this.activityLogItem_.id == item.id && + this.currentPage_.page == Page.ACTIVITY_LOG) { + this.activityLogItem_ = item; + } + }, - /** - * @param {!Event} e - * @private - */ - onViewExitStart_: function(e) { - const viewType = e.composedPath()[0].tagName; - this.fromActivityLog_ = viewType == 'EXTENSIONS-ACTIVITY-LOG'; - }, + /** + * @param {string} itemId The id of item to remove. + * @private + */ + removeItem_: function(itemId) { + // Search for the item to be deleted in |extensions_|. + let listId = 'extensions_'; + let index = this.getIndexInList_(listId, itemId); + if (index == -1) { + // If not in |extensions_| it must be in |apps_|. + listId = 'apps_'; + index = this.getIndexInList_(listId, itemId); + } - /** - * @param {!Event} e - * @private - */ - onViewExitFinish_: function(e) { - const viewType = e.composedPath()[0].tagName; - if (viewType == 'EXTENSIONS-ITEM-LIST' || - viewType == 'EXTENSIONS-KEYBOARD-SHORTCUTS' || - viewType == 'EXTENSIONS-ACTIVITY-LOG') { + // We should never try and remove a non-existent item. + assert(index >= 0); + this.splice(listId, index, 1); + if ((this.currentPage_.page == Page.ACTIVITY_LOG || + this.currentPage_.page == Page.DETAILS || + this.currentPage_.page == Page.ERRORS) && + this.currentPage_.extensionId == itemId) { + // Leave the details page (the 'list' page is a fine choice). + navigation.replaceWith({page: Page.LIST}); + } + }, + + /** + * @param {!CustomEvent<!chrome.developerPrivate.LoadError>} e + * @private + */ + onLoadError_: function(e) { + this.showLoadErrorDialog_ = true; + this.async(() => { + const dialog = this.$$('#load-error'); + dialog.loadError = e.detail; + dialog.show(); + }); + }, + + /** + * Changes the active page selection. + * @param {PageState} newPage + * @private + */ + changePage_: function(newPage) { + this.onCloseDrawer_(); + + const optionsDialog = this.$$('#options-dialog'); + if (optionsDialog && optionsDialog.open) { + this.showOptionsDialog_ = false; + } + + const fromPage = this.currentPage_ ? this.currentPage_.page : null; + const toPage = newPage.page; + let data; + let activityLogPlaceholder; + if (newPage.extensionId) { + data = this.getData_(newPage.extensionId); + if (!data) { + // Allow the user to navigate to the activity log page even if the + // extension ID is not valid. This enables the use case of seeing an + // extension's install-time activities by navigating to an extension's + // activity log page, then installing the extension. + if (this.showActivityLog && toPage == Page.ACTIVITY_LOG) { + activityLogPlaceholder = { + id: newPage.extensionId, + isPlaceholder: true, + }; + } else { + // Attempting to view an invalid (removed?) app or extension ID. + navigation.replaceWith({page: Page.LIST}); + return; + } + } + } + + if (toPage == Page.DETAILS) { + this.detailViewItem_ = assert(data); + } else if (toPage == Page.ERRORS) { + this.errorPageItem_ = assert(data); + } else if (toPage == Page.ACTIVITY_LOG) { + if (!this.showActivityLog) { + // Redirect back to the details page if we try to view the + // activity log of an extension but the flag is not set. + navigation.replaceWith( + {page: Page.DETAILS, extensionId: newPage.extensionId}); return; } - const extensionId = e.composedPath()[0].data.id; - const list = this.$$('extensions-item-list'); - const button = viewType == 'EXTENSIONS-DETAIL-VIEW' ? - list.getDetailsButton(extensionId) : - list.getErrorsButton(extensionId); + this.activityLogItem_ = data ? assert(data) : activityLogPlaceholder; + } - // The button will not exist, when returning from a details page - // because the corresponding extension/app was deleted. - if (button) { - button.focus(); - } - }, + if (fromPage != toPage) { + /** @type {CrViewManagerElement} */ (this.$.viewManager) + .switchView(/** @type {string} */ (toPage)); + } - /** - * @param {!CustomEvent<!Array<string>>} e - * @private - */ - onShowInstallWarnings_: function(e) { - // Leverage Polymer data bindings instead of just assigning the - // installWarnings on the dialog since the dialog hasn't been stamped - // in the DOM yet. - this.installWarnings_ = e.detail; - this.showInstallWarningsDialog_ = true; - }, + if (newPage.subpage) { + assert(newPage.subpage == Dialog.OPTIONS); + assert(newPage.extensionId); + this.showOptionsDialog_ = true; + this.async(() => { + this.$$('#options-dialog').show(data); + }); + } - /** @private */ - onInstallWarningsDialogClose_: function() { - this.installWarnings_ = null; - this.showInstallWarningsDialog_ = false; - }, + document.title = toPage == Page.DETAILS ? + `${loadTimeData.getString('title')} - ${this.detailViewItem_.name}` : + loadTimeData.getString('title'); + this.currentPage_ = newPage; + }, - // <if expr="chromeos"> - /** @private */ - onKioskTap_: function() { - this.showKioskDialog_ = true; - }, + /** + * This method detaches the drawer dialog completely. Should only be + * triggered by the dialog's 'close' event. + * @private + */ + onDrawerClose_: function() { + this.showDrawer_ = false; + }, - onKioskDialogClose_: function() { - this.showKioskDialog_ = false; - }, - // </if> - }); + /** + * This method animates the closing of the drawer. + * @private + */ + onCloseDrawer_: function() { + const drawer = this.$$('#drawer'); + if (drawer && drawer.open) { + drawer.close(); + } + }, + + /** @private */ + onLoadErrorDialogClose_: function() { + this.showLoadErrorDialog_ = false; + }, - return {Manager: Manager}; + /** @private */ + onOptionsDialogClose_: function() { + this.showOptionsDialog_ = false; + this.$$('extensions-detail-view').focusOptionsButton(); + }, + + /** @private */ + onViewEnterStart_: function() { + this.fromActivityLog_ = false; + }, + + /** + * @param {!Event} e + * @private + */ + onViewExitStart_: function(e) { + const viewType = e.composedPath()[0].tagName; + this.fromActivityLog_ = viewType == 'EXTENSIONS-ACTIVITY-LOG'; + }, + + /** + * @param {!Event} e + * @private + */ + onViewExitFinish_: function(e) { + const viewType = e.composedPath()[0].tagName; + if (viewType == 'EXTENSIONS-ITEM-LIST' || + viewType == 'EXTENSIONS-KEYBOARD-SHORTCUTS' || + viewType == 'EXTENSIONS-ACTIVITY-LOG') { + return; + } + + const extensionId = e.composedPath()[0].data.id; + const list = this.$$('extensions-item-list'); + const button = viewType == 'EXTENSIONS-DETAIL-VIEW' ? + list.getDetailsButton(extensionId) : + list.getErrorsButton(extensionId); + + // The button will not exist, when returning from a details page + // because the corresponding extension/app was deleted. + if (button) { + button.focus(); + } + }, + + /** + * @param {!CustomEvent<!Array<string>>} e + * @private + */ + onShowInstallWarnings_: function(e) { + // Leverage Polymer data bindings instead of just assigning the + // installWarnings on the dialog since the dialog hasn't been stamped + // in the DOM yet. + this.installWarnings_ = e.detail; + this.showInstallWarningsDialog_ = true; + }, + + /** @private */ + onInstallWarningsDialogClose_: function() { + this.installWarnings_ = null; + this.showInstallWarningsDialog_ = false; + }, + + // <if expr="chromeos"> + /** @private */ + onKioskTap_: function() { + this.showKioskDialog_ = true; + }, + + onKioskDialogClose_: function() { + this.showKioskDialog_ = false; + }, + // </if> }); diff --git a/chromium/chrome/browser/resources/extensions/navigation_helper.html b/chromium/chrome/browser/resources/extensions/navigation_helper.html deleted file mode 100644 index 4ebc7d42b7a..00000000000 --- a/chromium/chrome/browser/resources/extensions/navigation_helper.html +++ /dev/null @@ -1,6 +0,0 @@ -<link rel="import" href="chrome://resources/html/polymer.html"> - -<link rel="import" href="chrome://resources/html/assert.html"> -<link rel="import" href="chrome://resources/html/cr.html"> -<link rel="import" href="strings.html"> -<script src="navigation_helper.js"></script> diff --git a/chromium/chrome/browser/resources/extensions/navigation_helper.js b/chromium/chrome/browser/resources/extensions/navigation_helper.js index 75748cd70ef..b4bc143d1e7 100644 --- a/chromium/chrome/browser/resources/extensions/navigation_helper.js +++ b/chromium/chrome/browser/resources/extensions/navigation_helper.js @@ -2,238 +2,228 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -cr.define('extensions', function() { - 'use strict'; +import {assert} from 'chrome://resources/js/assert.m.js'; + + +/** + * The different pages that can be shown at a time. + * Note: This must remain in sync with the page ids in manager.html! + * @enum {string} + */ +export const Page = { + LIST: 'items-list', + DETAILS: 'details-view', + ACTIVITY_LOG: 'activity-log', + SHORTCUTS: 'keyboard-shortcuts', + ERRORS: 'error-page', +}; + +/** @enum {string} */ +export const Dialog = { + OPTIONS: 'options', +}; + +/** + * @typedef {{page: Page, + * extensionId: (string|undefined), + * subpage: (!Dialog|undefined)}} + */ +export let PageState; + +/** + * @param {!PageState} a + * @param {!PageState} b + * @return {boolean} Whether a and b are equal. + */ +function isPageStateEqual(a, b) { + return a.page == b.page && a.subpage == b.subpage && + a.extensionId == b.extensionId; +} + +/** + * Regular expression that captures the leading slash, the content and the + * trailing slash in three different groups. + * @const {!RegExp} + */ +const CANONICAL_PATH_REGEX = /(^\/)([\/-\w]+)(\/$)/; + +/** + * A helper object to manage in-page navigations. Since the extensions page + * needs to support different urls for different subpages (like the details + * page), we use this object to manage the history and url conversions. + */ +export class NavigationHelper { + constructor() { + this.processRoute_(); + + /** @private {number} */ + this.nextListenerId_ = 1; + + /** @private {!Map<number, function(!PageState)>} */ + this.listeners_ = new Map(); + + /** @private {!PageState} */ + this.previousPage_; + + window.addEventListener('popstate', () => { + this.notifyRouteChanged_(this.getCurrentPage()); + }); + } + + /** @private */ + get currentPath_() { + return location.pathname.replace(CANONICAL_PATH_REGEX, '$1$2'); + } /** - * The different pages that can be shown at a time. - * Note: This must remain in sync with the page ids in manager.html! - * @enum {string} + * Going to /configureCommands and /shortcuts should land you on /shortcuts. + * These are the only two supported routes, so all other cases will redirect + * you to root path if not already on it. + * @private */ - const Page = { - LIST: 'items-list', - DETAILS: 'details-view', - ACTIVITY_LOG: 'activity-log', - SHORTCUTS: 'keyboard-shortcuts', - ERRORS: 'error-page', - }; - - /** @enum {string} */ - const Dialog = { - OPTIONS: 'options', - }; + processRoute_() { + if (this.currentPath_ == '/configureCommands' || + this.currentPath_ == '/shortcuts') { + window.history.replaceState( + undefined /* stateObject */, '', '/shortcuts'); + } else if (this.currentPath_ !== '/') { + window.history.replaceState(undefined /* stateObject */, '', '/'); + } + } /** - * @typedef {{page: extensions.Page, - * extensionId: (string|undefined), - * subpage: (!extensions.Dialog|undefined)}} + * @return {!PageState} The page that should be displayed for the + * current URL. */ - let PageState; + getCurrentPage() { + const search = new URLSearchParams(location.search); + let id = search.get('id'); + if (id) { + return {page: Page.DETAILS, extensionId: id}; + } + id = search.get('activity'); + if (id) { + return {page: Page.ACTIVITY_LOG, extensionId: id}; + } + id = search.get('options'); + if (id) { + return {page: Page.DETAILS, extensionId: id, subpage: Dialog.OPTIONS}; + } + id = search.get('errors'); + if (id) { + return {page: Page.ERRORS, extensionId: id}; + } + + if (this.currentPath_ == '/shortcuts') { + return {page: Page.SHORTCUTS}; + } + + return {page: Page.LIST}; + } /** - * @param {!extensions.PageState} a - * @param {!extensions.PageState} b - * @return {boolean} Whether a and b are equal. + * Function to add subscribers. + * @param {!function(!PageState)} listener + * @return {number} A numerical ID to be used for removing the listener. */ - function isPageStateEqual(a, b) { - return a.page == b.page && a.subpage == b.subpage && - a.extensionId == b.extensionId; + addListener(listener) { + const nextListenerId = this.nextListenerId_++; + this.listeners_.set(nextListenerId, listener); + return nextListenerId; } /** - * Regular expression that captures the leading slash, the content and the - * trailing slash in three different groups. - * @const {!RegExp} + * Remove a previously registered listener. + * @param {number} id + * @return {boolean} Whether a listener with the given ID was actually found + * and removed. */ - const CANONICAL_PATH_REGEX = /(^\/)([\/-\w]+)(\/$)/; + removeListener(id) { + return this.listeners_.delete(id); + } /** - * A helper object to manage in-page navigations. Since the extensions page - * needs to support different urls for different subpages (like the details - * page), we use this object to manage the history and url conversions. + * Function to notify subscribers. + * @private */ - class NavigationHelper { - constructor() { - this.processRoute_(); - - /** @private {number} */ - this.nextListenerId_ = 1; - - /** @private {!Map<number, function(!extensions.PageState)>} */ - this.listeners_ = new Map(); - - /** @private {!extensions.PageState} */ - this.previousPage_; - - window.addEventListener('popstate', () => { - this.notifyRouteChanged_(this.getCurrentPage()); - }); - } - - /** @private */ - get currentPath_() { - return location.pathname.replace(CANONICAL_PATH_REGEX, '$1$2'); - } - - /** - * Going to /configureCommands and /shortcuts should land you on /shortcuts. - * These are the only two supported routes, so all other cases will redirect - * you to root path if not already on it. - * @private - */ - processRoute_() { - if (this.currentPath_ == '/configureCommands' || - this.currentPath_ == '/shortcuts') { - window.history.replaceState( - undefined /* stateObject */, '', '/shortcuts'); - } else if (this.currentPath_ !== '/') { - window.history.replaceState(undefined /* stateObject */, '', '/'); - } - } - - /** - * @return {!extensions.PageState} The page that should be displayed for the - * current URL. - */ - getCurrentPage() { - const search = new URLSearchParams(location.search); - let id = search.get('id'); - if (id) { - return {page: Page.DETAILS, extensionId: id}; - } - id = search.get('activity'); - if (id) { - return {page: Page.ACTIVITY_LOG, extensionId: id}; - } - id = search.get('options'); - if (id) { - return {page: Page.DETAILS, extensionId: id, subpage: Dialog.OPTIONS}; - } - id = search.get('errors'); - if (id) { - return {page: Page.ERRORS, extensionId: id}; - } - - if (this.currentPath_ == '/shortcuts') { - return {page: Page.SHORTCUTS}; - } - - return {page: Page.LIST}; - } - - /** - * Function to add subscribers. - * @param {!function(!extensions.PageState)} listener - * @return {number} A numerical ID to be used for removing the listener. - */ - addListener(listener) { - const nextListenerId = this.nextListenerId_++; - this.listeners_.set(nextListenerId, listener); - return nextListenerId; - } + notifyRouteChanged_(newPage) { + this.listeners_.forEach((listener, id) => { + listener(newPage); + }); + } - /** - * Remove a previously registered listener. - * @param {number} id - * @return {boolean} Whether a listener with the given ID was actually found - * and removed. - */ - removeListener(id) { - return this.listeners_.delete(id); + /** + * @param {!PageState} newPage the page to navigate to. + */ + navigateTo(newPage) { + const currentPage = this.getCurrentPage(); + if (currentPage && isPageStateEqual(currentPage, newPage)) { + return; } - /** - * Function to notify subscribers. - * @private - */ - notifyRouteChanged_(newPage) { - this.listeners_.forEach((listener, id) => { - listener(newPage); - }); - } + this.updateHistory(newPage, false /* replaceState */); + this.notifyRouteChanged_(newPage); + } - /** - * @param {!extensions.PageState} newPage the page to navigate to. - */ - navigateTo(newPage) { - const currentPage = this.getCurrentPage(); - if (currentPage && isPageStateEqual(currentPage, newPage)) { - return; - } - - this.updateHistory(newPage, false /* replaceState */); - this.notifyRouteChanged_(newPage); + /** + * @param {!PageState} newPage the page to replace the current + * page with. + */ + replaceWith(newPage) { + this.updateHistory(newPage, true /* replaceState */); + if (this.previousPage_ && isPageStateEqual(this.previousPage_, newPage)) { + // Skip the duplicate history entry. + history.back(); + return; } + this.notifyRouteChanged_(newPage); + } - /** - * @param {!extensions.PageState} newPage the page to replace the current - * page with. - */ - replaceWith(newPage) { - this.updateHistory(newPage, true /* replaceState */); - if (this.previousPage_ && isPageStateEqual(this.previousPage_, newPage)) { - // Skip the duplicate history entry. - history.back(); - return; - } - this.notifyRouteChanged_(newPage); + /** + * Called when a page changes, and pushes state to history to reflect it. + * @param {!PageState} entry + * @param {boolean} replaceState + */ + updateHistory(entry, replaceState) { + let path; + switch (entry.page) { + case Page.LIST: + path = '/'; + break; + case Page.ACTIVITY_LOG: + path = '/?activity=' + entry.extensionId; + break; + case Page.DETAILS: + if (entry.subpage) { + assert(entry.subpage == Dialog.OPTIONS); + path = '/?options=' + entry.extensionId; + } else { + path = '/?id=' + entry.extensionId; + } + break; + case Page.SHORTCUTS: + path = '/shortcuts'; + break; + case Page.ERRORS: + path = '/?errors=' + entry.extensionId; + break; } - - /** - * Called when a page changes, and pushes state to history to reflect it. - * @param {!extensions.PageState} entry - * @param {boolean} replaceState - */ - updateHistory(entry, replaceState) { - let path; - switch (entry.page) { - case Page.LIST: - path = '/'; - break; - case Page.ACTIVITY_LOG: - path = '/?activity=' + entry.extensionId; - break; - case Page.DETAILS: - if (entry.subpage) { - assert(entry.subpage == Dialog.OPTIONS); - path = '/?options=' + entry.extensionId; - } else { - path = '/?id=' + entry.extensionId; - } - break; - case Page.SHORTCUTS: - path = '/shortcuts'; - break; - case Page.ERRORS: - path = '/?errors=' + entry.extensionId; - break; - } - assert(path); - const state = {url: path}; - const currentPage = this.getCurrentPage(); - const isDialogNavigation = currentPage.page == entry.page && - currentPage.extensionId == entry.extensionId; - // Navigating to a dialog doesn't visually change pages; it just opens - // a dialog. As such, we replace state rather than pushing a new state - // on the stack so that hitting the back button doesn't just toggle the - // dialog. - if (replaceState || isDialogNavigation) { - history.replaceState(state, '', path); - } else { - this.previousPage_ = currentPage; - history.pushState(state, '', path); - } + assert(path); + const state = {url: path}; + const currentPage = this.getCurrentPage(); + const isDialogNavigation = currentPage.page == entry.page && + currentPage.extensionId == entry.extensionId; + // Navigating to a dialog doesn't visually change pages; it just opens + // a dialog. As such, we replace state rather than pushing a new state + // on the stack so that hitting the back button doesn't just toggle the + // dialog. + if (replaceState || isDialogNavigation) { + history.replaceState(state, '', path); + } else { + this.previousPage_ = currentPage; + history.pushState(state, '', path); } } +} - const navigation = new NavigationHelper(); - - return { - Dialog: Dialog, - // Constructor exposed for testing purposes. - NavigationHelper: NavigationHelper, - navigation: navigation, - Page: Page, - PageState: PageState, - }; -}); +export const navigation = new NavigationHelper(); diff --git a/chromium/chrome/browser/resources/extensions/options_dialog.html b/chromium/chrome/browser/resources/extensions/options_dialog.html index 77f5d941e0f..2319d0cdb9d 100644 --- a/chromium/chrome/browser/resources/extensions/options_dialog.html +++ b/chromium/chrome/browser/resources/extensions/options_dialog.html @@ -1,81 +1,68 @@ -<link rel="import" href="chrome://resources/html/polymer.html"> +<style> + #icon { + height: 32px; + margin-inline-end: 10px; + width: 32px; + } -<link rel="import" href="chrome://resources/cr_elements/cr_dialog/cr_dialog.html"> -<link rel="import" href="chrome://resources/html/assert.html"> -<link rel="import" href="chrome://resources/html/cr.html"> -<link rel="import" href="item_behavior.html"> -<link rel="import" href="navigation_helper.html"> + #icon-and-name-wrapper { + align-items: center; + display: flex; + } -<dom-module id="extensions-options-dialog"> - <template> - <style> - #icon { - height: 32px; - margin-inline-end: 10px; - width: 32px; - } + ExtensionOptions { + display: block; + height: 100%; + overflow: hidden; + } - #icon-and-name-wrapper { - align-items: center; - display: flex; - } + cr-dialog::part(dialog) { + /* CSS variables are set by the JS. */ + height: var(--dialog-height); + opacity: var(--dialog-opacity, 0); + /* When loading, it's possible for an size update to follow after the + initial size update. The debounce time on size updates is 50ms. + A 100ms delay for the opacity transition will allow two updates to + occur without showing the dialog resizing to the user. */ + transition: opacity 100ms ease 100ms; + width: var(--dialog-width); + } - ExtensionOptions { - display: block; - height: 100%; - overflow: hidden; - } + cr-dialog::part(wrapper) { + height: 100%; + max-height: initial; + overflow: hidden; + } - cr-dialog::part(dialog) { - /* CSS variables are set by the JS. */ - height: var(--dialog-height); - opacity: var(--dialog-opacity, 0); - /* When loading, it's possible for an size update to follow after the - initial size update. The debounce time on size updates is 50ms. - A 100ms delay for the opacity transition will allow two updates to - occur without showing the dialog resizing to the user. */ - transition: opacity 100ms ease 100ms; - width: var(--dialog-width); - } + cr-dialog #body { + height: 100%; + padding: 0; + } - cr-dialog::part(wrapper) { - height: 100%; - max-height: initial; - overflow: hidden; - } + cr-dialog { + --cr-dialog-body-border-bottom: none; + --cr-dialog-body-border-top: none; + --scroll-border: none; + } - cr-dialog #body { - height: 100%; - padding: 0; - } + cr-dialog::part(body-container) { + height: 100%; + min-height: initial; + } +</style> - cr-dialog { - --cr-dialog-body-border-bottom: none; - --cr-dialog-body-border-top: none; - --scroll-border: none; - } - - cr-dialog::part(body-container) { - height: 100%; - min-height: initial; - } - </style> - - <cr-dialog id="dialog" close-text="$i18n{close}" - on-close="onClose_" show-close-button> - <div slot="title"> - <div id="icon-and-name-wrapper"> - <img id="icon" src="[[data_.iconUrl]]" - alt$="[[appOrExtension( - data.type, - '$i18nPolymer{appIcon}', - '$i18nPolymer{extensionIcon}')]]"> - <span>[[data_.name]]</span> - </div> - </div> - <div slot="body" id="body"> - </div> - </cr-dialog> - </template> - <script src="options_dialog.js"></script> -</dom-module> +<cr-dialog id="dialog" close-text="$i18n{close}" + on-close="onClose_" show-close-button> + <div slot="title"> + <div id="icon-and-name-wrapper"> + <img id="icon" src="[[data_.iconUrl]]" + alt$="[[appOrExtension( + data.type, + '$i18nPolymer{appIcon}', + '$i18nPolymer{extensionIcon}')]]"> + <span>[[data_.name]]</span> + </div> + </div> + <div slot="body" id="body"> + </div> +</cr-dialog> diff --git a/chromium/chrome/browser/resources/extensions/options_dialog.js b/chromium/chrome/browser/resources/extensions/options_dialog.js index 6e5279922d2..ce9aa267217 100644 --- a/chromium/chrome/browser/resources/extensions/options_dialog.js +++ b/chromium/chrome/browser/resources/extensions/options_dialog.js @@ -2,132 +2,130 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -cr.define('extensions', function() { - 'use strict'; +import 'chrome://resources/cr_elements/cr_dialog/cr_dialog.m.js'; - /** - * @return {!Promise} A signal that the document is ready. Need to wait for - * this, otherwise the custom ExtensionOptions element might not have been - * registered yet. - */ - function whenDocumentReady() { - if (document.readyState == 'complete') { - return Promise.resolve(); - } +import {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; - return new Promise(function(resolve) { - document.addEventListener('readystatechange', function f() { - if (document.readyState == 'complete') { - document.removeEventListener('readystatechange', f); - resolve(); - } - }); - }); +import {ItemBehavior} from './item_behavior.js'; +import {navigation, Page} from './navigation_helper.js'; + +/** + * @return {!Promise} A signal that the document is ready. Need to wait for + * this, otherwise the custom ExtensionOptions element might not have been + * registered yet. + */ +function whenDocumentReady() { + if (document.readyState == 'complete') { + return Promise.resolve(); } - // The minimum width in pixels for the options dialog. - const MIN_WIDTH = 400; - - // The maximum height in pixels for the options dialog. - const MAX_HEIGHT = 640; - - const OptionsDialog = Polymer({ - is: 'extensions-options-dialog', - - behaviors: [extensions.ItemBehavior], - - properties: { - /** @private {Object} */ - extensionOptions_: Object, - - /** @private {chrome.developerPrivate.ExtensionInfo} */ - data_: Object, - }, - - /** @private {?Function} */ - boundUpdateDialogSize_: null, - - /** @private {?{height: number, width: number}} */ - preferredSize_: null, - - get open() { - return this.$.dialog.open; - }, - - /** - * Resizes the dialog to the width/height stored in |preferredSize_|, taking - * into account the window width/height. - * @private - */ - updateDialogSize_: function() { - const headerHeight = this.$.body.offsetTop; - const maxHeight = Math.min(0.9 * window.innerHeight, MAX_HEIGHT); - const effectiveHeight = - Math.min(maxHeight, headerHeight + this.preferredSize_.height); - const effectiveWidth = Math.max(MIN_WIDTH, this.preferredSize_.width); - - this.$.dialog.style.setProperty( - '--dialog-height', `${effectiveHeight}px`); - this.$.dialog.style.setProperty('--dialog-width', `${effectiveWidth}px`); - this.$.dialog.style.setProperty('--dialog-opacity', 1); - }, - - /** @param {chrome.developerPrivate.ExtensionInfo} data */ - show: function(data) { - this.data_ = data; - whenDocumentReady().then(() => { - if (!this.extensionOptions_) { - this.extensionOptions_ = document.createElement('ExtensionOptions'); - } - this.extensionOptions_.extension = this.data_.id; - this.extensionOptions_.onclose = () => this.$.dialog.close(); - - const boundUpdateDialogSize = this.updateDialogSize_.bind(this); - this.boundUpdateDialogSize_ = boundUpdateDialogSize; - this.extensionOptions_.onpreferredsizechanged = e => { - if (!this.$.dialog.open) { - this.$.dialog.showModal(); - } - this.preferredSize_ = e; - this.debounce('updateDialogSize_', boundUpdateDialogSize, 50); - }; - - // Add a 'resize' such that the dialog is resized when window size - // changes. - window.addEventListener('resize', this.boundUpdateDialogSize_); - this.$.body.appendChild(this.extensionOptions_); - }); - }, - - /** @private */ - onClose_: function() { - this.extensionOptions_.onpreferredsizechanged = null; - - if (this.boundUpdateDialogSize_) { - window.removeEventListener('resize', this.boundUpdateDialogSize_); - this.boundUpdateDialogSize_ = null; + return new Promise(function(resolve) { + document.addEventListener('readystatechange', function f() { + if (document.readyState == 'complete') { + document.removeEventListener('readystatechange', f); + resolve(); } + }); + }); +} + +// The minimum width in pixels for the options dialog. +export const OptionsDialogMinWidth = 400; + +// The maximum height in pixels for the options dialog. +export const OptionsDialogMaxHeight = 640; + +Polymer({ + is: 'extensions-options-dialog', + + _template: html`{__html_template__}`, + + behaviors: [ItemBehavior], - const currentPage = extensions.navigation.getCurrentPage(); - // We update the page when the options dialog closes, but only if we're - // still on the details page. We could be on a different page if the - // user hit back while the options dialog was visible; in that case, the - // new page is already correct. - if (currentPage && currentPage.page == extensions.Page.DETAILS) { - // This will update the currentPage_ and the NavigationHelper; since - // the active page is already the details page, no main page - // transition occurs. - extensions.navigation.navigateTo({ - page: extensions.Page.DETAILS, - extensionId: currentPage.extensionId - }); + properties: { + /** @private {Object} */ + extensionOptions_: Object, + + /** @private {chrome.developerPrivate.ExtensionInfo} */ + data_: Object, + }, + + /** @private {?Function} */ + boundUpdateDialogSize_: null, + + /** @private {?{height: number, width: number}} */ + preferredSize_: null, + + get open() { + return this.$.dialog.open; + }, + + /** + * Resizes the dialog to the width/height stored in |preferredSize_|, taking + * into account the window width/height. + * @private + */ + updateDialogSize_: function() { + const headerHeight = this.$.body.offsetTop; + const maxHeight = + Math.min(0.9 * window.innerHeight, OptionsDialogMaxHeight); + const effectiveHeight = + Math.min(maxHeight, headerHeight + this.preferredSize_.height); + const effectiveWidth = + Math.max(OptionsDialogMinWidth, this.preferredSize_.width); + + this.$.dialog.style.setProperty('--dialog-height', `${effectiveHeight}px`); + this.$.dialog.style.setProperty('--dialog-width', `${effectiveWidth}px`); + this.$.dialog.style.setProperty('--dialog-opacity', '1'); + }, + + /** @param {chrome.developerPrivate.ExtensionInfo} data */ + show: function(data) { + this.data_ = data; + whenDocumentReady().then(() => { + if (!this.extensionOptions_) { + this.extensionOptions_ = document.createElement('ExtensionOptions'); } - }, - }); + this.extensionOptions_.extension = this.data_.id; + this.extensionOptions_.onclose = () => this.$.dialog.close(); + + const boundUpdateDialogSize = this.updateDialogSize_.bind(this); + this.boundUpdateDialogSize_ = boundUpdateDialogSize; + this.extensionOptions_.onpreferredsizechanged = e => { + if (!this.$.dialog.open) { + this.$.dialog.showModal(); + } + this.preferredSize_ = e; + this.debounce('updateDialogSize_', boundUpdateDialogSize, 50); + }; + + // Add a 'resize' such that the dialog is resized when window size + // changes. + window.addEventListener('resize', this.boundUpdateDialogSize_); + this.$.body.appendChild(/** @type {Node} */ (this.extensionOptions_)); + }); + }, + + /** @private */ + onClose_: function() { + this.extensionOptions_.onpreferredsizechanged = null; - return { - OptionsDialog: OptionsDialog, - OptionsDialogMinWidth: MIN_WIDTH, - OptionsDialogMaxHeight: MAX_HEIGHT, - }; + if (this.boundUpdateDialogSize_) { + window.removeEventListener('resize', this.boundUpdateDialogSize_); + this.boundUpdateDialogSize_ = null; + } + + const currentPage = navigation.getCurrentPage(); + // We update the page when the options dialog closes, but only if we're + // still on the details page. We could be on a different page if the + // user hit back while the options dialog was visible; in that case, the + // new page is already correct. + if (currentPage && currentPage.page == Page.DETAILS) { + // This will update the currentPage_ and the NavigationHelper; since + // the active page is already the details page, no main page + // transition occurs. + navigation.navigateTo( + {page: Page.DETAILS, extensionId: currentPage.extensionId}); + } + }, }); diff --git a/chromium/chrome/browser/resources/extensions/pack_dialog.html b/chromium/chrome/browser/resources/extensions/pack_dialog.html index 6718e23fec0..e945104391a 100644 --- a/chromium/chrome/browser/resources/extensions/pack_dialog.html +++ b/chromium/chrome/browser/resources/extensions/pack_dialog.html @@ -1,66 +1,49 @@ -<link rel="import" href="chrome://resources/html/polymer.html"> +<style include="cr-shared-style"> + cr-input { + margin-top: var(--cr-form-field-bottom-spacing); + --cr-input-error-display: none; + } -<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html"> -<link rel="import" href="chrome://resources/cr_elements/cr_dialog/cr_dialog.html"> -<link rel="import" href="chrome://resources/cr_elements/cr_input/cr_input.html"> -<link rel="import" href="chrome://resources/cr_elements/shared_style_css.html"> -<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html"> -<link rel="import" href="chrome://resources/html/assert.html"> -<link rel="import" href="chrome://resources/html/cr.html"> -<link rel="import" href="chrome://resources/html/util.html"> -<link rel="import" href="pack_dialog_alert.html"> + cr-button[slot='suffix'] { + margin-inline-start: 10px; + } -<dom-module id="extensions-pack-dialog"> - <template> - <style include="cr-shared-style"> - cr-input { - margin-top: var(--cr-form-field-bottom-spacing); - --cr-input-error-display: none; - } - - cr-button[slot='suffix'] { - margin-inline-start: 10px; - } - - /* Prevent focus-outline from being chopped by bottom of dialog body. */ - cr-input { - margin-bottom: 2px; - } - </style> - <cr-dialog id="dialog" close-text="$i18n{close}"> - <div slot="title">$i18n{packDialogTitle}</div> - <div slot="body"> - <div>$i18n{packDialogContent}</div> - <cr-input id="root-dir" label="$i18n{packDialogExtensionRoot}" - value="{{packDirectory_}}"> - <cr-button id="root-dir-browse" on-click="onRootBrowse_" - slot="suffix"> - $i18n{packDialogBrowse} - </cr-button> - </cr-input> - <cr-input id="key-file" label="$i18n{packDialogKeyFile}" - value="{{keyFile_}}"> - <cr-button id="key-file-browse" on-click="onKeyBrowse_" - slot="suffix"> - $i18n{packDialogBrowse} - </cr-button> - </cr-input> - </div> - <div slot="button-container"> - <cr-button class="cancel-button" on-click="onCancelTap_"> - $i18n{cancel} - </cr-button> - <cr-button class="action-button" on-click="onConfirmTap_" - disabled="[[!packDirectory_]]"> - $i18n{packDialogConfirm} - </cr-button> - </div> - </cr-dialog> - <template is="dom-if" if="[[lastResponse_]]" restamp> - <extensions-pack-dialog-alert model="[[lastResponse_]]" - on-close="onAlertClose_"> - </extensions-pack-dialog-alert> - </template> - </template> - <script src="pack_dialog.js"></script> -</dom-module> + /* Prevent focus-outline from being chopped by bottom of dialog body. */ + cr-input { + margin-bottom: 2px; + } +</style> +<cr-dialog id="dialog" close-text="$i18n{close}"> + <div slot="title">$i18n{packDialogTitle}</div> + <div slot="body"> + <div>$i18n{packDialogContent}</div> + <cr-input id="root-dir" label="$i18n{packDialogExtensionRoot}" + value="{{packDirectory_}}"> + <cr-button id="root-dir-browse" on-click="onRootBrowse_" + slot="suffix"> + $i18n{packDialogBrowse} + </cr-button> + </cr-input> + <cr-input id="key-file" label="$i18n{packDialogKeyFile}" + value="{{keyFile_}}"> + <cr-button id="key-file-browse" on-click="onKeyBrowse_" + slot="suffix"> + $i18n{packDialogBrowse} + </cr-button> + </cr-input> + </div> + <div slot="button-container"> + <cr-button class="cancel-button" on-click="onCancelTap_"> + $i18n{cancel} + </cr-button> + <cr-button class="action-button" on-click="onConfirmTap_" + disabled="[[!packDirectory_]]"> + $i18n{packDialogConfirm} + </cr-button> + </div> +</cr-dialog> +<template is="dom-if" if="[[lastResponse_]]" restamp> + <extensions-pack-dialog-alert model="[[lastResponse_]]" + on-close="onAlertClose_"> + </extensions-pack-dialog-alert> +</template> diff --git a/chromium/chrome/browser/resources/extensions/pack_dialog.js b/chromium/chrome/browser/resources/extensions/pack_dialog.js index 720d86942a4..4791d6ba9eb 100644 --- a/chromium/chrome/browser/resources/extensions/pack_dialog.js +++ b/chromium/chrome/browser/resources/extensions/pack_dialog.js @@ -2,126 +2,132 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -cr.define('extensions', function() { - 'use strict'; - - /** @interface */ - class PackDialogDelegate { - /** - * Opens a file browser for the user to select the root directory. - * @return {Promise<string>} A promise that is resolved with the path the - * user selected. - */ - choosePackRootDirectory() {} - - /** - * Opens a file browser for the user to select the private key file. - * @return {Promise<string>} A promise that is resolved with the path the - * user selected. - */ - choosePrivateKeyPath() {} - - /** - * Packs the extension into a .crx. - * @param {string} rootPath - * @param {string} keyPath - * @param {number=} flag - * @param {function(chrome.developerPrivate.PackDirectoryResponse)=} - * callback - */ - packExtension(rootPath, keyPath, flag, callback) {} - } - - const PackDialog = Polymer({ - is: 'extensions-pack-dialog', - properties: { - /** @type {extensions.PackDialogDelegate} */ - delegate: Object, - - /** @private */ - packDirectory_: { - type: String, - value: '', // Initialized to trigger binding when attached. - }, - - /** @private */ - keyFile_: String, - - /** @private {?chrome.developerPrivate.PackDirectoryResponse} */ - lastResponse_: Object, - }, - - /** @override */ - attached: function() { - this.$.dialog.showModal(); - }, - - /** @private */ - onRootBrowse_: function() { - this.delegate.choosePackRootDirectory().then(path => { - if (path) { - this.set('packDirectory_', path); - } - }); - }, - - /** @private */ - onKeyBrowse_: function() { - this.delegate.choosePrivateKeyPath().then(path => { - if (path) { - this.set('keyFile_', path); - } - }); - }, +import 'chrome://resources/cr_elements/cr_button/cr_button.m.js'; +import 'chrome://resources/cr_elements/cr_dialog/cr_dialog.m.js'; +import 'chrome://resources/cr_elements/cr_input/cr_input.m.js'; +import 'chrome://resources/cr_elements/shared_style_css.m.js'; +import 'chrome://resources/cr_elements/shared_vars_css.m.js'; +import './pack_dialog_alert.js'; +import './strings.m.js'; + +import {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; + +/** @interface */ +export class PackDialogDelegate { + /** + * Opens a file browser for the user to select the root directory. + * @return {Promise<string>} A promise that is resolved with the path the + * user selected. + */ + choosePackRootDirectory() {} + + /** + * Opens a file browser for the user to select the private key file. + * @return {Promise<string>} A promise that is resolved with the path the + * user selected. + */ + choosePrivateKeyPath() {} + + /** + * Packs the extension into a .crx. + * @param {string} rootPath + * @param {string} keyPath + * @param {number=} flag + * @param {function(chrome.developerPrivate.PackDirectoryResponse)=} + * callback + */ + packExtension(rootPath, keyPath, flag, callback) {} +} + +Polymer({ + is: 'extensions-pack-dialog', + + _template: html`{__html_template__}`, + + properties: { + /** @type {PackDialogDelegate} */ + delegate: Object, /** @private */ - onCancelTap_: function() { - this.$.dialog.cancel(); + packDirectory_: { + type: String, + value: '', // Initialized to trigger binding when attached. }, /** @private */ - onConfirmTap_: function() { - this.delegate.packExtension( - this.packDirectory_, this.keyFile_, 0, - this.onPackResponse_.bind(this)); - }, - - /** - * @param {chrome.developerPrivate.PackDirectoryResponse} response the - * response from request to pack an extension. - * @private - */ - onPackResponse_: function(response) { - this.lastResponse_ = response; - }, - - /** - * In the case that the alert dialog was a success message, the entire - * pack-dialog should close. Otherwise, we detach the alert by setting - * lastResponse_ null. Additionally, if the user selected "proceed anyway" - * in the dialog, we pack the extension again with override flags. - * @param {!Event} e - * @private - */ - onAlertClose_: function(e) { - e.stopPropagation(); - - if (this.lastResponse_.status == - chrome.developerPrivate.PackStatus.SUCCESS) { - this.$.dialog.close(); - return; + keyFile_: String, + + /** @private {?chrome.developerPrivate.PackDirectoryResponse} */ + lastResponse_: Object, + }, + + /** @override */ + attached: function() { + this.$.dialog.showModal(); + }, + + /** @private */ + onRootBrowse_: function() { + this.delegate.choosePackRootDirectory().then(path => { + if (path) { + this.set('packDirectory_', path); } - - // This is only possible for a warning dialog. - if (this.$$('extensions-pack-dialog-alert').returnValue == 'success') { - this.delegate.packExtension( - this.lastResponse_.item_path, this.lastResponse_.pem_path, - this.lastResponse_.override_flags, this.onPackResponse_.bind(this)); + }); + }, + + /** @private */ + onKeyBrowse_: function() { + this.delegate.choosePrivateKeyPath().then(path => { + if (path) { + this.set('keyFile_', path); } + }); + }, + + /** @private */ + onCancelTap_: function() { + this.$.dialog.cancel(); + }, + + /** @private */ + onConfirmTap_: function() { + this.delegate.packExtension( + this.packDirectory_, this.keyFile_, 0, this.onPackResponse_.bind(this)); + }, + + /** + * @param {chrome.developerPrivate.PackDirectoryResponse} response the + * response from request to pack an extension. + * @private + */ + onPackResponse_: function(response) { + this.lastResponse_ = response; + }, + + /** + * In the case that the alert dialog was a success message, the entire + * pack-dialog should close. Otherwise, we detach the alert by setting + * lastResponse_ null. Additionally, if the user selected "proceed anyway" + * in the dialog, we pack the extension again with override flags. + * @param {!Event} e + * @private + */ + onAlertClose_: function(e) { + e.stopPropagation(); + + if (this.lastResponse_.status == + chrome.developerPrivate.PackStatus.SUCCESS) { + this.$.dialog.close(); + return; + } + + // This is only possible for a warning dialog. + if (this.$$('extensions-pack-dialog-alert').returnValue == 'success') { + this.delegate.packExtension( + this.lastResponse_.item_path, this.lastResponse_.pem_path, + this.lastResponse_.override_flags, this.onPackResponse_.bind(this)); + } - this.lastResponse_ = null; - }, - }); - - return {PackDialog: PackDialog, PackDialogDelegate: PackDialogDelegate}; + this.lastResponse_ = null; + }, }); diff --git a/chromium/chrome/browser/resources/extensions/pack_dialog_alert.html b/chromium/chrome/browser/resources/extensions/pack_dialog_alert.html index a08e9feb5e0..79fc2f76efc 100644 --- a/chromium/chrome/browser/resources/extensions/pack_dialog_alert.html +++ b/chromium/chrome/browser/resources/extensions/pack_dialog_alert.html @@ -1,35 +1,22 @@ -<link rel="import" href="chrome://resources/html/polymer.html"> +<style include="cr-shared-style"> + .body { + white-space: pre-wrap; + word-break: break-word; + } +</style> -<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html"> -<link rel="import" href="chrome://resources/cr_elements/cr_dialog/cr_dialog.html"> -<link rel="import" href="chrome://resources/cr_elements/shared_style_css.html"> -<link rel="import" href="chrome://resources/html/assert.html"> -<link rel="import" href="chrome://resources/html/cr.html"> - -<dom-module id="extensions-pack-dialog-alert"> - <template> - <style include="cr-shared-style"> - .body { - white-space: pre-wrap; - word-break: break-word; - } - </style> - - <cr-dialog id="dialog" close-text="$i18n{close}"> - <div class="title" slot="title">[[title_]]</div> - <!-- No whitespace or new-lines allowed within the div.body tag. --> - <div class="body" slot="body">[[model.message]]</div> - <div class="button-container" slot="button-container"> - <cr-button class$="[[getCancelButtonClass_(confirmLabel_)]]" - on-click="onCancelTap_" hidden="[[!cancelLabel_]]"> - [[cancelLabel_]] - </cr-button> - <cr-button class="action-button" on-click="onConfirmTap_" - hidden="[[!confirmLabel_]]"> - [[confirmLabel_]] - </cr-button> - </div> - </cr-dialog> - </template> - <script src="pack_dialog_alert.js"></script> -</dom-module> +<cr-dialog id="dialog" close-text="$i18n{close}"> + <div class="title" slot="title">[[title_]]</div> + <!-- No whitespace or new-lines allowed within the div.body tag. --> + <div class="body" slot="body">[[model.message]]</div> + <div class="button-container" slot="button-container"> + <cr-button class$="[[getCancelButtonClass_(confirmLabel_)]]" + on-click="onCancelTap_" hidden="[[!cancelLabel_]]"> + [[cancelLabel_]] + </cr-button> + <cr-button class="action-button" on-click="onConfirmTap_" + hidden="[[!confirmLabel_]]"> + [[confirmLabel_]] + </cr-button> + </div> +</cr-dialog> diff --git a/chromium/chrome/browser/resources/extensions/pack_dialog_alert.js b/chromium/chrome/browser/resources/extensions/pack_dialog_alert.js index 9b33668c7ca..a39869e2f70 100644 --- a/chromium/chrome/browser/resources/extensions/pack_dialog_alert.js +++ b/chromium/chrome/browser/resources/extensions/pack_dialog_alert.js @@ -2,93 +2,96 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -cr.define('extensions', function() { - 'use strict'; +import 'chrome://resources/cr_elements/cr_button/cr_button.m.js'; +import 'chrome://resources/cr_elements/cr_dialog/cr_dialog.m.js'; +import 'chrome://resources/cr_elements/shared_style_css.m.js'; +import {assert, assertNotReached} from 'chrome://resources/js/assert.m.js'; +import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js'; +import {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; - const PackDialogAlert = Polymer({ - is: 'extensions-pack-dialog-alert', - properties: { - /** @private {chrome.developerPrivate.PackDirectoryResponse} */ - model: Object, +Polymer({ + is: 'extensions-pack-dialog-alert', - /** @private */ - title_: String, + _template: html`{__html_template__}`, - /** @private */ - message_: String, + properties: { + /** @private {chrome.developerPrivate.PackDirectoryResponse} */ + model: Object, - /** @private {?string} */ - cancelLabel_: String, - - /** - * This needs to be initialized to trigger data-binding. - * @private {?string} - */ - confirmLabel_: { - type: String, - value: '', - } - }, - - /** @return {string} */ - get returnValue() { - return /** @type {!CrDialogElement} */ (this.$.dialog) - .getNative() - .returnValue; - }, - - /** @override */ - ready: function() { - // Initialize button label values for initial html binding. - this.cancelLabel_ = null; - this.confirmLabel_ = null; + /** @private */ + title_: String, - switch (this.model.status) { - case chrome.developerPrivate.PackStatus.WARNING: - this.title_ = loadTimeData.getString('packDialogWarningTitle'); - this.cancelLabel_ = loadTimeData.getString('cancel'); - this.confirmLabel_ = - loadTimeData.getString('packDialogProceedAnyway'); - break; - case chrome.developerPrivate.PackStatus.ERROR: - this.title_ = loadTimeData.getString('packDialogErrorTitle'); - this.cancelLabel_ = loadTimeData.getString('ok'); - break; - case chrome.developerPrivate.PackStatus.SUCCESS: - this.title_ = loadTimeData.getString('packDialogTitle'); - this.cancelLabel_ = loadTimeData.getString('ok'); - break; - default: - assertNotReached(); - return; - } - }, + /** @private */ + message_: String, - /** @override */ - attached: function() { - this.$.dialog.showModal(); - }, + /** @private {?string} */ + cancelLabel_: String, /** - * @return {string} - * @private + * This needs to be initialized to trigger data-binding. + * @private {?string} */ - getCancelButtonClass_: function() { - return this.confirmLabel_ ? 'cancel-button' : 'action-button'; - }, + confirmLabel_: { + type: String, + value: '', + } + }, - /** @private */ - onCancelTap_: function() { - this.$.dialog.cancel(); - }, + /** @return {string} */ + get returnValue() { + return /** @type {!CrDialogElement} */ (this.$.dialog) + .getNative() + .returnValue; + }, - /** @private */ - onConfirmTap_: function() { - // The confirm button should only be available in WARNING state. - assert(this.model.status === chrome.developerPrivate.PackStatus.WARNING); - this.$.dialog.close(); + /** @override */ + ready: function() { + // Initialize button label values for initial html binding. + this.cancelLabel_ = null; + this.confirmLabel_ = null; + + switch (this.model.status) { + case chrome.developerPrivate.PackStatus.WARNING: + this.title_ = loadTimeData.getString('packDialogWarningTitle'); + this.cancelLabel_ = loadTimeData.getString('cancel'); + this.confirmLabel_ = loadTimeData.getString('packDialogProceedAnyway'); + break; + case chrome.developerPrivate.PackStatus.ERROR: + this.title_ = loadTimeData.getString('packDialogErrorTitle'); + this.cancelLabel_ = loadTimeData.getString('ok'); + break; + case chrome.developerPrivate.PackStatus.SUCCESS: + this.title_ = loadTimeData.getString('packDialogTitle'); + this.cancelLabel_ = loadTimeData.getString('ok'); + break; + default: + assertNotReached(); + return; } - }); + }, + + /** @override */ + attached: function() { + this.$.dialog.showModal(); + }, + + /** + * @return {string} + * @private + */ + getCancelButtonClass_: function() { + return this.confirmLabel_ ? 'cancel-button' : 'action-button'; + }, + + /** @private */ + onCancelTap_: function() { + this.$.dialog.cancel(); + }, - return {PackDialogAlert: PackDialogAlert}; + /** @private */ + onConfirmTap_: function() { + // The confirm button should only be available in WARNING state. + assert(this.model.status === chrome.developerPrivate.PackStatus.WARNING); + this.$.dialog.close(); + } }); diff --git a/chromium/chrome/browser/resources/extensions/runtime_host_permissions.html b/chromium/chrome/browser/resources/extensions/runtime_host_permissions.html index 038343a0cdb..390e3e8c363 100644 --- a/chromium/chrome/browser/resources/extensions/runtime_host_permissions.html +++ b/chromium/chrome/browser/resources/extensions/runtime_host_permissions.html @@ -1,138 +1,115 @@ -<link rel="import" href="chrome://resources/html/polymer.html"> +<style include="cr-shared-style action-link md-select shared-style"> + iron-icon { + --iron-icon-height: var(--cr-icon-size); + --iron-icon-width: var(--cr-icon-size); + } -<link rel="import" href="chrome://resources/cr_elements/cr_action_menu/cr_action_menu.html"> -<link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html"> -<link rel="import" href="chrome://resources/cr_elements/cr_radio_group/cr_radio_group.html"> -<link rel="import" href="chrome://resources/cr_elements/cr_radio_button/cr_radio_button.html"> -<link rel="import" href="chrome://resources/cr_elements/icons.html"> -<link rel="import" href="chrome://resources/cr_elements/shared_style_css.html"> -<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html"> -<link rel="import" href="chrome://resources/html/action_link.html"> -<link rel="import" href="chrome://resources/cr_elements/action_link_css.html"> -<link rel="import" href="chrome://resources/html/cr.html"> -<link rel="import" href="chrome://resources/html/cr/ui/focus_without_ink.html"> -<link rel="import" href="chrome://resources/cr_elements/md_select_css.html"> -<link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html"> -<link rel="import" href="runtime_hosts_dialog.html"> -<link rel="import" href="shared_style.html"> -<link rel="import" href="strings.html"> + #section-heading { + align-items: center; + display: flex; + justify-content: space-between; + } -<dom-module id="extensions-runtime-host-permissions"> - <template> - <style include="cr-shared-style action-link md-select shared-style"> - iron-icon { - --iron-icon-height: var(--cr-icon-size); - --iron-icon-width: var(--cr-icon-size); - } + #host-access { + margin-inline-start: 20px; + width: 100%; + } - #section-heading { - align-items: center; - display: flex; - justify-content: space-between; - } + #hosts { + margin-bottom: 0; + padding: 0; + } - #host-access { - margin-inline-start: 20px; - width: 100%; - } + #hosts li { + align-items: center; + border-bottom: var(--cr-separator-line); + display: flex; + height: var(--cr-section-min-height); + justify-content: space-between; + } - #hosts { - margin-bottom: 0; - padding: 0; - } + #hosts li:last-child { + border-bottom: none; + } - #hosts li { - align-items: center; - border-bottom: var(--cr-separator-line); - display: flex; - height: var(--cr-section-min-height); - justify-content: space-between; - } + #add-host { + font-weight: 500; + width: 100%; + } - #hosts li:last-child { - border-bottom: none; - } + #permissions-mode { + color: var(--cr-primary-text-color); + margin-top: 12px; + } - #add-host { - font-weight: 500; - width: 100%; - } + cr-radio-button.multi-row { + align-items: normal; + } - #permissions-mode { - color: var(--cr-primary-text-color); - margin-top: 12px; - } - - cr-radio-button.multi-row { - align-items: normal; - } - - cr-icon-button { - margin: 0; - } - </style> - <div id="permissions-mode"> - <div id="section-heading"> - <span>$i18n{hostPermissionsHeading}</span> - <a class="link-icon-button" aria-label="$i18n{learnMore}" - href="$i18n{hostPermissionsLearnMoreLink}" target="_blank"> - <iron-icon icon="cr:help-outline"></iron-icon> - </a> + cr-icon-button { + margin: 0; + } +</style> +<div id="permissions-mode"> + <div id="section-heading"> + <span>$i18n{hostPermissionsHeading}</span> + <a class="link-icon-button" aria-label="$i18n{learnMore}" + href="$i18n{hostPermissionsLearnMoreLink}" target="_blank"> + <iron-icon icon="cr:help-outline"></iron-icon> + </a> + </div> + <cr-radio-group id="host-access" selected="[[permissions.hostAccess]]" + on-selected-changed="onHostAccessChange_"> + <cr-radio-button name="[[HostAccess_.ON_CLICK]]"> + $i18n{hostAccessOnClick} + </cr-radio-button> + <cr-radio-button name="[[HostAccess_.ON_SPECIFIC_SITES]]" + class="multi-row"> + <div> + $i18n{hostAccessOnSpecificSites} </div> - <cr-radio-group id="host-access" selected="[[permissions.hostAccess]]" - on-selected-changed="onHostAccessChange_"> - <cr-radio-button name="[[HostAccess_.ON_CLICK]]"> - $i18n{hostAccessOnClick} - </cr-radio-button> - <cr-radio-button name="[[HostAccess_.ON_SPECIFIC_SITES]]" - class="multi-row"> - <div> - $i18n{hostAccessOnSpecificSites} - </div> - <template is="dom-if" if="[[showSpecificSites_(permissions.*)]]"> - <ul id="hosts"> - <template is="dom-repeat" - items="[[getRuntimeHosts_(permissions.hosts)]]"> - <li> - <div>[[item]]</div> - <cr-icon-button class="icon-more-vert edit-host" - on-click="onEditHostClick_" - title="$i18n{hostPermissionsEdit}"></cr-icon-button> - </li> - </template> - <li> - <a id="add-host" is="action-link" on-click="onAddHostClick_"> - $i18n{itemSiteAccessAddHost} - </a> - </li> - </ul> + <template is="dom-if" if="[[showSpecificSites_(permissions.*)]]"> + <ul id="hosts"> + <template is="dom-repeat" + items="[[getRuntimeHosts_(permissions.hosts)]]"> + <li> + <div>[[item]]</div> + <cr-icon-button class="icon-more-vert edit-host" + on-click="onEditHostClick_" + title="$i18n{hostPermissionsEdit}"></cr-icon-button> + </li> </template> - </cr-radio-button> - <cr-radio-button name="[[HostAccess_.ON_ALL_SITES]]"> - $i18n{hostAccessOnAllSites} - </cr-radio-button> - </cr-radio-group> - </div> - <cr-action-menu id="hostActionMenu" - on-close="onActionMenuClose_"> - <button class="dropdown-item" id="action-menu-edit" - on-click="onActionMenuEditClick_"> - $i18n{hostPermissionsEdit} - </button> - <button class="dropdown-item" id="action-menu-remove" - on-click="onActionMenuRemoveClick_"> - $i18n{remove} - </button> - </cr-action-menu> - <template is="dom-if" if="[[showHostDialog_]]" restamp> - <extensions-runtime-hosts-dialog - delegate="[[delegate]]" item-id="[[itemId]]" - current-site="[[hostDialogModel_]]" - update-host-access="[[dialogShouldUpdateHostAccess_(oldHostAccess_)]]" - on-close="onHostDialogClose_" - on-cancel="onHostDialogCancel_"> - </extensions-runtime-hosts-dialog> - </template> - </template> - <script src="runtime_host_permissions.js"></script> -</dom-module> + <li> + <a id="add-host" is="action-link" on-click="onAddHostClick_"> + $i18n{itemSiteAccessAddHost} + </a> + </li> + </ul> + </template> + </cr-radio-button> + <cr-radio-button name="[[HostAccess_.ON_ALL_SITES]]"> + $i18n{hostAccessOnAllSites} + </cr-radio-button> + </cr-radio-group> +</div> +<cr-action-menu id="hostActionMenu" + role-description="$i18n{menu}" + on-close="onActionMenuClose_"> + <button class="dropdown-item" id="action-menu-edit" + on-click="onActionMenuEditClick_"> + $i18n{hostPermissionsEdit} + </button> + <button class="dropdown-item" id="action-menu-remove" + on-click="onActionMenuRemoveClick_"> + $i18n{remove} + </button> +</cr-action-menu> +<template is="dom-if" if="[[showHostDialog_]]" restamp> + <extensions-runtime-hosts-dialog + delegate="[[delegate]]" item-id="[[itemId]]" + current-site="[[hostDialogModel_]]" + update-host-access="[[dialogShouldUpdateHostAccess_(oldHostAccess_)]]" + on-close="onHostDialogClose_" + on-cancel="onHostDialogCancel_"> + </extensions-runtime-hosts-dialog> +</template> diff --git a/chromium/chrome/browser/resources/extensions/runtime_host_permissions.js b/chromium/chrome/browser/resources/extensions/runtime_host_permissions.js index 6fbc21ac3e7..1814e423fe8 100644 --- a/chromium/chrome/browser/resources/extensions/runtime_host_permissions.js +++ b/chromium/chrome/browser/resources/extensions/runtime_host_permissions.js @@ -2,250 +2,265 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -cr.define('extensions', function() { - 'use strict'; - - const RuntimeHostPermissions = Polymer({ - is: 'extensions-runtime-host-permissions', - - properties: { - /** - * The underlying permissions data. - * @type {chrome.developerPrivate.RuntimeHostPermissions} - */ - permissions: Object, - - /** @private */ - itemId: String, - - /** @type {!extensions.ItemDelegate} */ - delegate: Object, - - /** - * Whether the dialog to add a new host permission is shown. - * @private - */ - showHostDialog_: Boolean, - - /** - * The current site of the entry that the host dialog is editing, if the - * dialog is open for editing. - * @type {?string} - * @private - */ - hostDialogModel_: { - type: String, - value: null, - }, - - /** - * The element to return focus to once the host dialog closes. - * @type {?HTMLElement} - * @private - */ - hostDialogAnchorElement_: { - type: Object, - value: null, - }, - - /** - * If the action menu is open, the site of the entry it is open for. - * Otherwise null. - * @type {?string} - * @private - */ - actionMenuModel_: { - type: String, - value: null, - }, - - /** - * The element that triggered the action menu, so that the page will - * return focus once the action menu (or dialog) closes. - * @type {?HTMLElement} - * @private - */ - actionMenuAnchorElement_: { - type: Object, - value: null, - }, - - /** - * The old host access setting; used when we don't immediately commit the - * change to host access so that we can reset it if the user cancels. - * @type {?string} - * @private - */ - oldHostAccess_: { - type: String, - value: null, - }, - - /** - * Proxying the enum to be used easily by the html template. - * @private - */ - HostAccess_: { - type: Object, - value: chrome.developerPrivate.HostAccess, - }, - }, +import 'chrome://resources/cr_elements/cr_action_menu/cr_action_menu.m.js'; +import 'chrome://resources/cr_elements/cr_icon_button/cr_icon_button.m.js'; +import 'chrome://resources/cr_elements/cr_radio_group/cr_radio_group.m.js'; +import 'chrome://resources/cr_elements/cr_radio_button/cr_radio_button.m.js'; +import 'chrome://resources/cr_elements/icons.m.js'; +import 'chrome://resources/cr_elements/shared_style_css.m.js'; +import 'chrome://resources/cr_elements/shared_vars_css.m.js'; +import 'chrome://resources/js/action_link.js'; +import 'chrome://resources/cr_elements/action_link_css.m.js'; +import 'chrome://resources/cr_elements/md_select_css.m.js'; +import 'chrome://resources/polymer/v3_0/iron-icon/iron-icon.js'; +import './runtime_hosts_dialog.js'; +import './shared_style.js'; +import './strings.m.js'; + +import {assert} from 'chrome://resources/js/assert.m.js'; +import {focusWithoutInk} from 'chrome://resources/js/cr/ui/focus_without_ink.m.js'; +import {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; + +import {ItemDelegate} from './item.js'; + +Polymer({ + is: 'extensions-runtime-host-permissions', + + _template: html`{__html_template__}`, + properties: { /** - * @param {!Event} event - * @private + * The underlying permissions data. + * @type {chrome.developerPrivate.RuntimeHostPermissions} */ - onHostAccessChange_: function(event) { - const group = this.$['host-access']; - const access = group.selected; - - if (access == chrome.developerPrivate.HostAccess.ON_SPECIFIC_SITES && - this.permissions.hostAccess != - chrome.developerPrivate.HostAccess.ON_SPECIFIC_SITES) { - // If the user is transitioning to the "on specific sites" option, show - // the "add host" dialog. This serves two purposes: - // - The user is prompted to add a host immediately, since otherwise - // "on specific sites" is meaningless, and - // - The way the C++ code differentiates between "on click" and "on - // specific sites" is by checking if there are any specific sites. - // This ensures there will be at least one, so that the host access - // is properly calculated. - this.oldHostAccess_ = this.permissions.hostAccess; - this.doShowHostDialog_(group, null); - } else { - this.delegate.setItemHostAccess(this.itemId, access); - } - }, + permissions: Object, + + /** @private */ + itemId: String, + + /** @type {!ItemDelegate} */ + delegate: Object, /** - * @return {boolean} + * Whether the dialog to add a new host permission is shown. * @private */ - showSpecificSites_: function() { - return this.permissions.hostAccess == - chrome.developerPrivate.HostAccess.ON_SPECIFIC_SITES; - }, + showHostDialog_: Boolean, /** - * Returns the granted host permissions as a sorted set of strings. - * @return {!Array<string>} + * The current site of the entry that the host dialog is editing, if the + * dialog is open for editing. + * @type {?string} * @private */ - getRuntimeHosts_: function() { - if (!this.permissions.hosts) { - return []; - } - - // Only show granted hosts in the list. - // TODO(devlin): For extensions that request a finite set of hosts, - // display them in a toggle list. https://crbug.com/891803. - return this.permissions.hosts.filter(control => control.granted) - .map(control => control.host) - .sort(); + hostDialogModel_: { + type: String, + value: null, }, /** - * @param {Event} e + * The element to return focus to once the host dialog closes. + * @type {?HTMLElement} * @private */ - onAddHostClick_: function(e) { - const target = /** @type {!HTMLElement} */ (e.target); - this.doShowHostDialog_(target, null); + hostDialogAnchorElement_: { + type: Object, + value: null, }, /** - * @param {!HTMLElement} anchorElement The element to return focus to once - * the dialog closes. - * @param {?string} currentSite The site entry currently being - * edited, or null if this is to add a new entry. + * If the action menu is open, the site of the entry it is open for. + * Otherwise null. + * @type {?string} * @private */ - doShowHostDialog_: function(anchorElement, currentSite) { - this.hostDialogAnchorElement_ = anchorElement; - this.hostDialogModel_ = currentSite; - this.showHostDialog_ = true; - }, - - /** @private */ - onHostDialogClose_: function() { - this.hostDialogModel_ = null; - this.showHostDialog_ = false; - cr.ui.focusWithoutInk( - assert(this.hostDialogAnchorElement_, 'Host Anchor')); - this.hostDialogAnchorElement_ = null; - this.oldHostAccess_ = null; + actionMenuModel_: { + type: String, + value: null, }, - /** @private */ - onHostDialogCancel_: function() { - // The user canceled the dialog. Set host-access back to the old value, - // if the dialog was shown when just transitioning to a new state. - if (this.oldHostAccess_) { - assert(this.permissions.hostAccess == this.oldHostAccess_); - this.$['host-access'].selected = this.oldHostAccess_; - this.oldHostAccess_ = null; - } + /** + * The element that triggered the action menu, so that the page will + * return focus once the action menu (or dialog) closes. + * @type {?HTMLElement} + * @private + */ + actionMenuAnchorElement_: { + type: Object, + value: null, }, /** - * @return {boolean} + * The old host access setting; used when we don't immediately commit the + * change to host access so that we can reset it if the user cancels. + * @type {?string} * @private */ - dialogShouldUpdateHostAccess_: function() { - return !!this.oldHostAccess_; + oldHostAccess_: { + type: String, + value: null, }, /** - * @param {!{ - * model: !{item: string}, - * target: !HTMLElement, - * }} e + * Proxying the enum to be used easily by the html template. * @private */ - onEditHostClick_: function(e) { - this.actionMenuModel_ = e.model.item; - this.actionMenuAnchorElement_ = e.target; - const actionMenu = - /** @type {CrActionMenuElement} */ (this.$.hostActionMenu); - actionMenu.showAt(e.target); + HostAccess_: { + type: Object, + value: chrome.developerPrivate.HostAccess, }, + }, - /** @private */ - onActionMenuEditClick_: function() { - // Cache the site before closing the action menu, since it's cleared. - const site = this.actionMenuModel_; - - // Cache and reset actionMenuAnchorElement_ so focus is not returned - // to the action menu's trigger (since the dialog will be shown next). - // Instead, curry the element to the dialog, so once it closes, focus - // will be returned. - const anchorElement = - assert(this.actionMenuAnchorElement_, 'Menu Anchor'); - this.actionMenuAnchorElement_ = null; - this.closeActionMenu_(); - this.doShowHostDialog_(anchorElement, site); - }, + /** + * @param {!Event} event + * @private + */ + onHostAccessChange_: function(event) { + const group = /** @type {!HTMLElement} */ (this.$['host-access']); + const access = group.selected; - /** @private */ - onActionMenuRemoveClick_: function() { - this.delegate.removeRuntimeHostPermission( - this.itemId, assert(this.actionMenuModel_, 'Action Menu Model')); - this.closeActionMenu_(); - }, + if (access == chrome.developerPrivate.HostAccess.ON_SPECIFIC_SITES && + this.permissions.hostAccess != + chrome.developerPrivate.HostAccess.ON_SPECIFIC_SITES) { + // If the user is transitioning to the "on specific sites" option, show + // the "add host" dialog. This serves two purposes: + // - The user is prompted to add a host immediately, since otherwise + // "on specific sites" is meaningless, and + // - The way the C++ code differentiates between "on click" and "on + // specific sites" is by checking if there are any specific sites. + // This ensures there will be at least one, so that the host access + // is properly calculated. + this.oldHostAccess_ = this.permissions.hostAccess; + this.doShowHostDialog_(group, null); + } else { + this.delegate.setItemHostAccess(this.itemId, access); + } + }, - /** @private */ - closeActionMenu_: function() { - const menu = this.$.hostActionMenu; - assert(menu.open); - menu.close(); - }, + /** + * @return {boolean} + * @private + */ + showSpecificSites_: function() { + return this.permissions.hostAccess == + chrome.developerPrivate.HostAccess.ON_SPECIFIC_SITES; + }, - /** @private */ - onActionMenuClose_: function() { - this.actionMenuModel_ = null; - this.actionMenuAnchorElement_ = null; - }, - }); + /** + * Returns the granted host permissions as a sorted set of strings. + * @return {!Array<string>} + * @private + */ + getRuntimeHosts_: function() { + if (!this.permissions.hosts) { + return []; + } + + // Only show granted hosts in the list. + // TODO(devlin): For extensions that request a finite set of hosts, + // display them in a toggle list. https://crbug.com/891803. + return this.permissions.hosts.filter(control => control.granted) + .map(control => control.host) + .sort(); + }, + + /** + * @param {Event} e + * @private + */ + onAddHostClick_: function(e) { + const target = /** @type {!HTMLElement} */ (e.target); + this.doShowHostDialog_(target, null); + }, + + /** + * @param {!HTMLElement} anchorElement The element to return focus to once + * the dialog closes. + * @param {?string} currentSite The site entry currently being + * edited, or null if this is to add a new entry. + * @private + */ + doShowHostDialog_: function(anchorElement, currentSite) { + this.hostDialogAnchorElement_ = anchorElement; + this.hostDialogModel_ = currentSite; + this.showHostDialog_ = true; + }, + + /** @private */ + onHostDialogClose_: function() { + this.hostDialogModel_ = null; + this.showHostDialog_ = false; + focusWithoutInk(assert(this.hostDialogAnchorElement_, 'Host Anchor')); + this.hostDialogAnchorElement_ = null; + this.oldHostAccess_ = null; + }, + + /** @private */ + onHostDialogCancel_: function() { + // The user canceled the dialog. Set host-access back to the old value, + // if the dialog was shown when just transitioning to a new state. + if (this.oldHostAccess_) { + assert(this.permissions.hostAccess == this.oldHostAccess_); + this.$['host-access'].selected = this.oldHostAccess_; + this.oldHostAccess_ = null; + } + }, + + /** + * @return {boolean} + * @private + */ + dialogShouldUpdateHostAccess_: function() { + return !!this.oldHostAccess_; + }, + + /** + * @param {!{ + * model: !{item: string}, + * target: !HTMLElement, + * }} e + * @private + */ + onEditHostClick_: function(e) { + this.actionMenuModel_ = e.model.item; + this.actionMenuAnchorElement_ = e.target; + const actionMenu = + /** @type {CrActionMenuElement} */ (this.$.hostActionMenu); + actionMenu.showAt(e.target); + }, + + /** @private */ + onActionMenuEditClick_: function() { + // Cache the site before closing the action menu, since it's cleared. + const site = this.actionMenuModel_; + + // Cache and reset actionMenuAnchorElement_ so focus is not returned + // to the action menu's trigger (since the dialog will be shown next). + // Instead, curry the element to the dialog, so once it closes, focus + // will be returned. + const anchorElement = assert(this.actionMenuAnchorElement_, 'Menu Anchor'); + this.actionMenuAnchorElement_ = null; + this.closeActionMenu_(); + this.doShowHostDialog_(anchorElement, site); + }, + + /** @private */ + onActionMenuRemoveClick_: function() { + this.delegate.removeRuntimeHostPermission( + this.itemId, assert(this.actionMenuModel_, 'Action Menu Model')); + this.closeActionMenu_(); + }, + + /** @private */ + closeActionMenu_: function() { + const menu = this.$.hostActionMenu; + assert(menu.open); + menu.close(); + }, - return {RuntimeHostPermissions: RuntimeHostPermissions}; + /** @private */ + onActionMenuClose_: function() { + this.actionMenuModel_ = null; + this.actionMenuAnchorElement_ = null; + }, }); diff --git a/chromium/chrome/browser/resources/extensions/runtime_hosts_dialog.html b/chromium/chrome/browser/resources/extensions/runtime_hosts_dialog.html index db6b162825e..e8013bd69e0 100644 --- a/chromium/chrome/browser/resources/extensions/runtime_hosts_dialog.html +++ b/chromium/chrome/browser/resources/extensions/runtime_hosts_dialog.html @@ -1,36 +1,23 @@ -<link rel="import" href="chrome://resources/html/polymer.html"> - -<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html"> -<link rel="import" href="chrome://resources/cr_elements/cr_dialog/cr_dialog.html"> -<link rel="import" href="chrome://resources/cr_elements/cr_input/cr_input.html"> -<link rel="import" href="chrome://resources/cr_elements/shared_style_css.html"> -<link rel="import" href="strings.html"> - -<dom-module id="extensions-runtime-hosts-dialog"> - <template> - <style include="cr-shared-style"></style> - <cr-dialog id="dialog" close-text="$i18n{close}"> - <div slot="title">[[computeDialogTitle_(currentSite)]]</div> - <div slot="body"> - <cr-input id="input" label="$i18n{runtimeHostsDialogInputLabel}" - placeholder="http://example.com" - value="{{site_}}" on-input="validate_" - invalid="[[inputInvalid_]]" - error-message="$i18n{runtimeHostsDialogInputError}" - spellcheck="false" - autofocus> - </cr-input> - </div> - <div slot="button-container"> - <cr-button class="cancel-button" on-click="onCancelTap_"> - $i18n{cancel} - </cr-button> - <cr-button class="action-button" id="submit" on-click="onSubmitTap_" - disabled="[[computeSubmitButtonDisabled_(inputInvalid_, site_)]]"> - [[computeSubmitButtonLabel_(currentSite)]] - </cr-button> - </div> - </cr-dialog> - </template> - <script src="runtime_hosts_dialog.js"></script> -</dom-module> +<style include="cr-shared-style"></style> +<cr-dialog id="dialog" close-text="$i18n{close}"> + <div slot="title">[[computeDialogTitle_(currentSite)]]</div> + <div slot="body"> + <cr-input id="input" label="$i18n{runtimeHostsDialogInputLabel}" + placeholder="http://example.com" + value="{{site_}}" on-input="validate_" + invalid="[[inputInvalid_]]" + error-message="$i18n{runtimeHostsDialogInputError}" + spellcheck="false" + autofocus> + </cr-input> + </div> + <div slot="button-container"> + <cr-button class="cancel-button" on-click="onCancelTap_"> + $i18n{cancel} + </cr-button> + <cr-button class="action-button" id="submit" on-click="onSubmitTap_" + disabled="[[computeSubmitButtonDisabled_(inputInvalid_, site_)]]"> + [[computeSubmitButtonLabel_(currentSite)]] + </cr-button> + </div> +</cr-dialog> diff --git a/chromium/chrome/browser/resources/extensions/runtime_hosts_dialog.js b/chromium/chrome/browser/resources/extensions/runtime_hosts_dialog.js index 61ffd43ba63..bbe15acfcda 100644 --- a/chromium/chrome/browser/resources/extensions/runtime_hosts_dialog.js +++ b/chromium/chrome/browser/resources/extensions/runtime_hosts_dialog.js @@ -2,223 +2,228 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -cr.define('extensions', function() { - 'use strict'; - - // A RegExp to roughly match acceptable patterns entered by the user. - // exec'ing() this RegExp will match the following groups: - // 0: Full matched string. - // 1: Scheme + scheme separator (e.g., 'https://'). - // 2: Scheme only (e.g., 'https'). - // 3: Match subdomains ('*.'). - // 4: Hostname (e.g., 'example.com'). - // 5: Port, including ':' separator (e.g., ':80'). - // 6: Path, include '/' separator (e.g., '/*'). - const patternRegExp = new RegExp( - '^' + - // Scheme; optional. - '((http|https|\\*)://)?' + - // Include subdomains specifier; optional. - '(\\*\\.)?' + - // Hostname, required. - '([a-z0-9\\.-]+\\.[a-z0-9]+)' + - // Port, optional. - '(:[0-9]+)?' + - // Path, optional but if present must be '/' or '/*'. - '(\\/\\*|\\/)?' + - '$'); - - function getPatternFromSite(site) { - const res = patternRegExp.exec(site); - assert(res); - const scheme = res[1] || '*://'; - const host = (res[3] || '') + res[4]; - const port = res[5] || ''; - const path = '/*'; - return scheme + host + port + path; - } - - const RuntimeHostsDialog = Polymer({ - is: 'extensions-runtime-hosts-dialog', - - properties: { - /** @type {!extensions.ItemDelegate} */ - delegate: Object, - - /** @type {string} */ - itemId: String, - - /** - * The site that this entry is currently managing. Only non-empty if this - * is for editing an existing entry. - * @type {?string} - */ - currentSite: { - type: String, - value: null, - }, - - /** - * Whether the dialog should update the host access to be "on specific - * sites" before adding a new host permission. - */ - updateHostAccess: { - type: Boolean, - value: false, - }, - - /** - * The site to add an exception for. - * @private - */ - site_: String, - - /** - * Whether the currently-entered input is valid. - * @private - */ - inputInvalid_: { - type: Boolean, - value: false, - }, - }, - - /** @override */ - attached: function() { - if (this.currentSite !== null && this.currentSite !== undefined) { - this.site_ = this.currentSite; - this.validate_(); - } - this.$.dialog.showModal(); - }, - - /** @return {boolean} */ - isOpen: function() { - return this.$.dialog.open; - }, +import 'chrome://resources/cr_elements/cr_button/cr_button.m.js'; +import 'chrome://resources/cr_elements/cr_dialog/cr_dialog.m.js'; +import 'chrome://resources/cr_elements/cr_input/cr_input.m.js'; +import 'chrome://resources/cr_elements/shared_style_css.m.js'; +import './strings.m.js'; + +import {assert} from 'chrome://resources/js/assert.m.js'; +import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js'; +import {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; + +import {ItemDelegate} from './item.js'; + +// A RegExp to roughly match acceptable patterns entered by the user. +// exec'ing() this RegExp will match the following groups: +// 0: Full matched string. +// 1: Scheme + scheme separator (e.g., 'https://'). +// 2: Scheme only (e.g., 'https'). +// 3: Match subdomains ('*.'). +// 4: Hostname (e.g., 'example.com'). +// 5: Port, including ':' separator (e.g., ':80'). +// 6: Path, include '/' separator (e.g., '/*'). +const patternRegExp = new RegExp( + '^' + + // Scheme; optional. + '((http|https|\\*)://)?' + + // Include subdomains specifier; optional. + '(\\*\\.)?' + + // Hostname, required. + '([a-z0-9\\.-]+\\.[a-z0-9]+)' + + // Port, optional. + '(:[0-9]+)?' + + // Path, optional but if present must be '/' or '/*'. + '(\\/\\*|\\/)?' + + '$'); + +export function getPatternFromSite(site) { + const res = patternRegExp.exec(site); + assert(res); + const scheme = res[1] || '*://'; + const host = (res[3] || '') + res[4]; + const port = res[5] || ''; + const path = '/*'; + return scheme + host + port + path; +} + +Polymer({ + is: 'extensions-runtime-hosts-dialog', + + _template: html`{__html_template__}`, + + properties: { + /** @type {!ItemDelegate} */ + delegate: Object, + + /** @type {string} */ + itemId: String, /** - * Validates that the pattern entered is valid. - * @private + * The site that this entry is currently managing. Only non-empty if this + * is for editing an existing entry. + * @type {?string} */ - validate_: function() { - // If input is empty, disable the action button, but don't show the red - // invalid message. - if (this.site_.trim().length == 0) { - this.inputInvalid_ = false; - return; - } - - const valid = patternRegExp.test(this.site_); - this.inputInvalid_ = !valid; + currentSite: { + type: String, + value: null, }, /** - * @return {string} - * @private + * Whether the dialog should update the host access to be "on specific + * sites" before adding a new host permission. */ - computeDialogTitle_: function() { - const stringId = this.currentSite === null ? 'runtimeHostsDialogTitle' : - 'hostPermissionsEdit'; - return loadTimeData.getString(stringId); + updateHostAccess: { + type: Boolean, + value: false, }, /** - * @return {boolean} + * The site to add an exception for. * @private */ - computeSubmitButtonDisabled_: function() { - return this.inputInvalid_ || this.site_ === undefined || - this.site_.trim().length == 0; - }, + site_: String, /** - * @return {string} + * Whether the currently-entered input is valid. * @private */ - computeSubmitButtonLabel_: function() { - const stringId = this.currentSite === null ? 'add' : 'save'; - return loadTimeData.getString(stringId); + inputInvalid_: { + type: Boolean, + value: false, }, - - /** @private */ - onCancelTap_: function() { - this.$.dialog.cancel(); - }, - - /** - * The tap handler for the submit button (adds the pattern and closes - * the dialog). - * @private - */ - onSubmitTap_: function() { - if (this.currentSite !== null) { - this.handleEdit_(); - } else { - this.handleAdd_(); - } - }, - - /** - * Handles adding a new site entry. - * @private - */ - handleAdd_: function() { - assert(!this.currentSite); - - if (this.updateHostAccess) { - this.delegate.setItemHostAccess( - this.itemId, chrome.developerPrivate.HostAccess.ON_SPECIFIC_SITES); - } - - this.addPermission_(); - }, - - /** - * Handles editing an existing site entry. - * @private - */ - handleEdit_: function() { - assert(this.currentSite); - assert( - !this.updateHostAccess, - 'Editing host permissions should only be possible if the host ' + - 'access is already set to specific sites.'); - - if (this.currentSite == this.site_) { - // No change in values, so no need to update anything. - this.$.dialog.close(); - return; - } - - // Editing an existing entry is done by removing the current site entry, - // and then adding the new one. - this.delegate.removeRuntimeHostPermission(this.itemId, this.currentSite) - .then(() => { - this.addPermission_(); - }); - }, - - /** - * Adds the runtime host permission through the delegate. If successful, - * closes the dialog; otherwise displays the invalid input message. - * @private - */ - addPermission_: function() { - const pattern = getPatternFromSite(this.site_); - this.delegate.addRuntimeHostPermission(this.itemId, pattern) - .then( - () => { - this.$.dialog.close(); - }, - () => { - this.inputInvalid_ = true; - }); - }, - }); - - return { - RuntimeHostsDialog: RuntimeHostsDialog, - getPatternFromSite: getPatternFromSite - }; + }, + + /** @override */ + attached: function() { + if (this.currentSite !== null && this.currentSite !== undefined) { + this.site_ = this.currentSite; + this.validate_(); + } + this.$.dialog.showModal(); + }, + + /** @return {boolean} */ + isOpen: function() { + return this.$.dialog.open; + }, + + /** + * Validates that the pattern entered is valid. + * @private + */ + validate_: function() { + // If input is empty, disable the action button, but don't show the red + // invalid message. + if (this.site_.trim().length == 0) { + this.inputInvalid_ = false; + return; + } + + const valid = patternRegExp.test(this.site_); + this.inputInvalid_ = !valid; + }, + + /** + * @return {string} + * @private + */ + computeDialogTitle_: function() { + const stringId = this.currentSite === null ? 'runtimeHostsDialogTitle' : + 'hostPermissionsEdit'; + return loadTimeData.getString(stringId); + }, + + /** + * @return {boolean} + * @private + */ + computeSubmitButtonDisabled_: function() { + return this.inputInvalid_ || this.site_ === undefined || + this.site_.trim().length == 0; + }, + + /** + * @return {string} + * @private + */ + computeSubmitButtonLabel_: function() { + const stringId = this.currentSite === null ? 'add' : 'save'; + return loadTimeData.getString(stringId); + }, + + /** @private */ + onCancelTap_: function() { + this.$.dialog.cancel(); + }, + + /** + * The tap handler for the submit button (adds the pattern and closes + * the dialog). + * @private + */ + onSubmitTap_: function() { + if (this.currentSite !== null) { + this.handleEdit_(); + } else { + this.handleAdd_(); + } + }, + + /** + * Handles adding a new site entry. + * @private + */ + handleAdd_: function() { + assert(!this.currentSite); + + if (this.updateHostAccess) { + this.delegate.setItemHostAccess( + this.itemId, chrome.developerPrivate.HostAccess.ON_SPECIFIC_SITES); + } + + this.addPermission_(); + }, + + /** + * Handles editing an existing site entry. + * @private + */ + handleEdit_: function() { + assert(this.currentSite); + assert( + !this.updateHostAccess, + 'Editing host permissions should only be possible if the host ' + + 'access is already set to specific sites.'); + + if (this.currentSite == this.site_) { + // No change in values, so no need to update anything. + this.$.dialog.close(); + return; + } + + // Editing an existing entry is done by removing the current site entry, + // and then adding the new one. + this.delegate.removeRuntimeHostPermission(this.itemId, this.currentSite) + .then(() => { + this.addPermission_(); + }); + }, + + /** + * Adds the runtime host permission through the delegate. If successful, + * closes the dialog; otherwise displays the invalid input message. + * @private + */ + addPermission_: function() { + const pattern = getPatternFromSite(this.site_); + this.delegate.addRuntimeHostPermission(this.itemId, pattern) + .then( + () => { + this.$.dialog.close(); + }, + () => { + this.inputInvalid_ = true; + }); + }, }); diff --git a/chromium/chrome/browser/resources/extensions/service.html b/chromium/chrome/browser/resources/extensions/service.html deleted file mode 100644 index 21bf453e676..00000000000 --- a/chromium/chrome/browser/resources/extensions/service.html +++ /dev/null @@ -1,6 +0,0 @@ -<link rel="import" href="chrome://resources/html/assert.html"> -<link rel="import" href="chrome://resources/html/cr.html"> -<link rel="import" href="strings.html"> -<link rel="import" href="item.html"> -<link rel="import" href="navigation_helper.html"> -<script src="service.js"></script> diff --git a/chromium/chrome/browser/resources/extensions/service.js b/chromium/chrome/browser/resources/extensions/service.js index 1acc69798be..0e1e52cb28d 100644 --- a/chromium/chrome/browser/resources/extensions/service.js +++ b/chromium/chrome/browser/resources/extensions/service.js @@ -2,457 +2,462 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -cr.define('extensions', function() { - 'use strict'; - - /** - * @implements {extensions.ActivityLogDelegate} - * @implements {extensions.ActivityLogEventDelegate} - * @implements {extensions.ErrorPageDelegate} - * @implements {extensions.ItemDelegate} - * @implements {extensions.KeyboardShortcutDelegate} - * @implements {extensions.LoadErrorDelegate} - * @implements {extensions.PackDialogDelegate} - * @implements {extensions.ToolbarDelegate} - */ - class Service { - constructor() { - /** @private {boolean} */ - this.isDeleting_ = false; +import {assert} from 'chrome://resources/js/assert.m.js'; +import {addSingletonGetter} from 'chrome://resources/js/cr.m.js'; + +import {ActivityLogDelegate} from './activity_log/activity_log_history.js'; +import {ActivityLogEventDelegate} from './activity_log/activity_log_stream.js'; +import {ErrorPageDelegate} from './error_page.js'; +import {ItemDelegate} from './item.js'; +import {KeyboardShortcutDelegate} from './keyboard_shortcut_delegate.js'; +import {LoadErrorDelegate} from './load_error.js'; +import {Dialog, navigation, Page} from './navigation_helper.js'; +import {PackDialogDelegate} from './pack_dialog.js'; +import {ToolbarDelegate} from './toolbar.js'; + + +/** + * @implements {ActivityLogDelegate} + * @implements {ActivityLogEventDelegate} + * @implements {ErrorPageDelegate} + * @implements {ItemDelegate} + * @implements {KeyboardShortcutDelegate} + * @implements {LoadErrorDelegate} + * @implements {PackDialogDelegate} + * @implements {ToolbarDelegate} + */ +export class Service { + constructor() { + /** @private {boolean} */ + this.isDeleting_ = false; + + /** @private {!Set<string>} */ + this.eventsToIgnoreOnce_ = new Set(); + } - /** @private {!Set<string>} */ - this.eventsToIgnoreOnce_ = new Set(); - } + getProfileConfiguration() { + return new Promise(function(resolve, reject) { + chrome.developerPrivate.getProfileConfiguration(resolve); + }); + } - getProfileConfiguration() { - return new Promise(function(resolve, reject) { - chrome.developerPrivate.getProfileConfiguration(resolve); - }); - } + getItemStateChangedTarget() { + return chrome.developerPrivate.onItemStateChanged; + } - getItemStateChangedTarget() { - return chrome.developerPrivate.onItemStateChanged; - } + /** + * @param {string} extensionId + * @param {!chrome.developerPrivate.EventType} eventType + * @return {boolean} + */ + shouldIgnoreUpdate(extensionId, eventType) { + return this.eventsToIgnoreOnce_.delete(`${extensionId}_${eventType}`); + } - /** - * @param {string} extensionId - * @param {!chrome.developerPrivate.EventType} eventType - * @return {boolean} - */ - shouldIgnoreUpdate(extensionId, eventType) { - return this.eventsToIgnoreOnce_.delete(`${extensionId}_${eventType}`); - } + /** + * @param {string} extensionId + * @param {!chrome.developerPrivate.EventType} eventType + */ + ignoreNextEvent(extensionId, eventType) { + this.eventsToIgnoreOnce_.add(`${extensionId}_${eventType}`); + } - /** - * @param {string} extensionId - * @param {!chrome.developerPrivate.EventType} eventType - */ - ignoreNextEvent(extensionId, eventType) { - this.eventsToIgnoreOnce_.add(`${extensionId}_${eventType}`); - } + getProfileStateChangedTarget() { + return chrome.developerPrivate.onProfileStateChanged; + } - getProfileStateChangedTarget() { - return chrome.developerPrivate.onProfileStateChanged; - } + getExtensionsInfo() { + return new Promise(function(resolve, reject) { + chrome.developerPrivate.getExtensionsInfo( + {includeDisabled: true, includeTerminated: true}, resolve); + }); + } - getExtensionsInfo() { - return new Promise(function(resolve, reject) { - chrome.developerPrivate.getExtensionsInfo( - {includeDisabled: true, includeTerminated: true}, resolve); - }); - } + /** @override */ + getExtensionSize(id) { + return new Promise(function(resolve, reject) { + chrome.developerPrivate.getExtensionSize(id, resolve); + }); + } - /** @override */ - getExtensionSize(id) { - return new Promise(function(resolve, reject) { - chrome.developerPrivate.getExtensionSize(id, resolve); + /** @override */ + addRuntimeHostPermission(id, host) { + return new Promise((resolve, reject) => { + chrome.developerPrivate.addHostPermission(id, host, () => { + if (chrome.runtime.lastError) { + reject(chrome.runtime.lastError.message); + return; + } + resolve(); }); - } + }); + } - /** @override */ - addRuntimeHostPermission(id, host) { - return new Promise((resolve, reject) => { - chrome.developerPrivate.addHostPermission(id, host, () => { - if (chrome.runtime.lastError) { - reject(chrome.runtime.lastError.message); - return; - } - resolve(); - }); + /** @override */ + removeRuntimeHostPermission(id, host) { + return new Promise((resolve, reject) => { + chrome.developerPrivate.removeHostPermission(id, host, () => { + if (chrome.runtime.lastError) { + reject(chrome.runtime.lastError.message); + return; + } + resolve(); }); - } + }); + } - /** @override */ - removeRuntimeHostPermission(id, host) { - return new Promise((resolve, reject) => { - chrome.developerPrivate.removeHostPermission(id, host, () => { - if (chrome.runtime.lastError) { - reject(chrome.runtime.lastError.message); - return; - } - resolve(); - }); + /** + * Opens a file browser dialog for the user to select a file (or directory). + * @param {chrome.developerPrivate.SelectType} selectType + * @param {chrome.developerPrivate.FileType} fileType + * @return {Promise<string>} The promise to be resolved with the selected + * path. + */ + chooseFilePath_(selectType, fileType) { + return new Promise(function(resolve, reject) { + chrome.developerPrivate.choosePath(selectType, fileType, function(path) { + if (chrome.runtime.lastError && + chrome.runtime.lastError != 'File selection was canceled.') { + reject(chrome.runtime.lastError); + } else { + resolve(path || ''); + } }); - } + }); + } - /** - * Opens a file browser dialog for the user to select a file (or directory). - * @param {chrome.developerPrivate.SelectType} selectType - * @param {chrome.developerPrivate.FileType} fileType - * @return {Promise<string>} The promise to be resolved with the selected - * path. - */ - chooseFilePath_(selectType, fileType) { - return new Promise(function(resolve, reject) { - chrome.developerPrivate.choosePath( - selectType, fileType, function(path) { - if (chrome.runtime.lastError && - chrome.runtime.lastError != 'File selection was canceled.') { - reject(chrome.runtime.lastError); - } else { - resolve(path || ''); - } - }); - }); - } + /** @override */ + updateExtensionCommandKeybinding(extensionId, commandName, keybinding) { + chrome.developerPrivate.updateExtensionCommand({ + extensionId: extensionId, + commandName: commandName, + keybinding: keybinding, + }); + } - /** @override */ - updateExtensionCommandKeybinding(extensionId, commandName, keybinding) { - chrome.developerPrivate.updateExtensionCommand({ - extensionId: extensionId, - commandName: commandName, - keybinding: keybinding, - }); - } + /** @override */ + updateExtensionCommandScope(extensionId, commandName, scope) { + // The COMMAND_REMOVED event needs to be ignored since it is sent before + // the command is added back with the updated scope but can be handled + // after the COMMAND_ADDED event. + this.ignoreNextEvent( + extensionId, chrome.developerPrivate.EventType.COMMAND_REMOVED); + chrome.developerPrivate.updateExtensionCommand({ + extensionId: extensionId, + commandName: commandName, + scope: scope, + }); + } - /** @override */ - updateExtensionCommandScope(extensionId, commandName, scope) { - // The COMMAND_REMOVED event needs to be ignored since it is sent before - // the command is added back with the updated scope but can be handled - // after the COMMAND_ADDED event. - this.ignoreNextEvent( - extensionId, chrome.developerPrivate.EventType.COMMAND_REMOVED); - chrome.developerPrivate.updateExtensionCommand({ - extensionId: extensionId, - commandName: commandName, - scope: scope, - }); - } + /** @override */ + setShortcutHandlingSuspended(isCapturing) { + chrome.developerPrivate.setShortcutHandlingSuspended(isCapturing); + } - /** @override */ - setShortcutHandlingSuspended(isCapturing) { - chrome.developerPrivate.setShortcutHandlingSuspended(isCapturing); - } + /** + * @param {chrome.developerPrivate.LoadUnpackedOptions=} opt_options + * @return {!Promise} A signal that loading finished, rejected if any error + * occurred. + * @private + */ + loadUnpackedHelper_(opt_options) { + return new Promise(function(resolve, reject) { + const options = Object.assign( + { + failQuietly: true, + populateError: true, + }, + opt_options); + + chrome.developerPrivate.loadUnpacked(options, (loadError) => { + if (chrome.runtime.lastError && + chrome.runtime.lastError.message != + 'File selection was canceled.') { + throw new Error(chrome.runtime.lastError.message); + } + if (loadError) { + return reject(loadError); + } - /** - * @param {chrome.developerPrivate.LoadUnpackedOptions=} opt_options - * @return {!Promise} A signal that loading finished, rejected if any error - * occurred. - * @private - */ - loadUnpackedHelper_(opt_options) { - return new Promise(function(resolve, reject) { - const options = Object.assign( - { - failQuietly: true, - populateError: true, - }, - opt_options); - - chrome.developerPrivate.loadUnpacked( - options, (loadError) => { - if (chrome.runtime.lastError && - chrome.runtime.lastError.message != - 'File selection was canceled.') { - throw new Error(chrome.runtime.lastError.message); - } - if (loadError) { - return reject(loadError); - } - - resolve(); - }); + resolve(); }); - } + }); + } - /** @override */ - deleteItem(id) { - if (this.isDeleting_) { - return; - } - this.isDeleting_ = true; - chrome.management.uninstall(id, {showConfirmDialog: true}, () => { - // The "last error" was almost certainly the user canceling the dialog. - // Do nothing. We only check it so we don't get noisy logs. - /** @suppress {suspiciousCode} */ - chrome.runtime.lastError; - this.isDeleting_ = false; - }); - } + /** @override */ + deleteItem(id) { + if (this.isDeleting_) { + return; + } + this.isDeleting_ = true; + chrome.management.uninstall(id, {showConfirmDialog: true}, () => { + // The "last error" was almost certainly the user canceling the dialog. + // Do nothing. We only check it so we don't get noisy logs. + /** @suppress {suspiciousCode} */ + chrome.runtime.lastError; + this.isDeleting_ = false; + }); + } - /** @override */ - setItemEnabled(id, isEnabled) { - chrome.management.setEnabled(id, isEnabled); - } + /** @override */ + setItemEnabled(id, isEnabled) { + chrome.management.setEnabled(id, isEnabled); + } - /** @override */ - setItemAllowedIncognito(id, isAllowedIncognito) { - chrome.developerPrivate.updateExtensionConfiguration({ - extensionId: id, - incognitoAccess: isAllowedIncognito, - }); - } + /** @override */ + setItemAllowedIncognito(id, isAllowedIncognito) { + chrome.developerPrivate.updateExtensionConfiguration({ + extensionId: id, + incognitoAccess: isAllowedIncognito, + }); + } - /** @override */ - setItemAllowedOnFileUrls(id, isAllowedOnFileUrls) { - chrome.developerPrivate.updateExtensionConfiguration({ - extensionId: id, - fileAccess: isAllowedOnFileUrls, - }); - } + /** @override */ + setItemAllowedOnFileUrls(id, isAllowedOnFileUrls) { + chrome.developerPrivate.updateExtensionConfiguration({ + extensionId: id, + fileAccess: isAllowedOnFileUrls, + }); + } - /** @override */ - setItemHostAccess(id, hostAccess) { - chrome.developerPrivate.updateExtensionConfiguration({ - extensionId: id, - hostAccess: hostAccess, - }); - } + /** @override */ + setItemHostAccess(id, hostAccess) { + chrome.developerPrivate.updateExtensionConfiguration({ + extensionId: id, + hostAccess: hostAccess, + }); + } - /** @override */ - setItemCollectsErrors(id, collectsErrors) { - chrome.developerPrivate.updateExtensionConfiguration({ - extensionId: id, - errorCollection: collectsErrors, - }); - } + /** @override */ + setItemCollectsErrors(id, collectsErrors) { + chrome.developerPrivate.updateExtensionConfiguration({ + extensionId: id, + errorCollection: collectsErrors, + }); + } - /** @override */ - inspectItemView(id, view) { - chrome.developerPrivate.openDevTools({ - extensionId: id, - renderProcessId: view.renderProcessId, - renderViewId: view.renderViewId, - incognito: view.incognito, - }); - } + /** @override */ + inspectItemView(id, view) { + chrome.developerPrivate.openDevTools({ + extensionId: id, + renderProcessId: view.renderProcessId, + renderViewId: view.renderViewId, + incognito: view.incognito, + }); + } - /** - * @param {string} url - * @override - */ - openUrl(url) { - window.open(url); - } + /** + * @param {string} url + * @override + */ + openUrl(url) { + window.open(url); + } - /** @override */ - reloadItem(id) { - return new Promise(function(resolve, reject) { - chrome.developerPrivate.reload( - id, {failQuietly: true, populateErrorForUnpacked: true}, - (loadError) => { - if (loadError) { - reject(loadError); - return; - } - - resolve(); - }); - }); - } + /** @override */ + reloadItem(id) { + return new Promise(function(resolve, reject) { + chrome.developerPrivate.reload( + id, {failQuietly: true, populateErrorForUnpacked: true}, + (loadError) => { + if (loadError) { + reject(loadError); + return; + } + + resolve(); + }); + }); + } - /** @override */ - repairItem(id) { - chrome.developerPrivate.repairExtension(id); - } + /** @override */ + repairItem(id) { + chrome.developerPrivate.repairExtension(id); + } - /** @override */ - showItemOptionsPage(extension) { - assert(extension && extension.optionsPage); - if (extension.optionsPage.openInTab) { - chrome.developerPrivate.showOptions(extension.id); - } else { - extensions.navigation.navigateTo({ - page: extensions.Page.DETAILS, - subpage: extensions.Dialog.OPTIONS, - extensionId: extension.id, - }); - } + /** @override */ + showItemOptionsPage(extension) { + assert(extension && extension.optionsPage); + if (extension.optionsPage.openInTab) { + chrome.developerPrivate.showOptions(extension.id); + } else { + navigation.navigateTo({ + page: Page.DETAILS, + subpage: Dialog.OPTIONS, + extensionId: extension.id, + }); } + } - /** @override */ - setProfileInDevMode(inDevMode) { - chrome.developerPrivate.updateProfileConfiguration( - {inDeveloperMode: inDevMode}); - } + /** @override */ + setProfileInDevMode(inDevMode) { + chrome.developerPrivate.updateProfileConfiguration( + {inDeveloperMode: inDevMode}); + } - /** @override */ - loadUnpacked() { - return this.loadUnpackedHelper_(); - } + /** @override */ + loadUnpacked() { + return this.loadUnpackedHelper_(); + } - /** @override */ - retryLoadUnpacked(retryGuid) { - // Attempt to load an unpacked extension, optionally as another attempt at - // a previously-specified load. - return this.loadUnpackedHelper_({retryGuid: retryGuid}); - } + /** @override */ + retryLoadUnpacked(retryGuid) { + // Attempt to load an unpacked extension, optionally as another attempt at + // a previously-specified load. + return this.loadUnpackedHelper_({retryGuid: retryGuid}); + } - /** @override */ - choosePackRootDirectory() { - return this.chooseFilePath_( - chrome.developerPrivate.SelectType.FOLDER, - chrome.developerPrivate.FileType.LOAD); - } + /** @override */ + choosePackRootDirectory() { + return this.chooseFilePath_( + chrome.developerPrivate.SelectType.FOLDER, + chrome.developerPrivate.FileType.LOAD); + } - /** @override */ - choosePrivateKeyPath() { - return this.chooseFilePath_( - chrome.developerPrivate.SelectType.FILE, - chrome.developerPrivate.FileType.PEM); - } + /** @override */ + choosePrivateKeyPath() { + return this.chooseFilePath_( + chrome.developerPrivate.SelectType.FILE, + chrome.developerPrivate.FileType.PEM); + } - /** @override */ - packExtension(rootPath, keyPath, flag, callback) { - chrome.developerPrivate.packDirectory(rootPath, keyPath, flag, callback); - } + /** @override */ + packExtension(rootPath, keyPath, flag, callback) { + chrome.developerPrivate.packDirectory(rootPath, keyPath, flag, callback); + } - /** @override */ - updateAllExtensions() { - return new Promise((resolve) => { - chrome.developerPrivate.autoUpdate(resolve); - chrome.metricsPrivate.recordUserAction('Options_UpdateExtensions'); - }); - } + /** @override */ + updateAllExtensions() { + return new Promise((resolve) => { + chrome.developerPrivate.autoUpdate(resolve); + chrome.metricsPrivate.recordUserAction('Options_UpdateExtensions'); + }); + } - /** @override */ - deleteErrors(extensionId, errorIds, type) { - chrome.developerPrivate.deleteExtensionErrors({ - extensionId: extensionId, - errorIds: errorIds, - type: type, - }); - } + /** @override */ + deleteErrors(extensionId, errorIds, type) { + chrome.developerPrivate.deleteExtensionErrors({ + extensionId: extensionId, + errorIds: errorIds, + type: type, + }); + } - /** @override */ - requestFileSource(args) { - return new Promise(function(resolve, reject) { - chrome.developerPrivate.requestFileSource(args, resolve); - }); - } + /** @override */ + requestFileSource(args) { + return new Promise(function(resolve, reject) { + chrome.developerPrivate.requestFileSource(args, resolve); + }); + } - /** @override */ - showInFolder(id) { - chrome.developerPrivate.showPath(id); - } + /** @override */ + showInFolder(id) { + chrome.developerPrivate.showPath(id); + } - /** @override */ - getExtensionActivityLog(extensionId) { - return new Promise(function(resolve, reject) { - chrome.activityLogPrivate.getExtensionActivities( - { - activityType: - chrome.activityLogPrivate.ExtensionActivityFilter.ANY, - extensionId: extensionId - }, - resolve); - }); - } + /** @override */ + getExtensionActivityLog(extensionId) { + return new Promise(function(resolve, reject) { + chrome.activityLogPrivate.getExtensionActivities( + { + activityType: chrome.activityLogPrivate.ExtensionActivityFilter.ANY, + extensionId: extensionId + }, + resolve); + }); + } - /** @override */ - getFilteredExtensionActivityLog(extensionId, searchTerm) { - const anyType = chrome.activityLogPrivate.ExtensionActivityFilter.ANY; - - // Construct one filter for each API call we will make: one for substring - // search by api call, one for substring search by page URL, and one for - // substring search by argument URL. % acts as a wildcard. - const activityLogFilters = [ - { - activityType: anyType, - extensionId: extensionId, - apiCall: `%${searchTerm}%`, - }, - { - activityType: anyType, - extensionId: extensionId, - pageUrl: `%${searchTerm}%`, - }, - { - activityType: anyType, - extensionId: extensionId, - argUrl: `%${searchTerm}%` - } - ]; - - const promises = activityLogFilters.map( - filter => new Promise(function(resolve, reject) { - chrome.activityLogPrivate.getExtensionActivities(filter, resolve); - })); - - return Promise.all(promises).then(results => { - // We may have results that are present in one or more searches, so - // we merge them here. We also assume that every distinct activity - // id corresponds to exactly one activity. - const activitiesById = new Map(); - for (const result of results) { - for (const activity of result.activities) { - activitiesById.set(activity.activityId, activity); - } - } + /** @override */ + getFilteredExtensionActivityLog(extensionId, searchTerm) { + const anyType = chrome.activityLogPrivate.ExtensionActivityFilter.ANY; - return {activities: Array.from(activitiesById.values())}; - }); - } + // Construct one filter for each API call we will make: one for substring + // search by api call, one for substring search by page URL, and one for + // substring search by argument URL. % acts as a wildcard. + const activityLogFilters = [ + { + activityType: anyType, + extensionId: extensionId, + apiCall: `%${searchTerm}%`, + }, + { + activityType: anyType, + extensionId: extensionId, + pageUrl: `%${searchTerm}%`, + }, + { + activityType: anyType, + extensionId: extensionId, + argUrl: `%${searchTerm}%` + } + ]; + + const promises = activityLogFilters.map( + filter => new Promise(function(resolve, reject) { + chrome.activityLogPrivate.getExtensionActivities(filter, resolve); + })); + + return Promise.all(promises).then(results => { + // We may have results that are present in one or more searches, so + // we merge them here. We also assume that every distinct activity + // id corresponds to exactly one activity. + const activitiesById = new Map(); + for (const result of results) { + for (const activity of result.activities) { + activitiesById.set(activity.activityId, activity); + } + } - /** @override */ - deleteActivitiesById(activityIds) { - return new Promise(function(resolve, reject) { - chrome.activityLogPrivate.deleteActivities(activityIds, resolve); - }); - } + return {activities: Array.from(activitiesById.values())}; + }); + } - /** @override */ - deleteActivitiesFromExtension(extensionId) { - return new Promise(function(resolve, reject) { - chrome.activityLogPrivate.deleteActivitiesByExtension( - extensionId, resolve); - }); - } + /** @override */ + deleteActivitiesById(activityIds) { + return new Promise(function(resolve, reject) { + chrome.activityLogPrivate.deleteActivities(activityIds, resolve); + }); + } - /** @override */ - getOnExtensionActivity() { - return chrome.activityLogPrivate.onExtensionActivity; - } + /** @override */ + deleteActivitiesFromExtension(extensionId) { + return new Promise(function(resolve, reject) { + chrome.activityLogPrivate.deleteActivitiesByExtension( + extensionId, resolve); + }); + } - /** @override */ - downloadActivities(rawActivityData, fileName) { - const blob = new Blob([rawActivityData], {type: 'application/json'}); - const url = URL.createObjectURL(blob); - const a = document.createElement('a'); - a.href = url; - a.download = fileName; - a.click(); - } + /** @override */ + getOnExtensionActivity() { + return chrome.activityLogPrivate.onExtensionActivity; + } - /** - * Attempts to load an unpacked extension via a drag-n-drop gesture. - * @return {!Promise} - */ - loadUnpackedFromDrag() { - return this.loadUnpackedHelper_({useDraggedPath: true}); - } + /** @override */ + downloadActivities(rawActivityData, fileName) { + const blob = new Blob([rawActivityData], {type: 'application/json'}); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = fileName; + a.click(); + } - installDroppedFile() { - chrome.developerPrivate.installDroppedFile(); - } + /** + * Attempts to load an unpacked extension via a drag-n-drop gesture. + * @return {!Promise} + */ + loadUnpackedFromDrag() { + return this.loadUnpackedHelper_({useDraggedPath: true}); + } - notifyDragInstallInProgress() { - chrome.developerPrivate.notifyDragInstallInProgress(); - } + installDroppedFile() { + chrome.developerPrivate.installDroppedFile(); } - cr.addSingletonGetter(Service); + notifyDragInstallInProgress() { + chrome.developerPrivate.notifyDragInstallInProgress(); + } +} - return {Service: Service}; -}); +addSingletonGetter(Service); diff --git a/chromium/chrome/browser/resources/extensions/shared_style.html b/chromium/chrome/browser/resources/extensions/shared_style.html index 0410a18c21b..41ca789f85c 100644 --- a/chromium/chrome/browser/resources/extensions/shared_style.html +++ b/chromium/chrome/browser/resources/extensions/shared_style.html @@ -1,86 +1,81 @@ -<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html"> -<link rel="import" href="shared_vars.html"> +<template> + <style include="cr-shared-style"> + a[href] { + color: var(--cr-link-color); + text-decoration: none; + } -<dom-module id="shared-style"> - <template> - <style include="cr-shared-style"> - a[href] { - color: var(--cr-link-color); - text-decoration: none; - } + .activity-message { + color: var(--md-loading-message-color); + font-size: 123%; /* Should be 16px when 100% is 13px. */ + font-weight: 500; + margin-top: 80px; + text-align: center; + } - .activity-message { - color: var(--md-loading-message-color); - font-size: 123%; /* Should be 16px when 100% is 13px. */ - font-weight: 500; - margin-top: 80px; - text-align: center; - } + .activity-subpage-header { + display: flex; + justify-content: flex-end; + padding: 12px 12px; + } - .activity-subpage-header { - display: flex; - justify-content: flex-end; - padding: 12px 12px; - } + .activity-table-headings { + align-items: center; + display: flex; + flex-direction: row; + font-weight: 500; + margin-inline-end: auto; - .activity-table-headings { - align-items: center; - display: flex; - flex-direction: row; - font-weight: 500; - margin-inline-end: auto; + /* Match separator height. */ + min-height: calc(var(--cr-section-min-height) - var(--separator-gap)); + padding: 0 var(--cr-section-padding); + } - /* Match separator height. */ - min-height: calc(var(--cr-section-min-height) - var(--separator-gap)); - padding: 0 var(--cr-section-padding); - } + .clear-activities-button { + margin: 0 8px; + } - .clear-activities-button { - margin: 0 8px; - } + .page-container { + height: 100%; + overflow: overlay; + } - .page-container { - height: 100%; - overflow: overlay; - } + .page-content { + @apply --cr-card-elevation; + background-color: var(--cr-card-background-color); + box-sizing: border-box; + margin: auto; + min-height: 100%; + padding-bottom: 64px; + width: var(--cr-toolbar-field-width); + } - .page-content { - @apply --cr-card-elevation; - background-color: var(--cr-card-background-color); - box-sizing: border-box; - margin: auto; - min-height: 100%; - padding-bottom: 64px; - width: var(--cr-toolbar-field-width); - } + .page-header { + align-items: center; + display: flex; + height: 40px; + margin-bottom: 12px; + padding: 8px 12px 0; + } - .page-header { - align-items: center; - display: flex; - height: 40px; - margin-bottom: 12px; - padding: 8px 12px 0; - } + .link-icon-button { + align-items: center; + display: flex; + justify-content: center; + } - .link-icon-button { - align-items: center; - display: flex; - justify-content: center; - } + .separator { + border-inline-start: var(--cr-separator-line); + flex-shrink: 0; - .separator { - border-inline-start: var(--cr-separator-line); - flex-shrink: 0; + /* Section height - gap. */ + height: calc(var(--cr-section-min-height) - var(--separator-gap)); + margin-inline-end: var(--cr-section-padding); - /* Section height - gap. */ - height: calc(var(--cr-section-min-height) - var(--separator-gap)); - margin-inline-end: var(--cr-section-padding); - - /* Makes the tappable area fill its parent. - * TODO(crbug.com/949697): This is an explicit reminder to override - * once .separator styling is extracted from settings. */ - margin-inline-start: 0; - } - </style> - </template> -</dom-module> + /* Makes the tappable area fill its parent. + * TODO(crbug.com/949697): This is an explicit reminder to override + * once .separator styling is extracted from settings. */ + margin-inline-start: 0; + } + </style> +</template> diff --git a/chromium/chrome/browser/resources/extensions/shared_style.js b/chromium/chrome/browser/resources/extensions/shared_style.js new file mode 100644 index 00000000000..3b6d04fc5e9 --- /dev/null +++ b/chromium/chrome/browser/resources/extensions/shared_style.js @@ -0,0 +1,12 @@ +// Copyright 2019 The Chromium 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 'chrome://resources/cr_elements/shared_vars_css.m.js'; +import './shared_vars.js'; + +const template = document.createElement('template'); +template.innerHTML = ` +<dom-module id="shared-style">{__html_template__}</dom-module> +`; +document.body.appendChild(template.content.cloneNode(true)); diff --git a/chromium/chrome/browser/resources/extensions/shared_vars.html b/chromium/chrome/browser/resources/extensions/shared_vars.html index 86b2870213f..b826b8a16d0 100644 --- a/chromium/chrome/browser/resources/extensions/shared_vars.html +++ b/chromium/chrome/browser/resources/extensions/shared_vars.html @@ -1,8 +1,3 @@ -<link rel="import" href="chrome://resources/html/polymer.html"> - -<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html"> -<link rel="import" href="chrome://resources/polymer/v1_0/paper-styles/color.html"> - <custom-style> <style> html { diff --git a/chromium/chrome/browser/resources/extensions/shared_vars.js b/chromium/chrome/browser/resources/extensions/shared_vars.js new file mode 100644 index 00000000000..59a50b0cc6d --- /dev/null +++ b/chromium/chrome/browser/resources/extensions/shared_vars.js @@ -0,0 +1,12 @@ +// Copyright 2019 The Chromium 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 'chrome://resources/cr_elements/shared_vars_css.m.js'; +import 'chrome://resources/polymer/v3_0/paper-styles/color.js'; + +import {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; + +const $_documentContainer = document.createElement('template'); +$_documentContainer.innerHTML = `{__html_template__}`; +document.head.appendChild($_documentContainer.content); diff --git a/chromium/chrome/browser/resources/extensions/shortcut_input.html b/chromium/chrome/browser/resources/extensions/shortcut_input.html index d35415d4f8c..f2c4f4247fb 100644 --- a/chromium/chrome/browser/resources/extensions/shortcut_input.html +++ b/chromium/chrome/browser/resources/extensions/shortcut_input.html @@ -1,47 +1,31 @@ -<link rel="import" href="chrome://resources/html/polymer.html"> +<style include="cr-icons cr-hidden-style"> + #main { + position: relative; + width: 200px; + } -<link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html"> -<link rel="import" href="chrome://resources/cr_elements/cr_icons_css.html"> -<link rel="import" href="chrome://resources/cr_elements/cr_input/cr_input.html"> -<link rel="import" href="chrome://resources/cr_elements/hidden_style_css.html"> -<link rel="import" href="chrome://resources/html/assert.html"> -<link rel="import" href="chrome://resources/html/cr.html"> -<link rel="import" href="chrome://resources/html/i18n_behavior.html"> -<link rel="import" href="chrome://resources/polymer/v1_0/paper-styles/color.html"> -<link rel="import" href="shortcut_util.html"> + #clear { + --cr-icon-button-size: 28px; + position: absolute; + right: 2px; + } -<dom-module id="extensions-shortcut-input"> - <template> - <style include="cr-icons cr-hidden-style"> - #main { - position: relative; - width: 200px; - } - - #clear { - --cr-icon-button-size: 28px; - position: absolute; - right: 2px; - } - - :host-context([dir='rtl']) #clear { - left: -2px; - right: inherit; - } - </style> - <div id="main"> - <cr-input id="input" placeholder="$i18n{shortcutTypeAShortcut}" - error-message="[[getErrorString_(error_, - '$i18nPolymer{shortcutIncludeStartModifier}', - '$i18nPolymer{shortcutTooManyModifiers}', - '$i18nPolymer{shortcutNeedCharacter}')]]" - value="[[computeText_(capturing_, shortcut, pendingShortcut_)]]"> - <cr-icon-button id="clear" aria-label="$i18nPolymer{clear}" - slot="suffix" class="icon-cancel no-overlap" - hidden$="[[computeClearHidden_(capturing_, shortcut)]]" - on-click="onClearTap_"></cr-icon-button> - </cr-input> - </div> - </template> - <script src="shortcut_input.js"></script> -</dom-module> + :host-context([dir='rtl']) #clear { + left: -2px; + right: inherit; + } +</style> +<div id="main"> + <cr-input id="input" placeholder="$i18n{shortcutTypeAShortcut}" + invalid="[[getIsInvalid_(error_)]]" + error-message="[[getErrorString_(error_, + '$i18nPolymer{shortcutIncludeStartModifier}', + '$i18nPolymer{shortcutTooManyModifiers}', + '$i18nPolymer{shortcutNeedCharacter}')]]" + value="[[computeText_(capturing_, shortcut, pendingShortcut_)]]"> + <cr-icon-button id="clear" aria-label="$i18nPolymer{clear}" + slot="suffix" class="icon-cancel no-overlap" + hidden$="[[computeClearHidden_(capturing_, shortcut)]]" + on-click="onClearTap_"></cr-icon-button> + </cr-input> +</div> diff --git a/chromium/chrome/browser/resources/extensions/shortcut_input.js b/chromium/chrome/browser/resources/extensions/shortcut_input.js index 43f7d273ed1..6f56ac6c1aa 100644 --- a/chromium/chrome/browser/resources/extensions/shortcut_input.js +++ b/chromium/chrome/browser/resources/extensions/shortcut_input.js @@ -2,6 +2,18 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'chrome://resources/cr_elements/cr_icon_button/cr_icon_button.m.js'; +import 'chrome://resources/cr_elements/cr_icons_css.m.js'; +import 'chrome://resources/cr_elements/cr_input/cr_input.m.js'; +import 'chrome://resources/cr_elements/hidden_style_css.m.js'; +import 'chrome://resources/polymer/v3_0/paper-styles/color.js'; + +import {assert} from 'chrome://resources/js/assert.m.js'; +import {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; + +import {KeyboardShortcutDelegate} from './keyboard_shortcut_delegate.js'; +import {hasValidModifiers, isValidKeyCode, Key, keystrokeToString} from './shortcut_util.js'; + /** @enum {number} */ const ShortcutError = { NO_ERROR: 0, @@ -10,228 +22,233 @@ const ShortcutError = { NEED_CHARACTER: 3, }; -cr.define('extensions', function() { - 'use strict'; - - // The UI to display and manage keyboard shortcuts set for extension commands. - const ShortcutInput = Polymer({ - is: 'extensions-shortcut-input', - - properties: { - /** @type {!extensions.KeyboardShortcutDelegate} */ - delegate: Object, - - item: { - type: String, - value: '', - }, - - commandName: { - type: String, - value: '', - }, - - shortcut: { - type: String, - value: '', - }, - - /** @private */ - capturing_: { - type: Boolean, - value: false, - }, - - /** @private {!ShortcutError} */ - error_: { - type: Number, - value: 0, - }, - - /** @private */ - pendingShortcut_: { - type: String, - value: '', - }, - }, +// The UI to display and manage keyboard shortcuts set for extension commands. +Polymer({ + is: 'extensions-shortcut-input', - /** @override */ - ready: function() { - const node = this.$.input; - node.addEventListener('mouseup', this.startCapture_.bind(this)); - node.addEventListener('blur', this.endCapture_.bind(this)); - node.addEventListener('focus', this.startCapture_.bind(this)); - node.addEventListener('keydown', this.onKeyDown_.bind(this)); - node.addEventListener('keyup', this.onKeyUp_.bind(this)); - }, + _template: html`{__html_template__}`, - /** @private */ - startCapture_: function() { - if (this.capturing_) { - return; - } - this.capturing_ = true; - this.delegate.setShortcutHandlingSuspended(true); - }, + properties: { + /** @type {!KeyboardShortcutDelegate} */ + delegate: Object, - /** @private */ - endCapture_: function() { - if (!this.capturing_) { - return; - } - this.pendingShortcut_ = ''; - this.capturing_ = false; - const input = this.$.input; - input.blur(); - input.invalid = false; - this.delegate.setShortcutHandlingSuspended(false); + item: { + type: String, + value: '', }, - /** - * @param {!KeyboardEvent} e - * @private - */ - onKeyDown_: function(e) { - if (e.target == this.$.clear) { - return; - } - - if (e.keyCode == extensions.Key.Escape) { - if (!this.capturing_) { - // If we're not currently capturing, allow escape to propagate. - return; - } - // Otherwise, escape cancels capturing. - this.endCapture_(); - e.preventDefault(); - e.stopPropagation(); - return; - } - if (e.keyCode == extensions.Key.Tab) { - // Allow tab propagation for keyboard navigation. - return; - } - - if (!this.capturing_) { - this.startCapture_(); - } - - this.handleKey_(e); + commandName: { + type: String, + value: '', }, - /** - * @param {!KeyboardEvent} e - * @private - */ - onKeyUp_: function(e) { - // Ignores pressing 'Space' or 'Enter' on the clear button. In 'Enter's - // case, the clear button disappears before key-up, so 'Enter's key-up - // target becomes the input field, not the clear button, and needs to - // be caught explicitly. - if (e.target == this.$.clear || e.key == 'Enter') { - return; - } - - if (e.keyCode == extensions.Key.Escape || - e.keyCode == extensions.Key.Tab) { - return; - } - - this.handleKey_(e); - }, - - /** - * @param {!ShortcutError} error - * @param {string} includeStartModifier - * @param {string} tooManyModifiers - * @param {string} needCharacter - * @return {string} UI string. - * @private - */ - getErrorString_: function( - error, includeStartModifier, tooManyModifiers, needCharacter) { - if (error == ShortcutError.TOO_MANY_MODIFIERS) { - return tooManyModifiers; - } - if (error == ShortcutError.NEED_CHARACTER) { - return needCharacter; - } - return includeStartModifier; - }, - - /** - * @param {!KeyboardEvent} e - * @private - */ - handleKey_: function(e) { - // While capturing, we prevent all events from bubbling, to prevent - // shortcuts lacking the right modifier (F3 for example) from activating - // and ending capture prematurely. - e.preventDefault(); - e.stopPropagation(); - - // We don't allow both Ctrl and Alt in the same keybinding. - // TODO(devlin): This really should go in extensions.hasValidModifiers, - // but that requires updating the existing page as well. - if (e.ctrlKey && e.altKey) { - this.error_ = ShortcutError.TOO_MANY_MODIFIERS; - this.$.input.invalid = true; - return; - } - if (!extensions.hasValidModifiers(e)) { - this.pendingShortcut_ = ''; - this.error_ = ShortcutError.INCLUDE_START_MODIFIER; - this.$.input.invalid = true; - return; - } - this.pendingShortcut_ = extensions.keystrokeToString(e); - if (!extensions.isValidKeyCode(e.keyCode)) { - this.error_ = ShortcutError.NEED_CHARACTER; - this.$.input.invalid = true; - return; - } - this.$.input.invalid = false; - - this.commitPending_(); - this.endCapture_(); + shortcut: { + type: String, + value: '', }, /** @private */ - commitPending_: function() { - this.shortcut = this.pendingShortcut_; - this.delegate.updateExtensionCommandKeybinding( - this.item, this.commandName, this.shortcut); - }, - - /** - * @return {string} The text to be displayed in the shortcut field. - * @private - */ - computeText_: function() { - const shortcutString = - this.capturing_ ? this.pendingShortcut_ : this.shortcut; - return shortcutString.split('+').join(' + '); + capturing_: { + type: Boolean, + value: false, }, - /** - * @return {boolean} Whether the clear button is hidden. - * @private - */ - computeClearHidden_: function() { - // We don't want to show the clear button if the input is currently - // capturing a new shortcut or if there is no shortcut to clear. - return this.capturing_ || !this.shortcut; + /** @private {!ShortcutError} */ + error_: { + type: Number, + value: ShortcutError.NO_ERROR, }, /** @private */ - onClearTap_: function() { - assert(this.shortcut); - - this.pendingShortcut_ = ''; - this.commitPending_(); - this.endCapture_(); + pendingShortcut_: { + type: String, + value: '', }, - }); - - return {ShortcutInput: ShortcutInput}; + }, + + /** @override */ + ready: function() { + const node = this.$.input; + node.addEventListener('mouseup', this.startCapture_.bind(this)); + node.addEventListener('blur', this.endCapture_.bind(this)); + node.addEventListener('focus', this.startCapture_.bind(this)); + node.addEventListener('keydown', this.onKeyDown_.bind(this)); + node.addEventListener('keyup', this.onKeyUp_.bind(this)); + }, + + /** @private */ + startCapture_: function() { + if (this.capturing_) { + return; + } + this.capturing_ = true; + this.delegate.setShortcutHandlingSuspended(true); + }, + + /** @private */ + endCapture_: function() { + if (!this.capturing_) { + return; + } + this.pendingShortcut_ = ''; + this.capturing_ = false; + const input = this.$.input; + input.blur(); + this.error_ = ShortcutError.NO_ERROR; + this.delegate.setShortcutHandlingSuspended(false); + }, + + /** + * @param {!Event} e + * @private + */ + onKeyDown_: function(e) { + if (e.target == this.$.clear) { + return; + } + + if (e.keyCode == Key.Escape) { + if (!this.capturing_) { + // If we're not currently capturing, allow escape to propagate. + return; + } + // Otherwise, escape cancels capturing. + this.endCapture_(); + e.preventDefault(); + e.stopPropagation(); + return; + } + if (e.keyCode == Key.Tab) { + // Allow tab propagation for keyboard navigation. + return; + } + + if (!this.capturing_) { + this.startCapture_(); + } + + this.handleKey_(/** @type {!KeyboardEvent} */ (e)); + }, + + /** + * @param {!Event} e + * @private + */ + onKeyUp_: function(e) { + // Ignores pressing 'Space' or 'Enter' on the clear button. In 'Enter's + // case, the clear button disappears before key-up, so 'Enter's key-up + // target becomes the input field, not the clear button, and needs to + // be caught explicitly. + if (e.target == this.$.clear || e.key == 'Enter') { + return; + } + + if (e.keyCode == Key.Escape || e.keyCode == Key.Tab) { + return; + } + + this.handleKey_(/** @type {!KeyboardEvent} */ (e)); + }, + + /** + * @param {!ShortcutError} error + * @param {string} includeStartModifier + * @param {string} tooManyModifiers + * @param {string} needCharacter + * @return {string} UI string. + * @private + */ + getErrorString_: function( + error, includeStartModifier, tooManyModifiers, needCharacter) { + switch (this.error_) { + case ShortcutError.INCLUDE_START_MODIFIER: + return includeStartModifier; + case ShortcutError.TOO_MANY_MODIFIERS: + return tooManyModifiers; + case ShortcutError.NEED_CHARACTER: + return needCharacter; + default: + assert(this.error_ == ShortcutError.NO_ERROR); + return ''; + } + }, + + /** + * @param {!KeyboardEvent} e + * @private + */ + handleKey_: function(e) { + // While capturing, we prevent all events from bubbling, to prevent + // shortcuts lacking the right modifier (F3 for example) from activating + // and ending capture prematurely. + e.preventDefault(); + e.stopPropagation(); + + // We don't allow both Ctrl and Alt in the same keybinding. + // TODO(devlin): This really should go in hasValidModifiers, + // but that requires updating the existing page as well. + if (e.ctrlKey && e.altKey) { + this.error_ = ShortcutError.TOO_MANY_MODIFIERS; + return; + } + if (!hasValidModifiers(e)) { + this.pendingShortcut_ = ''; + this.error_ = ShortcutError.INCLUDE_START_MODIFIER; + return; + } + this.pendingShortcut_ = keystrokeToString(e); + if (!isValidKeyCode(e.keyCode)) { + this.error_ = ShortcutError.NEED_CHARACTER; + return; + } + + this.error_ = ShortcutError.NO_ERROR; + + this.commitPending_(); + this.endCapture_(); + }, + + /** @private */ + commitPending_: function() { + this.shortcut = this.pendingShortcut_; + this.delegate.updateExtensionCommandKeybinding( + this.item, this.commandName, this.shortcut); + }, + + /** + * @return {string} The text to be displayed in the shortcut field. + * @private + */ + computeText_: function() { + const shortcutString = + this.capturing_ ? this.pendingShortcut_ : this.shortcut; + return shortcutString.split('+').join(' + '); + }, + + /** + * @return {boolean} Whether the clear button is hidden. + * @private + */ + computeClearHidden_: function() { + // We don't want to show the clear button if the input is currently + // capturing a new shortcut or if there is no shortcut to clear. + return this.capturing_ || !this.shortcut; + }, + + /** + * @return {boolean} + * @private + */ + getIsInvalid_: function() { + return this.error_ != ShortcutError.NO_ERROR; + }, + + /** @private */ + onClearTap_: function() { + assert(this.shortcut); + + this.pendingShortcut_ = ''; + this.commitPending_(); + this.endCapture_(); + }, }); diff --git a/chromium/chrome/browser/resources/extensions/shortcut_util.html b/chromium/chrome/browser/resources/extensions/shortcut_util.html deleted file mode 100644 index e44359faac7..00000000000 --- a/chromium/chrome/browser/resources/extensions/shortcut_util.html +++ /dev/null @@ -1,3 +0,0 @@ -<link rel="import" href="chrome://resources/html/assert.html"> -<link rel="import" href="chrome://resources/html/cr.html"> -<script src="shortcut_util.js"></script> diff --git a/chromium/chrome/browser/resources/extensions/shortcut_util.js b/chromium/chrome/browser/resources/extensions/shortcut_util.js index 05bb904c9bd..0f61a402dab 100644 --- a/chromium/chrome/browser/resources/extensions/shortcut_util.js +++ b/chromium/chrome/browser/resources/extensions/shortcut_util.js @@ -2,202 +2,198 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -cr.define('extensions', function() { - 'use strict'; +import {assertNotReached} from 'chrome://resources/js/assert.m.js'; +import {isChromeOS, isMac} from 'chrome://resources/js/cr.m.js'; - /** @enum {number} */ - const Key = { - Comma: 188, - Del: 46, - Down: 40, - End: 35, - Escape: 27, - Home: 36, - Ins: 45, - Left: 37, - MediaNextTrack: 176, - MediaPlayPause: 179, - MediaPrevTrack: 177, - MediaStop: 178, - PageDown: 34, - PageUp: 33, - Period: 190, - Right: 39, - Space: 32, - Tab: 9, - Up: 38, - }; - /** - * Enum for whether we require modifiers of a keycode. - * @enum {number} - */ - const ModifierPolicy = {NOT_ALLOWED: 0, REQUIRED: 1}; +/** @enum {number} */ +export const Key = { + Comma: 188, + Del: 46, + Down: 40, + End: 35, + Escape: 27, + Home: 36, + Ins: 45, + Left: 37, + MediaNextTrack: 176, + MediaPlayPause: 179, + MediaPrevTrack: 177, + MediaStop: 178, + PageDown: 34, + PageUp: 33, + Period: 190, + Right: 39, + Space: 32, + Tab: 9, + Up: 38, +}; - /** - * Gets the ModifierPolicy. Currently only "MediaNextTrack", "MediaPrevTrack", - * "MediaStop", "MediaPlayPause" are required to be used without any modifier. - * @param {number} keyCode - * @return {ModifierPolicy} - */ - function getModifierPolicy(keyCode) { - switch (keyCode) { - case Key.MediaNextTrack: - case Key.MediaPlayPause: - case Key.MediaPrevTrack: - case Key.MediaStop: - return ModifierPolicy.NOT_ALLOWED; - default: - return ModifierPolicy.REQUIRED; - } - } +/** + * Enum for whether we require modifiers of a keycode. + * @enum {number} + */ +const ModifierPolicy = { + NOT_ALLOWED: 0, + REQUIRED: 1 +}; - /** - * Returns whether the keyboard event has a key modifier, which could affect - * how it's handled. - * @param {!KeyboardEvent} e - * @param {boolean} countShiftAsModifier Whether the 'Shift' key should be - * counted as modifier. - * @return {boolean} True if the event has any modifiers. - */ - function hasModifier(e, countShiftAsModifier) { - return e.ctrlKey || e.altKey || - // Meta key is only relevant on Mac and CrOS, where we treat Command - // and Search (respectively) as modifiers. - (cr.isMac && e.metaKey) || (cr.isChromeOS && e.metaKey) || - (countShiftAsModifier && e.shiftKey); +/** + * Gets the ModifierPolicy. Currently only "MediaNextTrack", "MediaPrevTrack", + * "MediaStop", "MediaPlayPause" are required to be used without any modifier. + * @param {number} keyCode + * @return {ModifierPolicy} + */ +function getModifierPolicy(keyCode) { + switch (keyCode) { + case Key.MediaNextTrack: + case Key.MediaPlayPause: + case Key.MediaPrevTrack: + case Key.MediaStop: + return ModifierPolicy.NOT_ALLOWED; + default: + return ModifierPolicy.REQUIRED; } +} - /** - * Checks whether the passed in |keyCode| is a valid extension command key. - * @param {number} keyCode - * @return {boolean} Whether the key is valid. - */ - function isValidKeyCode(keyCode) { - if (keyCode == Key.Escape) { - return false; - } - for (const k in Key) { - if (Key[k] == keyCode) { - return true; - } +/** + * Returns whether the keyboard event has a key modifier, which could affect + * how it's handled. + * @param {!KeyboardEvent} e + * @param {boolean} countShiftAsModifier Whether the 'Shift' key should be + * counted as modifier. + * @return {boolean} True if the event has any modifiers. + */ +function hasModifier(e, countShiftAsModifier) { + return e.ctrlKey || e.altKey || + // Meta key is only relevant on Mac and CrOS, where we treat Command + // and Search (respectively) as modifiers. + (isMac && e.metaKey) || (isChromeOS && e.metaKey) || + (countShiftAsModifier && e.shiftKey); +} + +/** + * Checks whether the passed in |keyCode| is a valid extension command key. + * @param {number} keyCode + * @return {boolean} Whether the key is valid. + */ +export function isValidKeyCode(keyCode) { + if (keyCode == Key.Escape) { + return false; + } + for (const k in Key) { + if (Key[k] == keyCode) { + return true; } - return (keyCode >= 'A'.charCodeAt(0) && keyCode <= 'Z'.charCodeAt(0)) || - (keyCode >= '0'.charCodeAt(0) && keyCode <= '9'.charCodeAt(0)); } + return (keyCode >= 'A'.charCodeAt(0) && keyCode <= 'Z'.charCodeAt(0)) || + (keyCode >= '0'.charCodeAt(0) && keyCode <= '9'.charCodeAt(0)); +} - /** - * Converts a keystroke event to string form, ignoring invalid extension - * commands. - * @param {!KeyboardEvent} e - * @return {string} The keystroke as a string. - */ - function keystrokeToString(e) { - const output = []; - // TODO(devlin): Should this be i18n'd? - if (cr.isMac && e.metaKey) { - output.push('Command'); - } - if (cr.isChromeOS && e.metaKey) { - output.push('Search'); - } - if (e.ctrlKey) { - output.push('Ctrl'); - } - if (!e.ctrlKey && e.altKey) { - output.push('Alt'); - } - if (e.shiftKey) { - output.push('Shift'); - } +/** + * Converts a keystroke event to string form, ignoring invalid extension + * commands. + * @param {!KeyboardEvent} e + * @return {string} The keystroke as a string. + */ +export function keystrokeToString(e) { + const output = []; + // TODO(devlin): Should this be i18n'd? + if (isMac && e.metaKey) { + output.push('Command'); + } + if (isChromeOS && e.metaKey) { + output.push('Search'); + } + if (e.ctrlKey) { + output.push('Ctrl'); + } + if (!e.ctrlKey && e.altKey) { + output.push('Alt'); + } + if (e.shiftKey) { + output.push('Shift'); + } - const keyCode = e.keyCode; - if (isValidKeyCode(keyCode)) { - if ((keyCode >= 'A'.charCodeAt(0) && keyCode <= 'Z'.charCodeAt(0)) || - (keyCode >= '0'.charCodeAt(0) && keyCode <= '9'.charCodeAt(0))) { - output.push(String.fromCharCode(keyCode)); - } else { - switch (keyCode) { - case Key.Comma: - output.push('Comma'); - break; - case Key.Del: - output.push('Delete'); - break; - case Key.Down: - output.push('Down'); - break; - case Key.End: - output.push('End'); - break; - case Key.Home: - output.push('Home'); - break; - case Key.Ins: - output.push('Insert'); - break; - case Key.Left: - output.push('Left'); - break; - case Key.MediaNextTrack: - output.push('MediaNextTrack'); - break; - case Key.MediaPlayPause: - output.push('MediaPlayPause'); - break; - case Key.MediaPrevTrack: - output.push('MediaPrevTrack'); - break; - case Key.MediaStop: - output.push('MediaStop'); - break; - case Key.PageDown: - output.push('PageDown'); - break; - case Key.PageUp: - output.push('PageUp'); - break; - case Key.Period: - output.push('Period'); - break; - case Key.Right: - output.push('Right'); - break; - case Key.Space: - output.push('Space'); - break; - case Key.Tab: - output.push('Tab'); - break; - case Key.Up: - output.push('Up'); - break; - } + const keyCode = e.keyCode; + if (isValidKeyCode(keyCode)) { + if ((keyCode >= 'A'.charCodeAt(0) && keyCode <= 'Z'.charCodeAt(0)) || + (keyCode >= '0'.charCodeAt(0) && keyCode <= '9'.charCodeAt(0))) { + output.push(String.fromCharCode(keyCode)); + } else { + switch (keyCode) { + case Key.Comma: + output.push('Comma'); + break; + case Key.Del: + output.push('Delete'); + break; + case Key.Down: + output.push('Down'); + break; + case Key.End: + output.push('End'); + break; + case Key.Home: + output.push('Home'); + break; + case Key.Ins: + output.push('Insert'); + break; + case Key.Left: + output.push('Left'); + break; + case Key.MediaNextTrack: + output.push('MediaNextTrack'); + break; + case Key.MediaPlayPause: + output.push('MediaPlayPause'); + break; + case Key.MediaPrevTrack: + output.push('MediaPrevTrack'); + break; + case Key.MediaStop: + output.push('MediaStop'); + break; + case Key.PageDown: + output.push('PageDown'); + break; + case Key.PageUp: + output.push('PageUp'); + break; + case Key.Period: + output.push('Period'); + break; + case Key.Right: + output.push('Right'); + break; + case Key.Space: + output.push('Space'); + break; + case Key.Tab: + output.push('Tab'); + break; + case Key.Up: + output.push('Up'); + break; } } - - return output.join('+'); } - /** - * Returns true if the event has valid modifiers. - * @param {!KeyboardEvent} e The keyboard event to consider. - * @return {boolean} True if the event is valid. - */ - function hasValidModifiers(e) { - switch (getModifierPolicy(e.keyCode)) { - case ModifierPolicy.REQUIRED: - return hasModifier(e, false); - case ModifierPolicy.NOT_ALLOWED: - return !hasModifier(e, true); - } - assertNotReached(); - } + return output.join('+'); +} - return { - isValidKeyCode: isValidKeyCode, - keystrokeToString: keystrokeToString, - hasValidModifiers: hasValidModifiers, - Key: Key, - }; -}); +/** + * Returns true if the event has valid modifiers. + * @param {!KeyboardEvent} e The keyboard event to consider. + * @return {boolean} True if the event is valid. + */ +export function hasValidModifiers(e) { + switch (getModifierPolicy(e.keyCode)) { + case ModifierPolicy.REQUIRED: + return hasModifier(e, false); + case ModifierPolicy.NOT_ALLOWED: + return !hasModifier(e, true); + } + assertNotReached(); +} diff --git a/chromium/chrome/browser/resources/extensions/sidebar.html b/chromium/chrome/browser/resources/extensions/sidebar.html index 4024fb27744..3aa0c784932 100644 --- a/chromium/chrome/browser/resources/extensions/sidebar.html +++ b/chromium/chrome/browser/resources/extensions/sidebar.html @@ -1,94 +1,80 @@ -<link rel="import" href="chrome://resources/html/polymer.html"> +<style include="cr-icons"> + :host { + --sidebar-inactive-color: #5a5a5a; + color: var(--sidebar-inactive-color); + display: flex; + flex-direction: column; + height: 100%; + justify-content: space-between; + overflow-x: hidden; + overflow-y: auto; + width: 256px; + } -<link rel="import" href="chrome://resources/cr_elements/cr_icons_css.html"> -<link rel="import" href="chrome://resources/html/cr.html"> -<link rel="import" href="chrome://resources/polymer/v1_0/iron-selector/iron-selector.html"> -<link rel="import" href="chrome://resources/polymer/v1_0/paper-ripple/paper-ripple.html"> -<link rel="import" href="chrome://resources/polymer/v1_0/paper-styles/color.html"> -<link rel="import" href="navigation_helper.html"> + @media (prefers-color-scheme: dark) { + :host { + --sidebar-inactive-color: var(--cr-primary-text-color); + } + } -<dom-module id="extensions-sidebar"> - <template> - <style include="cr-icons"> - :host { - --sidebar-inactive-color: #5a5a5a; - color: var(--sidebar-inactive-color); - display: flex; - flex-direction: column; - height: 100%; - justify-content: space-between; - overflow-x: hidden; - overflow-y: auto; - width: 256px; - } + iron-selector .iron-selected { + color: var(--cr-link-color); + } - @media (prefers-color-scheme: dark) { - :host { - --sidebar-inactive-color: var(--cr-primary-text-color); - } - } + #sectionMenu { + padding-top: 8px; + user-select: none; + } - iron-selector .iron-selected { - color: var(--cr-link-color); - } + .section-item { + align-items: center; + color: inherit; + display: flex; + font-weight: 500; + /* Ensure the focus outline appears correctly (crbug.com/655503). */ + margin-inline-end: 4px; + min-height: 40px; + padding-inline-start: 24px; + position: relative; + text-decoration: none; + } - #sectionMenu { - padding-top: 8px; - user-select: none; - } + .separator { + border-top: var(--cr-separator-line); + margin: 8px 0; + } - .section-item { - align-items: center; - color: inherit; - display: flex; - font-weight: 500; - /* Ensure the focus outline appears correctly (crbug.com/655503). */ - margin-inline-end: 4px; - min-height: 40px; - padding-inline-start: 24px; - position: relative; - text-decoration: none; - } + #more-extensions { + align-items: center; + display: flex; + justify-content: space-between; + margin-bottom: 8px; + } - .separator { - border-top: var(--cr-separator-line); - margin: 8px 0; - } - - #more-extensions { - align-items: center; - display: flex; - justify-content: space-between; - margin-bottom: 8px; - } - - .cr-icon { - margin-inline-end: calc( - var(--cr-section-padding) - var(--cr-icon-ripple-padding)); - } - </style> - <iron-selector id="sectionMenu"> - <!-- Values for "data-path" attribute must match the "Page" enum. --> - <a class="section-item" id="sections-extensions" href="/" - on-click="onLinkTap_" data-path="items-list"> - $i18n{sidebarExtensions} - <paper-ripple></paper-ripple> - </a> - <a class="section-item" id="sections-shortcuts" href="/shortcuts" - on-click="onLinkTap_" data-path="keyboard-shortcuts"> - $i18n{keyboardShortcuts} - <paper-ripple></paper-ripple> - </a> - </iron-selector> - <div hidden="[[isSupervised]]"> - <div class="separator"></div> - <a class="section-item" id="more-extensions" target="_blank" - href="$i18n{getMoreExtensionsUrl}" on-click="onMoreExtensionsTap_"> - <span>$i18n{openChromeWebStore}</span> - <div class="cr-icon icon-external"></div> - <paper-ripple></paper-ripple> - </a> - </div> - </template> - <script src="sidebar.js"></script> -</dom-module> + .cr-icon { + margin-inline-end: calc( + var(--cr-section-padding) - var(--cr-icon-ripple-padding)); + } +</style> +<iron-selector id="sectionMenu"> + <!-- Values for "data-path" attribute must match the "Page" enum. --> + <a class="section-item" id="sections-extensions" href="/" + on-click="onLinkTap_" data-path="items-list"> + $i18n{sidebarExtensions} + <paper-ripple></paper-ripple> + </a> + <a class="section-item" id="sections-shortcuts" href="/shortcuts" + on-click="onLinkTap_" data-path="keyboard-shortcuts"> + $i18n{keyboardShortcuts} + <paper-ripple></paper-ripple> + </a> +</iron-selector> +<div hidden="[[isSupervised]]"> + <div class="separator"></div> + <a class="section-item" id="more-extensions" target="_blank" + href="$i18n{getMoreExtensionsUrl}" on-click="onMoreExtensionsTap_"> + <span>$i18n{openChromeWebStore}</span> + <div class="cr-icon icon-external"></div> + <paper-ripple></paper-ripple> + </a> +</div> diff --git a/chromium/chrome/browser/resources/extensions/sidebar.js b/chromium/chrome/browser/resources/extensions/sidebar.js index 19619f17912..5252323fb94 100644 --- a/chromium/chrome/browser/resources/extensions/sidebar.js +++ b/chromium/chrome/browser/resources/extensions/sidebar.js @@ -1,43 +1,48 @@ // Copyright 2015 The Chromium 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('extensions', function() { - const Sidebar = Polymer({ - is: 'extensions-sidebar', - - properties: { - isSupervised: Boolean, - }, - - hostAttributes: { - role: 'navigation', - }, - - /** @override */ - attached: function() { - this.$.sectionMenu.select( - extensions.navigation.getCurrentPage().page == - extensions.Page.SHORTCUTS ? - 1 : - 0); - }, - - /** - * @param {!Event} e - * @private - */ - onLinkTap_: function(e) { - e.preventDefault(); - extensions.navigation.navigateTo({page: e.target.dataset.path}); - this.fire('close-drawer'); - }, - - /** @private */ - onMoreExtensionsTap_: function() { - assert(!this.isSupervised); - chrome.metricsPrivate.recordUserAction('Options_GetMoreExtensions'); - }, - }); - - return {Sidebar: Sidebar}; +import 'chrome://resources/cr_elements/cr_icons_css.m.js'; +import 'chrome://resources/polymer/v3_0/iron-selector/iron-selector.js'; +import 'chrome://resources/polymer/v3_0/paper-ripple/paper-ripple.js'; +import 'chrome://resources/polymer/v3_0/paper-styles/color.js'; + +import {assert} from 'chrome://resources/js/assert.m.js'; +import {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; + +import {navigation, Page} from './navigation_helper.js'; + +Polymer({ + is: 'extensions-sidebar', + + _template: html`{__html_template__}`, + + properties: { + isSupervised: Boolean, + }, + + hostAttributes: { + role: 'navigation', + }, + + /** @override */ + attached: function() { + this.$.sectionMenu.select( + navigation.getCurrentPage().page == Page.SHORTCUTS ? 1 : 0); + }, + + /** + * @param {!Event} e + * @private + */ + onLinkTap_: function(e) { + e.preventDefault(); + navigation.navigateTo({page: e.target.dataset.path}); + this.fire('close-drawer'); + }, + + /** @private */ + onMoreExtensionsTap_: function() { + assert(!this.isSupervised); + chrome.metricsPrivate.recordUserAction('Options_GetMoreExtensions'); + }, }); diff --git a/chromium/chrome/browser/resources/extensions/strings.html b/chromium/chrome/browser/resources/extensions/strings.html deleted file mode 100644 index cdc7c9b2a5b..00000000000 --- a/chromium/chrome/browser/resources/extensions/strings.html +++ /dev/null @@ -1,2 +0,0 @@ -<link rel="import" href="chrome://resources/html/load_time_data.html"> -<script src="strings.js"></script> diff --git a/chromium/chrome/browser/resources/extensions/toggle_row.html b/chromium/chrome/browser/resources/extensions/toggle_row.html index f8cdcb4aee9..68c0a299250 100644 --- a/chromium/chrome/browser/resources/extensions/toggle_row.html +++ b/chromium/chrome/browser/resources/extensions/toggle_row.html @@ -1,48 +1,38 @@ -<link rel="import" href="chrome://resources/html/polymer.html"> +<style> + :host { + flex-direction: column; + touch-action: none; + } -<link rel="import" href="chrome://resources/cr_elements/cr_toggle/cr_toggle.html"> -<link rel="import" href="chrome://resources/cr_elements/shared_style_css.html"> + input { + display: none; + } -<dom-module id="extensions-toggle-row"> - <template> - <style> - :host { - flex-direction: column; - touch-action: none; - } + label { + align-items: center; + box-sizing: border-box; + cursor: pointer; + display: flex; + flex: 1; + padding: var(--toggle-row-label-vertical-padding, 0) + var(--toggle-row-label-horizontal-padding, 0); + width: 100%; + } - input { - display: none; - } + cr-toggle { + display: inline-block; + } - label { - align-items: center; - box-sizing: border-box; - cursor: pointer; - display: flex; - flex: 1; - padding: var(--toggle-row-label-vertical-padding, 0) - var(--toggle-row-label-horizontal-padding, 0); - width: 100%; - } - - cr-toggle { - display: inline-block; - } - - :host ::slotted(*) { - flex: 1; - margin-inline-end: 20px; - } - </style> - <label id="label"> - <input id="native" type="checkbox" checked="[[checked]]" - on-change="onNativeChange_" on-click="onNativeClick_" - disabled="[[disabled]]"> - <slot></slot> - <cr-toggle id="crToggle" checked="{{checked}}" aria-labelledby="label" - on-change="onCrToggleChange_" disabled="[[disabled]]"></cr-toggle> - </label> - </template> - <script src="toggle_row.js"></script> -</dom-module> + :host ::slotted(*) { + flex: 1; + margin-inline-end: 20px; + } +</style> +<label id="label"> + <input id="native" type="checkbox" checked="[[checked]]" + on-change="onNativeChange_" on-click="onNativeClick_" + disabled="[[disabled]]"> + <slot></slot> + <cr-toggle id="crToggle" checked="{{checked}}" aria-labelledby="label" + on-change="onCrToggleChange_" disabled="[[disabled]]"></cr-toggle> +</label> diff --git a/chromium/chrome/browser/resources/extensions/toggle_row.js b/chromium/chrome/browser/resources/extensions/toggle_row.js index c4f67baec15..43be17b5f42 100644 --- a/chromium/chrome/browser/resources/extensions/toggle_row.js +++ b/chromium/chrome/browser/resources/extensions/toggle_row.js @@ -2,73 +2,75 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -cr.define('extensions', function() { - 'use strict'; +import 'chrome://resources/cr_elements/cr_toggle/cr_toggle.m.js'; +import 'chrome://resources/cr_elements/shared_style_css.m.js'; + +import {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; - /** - * An extensions-toggle-row provides a way of having a clickable row that can - * modify a cr-toggle, by leveraging the native <label> functionality. It uses - * a hidden native <input type="checkbox"> to achieve this. - */ - const ExtensionsToggleRow = Polymer({ - is: 'extensions-toggle-row', - properties: { - checked: Boolean, +/** + * An extensions-toggle-row provides a way of having a clickable row that can + * modify a cr-toggle, by leveraging the native <label> functionality. It uses + * a hidden native <input type="checkbox"> to achieve this. + */ +Polymer({ + is: 'extensions-toggle-row', - disabled: Boolean, - }, + _template: html`{__html_template__}`, - /** - * Exposing the clickable part of extensions-toggle-row for testing - * purposes. - * @return {!HTMLElement} - */ - getLabel() { - return this.$.label; - }, + properties: { + checked: Boolean, - /** - * @param {!Event} - * @private - */ - onNativeClick_: function(e) { - // Even though the native checkbox is hidden and can't be actually - // cilcked/tapped by the user, because it resides within the <label> the - // browser emits an extraneous event when the label is clicked. Stop - // propagation so that it does not interfere with |onLabelTap_| listener. - e.stopPropagation(); - }, + disabled: Boolean, + }, + + /** + * Exposing the clickable part of extensions-toggle-row for testing + * purposes. + * @return {!HTMLElement} + */ + getLabel() { + return /** @type {!HTMLElement} */ (this.$.label); + }, - /** - * Fires when the native checkbox changes value. This happens when the user - * clicks directly on the <label>. - * @param {!Event} e - * @private - */ - onNativeChange_: function(e) { - e.stopPropagation(); + /** + * @param {!Event} e + * @private + */ + onNativeClick_: function(e) { + // Even though the native checkbox is hidden and can't be actually + // cilcked/tapped by the user, because it resides within the <label> the + // browser emits an extraneous event when the label is clicked. Stop + // propagation so that it does not interfere with |onLabelTap_| listener. + e.stopPropagation(); + }, - // Sync value of native checkbox and cr-toggle and |checked|. - this.$.crToggle.checked = this.$.native.checked; - this.checked = this.$.native.checked; + /** + * Fires when the native checkbox changes value. This happens when the user + * clicks directly on the <label>. + * @param {!Event} e + * @private + */ + onNativeChange_: function(e) { + e.stopPropagation(); - this.fire('change', this.checked); - }, + // Sync value of native checkbox and cr-toggle and |checked|. + this.$.crToggle.checked = this.$.native.checked; + this.checked = this.$.native.checked; - /** - * @param {!CustomEvent<boolean>} e - * @private - */ - onCrToggleChange_: function(e) { - e.stopPropagation(); + this.fire('change', this.checked); + }, - // Sync value of native checkbox and cr-toggle. - this.$.native.checked = e.detail; + /** + * @param {!CustomEvent<boolean>} e + * @private + */ + onCrToggleChange_: function(e) { + e.stopPropagation(); - this.fire('change', this.checked); - }, - }); + // Sync value of native checkbox and cr-toggle. + this.$.native.checked = e.detail; - return {ExtensionsToggleRow: ExtensionsToggleRow}; + this.fire('change', this.checked); + }, }); diff --git a/chromium/chrome/browser/resources/extensions/toolbar.html b/chromium/chrome/browser/resources/extensions/toolbar.html index 38cf1124e61..fcc00263fd8 100644 --- a/chromium/chrome/browser/resources/extensions/toolbar.html +++ b/chromium/chrome/browser/resources/extensions/toolbar.html @@ -1,141 +1,119 @@ -<link rel="import" href="chrome://resources/html/polymer.html"> +<style include="cr-hidden-style"> + :host { + --border-bottom-height: 1px; + --button-row-height: calc(2 * var(--padding-top-bottom) + + var(--cr-button-height)); + --drawer-transition: 0.3s cubic-bezier(.25, .1, .25, 1); + --padding-top-bottom: 10px; + } -<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html"> -<link rel="import" href="chrome://resources/cr_elements/cr_toast/cr_toast_manager.html"> -<link rel="import" href="chrome://resources/cr_elements/cr_toggle/cr_toggle.html"> -<link rel="import" href="chrome://resources/cr_elements/cr_toolbar/cr_toolbar.html"> -<link rel="import" href="chrome://resources/cr_elements/hidden_style_css.html"> -<link rel="import" href="chrome://resources/cr_elements/policy/cr_tooltip_icon.html"> -<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html"> -<link rel="import" href="chrome://resources/html/assert.html"> -<link rel="import" href="chrome://resources/html/cr.html"> -<link rel="import" href="chrome://resources/html/util.html"> -<link rel="import" href="chrome://resources/html/i18n_behavior.html"> -<link rel="import" href="chrome://resources/polymer/v1_0/iron-a11y-announcer/iron-a11y-announcer.html"> -<link rel="import" href="chrome://resources/polymer/v1_0/paper-styles/color.html"> -<link rel="import" href="pack_dialog.html"> + /* This toggle needs special styling because it's on blue background. */ + @media (prefers-color-scheme: light) { + cr-toolbar cr-toggle { + --cr-toggle-checked-bar-color: var(--google-grey-refresh-100); + --cr-toggle-checked-button-color: white; + --cr-toggle-checked-ripple-color: rgba(255, 255, 255, .2); + --cr-toggle-unchecked-bar-color: var(--google-grey-600); + --cr-toggle-unchecked-ripple-color: rgba(255, 255, 255, .15); + } + } -<dom-module id="extensions-toolbar"> - <template> - <style include="cr-hidden-style"> - :host { - --border-bottom-height: 1px; - --button-row-height: calc(2 * var(--padding-top-bottom) + - var(--cr-button-height)); - --drawer-transition: 0.3s cubic-bezier(.25, .1, .25, 1); - --padding-top-bottom: 10px; - } + cr-tooltip-icon { + margin-inline-end: 20px; + } - /* This toggle needs special styling because it's on blue background. */ - @media (prefers-color-scheme: light) { - cr-toolbar cr-toggle { - --cr-toggle-checked-bar-color: var(--google-grey-refresh-100); - --cr-toggle-checked-button-color: white; - --cr-toggle-checked-ink-color: white; - --cr-toggle-unchecked-bar-color: var(--google-grey-600); - --cr-toggle-unchecked-ink-color: white; - } - } + #devDrawer[expanded] #buttonStrip { + top: 0; + } - cr-tooltip-icon { - margin-inline-end: 20px; - } + #devDrawer { + background: #fff; + border-bottom: 1px solid var(--google-grey-300); + height: 0; + overflow-x: hidden; + overflow-y: auto; + position: relative; + transition: height var(--drawer-transition); + } - #devDrawer[expanded] #buttonStrip { - top: 0; - } + @media (prefers-color-scheme: dark) { + #devDrawer { + background: none; + border-bottom-color: var(--cr-separator-color); + } + } - #devDrawer { - background: #fff; - border-bottom: 1px solid var(--google-grey-300); - height: 0; - overflow-x: hidden; - overflow-y: auto; - position: relative; - transition: height var(--drawer-transition); - } + #devDrawer[expanded] { + height: calc(var(--button-row-height) + var(--border-bottom-height)); + } - @media (prefers-color-scheme: dark) { - #devDrawer { - background: none; - border-bottom-color: var(--cr-separator-color); - } - } + #buttonStrip { + margin-inline-end: auto; + margin-inline-start: 24px; + padding: var(--padding-top-bottom) 0; + position: absolute; + top: calc(var(--button-row-height) * -1); + transition: top var(--drawer-transition); + /* Prevent selection of the blank space between buttons. */ + user-select: none; + width: 100%; + } - #devDrawer[expanded] { - height: calc(var(--button-row-height) + var(--border-bottom-height)); - } + #buttonStrip cr-button { + margin-inline-end: 16px; + } - #buttonStrip { - margin-inline-end: auto; - margin-inline-start: 24px; - padding: var(--padding-top-bottom) 0; - position: absolute; - top: calc(var(--button-row-height) * -1); - transition: top var(--drawer-transition); - /* Prevent selection of the blank space between buttons. */ - user-select: none; - width: 100%; - } + .more-actions { + align-items: center; + display: flex; + justify-content: flex-end; + } - #buttonStrip cr-button { - margin-inline-end: 16px; - } - - .more-actions { - align-items: center; - display: flex; - justify-content: flex-end; - } - - .more-actions span { - margin-inline-end: 16px; - } - </style> - <cr-toolbar page-name="$i18n{toolbarTitle}" search-prompt="$i18n{search}" - clear-label="$i18n{clearSearch}" menu-label="$i18n{mainMenu}" show-menu - narrow-threshold="1000"> - <div class="more-actions"> - <span id="devModeLabel">$i18n{toolbarDevMode}</span> - <cr-tooltip-icon hidden="[[!shouldDisableDevMode_( + .more-actions span { + margin-inline-end: 16px; + } +</style> +<cr-toolbar page-name="$i18n{toolbarTitle}" search-prompt="$i18n{search}" + clear-label="$i18n{clearSearch}" menu-label="$i18n{mainMenu}" show-menu + narrow-threshold="1000"> + <div class="more-actions"> + <span id="devModeLabel">$i18n{toolbarDevMode}</span> + <cr-tooltip-icon hidden="[[!shouldDisableDevMode_( + devModeControlledByPolicy, isSupervised)]]" + tooltip-text="[[getTooltipText_(isSupervised)]]" + icon-class="[[getIcon_(isSupervised)]]" + icon-aria-label="[[getTooltipText_(isSupervised)]]"> + </cr-tooltip-icon> + <cr-toggle id="devMode" on-change="onDevModeToggleChange_" + disabled="[[shouldDisableDevMode_( devModeControlledByPolicy, isSupervised)]]" - tooltip-text="[[getTooltipText_(isSupervised)]]" - icon-class="[[getIcon_(isSupervised)]]" - icon-aria-label="[[getTooltipText_(isSupervised)]]"> - </cr-tooltip-icon> - <cr-toggle id="devMode" on-change="onDevModeToggleChange_" - disabled="[[shouldDisableDevMode_( - devModeControlledByPolicy, isSupervised)]]" - checked="[[inDevMode]]" aria-labelledby="devModeLabel"> - </cr-toggle> - </div> - </cr-toolbar> - <template is="dom-if" if="[[showPackDialog_]]" restamp> - <extensions-pack-dialog delegate="[[delegate]]" - on-close="onPackDialogClose_"> - </extensions-pack-dialog> - </template> - <div id="devDrawer" expanded$="[[expanded_]]"> - <div id="buttonStrip"> - <cr-button hidden$="[[!canLoadUnpacked]]" id="loadUnpacked" - on-click="onLoadUnpackedTap_"> - $i18n{toolbarLoadUnpacked} - </cr-button> - <cr-button id="packExtensions" on-click="onPackTap_"> - $i18n{toolbarPack} - </cr-button> - <cr-button id="updateNow" on-click="onUpdateNowTap_" - title="$i18n{toolbarUpdateNowTooltip}"> - $i18n{toolbarUpdateNow} - </cr-button> + checked="[[inDevMode]]" aria-labelledby="devModeLabel"> + </cr-toggle> + </div> +</cr-toolbar> +<template is="dom-if" if="[[showPackDialog_]]" restamp> + <extensions-pack-dialog delegate="[[delegate]]" + on-close="onPackDialogClose_"> + </extensions-pack-dialog> +</template> +<div id="devDrawer" expanded$="[[expanded_]]"> + <div id="buttonStrip"> + <cr-button hidden$="[[!canLoadUnpacked]]" id="loadUnpacked" + on-click="onLoadUnpackedTap_"> + $i18n{toolbarLoadUnpacked} + </cr-button> + <cr-button id="packExtensions" on-click="onPackTap_"> + $i18n{toolbarPack} + </cr-button> + <cr-button id="updateNow" on-click="onUpdateNowTap_" + title="$i18n{toolbarUpdateNowTooltip}"> + $i18n{toolbarUpdateNow} + </cr-button> <if expr="chromeos"> - <cr-button id="kioskExtensions" on-click="onKioskTap_" - hidden$="[[!kioskEnabled]]"> - $i18n{manageKioskApp} - </cr-button> + <cr-button id="kioskExtensions" on-click="onKioskTap_" + hidden$="[[!kioskEnabled]]"> + $i18n{manageKioskApp} + </cr-button> </if> - </div> - </div> - </template> - <script src="toolbar.js"></script> -</dom-module> + </div> +</div> diff --git a/chromium/chrome/browser/resources/extensions/toolbar.js b/chromium/chrome/browser/resources/extensions/toolbar.js index 228cc45667d..ad6b7eae96c 100644 --- a/chromium/chrome/browser/resources/extensions/toolbar.js +++ b/chromium/chrome/browser/resources/extensions/toolbar.js @@ -2,192 +2,202 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -cr.define('extensions', function() { - /** @interface */ - class ToolbarDelegate { - /** - * Toggles whether or not the profile is in developer mode. - * @param {boolean} inDevMode - */ - setProfileInDevMode(inDevMode) {} - - /** - * Opens the dialog to load unpacked extensions. - * @return {!Promise} - */ - loadUnpacked() {} - - /** - * Updates all extensions. - * @return {!Promise} - */ - updateAllExtensions() {} - } - - const Toolbar = Polymer({ - is: 'extensions-toolbar', - - properties: { - /** @type {extensions.ToolbarDelegate} */ - delegate: Object, - - inDevMode: { - type: Boolean, - value: false, - observer: 'onInDevModeChanged_', - reflectToAttribute: true, - }, - - devModeControlledByPolicy: Boolean, - - isSupervised: Boolean, - - // <if expr="chromeos"> - kioskEnabled: Boolean, - // </if> - - canLoadUnpacked: Boolean, - - /** @private */ - expanded_: Boolean, - - /** @private */ - showPackDialog_: Boolean, - - /** - * Prevents initiating update while update is in progress. - * @private - */ - isUpdating_: {type: Boolean, value: false} +import 'chrome://resources/cr_elements/cr_button/cr_button.m.js'; +import 'chrome://resources/cr_elements/cr_toggle/cr_toggle.m.js'; +import 'chrome://resources/cr_elements/cr_toolbar/cr_toolbar.m.js'; +import 'chrome://resources/cr_elements/hidden_style_css.m.js'; +import 'chrome://resources/cr_elements/policy/cr_tooltip_icon.m.js'; +import 'chrome://resources/cr_elements/shared_vars_css.m.js'; +import 'chrome://resources/polymer/v3_0/paper-styles/color.js'; +import './pack_dialog.js'; + +import {getToastManager} from 'chrome://resources/cr_elements/cr_toast/cr_toast_manager.m.js'; +import {I18nBehavior} from 'chrome://resources/js/i18n_behavior.m.js'; +import {listenOnce} from 'chrome://resources/js/util.m.js'; +import {IronA11yAnnouncer} from 'chrome://resources/polymer/v3_0/iron-a11y-announcer/iron-a11y-announcer.js'; +import {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; + +/** @interface */ +export class ToolbarDelegate { + /** + * Toggles whether or not the profile is in developer mode. + * @param {boolean} inDevMode + */ + setProfileInDevMode(inDevMode) {} + + /** + * Opens the dialog to load unpacked extensions. + * @return {!Promise} + */ + loadUnpacked() {} + + /** + * Updates all extensions. + * @return {!Promise} + */ + updateAllExtensions() {} +} + +Polymer({ + is: 'extensions-toolbar', + + _template: html`{__html_template__}`, + + properties: { + /** @type {ToolbarDelegate} */ + delegate: Object, + + inDevMode: { + type: Boolean, + value: false, + observer: 'onInDevModeChanged_', + reflectToAttribute: true, }, - behaviors: [I18nBehavior], + devModeControlledByPolicy: Boolean, - hostAttributes: { - role: 'banner', - }, + isSupervised: Boolean, - /** - * @return {boolean} - * @private - */ - shouldDisableDevMode_: function() { - return this.devModeControlledByPolicy || this.isSupervised; - }, + // <if expr="chromeos"> + kioskEnabled: Boolean, + // </if> - /** - * @return {string} - * @private - */ - getTooltipText_: function() { - return this.i18n( - this.isSupervised ? 'controlledSettingChildRestriction' : - 'controlledSettingPolicy'); - }, + canLoadUnpacked: Boolean, - /** - * @return {string} - * @private - */ - getIcon_: function() { - return this.isSupervised ? 'cr20:kite' : 'cr20:domain'; - }, + /** @private */ + expanded_: Boolean, - /** - * @param {!CustomEvent<boolean>} e - * @private - */ - onDevModeToggleChange_: function(e) { - this.delegate.setProfileInDevMode(e.detail); - chrome.metricsPrivate.recordUserAction( - 'Options_ToggleDeveloperMode_' + (e.detail ? 'Enabled' : 'Disabled')); - }, + /** @private */ + showPackDialog_: Boolean, /** - * @param {boolean} current - * @param {boolean} previous + * Prevents initiating update while update is in progress. * @private */ - onInDevModeChanged_: function(current, previous) { - const drawer = this.$.devDrawer; - if (this.inDevMode) { - if (drawer.hidden) { - drawer.hidden = false; - // Requesting the offsetTop will cause a reflow (to account for - // hidden). - /** @suppress {suspiciousCode} */ drawer.offsetTop; - } - } else { - if (previous == undefined) { - drawer.hidden = true; - return; - } - - listenOnce(drawer, 'transitionend', e => { - if (!this.inDevMode) { - drawer.hidden = true; - } - }); + isUpdating_: {type: Boolean, value: false} + }, + + behaviors: [I18nBehavior], + + hostAttributes: { + role: 'banner', + }, + + /** + * @return {boolean} + * @private + */ + shouldDisableDevMode_: function() { + return this.devModeControlledByPolicy || this.isSupervised; + }, + + /** + * @return {string} + * @private + */ + getTooltipText_: function() { + return this.i18n( + this.isSupervised ? 'controlledSettingChildRestriction' : + 'controlledSettingPolicy'); + }, + + /** + * @return {string} + * @private + */ + getIcon_: function() { + return this.isSupervised ? 'cr20:kite' : 'cr20:domain'; + }, + + /** + * @param {!CustomEvent<boolean>} e + * @private + */ + onDevModeToggleChange_: function(e) { + this.delegate.setProfileInDevMode(e.detail); + chrome.metricsPrivate.recordUserAction( + 'Options_ToggleDeveloperMode_' + (e.detail ? 'Enabled' : 'Disabled')); + }, + + /** + * @param {boolean} current + * @param {boolean} previous + * @private + */ + onInDevModeChanged_: function(current, previous) { + const drawer = this.$.devDrawer; + if (this.inDevMode) { + if (drawer.hidden) { + drawer.hidden = false; + // Requesting the offsetTop will cause a reflow (to account for + // hidden). + /** @suppress {suspiciousCode} */ drawer.offsetTop; } - this.expanded_ = !this.expanded_; - }, - - /** @private */ - onLoadUnpackedTap_: function() { - this.delegate.loadUnpacked().catch(loadError => { - this.fire('load-error', loadError); - }); - chrome.metricsPrivate.recordUserAction('Options_LoadUnpackedExtension'); - }, - - /** @private */ - onPackTap_: function() { - chrome.metricsPrivate.recordUserAction('Options_PackExtension'); - this.showPackDialog_ = true; - }, - - /** @private */ - onPackDialogClose_: function() { - this.showPackDialog_ = false; - this.$.packExtensions.focus(); - }, - - // <if expr="chromeos"> - /** @private */ - onKioskTap_: function() { - this.fire('kiosk-tap'); - }, - // </if> - - /** @private */ - onUpdateNowTap_: function() { - // If already updating, do not initiate another update. - if (this.isUpdating_) { + } else { + if (previous == undefined) { + drawer.hidden = true; return; } - this.isUpdating_ = true; - - const toastManager = cr.toastManager.getInstance(); - // Keep the toast open indefinitely. - toastManager.duration = 0; - toastManager.show(this.i18n('toolbarUpdatingToast'), false); - this.delegate.updateAllExtensions().then( - () => { - toastManager.hide(); - toastManager.duration = 3000; - toastManager.show(this.i18n('toolbarUpdateDone'), false); - this.isUpdating_ = false; - }, - () => { - toastManager.hide(); - this.isUpdating_ = false; - }); - }, - }); - - return { - Toolbar: Toolbar, - ToolbarDelegate: ToolbarDelegate, - }; + listenOnce(drawer, 'transitionend', e => { + if (!this.inDevMode) { + drawer.hidden = true; + } + }); + } + this.expanded_ = !this.expanded_; + }, + + /** @private */ + onLoadUnpackedTap_: function() { + this.delegate.loadUnpacked().catch(loadError => { + this.fire('load-error', loadError); + }); + chrome.metricsPrivate.recordUserAction('Options_LoadUnpackedExtension'); + }, + + /** @private */ + onPackTap_: function() { + chrome.metricsPrivate.recordUserAction('Options_PackExtension'); + this.showPackDialog_ = true; + }, + + /** @private */ + onPackDialogClose_: function() { + this.showPackDialog_ = false; + this.$.packExtensions.focus(); + }, + + // <if expr="chromeos"> + /** @private */ + onKioskTap_: function() { + this.fire('kiosk-tap'); + }, + // </if> + + /** @private */ + onUpdateNowTap_: function() { + // If already updating, do not initiate another update. + if (this.isUpdating_) { + return; + } + + this.isUpdating_ = true; + + const toastManager = getToastManager(); + // Keep the toast open indefinitely. + toastManager.duration = 0; + toastManager.show(this.i18n('toolbarUpdatingToast')); + this.delegate.updateAllExtensions().then( + () => { + toastManager.hide(); + toastManager.duration = 3000; + toastManager.show(this.i18n('toolbarUpdateDone')); + this.isUpdating_ = false; + }, + () => { + toastManager.hide(); + this.isUpdating_ = false; + }); + }, }); |