// 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/root_window_win.h" #include #include "include/base/cef_bind.h" #include "include/base/cef_build.h" #include "include/cef_app.h" #include "tests/cefclient/browser/browser_window_osr_win.h" #include "tests/cefclient/browser/browser_window_std_win.h" #include "tests/cefclient/browser/main_context.h" #include "tests/cefclient/browser/resource.h" #include "tests/cefclient/browser/temp_window.h" #include "tests/cefclient/browser/window_test_runner_win.h" #include "tests/shared/browser/geometry_util.h" #include "tests/shared/browser/main_message_loop.h" #include "tests/shared/browser/util_win.h" #include "tests/shared/common/client_switches.h" #define MAX_URL_LENGTH 255 #define BUTTON_WIDTH 72 #define URLBAR_HEIGHT 24 namespace client { namespace { // Message handler for the About box. INT_PTR CALLBACK AboutWndProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) { UNREFERENCED_PARAMETER(lParam); switch (message) { case WM_INITDIALOG: return TRUE; case WM_COMMAND: if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL) { EndDialog(hDlg, LOWORD(wParam)); return TRUE; } break; } return FALSE; } // Returns true if the process is per monitor DPI aware. bool IsProcessPerMonitorDpiAware() { enum class PerMonitorDpiAware { UNKNOWN = 0, PER_MONITOR_DPI_UNAWARE, PER_MONITOR_DPI_AWARE, }; static PerMonitorDpiAware per_monitor_dpi_aware = PerMonitorDpiAware::UNKNOWN; if (per_monitor_dpi_aware == PerMonitorDpiAware::UNKNOWN) { per_monitor_dpi_aware = PerMonitorDpiAware::PER_MONITOR_DPI_UNAWARE; HMODULE shcore_dll = ::LoadLibrary(L"shcore.dll"); if (shcore_dll) { typedef HRESULT(WINAPI * GetProcessDpiAwarenessPtr)( HANDLE, PROCESS_DPI_AWARENESS*); GetProcessDpiAwarenessPtr func_ptr = reinterpret_cast( ::GetProcAddress(shcore_dll, "GetProcessDpiAwareness")); if (func_ptr) { PROCESS_DPI_AWARENESS awareness; if (SUCCEEDED(func_ptr(nullptr, &awareness)) && awareness == PROCESS_PER_MONITOR_DPI_AWARE) per_monitor_dpi_aware = PerMonitorDpiAware::PER_MONITOR_DPI_AWARE; } } } return per_monitor_dpi_aware == PerMonitorDpiAware::PER_MONITOR_DPI_AWARE; } // DPI value for 1x scale factor. #define DPI_1X 96.0f float GetWindowScaleFactor(HWND hwnd) { if (hwnd && IsProcessPerMonitorDpiAware()) { typedef UINT(WINAPI * GetDpiForWindowPtr)(HWND); static GetDpiForWindowPtr func_ptr = reinterpret_cast( GetProcAddress(GetModuleHandle(L"user32.dll"), "GetDpiForWindow")); if (func_ptr) return static_cast(func_ptr(hwnd)) / DPI_1X; } return client::GetDeviceScaleFactor(); } int GetButtonWidth(HWND hwnd) { return LogicalToDevice(BUTTON_WIDTH, GetWindowScaleFactor(hwnd)); } int GetURLBarHeight(HWND hwnd) { return LogicalToDevice(URLBAR_HEIGHT, GetWindowScaleFactor(hwnd)); } } // namespace RootWindowWin::RootWindowWin() : with_controls_(false), always_on_top_(false), with_osr_(false), with_extension_(false), is_popup_(false), start_rect_(), initialized_(false), hwnd_(NULL), draggable_region_(NULL), font_(NULL), font_height_(0), back_hwnd_(NULL), forward_hwnd_(NULL), reload_hwnd_(NULL), stop_hwnd_(NULL), edit_hwnd_(NULL), edit_wndproc_old_(NULL), find_hwnd_(NULL), find_message_id_(0), find_wndproc_old_(NULL), find_state_(), find_next_(false), find_match_case_last_(false), window_destroyed_(false), browser_destroyed_(false), called_enable_non_client_dpi_scaling_(false) { find_buff_[0] = 0; // Create a HRGN representing the draggable window area. draggable_region_ = ::CreateRectRgn(0, 0, 0, 0); } RootWindowWin::~RootWindowWin() { REQUIRE_MAIN_THREAD(); ::DeleteObject(draggable_region_); ::DeleteObject(font_); // The window and browser should already have been destroyed. DCHECK(window_destroyed_); DCHECK(browser_destroyed_); } void RootWindowWin::Init(RootWindow::Delegate* delegate, const RootWindowConfig& config, const CefBrowserSettings& settings) { DCHECK(delegate); DCHECK(!initialized_); delegate_ = delegate; with_controls_ = config.with_controls; always_on_top_ = config.always_on_top; with_osr_ = config.with_osr; with_extension_ = config.with_extension; start_rect_.left = config.bounds.x; start_rect_.top = config.bounds.y; start_rect_.right = config.bounds.x + config.bounds.width; start_rect_.bottom = config.bounds.y + config.bounds.height; CreateBrowserWindow(config.url); initialized_ = true; // Create the native root window on the main thread. if (CURRENTLY_ON_MAIN_THREAD()) { CreateRootWindow(settings, config.initially_hidden); } else { MAIN_POST_CLOSURE(base::Bind(&RootWindowWin::CreateRootWindow, this, settings, config.initially_hidden)); } } void RootWindowWin::InitAsPopup(RootWindow::Delegate* delegate, bool with_controls, bool with_osr, const CefPopupFeatures& popupFeatures, CefWindowInfo& windowInfo, CefRefPtr& client, CefBrowserSettings& settings) { CEF_REQUIRE_UI_THREAD(); DCHECK(delegate); DCHECK(!initialized_); delegate_ = delegate; with_controls_ = with_controls; with_osr_ = with_osr; is_popup_ = true; if (popupFeatures.xSet) start_rect_.left = popupFeatures.x; if (popupFeatures.ySet) start_rect_.top = popupFeatures.y; if (popupFeatures.widthSet) start_rect_.right = start_rect_.left + popupFeatures.width; if (popupFeatures.heightSet) start_rect_.bottom = start_rect_.top + popupFeatures.height; CreateBrowserWindow(std::string()); initialized_ = true; // The new popup is initially parented to a temporary window. The native root // window will be created after the browser is created and the popup window // will be re-parented to it at that time. browser_window_->GetPopupConfig(TempWindow::GetWindowHandle(), windowInfo, client, settings); } void RootWindowWin::Show(ShowMode mode) { REQUIRE_MAIN_THREAD(); if (!hwnd_) return; int nCmdShow = SW_SHOWNORMAL; switch (mode) { case ShowMinimized: nCmdShow = SW_SHOWMINIMIZED; break; case ShowMaximized: nCmdShow = SW_SHOWMAXIMIZED; break; case ShowNoActivate: nCmdShow = SW_SHOWNOACTIVATE; break; default: break; } ShowWindow(hwnd_, nCmdShow); UpdateWindow(hwnd_); } void RootWindowWin::Hide() { REQUIRE_MAIN_THREAD(); if (hwnd_) ShowWindow(hwnd_, SW_HIDE); } void RootWindowWin::SetBounds(int x, int y, size_t width, size_t height) { REQUIRE_MAIN_THREAD(); if (hwnd_) { SetWindowPos(hwnd_, NULL, x, y, static_cast(width), static_cast(height), SWP_NOZORDER); } } void RootWindowWin::Close(bool force) { REQUIRE_MAIN_THREAD(); if (hwnd_) { if (force) DestroyWindow(hwnd_); else PostMessage(hwnd_, WM_CLOSE, 0, 0); } } void RootWindowWin::SetDeviceScaleFactor(float device_scale_factor) { REQUIRE_MAIN_THREAD(); if (browser_window_ && with_osr_) browser_window_->SetDeviceScaleFactor(device_scale_factor); } float RootWindowWin::GetDeviceScaleFactor() const { REQUIRE_MAIN_THREAD(); if (browser_window_ && with_osr_) return browser_window_->GetDeviceScaleFactor(); NOTREACHED(); return 0.0f; } CefRefPtr RootWindowWin::GetBrowser() const { REQUIRE_MAIN_THREAD(); if (browser_window_) return browser_window_->GetBrowser(); return nullptr; } ClientWindowHandle RootWindowWin::GetWindowHandle() const { REQUIRE_MAIN_THREAD(); return hwnd_; } bool RootWindowWin::WithWindowlessRendering() const { REQUIRE_MAIN_THREAD(); return with_osr_; } bool RootWindowWin::WithExtension() const { REQUIRE_MAIN_THREAD(); return with_extension_; } void RootWindowWin::CreateBrowserWindow(const std::string& startup_url) { if (with_osr_) { OsrRendererSettings settings = {}; MainContext::Get()->PopulateOsrSettings(&settings); browser_window_.reset(new BrowserWindowOsrWin(this, startup_url, settings)); } else { browser_window_.reset(new BrowserWindowStdWin(this, startup_url)); } } void RootWindowWin::CreateRootWindow(const CefBrowserSettings& settings, bool initially_hidden) { REQUIRE_MAIN_THREAD(); DCHECK(!hwnd_); HINSTANCE hInstance = GetModuleHandle(NULL); // Load strings from the resource file. const std::wstring& window_title = GetResourceString(IDS_APP_TITLE); const std::wstring& window_class = GetResourceString(IDC_CEFCLIENT); const cef_color_t background_color = MainContext::Get()->GetBackgroundColor(); const HBRUSH background_brush = CreateSolidBrush( RGB(CefColorGetR(background_color), CefColorGetG(background_color), CefColorGetB(background_color))); // Register the window class. RegisterRootClass(hInstance, window_class, background_brush); // Register the message used with the find dialog. find_message_id_ = RegisterWindowMessage(FINDMSGSTRING); CHECK(find_message_id_); CefRefPtr command_line = CefCommandLine::GetGlobalCommandLine(); const bool no_activate = command_line->HasSwitch(switches::kNoActivate); const DWORD dwStyle = WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN; DWORD dwExStyle = always_on_top_ ? WS_EX_TOPMOST : 0; if (no_activate) { // Don't activate the browser window on creation. dwExStyle |= WS_EX_NOACTIVATE; } int x, y, width, height; if (::IsRectEmpty(&start_rect_)) { // Use the default window position/size. x = y = width = height = CW_USEDEFAULT; } else { // Adjust the window size to account for window frame and controls. RECT window_rect = start_rect_; ::AdjustWindowRectEx(&window_rect, dwStyle, with_controls_, dwExStyle); x = start_rect_.left; y = start_rect_.top; width = window_rect.right - window_rect.left; height = window_rect.bottom - window_rect.top; } browser_settings_ = settings; // Create the main window initially hidden. CreateWindowEx(dwExStyle, window_class.c_str(), window_title.c_str(), dwStyle, x, y, width, height, NULL, NULL, hInstance, this); CHECK(hwnd_); if (!called_enable_non_client_dpi_scaling_ && IsProcessPerMonitorDpiAware()) { // This call gets Windows to scale the non-client area when WM_DPICHANGED // is fired on Windows versions < 10.0.14393.0. // Derived signature; not available in headers. typedef LRESULT(WINAPI * EnableChildWindowDpiMessagePtr)(HWND, BOOL); static EnableChildWindowDpiMessagePtr func_ptr = reinterpret_cast(GetProcAddress( GetModuleHandle(L"user32.dll"), "EnableChildWindowDpiMessage")); if (func_ptr) func_ptr(hwnd_, TRUE); } if (!initially_hidden) { // Show this window. Show(no_activate ? ShowNoActivate : ShowNormal); } } // static void RootWindowWin::RegisterRootClass(HINSTANCE hInstance, const std::wstring& window_class, 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_HREDRAW | CS_VREDRAW; wcex.lpfnWndProc = RootWndProc; wcex.cbClsExtra = 0; wcex.cbWndExtra = 0; wcex.hInstance = hInstance; wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_CEFCLIENT)); wcex.hCursor = LoadCursor(NULL, IDC_ARROW); wcex.hbrBackground = background_brush; wcex.lpszMenuName = MAKEINTRESOURCE(IDC_CEFCLIENT); wcex.lpszClassName = window_class.c_str(); wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL)); RegisterClassEx(&wcex); } // static LRESULT CALLBACK RootWindowWin::EditWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { REQUIRE_MAIN_THREAD(); RootWindowWin* self = GetUserDataPtr(hWnd); DCHECK(self); DCHECK(hWnd == self->edit_hwnd_); switch (message) { case WM_CHAR: if (wParam == VK_RETURN) { // When the user hits the enter key load the URL. CefRefPtr browser = self->GetBrowser(); if (browser) { wchar_t strPtr[MAX_URL_LENGTH + 1] = {0}; *((LPWORD)strPtr) = MAX_URL_LENGTH; LRESULT strLen = SendMessage(hWnd, EM_GETLINE, 0, (LPARAM)strPtr); if (strLen > 0) { strPtr[strLen] = 0; browser->GetMainFrame()->LoadURL(strPtr); } } return 0; } break; case WM_NCDESTROY: // Clear the reference to |self|. SetUserDataPtr(hWnd, NULL); self->edit_hwnd_ = NULL; break; } return CallWindowProc(self->edit_wndproc_old_, hWnd, message, wParam, lParam); } // static LRESULT CALLBACK RootWindowWin::FindWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { REQUIRE_MAIN_THREAD(); RootWindowWin* self = GetUserDataPtr(hWnd); DCHECK(self); DCHECK(hWnd == self->find_hwnd_); switch (message) { case WM_ACTIVATE: // Set this dialog as current when activated. MainMessageLoop::Get()->SetCurrentModelessDialog(wParam == 0 ? NULL : hWnd); return FALSE; case WM_NCDESTROY: // Clear the reference to |self|. SetUserDataPtr(hWnd, NULL); self->find_hwnd_ = NULL; break; } return CallWindowProc(self->find_wndproc_old_, hWnd, message, wParam, lParam); } // static LRESULT CALLBACK RootWindowWin::RootWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { REQUIRE_MAIN_THREAD(); RootWindowWin* self = nullptr; if (message != WM_NCCREATE) { self = GetUserDataPtr(hWnd); if (!self) return DefWindowProc(hWnd, message, wParam, lParam); DCHECK_EQ(hWnd, self->hwnd_); } if (self && message == self->find_message_id_) { // Message targeting the find dialog. LPFINDREPLACE lpfr = reinterpret_cast(lParam); CHECK(lpfr == &self->find_state_); self->OnFindEvent(); return 0; } // Callback for the main window switch (message) { case WM_COMMAND: if (self->OnCommand(LOWORD(wParam))) return 0; break; 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(static_cast(lParam)); // Accessibility readers will send an OBJID_CLIENT message. if (static_cast(OBJID_CLIENT) == obj_id) { if (self->GetBrowser() && self->GetBrowser()->GetHost()) self->GetBrowser()->GetHost()->SetAccessibilityState(STATE_ENABLED); } } break; case WM_PAINT: self->OnPaint(); return 0; case WM_ACTIVATE: self->OnActivate(LOWORD(wParam) != WA_INACTIVE); // Allow DefWindowProc to set keyboard focus. break; case WM_SETFOCUS: self->OnFocus(); return 0; case WM_SIZE: self->OnSize(wParam == SIZE_MINIMIZED); break; case WM_MOVING: case WM_MOVE: self->OnMove(); return 0; case WM_DPICHANGED: self->OnDpiChanged(wParam, lParam); break; case WM_ERASEBKGND: if (self->OnEraseBkgnd()) break; // Don't erase the background. return 0; case WM_ENTERMENULOOP: if (!wParam) { // Entering the menu loop for the application menu. CefSetOSModalLoop(true); } break; case WM_EXITMENULOOP: if (!wParam) { // Exiting the menu loop for the application menu. CefSetOSModalLoop(false); } break; case WM_CLOSE: if (self->OnClose()) return 0; // Cancel the close. break; case WM_NCHITTEST: { LRESULT hit = DefWindowProc(hWnd, message, wParam, lParam); if (hit == HTCLIENT) { POINTS points = MAKEPOINTS(lParam); POINT point = {points.x, points.y}; ::ScreenToClient(hWnd, &point); if (::PtInRegion(self->draggable_region_, point.x, point.y)) { // If cursor is inside a draggable region return HTCAPTION to allow // dragging. return HTCAPTION; } } return hit; } case WM_NCCREATE: { CREATESTRUCT* cs = reinterpret_cast(lParam); self = reinterpret_cast(cs->lpCreateParams); DCHECK(self); // Associate |self| with the main window. SetUserDataPtr(hWnd, self); self->hwnd_ = hWnd; self->OnNCCreate(cs); } break; case WM_CREATE: self->OnCreate(reinterpret_cast(lParam)); break; case WM_NCDESTROY: // Clear the reference to |self|. SetUserDataPtr(hWnd, NULL); self->hwnd_ = NULL; self->OnDestroyed(); break; } return DefWindowProc(hWnd, message, wParam, lParam); } void RootWindowWin::OnPaint() { PAINTSTRUCT ps; BeginPaint(hwnd_, &ps); EndPaint(hwnd_, &ps); } void RootWindowWin::OnFocus() { // Selecting "Close window" from the task bar menu may send a focus // notification even though the window is currently disabled (e.g. while a // modal JS dialog is displayed). if (browser_window_ && ::IsWindowEnabled(hwnd_)) browser_window_->SetFocus(true); } void RootWindowWin::OnActivate(bool active) { if (active) delegate_->OnRootWindowActivated(this); } void RootWindowWin::OnSize(bool minimized) { if (minimized) { // Notify the browser window that it was hidden and do nothing further. if (browser_window_) browser_window_->Hide(); return; } if (browser_window_) browser_window_->Show(); RECT rect; GetClientRect(hwnd_, &rect); if (with_controls_ && edit_hwnd_) { const int button_width = GetButtonWidth(hwnd_); const int urlbar_height = GetURLBarHeight(hwnd_); const int font_height = LogicalToDevice(14, GetWindowScaleFactor(hwnd_)); if (font_height != font_height_) { font_height_ = font_height; if (font_) { DeleteObject(font_); } // Create a scaled font. font_ = ::CreateFont(-font_height, 0, 0, 0, FW_DONTCARE, FALSE, FALSE, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_DONTCARE, L"Arial"); SendMessage(back_hwnd_, WM_SETFONT, reinterpret_cast(font_), TRUE); SendMessage(forward_hwnd_, WM_SETFONT, reinterpret_cast(font_), TRUE); SendMessage(reload_hwnd_, WM_SETFONT, reinterpret_cast(font_), TRUE); SendMessage(stop_hwnd_, WM_SETFONT, reinterpret_cast(font_), TRUE); SendMessage(edit_hwnd_, WM_SETFONT, reinterpret_cast(font_), TRUE); } // Resize the window and address bar to match the new frame size. rect.top += urlbar_height; int x_offset = rect.left; // |browser_hwnd| may be NULL if the browser has not yet been created. HWND browser_hwnd = NULL; if (browser_window_) browser_hwnd = browser_window_->GetWindowHandle(); // Resize all controls. HDWP hdwp = BeginDeferWindowPos(browser_hwnd ? 6 : 5); hdwp = DeferWindowPos(hdwp, back_hwnd_, NULL, x_offset, 0, button_width, urlbar_height, SWP_NOZORDER); x_offset += button_width; hdwp = DeferWindowPos(hdwp, forward_hwnd_, NULL, x_offset, 0, button_width, urlbar_height, SWP_NOZORDER); x_offset += button_width; hdwp = DeferWindowPos(hdwp, reload_hwnd_, NULL, x_offset, 0, button_width, urlbar_height, SWP_NOZORDER); x_offset += button_width; hdwp = DeferWindowPos(hdwp, stop_hwnd_, NULL, x_offset, 0, button_width, urlbar_height, SWP_NOZORDER); x_offset += button_width; hdwp = DeferWindowPos(hdwp, edit_hwnd_, NULL, x_offset, 0, rect.right - x_offset, urlbar_height, SWP_NOZORDER); if (browser_hwnd) { hdwp = DeferWindowPos(hdwp, browser_hwnd, NULL, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, SWP_NOZORDER); } BOOL result = EndDeferWindowPos(hdwp); ALLOW_UNUSED_LOCAL(result); DCHECK(result); } else if (browser_window_) { // Size the browser window to the whole client area. browser_window_->SetBounds(0, 0, rect.right, rect.bottom); } } void RootWindowWin::OnMove() { // Notify the browser of move events so that popup windows are displayed // in the correct location and dismissed when the window moves. CefRefPtr browser = GetBrowser(); if (browser) browser->GetHost()->NotifyMoveOrResizeStarted(); } void RootWindowWin::OnDpiChanged(WPARAM wParam, LPARAM lParam) { if (LOWORD(wParam) != HIWORD(wParam)) { NOTIMPLEMENTED() << "Received non-square scaling factors"; return; } if (browser_window_ && with_osr_) { // Scale factor for the new display. const float display_scale_factor = static_cast(LOWORD(wParam)) / DPI_1X; browser_window_->SetDeviceScaleFactor(display_scale_factor); } // Suggested size and position of the current window scaled for the new DPI. const RECT* rect = reinterpret_cast(lParam); SetBounds(rect->left, rect->top, rect->right - rect->left, rect->bottom - rect->top); } bool RootWindowWin::OnEraseBkgnd() { // Erase the background when the browser does not exist. return (GetBrowser() == nullptr); } bool RootWindowWin::OnCommand(UINT id) { if (id >= ID_TESTS_FIRST && id <= ID_TESTS_LAST) { delegate_->OnTest(this, id); return true; } switch (id) { case IDM_ABOUT: OnAbout(); return true; case IDM_EXIT: delegate_->OnExit(this); return true; case ID_FIND: OnFind(); return true; case IDC_NAV_BACK: // Back button if (CefRefPtr browser = GetBrowser()) browser->GoBack(); return true; case IDC_NAV_FORWARD: // Forward button if (CefRefPtr browser = GetBrowser()) browser->GoForward(); return true; case IDC_NAV_RELOAD: // Reload button if (CefRefPtr browser = GetBrowser()) browser->Reload(); return true; case IDC_NAV_STOP: // Stop button if (CefRefPtr browser = GetBrowser()) browser->StopLoad(); return true; } return false; } void RootWindowWin::OnFind() { if (find_hwnd_) { // Give focus to the existing find dialog. ::SetFocus(find_hwnd_); return; } // Configure dialog state. ZeroMemory(&find_state_, sizeof(find_state_)); find_state_.lStructSize = sizeof(find_state_); find_state_.hwndOwner = hwnd_; find_state_.lpstrFindWhat = find_buff_; find_state_.wFindWhatLen = sizeof(find_buff_); find_state_.Flags = FR_HIDEWHOLEWORD | FR_DOWN; // Create the dialog. find_hwnd_ = FindText(&find_state_); // Override the dialog's window procedure. find_wndproc_old_ = SetWndProcPtr(find_hwnd_, FindWndProc); // Associate |self| with the dialog. SetUserDataPtr(find_hwnd_, this); } void RootWindowWin::OnFindEvent() { CefRefPtr browser = GetBrowser(); if (find_state_.Flags & FR_DIALOGTERM) { // The find dialog box has been dismissed so invalidate the handle and // reset the search results. if (browser) { browser->GetHost()->StopFinding(true); find_what_last_.clear(); find_next_ = false; } } else if ((find_state_.Flags & FR_FINDNEXT) && browser) { // Search for the requested string. bool match_case = ((find_state_.Flags & FR_MATCHCASE) ? true : false); const std::wstring& find_what = find_buff_; if (match_case != find_match_case_last_ || find_what != find_what_last_) { // The search string has changed, so reset the search results. if (!find_what.empty()) { browser->GetHost()->StopFinding(true); find_next_ = false; } find_match_case_last_ = match_case; find_what_last_ = find_buff_; } browser->GetHost()->Find(0, find_what, (find_state_.Flags & FR_DOWN) ? true : false, match_case, find_next_); if (!find_next_) find_next_ = true; } } void RootWindowWin::OnAbout() { // Show the about box. DialogBox(GetModuleHandle(NULL), MAKEINTRESOURCE(IDD_ABOUTBOX), hwnd_, AboutWndProc); } void RootWindowWin::OnNCCreate(LPCREATESTRUCT lpCreateStruct) { if (IsProcessPerMonitorDpiAware()) { // This call gets Windows to scale the non-client area when WM_DPICHANGED // is fired on Windows versions >= 10.0.14393.0. typedef BOOL(WINAPI * EnableNonClientDpiScalingPtr)(HWND); static EnableNonClientDpiScalingPtr func_ptr = reinterpret_cast(GetProcAddress( GetModuleHandle(L"user32.dll"), "EnableNonClientDpiScaling")); called_enable_non_client_dpi_scaling_ = !!(func_ptr && func_ptr(hwnd_)); } } void RootWindowWin::OnCreate(LPCREATESTRUCT lpCreateStruct) { const HINSTANCE hInstance = lpCreateStruct->hInstance; RECT rect; GetClientRect(hwnd_, &rect); if (with_controls_) { // Create the child controls. int x_offset = 0; const int button_width = GetButtonWidth(hwnd_); const int urlbar_height = GetURLBarHeight(hwnd_); back_hwnd_ = CreateWindow( L"BUTTON", L"Back", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | WS_DISABLED, x_offset, 0, button_width, urlbar_height, hwnd_, reinterpret_cast(IDC_NAV_BACK), hInstance, 0); CHECK(back_hwnd_); x_offset += button_width; forward_hwnd_ = CreateWindow(L"BUTTON", L"Forward", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | WS_DISABLED, x_offset, 0, button_width, urlbar_height, hwnd_, reinterpret_cast(IDC_NAV_FORWARD), hInstance, 0); CHECK(forward_hwnd_); x_offset += button_width; reload_hwnd_ = CreateWindow(L"BUTTON", L"Reload", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | WS_DISABLED, x_offset, 0, button_width, urlbar_height, hwnd_, reinterpret_cast(IDC_NAV_RELOAD), hInstance, 0); CHECK(reload_hwnd_); x_offset += button_width; stop_hwnd_ = CreateWindow( L"BUTTON", L"Stop", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | WS_DISABLED, x_offset, 0, button_width, urlbar_height, hwnd_, reinterpret_cast(IDC_NAV_STOP), hInstance, 0); CHECK(stop_hwnd_); x_offset += button_width; edit_hwnd_ = CreateWindow(L"EDIT", 0, WS_CHILD | WS_VISIBLE | WS_BORDER | ES_LEFT | ES_AUTOVSCROLL | ES_AUTOHSCROLL | WS_DISABLED, x_offset, 0, rect.right - button_width * 4, urlbar_height, hwnd_, 0, hInstance, 0); CHECK(edit_hwnd_); // Override the edit control's window procedure. edit_wndproc_old_ = SetWndProcPtr(edit_hwnd_, EditWndProc); // Associate |this| with the edit window. SetUserDataPtr(edit_hwnd_, this); rect.top += urlbar_height; if (!with_osr_) { // Remove the menu items that are only used with OSR. HMENU hMenu = ::GetMenu(hwnd_); if (hMenu) { HMENU hTestMenu = ::GetSubMenu(hMenu, 2); if (hTestMenu) { ::RemoveMenu(hTestMenu, ID_TESTS_OSR_FPS, MF_BYCOMMAND); ::RemoveMenu(hTestMenu, ID_TESTS_OSR_DSF, MF_BYCOMMAND); } } } } else { // No controls so also remove the default menu. ::SetMenu(hwnd_, NULL); } const float device_scale_factor = GetWindowScaleFactor(hwnd_); if (with_osr_) { browser_window_->SetDeviceScaleFactor(device_scale_factor); } if (!is_popup_) { // Create the browser window. CefRect cef_rect(rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top); browser_window_->CreateBrowser(hwnd_, cef_rect, browser_settings_, nullptr, delegate_->GetRequestContext(this)); } else { // With popups we already have a browser window. Parent the browser window // to the root window and show it in the correct location. browser_window_->ShowPopup(hwnd_, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top); } } bool RootWindowWin::OnClose() { if (browser_window_ && !browser_window_->IsClosing()) { CefRefPtr browser = GetBrowser(); if (browser) { // Notify the browser window that we would like to close it. This // will result in a call to ClientHandler::DoClose() if the // JavaScript 'onbeforeunload' event handler allows it. browser->GetHost()->CloseBrowser(false); // Cancel the close. return true; } } // Allow the close. return false; } void RootWindowWin::OnDestroyed() { window_destroyed_ = true; NotifyDestroyedIfDone(); } void RootWindowWin::OnBrowserCreated(CefRefPtr browser) { REQUIRE_MAIN_THREAD(); if (is_popup_) { // For popup browsers create the root window once the browser has been // created. CreateRootWindow(CefBrowserSettings(), false); } else { // Make sure the browser is sized correctly. OnSize(false); } delegate_->OnBrowserCreated(this, browser); } void RootWindowWin::OnBrowserWindowDestroyed() { REQUIRE_MAIN_THREAD(); browser_window_.reset(); if (!window_destroyed_) { // The browser was destroyed first. This could be due to the use of // off-screen rendering or execution of JavaScript window.close(). // Close the RootWindow. Close(true); } browser_destroyed_ = true; NotifyDestroyedIfDone(); } void RootWindowWin::OnSetAddress(const std::string& url) { REQUIRE_MAIN_THREAD(); if (edit_hwnd_) SetWindowText(edit_hwnd_, CefString(url).ToWString().c_str()); } void RootWindowWin::OnSetTitle(const std::string& title) { REQUIRE_MAIN_THREAD(); if (hwnd_) SetWindowText(hwnd_, CefString(title).ToWString().c_str()); } void RootWindowWin::OnSetFullscreen(bool fullscreen) { REQUIRE_MAIN_THREAD(); CefRefPtr browser = GetBrowser(); if (browser) { scoped_ptr test_runner( new window_test::WindowTestRunnerWin()); if (fullscreen) test_runner->Maximize(browser); else test_runner->Restore(browser); } } void RootWindowWin::OnAutoResize(const CefSize& new_size) { REQUIRE_MAIN_THREAD(); if (!hwnd_) return; int new_width = new_size.width; // Make the window wide enough to drag by the top menu bar. if (new_width < 200) new_width = 200; const float device_scale_factor = GetWindowScaleFactor(hwnd_); RECT rect = {0, 0, LogicalToDevice(new_width, device_scale_factor), LogicalToDevice(new_size.height, device_scale_factor)}; DWORD style = GetWindowLong(hwnd_, GWL_STYLE); DWORD ex_style = GetWindowLong(hwnd_, GWL_EXSTYLE); bool has_menu = !(style & WS_CHILD) && (GetMenu(hwnd_) != 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. // Also show the window if it's not currently visible. SetWindowPos(hwnd_, NULL, 0, 0, rect.right - rect.left, rect.bottom - rect.top, SWP_NOZORDER | SWP_NOMOVE | SWP_NOACTIVATE | SWP_SHOWWINDOW); } void RootWindowWin::OnSetLoadingState(bool isLoading, bool canGoBack, bool canGoForward) { REQUIRE_MAIN_THREAD(); if (with_controls_) { EnableWindow(back_hwnd_, canGoBack); EnableWindow(forward_hwnd_, canGoForward); EnableWindow(reload_hwnd_, !isLoading); EnableWindow(stop_hwnd_, isLoading); EnableWindow(edit_hwnd_, TRUE); } if (!isLoading && GetWindowLongPtr(hwnd_, GWL_EXSTYLE) & WS_EX_NOACTIVATE) { // Done with the initial navigation. Remove the WS_EX_NOACTIVATE style so // that future mouse clicks inside the browser correctly activate and focus // the window. For the top-level window removing this style causes Windows // to display the task bar button. SetWindowLongPtr(hwnd_, GWL_EXSTYLE, GetWindowLongPtr(hwnd_, GWL_EXSTYLE) & ~WS_EX_NOACTIVATE); if (browser_window_) { HWND browser_hwnd = browser_window_->GetWindowHandle(); SetWindowLongPtr( browser_hwnd, GWL_EXSTYLE, GetWindowLongPtr(browser_hwnd, GWL_EXSTYLE) & ~WS_EX_NOACTIVATE); } } } namespace { LPCWSTR kParentWndProc = L"CefParentWndProc"; LPCWSTR kDraggableRegion = L"CefDraggableRegion"; LRESULT CALLBACK SubclassedWindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { WNDPROC hParentWndProc = reinterpret_cast(::GetPropW(hWnd, kParentWndProc)); HRGN hRegion = reinterpret_cast(::GetPropW(hWnd, kDraggableRegion)); if (message == WM_NCHITTEST) { LRESULT hit = CallWindowProc(hParentWndProc, hWnd, message, wParam, lParam); if (hit == HTCLIENT) { POINTS points = MAKEPOINTS(lParam); POINT point = {points.x, points.y}; ::ScreenToClient(hWnd, &point); if (::PtInRegion(hRegion, point.x, point.y)) { // Let the parent window handle WM_NCHITTEST by returning HTTRANSPARENT // in child windows. return HTTRANSPARENT; } } return hit; } return CallWindowProc(hParentWndProc, hWnd, message, wParam, lParam); } void SubclassWindow(HWND hWnd, HRGN hRegion) { HANDLE hParentWndProc = ::GetPropW(hWnd, kParentWndProc); if (hParentWndProc) { return; } SetLastError(0); LONG_PTR hOldWndProc = SetWindowLongPtr( hWnd, GWLP_WNDPROC, reinterpret_cast(SubclassedWindowProc)); if (hOldWndProc == 0 && GetLastError() != ERROR_SUCCESS) { return; } ::SetPropW(hWnd, kParentWndProc, reinterpret_cast(hOldWndProc)); ::SetPropW(hWnd, kDraggableRegion, reinterpret_cast(hRegion)); } void UnSubclassWindow(HWND hWnd) { LONG_PTR hParentWndProc = reinterpret_cast(::GetPropW(hWnd, kParentWndProc)); if (hParentWndProc) { LONG_PTR hPreviousWndProc = SetWindowLongPtr(hWnd, GWLP_WNDPROC, hParentWndProc); ALLOW_UNUSED_LOCAL(hPreviousWndProc); DCHECK_EQ(hPreviousWndProc, reinterpret_cast(SubclassedWindowProc)); } ::RemovePropW(hWnd, kParentWndProc); ::RemovePropW(hWnd, kDraggableRegion); } BOOL CALLBACK SubclassWindowsProc(HWND hwnd, LPARAM lParam) { SubclassWindow(hwnd, reinterpret_cast(lParam)); return TRUE; } BOOL CALLBACK UnSubclassWindowsProc(HWND hwnd, LPARAM lParam) { UnSubclassWindow(hwnd); return TRUE; } } // namespace void RootWindowWin::OnSetDraggableRegions( const std::vector& regions) { REQUIRE_MAIN_THREAD(); // Reset draggable region. ::SetRectRgn(draggable_region_, 0, 0, 0, 0); // Determine new draggable region. std::vector::const_iterator it = regions.begin(); for (; it != regions.end(); ++it) { HRGN region = ::CreateRectRgn(it->bounds.x, it->bounds.y, it->bounds.x + it->bounds.width, it->bounds.y + it->bounds.height); ::CombineRgn(draggable_region_, draggable_region_, region, it->draggable ? RGN_OR : RGN_DIFF); ::DeleteObject(region); } // Subclass child window procedures in order to do hit-testing. // This will be a no-op, if it is already subclassed. if (hwnd_) { WNDENUMPROC proc = !regions.empty() ? SubclassWindowsProc : UnSubclassWindowsProc; ::EnumChildWindows(hwnd_, proc, reinterpret_cast(draggable_region_)); } } void RootWindowWin::NotifyDestroyedIfDone() { // Notify once both the window and the browser have been destroyed. if (window_destroyed_ && browser_destroyed_) delegate_->OnRootWindowDestroyed(this); } } // namespace client