cef/tests/cefclient/browser/osr_window_win.cc
reito 260dd0ca24 osr: Implement shared texture support (fixes #1006, fixes #2575)
Adds support for the OnAcceleratedPaint callback. Verified to work
on macOS and Windows. Linux support is present but not implemented
for cefclient, so it is not verified to work.

To test:
Run `cefclient --off-screen-rendering-enabled --shared-texture-enabled`
2024-04-23 13:03:56 -04:00

1224 lines
37 KiB
C++

// Copyright (c) 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 "tests/cefclient/browser/osr_window_win.h"
#include <windowsx.h>
#if defined(CEF_USE_ATL)
#include <oleacc.h>
#endif
#include <memory>
#include "include/base/cef_build.h"
#include "tests/cefclient/browser/main_context.h"
#include "tests/cefclient/browser/osr_accessibility_helper.h"
#include "tests/cefclient/browser/osr_accessibility_node.h"
#include "tests/cefclient/browser/osr_ime_handler_win.h"
#include "tests/cefclient/browser/osr_render_handler_win_d3d11.h"
#include "tests/cefclient/browser/osr_render_handler_win_gl.h"
#include "tests/cefclient/browser/resource.h"
#include "tests/shared/browser/geometry_util.h"
#include "tests/shared/browser/main_message_loop.h"
#include "tests/shared/browser/util_win.h"
namespace client {
namespace {
const wchar_t kWndClass[] = L"Client_OsrWindow";
// Helper funtion to check if it is Windows8 or greater.
// https://msdn.microsoft.com/en-us/library/ms724833(v=vs.85).aspx
inline BOOL IsWindows_8_Or_Newer() {
OSVERSIONINFOEX osvi = {0};
osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
osvi.dwMajorVersion = 6;
osvi.dwMinorVersion = 2;
DWORDLONG dwlConditionMask = 0;
VER_SET_CONDITION(dwlConditionMask, VER_MAJORVERSION, VER_GREATER_EQUAL);
VER_SET_CONDITION(dwlConditionMask, VER_MINORVERSION, VER_GREATER_EQUAL);
return ::VerifyVersionInfo(&osvi, VER_MAJORVERSION | VER_MINORVERSION,
dwlConditionMask);
}
// Helper function to detect mouse messages coming from emulation of touch
// events. These should be ignored.
bool IsMouseEventFromTouch(UINT message) {
#define MOUSEEVENTF_FROMTOUCH 0xFF515700
return (message >= WM_MOUSEFIRST) && (message <= WM_MOUSELAST) &&
(GetMessageExtraInfo() & MOUSEEVENTF_FROMTOUCH) ==
MOUSEEVENTF_FROMTOUCH;
}
class CreateBrowserHelper {
public:
CreateBrowserHelper(HWND hwnd,
const RECT& rect,
CefRefPtr<CefClient> handler,
const std::string& url,
const CefBrowserSettings& settings,
CefRefPtr<CefDictionaryValue> extra_info,
CefRefPtr<CefRequestContext> request_context,
OsrWindowWin* osr_window_win)
: hwnd_(hwnd),
rect_(rect),
handler_(handler),
url_(url),
settings_(settings),
extra_info_(extra_info),
request_context_(request_context),
osr_window_win_(osr_window_win) {}
HWND hwnd_;
RECT rect_;
CefRefPtr<CefClient> handler_;
std::string url_;
CefBrowserSettings settings_;
CefRefPtr<CefDictionaryValue> extra_info_;
CefRefPtr<CefRequestContext> request_context_;
OsrWindowWin* osr_window_win_;
};
} // namespace
OsrWindowWin::OsrWindowWin(Delegate* delegate,
const OsrRendererSettings& settings)
: delegate_(delegate),
settings_(settings),
last_mouse_pos_(),
current_mouse_pos_() {
DCHECK(delegate_);
client_rect_ = {0};
}
OsrWindowWin::~OsrWindowWin() {
CEF_REQUIRE_UI_THREAD();
// The native window should have already been destroyed.
DCHECK(!hwnd_ && !render_handler_.get());
}
void CreateBrowserWithHelper(CreateBrowserHelper* helper) {
helper->osr_window_win_->CreateBrowser(
helper->hwnd_, helper->rect_, helper->handler_, helper->settings_,
helper->extra_info_, helper->request_context_, helper->url_);
delete helper;
}
void OsrWindowWin::CreateBrowser(HWND parent_hwnd,
const RECT& rect,
CefRefPtr<CefClient> handler,
const CefBrowserSettings& settings,
CefRefPtr<CefDictionaryValue> extra_info,
CefRefPtr<CefRequestContext> request_context,
const std::string& startup_url) {
if (!CefCurrentlyOn(TID_UI)) {
// Execute this method on the UI thread.
CreateBrowserHelper* helper =
new CreateBrowserHelper(parent_hwnd, rect, handler, startup_url,
settings, extra_info, request_context, this);
CefPostTask(TID_UI, base::BindOnce(CreateBrowserWithHelper, helper));
return;
}
// Create the native window.
Create(parent_hwnd, rect);
CefWindowInfo window_info;
window_info.SetAsWindowless(hwnd_);
if (GetWindowLongPtr(parent_hwnd, GWL_EXSTYLE) & WS_EX_NOACTIVATE) {
// Don't activate the browser window on creation.
window_info.ex_style |= WS_EX_NOACTIVATE;
}
window_info.shared_texture_enabled = settings_.shared_texture_enabled;
window_info.external_begin_frame_enabled =
settings_.external_begin_frame_enabled;
// Create the browser asynchronously.
CefBrowserHost::CreateBrowser(window_info, handler, startup_url, settings,
extra_info, request_context);
}
void OsrWindowWin::ShowPopup(HWND parent_hwnd,
int x,
int y,
size_t width,
size_t height) {
if (!CefCurrentlyOn(TID_UI)) {
// Execute this method on the UI thread.
CefPostTask(TID_UI, base::BindOnce(&OsrWindowWin::ShowPopup, this,
parent_hwnd, x, y, width, height));
return;
}
DCHECK(browser_.get());
// Create the native window.
const RECT rect = {x, y, x + static_cast<int>(width),
y + static_cast<int>(height)};
Create(parent_hwnd, rect);
// Create the render handler.
EnsureRenderHandler();
render_handler_->SetBrowser(browser_);
// Send resize notification so the compositor is assigned the correct
// viewport size and begins rendering.
browser_->GetHost()->WasResized();
Show();
}
void OsrWindowWin::Show() {
if (!CefCurrentlyOn(TID_UI)) {
// Execute this method on the UI thread.
CefPostTask(TID_UI, base::BindOnce(&OsrWindowWin::Show, this));
return;
}
if (!browser_) {
return;
}
// Show the native window if not currently visible.
if (hwnd_ && !::IsWindowVisible(hwnd_)) {
ShowWindow(hwnd_, SW_SHOW);
}
if (hidden_) {
// Set the browser as visible.
browser_->GetHost()->WasHidden(false);
hidden_ = false;
}
// Give focus to the browser.
browser_->GetHost()->SetFocus(true);
}
void OsrWindowWin::Hide() {
if (!CefCurrentlyOn(TID_UI)) {
// Execute this method on the UI thread.
CefPostTask(TID_UI, base::BindOnce(&OsrWindowWin::Hide, this));
return;
}
if (!browser_) {
return;
}
// Remove focus from the browser.
browser_->GetHost()->SetFocus(false);
if (!hidden_) {
// Set the browser as hidden.
browser_->GetHost()->WasHidden(true);
hidden_ = true;
}
}
void OsrWindowWin::SetBounds(int x, int y, size_t width, size_t height) {
if (!CefCurrentlyOn(TID_UI)) {
// Execute this method on the UI thread.
CefPostTask(TID_UI, base::BindOnce(&OsrWindowWin::SetBounds, this, x, y,
width, height));
return;
}
if (hwnd_) {
// Set the browser window bounds.
::SetWindowPos(hwnd_, nullptr, x, y, static_cast<int>(width),
static_cast<int>(height), SWP_NOZORDER);
}
}
void OsrWindowWin::SetFocus() {
if (!CefCurrentlyOn(TID_UI)) {
// Execute this method on the UI thread.
CefPostTask(TID_UI, base::BindOnce(&OsrWindowWin::SetFocus, this));
return;
}
if (hwnd_) {
// Give focus to the native window.
::SetFocus(hwnd_);
}
}
void OsrWindowWin::SetDeviceScaleFactor(float device_scale_factor) {
if (!CefCurrentlyOn(TID_UI)) {
// Execute this method on the UI thread.
CefPostTask(TID_UI, base::BindOnce(&OsrWindowWin::SetDeviceScaleFactor,
this, device_scale_factor));
return;
}
if (device_scale_factor == device_scale_factor_) {
return;
}
device_scale_factor_ = device_scale_factor;
if (browser_) {
browser_->GetHost()->NotifyScreenInfoChanged();
browser_->GetHost()->WasResized();
}
}
void OsrWindowWin::Create(HWND parent_hwnd, const RECT& rect) {
CEF_REQUIRE_UI_THREAD();
DCHECK(!hwnd_ && !render_handler_.get());
DCHECK(parent_hwnd);
DCHECK(!::IsRectEmpty(&rect));
HINSTANCE hInst = ::GetModuleHandle(nullptr);
const cef_color_t background_color = MainContext::Get()->GetBackgroundColor();
const HBRUSH background_brush = CreateSolidBrush(
RGB(CefColorGetR(background_color), CefColorGetG(background_color),
CefColorGetB(background_color)));
RegisterOsrClass(hInst, background_brush);
DWORD ex_style = 0;
if (GetWindowLongPtr(parent_hwnd, GWL_EXSTYLE) & WS_EX_NOACTIVATE) {
// Don't activate the browser window on creation.
ex_style |= WS_EX_NOACTIVATE;
}
// Create the native window with a border so it's easier to visually identify
// OSR windows.
hwnd_ = ::CreateWindowEx(
ex_style, kWndClass, nullptr,
WS_BORDER | WS_CHILD | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_VISIBLE,
rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top,
parent_hwnd, nullptr, hInst, nullptr);
CHECK(hwnd_);
client_rect_ = rect;
// Associate |this| with the window.
SetUserDataPtr(hwnd_, this);
#if defined(CEF_USE_ATL)
accessibility_root_ = nullptr;
// Create/register the drag&drop handler.
drop_target_ = DropTargetWin::Create(this, hwnd_);
HRESULT register_res = RegisterDragDrop(hwnd_, drop_target_);
DCHECK_EQ(register_res, S_OK);
#endif
ime_handler_ = std::make_unique<OsrImeHandlerWin>(hwnd_);
// Enable Touch Events if requested
if (client::MainContext::Get()->TouchEventsEnabled()) {
RegisterTouchWindow(hwnd_, 0);
}
// Notify the window owner.
NotifyNativeWindowCreated(hwnd_);
}
void OsrWindowWin::Destroy() {
CEF_REQUIRE_UI_THREAD();
DCHECK(hwnd_ != nullptr);
#if defined(CEF_USE_ATL)
// Revoke/delete the drag&drop handler.
RevokeDragDrop(hwnd_);
drop_target_ = nullptr;
#endif
render_handler_.reset();
// Destroy the native window.
::DestroyWindow(hwnd_);
ime_handler_.reset();
hwnd_ = nullptr;
}
void OsrWindowWin::NotifyNativeWindowCreated(HWND hwnd) {
if (!CURRENTLY_ON_MAIN_THREAD()) {
// Execute this method on the main thread.
MAIN_POST_CLOSURE(
base::BindOnce(&OsrWindowWin::NotifyNativeWindowCreated, this, hwnd));
return;
}
delegate_->OnOsrNativeWindowCreated(hwnd);
}
// static
void OsrWindowWin::RegisterOsrClass(HINSTANCE hInstance,
HBRUSH background_brush) {
// Only register the class one time.
static bool class_registered = false;
if (class_registered) {
return;
}
class_registered = true;
WNDCLASSEX wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_OWNDC;
wcex.lpfnWndProc = OsrWndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = nullptr;
wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
wcex.hbrBackground = background_brush;
wcex.lpszMenuName = nullptr;
wcex.lpszClassName = kWndClass;
wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));
RegisterClassEx(&wcex);
}
void OsrWindowWin::OnIMESetContext(UINT message, WPARAM wParam, LPARAM lParam) {
// We handle the IME Composition Window ourselves (but let the IME Candidates
// Window be handled by IME through DefWindowProc()), so clear the
// ISC_SHOWUICOMPOSITIONWINDOW flag:
lParam &= ~ISC_SHOWUICOMPOSITIONWINDOW;
::DefWindowProc(hwnd_, message, wParam, lParam);
// Create Caret Window if required
if (ime_handler_) {
ime_handler_->CreateImeWindow();
ime_handler_->MoveImeWindow();
}
}
void OsrWindowWin::OnIMEStartComposition() {
if (ime_handler_) {
ime_handler_->CreateImeWindow();
ime_handler_->MoveImeWindow();
ime_handler_->ResetComposition();
}
}
void OsrWindowWin::OnIMEComposition(UINT message,
WPARAM wParam,
LPARAM lParam) {
if (browser_ && ime_handler_) {
CefString cTextStr;
if (ime_handler_->GetResult(lParam, cTextStr)) {
// Send the text to the browser. The |replacement_range| and
// |relative_cursor_pos| params are not used on Windows, so provide
// default invalid values.
browser_->GetHost()->ImeCommitText(cTextStr, CefRange::InvalidRange(), 0);
ime_handler_->ResetComposition();
// Continue reading the composition string - Japanese IMEs send both
// GCS_RESULTSTR and GCS_COMPSTR.
}
std::vector<CefCompositionUnderline> underlines;
int composition_start = 0;
if (ime_handler_->GetComposition(lParam, cTextStr, underlines,
composition_start)) {
// Send the composition string to the browser. The |replacement_range|
// param is not used on Windows, so provide a default invalid value.
browser_->GetHost()->ImeSetComposition(
cTextStr, underlines, CefRange::InvalidRange(),
CefRange(composition_start,
static_cast<int>(composition_start + cTextStr.length())));
// Update the Candidate Window position. The cursor is at the end so
// subtract 1. This is safe because IMM32 does not support non-zero-width
// in a composition. Also, negative values are safely ignored in
// MoveImeWindow
ime_handler_->UpdateCaretPosition(composition_start - 1);
} else {
OnIMECancelCompositionEvent();
}
}
}
void OsrWindowWin::OnIMECancelCompositionEvent() {
if (browser_ && ime_handler_) {
browser_->GetHost()->ImeCancelComposition();
ime_handler_->ResetComposition();
ime_handler_->DestroyImeWindow();
}
}
// static
LRESULT CALLBACK OsrWindowWin::OsrWndProc(HWND hWnd,
UINT message,
WPARAM wParam,
LPARAM lParam) {
CEF_REQUIRE_UI_THREAD();
OsrWindowWin* self = GetUserDataPtr<OsrWindowWin*>(hWnd);
if (!self) {
return DefWindowProc(hWnd, message, wParam, lParam);
}
// We want to handle IME events before the OS does any default handling.
switch (message) {
case WM_IME_SETCONTEXT:
self->OnIMESetContext(message, wParam, lParam);
return 0;
case WM_IME_STARTCOMPOSITION:
self->OnIMEStartComposition();
return 0;
case WM_IME_COMPOSITION:
self->OnIMEComposition(message, wParam, lParam);
return 0;
case WM_IME_ENDCOMPOSITION:
self->OnIMECancelCompositionEvent();
// Let WTL call::DefWindowProc() and release its resources.
break;
#if defined(CEF_USE_ATL)
case WM_GETOBJECT: {
// Only the lower 32 bits of lParam are valid when checking the object id
// because it sometimes gets sign-extended incorrectly (but not always).
DWORD obj_id = static_cast<DWORD>(static_cast<DWORD_PTR>(lParam));
// Accessibility readers will send an OBJID_CLIENT message.
if (static_cast<DWORD>(OBJID_CLIENT) == obj_id) {
if (self->accessibility_root_) {
return LresultFromObject(
IID_IAccessible, wParam,
static_cast<IAccessible*>(self->accessibility_root_));
} else {
// Notify the renderer to enable accessibility.
if (self->browser_ && self->browser_->GetHost()) {
self->browser_->GetHost()->SetAccessibilityState(STATE_ENABLED);
}
}
}
} break;
#endif
case WM_LBUTTONDOWN:
case WM_RBUTTONDOWN:
case WM_MBUTTONDOWN:
case WM_LBUTTONUP:
case WM_RBUTTONUP:
case WM_MBUTTONUP:
case WM_MOUSEMOVE:
case WM_MOUSELEAVE:
case WM_MOUSEWHEEL:
self->OnMouseEvent(message, wParam, lParam);
break;
case WM_SIZE:
self->OnSize();
break;
case WM_SETFOCUS:
case WM_KILLFOCUS:
self->OnFocus(message == WM_SETFOCUS);
break;
case WM_CAPTURECHANGED:
case WM_CANCELMODE:
self->OnCaptureLost();
break;
case WM_SYSCHAR:
case WM_SYSKEYDOWN:
case WM_SYSKEYUP:
case WM_KEYDOWN:
case WM_KEYUP:
case WM_CHAR:
self->OnKeyEvent(message, wParam, lParam);
break;
case WM_PAINT:
self->OnPaint();
return 0;
case WM_ERASEBKGND:
if (self->OnEraseBkgnd()) {
break;
}
// Don't erase the background.
return 0;
// If your application does not require Win7 support, please do consider
// using WM_POINTER* messages instead of WM_TOUCH. WM_POINTER are more
// intutive, complete and simpler to code.
// https://msdn.microsoft.com/en-us/library/hh454903(v=vs.85).aspx
case WM_TOUCH:
if (self->OnTouchEvent(message, wParam, lParam)) {
return 0;
}
break;
case WM_NCDESTROY:
// Clear the reference to |self|.
SetUserDataPtr(hWnd, nullptr);
self->hwnd_ = nullptr;
break;
}
return DefWindowProc(hWnd, message, wParam, lParam);
}
void OsrWindowWin::OnMouseEvent(UINT message, WPARAM wParam, LPARAM lParam) {
if (IsMouseEventFromTouch(message)) {
return;
}
CefRefPtr<CefBrowserHost> browser_host;
if (browser_) {
browser_host = browser_->GetHost();
}
LONG currentTime = 0;
bool cancelPreviousClick = false;
if (message == WM_LBUTTONDOWN || message == WM_RBUTTONDOWN ||
message == WM_MBUTTONDOWN || message == WM_MOUSEMOVE ||
message == WM_MOUSELEAVE) {
currentTime = GetMessageTime();
int x = GET_X_LPARAM(lParam);
int y = GET_Y_LPARAM(lParam);
cancelPreviousClick =
(abs(last_click_x_ - x) > (GetSystemMetrics(SM_CXDOUBLECLK) / 2)) ||
(abs(last_click_y_ - y) > (GetSystemMetrics(SM_CYDOUBLECLK) / 2)) ||
((currentTime - last_click_time_) > GetDoubleClickTime());
if (cancelPreviousClick &&
(message == WM_MOUSEMOVE || message == WM_MOUSELEAVE)) {
last_click_count_ = 1;
last_click_x_ = 0;
last_click_y_ = 0;
last_click_time_ = 0;
}
}
switch (message) {
case WM_LBUTTONDOWN:
case WM_RBUTTONDOWN:
case WM_MBUTTONDOWN: {
::SetCapture(hwnd_);
::SetFocus(hwnd_);
int x = GET_X_LPARAM(lParam);
int y = GET_Y_LPARAM(lParam);
if (wParam & MK_SHIFT) {
// Start rotation effect.
last_mouse_pos_.x = current_mouse_pos_.x = x;
last_mouse_pos_.y = current_mouse_pos_.y = y;
mouse_rotation_ = true;
} else {
CefBrowserHost::MouseButtonType btnType =
(message == WM_LBUTTONDOWN
? MBT_LEFT
: (message == WM_RBUTTONDOWN ? MBT_RIGHT : MBT_MIDDLE));
if (!cancelPreviousClick && (btnType == last_click_button_)) {
++last_click_count_;
} else {
last_click_count_ = 1;
last_click_x_ = x;
last_click_y_ = y;
}
last_click_time_ = currentTime;
last_click_button_ = btnType;
if (browser_host) {
CefMouseEvent mouse_event;
mouse_event.x = x;
mouse_event.y = y;
last_mouse_down_on_view_ = !IsOverPopupWidget(x, y);
ApplyPopupOffset(mouse_event.x, mouse_event.y);
DeviceToLogical(mouse_event, device_scale_factor_);
mouse_event.modifiers = GetCefMouseModifiers(wParam);
browser_host->SendMouseClickEvent(mouse_event, btnType, false,
last_click_count_);
}
}
} break;
case WM_LBUTTONUP:
case WM_RBUTTONUP:
case WM_MBUTTONUP:
if (GetCapture() == hwnd_) {
ReleaseCapture();
}
if (mouse_rotation_) {
// End rotation effect.
mouse_rotation_ = false;
render_handler_->SetSpin(0, 0);
} else {
int x = GET_X_LPARAM(lParam);
int y = GET_Y_LPARAM(lParam);
CefBrowserHost::MouseButtonType btnType =
(message == WM_LBUTTONUP
? MBT_LEFT
: (message == WM_RBUTTONUP ? MBT_RIGHT : MBT_MIDDLE));
if (browser_host) {
CefMouseEvent mouse_event;
mouse_event.x = x;
mouse_event.y = y;
if (last_mouse_down_on_view_ && IsOverPopupWidget(x, y) &&
(GetPopupXOffset() || GetPopupYOffset())) {
break;
}
ApplyPopupOffset(mouse_event.x, mouse_event.y);
DeviceToLogical(mouse_event, device_scale_factor_);
mouse_event.modifiers = GetCefMouseModifiers(wParam);
browser_host->SendMouseClickEvent(mouse_event, btnType, true,
last_click_count_);
}
}
break;
case WM_MOUSEMOVE: {
int x = GET_X_LPARAM(lParam);
int y = GET_Y_LPARAM(lParam);
if (mouse_rotation_) {
// Apply rotation effect.
current_mouse_pos_.x = x;
current_mouse_pos_.y = y;
render_handler_->IncrementSpin(
current_mouse_pos_.x - last_mouse_pos_.x,
current_mouse_pos_.y - last_mouse_pos_.y);
last_mouse_pos_.x = current_mouse_pos_.x;
last_mouse_pos_.y = current_mouse_pos_.y;
} else {
if (!mouse_tracking_) {
// Start tracking mouse leave. Required for the WM_MOUSELEAVE event to
// be generated.
TRACKMOUSEEVENT tme;
tme.cbSize = sizeof(TRACKMOUSEEVENT);
tme.dwFlags = TME_LEAVE;
tme.hwndTrack = hwnd_;
TrackMouseEvent(&tme);
mouse_tracking_ = true;
}
if (browser_host) {
CefMouseEvent mouse_event;
mouse_event.x = x;
mouse_event.y = y;
ApplyPopupOffset(mouse_event.x, mouse_event.y);
DeviceToLogical(mouse_event, device_scale_factor_);
mouse_event.modifiers = GetCefMouseModifiers(wParam);
browser_host->SendMouseMoveEvent(mouse_event, false);
}
}
break;
}
case WM_MOUSELEAVE: {
if (mouse_tracking_) {
// Stop tracking mouse leave.
TRACKMOUSEEVENT tme;
tme.cbSize = sizeof(TRACKMOUSEEVENT);
tme.dwFlags = TME_LEAVE & TME_CANCEL;
tme.hwndTrack = hwnd_;
TrackMouseEvent(&tme);
mouse_tracking_ = false;
}
if (browser_host) {
// Determine the cursor position in screen coordinates.
POINT p;
::GetCursorPos(&p);
::ScreenToClient(hwnd_, &p);
CefMouseEvent mouse_event;
mouse_event.x = p.x;
mouse_event.y = p.y;
DeviceToLogical(mouse_event, device_scale_factor_);
mouse_event.modifiers = GetCefMouseModifiers(wParam);
browser_host->SendMouseMoveEvent(mouse_event, true);
}
} break;
case WM_MOUSEWHEEL:
if (browser_host) {
POINT screen_point = {GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)};
HWND scrolled_wnd = ::WindowFromPoint(screen_point);
if (scrolled_wnd != hwnd_) {
break;
}
ScreenToClient(hwnd_, &screen_point);
int delta = GET_WHEEL_DELTA_WPARAM(wParam);
CefMouseEvent mouse_event;
mouse_event.x = screen_point.x;
mouse_event.y = screen_point.y;
ApplyPopupOffset(mouse_event.x, mouse_event.y);
DeviceToLogical(mouse_event, device_scale_factor_);
mouse_event.modifiers = GetCefMouseModifiers(wParam);
browser_host->SendMouseWheelEvent(mouse_event,
IsKeyDown(VK_SHIFT) ? delta : 0,
!IsKeyDown(VK_SHIFT) ? delta : 0);
}
break;
}
}
void OsrWindowWin::OnSize() {
// Keep |client_rect_| up to date.
::GetClientRect(hwnd_, &client_rect_);
if (browser_) {
browser_->GetHost()->WasResized();
}
}
void OsrWindowWin::OnFocus(bool setFocus) {
if (browser_) {
browser_->GetHost()->SetFocus(setFocus);
}
}
void OsrWindowWin::OnCaptureLost() {
if (mouse_rotation_) {
return;
}
if (browser_) {
browser_->GetHost()->SendCaptureLostEvent();
}
}
void OsrWindowWin::OnKeyEvent(UINT message, WPARAM wParam, LPARAM lParam) {
if (!browser_) {
return;
}
CefKeyEvent event;
event.windows_key_code = wParam;
event.native_key_code = lParam;
event.is_system_key = message == WM_SYSCHAR || message == WM_SYSKEYDOWN ||
message == WM_SYSKEYUP;
if (message == WM_KEYDOWN || message == WM_SYSKEYDOWN) {
event.type = KEYEVENT_RAWKEYDOWN;
} else if (message == WM_KEYUP || message == WM_SYSKEYUP) {
event.type = KEYEVENT_KEYUP;
} else {
event.type = KEYEVENT_CHAR;
}
event.modifiers = GetCefKeyboardModifiers(wParam, lParam);
// mimic alt-gr check behaviour from
// src/ui/events/win/events_win_utils.cc: GetModifiersFromKeyState
if ((event.type == KEYEVENT_CHAR) && IsKeyDown(VK_RMENU)) {
// reverse AltGr detection taken from PlatformKeyMap::UsesAltGraph
// instead of checking all combination for ctrl-alt, just check current char
HKL current_layout = ::GetKeyboardLayout(0);
// https://docs.microsoft.com/en-gb/windows/win32/api/winuser/nf-winuser-vkkeyscanexw
// ... high-order byte contains the shift state,
// which can be a combination of the following flag bits.
// 1 Either SHIFT key is pressed.
// 2 Either CTRL key is pressed.
// 4 Either ALT key is pressed.
SHORT scan_res = ::VkKeyScanExW(wParam, current_layout);
constexpr auto ctrlAlt = (2 | 4);
if (((scan_res >> 8) & ctrlAlt) == ctrlAlt) { // ctrl-alt pressed
event.modifiers &= ~(EVENTFLAG_CONTROL_DOWN | EVENTFLAG_ALT_DOWN);
event.modifiers |= EVENTFLAG_ALTGR_DOWN;
}
}
browser_->GetHost()->SendKeyEvent(event);
}
void OsrWindowWin::OnPaint() {
// Paint nothing here. Invalidate will cause OnPaint to be called for the
// render handler.
PAINTSTRUCT ps;
BeginPaint(hwnd_, &ps);
EndPaint(hwnd_, &ps);
if (browser_) {
browser_->GetHost()->Invalidate(PET_VIEW);
}
}
bool OsrWindowWin::OnEraseBkgnd() {
// Erase the background when the browser does not exist.
return (browser_ == nullptr);
}
bool OsrWindowWin::OnTouchEvent(UINT message, WPARAM wParam, LPARAM lParam) {
// Handle touch events on Windows.
int num_points = LOWORD(wParam);
// Chromium only supports upto 16 touch points.
if (num_points < 0 || num_points > 16) {
return false;
}
std::unique_ptr<TOUCHINPUT[]> input(new TOUCHINPUT[num_points]);
if (GetTouchInputInfo(reinterpret_cast<HTOUCHINPUT>(lParam), num_points,
input.get(), sizeof(TOUCHINPUT))) {
CefTouchEvent touch_event;
for (int i = 0; i < num_points; ++i) {
POINT point;
point.x = TOUCH_COORD_TO_PIXEL(input[i].x);
point.y = TOUCH_COORD_TO_PIXEL(input[i].y);
if (!IsWindows_8_Or_Newer()) {
// Windows 7 sends touch events for touches in the non-client area,
// whereas Windows 8 does not. In order to unify the behaviour, always
// ignore touch events in the non-client area.
LPARAM l_param_ht = MAKELPARAM(point.x, point.y);
LRESULT hittest = SendMessage(hwnd_, WM_NCHITTEST, 0, l_param_ht);
if (hittest != HTCLIENT) {
return false;
}
}
ScreenToClient(hwnd_, &point);
touch_event.x = DeviceToLogical(point.x, device_scale_factor_);
touch_event.y = DeviceToLogical(point.y, device_scale_factor_);
// Touch point identifier stays consistent in a touch contact sequence
touch_event.id = input[i].dwID;
if (input[i].dwFlags & TOUCHEVENTF_DOWN) {
touch_event.type = CEF_TET_PRESSED;
} else if (input[i].dwFlags & TOUCHEVENTF_MOVE) {
touch_event.type = CEF_TET_MOVED;
} else if (input[i].dwFlags & TOUCHEVENTF_UP) {
touch_event.type = CEF_TET_RELEASED;
}
touch_event.radius_x = 0;
touch_event.radius_y = 0;
touch_event.rotation_angle = 0;
touch_event.pressure = 0;
touch_event.modifiers = 0;
// Notify the browser of touch event
if (browser_) {
browser_->GetHost()->SendTouchEvent(touch_event);
}
}
CloseTouchInputHandle(reinterpret_cast<HTOUCHINPUT>(lParam));
return true;
}
return false;
}
bool OsrWindowWin::IsOverPopupWidget(int x, int y) const {
if (!render_handler_) {
return false;
}
return render_handler_->IsOverPopupWidget(x, y);
}
int OsrWindowWin::GetPopupXOffset() const {
return render_handler_->GetPopupXOffset();
}
int OsrWindowWin::GetPopupYOffset() const {
return render_handler_->GetPopupYOffset();
}
void OsrWindowWin::ApplyPopupOffset(int& x, int& y) const {
if (IsOverPopupWidget(x, y)) {
x += GetPopupXOffset();
y += GetPopupYOffset();
}
}
void OsrWindowWin::OnAfterCreated(CefRefPtr<CefBrowser> browser) {
CEF_REQUIRE_UI_THREAD();
DCHECK(!browser_);
browser_ = browser;
if (hwnd_) {
// The native window will already exist for non-popup browsers.
EnsureRenderHandler();
render_handler_->SetBrowser(browser);
}
if (hwnd_) {
// Show the browser window. Called asynchronously so that the browser has
// time to create associated internal objects.
CefPostTask(TID_UI, base::BindOnce(&OsrWindowWin::Show, this));
}
}
void OsrWindowWin::OnBeforeClose(CefRefPtr<CefBrowser> browser) {
CEF_REQUIRE_UI_THREAD();
// Detach |this| from the ClientHandlerOsr.
static_cast<ClientHandlerOsr*>(browser_->GetHost()->GetClient().get())
->DetachOsrDelegate();
browser_ = nullptr;
render_handler_->SetBrowser(nullptr);
Destroy();
}
bool OsrWindowWin::GetRootScreenRect(CefRefPtr<CefBrowser> browser,
CefRect& rect) {
CEF_REQUIRE_UI_THREAD();
return false;
}
void OsrWindowWin::GetViewRect(CefRefPtr<CefBrowser> browser, CefRect& rect) {
CEF_REQUIRE_UI_THREAD();
DCHECK_GT(device_scale_factor_, 0);
rect.x = rect.y = 0;
rect.width = DeviceToLogical(client_rect_.right - client_rect_.left,
device_scale_factor_);
if (rect.width == 0) {
rect.width = 1;
}
rect.height = DeviceToLogical(client_rect_.bottom - client_rect_.top,
device_scale_factor_);
if (rect.height == 0) {
rect.height = 1;
}
}
bool OsrWindowWin::GetScreenPoint(CefRefPtr<CefBrowser> browser,
int viewX,
int viewY,
int& screenX,
int& screenY) {
CEF_REQUIRE_UI_THREAD();
DCHECK_GT(device_scale_factor_, 0);
if (!::IsWindow(hwnd_)) {
return false;
}
// Convert from view DIP coordinates to screen device (pixel) coordinates.
POINT screen_pt = {LogicalToDevice(viewX, device_scale_factor_),
LogicalToDevice(viewY, device_scale_factor_)};
ClientToScreen(hwnd_, &screen_pt);
screenX = screen_pt.x;
screenY = screen_pt.y;
return true;
}
bool OsrWindowWin::GetScreenInfo(CefRefPtr<CefBrowser> browser,
CefScreenInfo& screen_info) {
CEF_REQUIRE_UI_THREAD();
DCHECK_GT(device_scale_factor_, 0);
if (!::IsWindow(hwnd_)) {
return false;
}
CefRect view_rect;
GetViewRect(browser, view_rect);
screen_info.device_scale_factor = device_scale_factor_;
// The screen info rectangles are used by the renderer to create and position
// popups. Keep popups inside the view rectangle.
screen_info.rect = view_rect;
screen_info.available_rect = view_rect;
return true;
}
void OsrWindowWin::OnPopupShow(CefRefPtr<CefBrowser> browser, bool show) {
render_handler_->OnPopupShow(browser, show);
}
void OsrWindowWin::OnPopupSize(CefRefPtr<CefBrowser> browser,
const CefRect& rect) {
render_handler_->OnPopupSize(browser,
LogicalToDevice(rect, device_scale_factor_));
}
void OsrWindowWin::OnPaint(CefRefPtr<CefBrowser> browser,
CefRenderHandler::PaintElementType type,
const CefRenderHandler::RectList& dirtyRects,
const void* buffer,
int width,
int height) {
EnsureRenderHandler();
render_handler_->OnPaint(browser, type, dirtyRects, buffer, width, height);
}
void OsrWindowWin::OnAcceleratedPaint(
CefRefPtr<CefBrowser> browser,
CefRenderHandler::PaintElementType type,
const CefRenderHandler::RectList& dirtyRects,
const CefAcceleratedPaintInfo& info) {
EnsureRenderHandler();
render_handler_->OnAcceleratedPaint(browser, type, dirtyRects, info);
}
void OsrWindowWin::OnCursorChange(CefRefPtr<CefBrowser> browser,
CefCursorHandle cursor,
cef_cursor_type_t type,
const CefCursorInfo& custom_cursor_info) {
CEF_REQUIRE_UI_THREAD();
if (!::IsWindow(hwnd_)) {
return;
}
// Change the window's cursor.
SetClassLongPtr(hwnd_, GCLP_HCURSOR,
static_cast<LONG>(reinterpret_cast<LONG_PTR>(cursor)));
SetCursor(cursor);
}
bool OsrWindowWin::StartDragging(
CefRefPtr<CefBrowser> browser,
CefRefPtr<CefDragData> drag_data,
CefRenderHandler::DragOperationsMask allowed_ops,
int x,
int y) {
CEF_REQUIRE_UI_THREAD();
#if defined(CEF_USE_ATL)
if (!drop_target_) {
return false;
}
current_drag_op_ = DRAG_OPERATION_NONE;
CefBrowserHost::DragOperationsMask result =
drop_target_->StartDragging(browser, drag_data, allowed_ops, x, y);
current_drag_op_ = DRAG_OPERATION_NONE;
POINT pt = {};
GetCursorPos(&pt);
ScreenToClient(hwnd_, &pt);
browser->GetHost()->DragSourceEndedAt(
DeviceToLogical(pt.x, device_scale_factor_),
DeviceToLogical(pt.y, device_scale_factor_), result);
browser->GetHost()->DragSourceSystemDragEnded();
return true;
#else
// Cancel the drag. The dragging implementation requires ATL support.
return false;
#endif
}
void OsrWindowWin::UpdateDragCursor(CefRefPtr<CefBrowser> browser,
CefRenderHandler::DragOperation operation) {
CEF_REQUIRE_UI_THREAD();
#if defined(CEF_USE_ATL)
current_drag_op_ = operation;
#endif
}
void OsrWindowWin::OnImeCompositionRangeChanged(
CefRefPtr<CefBrowser> browser,
const CefRange& selection_range,
const CefRenderHandler::RectList& character_bounds) {
CEF_REQUIRE_UI_THREAD();
if (ime_handler_) {
// Convert from view coordinates to device coordinates.
CefRenderHandler::RectList device_bounds;
CefRenderHandler::RectList::const_iterator it = character_bounds.begin();
for (; it != character_bounds.end(); ++it) {
device_bounds.push_back(LogicalToDevice(*it, device_scale_factor_));
}
ime_handler_->ChangeCompositionRange(selection_range, device_bounds);
}
}
void OsrWindowWin::UpdateAccessibilityTree(CefRefPtr<CefValue> value) {
CEF_REQUIRE_UI_THREAD();
#if defined(CEF_USE_ATL)
if (!accessibility_handler_) {
accessibility_handler_ =
std::make_unique<OsrAccessibilityHelper>(value, browser_);
} else {
accessibility_handler_->UpdateAccessibilityTree(value);
}
// Update |accessibility_root_| because UpdateAccessibilityTree may have
// cleared it.
OsrAXNode* root = accessibility_handler_->GetRootNode();
accessibility_root_ =
root ? root->GetNativeAccessibleObject(nullptr) : nullptr;
#endif // defined(CEF_USE_ATL)
}
void OsrWindowWin::UpdateAccessibilityLocation(CefRefPtr<CefValue> value) {
CEF_REQUIRE_UI_THREAD();
#if defined(CEF_USE_ATL)
if (accessibility_handler_) {
accessibility_handler_->UpdateAccessibilityLocation(value);
}
#endif // defined(CEF_USE_ATL)
}
#if defined(CEF_USE_ATL)
CefBrowserHost::DragOperationsMask OsrWindowWin::OnDragEnter(
CefRefPtr<CefDragData> drag_data,
CefMouseEvent ev,
CefBrowserHost::DragOperationsMask effect) {
if (browser_) {
DeviceToLogical(ev, device_scale_factor_);
browser_->GetHost()->DragTargetDragEnter(drag_data, ev, effect);
browser_->GetHost()->DragTargetDragOver(ev, effect);
}
return current_drag_op_;
}
CefBrowserHost::DragOperationsMask OsrWindowWin::OnDragOver(
CefMouseEvent ev,
CefBrowserHost::DragOperationsMask effect) {
if (browser_) {
DeviceToLogical(ev, device_scale_factor_);
browser_->GetHost()->DragTargetDragOver(ev, effect);
}
return current_drag_op_;
}
void OsrWindowWin::OnDragLeave() {
if (browser_) {
browser_->GetHost()->DragTargetDragLeave();
}
}
CefBrowserHost::DragOperationsMask OsrWindowWin::OnDrop(
CefMouseEvent ev,
CefBrowserHost::DragOperationsMask effect) {
if (browser_) {
DeviceToLogical(ev, device_scale_factor_);
browser_->GetHost()->DragTargetDragOver(ev, effect);
browser_->GetHost()->DragTargetDrop(ev);
}
return current_drag_op_;
}
#endif // defined(CEF_USE_ATL)
void OsrWindowWin::EnsureRenderHandler() {
CEF_REQUIRE_UI_THREAD();
if (!render_handler_) {
if (settings_.shared_texture_enabled) {
// Try to initialize D3D11 rendering.
auto render_handler = new OsrRenderHandlerWinD3D11(settings_, hwnd_);
if (render_handler->Initialize(browser_,
client_rect_.right - client_rect_.left,
client_rect_.bottom - client_rect_.top)) {
render_handler_.reset(render_handler);
} else {
LOG(ERROR) << "Failed to initialize D3D11 rendering.";
delete render_handler;
}
}
// Fall back to GL rendering.
if (!render_handler_) {
auto render_handler = new OsrRenderHandlerWinGL(settings_, hwnd_);
render_handler->Initialize(browser_);
render_handler_.reset(render_handler);
}
}
}
} // namespace client