1 // Copyright (c) 2021 The Chromium Embedded Framework Authors. All rights
2 // reserved. Use of this source code is governed by a BSD-style license that can
3 // be found in the LICENSE file.
4
5 #include "libcef/browser/chrome/chrome_context_menu_handler.h"
6
7 #include "libcef/browser/browser_host_base.h"
8 #include "libcef/browser/context_menu_params_impl.h"
9 #include "libcef/browser/simple_menu_model_impl.h"
10
11 #include "chrome/browser/renderer_context_menu/render_view_context_menu.h"
12
13 namespace context_menu {
14
15 namespace {
16
17 // Lifespan is controlled by RenderViewContextMenu.
18 class CefContextMenuObserver : public RenderViewContextMenuObserver,
19 public CefSimpleMenuModelImpl::StateDelegate {
20 public:
CefContextMenuObserver(RenderViewContextMenu * context_menu,CefRefPtr<CefBrowserHostBase> browser,CefRefPtr<CefContextMenuHandler> handler)21 CefContextMenuObserver(RenderViewContextMenu* context_menu,
22 CefRefPtr<CefBrowserHostBase> browser,
23 CefRefPtr<CefContextMenuHandler> handler)
24 : context_menu_(context_menu), browser_(browser), handler_(handler) {}
25
26 CefContextMenuObserver(const CefContextMenuObserver&) = delete;
27 CefContextMenuObserver& operator=(const CefContextMenuObserver&) = delete;
28
29 // RenderViewContextMenuObserver methods:
30
InitMenu(const content::ContextMenuParams & params)31 void InitMenu(const content::ContextMenuParams& params) override {
32 params_ = new CefContextMenuParamsImpl(
33 const_cast<content::ContextMenuParams*>(&context_menu_->params()));
34 model_ = new CefSimpleMenuModelImpl(
35 const_cast<ui::SimpleMenuModel*>(&context_menu_->menu_model()),
36 context_menu_, this, /*is_owned=*/false, /*is_popup=*/false);
37
38 handler_->OnBeforeContextMenu(browser_, GetFrame(), params_, model_);
39 }
40
IsCommandIdSupported(int command_id)41 bool IsCommandIdSupported(int command_id) override {
42 // Always claim support for the reserved user ID range.
43 if (command_id >= MENU_ID_USER_FIRST && command_id <= MENU_ID_USER_LAST)
44 return true;
45
46 // Also claim support in specific cases where an ItemInfo exists.
47 return GetItemInfo(command_id) != nullptr;
48 }
49
50 // Only called if IsCommandIdSupported() returns true.
IsCommandIdEnabled(int command_id)51 bool IsCommandIdEnabled(int command_id) override {
52 // Always return true to use the SimpleMenuModel state.
53 return true;
54 }
55
56 // Only called if IsCommandIdSupported() returns true.
IsCommandIdChecked(int command_id)57 bool IsCommandIdChecked(int command_id) override {
58 auto* info = GetItemInfo(command_id);
59 return info ? info->checked : false;
60 }
61
62 // Only called if IsCommandIdSupported() returns true.
GetAccelerator(int command_id,ui::Accelerator * accel)63 bool GetAccelerator(int command_id, ui::Accelerator* accel) override {
64 auto* info = GetItemInfo(command_id);
65 if (info && info->accel) {
66 *accel = *info->accel;
67 return true;
68 }
69 return false;
70 }
71
CommandWillBeExecuted(int command_id)72 void CommandWillBeExecuted(int command_id) override {
73 if (handler_->OnContextMenuCommand(browser_, GetFrame(), params_,
74 command_id, EVENTFLAG_NONE)) {
75 // Create an ItemInfo so that we get the ExecuteCommand() callback
76 // instead of the default handler.
77 GetOrCreateItemInfo(command_id);
78 }
79 }
80
81 // Only called if IsCommandIdSupported() returns true.
ExecuteCommand(int command_id)82 void ExecuteCommand(int command_id) override {
83 auto* info = GetItemInfo(command_id);
84 if (info) {
85 // In case it was added in CommandWillBeExecuted().
86 MaybeDeleteItemInfo(command_id, info);
87 }
88 }
89
OnMenuClosed()90 void OnMenuClosed() override {
91 handler_->OnContextMenuDismissed(browser_, GetFrame());
92 model_->Detach();
93
94 // Clear stored state because this object won't be deleted until a new
95 // context menu is created or the associated browser is destroyed.
96 browser_ = nullptr;
97 handler_ = nullptr;
98 params_ = nullptr;
99 model_ = nullptr;
100 iteminfomap_.clear();
101 }
102
103 // CefSimpleMenuModelImpl::StateDelegate methods:
104
SetChecked(int command_id,bool checked)105 void SetChecked(int command_id, bool checked) override {
106 // No-op if already at the default state.
107 if (!checked && !GetItemInfo(command_id))
108 return;
109
110 auto* info = GetOrCreateItemInfo(command_id);
111 info->checked = checked;
112 if (!checked)
113 MaybeDeleteItemInfo(command_id, info);
114 }
115
SetAccelerator(int command_id,absl::optional<ui::Accelerator> accel)116 void SetAccelerator(int command_id,
117 absl::optional<ui::Accelerator> accel) override {
118 // No-op if already at the default state.
119 if (!accel && !GetItemInfo(command_id))
120 return;
121
122 auto* info = GetOrCreateItemInfo(command_id);
123 info->accel = accel;
124 if (!accel)
125 MaybeDeleteItemInfo(command_id, info);
126 }
127
128 private:
129 struct ItemInfo {
ItemInfocontext_menu::__anon140a525b0111::CefContextMenuObserver::ItemInfo130 ItemInfo() {}
131
132 bool checked = false;
133 absl::optional<ui::Accelerator> accel;
134 };
135
GetItemInfo(int command_id)136 ItemInfo* GetItemInfo(int command_id) {
137 auto it = iteminfomap_.find(command_id);
138 if (it != iteminfomap_.end()) {
139 return &it->second;
140 }
141 return nullptr;
142 }
143
GetOrCreateItemInfo(int command_id)144 ItemInfo* GetOrCreateItemInfo(int command_id) {
145 if (auto info = GetItemInfo(command_id))
146 return info;
147
148 auto result = iteminfomap_.insert(std::make_pair(command_id, ItemInfo()));
149 return &result.first->second;
150 }
151
MaybeDeleteItemInfo(int command_id,ItemInfo * info)152 void MaybeDeleteItemInfo(int command_id, ItemInfo* info) {
153 // Remove if all info has reverted to the default state.
154 if (!info->checked && !info->accel) {
155 auto it = iteminfomap_.find(command_id);
156 iteminfomap_.erase(it);
157 }
158 }
159
GetFrame() const160 CefRefPtr<CefFrame> GetFrame() const {
161 CefRefPtr<CefFrame> frame;
162
163 // May return nullptr if the frame is destroyed while the menu is pending.
164 auto* rfh = context_menu_->GetRenderFrameHost();
165 if (rfh) {
166 frame = browser_->GetFrameForHost(rfh);
167 }
168 if (!frame) {
169 frame = browser_->GetMainFrame();
170 }
171 return frame;
172 }
173
174 RenderViewContextMenu* const context_menu_;
175 CefRefPtr<CefBrowserHostBase> browser_;
176 CefRefPtr<CefContextMenuHandler> handler_;
177 CefRefPtr<CefContextMenuParams> params_;
178 CefRefPtr<CefSimpleMenuModelImpl> model_;
179
180 // Map of command_id to ItemInfo.
181 using ItemInfoMap = std::map<int, ItemInfo>;
182 ItemInfoMap iteminfomap_;
183 };
184
MenuCreatedCallback(RenderViewContextMenu * context_menu)185 std::unique_ptr<RenderViewContextMenuObserver> MenuCreatedCallback(
186 RenderViewContextMenu* context_menu) {
187 auto browser = CefBrowserHostBase::GetBrowserForContents(
188 context_menu->source_web_contents());
189 if (browser) {
190 if (auto client = browser->GetClient()) {
191 if (auto handler = client->GetContextMenuHandler()) {
192 return std::make_unique<CefContextMenuObserver>(context_menu, browser,
193 handler);
194 }
195 }
196 }
197
198 return nullptr;
199 }
200
201 } // namespace
202
RegisterMenuCreatedCallback()203 void RegisterMenuCreatedCallback() {
204 RenderViewContextMenu::RegisterMenuCreatedCallback(
205 base::BindRepeating(&MenuCreatedCallback));
206 }
207
208 } // namespace context_menu
209