diff --git chrome/browser/renderer_context_menu/render_view_context_menu.cc chrome/browser/renderer_context_menu/render_view_context_menu.cc index b62e659a27d00..e76cc7c0a7813 100644 --- chrome/browser/renderer_context_menu/render_view_context_menu.cc +++ chrome/browser/renderer_context_menu/render_view_context_menu.cc @@ -357,6 +357,18 @@ base::OnceCallback* GetMenuShownCallback() { return callback.get(); } +RenderViewContextMenu::MenuCreatedCallback* GetMenuCreatedCallback() { + static base::NoDestructor + callback; + return callback.get(); +} + +RenderViewContextMenu::MenuShowHandlerCallback* GetMenuShowHandlerCallback() { + static base::NoDestructor + callback; + return callback.get(); +} + enum class UmaEnumIdLookupType { GeneralEnumId, ContextSpecificEnumId, @@ -618,6 +630,10 @@ int FindUMAEnumValueForCommand(int id, UmaEnumIdLookupType type) { if (ContextMenuMatcher::IsExtensionsCustomCommandId(id)) return 1; + // Match the MENU_ID_USER_FIRST to MENU_ID_USER_LAST range from cef_types.h. + if (id >= 26500 && id <= 28500) + return 1; + id = CollapseCommandsForUMA(id); const auto& map = GetIdcToUmaMap(type); auto it = map.find(id); @@ -868,6 +884,14 @@ RenderViewContextMenu::RenderViewContextMenu( pdf_ocr_submenu_model_ = std::make_unique(this); #endif // BUILDFLAG(ENABLE_SCREEN_AI_SERVICE) + auto* cb = GetMenuCreatedCallback(); + if (!cb->is_null()) { + first_observer_ = cb->Run(this); + if (first_observer_) { + observers_.AddObserver(first_observer_.get()); + } + } + observers_.AddObserver(&autofill_context_menu_manager_); } @@ -1343,6 +1367,12 @@ void RenderViewContextMenu::InitMenu() { autofill::SuggestionHidingReason::kContextMenuOpened); } } + + if (first_observer_) { + // Do this last so that the observer can optionally modify previously + // created items. + first_observer_->InitMenu(params_); + } } Profile* RenderViewContextMenu::GetProfile() const { @@ -3564,6 +3594,26 @@ void RenderViewContextMenu::RegisterExecutePluginActionCallbackForTesting( execute_plugin_action_callback_ = std::move(cb); } +// static +void RenderViewContextMenu::RegisterMenuCreatedCallback( + MenuCreatedCallback cb) { + *GetMenuCreatedCallback() = cb; +} + +// static +void RenderViewContextMenu::RegisterMenuShowHandlerCallback( + MenuShowHandlerCallback cb) { + *GetMenuShowHandlerCallback() = cb; +} + +bool RenderViewContextMenu::UseShowHandler() { + auto* cb = GetMenuShowHandlerCallback(); + if (!cb->is_null() && cb->Run(this)) { + return true; + } + return false; +} + custom_handlers::ProtocolHandlerRegistry::ProtocolHandlerList RenderViewContextMenu::GetHandlersForLinkUrl() { custom_handlers::ProtocolHandlerRegistry::ProtocolHandlerList handlers = diff --git chrome/browser/renderer_context_menu/render_view_context_menu.h chrome/browser/renderer_context_menu/render_view_context_menu.h index 6c59d4ccaf3d5..21c959aea9c21 100644 --- chrome/browser/renderer_context_menu/render_view_context_menu.h +++ chrome/browser/renderer_context_menu/render_view_context_menu.h @@ -155,7 +155,21 @@ class RenderViewContextMenu } #endif + // Registers a callback that will be called each time a context menu is + // created. + using MenuCreatedCallback = base::RepeatingCallback< + std::unique_ptr(RenderViewContextMenu*)>; + static void RegisterMenuCreatedCallback(MenuCreatedCallback cb); + + // Registers a callback that will be called each time before context menu is + // shown. The callback should return true if the show has handled. + using MenuShowHandlerCallback = base::RepeatingCallback< + bool(RenderViewContextMenu*)>; + static void RegisterMenuShowHandlerCallback(MenuShowHandlerCallback cb); + protected: + bool UseShowHandler(); + Profile* GetProfile() const; // This may return nullptr (e.g. for WebUI dialogs). Virtual to allow tests to @@ -464,6 +478,9 @@ class RenderViewContextMenu // built. bool is_protocol_submenu_valid_ = false; + // An observer returned via MenuCreatedCallback that will be called first. + std::unique_ptr first_observer_; + // An observer that handles spelling suggestions, "Add to dictionary", and // "Use enhanced spell check" items. std::unique_ptr spelling_suggestions_menu_observer_; diff --git chrome/browser/ui/cocoa/renderer_context_menu/render_view_context_menu_mac_cocoa.h chrome/browser/ui/cocoa/renderer_context_menu/render_view_context_menu_mac_cocoa.h index cb51224f9892c..b5a3946999d8f 100644 --- chrome/browser/ui/cocoa/renderer_context_menu/render_view_context_menu_mac_cocoa.h +++ chrome/browser/ui/cocoa/renderer_context_menu/render_view_context_menu_mac_cocoa.h @@ -29,6 +29,7 @@ class RenderViewContextMenuMacCocoa : public RenderViewContextMenuMac { // RenderViewContextMenu: void Show() override; + bool IsRunning() override; private: // RenderViewContextMenuViewsMac: diff --git chrome/browser/ui/cocoa/renderer_context_menu/render_view_context_menu_mac_cocoa.mm chrome/browser/ui/cocoa/renderer_context_menu/render_view_context_menu_mac_cocoa.mm index b130e9768c2d6..3e46b2fe18d0a 100644 --- chrome/browser/ui/cocoa/renderer_context_menu/render_view_context_menu_mac_cocoa.mm +++ chrome/browser/ui/cocoa/renderer_context_menu/render_view_context_menu_mac_cocoa.mm @@ -68,6 +68,10 @@ RenderViewContextMenuMacCocoa::~RenderViewContextMenuMacCocoa() { } void RenderViewContextMenuMacCocoa::Show() { + if (UseShowHandler()) { + return; + } + views::Widget* widget = views::Widget::GetTopLevelWidgetForNativeView( source_web_contents_->GetNativeView()); @@ -93,6 +97,10 @@ void RenderViewContextMenuMacCocoa::Show() { views::ElementTrackerViews::GetContextForWidget(widget)); } +bool RenderViewContextMenuMacCocoa::IsRunning() { + return menu_controller_ && [menu_controller_ isMenuOpen]; +} + void RenderViewContextMenuMacCocoa::CancelToolkitMenu() { [menu_controller_ cancel]; } diff --git chrome/browser/ui/cocoa/renderer_context_menu/render_view_context_menu_mac_remote_cocoa.h chrome/browser/ui/cocoa/renderer_context_menu/render_view_context_menu_mac_remote_cocoa.h index fb86992cee93f..fe674fe471cc4 100644 --- chrome/browser/ui/cocoa/renderer_context_menu/render_view_context_menu_mac_remote_cocoa.h +++ chrome/browser/ui/cocoa/renderer_context_menu/render_view_context_menu_mac_remote_cocoa.h @@ -27,6 +27,7 @@ class RenderViewContextMenuMacRemoteCocoa : public RenderViewContextMenuMac { // RenderViewContextMenu: void Show() override; + bool IsRunning() override; private: // RenderViewContextMenuViewsMac: diff --git chrome/browser/ui/cocoa/renderer_context_menu/render_view_context_menu_mac_remote_cocoa.mm chrome/browser/ui/cocoa/renderer_context_menu/render_view_context_menu_mac_remote_cocoa.mm index 9f6c5fd44f206..dc50bc909897f 100644 --- chrome/browser/ui/cocoa/renderer_context_menu/render_view_context_menu_mac_remote_cocoa.mm +++ chrome/browser/ui/cocoa/renderer_context_menu/render_view_context_menu_mac_remote_cocoa.mm @@ -42,6 +42,10 @@ void RenderViewContextMenuMacRemoteCocoa::Show() { target_view_id_); } +bool RenderViewContextMenuMacRemoteCocoa::IsRunning() { + return runner_ && runner_->IsRunning(); +} + void RenderViewContextMenuMacRemoteCocoa::CancelToolkitMenu() { runner_->Cancel(); } diff --git chrome/browser/ui/views/renderer_context_menu/render_view_context_menu_views.cc chrome/browser/ui/views/renderer_context_menu/render_view_context_menu_views.cc index c88a77a0b49e2..31b7224a36ae8 100644 --- chrome/browser/ui/views/renderer_context_menu/render_view_context_menu_views.cc +++ chrome/browser/ui/views/renderer_context_menu/render_view_context_menu_views.cc @@ -149,6 +149,9 @@ void RenderViewContextMenuViews::RunMenuAt(views::Widget* parent, bool RenderViewContextMenuViews::GetAcceleratorForCommandId( int command_id, ui::Accelerator* accel) const { + if (RenderViewContextMenu::GetAcceleratorForCommandId(command_id, accel)) + return true; + // There are no formally defined accelerators we can query so we assume // that Ctrl+C, Ctrl+V, Ctrl+X, Ctrl-A, etc do what they normally do. switch (command_id) { @@ -385,6 +388,10 @@ void RenderViewContextMenuViews::AppendPlatformEditableItems() { } void RenderViewContextMenuViews::Show() { + if (UseShowHandler()) { + return; + } + if (base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kKioskMode)) return; @@ -427,6 +434,11 @@ void RenderViewContextMenuViews::Show() { } } +bool RenderViewContextMenuViews::IsRunning() { + return static_cast(toolkit_delegate()) + ->IsMenuRunning(); +} + views::Widget* RenderViewContextMenuViews::GetTopLevelWidget() { return views::Widget::GetTopLevelWidgetForNativeView(GetActiveNativeView()); } diff --git chrome/browser/ui/views/tab_contents/chrome_web_contents_view_delegate_views.cc chrome/browser/ui/views/tab_contents/chrome_web_contents_view_delegate_views.cc index eb855deeb6040..3ff97b28fa4c6 100644 --- chrome/browser/ui/views/tab_contents/chrome_web_contents_view_delegate_views.cc +++ chrome/browser/ui/views/tab_contents/chrome_web_contents_view_delegate_views.cc @@ -91,6 +91,10 @@ void ChromeWebContentsViewDelegateViews::ShowMenu( context_menu_->Show(); } +bool ChromeWebContentsViewDelegateViews::IsMenuRunning() { + return context_menu_ && context_menu_->IsRunning(); +} + void ChromeWebContentsViewDelegateViews::ShowContextMenu( content::RenderFrameHost& render_frame_host, const content::ContextMenuParams& params) { diff --git chrome/browser/ui/views/tab_contents/chrome_web_contents_view_delegate_views_mac.h chrome/browser/ui/views/tab_contents/chrome_web_contents_view_delegate_views_mac.h index 07e5b3613a2df..293282690bd5a 100644 --- chrome/browser/ui/views/tab_contents/chrome_web_contents_view_delegate_views_mac.h +++ chrome/browser/ui/views/tab_contents/chrome_web_contents_view_delegate_views_mac.h @@ -55,6 +55,7 @@ class ChromeWebContentsViewDelegateViewsMac content::RenderFrameHost& render_frame_host, const content::ContextMenuParams& params) override; void ShowMenu(std::unique_ptr menu) override; + bool IsMenuRunning() override; private: content::RenderWidgetHostView* GetActiveRenderWidgetHostView() const; diff --git chrome/browser/ui/views/tab_contents/chrome_web_contents_view_delegate_views_mac.mm chrome/browser/ui/views/tab_contents/chrome_web_contents_view_delegate_views_mac.mm index 0e2cf71973741..e8d9c38b49b6c 100644 --- chrome/browser/ui/views/tab_contents/chrome_web_contents_view_delegate_views_mac.mm +++ chrome/browser/ui/views/tab_contents/chrome_web_contents_view_delegate_views_mac.mm @@ -131,6 +131,10 @@ void ChromeWebContentsViewDelegateViewsMac::ShowMenu( context_menu_->Show(); } +bool ChromeWebContentsViewDelegateViewsMac::IsMenuRunning() { + return context_menu_ && context_menu_->IsRunning(); +} + content::RenderWidgetHostView* ChromeWebContentsViewDelegateViewsMac::GetActiveRenderWidgetHostView() const { return web_contents_->GetTopLevelRenderWidgetHostView(); diff --git components/renderer_context_menu/context_menu_delegate.h components/renderer_context_menu/context_menu_delegate.h index 042428f77f4ad..e4efd98ca45d5 100644 --- components/renderer_context_menu/context_menu_delegate.h +++ components/renderer_context_menu/context_menu_delegate.h @@ -45,6 +45,8 @@ class ContextMenuDelegate { // Displays the context menu. virtual void ShowMenu(std::unique_ptr menu) = 0; + virtual bool IsMenuRunning() = 0; + private: raw_ptr web_contents_ = nullptr; }; diff --git components/renderer_context_menu/render_view_context_menu_base.cc components/renderer_context_menu/render_view_context_menu_base.cc index 8e45cecb17039..e40115e23ee82 100644 --- components/renderer_context_menu/render_view_context_menu_base.cc +++ components/renderer_context_menu/render_view_context_menu_base.cc @@ -374,6 +374,17 @@ bool RenderViewContextMenuBase::IsCommandIdChecked(int id) const { return false; } +bool RenderViewContextMenuBase::GetAcceleratorForCommandId( + int id, + ui::Accelerator* accelerator) const { + for (auto& observer : observers_) { + if (observer.IsCommandIdSupported(id)) + return observer.GetAccelerator(id, accelerator); + } + + return false; +} + void RenderViewContextMenuBase::ExecuteCommand(int id, int event_flags) { command_executed_ = true; RecordUsedItem(id); diff --git components/renderer_context_menu/render_view_context_menu_base.h components/renderer_context_menu/render_view_context_menu_base.h index 57b288bc885e6..9909899e3b5fe 100644 --- components/renderer_context_menu/render_view_context_menu_base.h +++ components/renderer_context_menu/render_view_context_menu_base.h @@ -85,8 +85,13 @@ class RenderViewContextMenuBase : public ui::SimpleMenuModel::Delegate, // Programmatically closes the context menu. void Cancel(); + virtual bool IsRunning() = 0; + const ui::SimpleMenuModel& menu_model() const { return menu_model_; } const content::ContextMenuParams& params() const { return params_; } + content::WebContents* source_web_contents() const { + return source_web_contents_; + } // Returns true if the specified command id is known and valid for // this menu. If the command is known |enabled| is set to indicate @@ -95,6 +100,9 @@ class RenderViewContextMenuBase : public ui::SimpleMenuModel::Delegate, // SimpleMenuModel::Delegate implementation. bool IsCommandIdChecked(int command_id) const override; + bool GetAcceleratorForCommandId( + int command_id, + ui::Accelerator* accelerator) const override; void ExecuteCommand(int command_id, int event_flags) override; void OnMenuWillShow(ui::SimpleMenuModel* source) override; void MenuClosed(ui::SimpleMenuModel* source) override; diff --git components/renderer_context_menu/render_view_context_menu_observer.cc components/renderer_context_menu/render_view_context_menu_observer.cc index 9fdda1636003d..538bd05a41296 100644 --- components/renderer_context_menu/render_view_context_menu_observer.cc +++ components/renderer_context_menu/render_view_context_menu_observer.cc @@ -15,3 +15,8 @@ bool RenderViewContextMenuObserver::IsCommandIdChecked(int command_id) { bool RenderViewContextMenuObserver::IsCommandIdEnabled(int command_id) { return false; } + +bool RenderViewContextMenuObserver::GetAccelerator(int command_id, + ui::Accelerator* accel) { + return false; +} diff --git components/renderer_context_menu/render_view_context_menu_observer.h components/renderer_context_menu/render_view_context_menu_observer.h index 0527c57abd51c..70bebcbb50461 100644 --- components/renderer_context_menu/render_view_context_menu_observer.h +++ components/renderer_context_menu/render_view_context_menu_observer.h @@ -11,6 +11,10 @@ namespace content { struct ContextMenuParams; } +namespace ui { +class Accelerator; +} + // The interface used for implementing context-menu items. The following // instruction describe how to implement a context-menu item with this // interface. @@ -100,6 +104,8 @@ class RenderViewContextMenuObserver { virtual bool IsCommandIdChecked(int command_id); virtual bool IsCommandIdEnabled(int command_id); + virtual bool GetAccelerator(int command_id, ui::Accelerator* accel); + // Called when a user selects the specified context-menu item. This is // only called when the observer returns true for IsCommandIdSupported() // for that |command_id|. diff --git components/renderer_context_menu/views/toolkit_delegate_views.cc components/renderer_context_menu/views/toolkit_delegate_views.cc index 823f044316527..85439bf7ad2eb 100644 --- components/renderer_context_menu/views/toolkit_delegate_views.cc +++ components/renderer_context_menu/views/toolkit_delegate_views.cc @@ -30,6 +30,10 @@ void ToolkitDelegateViews::RunMenuAt(views::Widget* parent, anchor_position, type); } +bool ToolkitDelegateViews::IsMenuRunning() const { + return menu_runner_ && menu_runner_->IsRunning(); +} + void ToolkitDelegateViews::Init(ui::SimpleMenuModel* menu_model) { menu_adapter_ = std::make_unique(menu_model); std::unique_ptr menu_view = menu_adapter_->CreateMenu(); diff --git components/renderer_context_menu/views/toolkit_delegate_views.h components/renderer_context_menu/views/toolkit_delegate_views.h index eacdc12813204..9084effdaa121 100644 --- components/renderer_context_menu/views/toolkit_delegate_views.h +++ components/renderer_context_menu/views/toolkit_delegate_views.h @@ -39,6 +39,7 @@ class ToolkitDelegateViews : public RenderViewContextMenuBase::ToolkitDelegate { const gfx::Point& point, ui::MenuSourceType type); views::MenuItemView* menu_view() { return menu_view_; } + bool IsMenuRunning() const; protected: // ToolkitDelegate: