diff --git a/src/CEF/BindingTest.cpp b/src/CEF/BindingTest.cpp new file mode 100644 index 0000000..1e70dd3 --- /dev/null +++ b/src/CEF/BindingTest.cpp @@ -0,0 +1,49 @@ +// Copyright (c) 2012 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 "CEF/BindingTest.h" + +#include +#include + +#include "CEF/TestRunner.h" + +const char kTestUrlPath[] = "/binding"; +const char kTestMessageName[] = "BindingTest"; + +// Handle messages in the browser process. +class Handler : public CefMessageRouterBrowserSide::Handler { + public: + Handler() {} + + // Called due to cefQuery execution in binding.html. + virtual bool OnQuery(CefRefPtr browser, + CefRefPtr frame, + int64 query_id, + const CefString& request, + bool persistent, + CefRefPtr callback) override { + // Only handle messages from the test URL. + const std::string& url = frame->GetURL(); + if (!IsTestURL(url, kTestUrlPath)) + return false; + + const std::string& message_name = request; + if (message_name.find(kTestMessageName) == 0) { + // Reverse the string and return. + std::string result = message_name.substr(sizeof(kTestMessageName)); + std::reverse(result.begin(), result.end()); + callback->Success(result); + return true; + } + + return false; + } +}; + +namespace binding_test { + void CreateMessageHandlers(MessageHandlerSet& handlers) { + handlers.insert(new Handler()); + } +} diff --git a/src/CEF/BindingTest.h b/src/CEF/BindingTest.h new file mode 100644 index 0000000..1786c40 --- /dev/null +++ b/src/CEF/BindingTest.h @@ -0,0 +1,12 @@ +// Copyright (c) 2012 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. + +#pragma once + +#include "CEF/TestRunner.h" + +namespace binding_test { + // Create message handlers. Called from test_runner.cc. + void CreateMessageHandlers(MessageHandlerSet& handlers); +} diff --git a/src/CEF/BrowserWindow.cpp b/src/CEF/BrowserWindow.cpp new file mode 100644 index 0000000..aada2c0 --- /dev/null +++ b/src/CEF/BrowserWindow.cpp @@ -0,0 +1,92 @@ +// 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 "CEF/BrowserWindow.h" + +#include "include/base/cef_callback.h" +#include "CEF/MainMessageLoop.h" + +BrowserWindow::BrowserWindow(Delegate* delegate) + : delegate_(delegate), is_closing_(false) { + DCHECK(delegate_); +} + +void BrowserWindow::SetDeviceScaleFactor(float device_scale_factor) {} + +float BrowserWindow::GetDeviceScaleFactor() const { + return 1.0f; +} + +CefRefPtr BrowserWindow::GetBrowser() const { + REQUIRE_MAIN_THREAD(); + return browser_; +} + +bool BrowserWindow::IsClosing() const { + REQUIRE_MAIN_THREAD(); + return is_closing_; +} + +void BrowserWindow::OnBrowserCreated(CefRefPtr browser) { + REQUIRE_MAIN_THREAD(); + DCHECK(!browser_); + browser_ = browser; + + delegate_->OnBrowserCreated(browser); +} + +void BrowserWindow::OnBrowserClosing(CefRefPtr browser) { + REQUIRE_MAIN_THREAD(); + DCHECK_EQ(browser->GetIdentifier(), browser_->GetIdentifier()); + is_closing_ = true; + + delegate_->OnBrowserWindowClosing(); +} + +void BrowserWindow::OnBrowserClosed(CefRefPtr browser) { + REQUIRE_MAIN_THREAD(); + if (browser_.get()) { + DCHECK_EQ(browser->GetIdentifier(), browser_->GetIdentifier()); + browser_ = nullptr; + } + + client_handler_->DetachDelegate(); + client_handler_ = nullptr; + + // |this| may be deleted. + delegate_->OnBrowserWindowDestroyed(); +} + +void BrowserWindow::OnSetAddress(const std::string& url) { + REQUIRE_MAIN_THREAD(); + delegate_->OnSetAddress(url); +} + +void BrowserWindow::OnSetTitle(const std::string& title) { + REQUIRE_MAIN_THREAD(); + delegate_->OnSetTitle(title); +} + +void BrowserWindow::OnSetFullscreen(bool fullscreen) { + REQUIRE_MAIN_THREAD(); + delegate_->OnSetFullscreen(fullscreen); +} + +void BrowserWindow::OnAutoResize(const CefSize& new_size) { + REQUIRE_MAIN_THREAD(); + delegate_->OnAutoResize(new_size); +} + +void BrowserWindow::OnSetLoadingState(bool isLoading, + bool canGoBack, + bool canGoForward) { + REQUIRE_MAIN_THREAD(); + delegate_->OnSetLoadingState(isLoading, canGoBack, canGoForward); +} + +void BrowserWindow::OnSetDraggableRegions( + const std::vector& regions) { + REQUIRE_MAIN_THREAD(); + delegate_->OnSetDraggableRegions(regions); +} diff --git a/src/CEF/BrowserWindow.h b/src/CEF/BrowserWindow.h new file mode 100644 index 0000000..e8fbca5 --- /dev/null +++ b/src/CEF/BrowserWindow.h @@ -0,0 +1,139 @@ +// 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. + +#pragma once + +#include + +#include "include/cef_browser.h" +#include "CEF/ClientHandler.h" +#include "CEF/HumanApptypes.h" + +// Represents a native child window hosting a single browser instance. The +// methods of this class must be called on the main thread unless otherwise +// indicated. +class BrowserWindow : public ClientHandler::Delegate { + public: + // This interface is implemented by the owner of the BrowserWindow. The + // methods of this class will be called on the main thread. + class Delegate { + public: + // Called when the browser has been created. + virtual void OnBrowserCreated(CefRefPtr browser) = 0; + + // Called when the BrowserWindow is closing. + virtual void OnBrowserWindowClosing() {} + + // Called when the BrowserWindow has been destroyed. + virtual void OnBrowserWindowDestroyed() = 0; + + // Set the window URL address. + virtual void OnSetAddress(const std::string& url) = 0; + + // Set the window title. + virtual void OnSetTitle(const std::string& title) = 0; + + // Set fullscreen mode. + virtual void OnSetFullscreen(bool fullscreen) = 0; + + // Auto-resize contents. + virtual void OnAutoResize(const CefSize& new_size) = 0; + + // Set the loading state. + virtual void OnSetLoadingState(bool isLoading, + bool canGoBack, + bool canGoForward) = 0; + + // Set the draggable regions. + virtual void OnSetDraggableRegions( + const std::vector& regions) = 0; + + protected: + virtual ~Delegate() {} + }; + + // Create a new browser and native window. + virtual void CreateBrowser(ClientWindowHandle parent_handle, + const CefRect& rect, + const CefBrowserSettings& settings, + CefRefPtr extra_info, + CefRefPtr request_context) = 0; + + // Retrieve the configuration that will be used when creating a popup window. + // The popup browser will initially be parented to |temp_handle| which should + // be a pre-existing hidden window. The native window will be created later + // after the browser has been created. This method will be called on the + // browser process UI thread. + virtual void GetPopupConfig(CefWindowHandle temp_handle, + CefWindowInfo& windowInfo, + CefRefPtr& client, + CefBrowserSettings& settings) = 0; + + // Show the popup window with correct parent and bounds in parent coordinates. + virtual void ShowPopup(ClientWindowHandle parent_handle, + int x, + int y, + size_t width, + size_t height) = 0; + + // Show the window. + virtual void Show() = 0; + + // Hide the window. + virtual void Hide() = 0; + + // Set the window bounds in parent coordinates. + virtual void SetBounds(int x, int y, size_t width, size_t height) = 0; + + // Set focus to the window. + virtual void SetFocus(bool focus) = 0; + + // Set the device scale factor. Only used in combination with off-screen + // rendering. + virtual void SetDeviceScaleFactor(float device_scale_factor); + + // Returns the device scale factor. Only used in combination with off-screen + // rendering. + virtual float GetDeviceScaleFactor() const; + + // Returns the window handle. + virtual ClientWindowHandle GetWindowHandle() const = 0; + + // Returns the browser owned by the window. + CefRefPtr GetBrowser() const; + + // Returns true if the browser is closing. + bool IsClosing() const; + + protected: + // Allow deletion via std::unique_ptr only. + friend std::default_delete; + + // Constructor may be called on any thread. + // |delegate| must outlive this object. + explicit BrowserWindow(Delegate* delegate); + + // ClientHandler::Delegate methods. + void OnBrowserCreated(CefRefPtr browser) override; + void OnBrowserClosing(CefRefPtr browser) override; + void OnBrowserClosed(CefRefPtr browser) override; + void OnSetAddress(const std::string& url) override; + void OnSetTitle(const std::string& title) override; + void OnSetFullscreen(bool fullscreen) override; + void OnAutoResize(const CefSize& new_size) override; + void OnSetLoadingState(bool isLoading, + bool canGoBack, + bool canGoForward) override; + void OnSetDraggableRegions( + const std::vector& regions) override; + + Delegate* delegate_; + CefRefPtr browser_; + CefRefPtr client_handler_; + bool is_closing_; + + private: + DISALLOW_COPY_AND_ASSIGN(BrowserWindow); +}; + \ No newline at end of file diff --git a/src/CEF/BrowserWindowOsrWin.cpp b/src/CEF/BrowserWindowOsrWin.cpp new file mode 100644 index 0000000..8c3ae83 --- /dev/null +++ b/src/CEF/BrowserWindowOsrWin.cpp @@ -0,0 +1,125 @@ +// 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 "CEF/BrowserWindowOsrWin.h" + +#include "CEF/MainMessageLoop.h" + + +BrowserWindowOsrWin::BrowserWindowOsrWin(BrowserWindow::Delegate* delegate, + bool with_controls, + const std::string& startup_url, + const OsrRendererSettings& settings) + : BrowserWindow(delegate), osr_hwnd_(nullptr), device_scale_factor_(0) { + osr_window_ = new OsrWindowWin(this, settings); + client_handler_ = + new ClientHandlerOsr(this, osr_window_.get(), with_controls, startup_url); +} + +void BrowserWindowOsrWin::CreateBrowser( + ClientWindowHandle parent_handle, + const CefRect& rect, + const CefBrowserSettings& settings, + CefRefPtr extra_info, + CefRefPtr request_context) { + REQUIRE_MAIN_THREAD(); + + // Create the new browser and native window on the UI thread. + RECT wnd_rect = {rect.x, rect.y, rect.x + rect.width, rect.y + rect.height}; + osr_window_->CreateBrowser(parent_handle, wnd_rect, client_handler_, settings, + extra_info, request_context, + client_handler_->startup_url()); +} + +void BrowserWindowOsrWin::GetPopupConfig(CefWindowHandle temp_handle, + CefWindowInfo& windowInfo, + CefRefPtr& client, + CefBrowserSettings& settings) { + CEF_REQUIRE_UI_THREAD(); + + windowInfo.SetAsWindowless(temp_handle); + windowInfo.shared_texture_enabled = + osr_window_->settings().shared_texture_enabled; + windowInfo.external_begin_frame_enabled = + osr_window_->settings().external_begin_frame_enabled; + + // Don't activate the hidden browser on creation. + windowInfo.ex_style |= WS_EX_NOACTIVATE; + + client = client_handler_; +} + +void BrowserWindowOsrWin::ShowPopup(ClientWindowHandle parent_handle, + int x, + int y, + size_t width, + size_t height) { + REQUIRE_MAIN_THREAD(); + if (osr_window_) + osr_window_->ShowPopup(parent_handle, x, y, width, height); +} + +void BrowserWindowOsrWin::Show() { + REQUIRE_MAIN_THREAD(); + if (osr_window_) + osr_window_->Show(); +} + +void BrowserWindowOsrWin::Hide() { + REQUIRE_MAIN_THREAD(); + if (osr_window_) + osr_window_->Hide(); +} + +void BrowserWindowOsrWin::SetBounds(int x, int y, size_t width, size_t height) { + REQUIRE_MAIN_THREAD(); + if (osr_window_) + osr_window_->SetBounds(x, y, width, height); +} + +void BrowserWindowOsrWin::SetFocus(bool focus) { + REQUIRE_MAIN_THREAD(); + if (osr_window_ && focus) + osr_window_->SetFocus(); +} + +void BrowserWindowOsrWin::SetDeviceScaleFactor(float device_scale_factor) { + REQUIRE_MAIN_THREAD(); + if (device_scale_factor == device_scale_factor_) + return; + + // Apply some sanity checks. + if (device_scale_factor < 1.0f || device_scale_factor > 4.0f) + return; + + device_scale_factor_ = device_scale_factor; + if (osr_window_) + osr_window_->SetDeviceScaleFactor(device_scale_factor); +} + +float BrowserWindowOsrWin::GetDeviceScaleFactor() const { + REQUIRE_MAIN_THREAD(); + DCHECK_GT(device_scale_factor_, 0); + return device_scale_factor_; +} + +ClientWindowHandle BrowserWindowOsrWin::GetWindowHandle() const { + REQUIRE_MAIN_THREAD(); + return osr_hwnd_; +} + +void BrowserWindowOsrWin::OnBrowserClosed(CefRefPtr browser) { + REQUIRE_MAIN_THREAD(); + + // Release the OSR window reference. It will be deleted on the UI thread. + osr_window_ = nullptr; + + BrowserWindow::OnBrowserClosed(browser); +} + +void BrowserWindowOsrWin::OnOsrNativeWindowCreated(HWND hwnd) { + REQUIRE_MAIN_THREAD(); + DCHECK(!osr_hwnd_); + osr_hwnd_ = hwnd; +} diff --git a/src/CEF/BrowserWindowOsrWin.h b/src/CEF/BrowserWindowOsrWin.h new file mode 100644 index 0000000..d7cb52a --- /dev/null +++ b/src/CEF/BrowserWindowOsrWin.h @@ -0,0 +1,61 @@ +// 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. + +#pragma once + +#include "CEF/BrowserWindow.h" +#include "CEF/osr_window_win.h" + +// Represents a native child window hosting a single off-screen browser +// instance. The methods of this class must be called on the main thread unless +// otherwise indicated. +class BrowserWindowOsrWin : public BrowserWindow, + public OsrWindowWin::Delegate { + public: + // Constructor may be called on any thread. + // |delegate| must outlive this object. + BrowserWindowOsrWin(BrowserWindow::Delegate* delegate, + bool with_controls, + const std::string& startup_url, + const OsrRendererSettings& settings); + + // BrowserWindow methods. + void CreateBrowser(ClientWindowHandle parent_handle, + const CefRect& rect, + const CefBrowserSettings& settings, + CefRefPtr extra_info, + CefRefPtr request_context) override; + void GetPopupConfig(CefWindowHandle temp_handle, + CefWindowInfo& windowInfo, + CefRefPtr& client, + CefBrowserSettings& settings) override; + void ShowPopup(ClientWindowHandle parent_handle, + int x, + int y, + size_t width, + size_t height) override; + void Show() override; + void Hide() override; + void SetBounds(int x, int y, size_t width, size_t height) override; + void SetFocus(bool focus) override; + void SetDeviceScaleFactor(float device_scale_factor) override; + float GetDeviceScaleFactor() const override; + ClientWindowHandle GetWindowHandle() const override; + + private: + // ClienHandler::Delegate methods. + void OnBrowserClosed(CefRefPtr browser) override; + + // OsrWindowWin::Delegate methods. + void OnOsrNativeWindowCreated(HWND hwnd) override; + + // The below members are only accessed on the main thread. + scoped_refptr osr_window_; + HWND osr_hwnd_; + + float device_scale_factor_; + + DISALLOW_COPY_AND_ASSIGN(BrowserWindowOsrWin); +}; + diff --git a/src/CEF/BrowserWindowStdWin.cpp b/src/CEF/BrowserWindowStdWin.cpp new file mode 100644 index 0000000..b8fa24c --- /dev/null +++ b/src/CEF/BrowserWindowStdWin.cpp @@ -0,0 +1,120 @@ +// 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/browser_window_std_win.h" + +#include "tests/cefclient/browser/client_handler_std.h" +#include "tests/shared/browser/main_message_loop.h" + +namespace client { + +BrowserWindowStdWin::BrowserWindowStdWin(Delegate* delegate, + bool with_controls, + const std::string& startup_url) + : BrowserWindow(delegate) { + client_handler_ = new ClientHandlerStd(this, with_controls, startup_url); +} + +void BrowserWindowStdWin::CreateBrowser( + ClientWindowHandle parent_handle, + const CefRect& rect, + const CefBrowserSettings& settings, + CefRefPtr extra_info, + CefRefPtr request_context) { + REQUIRE_MAIN_THREAD(); + + CefWindowInfo window_info; + window_info.SetAsChild(parent_handle, rect); + + if (GetWindowLongPtr(parent_handle, GWL_EXSTYLE) & WS_EX_NOACTIVATE) { + // Don't activate the browser window on creation. + window_info.ex_style |= WS_EX_NOACTIVATE; + } + + CefBrowserHost::CreateBrowser(window_info, client_handler_, + client_handler_->startup_url(), settings, + extra_info, request_context); +} + +void BrowserWindowStdWin::GetPopupConfig(CefWindowHandle temp_handle, + CefWindowInfo& windowInfo, + CefRefPtr& client, + CefBrowserSettings& settings) { + CEF_REQUIRE_UI_THREAD(); + + // The window will be properly sized after the browser is created. + windowInfo.SetAsChild(temp_handle, CefRect()); + + // Don't activate the hidden browser window on creation. + windowInfo.ex_style |= WS_EX_NOACTIVATE; + + client = client_handler_; +} + +void BrowserWindowStdWin::ShowPopup(ClientWindowHandle parent_handle, + int x, + int y, + size_t width, + size_t height) { + REQUIRE_MAIN_THREAD(); + + HWND hwnd = GetWindowHandle(); + if (hwnd) { + SetParent(hwnd, parent_handle); + SetWindowPos(hwnd, nullptr, x, y, static_cast(width), + static_cast(height), SWP_NOZORDER | SWP_NOACTIVATE); + + const bool no_activate = + GetWindowLongPtr(parent_handle, GWL_EXSTYLE) & WS_EX_NOACTIVATE; + ShowWindow(hwnd, no_activate ? SW_SHOWNOACTIVATE : SW_SHOW); + } +} + +void BrowserWindowStdWin::Show() { + REQUIRE_MAIN_THREAD(); + + HWND hwnd = GetWindowHandle(); + if (hwnd && !::IsWindowVisible(hwnd)) + ShowWindow(hwnd, SW_SHOW); +} + +void BrowserWindowStdWin::Hide() { + REQUIRE_MAIN_THREAD(); + + HWND hwnd = GetWindowHandle(); + if (hwnd) { + // When the frame window is minimized set the browser window size to 0x0 to + // reduce resource usage. + SetWindowPos(hwnd, nullptr, 0, 0, 0, 0, + SWP_NOZORDER | SWP_NOMOVE | SWP_NOACTIVATE); + } +} + +void BrowserWindowStdWin::SetBounds(int x, int y, size_t width, size_t height) { + REQUIRE_MAIN_THREAD(); + + HWND hwnd = GetWindowHandle(); + if (hwnd) { + // Set the browser window bounds. + SetWindowPos(hwnd, nullptr, x, y, static_cast(width), + static_cast(height), SWP_NOZORDER); + } +} + +void BrowserWindowStdWin::SetFocus(bool focus) { + REQUIRE_MAIN_THREAD(); + + if (browser_) + browser_->GetHost()->SetFocus(focus); +} + +ClientWindowHandle BrowserWindowStdWin::GetWindowHandle() const { + REQUIRE_MAIN_THREAD(); + + if (browser_) + return browser_->GetHost()->GetWindowHandle(); + return nullptr; +} + +} // namespace client diff --git a/src/CEF/BrowserWindowStdWin.h b/src/CEF/BrowserWindowStdWin.h new file mode 100644 index 0000000..5a7ee2a --- /dev/null +++ b/src/CEF/BrowserWindowStdWin.h @@ -0,0 +1,51 @@ +// 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. + +#ifndef CEF_TESTS_CEFCLIENT_BROWSER_BROWSER_WINDOW_STD_WIN_H_ +#define CEF_TESTS_CEFCLIENT_BROWSER_BROWSER_WINDOW_STD_WIN_H_ +#pragma once + +#include "tests/cefclient/browser/browser_window.h" + +namespace client { + +// Represents a native child window hosting a single windowed browser instance. +// The methods of this class must be called on the main thread unless otherwise +// indicated. +class BrowserWindowStdWin : public BrowserWindow { + public: + // Constructor may be called on any thread. + // |delegate| must outlive this object. + BrowserWindowStdWin(Delegate* delegate, + bool with_controls, + const std::string& startup_url); + + // BrowserWindow methods. + void CreateBrowser(ClientWindowHandle parent_handle, + const CefRect& rect, + const CefBrowserSettings& settings, + CefRefPtr extra_info, + CefRefPtr request_context) override; + void GetPopupConfig(CefWindowHandle temp_handle, + CefWindowInfo& windowInfo, + CefRefPtr& client, + CefBrowserSettings& settings) override; + void ShowPopup(ClientWindowHandle parent_handle, + int x, + int y, + size_t width, + size_t height) override; + void Show() override; + void Hide() override; + void SetBounds(int x, int y, size_t width, size_t height) override; + void SetFocus(bool focus) override; + ClientWindowHandle GetWindowHandle() const override; + + private: + DISALLOW_COPY_AND_ASSIGN(BrowserWindowStdWin); +}; + +} // namespace client + +#endif // CEF_TESTS_CEFCLIENT_BROWSER_BROWSER_WINDOW_STD_WIN_H_ diff --git a/src/CEF/ClientHandler.cpp b/src/CEF/ClientHandler.cpp index 7fb7c1b..f5288bd 100644 --- a/src/CEF/ClientHandler.cpp +++ b/src/CEF/ClientHandler.cpp @@ -21,7 +21,7 @@ #include "include/wrapper/cef_closure_task.h" #include "CEF/HumanAppContext.h" #include "CEF/RootWindoManager.h" -//#include "tests/cefclient/browser/test_runner.h" +#include "CEF/TestRunner.h" #include "CEF/ExtensionUtil.h" #include "CEF/ResourceUtil.h" #include "CEF/HumanAppSwitches.h" @@ -157,14 +157,14 @@ void LoadErrorPage(CefRefPtr frame, "

Page failed to load.

" "URL: " << failed_url - << "
Error: " << test_runner::GetErrorString(error_code) << " (" + << "
Error: " << GetErrorString(error_code) << " (" << error_code << ")"; if (!other_info.empty()) ss << "
" << other_info; ss << ""; - frame->LoadURL(test_runner::GetDataURI(ss.str(), "text/html")); + frame->LoadURL(GetDataURI(ss.str(), "text/html")); } // Return HTML string with information about a certificate. @@ -220,8 +220,6 @@ std::string GetCertificateInformation(CefRefPtr cert, return ss.str(); } -} // namespace - class ClientDownloadImageCallback : public CefDownloadImageCallback { public: explicit ClientDownloadImageCallback(CefRefPtr client_handler) @@ -251,21 +249,21 @@ ClientHandler::ClientHandler(Delegate* delegate, download_favicon_images_(false), delegate_(delegate), browser_count_(0), - console_log_file_(MainContext::Get()->GetConsoleLogPath()), + console_log_file_(HumanAppContext::Get()->GetConsoleLogPath()), first_console_message_(true), focus_on_editable_field_(false), initial_navigation_(true) { DCHECK(!console_log_file_.empty()); resource_manager_ = new CefResourceManager(); - test_runner::SetupResourceManager(resource_manager_, &string_resource_map_); + SetupResourceManager(resource_manager_, &string_resource_map_); // Read command line settings. CefRefPtr command_line = CefCommandLine::GetGlobalCommandLine(); mouse_cursor_change_disabled_ = - command_line->HasSwitch(switches::kMouseCursorChangeDisabled); - offline_ = command_line->HasSwitch(switches::kOffline); + command_line->HasSwitch(kMouseCursorChangeDisabled); + offline_ = command_line->HasSwitch(kOffline); #if defined(OS_LINUX) // Optionally use the client-provided dialog implementation. @@ -337,7 +335,7 @@ bool ClientHandler::OnChromeCommand(CefRefPtr browser, int command_id, cef_window_open_disposition_t disposition) { CEF_REQUIRE_UI_THREAD(); - DCHECK(MainContext::Get()->UseChromeRuntime()); + DCHECK(HumanAppContext::Get()->UseChromeRuntime()); if (!with_controls_ && (disposition != WOD_CURRENT_TAB || !IsAllowedCommandId(command_id))) { @@ -358,7 +356,7 @@ void ClientHandler::OnBeforeContextMenu(CefRefPtr browser, CefRefPtr model) { CEF_REQUIRE_UI_THREAD(); - const bool use_chrome_runtime = MainContext::Get()->UseChromeRuntime(); + const bool use_chrome_runtime = HumanAppContext::Get()->UseChromeRuntime(); if (use_chrome_runtime && !with_controls_) { // Remove all disallowed menu items. FilterMenuModel(model); @@ -507,7 +505,7 @@ bool ClientHandler::OnConsoleMessage(CefRefPtr browser, fclose(file); if (first_console_message_) { - test_runner::Alert( + Alert( browser, "Console messages written to \"" + console_log_file_ + "\""); first_console_message_ = false; } @@ -557,7 +555,7 @@ void ClientHandler::OnBeforeDownload( CEF_REQUIRE_UI_THREAD(); // Continue the download and show the "Save As" dialog. - callback->Continue(MainContext::Get()->GetDownloadPath(suggested_name), true); + callback->Continue(HumanAppContext::Get()->GetDownloadPath(suggested_name), true); } void ClientHandler::OnDownloadUpdated( @@ -567,7 +565,7 @@ void ClientHandler::OnDownloadUpdated( CEF_REQUIRE_UI_THREAD(); if (download_item->IsComplete()) { - test_runner::Alert(browser, "File \"" + + Alert(browser, "File \"" + download_item->GetFullPath().ToString() + "\" downloaded successfully."); } @@ -580,7 +578,7 @@ bool ClientHandler::OnDragEnter(CefRefPtr browser, // Forbid dragging of URLs and files. if ((mask & DRAG_OPERATION_LINK) && !dragData->IsFragment()) { - test_runner::Alert(browser, "cefclient blocks dragging of URLs and files"); + Alert(browser, "cefclient blocks dragging of URLs and files"); return true; } @@ -609,7 +607,7 @@ bool ClientHandler::OnSetFocus(CefRefPtr browser, if (initial_navigation_) { CefRefPtr command_line = CefCommandLine::GetGlobalCommandLine(); - if (command_line->HasSwitch(switches::kNoActivate)) { + if (command_line->HasSwitch(kNoActivate)) { // Don't give focus to the browser on creation. return true; } @@ -687,9 +685,9 @@ void ClientHandler::OnAfterCreated(CefRefPtr browser) { CefSize(1000, 1000)); CefRefPtr extension = browser->GetHost()->GetExtension(); - if (extension_util::IsInternalExtension(extension->GetPath())) { + if (IsInternalExtension(extension->GetPath())) { // Register the internal handler for extension resources. - extension_util::AddInternalExtensionToResourceManager(extension, + AddInternalExtensionToResourceManager(extension, resource_manager_); } } @@ -785,7 +783,7 @@ bool ClientHandler::OnOpenURLFromTab( config->with_controls = with_controls_; config->with_osr = is_osr_; config->url = target_url; - MainContext::Get()->GetRootWindowManager()->CreateRootWindow( + HumanAppContext::Get()->GetRootWindowManager()->CreateRootWindow( std::move(config)); return true; } @@ -883,12 +881,12 @@ bool ClientHandler::OnSelectClientCertificate( CefRefPtr command_line = CefCommandLine::GetGlobalCommandLine(); - if (!command_line->HasSwitch(switches::kSslClientCertificate)) { + if (!command_line->HasSwitch(kSslClientCertificate)) { return false; } const std::string& cert_name = - command_line->GetSwitchValue(switches::kSslClientCertificate); + command_line->GetSwitchValue(kSslClientCertificate); if (cert_name.empty()) { callback->Select(nullptr); @@ -977,7 +975,7 @@ CefRefPtr ClientHandler::GetResourceResponseFilter( CefRefPtr response) { CEF_REQUIRE_IO_THREAD(); - return test_runner::GetResourceResponseFilter(browser, frame, request, + return GetResourceResponseFilter(browser, frame, request, response); } @@ -1012,7 +1010,7 @@ void ClientHandler::ShowDevTools(CefRefPtr browser, CefRefPtr client; CefBrowserSettings settings; - MainContext::Get()->PopulateBrowserSettings(&settings); + HumanAppContext::Get()->PopulateBrowserSettings(&settings); CefRefPtr host = browser->GetHost(); @@ -1086,8 +1084,8 @@ void ClientHandler::ShowSSLInformation(CefRefPtr browser) { auto config = std::make_unique(); config->with_controls = false; config->with_osr = is_osr_; - config->url = test_runner::GetDataURI(ss.str(), "text/html"); - MainContext::Get()->GetRootWindowManager()->CreateRootWindow( + config->url = GetDataURI(ss.str(), "text/html"); + HumanAppContext::Get()->GetRootWindowManager()->CreateRootWindow( std::move(config)); } @@ -1112,7 +1110,7 @@ bool ClientHandler::CreatePopupWindow(CefRefPtr browser, // The popup browser will be parented to a new native window. // Don't show URL bar and navigation buttons on DevTools windows. - MainContext::Get()->GetRootWindowManager()->CreateRootWindowAsPopup( + HumanAppContext::Get()->GetRootWindowManager()->CreateRootWindowAsPopup( with_controls_ && !is_devtools, is_osr_, popupFeatures, windowInfo, client, settings); diff --git a/src/CEF/ClientHandler.h b/src/CEF/ClientHandler.h index 7972be3..41bf26d 100644 --- a/src/CEF/ClientHandler.h +++ b/src/CEF/ClientHandler.h @@ -11,7 +11,7 @@ #include "include/wrapper/cef_message_router.h" #include "include/wrapper/cef_resource_manager.h" #include "CEF/HumanApptypes.h" -//#include "tests/cefclient/browser/test_runner.h" +#include "CEf/TestRunner.h" #if defined(OS_LINUX) #include "tests/cefclient/browser/dialog_handler_gtk.h" @@ -399,7 +399,7 @@ class ClientHandler : public CefClient, // Used to manage string resources in combination with StringResourceProvider. // Only accessed on the IO thread. - //test_runner::StringResourceMap string_resource_map_; + StringResourceMap string_resource_map_; // MAIN THREAD MEMBERS // The following members will only be accessed on the main thread. This will diff --git a/src/CEF/ClientHandlerOSR.cpp b/src/CEF/ClientHandlerOSR.cpp index 14ea328..29c17b1 100644 --- a/src/CEF/ClientHandlerOSR.cpp +++ b/src/CEF/ClientHandlerOSR.cpp @@ -2,14 +2,12 @@ // 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/client_handler_osr.h" +#include "CEF/ClientHandlerOSR.h" #include "include/base/cef_callback.h" #include "include/wrapper/cef_closure_task.h" #include "include/wrapper/cef_helpers.h" -namespace client { - ClientHandlerOsr::ClientHandlerOsr(Delegate* delegate, OsrDelegate* osr_delegate, bool with_controls, @@ -182,5 +180,3 @@ void ClientHandlerOsr::OnAccessibilityLocationChange( return; osr_delegate_->UpdateAccessibilityLocation(value); } - -} // namespace client diff --git a/src/CEF/ClientHandlerOSR.h b/src/CEF/ClientHandlerOSR.h index 1fe247e..59c6fdd 100644 --- a/src/CEF/ClientHandlerOSR.h +++ b/src/CEF/ClientHandlerOSR.h @@ -2,13 +2,9 @@ // reserved. Use of this source code is governed by a BSD-style license that // can be found in the LICENSE file. -#ifndef CEF_TESTS_CEFCLIENT_BROWSER_CLIENT_HANDLER_OSR_H_ -#define CEF_TESTS_CEFCLIENT_BROWSER_CLIENT_HANDLER_OSR_H_ #pragma once -#include "tests/cefclient/browser/client_handler.h" - -namespace client { +#include "CEF/ClientHandler.h" // Client handler implementation for windowless browsers. There will only ever // be one browser per handler instance. @@ -146,7 +142,3 @@ class ClientHandlerOsr : public ClientHandler, IMPLEMENT_REFCOUNTING(ClientHandlerOsr); DISALLOW_COPY_AND_ASSIGN(ClientHandlerOsr); }; - -} // namespace client - -#endif // CEF_TESTS_CEFCLIENT_BROWSER_CLIENT_HANDLER_OSR_H_ diff --git a/src/CEF/ClientHandlerStd.cpp b/src/CEF/ClientHandlerStd.cpp index e78166d..a61e80d 100644 --- a/src/CEF/ClientHandlerStd.cpp +++ b/src/CEF/ClientHandlerStd.cpp @@ -2,13 +2,9 @@ // 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/client_handler_std.h" - -namespace client { +#include "CEF/ClientHandlerStd.h" ClientHandlerStd::ClientHandlerStd(Delegate* delegate, bool with_controls, const std::string& startup_url) : ClientHandler(delegate, /*is_osr=*/false, with_controls, startup_url) {} - -} // namespace client diff --git a/src/CEF/ClientHandlerStd.h b/src/CEF/ClientHandlerStd.h index 02aa747..becb4b7 100644 --- a/src/CEF/ClientHandlerStd.h +++ b/src/CEF/ClientHandlerStd.h @@ -2,13 +2,9 @@ // reserved. Use of this source code is governed by a BSD-style license that // can be found in the LICENSE file. -#ifndef CEF_TESTS_CEFCLIENT_BROWSER_CLIENT_HANDLER_STD_H_ -#define CEF_TESTS_CEFCLIENT_BROWSER_CLIENT_HANDLER_STD_H_ #pragma once -#include "tests/cefclient/browser/client_handler.h" - -namespace client { +#include "CEF/ClientHandler.h" // Client handler implementation for windowed browsers. There will only ever be // one browser per handler instance. @@ -23,7 +19,3 @@ class ClientHandlerStd : public ClientHandler { IMPLEMENT_REFCOUNTING(ClientHandlerStd); DISALLOW_COPY_AND_ASSIGN(ClientHandlerStd); }; - -} // namespace client - -#endif // CEF_TESTS_CEFCLIENT_BROWSER_CLIENT_HANDLER_STD_H_ diff --git a/src/CEF/DialogTest.cpp b/src/CEF/DialogTest.cpp new file mode 100644 index 0000000..6d09bb7 --- /dev/null +++ b/src/CEF/DialogTest.cpp @@ -0,0 +1,168 @@ +// Copyright (c) 2012 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 "CEF/DialogTest.h" + +#include + +#include "include/cef_browser.h" +#include "include/wrapper/cef_helpers.h" +#include "CEF/TestRunner.h" +#include "CEF/FileUtil.h" + +const char kTestUrlPath[] = "/dialogs"; +const char kFileOpenPngMessageName[] = "DialogTest.FileOpenPng"; +const char kFileOpenImageMessageName[] = "DialogTest.FileOpenImage"; +const char kFileOpenMultipleMessageName[] = "DialogTest.FileOpenMultiple"; +const char kFileOpenFolderMessageName[] = "DialogTest.FileOpenFolder"; +const char kFileSaveMessageName[] = "DialogTest.FileSave"; + +// Store persistent dialog state information. +class DialogState : public base::RefCountedThreadSafe { + public: + DialogState() : mode_(FILE_DIALOG_OPEN), pending_(false) {} + + cef_file_dialog_mode_t mode_; + CefString last_file_; + bool pending_; + + DISALLOW_COPY_AND_ASSIGN(DialogState); +}; + +// Callback executed when the file dialog is dismissed. +class DialogCallback : public CefRunFileDialogCallback { + public: + DialogCallback( + CefRefPtr router_callback, + scoped_refptr dialog_state) + : router_callback_(router_callback), dialog_state_(dialog_state) {} + + virtual void OnFileDialogDismissed( + const std::vector& file_paths) override { + CEF_REQUIRE_UI_THREAD(); + DCHECK(dialog_state_->pending_); + + if (!file_paths.empty()) { + dialog_state_->last_file_ = file_paths[0]; + if (dialog_state_->mode_ == FILE_DIALOG_OPEN_FOLDER) { + std::string last_file = dialog_state_->last_file_; + if (last_file[last_file.length() - 1] != kPathSep) { + // Add a trailing slash so we know it's a directory. Otherwise, file + // dialogs will think the last path component is a file name. + last_file += kPathSep; + dialog_state_->last_file_ = last_file; + } + } + } + + // Send a message back to the render process with the list of file paths. + std::string response; + for (int i = 0; i < static_cast(file_paths.size()); ++i) { + if (!response.empty()) + response += "|"; // Use a delimiter disallowed in file paths. + response += file_paths[i]; + } + + router_callback_->Success(response); + router_callback_ = nullptr; + + dialog_state_->pending_ = false; + dialog_state_ = nullptr; + } + + private: + CefRefPtr router_callback_; + scoped_refptr dialog_state_; + + IMPLEMENT_REFCOUNTING(DialogCallback); + DISALLOW_COPY_AND_ASSIGN(DialogCallback); +}; + +// Handle messages in the browser process. +class Handler : public CefMessageRouterBrowserSide::Handler { + public: + Handler() {} + + // Called due to cefQuery execution in dialogs.html. + virtual bool OnQuery(CefRefPtr browser, + CefRefPtr frame, + int64 query_id, + const CefString& request, + bool persistent, + CefRefPtr callback) override { + CEF_REQUIRE_UI_THREAD(); + + // Only handle messages from the test URL. + const std::string& url = frame->GetURL(); + if (!IsTestURL(url, kTestUrlPath)) + return false; + + if (!dialog_state_.get()) + dialog_state_ = new DialogState; + + // Make sure we're only running one dialog at a time. + DCHECK(!dialog_state_->pending_); + + std::vector accept_filters; + std::string title; + + const std::string& message_name = request; + if (message_name == kFileOpenPngMessageName) { + dialog_state_->mode_ = FILE_DIALOG_OPEN; + title = "My Open PNG Dialog"; + accept_filters.push_back(".png"); + } else if (message_name == kFileOpenImageMessageName) { + dialog_state_->mode_ = FILE_DIALOG_OPEN; + title = "My Open Image Dialog"; + accept_filters.push_back("image/*"); + } else if (message_name == kFileOpenMultipleMessageName) { + dialog_state_->mode_ = FILE_DIALOG_OPEN_MULTIPLE; + title = "My Open MultiType Dialog"; + } else if (message_name == kFileOpenFolderMessageName) { + dialog_state_->mode_ = FILE_DIALOG_OPEN_FOLDER; + title = "My Open Folder Dialog"; + } else if (message_name == kFileSaveMessageName) { + dialog_state_->mode_ = FILE_DIALOG_SAVE; + title = "My Save Dialog"; + } else { + NOTREACHED(); + return true; + } + + if (accept_filters.empty() && + dialog_state_->mode_ != FILE_DIALOG_OPEN_FOLDER) { + // Build filters based on mime time. + accept_filters.push_back("text/*"); + + // Build filters based on file extension. + accept_filters.push_back(".log"); + accept_filters.push_back(".patch"); + + // Add specific filters as-is. + accept_filters.push_back("Document Files|.doc;.odt"); + accept_filters.push_back("Image Files|.png;.jpg;.gif"); + accept_filters.push_back("PDF Files|.pdf"); + } + + dialog_state_->pending_ = true; + + browser->GetHost()->RunFileDialog( + dialog_state_->mode_, title, dialog_state_->last_file_, accept_filters, + new DialogCallback(callback, dialog_state_)); + + return true; + } + + private: + scoped_refptr dialog_state_; + + DISALLOW_COPY_AND_ASSIGN(Handler); +}; + + +namespace dialog_test { + void CreateMessageHandlers(MessageHandlerSet& handlers) { + handlers.insert(new Handler()); + } +} diff --git a/src/CEF/DialogTest.h b/src/CEF/DialogTest.h new file mode 100644 index 0000000..619d7c4 --- /dev/null +++ b/src/CEF/DialogTest.h @@ -0,0 +1,12 @@ +// Copyright (c) 2012 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. + +#pragma once + +#include "CEF/TestRunner.h" + +namespace dialog_test { + // Create message handlers. Called from test_runner.cc. + void CreateMessageHandlers(MessageHandlerSet& handlers); +} diff --git a/src/CEF/FileUtil.cpp b/src/CEF/FileUtil.cpp index 62ef50e..b9b4029 100644 --- a/src/CEF/FileUtil.cpp +++ b/src/CEF/FileUtil.cpp @@ -48,7 +48,7 @@ bool ReadFileToString(const std::string& path, // Hence, the file is read sequentially as opposed to a one-shot read. while ((len = fread(buf.get(), 1, kBufferSize, file)) > 0) { if (contents) - contents->append(buf.get(), std::min(len, max_size - size)); + contents->append(buf.get(), min(len, max_size - size)); if ((max_size - size) < len) { read_status = false; diff --git a/src/CEF/FileUtil.h b/src/CEF/FileUtil.h index a18b99c..7ae7455 100644 --- a/src/CEF/FileUtil.h +++ b/src/CEF/FileUtil.h @@ -22,7 +22,7 @@ extern const char kPathSep; // threads is not allowed. bool ReadFileToString(const std::string& path, std::string* contents, - size_t max_size = std::numeric_limits::max()); + size_t max_size = (std::numeric_limits::max)()); // Writes the given buffer into the file, overwriting any data that was // previously there. Returns the number of bytes written, or -1 on error. diff --git a/src/CEF/GeometryUtil.cpp b/src/CEF/GeometryUtil.cpp new file mode 100644 index 0000000..445c120 --- /dev/null +++ b/src/CEF/GeometryUtil.cpp @@ -0,0 +1,34 @@ +// 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 "CEF/GeometryUtil.h" + +#include + +int LogicalToDevice(int value, float device_scale_factor) { + float scaled_val = static_cast(value) * device_scale_factor; + return static_cast(std::floor(scaled_val)); +} + +CefRect LogicalToDevice(const CefRect& value, float device_scale_factor) { + return CefRect(LogicalToDevice(value.x, device_scale_factor), + LogicalToDevice(value.y, device_scale_factor), + LogicalToDevice(value.width, device_scale_factor), + LogicalToDevice(value.height, device_scale_factor)); +} + +int DeviceToLogical(int value, float device_scale_factor) { + float scaled_val = static_cast(value) / device_scale_factor; + return static_cast(std::floor(scaled_val)); +} + +void DeviceToLogical(CefMouseEvent& value, float device_scale_factor) { + value.x = DeviceToLogical(value.x, device_scale_factor); + value.y = DeviceToLogical(value.y, device_scale_factor); +} + +void DeviceToLogical(CefTouchEvent& value, float device_scale_factor) { + value.x = DeviceToLogical(value.x, device_scale_factor); + value.y = DeviceToLogical(value.y, device_scale_factor); +} diff --git a/src/CEF/GeometryUtil.h b/src/CEF/GeometryUtil.h new file mode 100644 index 0000000..3bd4976 --- /dev/null +++ b/src/CEF/GeometryUtil.h @@ -0,0 +1,16 @@ +// 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. + +#pragma once + +#include "include/internal/cef_types_wrappers.h" + +// Convert |value| from logical coordinates to device coordinates. +int LogicalToDevice(int value, float device_scale_factor); +CefRect LogicalToDevice(const CefRect& value, float device_scale_factor); + +// Convert |value| from device coordinates to logical coordinates. +int DeviceToLogical(int value, float device_scale_factor); +void DeviceToLogical(CefMouseEvent& value, float device_scale_factor); +void DeviceToLogical(CefTouchEvent& value, float device_scale_factor); diff --git a/src/CEF/HumanAppBrowser.cpp b/src/CEF/HumanAppBrowser.cpp index c004fee..967438b 100644 --- a/src/CEF/HumanAppBrowser.cpp +++ b/src/CEF/HumanAppBrowser.cpp @@ -6,6 +6,7 @@ #include +#include "HumanAppContext.h" #include "include/cef_browser.h" #include "include/cef_command_line.h" #include "include/views/cef_browser_view.h" @@ -46,7 +47,7 @@ public: CefRefPtr app, CefRefPtr command_line) override { // Append Chromium command line parameters if touch events are enabled - if (MainContext::Get()->TouchEventsEnabled()) + if (HumanAppContext::Get()->TouchEventsEnabled()) command_line->AppendSwitchWithValue("touch-events", "enabled"); } diff --git a/src/CEF/HumanAppContentImplWin.cpp b/src/CEF/HumanAppContentImplWin.cpp new file mode 100644 index 0000000..fc157b5 --- /dev/null +++ b/src/CEF/HumanAppContentImplWin.cpp @@ -0,0 +1,36 @@ +// 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 "CEF/HumanAppContextImpl.h" + +#include +#include + +std::string HumanAppContextImpl::GetDownloadPath(const std::string& file_name) { + TCHAR szFolderPath[MAX_PATH]; + std::string path; + + // Save the file in the user's "My Documents" folder. + if (SUCCEEDED(SHGetFolderPath(nullptr, CSIDL_PERSONAL | CSIDL_FLAG_CREATE, + nullptr, 0, szFolderPath))) { + path = CefString(szFolderPath); + path += "\\" + file_name; + } + + return path; +} + +std::string HumanAppContextImpl::GetAppWorkingDirectory() { + char szWorkingDir[MAX_PATH + 1]; + if (_getcwd(szWorkingDir, MAX_PATH) == nullptr) { + szWorkingDir[0] = 0; + } else { + // Add trailing path separator. + size_t len = strlen(szWorkingDir); + szWorkingDir[len] = '\\'; + szWorkingDir[len + 1] = 0; + } + return szWorkingDir; +} + diff --git a/src/CEF/HumanAppContextImpl.cpp b/src/CEF/HumanAppContextImpl.cpp index c843c4e..56f1bcd 100644 --- a/src/CEF/HumanAppContextImpl.cpp +++ b/src/CEF/HumanAppContextImpl.cpp @@ -9,6 +9,7 @@ #include "include/cef_parser.h" #include "CEF/HumanAppBrowser.h" #include "CEF/HumanAppSwitches.h" +#include "CEF/RootWindoManager.h" // The default URL to load in a browser window. const char kDefaultUrl[] = "http://www.google.com"; diff --git a/src/CEF/MainMessageLoop.cpp b/src/CEF/MainMessageLoop.cpp index 23446e0..d3c2f84 100644 --- a/src/CEF/MainMessageLoop.cpp +++ b/src/CEF/MainMessageLoop.cpp @@ -2,19 +2,13 @@ // reserved. Use of this source code is governed by a BSD-style license that // can be found in the LICENSE file. -#include "tests/shared/browser/main_message_loop.h" +#include "CEF/MainMessageLoop.h" #include "include/cef_task.h" #include "include/wrapper/cef_closure_task.h" -namespace client { - -namespace { - MainMessageLoop* g_main_message_loop = nullptr; -} // namespace - MainMessageLoop::MainMessageLoop() { DCHECK(!g_main_message_loop); g_main_message_loop = this; @@ -38,4 +32,3 @@ void MainMessageLoop::PostClosure(const base::RepeatingClosure& closure) { PostTask(CefCreateClosureTask(closure)); } -} // namespace client diff --git a/src/CEF/MainMessageLoop.h b/src/CEF/MainMessageLoop.h index 9641601..100e9fd 100644 --- a/src/CEF/MainMessageLoop.h +++ b/src/CEF/MainMessageLoop.h @@ -60,14 +60,14 @@ class MainMessageLoop { }; #define CURRENTLY_ON_MAIN_THREAD() \ - client::MainMessageLoop::Get()->RunsTasksOnCurrentThread() + MainMessageLoop::Get()->RunsTasksOnCurrentThread() #define REQUIRE_MAIN_THREAD() DCHECK(CURRENTLY_ON_MAIN_THREAD()) -#define MAIN_POST_TASK(task) client::MainMessageLoop::Get()->PostTask(task) +#define MAIN_POST_TASK(task) MainMessageLoop::Get()->PostTask(task) #define MAIN_POST_CLOSURE(closure) \ - client::MainMessageLoop::Get()->PostClosure(closure) + MainMessageLoop::Get()->PostClosure(closure) // Use this struct in conjuction with RefCountedThreadSafe to ensure that an // object is deleted on the main thread. For example: @@ -95,7 +95,7 @@ struct DeleteOnMainThread { if (CURRENTLY_ON_MAIN_THREAD()) { delete x; } else { - client::MainMessageLoop::Get()->PostClosure(base::BindOnce( + MainMessageLoop::Get()->PostClosure(base::BindOnce( &DeleteOnMainThread::Destruct, base::Unretained(x))); } } diff --git a/src/CEF/MainMessageLoopExternalPump.cpp b/src/CEF/MainMessageLoopExternalPump.cpp index b589f66..c8a84b6 100644 --- a/src/CEF/MainMessageLoopExternalPump.cpp +++ b/src/CEF/MainMessageLoopExternalPump.cpp @@ -2,17 +2,13 @@ // reserved. Use of this source code is governed by a BSD-style license that // can be found in the LICENSE file. -#include "tests/shared/browser/main_message_loop_external_pump.h" +#include "CEF/MainMessageLoopExternalPump.h" #include #include "include/cef_app.h" #include "include/wrapper/cef_helpers.h" -#include "tests/shared/browser/main_message_loop.h" - -namespace client { - -namespace { +#include "CEF/MainMessageLoop.h" // Special timer delay placeholder value. Intentionally 32-bit for Windows and // OS X platform API compatibility. @@ -22,9 +18,8 @@ const int32 kTimerDelayPlaceholder = INT_MAX; // DoWork(). const int64 kMaxTimerDelay = 1000 / 30; // 30fps -client::MainMessageLoopExternalPump* g_external_message_pump = nullptr; +MainMessageLoopExternalPump* g_external_message_pump = nullptr; -} // namespace MainMessageLoopExternalPump::MainMessageLoopExternalPump() : is_active_(false), reentrancy_detected_(false) { @@ -104,4 +99,3 @@ bool MainMessageLoopExternalPump::PerformMessageLoopWork() { return reentrancy_detected_; } -} // namespace client diff --git a/src/CEF/MainMessageLoopExternalPump.h b/src/CEF/MainMessageLoopExternalPump.h index fa3236e..555214b 100644 --- a/src/CEF/MainMessageLoopExternalPump.h +++ b/src/CEF/MainMessageLoopExternalPump.h @@ -2,13 +2,9 @@ // reserved. Use of this source code is governed by a BSD-style license that // can be found in the LICENSE file. -#ifndef CEF_TESTS_SHARED_BROWSER_MAIN_MESSAGE_LOOP_EXTERNAL_PUMP_H_ -#define CEF_TESTS_SHARED_BROWSER_MAIN_MESSAGE_LOOP_EXTERNAL_PUMP_H_ #pragma once -#include "tests/shared/browser/main_message_loop_std.h" - -namespace client { +#include "CEF/MainMessageLoopStd.h" // This MessageLoop implementation simulates the embedding of CEF into an // existing host application that runs its own message loop. The scheduling @@ -64,7 +60,3 @@ class MainMessageLoopExternalPump : public MainMessageLoopStd { bool is_active_; bool reentrancy_detected_; }; - -} // namespace client - -#endif // CEF_TESTS_SHARED_BROWSER_MAIN_MESSAGE_LOOP_EXTERNAL_PUMP_H_ diff --git a/src/CEF/MainMessageLoopExternalPumpWin.cpp b/src/CEF/MainMessageLoopExternalPumpWin.cpp index ae95358..9becdb2 100644 --- a/src/CEF/MainMessageLoopExternalPumpWin.cpp +++ b/src/CEF/MainMessageLoopExternalPumpWin.cpp @@ -2,18 +2,14 @@ // reserved. Use of this source code is governed by a BSD-style license that // can be found in the LICENSE file. -#include "tests/shared/browser/main_message_loop_external_pump.h" +#include "CEF/MainMessageLoopExternalPump.h" #include #include #include "include/cef_app.h" -#include "tests/shared/browser/util_win.h" - -namespace client { - -namespace { +#include "CEF/UtilWin.h" // Message sent to get an additional time slice for pumping (processing) another // task (a series of such messages creates a continuous task pump). @@ -143,12 +139,8 @@ LRESULT CALLBACK MainMessageLoopExternalPumpWin::WndProc(HWND hwnd, return DefWindowProc(hwnd, msg, wparam, lparam); } -} // namespace - // static std::unique_ptr MainMessageLoopExternalPump::Create() { return std::make_unique(); } - -} // namespace client diff --git a/src/CEF/MainMessageLoopStd.cpp b/src/CEF/MainMessageLoopStd.cpp index d329f90..e6ad2c8 100644 --- a/src/CEF/MainMessageLoopStd.cpp +++ b/src/CEF/MainMessageLoopStd.cpp @@ -2,12 +2,10 @@ // reserved. Use of this source code is governed by a BSD-style license that // can be found in the LICENSE file. -#include "tests/shared/browser/main_message_loop_std.h" +#include "CEF/MainMessageLoopStd.h" #include "include/cef_app.h" -namespace client { - MainMessageLoopStd::MainMessageLoopStd() {} int MainMessageLoopStd::Run() { @@ -33,5 +31,3 @@ void MainMessageLoopStd::SetCurrentModelessDialog(HWND hWndDialog) { // internally route dialog messages. } #endif - -} // namespace client diff --git a/src/CEF/MainMessageLoopStd.h b/src/CEF/MainMessageLoopStd.h index 3bded74..7443ea6 100644 --- a/src/CEF/MainMessageLoopStd.h +++ b/src/CEF/MainMessageLoopStd.h @@ -2,13 +2,9 @@ // reserved. Use of this source code is governed by a BSD-style license that // can be found in the LICENSE file. -#ifndef CEF_TESTS_SHARED_BROWSER_MAIN_MESSAGE_LOOP_STD_H_ -#define CEF_TESTS_SHARED_BROWSER_MAIN_MESSAGE_LOOP_STD_H_ #pragma once -#include "tests/shared/browser/main_message_loop.h" - -namespace client { +#include "CEF/MainMessageLoop.h" // Represents the main message loop in the browser process. This implementation // is a light-weight wrapper around the Chromium UI thread. @@ -30,6 +26,3 @@ class MainMessageLoopStd : public MainMessageLoop { DISALLOW_COPY_AND_ASSIGN(MainMessageLoopStd); }; -} // namespace client - -#endif // CEF_TESTS_SHARED_BROWSER_MAIN_MESSAGE_LOOP_STD_H_ diff --git a/src/CEF/MediaRouterTest.cpp b/src/CEF/MediaRouterTest.cpp new file mode 100644 index 0000000..a8b6c5f --- /dev/null +++ b/src/CEF/MediaRouterTest.cpp @@ -0,0 +1,584 @@ +// Copyright (c) 2020 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 "CEF/MediaRouterTest.h" + +#include +#include + +#include "include/base/cef_logging.h" +#include "include/cef_media_router.h" +#include "include/cef_parser.h" +#include "CEF/TestRunner.h" + +const char kTestUrlPath[] = "/media_router"; + +// Application-specific error codes. +const int kMessageFormatError = 1; +const int kRequestFailedError = 2; + +// Message strings. +const char kNameKey[] = "name"; +const char kNameValueSubscribe[] = "subscribe"; +const char kNameValueCreateRoute[] = "createRoute"; +const char kNameValueTerminateRoute[] = "terminateRoute"; +const char kNameValueSendMessage[] = "sendMessage"; +const char kSourceKey[] = "source_urn"; +const char kSinkKey[] = "sink_id"; +const char kRouteKey[] = "route_id"; +const char kMessageKey[] = "message"; +const char kSuccessKey[] = "success"; +const char kPayloadKey[] = "payload"; + +// Convert a dictionary value to a JSON string. +CefString GetJSON(CefRefPtr dictionary) { + CefRefPtr value = CefValue::Create(); + value->SetDictionary(dictionary); + return CefWriteJSON(value, JSON_WRITER_DEFAULT); +} + +typedef CefMessageRouterBrowserSide::Callback CallbackType; + +void SendSuccess(CefRefPtr callback, + CefRefPtr result) { + callback->Success(GetJSON(result)); +} + +void SendFailure(CefRefPtr callback, + int error_code, + const std::string& error_message) { + callback->Failure(error_code, error_message); +} + +// Callback for CefMediaRouter::CreateRoute. +class MediaRouteCreateCallback : public CefMediaRouteCreateCallback { + public: + explicit MediaRouteCreateCallback(CefRefPtr create_callback) + : create_callback_(create_callback) {} + + // CefMediaRouteCreateCallback method: + void OnMediaRouteCreateFinished(RouteCreateResult result, + const CefString& error, + CefRefPtr route) override { + CEF_REQUIRE_UI_THREAD(); + if (result == CEF_MRCR_OK) { + CefRefPtr dict = CefDictionaryValue::Create(); + dict->SetString(kRouteKey, route->GetId()); + SendSuccess(create_callback_, dict); + } else { + SendFailure(create_callback_, kRequestFailedError + result, error); + } + create_callback_ = nullptr; + } + + private: + CefRefPtr create_callback_; + + IMPLEMENT_REFCOUNTING(MediaRouteCreateCallback); + DISALLOW_COPY_AND_ASSIGN(MediaRouteCreateCallback); +}; + +// Observes MediaRouter events. Only accessed on the UI thread. +class MediaObserver : public CefMediaObserver { + public: + typedef std::vector> MediaRouteVector; + typedef std::vector> MediaSinkVector; + + MediaObserver(CefRefPtr media_router, + CefRefPtr subscription_callback) + : media_router_(media_router), + subscription_callback_(subscription_callback), + next_sink_query_id_(0), + pending_sink_query_id_(-1), + pending_sink_callbacks_(0U) {} + + ~MediaObserver() override { ClearSinkInfoMap(); } + + bool CreateRoute(const std::string& source_urn, + const std::string& sink_id, + CefRefPtr callback, + std::string& error) { + CefRefPtr source = GetSource(source_urn); + if (!source) { + error = "Invalid source: " + source_urn; + return false; + } + + CefRefPtr sink = GetSink(sink_id); + if (!sink) { + error = "Invalid sink: " + sink_id; + return false; + } + + media_router_->CreateRoute(source, sink, + new MediaRouteCreateCallback(callback)); + return true; + } + + bool TerminateRoute(const std::string& route_id, std::string& error) { + CefRefPtr route = GetRoute(route_id); + if (!route) { + error = "Invalid route: " + route_id; + return false; + } + + route->Terminate(); + return true; + } + + bool SendRouteMessage(const std::string& route_id, + const std::string& message, + std::string& error) { + CefRefPtr route = GetRoute(route_id); + if (!route) { + error = "Invalid route: " + route_id; + return false; + } + + route->SendRouteMessage(message.c_str(), message.size()); + return true; + } + + protected: + class DeviceInfoCallback : public CefMediaSinkDeviceInfoCallback { + public: + // Callback to be executed when the device info is available. + using CallbackType = + base::OnceCallback; + + DeviceInfoCallback(const std::string& sink_id, CallbackType callback) + : sink_id_(sink_id), callback_(std::move(callback)) {} + + void OnMediaSinkDeviceInfo( + const CefMediaSinkDeviceInfo& device_info) override { + CEF_REQUIRE_UI_THREAD(); + std::move(callback_).Run(sink_id_, device_info); + } + + private: + const std::string sink_id_; + CallbackType callback_; + + IMPLEMENT_REFCOUNTING(DeviceInfoCallback); + DISALLOW_COPY_AND_ASSIGN(DeviceInfoCallback); + }; + + // CefMediaObserver methods: + void OnSinks(const MediaSinkVector& sinks) override { + CEF_REQUIRE_UI_THREAD(); + + ClearSinkInfoMap(); + + // Reset pending sink state. + pending_sink_callbacks_ = sinks.size(); + pending_sink_query_id_ = ++next_sink_query_id_; + + if (sinks.empty()) { + // No sinks, send the response immediately. + SendSinksResponse(); + return; + } + + MediaSinkVector::const_iterator it = sinks.begin(); + for (size_t idx = 0; it != sinks.end(); ++it, ++idx) { + CefRefPtr sink = *it; + const std::string& sink_id = sink->GetId(); + SinkInfo* info = new SinkInfo; + info->sink = sink; + sink_info_map_.insert(std::make_pair(sink_id, info)); + + // Request the device info asynchronously. Send the response once all + // callbacks have executed. + auto callback = base::BindOnce(&MediaObserver::OnSinkDeviceInfo, this, + pending_sink_query_id_); + sink->GetDeviceInfo(new DeviceInfoCallback(sink_id, std::move(callback))); + } + } + + void OnRoutes(const MediaRouteVector& routes) override { + CEF_REQUIRE_UI_THREAD(); + + route_map_.clear(); + + CefRefPtr payload = CefDictionaryValue::Create(); + CefRefPtr routes_list = CefListValue::Create(); + routes_list->SetSize(routes.size()); + + MediaRouteVector::const_iterator it = routes.begin(); + for (size_t idx = 0; it != routes.end(); ++it, ++idx) { + CefRefPtr route = *it; + const std::string& route_id = route->GetId(); + route_map_.insert(std::make_pair(route_id, route)); + + CefRefPtr route_dict = CefDictionaryValue::Create(); + route_dict->SetString("id", route_id); + route_dict->SetString(kSourceKey, route->GetSource()->GetId()); + route_dict->SetString(kSinkKey, route->GetSink()->GetId()); + routes_list->SetDictionary(idx, route_dict); + } + + payload->SetList("routes_list", routes_list); + SendResponse("onRoutes", payload); + } + + void OnRouteStateChanged(CefRefPtr route, + ConnectionState state) override { + CEF_REQUIRE_UI_THREAD(); + + CefRefPtr payload = CefDictionaryValue::Create(); + payload->SetString(kRouteKey, route->GetId()); + payload->SetInt("connection_state", state); + SendResponse("onRouteStateChanged", payload); + } + + void OnRouteMessageReceived(CefRefPtr route, + const void* message, + size_t message_size) override { + CEF_REQUIRE_UI_THREAD(); + + std::string message_str(static_cast(message), message_size); + + CefRefPtr payload = CefDictionaryValue::Create(); + payload->SetString(kRouteKey, route->GetId()); + payload->SetString(kMessageKey, message_str); + SendResponse("onRouteMessageReceived", payload); + } + + private: + CefRefPtr GetSource(const std::string& source_urn) { + CefRefPtr source = media_router_->GetSource(source_urn); + if (!source) + return nullptr; + return source; + } + + CefRefPtr GetSink(const std::string& sink_id) { + SinkInfoMap::const_iterator it = sink_info_map_.find(sink_id); + if (it != sink_info_map_.end()) + return it->second->sink; + return nullptr; + } + + void ClearSinkInfoMap() { + SinkInfoMap::const_iterator it = sink_info_map_.begin(); + for (; it != sink_info_map_.end(); ++it) { + delete it->second; + } + sink_info_map_.clear(); + } + + void OnSinkDeviceInfo(int sink_query_id, + const std::string& sink_id, + const CefMediaSinkDeviceInfo& device_info) { + // Discard callbacks that arrive after a new call to OnSinks(). + if (sink_query_id != pending_sink_query_id_) + return; + + SinkInfoMap::const_iterator it = sink_info_map_.find(sink_id); + if (it != sink_info_map_.end()) { + it->second->device_info = device_info; + } + + // Send the response once we've received all expected callbacks. + DCHECK_GT(pending_sink_callbacks_, 0U); + if (--pending_sink_callbacks_ == 0U) { + SendSinksResponse(); + } + } + + CefRefPtr GetRoute(const std::string& route_id) { + RouteMap::const_iterator it = route_map_.find(route_id); + if (it != route_map_.end()) + return it->second; + return nullptr; + } + + void SendResponse(const std::string& name, + CefRefPtr payload) { + CefRefPtr result = CefDictionaryValue::Create(); + result->SetString(kNameKey, name); + result->SetDictionary(kPayloadKey, payload); + SendSuccess(subscription_callback_, result); + } + + void SendSinksResponse() { + CefRefPtr payload = CefDictionaryValue::Create(); + CefRefPtr sinks_list = CefListValue::Create(); + sinks_list->SetSize(sink_info_map_.size()); + + SinkInfoMap::const_iterator it = sink_info_map_.begin(); + for (size_t idx = 0; it != sink_info_map_.end(); ++it, ++idx) { + const SinkInfo* info = it->second; + + CefRefPtr sink_dict = CefDictionaryValue::Create(); + sink_dict->SetString("id", it->first); + sink_dict->SetString("name", info->sink->GetName()); + sink_dict->SetString("desc", info->sink->GetDescription()); + sink_dict->SetInt("icon", info->sink->GetIconType()); + sink_dict->SetString("ip_address", + CefString(&info->device_info.ip_address)); + sink_dict->SetInt("port", info->device_info.port); + sink_dict->SetString("model_name", + CefString(&info->device_info.model_name)); + sink_dict->SetString("type", + info->sink->IsCastSink() + ? "cast" + : info->sink->IsDialSink() ? "dial" : "unknown"); + sinks_list->SetDictionary(idx, sink_dict); + } + + payload->SetList("sinks_list", sinks_list); + SendResponse("onSinks", payload); + } + + CefRefPtr media_router_; + CefRefPtr subscription_callback_; + + struct SinkInfo { + CefRefPtr sink; + CefMediaSinkDeviceInfo device_info; + }; + typedef std::map SinkInfoMap; + + // Used to uniquely identify a call to OnSinks(), for the purpose of + // associating OnMediaSinkDeviceInfo() callbacks. + int next_sink_query_id_; + + // State from the most recent call to OnSinks(). + SinkInfoMap sink_info_map_; + int pending_sink_query_id_; + size_t pending_sink_callbacks_; + + // State from the most recent call to OnRoutes(). + typedef std::map> RouteMap; + RouteMap route_map_; + + IMPLEMENT_REFCOUNTING(MediaObserver); + DISALLOW_COPY_AND_ASSIGN(MediaObserver); +}; + +// Handle messages in the browser process. Only accessed on the UI thread. +class Handler : public CefMessageRouterBrowserSide::Handler { + public: + typedef std::vector NameVector; + + Handler() { CEF_REQUIRE_UI_THREAD(); } + + virtual ~Handler() { + SubscriptionStateMap::iterator it = subscription_state_map_.begin(); + for (; it != subscription_state_map_.end(); ++it) { + delete it->second; + } + } + + // Called due to cefQuery execution in media_router.html. + bool OnQuery(CefRefPtr browser, + CefRefPtr frame, + int64 query_id, + const CefString& request, + bool persistent, + CefRefPtr callback) override { + CEF_REQUIRE_UI_THREAD(); + + // Only handle messages from the test URL. + const std::string& url = frame->GetURL(); + if (!IsTestURL(url, kTestUrlPath)) + return false; + + // Parse |request| as a JSON dictionary. + CefRefPtr request_dict = ParseJSON(request); + if (!request_dict) { + SendFailure(callback, kMessageFormatError, "Incorrect message format"); + return true; + } + + // Verify the "name" key. + if (!VerifyKey(request_dict, kNameKey, VTYPE_STRING, callback)) + return true; + + const std::string& message_name = request_dict->GetString(kNameKey); + if (message_name == kNameValueSubscribe) { + // Subscribe to notifications from the media router. + + if (!persistent) { + SendFailure(callback, kMessageFormatError, + "Subscriptions must be persistent"); + return true; + } + + if (!CreateSubscription(browser, query_id, callback)) { + SendFailure(callback, kRequestFailedError, + "Browser is already subscribed"); + } + return true; + } + + // All other messages require a current subscription. + CefRefPtr media_observer = + GetMediaObserver(browser->GetIdentifier()); + if (!media_observer) { + SendFailure(callback, kRequestFailedError, + "Browser is not currently subscribed"); + } + + if (message_name == kNameValueCreateRoute) { + // Create a new route. + + // Verify the "source_urn" key. + if (!VerifyKey(request_dict, kSourceKey, VTYPE_STRING, callback)) + return true; + // Verify the "sink_id" key. + if (!VerifyKey(request_dict, kSinkKey, VTYPE_STRING, callback)) + return true; + + const std::string& source_urn = request_dict->GetString(kSourceKey); + const std::string& sink_id = request_dict->GetString(kSinkKey); + + // |callback| will be executed once the route is created. + std::string error; + if (!media_observer->CreateRoute(source_urn, sink_id, callback, error)) { + SendFailure(callback, kRequestFailedError, error); + } + return true; + } else if (message_name == kNameValueTerminateRoute) { + // Terminate an existing route. + + // Verify the "route" key. + if (!VerifyKey(request_dict, kRouteKey, VTYPE_STRING, callback)) + return true; + + const std::string& route_id = request_dict->GetString(kRouteKey); + std::string error; + if (!media_observer->TerminateRoute(route_id, error)) { + SendFailure(callback, kRequestFailedError, error); + } else { + SendSuccessACK(callback); + } + return true; + } else if (message_name == kNameValueSendMessage) { + // Send a route message. + + // Verify the "route_id" key. + if (!VerifyKey(request_dict, kRouteKey, VTYPE_STRING, callback)) + return true; + // Verify the "message" key. + if (!VerifyKey(request_dict, kMessageKey, VTYPE_STRING, callback)) + return true; + + const std::string& route_id = request_dict->GetString(kRouteKey); + const std::string& message = request_dict->GetString(kMessageKey); + std::string error; + if (!media_observer->SendRouteMessage(route_id, message, error)) { + SendFailure(callback, kRequestFailedError, error); + } else { + SendSuccessACK(callback); + } + return true; + } + + return false; + } + + void OnQueryCanceled(CefRefPtr browser, + CefRefPtr frame, + int64 query_id) override { + CEF_REQUIRE_UI_THREAD(); + RemoveSubscription(browser->GetIdentifier(), query_id); + } + + private: + static void SendSuccessACK(CefRefPtr callback) { + CefRefPtr result = CefDictionaryValue::Create(); + result->SetBool(kSuccessKey, true); + SendSuccess(callback, result); + } + + // Convert a JSON string to a dictionary value. + static CefRefPtr ParseJSON(const CefString& string) { + CefRefPtr value = CefParseJSON(string, JSON_PARSER_RFC); + if (value.get() && value->GetType() == VTYPE_DICTIONARY) + return value->GetDictionary(); + return nullptr; + } + + // Verify that |key| exists in |dictionary| and has type |value_type|. Fails + // |callback| and returns false on failure. + static bool VerifyKey(CefRefPtr dictionary, + const char* key, + cef_value_type_t value_type, + CefRefPtr callback) { + if (!dictionary->HasKey(key) || dictionary->GetType(key) != value_type) { + SendFailure( + callback, kMessageFormatError, + "Missing or incorrectly formatted message key: " + std::string(key)); + return false; + } + return true; + } + + // Subscription state associated with a single browser. + struct SubscriptionState { + int64 query_id; + CefRefPtr observer; + CefRefPtr registration; + }; + + bool CreateSubscription(CefRefPtr browser, + int64 query_id, + CefRefPtr callback) { + const int browser_id = browser->GetIdentifier(); + if (subscription_state_map_.find(browser_id) != + subscription_state_map_.end()) { + // An subscription already exists for this browser. + return false; + } + + CefRefPtr media_router = + browser->GetHost()->GetRequestContext()->GetMediaRouter(nullptr); + + SubscriptionState* state = new SubscriptionState(); + state->query_id = query_id; + state->observer = new MediaObserver(media_router, callback); + state->registration = media_router->AddObserver(state->observer); + subscription_state_map_.insert(std::make_pair(browser_id, state)); + + // Trigger sink and route callbacks. + media_router->NotifyCurrentSinks(); + media_router->NotifyCurrentRoutes(); + + return true; + } + + void RemoveSubscription(int browser_id, int64 query_id) { + SubscriptionStateMap::iterator it = + subscription_state_map_.find(browser_id); + if (it != subscription_state_map_.end() && + it->second->query_id == query_id) { + delete it->second; + subscription_state_map_.erase(it); + } + } + + CefRefPtr GetMediaObserver(int browser_id) { + SubscriptionStateMap::const_iterator it = + subscription_state_map_.find(browser_id); + if (it != subscription_state_map_.end()) { + return it->second->observer; + } + return nullptr; + } + + // Map of browser ID to SubscriptionState object. + typedef std::map SubscriptionStateMap; + SubscriptionStateMap subscription_state_map_; + + DISALLOW_COPY_AND_ASSIGN(Handler); +}; + +namespace media_router_test { + void CreateMessageHandlers(MessageHandlerSet& handlers) { + handlers.insert(new Handler()); + } +} diff --git a/src/CEF/MediaRouterTest.h b/src/CEF/MediaRouterTest.h new file mode 100644 index 0000000..410cb95 --- /dev/null +++ b/src/CEF/MediaRouterTest.h @@ -0,0 +1,13 @@ +// Copyright (c) 2020 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. + + +#pragma once + +#include "CEF/TestRunner.h" + +namespace media_router_test { + // Create message handlers. Called from test_runner.cc. + void CreateMessageHandlers(MessageHandlerSet& handlers); +} diff --git a/src/CEF/OsrAccessibilityHelper.cpp b/src/CEF/OsrAccessibilityHelper.cpp new file mode 100644 index 0000000..01fafc0 --- /dev/null +++ b/src/CEF/OsrAccessibilityHelper.cpp @@ -0,0 +1,270 @@ +// Copyright 2017 The Chromium Embedded Framework Authors. Portions copyright +// 2013 The Chromium 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_accessibility_helper.h" +#include "tests/cefclient/browser/osr_accessibility_node.h" + +namespace client { + +OsrAXTree::OsrAXTree() : root_node_id_(-1) {} + +OsrAXNode* OsrAXTree::GetNode(int nodeId) const { + auto result = node_map_.find(nodeId); + if (result != node_map_.end()) { + return result->second; + } + return nullptr; +} + +void OsrAXTree::EraseNode(int nodeId) { + node_map_.erase(nodeId); +} + +void OsrAXTree::AddNode(OsrAXNode* node) { + node_map_[node->OsrAXNodeId()] = node; +} + +void OsrAXTree::UpdateTreeData(CefRefPtr value) { + if (value->HasKey("parent_tree_id")) { + parent_tree_id_ = value->GetString("parent_tree_id"); + } else { + parent_tree_id_ = ""; + } + + // may also update following: + // doctype, title, url, mimetype +} + +OsrAccessibilityHelper::OsrAccessibilityHelper(CefRefPtr value, + CefRefPtr browser) + : focused_node_id_(-1), + browser_(browser) { + UpdateAccessibilityTree(value); +} + +int OsrAccessibilityHelper::CastToInt(CefRefPtr value) { + if (value->GetType() == VTYPE_STRING) { + const std::string& str = value->GetString(); + return atoi(str.c_str()); + } else { + return value->GetInt(); + } +} + +void OsrAccessibilityHelper::UpdateAccessibilityLocation( + CefRefPtr value) { + if (!value || value->GetType() != VTYPE_LIST) { + return; + } + + CefRefPtr locationChangeList = value->GetList(); + size_t locationChangeCount = locationChangeList->GetSize(); + if (locationChangeCount == 0) { + return; + } + + for (size_t i = 0; i < locationChangeCount; i++) { + CefRefPtr locationChangeDict = + locationChangeList->GetDictionary(i); + if (!locationChangeDict->HasKey("ax_tree_id") || + !locationChangeDict->HasKey("new_location") || + !locationChangeDict->HasKey("id")) { + continue; + } + CefString treeId = locationChangeDict->GetString("ax_tree_id"); + int nodeId = CastToInt(locationChangeDict->GetValue("id")); + + CefRefPtr newLocationDict = + locationChangeDict->GetDictionary("new_location"); + if (!newLocationDict) { + continue; + } + + OsrAXNode* node = GetNode(treeId, nodeId); + if (!node) { + continue; + } + node->UpdateLocation(newLocationDict); + } +} + +void OsrAccessibilityHelper::UpdateAccessibilityTree( + CefRefPtr value) { + if (!value || value->GetType() != VTYPE_DICTIONARY) { + return; + } + + CefRefPtr mainDict = value->GetDictionary(); + if (!mainDict->HasKey("ax_tree_id") || !mainDict->HasKey("updates")) { + return; + } + + CefString treeId = mainDict->GetString("ax_tree_id"); + CefRefPtr updatesList = mainDict->GetList("updates"); + + size_t updatesCount = updatesList->GetSize(); + if (updatesCount == 0) { + return; + } + + for (size_t i = 0; i < updatesCount; i++) { + CefRefPtr updateDict = updatesList->GetDictionary(i); + UpdateLayout(treeId, updateDict); + } +} + +OsrAXNode* OsrAccessibilityHelper::GetRootNode() const { + return GetTreeRootNode(root_tree_id_); +} + +OsrAXNode* OsrAccessibilityHelper::GetFocusedNode() const { + auto tree = accessibility_node_map_.find(focused_tree_id_); + if (tree != accessibility_node_map_.end()) { + return tree->second.GetNode(focused_node_id_); + } + + return nullptr; +} + +OsrAXNode* OsrAccessibilityHelper::GetTreeRootNode( + const CefString& treeId) const { + auto tree = accessibility_node_map_.find(treeId); + if (tree != accessibility_node_map_.end()) { + return tree->second.GetNode(tree->second.GetRootNodeId()); + } + + return nullptr; +} + +void OsrAccessibilityHelper::UpdateLayout( + const CefString& treeId, + CefRefPtr update) { + if (!update) { + return; + } + + // If a node is to be cleared + if (update->HasKey("node_id_to_clear")) { + int nodeId = CastToInt(update->GetValue("node_id_to_clear")); + + // reset root node if that is to be cleared + auto tree = accessibility_node_map_.find(treeId); + if (tree != accessibility_node_map_.end()) { + if (tree->second.GetRootNodeId() == nodeId) { + root_tree_id_ = ""; + tree->second.SetRootNodeId(-1); + } + } + if ((focused_tree_id_ == treeId) && (focused_node_id_ == nodeId)) { + UpdateFocusedNode("", -1); + } + OsrAXNode* node = GetNode(treeId, nodeId); + DestroyNode(node); + } + + // get tree data + if (update->HasKey("tree_data") && update->HasKey("has_tree_data") && + update->GetBool("has_tree_data")) { + CefRefPtr tree_data = + update->GetDictionary("tree_data"); + auto& tree = accessibility_node_map_[treeId]; + tree.UpdateTreeData(tree_data); + if (tree.GetParentTreeId().empty()) { + root_tree_id_ = treeId; + } + if (tree_data->HasKey("focus_id") && tree_data->HasKey("focused_tree_id")) { + UpdateFocusedNode(tree_data->GetString("focused_tree_id"), + CastToInt(tree_data->GetValue("focus_id"))); + } + } + + // Now initialize/update the node data. + if (update->HasKey("nodes")) { + CefRefPtr nodes = update->GetList("nodes"); + + for (size_t index = 0; index < nodes->GetSize(); index++) { + CefRefPtr node = nodes->GetDictionary(index); + if (node) { + auto& tree = accessibility_node_map_[treeId]; + int nodeId = CastToInt(node->GetValue("id")); + OsrAXNode* axNode = tree.GetNode(nodeId); + // Create if it is a new one + if (axNode) { + axNode->UpdateValue(node); + } else { + axNode = OsrAXNode::CreateNode(treeId, nodeId, node, this); + tree.AddNode(axNode); + } + } + } + } + + if (update->HasKey("root_id")) { + int nodeId = CastToInt(update->GetValue("root_id")); + OsrAXNode* node = GetNode(treeId, nodeId); + if (node != nullptr) { + auto& tree = accessibility_node_map_[treeId]; + tree.SetRootNodeId(nodeId); + } + } +} + +void OsrAccessibilityHelper::UpdateFocusedNode(const CefString& treeId, + int nodeId) { + if ((focused_tree_id_ == treeId) && (focused_node_id_ == nodeId)) { + return; + } + focused_tree_id_ = treeId; + focused_node_id_ = nodeId; + + // Now Notify Screen Reader + OsrAXNode* axNode = GetFocusedNode(); + if (axNode) { + axNode->NotifyAccessibilityEvent("focus"); + } +} + +void OsrAccessibilityHelper::Reset() { + accessibility_node_map_.clear(); + root_tree_id_ = ""; + focused_tree_id_ = ""; + focused_node_id_ = -1; +} + +void OsrAccessibilityHelper::DestroyNode(OsrAXNode* node) { + if (node) { + CefString treeId = node->OsrAXTreeId(); + int numChilds = node->GetChildCount(); + if (numChilds > 0) { + for (int i = 0; i < numChilds; i++) { + OsrAXNode* childNode = node->ChildAtIndex(i); + if (!childNode) { + continue; + } + childNode->SetParent(nullptr); + if (childNode->OsrAXTreeId() == treeId) { + DestroyNode(childNode); + } + } + } + auto tree = accessibility_node_map_.find(treeId); + if (tree != accessibility_node_map_.end()) { + tree->second.EraseNode(node->OsrAXNodeId()); + } + + node->Destroy(); + } +} + +OsrAXNode* OsrAccessibilityHelper::GetNode(const CefString& treeId, + int nodeId) const { + auto tree = accessibility_node_map_.find(treeId); + if (tree != accessibility_node_map_.end()) { + return tree->second.GetNode(nodeId); + } + + return nullptr; +} + +} // namespace client diff --git a/src/CEF/OsrAccessibilityHelper.h b/src/CEF/OsrAccessibilityHelper.h new file mode 100644 index 0000000..83f271e --- /dev/null +++ b/src/CEF/OsrAccessibilityHelper.h @@ -0,0 +1,81 @@ +// Copyright 2017 The Chromium Embedded Framework Authors. Portions copyright +// 2013 The Chromium Authors. All rights reserved. Use of this source code is +// governed by a BSD-style license that can be found in the LICENSE file. + +#ifndef CEF_TESTS_CEFCLIENT_BROWSER_OSR_ACCESSIBILITY_HELPER_H_ +#define CEF_TESTS_CEFCLIENT_BROWSER_OSR_ACCESSIBILITY_HELPER_H_ + +#include + +#include "include/cef_browser.h" + +namespace client { + +class OsrAXNode; +class OsrAccessibilityHelper; + +class OsrAXTree { + public: + OsrAXTree(); + OsrAXNode* GetNode(int nodeId) const; + void EraseNode(int nodeId); + void UpdateTreeData(CefRefPtr value); + void AddNode(OsrAXNode* node); + const CefString& GetParentTreeId() const { return parent_tree_id_; } + int GetRootNodeId() const { return root_node_id_; } + void SetRootNodeId(int nodeId) { root_node_id_ = nodeId; } + + private: + CefString parent_tree_id_; + int root_node_id_; + std::map node_map_; +}; + +// Helper class that abstracts Renderer Accessibility tree and provides a +// uniform interface to be consumed by IAccessible interface on Windows and +// NSAccessibility implementation on Mac in CefClient. +class OsrAccessibilityHelper { + public: + OsrAccessibilityHelper(CefRefPtr value, + CefRefPtr browser); + + void UpdateAccessibilityTree(CefRefPtr value); + + void UpdateAccessibilityLocation(CefRefPtr value); + + OsrAXNode* GetRootNode() const; + + OsrAXNode* GetFocusedNode() const; + + CefWindowHandle GetWindowHandle() const { + return browser_->GetHost()->GetWindowHandle(); + } + + CefRefPtr GetBrowser() const { return browser_; } + + OsrAXNode* GetNode(const CefString& treeId, int nodeId) const; + + OsrAXNode* GetTreeRootNode(const CefString& treeId) const; + + static int CastToInt(CefRefPtr value); + + private: + void Reset(); + + void UpdateLayout(const CefString& treeId, + CefRefPtr update); + + void UpdateFocusedNode(const CefString& treeId, int nodeId); + + // Destroy the node and remove from Map + void DestroyNode(OsrAXNode* node); + CefString root_tree_id_; + CefString focused_tree_id_; + int focused_node_id_; + CefRefPtr browser_; + std::map accessibility_node_map_; +}; + +} // namespace client + +#endif // CEF_TESTS_CEFCLIENT_BROWSER_OSR_ACCESSIBILITY_HELPER_H_ diff --git a/src/CEF/OsrWindowWin.cpp b/src/CEF/OsrWindowWin.cpp new file mode 100644 index 0000000..ef74894 --- /dev/null +++ b/src/CEF/OsrWindowWin.cpp @@ -0,0 +1,1198 @@ +// 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 "CEF/OsrWindowWin.h" + +#include +#if defined(CEF_USE_ATL) +#include +#endif + +#include "include/base/cef_build.h" +#include "CEF/HumanAppContext.h" +#include "CEF/osr_accessibility_helper.h" +#include "CEF/osr_accessibility_node.h" +#include "CEF/osr_ime_handler_win.h" +#include "CEF/osr_render_handler_win_d3d11.h" +#include "CEF/osr_render_handler_win_gl.h" +#include "CEF/osr_render_handler_win_native.h" +#include "CEF/resource.h" +#include "CEF/GeometryUtil.h" +#include "CEF/MainMessageLoop.h" +#include "CEF/UtilWin.h" + +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 handler, + const std::string& url, + const CefBrowserSettings& settings, + CefRefPtr extra_info, + CefRefPtr 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 handler_; + std::string url_; + CefBrowserSettings settings_; + CefRefPtr extra_info_; + CefRefPtr request_context_; + OsrWindowWin* osr_window_win_; +}; + + +OsrWindowWin::OsrWindowWin(Delegate* delegate, + const OsrRendererSettings& settings) + : delegate_(delegate), + settings_(settings), + hwnd_(nullptr), + device_scale_factor_(0), + hidden_(false), + last_mouse_pos_(), + current_mouse_pos_(), + mouse_rotation_(false), + mouse_tracking_(false), + last_click_x_(0), + last_click_y_(0), + last_click_button_(MBT_LEFT), + last_click_count_(1), + last_click_time_(0), + last_mouse_down_on_view_(false) { + 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 handler, + const CefBrowserSettings& settings, + CefRefPtr extra_info, + CefRefPtr 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(width), + y + static_cast(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(width), + static_cast(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, 0, + WS_CHILD | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_VISIBLE | WS_EX_LAYERED, + rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, + parent_hwnd, 0, hInst, 0); + 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_.reset(new 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(UINT32_MAX, UINT32_MAX), 0); + ime_handler_->ResetComposition(); + // Continue reading the composition string - Japanese IMEs send both + // GCS_RESULTSTR and GCS_COMPSTR. + } + + std::vector 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(UINT32_MAX, UINT32_MAX), + CefRange(composition_start, + static_cast(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(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(static_cast(lParam)); + + // Accessibility readers will send an OBJID_CLIENT message. + if (static_cast(OBJID_CLIENT) == obj_id) { + if (self->accessibility_root_) { + return LresultFromObject( + IID_IAccessible, wParam, + static_cast(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 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); + return false; +} + +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 input(new TOUCHINPUT[num_points]); + if (GetTouchInputInfo(reinterpret_cast(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(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 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 browser) { + CEF_REQUIRE_UI_THREAD(); + // Detach |this| from the ClientHandlerOsr. + static_cast(browser_->GetHost()->GetClient().get()) + ->DetachOsrDelegate(); + browser_ = nullptr; + render_handler_->SetBrowser(nullptr); + Destroy(); +} + +bool OsrWindowWin::GetRootScreenRect(CefRefPtr browser, + CefRect& rect) { + CEF_REQUIRE_UI_THREAD(); + return false; +} + +void OsrWindowWin::GetViewRect(CefRefPtr 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 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 the point from view coordinates to actual screen 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 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 browser, bool show) { + render_handler_->OnPopupShow(browser, show); +} + +void OsrWindowWin::OnPopupSize(CefRefPtr browser, + const CefRect& rect) { + render_handler_->OnPopupSize(browser, + LogicalToDevice(rect, device_scale_factor_)); +} + +void OsrWindowWin::OnPaint(CefRefPtr 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 browser, + CefRenderHandler::PaintElementType type, + const CefRenderHandler::RectList& dirtyRects, + void* share_handle) { + EnsureRenderHandler(); + render_handler_->OnAcceleratedPaint(browser, type, dirtyRects, share_handle); +} + +void OsrWindowWin::OnCursorChange(CefRefPtr 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(reinterpret_cast(cursor))); + SetCursor(cursor); +} + +bool OsrWindowWin::StartDragging( + CefRefPtr browser, + CefRefPtr 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 browser, + CefRenderHandler::DragOperation operation) { + CEF_REQUIRE_UI_THREAD(); + +#if defined(CEF_USE_ATL) + current_drag_op_ = operation; +#endif +} + +void OsrWindowWin::OnImeCompositionRangeChanged( + CefRefPtr 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 value) { + CEF_REQUIRE_UI_THREAD(); + +#if defined(CEF_USE_ATL) + if (!accessibility_handler_) { + accessibility_handler_.reset(new 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 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 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); + //} + if (!render_handler_) { + auto render_handler = new OsrRenderHandlerWinNative(settings_, hwnd_); + render_handler->Initialize(browser_); + render_handler_.reset(render_handler); + } + } +} diff --git a/src/CEF/OsrWindowWin.h b/src/CEF/OsrWindowWin.h new file mode 100644 index 0000000..0f19b31 --- /dev/null +++ b/src/CEF/OsrWindowWin.h @@ -0,0 +1,207 @@ +// 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. + +#pragma once + +#include "include/base/cef_callback.h" +#include "include/base/cef_ref_counted.h" +#include "include/wrapper/cef_closure_task.h" +#include "include/wrapper/cef_helpers.h" +#include "CEF/ClientHandlerOSR.h.h" +#include "CEF/osr_accessibility_node.h" +#include "CEF/osr_dragdrop_win.h" +#include "CEF/osr_render_handler_win.h" +#include "CEF/osr_renderer_settings.h" + +class OsrAccessibilityHelper; +class OsrImeHandlerWin; + +// Represents the native parent window for an off-screen browser. This object +// must live on the CEF UI thread in order to handle CefRenderHandler callbacks. +// The methods of this class are thread-safe unless otherwise indicated. +class OsrWindowWin + : public base::RefCountedThreadSafe, + public ClientHandlerOsr::OsrDelegate +#if defined(CEF_USE_ATL) + , + public OsrDragEvents +#endif +{ + public: + // This interface is implemented by the owner of the OsrWindowWin. The + // methods of this class will be called on the main thread. + class Delegate { + public: + // Called after the native window has been created. + virtual void OnOsrNativeWindowCreated(HWND hwnd) = 0; + + protected: + virtual ~Delegate() {} + }; + + // |delegate| must outlive this object. + OsrWindowWin(Delegate* delegate, const OsrRendererSettings& settings); + + // Create a new browser and native window. + void CreateBrowser(HWND parent_hwnd, + const RECT& rect, + CefRefPtr handler, + const CefBrowserSettings& settings, + CefRefPtr extra_info, + CefRefPtr request_context, + const std::string& startup_url); + + // Show the popup window with correct parent and bounds in parent coordinates. + void ShowPopup(HWND parent_hwnd, int x, int y, size_t width, size_t height); + + void Show(); + void Hide(); + void SetBounds(int x, int y, size_t width, size_t height); + void SetFocus(); + void SetDeviceScaleFactor(float device_scale_factor); + + const OsrRendererSettings& settings() const { return settings_; } + + private: + // Only allow deletion via scoped_refptr. + friend struct CefDeleteOnThread; + friend class base::RefCountedThreadSafe; + + ~OsrWindowWin(); + + // Manage native window lifespan. + void Create(HWND parent_hwnd, const RECT& rect); + void Destroy(); + + void NotifyNativeWindowCreated(HWND hwnd); + + static void RegisterOsrClass(HINSTANCE hInstance, HBRUSH background_brush); + static LRESULT CALLBACK OsrWndProc(HWND hWnd, + UINT message, + WPARAM wParam, + LPARAM lParam); + + // WndProc message handlers. + void OnMouseEvent(UINT message, WPARAM wParam, LPARAM lParam); + void OnSize(); + void OnFocus(bool setFocus); + void OnCaptureLost(); + void OnKeyEvent(UINT message, WPARAM wParam, LPARAM lParam); + void OnPaint(); + bool OnEraseBkgnd(); + bool OnTouchEvent(UINT message, WPARAM wParam, LPARAM lParam); + + void OnIMESetContext(UINT message, WPARAM wParam, LPARAM lParam); + void OnIMEStartComposition(); + void OnIMEComposition(UINT message, WPARAM wParam, LPARAM lParam); + void OnIMECancelCompositionEvent(); + + // Manage popup bounds. + bool IsOverPopupWidget(int x, int y) const; + int GetPopupXOffset() const; + int GetPopupYOffset() const; + void ApplyPopupOffset(int& x, int& y) const; + + // ClientHandlerOsr::OsrDelegate methods. + void OnAfterCreated(CefRefPtr browser) override; + void OnBeforeClose(CefRefPtr browser) override; + bool GetRootScreenRect(CefRefPtr browser, CefRect& rect) override; + void GetViewRect(CefRefPtr browser, CefRect& rect) override; + bool GetScreenPoint(CefRefPtr browser, + int viewX, + int viewY, + int& screenX, + int& screenY) override; + bool GetScreenInfo(CefRefPtr browser, + CefScreenInfo& screen_info) override; + void OnPopupShow(CefRefPtr browser, bool show) override; + void OnPopupSize(CefRefPtr browser, const CefRect& rect) override; + void OnPaint(CefRefPtr browser, + CefRenderHandler::PaintElementType type, + const CefRenderHandler::RectList& dirtyRects, + const void* buffer, + int width, + int height) override; + void OnAcceleratedPaint(CefRefPtr browser, + CefRenderHandler::PaintElementType type, + const CefRenderHandler::RectList& dirtyRects, + void* share_handle) override; + void OnCursorChange(CefRefPtr browser, + CefCursorHandle cursor, + cef_cursor_type_t type, + const CefCursorInfo& custom_cursor_info) override; + bool StartDragging(CefRefPtr browser, + CefRefPtr drag_data, + CefRenderHandler::DragOperationsMask allowed_ops, + int x, + int y) override; + void UpdateDragCursor(CefRefPtr browser, + CefRenderHandler::DragOperation operation) override; + void OnImeCompositionRangeChanged( + CefRefPtr browser, + const CefRange& selection_range, + const CefRenderHandler::RectList& character_bounds) override; + + void UpdateAccessibilityTree(CefRefPtr value) override; + + void UpdateAccessibilityLocation(CefRefPtr value) override; + +#if defined(CEF_USE_ATL) + // OsrDragEvents methods. + CefBrowserHost::DragOperationsMask OnDragEnter( + CefRefPtr drag_data, + CefMouseEvent ev, + CefBrowserHost::DragOperationsMask effect) override; + CefBrowserHost::DragOperationsMask OnDragOver( + CefMouseEvent ev, + CefBrowserHost::DragOperationsMask effect) override; + void OnDragLeave() override; + CefBrowserHost::DragOperationsMask OnDrop( + CefMouseEvent ev, + CefBrowserHost::DragOperationsMask effect) override; +#endif // defined(CEF_USE_ATL) + + void EnsureRenderHandler(); + + // Only accessed on the main thread. + Delegate* delegate_; + + const OsrRendererSettings settings_; + HWND hwnd_; + std::unique_ptr render_handler_; + + // Class that encapsulates IMM32 APIs and controls IMEs attached to a window. + std::unique_ptr ime_handler_; + + RECT client_rect_; + float device_scale_factor_; + + CefRefPtr browser_; + +#if defined(CEF_USE_ATL) + CComPtr drop_target_; + CefRenderHandler::DragOperation current_drag_op_; + + // Class that abstracts the accessibility information received from the + // renderer. + std::unique_ptr accessibility_handler_; + IAccessible* accessibility_root_; +#endif + + bool hidden_; + + // Mouse state tracking. + POINT last_mouse_pos_; + POINT current_mouse_pos_; + bool mouse_rotation_; + bool mouse_tracking_; + int last_click_x_; + int last_click_y_; + CefBrowserHost::MouseButtonType last_click_button_; + int last_click_count_; + double last_click_time_; + bool last_mouse_down_on_view_; + + DISALLOW_COPY_AND_ASSIGN(OsrWindowWin); +}; diff --git a/src/CEF/PreferencesTest.cpp b/src/CEF/PreferencesTest.cpp new file mode 100644 index 0000000..26a03b5 --- /dev/null +++ b/src/CEF/PreferencesTest.cpp @@ -0,0 +1,326 @@ +// 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 "CEF/PreferencesTest.h" + +#include +#include +#include + +#include "include/base/cef_logging.h" +#include "include/cef_command_line.h" +#include "include/cef_parser.h" +#include "CEF/TestRunner.h" + +const char kTestUrlPath[] = "/preferences"; + +// Application-specific error codes. +const int kMessageFormatError = 1; +const int kPreferenceApplicationError = 1; + +// Common to all messages. +const char kNameKey[] = "name"; +const char kNameValueGet[] = "preferences_get"; +const char kNameValueSet[] = "preferences_set"; +const char kNameValueState[] = "preferences_state"; + +// Used with "preferences_get" messages. +const char kIncludeDefaultsKey[] = "include_defaults"; + +// Used with "preferences_set" messages. +const char kPreferencesKey[] = "preferences"; + +// Handle messages in the browser process. Only accessed on the UI thread. +class Handler : public CefMessageRouterBrowserSide::Handler { + public: + typedef std::vector NameVector; + + Handler() { CEF_REQUIRE_UI_THREAD(); } + + // Called due to cefQuery execution in preferences.html. + bool OnQuery(CefRefPtr browser, + CefRefPtr frame, + int64 query_id, + const CefString& request, + bool persistent, + CefRefPtr callback) override { + CEF_REQUIRE_UI_THREAD(); + + // Only handle messages from the test URL. + const std::string& url = frame->GetURL(); + if (!IsTestURL(url, kTestUrlPath)) + return false; + + // Parse |request| as a JSON dictionary. + CefRefPtr request_dict = ParseJSON(request); + if (!request_dict) { + callback->Failure(kMessageFormatError, "Incorrect message format"); + return true; + } + + // Verify the "name" key. + if (!VerifyKey(request_dict, kNameKey, VTYPE_STRING, callback)) + return true; + + const std::string& message_name = request_dict->GetString(kNameKey); + if (message_name == kNameValueGet) { + // JavaScript is requesting a JSON representation of the preferences tree. + + // Verify the "include_defaults" key. + if (!VerifyKey(request_dict, kIncludeDefaultsKey, VTYPE_BOOL, callback)) + return true; + + const bool include_defaults = request_dict->GetBool(kIncludeDefaultsKey); + + OnPreferencesGet(browser, include_defaults, callback); + + return true; + } else if (message_name == kNameValueSet) { + // JavaScript is requesting that preferences be updated to match the + // specified JSON representation. + + // Verify the "preferences" key. + if (!VerifyKey(request_dict, kPreferencesKey, VTYPE_DICTIONARY, callback)) + return true; + + CefRefPtr preferences = + request_dict->GetDictionary(kPreferencesKey); + + OnPreferencesSet(browser, preferences, callback); + + return true; + } else if (message_name == kNameValueState) { + // JavaScript is requesting global state information. + + OnPreferencesState(browser, callback); + + return true; + } + + return false; + } + + private: + // Execute |callback| with the preferences dictionary as a JSON string. + static void OnPreferencesGet(CefRefPtr browser, + bool include_defaults, + CefRefPtr callback) { + CefRefPtr context = + browser->GetHost()->GetRequestContext(); + + // Retrieve all preference values. + CefRefPtr prefs = + context->GetAllPreferences(include_defaults); + + // Serialize the preferences to JSON and return to the JavaScript caller. + callback->Success(GetJSON(prefs)); + } + + // Set preferences based on the contents of |preferences|. Execute |callback| + // with a descriptive result message. + static void OnPreferencesSet(CefRefPtr browser, + CefRefPtr preferences, + CefRefPtr callback) { + CefRefPtr context = + browser->GetHost()->GetRequestContext(); + + CefRefPtr value = CefValue::Create(); + value->SetDictionary(preferences); + + std::string error; + NameVector changed_names; + + // Apply preferences. This may result in errors. + const bool success = + ApplyPrefs(context, std::string(), value, error, changed_names); + + // Create a message that accurately represents the result. + std::string message; + if (!changed_names.empty()) { + std::stringstream ss; + ss << "Successfully changed " << changed_names.size() << " preferences; "; + for (size_t i = 0; i < changed_names.size(); ++i) { + ss << changed_names[i]; + if (i < changed_names.size() - 1) + ss << ", "; + } + message = ss.str(); + } + + if (!success) { + DCHECK(!error.empty()); + if (!message.empty()) + message += "\n"; + message += error; + } + + if (changed_names.empty()) { + if (!message.empty()) + message += "\n"; + message += "No preferences changed."; + } + + // Return the message to the JavaScript caller. + if (success) + callback->Success(message); + else + callback->Failure(kPreferenceApplicationError, message); + } + + // Execute |callback| with the global state dictionary as a JSON string. + static void OnPreferencesState(CefRefPtr browser, + CefRefPtr callback) { + CefRefPtr command_line = + CefCommandLine::GetGlobalCommandLine(); + + CefRefPtr dict = CefDictionaryValue::Create(); + + // If spell checking is disabled via the command-line then it cannot be + // enabled via preferences. + dict->SetBool("spellcheck_disabled", + command_line->HasSwitch("disable-spell-checking")); + + // If proxy settings are configured via the command-line then they cannot + // be modified via preferences. + dict->SetBool("proxy_configured", + command_line->HasSwitch("no-proxy-server") || + command_line->HasSwitch("proxy-auto-detect") || + command_line->HasSwitch("proxy-pac-url") || + command_line->HasSwitch("proxy-server")); + + // If allow running insecure content is enabled via the command-line then it + // cannot be enabled via preferences. + dict->SetBool("allow_running_insecure_content", + command_line->HasSwitch("allow-running-insecure-content")); + + // Serialize the state to JSON and return to the JavaScript caller. + callback->Success(GetJSON(dict)); + } + + // Convert a JSON string to a dictionary value. + static CefRefPtr ParseJSON(const CefString& string) { + CefRefPtr value = CefParseJSON(string, JSON_PARSER_RFC); + if (value.get() && value->GetType() == VTYPE_DICTIONARY) + return value->GetDictionary(); + return nullptr; + } + + // Convert a dictionary value to a JSON string. + static CefString GetJSON(CefRefPtr dictionary) { + CefRefPtr value = CefValue::Create(); + value->SetDictionary(dictionary); + return CefWriteJSON(value, JSON_WRITER_DEFAULT); + } + + // Verify that |key| exists in |dictionary| and has type |value_type|. Fails + // |callback| and returns false on failure. + static bool VerifyKey(CefRefPtr dictionary, + const char* key, + cef_value_type_t value_type, + CefRefPtr callback) { + if (!dictionary->HasKey(key) || dictionary->GetType(key) != value_type) { + callback->Failure( + kMessageFormatError, + "Missing or incorrectly formatted message key: " + std::string(key)); + return false; + } + return true; + } + + // Apply preferences. Returns true on success. Returns false and sets |error| + // to a descriptive error string on failure. |changed_names| is the list of + // preferences that were successfully changed. + static bool ApplyPrefs(CefRefPtr context, + const std::string& name, + CefRefPtr value, + std::string& error, + NameVector& changed_names) { + if (!name.empty() && context->HasPreference(name)) { + // The preference exists. Set the value. + return SetPref(context, name, value, error, changed_names); + } + + if (value->GetType() == VTYPE_DICTIONARY) { + // A dictionary type value that is not an existing preference. Try to set + // each of the elements individually. + CefRefPtr dict = value->GetDictionary(); + + CefDictionaryValue::KeyList keys; + dict->GetKeys(keys); + for (size_t i = 0; i < keys.size(); ++i) { + const std::string& key = keys[i]; + const std::string& current_name = name.empty() ? key : name + "." + key; + if (!ApplyPrefs(context, current_name, dict->GetValue(key), error, + changed_names)) { + return false; + } + } + + return true; + } + + error = "Trying to create an unregistered preference: " + name; + return false; + } + + // Set a specific preference value. Returns true if the value is set + // successfully or has not changed. If the value has changed then |name| will + // be added to |changed_names|. Returns false and sets |error| to a + // descriptive error string on failure. + static bool SetPref(CefRefPtr context, + const std::string& name, + CefRefPtr value, + std::string& error, + NameVector& changed_names) { + CefRefPtr existing_value = context->GetPreference(name); + DCHECK(existing_value); + + if (value->GetType() == VTYPE_STRING && + existing_value->GetType() != VTYPE_STRING) { + // Since |value| is coming from JSON all basic types will be represented + // as strings. Convert to the expected data type. + const std::string& string_val = value->GetString(); + switch (existing_value->GetType()) { + case VTYPE_BOOL: + if (string_val == "true" || string_val == "1") + value->SetBool(true); + else if (string_val == "false" || string_val == "0") + value->SetBool(false); + break; + case VTYPE_INT: + value->SetInt(atoi(string_val.c_str())); + break; + case VTYPE_DOUBLE: + value->SetInt(atof(string_val.c_str())); + break; + default: + // Other types cannot be converted. + break; + } + } + + // Nothing to do if the value hasn't changed. + if (existing_value->IsEqual(value)) + return true; + + // Attempt to set the preference. + CefString error_str; + if (!context->SetPreference(name, value, error_str)) { + error = error_str.ToString() + ": " + name; + return false; + } + + // The preference was set successfully. + changed_names.push_back(name); + return true; + } + + DISALLOW_COPY_AND_ASSIGN(Handler); +}; + +namespace preferences_test { + void CreateMessageHandlers(MessageHandlerSet& handlers) { + handlers.insert(new Handler()); + } +} diff --git a/src/CEF/PreferencesTest.h b/src/CEF/PreferencesTest.h new file mode 100644 index 0000000..b95d29e --- /dev/null +++ b/src/CEF/PreferencesTest.h @@ -0,0 +1,12 @@ +// 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. + +#pragma once + +#include "CEF/TestRunner.h" + +namespace preferences_test { + // Create message handlers. Called from test_runner.cc. + void CreateMessageHandlers(MessageHandlerSet& handlers); +} diff --git a/src/CEF/ResourceUtilWin.cpp b/src/CEF/ResourceUtilWin.cpp index 76a5c05..2007b53 100644 --- a/src/CEF/ResourceUtilWin.cpp +++ b/src/CEF/ResourceUtilWin.cpp @@ -75,8 +75,6 @@ class BinaryResourceProvider : public CefResourceManager::Provider { DISALLOW_COPY_AND_ASSIGN(BinaryResourceProvider); }; -} // namespace - // Implemented in resource_util_win_idmap.cc. extern int GetResourceId(const char* resource_name); diff --git a/src/CEF/ResourceUtilWinIdMap.cpp b/src/CEF/ResourceUtilWinIdMap.cpp new file mode 100644 index 0000000..9becaf4 --- /dev/null +++ b/src/CEF/ResourceUtilWinIdMap.cpp @@ -0,0 +1,55 @@ +// Copyright (c) 2013 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 + +#include "CEF/resource.h" + +int GetResourceId(const char* resource_name) { + // Map of resource labels to BINARY id values. + static struct _resource_map { + const char* name; + int id; + } resource_map[] = { + {"binding.html", IDS_BINDING_HTML}, + {"dialogs.html", IDS_DIALOGS_HTML}, + {"draggable.html", IDS_DRAGGABLE_HTML}, + {"extensions/set_page_color/icon.png", + IDS_EXTENSIONS_SET_PAGE_COLOR_ICON_PNG}, + {"extensions/set_page_color/manifest.json", + IDS_EXTENSIONS_SET_PAGE_COLOR_MANIFEST_JSON}, + {"extensions/set_page_color/popup.html", + IDS_EXTENSIONS_SET_PAGE_COLOR_POPUP_HTML}, + {"extensions/set_page_color/popup.js", + IDS_EXTENSIONS_SET_PAGE_COLOR_POPUP_JS}, + {"localstorage.html", IDS_LOCALSTORAGE_HTML}, + {"logo.png", IDS_LOGO_PNG}, + {"media_router.html", IDS_MEDIA_ROUTER_HTML}, + {"menu_icon.1x.png", IDS_MENU_ICON_1X_PNG}, + {"menu_icon.2x.png", IDS_MENU_ICON_2X_PNG}, + {"osr_test.html", IDS_OSRTEST_HTML}, + {"other_tests.html", IDS_OTHER_TESTS_HTML}, + {"pdf.html", IDS_PDF_HTML}, + {"pdf.pdf", IDS_PDF_PDF}, + {"performance.html", IDS_PERFORMANCE_HTML}, + {"performance2.html", IDS_PERFORMANCE2_HTML}, + {"preferences.html", IDS_PREFERENCES_HTML}, + {"response_filter.html", IDS_RESPONSE_FILTER_HTML}, + {"server.html", IDS_SERVER_HTML}, + {"transparency.html", IDS_TRANSPARENCY_HTML}, + {"urlrequest.html", IDS_URLREQUEST_HTML}, + {"websocket.html", IDS_WEBSOCKET_HTML}, + {"window.html", IDS_WINDOW_HTML}, + {"window_icon.1x.png", IDS_WINDOW_ICON_1X_PNG}, + {"window_icon.2x.png", IDS_WINDOW_ICON_2X_PNG}, + {"xmlhttprequest.html", IDS_XMLHTTPREQUEST_HTML}, + }; + + for (size_t i = 0; i < sizeof(resource_map) / sizeof(_resource_map); ++i) { + if (!strcmp(resource_map[i].name, resource_name)) + return resource_map[i].id; + } + + return 0; +} diff --git a/src/CEF/ResponseFilterTest.cpp b/src/CEF/ResponseFilterTest.cpp new file mode 100644 index 0000000..d7fbb7f --- /dev/null +++ b/src/CEF/ResponseFilterTest.cpp @@ -0,0 +1,234 @@ +// 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 "CEF/ResponseFilterTest.h" + +#include +#include +#include + +#include "include/base/cef_logging.h" +#include "include/cef_command_line.h" +#include "CEF/TestRunner.h" +#include "CEF/HumanAppSwitches.h" + + +const char kTestUrlPath[] = "/response_filter"; +const char kFindString[] = "REPLACE_THIS_STRING"; +const char kReplaceString[] = "This is the replaced string!"; + +// Helper for passing params to Write(). +#define WRITE_PARAMS data_out_ptr, data_out_size, data_out_written + +// Filter the contents of response_filter.html by replacing all instances of +// |kFindString| with |kReplaceString|. Pass the `--enable-filter-testing` +// command-line flag (which shrinks the buffer size to 32 bytes) to better test +// the logic in this implementation. +class FindReplaceResponseFilter : public CefResponseFilter { + public: + FindReplaceResponseFilter() + : find_match_offset_(0U), + replace_overflow_size_(0U), + replace_count_(0U) {} + + bool InitFilter() override { + const size_t find_size = sizeof(kFindString) - 1; + const size_t replace_size = sizeof(kReplaceString) - 1; + + // Determine a reasonable amount of space for find/replace overflow. For + // example, the amount of space required if the search string is + // found/replaced 10 times (plus space for the count). + if (replace_size > find_size) + replace_overflow_size_ = (replace_size - find_size + 3) * 10; + + return true; + } + + FilterStatus Filter(void* data_in, + size_t data_in_size, + size_t& data_in_read, + void* data_out, + size_t data_out_size, + size_t& data_out_written) override { + DCHECK((data_in_size == 0U && !data_in) || (data_in_size > 0U && data_in)); + DCHECK_EQ(data_in_read, 0U); + DCHECK(data_out); + DCHECK_GT(data_out_size, 0U); + DCHECK_EQ(data_out_written, 0U); + + // All data will be read. + data_in_read = data_in_size; + + const size_t find_size = sizeof(kFindString) - 1; + + const char* data_in_ptr = static_cast(data_in); + char* data_out_ptr = static_cast(data_out); + + // Reset the overflow. + std::string old_overflow; + if (!overflow_.empty()) { + old_overflow = overflow_; + overflow_.clear(); + } + + const size_t likely_out_size = + data_in_size + replace_overflow_size_ + old_overflow.size(); + if (data_out_size < likely_out_size) { + // We'll likely need to use the overflow buffer. Size it appropriately. + overflow_.reserve(likely_out_size - data_out_size); + } + + if (!old_overflow.empty()) { + // Write the overflow from last time. + Write(old_overflow.c_str(), old_overflow.size(), WRITE_PARAMS); + } + + // Evaluate each character in the input buffer. Track how many characters in + // a row match kFindString. If kFindString is completely matched then write + // kReplaceString. Otherwise, write the input characters as-is. + for (size_t i = 0U; i < data_in_size; ++i) { + if (data_in_ptr[i] == kFindString[find_match_offset_]) { + // Matched the next character in the find string. + if (++find_match_offset_ == find_size) { + // Complete match of the find string. Write the replace string. + std::stringstream ss; + ss << ++replace_count_ << ". " << kReplaceString; + const std::string& replace_str = ss.str(); + Write(replace_str.c_str(), replace_str.size(), WRITE_PARAMS); + + // Start over looking for a match. + find_match_offset_ = 0; + } + continue; + } + + // Character did not match the find string. + if (find_match_offset_ > 0) { + // Write the portion of the find string that has matched so far. + Write(kFindString, find_match_offset_, WRITE_PARAMS); + + // Start over looking for a match. + find_match_offset_ = 0; + } + + // Write the current character. + Write(&data_in_ptr[i], 1, WRITE_PARAMS); + } + + // If a match is currently in-progress we need more data. Otherwise, we're + // done. + return find_match_offset_ > 0 ? RESPONSE_FILTER_NEED_MORE_DATA + : RESPONSE_FILTER_DONE; + } + + private: + inline void Write(const char* str, + size_t str_size, + char*& data_out_ptr, + size_t data_out_size, + size_t& data_out_written) { + // Number of bytes remaining in the output buffer. + const size_t remaining_space = data_out_size - data_out_written; + // Maximum number of bytes we can write into the output buffer. + const size_t max_write = min(str_size, remaining_space); + + // Write the maximum portion that fits in the output buffer. + if (max_write == 1) { + // Small optimization for single character writes. + *data_out_ptr = str[0]; + data_out_ptr += 1; + data_out_written += 1; + } else if (max_write > 1) { + memcpy(data_out_ptr, str, max_write); + data_out_ptr += max_write; + data_out_written += max_write; + } + + if (max_write < str_size) { + // Need to write more bytes than will fit in the output buffer. Store the + // remainder in the overflow buffer. + overflow_ += std::string(str + max_write, str_size - max_write); + } + } + + // The portion of the find string that is currently matching. + size_t find_match_offset_; + + // The likely amount of overflow. + size_t replace_overflow_size_; + + // Overflow from the output buffer. + std::string overflow_; + + // Number of times the the string was found/replaced. + size_t replace_count_; + + IMPLEMENT_REFCOUNTING(FindReplaceResponseFilter); +}; + +// Filter that writes out all of the contents unchanged. +class PassThruResponseFilter : public CefResponseFilter { + public: + PassThruResponseFilter() {} + + bool InitFilter() override { return true; } + + FilterStatus Filter(void* data_in, + size_t data_in_size, + size_t& data_in_read, + void* data_out, + size_t data_out_size, + size_t& data_out_written) override { + DCHECK((data_in_size == 0U && !data_in) || (data_in_size > 0U && data_in)); + DCHECK_EQ(data_in_read, 0U); + DCHECK(data_out); + DCHECK_GT(data_out_size, 0U); + DCHECK_EQ(data_out_written, 0U); + + // All data will be read. + data_in_read = data_in_size; + + // Write out the contents unchanged. + data_out_written = min(data_in_read, data_out_size); + if (data_out_written > 0) + memcpy(data_out, data_in, data_out_written); + + return RESPONSE_FILTER_DONE; + } + + private: + IMPLEMENT_REFCOUNTING(PassThruResponseFilter); +}; + +// Returns true if |url| starts with the value specified via the `--filter-url` +// command-line flag. +bool MatchesFilterURL(const std::string& url) { + CefRefPtr command_line = + CefCommandLine::GetGlobalCommandLine(); + if (command_line->HasSwitch(kFilterURL)) { + const std::string& filter_url = + command_line->GetSwitchValue(kFilterURL); + return url.find(filter_url) == 0; + } + return false; +} + +namespace response_filter_test { + CefRefPtr GetResourceResponseFilter( + CefRefPtr browser, + CefRefPtr frame, + CefRefPtr request, + CefRefPtr response) { + // Use the find/replace filter on the test URL. + const std::string& url = request->GetURL(); + + if (IsTestURL(url, kTestUrlPath)) + return new FindReplaceResponseFilter(); + + if (MatchesFilterURL(url)) + return new PassThruResponseFilter(); + + return nullptr; + } +} diff --git a/src/CEF/ResponseFilterTest.h b/src/CEF/ResponseFilterTest.h new file mode 100644 index 0000000..400a62b --- /dev/null +++ b/src/CEF/ResponseFilterTest.h @@ -0,0 +1,20 @@ +// 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. + + +#pragma once + +#include "include/cef_browser.h" +#include "include/cef_request.h" +#include "include/cef_response.h" +#include "include/cef_response_filter.h" + +namespace response_filter_test { + // Create a resource response filter. Called from test_runner.cc. + CefRefPtr GetResourceResponseFilter( + CefRefPtr browser, + CefRefPtr frame, + CefRefPtr request, + CefRefPtr response); +} diff --git a/src/CEF/RootWindoManager.cpp b/src/CEF/RootWindoManager.cpp index c4b066a..55f15b6 100644 --- a/src/CEF/RootWindoManager.cpp +++ b/src/CEF/RootWindoManager.cpp @@ -50,7 +50,7 @@ class ClientRequestContextHandler : public CefRequestContextHandler, std::istringstream f(extension_path); while (getline(f, part, ';')) { if (!part.empty()) - extension_util::LoadExtension(request_context, part, this); + LoadExtension(request_context, part, this); } } } @@ -159,7 +159,7 @@ scoped_refptr RootWindowManager::CreateRootWindowAsExtension( base::OnceClosure close_callback, bool with_controls, bool with_osr) { - const std::string& extension_url = extension_util::GetExtensionURL(extension); + const std::string& extension_url = GetExtensionURL(extension); if (extension_url.empty()) { NOTREACHED() << "Extension cannot be loaded directly."; return nullptr; @@ -253,7 +253,7 @@ void RootWindowManager::AddExtension(CefRefPtr extension) { } // Don't track extensions that can't be loaded directly. - if (extension_util::GetExtensionURL(extension).empty()) + if (GetExtensionURL(extension).empty()) return; // Don't add the same extension multiple times. diff --git a/src/CEF/RootWindow.cpp b/src/CEF/RootWindow.cpp index 3b26eb0..8bf7d1e 100644 --- a/src/CEF/RootWindow.cpp +++ b/src/CEF/RootWindow.cpp @@ -8,6 +8,7 @@ #include "CEF/HumanAppContext.h" #include "CEF/RootWindoManager.h" +#include "CEF/MainMessageLoop.h" RootWindowConfig::RootWindowConfig() : always_on_top(false), diff --git a/src/CEF/RootWindowCreate.cpp b/src/CEF/RootWindowCreate.cpp new file mode 100644 index 0000000..dc138e6 --- /dev/null +++ b/src/CEF/RootWindowCreate.cpp @@ -0,0 +1,36 @@ +// Copyright (c) 2016 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 "CEF//RootWindow.h" + +// #include "CEF/Rroot_window_views.h" + +#if defined(OS_WIN) +#include "CEF/RootWindowWin.h" +#elif defined(OS_LINUX) +#include "tests/cefclient/browser/root_window_gtk.h" +#elif defined(OS_MAC) +#include "tests/cefclient/browser/root_window_mac.h" +#endif + +namespace client { + +// static +scoped_refptr RootWindow::Create(bool use_views) { + // if (use_views) { + // return new RootWindowViews(); + // } + +#if defined(OS_WIN) + return new RootWindowWin(); +#elif defined(OS_LINUX) + return new RootWindowGtk(); +#elif defined(OS_MAC) + return new RootWindowMac(); +#else +#error Unsupported platform +#endif +} + +} // namespace client diff --git a/src/CEF/RootWindowWin.cpp b/src/CEF/RootWindowWin.cpp new file mode 100644 index 0000000..b5a65fd --- /dev/null +++ b/src/CEF/RootWindowWin.cpp @@ -0,0 +1,1208 @@ +// 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 "CEF/RootWindowWin.h" + +#include + +#include "include/base/cef_build.h" +#include "include/base/cef_callback.h" +#include "include/cef_app.h" +#include "CEF/BrowserWindowOsrWin.h" +#include "CEF/BrowserWindowStdWin.h" +#include "CEF/HumanAppContext.h" +#include "CEF/resource.h" +#include "CEF/TempWindow.h" +#include "CEF/WindowTestRunnerWin.h" +#include "CEF/GeometryUtil.h" +#include "CEF/MainMessageLoop.h" +#include "CEF/UtilWin.h" +#include "CEF/HumanAppSwitches.h" + +#define MAX_URL_LENGTH 255 +#define BUTTON_WIDTH 72 +#define URLBAR_HEIGHT 24 + +// 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_(nullptr), + draggable_region_(nullptr), + font_(nullptr), + font_height_(0), + back_hwnd_(nullptr), + forward_hwnd_(nullptr), + reload_hwnd_(nullptr), + stop_hwnd_(nullptr), + edit_hwnd_(nullptr), + edit_wndproc_old_(nullptr), + find_hwnd_(nullptr), + find_message_id_(0), + find_wndproc_old_(nullptr), + 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, + std::unique_ptr 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::BindOnce(&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_, nullptr, 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); + settings.background_color = CefColorSetARGB(0, 0, 0, 0); + browser_window_.reset( + new BrowserWindowOsrWin(this, with_controls_, startup_url, settings)); + } else { + browser_window_.reset( + new BrowserWindowStdWin(this, with_controls_, startup_url)); + } +} + +void RootWindowWin::CreateRootWindow(const CefBrowserSettings& settings, + bool initially_hidden) { + REQUIRE_MAIN_THREAD(); + DCHECK(!hwnd_); + + HINSTANCE hInstance = GetModuleHandle(nullptr); + + // 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, nullptr, nullptr, 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(nullptr, 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, nullptr); + self->edit_hwnd_ = nullptr; + 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 ? nullptr + : hWnd); + return FALSE; + case WM_NCDESTROY: + // Clear the reference to |self|. + SetUserDataPtr(hWnd, nullptr); + self->find_hwnd_ = nullptr; + 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, nullptr); + self->hwnd_ = nullptr; + 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 nullptr if the browser has not yet been created. + HWND browser_hwnd = nullptr; + if (browser_window_) + browser_hwnd = browser_window_->GetWindowHandle(); + + // Resize all controls. + HDWP hdwp = BeginDeferWindowPos(browser_hwnd ? 6 : 5); + hdwp = DeferWindowPos(hdwp, back_hwnd_, nullptr, x_offset, 0, button_width, + urlbar_height, SWP_NOZORDER); + x_offset += button_width; + hdwp = DeferWindowPos(hdwp, forward_hwnd_, nullptr, x_offset, 0, + button_width, urlbar_height, SWP_NOZORDER); + x_offset += button_width; + hdwp = DeferWindowPos(hdwp, reload_hwnd_, nullptr, x_offset, 0, + button_width, urlbar_height, SWP_NOZORDER); + x_offset += button_width; + hdwp = DeferWindowPos(hdwp, stop_hwnd_, nullptr, x_offset, 0, button_width, + urlbar_height, SWP_NOZORDER); + x_offset += button_width; + hdwp = DeferWindowPos(hdwp, edit_hwnd_, nullptr, x_offset, 0, + rect.right - x_offset, urlbar_height, SWP_NOZORDER); + + if (browser_hwnd) { + hdwp = DeferWindowPos(hdwp, browser_hwnd, nullptr, 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(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(nullptr), 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_, nullptr); + } + + 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) { + std::unique_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_) != nullptr); + + // 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_, nullptr, 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; +} + +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); +} diff --git a/src/CEF/RootWindowWin.h b/src/CEF/RootWindowWin.h new file mode 100644 index 0000000..693b938 --- /dev/null +++ b/src/CEF/RootWindowWin.h @@ -0,0 +1,158 @@ +// 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. + +#pragma once + +#include + +#include + +#include +#include + +#include "CEF/BrowserWindow.h" +#include "CEF/RootWindow.h" + +// Windows implementation of a top-level native window in the browser process. +// The methods of this class must be called on the main thread unless otherwise +// indicated. +class RootWindowWin : public RootWindow, public BrowserWindow::Delegate { + public: + // Constructor may be called on any thread. + RootWindowWin(); + ~RootWindowWin(); + + // RootWindow methods. + void Init(RootWindow::Delegate* delegate, + std::unique_ptr config, + const CefBrowserSettings& settings) override; + void InitAsPopup(RootWindow::Delegate* delegate, + bool with_controls, + bool with_osr, + const CefPopupFeatures& popupFeatures, + CefWindowInfo& windowInfo, + CefRefPtr& client, + CefBrowserSettings& settings) override; + void Show(ShowMode mode) override; + void Hide() override; + void SetBounds(int x, int y, size_t width, size_t height) override; + void Close(bool force) override; + void SetDeviceScaleFactor(float device_scale_factor) override; + float GetDeviceScaleFactor() const override; + CefRefPtr GetBrowser() const override; + ClientWindowHandle GetWindowHandle() const override; + bool WithWindowlessRendering() const override; + bool WithExtension() const override; + + private: + void CreateBrowserWindow(const std::string& startup_url); + void CreateRootWindow(const CefBrowserSettings& settings, + bool initially_hidden); + + // Register the root window class. + static void RegisterRootClass(HINSTANCE hInstance, + const std::wstring& window_class, + HBRUSH background_brush); + + // Window procedure for the edit field. + static LRESULT CALLBACK EditWndProc(HWND hWnd, + UINT message, + WPARAM wParam, + LPARAM lParam); + + // Window procedure for the find dialog. + static LRESULT CALLBACK FindWndProc(HWND hWnd, + UINT message, + WPARAM wParam, + LPARAM lParam); + + // Window procedure for the root window. + static LRESULT CALLBACK RootWndProc(HWND hWnd, + UINT message, + WPARAM wParam, + LPARAM lParam); + + // Event handlers. + void OnPaint(); + void OnFocus(); + void OnActivate(bool active); + void OnSize(bool minimized); + void OnMove(); + void OnDpiChanged(WPARAM wParam, LPARAM lParam); + bool OnEraseBkgnd(); + bool OnCommand(UINT id); + void OnFind(); + void OnFindEvent(); + void OnAbout(); + void OnNCCreate(LPCREATESTRUCT lpCreateStruct); + void OnCreate(LPCREATESTRUCT lpCreateStruct); + bool OnClose(); + void OnDestroyed(); + + // BrowserWindow::Delegate methods. + void OnBrowserCreated(CefRefPtr browser) override; + void OnBrowserWindowDestroyed() override; + void OnSetAddress(const std::string& url) override; + void OnSetTitle(const std::string& title) override; + void OnSetFullscreen(bool fullscreen) override; + void OnAutoResize(const CefSize& new_size) override; + void OnSetLoadingState(bool isLoading, + bool canGoBack, + bool canGoForward) override; + void OnSetDraggableRegions( + const std::vector& regions) override; + + void NotifyDestroyedIfDone(); + + // After initialization all members are only accessed on the main thread. + // Members set during initialization. + bool with_controls_; + bool always_on_top_; + bool with_osr_; + bool with_extension_; + bool is_popup_; + RECT start_rect_; + std::unique_ptr browser_window_; + CefBrowserSettings browser_settings_; + bool initialized_; + + // Main window. + HWND hwnd_; + + // Draggable region. + HRGN draggable_region_; + + // Font for buttons and text fields. + HFONT font_; + int font_height_; + + // Buttons. + HWND back_hwnd_; + HWND forward_hwnd_; + HWND reload_hwnd_; + HWND stop_hwnd_; + + // URL text field. + HWND edit_hwnd_; + WNDPROC edit_wndproc_old_; + + // Find dialog. + HWND find_hwnd_; + UINT find_message_id_; + WNDPROC find_wndproc_old_; + + // Find dialog state. + FINDREPLACE find_state_; + WCHAR find_buff_[80]; + std::wstring find_what_last_; + bool find_next_; + bool find_match_case_last_; + + bool window_destroyed_; + bool browser_destroyed_; + + bool called_enable_non_client_dpi_scaling_; + + DISALLOW_COPY_AND_ASSIGN(RootWindowWin); +}; diff --git a/src/CEF/SchemeTest.cpp b/src/CEF/SchemeTest.cpp new file mode 100644 index 0000000..22023de --- /dev/null +++ b/src/CEF/SchemeTest.cpp @@ -0,0 +1,145 @@ +// Copyright (c) 2012 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 "CEF/SchemeTest.h" + +#include +#include +#include + +#include "include/cef_browser.h" +#include "include/cef_callback.h" +#include "include/cef_frame.h" +#include "include/cef_request.h" +#include "include/cef_resource_handler.h" +#include "include/cef_response.h" +#include "include/cef_scheme.h" +#include "include/wrapper/cef_helpers.h" +#include "CEF/TestRunner.h" +#include "CEF/ResourceUtil.h" + +// Implementation of the schema handler for client:// requests. +class ClientSchemeHandler : public CefResourceHandler { + public: + ClientSchemeHandler() : offset_(0) {} + + bool Open(CefRefPtr request, + bool& handle_request, + CefRefPtr callback) override { + DCHECK(!CefCurrentlyOn(TID_UI) && !CefCurrentlyOn(TID_IO)); + + // The request will be continued or canceled based on the return value. + handle_request = true; + + bool handled = false; + + std::string url = request->GetURL(); + if (strstr(url.c_str(), "handler.html") != nullptr) { + // Build the response html + data_ = + "Client Scheme Handler" + "" + "This contents of this page page are served by the " + "ClientSchemeHandler class handling the client:// protocol." + "
You should see an image:" + "
";
+
+      // Output a string representation of the request
+      const std::string& dump = DumpRequestContents(request);
+      data_.append(dump);
+
+      data_.append(
+          "

Try the test form:" + "
" + "" + "" + "" + "
"); + + handled = true; + + // Set the resulting mime type + mime_type_ = "text/html"; + } else if (strstr(url.c_str(), "logo.png") != nullptr) { + // Load the response image + if (LoadBinaryResource("logo.png", data_)) { + handled = true; + // Set the resulting mime type + mime_type_ = "image/png"; + } + } + + return handled; + } + + void GetResponseHeaders(CefRefPtr response, + int64& response_length, + CefString& redirectUrl) override { + CEF_REQUIRE_IO_THREAD(); + + DCHECK(!data_.empty()); + + response->SetMimeType(mime_type_); + response->SetStatus(200); + + // Set the resulting response length + response_length = data_.length(); + } + + void Cancel() override { CEF_REQUIRE_IO_THREAD(); } + + bool Read(void* data_out, + int bytes_to_read, + int& bytes_read, + CefRefPtr callback) override { + DCHECK(!CefCurrentlyOn(TID_UI) && !CefCurrentlyOn(TID_IO)); + + bool has_data = false; + bytes_read = 0; + + if (offset_ < data_.length()) { + // Copy the next block of data into the buffer. + int transfer_size = + min(bytes_to_read, static_cast(data_.length() - offset_)); + memcpy(data_out, data_.c_str() + offset_, transfer_size); + offset_ += transfer_size; + + bytes_read = transfer_size; + has_data = true; + } + + return has_data; + } + + private: + std::string data_; + std::string mime_type_; + size_t offset_; + + IMPLEMENT_REFCOUNTING(ClientSchemeHandler); + DISALLOW_COPY_AND_ASSIGN(ClientSchemeHandler); +}; + +// Implementation of the factory for for creating schema handlers. +class ClientSchemeHandlerFactory : public CefSchemeHandlerFactory { + public: + ClientSchemeHandlerFactory() {} + + // Return a new scheme handler instance to handle the request. + CefRefPtr Create(CefRefPtr browser, + CefRefPtr frame, + const CefString& scheme_name, + CefRefPtr request) override { + CEF_REQUIRE_IO_THREAD(); + return new ClientSchemeHandler(); + } + + IMPLEMENT_REFCOUNTING(ClientSchemeHandlerFactory); + DISALLOW_COPY_AND_ASSIGN(ClientSchemeHandlerFactory); +}; + +void RegisterSchemeHandlers() { + CefRegisterSchemeHandlerFactory("client", "tests", + new ClientSchemeHandlerFactory()); +} diff --git a/src/CEF/SchemeTest.h b/src/CEF/SchemeTest.h new file mode 100644 index 0000000..e696775 --- /dev/null +++ b/src/CEF/SchemeTest.h @@ -0,0 +1,11 @@ +// Copyright (c) 2009 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. + +#pragma once + +// Create and register the custom scheme handler. See +// common/scheme_handler_common.h for registration of the custom scheme +// name/type which must occur in all processes. Called from test_runner.cc. +void RegisterSchemeHandlers(); + diff --git a/src/CEF/ServerTest.cpp b/src/CEF/ServerTest.cpp new file mode 100644 index 0000000..53a8ad0 --- /dev/null +++ b/src/CEF/ServerTest.cpp @@ -0,0 +1,379 @@ +// Copyright (c) 2017 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 "CEF/ServerTest.h" + +#include +#include +#include + +#include "include/base/cef_callback.h" +#include "include/base/cef_weak_ptr.h" +#include "include/cef_parser.h" +#include "include/cef_server.h" +#include "include/wrapper/cef_closure_task.h" +#include "CEF/ResourceUtil.h" + +// Application-specific error codes. +const int kMessageFormatError = 1; +const int kActionStateError = 1; + +// JSON dictionary keys. +const char kActionKey[] = "action"; +const char kResultKey[] = "result"; +const char kPortKey[] = "port"; +const char kStatusKey[] = "status"; +const char kMessageKey[] = "message"; + +// Required URL for cefQuery execution. +const char kTestUrl[] = "http://tests/server"; + +// Server default values. +const char kServerAddress[] = "127.0.0.1"; +const int kServerPortDefault = 8099; +const int kServerBacklog = 10; +const char kDefaultPath[] = "websocket.html"; + +// Handles the HTTP/WebSocket server. +class ServerHandler : public CefServerHandler { + public: + using CompleteCallback = base::OnceCallback; + + ServerHandler() {} + + // |complete_callback| will be executed on the UI thread after completion. + void StartServer(int port, CompleteCallback complete_callback) { + CEF_REQUIRE_UI_THREAD(); + DCHECK(!server_); + DCHECK(port >= 1025 && port <= 65535); + port_ = port; + complete_callback_ = std::move(complete_callback); + CefServer::CreateServer(kServerAddress, port, kServerBacklog, this); + } + + // |complete_callback| will be executed on the UI thread after completion. + void StopServer(CompleteCallback complete_callback) { + CEF_REQUIRE_UI_THREAD(); + DCHECK(server_); + complete_callback_ = std::move(complete_callback); + server_->Shutdown(); + } + + // CefServerHandler methods are called on the server thread. + + void OnServerCreated(CefRefPtr server) override { + DCHECK(!server_); + server_ = server; + RunCompleteCallback(server->IsRunning()); + } + + void OnServerDestroyed(CefRefPtr server) override { + DCHECK(server_); + server_ = nullptr; + RunCompleteCallback(true); + } + + void OnClientConnected(CefRefPtr server, + int connection_id) override {} + + void OnClientDisconnected(CefRefPtr server, + int connection_id) override {} + + void OnHttpRequest(CefRefPtr server, + int connection_id, + const CefString& client_address, + CefRefPtr request) override { + // Parse the request URL and retrieve the path without leading slash. + CefURLParts url_parts; + CefParseURL(request->GetURL(), url_parts); + std::string path = CefString(&url_parts.path); + if (!path.empty() && path[0] == '/') + path = path.substr(1); + + if (path.empty()) + path = kDefaultPath; + + std::string mime_type; + const size_t sep = path.find_last_of("."); + if (sep != std::string::npos) { + // Determine the mime type based on the extension. + mime_type = CefGetMimeType(path.substr(sep + 1)); + } else { + // No extension. Assume html. + path += ".html"; + } + if (mime_type.empty()) + mime_type = "text/html"; + + CefRefPtr stream; + CefResponse::HeaderMap extra_headers; + + if (path == "request.html") { + // Return the request contents. + stream = GetDumpResponse(request, extra_headers); + } + + if (!stream) { + // Load any resource supported by cefclient. + stream = GetBinaryResourceReader(path.c_str()); + } + + if (stream) { + SendHttpResponseStream(server, connection_id, mime_type, stream, + extra_headers); + } else { + server->SendHttp404Response(connection_id); + } + } + + void OnWebSocketRequest(CefRefPtr server, + int connection_id, + const CefString& client_address, + CefRefPtr request, + CefRefPtr callback) override { + // Always accept WebSocket connections. + callback->Continue(); + } + + void OnWebSocketConnected(CefRefPtr server, + int connection_id) override {} + + void OnWebSocketMessage(CefRefPtr server, + int connection_id, + const void* data, + size_t data_size) override { + // Echo the reverse of the message. + std::string message(static_cast(data), data_size); + std::reverse(message.begin(), message.end()); + + server->SendWebSocketMessage(connection_id, message.data(), message.size()); + } + + int port() const { return port_; } + + private: + void RunCompleteCallback(bool success) { + if (!CefCurrentlyOn(TID_UI)) { + CefPostTask(TID_UI, base::BindOnce(&ServerHandler::RunCompleteCallback, + this, success)); + return; + } + + if (!complete_callback_.is_null()) { + std::move(complete_callback_).Run(success); + } + } + + static void SendHttpResponseStream(CefRefPtr server, + int connection_id, + const std::string& mime_type, + CefRefPtr stream, + CefResponse::HeaderMap extra_headers) { + // Determine the stream size. + stream->Seek(0, SEEK_END); + int64 content_length = stream->Tell(); + stream->Seek(0, SEEK_SET); + + // Send response headers. + server->SendHttpResponse(connection_id, 200, mime_type, content_length, + extra_headers); + + // Send stream contents. + char buffer[8192]; + size_t read; + do { + read = stream->Read(buffer, 1, sizeof(buffer)); + if (read > 0) + server->SendRawData(connection_id, buffer, read); + } while (!stream->Eof() && read != 0); + + // Close the connection. + server->CloseConnection(connection_id); + } + + CefRefPtr server_; + + // The below members are only accessed on the UI thread. + int port_; + CompleteCallback complete_callback_; + + IMPLEMENT_REFCOUNTING(ServerHandler); + DISALLOW_COPY_AND_ASSIGN(ServerHandler); +}; + +// Handle messages in the browser process. +class Handler : public CefMessageRouterBrowserSide::Handler { + public: + Handler() : weak_ptr_factory_(this) {} + + virtual ~Handler() { + if (handler_) { + handler_->StopServer(ServerHandler::CompleteCallback()); + handler_ = nullptr; + } + } + + // Called due to cefQuery execution in server.html. + virtual bool OnQuery(CefRefPtr browser, + CefRefPtr frame, + int64 query_id, + const CefString& request, + bool persistent, + CefRefPtr callback) override { + CEF_REQUIRE_UI_THREAD(); + + // Only handle messages from the test URL. + const std::string& url = frame->GetURL(); + if (url.find(kTestUrl) != 0) + return false; + + // Parse |request| as a JSON dictionary. + CefRefPtr request_dict = ParseJSON(request); + if (!request_dict) { + callback->Failure(kMessageFormatError, "Incorrect message format"); + return true; + } + + if (!VerifyKey(request_dict, kActionKey, VTYPE_STRING, callback)) + return true; + + const std::string& action = request_dict->GetString(kActionKey); + if (action == "query") { + HandleQueryAction(request_dict, callback); + } else if (action == "start") { + HandleStartAction(request_dict, callback); + } else if (action == "stop") { + HandleStopAction(request_dict, callback); + } else { + callback->Failure(kMessageFormatError, "Unrecognized action: " + action); + } + + return true; + } + + private: + // Return current server status. + void HandleQueryAction(CefRefPtr request_dict, + CefRefPtr callback) { + CefRefPtr result_dict = CefDictionaryValue::Create(); + if (handler_) { + result_dict->SetInt(kPortKey, handler_->port()); + result_dict->SetString(kStatusKey, "running"); + } else { + result_dict->SetInt(kPortKey, kServerPortDefault); + result_dict->SetString(kStatusKey, "stopped"); + } + SendResponse(callback, true, result_dict); + } + + // Start the server. + void HandleStartAction(CefRefPtr request_dict, + CefRefPtr callback) { + if (handler_) { + callback->Failure(kActionStateError, "Server is currently running"); + return; + } + + if (!VerifyKey(request_dict, kPortKey, VTYPE_INT, callback)) + return; + + const int port = request_dict->GetInt(kPortKey); + if (port < 8000 || port > 65535) { + callback->Failure(kMessageFormatError, "Invalid port number specified"); + return; + } + + handler_ = new ServerHandler(); + + // Start the server. OnComplete will be executed upon completion. + handler_->StartServer( + port, base::BindOnce(&Handler::OnStartComplete, + weak_ptr_factory_.GetWeakPtr(), callback)); + } + + // Stop the server. + void HandleStopAction(CefRefPtr request_dict, + CefRefPtr callback) { + if (!handler_) { + callback->Failure(kActionStateError, "Server is not currently running"); + return; + } + + // Stop the server. OnComplete will be executed upon completion. + handler_->StopServer(base::BindOnce( + &Handler::OnStopComplete, weak_ptr_factory_.GetWeakPtr(), callback)); + + handler_ = nullptr; + } + + // Server start completed. + void OnStartComplete(CefRefPtr callback, bool success) { + CEF_REQUIRE_UI_THREAD(); + CefRefPtr result_dict = CefDictionaryValue::Create(); + if (!success) { + handler_ = nullptr; + result_dict->SetString(kMessageKey, "Server failed to start."); + } + SendResponse(callback, success, result_dict); + } + + // Server stop completed. + void OnStopComplete(CefRefPtr callback, bool success) { + CEF_REQUIRE_UI_THREAD(); + CefRefPtr result_dict = CefDictionaryValue::Create(); + if (!success) { + result_dict->SetString(kMessageKey, "Server failed to stop."); + } + SendResponse(callback, success, result_dict); + } + + // Send a response in the format expected by server.html. + static void SendResponse(CefRefPtr callback, + bool success, + CefRefPtr result_dict) { + if (!result_dict) { + result_dict = CefDictionaryValue::Create(); + } + result_dict->SetString(kResultKey, success ? "success" : "failure"); + CefRefPtr value = CefValue::Create(); + value->SetDictionary(result_dict); + const std::string& response = CefWriteJSON(value, JSON_WRITER_DEFAULT); + callback->Success(response); + } + + // Convert a JSON string to a dictionary value. + static CefRefPtr ParseJSON(const CefString& string) { + CefRefPtr value = CefParseJSON(string, JSON_PARSER_RFC); + if (value.get() && value->GetType() == VTYPE_DICTIONARY) + return value->GetDictionary(); + return nullptr; + } + + // Verify that |key| exists in |dictionary| and has type |value_type|. Fails + // |callback| and returns false on failure. + static bool VerifyKey(CefRefPtr dictionary, + const char* key, + cef_value_type_t value_type, + CefRefPtr callback) { + if (!dictionary->HasKey(key) || dictionary->GetType(key) != value_type) { + callback->Failure( + kMessageFormatError, + "Missing or incorrectly formatted message key: " + std::string(key)); + return false; + } + return true; + } + + // Non-nullptr while the server is running. + CefRefPtr handler_; + + // Must be the last member. + base::WeakPtrFactory weak_ptr_factory_; +}; + +namespace server_test { + void CreateMessageHandlers(MessageHandlerSet& handlers) { + handlers.insert(new Handler()); + } +} diff --git a/src/CEF/ServerTest.h b/src/CEF/ServerTest.h new file mode 100644 index 0000000..a78717f --- /dev/null +++ b/src/CEF/ServerTest.h @@ -0,0 +1,12 @@ +// Copyright (c) 2017 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. + +#pragma once + +#include "CEF/TestRunner.h" + +namespace server_test { + // Create message handlers. Called from test_runner.cc. + void CreateMessageHandlers(MessageHandlerSet& handlers); +} diff --git a/src/CEF/TestRunner.cpp b/src/CEF/TestRunner.cpp index 68665ff..5f5ff90 100644 --- a/src/CEF/TestRunner.cpp +++ b/src/CEF/TestRunner.cpp @@ -2,7 +2,7 @@ // 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/test_runner.h" +#include "CEF/TestRunner.h" #include #include @@ -15,25 +15,21 @@ #include "include/cef_trace.h" #include "include/wrapper/cef_closure_task.h" #include "include/wrapper/cef_stream_resource_handler.h" -#include "tests/cefclient/browser/binding_test.h" -#include "tests/cefclient/browser/client_handler.h" -#include "tests/cefclient/browser/dialog_test.h" -#include "tests/cefclient/browser/main_context.h" -#include "tests/cefclient/browser/media_router_test.h" -#include "tests/cefclient/browser/preferences_test.h" -#include "tests/cefclient/browser/resource.h" -#include "tests/cefclient/browser/response_filter_test.h" -#include "tests/cefclient/browser/root_window_manager.h" -#include "tests/cefclient/browser/scheme_test.h" -#include "tests/cefclient/browser/server_test.h" -#include "tests/cefclient/browser/urlrequest_test.h" -#include "tests/cefclient/browser/window_test.h" -#include "tests/shared/browser/resource_util.h" +#include "CEF/BindingTest.h" +#include "CEF/ClientHandler.h" +#include "CEF/DialogTest.h" +#include "CEF/HumanAppContext.h" +#include "CEF/MediaRouterTest.h" +#include "CEF/PreferencesTest.h" +// #include "CEF/resource.h" +#include "CEF/ResponseFilterTest.h" +#include "CEF/RootWindoManager.h" +#include "CEF/SchemeTest.h" +#include "CEF/ServerTest.h" +#include "CEF/UrlrequestTest.h" +#include "CEF/WindowTest.h" +#include "CEF/ResourceUtil.h" -namespace client { -namespace test_runner { - -namespace { const char kTestHost[] = "tests"; const char kLocalHost[] = "localhost"; @@ -153,7 +149,7 @@ void RunNewWindowTest(CefRefPtr browser) { auto config = std::make_unique(); config->with_controls = true; config->with_osr = browser->GetHost()->IsWindowRenderingDisabled(); - MainContext::Get()->GetRootWindowManager()->CreateRootWindow( + HumanAppContext::Get()->GetRootWindowManager()->CreateRootWindow( std::move(config)); } @@ -219,7 +215,7 @@ class PromptHandler : public CefMessageRouterBrowserSide::Handler { if (fps <= 0) { // Reset to the default value. CefBrowserSettings settings; - MainContext::Get()->PopulateBrowserSettings(&settings); + HumanAppContext::Get()->PopulateBrowserSettings(&settings); fps = settings.windowless_frame_rate; } @@ -306,7 +302,7 @@ void EndTracing(CefRefPtr browser) { void RunDialog() { static const char kDefaultFileName[] = "trace.txt"; - std::string path = MainContext::Get()->GetDownloadPath(kDefaultFileName); + std::string path = HumanAppContext::Get()->GetDownloadPath(kDefaultFileName); if (path.empty()) path = kDefaultFileName; @@ -357,7 +353,7 @@ void PrintToPDF(CefRefPtr browser) { void RunDialog() { static const char kDefaultFileName[] = "output.pdf"; - std::string path = MainContext::Get()->GetDownloadPath(kDefaultFileName); + std::string path = HumanAppContext::Get()->GetDownloadPath(kDefaultFileName); if (path.empty()) path = kDefaultFileName; @@ -525,65 +521,64 @@ std::string RequestUrlFilter(const std::string& url) { return url_base + ".html" + url_suffix; } -} // namespace void RunTest(CefRefPtr browser, int id) { if (!browser) return; - - switch (id) { - case ID_TESTS_GETSOURCE: - RunGetSourceTest(browser); - break; - case ID_TESTS_GETTEXT: - RunGetTextTest(browser); - break; - case ID_TESTS_WINDOW_NEW: - RunNewWindowTest(browser); - break; - case ID_TESTS_WINDOW_POPUP: - RunPopupWindowTest(browser); - break; - case ID_TESTS_REQUEST: - RunRequestTest(browser); - break; - case ID_TESTS_ZOOM_IN: - ModifyZoom(browser, 0.5); - break; - case ID_TESTS_ZOOM_OUT: - ModifyZoom(browser, -0.5); - break; - case ID_TESTS_ZOOM_RESET: - browser->GetHost()->SetZoomLevel(0.0); - break; - case ID_TESTS_OSR_FPS: - PromptFPS(browser); - break; - case ID_TESTS_OSR_DSF: - PromptDSF(browser); - break; - case ID_TESTS_TRACING_BEGIN: - BeginTracing(); - break; - case ID_TESTS_TRACING_END: - EndTracing(browser); - break; - case ID_TESTS_PRINT: - browser->GetHost()->Print(); - break; - case ID_TESTS_PRINT_TO_PDF: - PrintToPDF(browser); - break; - case ID_TESTS_MUTE_AUDIO: - MuteAudio(browser, true); - break; - case ID_TESTS_UNMUTE_AUDIO: - MuteAudio(browser, false); - break; - case ID_TESTS_OTHER_TESTS: - RunOtherTests(browser); - break; - } + // + // switch (id) { + // case ID_TESTS_GETSOURCE: + // RunGetSourceTest(browser); + // break; + // case ID_TESTS_GETTEXT: + // RunGetTextTest(browser); + // break; + // case ID_TESTS_WINDOW_NEW: + // RunNewWindowTest(browser); + // break; + // case ID_TESTS_WINDOW_POPUP: + // RunPopupWindowTest(browser); + // break; + // case ID_TESTS_REQUEST: + // RunRequestTest(browser); + // break; + // case ID_TESTS_ZOOM_IN: + // ModifyZoom(browser, 0.5); + // break; + // case ID_TESTS_ZOOM_OUT: + // ModifyZoom(browser, -0.5); + // break; + // case ID_TESTS_ZOOM_RESET: + // browser->GetHost()->SetZoomLevel(0.0); + // break; + // case ID_TESTS_OSR_FPS: + // PromptFPS(browser); + // break; + // case ID_TESTS_OSR_DSF: + // PromptDSF(browser); + // break; + // case ID_TESTS_TRACING_BEGIN: + // BeginTracing(); + // break; + // case ID_TESTS_TRACING_END: + // EndTracing(browser); + // break; + // case ID_TESTS_PRINT: + // browser->GetHost()->Print(); + // break; + // case ID_TESTS_PRINT_TO_PDF: + // PrintToPDF(browser); + // break; + // case ID_TESTS_MUTE_AUDIO: + // MuteAudio(browser, true); + // break; + // case ID_TESTS_UNMUTE_AUDIO: + // MuteAudio(browser, false); + // break; + // case ID_TESTS_OTHER_TESTS: + // RunOtherTests(browser); + // break; + // } } std::string DumpRequestContents(CefRefPtr request) { @@ -795,7 +790,7 @@ void Alert(CefRefPtr browser, const std::string& message) { if (browser->GetHost()->GetExtension()) { // Alerts originating from extension hosts should instead be displayed in // the active browser. - browser = MainContext::Get()->GetRootWindowManager()->GetActiveBrowser(); + browser = HumanAppContext::Get()->GetRootWindowManager()->GetActiveBrowser(); if (!browser) return; } @@ -821,45 +816,43 @@ bool IsTestURL(const std::string& url, const std::string& path) { return url_path.find(path) == 0; } -void CreateMessageHandlers(MessageHandlerSet& handlers) { - handlers.insert(new PromptHandler); +namespace test_runner { + void CreateMessageHandlers(MessageHandlerSet& handlers) { + handlers.insert(new PromptHandler); - // Create the binding test handlers. - binding_test::CreateMessageHandlers(handlers); + // Create the binding test handlers. + binding_test::CreateMessageHandlers(handlers); - // Create the dialog test handlers. - dialog_test::CreateMessageHandlers(handlers); + // Create the dialog test handlers. + dialog_test::CreateMessageHandlers(handlers); - // Create the media router test handlers. - media_router_test::CreateMessageHandlers(handlers); + // Create the media router test handlers. + media_router_test::CreateMessageHandlers(handlers); - // Create the preferences test handlers. - preferences_test::CreateMessageHandlers(handlers); + // Create the preferences test handlers. + preferences_test::CreateMessageHandlers(handlers); - // Create the server test handlers. - server_test::CreateMessageHandlers(handlers); + // Create the server test handlers. + server_test::CreateMessageHandlers(handlers); - // Create the urlrequest test handlers. - urlrequest_test::CreateMessageHandlers(handlers); + // Create the urlrequest test handlers. + urlrequest_test::CreateMessageHandlers(handlers); - // Create the window test handlers. - window_test::CreateMessageHandlers(handlers); + // Create the window test handlers. + window_test::CreateMessageHandlers(handlers); + } + + void RegisterSchemeHandlers() { + // Register the scheme handler. + RegisterSchemeHandlers(); + } } - -void RegisterSchemeHandlers() { - // Register the scheme handler. - scheme_test::RegisterSchemeHandlers(); -} - CefRefPtr GetResourceResponseFilter( CefRefPtr browser, CefRefPtr frame, CefRefPtr request, CefRefPtr response) { // Create the response filter. - return response_filter_test::GetResourceResponseFilter(browser, frame, + return GetResourceResponseFilter(browser, frame, request, response); } - -} // namespace test_runner -} // namespace client diff --git a/src/CEF/TestRunner.h b/src/CEF/TestRunner.h index ed0172d..95e8ccd 100644 --- a/src/CEF/TestRunner.h +++ b/src/CEF/TestRunner.h @@ -2,8 +2,7 @@ // reserved. Use of this source code is governed by a BSD-style license that // can be found in the LICENSE file. -#ifndef CEF_TESTS_CEFCLIENT_BROWSER_TEST_RUNNER_H_ -#define CEF_TESTS_CEFCLIENT_BROWSER_TEST_RUNNER_H_ + #pragma once #include @@ -14,9 +13,6 @@ #include "include/wrapper/cef_message_router.h" #include "include/wrapper/cef_resource_manager.h" -namespace client { -namespace test_runner { - // Run a test. void RunTest(CefRefPtr browser, int id); @@ -51,10 +47,12 @@ bool IsTestURL(const std::string& url, const std::string& path); // Create all CefMessageRouterBrowserSide::Handler objects. They will be // deleted when the ClientHandler is destroyed. typedef std::set MessageHandlerSet; -void CreateMessageHandlers(MessageHandlerSet& handlers); +namespace test_runner { + void CreateMessageHandlers(MessageHandlerSet& handlers); -// Register scheme handlers for tests. -void RegisterSchemeHandlers(); + // Register scheme handlers for tests. + void RegisterSchemeHandlers(); +} // Create a resource response filter for tests. CefRefPtr GetResourceResponseFilter( @@ -62,8 +60,3 @@ CefRefPtr GetResourceResponseFilter( CefRefPtr frame, CefRefPtr request, CefRefPtr response); - -} // namespace test_runner -} // namespace client - -#endif // CEF_TESTS_CEFCLIENT_BROWSER_TEST_RUNNER_H_ diff --git a/src/CEF/UrlrequestTest.cpp b/src/CEF/UrlrequestTest.cpp new file mode 100644 index 0000000..b29e4f9 --- /dev/null +++ b/src/CEF/UrlrequestTest.cpp @@ -0,0 +1,177 @@ +// 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 "CEF/UrlrequestTest.h" + +#include +#include + +#include "include/base/cef_callback.h" +#include "include/base/cef_logging.h" +#include "include/cef_urlrequest.h" +#include "include/wrapper/cef_helpers.h" +#include "CEF/TestRunner.h" + +const char kTestUrlPath[] = "/urlrequest"; +const char kTestMessageName[] = "URLRequestTest"; + +// Implementation of CefURLRequestClient that stores response information. Only +// accessed on the UI thread. +class RequestClient : public CefURLRequestClient { + public: + // Callback to be executed on request completion. + using Callback = + base::OnceCallback; + + explicit RequestClient(Callback callback) : callback_(std::move(callback)) { + CEF_REQUIRE_UI_THREAD(); + DCHECK(!callback_.is_null()); + } + + void Detach() { + CEF_REQUIRE_UI_THREAD(); + if (!callback_.is_null()) + callback_.Reset(); + } + + void OnRequestComplete(CefRefPtr request) override { + CEF_REQUIRE_UI_THREAD(); + if (!callback_.is_null()) { + std::move(callback_).Run(request->GetRequestError(), download_data_); + } + } + + void OnUploadProgress(CefRefPtr request, + int64 current, + int64 total) override {} + + void OnDownloadProgress(CefRefPtr request, + int64 current, + int64 total) override {} + + void OnDownloadData(CefRefPtr request, + const void* data, + size_t data_length) override { + CEF_REQUIRE_UI_THREAD(); + download_data_ += std::string(static_cast(data), data_length); + } + + bool GetAuthCredentials(bool isProxy, + const CefString& host, + int port, + const CefString& realm, + const CefString& scheme, + CefRefPtr callback) override { + return false; + } + + private: + Callback callback_; + std::string download_data_; + + IMPLEMENT_REFCOUNTING(RequestClient); + DISALLOW_COPY_AND_ASSIGN(RequestClient); +}; + +// Handle messages in the browser process. Only accessed on the UI thread. +class Handler : public CefMessageRouterBrowserSide::Handler { + public: + Handler() { CEF_REQUIRE_UI_THREAD(); } + + ~Handler() { CancelPendingRequest(); } + + // Called due to cefQuery execution in urlrequest.html. + bool OnQuery(CefRefPtr browser, + CefRefPtr frame, + int64 query_id, + const CefString& request, + bool persistent, + CefRefPtr callback) override { + CEF_REQUIRE_UI_THREAD(); + + // Only handle messages from the test URL. + const std::string& url = frame->GetURL(); + if (!IsTestURL(url, kTestUrlPath)) + return false; + + const std::string& message_name = request; + if (message_name.find(kTestMessageName) == 0) { + const std::string& load_url = + message_name.substr(sizeof(kTestMessageName)); + + CancelPendingRequest(); + + DCHECK(!callback_.get()); + DCHECK(!urlrequest_.get()); + + callback_ = callback; + + // Create a CefRequest for the specified URL. + CefRefPtr cef_request = CefRequest::Create(); + cef_request->SetURL(load_url); + cef_request->SetMethod("GET"); + + // Callback to be executed on request completion. + // It's safe to use base::Unretained() here because there is only one + // RequestClient pending at any given time and we explicitly detach the + // callback in the Handler destructor. + auto request_callback = + base::BindOnce(&Handler::OnRequestComplete, base::Unretained(this)); + + // Create and start a new CefURLRequest associated with the frame, so + // that it shares authentication with ClientHandler::GetAuthCredentials. + urlrequest_ = frame->CreateURLRequest( + cef_request, new RequestClient(std::move(request_callback))); + + return true; + } + + return false; + } + + private: + // Cancel the currently pending URL request, if any. + void CancelPendingRequest() { + CEF_REQUIRE_UI_THREAD(); + + if (urlrequest_.get()) { + // Don't execute the callback when we explicitly cancel the request. + static_cast(urlrequest_->GetClient().get())->Detach(); + + urlrequest_->Cancel(); + urlrequest_ = nullptr; + } + + if (callback_.get()) { + // Must always execute |callback_| before deleting it. + callback_->Failure(ERR_ABORTED, GetErrorString(ERR_ABORTED)); + callback_ = nullptr; + } + } + + void OnRequestComplete(CefURLRequest::ErrorCode error_code, + const std::string& download_data) { + CEF_REQUIRE_UI_THREAD(); + + if (error_code == ERR_NONE) + callback_->Success(download_data); + else + callback_->Failure(error_code, GetErrorString(error_code)); + + callback_ = nullptr; + urlrequest_ = nullptr; + } + + CefRefPtr callback_; + CefRefPtr urlrequest_; + + DISALLOW_COPY_AND_ASSIGN(Handler); +}; + +namespace urlrequest_test { + void CreateMessageHandlers(MessageHandlerSet& handlers) { + handlers.insert(new Handler()); + } +} \ No newline at end of file diff --git a/src/CEF/UrlrequestTest.h b/src/CEF/UrlrequestTest.h new file mode 100644 index 0000000..a7b5746 --- /dev/null +++ b/src/CEF/UrlrequestTest.h @@ -0,0 +1,12 @@ +// 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. + +#pragma once + +#include "CEF/TestRunner.h" + +namespace urlrequest_test { + // Create message handlers. Called from test_runner.cc. + void CreateMessageHandlers(MessageHandlerSet& handlers); +} diff --git a/src/CEF/UtilWin.cpp b/src/CEF/UtilWin.cpp new file mode 100644 index 0000000..f757f54 --- /dev/null +++ b/src/CEF/UtilWin.cpp @@ -0,0 +1,169 @@ +// 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 "CEF/UtilWin.h" + +#include "include/base/cef_logging.h" + + +LARGE_INTEGER qi_freq_ = {}; + +uint64_t GetTimeNow() { + if (!qi_freq_.HighPart && !qi_freq_.LowPart) { + QueryPerformanceFrequency(&qi_freq_); + } + LARGE_INTEGER t = {}; + QueryPerformanceCounter(&t); + return static_cast((t.QuadPart / double(qi_freq_.QuadPart)) * + 1000000); +} + +void SetUserDataPtr(HWND hWnd, void* ptr) { + SetLastError(ERROR_SUCCESS); + LONG_PTR result = + ::SetWindowLongPtr(hWnd, GWLP_USERDATA, reinterpret_cast(ptr)); + CHECK(result != 0 || GetLastError() == ERROR_SUCCESS); +} + +WNDPROC SetWndProcPtr(HWND hWnd, WNDPROC wndProc) { + WNDPROC old = + reinterpret_cast(::GetWindowLongPtr(hWnd, GWLP_WNDPROC)); + CHECK(old != nullptr); + LONG_PTR result = ::SetWindowLongPtr(hWnd, GWLP_WNDPROC, + reinterpret_cast(wndProc)); + CHECK(result != 0 || GetLastError() == ERROR_SUCCESS); + return old; +} + +std::wstring GetResourceString(UINT id) { +#define MAX_LOADSTRING 100 + TCHAR buff[MAX_LOADSTRING] = {0}; + LoadString(::GetModuleHandle(nullptr), id, buff, MAX_LOADSTRING); + return buff; +} + +int GetCefMouseModifiers(WPARAM wparam) { + int modifiers = 0; + if (wparam & MK_CONTROL) + modifiers |= EVENTFLAG_CONTROL_DOWN; + if (wparam & MK_SHIFT) + modifiers |= EVENTFLAG_SHIFT_DOWN; + if (IsKeyDown(VK_MENU)) + modifiers |= EVENTFLAG_ALT_DOWN; + if (wparam & MK_LBUTTON) + modifiers |= EVENTFLAG_LEFT_MOUSE_BUTTON; + if (wparam & MK_MBUTTON) + modifiers |= EVENTFLAG_MIDDLE_MOUSE_BUTTON; + if (wparam & MK_RBUTTON) + modifiers |= EVENTFLAG_RIGHT_MOUSE_BUTTON; + + // Low bit set from GetKeyState indicates "toggled". + if (::GetKeyState(VK_NUMLOCK) & 1) + modifiers |= EVENTFLAG_NUM_LOCK_ON; + if (::GetKeyState(VK_CAPITAL) & 1) + modifiers |= EVENTFLAG_CAPS_LOCK_ON; + return modifiers; +} + +int GetCefKeyboardModifiers(WPARAM wparam, LPARAM lparam) { + int modifiers = 0; + if (IsKeyDown(VK_SHIFT)) + modifiers |= EVENTFLAG_SHIFT_DOWN; + if (IsKeyDown(VK_CONTROL)) + modifiers |= EVENTFLAG_CONTROL_DOWN; + if (IsKeyDown(VK_MENU)) + modifiers |= EVENTFLAG_ALT_DOWN; + + // Low bit set from GetKeyState indicates "toggled". + if (::GetKeyState(VK_NUMLOCK) & 1) + modifiers |= EVENTFLAG_NUM_LOCK_ON; + if (::GetKeyState(VK_CAPITAL) & 1) + modifiers |= EVENTFLAG_CAPS_LOCK_ON; + + switch (wparam) { + case VK_RETURN: + if ((lparam >> 16) & KF_EXTENDED) + modifiers |= EVENTFLAG_IS_KEY_PAD; + break; + case VK_INSERT: + case VK_DELETE: + case VK_HOME: + case VK_END: + case VK_PRIOR: + case VK_NEXT: + case VK_UP: + case VK_DOWN: + case VK_LEFT: + case VK_RIGHT: + if (!((lparam >> 16) & KF_EXTENDED)) + modifiers |= EVENTFLAG_IS_KEY_PAD; + break; + case VK_NUMLOCK: + case VK_NUMPAD0: + case VK_NUMPAD1: + case VK_NUMPAD2: + case VK_NUMPAD3: + case VK_NUMPAD4: + case VK_NUMPAD5: + case VK_NUMPAD6: + case VK_NUMPAD7: + case VK_NUMPAD8: + case VK_NUMPAD9: + case VK_DIVIDE: + case VK_MULTIPLY: + case VK_SUBTRACT: + case VK_ADD: + case VK_DECIMAL: + case VK_CLEAR: + modifiers |= EVENTFLAG_IS_KEY_PAD; + break; + case VK_SHIFT: + if (IsKeyDown(VK_LSHIFT)) + modifiers |= EVENTFLAG_IS_LEFT; + else if (IsKeyDown(VK_RSHIFT)) + modifiers |= EVENTFLAG_IS_RIGHT; + break; + case VK_CONTROL: + if (IsKeyDown(VK_LCONTROL)) + modifiers |= EVENTFLAG_IS_LEFT; + else if (IsKeyDown(VK_RCONTROL)) + modifiers |= EVENTFLAG_IS_RIGHT; + break; + case VK_MENU: + if (IsKeyDown(VK_LMENU)) + modifiers |= EVENTFLAG_IS_LEFT; + else if (IsKeyDown(VK_RMENU)) + modifiers |= EVENTFLAG_IS_RIGHT; + break; + case VK_LWIN: + modifiers |= EVENTFLAG_IS_LEFT; + break; + case VK_RWIN: + modifiers |= EVENTFLAG_IS_RIGHT; + break; + } + return modifiers; +} + +bool IsKeyDown(WPARAM wparam) { + return (GetKeyState(wparam) & 0x8000) != 0; +} + +float GetDeviceScaleFactor() { + static float scale_factor = 1.0; + static bool initialized = false; + + if (!initialized) { + // This value is safe to cache for the life time of the app since the user + // must logout to change the DPI setting. This value also applies to all + // screens. + HDC screen_dc = ::GetDC(nullptr); + int dpi_x = GetDeviceCaps(screen_dc, LOGPIXELSX); + scale_factor = static_cast(dpi_x) / 96.0f; + ::ReleaseDC(nullptr, screen_dc); + initialized = true; + } + + return scale_factor; +} diff --git a/src/CEF/UtilWin.h b/src/CEF/UtilWin.h new file mode 100644 index 0000000..8faaa33 --- /dev/null +++ b/src/CEF/UtilWin.h @@ -0,0 +1,36 @@ +// 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. + +#pragma once + +#include +#include + +#include "include/internal/cef_types_wrappers.h" + +// Returns the current time in microseconds. +uint64_t GetTimeNow(); + +// Set the window's user data pointer. +void SetUserDataPtr(HWND hWnd, void* ptr); + +// Return the window's user data pointer. +template +T GetUserDataPtr(HWND hWnd) { + return reinterpret_cast(GetWindowLongPtr(hWnd, GWLP_USERDATA)); +} + +// Set the window's window procedure pointer and return the old value. +WNDPROC SetWndProcPtr(HWND hWnd, WNDPROC wndProc); + +// Return the resource string with the specified id. +std::wstring GetResourceString(UINT id); + +int GetCefMouseModifiers(WPARAM wparam); +int GetCefKeyboardModifiers(WPARAM wparam, LPARAM lparam); +bool IsKeyDown(WPARAM wparam); + +// Returns the device scale factor. For example, 200% display scaling will +// return 2.0. +float GetDeviceScaleFactor(); diff --git a/src/CEF/WindowTest.cpp b/src/CEF/WindowTest.cpp new file mode 100644 index 0000000..5d1e999 --- /dev/null +++ b/src/CEF/WindowTest.cpp @@ -0,0 +1,114 @@ +// Copyright (c) 2013 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 "CEF/WindowTest.h" + +#include +#include +#include +#include + +#include "include/base/cef_callback.h" +#include "include/wrapper/cef_stream_resource_handler.h" +#include "CEF/HumanAppContext.h" +#include "CEF/TestRunner.h" +#include "CEF/WindowTestRunner.h" + +#if defined(OS_WIN) || defined(OS_LINUX) +#include "CEF/WindowTestRunnerViews.h" +#endif + +#if defined(OS_WIN) +#include "CEF/WindowTestRunnerWin.h" +#elif defined(OS_LINUX) +#include "tests/cefclient/browser/window_test_runner_gtk.h" +#elif defined(OS_MAC) +#include "tests/cefclient/browser/window_test_runner_mac.h" +#endif + +const char kTestUrlPath[] = "/window"; +const char kMessagePositionName[] = "WindowTest.Position"; +const char kMessageMinimizeName[] = "WindowTest.Minimize"; +const char kMessageMaximizeName[] = "WindowTest.Maximize"; +const char kMessageRestoreName[] = "WindowTest.Restore"; + +// Create the appropriate platform test runner object. +std::unique_ptr CreateWindowTestRunner() { +#if defined(OS_WIN) || defined(OS_LINUX) + if (HumanAppContext::Get()->UseViews()) + return std::make_unique(); +#endif + +#if defined(OS_WIN) + return std::make_unique(); +#elif defined(OS_LINUX) + return std::make_unique(); +#elif defined(OS_MAC) + return std::make_unique(); +#else +#error "No implementation available for your platform." +#endif +} + +// Handle messages in the browser process. +class Handler : public CefMessageRouterBrowserSide::Handler { + public: + Handler() : runner_(CreateWindowTestRunner()) {} + + // Called due to cefBroadcast execution in window.html. + virtual bool OnQuery(CefRefPtr browser, + CefRefPtr frame, + int64 query_id, + const CefString& request, + bool persistent, + CefRefPtr callback) override { + // Only handle messages from the test URL. + const std::string& url = frame->GetURL(); + if (!IsTestURL(url, kTestUrlPath)) + return false; + + const std::string& message_name = request; + if (message_name.find(kMessagePositionName) == 0) { + // Parse the comma-delimited list of integer values. + std::vector vec; + const std::string& vals = + message_name.substr(sizeof(kMessagePositionName)); + std::stringstream ss(vals); + int i; + while (ss >> i) { + vec.push_back(i); + if (ss.peek() == ',') + ss.ignore(); + } + + if (vec.size() == 4) { + // Execute SetPos() on the main thread. + runner_->SetPos(browser, vec[0], vec[1], vec[2], vec[3]); + } + } else if (message_name == kMessageMinimizeName) { + // Execute Minimize() on the main thread. + runner_->Minimize(browser); + } else if (message_name == kMessageMaximizeName) { + // Execute Maximize() on the main thread. + runner_->Maximize(browser); + } else if (message_name == kMessageRestoreName) { + // Execute Restore() on the main thread. + runner_->Restore(browser); + } else { + NOTREACHED(); + } + + callback->Success(""); + return true; + } + + private: + std::unique_ptr runner_; +}; + +namespace window_test { + void CreateMessageHandlers(MessageHandlerSet& handlers) { + handlers.insert(new Handler()); + } +} \ No newline at end of file diff --git a/src/CEF/WindowTest.h b/src/CEF/WindowTest.h new file mode 100644 index 0000000..3370b18 --- /dev/null +++ b/src/CEF/WindowTest.h @@ -0,0 +1,12 @@ +// Copyright (c) 2013 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. + +#pragma once + +#include "CEF/TestRunner.h" + +namespace window_test { + // Create message handlers. Called from test_runner.cc. + void CreateMessageHandlers(MessageHandlerSet& handlers); +} diff --git a/src/CEF/WindowTestRunner.cpp b/src/CEF/WindowTestRunner.cpp new file mode 100644 index 0000000..795756b --- /dev/null +++ b/src/CEF/WindowTestRunner.cpp @@ -0,0 +1,28 @@ +// Copyright (c) 2016 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 "CEF/WindowTestRunner.h" + +// static +void WindowTestRunner::ModifyBounds(const CefRect& display, CefRect& window) { + window.x += display.x; + window.y += display.y; + + if (window.x < display.x) + window.x = display.x; + if (window.y < display.y) + window.y = display.y; + if (window.width < 100) + window.width = 100; + else if (window.width >= display.width) + window.width = display.width; + if (window.height < 100) + window.height = 100; + else if (window.height >= display.height) + window.height = display.height; + if (window.x + window.width >= display.x + display.width) + window.x = display.x + display.width - window.width; + if (window.y + window.height >= display.y + display.height) + window.y = display.y + display.height - window.height; +} diff --git a/src/CEF/WindowTestRunner.h b/src/CEF/WindowTestRunner.h new file mode 100644 index 0000000..92984ac --- /dev/null +++ b/src/CEF/WindowTestRunner.h @@ -0,0 +1,27 @@ +// Copyright (c) 2016 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. + +#pragma once + +#include "include/cef_browser.h" + +// Implement this interface for different platforms. Methods will be called on +// the browser process UI thread unless otherwise indicated. +class WindowTestRunner { + public: + virtual void SetPos(CefRefPtr browser, + int x, + int y, + int width, + int height) = 0; + virtual void Minimize(CefRefPtr browser) = 0; + virtual void Maximize(CefRefPtr browser) = 0; + virtual void Restore(CefRefPtr browser) = 0; + + // Fit |window| inside |display|. Coordinates are relative to the upper-left + // corner of the display. + static void ModifyBounds(const CefRect& display, CefRect& window); + + virtual ~WindowTestRunner() {} +}; diff --git a/src/CEF/WindowTestRunnerViews.cpp b/src/CEF/WindowTestRunnerViews.cpp new file mode 100644 index 0000000..6769779 --- /dev/null +++ b/src/CEF/WindowTestRunnerViews.cpp @@ -0,0 +1,51 @@ +// Copyright (c) 2016 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 "CEF/WindowTestRunnerViews.h" + +#include "include/views/cef_browser_view.h" +#include "include/views/cef_display.h" +#include "include/views/cef_window.h" +#include "include/wrapper/cef_helpers.h" + + +CefRefPtr GetWindow(CefRefPtr browser) { + CEF_REQUIRE_UI_THREAD(); + DCHECK(browser->GetHost()->HasView()); + + CefRefPtr browser_view = + CefBrowserView::GetForBrowser(browser); + DCHECK(browser_view.get()); + + CefRefPtr window = browser_view->GetWindow(); + DCHECK(window.get()); + return window; +} + +WindowTestRunnerViews::WindowTestRunnerViews() {} + +void WindowTestRunnerViews::SetPos(CefRefPtr browser, + int x, + int y, + int width, + int height) { + CefRefPtr window = GetWindow(browser); + + CefRect window_bounds(x, y, width, height); + ModifyBounds(window->GetDisplay()->GetWorkArea(), window_bounds); + + window->SetBounds(window_bounds); +} + +void WindowTestRunnerViews::Minimize(CefRefPtr browser) { + GetWindow(browser)->Minimize(); +} + +void WindowTestRunnerViews::Maximize(CefRefPtr browser) { + GetWindow(browser)->Maximize(); +} + +void WindowTestRunnerViews::Restore(CefRefPtr browser) { + GetWindow(browser)->Restore(); +} diff --git a/src/CEF/WindowTestRunnerViews.h b/src/CEF/WindowTestRunnerViews.h new file mode 100644 index 0000000..56a7780 --- /dev/null +++ b/src/CEF/WindowTestRunnerViews.h @@ -0,0 +1,22 @@ +// Copyright (c) 2016 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. + +#pragma once + +#include "CEF/WindowTestRunner.h" + +// Views platform implementation. +class WindowTestRunnerViews : public WindowTestRunner { + public: + WindowTestRunnerViews(); + + void SetPos(CefRefPtr browser, + int x, + int y, + int width, + int height) override; + void Minimize(CefRefPtr browser) override; + void Maximize(CefRefPtr browser) override; + void Restore(CefRefPtr browser) override; +}; diff --git a/src/CEF/WindowTestRunnerWin.cpp b/src/CEF/WindowTestRunnerWin.cpp new file mode 100644 index 0000000..087a2e2 --- /dev/null +++ b/src/CEF/WindowTestRunnerWin.cpp @@ -0,0 +1,129 @@ +// Copyright (c) 2013 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 "CEF/WindowTestRunnerWin.h" + +#include "CEF/MainMessageLoop.h" + +HWND GetRootHwnd(CefRefPtr browser) { + return ::GetAncestor(browser->GetHost()->GetWindowHandle(), GA_ROOT); +} + +// Toggles the current display state. +void Toggle(HWND root_hwnd, UINT nCmdShow) { + // Retrieve current window placement information. + WINDOWPLACEMENT placement; + ::GetWindowPlacement(root_hwnd, &placement); + + if (placement.showCmd == nCmdShow) + ::ShowWindow(root_hwnd, SW_RESTORE); + else + ::ShowWindow(root_hwnd, nCmdShow); +} + +void SetPosImpl(CefRefPtr browser, + int x, + int y, + int width, + int height) { + HWND root_hwnd = GetRootHwnd(browser); + if (!root_hwnd) + return; + + // Retrieve current window placement information. + WINDOWPLACEMENT placement; + ::GetWindowPlacement(root_hwnd, &placement); + + // Retrieve information about the display that contains the window. + HMONITOR monitor = + MonitorFromRect(&placement.rcNormalPosition, MONITOR_DEFAULTTONEAREST); + MONITORINFO info; + info.cbSize = sizeof(info); + GetMonitorInfo(monitor, &info); + + // Make sure the window is inside the display. + CefRect display_rect(info.rcWork.left, info.rcWork.top, + info.rcWork.right - info.rcWork.left, + info.rcWork.bottom - info.rcWork.top); + CefRect window_rect(x, y, width, height); + WindowTestRunner::ModifyBounds(display_rect, window_rect); + + if (placement.showCmd == SW_MINIMIZE || placement.showCmd == SW_MAXIMIZE) { + // The window is currently minimized or maximized. Restore it to the desired + // position. + placement.rcNormalPosition.left = window_rect.x; + placement.rcNormalPosition.right = window_rect.x + window_rect.width; + placement.rcNormalPosition.top = window_rect.y; + placement.rcNormalPosition.bottom = window_rect.y + window_rect.height; + ::SetWindowPlacement(root_hwnd, &placement); + ::ShowWindow(root_hwnd, SW_RESTORE); + } else { + // Set the window position. + ::SetWindowPos(root_hwnd, nullptr, window_rect.x, window_rect.y, + window_rect.width, window_rect.height, SWP_NOZORDER); + } +} + +void MinimizeImpl(CefRefPtr browser) { + HWND root_hwnd = GetRootHwnd(browser); + if (!root_hwnd) + return; + Toggle(root_hwnd, SW_MINIMIZE); +} + +void MaximizeImpl(CefRefPtr browser) { + HWND root_hwnd = GetRootHwnd(browser); + if (!root_hwnd) + return; + Toggle(root_hwnd, SW_MAXIMIZE); +} + +void RestoreImpl(CefRefPtr browser) { + HWND root_hwnd = GetRootHwnd(browser); + if (!root_hwnd) + return; + ::ShowWindow(root_hwnd, SW_RESTORE); +} + +WindowTestRunnerWin::WindowTestRunnerWin() {} + +void WindowTestRunnerWin::SetPos(CefRefPtr browser, + int x, + int y, + int width, + int height) { + if (CURRENTLY_ON_MAIN_THREAD()) { + SetPosImpl(browser, x, y, width, height); + } else { + // Execute on the main application thread. + MAIN_POST_CLOSURE(base::BindOnce(SetPosImpl, browser, x, y, width, height)); + } +} + +void WindowTestRunnerWin::Minimize(CefRefPtr browser) { + if (CURRENTLY_ON_MAIN_THREAD()) { + MinimizeImpl(browser); + } else { + // Execute on the main application thread. + MAIN_POST_CLOSURE(base::BindOnce(MinimizeImpl, browser)); + } +} + +void WindowTestRunnerWin::Maximize(CefRefPtr browser) { + if (CURRENTLY_ON_MAIN_THREAD()) { + MaximizeImpl(browser); + } else { + // Execute on the main application thread. + MAIN_POST_CLOSURE(base::BindOnce(MaximizeImpl, browser)); + } +} + +void WindowTestRunnerWin::Restore(CefRefPtr browser) { + if (CURRENTLY_ON_MAIN_THREAD()) { + RestoreImpl(browser); + } else { + // Execute on the main application thread. + MAIN_POST_CLOSURE(base::BindOnce(RestoreImpl, browser)); + } +} diff --git a/src/CEF/WindowTestRunnerWin.h b/src/CEF/WindowTestRunnerWin.h new file mode 100644 index 0000000..08edbb6 --- /dev/null +++ b/src/CEF/WindowTestRunnerWin.h @@ -0,0 +1,23 @@ +// Copyright (c) 2016 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. + +#pragma once + +#include "CEF/WindowTestRunner.h" + +// Windows platform implementation. Methods are safe to call on any browser +// process thread. +class WindowTestRunnerWin : public WindowTestRunner { + public: + WindowTestRunnerWin(); + + void SetPos(CefRefPtr browser, + int x, + int y, + int width, + int height) override; + void Minimize(CefRefPtr browser) override; + void Maximize(CefRefPtr browser) override; + void Restore(CefRefPtr browser) override; +}; diff --git a/src/CEF/resource.h b/src/CEF/resource.h new file mode 100644 index 0000000..584c21b --- /dev/null +++ b/src/CEF/resource.h @@ -0,0 +1,86 @@ +// Copyright (c) 2013 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. + +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by cefclient.rc +// +#define BINARY 256 +#define IDC_MYICON 2 +#define IDD_CEFCLIENT_DIALOG 102 +#define IDS_APP_TITLE 103 +#define IDD_ABOUTBOX 103 +#define IDM_ABOUT 104 +#define IDM_EXIT 105 +#define IDI_CEFCLIENT 107 +#define IDI_SMALL 108 +#define IDC_CEFCLIENT 109 +#define IDR_MAINFRAME 128 +#define IDC_NAV_BACK 200 +#define IDC_NAV_FORWARD 201 +#define IDC_NAV_RELOAD 202 +#define IDC_NAV_STOP 203 +#define ID_QUIT 32500 +#define ID_FIND 32501 +#define ID_TESTS_FIRST 32700 +#define ID_TESTS_GETSOURCE 32700 +#define ID_TESTS_GETTEXT 32701 +#define ID_TESTS_OTHER_TESTS 32702 +#define ID_TESTS_WINDOW_NEW 32703 +#define ID_TESTS_WINDOW_POPUP 32704 +#define ID_TESTS_PRINT 32705 +#define ID_TESTS_REQUEST 32706 +#define ID_TESTS_TRACING_BEGIN 32707 +#define ID_TESTS_TRACING_END 32708 +#define ID_TESTS_ZOOM_IN 32709 +#define ID_TESTS_ZOOM_OUT 32710 +#define ID_TESTS_ZOOM_RESET 32711 +#define ID_TESTS_OSR_FPS 32712 +#define ID_TESTS_OSR_DSF 32713 +#define ID_TESTS_PRINT_TO_PDF 32714 +#define ID_TESTS_MUTE_AUDIO 32715 +#define ID_TESTS_UNMUTE_AUDIO 32716 +#define ID_TESTS_LAST 32716 +#define IDC_STATIC -1 +#define IDS_BINDING_HTML 1000 +#define IDS_DIALOGS_HTML 1001 +#define IDS_DRAGGABLE_HTML 1002 +#define IDS_LOCALSTORAGE_HTML 1003 +#define IDS_LOGO_PNG 1004 +#define IDS_MEDIA_ROUTER_HTML 1005 +#define IDS_MENU_ICON_1X_PNG 1006 +#define IDS_MENU_ICON_2X_PNG 1007 +#define IDS_OSRTEST_HTML 1008 +#define IDS_OTHER_TESTS_HTML 1009 +#define IDS_PDF_HTML 1010 +#define IDS_PDF_PDF 1011 +#define IDS_PERFORMANCE_HTML 1012 +#define IDS_PERFORMANCE2_HTML 1013 +#define IDS_PREFERENCES_HTML 1014 +#define IDS_RESPONSE_FILTER_HTML 1015 +#define IDS_SERVER_HTML 1016 +#define IDS_TRANSPARENCY_HTML 1017 +#define IDS_URLREQUEST_HTML 1018 +#define IDS_WEBSOCKET_HTML 1019 +#define IDS_WINDOW_HTML 1020 +#define IDS_WINDOW_ICON_1X_PNG 1021 +#define IDS_WINDOW_ICON_2X_PNG 1022 +#define IDS_XMLHTTPREQUEST_HTML 1023 + +#define IDS_EXTENSIONS_SET_PAGE_COLOR_ICON_PNG 1030 +#define IDS_EXTENSIONS_SET_PAGE_COLOR_MANIFEST_JSON 1031 +#define IDS_EXTENSIONS_SET_PAGE_COLOR_POPUP_HTML 1032 +#define IDS_EXTENSIONS_SET_PAGE_COLOR_POPUP_JS 1033 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NO_MFC 1 +#define _APS_NEXT_RESOURCE_VALUE 130 +#define _APS_NEXT_COMMAND_VALUE 32774 +#define _APS_NEXT_CONTROL_VALUE 1000 +#define _APS_NEXT_SYMED_VALUE 111 +#endif +#endif