Files
cef/patch/patches/chrome_browser_context_menus.patch
Marshall Greenblatt 6090afb12e chrome: Add cleanup when context menu isn't running (fixes #3711)
The menu may not be running in the following cases:

- If the menu is empty (e.g. cleared in OnBeforeContextMenu).
- If the menu is disabled (see e.g. RenderViewContextMenuViews::Show).
- When the run call blocks until the menu is dismissed (macOS behavior).

We explicitly clean up in these cases instead of waiting for OnMenuClosed
which will otherwise never be called for the first 2 cases.

Menu run status is exposed via new ContextMenuDelegate and
RenderViewContextMenuBase methods.
2024-06-13 13:43:40 -04:00

397 lines
17 KiB
Diff

diff --git chrome/browser/renderer_context_menu/render_view_context_menu.cc chrome/browser/renderer_context_menu/render_view_context_menu.cc
index 4f866d176dccf..c2abc6231c727 100644
--- chrome/browser/renderer_context_menu/render_view_context_menu.cc
+++ chrome/browser/renderer_context_menu/render_view_context_menu.cc
@@ -359,6 +359,18 @@ base::OnceCallback<void(RenderViewContextMenu*)>* GetMenuShownCallback() {
return callback.get();
}
+RenderViewContextMenu::MenuCreatedCallback* GetMenuCreatedCallback() {
+ static base::NoDestructor<RenderViewContextMenu::MenuCreatedCallback>
+ callback;
+ return callback.get();
+}
+
+RenderViewContextMenu::MenuShowHandlerCallback* GetMenuShowHandlerCallback() {
+ static base::NoDestructor<RenderViewContextMenu::MenuShowHandlerCallback>
+ callback;
+ return callback.get();
+}
+
enum class UmaEnumIdLookupType {
GeneralEnumId,
ContextSpecificEnumId,
@@ -620,6 +632,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);
@@ -879,6 +895,14 @@ RenderViewContextMenu::RenderViewContextMenu(
pdf_ocr_submenu_model_ = std::make_unique<ui::SimpleMenuModel>(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_);
}
@@ -1354,6 +1378,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 {
@@ -3578,6 +3608,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 d9f97fbdced53..60a01f70592a3 100644
--- chrome/browser/renderer_context_menu/render_view_context_menu.h
+++ chrome/browser/renderer_context_menu/render_view_context_menu.h
@@ -157,7 +157,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<RenderViewContextMenuObserver>(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
@@ -479,6 +493,9 @@ class RenderViewContextMenu
// built.
bool is_protocol_submenu_valid_ = false;
+ // An observer returned via MenuCreatedCallback that will be called first.
+ std::unique_ptr<RenderViewContextMenuObserver> first_observer_;
+
// An observer that handles spelling suggestions, "Add to dictionary", and
// "Use enhanced spell check" items.
std::unique_ptr<SpellingMenuObserver> 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<ToolkitDelegateViews*>(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<RenderViewContextMenuBase> 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<RenderViewContextMenuBase> menu) = 0;
+ virtual bool IsMenuRunning() = 0;
+
private:
raw_ptr<content::WebContents> 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<views::MenuModelAdapter>(menu_model);
std::unique_ptr<views::MenuItemView> 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: