cef/libcef/browser/menu_manager.cc
Marshall Greenblatt b3a8da9b25 Add CefAppManager and remove global ContentClient accessors (see issue #2969)
This is the first pass in removing direct dependencies on the Alloy
runtime from code that can potentially be shared between runtimes.

CefBrowserHost and CefRequestContext APIs (including CefCookieManager,
CefURLRequest, etc.) are not yet implemented for the Chrome runtime.
Assert early if these API methods are called while the Chrome runtime
is enabled.
2020-06-29 16:17:58 -04:00

474 lines
15 KiB
C++

// Copyright (c) 2012 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/menu_manager.h"
#include <utility>
#include "libcef/browser/browser_host_impl.h"
#include "libcef/browser/context_menu_params_impl.h"
#include "libcef/browser/menu_runner.h"
#include "libcef/browser/thread_util.h"
#include "libcef/common/app_manager.h"
#include "base/compiler_specific.h"
#include "base/logging.h"
#include "cef/grit/cef_strings.h"
#include "chrome/grit/generated_resources.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_widget_host_view.h"
namespace {
CefString GetLabel(int message_id) {
base::string16 label =
CefAppManager::Get()->GetContentClient()->GetLocalizedString(message_id);
DCHECK(!label.empty());
return label;
}
const int kInvalidCommandId = -1;
const cef_event_flags_t kEmptyEventFlags = static_cast<cef_event_flags_t>(0);
class CefRunContextMenuCallbackImpl : public CefRunContextMenuCallback {
public:
typedef base::Callback<void(int, cef_event_flags_t)> Callback;
explicit CefRunContextMenuCallbackImpl(const Callback& callback)
: callback_(callback) {}
~CefRunContextMenuCallbackImpl() {
if (!callback_.is_null()) {
// The callback is still pending. Cancel it now.
if (CEF_CURRENTLY_ON_UIT()) {
RunNow(callback_, kInvalidCommandId, kEmptyEventFlags);
} else {
CEF_POST_TASK(
CEF_UIT,
base::Bind(&CefRunContextMenuCallbackImpl::RunNow, 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(callback_, command_id, event_flags);
callback_.Reset();
}
} else {
CEF_POST_TASK(CEF_UIT,
base::Bind(&CefRunContextMenuCallbackImpl::Continue, this,
command_id, event_flags));
}
}
void Cancel() override { Continue(kInvalidCommandId, kEmptyEventFlags); }
void Disconnect() { callback_.Reset(); }
private:
static void RunNow(const Callback& callback,
int command_id,
cef_event_flags_t event_flags) {
CEF_REQUIRE_UIT();
callback.Run(command_id, event_flags);
}
Callback callback_;
IMPLEMENT_REFCOUNTING(CefRunContextMenuCallbackImpl);
DISALLOW_COPY_AND_ASSIGN(CefRunContextMenuCallbackImpl);
};
} // namespace
CefMenuManager::CefMenuManager(CefBrowserHostImpl* browser,
std::unique_ptr<CefMenuRunner> runner)
: content::WebContentsObserver(browser->web_contents()),
browser_(browser),
runner_(std::move(runner)),
custom_menu_callback_(nullptr),
weak_ptr_factory_(this) {
DCHECK(web_contents());
model_ = new CefMenuModelImpl(this, nullptr, false);
}
CefMenuManager::~CefMenuManager() {
// The model may outlive the delegate if the context menu is visible when the
// application is closed.
model_->set_delegate(nullptr);
}
void CefMenuManager::Destroy() {
CancelContextMenu();
if (runner_)
runner_.reset(nullptr);
}
bool CefMenuManager::IsShowingContextMenu() {
if (!web_contents())
return false;
return web_contents()->IsShowingContextMenu();
}
bool CefMenuManager::CreateContextMenu(
const content::ContextMenuParams& params) {
// The renderer may send the "show context menu" message multiple times, one
// for each right click mouse event it receives. Normally, this doesn't happen
// because mouse events are not forwarded once the context menu is showing.
// However, there's a race - the context menu may not yet be showing when
// the second mouse event arrives. In this case, |HandleContextMenu()| will
// get called multiple times - if so, don't create another context menu.
// TODO(asvitkine): Fix the renderer so that it doesn't do this.
if (IsShowingContextMenu())
return true;
params_ = params;
model_->Clear();
// Create the default menu model.
CreateDefaultModel();
bool custom_menu = false;
DCHECK(!custom_menu_callback_);
// Give the client a chance to modify the model.
CefRefPtr<CefClient> client = browser_->GetClient();
if (client.get()) {
CefRefPtr<CefContextMenuHandler> handler = client->GetContextMenuHandler();
if (handler.get()) {
CefRefPtr<CefContextMenuParamsImpl> paramsPtr(
new CefContextMenuParamsImpl(&params_));
CefRefPtr<CefFrame> frame = browser_->GetFocusedFrame();
handler->OnBeforeContextMenu(browser_, frame, paramsPtr.get(),
model_.get());
MenuWillShow(model_);
if (model_->GetCount() > 0) {
CefRefPtr<CefRunContextMenuCallbackImpl> callbackImpl(
new CefRunContextMenuCallbackImpl(
base::Bind(&CefMenuManager::ExecuteCommandCallback,
weak_ptr_factory_.GetWeakPtr())));
// This reference will be cleared when the callback is executed or
// the callback object is deleted.
custom_menu_callback_ = callbackImpl.get();
if (handler->RunContextMenu(browser_, frame, paramsPtr.get(),
model_.get(), callbackImpl.get())) {
custom_menu = true;
} else {
// Callback should not be executed if the handler returns false.
DCHECK(custom_menu_callback_);
custom_menu_callback_ = nullptr;
callbackImpl->Disconnect();
}
}
// Do not keep references to the parameters in the callback.
paramsPtr->Detach(nullptr);
DCHECK(paramsPtr->HasOneRef());
DCHECK(model_->VerifyRefCount());
// Menu is empty so notify the client and return.
if (model_->GetCount() == 0 && !custom_menu) {
MenuClosed(model_);
return true;
}
}
}
if (custom_menu || !runner_)
return true;
return runner_->RunContextMenu(browser_, model_.get(), params_);
}
void CefMenuManager::CancelContextMenu() {
if (IsShowingContextMenu()) {
if (custom_menu_callback_)
custom_menu_callback_->Cancel();
else if (runner_)
runner_->CancelContextMenu();
}
}
void CefMenuManager::ExecuteCommand(CefRefPtr<CefMenuModelImpl> source,
int command_id,
cef_event_flags_t event_flags) {
// Give the client a chance to handle the command.
CefRefPtr<CefClient> client = browser_->GetClient();
if (client.get()) {
CefRefPtr<CefContextMenuHandler> handler = client->GetContextMenuHandler();
if (handler.get()) {
CefRefPtr<CefContextMenuParamsImpl> paramsPtr(
new CefContextMenuParamsImpl(&params_));
bool handled = handler->OnContextMenuCommand(
browser_, browser_->GetFocusedFrame(), paramsPtr.get(), command_id,
event_flags);
// Do not keep references to the parameters in the callback.
paramsPtr->Detach(nullptr);
DCHECK(paramsPtr->HasOneRef());
if (handled)
return;
}
}
// Execute the default command handling.
ExecuteDefaultCommand(command_id);
}
void CefMenuManager::MenuWillShow(CefRefPtr<CefMenuModelImpl> source) {
// May be called for sub-menus as well.
if (source.get() != model_.get())
return;
if (!web_contents())
return;
// May be called multiple times.
if (IsShowingContextMenu())
return;
// Notify the host before showing the context menu.
web_contents()->SetShowingContextMenu(true);
}
void CefMenuManager::MenuClosed(CefRefPtr<CefMenuModelImpl> source) {
// May be called for sub-menus as well.
if (source.get() != model_.get())
return;
if (!web_contents())
return;
DCHECK(IsShowingContextMenu());
// Notify the client.
CefRefPtr<CefClient> client = browser_->GetClient();
if (client.get()) {
CefRefPtr<CefContextMenuHandler> handler = client->GetContextMenuHandler();
if (handler.get()) {
handler->OnContextMenuDismissed(browser_, browser_->GetFocusedFrame());
}
}
// Notify the host after closing the context menu.
web_contents()->SetShowingContextMenu(false);
web_contents()->NotifyContextMenuClosed(params_.custom_context);
}
bool CefMenuManager::FormatLabel(CefRefPtr<CefMenuModelImpl> source,
base::string16& label) {
if (!runner_)
return false;
return runner_->FormatLabel(label);
}
void CefMenuManager::ExecuteCommandCallback(int command_id,
cef_event_flags_t event_flags) {
DCHECK(IsShowingContextMenu());
DCHECK(custom_menu_callback_);
if (command_id != kInvalidCommandId)
ExecuteCommand(model_, command_id, event_flags);
MenuClosed(model_);
custom_menu_callback_ = nullptr;
}
void CefMenuManager::CreateDefaultModel() {
if (!params_.custom_items.empty()) {
// Custom menu items originating from the renderer process. For example,
// plugin placeholder menu items or Flash menu items.
for (size_t i = 0; i < params_.custom_items.size(); ++i) {
content::MenuItem menu_item = params_.custom_items[i];
menu_item.action += MENU_ID_CUSTOM_FIRST;
DCHECK_LE(static_cast<int>(menu_item.action), MENU_ID_CUSTOM_LAST);
model_->AddMenuItem(menu_item);
}
return;
}
if (params_.is_editable) {
// Editable node.
model_->AddItem(MENU_ID_UNDO, GetLabel(IDS_CONTENT_CONTEXT_UNDO));
model_->AddItem(MENU_ID_REDO, GetLabel(IDS_CONTENT_CONTEXT_REDO));
model_->AddSeparator();
model_->AddItem(MENU_ID_CUT, GetLabel(IDS_CONTENT_CONTEXT_CUT));
model_->AddItem(MENU_ID_COPY, GetLabel(IDS_CONTENT_CONTEXT_COPY));
model_->AddItem(MENU_ID_PASTE, GetLabel(IDS_CONTENT_CONTEXT_PASTE));
model_->AddSeparator();
model_->AddItem(MENU_ID_SELECT_ALL,
GetLabel(IDS_CONTENT_CONTEXT_SELECTALL));
if (!(params_.edit_flags & CM_EDITFLAG_CAN_UNDO))
model_->SetEnabled(MENU_ID_UNDO, false);
if (!(params_.edit_flags & CM_EDITFLAG_CAN_REDO))
model_->SetEnabled(MENU_ID_REDO, false);
if (!(params_.edit_flags & CM_EDITFLAG_CAN_CUT))
model_->SetEnabled(MENU_ID_CUT, false);
if (!(params_.edit_flags & CM_EDITFLAG_CAN_COPY))
model_->SetEnabled(MENU_ID_COPY, false);
if (!(params_.edit_flags & CM_EDITFLAG_CAN_PASTE))
model_->SetEnabled(MENU_ID_PASTE, false);
if (!(params_.edit_flags & CM_EDITFLAG_CAN_DELETE))
model_->SetEnabled(MENU_ID_DELETE, false);
if (!(params_.edit_flags & CM_EDITFLAG_CAN_SELECT_ALL))
model_->SetEnabled(MENU_ID_SELECT_ALL, false);
if (!params_.misspelled_word.empty()) {
// Always add a separator before the list of dictionary suggestions or
// "No spelling suggestions".
model_->AddSeparator();
if (!params_.dictionary_suggestions.empty()) {
for (size_t i = 0; i < params_.dictionary_suggestions.size() &&
MENU_ID_SPELLCHECK_SUGGESTION_0 + i <=
MENU_ID_SPELLCHECK_SUGGESTION_LAST;
++i) {
model_->AddItem(MENU_ID_SPELLCHECK_SUGGESTION_0 + static_cast<int>(i),
params_.dictionary_suggestions[i].c_str());
}
// When there are dictionary suggestions add a separator before "Add to
// dictionary".
model_->AddSeparator();
} else {
model_->AddItem(MENU_ID_NO_SPELLING_SUGGESTIONS,
GetLabel(IDS_CONTENT_CONTEXT_NO_SPELLING_SUGGESTIONS));
model_->SetEnabled(MENU_ID_NO_SPELLING_SUGGESTIONS, false);
}
model_->AddItem(MENU_ID_ADD_TO_DICTIONARY,
GetLabel(IDS_CONTENT_CONTEXT_ADD_TO_DICTIONARY));
}
} else if (!params_.selection_text.empty()) {
// Something is selected.
model_->AddItem(MENU_ID_COPY, GetLabel(IDS_CONTENT_CONTEXT_COPY));
} else if (!params_.page_url.is_empty() || !params_.frame_url.is_empty()) {
// Page or frame.
model_->AddItem(MENU_ID_BACK, GetLabel(IDS_CONTENT_CONTEXT_BACK));
model_->AddItem(MENU_ID_FORWARD, GetLabel(IDS_CONTENT_CONTEXT_FORWARD));
model_->AddSeparator();
model_->AddItem(MENU_ID_PRINT, GetLabel(IDS_CONTENT_CONTEXT_PRINT));
model_->AddItem(MENU_ID_VIEW_SOURCE,
GetLabel(IDS_CONTENT_CONTEXT_VIEWPAGESOURCE));
if (!browser_->CanGoBack())
model_->SetEnabled(MENU_ID_BACK, false);
if (!browser_->CanGoForward())
model_->SetEnabled(MENU_ID_FORWARD, false);
}
}
void CefMenuManager::ExecuteDefaultCommand(int command_id) {
if (IsCustomContextMenuCommand(command_id)) {
if (web_contents()) {
web_contents()->ExecuteCustomContextMenuCommand(
command_id - MENU_ID_CUSTOM_FIRST, params_.custom_context);
}
return;
}
// If the user chose a replacement word for a misspelling, replace it here.
if (command_id >= MENU_ID_SPELLCHECK_SUGGESTION_0 &&
command_id <= MENU_ID_SPELLCHECK_SUGGESTION_LAST) {
const size_t suggestion_index =
static_cast<size_t>(command_id) - MENU_ID_SPELLCHECK_SUGGESTION_0;
if (suggestion_index < params_.dictionary_suggestions.size()) {
browser_->ReplaceMisspelling(
params_.dictionary_suggestions[suggestion_index]);
}
return;
}
switch (command_id) {
// Navigation.
case MENU_ID_BACK:
browser_->GoBack();
break;
case MENU_ID_FORWARD:
browser_->GoForward();
break;
case MENU_ID_RELOAD:
browser_->Reload();
break;
case MENU_ID_RELOAD_NOCACHE:
browser_->ReloadIgnoreCache();
break;
case MENU_ID_STOPLOAD:
browser_->StopLoad();
break;
// Editing.
case MENU_ID_UNDO:
browser_->GetFocusedFrame()->Undo();
break;
case MENU_ID_REDO:
browser_->GetFocusedFrame()->Redo();
break;
case MENU_ID_CUT:
browser_->GetFocusedFrame()->Cut();
break;
case MENU_ID_COPY:
browser_->GetFocusedFrame()->Copy();
break;
case MENU_ID_PASTE:
browser_->GetFocusedFrame()->Paste();
break;
case MENU_ID_DELETE:
browser_->GetFocusedFrame()->Delete();
break;
case MENU_ID_SELECT_ALL:
browser_->GetFocusedFrame()->SelectAll();
break;
// Miscellaneous.
case MENU_ID_FIND:
// TODO(cef): Implement.
NOTIMPLEMENTED();
break;
case MENU_ID_PRINT:
browser_->Print();
break;
case MENU_ID_VIEW_SOURCE:
browser_->GetFocusedFrame()->ViewSource();
break;
// Spell checking.
case MENU_ID_ADD_TO_DICTIONARY:
browser_->GetHost()->AddWordToDictionary(params_.misspelled_word);
break;
default:
break;
}
}
bool CefMenuManager::IsCustomContextMenuCommand(int command_id) {
// Verify that the command ID is in the correct range.
if (command_id < MENU_ID_CUSTOM_FIRST || command_id > MENU_ID_CUSTOM_LAST)
return false;
command_id -= MENU_ID_CUSTOM_FIRST;
// Verify that the specific command ID was passed from the renderer process.
if (!params_.custom_items.empty()) {
for (size_t i = 0; i < params_.custom_items.size(); ++i) {
if (static_cast<int>(params_.custom_items[i].action) == command_id)
return true;
}
}
return false;
}