mirror of
https://bitbucket.org/chromiumembedded/cef
synced 2024-12-11 09:08:06 +01:00
5065aba1b4
This is no longer required now that we have implicit exclusion of certain frame types including guest view frames. Rename GuestView to ExcludedView in the renderer process.
341 lines
11 KiB
C++
341 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 "libcef/browser/chrome/chrome_context_menu_handler.h"
|
|
|
|
#include "base/memory/weak_ptr.h"
|
|
|
|
#include "libcef/browser/alloy/alloy_browser_host_impl.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 {
|
|
|
|
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();
|
|
}
|
|
|
|
RenderViewContextMenu* const 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
|