views: Add support for accelerators (issue #2102)

This commit is contained in:
Marshall Greenblatt
2017-02-16 18:19:43 -05:00
parent e4867b5afb
commit bd1b80198f
35 changed files with 948 additions and 74 deletions

View File

@@ -216,7 +216,8 @@ void CefBrowserPlatformDelegateViews::ViewText(const std::string& text) {
void CefBrowserPlatformDelegateViews::HandleKeyboardEvent(
const content::NativeWebKeyboardEvent& event) {
native_delegate_->HandleKeyboardEvent(event);
// The BrowserView will handle accelerators.
browser_view_->HandleKeyboardEvent(event);
}
void CefBrowserPlatformDelegateViews::HandleExternalProtocol(const GURL& url) {

View File

@@ -5,9 +5,14 @@
#include "libcef/browser/views/browser_view_impl.h"
#include "libcef/browser/browser_host_impl.h"
#include "libcef/browser/browser_util.h"
#include "libcef/browser/context.h"
#include "libcef/browser/views/window_impl.h"
#include "libcef/browser/thread_util.h"
#include "content/public/browser/native_web_keyboard_event.h"
#include "ui/content_accelerators/accelerator_util.h"
// static
CefRefPtr<CefBrowserView> CefBrowserView::CreateBrowserView(
CefRefPtr<CefClient> client,
@@ -75,11 +80,45 @@ void CefBrowserViewImpl::BrowserDestroyed(CefBrowserHostImpl* browser) {
root_view()->SetWebContents(nullptr);
}
void CefBrowserViewImpl::HandleKeyboardEvent(
const content::NativeWebKeyboardEvent& event) {
if (!root_view())
return;
views::FocusManager* focus_manager = root_view()->GetFocusManager();
if (!focus_manager)
return;
if (HandleAccelerator(event, focus_manager))
return;
// Give the CefWindowDelegate a chance to handle the event.
CefRefPtr<CefWindow> window =
view_util::GetWindowFor(root_view()->GetWidget());
CefWindowImpl* window_impl = static_cast<CefWindowImpl*>(window.get());
if (window_impl) {
CefKeyEvent cef_event;
if (browser_util::GetCefKeyEvent(event, cef_event) &&
window_impl->OnKeyEvent(cef_event)) {
return;
}
}
// Proceed with default native handling.
unhandled_keyboard_event_handler_.HandleKeyboardEvent(event, focus_manager);
}
CefRefPtr<CefBrowser> CefBrowserViewImpl::GetBrowser() {
CEF_REQUIRE_VALID_RETURN(nullptr);
return browser_;
}
void CefBrowserViewImpl::SetPreferAccelerators(bool prefer_accelerators) {
CEF_REQUIRE_VALID_RETURN_VOID();
if (root_view())
root_view()->set_allow_accelerators(prefer_accelerators);
}
void CefBrowserViewImpl::SetBackgroundColor(cef_color_t color) {
CEF_REQUIRE_VALID_RETURN_VOID();
ParentClass::SetBackgroundColor(color);
@@ -167,3 +206,39 @@ CefBrowserViewView* CefBrowserViewImpl::CreateRootView() {
void CefBrowserViewImpl::InitializeRootView() {
static_cast<CefBrowserViewView*>(root_view())->Initialize();
}
bool CefBrowserViewImpl::HandleAccelerator(
const content::NativeWebKeyboardEvent& event,
views::FocusManager* focus_manager) {
// Previous calls to TranslateMessage can generate Char events as well as
// RawKeyDown events, even if the latter triggered an accelerator. In these
// cases, we discard the Char events.
if (event.type() == blink::WebInputEvent::Char && ignore_next_char_event_) {
ignore_next_char_event_ = false;
return true;
}
// It's necessary to reset this flag, because a RawKeyDown event may not
// always generate a Char event.
ignore_next_char_event_ = false;
if (event.type() == blink::WebInputEvent::RawKeyDown) {
ui::Accelerator accelerator =
ui::GetAcceleratorFromNativeWebKeyboardEvent(event);
// This is tricky: we want to set ignore_next_char_event_ if
// ProcessAccelerator returns true. But ProcessAccelerator might delete
// |this| if the accelerator is a "close tab" one. So we speculatively
// set the flag and fix it if no event was handled.
ignore_next_char_event_ = true;
if (focus_manager->ProcessAccelerator(accelerator))
return true;
// ProcessAccelerator didn't handle the accelerator, so we know both
// that |this| is still valid, and that we didn't want to set the flag.
ignore_next_char_event_ = false;
}
return false;
}

View File

@@ -14,6 +14,8 @@
#include "libcef/browser/views/view_impl.h"
#include "libcef/browser/views/browser_view_view.h"
#include "ui/views/controls/webview/unhandled_keyboard_event_handler.h"
class CefBrowserViewImpl :
public CefViewImpl<CefBrowserViewView, CefBrowserView,
CefBrowserViewDelegate>,
@@ -41,8 +43,13 @@ class CefBrowserViewImpl :
void BrowserCreated(CefBrowserHostImpl* browser);
void BrowserDestroyed(CefBrowserHostImpl* browser);
// Called to handle accelerators when the event is unhandled by the web
// content and the browser client.
void HandleKeyboardEvent(const content::NativeWebKeyboardEvent& event);
// CefBrowserView methods:
CefRefPtr<CefBrowser> GetBrowser() override;
void SetPreferAccelerators(bool prefer_accelerators) override;
// CefView methods:
CefRefPtr<CefBrowserView> AsBrowserView() override { return this; }
@@ -75,10 +82,20 @@ class CefBrowserViewImpl :
CefBrowserViewView* CreateRootView() override;
void InitializeRootView() override;
std::unique_ptr<CefBrowserHostImpl::CreateParams> pending_browser_create_params_;
// Logic extracted from UnhandledKeyboardEventHandler::HandleKeyboardEvent for
// the handling of accelerators. Returns true if the event was handled by the
// accelerator.
bool HandleAccelerator(const content::NativeWebKeyboardEvent& event,
views::FocusManager* focus_manager);
std::unique_ptr<CefBrowserHostImpl::CreateParams>
pending_browser_create_params_;
CefRefPtr<CefBrowserHostImpl> browser_;
views::UnhandledKeyboardEventHandler unhandled_keyboard_event_handler_;
bool ignore_next_char_event_ = false;
IMPLEMENT_REFCOUNTING_DELETE_ON_UIT(CefBrowserViewImpl);
DISALLOW_COPY_AND_ASSIGN(CefBrowserViewImpl);
};

View File

@@ -35,6 +35,7 @@ CEF_BUTTON_IMPL_T class CefButtonImpl : public CEF_VIEW_IMPL_D {
// CefView methods:
CefRefPtr<CefButton> AsButton() override { return this; }
void SetFocusable(bool focusable) override;
protected:
// Create a new implementation object.
@@ -68,4 +69,11 @@ CEF_BUTTON_IMPL_T void CEF_BUTTON_IMPL_D::SetAccessibleName(
ParentClass::root_view()->SetAccessibleName(name);
}
CEF_BUTTON_IMPL_T void CEF_BUTTON_IMPL_D::SetFocusable(
bool focusable) {
CEF_REQUIRE_VALID_RETURN_VOID();
ParentClass::root_view()->set_request_focus_on_press(focusable);
ParentClass::SetFocusable(focusable);
}
#endif // CEF_LIBCEF_BROWSER_VIEWS_BUTTON_IMPL_H_

View File

@@ -4,6 +4,7 @@
#include "libcef/browser/views/window_impl.h"
#include "libcef/browser/browser_util.h"
#include "libcef/browser/thread_util.h"
#include "libcef/browser/views/display_impl.h"
#include "libcef/browser/views/fill_layout_impl.h"
@@ -17,6 +18,7 @@
#if defined(USE_AURA)
#include "ui/aura/test/ui_controls_factory_aura.h"
#include "ui/aura/window.h"
#include "ui/base/test/ui_controls_aura.h"
#if defined(OS_LINUX)
#include "ui/views/test/ui_controls_factory_desktop_aurax11.h"
@@ -48,6 +50,58 @@ void InitializeUITesting() {
}
}
#if defined(USE_AURA)
// This class forwards KeyEvents to the CefWindowImpl associated with a widget.
// This allows KeyEvents to be processed after all other targets.
// Events originating from CefBrowserView will instead be delivered via
// CefBrowserViewImpl::HandleKeyboardEvent.
class CefUnhandledKeyEventHandler : public ui::EventHandler {
public:
CefUnhandledKeyEventHandler(CefWindowImpl* window_impl,
views::Widget* widget)
: window_impl_(window_impl),
widget_(widget),
window_(widget->GetNativeWindow()) {
DCHECK(window_);
window_->AddPostTargetHandler(this);
}
~CefUnhandledKeyEventHandler() override {
window_->RemovePostTargetHandler(this);
}
// Implementation of ui::EventHandler:
void OnKeyEvent(ui::KeyEvent* event) override {
// Give the FocusManager a chance to handle accelerators first.
// Widget::OnKeyEvent would normally call this after all EventHandlers have
// had a shot but we don't want to wait.
if (widget_->GetFocusManager() &&
!widget_->GetFocusManager()->OnKeyEvent(*event)) {
event->StopPropagation();
return;
}
CefKeyEvent cef_event;
if (browser_util::GetCefKeyEvent(*event, cef_event) &&
window_impl_->OnKeyEvent(cef_event)) {
event->StopPropagation();
}
}
private:
// Members are guaranteed to outlive this object.
CefWindowImpl* window_impl_;
views::Widget* widget_;
// |window_| is the event target that is associated with this class.
aura::Window* window_;
DISALLOW_COPY_AND_ASSIGN(CefUnhandledKeyEventHandler);
};
#endif // defined(USE_AURA)
} // namespace
// static
@@ -315,6 +369,12 @@ bool CefWindowImpl::CanWidgetClose() {
return true;
}
void CefWindowImpl::OnWindowClosing() {
#if defined(USE_AURA)
unhandled_key_event_handler_.reset();
#endif
}
void CefWindowImpl::OnWindowViewDeleted() {
CancelMenu();
@@ -337,6 +397,27 @@ void CefWindowImpl::MenuClosed(CefRefPtr<CefMenuModelImpl> source) {
menu_runner_.reset(nullptr);
}
// Will only be called if CanHandleAccelerators() returns true.
bool CefWindowImpl::AcceleratorPressed(const ui::Accelerator& accelerator) {
for (const auto& entry : accelerator_map_) {
if (entry.second == accelerator)
return delegate()->OnAccelerator(this, entry.first);
}
return false;
}
bool CefWindowImpl::CanHandleAccelerators() const {
if (delegate() && widget_)
return widget_->IsActive();
return false;
}
bool CefWindowImpl::OnKeyEvent(const CefKeyEvent& event) {
if (delegate())
return delegate()->OnKeyEvent(this, event);
return false;
}
void CefWindowImpl::ShowMenu(views::MenuButton* menu_button,
CefRefPtr<CefMenuModel> menu_model,
const CefPoint& screen_point,
@@ -474,6 +555,67 @@ void CefWindowImpl::SendMouseEvents(cef_mouse_button_type_t button,
ui_controls::SendMouseEvents(type, state);
}
void CefWindowImpl::SetAccelerator(int command_id,
int key_code,
bool shift_pressed,
bool ctrl_pressed,
bool alt_pressed) {
CEF_REQUIRE_VALID_RETURN_VOID();
if (!widget_)
return;
AcceleratorMap::const_iterator it = accelerator_map_.find(command_id);
if (it != accelerator_map_.end())
RemoveAccelerator(command_id);
int modifiers = 0;
if (shift_pressed)
modifiers |= ui::EF_SHIFT_DOWN;
if (ctrl_pressed)
modifiers |= ui::EF_CONTROL_DOWN;
if (alt_pressed)
modifiers |= ui::EF_ALT_DOWN;
ui::Accelerator accelerator(static_cast<ui::KeyboardCode>(key_code),
modifiers);
accelerator_map_.insert(std::make_pair(command_id, accelerator));
views::FocusManager* focus_manager = widget_->GetFocusManager();
DCHECK(focus_manager);
focus_manager->RegisterAccelerator(
accelerator, ui::AcceleratorManager::kNormalPriority, this);
}
void CefWindowImpl::RemoveAccelerator(int command_id) {
CEF_REQUIRE_VALID_RETURN_VOID();
if (!widget_)
return;
AcceleratorMap::iterator it = accelerator_map_.find(command_id);
if (it == accelerator_map_.end())
return;
ui::Accelerator accelerator = it->second;
accelerator_map_.erase(it);
views::FocusManager* focus_manager = widget_->GetFocusManager();
DCHECK(focus_manager);
focus_manager->UnregisterAccelerator(accelerator, this);
}
void CefWindowImpl::RemoveAllAccelerators() {
CEF_REQUIRE_VALID_RETURN_VOID();
if (!widget_)
return;
accelerator_map_.clear();
views::FocusManager* focus_manager = widget_->GetFocusManager();
DCHECK(focus_manager);
focus_manager->UnregisterAccelerators(this);
}
CefWindowImpl::CefWindowImpl(CefRefPtr<CefWindowDelegate> delegate)
: ParentClass(delegate),
widget_(nullptr),
@@ -495,6 +637,11 @@ void CefWindowImpl::CreateWidget() {
widget_ = root_view()->GetWidget();
DCHECK(widget_);
#if defined(USE_AURA)
unhandled_key_event_handler_ =
base::MakeUnique<CefUnhandledKeyEventHandler>(this, widget_);
#endif
// The Widget and root View are owned by the native window. Therefore don't
// keep an owned reference.
std::unique_ptr<views::View> view_ptr = view_util::PassOwnership(this);

View File

@@ -6,6 +6,8 @@
#define CEF_LIBCEF_BROWSER_VIEWS_WINDOW_IMPL_H_
#pragma once
#include <map>
#include "include/views/cef_window.h"
#include "include/views/cef_window_delegate.h"
@@ -13,6 +15,7 @@
#include "libcef/browser/views/panel_impl.h"
#include "libcef/browser/views/window_view.h"
#include "ui/base/accelerators/accelerator.h"
#include "ui/views/controls/menu/menu_runner.h"
#include "ui/views/widget/widget.h"
@@ -23,7 +26,8 @@ class MenuButton;
class CefWindowImpl :
public CefPanelImpl<CefWindowView, CefWindow, CefWindowDelegate>,
public CefWindowView::Delegate,
public CefMenuModelImpl::Observer {
public CefMenuModelImpl::Observer,
public ui::AcceleratorTarget {
public:
typedef CefPanelImpl<CefWindowView, CefWindow, CefWindowDelegate> ParentClass;
@@ -70,6 +74,13 @@ class CefWindowImpl :
void SendMouseEvents(cef_mouse_button_type_t button,
bool mouse_down,
bool mouse_up) override;
void SetAccelerator(int command_id,
int key_code,
bool shift_pressed,
bool ctrl_pressed,
bool alt_pressed) override;
void RemoveAccelerator(int command_id) override;
void RemoveAllAccelerators() override;
// CefViewAdapter methods:
void Detach() override;
@@ -91,6 +102,7 @@ class CefWindowImpl :
// CefWindowView::Delegate methods:
bool CanWidgetClose() override;
void OnWindowClosing() override;
void OnWindowViewDeleted() override;
// CefMenuModelImpl::Observer methods:
@@ -101,6 +113,14 @@ class CefWindowImpl :
void GetDebugInfo(base::DictionaryValue* info,
bool include_children) override;
// ui::AcceleratorTarget methods:
bool AcceleratorPressed(const ui::Accelerator& accelerator) override;
bool CanHandleAccelerators() const override;
// Called for key events that have not been handled by other controls in the
// window. Returns true if the event was handled.
bool OnKeyEvent(const CefKeyEvent& event);
void ShowMenu(views::MenuButton* menu_button,
CefRefPtr<CefMenuModel> menu_model,
const CefPoint& screen_point,
@@ -128,6 +148,15 @@ class CefWindowImpl :
CefRefPtr<CefMenuModelImpl> menu_model_;
std::unique_ptr<views::MenuRunner> menu_runner_;
// Map of command_id to accelerator.
typedef std::map<int, ui::Accelerator> AcceleratorMap;
AcceleratorMap accelerator_map_;
#if defined(USE_AURA)
// Native widget's handler to receive events after the event target.
std::unique_ptr<ui::EventHandler> unhandled_key_event_handler_;
#endif
IMPLEMENT_REFCOUNTING_DELETE_ON_UIT(CefWindowImpl);
DISALLOW_COPY_AND_ASSIGN(CefWindowImpl);
};

View File

@@ -368,6 +368,10 @@ gfx::ImageSkia CefWindowView::GetWindowAppIcon() {
GetForced1xScaleRepresentation(GetDisplay().device_scale_factor());
}
void CefWindowView::WindowClosing() {
window_delegate_->OnWindowClosing();
}
views::View* CefWindowView::GetContentsView() {
// |this| will be the "Contents View" hosted by the Widget via ClientView and
// RootView.

View File

@@ -29,6 +29,9 @@ class CefWindowView :
// Returns true to signal that the Widget can be closed.
virtual bool CanWidgetClose() = 0;
// Called when the underlying platform window is closing.
virtual void OnWindowClosing() = 0;
// Called when the WindowView is about to be deleted.
virtual void OnWindowViewDeleted() = 0;
@@ -58,6 +61,7 @@ class CefWindowView :
base::string16 GetWindowTitle() const override;
gfx::ImageSkia GetWindowIcon() override;
gfx::ImageSkia GetWindowAppIcon() override;
void WindowClosing() override;
views::View* GetContentsView() override;
views::ClientView* CreateClientView(views::Widget* widget) override;
views::NonClientFrameView* CreateNonClientFrameView(