// 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 "libcef/browser/chrome/chrome_context_menu_handler.h" #include "libcef/browser/browser_host_base.h" #include "libcef/browser/context_menu_params_impl.h" #include "libcef/browser/simple_menu_model_impl.h" #include "chrome/browser/renderer_context_menu/render_view_context_menu.h" namespace context_menu { namespace { // Lifespan is controlled by RenderViewContextMenu. class CefContextMenuObserver : public RenderViewContextMenuObserver, public CefSimpleMenuModelImpl::StateDelegate { public: CefContextMenuObserver(RenderViewContextMenu* context_menu, CefRefPtr browser, CefRefPtr handler) : context_menu_(context_menu), browser_(browser), handler_(handler) {} CefContextMenuObserver(const CefContextMenuObserver&) = delete; CefContextMenuObserver& operator=(const CefContextMenuObserver&) = delete; // RenderViewContextMenuObserver methods: void InitMenu(const content::ContextMenuParams& params) override { params_ = new CefContextMenuParamsImpl( const_cast(&context_menu_->params())); model_ = new CefSimpleMenuModelImpl( const_cast(&context_menu_->menu_model()), context_menu_, this, /*is_owned=*/false, /*is_popup=*/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, absl::optional 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); } } private: struct ItemInfo { ItemInfo() {} bool checked = false; absl::optional 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 GetFrame() const { CefRefPtr frame; // May return nullptr if the frame is destroyed while the menu is pending. auto* rfh = context_menu_->GetRenderFrameHost(); if (rfh) { frame = browser_->GetFrameForHost(rfh); } if (!frame) { frame = browser_->GetMainFrame(); } return frame; } RenderViewContextMenu* const context_menu_; CefRefPtr browser_; CefRefPtr handler_; CefRefPtr params_; CefRefPtr model_; // Map of command_id to ItemInfo. using ItemInfoMap = std::map; ItemInfoMap iteminfomap_; }; std::unique_ptr 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(context_menu, browser, handler); } } } return nullptr; } } // namespace void RegisterMenuCreatedCallback() { RenderViewContextMenu::RegisterMenuCreatedCallback( base::BindRepeating(&MenuCreatedCallback)); } } // namespace context_menu