mirror of
				https://bitbucket.org/chromiumembedded/cef
				synced 2025-06-05 21:39:12 +02:00 
			
		
		
		
	
		
			
				
	
	
		
			340 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			340 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| // Copyright (c) 2021 The Chromium Embedded Framework Authors. All rights
 | |
| // reserved. Use of this source code is governed by a BSD-style license that can
 | |
| // be found in the LICENSE file.
 | |
| 
 | |
| #include "cef/libcef/browser/chrome/chrome_context_menu_handler.h"
 | |
| 
 | |
| #include "base/memory/raw_ptr.h"
 | |
| #include "base/memory/weak_ptr.h"
 | |
| #include "cef/libcef/browser/alloy/alloy_browser_host_impl.h"
 | |
| #include "cef/libcef/browser/browser_host_base.h"
 | |
| #include "cef/libcef/browser/context_menu_params_impl.h"
 | |
| #include "cef/libcef/browser/simple_menu_model_impl.h"
 | |
| #include "chrome/browser/renderer_context_menu/render_view_context_menu.h"
 | |
| 
 | |
| namespace context_menu {
 | |
| 
 | |
| namespace {
 | |
| 
 | |
| constexpr int kInvalidCommandId = -1;
 | |
| const cef_event_flags_t kEmptyEventFlags = static_cast<cef_event_flags_t>(0);
 | |
| 
 | |
| class CefRunContextMenuCallbackImpl : public CefRunContextMenuCallback {
 | |
|  public:
 | |
|   using Callback =
 | |
|       base::OnceCallback<void(int /*command_id*/, int /*event_flags*/)>;
 | |
| 
 | |
|   explicit CefRunContextMenuCallbackImpl(Callback callback)
 | |
|       : callback_(std::move(callback)) {}
 | |
| 
 | |
|   CefRunContextMenuCallbackImpl(const CefRunContextMenuCallbackImpl&) = delete;
 | |
|   CefRunContextMenuCallbackImpl& operator=(
 | |
|       const CefRunContextMenuCallbackImpl&) = delete;
 | |
| 
 | |
|   ~CefRunContextMenuCallbackImpl() override {
 | |
|     if (!callback_.is_null()) {
 | |
|       // The callback is still pending. Cancel it now.
 | |
|       if (CEF_CURRENTLY_ON_UIT()) {
 | |
|         RunNow(std::move(callback_), kInvalidCommandId, kEmptyEventFlags);
 | |
|       } else {
 | |
|         CEF_POST_TASK(CEF_UIT,
 | |
|                       base::BindOnce(&CefRunContextMenuCallbackImpl::RunNow,
 | |
|                                      std::move(callback_), kInvalidCommandId,
 | |
|                                      kEmptyEventFlags));
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   void Continue(int command_id, cef_event_flags_t event_flags) override {
 | |
|     if (CEF_CURRENTLY_ON_UIT()) {
 | |
|       if (!callback_.is_null()) {
 | |
|         RunNow(std::move(callback_), command_id, event_flags);
 | |
|         callback_.Reset();
 | |
|       }
 | |
|     } else {
 | |
|       CEF_POST_TASK(CEF_UIT,
 | |
|                     base::BindOnce(&CefRunContextMenuCallbackImpl::Continue,
 | |
|                                    this, command_id, event_flags));
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   void Cancel() override { Continue(kInvalidCommandId, kEmptyEventFlags); }
 | |
| 
 | |
|   bool IsDisconnected() const { return callback_.is_null(); }
 | |
|   void Disconnect() { callback_.Reset(); }
 | |
| 
 | |
|  private:
 | |
|   static void RunNow(Callback callback, int command_id, int event_flags) {
 | |
|     CEF_REQUIRE_UIT();
 | |
|     std::move(callback).Run(command_id, event_flags);
 | |
|   }
 | |
| 
 | |
|   Callback callback_;
 | |
| 
 | |
|   IMPLEMENT_REFCOUNTING(CefRunContextMenuCallbackImpl);
 | |
| };
 | |
| 
 | |
| // Lifespan is controlled by RenderViewContextMenu.
 | |
| class CefContextMenuObserver : public RenderViewContextMenuObserver,
 | |
|                                public CefSimpleMenuModelImpl::StateDelegate {
 | |
|  public:
 | |
|   CefContextMenuObserver(RenderViewContextMenu* context_menu,
 | |
|                          CefRefPtr<CefBrowserHostBase> browser,
 | |
|                          CefRefPtr<CefContextMenuHandler> handler)
 | |
|       : context_menu_(context_menu), browser_(browser), handler_(handler) {
 | |
|     // This remains valid until the next time a context menu is created.
 | |
|     browser_->set_context_menu_observer(this);
 | |
|   }
 | |
| 
 | |
|   CefContextMenuObserver(const CefContextMenuObserver&) = delete;
 | |
|   CefContextMenuObserver& operator=(const CefContextMenuObserver&) = delete;
 | |
| 
 | |
|   // RenderViewContextMenuObserver methods:
 | |
| 
 | |
|   void InitMenu(const content::ContextMenuParams& params) override {
 | |
|     params_ = new CefContextMenuParamsImpl(
 | |
|         const_cast<content::ContextMenuParams*>(&context_menu_->params()));
 | |
|     model_ = new CefSimpleMenuModelImpl(
 | |
|         const_cast<ui::SimpleMenuModel*>(&context_menu_->menu_model()),
 | |
|         context_menu_, this, /*is_owned=*/false, /*is_submenu=*/false);
 | |
| 
 | |
|     handler_->OnBeforeContextMenu(browser_, GetFrame(), params_, model_);
 | |
|   }
 | |
| 
 | |
|   bool IsCommandIdSupported(int command_id) override {
 | |
|     // Always claim support for the reserved user ID range.
 | |
|     if (command_id >= MENU_ID_USER_FIRST && command_id <= MENU_ID_USER_LAST) {
 | |
|       return true;
 | |
|     }
 | |
| 
 | |
|     // Also claim support in specific cases where an ItemInfo exists.
 | |
|     return GetItemInfo(command_id) != nullptr;
 | |
|   }
 | |
| 
 | |
|   // Only called if IsCommandIdSupported() returns true.
 | |
|   bool IsCommandIdEnabled(int command_id) override {
 | |
|     // Always return true to use the SimpleMenuModel state.
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   // Only called if IsCommandIdSupported() returns true.
 | |
|   bool IsCommandIdChecked(int command_id) override {
 | |
|     auto* info = GetItemInfo(command_id);
 | |
|     return info ? info->checked : false;
 | |
|   }
 | |
| 
 | |
|   // Only called if IsCommandIdSupported() returns true.
 | |
|   bool GetAccelerator(int command_id, ui::Accelerator* accel) override {
 | |
|     auto* info = GetItemInfo(command_id);
 | |
|     if (info && info->accel) {
 | |
|       *accel = *info->accel;
 | |
|       return true;
 | |
|     }
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   void CommandWillBeExecuted(int command_id) override {
 | |
|     if (handler_->OnContextMenuCommand(browser_, GetFrame(), params_,
 | |
|                                        command_id, EVENTFLAG_NONE)) {
 | |
|       // Create an ItemInfo so that we get the ExecuteCommand() callback
 | |
|       // instead of the default handler.
 | |
|       GetOrCreateItemInfo(command_id);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Only called if IsCommandIdSupported() returns true.
 | |
|   void ExecuteCommand(int command_id) override {
 | |
|     auto* info = GetItemInfo(command_id);
 | |
|     if (info) {
 | |
|       // In case it was added in CommandWillBeExecuted().
 | |
|       MaybeDeleteItemInfo(command_id, info);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   void OnMenuClosed() override {
 | |
|     handler_->OnContextMenuDismissed(browser_, GetFrame());
 | |
|     model_->Detach();
 | |
| 
 | |
|     // Clear stored state because this object won't be deleted until a new
 | |
|     // context menu is created or the associated browser is destroyed.
 | |
|     browser_ = nullptr;
 | |
|     handler_ = nullptr;
 | |
|     params_ = nullptr;
 | |
|     model_ = nullptr;
 | |
|     iteminfomap_.clear();
 | |
|   }
 | |
| 
 | |
|   // CefSimpleMenuModelImpl::StateDelegate methods:
 | |
| 
 | |
|   void SetChecked(int command_id, bool checked) override {
 | |
|     // No-op if already at the default state.
 | |
|     if (!checked && !GetItemInfo(command_id)) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     auto* info = GetOrCreateItemInfo(command_id);
 | |
|     info->checked = checked;
 | |
|     if (!checked) {
 | |
|       MaybeDeleteItemInfo(command_id, info);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   void SetAccelerator(int command_id,
 | |
|                       std::optional<ui::Accelerator> accel) override {
 | |
|     // No-op if already at the default state.
 | |
|     if (!accel && !GetItemInfo(command_id)) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     auto* info = GetOrCreateItemInfo(command_id);
 | |
|     info->accel = accel;
 | |
|     if (!accel) {
 | |
|       MaybeDeleteItemInfo(command_id, info);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   bool HandleShow() {
 | |
|     if (model_->GetCount() == 0) {
 | |
|       return false;
 | |
|     }
 | |
| 
 | |
|     CefRefPtr<CefRunContextMenuCallbackImpl> callbackImpl(
 | |
|         new CefRunContextMenuCallbackImpl(
 | |
|             base::BindOnce(&CefContextMenuObserver::ExecuteCommandCallback,
 | |
|                            weak_ptr_factory_.GetWeakPtr())));
 | |
| 
 | |
|     bool handled = handler_->RunContextMenu(browser_, GetFrame(), params_,
 | |
|                                             model_, callbackImpl.get());
 | |
|     if (!handled && callbackImpl->IsDisconnected()) {
 | |
|       LOG(ERROR) << "Should return true from RunContextMenu when executing the "
 | |
|                     "callback";
 | |
|       handled = true;
 | |
|     }
 | |
|     if (!handled) {
 | |
|       callbackImpl->Disconnect();
 | |
|     }
 | |
|     return handled;
 | |
|   }
 | |
| 
 | |
|  private:
 | |
|   struct ItemInfo {
 | |
|     ItemInfo() = default;
 | |
| 
 | |
|     bool checked = false;
 | |
|     std::optional<ui::Accelerator> accel;
 | |
|   };
 | |
| 
 | |
|   ItemInfo* GetItemInfo(int command_id) {
 | |
|     auto it = iteminfomap_.find(command_id);
 | |
|     if (it != iteminfomap_.end()) {
 | |
|       return &it->second;
 | |
|     }
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   ItemInfo* GetOrCreateItemInfo(int command_id) {
 | |
|     if (auto info = GetItemInfo(command_id)) {
 | |
|       return info;
 | |
|     }
 | |
| 
 | |
|     auto result = iteminfomap_.insert(std::make_pair(command_id, ItemInfo()));
 | |
|     return &result.first->second;
 | |
|   }
 | |
| 
 | |
|   void MaybeDeleteItemInfo(int command_id, ItemInfo* info) {
 | |
|     // Remove if all info has reverted to the default state.
 | |
|     if (!info->checked && !info->accel) {
 | |
|       auto it = iteminfomap_.find(command_id);
 | |
|       iteminfomap_.erase(it);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   CefRefPtr<CefFrame> GetFrame() const {
 | |
|     CefRefPtr<CefFrame> frame;
 | |
| 
 | |
|     // May return nullptr if the frame is destroyed while the menu is pending.
 | |
|     auto* rfh = context_menu_->GetRenderFrameHost();
 | |
|     if (rfh) {
 | |
|       // May return nullptr for excluded views.
 | |
|       frame = browser_->GetFrameForHost(rfh);
 | |
|     }
 | |
|     if (!frame) {
 | |
|       frame = browser_->GetMainFrame();
 | |
|     }
 | |
|     return frame;
 | |
|   }
 | |
| 
 | |
|   void ExecuteCommandCallback(int command_id, int event_flags) {
 | |
|     if (command_id != kInvalidCommandId) {
 | |
|       context_menu_->ExecuteCommand(command_id, event_flags);
 | |
|     }
 | |
|     context_menu_->Cancel();
 | |
|     OnMenuClosed();
 | |
|   }
 | |
| 
 | |
|   const raw_ptr<RenderViewContextMenu> context_menu_;
 | |
|   CefRefPtr<CefBrowserHostBase> browser_;
 | |
|   CefRefPtr<CefContextMenuHandler> handler_;
 | |
|   CefRefPtr<CefContextMenuParams> params_;
 | |
|   CefRefPtr<CefSimpleMenuModelImpl> model_;
 | |
| 
 | |
|   // Map of command_id to ItemInfo.
 | |
|   using ItemInfoMap = std::map<int, ItemInfo>;
 | |
|   ItemInfoMap iteminfomap_;
 | |
| 
 | |
|   base::WeakPtrFactory<CefContextMenuObserver> weak_ptr_factory_{this};
 | |
| };
 | |
| 
 | |
| std::unique_ptr<RenderViewContextMenuObserver> MenuCreatedCallback(
 | |
|     RenderViewContextMenu* context_menu) {
 | |
|   auto browser = CefBrowserHostBase::GetBrowserForContents(
 | |
|       context_menu->source_web_contents());
 | |
|   if (browser) {
 | |
|     if (auto client = browser->GetClient()) {
 | |
|       if (auto handler = client->GetContextMenuHandler()) {
 | |
|         return std::make_unique<CefContextMenuObserver>(context_menu, browser,
 | |
|                                                         handler);
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     // Don't leave the old pointer, if any.
 | |
|     browser->set_context_menu_observer(nullptr);
 | |
|   }
 | |
| 
 | |
|   return nullptr;
 | |
| }
 | |
| 
 | |
| bool MenuShowHandlerCallback(RenderViewContextMenu* context_menu) {
 | |
|   auto browser = CefBrowserHostBase::GetBrowserForContents(
 | |
|       context_menu->source_web_contents());
 | |
|   if (browser && browser->context_menu_observer()) {
 | |
|     return static_cast<CefContextMenuObserver*>(
 | |
|                browser->context_menu_observer())
 | |
|         ->HandleShow();
 | |
|   }
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| }  // namespace
 | |
| 
 | |
| void RegisterCallbacks() {
 | |
|   RenderViewContextMenu::RegisterMenuCreatedCallback(
 | |
|       base::BindRepeating(&MenuCreatedCallback));
 | |
|   RenderViewContextMenu::RegisterMenuShowHandlerCallback(
 | |
|       base::BindRepeating(&MenuShowHandlerCallback));
 | |
| }
 | |
| 
 | |
| bool HandleContextMenu(content::WebContents* opener,
 | |
|                        const content::ContextMenuParams& params) {
 | |
|   auto browser = CefBrowserHostBase::GetBrowserForContents(opener);
 | |
|   if (browser && browser->IsAlloyStyle()) {
 | |
|     AlloyBrowserHostImpl::FromBaseChecked(browser)->ShowContextMenu(params);
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   // Continue with creating the RenderViewContextMenu.
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| }  // namespace context_menu
 |