cef/libcef/browser/native/browser_platform_delegate_n...

648 lines
21 KiB
C++

// Copyright 2015 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/native/browser_platform_delegate_native_win.h"
#include <shellapi.h>
#include <wininet.h>
#include <winspool.h>
#include "libcef/browser/browser_host_impl.h"
#include "libcef/browser/context.h"
#include "libcef/browser/native/file_dialog_runner_win.h"
#include "libcef/browser/native/javascript_dialog_runner_win.h"
#include "libcef/browser/native/menu_runner_win.h"
#include "libcef/browser/native/window_delegate_view.h"
#include "libcef/browser/thread_util.h"
#include "base/files/file_util.h"
#include "base/memory/ref_counted_memory.h"
#include "base/strings/utf_string_conversions.h"
#include "base/win/registry.h"
#include "content/public/browser/native_web_keyboard_event.h"
#include "ui/aura/window.h"
#include "ui/base/win/shell.h"
#include "ui/display/display.h"
#include "ui/display/screen.h"
#include "ui/events/keycodes/dom/dom_key.h"
#include "ui/events/keycodes/dom/keycode_converter.h"
#include "ui/events/keycodes/keyboard_code_conversion_win.h"
#include "ui/events/keycodes/platform_key_map_win.h"
#include "ui/gfx/win/hwnd_util.h"
#include "ui/views/widget/desktop_aura/desktop_window_tree_host_win.h"
#include "ui/views/widget/widget.h"
#include "ui/views/win/hwnd_util.h"
#pragma comment(lib, "dwmapi.lib")
namespace {
void WriteTempFileAndView(scoped_refptr<base::RefCountedString> str) {
CEF_REQUIRE_FILET();
base::FilePath tmp_file;
if (!base::CreateTemporaryFile(&tmp_file))
return;
// The shell command will look at the file extension to identify the correct
// program to open.
tmp_file = tmp_file.AddExtension(L"txt");
const std::string& data = str->data();
int write_ct = base::WriteFile(tmp_file, data.c_str(), data.size());
DCHECK_EQ(static_cast<int>(data.size()), write_ct);
ui::win::OpenFileViaShell(tmp_file);
}
// According to Mozilla in uriloader/exthandler/win/nsOSHelperAppService.cpp:
// "Some versions of windows (Win2k before SP3, Win XP before SP1) crash in
// ShellExecute on long URLs (bug 161357 on bugzilla.mozilla.org). IE 5 and 6
// support URLS of 2083 chars in length, 2K is safe."
const int kMaxAddressLengthChars = 2048;
bool HasExternalHandler(const std::string& scheme) {
base::win::RegKey key;
const std::wstring registry_path =
base::ASCIIToUTF16(scheme + "\\shell\\open\\command");
key.Open(HKEY_CLASSES_ROOT, registry_path.c_str(), KEY_READ);
if (key.Valid()) {
DWORD size = 0;
key.ReadValue(NULL, NULL, &size, NULL);
if (size > 2) {
// ShellExecute crashes the process when the command is empty.
// We check for "2" because it always returns the trailing NULL.
return true;
}
}
return false;
}
void ExecuteExternalProtocol(const GURL& url) {
CEF_REQUIRE_FILET();
if (!HasExternalHandler(url.scheme()))
return;
const std::string& address = url.spec();
if (address.length() > kMaxAddressLengthChars)
return;
ShellExecuteA(NULL, "open", address.c_str(), NULL, NULL, SW_SHOWNORMAL);
}
WORD KeyStatesToWord() {
static const USHORT kHighBitMaskShort = 0x8000;
WORD result = 0;
if (GetKeyState(VK_CONTROL) & kHighBitMaskShort)
result |= MK_CONTROL;
if (GetKeyState(VK_SHIFT) & kHighBitMaskShort)
result |= MK_SHIFT;
if (GetKeyState(VK_LBUTTON) & kHighBitMaskShort)
result |= MK_LBUTTON;
if (GetKeyState(VK_MBUTTON) & kHighBitMaskShort)
result |= MK_MBUTTON;
if (GetKeyState(VK_RBUTTON) & kHighBitMaskShort)
result |= MK_RBUTTON;
return result;
}
} // namespace
CefBrowserPlatformDelegateNativeWin::CefBrowserPlatformDelegateNativeWin(
const CefWindowInfo& window_info)
: CefBrowserPlatformDelegateNative(window_info),
host_window_created_(false),
window_widget_(nullptr) {
}
void CefBrowserPlatformDelegateNativeWin::BrowserDestroyed(
CefBrowserHostImpl* browser) {
CefBrowserPlatformDelegate::BrowserDestroyed(browser);
if (host_window_created_) {
// Release the reference added in CreateHostWindow().
browser->Release();
}
}
bool CefBrowserPlatformDelegateNativeWin::CreateHostWindow() {
RegisterWindowClass();
std::wstring windowName(CefString(&window_info_.window_name));
// Create the new browser window.
window_info_.window = CreateWindowEx(window_info_.ex_style,
GetWndClass(), windowName.c_str(), window_info_.style,
window_info_.x, window_info_.y, window_info_.width,
window_info_.height, window_info_.parent_window, window_info_.menu,
::GetModuleHandle(NULL), NULL);
// It's possible for CreateWindowEx to fail if the parent window was
// destroyed between the call to CreateBrowser and the above one.
DCHECK(window_info_.window != NULL);
if (!window_info_.window)
return false;
host_window_created_ = true;
// Set window user data to this object for future reference from the window
// procedure.
gfx::SetWindowUserData(window_info_.window, this);
// Add a reference that will later be released in DestroyBrowser().
browser_->AddRef();
RECT cr;
GetClientRect(window_info_.window, &cr);
DCHECK(!window_widget_);
SkColor background_color = SK_ColorWHITE;
const CefSettings& settings = CefContext::Get()->settings();
if (CefColorGetA(settings.background_color) > 0) {
background_color = SkColorSetRGB(
CefColorGetR(settings.background_color),
CefColorGetG(settings.background_color),
CefColorGetB(settings.background_color));
}
// Adjust for potential display scaling.
gfx::Point point = gfx::Point(cr.right, cr.bottom);
float scale = display::Screen::GetScreen()->
GetDisplayNearestPoint(point).device_scale_factor();
point = gfx::ToFlooredPoint(
gfx::ScalePoint(gfx::PointF(point), 1.0f / scale));
CefWindowDelegateView* delegate_view =
new CefWindowDelegateView(background_color);
delegate_view->Init(window_info_.window,
browser_->web_contents(),
gfx::Rect(0, 0, point.x(), point.y()));
window_widget_ = delegate_view->GetWidget();
window_widget_->Show();
return true;
}
void CefBrowserPlatformDelegateNativeWin::CloseHostWindow() {
if (window_info_.window != NULL) {
HWND frameWnd = GetAncestor(window_info_.window, GA_ROOT);
PostMessage(frameWnd, WM_CLOSE, 0, 0);
}
}
CefWindowHandle
CefBrowserPlatformDelegateNativeWin::GetHostWindowHandle() const {
if (windowless_handler_)
return windowless_handler_->GetParentWindowHandle();
return window_info_.window;
}
views::Widget* CefBrowserPlatformDelegateNativeWin::GetWindowWidget() const {
return window_widget_;
}
void CefBrowserPlatformDelegateNativeWin::SendFocusEvent(bool setFocus) {
if (!setFocus)
return;
if (browser_->web_contents()) {
// Give logical focus to the RenderWidgetHostViewAura in the views
// hierarchy. This does not change the native keyboard focus.
browser_->web_contents()->Focus();
}
if (window_widget_) {
// Give native focus to the DesktopWindowTreeHostWin associated with the
// root window.
//
// The DesktopWindowTreeHostWin HandleNativeFocus/HandleNativeBlur methods
// are called in response to WM_SETFOCUS/WM_KILLFOCUS respectively. The
// implementation has been patched to call HandleActivationChanged which
// results in the following behaviors:
// 1. Update focus/activation state of the aura::Window indirectly via
// wm::FocusController. This allows focus-related behaviors (e.g. focus
// rings, flashing caret, onFocus/onBlur JS events, etc.) to work as
// expected (see issue #1677).
// 2. Update focus state of the ui::InputMethod. If this does not occur
// then InputMethodBase::GetTextInputClient will return NULL and
// InputMethodWin::OnChar will fail to sent character events to the
// renderer (see issue #1700).
//
// This differs from activation in Chrome which is handled via
// HWNDMessageHandler::PostProcessActivateMessage (Widget::Show indirectly
// calls HWNDMessageHandler::Activate which calls ::SetForegroundWindow
// resulting in a WM_ACTIVATE message being sent to the window). The Chrome
// code path doesn't work for CEF because IsTopLevelWindow in
// hwnd_message_handler.cc will return false and consequently
// HWNDMessageHandler::PostProcessActivateMessage will not be called.
//
// Activation events are usually reserved for the top-level window so
// triggering activation based on focus events may be incorrect in some
// circumstances. Revisit this implementation if additional problems are
// discovered.
::SetFocus(HWNDForWidget(window_widget_));
}
}
void CefBrowserPlatformDelegateNativeWin::NotifyMoveOrResizeStarted() {
// Call the parent method to dismiss any existing popups.
CefBrowserPlatformDelegate::NotifyMoveOrResizeStarted();
if (!window_widget_)
return;
// Notify DesktopWindowTreeHostWin of move events so that screen rectangle
// information is communicated to the renderer process and popups are
// displayed in the correct location.
views::DesktopWindowTreeHostWin* tree_host =
static_cast<views::DesktopWindowTreeHostWin*>(
aura::WindowTreeHost::GetForAcceleratedWidget(
HWNDForWidget(window_widget_)));
DCHECK(tree_host);
if (tree_host) {
// Cast to HWNDMessageHandlerDelegate so we can access HandleMove().
static_cast<views::HWNDMessageHandlerDelegate*>(tree_host)->HandleMove();
}
}
void CefBrowserPlatformDelegateNativeWin::SizeTo(int width, int height) {
HWND window = window_info_.window;
RECT rect = {0, 0, width, height};
DWORD style = GetWindowLong(window, GWL_STYLE);
DWORD ex_style = GetWindowLong(window, GWL_EXSTYLE);
bool has_menu = !(style & WS_CHILD) && (GetMenu(window) != NULL);
// The size value is for the client area. Calculate the whole window size
// based on the current style.
AdjustWindowRectEx(&rect, style, has_menu, ex_style);
// Size the window.
SetWindowPos(window, NULL, 0, 0, rect.right,
rect.bottom, SWP_NOZORDER | SWP_NOMOVE | SWP_NOACTIVATE);
}
gfx::Point CefBrowserPlatformDelegateNativeWin::GetScreenPoint(
const gfx::Point& view) const {
if (windowless_handler_)
return windowless_handler_->GetParentScreenPoint(view);
if (!window_widget_)
return view;
aura::Window* window = window_widget_->GetNativeView();
const gfx::Rect& bounds_in_screen = window->GetBoundsInScreen();
const gfx::Point& screen_point = gfx::Point(bounds_in_screen.x() + view.x(),
bounds_in_screen.y() + view.y());
// Adjust for potential display scaling.
float scale = display::Screen::GetScreen()->
GetDisplayNearestPoint(screen_point).device_scale_factor();
return gfx::ToFlooredPoint(
gfx::ScalePoint(gfx::PointF(screen_point), scale));
}
void CefBrowserPlatformDelegateNativeWin::ViewText(const std::string& text) {
std::string str = text;
scoped_refptr<base::RefCountedString> str_ref =
base::RefCountedString::TakeString(&str);
CEF_POST_TASK(CEF_FILET, base::Bind(WriteTempFileAndView, str_ref));
}
void CefBrowserPlatformDelegateNativeWin::HandleKeyboardEvent(
const content::NativeWebKeyboardEvent& event) {
// Any unhandled keyboard/character messages are sent to DefWindowProc so that
// shortcut keys work correctly.
if (event.os_event) {
const MSG& msg = event.os_event->native_event();
DefWindowProc(msg.hwnd, msg.message, msg.wParam, msg.lParam);
} else {
MSG msg = {};
msg.hwnd = GetHostWindowHandle();
if (!msg.hwnd)
return;
switch (event.type) {
case blink::WebInputEvent::RawKeyDown:
msg.message = event.isSystemKey ? WM_SYSKEYDOWN : WM_KEYDOWN;
break;
case blink::WebInputEvent::KeyUp:
msg.message = event.isSystemKey ? WM_SYSKEYUP : WM_KEYUP;
break;
case blink::WebInputEvent::Char:
msg.message = event.isSystemKey ? WM_SYSCHAR: WM_CHAR;
break;
default:
NOTREACHED();
return;
}
msg.wParam = event.windowsKeyCode;
UINT scan_code = ::MapVirtualKeyW(event.windowsKeyCode, MAPVK_VK_TO_VSC);
msg.lParam = (scan_code << 16) | // key scan code
1; // key repeat count
if (event.modifiers & content::NativeWebKeyboardEvent::AltKey)
msg.lParam |= (1 << 29);
DefWindowProc(msg.hwnd, msg.message, msg.wParam, msg.lParam);
}
}
void CefBrowserPlatformDelegateNativeWin::HandleExternalProtocol(
const GURL& url) {
// Execute on the FILE thread.
CEF_POST_TASK(CEF_FILET,
base::Bind(ExecuteExternalProtocol, url));
}
void CefBrowserPlatformDelegateNativeWin::TranslateKeyEvent(
content::NativeWebKeyboardEvent& result,
const CefKeyEvent& key_event) const {
result.timeStampSeconds = GetMessageTime() / 1000.0;
result.windowsKeyCode = key_event.windows_key_code;
result.nativeKeyCode = key_event.native_key_code;
result.isSystemKey = key_event.is_system_key ? 1 : 0;
switch (key_event.type) {
case KEYEVENT_RAWKEYDOWN:
case KEYEVENT_KEYDOWN:
result.type = blink::WebInputEvent::RawKeyDown;
break;
case KEYEVENT_KEYUP:
result.type = blink::WebInputEvent::KeyUp;
break;
case KEYEVENT_CHAR:
result.type = blink::WebInputEvent::Char;
break;
default:
NOTREACHED();
}
// Populate DOM values that will be passed to JavaScript handlers via
// KeyboardEvent.
result.domCode =
static_cast<int>(ui::KeycodeConverter::NativeKeycodeToDomCode(
key_event.native_key_code));
if (result.type == blink::WebInputEvent::Char) {
result.domKey = ui::DomKey::FromCharacter(key_event.windows_key_code);
} else {
// TODO(cef): CefKeyEvent does not currently pass extended key status (see
// WM_KEYDOWN docs) which would be necessary to pass EF_IS_EXTENDED_KEY as
// the |flags| parameter to DomKeyFromKeyboardCode().
result.domKey = ui::PlatformKeyMap::DomKeyFromKeyboardCode(
ui::KeyboardCodeForWindowsKeyCode(key_event.windows_key_code), 0);
}
if (result.type == blink::WebInputEvent::Char ||
result.type == blink::WebInputEvent::RawKeyDown) {
result.text[0] = result.windowsKeyCode;
result.unmodifiedText[0] = result.windowsKeyCode;
}
result.modifiers |= TranslateModifiers(key_event.modifiers);
}
void CefBrowserPlatformDelegateNativeWin::TranslateClickEvent(
blink::WebMouseEvent& result,
const CefMouseEvent& mouse_event,
CefBrowserHost::MouseButtonType type,
bool mouseUp, int clickCount) const {
TranslateMouseEvent(result, mouse_event);
switch (type) {
case MBT_LEFT:
result.type = mouseUp ? blink::WebInputEvent::MouseUp :
blink::WebInputEvent::MouseDown;
result.button = blink::WebMouseEvent::Button::Left;
break;
case MBT_MIDDLE:
result.type = mouseUp ? blink::WebInputEvent::MouseUp :
blink::WebInputEvent::MouseDown;
result.button = blink::WebMouseEvent::Button::Middle;
break;
case MBT_RIGHT:
result.type = mouseUp ? blink::WebInputEvent::MouseUp :
blink::WebInputEvent::MouseDown;
result.button = blink::WebMouseEvent::Button::Right;
break;
default:
NOTREACHED();
}
result.clickCount = clickCount;
}
void CefBrowserPlatformDelegateNativeWin::TranslateMoveEvent(
blink::WebMouseEvent& result,
const CefMouseEvent& mouse_event,
bool mouseLeave) const {
TranslateMouseEvent(result, mouse_event);
if (!mouseLeave) {
result.type = blink::WebInputEvent::MouseMove;
if (mouse_event.modifiers & EVENTFLAG_LEFT_MOUSE_BUTTON)
result.button = blink::WebMouseEvent::Button::Left;
else if (mouse_event.modifiers & EVENTFLAG_MIDDLE_MOUSE_BUTTON)
result.button = blink::WebMouseEvent::Button::Middle;
else if (mouse_event.modifiers & EVENTFLAG_RIGHT_MOUSE_BUTTON)
result.button = blink::WebMouseEvent::Button::Right;
else
result.button = blink::WebMouseEvent::Button::NoButton;
} else {
result.type = blink::WebInputEvent::MouseLeave;
result.button = blink::WebMouseEvent::Button::NoButton;
}
result.clickCount = 0;
}
void CefBrowserPlatformDelegateNativeWin::TranslateWheelEvent(
blink::WebMouseWheelEvent& result,
const CefMouseEvent& mouse_event,
int deltaX, int deltaY) const {
TranslateMouseEvent(result, mouse_event);
result.type = blink::WebInputEvent::MouseWheel;
result.button = blink::WebMouseEvent::Button::NoButton;
float wheelDelta;
bool horizontalScroll = false;
wheelDelta = static_cast<float>(deltaY ? deltaY : deltaX);
horizontalScroll = (deltaY == 0);
static const ULONG defaultScrollCharsPerWheelDelta = 1;
static const FLOAT scrollbarPixelsPerLine = 100.0f / 3.0f;
static const ULONG defaultScrollLinesPerWheelDelta = 3;
wheelDelta /= WHEEL_DELTA;
float scrollDelta = wheelDelta;
if (horizontalScroll) {
ULONG scrollChars = defaultScrollCharsPerWheelDelta;
SystemParametersInfo(SPI_GETWHEELSCROLLCHARS, 0, &scrollChars, 0);
scrollDelta *= static_cast<FLOAT>(scrollChars) * scrollbarPixelsPerLine;
} else {
ULONG scrollLines = defaultScrollLinesPerWheelDelta;
SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &scrollLines, 0);
if (scrollLines == WHEEL_PAGESCROLL)
result.scrollByPage = true;
if (!result.scrollByPage)
scrollDelta *= static_cast<FLOAT>(scrollLines) * scrollbarPixelsPerLine;
}
// Set scroll amount based on above calculations. WebKit expects positive
// deltaY to mean "scroll up" and positive deltaX to mean "scroll left".
if (horizontalScroll) {
result.deltaX = scrollDelta;
result.wheelTicksX = wheelDelta;
} else {
result.deltaY = scrollDelta;
result.wheelTicksY = wheelDelta;
}
}
CefEventHandle CefBrowserPlatformDelegateNativeWin::GetEventHandle(
const content::NativeWebKeyboardEvent& event) const {
if (!event.os_event)
return NULL;
return const_cast<CefEventHandle>(&event.os_event->native_event());
}
std::unique_ptr<CefFileDialogRunner>
CefBrowserPlatformDelegateNativeWin::CreateFileDialogRunner() {
return base::WrapUnique(new CefFileDialogRunnerWin);
}
std::unique_ptr<CefJavaScriptDialogRunner>
CefBrowserPlatformDelegateNativeWin::CreateJavaScriptDialogRunner() {
return base::WrapUnique(new CefJavaScriptDialogRunnerWin);
}
std::unique_ptr<CefMenuRunner>
CefBrowserPlatformDelegateNativeWin::CreateMenuRunner() {
return base::WrapUnique(new CefMenuRunnerWin);
}
void CefBrowserPlatformDelegateNativeWin::TranslateMouseEvent(
blink::WebMouseEvent& result,
const CefMouseEvent& mouse_event) const {
// position
result.x = mouse_event.x;
result.y = mouse_event.y;
result.windowX = result.x;
result.windowY = result.y;
const gfx::Point& screen_pt = GetScreenPoint(gfx::Point(result.x, result.y));
result.globalX = screen_pt.x();
result.globalY = screen_pt.y();
// modifiers
result.modifiers |= TranslateModifiers(mouse_event.modifiers);
// timestamp
result.timeStampSeconds = GetMessageTime() / 1000.0;
}
// static
void CefBrowserPlatformDelegateNativeWin::RegisterWindowClass() {
static bool registered = false;
if (registered)
return;
// Register the window class
WNDCLASSEX wcex = {
/* cbSize = */ sizeof(WNDCLASSEX),
/* style = */ CS_HREDRAW | CS_VREDRAW,
/* lpfnWndProc = */ CefBrowserPlatformDelegateNativeWin::WndProc,
/* cbClsExtra = */ 0,
/* cbWndExtra = */ 0,
/* hInstance = */ ::GetModuleHandle(NULL),
/* hIcon = */ NULL,
/* hCursor = */ LoadCursor(NULL, IDC_ARROW),
/* hbrBackground = */ 0,
/* lpszMenuName = */ NULL,
/* lpszClassName = */ CefBrowserPlatformDelegateNativeWin::GetWndClass(),
/* hIconSm = */ NULL,
};
RegisterClassEx(&wcex);
registered = true;
}
// static
LPCTSTR CefBrowserPlatformDelegateNativeWin::GetWndClass() {
return L"CefBrowserWindow";
}
// static
LRESULT CALLBACK CefBrowserPlatformDelegateNativeWin::WndProc(
HWND hwnd, UINT message,
WPARAM wParam, LPARAM lParam) {
CefBrowserPlatformDelegateNativeWin* platform_delegate =
static_cast<CefBrowserPlatformDelegateNativeWin*>(
gfx::GetWindowUserData(hwnd));
CefBrowserHostImpl* browser = nullptr;
if (platform_delegate)
browser = platform_delegate->browser_;
switch (message) {
case WM_CLOSE:
if (browser && !browser->TryCloseBrowser()) {
// Cancel the close.
return 0;
}
// Allow the close.
break;
case WM_DESTROY:
if (platform_delegate) {
// Clear the user data pointer.
gfx::SetWindowUserData(hwnd, NULL);
// Force the browser to be destroyed. This will result in a call to
// BrowserDestroyed() that will release the reference added in
// CreateHostWindow().
browser->WindowDestroyed();
}
return 0;
case WM_SIZE:
if (platform_delegate && platform_delegate->window_widget_) {
// Pass window resize events to the HWND for the DesktopNativeWidgetAura
// root window. Passing size 0x0 (wParam == SIZE_MINIMIZED, for example)
// will cause the widget to be hidden which reduces resource usage.
RECT rc;
GetClientRect(hwnd, &rc);
SetWindowPos(HWNDForWidget(platform_delegate->window_widget_), NULL,
rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top,
SWP_NOZORDER);
}
return 0;
case WM_MOVING:
case WM_MOVE:
if (browser)
browser->NotifyMoveOrResizeStarted();
return 0;
case WM_SETFOCUS:
if (browser)
browser->SetFocus(true);
return 0;
case WM_ERASEBKGND:
return 0;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}