diff --git a/app/BUILD.gn b/app/BUILD.gn index 936e92f32230..665aa48651d7 100644 --- a/app/BUILD.gn +++ b/app/BUILD.gn @@ -12,7 +12,15 @@ import("//build/config/features.gni") import("//build/config/locales.gni") source_set("command_ids") { - sources = [ "brave_command_ids.h" ] + sources = [ + "brave_command_ids.h", + "command_utils.cc", + "command_utils.h", + ] + deps = [ + "//base", + "//chrome/app:command_ids", + ] } brave_grit("brave_generated_resources_grit") { diff --git a/app/command_utils.cc b/app/command_utils.cc new file mode 100644 index 000000000000..f03c18152e14 --- /dev/null +++ b/app/command_utils.cc @@ -0,0 +1,216 @@ +// Copyright (c) 2023 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at https://mozilla.org/MPL/2.0/. + +#include "brave/app/command_utils.h" + +#include +#include +#include + +#include "base/strings/string_split.h" +#include "base/strings/string_util.h" +#include "brave/app/brave_command_ids.h" +#include "chrome/app/chrome_command_ids.h" + +namespace commands { +namespace { +std::string GetName(std::string raw_name) { + auto words = base::SplitString(raw_name, "_", base::TRIM_WHITESPACE, + base::SPLIT_WANT_NONEMPTY); + for (auto& word : words) { + if (word.size() == 1) { + continue; + } + word = word[0] + base::ToLowerASCII(word.substr(1)); + } + return base::JoinString(words, " "); +} + +} // namespace + +std::map GetCommandInfo() { + static const std::map kCommands { + // Navigation commands. + ADD_UNTRANSLATED_COMMAND(BACK), ADD_UNTRANSLATED_COMMAND(FORWARD), + ADD_UNTRANSLATED_COMMAND(RELOAD), + ADD_UNTRANSLATED_COMMAND(RELOAD_BYPASSING_CACHE), + ADD_UNTRANSLATED_COMMAND(RELOAD_CLEARING_CACHE), + ADD_UNTRANSLATED_COMMAND(HOME), ADD_UNTRANSLATED_COMMAND(STOP), + + // Window management commands + ADD_UNTRANSLATED_COMMAND(NEW_WINDOW), + ADD_UNTRANSLATED_COMMAND(NEW_INCOGNITO_WINDOW), + ADD_UNTRANSLATED_COMMAND(CLOSE_WINDOW), + ADD_UNTRANSLATED_COMMAND(NEW_TAB), ADD_UNTRANSLATED_COMMAND(CLOSE_TAB), + ADD_UNTRANSLATED_COMMAND(SELECT_NEXT_TAB), + ADD_UNTRANSLATED_COMMAND(SELECT_PREVIOUS_TAB), + ADD_UNTRANSLATED_COMMAND(SELECT_TAB_0), + ADD_UNTRANSLATED_COMMAND(SELECT_TAB_1), + ADD_UNTRANSLATED_COMMAND(SELECT_TAB_2), + ADD_UNTRANSLATED_COMMAND(SELECT_TAB_3), + ADD_UNTRANSLATED_COMMAND(SELECT_TAB_4), + ADD_UNTRANSLATED_COMMAND(SELECT_TAB_5), + ADD_UNTRANSLATED_COMMAND(SELECT_TAB_6), + ADD_UNTRANSLATED_COMMAND(SELECT_TAB_7), + ADD_UNTRANSLATED_COMMAND(SELECT_LAST_TAB), + ADD_UNTRANSLATED_COMMAND(MOVE_TAB_TO_NEW_WINDOW), + ADD_UNTRANSLATED_COMMAND(DUPLICATE_TAB), + ADD_UNTRANSLATED_COMMAND(RESTORE_TAB), + ADD_UNTRANSLATED_COMMAND(FULLSCREEN), ADD_UNTRANSLATED_COMMAND(EXIT), + ADD_UNTRANSLATED_COMMAND(MOVE_TAB_NEXT), + ADD_UNTRANSLATED_COMMAND(MOVE_TAB_PREVIOUS), + ADD_UNTRANSLATED_COMMAND(SEARCH), + ADD_UNTRANSLATED_COMMAND(DEBUG_FRAME_TOGGLE), + ADD_UNTRANSLATED_COMMAND(WINDOW_MENU), + ADD_UNTRANSLATED_COMMAND(MINIMIZE_WINDOW), + ADD_UNTRANSLATED_COMMAND(MAXIMIZE_WINDOW), + ADD_UNTRANSLATED_COMMAND(NAME_WINDOW), + ADD_UNTRANSLATED_COMMAND(RESTORE_WINDOW), + +#if BUILDFLAG(IS_LINUX) + ADD_UNTRANSLATED_COMMAND(USE_SYSTEM_TITLE_BAR), +#endif + + // Web app window commands + ADD_UNTRANSLATED_COMMAND(OPEN_IN_PWA_WINDOW), + ADD_UNTRANSLATED_COMMAND(COPY_URL), + ADD_UNTRANSLATED_COMMAND(SITE_SETTINGS), + ADD_UNTRANSLATED_COMMAND(WEB_APP_MENU_APP_INFO), + + // Page related commands + ADD_UNTRANSLATED_COMMAND(BOOKMARK_THIS_TAB), + ADD_UNTRANSLATED_COMMAND(BOOKMARK_ALL_TABS), + ADD_UNTRANSLATED_COMMAND(VIEW_SOURCE), ADD_UNTRANSLATED_COMMAND(PRINT), + ADD_UNTRANSLATED_COMMAND(SAVE_PAGE), + ADD_UNTRANSLATED_COMMAND(EMAIL_PAGE_LOCATION), + ADD_UNTRANSLATED_COMMAND(BASIC_PRINT), + ADD_UNTRANSLATED_COMMAND(TRANSLATE_PAGE), + ADD_UNTRANSLATED_COMMAND(WINDOW_MUTE_SITE), + ADD_UNTRANSLATED_COMMAND(WINDOW_PIN_TAB), + ADD_UNTRANSLATED_COMMAND(WINDOW_GROUP_TAB), + ADD_UNTRANSLATED_COMMAND(QRCODE_GENERATOR), + ADD_UNTRANSLATED_COMMAND(WINDOW_CLOSE_TABS_TO_RIGHT), + ADD_UNTRANSLATED_COMMAND(WINDOW_CLOSE_OTHER_TABS), + ADD_UNTRANSLATED_COMMAND(NEW_TAB_TO_RIGHT), + + // Page manipulation for specific tab + ADD_UNTRANSLATED_COMMAND(MUTE_TARGET_SITE), + ADD_UNTRANSLATED_COMMAND(PIN_TARGET_TAB), + ADD_UNTRANSLATED_COMMAND(GROUP_TARGET_TAB), + ADD_UNTRANSLATED_COMMAND(DUPLICATE_TARGET_TAB), + + // Edit + ADD_UNTRANSLATED_COMMAND(CUT), ADD_UNTRANSLATED_COMMAND(COPY), + ADD_UNTRANSLATED_COMMAND(PASTE), ADD_UNTRANSLATED_COMMAND(EDIT_MENU), + + // Find + ADD_UNTRANSLATED_COMMAND(FIND), ADD_UNTRANSLATED_COMMAND(FIND_NEXT), + ADD_UNTRANSLATED_COMMAND(FIND_PREVIOUS), + ADD_UNTRANSLATED_COMMAND(CLOSE_FIND_OR_STOP), + + // Zoom + ADD_UNTRANSLATED_COMMAND(ZOOM_MENU), + ADD_UNTRANSLATED_COMMAND(ZOOM_PLUS), + ADD_UNTRANSLATED_COMMAND(ZOOM_NORMAL), + ADD_UNTRANSLATED_COMMAND(ZOOM_MINUS), + ADD_UNTRANSLATED_COMMAND(ZOOM_PERCENT_DISPLAY), + + // Focus + ADD_UNTRANSLATED_COMMAND(FOCUS_TOOLBAR), + ADD_UNTRANSLATED_COMMAND(FOCUS_LOCATION), + ADD_UNTRANSLATED_COMMAND(FOCUS_SEARCH), + ADD_UNTRANSLATED_COMMAND(FOCUS_MENU_BAR), + ADD_UNTRANSLATED_COMMAND(FOCUS_NEXT_PANE), + ADD_UNTRANSLATED_COMMAND(FOCUS_PREVIOUS_PANE), + ADD_UNTRANSLATED_COMMAND(FOCUS_BOOKMARKS), + ADD_UNTRANSLATED_COMMAND(FOCUS_INACTIVE_POPUP_FOR_ACCESSIBILITY), + ADD_UNTRANSLATED_COMMAND(FOCUS_WEB_CONTENTS_PANE), + + // UI bits + ADD_UNTRANSLATED_COMMAND(OPEN_FILE), + ADD_UNTRANSLATED_COMMAND(CREATE_SHORTCUT), + ADD_UNTRANSLATED_COMMAND(DEVELOPER_MENU), + ADD_UNTRANSLATED_COMMAND(DEV_TOOLS), + ADD_UNTRANSLATED_COMMAND(DEV_TOOLS_CONSOLE), + ADD_UNTRANSLATED_COMMAND(TASK_MANAGER), + ADD_UNTRANSLATED_COMMAND(DEV_TOOLS_DEVICES), + ADD_UNTRANSLATED_COMMAND(FEEDBACK), + ADD_UNTRANSLATED_COMMAND(SHOW_BOOKMARK_BAR), + ADD_UNTRANSLATED_COMMAND(SHOW_HISTORY), + ADD_UNTRANSLATED_COMMAND(SHOW_BOOKMARK_MANAGER), + ADD_UNTRANSLATED_COMMAND(SHOW_DOWNLOADS), + ADD_UNTRANSLATED_COMMAND(CLEAR_BROWSING_DATA), + ADD_UNTRANSLATED_COMMAND(IMPORT_SETTINGS), + ADD_UNTRANSLATED_COMMAND(OPTIONS), + ADD_UNTRANSLATED_COMMAND(EDIT_SEARCH_ENGINES), + ADD_UNTRANSLATED_COMMAND(VIEW_PASSWORDS), + ADD_UNTRANSLATED_COMMAND(ABOUT), + ADD_UNTRANSLATED_COMMAND(HELP_PAGE_VIA_KEYBOARD), + ADD_UNTRANSLATED_COMMAND(SHOW_APP_MENU), + ADD_UNTRANSLATED_COMMAND(MANAGE_EXTENSIONS), + ADD_UNTRANSLATED_COMMAND(DEV_TOOLS_INSPECT), + ADD_UNTRANSLATED_COMMAND(BOOKMARKS_MENU), + ADD_UNTRANSLATED_COMMAND(SHOW_AVATAR_MENU), + ADD_UNTRANSLATED_COMMAND(TOGGLE_REQUEST_TABLET_SITE), + ADD_UNTRANSLATED_COMMAND(DEV_TOOLS_TOGGLE), + ADD_UNTRANSLATED_COMMAND(TAKE_SCREENSHOT), + ADD_UNTRANSLATED_COMMAND(TOGGLE_FULLSCREEN_TOOLBAR), + ADD_UNTRANSLATED_COMMAND(INSTALL_PWA), + ADD_UNTRANSLATED_COMMAND(PASTE_AND_GO), + ADD_UNTRANSLATED_COMMAND(SHOW_FULL_URLS), + ADD_UNTRANSLATED_COMMAND(CARET_BROWSING_TOGGLE), + ADD_UNTRANSLATED_COMMAND(TOGGLE_QUICK_COMMANDS), + + // Media + ADD_UNTRANSLATED_COMMAND(CONTENT_CONTEXT_PLAYPAUSE), + ADD_UNTRANSLATED_COMMAND(CONTENT_CONTEXT_MUTE), + ADD_UNTRANSLATED_COMMAND(CONTENT_CONTEXT_LOOP), + ADD_UNTRANSLATED_COMMAND(CONTENT_CONTEXT_CONTROLS), + +#if BUILDFLAG(ENABLE_SCREEN_AI_SERVICE) + // Screen AI Visual Annotations. + ADD_UNTRANSLATED_COMMAND(RUN_SCREEN_AI_VISUAL_ANNOTATIONS), +#endif + + // Tab search + ADD_UNTRANSLATED_COMMAND(TAB_SEARCH), + ADD_UNTRANSLATED_COMMAND(TAB_SEARCH_CLOSE), + + // Brave Commands + ADD_UNTRANSLATED_COMMAND(SHOW_BRAVE_REWARDS), + ADD_UNTRANSLATED_COMMAND(NEW_TOR_CONNECTION_FOR_SITE), + ADD_UNTRANSLATED_COMMAND(NEW_OFFTHERECORD_WINDOW_TOR), + ADD_UNTRANSLATED_COMMAND(SHOW_BRAVE_SYNC), + ADD_UNTRANSLATED_COMMAND(SHOW_BRAVE_WALLET), + ADD_UNTRANSLATED_COMMAND(ADD_NEW_PROFILE), + ADD_UNTRANSLATED_COMMAND(OPEN_GUEST_PROFILE), + ADD_UNTRANSLATED_COMMAND(SHOW_BRAVE_WALLET_PANEL), + ADD_UNTRANSLATED_COMMAND(SHOW_BRAVE_VPN_PANEL), + ADD_UNTRANSLATED_COMMAND(TOGGLE_BRAVE_VPN_TOOLBAR_BUTTON), + ADD_UNTRANSLATED_COMMAND(MANAGE_BRAVE_VPN_PLAN), + ADD_UNTRANSLATED_COMMAND(TOGGLE_BRAVE_VPN), + ADD_UNTRANSLATED_COMMAND(COPY_CLEAN_LINK), + ADD_UNTRANSLATED_COMMAND(SIDEBAR_TOGGLE_POSITION), + ADD_UNTRANSLATED_COMMAND(TOGGLE_TAB_MUTE) + }; + return kCommands; +} + +std::vector GetCommands() { + const auto& info = GetCommandInfo(); + std::vector result; + for (const auto& [id, _] : info) { + result.push_back(id); + } + return result; +} + +std::string GetCommandName(int command_id) { + const auto& info = GetCommandInfo(); + auto it = info.find(command_id); + CHECK(it != info.end()); + return it->second; +} +} // namespace commands diff --git a/app/command_utils.h b/app/command_utils.h new file mode 100644 index 000000000000..fcb80ab299b2 --- /dev/null +++ b/app/command_utils.h @@ -0,0 +1,25 @@ +// Copyright (c) 2023 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at https://mozilla.org/MPL/2.0/. + +#ifndef BRAVE_APP_COMMAND_UTILS_H_ +#define BRAVE_APP_COMMAND_UTILS_H_ + +#include +#include + +#define ADD_UNTRANSLATED_COMMAND(name) \ + { \ + IDC_##name, { \ + GetName(#name) \ + } \ + } + +namespace commands { + +std::vector GetCommands(); +std::string GetCommandName(int command_id); +} // namespace commands + +#endif // BRAVE_APP_COMMAND_UTILS_H_ diff --git a/app/command_utils_browsertest.cc b/app/command_utils_browsertest.cc new file mode 100644 index 000000000000..62aabcc427a7 --- /dev/null +++ b/app/command_utils_browsertest.cc @@ -0,0 +1,35 @@ +// Copyright (c) 2023 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at https://mozilla.org/MPL/2.0/. + +#include "brave/app/command_utils.h" + +#include "base/test/scoped_feature_list.h" +#include "brave/components/commands/common/features.h" +#include "chrome/browser/ui/browser_commands.h" +#include "chrome/test/base/in_process_browser_test.h" +#include "chrome/test/base/ui_test_utils.h" +#include "content/public/test/browser_test.h" + +class CommandUtilsBrowserTest : public InProcessBrowserTest { + public: + void SetUp() override { + features_.InitAndEnableFeature(commands::features::kBraveCommandsFeature); + InProcessBrowserTest::SetUp(); + } + + private: + base::test::ScopedFeatureList features_; +}; + +IN_PROC_BROWSER_TEST_F(CommandUtilsBrowserTest, + AllCommandsShouldBeExecutableWithoutCrash) { + ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL("chrome://newtab"))); + auto commands = commands::GetCommands(); + for (const auto& command : commands) { + LOG(ERROR) << "Executing command: " << commands::GetCommandName(command); + chrome::ExecuteCommand(browser(), command); + LOG(ERROR) << "Executed!"; + } +} diff --git a/app/command_utils_unittest.cc b/app/command_utils_unittest.cc new file mode 100644 index 000000000000..fdc7e97af14b --- /dev/null +++ b/app/command_utils_unittest.cc @@ -0,0 +1,24 @@ +// Copyright (c) 2023 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at https://mozilla.org/MPL/2.0/. + +#include "brave/app/command_utils.h" + +#include "base/ranges/algorithm.h" +#include "chrome/browser/ui/views/accelerator_table.h" +#include "gtest/gtest.h" + +// Note: If this test fails because an accelerated command isn't present just +// add the missing command to |commands::GetCommandInfo| in command_utils.h. +TEST(CommandUtilsUnitTest, AllAcceleratedCommandsShouldBeAvailable) { + auto accelerators = GetAcceleratorList(); + auto commands = commands::GetCommands(); + + for (const auto& accelerator : accelerators) { + EXPECT_NE(base::ranges::find(commands, accelerator.command_id), + commands.end()) + << "Accelerated command '" << accelerator.command_id + << "' was not present in the list of commands."; + } +} diff --git a/browser/about_flags.cc b/browser/about_flags.cc index a74ee5bb3c09..1aac269060ff 100644 --- a/browser/about_flags.cc +++ b/browser/about_flags.cc @@ -19,6 +19,7 @@ #include "brave/components/brave_today/common/features.h" #include "brave/components/brave_vpn/common/buildflags/buildflags.h" #include "brave/components/brave_wallet/common/features.h" +#include "brave/components/commands/common/features.h" #include "brave/components/de_amp/common/features.h" #include "brave/components/debounce/common/features.h" #include "brave/components/google_sign_in_permission/features.h" @@ -208,6 +209,10 @@ constexpr char kBraveBlockScreenFingerprintingDescription[] = "Prevents JavaScript and CSS from learning the user's screen dimensions " "or window position."; +constexpr char kBraveCommandsName[] = "Brave Commands"; +constexpr char kBraveCommandsDescription[] = + "Enable experimental page for viewing and executing commands in Brave"; + constexpr char kBraveTorWindowsHttpsOnlyName[] = "Use HTTPS-Only Mode in Private Windows with Tor"; constexpr char kBraveTorWindowsHttpsOnlyDescription[] = @@ -611,6 +616,13 @@ constexpr char kRestrictEventSourcePoolDescription[] = #define PLAYLIST_FEATURE_ENTRIES #endif +#define BRAVE_COMMANDS_FEATURE_ENTRIES \ + {"brave-commands", \ + flag_descriptions::kBraveCommandsName, \ + flag_descriptions::kBraveCommandsDescription, \ + kOsDesktop, \ + FEATURE_VALUE_TYPE(commands::features::kBraveCommandsFeature)}, + #if defined(TOOLKIT_VIEWS) #define BRAVE_VERTICAL_TABS_FEATURE_ENTRY \ {"brave-vertical-tabs", \ @@ -840,6 +852,7 @@ constexpr char kRestrictEventSourcePoolDescription[] = SPEEDREADER_FEATURE_ENTRIES \ BRAVE_FEDERATED_FEATURE_ENTRIES \ PLAYLIST_FEATURE_ENTRIES \ + BRAVE_COMMANDS_FEATURE_ENTRIES \ BRAVE_VERTICAL_TABS_FEATURE_ENTRY \ BRAVE_BACKGROUND_VIDEO_PLAYBACK_ANDROID \ BRAVE_SAFE_BROWSING_ANDROID \ diff --git a/browser/brave_content_browser_client.cc b/browser/brave_content_browser_client.cc index 849a42d70622..d8555436e3d6 100644 --- a/browser/brave_content_browser_client.cc +++ b/browser/brave_content_browser_client.cc @@ -33,6 +33,7 @@ #include "brave/browser/profiles/brave_renderer_updater_factory.h" #include "brave/browser/profiles/profile_util.h" #include "brave/browser/skus/skus_service_factory.h" +#include "brave/browser/ui/webui/commands_ui.h" #include "brave/components/brave_ads/common/features.h" #include "brave/components/brave_federated/features.h" #include "brave/components/brave_rewards/browser/rewards_protocol_handler.h" @@ -56,6 +57,8 @@ #include "brave/components/brave_wallet/browser/solana_provider_impl.h" #include "brave/components/brave_wallet/common/brave_wallet.mojom.h" #include "brave/components/brave_webtorrent/browser/buildflags/buildflags.h" +#include "brave/components/commands/common/commands.mojom.h" +#include "brave/components/commands/common/features.h" #include "brave/components/constants/pref_names.h" #include "brave/components/constants/webui_url_constants.h" #include "brave/components/cosmetic_filters/browser/cosmetic_filters_resources.h" @@ -481,6 +484,10 @@ void BraveContentBrowserClient::RegisterWebUIInterfaceBrokers( .Add(); } #endif + + if (base::FeatureList::IsEnabled(commands::features::kBraveCommandsFeature)) { + registry.ForWebUI().Add(); + } } bool BraveContentBrowserClient::AllowWorkerFingerprinting( diff --git a/browser/sources.gni b/browser/sources.gni index eaf2de5caa8b..9bc1a9baaff8 100644 --- a/browser/sources.gni +++ b/browser/sources.gni @@ -158,6 +158,7 @@ brave_chrome_browser_deps = [ "//brave/components/brave_wallet/common:mojom", "//brave/components/brave_wayback_machine/buildflags", "//brave/components/brave_webtorrent/browser/buildflags", + "//brave/components/commands/browser", "//brave/components/constants", "//brave/components/cosmetic_filters/browser", "//brave/components/cosmetic_filters/common:mojom", diff --git a/browser/ui/BUILD.gn b/browser/ui/BUILD.gn index f038d8b37ff7..d3f1eb4e9869 100644 --- a/browser/ui/BUILD.gn +++ b/browser/ui/BUILD.gn @@ -61,6 +61,8 @@ source_set("ui") { "webui/brave_web_ui_controller_factory.h", "webui/brave_webui_source.cc", "webui/brave_webui_source.h", + "webui/commands_ui.cc", + "webui/commands_ui.h", "webui/webcompat_reporter_ui.cc", "webui/webcompat_reporter_ui.h", ] @@ -411,6 +413,7 @@ source_set("ui") { "//brave/components/brave_vpn/common/buildflags", "//brave/components/brave_vpn/common/mojom", "//brave/components/brave_wayback_machine/buildflags", + "//brave/components/commands/browser", "//brave/components/constants", "//brave/components/cosmetic_filters/resources/data:generated_resources", "//brave/components/l10n/common", @@ -781,6 +784,7 @@ source_set("ui") { "//brave/components/brave_wallet_ui/page:brave_wallet_page_generated", "//brave/components/brave_wallet_ui/panel:brave_wallet_panel_generated", "//brave/components/brave_wallet_ui/trezor:trezor_bridge_generated", + "//brave/components/commands/browser/resources:generated_resources", "//brave/components/resources:strings_grit", "//components/permissions", ] diff --git a/browser/ui/views/frame/brave_browser_view.cc b/browser/ui/views/frame/brave_browser_view.cc index 3e6f7ff2634f..4ad78b132ba8 100644 --- a/browser/ui/views/frame/brave_browser_view.cc +++ b/browser/ui/views/frame/brave_browser_view.cc @@ -5,7 +5,9 @@ #include "brave/browser/ui/views/frame/brave_browser_view.h" +#include #include +#include #include "base/bind.h" #include "brave/browser/brave_rewards/rewards_panel/rewards_panel_coordinator.h" @@ -40,6 +42,7 @@ #include "chrome/common/pref_names.h" #include "extensions/buildflags/buildflags.h" #include "third_party/abseil-cpp/absl/types/optional.h" +#include "ui/base/accelerators/accelerator.h" #include "ui/events/event_observer.h" #include "ui/views/bubble/bubble_dialog_delegate_view.h" #include "ui/views/event_monitor.h" @@ -81,9 +84,7 @@ class BraveBrowserView::TabCyclingEventHandler : public ui::EventObserver, Start(); } - ~TabCyclingEventHandler() override { - Stop(); - } + ~TabCyclingEventHandler() override { Stop(); } TabCyclingEventHandler(const TabCyclingEventHandler&) = delete; TabCyclingEventHandler& operator=(const TabCyclingEventHandler&) = delete; @@ -110,17 +111,14 @@ class BraveBrowserView::TabCyclingEventHandler : public ui::EventObserver, } // Handle Browser widget closing while tab Cycling is in-progress. - void OnWidgetClosing(views::Widget* widget) override { - Stop(); - } + void OnWidgetClosing(views::Widget* widget) override { Stop(); } void Start() { // Add the event handler auto* widget = browser_view_->GetWidget(); if (widget->GetNativeWindow()) { monitor_ = views::EventMonitor::CreateWindowMonitor( - this, - widget->GetNativeWindow(), + this, widget->GetNativeWindow(), {ui::ET_MOUSE_PRESSED, ui::ET_KEY_RELEASED}); } @@ -405,6 +403,15 @@ views::View* BraveBrowserView::GetWalletButtonAnchorView() { ->GetAsAnchorView(); } +std::map> +BraveBrowserView::GetAcceleratedCommands() { + std::map> result; + for (const auto& [accelerator, command] : accelerator_table_) { + result[command].push_back(accelerator); + } + return result; +} + void BraveBrowserView::CreateWalletBubble() { DCHECK(GetWalletButton()); GetWalletButton()->ShowWalletBubble(); @@ -559,6 +566,6 @@ void BraveBrowserView::StartTabCycling() { void BraveBrowserView::StopTabCycling() { tab_cycling_event_handler_.reset(); - static_cast(browser()->tab_strip_model())-> - StopMRUCycling(); + static_cast(browser()->tab_strip_model()) + ->StopMRUCycling(); } diff --git a/browser/ui/views/frame/brave_browser_view.h b/browser/ui/views/frame/brave_browser_view.h index e825e0ed3beb..df585722c26e 100644 --- a/browser/ui/views/frame/brave_browser_view.h +++ b/browser/ui/views/frame/brave_browser_view.h @@ -6,8 +6,10 @@ #ifndef BRAVE_BROWSER_UI_VIEWS_FRAME_BRAVE_BROWSER_VIEW_H_ #define BRAVE_BROWSER_UI_VIEWS_FRAME_BRAVE_BROWSER_VIEW_H_ +#include #include #include +#include #include "base/memory/raw_ptr.h" #include "base/memory/weak_ptr.h" @@ -15,6 +17,7 @@ #include "brave/components/brave_vpn/common/buildflags/buildflags.h" #include "build/build_config.h" #include "chrome/browser/ui/views/frame/browser_view.h" +#include "ui/base/accelerators/accelerator.h" #if BUILDFLAG(ENABLE_BRAVE_VPN) #include "brave/browser/ui/views/toolbar/brave_vpn_panel_controller.h" @@ -61,6 +64,7 @@ class BraveBrowserView : public BrowserView { void CloseWalletBubble(); WalletButton* GetWalletButton(); views::View* GetWalletButtonAnchorView(); + std::map> GetAcceleratedCommands(); // BrowserView overrides: void StartTabCycling() override; diff --git a/browser/ui/webui/brave_web_ui_controller_factory.cc b/browser/ui/webui/brave_web_ui_controller_factory.cc index 01063c428033..12c7ca33cc83 100644 --- a/browser/ui/webui/brave_web_ui_controller_factory.cc +++ b/browser/ui/webui/brave_web_ui_controller_factory.cc @@ -17,10 +17,12 @@ #include "brave/browser/ui/webui/brave_rewards_internals_ui.h" #include "brave/browser/ui/webui/brave_rewards_page_ui.h" #include "brave/browser/ui/webui/brave_tip_ui.h" +#include "brave/browser/ui/webui/commands_ui.h" #include "brave/browser/ui/webui/webcompat_reporter_ui.h" #include "brave/components/brave_federated/features.h" #include "brave/components/brave_rewards/common/rewards_util.h" #include "brave/components/brave_shields/common/features.h" +#include "brave/components/commands/common/features.h" #include "brave/components/constants/pref_names.h" #include "brave/components/constants/webui_url_constants.h" #include "brave/components/ipfs/buildflags/buildflags.h" @@ -96,6 +98,10 @@ WebUIController* NewWebUI(WebUI* web_ui, const GURL& url) { return new IPFSUI(web_ui, url.host()); #endif #if !BUILDFLAG(IS_ANDROID) + } else if (host == kCommandsHost && + base::FeatureList::IsEnabled( + commands::features::kBraveCommandsFeature)) { + return new commands::CommandsUI(web_ui, url.host()); } else if (host == kWalletPageHost && // We don't want to check for supported profile type here because // we want private windows to redirect to the regular profile. @@ -197,6 +203,7 @@ WebUIFactoryFunction GetWebUIFactoryFunction(WebUI* web_ui, const GURL& url) { url.host_piece() == kTipHost || url.host_piece() == kBraveRewardsPanelHost || url.host_piece() == kSpeedreaderPanelHost || + url.host_piece() == kCommandsHost || #endif #if BUILDFLAG(ENABLE_TOR) url.host_piece() == kTorInternalsHost || diff --git a/browser/ui/webui/commands_ui.cc b/browser/ui/webui/commands_ui.cc new file mode 100644 index 000000000000..ceb0594c08bc --- /dev/null +++ b/browser/ui/webui/commands_ui.cc @@ -0,0 +1,93 @@ +// Copyright (c) 2023 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at https://mozilla.org/MPL/2.0/. + +#include "brave/browser/ui/webui/commands_ui.h" + +#include +#include + +#include "base/strings/utf_string_conversions.h" +#include "brave/app/command_utils.h" +#include "brave/browser/ui/views/frame/brave_browser_view.h" +#include "brave/browser/ui/webui/brave_webui_source.h" +#include "brave/components/commands/browser/resources/grit/commands_generated_map.h" +#include "brave/components/commands/common/commands.mojom.h" +#include "brave/components/commands/common/key_names.h" +#include "chrome/browser/ui/browser_commands.h" +#include "chrome/browser/ui/browser_finder.h" +#include "components/grit/brave_components_resources.h" +#include "content/public/browser/web_ui_controller.h" +#include "ui/base/accelerators/accelerator.h" +#include "ui/events/event_constants.h" + +namespace commands { +CommandsUI::CommandsUI(content::WebUI* web_ui, const std::string& name) + : content::WebUIController(web_ui) { + CreateAndAddWebUIDataSource(web_ui, name, kCommandsGenerated, + kCommandsGeneratedSize, IDR_COMMANDS_HTML); +} + +CommandsUI::~CommandsUI() = default; + +void CommandsUI::BindInterface( + mojo::PendingReceiver pending_receiver) { + if (receiver_.is_bound()) { + receiver_.reset(); + } + + receiver_.Bind(std::move(pending_receiver)); +} + +void CommandsUI::GetCommands(GetCommandsCallback callback) { + auto command_ids = commands::GetCommands(); + auto accelerated_commands = browser_view()->GetAcceleratedCommands(); + + std::vector result; + for (const auto& command_id : command_ids) { + if (!chrome::SupportsCommand(browser(), command_id)) { + continue; + } + + auto command = Command::New(); + command->id = command_id; + command->name = commands::GetCommandName(command_id); + command->enabled = + chrome::IsCommandEnabled(browser_view()->browser(), command_id); + + auto it = accelerated_commands.find(command_id); + if (it != accelerated_commands.end()) { + for (const auto& accel : it->second) { + auto a = Accelerator::New(); + a->keycode = commands::GetKeyName(accel.key_code()); + a->modifiers = commands::GetModifierName(accel.modifiers()); + + if ((!a->modifiers.size() && accel.modifiers() != ui::EF_NONE) || + a->keycode.empty()) { + continue; + } + command->accelerators.push_back(std::move(a)); + } + } + result.push_back(std::move(command)); + } + + std::move(callback).Run(std::move(result)); +} + +void CommandsUI::TryExecuteCommand(uint32_t command_id) { + chrome::ExecuteCommand(browser(), command_id); +} + +Browser* CommandsUI::browser() { + return chrome::FindBrowserWithWebContents(web_ui()->GetWebContents()); +} + +BraveBrowserView* CommandsUI::browser_view() { + return static_cast(browser()->window()); +} + +WEB_UI_CONTROLLER_TYPE_IMPL(CommandsUI) + +} // namespace commands diff --git a/browser/ui/webui/commands_ui.h b/browser/ui/webui/commands_ui.h new file mode 100644 index 000000000000..f0d6a9a38d9b --- /dev/null +++ b/browser/ui/webui/commands_ui.h @@ -0,0 +1,46 @@ +// Copyright (c) 2023 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at https://mozilla.org/MPL/2.0/. + +#ifndef BRAVE_BROWSER_UI_WEBUI_COMMANDS_UI_H_ +#define BRAVE_BROWSER_UI_WEBUI_COMMANDS_UI_H_ + +#include + +#include "brave/browser/ui/views/frame/brave_browser_view.h" +#include "brave/components/commands/common/commands.mojom.h" +#include "brave/components/playlist/common/mojom/playlist.mojom.h" +#include "content/public/browser/web_ui_controller.h" +#include "mojo/public/cpp/bindings/pending_receiver.h" +#include "mojo/public/cpp/bindings/receiver.h" +#include "mojo/public/cpp/bindings/receiver_set.h" + +namespace commands { + +class CommandsUI : public content::WebUIController, public CommandsService { + public: + CommandsUI(content::WebUI* web_ui, const std::string& host); + ~CommandsUI() override; + CommandsUI(const CommandsUI&) = delete; + CommandsUI& operator=(const CommandsUI&) = delete; + + void BindInterface(mojo::PendingReceiver pending_receiver); + + // CommandsService: + void GetCommands(GetCommandsCallback callback) override; + void TryExecuteCommand(uint32_t command_id) override; + + protected: + Browser* browser(); + BraveBrowserView* browser_view(); + + private: + mojo::Receiver receiver_{this}; + + WEB_UI_CONTROLLER_TYPE_DECL(); +}; + +} // namespace commands + +#endif // BRAVE_BROWSER_UI_WEBUI_COMMANDS_UI_H_ diff --git a/components/commands/DEPS b/components/commands/DEPS new file mode 100644 index 000000000000..e5c667693db9 --- /dev/null +++ b/components/commands/DEPS @@ -0,0 +1,3 @@ +include_rules = [ + "+ui/events" +] diff --git a/components/commands/browser/BUILD.gn b/components/commands/browser/BUILD.gn new file mode 100644 index 000000000000..aacbdf5badc3 --- /dev/null +++ b/components/commands/browser/BUILD.gn @@ -0,0 +1,17 @@ +# Copyright (c) 2023 The Brave Authors. All rights reserved. +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this file, +# You can obtain one at https://mozilla.org/MPL/2.0/. + +import("//brave/build/config.gni") + +static_library("browser") { + sources = [] + + public_deps = [ "//brave/components/commands/common" ] + + deps = [ + "//base", + "//brave/components/resources:static_resources", + ] +} diff --git a/components/commands/browser/resources/BUILD.gn b/components/commands/browser/resources/BUILD.gn new file mode 100644 index 000000000000..b308468b09d2 --- /dev/null +++ b/components/commands/browser/resources/BUILD.gn @@ -0,0 +1,26 @@ +# Copyright (c) 2023 The Brave Authors. All rights reserved. +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this file, +# You can obtain one at https://mozilla.org/MPL/2.0/. + +import("//brave/components/common/typescript.gni") + +transpile_web_ui("commands_ui") { + entry_points = [ [ + "commands", + rebase_path("commands.tsx"), + ] ] + + public_deps = [ + "//brave/components/commands/common:mojom_js", + "//mojo/public/mojom/base", + ] + + resource_name = "commands" +} + +pack_web_resources("generated_resources") { + resource_name = "commands" + output_dir = "$root_gen_dir/brave/components/commands/browser/resources" + deps = [ ":commands_ui" ] +} diff --git a/components/commands/browser/resources/commands.html b/components/commands/browser/resources/commands.html new file mode 100644 index 000000000000..968f151837f3 --- /dev/null +++ b/components/commands/browser/resources/commands.html @@ -0,0 +1,21 @@ + + + + + + + Brave Commands + + + + + + +
+ + + diff --git a/components/commands/browser/resources/commands.tsx b/components/commands/browser/resources/commands.tsx new file mode 100644 index 000000000000..d691e6130cad --- /dev/null +++ b/components/commands/browser/resources/commands.tsx @@ -0,0 +1,90 @@ +// Copyright (c) 2023 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at https://mozilla.org/MPL/2.0/. + +import * as CommandsMojo from 'gen/brave/components/commands/common/commands.mojom.m.js' +import * as React from 'react' +import { render } from 'react-dom' +import styled from 'styled-components' +import Command from './components/Command' +import { match } from './utils/match' + +const CommandsContainer = styled.div` + display: flex; + flex-direction: column; + gap: 4px; +` + +const FilterBox = styled.input` + margin: 4px; + padding: 4px; + border: 1px solid lightgray; +` + +const FiltersRow = styled.div` + display: flex; + flex-direction: row; + gap: 8px; +` + +export const api = CommandsMojo.CommandsService.getRemote() + +function usePromise (getPromise: () => Promise, deps: any[]) { + const [result, setResult] = React.useState() + React.useEffect(() => { + getPromise().then(setResult) + }, deps) + + return result +} + +function App () { + const [filter, setFilter] = React.useState('') + const [withAccelerator, setWithAccelerator] = React.useState(false) + const [enabledOnly, setEnabledOnly] = React.useState(true) + + const commands = usePromise( + () => api.getCommands().then((r) => r.commands), + [] + ) + + const filteredCommands = React.useMemo( + () => + commands + ?.filter((c) => match(filter, c)) + .filter((c) => !withAccelerator || c.accelerators.length) + .filter((c) => !enabledOnly || c.enabled), + [filter, withAccelerator, enabledOnly, commands] + ) + return ( + + setFilter(e.target.value)} /> + + + + + {filteredCommands?.map((c) => ( + + ))} + + ) +} + +document.addEventListener('DOMContentLoaded', () => + render(, document.getElementById('root')) +) diff --git a/components/commands/browser/resources/commands_resources.grdp b/components/commands/browser/resources/commands_resources.grdp new file mode 100644 index 000000000000..dee3f380c96c --- /dev/null +++ b/components/commands/browser/resources/commands_resources.grdp @@ -0,0 +1,4 @@ + + + + diff --git a/components/commands/browser/resources/components/Command.tsx b/components/commands/browser/resources/components/Command.tsx new file mode 100644 index 000000000000..b2bf80480c58 --- /dev/null +++ b/components/commands/browser/resources/components/Command.tsx @@ -0,0 +1,79 @@ +// Copyright (c) 2023 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at https://mozilla.org/MPL/2.0/. + +import * as React from 'react' +import * as CommandsMojo from 'gen/brave/components/commands/common/commands.mojom.m.js' +import styled from 'styled-components' +import { api } from '../commands' +import { allKeys } from '../utils/accelerator' + +const Grid = styled.div` + display: grid; + grid-template-columns: 200px min-content auto; + gap: 16px; + align-items: center; + padding: 4px; + min-height: 32px; + + &:hover { + background-color: lightgray; + } +` + +const Column = styled.div` + display: flex; + flex-direction: column; + gap: 4px; +` + +const Kbd = styled.div` + display: inline-block; + border-radius: 4px; + padding: 4px; + background-color: #f6f8fa; + border: 1px solid rgba(174, 184, 193, 0.2); + box-shadow: inset 0 -1px 0 rgba(174, 184, 193, 0.2); +` + +let isSure = false +const ifSure = () => { + if (isSure) return true + return isSure = window.confirm('This is experimental. Executing commands may not behave as expected, or cause your browser to crash. Continue?') +} + +function Accelerator ({ + accelerator +}: { + accelerator: CommandsMojo.Accelerator +}) { + return ( +
+ {allKeys(accelerator).map((k, i) => ( + + {i !== 0 && +} + {k} + + ))} +
+ ) +} + +export default function Command ({ + command +}: { + command: CommandsMojo.Command +}) { + return ( + +
{command.name}
+ + + {command.accelerators.map((a, i) => ( + + ))} + +
+ ) +} diff --git a/components/commands/browser/resources/tsconfig.json b/components/commands/browser/resources/tsconfig.json new file mode 100644 index 000000000000..868d3fa89936 --- /dev/null +++ b/components/commands/browser/resources/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../../../tsconfig", + "include": [ + "**/*.ts", + "**/*.tsx", + "**/*.d.ts", + "../../definitions/*.d.ts" + ] +} diff --git a/components/commands/browser/resources/utils/accelerator.ts b/components/commands/browser/resources/utils/accelerator.ts new file mode 100644 index 000000000000..db986193bc15 --- /dev/null +++ b/components/commands/browser/resources/utils/accelerator.ts @@ -0,0 +1,7 @@ +// Copyright (c) 2023 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at https://mozilla.org/MPL/2.0/. +import { Accelerator } from 'gen/brave/components/commands/common/commands.mojom.m' + +export const allKeys = (accelerator: Accelerator) => [...accelerator.modifiers, accelerator.keycode] diff --git a/components/commands/browser/resources/utils/match.ts b/components/commands/browser/resources/utils/match.ts new file mode 100644 index 000000000000..8dfda3040c0b --- /dev/null +++ b/components/commands/browser/resources/utils/match.ts @@ -0,0 +1,19 @@ +// Copyright (c) 2023 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at https://mozilla.org/MPL/2.0/. +import { Command } from '../../../../../../out/Component/gen/brave/components/commands/common/commands.mojom.m' +import { allKeys } from './accelerator' + +export const match = (query: string, command: Command) => { + if (command.id === parseInt(query)) return true + + const queryUpper = query.toUpperCase() + if (command.name.toUpperCase().includes(queryUpper)) return true + + const keys = queryUpper.split('+').map(k => k.trim()).filter(k => k) + return command.accelerators.some(a => { + const acceleratorKeys = new Set(allKeys(a).map(k => k.toUpperCase())) + return keys.every(k => acceleratorKeys.has(k)) + }) +} diff --git a/components/commands/common/BUILD.gn b/components/commands/common/BUILD.gn new file mode 100644 index 000000000000..22cdc9c681f8 --- /dev/null +++ b/components/commands/common/BUILD.gn @@ -0,0 +1,28 @@ +# Copyright (c) 2023 The Brave Authors. All rights reserved. +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this file, +# You can obtain one at https://mozilla.org/MPL/2.0/. + +import("//mojo/public/tools/bindings/mojom.gni") + +static_library("common") { + sources = [ + "features.cc", + "features.h", + "key_names.cc", + "key_names.h", + ] + + deps = [ + "//base", + "//ui/events:event_constants", + "//ui/events:events_base", + ] + + public_deps = [ ":mojom" ] +} + +mojom("mojom") { + sources = [ "commands.mojom" ] + public_deps = [ "//mojo/public/mojom/base" ] +} diff --git a/components/commands/common/commands.mojom b/components/commands/common/commands.mojom new file mode 100644 index 000000000000..fc3e5d768a24 --- /dev/null +++ b/components/commands/common/commands.mojom @@ -0,0 +1,23 @@ +// Copyright (c) 2023 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at https://mozilla.org/MPL/2.0/. + +struct Command { + string name; + uint32 id; + + bool enabled; + + array accelerators; +}; + +struct Accelerator { + string keycode; + array modifiers; +}; + +interface CommandsService { + GetCommands() => (array commands); + TryExecuteCommand(uint32 command_id); +}; diff --git a/components/commands/common/features.cc b/components/commands/common/features.cc new file mode 100644 index 000000000000..6cefd6951c1b --- /dev/null +++ b/components/commands/common/features.cc @@ -0,0 +1,12 @@ +// Copyright (c) 2023 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at https://mozilla.org/MPL/2.0/. + +#include "brave/components/commands/common/features.h" + +namespace commands::features { +BASE_FEATURE(kBraveCommandsFeature, + "BraveCommands", + base::FEATURE_DISABLED_BY_DEFAULT); +} diff --git a/components/commands/common/features.h b/components/commands/common/features.h new file mode 100644 index 000000000000..623729cdac3a --- /dev/null +++ b/components/commands/common/features.h @@ -0,0 +1,15 @@ +// Copyright (c) 2023 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at https://mozilla.org/MPL/2.0/. + +#ifndef BRAVE_COMPONENTS_COMMANDS_COMMON_FEATURES_H_ +#define BRAVE_COMPONENTS_COMMANDS_COMMON_FEATURES_H_ + +#include "base/feature_list.h" + +namespace commands::features { +BASE_DECLARE_FEATURE(kBraveCommandsFeature); +} // namespace commands::features + +#endif // BRAVE_COMPONENTS_COMMANDS_COMMON_FEATURES_H_ diff --git a/components/commands/common/key_names.cc b/components/commands/common/key_names.cc new file mode 100644 index 000000000000..ed94717e11bc --- /dev/null +++ b/components/commands/common/key_names.cc @@ -0,0 +1,282 @@ +// Copyright (c) 2023 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at https://mozilla.org/MPL/2.0/. + +#include "brave/components/commands/common/key_names.h" + +namespace commands { +std::string GetKeyName(ui::KeyboardCode code) { + switch (code) { + case ui::VKEY_F1: + return "F1"; + case ui::VKEY_F2: + return "F2"; + case ui::VKEY_F3: + return "F3"; + case ui::VKEY_F4: + return "F4"; + case ui::VKEY_F5: + return "F5"; + case ui::VKEY_F6: + return "F6"; + case ui::VKEY_F7: + return "F7"; + case ui::VKEY_F8: + return "F8"; + case ui::VKEY_F9: + return "F9"; + case ui::VKEY_F10: + return "F10"; + case ui::VKEY_F11: + return "F11"; + case ui::VKEY_F12: + return "F12"; + case ui::VKEY_F13: + return "F13"; + case ui::VKEY_F14: + return "F14"; + case ui::VKEY_F15: + return "F15"; + case ui::VKEY_F16: + return "F16"; + case ui::VKEY_F17: + return "F17"; + case ui::VKEY_F18: + return "F18"; + case ui::VKEY_F19: + return "F19"; + case ui::VKEY_F20: + return "F20"; + case ui::VKEY_F21: + return "F21"; + case ui::VKEY_F22: + return "F22"; + case ui::VKEY_F23: + return "F23"; + case ui::VKEY_F24: + return "F24"; + case ui::VKEY_ESCAPE: + return "Esc"; + case ui::VKEY_BROWSER_SEARCH: + return "Search"; + case ui::VKEY_LMENU: + case ui::VKEY_RMENU: + case ui::VKEY_MENU: + return "Menu"; + case ui::VKEY_BROWSER_FORWARD: + return "Forward"; + case ui::VKEY_BROWSER_BACK: + return "Back"; + case ui::VKEY_BROWSER_REFRESH: + return "Refresh"; + case ui::VKEY_BROWSER_HOME: + return "Home"; + case ui::VKEY_BROWSER_STOP: + return "Stop"; + case ui::VKEY_BROWSER_FAVORITES: + return "Favorites"; + case ui::VKEY_NEW: + return "New"; + case ui::VKEY_CLOSE: + return "Close"; + case ui::VKEY_BACK: + return "Back"; + case ui::VKEY_DELETE: + return "Delete"; + case ui::VKEY_MEDIA_PLAY_PAUSE: + return "Play Pause"; + case ui::VKEY_MEDIA_PLAY: + return "Play"; + case ui::VKEY_MEDIA_PAUSE: + return "Pause"; + case ui::VKEY_VOLUME_MUTE: + return "Mute"; + case ui::VKEY_TAB: + return "Tab"; + case ui::VKEY_NEXT: + return "PgDn"; + case ui::VKEY_PRIOR: + return "PgUp"; + case ui::VKEY_RETURN: + return "Enter"; + case ui::VKEY_1: + case ui::VKEY_NUMPAD1: + return "1"; + case ui::VKEY_2: + case ui::VKEY_NUMPAD2: + return "2"; + case ui::VKEY_3: + case ui::VKEY_NUMPAD3: + return "3"; + case ui::VKEY_4: + case ui::VKEY_NUMPAD4: + return "4"; + case ui::VKEY_5: + case ui::VKEY_NUMPAD5: + return "5"; + case ui::VKEY_6: + case ui::VKEY_NUMPAD6: + return "6"; + case ui::VKEY_7: + case ui::VKEY_NUMPAD7: + return "7"; + case ui::VKEY_8: + case ui::VKEY_NUMPAD8: + return "8"; + case ui::VKEY_9: + case ui::VKEY_NUMPAD9: + return "9"; + case ui::VKEY_0: + case ui::VKEY_NUMPAD0: + return "0"; + case ui::VKEY_SUBTRACT: + case ui::VKEY_OEM_MINUS: + return "-"; + case ui::VKEY_ADD: + case ui::VKEY_OEM_PLUS: + return "+"; + case ui::VKEY_SPACE: + return "Space"; + case ui::VKEY_LEFT: + return "Left"; + case ui::VKEY_RIGHT: + return "Right"; + case ui::VKEY_UP: + return "Up"; + case ui::VKEY_DOWN: + return "Down"; + case ui::VKEY_HOME: + return "Home"; + case ui::VKEY_END: + return "End"; + case ui::VKEY_CAPITAL: + return "Caps"; + case ui::VKEY_A: + return "A"; + case ui::VKEY_B: + return "B"; + case ui::VKEY_C: + return "C"; + case ui::VKEY_D: + return "D"; + case ui::VKEY_E: + return "E"; + case ui::VKEY_F: + return "F"; + case ui::VKEY_G: + return "G"; + case ui::VKEY_H: + return "H"; + case ui::VKEY_I: + return "I"; + case ui::VKEY_J: + return "J"; + case ui::VKEY_K: + return "K"; + case ui::VKEY_L: + return "L"; + case ui::VKEY_M: + return "M"; + case ui::VKEY_N: + return "N"; + case ui::VKEY_O: + return "O"; + case ui::VKEY_P: + return "P"; + case ui::VKEY_Q: + return "Q"; + case ui::VKEY_R: + return "R"; + case ui::VKEY_S: + return "S"; + case ui::VKEY_T: + return "T"; + case ui::VKEY_U: + return "U"; + case ui::VKEY_V: + return "V"; + case ui::VKEY_W: + return "W"; + case ui::VKEY_X: + return "X"; + case ui::VKEY_Y: + return "Y"; + case ui::VKEY_Z: + return "Z"; + case ui::VKEY_CANCEL: + return "Cancel"; + case ui::VKEY_BACKTAB: + return "Backtab"; + case ui::VKEY_CLEAR: + return "Clear"; + case ui::VKEY_SHIFT: + return "Shift"; + case ui::VKEY_CONTROL: + return "Ctrk"; + case ui::VKEY_PAUSE: + return "Pause"; + case ui::VKEY_KANA: + return "Kana"; + case ui::VKEY_PASTE: + return "Paste"; + case ui::VKEY_JUNJA: + return "Junja"; + case ui::VKEY_FINAL: + return "Final"; + case ui::VKEY_HANJA: + return "Hanja"; + case ui::VKEY_CONVERT: + return "Convert"; + case ui::VKEY_NONCONVERT: + return "Non Convert"; + case ui::VKEY_ACCEPT: + return ""; + case ui::VKEY_MODECHANGE: + return "Mode"; + case ui::VKEY_SELECT: + return "Select"; + case ui::VKEY_PRINT: + return "Print"; + case ui::VKEY_EXECUTE: + return "Execute"; + case ui::VKEY_SNAPSHOT: + return "PrtScn"; + case ui::VKEY_INSERT: + return "Ins"; + case ui::VKEY_HELP: + return "Help"; + case ui::VKEY_RWIN: + case ui::VKEY_COMMAND: + return "Cmd"; + default: + return "Unknown"; + } +} + +std::vector GetModifierName(ui::KeyEventFlags flags) { + std::vector result; + + if (flags & ui::EF_COMMAND_DOWN) { + result.push_back("Cmd"); + } + + if (flags & ui::EF_CONTROL_DOWN) { + result.push_back("Ctrl"); + } + + if (flags & ui::EF_ALT_DOWN) { + result.push_back("Alt"); + } + + if (flags & ui::EF_SHIFT_DOWN) { + result.push_back("Shift"); + } + + if (flags & ui::EF_FUNCTION_DOWN) { + result.push_back("Fn"); + } + + return result; +} +} // namespace commands diff --git a/components/commands/common/key_names.h b/components/commands/common/key_names.h new file mode 100644 index 000000000000..900ae2215f4c --- /dev/null +++ b/components/commands/common/key_names.h @@ -0,0 +1,20 @@ +// Copyright (c) 2023 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at https://mozilla.org/MPL/2.0/. + +#ifndef BRAVE_COMPONENTS_COMMANDS_COMMON_KEY_NAMES_H_ +#define BRAVE_COMPONENTS_COMMANDS_COMMON_KEY_NAMES_H_ + +#include +#include + +#include "ui/events/event_constants.h" +#include "ui/events/keycodes/keyboard_codes.h" + +namespace commands { +std::string GetKeyName(ui::KeyboardCode code); +std::vector GetModifierName(ui::KeyEventFlags flags); +} // namespace commands + +#endif // BRAVE_COMPONENTS_COMMANDS_COMMON_KEY_NAMES_H_ diff --git a/components/constants/webui_url_constants.cc b/components/constants/webui_url_constants.cc index 03faebbc8e17..6e9eb6a4b018 100644 --- a/components/constants/webui_url_constants.cc +++ b/components/constants/webui_url_constants.cc @@ -57,3 +57,4 @@ const char kPlaylistHost[] = "playlist"; const char kPlaylistURL[] = "chrome-untrusted://playlist/"; const char kSpeedreaderPanelURL[] = "chrome://brave-speedreader.top-chrome"; const char kSpeedreaderPanelHost[] = "brave-speedreader.top-chrome"; +const char kCommandsHost[] = "commands"; diff --git a/components/constants/webui_url_constants.h b/components/constants/webui_url_constants.h index a8a3b6112a4c..77301a94cd28 100644 --- a/components/constants/webui_url_constants.h +++ b/components/constants/webui_url_constants.h @@ -58,5 +58,6 @@ extern const char kPlaylistHost[]; extern const char kPlaylistURL[]; extern const char kSpeedreaderPanelURL[]; extern const char kSpeedreaderPanelHost[]; +extern const char kCommandsHost[]; #endif // BRAVE_COMPONENTS_CONSTANTS_WEBUI_URL_CONSTANTS_H_ diff --git a/components/resources/BUILD.gn b/components/resources/BUILD.gn index ea7d974d6b6d..c1ff29d421a9 100644 --- a/components/resources/BUILD.gn +++ b/components/resources/BUILD.gn @@ -124,6 +124,10 @@ repack("resources") { sources += [ "$root_gen_dir/brave/components/playlist/browser/resources/playlist_generated.pak" ] } + deps += + [ "//brave/components/commands/browser/resources:generated_resources" ] + sources += [ "$root_gen_dir/brave/components/commands/browser/resources/commands_generated.pak" ] + output = "$root_gen_dir/components/brave_components_resources.pak" } diff --git a/components/resources/brave_components_resources.grd b/components/resources/brave_components_resources.grd index 214df046b983..1b70e686e568 100644 --- a/components/resources/brave_components_resources.grd +++ b/components/resources/brave_components_resources.grd @@ -64,6 +64,7 @@ + diff --git a/resources/resource_ids.spec b/resources/resource_ids.spec index a8fa337fbc24..ac1b99750ce4 100644 --- a/resources/resource_ids.spec +++ b/resources/resource_ids.spec @@ -182,6 +182,10 @@ "META": {"sizes": {"includes": [250]}}, "includes": [59270] }, + "<(SHARED_INTERMEDIATE_DIR)/brave/web-ui-commands/commands.grd": { + "META": {"sizes": {"includes": [250]}}, + "includes": [59280] + }, "<(SHARED_INTERMEDIATE_DIR)/brave/web-ui-ledger_bridge/ledger_bridge.grd": { "META": {"sizes": {"includes": [250]}}, "includes": [59520] diff --git a/test/BUILD.gn b/test/BUILD.gn index 6b8b48073dab..b1776679369b 100644 --- a/test/BUILD.gn +++ b/test/BUILD.gn @@ -91,6 +91,7 @@ test("brave_unit_tests") { ] sources = [ + "//brave/app/command_utils_unittest.cc", "//brave/browser/brave_content_browser_client_unittest.cc", "//brave/browser/brave_resources_util_unittest.cc", "//brave/browser/brave_stats/brave_stats_updater_unittest.cc", @@ -218,6 +219,7 @@ test("brave_unit_tests") { "//brave/components/brave_wallet/common:unit_tests", "//brave/components/brave_wallet/renderer/test:unit_tests", "//brave/components/child_process_monitor:unittests", + "//brave/components/commands/common", "//brave/components/constants", "//brave/components/de_amp/browser/test:unit_tests", "//brave/components/debounce/browser/test:unit_tests", @@ -676,6 +678,7 @@ test("brave_browser_tests") { sources = [ "//brave/app/brave_main_delegate_browsertest.cc", "//brave/app/brave_main_delegate_runtime_flags_browsertest.cc", + "//brave/app/command_utils_browsertest.cc", "//brave/browser/brave_ads/ads_service_browsertest.cc", "//brave/browser/brave_ads/brave_stats_updater_helper_browsertest.cc", "//brave/browser/brave_ads/notification_helper/notification_helper_impl_mock.cc", @@ -860,6 +863,7 @@ test("brave_browser_tests") { "//brave/components/brave_wallet/renderer", "//brave/components/brave_wallet/resources:ethereum_provider_generated_resources", "//brave/components/brave_wayback_machine/buildflags", + "//brave/components/commands/common", "//brave/components/constants", "//brave/components/de_amp/browser/test:browser_tests", "//brave/components/de_amp/common:common",