// 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 #include #include #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 "third_party/WebKit/public/platform/WebMouseEvent.h" #include "third_party/WebKit/public/platform/WebMouseWheelEvent.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 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(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, SkColor background_color) : CefBrowserPlatformDelegateNative(window_info, background_color), 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_); // 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(GetBackgroundColor()); 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( aura::WindowTreeHost::GetForAcceleratedWidget( HWNDForWidget(window_widget_))); DCHECK(tree_host); if (tree_host) { // Cast to HWNDMessageHandlerDelegate so we can access HandleMove(). static_cast(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. The left/top values may be negative. SetWindowPos(window, NULL, 0, 0, rect.right - rect.left, rect.bottom - rect.top, 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 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.GetType()) { case blink::WebInputEvent::kRawKeyDown: msg.message = event.is_system_key ? WM_SYSKEYDOWN : WM_KEYDOWN; break; case blink::WebInputEvent::kKeyUp: msg.message = event.is_system_key ? WM_SYSKEYUP : WM_KEYUP; break; case blink::WebInputEvent::kChar: msg.message = event.is_system_key ? WM_SYSCHAR : WM_CHAR; break; default: NOTREACHED(); return; } msg.wParam = event.windows_key_code; UINT scan_code = ::MapVirtualKeyW(event.windows_key_code, MAPVK_VK_TO_VSC); msg.lParam = (scan_code << 16) | // key scan code 1; // key repeat count if (event.GetModifiers() & content::NativeWebKeyboardEvent::kAltKey) 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.windows_key_code = key_event.windows_key_code; result.native_key_code = key_event.native_key_code; result.is_system_key = key_event.is_system_key ? 1 : 0; switch (key_event.type) { case KEYEVENT_RAWKEYDOWN: case KEYEVENT_KEYDOWN: result.SetType(blink::WebInputEvent::kRawKeyDown); break; case KEYEVENT_KEYUP: result.SetType(blink::WebInputEvent::kKeyUp); break; case KEYEVENT_CHAR: result.SetType(blink::WebInputEvent::kChar); break; default: NOTREACHED(); } // Populate DOM values that will be passed to JavaScript handlers via // KeyboardEvent. result.dom_code = static_cast( ui::KeycodeConverter::NativeKeycodeToDomCode(key_event.native_key_code)); if (result.GetType() == blink::WebInputEvent::kChar) { result.dom_key = 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.dom_key = ui::PlatformKeyMap::DomKeyFromKeyboardCode( ui::KeyboardCodeForWindowsKeyCode(key_event.windows_key_code), 0); } if (result.GetType() == blink::WebInputEvent::kChar || result.GetType() == blink::WebInputEvent::kRawKeyDown) { result.text[0] = result.windows_key_code; result.unmodified_text[0] = result.windows_key_code; } result.SetModifiers(result.GetModifiers() | 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.SetType(mouseUp ? blink::WebInputEvent::kMouseUp : blink::WebInputEvent::kMouseDown); result.button = blink::WebMouseEvent::Button::kLeft; break; case MBT_MIDDLE: result.SetType(mouseUp ? blink::WebInputEvent::kMouseUp : blink::WebInputEvent::kMouseDown); result.button = blink::WebMouseEvent::Button::kMiddle; break; case MBT_RIGHT: result.SetType(mouseUp ? blink::WebInputEvent::kMouseUp : blink::WebInputEvent::kMouseDown); result.button = blink::WebMouseEvent::Button::kRight; break; default: NOTREACHED(); } result.click_count = clickCount; } void CefBrowserPlatformDelegateNativeWin::TranslateMoveEvent( blink::WebMouseEvent& result, const CefMouseEvent& mouse_event, bool mouseLeave) const { TranslateMouseEvent(result, mouse_event); if (!mouseLeave) { result.SetType(blink::WebInputEvent::kMouseMove); if (mouse_event.modifiers & EVENTFLAG_LEFT_MOUSE_BUTTON) result.button = blink::WebMouseEvent::Button::kLeft; else if (mouse_event.modifiers & EVENTFLAG_MIDDLE_MOUSE_BUTTON) result.button = blink::WebMouseEvent::Button::kMiddle; else if (mouse_event.modifiers & EVENTFLAG_RIGHT_MOUSE_BUTTON) result.button = blink::WebMouseEvent::Button::kRight; else result.button = blink::WebMouseEvent::Button::kNoButton; } else { result.SetType(blink::WebInputEvent::kMouseLeave); result.button = blink::WebMouseEvent::Button::kNoButton; } result.click_count = 0; } void CefBrowserPlatformDelegateNativeWin::TranslateWheelEvent( blink::WebMouseWheelEvent& result, const CefMouseEvent& mouse_event, int deltaX, int deltaY) const { TranslateMouseEvent(result, mouse_event); result.SetType(blink::WebInputEvent::kMouseWheel); result.button = blink::WebMouseEvent::Button::kNoButton; float wheelDelta; bool horizontalScroll = false; wheelDelta = static_cast(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(scrollChars) * scrollbarPixelsPerLine; } else { ULONG scrollLines = defaultScrollLinesPerWheelDelta; SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &scrollLines, 0); if (scrollLines == WHEEL_PAGESCROLL) result.scroll_by_page = true; if (!result.scroll_by_page) scrollDelta *= static_cast(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.delta_x = scrollDelta; result.wheel_ticks_x = wheelDelta; } else { result.delta_y = scrollDelta; result.wheel_ticks_y = wheelDelta; } } CefEventHandle CefBrowserPlatformDelegateNativeWin::GetEventHandle( const content::NativeWebKeyboardEvent& event) const { if (!event.os_event) return NULL; return const_cast(&event.os_event->native_event()); } std::unique_ptr CefBrowserPlatformDelegateNativeWin::CreateFileDialogRunner() { return base::WrapUnique(new CefFileDialogRunnerWin); } std::unique_ptr CefBrowserPlatformDelegateNativeWin::CreateJavaScriptDialogRunner() { return base::WrapUnique(new CefJavaScriptDialogRunnerWin); } std::unique_ptr CefBrowserPlatformDelegateNativeWin::CreateMenuRunner() { return base::WrapUnique(new CefMenuRunnerWin); } void CefBrowserPlatformDelegateNativeWin::TranslateMouseEvent( blink::WebMouseEvent& result, const CefMouseEvent& mouse_event) const { // position result.SetPositionInWidget(mouse_event.x, mouse_event.y); const gfx::Point& screen_pt = GetScreenPoint(gfx::Point(mouse_event.x, mouse_event.y)); result.SetPositionInScreen(screen_pt.x(), screen_pt.y()); // modifiers result.SetModifiers(result.GetModifiers() | TranslateModifiers(mouse_event.modifiers)); // timestamp result.SetTimeStampSeconds(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( 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_NCDESTROY: 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(); } break; 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); }