cef/tests/cefclient/root_window_win.cc

754 lines
21 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 "cefclient/root_window_win.h"
#include "include/base/cef_bind.h"
#include "include/cef_app.h"
#include "cefclient/browser_window_osr_win.h"
#include "cefclient/browser_window_std_win.h"
#include "cefclient/client_switches.h"
#include "cefclient/main_message_loop.h"
#include "cefclient/resource.h"
#include "cefclient/temp_window.h"
#include "cefclient/util_win.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;
}
} // namespace
RootWindowWin::RootWindowWin()
: delegate_(NULL),
with_controls_(false),
is_popup_(false),
start_rect_(),
initialized_(false),
hwnd_(NULL),
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_buff_(),
find_next_(false),
find_match_case_last_(false),
window_destroyed_(false),
browser_destroyed_(false) {
}
RootWindowWin::~RootWindowWin() {
REQUIRE_MAIN_THREAD();
// The window and browser should already have been destroyed.
DCHECK(window_destroyed_);
DCHECK(browser_destroyed_);
}
void RootWindowWin::Init(RootWindow::Delegate* delegate,
bool with_controls,
bool with_osr,
const CefRect& bounds,
const CefBrowserSettings& settings,
const std::string& url) {
DCHECK(delegate);
DCHECK(!initialized_);
delegate_ = delegate;
with_controls_ = with_controls;
start_rect_.left = bounds.x;
start_rect_.top = bounds.y;
start_rect_.right = bounds.x + bounds.width;
start_rect_.bottom = bounds.y + bounds.height;
CreateBrowserWindow(with_osr, url);
initialized_ = true;
// Create the native root window on the main thread.
if (CURRENTLY_ON_MAIN_THREAD()) {
CreateRootWindow(settings);
} else {
MAIN_POST_CLOSURE(
base::Bind(&RootWindowWin::CreateRootWindow, this, settings));
}
}
void RootWindowWin::InitAsPopup(RootWindow::Delegate* delegate,
bool with_controls,
bool with_osr,
const CefPopupFeatures& popupFeatures,
CefWindowInfo& windowInfo,
CefRefPtr<CefClient>& client,
CefBrowserSettings& settings) {
DCHECK(delegate);
DCHECK(!initialized_);
delegate_ = delegate;
with_controls_ = with_controls;
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(with_osr, 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;
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, 0, 0, 0, 0, SWP_NOZORDER);
}
void RootWindowWin::Close(bool force) {
REQUIRE_MAIN_THREAD();
if (hwnd_) {
if (force)
DestroyWindow(hwnd_);
else
PostMessage(hwnd_, WM_CLOSE, 0, 0);
}
}
CefRefPtr<CefBrowser> RootWindowWin::GetBrowser() const {
REQUIRE_MAIN_THREAD();
if (browser_window_)
return browser_window_->GetBrowser();
return NULL;
}
ClientWindowHandle RootWindowWin::GetWindowHandle() const {
REQUIRE_MAIN_THREAD();
return hwnd_;
}
void RootWindowWin::CreateBrowserWindow(bool with_osr,
const std::string& startup_url) {
if (with_osr) {
CefRefPtr<CefCommandLine> command_line =
CefCommandLine::GetGlobalCommandLine();
const bool transparent =
command_line->HasSwitch(switches::kTransparentPaintingEnabled);
const bool show_update_rect =
command_line->HasSwitch(switches::kShowUpdateRect);
browser_window_.reset(new BrowserWindowOsrWin(this, startup_url,
transparent,
show_update_rect));
} else {
browser_window_.reset(new BrowserWindowStdWin(this, startup_url));
}
}
void RootWindowWin::CreateRootWindow(const CefBrowserSettings& settings) {
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);
// Register the window class.
RegisterRootClass(hInstance, window_class);
// Register the message used with the find dialog.
find_message_id_ = RegisterWindowMessage(FINDMSGSTRING);
CHECK(find_message_id_);
const DWORD dwStyle = WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN;
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_, 0);
if (with_controls_)
window_rect.bottom += URLBAR_HEIGHT;
x = start_rect_.left;
y = start_rect_.top;
width = window_rect.right - window_rect.left;
height = window_rect.bottom - window_rect.top;
}
// Create the main window initially hidden.
hwnd_ = CreateWindow(window_class.c_str(), window_title.c_str(),
dwStyle,
x, y, width, height,
NULL, NULL, hInstance, NULL);
CHECK(hwnd_);
// Associate |this| with the main window.
SetUserDataPtr(hwnd_, this);
RECT rect;
GetClientRect(hwnd_, &rect);
if (with_controls_) {
// Create the child controls.
int x = 0;
back_hwnd_ = CreateWindow(
L"BUTTON", L"Back",
WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | WS_DISABLED,
x, 0, BUTTON_WIDTH, URLBAR_HEIGHT,
hwnd_, reinterpret_cast<HMENU>(IDC_NAV_BACK), hInstance, 0);
CHECK(back_hwnd_);
x += BUTTON_WIDTH;
forward_hwnd_ = CreateWindow(
L"BUTTON", L"Forward",
WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | WS_DISABLED,
x, 0, BUTTON_WIDTH, URLBAR_HEIGHT,
hwnd_, reinterpret_cast<HMENU>(IDC_NAV_FORWARD), hInstance, 0);
CHECK(forward_hwnd_);
x += BUTTON_WIDTH;
reload_hwnd_ = CreateWindow(
L"BUTTON", L"Reload",
WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON| WS_DISABLED,
x, 0, BUTTON_WIDTH, URLBAR_HEIGHT,
hwnd_, reinterpret_cast<HMENU>(IDC_NAV_RELOAD), hInstance, 0);
CHECK(reload_hwnd_);
x += BUTTON_WIDTH;
stop_hwnd_ = CreateWindow(
L"BUTTON", L"Stop",
WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | WS_DISABLED,
x, 0, BUTTON_WIDTH, URLBAR_HEIGHT,
hwnd_, reinterpret_cast<HMENU>(IDC_NAV_STOP), hInstance, 0);
CHECK(stop_hwnd_);
x += BUTTON_WIDTH;
edit_hwnd_ = CreateWindow(
L"EDIT", 0,
WS_CHILD | WS_VISIBLE | WS_BORDER | ES_LEFT | ES_AUTOVSCROLL |
ES_AUTOHSCROLL| WS_DISABLED,
x, 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;
} else {
// No controls so also remove the default menu.
::SetMenu(hwnd_, NULL);
}
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, settings);
} 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);
}
// Show this window.
Show(ShowNormal);
}
// static
void RootWindowWin::RegisterRootClass(HINSTANCE hInstance,
const std::wstring& window_class) {
// 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 = (HBRUSH)(COLOR_WINDOW+1);
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<RootWindowWin*>(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<CefBrowser> 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<RootWindowWin*>(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 = GetUserDataPtr<RootWindowWin*>(hWnd);
if (!self)
return DefWindowProc(hWnd, message, wParam, lParam);
DCHECK(hWnd == self->hwnd_);
if (message == self->find_message_id_) {
// Message targeting the find dialog.
LPFINDREPLACE lpfr = reinterpret_cast<LPFINDREPLACE>(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_PAINT:
self->OnPaint();
return 0;
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_ERASEBKGND:
// Never 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_NCDESTROY:
// Clear the reference to |self|.
SetUserDataPtr(hWnd, NULL);
self->hwnd_ = NULL;
self->OnDestroyed();
return 0;
}
return DefWindowProc(hWnd, message, wParam, lParam);
}
void RootWindowWin::OnPaint() {
PAINTSTRUCT ps;
BeginPaint(hwnd_, &ps);
EndPaint(hwnd_, &ps);
}
void RootWindowWin::OnFocus() {
if (browser_window_)
browser_window_->SetFocus(true);
}
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_) {
// Resize the window and address bar to match the new frame size.
rect.top += URLBAR_HEIGHT;
int urloffset = rect.left + BUTTON_WIDTH * 4;
if (browser_window_) {
HWND browser_hwnd = browser_window_->GetWindowHandle();
HDWP hdwp = BeginDeferWindowPos(1);
hdwp = DeferWindowPos(hdwp, edit_hwnd_, NULL, urloffset,
0, rect.right - urloffset, URLBAR_HEIGHT, SWP_NOZORDER);
hdwp = DeferWindowPos(hdwp, browser_hwnd, NULL,
rect.left, rect.top, rect.right - rect.left,
rect.bottom - rect.top, SWP_NOZORDER);
EndDeferWindowPos(hdwp);
} else {
SetWindowPos(edit_hwnd_, NULL, urloffset,
0, rect.right - urloffset, URLBAR_HEIGHT, SWP_NOZORDER);
}
} 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<CefBrowser> browser = GetBrowser();
if (browser)
browser->GetHost()->NotifyMoveOrResizeStarted();
}
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<CefBrowser> browser = GetBrowser())
browser->GoBack();
return true;
case IDC_NAV_FORWARD: // Forward button
if (CefRefPtr<CefBrowser> browser = GetBrowser())
browser->GoForward();
return true;
case IDC_NAV_RELOAD: // Reload button
if (CefRefPtr<CefBrowser> browser = GetBrowser())
browser->Reload();
return true;
case IDC_NAV_STOP: // Stop button
if (CefRefPtr<CefBrowser> 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<CefBrowser> 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);
}
bool RootWindowWin::OnClose() {
if (browser_window_ && !browser_window_->IsClosing()) {
CefRefPtr<CefBrowser> 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<CefBrowser> browser) {
REQUIRE_MAIN_THREAD();
// For popup browsers create the root window once the browser has been
// created.
if (is_popup_)
CreateRootWindow(CefBrowserSettings());
}
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::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);
}
}
void RootWindowWin::NotifyDestroyedIfDone() {
// Notify once both the window and the browser have been destroyed.
if (window_destroyed_ && browser_destroyed_)
delegate_->OnRootWindowDestroyed(this);
}
// static
scoped_refptr<RootWindow> RootWindow::Create() {
return new RootWindowWin();
}
} // namespace client