• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2012 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/menu_manager.h"
6 
7 #include <tuple>
8 #include <utility>
9 
10 #include "libcef/browser/alloy/alloy_browser_host_impl.h"
11 #include "libcef/browser/context_menu_params_impl.h"
12 #include "libcef/browser/menu_runner.h"
13 #include "libcef/browser/thread_util.h"
14 #include "libcef/common/app_manager.h"
15 
16 #include "base/compiler_specific.h"
17 #include "base/logging.h"
18 #include "cef/grit/cef_strings.h"
19 #include "chrome/grit/generated_resources.h"
20 #include "content/public/browser/render_frame_host.h"
21 #include "content/public/browser/render_process_host.h"
22 #include "content/public/browser/render_widget_host_view.h"
23 #include "third_party/blink/public/mojom/context_menu/context_menu.mojom.h"
24 
25 namespace {
26 
GetLabel(int message_id)27 CefString GetLabel(int message_id) {
28   std::u16string label =
29       CefAppManager::Get()->GetContentClient()->GetLocalizedString(message_id);
30   DCHECK(!label.empty());
31   return label;
32 }
33 
34 const int kInvalidCommandId = -1;
35 const cef_event_flags_t kEmptyEventFlags = static_cast<cef_event_flags_t>(0);
36 
37 class CefRunContextMenuCallbackImpl : public CefRunContextMenuCallback {
38  public:
39   using Callback = base::OnceCallback<void(int, cef_event_flags_t)>;
40 
CefRunContextMenuCallbackImpl(Callback callback)41   explicit CefRunContextMenuCallbackImpl(Callback callback)
42       : callback_(std::move(callback)) {}
43 
44   CefRunContextMenuCallbackImpl(const CefRunContextMenuCallbackImpl&) = delete;
45   CefRunContextMenuCallbackImpl& operator=(
46       const CefRunContextMenuCallbackImpl&) = delete;
47 
~CefRunContextMenuCallbackImpl()48   ~CefRunContextMenuCallbackImpl() {
49     if (!callback_.is_null()) {
50       // The callback is still pending. Cancel it now.
51       if (CEF_CURRENTLY_ON_UIT()) {
52         RunNow(std::move(callback_), kInvalidCommandId, kEmptyEventFlags);
53       } else {
54         CEF_POST_TASK(CEF_UIT,
55                       base::BindOnce(&CefRunContextMenuCallbackImpl::RunNow,
56                                      std::move(callback_), kInvalidCommandId,
57                                      kEmptyEventFlags));
58       }
59     }
60   }
61 
Continue(int command_id,cef_event_flags_t event_flags)62   void Continue(int command_id, cef_event_flags_t event_flags) override {
63     if (CEF_CURRENTLY_ON_UIT()) {
64       if (!callback_.is_null()) {
65         RunNow(std::move(callback_), command_id, event_flags);
66         callback_.Reset();
67       }
68     } else {
69       CEF_POST_TASK(CEF_UIT,
70                     base::BindOnce(&CefRunContextMenuCallbackImpl::Continue,
71                                    this, command_id, event_flags));
72     }
73   }
74 
Cancel()75   void Cancel() override { Continue(kInvalidCommandId, kEmptyEventFlags); }
76 
Disconnect()77   void Disconnect() { callback_.Reset(); }
78 
79  private:
RunNow(Callback callback,int command_id,cef_event_flags_t event_flags)80   static void RunNow(Callback callback,
81                      int command_id,
82                      cef_event_flags_t event_flags) {
83     CEF_REQUIRE_UIT();
84     std::move(callback).Run(command_id, event_flags);
85   }
86 
87   Callback callback_;
88 
89   IMPLEMENT_REFCOUNTING(CefRunContextMenuCallbackImpl);
90 };
91 
92 }  // namespace
93 
CefMenuManager(AlloyBrowserHostImpl * browser,std::unique_ptr<CefMenuRunner> runner)94 CefMenuManager::CefMenuManager(AlloyBrowserHostImpl* browser,
95                                std::unique_ptr<CefMenuRunner> runner)
96     : content::WebContentsObserver(browser->web_contents()),
97       browser_(browser),
98       runner_(std::move(runner)),
99       custom_menu_callback_(nullptr),
100       weak_ptr_factory_(this) {
101   DCHECK(web_contents());
102   model_ = new CefMenuModelImpl(this, nullptr, false);
103 }
104 
~CefMenuManager()105 CefMenuManager::~CefMenuManager() {
106   // The model may outlive the delegate if the context menu is visible when the
107   // application is closed.
108   model_->set_delegate(nullptr);
109 }
110 
Destroy()111 void CefMenuManager::Destroy() {
112   CancelContextMenu();
113   if (runner_)
114     runner_.reset(nullptr);
115 }
116 
IsShowingContextMenu()117 bool CefMenuManager::IsShowingContextMenu() {
118   if (!web_contents())
119     return false;
120   return web_contents()->IsShowingContextMenu();
121 }
122 
CreateContextMenu(const content::ContextMenuParams & params)123 bool CefMenuManager::CreateContextMenu(
124     const content::ContextMenuParams& params) {
125   // The renderer may send the "show context menu" message multiple times, one
126   // for each right click mouse event it receives. Normally, this doesn't happen
127   // because mouse events are not forwarded once the context menu is showing.
128   // However, there's a race - the context menu may not yet be showing when
129   // the second mouse event arrives. In this case, |HandleContextMenu()| will
130   // get called multiple times - if so, don't create another context menu.
131   // TODO(asvitkine): Fix the renderer so that it doesn't do this.
132   if (IsShowingContextMenu())
133     return true;
134 
135   params_ = params;
136   model_->Clear();
137 
138   // Create the default menu model.
139   CreateDefaultModel();
140 
141   bool custom_menu = false;
142   DCHECK(!custom_menu_callback_);
143 
144   // Give the client a chance to modify the model.
145   CefRefPtr<CefClient> client = browser_->GetClient();
146   if (client.get()) {
147     CefRefPtr<CefContextMenuHandler> handler = client->GetContextMenuHandler();
148     if (handler.get()) {
149       CefRefPtr<CefContextMenuParamsImpl> paramsPtr(
150           new CefContextMenuParamsImpl(&params_));
151       CefRefPtr<CefFrame> frame = browser_->GetFocusedFrame();
152 
153       handler->OnBeforeContextMenu(browser_, frame, paramsPtr.get(),
154                                    model_.get());
155 
156       MenuWillShow(model_);
157 
158       if (model_->GetCount() > 0) {
159         CefRefPtr<CefRunContextMenuCallbackImpl> callbackImpl(
160             new CefRunContextMenuCallbackImpl(
161                 base::BindOnce(&CefMenuManager::ExecuteCommandCallback,
162                                weak_ptr_factory_.GetWeakPtr())));
163 
164         // This reference will be cleared when the callback is executed or
165         // the callback object is deleted.
166         custom_menu_callback_ = callbackImpl.get();
167 
168         if (handler->RunContextMenu(browser_, frame, paramsPtr.get(),
169                                     model_.get(), callbackImpl.get())) {
170           custom_menu = true;
171         } else {
172           // Callback should not be executed if the handler returns false.
173           DCHECK(custom_menu_callback_);
174           custom_menu_callback_ = nullptr;
175           callbackImpl->Disconnect();
176         }
177       }
178 
179       // Do not keep references to the parameters in the callback.
180       std::ignore = paramsPtr->Detach(nullptr);
181       DCHECK(paramsPtr->HasOneRef());
182       DCHECK(model_->VerifyRefCount());
183 
184       // Menu is empty so notify the client and return.
185       if (model_->GetCount() == 0 && !custom_menu) {
186         MenuClosed(model_);
187         return true;
188       }
189     }
190   }
191 
192   if (custom_menu || !runner_)
193     return true;
194   return runner_->RunContextMenu(browser_, model_.get(), params_);
195 }
196 
CancelContextMenu()197 void CefMenuManager::CancelContextMenu() {
198   if (IsShowingContextMenu()) {
199     if (custom_menu_callback_)
200       custom_menu_callback_->Cancel();
201     else if (runner_)
202       runner_->CancelContextMenu();
203   }
204 }
205 
ExecuteCommand(CefRefPtr<CefMenuModelImpl> source,int command_id,cef_event_flags_t event_flags)206 void CefMenuManager::ExecuteCommand(CefRefPtr<CefMenuModelImpl> source,
207                                     int command_id,
208                                     cef_event_flags_t event_flags) {
209   // Give the client a chance to handle the command.
210   CefRefPtr<CefClient> client = browser_->GetClient();
211   if (client.get()) {
212     CefRefPtr<CefContextMenuHandler> handler = client->GetContextMenuHandler();
213     if (handler.get()) {
214       CefRefPtr<CefContextMenuParamsImpl> paramsPtr(
215           new CefContextMenuParamsImpl(&params_));
216 
217       bool handled = handler->OnContextMenuCommand(
218           browser_, browser_->GetFocusedFrame(), paramsPtr.get(), command_id,
219           event_flags);
220 
221       // Do not keep references to the parameters in the callback.
222       std::ignore = paramsPtr->Detach(nullptr);
223       DCHECK(paramsPtr->HasOneRef());
224 
225       if (handled)
226         return;
227     }
228   }
229 
230   // Execute the default command handling.
231   ExecuteDefaultCommand(command_id);
232 }
233 
MenuWillShow(CefRefPtr<CefMenuModelImpl> source)234 void CefMenuManager::MenuWillShow(CefRefPtr<CefMenuModelImpl> source) {
235   // May be called for sub-menus as well.
236   if (source.get() != model_.get())
237     return;
238 
239   if (!web_contents())
240     return;
241 
242   // May be called multiple times.
243   if (IsShowingContextMenu())
244     return;
245 
246   // Notify the host before showing the context menu.
247   web_contents()->SetShowingContextMenu(true);
248 }
249 
MenuClosed(CefRefPtr<CefMenuModelImpl> source)250 void CefMenuManager::MenuClosed(CefRefPtr<CefMenuModelImpl> source) {
251   // May be called for sub-menus as well.
252   if (source.get() != model_.get())
253     return;
254 
255   if (!web_contents())
256     return;
257 
258   DCHECK(IsShowingContextMenu());
259 
260   // Notify the client.
261   CefRefPtr<CefClient> client = browser_->GetClient();
262   if (client.get()) {
263     CefRefPtr<CefContextMenuHandler> handler = client->GetContextMenuHandler();
264     if (handler.get()) {
265       handler->OnContextMenuDismissed(browser_, browser_->GetFocusedFrame());
266     }
267   }
268 
269   // Notify the host after closing the context menu.
270   web_contents()->SetShowingContextMenu(false);
271   web_contents()->NotifyContextMenuClosed(params_.link_followed);
272 }
273 
FormatLabel(CefRefPtr<CefMenuModelImpl> source,std::u16string & label)274 bool CefMenuManager::FormatLabel(CefRefPtr<CefMenuModelImpl> source,
275                                  std::u16string& label) {
276   if (!runner_)
277     return false;
278   return runner_->FormatLabel(label);
279 }
280 
ExecuteCommandCallback(int command_id,cef_event_flags_t event_flags)281 void CefMenuManager::ExecuteCommandCallback(int command_id,
282                                             cef_event_flags_t event_flags) {
283   DCHECK(IsShowingContextMenu());
284   DCHECK(custom_menu_callback_);
285   if (command_id != kInvalidCommandId)
286     ExecuteCommand(model_, command_id, event_flags);
287   MenuClosed(model_);
288   custom_menu_callback_ = nullptr;
289 }
290 
CreateDefaultModel()291 void CefMenuManager::CreateDefaultModel() {
292   if (!params_.custom_items.empty()) {
293     // Custom menu items originating from the renderer process. For example,
294     // plugin placeholder menu items.
295     for (auto& item : params_.custom_items) {
296       auto new_item = item->Clone();
297       new_item->action += MENU_ID_CUSTOM_FIRST;
298       DCHECK_LE(static_cast<int>(new_item->action), MENU_ID_CUSTOM_LAST);
299       model_->AddMenuItem(*new_item);
300     }
301     return;
302   }
303 
304   if (params_.is_editable) {
305     // Editable node.
306     model_->AddItem(MENU_ID_UNDO, GetLabel(IDS_CONTENT_CONTEXT_UNDO));
307     model_->AddItem(MENU_ID_REDO, GetLabel(IDS_CONTENT_CONTEXT_REDO));
308 
309     model_->AddSeparator();
310     model_->AddItem(MENU_ID_CUT, GetLabel(IDS_CONTENT_CONTEXT_CUT));
311     model_->AddItem(MENU_ID_COPY, GetLabel(IDS_CONTENT_CONTEXT_COPY));
312     model_->AddItem(MENU_ID_PASTE, GetLabel(IDS_CONTENT_CONTEXT_PASTE));
313 
314     model_->AddSeparator();
315     model_->AddItem(MENU_ID_SELECT_ALL,
316                     GetLabel(IDS_CONTENT_CONTEXT_SELECTALL));
317 
318     if (!(params_.edit_flags & CM_EDITFLAG_CAN_UNDO))
319       model_->SetEnabled(MENU_ID_UNDO, false);
320     if (!(params_.edit_flags & CM_EDITFLAG_CAN_REDO))
321       model_->SetEnabled(MENU_ID_REDO, false);
322     if (!(params_.edit_flags & CM_EDITFLAG_CAN_CUT))
323       model_->SetEnabled(MENU_ID_CUT, false);
324     if (!(params_.edit_flags & CM_EDITFLAG_CAN_COPY))
325       model_->SetEnabled(MENU_ID_COPY, false);
326     if (!(params_.edit_flags & CM_EDITFLAG_CAN_PASTE))
327       model_->SetEnabled(MENU_ID_PASTE, false);
328     if (!(params_.edit_flags & CM_EDITFLAG_CAN_DELETE))
329       model_->SetEnabled(MENU_ID_DELETE, false);
330     if (!(params_.edit_flags & CM_EDITFLAG_CAN_SELECT_ALL))
331       model_->SetEnabled(MENU_ID_SELECT_ALL, false);
332 
333     if (!params_.misspelled_word.empty()) {
334       // Always add a separator before the list of dictionary suggestions or
335       // "No spelling suggestions".
336       model_->AddSeparator();
337 
338       if (!params_.dictionary_suggestions.empty()) {
339         for (size_t i = 0; i < params_.dictionary_suggestions.size() &&
340                            MENU_ID_SPELLCHECK_SUGGESTION_0 + i <=
341                                MENU_ID_SPELLCHECK_SUGGESTION_LAST;
342              ++i) {
343           model_->AddItem(MENU_ID_SPELLCHECK_SUGGESTION_0 + static_cast<int>(i),
344                           params_.dictionary_suggestions[i]);
345         }
346 
347         // When there are dictionary suggestions add a separator before "Add to
348         // dictionary".
349         model_->AddSeparator();
350       } else {
351         model_->AddItem(MENU_ID_NO_SPELLING_SUGGESTIONS,
352                         GetLabel(IDS_CONTENT_CONTEXT_NO_SPELLING_SUGGESTIONS));
353         model_->SetEnabled(MENU_ID_NO_SPELLING_SUGGESTIONS, false);
354       }
355 
356       model_->AddItem(MENU_ID_ADD_TO_DICTIONARY,
357                       GetLabel(IDS_CONTENT_CONTEXT_ADD_TO_DICTIONARY));
358     }
359   } else if (!params_.selection_text.empty()) {
360     // Something is selected.
361     model_->AddItem(MENU_ID_COPY, GetLabel(IDS_CONTENT_CONTEXT_COPY));
362   } else if (!params_.page_url.is_empty() || !params_.frame_url.is_empty()) {
363     // Page or frame.
364     model_->AddItem(MENU_ID_BACK, GetLabel(IDS_CONTENT_CONTEXT_BACK));
365     model_->AddItem(MENU_ID_FORWARD, GetLabel(IDS_CONTENT_CONTEXT_FORWARD));
366 
367     model_->AddSeparator();
368     model_->AddItem(MENU_ID_PRINT, GetLabel(IDS_CONTENT_CONTEXT_PRINT));
369     model_->AddItem(MENU_ID_VIEW_SOURCE,
370                     GetLabel(IDS_CONTENT_CONTEXT_VIEWPAGESOURCE));
371 
372     if (!browser_->CanGoBack())
373       model_->SetEnabled(MENU_ID_BACK, false);
374     if (!browser_->CanGoForward())
375       model_->SetEnabled(MENU_ID_FORWARD, false);
376   }
377 }
378 
ExecuteDefaultCommand(int command_id)379 void CefMenuManager::ExecuteDefaultCommand(int command_id) {
380   if (IsCustomContextMenuCommand(command_id)) {
381     if (web_contents()) {
382       web_contents()->ExecuteCustomContextMenuCommand(
383           command_id - MENU_ID_CUSTOM_FIRST, params_.link_followed);
384     }
385     return;
386   }
387 
388   // If the user chose a replacement word for a misspelling, replace it here.
389   if (command_id >= MENU_ID_SPELLCHECK_SUGGESTION_0 &&
390       command_id <= MENU_ID_SPELLCHECK_SUGGESTION_LAST) {
391     const size_t suggestion_index =
392         static_cast<size_t>(command_id) - MENU_ID_SPELLCHECK_SUGGESTION_0;
393     if (suggestion_index < params_.dictionary_suggestions.size()) {
394       browser_->ReplaceMisspelling(
395           params_.dictionary_suggestions[suggestion_index]);
396     }
397     return;
398   }
399 
400   switch (command_id) {
401     // Navigation.
402     case MENU_ID_BACK:
403       browser_->GoBack();
404       break;
405     case MENU_ID_FORWARD:
406       browser_->GoForward();
407       break;
408     case MENU_ID_RELOAD:
409       browser_->Reload();
410       break;
411     case MENU_ID_RELOAD_NOCACHE:
412       browser_->ReloadIgnoreCache();
413       break;
414     case MENU_ID_STOPLOAD:
415       browser_->StopLoad();
416       break;
417 
418     // Editing.
419     case MENU_ID_UNDO:
420       browser_->GetFocusedFrame()->Undo();
421       break;
422     case MENU_ID_REDO:
423       browser_->GetFocusedFrame()->Redo();
424       break;
425     case MENU_ID_CUT:
426       browser_->GetFocusedFrame()->Cut();
427       break;
428     case MENU_ID_COPY:
429       browser_->GetFocusedFrame()->Copy();
430       break;
431     case MENU_ID_PASTE:
432       browser_->GetFocusedFrame()->Paste();
433       break;
434     case MENU_ID_DELETE:
435       browser_->GetFocusedFrame()->Delete();
436       break;
437     case MENU_ID_SELECT_ALL:
438       browser_->GetFocusedFrame()->SelectAll();
439       break;
440 
441     // Miscellaneous.
442     case MENU_ID_FIND:
443       // TODO(cef): Implement.
444       NOTIMPLEMENTED();
445       break;
446     case MENU_ID_PRINT:
447       browser_->Print();
448       break;
449     case MENU_ID_VIEW_SOURCE:
450       browser_->GetFocusedFrame()->ViewSource();
451       break;
452 
453     // Spell checking.
454     case MENU_ID_ADD_TO_DICTIONARY:
455       browser_->GetHost()->AddWordToDictionary(params_.misspelled_word);
456       break;
457 
458     default:
459       break;
460   }
461 }
462 
IsCustomContextMenuCommand(int command_id)463 bool CefMenuManager::IsCustomContextMenuCommand(int command_id) {
464   // Verify that the command ID is in the correct range.
465   if (command_id < MENU_ID_CUSTOM_FIRST || command_id > MENU_ID_CUSTOM_LAST)
466     return false;
467 
468   command_id -= MENU_ID_CUSTOM_FIRST;
469 
470   // Verify that the specific command ID was passed from the renderer process.
471   if (!params_.custom_items.empty()) {
472     for (size_t i = 0; i < params_.custom_items.size(); ++i) {
473       if (static_cast<int>(params_.custom_items[i]->action) == command_id)
474         return true;
475     }
476   }
477   return false;
478 }
479