diff --git a/src/CEF/ClientHandler.cpp b/src/CEF/ClientHandler.cpp new file mode 100644 index 0000000..7fb7c1b --- /dev/null +++ b/src/CEF/ClientHandler.cpp @@ -0,0 +1,1357 @@ +// 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/ClientHandler.h" + +#include +#include +#include +#include +#include + +#include "include/base/cef_callback.h" +#include "include/base/cef_cxx17_backports.h" +#include "include/cef_browser.h" +#include "include/cef_command_ids.h" +#include "include/cef_frame.h" +#include "include/cef_parser.h" +#include "include/cef_ssl_status.h" +#include "include/cef_x509_certificate.h" +#include "include/wrapper/cef_closure_task.h" +#include "CEF/HumanAppContext.h" +#include "CEF/RootWindoManager.h" +//#include "tests/cefclient/browser/test_runner.h" +#include "CEF/ExtensionUtil.h" +#include "CEF/ResourceUtil.h" +#include "CEF/HumanAppSwitches.h" + +#if defined(OS_WIN) +#define NEWLINE "\r\n" +#else +#define NEWLINE "\n" +#endif + + +// Custom menu command Ids. +enum client_menu_ids { + CLIENT_ID_SHOW_DEVTOOLS = MENU_ID_USER_FIRST, + CLIENT_ID_CLOSE_DEVTOOLS, + CLIENT_ID_INSPECT_ELEMENT, + CLIENT_ID_SHOW_SSL_INFO, + CLIENT_ID_CURSOR_CHANGE_DISABLED, + CLIENT_ID_OFFLINE, + CLIENT_ID_TESTMENU_SUBMENU, + CLIENT_ID_TESTMENU_CHECKITEM, + CLIENT_ID_TESTMENU_RADIOITEM1, + CLIENT_ID_TESTMENU_RADIOITEM2, + CLIENT_ID_TESTMENU_RADIOITEM3, +}; + +// Musr match the value in client_renderer.cc. +const char kFocusedNodeChangedMessage[] = "ClientRenderer.FocusedNodeChanged"; + +std::string GetTimeString(const CefTime& value) { + if (value.GetTimeT() == 0) + return "Unspecified"; + + static const char* kMonths[] = { + "January", "February", "March", "April", "May", "June", + "July", "August", "September", "October", "November", "December"}; + std::string month; + if (value.month >= 1 && value.month <= 12) + month = kMonths[value.month - 1]; + else + month = "Invalid"; + + std::stringstream ss; + ss << month << " " << value.day_of_month << ", " << value.year << " " + << std::setfill('0') << std::setw(2) << value.hour << ":" + << std::setfill('0') << std::setw(2) << value.minute << ":" + << std::setfill('0') << std::setw(2) << value.second; + return ss.str(); +} + +std::string GetBinaryString(CefRefPtr value) { + if (!value.get()) + return " "; + + // Retrieve the value. + const size_t size = value->GetSize(); + std::string src; + src.resize(size); + value->GetData(const_cast(src.data()), size, 0); + + // Encode the value. + return CefBase64Encode(src.data(), src.size()); +} + +#define FLAG(flag) \ + if (status & flag) { \ + result += std::string(#flag) + "
"; \ + } + +#define VALUE(val, def) \ + if (val == def) { \ + return std::string(#def); \ + } + +std::string GetCertStatusString(cef_cert_status_t status) { + std::string result; + + FLAG(CERT_STATUS_COMMON_NAME_INVALID); + FLAG(CERT_STATUS_DATE_INVALID); + FLAG(CERT_STATUS_AUTHORITY_INVALID); + FLAG(CERT_STATUS_NO_REVOCATION_MECHANISM); + FLAG(CERT_STATUS_UNABLE_TO_CHECK_REVOCATION); + FLAG(CERT_STATUS_REVOKED); + FLAG(CERT_STATUS_INVALID); + FLAG(CERT_STATUS_WEAK_SIGNATURE_ALGORITHM); + FLAG(CERT_STATUS_NON_UNIQUE_NAME); + FLAG(CERT_STATUS_WEAK_KEY); + FLAG(CERT_STATUS_PINNED_KEY_MISSING); + FLAG(CERT_STATUS_NAME_CONSTRAINT_VIOLATION); + FLAG(CERT_STATUS_VALIDITY_TOO_LONG); + FLAG(CERT_STATUS_IS_EV); + FLAG(CERT_STATUS_REV_CHECKING_ENABLED); + FLAG(CERT_STATUS_SHA1_SIGNATURE_PRESENT); + FLAG(CERT_STATUS_CT_COMPLIANCE_FAILED); + + if (result.empty()) + return " "; + return result; +} + +std::string GetSSLVersionString(cef_ssl_version_t version) { + VALUE(version, SSL_CONNECTION_VERSION_UNKNOWN); + VALUE(version, SSL_CONNECTION_VERSION_SSL2); + VALUE(version, SSL_CONNECTION_VERSION_SSL3); + VALUE(version, SSL_CONNECTION_VERSION_TLS1); + VALUE(version, SSL_CONNECTION_VERSION_TLS1_1); + VALUE(version, SSL_CONNECTION_VERSION_TLS1_2); + VALUE(version, SSL_CONNECTION_VERSION_TLS1_3); + VALUE(version, SSL_CONNECTION_VERSION_QUIC); + return std::string(); +} + +std::string GetContentStatusString(cef_ssl_content_status_t status) { + std::string result; + + VALUE(status, SSL_CONTENT_NORMAL_CONTENT); + FLAG(SSL_CONTENT_DISPLAYED_INSECURE_CONTENT); + FLAG(SSL_CONTENT_RAN_INSECURE_CONTENT); + + if (result.empty()) + return " "; + return result; +} + +// Load a data: URI containing the error message. +void LoadErrorPage(CefRefPtr frame, + const std::string& failed_url, + cef_errorcode_t error_code, + const std::string& other_info) { + std::stringstream ss; + ss << "Page failed to load" + "" + "

Page failed to load.

" + "URL: " << failed_url + << "
Error: " << test_runner::GetErrorString(error_code) << " (" + << error_code << ")"; + + if (!other_info.empty()) + ss << "
" << other_info; + + ss << ""; + frame->LoadURL(test_runner::GetDataURI(ss.str(), "text/html")); +} + +// Return HTML string with information about a certificate. +std::string GetCertificateInformation(CefRefPtr cert, + cef_cert_status_t certstatus) { + CefRefPtr subject = cert->GetSubject(); + CefRefPtr issuer = cert->GetIssuer(); + + // Build a table showing certificate information. Various types of invalid + // certificates can be tested using https://badssl.com/. + std::stringstream ss; + ss << "

X.509 Certificate Information:

" + ""; + + if (certstatus != CERT_STATUS_NONE) { + ss << ""; + } + + ss << "" + "" + "" + << "" + ""; + + CefX509Certificate::IssuerChainBinaryList der_chain_list; + CefX509Certificate::IssuerChainBinaryList pem_chain_list; + cert->GetDEREncodedIssuerChain(der_chain_list); + cert->GetPEMEncodedIssuerChain(pem_chain_list); + DCHECK_EQ(der_chain_list.size(), pem_chain_list.size()); + + der_chain_list.insert(der_chain_list.begin(), cert->GetDEREncoded()); + pem_chain_list.insert(pem_chain_list.begin(), cert->GetPEMEncoded()); + + for (size_t i = 0U; i < der_chain_list.size(); ++i) { + ss << "" + "" + "" + ""; + } + + ss << "
FieldValue
Status" << GetCertStatusString(certstatus) + << "
Subject" + << (subject.get() ? subject->GetDisplayName().ToString() : " ") + << "
Issuer" + << (issuer.get() ? issuer->GetDisplayName().ToString() : " ") + << "
Serial #*" + << GetBinaryString(cert->GetSerialNumber()) << "
Valid Start" << GetTimeString(cert->GetValidStart()) + << "
Valid Expiry" + << GetTimeString(cert->GetValidExpiry()) << "
DER Encoded*" + << GetBinaryString(der_chain_list[i]) + << "
PEM Encoded*" + << GetBinaryString(pem_chain_list[i]) << "
* Displayed value is base64 encoded."; + return ss.str(); +} + +} // namespace + +class ClientDownloadImageCallback : public CefDownloadImageCallback { + public: + explicit ClientDownloadImageCallback(CefRefPtr client_handler) + : client_handler_(client_handler) {} + + void OnDownloadImageFinished(const CefString& image_url, + int http_status_code, + CefRefPtr image) override { + if (image) + client_handler_->NotifyFavicon(image); + } + + private: + CefRefPtr client_handler_; + + IMPLEMENT_REFCOUNTING(ClientDownloadImageCallback); + DISALLOW_COPY_AND_ASSIGN(ClientDownloadImageCallback); +}; + +ClientHandler::ClientHandler(Delegate* delegate, + bool is_osr, + bool with_controls, + const std::string& startup_url) + : is_osr_(is_osr), + with_controls_(with_controls), + startup_url_(startup_url), + download_favicon_images_(false), + delegate_(delegate), + browser_count_(0), + console_log_file_(MainContext::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_); + + // Read command line settings. + CefRefPtr command_line = + CefCommandLine::GetGlobalCommandLine(); + mouse_cursor_change_disabled_ = + command_line->HasSwitch(switches::kMouseCursorChangeDisabled); + offline_ = command_line->HasSwitch(switches::kOffline); + +#if defined(OS_LINUX) + // Optionally use the client-provided dialog implementation. + bool use_client_dialogs = + command_line->HasSwitch(switches::kUseClientDialogs); + + if (!use_client_dialogs && + command_line->HasSwitch(switches::kMultiThreadedMessageLoop)) { + // Default dialogs are not supported in combination with + // multi-threaded-message-loop because Chromium doesn't support GDK threads + // internally. + LOG(WARNING) << "Client dialogs must be used in combination with " + "multi-threaded-message-loop."; + use_client_dialogs = true; + } + + if (use_client_dialogs && MainContext::Get()->UseViews()) { + // Client dialogs cannot be used in combination with Views because the + // implementation of ClientDialogHandlerGtk requires a top-level GtkWindow. + LOG(ERROR) << "Client dialogs cannot be used in combination with Views."; + use_client_dialogs = false; + } + + if (use_client_dialogs) { + dialog_handler_ = new ClientDialogHandlerGtk(); + print_handler_ = new ClientPrintHandlerGtk(); + } +#endif // defined(OS_LINUX) +} + +void ClientHandler::DetachDelegate() { + if (!CURRENTLY_ON_MAIN_THREAD()) { + // Execute this method on the main thread. + MAIN_POST_CLOSURE(base::BindOnce(&ClientHandler::DetachDelegate, this)); + return; + } + + DCHECK(delegate_); + delegate_ = nullptr; +} + +bool ClientHandler::OnProcessMessageReceived( + CefRefPtr browser, + CefRefPtr frame, + CefProcessId source_process, + CefRefPtr message) { + CEF_REQUIRE_UI_THREAD(); + + if (message_router_->OnProcessMessageReceived(browser, frame, source_process, + message)) { + return true; + } + + // Check for messages from the client renderer. + std::string message_name = message->GetName(); + if (message_name == kFocusedNodeChangedMessage) { + // A message is sent from ClientRenderDelegate to tell us whether the + // currently focused DOM node is editable. Use of |focus_on_editable_field_| + // is redundant with CefKeyEvent.focus_on_editable_field in OnPreKeyEvent + // but is useful for demonstration purposes. + focus_on_editable_field_ = message->GetArgumentList()->GetBool(0); + return true; + } + + return false; +} + +bool ClientHandler::OnChromeCommand(CefRefPtr browser, + int command_id, + cef_window_open_disposition_t disposition) { + CEF_REQUIRE_UI_THREAD(); + DCHECK(MainContext::Get()->UseChromeRuntime()); + + if (!with_controls_ && + (disposition != WOD_CURRENT_TAB || !IsAllowedCommandId(command_id))) { + // Block everything that doesn't target the current tab or isn't an + // allowed command ID. + LOG(INFO) << "Blocking command " << command_id << " with disposition " + << disposition; + return true; + } + + // Default handling. + return false; +} + +void ClientHandler::OnBeforeContextMenu(CefRefPtr browser, + CefRefPtr frame, + CefRefPtr params, + CefRefPtr model) { + CEF_REQUIRE_UI_THREAD(); + + const bool use_chrome_runtime = MainContext::Get()->UseChromeRuntime(); + if (use_chrome_runtime && !with_controls_) { + // Remove all disallowed menu items. + FilterMenuModel(model); + } + + if ((params->GetTypeFlags() & (CM_TYPEFLAG_PAGE | CM_TYPEFLAG_FRAME)) != 0) { + // Add a separator if the menu already has items. + if (model->GetCount() > 0) + model->AddSeparator(); + + if (!use_chrome_runtime) { + // TODO(chrome-runtime): Add support for this. + // Add DevTools items to all context menus. + model->AddItem(CLIENT_ID_SHOW_DEVTOOLS, "&Show DevTools"); + model->AddItem(CLIENT_ID_CLOSE_DEVTOOLS, "Close DevTools"); + model->AddSeparator(); + model->AddItem(CLIENT_ID_INSPECT_ELEMENT, "Inspect Element"); + } + + if (HasSSLInformation(browser)) { + model->AddSeparator(); + model->AddItem(CLIENT_ID_SHOW_SSL_INFO, "Show SSL information"); + } + + if (!use_chrome_runtime) { + // TODO(chrome-runtime): Add support for this. + model->AddSeparator(); + model->AddCheckItem(CLIENT_ID_CURSOR_CHANGE_DISABLED, + "Cursor change disabled"); + if (mouse_cursor_change_disabled_) + model->SetChecked(CLIENT_ID_CURSOR_CHANGE_DISABLED, true); + } + + model->AddSeparator(); + model->AddCheckItem(CLIENT_ID_OFFLINE, "Offline mode"); + if (offline_) + model->SetChecked(CLIENT_ID_OFFLINE, true); + + // Test context menu features. + BuildTestMenu(model); + } + + if (delegate_) + delegate_->OnBeforeContextMenu(model); +} + +bool ClientHandler::OnContextMenuCommand(CefRefPtr browser, + CefRefPtr frame, + CefRefPtr params, + int command_id, + EventFlags event_flags) { + CEF_REQUIRE_UI_THREAD(); + + switch (command_id) { + case CLIENT_ID_SHOW_DEVTOOLS: + ShowDevTools(browser, CefPoint()); + return true; + case CLIENT_ID_CLOSE_DEVTOOLS: + CloseDevTools(browser); + return true; + case CLIENT_ID_INSPECT_ELEMENT: + ShowDevTools(browser, CefPoint(params->GetXCoord(), params->GetYCoord())); + return true; + case CLIENT_ID_SHOW_SSL_INFO: + ShowSSLInformation(browser); + return true; + case CLIENT_ID_CURSOR_CHANGE_DISABLED: + mouse_cursor_change_disabled_ = !mouse_cursor_change_disabled_; + return true; + case CLIENT_ID_OFFLINE: + offline_ = !offline_; + SetOfflineState(browser, offline_); + return true; + default: // Allow default handling, if any. + return ExecuteTestMenu(command_id); + } +} + +void ClientHandler::OnAddressChange(CefRefPtr browser, + CefRefPtr frame, + const CefString& url) { + CEF_REQUIRE_UI_THREAD(); + + // Only update the address for the main (top-level) frame. + if (frame->IsMain()) + NotifyAddress(url); +} + +void ClientHandler::OnTitleChange(CefRefPtr browser, + const CefString& title) { + CEF_REQUIRE_UI_THREAD(); + + NotifyTitle(title); +} + +void ClientHandler::OnFaviconURLChange( + CefRefPtr browser, + const std::vector& icon_urls) { + CEF_REQUIRE_UI_THREAD(); + + if (!icon_urls.empty() && download_favicon_images_) { + browser->GetHost()->DownloadImage(icon_urls[0], true, 16, false, + new ClientDownloadImageCallback(this)); + } +} + +void ClientHandler::OnFullscreenModeChange(CefRefPtr browser, + bool fullscreen) { + CEF_REQUIRE_UI_THREAD(); + + NotifyFullscreen(fullscreen); +} + +bool ClientHandler::OnConsoleMessage(CefRefPtr browser, + cef_log_severity_t level, + const CefString& message, + const CefString& source, + int line) { + CEF_REQUIRE_UI_THREAD(); + + FILE* file = fopen(console_log_file_.c_str(), "a"); + if (file) { + std::stringstream ss; + ss << "Level: "; + switch (level) { + case LOGSEVERITY_DEBUG: + ss << "Debug" << NEWLINE; + break; + case LOGSEVERITY_INFO: + ss << "Info" << NEWLINE; + break; + case LOGSEVERITY_WARNING: + ss << "Warn" << NEWLINE; + break; + case LOGSEVERITY_ERROR: + ss << "Error" << NEWLINE; + break; + default: + NOTREACHED(); + break; + } + ss << "Message: " << message.ToString() << NEWLINE + << "Source: " << source.ToString() << NEWLINE << "Line: " << line + << NEWLINE << "-----------------------" << NEWLINE; + fputs(ss.str().c_str(), file); + fclose(file); + + if (first_console_message_) { + test_runner::Alert( + browser, "Console messages written to \"" + console_log_file_ + "\""); + first_console_message_ = false; + } + } + + return false; +} + +bool ClientHandler::OnAutoResize(CefRefPtr browser, + const CefSize& new_size) { + CEF_REQUIRE_UI_THREAD(); + + NotifyAutoResize(new_size); + return true; +} + +bool ClientHandler::OnCursorChange(CefRefPtr browser, + CefCursorHandle cursor, + cef_cursor_type_t type, + const CefCursorInfo& custom_cursor_info) { + CEF_REQUIRE_UI_THREAD(); + + // Return true to disable default handling of cursor changes. + return mouse_cursor_change_disabled_; +} + +bool ClientHandler::CanDownload(CefRefPtr browser, + const CefString& url, + const CefString& request_method) { + CEF_REQUIRE_UI_THREAD(); + + if (!with_controls_) { + // Block the download. + LOG(INFO) << "Blocking download"; + return false; + } + + // Allow the download. + return true; +} + +void ClientHandler::OnBeforeDownload( + CefRefPtr browser, + CefRefPtr download_item, + const CefString& suggested_name, + CefRefPtr callback) { + CEF_REQUIRE_UI_THREAD(); + + // Continue the download and show the "Save As" dialog. + callback->Continue(MainContext::Get()->GetDownloadPath(suggested_name), true); +} + +void ClientHandler::OnDownloadUpdated( + CefRefPtr browser, + CefRefPtr download_item, + CefRefPtr callback) { + CEF_REQUIRE_UI_THREAD(); + + if (download_item->IsComplete()) { + test_runner::Alert(browser, "File \"" + + download_item->GetFullPath().ToString() + + "\" downloaded successfully."); + } +} + +bool ClientHandler::OnDragEnter(CefRefPtr browser, + CefRefPtr dragData, + CefDragHandler::DragOperationsMask mask) { + CEF_REQUIRE_UI_THREAD(); + + // Forbid dragging of URLs and files. + if ((mask & DRAG_OPERATION_LINK) && !dragData->IsFragment()) { + test_runner::Alert(browser, "cefclient blocks dragging of URLs and files"); + return true; + } + + return false; +} + +void ClientHandler::OnDraggableRegionsChanged( + CefRefPtr browser, + CefRefPtr frame, + const std::vector& regions) { + CEF_REQUIRE_UI_THREAD(); + + NotifyDraggableRegions(regions); +} + +void ClientHandler::OnTakeFocus(CefRefPtr browser, bool next) { + CEF_REQUIRE_UI_THREAD(); + + NotifyTakeFocus(next); +} + +bool ClientHandler::OnSetFocus(CefRefPtr browser, + FocusSource source) { + CEF_REQUIRE_UI_THREAD(); + + if (initial_navigation_) { + CefRefPtr command_line = + CefCommandLine::GetGlobalCommandLine(); + if (command_line->HasSwitch(switches::kNoActivate)) { + // Don't give focus to the browser on creation. + return true; + } + } + + return false; +} + +bool ClientHandler::OnPreKeyEvent(CefRefPtr browser, + const CefKeyEvent& event, + CefEventHandle os_event, + bool* is_keyboard_shortcut) { + CEF_REQUIRE_UI_THREAD(); + + /* + if (!event.focus_on_editable_field && event.windows_key_code == 0x20) { + // Special handling for the space character when an input element does not + // have focus. Handling the event in OnPreKeyEvent() keeps the event from + // being processed in the renderer. If we instead handled the event in the + // OnKeyEvent() method the space key would cause the window to scroll in + // addition to showing the alert box. + if (event.type == KEYEVENT_RAWKEYDOWN) + test_runner::Alert(browser, "You pressed the space bar!"); + return true; + } + */ + + return false; +} + +bool ClientHandler::OnBeforePopup( + CefRefPtr browser, + CefRefPtr frame, + const CefString& target_url, + const CefString& target_frame_name, + CefLifeSpanHandler::WindowOpenDisposition target_disposition, + bool user_gesture, + const CefPopupFeatures& popupFeatures, + CefWindowInfo& windowInfo, + CefRefPtr& client, + CefBrowserSettings& settings, + CefRefPtr& extra_info, + bool* no_javascript_access) { + CEF_REQUIRE_UI_THREAD(); + + // Return true to cancel the popup window. + return !CreatePopupWindow(browser, false, popupFeatures, windowInfo, client, + settings); +} + +void ClientHandler::OnAfterCreated(CefRefPtr browser) { + CEF_REQUIRE_UI_THREAD(); + + browser_count_++; + + if (!message_router_) { + // Create the browser-side router for query handling. + CefMessageRouterConfig config; + message_router_ = CefMessageRouterBrowserSide::Create(config); + + // Register handlers with the router. + test_runner::CreateMessageHandlers(message_handler_set_); + MessageHandlerSet::const_iterator it = message_handler_set_.begin(); + for (; it != message_handler_set_.end(); ++it) + message_router_->AddHandler(*(it), false); + } + + // Set offline mode if requested via the command-line flag. + if (offline_) + SetOfflineState(browser, true); + + if (browser->GetHost()->GetExtension()) { + // Browsers hosting extension apps should auto-resize. + browser->GetHost()->SetAutoResizeEnabled(true, CefSize(20, 20), + CefSize(1000, 1000)); + + CefRefPtr extension = browser->GetHost()->GetExtension(); + if (extension_util::IsInternalExtension(extension->GetPath())) { + // Register the internal handler for extension resources. + extension_util::AddInternalExtensionToResourceManager(extension, + resource_manager_); + } + } + + NotifyBrowserCreated(browser); +} + +bool ClientHandler::DoClose(CefRefPtr browser) { + CEF_REQUIRE_UI_THREAD(); + + NotifyBrowserClosing(browser); + + // Allow the close. For windowed browsers this will result in the OS close + // event being sent. + return false; +} + +void ClientHandler::OnBeforeClose(CefRefPtr browser) { + CEF_REQUIRE_UI_THREAD(); + + if (--browser_count_ == 0) { + // Remove and delete message router handlers. + MessageHandlerSet::const_iterator it = message_handler_set_.begin(); + for (; it != message_handler_set_.end(); ++it) { + message_router_->RemoveHandler(*(it)); + delete *(it); + } + message_handler_set_.clear(); + message_router_ = nullptr; + } + + NotifyBrowserClosed(browser); +} + +void ClientHandler::OnLoadingStateChange(CefRefPtr browser, + bool isLoading, + bool canGoBack, + bool canGoForward) { + CEF_REQUIRE_UI_THREAD(); + + if (!isLoading && initial_navigation_) { + initial_navigation_ = false; + } + + NotifyLoadingState(isLoading, canGoBack, canGoForward); +} + +void ClientHandler::OnLoadError(CefRefPtr browser, + CefRefPtr frame, + ErrorCode errorCode, + const CefString& errorText, + const CefString& failedUrl) { + CEF_REQUIRE_UI_THREAD(); + + // Don't display an error for downloaded files. + if (errorCode == ERR_ABORTED) + return; + + // Don't display an error for external protocols that we allow the OS to + // handle. See OnProtocolExecution(). + if (errorCode == ERR_UNKNOWN_URL_SCHEME) { + std::string urlStr = frame->GetURL(); + if (urlStr.find("spotify:") == 0) + return; + } + + // Load the error page. + LoadErrorPage(frame, failedUrl, errorCode, errorText); +} + +bool ClientHandler::OnBeforeBrowse(CefRefPtr browser, + CefRefPtr frame, + CefRefPtr request, + bool user_gesture, + bool is_redirect) { + CEF_REQUIRE_UI_THREAD(); + + message_router_->OnBeforeBrowse(browser, frame); + return false; +} + +bool ClientHandler::OnOpenURLFromTab( + CefRefPtr browser, + CefRefPtr frame, + const CefString& target_url, + CefRequestHandler::WindowOpenDisposition target_disposition, + bool user_gesture) { + if (target_disposition == WOD_NEW_BACKGROUND_TAB || + target_disposition == WOD_NEW_FOREGROUND_TAB) { + // Handle middle-click and ctrl + left-click by opening the URL in a new + // browser window. + auto config = std::make_unique(); + config->with_controls = with_controls_; + config->with_osr = is_osr_; + config->url = target_url; + MainContext::Get()->GetRootWindowManager()->CreateRootWindow( + std::move(config)); + return true; + } + + // Open the URL in the current browser window. + return false; +} + +CefRefPtr ClientHandler::GetResourceRequestHandler( + CefRefPtr browser, + CefRefPtr frame, + CefRefPtr request, + bool is_navigation, + bool is_download, + const CefString& request_initiator, + bool& disable_default_handling) { + CEF_REQUIRE_IO_THREAD(); + return this; +} + +bool ClientHandler::GetAuthCredentials(CefRefPtr browser, + const CefString& origin_url, + bool isProxy, + const CefString& host, + int port, + const CefString& realm, + const CefString& scheme, + CefRefPtr callback) { + CEF_REQUIRE_IO_THREAD(); + + // Used for testing authentication with a proxy server. + // For example, CCProxy on Windows. + if (isProxy) { + callback->Continue("guest", "guest"); + return true; + } + + // Used for testing authentication with https://jigsaw.w3.org/HTTP/. + if (host == "jigsaw.w3.org") { + callback->Continue("guest", "guest"); + return true; + } + + return false; +} + +bool ClientHandler::OnQuotaRequest(CefRefPtr browser, + const CefString& origin_url, + int64 new_size, + CefRefPtr callback) { + CEF_REQUIRE_IO_THREAD(); + + static const int64 max_size = 1024 * 1024 * 20; // 20mb. + + // Grant the quota request if the size is reasonable. + if (new_size <= max_size) + callback->Continue(); + else + callback->Cancel(); + return true; +} + +bool ClientHandler::OnCertificateError(CefRefPtr browser, + ErrorCode cert_error, + const CefString& request_url, + CefRefPtr ssl_info, + CefRefPtr callback) { + CEF_REQUIRE_UI_THREAD(); + + if (cert_error == ERR_CERT_AUTHORITY_INVALID && + request_url.ToString().find("https://www.magpcss.org/") == 0U) { + // Allow the CEF Forum to load. It has a self-signed certificate. + callback->Continue(); + return true; + } + + CefRefPtr cert = ssl_info->GetX509Certificate(); + if (cert.get()) { + // Load the error page. + LoadErrorPage(browser->GetMainFrame(), request_url, cert_error, + GetCertificateInformation(cert, ssl_info->GetCertStatus())); + } + + return false; // Cancel the request. +} + +bool ClientHandler::OnSelectClientCertificate( + CefRefPtr browser, + bool isProxy, + const CefString& host, + int port, + const X509CertificateList& certificates, + CefRefPtr callback) { + CEF_REQUIRE_UI_THREAD(); + + CefRefPtr command_line = + CefCommandLine::GetGlobalCommandLine(); + if (!command_line->HasSwitch(switches::kSslClientCertificate)) { + return false; + } + + const std::string& cert_name = + command_line->GetSwitchValue(switches::kSslClientCertificate); + + if (cert_name.empty()) { + callback->Select(nullptr); + return true; + } + + std::vector>::const_iterator it = + certificates.begin(); + for (; it != certificates.end(); ++it) { + CefString subject((*it)->GetSubject()->GetDisplayName()); + if (subject == cert_name) { + callback->Select(*it); + return true; + } + } + + return true; +} + +void ClientHandler::OnRenderProcessTerminated(CefRefPtr browser, + TerminationStatus status) { + CEF_REQUIRE_UI_THREAD(); + + message_router_->OnRenderProcessTerminated(browser); + + // Don't reload if there's no start URL, or if the crash URL was specified. + if (startup_url_.empty() || startup_url_ == "chrome://crash") + return; + + CefRefPtr frame = browser->GetMainFrame(); + std::string url = frame->GetURL(); + + // Don't reload if the termination occurred before any URL had successfully + // loaded. + if (url.empty()) + return; + + std::string start_url = startup_url_; + + // Convert URLs to lowercase for easier comparison. + std::transform(url.begin(), url.end(), url.begin(), tolower); + std::transform(start_url.begin(), start_url.end(), start_url.begin(), + tolower); + + // Don't reload the URL that just resulted in termination. + if (url.find(start_url) == 0) + return; + + frame->LoadURL(startup_url_); +} + +void ClientHandler::OnDocumentAvailableInMainFrame( + CefRefPtr browser) { + CEF_REQUIRE_UI_THREAD(); + + // Restore offline mode after main frame navigation. Otherwise, offline state + // (e.g. `navigator.onLine`) might be wrong in the renderer process. + if (offline_) + SetOfflineState(browser, true); +} + +cef_return_value_t ClientHandler::OnBeforeResourceLoad( + CefRefPtr browser, + CefRefPtr frame, + CefRefPtr request, + CefRefPtr callback) { + CEF_REQUIRE_IO_THREAD(); + + return resource_manager_->OnBeforeResourceLoad(browser, frame, request, + callback); +} + +CefRefPtr ClientHandler::GetResourceHandler( + CefRefPtr browser, + CefRefPtr frame, + CefRefPtr request) { + CEF_REQUIRE_IO_THREAD(); + + return resource_manager_->GetResourceHandler(browser, frame, request); +} + +CefRefPtr ClientHandler::GetResourceResponseFilter( + CefRefPtr browser, + CefRefPtr frame, + CefRefPtr request, + CefRefPtr response) { + CEF_REQUIRE_IO_THREAD(); + + return test_runner::GetResourceResponseFilter(browser, frame, request, + response); +} + +void ClientHandler::OnProtocolExecution(CefRefPtr browser, + CefRefPtr frame, + CefRefPtr request, + bool& allow_os_execution) { + CEF_REQUIRE_IO_THREAD(); + + std::string urlStr = request->GetURL(); + + // Allow OS execution of Spotify URIs. + if (urlStr.find("spotify:") == 0) + allow_os_execution = true; +} + +int ClientHandler::GetBrowserCount() const { + CEF_REQUIRE_UI_THREAD(); + return browser_count_; +} + +void ClientHandler::ShowDevTools(CefRefPtr browser, + const CefPoint& inspect_element_at) { + if (!CefCurrentlyOn(TID_UI)) { + // Execute this method on the UI thread. + CefPostTask(TID_UI, base::BindOnce(&ClientHandler::ShowDevTools, this, + browser, inspect_element_at)); + return; + } + + CefWindowInfo windowInfo; + CefRefPtr client; + CefBrowserSettings settings; + + MainContext::Get()->PopulateBrowserSettings(&settings); + + CefRefPtr host = browser->GetHost(); + + // Test if the DevTools browser already exists. + bool has_devtools = host->HasDevTools(); + if (!has_devtools) { + // Create a new RootWindow for the DevTools browser that will be created + // by ShowDevTools(). + has_devtools = CreatePopupWindow(browser, true, CefPopupFeatures(), + windowInfo, client, settings); + } + + if (has_devtools) { + // Create the DevTools browser if it doesn't already exist. + // Otherwise, focus the existing DevTools browser and inspect the element + // at |inspect_element_at| if non-empty. + host->ShowDevTools(windowInfo, client, settings, inspect_element_at); + } +} + +void ClientHandler::CloseDevTools(CefRefPtr browser) { + browser->GetHost()->CloseDevTools(); +} + +bool ClientHandler::HasSSLInformation(CefRefPtr browser) { + CefRefPtr nav = + browser->GetHost()->GetVisibleNavigationEntry(); + + return (nav && nav->GetSSLStatus() && + nav->GetSSLStatus()->IsSecureConnection()); +} + +void ClientHandler::ShowSSLInformation(CefRefPtr browser) { + std::stringstream ss; + CefRefPtr nav = + browser->GetHost()->GetVisibleNavigationEntry(); + if (!nav) + return; + + CefRefPtr ssl = nav->GetSSLStatus(); + if (!ssl) + return; + + ss << "SSL Information" + "" + "

SSL Connection

" + << ""; + + CefURLParts urlparts; + if (CefParseURL(nav->GetURL(), urlparts)) { + CefString port(&urlparts.port); + ss << ""; + } + + ss << ""; + ss << ""; + + ss << "
FieldValue
Server" << CefString(&urlparts.host).ToString(); + if (!port.empty()) + ss << ":" << port.ToString(); + ss << "
SSL Version" + << GetSSLVersionString(ssl->GetSSLVersion()) << "
Content Status" + << GetContentStatusString(ssl->GetContentStatus()) << "
"; + + CefRefPtr cert = ssl->GetX509Certificate(); + if (cert.get()) + ss << GetCertificateInformation(cert, ssl->GetCertStatus()); + + ss << ""; + + 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( + std::move(config)); +} + +void ClientHandler::SetStringResource(const std::string& page, + const std::string& data) { + if (!CefCurrentlyOn(TID_IO)) { + CefPostTask(TID_IO, base::BindOnce(&ClientHandler::SetStringResource, this, + page, data)); + return; + } + + string_resource_map_[page] = data; +} + +bool ClientHandler::CreatePopupWindow(CefRefPtr browser, + bool is_devtools, + const CefPopupFeatures& popupFeatures, + CefWindowInfo& windowInfo, + CefRefPtr& client, + CefBrowserSettings& settings) { + CEF_REQUIRE_UI_THREAD(); + + // 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( + with_controls_ && !is_devtools, is_osr_, popupFeatures, windowInfo, + client, settings); + + return true; +} + +void ClientHandler::NotifyBrowserCreated(CefRefPtr browser) { + if (!CURRENTLY_ON_MAIN_THREAD()) { + // Execute this method on the main thread. + MAIN_POST_CLOSURE( + base::BindOnce(&ClientHandler::NotifyBrowserCreated, this, browser)); + return; + } + + if (delegate_) + delegate_->OnBrowserCreated(browser); +} + +void ClientHandler::NotifyBrowserClosing(CefRefPtr browser) { + if (!CURRENTLY_ON_MAIN_THREAD()) { + // Execute this method on the main thread. + MAIN_POST_CLOSURE( + base::BindOnce(&ClientHandler::NotifyBrowserClosing, this, browser)); + return; + } + + if (delegate_) + delegate_->OnBrowserClosing(browser); +} + +void ClientHandler::NotifyBrowserClosed(CefRefPtr browser) { + if (!CURRENTLY_ON_MAIN_THREAD()) { + // Execute this method on the main thread. + MAIN_POST_CLOSURE( + base::BindOnce(&ClientHandler::NotifyBrowserClosed, this, browser)); + return; + } + + if (delegate_) + delegate_->OnBrowserClosed(browser); +} + +void ClientHandler::NotifyAddress(const CefString& url) { + if (!CURRENTLY_ON_MAIN_THREAD()) { + // Execute this method on the main thread. + MAIN_POST_CLOSURE(base::BindOnce(&ClientHandler::NotifyAddress, this, url)); + return; + } + + if (delegate_) + delegate_->OnSetAddress(url); +} + +void ClientHandler::NotifyTitle(const CefString& title) { + if (!CURRENTLY_ON_MAIN_THREAD()) { + // Execute this method on the main thread. + MAIN_POST_CLOSURE(base::BindOnce(&ClientHandler::NotifyTitle, this, title)); + return; + } + + if (delegate_) + delegate_->OnSetTitle(title); +} + +void ClientHandler::NotifyFavicon(CefRefPtr image) { + if (!CURRENTLY_ON_MAIN_THREAD()) { + // Execute this method on the main thread. + MAIN_POST_CLOSURE( + base::BindOnce(&ClientHandler::NotifyFavicon, this, image)); + return; + } + + if (delegate_) + delegate_->OnSetFavicon(image); +} + +void ClientHandler::NotifyFullscreen(bool fullscreen) { + if (!CURRENTLY_ON_MAIN_THREAD()) { + // Execute this method on the main thread. + MAIN_POST_CLOSURE( + base::BindOnce(&ClientHandler::NotifyFullscreen, this, fullscreen)); + return; + } + + if (delegate_) + delegate_->OnSetFullscreen(fullscreen); +} + +void ClientHandler::NotifyAutoResize(const CefSize& new_size) { + if (!CURRENTLY_ON_MAIN_THREAD()) { + // Execute this method on the main thread. + MAIN_POST_CLOSURE( + base::BindOnce(&ClientHandler::NotifyAutoResize, this, new_size)); + return; + } + + if (delegate_) + delegate_->OnAutoResize(new_size); +} + +void ClientHandler::NotifyLoadingState(bool isLoading, + bool canGoBack, + bool canGoForward) { + if (!CURRENTLY_ON_MAIN_THREAD()) { + // Execute this method on the main thread. + MAIN_POST_CLOSURE(base::BindOnce(&ClientHandler::NotifyLoadingState, this, + isLoading, canGoBack, canGoForward)); + return; + } + + if (delegate_) + delegate_->OnSetLoadingState(isLoading, canGoBack, canGoForward); +} + +void ClientHandler::NotifyDraggableRegions( + const std::vector& regions) { + if (!CURRENTLY_ON_MAIN_THREAD()) { + // Execute this method on the main thread. + MAIN_POST_CLOSURE( + base::BindOnce(&ClientHandler::NotifyDraggableRegions, this, regions)); + return; + } + + if (delegate_) + delegate_->OnSetDraggableRegions(regions); +} + +void ClientHandler::NotifyTakeFocus(bool next) { + if (!CURRENTLY_ON_MAIN_THREAD()) { + // Execute this method on the main thread. + MAIN_POST_CLOSURE( + base::BindOnce(&ClientHandler::NotifyTakeFocus, this, next)); + return; + } + + if (delegate_) + delegate_->OnTakeFocus(next); +} + +void ClientHandler::BuildTestMenu(CefRefPtr model) { + if (model->GetCount() > 0) + model->AddSeparator(); + + // Build the sub menu. + CefRefPtr submenu = + model->AddSubMenu(CLIENT_ID_TESTMENU_SUBMENU, "Context Menu Test"); + submenu->AddCheckItem(CLIENT_ID_TESTMENU_CHECKITEM, "Check Item"); + submenu->AddRadioItem(CLIENT_ID_TESTMENU_RADIOITEM1, "Radio Item 1", 0); + submenu->AddRadioItem(CLIENT_ID_TESTMENU_RADIOITEM2, "Radio Item 2", 0); + submenu->AddRadioItem(CLIENT_ID_TESTMENU_RADIOITEM3, "Radio Item 3", 0); + + // Check the check item. + if (test_menu_state_.check_item) + submenu->SetChecked(CLIENT_ID_TESTMENU_CHECKITEM, true); + + // Check the selected radio item. + submenu->SetChecked( + CLIENT_ID_TESTMENU_RADIOITEM1 + test_menu_state_.radio_item, true); +} + +bool ClientHandler::ExecuteTestMenu(int command_id) { + if (command_id == CLIENT_ID_TESTMENU_CHECKITEM) { + // Toggle the check item. + test_menu_state_.check_item ^= 1; + return true; + } else if (command_id >= CLIENT_ID_TESTMENU_RADIOITEM1 && + command_id <= CLIENT_ID_TESTMENU_RADIOITEM3) { + // Store the selected radio item. + test_menu_state_.radio_item = (command_id - CLIENT_ID_TESTMENU_RADIOITEM1); + return true; + } + + // Allow default handling to proceed. + return false; +} + +void ClientHandler::SetOfflineState(CefRefPtr browser, + bool offline) { + // See DevTools protocol docs for message format specification. + CefRefPtr params = CefDictionaryValue::Create(); + params->SetBool("offline", offline); + params->SetDouble("latency", 0); + params->SetDouble("downloadThroughput", 0); + params->SetDouble("uploadThroughput", 0); + browser->GetHost()->ExecuteDevToolsMethod( + /*message_id=*/0, "Network.emulateNetworkConditions", params); +} + +void ClientHandler::FilterMenuModel(CefRefPtr model) { + // Evaluate from the bottom to the top because we'll be removing menu items. + for (int i = model->GetCount() - 1; i >= 0; --i) { + const auto type = model->GetTypeAt(i); + if (type == MENUITEMTYPE_SUBMENU) { + // Filter sub-menu and remove if empty. + auto sub_model = model->GetSubMenuAt(i); + FilterMenuModel(sub_model); + if (sub_model->GetCount() == 0) { + model->RemoveAt(i); + } + } else if (type == MENUITEMTYPE_SEPARATOR) { + // A separator shouldn't be the first or last element in the menu, and + // there shouldn't be multiple in a row. + if (i == 0 || i == model->GetCount() - 1 || + model->GetTypeAt(i + 1) == MENUITEMTYPE_SEPARATOR) { + model->RemoveAt(i); + } + } else if (!IsAllowedCommandId(model->GetCommandIdAt(i))) { + model->RemoveAt(i); + } + } +} + +bool ClientHandler::IsAllowedCommandId(int command_id) { + // Only the commands in this array will be allowed. + static const int kAllowedCommandIds[] = { + // Page navigation. + IDC_BACK, + IDC_FORWARD, + IDC_RELOAD, + IDC_RELOAD_BYPASSING_CACHE, + IDC_RELOAD_CLEARING_CACHE, + IDC_STOP, + + // Printing. + IDC_PRINT, + + // Edit controls. + IDC_CONTENT_CONTEXT_CUT, + IDC_CONTENT_CONTEXT_COPY, + IDC_CONTENT_CONTEXT_PASTE, + IDC_CONTENT_CONTEXT_PASTE_AND_MATCH_STYLE, + IDC_CONTENT_CONTEXT_DELETE, + IDC_CONTENT_CONTEXT_SELECTALL, + IDC_CONTENT_CONTEXT_UNDO, + IDC_CONTENT_CONTEXT_REDO, + }; + for (size_t i = 0; i < base::size(kAllowedCommandIds); ++i) { + if (command_id == kAllowedCommandIds[i]) + return true; + } + return false; +} diff --git a/src/CEF/ClientHandler.h b/src/CEF/ClientHandler.h new file mode 100644 index 0000000..7972be3 --- /dev/null +++ b/src/CEF/ClientHandler.h @@ -0,0 +1,439 @@ +// Copyright (c) 2011 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/cef_client.h" +#include "include/wrapper/cef_helpers.h" +#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" + +#if defined(OS_LINUX) +#include "tests/cefclient/browser/dialog_handler_gtk.h" +#include "tests/cefclient/browser/print_handler_gtk.h" +#endif + + +class ClientDownloadImageCallback; + +// Client handler abstract base class. Provides common functionality shared by +// all concrete client handler implementations. +class ClientHandler : public CefClient, + public CefCommandHandler, + public CefContextMenuHandler, + public CefDisplayHandler, + public CefDownloadHandler, + public CefDragHandler, + public CefFocusHandler, + public CefKeyboardHandler, + public CefLifeSpanHandler, + public CefLoadHandler, + public CefRequestHandler, + public CefResourceRequestHandler { + public: + // Implement this interface to receive notification of ClientHandler + // events. The methods of this class will be called on the main thread unless + // otherwise indicated. + class Delegate { + public: + // Called when the browser is created. + virtual void OnBrowserCreated(CefRefPtr browser) = 0; + + // Called when the browser is closing. + virtual void OnBrowserClosing(CefRefPtr browser) = 0; + + // Called when the browser has been closed. + virtual void OnBrowserClosed(CefRefPtr browser) = 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 the Favicon image. + virtual void OnSetFavicon(CefRefPtr image) {} + + // 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; + + // Set focus to the next/previous control. + virtual void OnTakeFocus(bool next) {} + + // Called on the UI thread before a context menu is displayed. + virtual void OnBeforeContextMenu(CefRefPtr model) {} + + protected: + virtual ~Delegate() {} + }; + + typedef std::set MessageHandlerSet; + + // Constructor may be called on any thread. + // |delegate| must outlive this object or DetachDelegate() must be called. + ClientHandler(Delegate* delegate, + bool is_osr, + bool with_controls, + const std::string& startup_url); + + // This object may outlive the Delegate object so it's necessary for the + // Delegate to detach itself before destruction. + void DetachDelegate(); + + // CefClient methods + CefRefPtr GetCommandHandler() override { return this; } + CefRefPtr GetContextMenuHandler() override { + return this; + } + CefRefPtr GetDisplayHandler() override { return this; } + CefRefPtr GetDownloadHandler() override { return this; } + CefRefPtr GetDragHandler() override { return this; } + CefRefPtr GetFocusHandler() override { return this; } + CefRefPtr GetKeyboardHandler() override { return this; } + CefRefPtr GetLifeSpanHandler() override { return this; } + CefRefPtr GetLoadHandler() override { return this; } + CefRefPtr GetRequestHandler() override { return this; } + bool OnProcessMessageReceived(CefRefPtr browser, + CefRefPtr frame, + CefProcessId source_process, + CefRefPtr message) override; + +#if defined(OS_LINUX) + CefRefPtr GetDialogHandler() override { + return dialog_handler_; + } + CefRefPtr GetJSDialogHandler() override { + return dialog_handler_; + } + CefRefPtr GetPrintHandler() override { + return print_handler_; + } +#endif + + // CefCommandHandler methods + bool OnChromeCommand(CefRefPtr browser, + int command_id, + cef_window_open_disposition_t disposition) override; + + // CefContextMenuHandler methods + void OnBeforeContextMenu(CefRefPtr browser, + CefRefPtr frame, + CefRefPtr params, + CefRefPtr model) override; + bool OnContextMenuCommand(CefRefPtr browser, + CefRefPtr frame, + CefRefPtr params, + int command_id, + EventFlags event_flags) override; + + // CefDisplayHandler methods + void OnAddressChange(CefRefPtr browser, + CefRefPtr frame, + const CefString& url) override; + void OnTitleChange(CefRefPtr browser, + const CefString& title) override; + void OnFaviconURLChange(CefRefPtr browser, + const std::vector& icon_urls) override; + void OnFullscreenModeChange(CefRefPtr browser, + bool fullscreen) override; + bool OnConsoleMessage(CefRefPtr browser, + cef_log_severity_t level, + const CefString& message, + const CefString& source, + int line) override; + bool OnAutoResize(CefRefPtr browser, + const CefSize& new_size) override; + bool OnCursorChange(CefRefPtr browser, + CefCursorHandle cursor, + cef_cursor_type_t type, + const CefCursorInfo& custom_cursor_info) override; + + // CefDownloadHandler methods + bool CanDownload(CefRefPtr browser, + const CefString& url, + const CefString& request_method) override; + void OnBeforeDownload(CefRefPtr browser, + CefRefPtr download_item, + const CefString& suggested_name, + CefRefPtr callback) override; + void OnDownloadUpdated(CefRefPtr browser, + CefRefPtr download_item, + CefRefPtr callback) override; + + // CefDragHandler methods + bool OnDragEnter(CefRefPtr browser, + CefRefPtr dragData, + CefDragHandler::DragOperationsMask mask) override; + void OnDraggableRegionsChanged( + CefRefPtr browser, + CefRefPtr frame, + const std::vector& regions) override; + + // CefFocusHandler methods + void OnTakeFocus(CefRefPtr browser, bool next) override; + bool OnSetFocus(CefRefPtr browser, FocusSource source) override; + + // CefKeyboardHandler methods + bool OnPreKeyEvent(CefRefPtr browser, + const CefKeyEvent& event, + CefEventHandle os_event, + bool* is_keyboard_shortcut) override; + + // CefLifeSpanHandler methods + bool OnBeforePopup( + CefRefPtr browser, + CefRefPtr frame, + const CefString& target_url, + const CefString& target_frame_name, + CefLifeSpanHandler::WindowOpenDisposition target_disposition, + bool user_gesture, + const CefPopupFeatures& popupFeatures, + CefWindowInfo& windowInfo, + CefRefPtr& client, + CefBrowserSettings& settings, + CefRefPtr& extra_info, + bool* no_javascript_access) override; + void OnAfterCreated(CefRefPtr browser) override; + bool DoClose(CefRefPtr browser) override; + void OnBeforeClose(CefRefPtr browser) override; + + // CefLoadHandler methods + void OnLoadingStateChange(CefRefPtr browser, + bool isLoading, + bool canGoBack, + bool canGoForward) override; + void OnLoadError(CefRefPtr browser, + CefRefPtr frame, + ErrorCode errorCode, + const CefString& errorText, + const CefString& failedUrl) override; + + // CefRequestHandler methods + bool OnBeforeBrowse(CefRefPtr browser, + CefRefPtr frame, + CefRefPtr request, + bool user_gesture, + bool is_redirect) override; + bool OnOpenURLFromTab( + CefRefPtr browser, + CefRefPtr frame, + const CefString& target_url, + CefRequestHandler::WindowOpenDisposition target_disposition, + bool user_gesture) override; + CefRefPtr GetResourceRequestHandler( + CefRefPtr browser, + CefRefPtr frame, + CefRefPtr request, + bool is_navigation, + bool is_download, + const CefString& request_initiator, + bool& disable_default_handling) override; + bool GetAuthCredentials(CefRefPtr browser, + const CefString& origin_url, + bool isProxy, + const CefString& host, + int port, + const CefString& realm, + const CefString& scheme, + CefRefPtr callback) override; + bool OnQuotaRequest(CefRefPtr browser, + const CefString& origin_url, + int64 new_size, + CefRefPtr callback) override; + bool OnCertificateError(CefRefPtr browser, + ErrorCode cert_error, + const CefString& request_url, + CefRefPtr ssl_info, + CefRefPtr callback) override; + bool OnSelectClientCertificate( + CefRefPtr browser, + bool isProxy, + const CefString& host, + int port, + const X509CertificateList& certificates, + CefRefPtr callback) override; + void OnRenderProcessTerminated(CefRefPtr browser, + TerminationStatus status) override; + void OnDocumentAvailableInMainFrame(CefRefPtr browser) override; + + // CefResourceRequestHandler methods + cef_return_value_t OnBeforeResourceLoad( + CefRefPtr browser, + CefRefPtr frame, + CefRefPtr request, + CefRefPtr callback) override; + CefRefPtr GetResourceHandler( + CefRefPtr browser, + CefRefPtr frame, + CefRefPtr request) override; + CefRefPtr GetResourceResponseFilter( + CefRefPtr browser, + CefRefPtr frame, + CefRefPtr request, + CefRefPtr response) override; + void OnProtocolExecution(CefRefPtr browser, + CefRefPtr frame, + CefRefPtr request, + bool& allow_os_execution) override; + + // Returns the number of browsers currently using this handler. Can only be + // called on the CEF UI thread. + int GetBrowserCount() const; + + // Show a new DevTools popup window. + void ShowDevTools(CefRefPtr browser, + const CefPoint& inspect_element_at); + + // Close the existing DevTools popup window, if any. + void CloseDevTools(CefRefPtr browser); + + // Test if the current site has SSL information available. + bool HasSSLInformation(CefRefPtr browser); + + // Show SSL information for the current site. + void ShowSSLInformation(CefRefPtr browser); + + // Set a string resource for loading via StringResourceProvider. + void SetStringResource(const std::string& page, const std::string& data); + + // Returns the Delegate. + Delegate* delegate() const { return delegate_; } + + // Returns the startup URL. + std::string startup_url() const { return startup_url_; } + + // Set/get whether the client should download favicon images. Only safe to + // call immediately after client creation or on the browser process UI thread. + bool download_favicon_images() const { return download_favicon_images_; } + void set_download_favicon_images(bool allow) { + download_favicon_images_ = allow; + } + + private: + friend class ClientDownloadImageCallback; + + // Create a new popup window using the specified information. |is_devtools| + // will be true if the window will be used for DevTools. Return true to + // proceed with popup browser creation or false to cancel the popup browser. + // May be called on any thead. + bool CreatePopupWindow(CefRefPtr browser, + bool is_devtools, + const CefPopupFeatures& popupFeatures, + CefWindowInfo& windowInfo, + CefRefPtr& client, + CefBrowserSettings& settings); + + // Execute Delegate notifications on the main thread. + void NotifyBrowserCreated(CefRefPtr browser); + void NotifyBrowserClosing(CefRefPtr browser); + void NotifyBrowserClosed(CefRefPtr browser); + void NotifyAddress(const CefString& url); + void NotifyTitle(const CefString& title); + void NotifyFavicon(CefRefPtr image); + void NotifyFullscreen(bool fullscreen); + void NotifyAutoResize(const CefSize& new_size); + void NotifyLoadingState(bool isLoading, bool canGoBack, bool canGoForward); + void NotifyDraggableRegions(const std::vector& regions); + void NotifyTakeFocus(bool next); + + // Test context menu creation. + void BuildTestMenu(CefRefPtr model); + bool ExecuteTestMenu(int command_id); + + void SetOfflineState(CefRefPtr browser, bool offline); + + // Filter menu and keyboard shortcut commands. + void FilterMenuModel(CefRefPtr model); + bool IsAllowedCommandId(int command_id); + + // THREAD SAFE MEMBERS + // The following members may be accessed from any thread. + + // True if this handler uses off-screen rendering. + const bool is_osr_; + + // True if this handler shows controls. + const bool with_controls_; + + // The startup URL. + const std::string startup_url_; + + // True if mouse cursor change is disabled. + bool mouse_cursor_change_disabled_; + + // True if the browser is currently offline. + bool offline_; + + // True if Favicon images should be downloaded. + bool download_favicon_images_; + +#if defined(OS_LINUX) + // Custom dialog handler for GTK. + CefRefPtr dialog_handler_; + CefRefPtr print_handler_; +#endif + + // Handles the browser side of query routing. The renderer side is handled + // in client_renderer.cc. + CefRefPtr message_router_; + + // Manages the registration and delivery of resources. + CefRefPtr resource_manager_; + + // Used to manage string resources in combination with StringResourceProvider. + // Only accessed on the IO thread. + //test_runner::StringResourceMap string_resource_map_; + + // MAIN THREAD MEMBERS + // The following members will only be accessed on the main thread. This will + // be the same as the CEF UI thread except when using multi-threaded message + // loop mode on Windows. + + Delegate* delegate_; + + // UI THREAD MEMBERS + // The following members will only be accessed on the CEF UI thread. + + // Track state information for the text context menu. + struct TestMenuState { + TestMenuState() : check_item(true), radio_item(0) {} + bool check_item; + int radio_item; + } test_menu_state_; + + // The current number of browsers using this handler. + int browser_count_; + + // Console logging state. + const std::string console_log_file_; + bool first_console_message_; + + // True if an editable field currently has focus. + bool focus_on_editable_field_; + + // True for the initial navigation after browser creation. + bool initial_navigation_; + + // Set of Handlers registered with the message router. + MessageHandlerSet message_handler_set_; + + DISALLOW_COPY_AND_ASSIGN(ClientHandler); +}; + diff --git a/src/CEF/ClientHandlerOSR.cpp b/src/CEF/ClientHandlerOSR.cpp new file mode 100644 index 0000000..14ea328 --- /dev/null +++ b/src/CEF/ClientHandlerOSR.cpp @@ -0,0 +1,186 @@ +// 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/client_handler_osr.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, + const std::string& startup_url) + : ClientHandler(delegate, /*is_osr=*/true, with_controls, startup_url), + osr_delegate_(osr_delegate) { + DCHECK(osr_delegate_); +} + +void ClientHandlerOsr::DetachOsrDelegate() { + if (!CefCurrentlyOn(TID_UI)) { + // Execute this method on the UI thread. + CefPostTask(TID_UI, + base::BindOnce(&ClientHandlerOsr::DetachOsrDelegate, this)); + return; + } + + DCHECK(osr_delegate_); + osr_delegate_ = nullptr; +} + +void ClientHandlerOsr::OnAfterCreated(CefRefPtr browser) { + CEF_REQUIRE_UI_THREAD(); + if (osr_delegate_) + osr_delegate_->OnAfterCreated(browser); + ClientHandler::OnAfterCreated(browser); +} + +void ClientHandlerOsr::OnBeforeClose(CefRefPtr browser) { + CEF_REQUIRE_UI_THREAD(); + if (osr_delegate_) + osr_delegate_->OnBeforeClose(browser); + ClientHandler::OnBeforeClose(browser); +} + +bool ClientHandlerOsr::GetRootScreenRect(CefRefPtr browser, + CefRect& rect) { + CEF_REQUIRE_UI_THREAD(); + if (!osr_delegate_) + return false; + return osr_delegate_->GetRootScreenRect(browser, rect); +} + +void ClientHandlerOsr::GetViewRect(CefRefPtr browser, + CefRect& rect) { + CEF_REQUIRE_UI_THREAD(); + if (!osr_delegate_) { + // Never return an empty rectangle. + rect.width = rect.height = 1; + return; + } + osr_delegate_->GetViewRect(browser, rect); +} + +bool ClientHandlerOsr::GetScreenPoint(CefRefPtr browser, + int viewX, + int viewY, + int& screenX, + int& screenY) { + CEF_REQUIRE_UI_THREAD(); + if (!osr_delegate_) + return false; + return osr_delegate_->GetScreenPoint(browser, viewX, viewY, screenX, screenY); +} + +bool ClientHandlerOsr::GetScreenInfo(CefRefPtr browser, + CefScreenInfo& screen_info) { + CEF_REQUIRE_UI_THREAD(); + if (!osr_delegate_) + return false; + return osr_delegate_->GetScreenInfo(browser, screen_info); +} + +void ClientHandlerOsr::OnPopupShow(CefRefPtr browser, bool show) { + CEF_REQUIRE_UI_THREAD(); + if (!osr_delegate_) + return; + return osr_delegate_->OnPopupShow(browser, show); +} + +void ClientHandlerOsr::OnPopupSize(CefRefPtr browser, + const CefRect& rect) { + CEF_REQUIRE_UI_THREAD(); + if (!osr_delegate_) + return; + return osr_delegate_->OnPopupSize(browser, rect); +} + +void ClientHandlerOsr::OnPaint(CefRefPtr browser, + PaintElementType type, + const RectList& dirtyRects, + const void* buffer, + int width, + int height) { + CEF_REQUIRE_UI_THREAD(); + if (!osr_delegate_) + return; + osr_delegate_->OnPaint(browser, type, dirtyRects, buffer, width, height); +} + +void ClientHandlerOsr::OnAcceleratedPaint( + CefRefPtr browser, + CefRenderHandler::PaintElementType type, + const CefRenderHandler::RectList& dirtyRects, + void* share_handle) { + CEF_REQUIRE_UI_THREAD(); + if (!osr_delegate_) + return; + osr_delegate_->OnAcceleratedPaint(browser, type, dirtyRects, share_handle); +} + +bool ClientHandlerOsr::StartDragging( + CefRefPtr browser, + CefRefPtr drag_data, + CefRenderHandler::DragOperationsMask allowed_ops, + int x, + int y) { + CEF_REQUIRE_UI_THREAD(); + if (!osr_delegate_) + return false; + return osr_delegate_->StartDragging(browser, drag_data, allowed_ops, x, y); +} + +void ClientHandlerOsr::UpdateDragCursor( + CefRefPtr browser, + CefRenderHandler::DragOperation operation) { + CEF_REQUIRE_UI_THREAD(); + if (!osr_delegate_) + return; + osr_delegate_->UpdateDragCursor(browser, operation); +} + +void ClientHandlerOsr::OnImeCompositionRangeChanged( + CefRefPtr browser, + const CefRange& selection_range, + const CefRenderHandler::RectList& character_bounds) { + CEF_REQUIRE_UI_THREAD(); + if (!osr_delegate_) + return; + osr_delegate_->OnImeCompositionRangeChanged(browser, selection_range, + character_bounds); +} + +void ClientHandlerOsr::OnAccessibilityTreeChange(CefRefPtr value) { + CEF_REQUIRE_UI_THREAD(); + if (!osr_delegate_) + return; + osr_delegate_->UpdateAccessibilityTree(value); +} + +bool ClientHandlerOsr::OnCursorChange(CefRefPtr browser, + CefCursorHandle cursor, + cef_cursor_type_t type, + const CefCursorInfo& custom_cursor_info) { + CEF_REQUIRE_UI_THREAD(); + if (ClientHandler::OnCursorChange(browser, cursor, type, + custom_cursor_info)) { + return true; + } + if (osr_delegate_) { + osr_delegate_->OnCursorChange(browser, cursor, type, custom_cursor_info); + } + return true; +} + +void ClientHandlerOsr::OnAccessibilityLocationChange( + CefRefPtr value) { + CEF_REQUIRE_UI_THREAD(); + if (!osr_delegate_) + return; + osr_delegate_->UpdateAccessibilityLocation(value); +} + +} // namespace client diff --git a/src/CEF/ClientHandlerOSR.h b/src/CEF/ClientHandlerOSR.h new file mode 100644 index 0000000..1fe247e --- /dev/null +++ b/src/CEF/ClientHandlerOSR.h @@ -0,0 +1,152 @@ +// 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_CLIENT_HANDLER_OSR_H_ +#define CEF_TESTS_CEFCLIENT_BROWSER_CLIENT_HANDLER_OSR_H_ +#pragma once + +#include "tests/cefclient/browser/client_handler.h" + +namespace client { + +// Client handler implementation for windowless browsers. There will only ever +// be one browser per handler instance. +class ClientHandlerOsr : public ClientHandler, + public CefAccessibilityHandler, + public CefRenderHandler { + public: + // Implement this interface to receive notification of ClientHandlerOsr + // events. The methods of this class will be called on the CEF UI thread. + class OsrDelegate { + public: + // These methods match the CefLifeSpanHandler interface. + virtual void OnAfterCreated(CefRefPtr browser) = 0; + virtual void OnBeforeClose(CefRefPtr browser) = 0; + + // These methods match the CefRenderHandler interface. + virtual bool GetRootScreenRect(CefRefPtr browser, + CefRect& rect) = 0; + virtual void GetViewRect(CefRefPtr browser, CefRect& rect) = 0; + virtual bool GetScreenPoint(CefRefPtr browser, + int viewX, + int viewY, + int& screenX, + int& screenY) = 0; + virtual bool GetScreenInfo(CefRefPtr browser, + CefScreenInfo& screen_info) = 0; + virtual void OnPopupShow(CefRefPtr browser, bool show) = 0; + virtual void OnPopupSize(CefRefPtr browser, + const CefRect& rect) = 0; + virtual void OnPaint(CefRefPtr browser, + CefRenderHandler::PaintElementType type, + const CefRenderHandler::RectList& dirtyRects, + const void* buffer, + int width, + int height) = 0; + virtual void OnAcceleratedPaint( + CefRefPtr browser, + CefRenderHandler::PaintElementType type, + const CefRenderHandler::RectList& dirtyRects, + void* share_handle) {} + virtual bool StartDragging(CefRefPtr browser, + CefRefPtr drag_data, + CefRenderHandler::DragOperationsMask allowed_ops, + int x, + int y) = 0; + virtual void UpdateDragCursor( + CefRefPtr browser, + CefRenderHandler::DragOperation operation) = 0; + virtual void OnImeCompositionRangeChanged( + CefRefPtr browser, + const CefRange& selection_range, + const CefRenderHandler::RectList& character_bounds) = 0; + + // These methods match the CefDisplayHandler interface. + virtual void OnCursorChange(CefRefPtr browser, + CefCursorHandle cursor, + cef_cursor_type_t type, + const CefCursorInfo& custom_cursor_info) = 0; + + virtual void UpdateAccessibilityTree(CefRefPtr value) = 0; + virtual void UpdateAccessibilityLocation(CefRefPtr value) = 0; + + protected: + virtual ~OsrDelegate() {} + }; + + ClientHandlerOsr(Delegate* delegate, + OsrDelegate* osr_delegate, + bool with_controls, + const std::string& startup_url); + + // This object may outlive the OsrDelegate object so it's necessary for the + // OsrDelegate to detach itself before destruction. + void DetachOsrDelegate(); + + // CefClient methods. + CefRefPtr GetRenderHandler() override { return this; } + CefRefPtr GetAccessibilityHandler() override { + return this; + } + + // CefLifeSpanHandler methods. + void OnAfterCreated(CefRefPtr browser) override; + void OnBeforeClose(CefRefPtr browser) override; + + // CefRenderHandler methods. + 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; + 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; + + // CefDisplayHandler methods. + bool OnCursorChange(CefRefPtr browser, + CefCursorHandle cursor, + cef_cursor_type_t type, + const CefCursorInfo& custom_cursor_info) override; + + // CefAccessibilityHandler methods. + void OnAccessibilityTreeChange(CefRefPtr value) override; + void OnAccessibilityLocationChange(CefRefPtr value) override; + + private: + // Only accessed on the UI thread. + OsrDelegate* osr_delegate_; + + // Include the default reference counting implementation. + 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 new file mode 100644 index 0000000..e78166d --- /dev/null +++ b/src/CEF/ClientHandlerStd.cpp @@ -0,0 +1,14 @@ +// 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/client_handler_std.h" + +namespace client { + +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 new file mode 100644 index 0000000..02aa747 --- /dev/null +++ b/src/CEF/ClientHandlerStd.h @@ -0,0 +1,29 @@ +// 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_CLIENT_HANDLER_STD_H_ +#define CEF_TESTS_CEFCLIENT_BROWSER_CLIENT_HANDLER_STD_H_ +#pragma once + +#include "tests/cefclient/browser/client_handler.h" + +namespace client { + +// Client handler implementation for windowed browsers. There will only ever be +// one browser per handler instance. +class ClientHandlerStd : public ClientHandler { + public: + ClientHandlerStd(Delegate* delegate, + bool with_controls, + const std::string& startup_url); + + private: + // Include the default reference counting implementation. + IMPLEMENT_REFCOUNTING(ClientHandlerStd); + DISALLOW_COPY_AND_ASSIGN(ClientHandlerStd); +}; + +} // namespace client + +#endif // CEF_TESTS_CEFCLIENT_BROWSER_CLIENT_HANDLER_STD_H_ diff --git a/src/CEF/ExtensionUtil.cpp b/src/CEF/ExtensionUtil.cpp new file mode 100644 index 0000000..4d375c1 --- /dev/null +++ b/src/CEF/ExtensionUtil.cpp @@ -0,0 +1,247 @@ +// 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/ExtensionUtil.h" + +#include +#include + +#include "include/base/cef_callback.h" +#include "include/base/cef_cxx17_backports.h" +#include "include/cef_parser.h" +#include "include/cef_path_util.h" +#include "include/wrapper/cef_closure_task.h" +#include "CEF/FileUtil.h" +#include "CEF/ResourceUtil.h" + + +std::string GetResourcesPath() { + CefString resources_dir; + if (CefGetPath(PK_DIR_RESOURCES, resources_dir) && !resources_dir.empty()) { + return resources_dir.ToString() + kPathSep; + } + return std::string(); +} + +// Internal extension paths may be prefixed with PK_DIR_RESOURCES and always +// use forward slash as path separator. +std::string GetInternalPath(const std::string& extension_path) { + std::string resources_path_lower = GetResourcesPath(); + std::string extension_path_lower = extension_path; + +#if defined(OS_WIN) + // Convert to lower-case, since Windows paths are case-insensitive. + std::transform(resources_path_lower.begin(), resources_path_lower.end(), + resources_path_lower.begin(), ::tolower); + std::transform(extension_path_lower.begin(), extension_path_lower.end(), + extension_path_lower.begin(), ::tolower); +#endif + + std::string internal_path; + if (!resources_path_lower.empty() && + extension_path_lower.find(resources_path_lower) == 0U) { + internal_path = extension_path.substr(resources_path_lower.size()); + } else { + internal_path = extension_path; + } + +#if defined(OS_WIN) + // Normalize path separators. + std::replace(internal_path.begin(), internal_path.end(), '\\', '/'); +#endif + + return internal_path; +} + +using ManifestCallback = + base::OnceCallback /*manifest*/)>; + +void RunManifestCallback(ManifestCallback callback, + CefRefPtr manifest) { + if (!CefCurrentlyOn(TID_UI)) { + // Execute on the browser UI thread. + CefPostTask(TID_UI, base::BindOnce(std::move(callback), manifest)); + return; + } + std::move(callback).Run(manifest); +} + +// Asynchronously reads the manifest and executes |callback| on the UI thread. +void GetInternalManifest(const std::string& extension_path, + ManifestCallback callback) { + if (!CefCurrentlyOn(TID_FILE_USER_BLOCKING)) { + // Execute on the browser FILE thread. + CefPostTask(TID_FILE_USER_BLOCKING, + base::BindOnce(GetInternalManifest, extension_path, + std::move(callback))); + return; + } + + const std::string& manifest_path = GetInternalExtensionResourcePath( + JoinPath(extension_path, "manifest.json")); + std::string manifest_contents; + if (!LoadBinaryResource(manifest_path.c_str(), manifest_contents) || + manifest_contents.empty()) { + LOG(ERROR) << "Failed to load manifest from " << manifest_path; + RunManifestCallback(std::move(callback), nullptr); + return; + } + + CefString error_msg; + CefRefPtr value = + CefParseJSONAndReturnError(manifest_contents, JSON_PARSER_RFC, error_msg); + if (!value || value->GetType() != VTYPE_DICTIONARY) { + if (error_msg.empty()) + error_msg = "Incorrectly formatted dictionary contents."; + LOG(ERROR) << "Failed to parse manifest from " << manifest_path << "; " + << error_msg.ToString(); + RunManifestCallback(std::move(callback), nullptr); + return; + } + + RunManifestCallback(std::move(callback), value->GetDictionary()); +} + +void LoadExtensionWithManifest(CefRefPtr request_context, + const std::string& extension_path, + CefRefPtr handler, + CefRefPtr manifest) { + CEF_REQUIRE_UI_THREAD(); + + // Load the extension internally. Resource requests will be handled via + // AddInternalExtensionToResourceManager. + request_context->LoadExtension(extension_path, manifest, handler); +} + +bool IsInternalExtension(const std::string& extension_path) { + // List of internally handled extensions. + static const char* extensions[] = {"set_page_color"}; + + const std::string& internal_path = GetInternalPath(extension_path); + for (size_t i = 0; i < base::size(extensions); ++i) { + // Exact match or first directory component. + const std::string& extension = extensions[i]; + if (internal_path == extension || + internal_path.find(extension + '/') == 0) { + return true; + } + } + + return false; +} + +std::string GetInternalExtensionResourcePath( + const std::string& extension_path) { + return "extensions/" + GetInternalPath(extension_path); +} + +std::string GetExtensionResourcePath(const std::string& extension_path, + bool* internal) { + const bool is_internal = IsInternalExtension(extension_path); + if (internal) + *internal = is_internal; + if (is_internal) + return GetInternalExtensionResourcePath(extension_path); + return extension_path; +} + +bool GetExtensionResourceContents(const std::string& extension_path, + std::string& contents) { + CEF_REQUIRE_FILE_USER_BLOCKING_THREAD(); + + if (IsInternalExtension(extension_path)) { + const std::string& contents_path = + GetInternalExtensionResourcePath(extension_path); + return LoadBinaryResource(contents_path.c_str(), contents); + } + + return ReadFileToString(extension_path, &contents); +} + +void LoadExtension(CefRefPtr request_context, + const std::string& extension_path, + CefRefPtr handler) { + if (!CefCurrentlyOn(TID_UI)) { + // Execute on the browser UI thread. + CefPostTask(TID_UI, base::BindOnce(LoadExtension, request_context, + extension_path, handler)); + return; + } + + if (IsInternalExtension(extension_path)) { + // Read the extension manifest and load asynchronously. + GetInternalManifest( + extension_path, + base::BindOnce(LoadExtensionWithManifest, request_context, + extension_path, handler)); + } else { + // Load the extension from disk. + request_context->LoadExtension(extension_path, nullptr, handler); + } +} + +void AddInternalExtensionToResourceManager( + CefRefPtr extension, + CefRefPtr resource_manager) { + DCHECK(IsInternalExtension(extension->GetPath())); + + if (!CefCurrentlyOn(TID_IO)) { + // Execute on the browser IO thread. + CefPostTask(TID_IO, base::BindOnce(AddInternalExtensionToResourceManager, + extension, resource_manager)); + return; + } + + const std::string& origin = GetExtensionOrigin(extension->GetIdentifier()); + const std::string& resource_path = + GetInternalExtensionResourcePath(extension->GetPath()); + +// Add provider for bundled resource files. +#if defined(OS_WIN) + // Read resources from the binary. + resource_manager->AddProvider( + CreateBinaryResourceProvider(origin, resource_path), 50, std::string()); +#elif defined(OS_POSIX) + // Read resources from a directory on disk. + std::string resource_dir; + if (GetResourceDir(resource_dir)) { + resource_dir += "/" + resource_path; + resource_manager->AddDirectoryProvider(origin, resource_dir, 50, + std::string()); + } +#endif +} + +std::string GetExtensionOrigin(const std::string& extension_id) { + return "chrome-extension://" + extension_id + "/"; +} + +std::string GetExtensionURL(CefRefPtr extension) { + CefRefPtr browser_action = + extension->GetManifest()->GetDictionary("browser_action"); + if (browser_action) { + const std::string& default_popup = + browser_action->GetString("default_popup"); + if (!default_popup.empty()) + return GetExtensionOrigin(extension->GetIdentifier()) + default_popup; + } + + return std::string(); +} + +std::string GetExtensionIconPath(CefRefPtr extension, + bool* internal) { + CefRefPtr browser_action = + extension->GetManifest()->GetDictionary("browser_action"); + if (browser_action) { + const std::string& default_icon = browser_action->GetString("default_icon"); + if (!default_icon.empty()) { + return GetExtensionResourcePath( + JoinPath(extension->GetPath(), default_icon), internal); + } + } + + return std::string(); +} + diff --git a/src/CEF/ExtensionUtil.h b/src/CEF/ExtensionUtil.h new file mode 100644 index 0000000..dfa0172 --- /dev/null +++ b/src/CEF/ExtensionUtil.h @@ -0,0 +1,71 @@ +// 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 + +#include "include/cef_extension.h" +#include "include/cef_extension_handler.h" +#include "include/wrapper/cef_resource_manager.h" + +// Returns true if |extension_path| can be handled internally via +// LoadBinaryResource. This checks a hard-coded list of allowed extension path +// components. +bool IsInternalExtension(const std::string& extension_path); + +// Returns the path relative to the resource directory after removing the +// PK_DIR_RESOURCES prefix. This will be the relative path expected by +// LoadBinaryResource (uses '/' as path separator on all platforms). Only call +// this method for internal extensions, either when IsInternalExtension returns +// true or when the extension is handled internally through some means other +// than LoadBinaryResource. Use GetExtensionResourcePath instead if you are +// unsure whether the extension is internal or external. +std::string GetInternalExtensionResourcePath(const std::string& extension_path); + +// Returns the resource path for |extension_path|. For external extensions this +// will be the full file path on disk. For internal extensions this will be the +// relative path expected by LoadBinaryResource (uses '/' as path separator on +// all platforms). Internal extensions must be on the hard-coded list enforced +// by IsInternalExtension. If |internal| is non-nullptr it will be set to true +// if the extension is handled internally. +std::string GetExtensionResourcePath(const std::string& extension_path, + bool* internal); + +// Read the contents of |extension_path| into |contents|. For external +// extensions this will read the file from disk. For internal extensions this +// will call LoadBinaryResource. Internal extensions must be on the hard-coded +// list enforced by IsInternalExtension. Returns true on success. Must be +// called on the FILE thread. +bool GetExtensionResourceContents(const std::string& extension_path, + std::string& contents); + +// Load |extension_path| in |request_context|. May be an internal or external +// extension. Internal extensions must be on the hard-coded list enforced by +// IsInternalExtension. +void LoadExtension(CefRefPtr request_context, + const std::string& extension_path, + CefRefPtr handler); + +// Register an internal handler for extension resources. Internal extensions +// must be on the hard-coded list enforced by IsInternalExtension. +void AddInternalExtensionToResourceManager( + CefRefPtr extension, + CefRefPtr resource_manager); + +// Returns the URL origin for |extension_id|. +std::string GetExtensionOrigin(const std::string& extension_id); + +// Parse browser_action manifest values as defined at +// https://developer.chrome.com/extensions/browserAction + +// Look for a browser_action.default_popup manifest value. +std::string GetExtensionURL(CefRefPtr extension); + +// Look for a browser_action.default_icon manifest value and return the resource +// path. If |internal| is non-nullptr it will be set to true if the extension is +// handled internally. +std::string GetExtensionIconPath(CefRefPtr extension, + bool* internal); + diff --git a/src/CEF/FileUtil.cpp b/src/CEF/FileUtil.cpp new file mode 100644 index 0000000..62ef50e --- /dev/null +++ b/src/CEF/FileUtil.cpp @@ -0,0 +1,112 @@ +// Copyright 2016 The Chromium Embedded Framework Authors. Portions copyright +// 2012 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 "CEF/FileUtil.h" + +#include +#include +#include + +#include "include/base/cef_build.h" +#include "include/cef_task.h" + + +bool AllowFileIO() { + if (CefCurrentlyOn(TID_UI) || CefCurrentlyOn(TID_IO)) { + NOTREACHED() << "file IO is not allowed on the current thread"; + return false; + } + return true; +} + +#if defined(OS_WIN) +const char kPathSep = '\\'; +#else +const char kPathSep = '/'; +#endif + +bool ReadFileToString(const std::string& path, + std::string* contents, + size_t max_size) { + if (!AllowFileIO()) + return false; + + if (contents) + contents->clear(); + FILE* file = fopen(path.c_str(), "rb"); + if (!file) + return false; + + const size_t kBufferSize = 1 << 16; + std::unique_ptr buf(new char[kBufferSize]); + size_t len; + size_t size = 0; + bool read_status = true; + + // Many files supplied in |path| have incorrect size (proc files etc). + // 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)); + + if ((max_size - size) < len) { + read_status = false; + break; + } + + size += len; + } + read_status = read_status && !ferror(file); + fclose(file); + + return read_status; +} + +int WriteFile(const std::string& path, const char* data, int size) { + if (!AllowFileIO()) + return -1; + + FILE* file = fopen(path.c_str(), "wb"); + if (!file) + return -1; + + int written = 0; + + do { + size_t write = fwrite(data + written, 1, size - written, file); + if (write == 0) + break; + written += static_cast(write); + } while (written < size); + + fclose(file); + + return written; +} + +std::string JoinPath(const std::string& path1, const std::string& path2) { + if (path1.empty() && path2.empty()) + return std::string(); + if (path1.empty()) + return path2; + if (path2.empty()) + return path1; + + std::string result = path1; + if (result[result.size() - 1] != kPathSep) + result += kPathSep; + if (path2[0] == kPathSep) + result += path2.substr(1); + else + result += path2; + return result; +} + +std::string GetFileExtension(const std::string& path) { + size_t sep = path.find_last_of("."); + if (sep != std::string::npos) + return path.substr(sep + 1); + return std::string(); +} + diff --git a/src/CEF/FileUtil.h b/src/CEF/FileUtil.h new file mode 100644 index 0000000..a18b99c --- /dev/null +++ b/src/CEF/FileUtil.h @@ -0,0 +1,38 @@ +// Copyright 2016 The Chromium Embedded Framework Authors. Portions copyright +// 2012 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. + + +#pragma once + +#include +#include + + +// Platform-specific path separator. +extern const char kPathSep; + +// Reads the file at |path| into |contents| and returns true on success and +// false on error. In case of I/O error, |contents| holds the data that could +// be read from the file before the error occurred. When the file size exceeds +// max_size|, the function returns false with |contents| holding the file +// truncated to |max_size|. |contents| may be nullptr, in which case this +// function is useful for its side effect of priming the disk cache (could be +// used for unit tests). Calling this function on the browser process UI or IO +// threads is not allowed. +bool ReadFileToString(const std::string& path, + std::string* contents, + 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. +// Calling this function on the browser process UI or IO threads is not allowed. +int WriteFile(const std::string& path, const char* data, int size); + +// Combines |path1| and |path2| with the correct platform-specific path +// separator. +std::string JoinPath(const std::string& path1, const std::string& path2); + +// Extracts the file extension from |path|. +std::string GetFileExtension(const std::string& path); + diff --git a/src/CEF/HuamAppRendererDelegate.cpp b/src/CEF/HuamAppRendererDelegate.cpp index 12e6e47..d54a9c6 100644 --- a/src/CEF/HuamAppRendererDelegate.cpp +++ b/src/CEF/HuamAppRendererDelegate.cpp @@ -375,3 +375,8 @@ const PerfTestEntry kPerfTests[] = { const int kPerfTestsCount = (sizeof(kPerfTests) / sizeof(kPerfTests[0])); +#if DCHECK_IS_ON() +const int kDefaultIterations = 100000; +#else +const int kDefaultIterations = 10000; +#endif diff --git a/src/CEF/HumanAppBrowser.cpp b/src/CEF/HumanAppBrowser.cpp index 64928ae..c004fee 100644 --- a/src/CEF/HumanAppBrowser.cpp +++ b/src/CEF/HumanAppBrowser.cpp @@ -11,12 +11,54 @@ #include "include/views/cef_browser_view.h" #include "include/views/cef_window.h" #include "include/wrapper/cef_helpers.h" +#include "include/cef_crash_util.h" +#include "include/cef_file_util.h" #include "CEF/HumanAppSwitches.h" +class HumanAppBrowserDelegate : public HumanAppBrowser::Delegate { +public: + HumanAppBrowserDelegate() = default; + + void OnContextInitialized(CefRefPtr app) override { + if (CefCrashReportingEnabled()) { + // Set some crash keys for testing purposes. Keys must be defined in the + // "crash_reporter.cfg" file. See cef_crash_util.h for details. + CefSetCrashKeyValue("testkey_small1", "value1_small_browser"); + CefSetCrashKeyValue("testkey_small2", "value2_small_browser"); + CefSetCrashKeyValue("testkey_medium1", "value1_medium_browser"); + CefSetCrashKeyValue("testkey_medium2", "value2_medium_browser"); + CefSetCrashKeyValue("testkey_large1", "value1_large_browser"); + CefSetCrashKeyValue("testkey_large2", "value2_large_browser"); + } + + const std::string& crl_sets_path = + CefCommandLine::GetGlobalCommandLine()->GetSwitchValue( + kCRLSetsPath); + if (!crl_sets_path.empty()) { + // Load the CRLSets file from the specified path. + CefLoadCRLSetsFile(crl_sets_path); + } + } + + void OnBeforeCommandLineProcessing( + CefRefPtr app, + CefRefPtr command_line) override { + // Append Chromium command line parameters if touch events are enabled + if (MainContext::Get()->TouchEventsEnabled()) + command_line->AppendSwitchWithValue("touch-events", "enabled"); + } + +private: + DISALLOW_COPY_AND_ASSIGN(HumanAppBrowserDelegate); + IMPLEMENT_REFCOUNTING(HumanAppBrowserDelegate); +}; + + + HumanAppBrowser::HumanAppBrowser() { - CreateDelegates(delegates_); + } // static @@ -33,7 +75,6 @@ void HumanAppBrowser::PopulateSettings(CefRefPtr command_line, } std::vector cookieable_schemes; - RegisterCookieableSchemes(cookieable_schemes); if (!cookieable_schemes.empty()) { std::string list_str; for (const auto& scheme : cookieable_schemes) { diff --git a/src/CEF/HumanAppBrowser.h b/src/CEF/HumanAppBrowser.h index 23feb87..e3ed566 100644 --- a/src/CEF/HumanAppBrowser.h +++ b/src/CEF/HumanAppBrowser.h @@ -28,11 +28,6 @@ class HumanAppBrowser : public HumanApp, public CefBrowserProcessHandler { CefSettings& settings); private: - static void RegisterCookieableSchemes( - std::vector& cookieable_schemes); - - static void CreateDelegates(DelegateSet& delegates); - void OnBeforeCommandLineProcessing( const CefString& process_type, CefRefPtr command_line) override; diff --git a/src/CEF/HumanAppContext.cpp b/src/CEF/HumanAppContext.cpp new file mode 100644 index 0000000..0e6cbd1 --- /dev/null +++ b/src/CEF/HumanAppContext.cpp @@ -0,0 +1,25 @@ +// 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/HumanAppContext.h" + +#include "include/base/cef_logging.h" + +HumanAppContext* g_main_context = nullptr; + +// static +HumanAppContext* HumanAppContext::Get() { + DCHECK(g_main_context); + return g_main_context; +} + +HumanAppContext::HumanAppContext() { + DCHECK(!g_main_context); + g_main_context = this; +} + +HumanAppContext::~HumanAppContext() { + g_main_context = nullptr; +} + diff --git a/src/CEF/HumanAppContext.h b/src/CEF/HumanAppContext.h new file mode 100644 index 0000000..fcd0584 --- /dev/null +++ b/src/CEF/HumanAppContext.h @@ -0,0 +1,69 @@ +// 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/base/cef_macros.h" +#include "include/base/cef_ref_counted.h" +#include "include/internal/cef_types_wrappers.h" +#include "CEF/OsrRendererSettings.h" + + +class RootWindowManager; + +// Used to store global context in the browser process. The methods of this +// class are thread-safe unless otherwise indicated. +class HumanAppContext { + public: + // Returns the singleton instance of this object. + static HumanAppContext* Get(); + + // Returns the full path to the console log file. + virtual std::string GetConsoleLogPath() = 0; + + // Returns the full path to |file_name|. + virtual std::string GetDownloadPath(const std::string& file_name) = 0; + + // Returns the app working directory including trailing path separator. + virtual std::string GetAppWorkingDirectory() = 0; + + // Returns the main application URL. + virtual std::string GetMainURL() = 0; + + // Returns the background color. + virtual cef_color_t GetBackgroundColor() = 0; + + // Returns true if the Chrome runtime will be used. + virtual bool UseChromeRuntime() = 0; + + // Returns true if the Views framework will be used. + virtual bool UseViews() = 0; + + // Returns true if windowless (off-screen) rendering will be used. + virtual bool UseWindowlessRendering() = 0; + + // Returns true if touch events are enabled. + virtual bool TouchEventsEnabled() = 0; + + // Returns true if the default popup implementation should be used. + virtual bool UseDefaultPopup() = 0; + + // Populate |settings| based on command-line arguments. + virtual void PopulateSettings(CefSettings* settings) = 0; + virtual void PopulateBrowserSettings(CefBrowserSettings* settings) = 0; + virtual void PopulateOsrSettings(OsrRendererSettings* settings) = 0; + + // Returns the object used to create/manage RootWindow instances. + virtual RootWindowManager* GetRootWindowManager() = 0; + + protected: + HumanAppContext(); + virtual ~HumanAppContext(); + + private: + DISALLOW_COPY_AND_ASSIGN(HumanAppContext); +}; diff --git a/src/CEF/HumanAppContextImpl.cpp b/src/CEF/HumanAppContextImpl.cpp new file mode 100644 index 0000000..c843c4e --- /dev/null +++ b/src/CEF/HumanAppContextImpl.cpp @@ -0,0 +1,267 @@ +// 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 "include/cef_parser.h" +#include "CEF/HumanAppBrowser.h" +#include "CEF/HumanAppSwitches.h" + +// The default URL to load in a browser window. +const char kDefaultUrl[] = "http://www.google.com"; + +// Returns the ARGB value for |color|. +cef_color_t ParseColor(const std::string& color) { + std::string colorToLower; + colorToLower.resize(color.size()); + std::transform(color.begin(), color.end(), colorToLower.begin(), ::tolower); + + if (colorToLower == "black") + return CefColorSetARGB(255, 0, 0, 0); + else if (colorToLower == "blue") + return CefColorSetARGB(255, 0, 0, 255); + else if (colorToLower == "green") + return CefColorSetARGB(255, 0, 255, 0); + else if (colorToLower == "red") + return CefColorSetARGB(255, 255, 0, 0); + else if (colorToLower == "white") + return CefColorSetARGB(255, 255, 255, 255); + + // Use the default color. + return 0; +} + +HumanAppContextImpl::HumanAppContextImpl(CefRefPtr command_line, + bool terminate_when_all_windows_closed) + : command_line_(command_line), + terminate_when_all_windows_closed_(terminate_when_all_windows_closed) { + DCHECK(command_line_.get()); + + // Set the main URL. + if (command_line_->HasSwitch(kUrl)) + main_url_ = command_line_->GetSwitchValue(kUrl); + if (main_url_.empty()) + main_url_ = kDefaultUrl; + + // Whether windowless (off-screen) rendering will be used. + use_windowless_rendering_ = + command_line_->HasSwitch(kOffScreenRenderingEnabled); + + if (use_windowless_rendering_ && + command_line_->HasSwitch(kOffScreenFrameRate)) { + windowless_frame_rate_ = + atoi(command_line_->GetSwitchValue(kOffScreenFrameRate) + .ToString() + .c_str()); + } + + // Whether transparent painting is used with windowless rendering. + const bool use_transparent_painting = + use_windowless_rendering_ && + command_line_->HasSwitch(kTransparentPaintingEnabled); + +#if defined(OS_WIN) + // Shared texture is only supported on Windows. + shared_texture_enabled_ = + use_windowless_rendering_ && + command_line_->HasSwitch(kSharedTextureEnabled); +#endif + + external_begin_frame_enabled_ = + use_windowless_rendering_ && + command_line_->HasSwitch(kExternalBeginFrameEnabled); + + if (windowless_frame_rate_ <= 0) { +// Choose a reasonable default rate based on the OSR mode. +#if defined(OS_WIN) + windowless_frame_rate_ = shared_texture_enabled_ ? 60 : 30; +#else + windowless_frame_rate_ = 30; +#endif + } + + // Enable experimental Chrome runtime. See issue #2969 for details. + use_chrome_runtime_ = + command_line_->HasSwitch(kEnableChromeRuntime); + + if (use_windowless_rendering_ && use_chrome_runtime_) { + LOG(ERROR) + << "Windowless rendering is not supported with the Chrome runtime."; + use_chrome_runtime_ = false; + } + + // Whether the Views framework will be used. + use_views_ = command_line_->HasSwitch(kUseViews); + + if (use_windowless_rendering_ && use_views_) { + LOG(ERROR) + << "Windowless rendering is not supported by the Views framework."; + use_views_ = false; + } + +#if defined(OS_WIN) || defined(OS_LINUX) + if (use_chrome_runtime_ && !use_views_ && + !command_line->HasSwitch(kUseNative)) { + LOG(WARNING) << "Chrome runtime defaults to the Views framework."; + use_views_ = true; + } +#else // !(defined(OS_WIN) || defined(OS_LINUX)) + if (use_chrome_runtime_ && !use_views_) { + // TODO(chrome): Add support for this runtime configuration (e.g. a fully + // styled Chrome window with cefclient menu customizations). In the mean + // time this can be demo'd with "cefsimple --enable-chrome-runtime". + LOG(WARNING) << "Chrome runtime requires the Views framework."; + use_views_ = true; + } +#endif // !(defined(OS_WIN) || defined(OS_LINUX)) + + if (use_views_ && command_line->HasSwitch(kHideFrame) && + !command_line_->HasSwitch(kUrl)) { + // Use the draggable regions test as the default URL for frameless windows. + main_url_ = "http://tests/draggable"; + } + + if (command_line_->HasSwitch(kBackgroundColor)) { + // Parse the background color value. + background_color_ = + ParseColor(command_line_->GetSwitchValue(kBackgroundColor)); + } + + if (background_color_ == 0 && !use_views_) { + // Set an explicit background color. + background_color_ = CefColorSetARGB(255, 255, 255, 255); + } + + // |browser_background_color_| should remain 0 to enable transparent painting. + if (!use_transparent_painting) { + browser_background_color_ = background_color_; + } +} + +HumanAppContextImpl::~HumanAppContextImpl() { + // The context must either not have been initialized, or it must have also + // been shut down. + DCHECK(!initialized_ || shutdown_); +} + +std::string HumanAppContextImpl::GetConsoleLogPath() { + return GetAppWorkingDirectory() + "console.log"; +} + +std::string HumanAppContextImpl::GetMainURL() { + return main_url_; +} + +cef_color_t HumanAppContextImpl::GetBackgroundColor() { + return background_color_; +} + +bool HumanAppContextImpl::UseChromeRuntime() { + return use_chrome_runtime_; +} + +bool HumanAppContextImpl::UseViews() { + return use_views_; +} + +bool HumanAppContextImpl::UseWindowlessRendering() { + return use_windowless_rendering_; +} + +bool HumanAppContextImpl::TouchEventsEnabled() { + return command_line_->GetSwitchValue("touch-events") == "enabled"; +} + +bool HumanAppContextImpl::UseDefaultPopup() { + return !use_windowless_rendering_ && + command_line_->HasSwitch(kUseDefaultPopup); +} + +void HumanAppContextImpl::PopulateSettings(CefSettings* settings) { + HumanAppBrowser::PopulateSettings(command_line_, *settings); + + if (use_chrome_runtime_) + settings->chrome_runtime = true; + + CefString(&settings->cache_path) = + command_line_->GetSwitchValue(kCachePath); + + if (use_windowless_rendering_) + settings->windowless_rendering_enabled = true; + + if (browser_background_color_ != 0) + settings->background_color = browser_background_color_; + + if (command_line_->HasSwitch("lang")) { + // Use the same locale for the Accept-Language HTTP request header. + CefString(&settings->accept_language_list) = + command_line_->GetSwitchValue("lang"); + } +} + +void HumanAppContextImpl::PopulateBrowserSettings(CefBrowserSettings* settings) { + settings->windowless_frame_rate = windowless_frame_rate_; + + if (browser_background_color_ != 0) + settings->background_color = browser_background_color_; + + if (use_chrome_runtime_ && + command_line_->HasSwitch(kHideChromeStatusBubble)) { + settings->chrome_status_bubble = STATE_DISABLED; + } +} + +void HumanAppContextImpl::PopulateOsrSettings(OsrRendererSettings* settings) { + settings->show_update_rect = + command_line_->HasSwitch(kShowUpdateRect); + +#if defined(OS_WIN) + settings->shared_texture_enabled = shared_texture_enabled_; +#endif + settings->external_begin_frame_enabled = external_begin_frame_enabled_; + settings->begin_frame_rate = windowless_frame_rate_; + + if (browser_background_color_ != 0) + settings->background_color = browser_background_color_; +} + +RootWindowManager* HumanAppContextImpl::GetRootWindowManager() { + DCHECK(InValidState()); + return root_window_manager_.get(); +} + +bool HumanAppContextImpl::Initialize(const CefMainArgs& args, + const CefSettings& settings, + CefRefPtr application, + void* windows_sandbox_info) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(!initialized_); + DCHECK(!shutdown_); + + if (!CefInitialize(args, settings, application, windows_sandbox_info)) + return false; + + // Need to create the RootWindowManager after calling CefInitialize because + // TempWindowX11 uses cef_get_xdisplay(). + root_window_manager_.reset( + new RootWindowManager(terminate_when_all_windows_closed_)); + + initialized_ = true; + + return true; +} + +void HumanAppContextImpl::Shutdown() { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(initialized_); + DCHECK(!shutdown_); + + root_window_manager_.reset(); + + CefShutdown(); + + shutdown_ = true; +} diff --git a/src/CEF/HumanAppContextImpl.h b/src/CEF/HumanAppContextImpl.h new file mode 100644 index 0000000..355c710 --- /dev/null +++ b/src/CEF/HumanAppContextImpl.h @@ -0,0 +1,87 @@ +// 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/base/cef_thread_checker.h" +#include "include/cef_app.h" +#include "include/cef_command_line.h" +#include "CEF/HumanAppContext.h" + + + +class HumanAppContextImpl : public HumanAppContext { + public: + HumanAppContextImpl(CefRefPtr command_line, + bool terminate_when_all_windows_closed); + + // MainContext members. + std::string GetConsoleLogPath() override; + std::string GetDownloadPath(const std::string& file_name) override; + std::string GetAppWorkingDirectory() override; + std::string GetMainURL() override; + cef_color_t GetBackgroundColor() override; + bool UseChromeRuntime() override; + bool UseViews() override; + bool UseWindowlessRendering() override; + bool TouchEventsEnabled() override; + bool UseDefaultPopup() override; + void PopulateSettings(CefSettings* settings) override; + void PopulateBrowserSettings(CefBrowserSettings* settings) override; + void PopulateOsrSettings(OsrRendererSettings* settings) override; + RootWindowManager* GetRootWindowManager() override; + + // Initialize CEF and associated main context state. This method must be + // called on the same thread that created this object. + bool Initialize(const CefMainArgs& args, + const CefSettings& settings, + CefRefPtr application, + void* windows_sandbox_info); + + // Shut down CEF and associated context state. This method must be called on + // the same thread that created this object. + void Shutdown(); + + private: + // Allow deletion via std::unique_ptr only. + friend std::default_delete; + + ~HumanAppContextImpl(); + + // Returns true if the context is in a valid state (initialized and not yet + // shut down). + bool InValidState() const { return initialized_ && !shutdown_; } + + CefRefPtr command_line_; + const bool terminate_when_all_windows_closed_; + + // Track context state. Accessing these variables from multiple threads is + // safe because only a single thread will exist at the time that they're set + // (during context initialization and shutdown). + bool initialized_ = false; + bool shutdown_ = false; + + std::string main_url_; + cef_color_t background_color_ = 0; + cef_color_t browser_background_color_ = 0; + bool use_windowless_rendering_; + int windowless_frame_rate_ = 0; + bool use_chrome_runtime_; + bool use_views_; + + std::unique_ptr root_window_manager_; + +#if defined(OS_WIN) + bool shared_texture_enabled_; +#endif + + bool external_begin_frame_enabled_; + + // Used to verify that methods are called on the correct thread. + base::ThreadChecker thread_checker_; + + DISALLOW_COPY_AND_ASSIGN(HumanAppContextImpl); +}; diff --git a/src/CEF/HumanApptypes.h b/src/CEF/HumanApptypes.h new file mode 100644 index 0000000..092c711 --- /dev/null +++ b/src/CEF/HumanApptypes.h @@ -0,0 +1,29 @@ +// 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_CLIENT_TYPES_H_ +#define CEF_TESTS_CEFCLIENT_BROWSER_CLIENT_TYPES_H_ +#pragma once + +#include "include/cef_base.h" + +#if defined(OS_LINUX) +#include + +// The Linux client uses GTK instead of the underlying platform type (X11). +#define ClientWindowHandle GtkWidget* +#else +#define ClientWindowHandle CefWindowHandle +#endif + +#if defined(OS_MAC) +#define ClientNativeMacWindow void* +#ifdef __OBJC__ +#define CAST_CLIENT_NATIVE_MAC_WINDOW_TO_NSWINDOW(native) \ + (__bridge NSWindow*)native +#define CAST_NSWINDOW_TO_CLIENT_NATIVE_MAC_WINDOW(window) (__bridge void*)window +#endif // __OBJC__ +#endif // defined OS_MAC + +#endif // CEF_TESTS_CEFCLIENT_BROWSER_CLIENT_TYPES_H_ diff --git a/src/CEF/ImageCache.cpp b/src/CEF/ImageCache.cpp new file mode 100644 index 0000000..f10b90b --- /dev/null +++ b/src/CEF/ImageCache.cpp @@ -0,0 +1,304 @@ +// 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/ImageCache.h" + +#include + +#include "CEF/FileUtil.h" +#include "CEF/ResourceUtil.h" + + +const char kEmptyId[] = "__empty"; + + +ImageCache::ImageCache() {} + +ImageCache::~ImageCache() { + CEF_REQUIRE_UI_THREAD(); +} + +ImageCache::ImageRep::ImageRep(const std::string& path, float scale_factor) + : path_(path), scale_factor_(scale_factor) { + DCHECK(!path_.empty()); + DCHECK_GT(scale_factor_, 0.0f); +} + +ImageCache::ImageInfo::ImageInfo(const std::string& id, + const ImageRepSet& reps, + bool internal, + bool force_reload) + : id_(id), reps_(reps), internal_(internal), force_reload_(force_reload) { +#ifndef NDEBUG + DCHECK(!id_.empty()); + if (id_ != kEmptyId) + DCHECK(!reps_.empty()); +#endif +} + +// static +ImageCache::ImageInfo ImageCache::ImageInfo::Empty() { + return ImageInfo(kEmptyId, ImageRepSet(), true, false); +} + +// static +ImageCache::ImageInfo ImageCache::ImageInfo::Create1x( + const std::string& id, + const std::string& path_1x, + bool internal) { + ImageRepSet reps; + reps.push_back(ImageRep(path_1x, 1.0f)); + return ImageInfo(id, reps, internal, false); +} + +// static +ImageCache::ImageInfo ImageCache::ImageInfo::Create2x( + const std::string& id, + const std::string& path_1x, + const std::string& path_2x, + bool internal) { + ImageRepSet reps; + reps.push_back(ImageRep(path_1x, 1.0f)); + reps.push_back(ImageRep(path_2x, 2.0f)); + return ImageInfo(id, reps, internal, false); +} + +// static +ImageCache::ImageInfo ImageCache::ImageInfo::Create2x(const std::string& id) { + return Create2x(id, id + ".1x.png", id + ".2x.png", true); +} + +struct ImageCache::ImageContent { + ImageContent() {} + + struct RepContent { + RepContent(ImageType type, float scale_factor, const std::string& contents) + : type_(type), scale_factor_(scale_factor), contents_(contents) {} + + ImageType type_; + float scale_factor_; + std::string contents_; + }; + typedef std::vector RepContentSet; + RepContentSet contents_; + + CefRefPtr image_; +}; + +void ImageCache::LoadImages(const ImageInfoSet& image_info, + LoadImagesCallback callback) { + DCHECK(!image_info.empty()); + DCHECK(!callback.is_null()); + + if (!CefCurrentlyOn(TID_UI)) { + CefPostTask(TID_UI, base::BindOnce(&ImageCache::LoadImages, this, + image_info, std::move(callback))); + return; + } + + ImageSet images; + bool missing_images = false; + + ImageInfoSet::const_iterator it = image_info.begin(); + for (; it != image_info.end(); ++it) { + const ImageInfo& info = *it; + + if (info.id_ == kEmptyId) { + // Image intentionally left empty. + images.push_back(nullptr); + continue; + } + + ImageMap::iterator it2 = image_map_.find(info.id_); + if (it2 != image_map_.end()) { + if (!info.force_reload_) { + // Image already exists. + images.push_back(it2->second); + continue; + } + + // Remove the existing image from the map. + image_map_.erase(it2); + } + + // Load the image. + images.push_back(nullptr); + if (!missing_images) + missing_images = true; + } + + if (missing_images) { + CefPostTask(TID_FILE_USER_BLOCKING, + base::BindOnce(&ImageCache::LoadMissing, this, image_info, + images, std::move(callback))); + } else { + std::move(callback).Run(images); + } +} + +CefRefPtr ImageCache::GetCachedImage(const std::string& image_id) { + CEF_REQUIRE_UI_THREAD(); + DCHECK(!image_id.empty()); + + ImageMap::const_iterator it = image_map_.find(image_id); + if (it != image_map_.end()) + return it->second; + + return nullptr; +} + +// static +ImageCache::ImageType ImageCache::GetImageType(const std::string& path) { + std::string ext = GetFileExtension(path); + if (ext.empty()) + return TYPE_NONE; + + std::transform(ext.begin(), ext.end(), ext.begin(), tolower); + if (ext == "png") + return TYPE_PNG; + if (ext == "jpg" || ext == "jpeg") + return TYPE_JPEG; + + return TYPE_NONE; +} + +void ImageCache::LoadMissing(const ImageInfoSet& image_info, + const ImageSet& images, + LoadImagesCallback callback) { + CEF_REQUIRE_FILE_USER_BLOCKING_THREAD(); + + DCHECK_EQ(image_info.size(), images.size()); + + ImageContentSet contents; + + ImageInfoSet::const_iterator it1 = image_info.begin(); + ImageSet::const_iterator it2 = images.begin(); + for (; it1 != image_info.end() && it2 != images.end(); ++it1, ++it2) { + const ImageInfo& info = *it1; + ImageContent content; + if (*it2 || info.id_ == kEmptyId) { + // Image already exists or is intentionally empty. + content.image_ = *it2; + } else { + LoadImageContents(info, &content); + } + contents.push_back(content); + } + + CefPostTask(TID_UI, base::BindOnce(&ImageCache::UpdateCache, this, image_info, + contents, std::move(callback))); +} + +// static +bool ImageCache::LoadImageContents(const ImageInfo& info, + ImageContent* content) { + CEF_REQUIRE_FILE_USER_BLOCKING_THREAD(); + + ImageRepSet::const_iterator it = info.reps_.begin(); + for (; it != info.reps_.end(); ++it) { + const ImageRep& rep = *it; + ImageType rep_type; + std::string rep_contents; + if (!LoadImageContents(rep.path_, info.internal_, &rep_type, + &rep_contents)) { + LOG(ERROR) << "Failed to load image " << info.id_ << " from path " + << rep.path_; + return false; + } + content->contents_.push_back( + ImageContent::RepContent(rep_type, rep.scale_factor_, rep_contents)); + } + + return true; +} + +// static +bool ImageCache::LoadImageContents(const std::string& path, + bool internal, + ImageType* type, + std::string* contents) { + CEF_REQUIRE_FILE_USER_BLOCKING_THREAD(); + + *type = GetImageType(path); + if (*type == TYPE_NONE) + return false; + + if (internal) { + if (!LoadBinaryResource(path.c_str(), *contents)) + return false; + } else if (!ReadFileToString(path, contents)) { + return false; + } + + return !contents->empty(); +} + +void ImageCache::UpdateCache(const ImageInfoSet& image_info, + const ImageContentSet& contents, + LoadImagesCallback callback) { + CEF_REQUIRE_UI_THREAD(); + + DCHECK_EQ(image_info.size(), contents.size()); + + ImageSet images; + + ImageInfoSet::const_iterator it1 = image_info.begin(); + ImageContentSet::const_iterator it2 = contents.begin(); + for (; it1 != image_info.end() && it2 != contents.end(); ++it1, ++it2) { + const ImageInfo& info = *it1; + const ImageContent& content = *it2; + if (content.image_ || info.id_ == kEmptyId) { + // Image already exists or is intentionally empty. + images.push_back(content.image_); + } else { + CefRefPtr image = CreateImage(info.id_, content); + images.push_back(image); + + // Add the image to the map. + image_map_.insert(std::make_pair(info.id_, image)); + } + } + + std::move(callback).Run(images); +} + +// static +CefRefPtr ImageCache::CreateImage(const std::string& image_id, + const ImageContent& content) { + CEF_REQUIRE_UI_THREAD(); + + // Shouldn't be creating an image if one already exists. + DCHECK(!content.image_); + + if (content.contents_.empty()) + return nullptr; + + CefRefPtr image = CefImage::CreateImage(); + + ImageContent::RepContentSet::const_iterator it = content.contents_.begin(); + for (; it != content.contents_.end(); ++it) { + const ImageContent::RepContent& rep = *it; + if (rep.type_ == TYPE_PNG) { + if (!image->AddPNG(rep.scale_factor_, rep.contents_.c_str(), + rep.contents_.size())) { + LOG(ERROR) << "Failed to create image " << image_id << " for PNG@" + << rep.scale_factor_; + return nullptr; + } + } else if (rep.type_ == TYPE_JPEG) { + if (!image->AddJPEG(rep.scale_factor_, rep.contents_.c_str(), + rep.contents_.size())) { + LOG(ERROR) << "Failed to create image " << image_id << " for JPG@" + << rep.scale_factor_; + return nullptr; + } + } else { + NOTREACHED(); + return nullptr; + } + } + + return image; +} + diff --git a/src/CEF/ImageCache.h b/src/CEF/ImageCache.h new file mode 100644 index 0000000..e307ceb --- /dev/null +++ b/src/CEF/ImageCache.h @@ -0,0 +1,121 @@ +// 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 +#include + +#include "include/base/cef_callback.h" +#include "include/base/cef_ref_counted.h" +#include "include/cef_image.h" +#include "include/wrapper/cef_closure_task.h" +#include "include/wrapper/cef_helpers.h" + + +// Simple image caching implementation. +class ImageCache + : public base::RefCountedThreadSafe { + public: + ImageCache(); + + // Image representation at a specific scale factor. + struct ImageRep { + ImageRep(const std::string& path, float scale_factor); + + // Full file system path. + std::string path_; + + // Image scale factor (usually 1.0f or 2.0f). + float scale_factor_; + }; + using ImageRepSet = std::vector; + + // Unique image that may have multiple representations. + struct ImageInfo { + ImageInfo(const std::string& id, + const ImageRepSet& reps, + bool internal, + bool force_reload); + + // Helper for returning an empty image. + static ImageInfo Empty(); + + // Helpers for creating common representations. + static ImageInfo Create1x(const std::string& id, + const std::string& path_1x, + bool internal); + static ImageInfo Create2x(const std::string& id, + const std::string& path_1x, + const std::string& path_2x, + bool internal); + static ImageInfo Create2x(const std::string& id); + + // Image unique ID. + std::string id_; + + // Image representations to load. + ImageRepSet reps_; + + // True if the image is internal (loaded via LoadBinaryResource). + bool internal_; + + // True to force reload. + bool force_reload_; + }; + using ImageInfoSet = std::vector; + + using ImageSet = std::vector>; + + using LoadImagesCallback = + base::OnceCallback; + + // Loads the images represented by |image_info|. Executes |callback| + // either synchronously or asychronously on the UI thread after completion. + void LoadImages(const ImageInfoSet& image_info, LoadImagesCallback callback); + + // Returns an image that has already been cached. Must be called on the + // UI thread. + CefRefPtr GetCachedImage(const std::string& image_id); + + private: + // Only allow deletion via scoped_refptr. + friend struct CefDeleteOnThread; + friend class base::RefCountedThreadSafe; + + ~ImageCache(); + + enum ImageType { + TYPE_NONE, + TYPE_PNG, + TYPE_JPEG, + }; + + static ImageType GetImageType(const std::string& path); + + struct ImageContent; + using ImageContentSet = std::vector; + + // Load missing image contents on the FILE thread. + void LoadMissing(const ImageInfoSet& image_info, + const ImageSet& images, + LoadImagesCallback callback); + static bool LoadImageContents(const ImageInfo& info, ImageContent* content); + static bool LoadImageContents(const std::string& path, + bool internal, + ImageType* type, + std::string* contents); + + // Create missing CefImage representations on the UI thread. + void UpdateCache(const ImageInfoSet& image_info, + const ImageContentSet& contents, + LoadImagesCallback callback); + static CefRefPtr CreateImage(const std::string& image_id, + const ImageContent& content); + + // Map image ID to image representation. Only accessed on the UI thread. + using ImageMap = std::map>; + ImageMap image_map_; +}; + diff --git a/src/CEF/MainMessageLoop.cpp b/src/CEF/MainMessageLoop.cpp new file mode 100644 index 0000000..23446e0 --- /dev/null +++ b/src/CEF/MainMessageLoop.cpp @@ -0,0 +1,41 @@ +// 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/shared/browser/main_message_loop.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; +} + +MainMessageLoop::~MainMessageLoop() { + g_main_message_loop = nullptr; +} + +// static +MainMessageLoop* MainMessageLoop::Get() { + DCHECK(g_main_message_loop); + return g_main_message_loop; +} + +void MainMessageLoop::PostClosure(base::OnceClosure closure) { + PostTask(CefCreateClosureTask(std::move(closure))); +} + +void MainMessageLoop::PostClosure(const base::RepeatingClosure& closure) { + PostTask(CefCreateClosureTask(closure)); +} + +} // namespace client diff --git a/src/CEF/MainMessageLoop.h b/src/CEF/MainMessageLoop.h new file mode 100644 index 0000000..9641601 --- /dev/null +++ b/src/CEF/MainMessageLoop.h @@ -0,0 +1,103 @@ +// 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/base/cef_callback.h" +#include "include/cef_task.h" + +#if defined(OS_WIN) +#include +#endif + + +// Represents the message loop running on the main application thread in the +// browser process. This will be the same as the CEF UI thread on Linux, OS X +// and Windows when not using multi-threaded message loop mode. The methods of +// this class are thread-safe unless otherwise indicated. +class MainMessageLoop { + public: + // Returns the singleton instance of this object. + static MainMessageLoop* Get(); + + // Run the message loop. The thread that this method is called on will be + // considered the main thread. This blocks until Quit() is called. + virtual int Run() = 0; + + // Quit the message loop. + virtual void Quit() = 0; + + // Post a task for execution on the main message loop. + virtual void PostTask(CefRefPtr task) = 0; + + // Returns true if this message loop runs tasks on the current thread. + virtual bool RunsTasksOnCurrentThread() const = 0; + +#if defined(OS_WIN) + // Set the current modeless dialog on Windows for proper delivery of dialog + // messages when using multi-threaded message loop mode. This method must be + // called from the main thread. See http://support.microsoft.com/kb/71450 for + // background. + virtual void SetCurrentModelessDialog(HWND hWndDialog) = 0; +#endif + + // Post a closure for execution on the main message loop. + void PostClosure(base::OnceClosure closure); + void PostClosure(const base::RepeatingClosure& closure); + + protected: + // Only allow deletion via std::unique_ptr. + friend std::default_delete; + + MainMessageLoop(); + virtual ~MainMessageLoop(); + + private: + DISALLOW_COPY_AND_ASSIGN(MainMessageLoop); +}; + +#define CURRENTLY_ON_MAIN_THREAD() \ + client::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_CLOSURE(closure) \ + client::MainMessageLoop::Get()->PostClosure(closure) + +// Use this struct in conjuction with RefCountedThreadSafe to ensure that an +// object is deleted on the main thread. For example: +// +// class Foo : public base::RefCountedThreadSafe { +// public: +// Foo(); +// void DoSomething(); +// +// private: +// // Allow deletion via scoped_refptr only. +// friend struct DeleteOnMainThread; +// friend class base::RefCountedThreadSafe; +// +// virtual ~Foo() {} +// }; +// +// base::scoped_refptr foo = new Foo(); +// foo->DoSomething(); +// foo = nullptr; // Deletion of |foo| will occur on the main thread. +// +struct DeleteOnMainThread { + template + static void Destruct(const T* x) { + if (CURRENTLY_ON_MAIN_THREAD()) { + delete x; + } else { + client::MainMessageLoop::Get()->PostClosure(base::BindOnce( + &DeleteOnMainThread::Destruct, base::Unretained(x))); + } + } +}; + diff --git a/src/CEF/MainMessageLoopExternalPump.cpp b/src/CEF/MainMessageLoopExternalPump.cpp new file mode 100644 index 0000000..b589f66 --- /dev/null +++ b/src/CEF/MainMessageLoopExternalPump.cpp @@ -0,0 +1,107 @@ +// 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 "tests/shared/browser/main_message_loop_external_pump.h" + +#include + +#include "include/cef_app.h" +#include "include/wrapper/cef_helpers.h" +#include "tests/shared/browser/main_message_loop.h" + +namespace client { + +namespace { + +// Special timer delay placeholder value. Intentionally 32-bit for Windows and +// OS X platform API compatibility. +const int32 kTimerDelayPlaceholder = INT_MAX; + +// The maximum number of milliseconds we're willing to wait between calls to +// DoWork(). +const int64 kMaxTimerDelay = 1000 / 30; // 30fps + +client::MainMessageLoopExternalPump* g_external_message_pump = nullptr; + +} // namespace + +MainMessageLoopExternalPump::MainMessageLoopExternalPump() + : is_active_(false), reentrancy_detected_(false) { + DCHECK(!g_external_message_pump); + g_external_message_pump = this; +} + +MainMessageLoopExternalPump::~MainMessageLoopExternalPump() { + g_external_message_pump = nullptr; +} + +MainMessageLoopExternalPump* MainMessageLoopExternalPump::Get() { + return g_external_message_pump; +} + +void MainMessageLoopExternalPump::OnScheduleWork(int64 delay_ms) { + REQUIRE_MAIN_THREAD(); + + if (delay_ms == kTimerDelayPlaceholder && IsTimerPending()) { + // Don't set the maximum timer requested from DoWork() if a timer event is + // currently pending. + return; + } + + KillTimer(); + + if (delay_ms <= 0) { + // Execute the work immediately. + DoWork(); + } else { + // Never wait longer than the maximum allowed time. + if (delay_ms > kMaxTimerDelay) + delay_ms = kMaxTimerDelay; + + // Results in call to OnTimerTimeout() after the specified delay. + SetTimer(delay_ms); + } +} + +void MainMessageLoopExternalPump::OnTimerTimeout() { + REQUIRE_MAIN_THREAD(); + + KillTimer(); + DoWork(); +} + +void MainMessageLoopExternalPump::DoWork() { + const bool was_reentrant = PerformMessageLoopWork(); + if (was_reentrant) { + // Execute the remaining work as soon as possible. + OnScheduleMessagePumpWork(0); + } else if (!IsTimerPending()) { + // Schedule a timer event at the maximum allowed time. This may be dropped + // in OnScheduleWork() if another timer event is already in-flight. + OnScheduleMessagePumpWork(kTimerDelayPlaceholder); + } +} + +bool MainMessageLoopExternalPump::PerformMessageLoopWork() { + if (is_active_) { + // When CefDoMessageLoopWork() is called there may be various callbacks + // (such as paint and IPC messages) that result in additional calls to this + // method. If re-entrancy is detected we must repost a request again to the + // owner thread to ensure that the discarded call is executed in the future. + reentrancy_detected_ = true; + return false; + } + + reentrancy_detected_ = false; + + is_active_ = true; + CefDoMessageLoopWork(); + is_active_ = false; + + // |reentrancy_detected_| may have changed due to re-entrant calls to this + // method. + return reentrancy_detected_; +} + +} // namespace client diff --git a/src/CEF/MainMessageLoopExternalPump.h b/src/CEF/MainMessageLoopExternalPump.h new file mode 100644 index 0000000..fa3236e --- /dev/null +++ b/src/CEF/MainMessageLoopExternalPump.h @@ -0,0 +1,70 @@ +// 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. + +#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 { + +// This MessageLoop implementation simulates the embedding of CEF into an +// existing host application that runs its own message loop. The scheduling +// implementation provided by this class is very simplistic and does not handle +// all cases (for example, nested message loops on Windows will not function +// correctly). See comments in Chromium's platform-specific +// base/message_loop/message_pump_* source files for additional guidance when +// implementing CefBrowserProcessHandler::OnScheduleMessagePumpWork() in your +// application. Run cefclient or ceftests with the +// "--external-message-pump" command-line flag to test this mode. +class MainMessageLoopExternalPump : public MainMessageLoopStd { + public: + // Creates the singleton instance of this object. Must be called on the main + // application thread. + static std::unique_ptr Create(); + + // Returns the singleton instance of this object. Safe to call from any + // thread. + static MainMessageLoopExternalPump* Get(); + + // Called from CefBrowserProcessHandler::OnScheduleMessagePumpWork() on any + // thread. The platform subclass must implement this method and schedule a + // call to OnScheduleWork() on the main application thread. + virtual void OnScheduleMessagePumpWork(int64 delay_ms) = 0; + + protected: + // Only allow deletion via std::unique_ptr. + friend std::default_delete; + + // Construct and destruct this object on the main application thread. + MainMessageLoopExternalPump(); + ~MainMessageLoopExternalPump(); + + // The platform subclass calls this method on the main application thread in + // response to the OnScheduleMessagePumpWork() call. + void OnScheduleWork(int64 delay_ms); + + // The platform subclass calls this method on the main application thread when + // the pending work timer times out. + void OnTimerTimeout(); + + // Control the pending work timer in the platform subclass. Only called on + // the main application thread. + virtual void SetTimer(int64 delay_ms) = 0; + virtual void KillTimer() = 0; + virtual bool IsTimerPending() = 0; + + private: + // Handle work processing. + void DoWork(); + bool PerformMessageLoopWork(); + + 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 new file mode 100644 index 0000000..ae95358 --- /dev/null +++ b/src/CEF/MainMessageLoopExternalPumpWin.cpp @@ -0,0 +1,154 @@ +// 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 "tests/shared/browser/main_message_loop_external_pump.h" + +#include + +#include + +#include "include/cef_app.h" +#include "tests/shared/browser/util_win.h" + +namespace client { + +namespace { + +// Message sent to get an additional time slice for pumping (processing) another +// task (a series of such messages creates a continuous task pump). +static const int kMsgHaveWork = WM_USER + 1; + +class MainMessageLoopExternalPumpWin : public MainMessageLoopExternalPump { + public: + MainMessageLoopExternalPumpWin(); + ~MainMessageLoopExternalPumpWin(); + + // MainMessageLoopStd methods: + void Quit() override; + int Run() override; + + // MainMessageLoopExternalPump methods: + void OnScheduleMessagePumpWork(int64 delay_ms) override; + + protected: + // MainMessageLoopExternalPump methods: + void SetTimer(int64 delay_ms) override; + void KillTimer() override; + bool IsTimerPending() override { return timer_pending_; } + + private: + static LRESULT CALLBACK WndProc(HWND hwnd, + UINT msg, + WPARAM wparam, + LPARAM lparam); + + // True if a timer event is currently pending. + bool timer_pending_; + + // HWND owned by the thread that CefDoMessageLoopWork should be invoked on. + HWND main_thread_target_; +}; + +MainMessageLoopExternalPumpWin::MainMessageLoopExternalPumpWin() + : timer_pending_(false), main_thread_target_(nullptr) { + HINSTANCE hInstance = GetModuleHandle(nullptr); + const wchar_t* const kClassName = L"CEFMainTargetHWND"; + + WNDCLASSEX wcex = {}; + wcex.cbSize = sizeof(WNDCLASSEX); + wcex.lpfnWndProc = WndProc; + wcex.hInstance = hInstance; + wcex.lpszClassName = kClassName; + RegisterClassEx(&wcex); + + // Create the message handling window. + main_thread_target_ = + CreateWindowW(kClassName, nullptr, WS_OVERLAPPEDWINDOW, 0, 0, 0, 0, + HWND_MESSAGE, nullptr, hInstance, nullptr); + DCHECK(main_thread_target_); + SetUserDataPtr(main_thread_target_, this); +} + +MainMessageLoopExternalPumpWin::~MainMessageLoopExternalPumpWin() { + KillTimer(); + if (main_thread_target_) + DestroyWindow(main_thread_target_); +} + +void MainMessageLoopExternalPumpWin::Quit() { + PostMessage(nullptr, WM_QUIT, 0, 0); +} + +int MainMessageLoopExternalPumpWin::Run() { + // Run the message loop. + MSG msg; + while (GetMessage(&msg, nullptr, 0, 0)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + KillTimer(); + + // We need to run the message pump until it is idle. However we don't have + // that information here so we run the message loop "for a while". + for (int i = 0; i < 10; ++i) { + // Do some work. + CefDoMessageLoopWork(); + + // Sleep to allow the CEF proc to do work. + Sleep(50); + } + + return 0; +} + +void MainMessageLoopExternalPumpWin::OnScheduleMessagePumpWork(int64 delay_ms) { + // This method may be called on any thread. + PostMessage(main_thread_target_, kMsgHaveWork, 0, + static_cast(delay_ms)); +} + +void MainMessageLoopExternalPumpWin::SetTimer(int64 delay_ms) { + DCHECK(!timer_pending_); + DCHECK_GT(delay_ms, 0); + timer_pending_ = true; + ::SetTimer(main_thread_target_, 1, static_cast(delay_ms), nullptr); +} + +void MainMessageLoopExternalPumpWin::KillTimer() { + if (timer_pending_) { + ::KillTimer(main_thread_target_, 1); + timer_pending_ = false; + } +} + +// static +LRESULT CALLBACK MainMessageLoopExternalPumpWin::WndProc(HWND hwnd, + UINT msg, + WPARAM wparam, + LPARAM lparam) { + if (msg == WM_TIMER || msg == kMsgHaveWork) { + MainMessageLoopExternalPumpWin* message_loop = + GetUserDataPtr(hwnd); + if (msg == kMsgHaveWork) { + // OnScheduleMessagePumpWork() request. + const int64 delay_ms = static_cast(lparam); + message_loop->OnScheduleWork(delay_ms); + } else { + // Timer timed out. + message_loop->OnTimerTimeout(); + } + } + 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 new file mode 100644 index 0000000..d329f90 --- /dev/null +++ b/src/CEF/MainMessageLoopStd.cpp @@ -0,0 +1,37 @@ +// 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/shared/browser/main_message_loop_std.h" + +#include "include/cef_app.h" + +namespace client { + +MainMessageLoopStd::MainMessageLoopStd() {} + +int MainMessageLoopStd::Run() { + CefRunMessageLoop(); + return 0; +} + +void MainMessageLoopStd::Quit() { + CefQuitMessageLoop(); +} + +void MainMessageLoopStd::PostTask(CefRefPtr task) { + CefPostTask(TID_UI, task); +} + +bool MainMessageLoopStd::RunsTasksOnCurrentThread() const { + return CefCurrentlyOn(TID_UI); +} + +#if defined(OS_WIN) +void MainMessageLoopStd::SetCurrentModelessDialog(HWND hWndDialog) { + // Nothing to do here. The Chromium message loop implementation will + // internally route dialog messages. +} +#endif + +} // namespace client diff --git a/src/CEF/MainMessageLoopStd.h b/src/CEF/MainMessageLoopStd.h new file mode 100644 index 0000000..3bded74 --- /dev/null +++ b/src/CEF/MainMessageLoopStd.h @@ -0,0 +1,35 @@ +// 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_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 { + +// Represents the main message loop in the browser process. This implementation +// is a light-weight wrapper around the Chromium UI thread. +class MainMessageLoopStd : public MainMessageLoop { + public: + MainMessageLoopStd(); + + // MainMessageLoop methods. + int Run() override; + void Quit() override; + void PostTask(CefRefPtr task) override; + bool RunsTasksOnCurrentThread() const override; + +#if defined(OS_WIN) + void SetCurrentModelessDialog(HWND hWndDialog) override; +#endif + + private: + DISALLOW_COPY_AND_ASSIGN(MainMessageLoopStd); +}; + +} // namespace client + +#endif // CEF_TESTS_SHARED_BROWSER_MAIN_MESSAGE_LOOP_STD_H_ diff --git a/src/CEF/OsrRendererSettings.h b/src/CEF/OsrRendererSettings.h new file mode 100644 index 0000000..7910171 --- /dev/null +++ b/src/CEF/OsrRendererSettings.h @@ -0,0 +1,30 @@ +// 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 "include/internal/cef_types.h" + +struct OsrRendererSettings { + OsrRendererSettings() + : show_update_rect(false), + background_color(0), + shared_texture_enabled(false), + external_begin_frame_enabled(false), + begin_frame_rate(0) {} + + // If true draw a border around update rectangles. + bool show_update_rect; + + // Background color. Enables transparency if the alpha component is 0. + cef_color_t background_color; + + // Render using shared textures. Supported on Windows only via D3D11. + bool shared_texture_enabled; + + // Client implements a BeginFrame timer by calling + // CefBrowserHost::SendExternalBeginFrame at the specified frame rate. + bool external_begin_frame_enabled; + int begin_frame_rate; +}; diff --git a/src/CEF/ResourceUtil.h b/src/CEF/ResourceUtil.h new file mode 100644 index 0000000..85e979e --- /dev/null +++ b/src/CEF/ResourceUtil.h @@ -0,0 +1,33 @@ +// 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 +#include "include/cef_image.h" +#include "include/cef_stream.h" + +#if defined(OS_WIN) +#include "include/wrapper/cef_resource_manager.h" +#endif + + +#if defined(OS_POSIX) +// Returns the directory containing resource files. +bool GetResourceDir(std::string& dir); +#endif + +// Retrieve a resource as a string. +bool LoadBinaryResource(const char* resource_name, std::string& resource_data); + +// Retrieve a resource as a steam reader. +CefRefPtr GetBinaryResourceReader(const char* resource_name); + +#if defined(OS_WIN) +// Create a new provider for loading binary resources. +CefResourceManager::Provider* CreateBinaryResourceProvider( + const std::string& url_path, + const std::string& resource_path_prefix); +#endif + diff --git a/src/CEF/ResourceUtilWin.cpp b/src/CEF/ResourceUtilWin.cpp new file mode 100644 index 0000000..76a5c05 --- /dev/null +++ b/src/CEF/ResourceUtilWin.cpp @@ -0,0 +1,121 @@ +// 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/ResourceUtil.h" + +#include "include/base/cef_logging.h" +#include "include/cef_stream.h" +#include "include/wrapper/cef_byte_read_handler.h" +#include "include/wrapper/cef_stream_resource_handler.h" + + +bool LoadBinaryResource(int binaryId, DWORD& dwSize, LPBYTE& pBytes) { + HINSTANCE hInst = GetModuleHandle(nullptr); + HRSRC hRes = + FindResource(hInst, MAKEINTRESOURCE(binaryId), MAKEINTRESOURCE(256)); + if (hRes) { + HGLOBAL hGlob = LoadResource(hInst, hRes); + if (hGlob) { + dwSize = SizeofResource(hInst, hRes); + pBytes = (LPBYTE)LockResource(hGlob); + if (dwSize > 0 && pBytes) + return true; + } + } + + return false; +} + +// Provider of binary resources. +class BinaryResourceProvider : public CefResourceManager::Provider { + public: + BinaryResourceProvider(const std::string& url_path, + const std::string& resource_path_prefix) + : url_path_(url_path), resource_path_prefix_(resource_path_prefix) { + DCHECK(!url_path.empty()); + if (!resource_path_prefix_.empty() && + resource_path_prefix_[resource_path_prefix_.length() - 1] != '/') { + resource_path_prefix_ += "/"; + } + } + + bool OnRequest(scoped_refptr request) override { + CEF_REQUIRE_IO_THREAD(); + + const std::string& url = request->url(); + if (url.find(url_path_) != 0L) { + // Not handled by this provider. + return false; + } + + CefRefPtr handler; + + std::string relative_path = url.substr(url_path_.length()); + if (!relative_path.empty()) { + if (!resource_path_prefix_.empty()) + relative_path = resource_path_prefix_ + relative_path; + + CefRefPtr stream = + GetBinaryResourceReader(relative_path.data()); + if (stream.get()) { + handler = new CefStreamResourceHandler( + request->mime_type_resolver().Run(url), stream); + } + } + + request->Continue(handler); + return true; + } + + private: + std::string url_path_; + std::string resource_path_prefix_; + + DISALLOW_COPY_AND_ASSIGN(BinaryResourceProvider); +}; + +} // namespace + +// Implemented in resource_util_win_idmap.cc. +extern int GetResourceId(const char* resource_name); + +bool LoadBinaryResource(const char* resource_name, std::string& resource_data) { + int resource_id = GetResourceId(resource_name); + if (resource_id == 0) + return false; + + DWORD dwSize; + LPBYTE pBytes; + + if (LoadBinaryResource(resource_id, dwSize, pBytes)) { + resource_data = std::string(reinterpret_cast(pBytes), dwSize); + return true; + } + + NOTREACHED(); // The resource should be found. + return false; +} + +CefRefPtr GetBinaryResourceReader(const char* resource_name) { + int resource_id = GetResourceId(resource_name); + if (resource_id == 0) + return nullptr; + + DWORD dwSize; + LPBYTE pBytes; + + if (LoadBinaryResource(resource_id, dwSize, pBytes)) { + return CefStreamReader::CreateForHandler( + new CefByteReadHandler(pBytes, dwSize, nullptr)); + } + + NOTREACHED(); // The resource should be found. + return nullptr; +} + +CefResourceManager::Provider* CreateBinaryResourceProvider( + const std::string& url_path, + const std::string& resource_path_prefix) { + return new BinaryResourceProvider(url_path, resource_path_prefix); +} diff --git a/src/CEF/RootWindoManager.cpp b/src/CEF/RootWindoManager.cpp new file mode 100644 index 0000000..c4b066a --- /dev/null +++ b/src/CEF/RootWindoManager.cpp @@ -0,0 +1,441 @@ +// 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/RootWindoManager.h" + +#include + +#include "include/base/cef_callback.h" +#include "include/base/cef_logging.h" +#include "include/wrapper/cef_helpers.h" +#include "CEF/ClientHandlerStd.h" +#include "CEF/HumanAppContext.h" +//#include "tests/cefclient/browser/test_runner.h" +#include "CEF/ExtensionUtil.h" +#include "CEF/FileUtil.h" +#include "CEF/ResourceUtil.h" +#include "CEF/HumanAppSwitches.h" + + +class ClientRequestContextHandler : public CefRequestContextHandler, + public CefExtensionHandler { + public: + ClientRequestContextHandler() {} + + // CefRequestContextHandler methods: + void OnRequestContextInitialized( + CefRefPtr request_context) override { + CEF_REQUIRE_UI_THREAD(); + + CefRefPtr command_line = + CefCommandLine::GetGlobalCommandLine(); + if (command_line->HasSwitch(kLoadExtension)) { + if (HumanAppContext::Get() + ->GetRootWindowManager() + ->request_context_per_browser()) { + // The example extension loading implementation requires all browsers to + // share the same request context. + LOG(ERROR) + << "Cannot mix --load-extension and --request-context-per-browser"; + return; + } + + // Load one or more extension paths specified on the command-line and + // delimited with semicolon. + const std::string& extension_path = + command_line->GetSwitchValue(kLoadExtension); + if (!extension_path.empty()) { + std::string part; + std::istringstream f(extension_path); + while (getline(f, part, ';')) { + if (!part.empty()) + extension_util::LoadExtension(request_context, part, this); + } + } + } + } + + // CefExtensionHandler methods: + void OnExtensionLoaded(CefRefPtr extension) override { + CEF_REQUIRE_UI_THREAD(); + HumanAppContext::Get()->GetRootWindowManager()->AddExtension(extension); + } + + CefRefPtr GetActiveBrowser(CefRefPtr extension, + CefRefPtr browser, + bool include_incognito) override { + CEF_REQUIRE_UI_THREAD(); + + // Return the browser for the active/foreground window. + CefRefPtr active_browser = + HumanAppContext::Get()->GetRootWindowManager()->GetActiveBrowser(); + if (!active_browser) { + LOG(WARNING) + << "No active browser available for extension " + << browser->GetHost()->GetExtension()->GetIdentifier().ToString(); + } else { + // The active browser should not be hosting an extension. + DCHECK(!active_browser->GetHost()->GetExtension()); + } + return active_browser; + } + + private: + IMPLEMENT_REFCOUNTING(ClientRequestContextHandler); + DISALLOW_COPY_AND_ASSIGN(ClientRequestContextHandler); +}; + +RootWindowManager::RootWindowManager(bool terminate_when_all_windows_closed) + : terminate_when_all_windows_closed_(terminate_when_all_windows_closed) { + CefRefPtr command_line = + CefCommandLine::GetGlobalCommandLine(); + DCHECK(command_line.get()); + request_context_per_browser_ = + command_line->HasSwitch(kRequestContextPerBrowser); + request_context_shared_cache_ = + command_line->HasSwitch(kRequestContextSharedCache); +} + +RootWindowManager::~RootWindowManager() { + // All root windows should already have been destroyed. + DCHECK(root_windows_.empty()); +} + +scoped_refptr RootWindowManager::CreateRootWindow( + std::unique_ptr config) { + CefBrowserSettings settings; + HumanAppContext::Get()->PopulateBrowserSettings(&settings); + + scoped_refptr root_window = + RootWindow::Create(HumanAppContext::Get()->UseViews()); + root_window->Init(this, std::move(config), settings); + + // Store a reference to the root window on the main thread. + OnRootWindowCreated(root_window); + + return root_window; +} + +scoped_refptr RootWindowManager::CreateRootWindowAsPopup( + bool with_controls, + bool with_osr, + const CefPopupFeatures& popupFeatures, + CefWindowInfo& windowInfo, + CefRefPtr& client, + CefBrowserSettings& settings) { + CEF_REQUIRE_UI_THREAD(); + + HumanAppContext::Get()->PopulateBrowserSettings(&settings); + + if (HumanAppContext::Get()->UseDefaultPopup()) { + // Use default window creation for the popup. A new |client| instance is + // still required by cefclient architecture. + client = new ClientHandlerStd(/*delegate=*/nullptr, with_controls, + /*startup_url=*/CefString()); + return nullptr; + } + + if (!temp_window_) { + // TempWindow must be created on the UI thread. + temp_window_.reset(new TempWindow()); + } + + scoped_refptr root_window = + RootWindow::Create(HumanAppContext::Get()->UseViews()); + root_window->InitAsPopup(this, with_controls, with_osr, popupFeatures, + windowInfo, client, settings); + + // Store a reference to the root window on the main thread. + OnRootWindowCreated(root_window); + + return root_window; +} + +scoped_refptr RootWindowManager::CreateRootWindowAsExtension( + CefRefPtr extension, + const CefRect& source_bounds, + CefRefPtr parent_window, + base::OnceClosure close_callback, + bool with_controls, + bool with_osr) { + const std::string& extension_url = extension_util::GetExtensionURL(extension); + if (extension_url.empty()) { + NOTREACHED() << "Extension cannot be loaded directly."; + return nullptr; + } + + // Create an initially hidden browser window that loads the extension URL. + // We'll show the window when the desired size becomes available via + // ClientHandler::OnAutoResize. + auto config = std::make_unique(); + config->with_controls = with_controls; + config->with_osr = with_osr; + config->with_extension = true; + config->initially_hidden = true; + config->source_bounds = source_bounds; + config->parent_window = parent_window; + config->close_callback = std::move(close_callback); + config->url = extension_url; + return CreateRootWindow(std::move(config)); +} + +bool RootWindowManager::HasRootWindowAsExtension( + CefRefPtr extension) { + REQUIRE_MAIN_THREAD(); + + for (auto root_window : root_windows_) { + if (!root_window->WithExtension()) + continue; + + CefRefPtr browser = root_window->GetBrowser(); + if (!browser) + continue; + + CefRefPtr browser_extension = + browser->GetHost()->GetExtension(); + DCHECK(browser_extension); + if (browser_extension->GetIdentifier() == extension->GetIdentifier()) + return true; + } + + return false; +} + +scoped_refptr RootWindowManager::GetWindowForBrowser( + int browser_id) const { + REQUIRE_MAIN_THREAD(); + + for (auto root_window : root_windows_) { + CefRefPtr browser = root_window->GetBrowser(); + if (browser.get() && browser->GetIdentifier() == browser_id) + return root_window; + } + return nullptr; +} + +scoped_refptr RootWindowManager::GetActiveRootWindow() const { + REQUIRE_MAIN_THREAD(); + return active_root_window_; +} + +CefRefPtr RootWindowManager::GetActiveBrowser() const { + base::AutoLock lock_scope(active_browser_lock_); + return active_browser_; +} + +void RootWindowManager::CloseAllWindows(bool force) { + if (!CURRENTLY_ON_MAIN_THREAD()) { + // Execute this method on the main thread. + MAIN_POST_CLOSURE(base::BindOnce(&RootWindowManager::CloseAllWindows, + base::Unretained(this), force)); + return; + } + + if (root_windows_.empty()) + return; + + // Use a copy of |root_windows_| because the original set may be modified + // in OnRootWindowDestroyed while iterating. + RootWindowSet root_windows = root_windows_; + + for (auto root_window : root_windows) { + root_window->Close(force); + } +} + +void RootWindowManager::AddExtension(CefRefPtr extension) { + if (!CURRENTLY_ON_MAIN_THREAD()) { + // Execute this method on the main thread. + MAIN_POST_CLOSURE(base::BindOnce(&RootWindowManager::AddExtension, + base::Unretained(this), extension)); + return; + } + + // Don't track extensions that can't be loaded directly. + if (extension_util::GetExtensionURL(extension).empty()) + return; + + // Don't add the same extension multiple times. + ExtensionSet::const_iterator it = extensions_.begin(); + for (; it != extensions_.end(); ++it) { + if ((*it)->GetIdentifier() == extension->GetIdentifier()) + return; + } + + extensions_.insert(extension); + NotifyExtensionsChanged(); +} + +void RootWindowManager::OnRootWindowCreated( + scoped_refptr root_window) { + if (!CURRENTLY_ON_MAIN_THREAD()) { + // Execute this method on the main thread. + MAIN_POST_CLOSURE(base::BindOnce(&RootWindowManager::OnRootWindowCreated, + base::Unretained(this), root_window)); + return; + } + + root_windows_.insert(root_window); + if (!root_window->WithExtension()) { + root_window->OnExtensionsChanged(extensions_); + + if (root_windows_.size() == 1U) { + // The first non-extension root window should be considered the active + // window. + OnRootWindowActivated(root_window.get()); + } + } +} + +void RootWindowManager::NotifyExtensionsChanged() { + REQUIRE_MAIN_THREAD(); + + for (auto root_window : root_windows_) { + if (!root_window->WithExtension()) + root_window->OnExtensionsChanged(extensions_); + } +} + +CefRefPtr RootWindowManager::GetRequestContext( + RootWindow* root_window) { + REQUIRE_MAIN_THREAD(); + + if (request_context_per_browser_) { + // Create a new request context for each browser. + CefRequestContextSettings settings; + + CefRefPtr command_line = + CefCommandLine::GetGlobalCommandLine(); + if (command_line->HasSwitch(kCachePath)) { + if (request_context_shared_cache_) { + // Give each browser the same cache path. The resulting context objects + // will share the same storage internally. + CefString(&settings.cache_path) = + command_line->GetSwitchValue(kCachePath); + } else { + // Give each browser a unique cache path. This will create completely + // isolated context objects. + std::stringstream ss; + ss << command_line->GetSwitchValue(kCachePath).ToString() + << kPathSep << time(nullptr); + CefString(&settings.cache_path) = ss.str(); + } + } + + return CefRequestContext::CreateContext(settings, + new ClientRequestContextHandler); + } + + // All browsers will share the global request context. + if (!shared_request_context_.get()) { + shared_request_context_ = CefRequestContext::CreateContext( + CefRequestContext::GetGlobalContext(), new ClientRequestContextHandler); + } + return shared_request_context_; +} + +scoped_refptr RootWindowManager::GetImageCache() { + CEF_REQUIRE_UI_THREAD(); + + if (!image_cache_) { + image_cache_ = new ImageCache; + } + return image_cache_; +} + +void RootWindowManager::OnTest(RootWindow* root_window, int test_id) { + REQUIRE_MAIN_THREAD(); + + //test_runner::RunTest(root_window->GetBrowser(), test_id); +} + +void RootWindowManager::OnExit(RootWindow* root_window) { + REQUIRE_MAIN_THREAD(); + + CloseAllWindows(false); +} + +void RootWindowManager::OnRootWindowDestroyed(RootWindow* root_window) { + REQUIRE_MAIN_THREAD(); + + RootWindowSet::iterator it = root_windows_.find(root_window); + DCHECK(it != root_windows_.end()); + if (it != root_windows_.end()) + root_windows_.erase(it); + + if (root_window == active_root_window_) { + active_root_window_ = nullptr; + + base::AutoLock lock_scope(active_browser_lock_); + active_browser_ = nullptr; + } + + if (terminate_when_all_windows_closed_ && root_windows_.empty()) { + // All windows have closed. Clean up on the UI thread. + CefPostTask(TID_UI, base::BindOnce(&RootWindowManager::CleanupOnUIThread, + base::Unretained(this))); + } +} + +void RootWindowManager::OnRootWindowActivated(RootWindow* root_window) { + REQUIRE_MAIN_THREAD(); + + if (root_window->WithExtension()) { + // We don't want extension apps to become the active RootWindow. + return; + } + + if (root_window == active_root_window_) + return; + + active_root_window_ = root_window; + + { + base::AutoLock lock_scope(active_browser_lock_); + // May be nullptr at this point, in which case we'll make the association in + // OnBrowserCreated. + active_browser_ = active_root_window_->GetBrowser(); + } +} + +void RootWindowManager::OnBrowserCreated(RootWindow* root_window, + CefRefPtr browser) { + REQUIRE_MAIN_THREAD(); + + if (root_window == active_root_window_) { + base::AutoLock lock_scope(active_browser_lock_); + active_browser_ = browser; + } +} + +void RootWindowManager::CreateExtensionWindow( + CefRefPtr extension, + const CefRect& source_bounds, + CefRefPtr parent_window, + base::OnceClosure close_callback, + bool with_osr) { + REQUIRE_MAIN_THREAD(); + + if (!HasRootWindowAsExtension(extension)) { + CreateRootWindowAsExtension(extension, source_bounds, parent_window, + std::move(close_callback), false, with_osr); + } +} + +void RootWindowManager::CleanupOnUIThread() { + CEF_REQUIRE_UI_THREAD(); + + if (temp_window_) { + // TempWindow must be destroyed on the UI thread. + temp_window_.reset(nullptr); + } + + if (image_cache_) { + image_cache_ = nullptr; + } + + // Quit the main message loop. + MainMessageLoop::Get()->Quit(); +} + diff --git a/src/CEF/RootWindoManager.h b/src/CEF/RootWindoManager.h new file mode 100644 index 0000000..9e5d8a9 --- /dev/null +++ b/src/CEF/RootWindoManager.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 "include/cef_command_line.h" +#include "include/cef_request_context_handler.h" + +#include "CEF/ImageCache.h" +#include "CEF/RootWindow.h" +#include "CEF/TempWindow.h" + +// Used to create/manage RootWindow instances. The methods of this class can be +// called from any browser process thread unless otherwise indicated. +class RootWindowManager : public RootWindow::Delegate { + public: + // If |terminate_when_all_windows_closed| is true quit the main message loop + // after all windows have closed. + explicit RootWindowManager(bool terminate_when_all_windows_closed); + + // Create a new top-level native window. This method can be called from + // anywhere. + scoped_refptr CreateRootWindow( + std::unique_ptr config); + + // Create a new native popup window. + // If |with_controls| is true the window will show controls. + // If |with_osr| is true the window will use off-screen rendering. + // This method is called from ClientHandler::CreatePopupWindow() to + // create a new popup or DevTools window. Must be called on the UI thread. + scoped_refptr CreateRootWindowAsPopup( + bool with_controls, + bool with_osr, + const CefPopupFeatures& popupFeatures, + CefWindowInfo& windowInfo, + CefRefPtr& client, + CefBrowserSettings& settings); + + // Create a new top-level native window to host |extension|. + // If |with_controls| is true the window will show controls. + // If |with_osr| is true the window will use off-screen rendering. + // This method can be called from anywhere. + scoped_refptr CreateRootWindowAsExtension( + CefRefPtr extension, + const CefRect& source_bounds, + CefRefPtr parent_window, + base::OnceClosure close_callback, + bool with_controls, + bool with_osr); + + // Returns true if a window hosting |extension| currently exists. Must be + // called on the main thread. + bool HasRootWindowAsExtension(CefRefPtr extension); + + // Returns the RootWindow associated with the specified browser ID. Must be + // called on the main thread. + scoped_refptr GetWindowForBrowser(int browser_id) const; + + // Returns the currently active/foreground RootWindow. May return nullptr. + // Must be called on the main thread. + scoped_refptr GetActiveRootWindow() const; + + // Returns the currently active/foreground browser. May return nullptr. Safe + // to call from any thread. + CefRefPtr GetActiveBrowser() const; + + // Close all existing windows. If |force| is true onunload handlers will not + // be executed. + void CloseAllWindows(bool force); + + // Manage the set of loaded extensions. RootWindows will be notified via the + // OnExtensionsChanged method. + void AddExtension(CefRefPtr extension); + + bool request_context_per_browser() const { + return request_context_per_browser_; + } + + private: + // Allow deletion via std::unique_ptr only. + friend std::default_delete; + + ~RootWindowManager(); + + void OnRootWindowCreated(scoped_refptr root_window); + void NotifyExtensionsChanged(); + + // RootWindow::Delegate methods. + CefRefPtr GetRequestContext( + RootWindow* root_window) override; + scoped_refptr GetImageCache() override; + void OnTest(RootWindow* root_window, int test_id) override; + void OnExit(RootWindow* root_window) override; + void OnRootWindowDestroyed(RootWindow* root_window) override; + void OnRootWindowActivated(RootWindow* root_window) override; + void OnBrowserCreated(RootWindow* root_window, + CefRefPtr browser) override; + void CreateExtensionWindow(CefRefPtr extension, + const CefRect& source_bounds, + CefRefPtr parent_window, + base::OnceClosure close_callback, + bool with_osr) override; + + void CleanupOnUIThread(); + + const bool terminate_when_all_windows_closed_; + bool request_context_per_browser_; + bool request_context_shared_cache_; + + // Existing root windows. Only accessed on the main thread. + typedef std::set> RootWindowSet; + RootWindowSet root_windows_; + + // The currently active/foreground RootWindow. Only accessed on the main + // thread. + scoped_refptr active_root_window_; + + // The currently active/foreground browser. Access is protected by + // |active_browser_lock_; + mutable base::Lock active_browser_lock_; + CefRefPtr active_browser_; + + // Singleton window used as the temporary parent for popup browsers. + std::unique_ptr temp_window_; + + CefRefPtr shared_request_context_; + + // Loaded extensions. Only accessed on the main thread. + ExtensionSet extensions_; + + scoped_refptr image_cache_; + + DISALLOW_COPY_AND_ASSIGN(RootWindowManager); +}; + diff --git a/src/CEF/RootWindow.cpp b/src/CEF/RootWindow.cpp new file mode 100644 index 0000000..3b26eb0 --- /dev/null +++ b/src/CEF/RootWindow.cpp @@ -0,0 +1,43 @@ +// 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/RootWindow.h" + +#include "include/base/cef_callback_helpers.h" + +#include "CEF/HumanAppContext.h" +#include "CEF/RootWindoManager.h" + +RootWindowConfig::RootWindowConfig() + : always_on_top(false), + with_controls(true), + with_osr(false), + with_extension(false), + initially_hidden(false), + url(HumanAppContext::Get()->GetMainURL()) {} + +RootWindow::RootWindow() : delegate_(nullptr) {} + +RootWindow::~RootWindow() {} + +// static +scoped_refptr RootWindow::GetForBrowser(int browser_id) { + RootWindowManager* rootWindowManager = HumanAppContext::Get()->GetRootWindowManager(); + return rootWindowManager->GetWindowForBrowser(browser_id); +} + +void RootWindow::OnExtensionsChanged(const ExtensionSet& extensions) { + REQUIRE_MAIN_THREAD(); + DCHECK(delegate_); + DCHECK(!WithExtension()); + + if (extensions.empty()) + return; + + ExtensionSet::const_iterator it = extensions.begin(); + for (; it != extensions.end(); ++it) { + delegate_->CreateExtensionWindow(*it, CefRect(), nullptr, base::DoNothing(), + WithWindowlessRendering()); + } +} diff --git a/src/CEF/RootWindow.h b/src/CEF/RootWindow.h new file mode 100644 index 0000000..bfb58ce --- /dev/null +++ b/src/CEF/RootWindow.h @@ -0,0 +1,194 @@ +// 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/base/cef_callback_forward.h" +#include "include/base/cef_ref_counted.h" +#include "include/cef_browser.h" +#include "include/views/cef_window.h" + +#include "CEF/HumanApptypes.h" +#include "CEF/ImageCache.h" +#include "CEF/MainMessageLoop.h" + +// Used to configure how a RootWindow is created. +struct RootWindowConfig { + RootWindowConfig(); + + // If true the window will always display above other windows. + bool always_on_top; + + // If true the window will show controls. + bool with_controls; + + // If true the window will use off-screen rendering. + bool with_osr; + + // If true the window is hosting an extension app. + bool with_extension; + + // If true the window will be created initially hidden. + bool initially_hidden; + + // Requested window position. If |bounds| and |source_bounds| are empty the + // default window size and location will be used. + CefRect bounds; + + // Position of the UI element that triggered the window creation. If |bounds| + // is empty and |source_bounds| is non-empty the new window will be positioned + // relative to |source_bounds|. This is currently only implemented for Views- + // based windows when |initially_hidden| is also true. + CefRect source_bounds; + + // Parent window. Only used for Views-based windows. + CefRefPtr parent_window; + + // Callback to be executed when the window is closed. Will be executed on the + // main thread. This is currently only implemented for Views-based windows. + base::OnceClosure close_callback; + + // Initial URL to load. + std::string url; +}; + +typedef std::set> ExtensionSet; + +// Represents a top-level native window in the browser process. While references +// to this object are thread-safe the methods must be called on the main thread +// unless otherwise indicated. +class RootWindow + : public base::RefCountedThreadSafe { + public: + // This interface is implemented by the owner of the RootWindow. The methods + // of this class will be called on the main thread. + class Delegate { + public: + // Called to retrieve the CefRequestContext for browser. Only called for + // non-popup browsers. May return nullptr. + virtual CefRefPtr GetRequestContext( + RootWindow* root_window) = 0; + + // Returns the ImageCache. + virtual scoped_refptr GetImageCache() = 0; + + // Called to execute a test. See resource.h for |test_id| values. + virtual void OnTest(RootWindow* root_window, int test_id) = 0; + + // Called to exit the application. + virtual void OnExit(RootWindow* root_window) = 0; + + // Called when the RootWindow has been destroyed. + virtual void OnRootWindowDestroyed(RootWindow* root_window) = 0; + + // Called when the RootWindow is activated (becomes the foreground window). + virtual void OnRootWindowActivated(RootWindow* root_window) = 0; + + // Called when the browser is created for the RootWindow. + virtual void OnBrowserCreated(RootWindow* root_window, + CefRefPtr browser) = 0; + + // Create a window for |extension|. |source_bounds| are the bounds of the + // UI element, like a button, that triggered the extension. + virtual void CreateExtensionWindow(CefRefPtr extension, + const CefRect& source_bounds, + CefRefPtr parent_window, + base::OnceClosure close_callback, + bool with_osr) = 0; + + protected: + virtual ~Delegate() {} + }; + + // Create a new RootWindow object. This method may be called on any thread. + // Use RootWindowManager::CreateRootWindow() or CreateRootWindowAsPopup() + // instead of calling this method directly. |use_views| will be true if the + // Views framework should be used. + static scoped_refptr Create(bool use_views); + + // Returns the RootWindow associated with the specified |browser_id|. Must be + // called on the main thread. + static scoped_refptr GetForBrowser(int browser_id); + + // Initialize as a normal window. This will create and show a native window + // hosting a single browser instance. This method may be called on any thread. + // |delegate| must be non-nullptr and outlive this object. + // Use RootWindowManager::CreateRootWindow() instead of calling this method + // directly. + virtual void Init(RootWindow::Delegate* delegate, + std::unique_ptr config, + const CefBrowserSettings& settings) = 0; + + // Initialize as a popup window. This is used to attach a new native window to + // a single browser instance that will be created later. The native window + // will be created and shown once the browser is available. This method may be + // called on any thread. |delegate| must be non-nullptr and outlive this + // object. Use RootWindowManager::CreateRootWindowAsPopup() instead of calling + // this method directly. Called on the UI thread. + virtual void InitAsPopup(RootWindow::Delegate* delegate, + bool with_controls, + bool with_osr, + const CefPopupFeatures& popupFeatures, + CefWindowInfo& windowInfo, + CefRefPtr& client, + CefBrowserSettings& settings) = 0; + + enum ShowMode { + ShowNormal, + ShowMinimized, + ShowMaximized, + ShowNoActivate, + }; + + // Show the window. + virtual void Show(ShowMode mode) = 0; + + // Hide the window. + virtual void Hide() = 0; + + // Set the window bounds in screen coordinates. + virtual void SetBounds(int x, int y, size_t width, size_t height) = 0; + + // Close the window. If |force| is true onunload handlers will not be + // executed. + virtual void Close(bool force) = 0; + + // Set the device scale factor. Only used in combination with off-screen + // rendering. + virtual void SetDeviceScaleFactor(float device_scale_factor) = 0; + + // Returns the device scale factor. Only used in combination with off-screen + // rendering. + virtual float GetDeviceScaleFactor() const = 0; + + // Returns the browser that this window contains, if any. + virtual CefRefPtr GetBrowser() const = 0; + + // Returns the native handle for this window, if any. + virtual ClientWindowHandle GetWindowHandle() const = 0; + + // Returns true if this window is using windowless rendering (osr). + virtual bool WithWindowlessRendering() const = 0; + + // Returns true if this window is hosting an extension app. + virtual bool WithExtension() const = 0; + + // Called when the set of loaded extensions changes. The default + // implementation will create a single window instance for each extension. + virtual void OnExtensionsChanged(const ExtensionSet& extensions); + + protected: + // Allow deletion via scoped_refptr only. + friend struct DeleteOnMainThread; + friend class base::RefCountedThreadSafe; + + RootWindow(); + virtual ~RootWindow(); + + Delegate* delegate_; +}; diff --git a/src/CEF/TempWindow.h b/src/CEF/TempWindow.h new file mode 100644 index 0000000..c04951d --- /dev/null +++ b/src/CEF/TempWindow.h @@ -0,0 +1,24 @@ +// 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/HumanApptypes.h" + +#if defined(OS_WIN) +#include "CEF/TempWindowWin.h" +#elif defined(OS_LINUX) +//#include "tests/cefclient/browser/temp_window_x11.h" +#elif defined(OS_MAC) +//#include "tests/cefclient/browser/temp_window_mac.h" +#endif + +#if defined(OS_WIN) +typedef TempWindowWin TempWindow; +#elif defined(OS_LINUX) +typedef TempWindowX11 TempWindow; +#elif defined(OS_MAC) +typedef TempWindowMac TempWindow; +#endif + diff --git a/src/CEF/TempWindowWin.cpp b/src/CEF/TempWindowWin.cpp new file mode 100644 index 0000000..e1b7c51 --- /dev/null +++ b/src/CEF/TempWindowWin.cpp @@ -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. + +#include "CEF/TempWindowWin.h" + +#include + +#include "include/base/cef_logging.h" + + +const wchar_t kWndClass[] = L"Client_TempWindow"; + +// Create the temp window. +HWND CreateTempWindow() { + HINSTANCE hInstance = ::GetModuleHandle(nullptr); + + WNDCLASSEX wc = {0}; + wc.cbSize = sizeof(wc); + wc.lpfnWndProc = DefWindowProc; + wc.hInstance = hInstance; + wc.lpszClassName = kWndClass; + RegisterClassEx(&wc); + + // Create a 1x1 pixel hidden window. + return CreateWindow(kWndClass, 0, WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN, 0, 0, + 1, 1, nullptr, nullptr, hInstance, nullptr); +} + +TempWindowWin* g_temp_window = nullptr; + + +TempWindowWin::TempWindowWin() : hwnd_(nullptr) { + DCHECK(!g_temp_window); + g_temp_window = this; + + hwnd_ = CreateTempWindow(); + CHECK(hwnd_); +} + +TempWindowWin::~TempWindowWin() { + g_temp_window = nullptr; + DCHECK(hwnd_); + DestroyWindow(hwnd_); +} + +// static +CefWindowHandle TempWindowWin::GetWindowHandle() { + DCHECK(g_temp_window); + return g_temp_window->hwnd_; +} diff --git a/src/CEF/TempWindowWin.h b/src/CEF/TempWindowWin.h new file mode 100644 index 0000000..e59d09d --- /dev/null +++ b/src/CEF/TempWindowWin.h @@ -0,0 +1,29 @@ +// 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_base.h" + +// Represents a singleton hidden window that acts as a temporary parent for +// popup browsers. Only accessed on the UI thread. +class TempWindowWin { + public: + // Returns the singleton window handle. + static CefWindowHandle GetWindowHandle(); + + private: + // A single instance will be created/owned by RootWindowManager. + friend class RootWindowManager; + // Allow deletion via std::unique_ptr only. + friend std::default_delete; + + TempWindowWin(); + ~TempWindowWin(); + + CefWindowHandle hwnd_; + + DISALLOW_COPY_AND_ASSIGN(TempWindowWin); +}; + diff --git a/src/CEF/TestRunner.cpp b/src/CEF/TestRunner.cpp new file mode 100644 index 0000000..68665ff --- /dev/null +++ b/src/CEF/TestRunner.cpp @@ -0,0 +1,865 @@ +// 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/test_runner.h" + +#include +#include +#include +#include + +#include "include/base/cef_callback.h" +#include "include/cef_parser.h" +#include "include/cef_task.h" +#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" + +namespace client { +namespace test_runner { + +namespace { + +const char kTestHost[] = "tests"; +const char kLocalHost[] = "localhost"; +const char kTestOrigin[] = "http://tests/"; + +// Pages handled via StringResourceProvider. +const char kTestGetSourcePage[] = "get_source.html"; +const char kTestGetTextPage[] = "get_text.html"; + +// Set page data and navigate the browser. Used in combination with +// StringResourceProvider. +void LoadStringResourcePage(CefRefPtr browser, + const std::string& page, + const std::string& data) { + CefRefPtr client = browser->GetHost()->GetClient(); + ClientHandler* client_handler = static_cast(client.get()); + client_handler->SetStringResource(page, data); + browser->GetMainFrame()->LoadURL(kTestOrigin + page); +} + +// Replace all instances of |from| with |to| in |str|. +std::string StringReplace(const std::string& str, + const std::string& from, + const std::string& to) { + std::string result = str; + std::string::size_type pos = 0; + std::string::size_type from_len = from.length(); + std::string::size_type to_len = to.length(); + do { + pos = result.find(from, pos); + if (pos != std::string::npos) { + result.replace(pos, from_len, to); + pos += to_len; + } + } while (pos != std::string::npos); + return result; +} + +void RunGetSourceTest(CefRefPtr browser) { + class Visitor : public CefStringVisitor { + public: + explicit Visitor(CefRefPtr browser) : browser_(browser) {} + virtual void Visit(const CefString& string) override { + std::string source = StringReplace(string, "<", "<"); + source = StringReplace(source, ">", ">"); + std::stringstream ss; + ss << "Source:
" << source
+         << "
"; + LoadStringResourcePage(browser_, kTestGetSourcePage, ss.str()); + } + + private: + CefRefPtr browser_; + IMPLEMENT_REFCOUNTING(Visitor); + }; + + browser->GetMainFrame()->GetSource(new Visitor(browser)); +} + +void RunGetTextTest(CefRefPtr browser) { + class Visitor : public CefStringVisitor { + public: + explicit Visitor(CefRefPtr browser) : browser_(browser) {} + virtual void Visit(const CefString& string) override { + std::string text = StringReplace(string, "<", "<"); + text = StringReplace(text, ">", ">"); + std::stringstream ss; + ss << "Text:
" << text
+         << "
"; + LoadStringResourcePage(browser_, kTestGetTextPage, ss.str()); + } + + private: + CefRefPtr browser_; + IMPLEMENT_REFCOUNTING(Visitor); + }; + + browser->GetMainFrame()->GetText(new Visitor(browser)); +} + +void RunRequestTest(CefRefPtr browser) { + // Create a new request + CefRefPtr request(CefRequest::Create()); + + if (browser->GetMainFrame()->GetURL().ToString().find("http://tests/") != 0) { + // The LoadRequest method will fail with "bad IPC message" reason + // INVALID_INITIATOR_ORIGIN (213) unless you first navigate to the + // request origin using some other mechanism (LoadURL, link click, etc). + Alert(browser, + "Please first navigate to a http://tests/ URL. " + "For example, first load Tests > Other Tests."); + return; + } + + // Set the request URL + request->SetURL("http://tests/request"); + + // Add post data to the request. The correct method and content- + // type headers will be set by CEF. + CefRefPtr postDataElement(CefPostDataElement::Create()); + std::string data = "arg1=val1&arg2=val2"; + postDataElement->SetToBytes(data.length(), data.c_str()); + CefRefPtr postData(CefPostData::Create()); + postData->AddElement(postDataElement); + request->SetPostData(postData); + + // Add a custom header + CefRequest::HeaderMap headerMap; + headerMap.insert(std::make_pair("X-My-Header", "My Header Value")); + request->SetHeaderMap(headerMap); + + // Load the request + browser->GetMainFrame()->LoadRequest(request); +} + +void RunNewWindowTest(CefRefPtr browser) { + auto config = std::make_unique(); + config->with_controls = true; + config->with_osr = browser->GetHost()->IsWindowRenderingDisabled(); + MainContext::Get()->GetRootWindowManager()->CreateRootWindow( + std::move(config)); +} + +void RunPopupWindowTest(CefRefPtr browser) { + browser->GetMainFrame()->ExecuteJavaScript( + "window.open('http://www.google.com');", "about:blank", 0); +} + +void ModifyZoom(CefRefPtr browser, double delta) { + if (!CefCurrentlyOn(TID_UI)) { + // Execute on the UI thread. + CefPostTask(TID_UI, base::BindOnce(&ModifyZoom, browser, delta)); + return; + } + + browser->GetHost()->SetZoomLevel(browser->GetHost()->GetZoomLevel() + delta); +} + +const char kPrompt[] = "Prompt."; +const char kPromptFPS[] = "FPS"; +const char kPromptDSF[] = "DSF"; + +// Handles execution of prompt results. +class PromptHandler : public CefMessageRouterBrowserSide::Handler { + public: + PromptHandler() {} + + // Called due to cefQuery execution. + virtual bool OnQuery(CefRefPtr browser, + CefRefPtr frame, + int64 query_id, + const CefString& request, + bool persistent, + CefRefPtr callback) override { + // Parse |request| which takes the form "Prompt.[type]:[value]". + const std::string& request_str = request; + if (request_str.find(kPrompt) != 0) + return false; + + std::string type = request_str.substr(sizeof(kPrompt) - 1); + size_t delim = type.find(':'); + if (delim == std::string::npos) + return false; + + const std::string& value = type.substr(delim + 1); + type = type.substr(0, delim); + + // Canceling the prompt dialog returns a value of "null". + if (value != "null") { + if (type == kPromptFPS) + SetFPS(browser, atoi(value.c_str())); + else if (type == kPromptDSF) + SetDSF(browser, static_cast(atof(value.c_str()))); + } + + // Nothing is done with the response. + callback->Success(CefString()); + return true; + } + + private: + void SetFPS(CefRefPtr browser, int fps) { + if (fps <= 0) { + // Reset to the default value. + CefBrowserSettings settings; + MainContext::Get()->PopulateBrowserSettings(&settings); + fps = settings.windowless_frame_rate; + } + + browser->GetHost()->SetWindowlessFrameRate(fps); + } + + void SetDSF(CefRefPtr browser, float dsf) { + MainMessageLoop::Get()->PostClosure( + base::BindOnce(&PromptHandler::SetDSFOnMainThread, browser, dsf)); + } + + static void SetDSFOnMainThread(CefRefPtr browser, float dsf) { + RootWindow::GetForBrowser(browser->GetIdentifier()) + ->SetDeviceScaleFactor(dsf); + } +}; + +void Prompt(CefRefPtr browser, + const std::string& type, + const std::string& label, + const std::string& default_value) { + // Prompt the user for a new value. Works as follows: + // 1. Show a prompt() dialog via JavaScript. + // 2. Pass the result to window.cefQuery(). + // 3. Handle the result in PromptHandler::OnQuery. + const std::string& code = "window.cefQuery({'request': '" + + std::string(kPrompt) + type + ":' + prompt('" + + label + "', '" + default_value + "')});"; + browser->GetMainFrame()->ExecuteJavaScript( + code, browser->GetMainFrame()->GetURL(), 0); +} + +void PromptFPS(CefRefPtr browser) { + if (!CefCurrentlyOn(TID_UI)) { + // Execute on the UI thread. + CefPostTask(TID_UI, base::BindOnce(&PromptFPS, browser)); + return; + } + + // Format the default value string. + std::stringstream ss; + ss << browser->GetHost()->GetWindowlessFrameRate(); + + Prompt(browser, kPromptFPS, "Enter FPS", ss.str()); +} + +void PromptDSF(CefRefPtr browser) { + if (!MainMessageLoop::Get()->RunsTasksOnCurrentThread()) { + // Execute on the main thread. + MainMessageLoop::Get()->PostClosure(base::BindOnce(&PromptDSF, browser)); + return; + } + + // Format the default value string. + std::stringstream ss; + ss << RootWindow::GetForBrowser(browser->GetIdentifier()) + ->GetDeviceScaleFactor(); + + Prompt(browser, kPromptDSF, "Enter Device Scale Factor", ss.str()); +} + +void BeginTracing() { + if (!CefCurrentlyOn(TID_UI)) { + // Execute on the UI thread. + CefPostTask(TID_UI, base::BindOnce(&BeginTracing)); + return; + } + + CefBeginTracing(CefString(), nullptr); +} + +void EndTracing(CefRefPtr browser) { + if (!CefCurrentlyOn(TID_UI)) { + // Execute on the UI thread. + CefPostTask(TID_UI, base::BindOnce(&EndTracing, browser)); + return; + } + + class Client : public CefEndTracingCallback, public CefRunFileDialogCallback { + public: + explicit Client(CefRefPtr browser) : browser_(browser) { + RunDialog(); + } + + void RunDialog() { + static const char kDefaultFileName[] = "trace.txt"; + std::string path = MainContext::Get()->GetDownloadPath(kDefaultFileName); + if (path.empty()) + path = kDefaultFileName; + + // Results in a call to OnFileDialogDismissed. + browser_->GetHost()->RunFileDialog( + FILE_DIALOG_SAVE, + /*title=*/CefString(), path, + /*accept_filters=*/std::vector(), this); + } + + void OnFileDialogDismissed( + const std::vector& file_paths) override { + if (!file_paths.empty()) { + // File selected. Results in a call to OnEndTracingComplete. + CefEndTracing(file_paths.front(), this); + } else { + // No file selected. Discard the trace data. + CefEndTracing(CefString(), nullptr); + } + } + + void OnEndTracingComplete(const CefString& tracing_file) override { + Alert(browser_, + "File \"" + tracing_file.ToString() + "\" saved successfully."); + } + + private: + CefRefPtr browser_; + + IMPLEMENT_REFCOUNTING(Client); + }; + + new Client(browser); +} + +void PrintToPDF(CefRefPtr browser) { + if (!CefCurrentlyOn(TID_UI)) { + // Execute on the UI thread. + CefPostTask(TID_UI, base::BindOnce(&PrintToPDF, browser)); + return; + } + + class Client : public CefPdfPrintCallback, public CefRunFileDialogCallback { + public: + explicit Client(CefRefPtr browser) : browser_(browser) { + RunDialog(); + } + + void RunDialog() { + static const char kDefaultFileName[] = "output.pdf"; + std::string path = MainContext::Get()->GetDownloadPath(kDefaultFileName); + if (path.empty()) + path = kDefaultFileName; + + std::vector accept_filters; + accept_filters.push_back(".pdf"); + + // Results in a call to OnFileDialogDismissed. + browser_->GetHost()->RunFileDialog(FILE_DIALOG_SAVE, + /*title=*/CefString(), path, + accept_filters, this); + } + + void OnFileDialogDismissed( + const std::vector& file_paths) override { + if (!file_paths.empty()) { + CefPdfPrintSettings settings; + + // Show the URL in the footer. + settings.header_footer_enabled = true; + CefString(&settings.header_footer_url) = + browser_->GetMainFrame()->GetURL(); + + // Print to the selected PDF file. + browser_->GetHost()->PrintToPDF(file_paths[0], settings, this); + } + } + + void OnPdfPrintFinished(const CefString& path, bool ok) override { + Alert(browser_, "File \"" + path.ToString() + "\" " + + (ok ? "saved successfully." : "failed to save.")); + } + + private: + CefRefPtr browser_; + + IMPLEMENT_REFCOUNTING(Client); + }; + + new Client(browser); +} + +void MuteAudio(CefRefPtr browser, bool mute) { + CefRefPtr host = browser->GetHost(); + host->SetAudioMuted(mute); +} + +void RunOtherTests(CefRefPtr browser) { + browser->GetMainFrame()->LoadURL("http://tests/other_tests"); +} + +// Provider that dumps the request contents. +class RequestDumpResourceProvider : public CefResourceManager::Provider { + public: + explicit RequestDumpResourceProvider(const std::string& url) : url_(url) { + DCHECK(!url.empty()); + } + + bool OnRequest(scoped_refptr request) override { + CEF_REQUIRE_IO_THREAD(); + + const std::string& url = request->url(); + if (url != url_) { + // Not handled by this provider. + return false; + } + + CefResponse::HeaderMap response_headers; + CefRefPtr response = + GetDumpResponse(request->request(), response_headers); + + request->Continue(new CefStreamResourceHandler(200, "OK", "text/html", + response_headers, response)); + return true; + } + + private: + std::string url_; + + DISALLOW_COPY_AND_ASSIGN(RequestDumpResourceProvider); +}; + +// Provider that returns string data for specific pages. Used in combination +// with LoadStringResourcePage(). +class StringResourceProvider : public CefResourceManager::Provider { + public: + StringResourceProvider(const std::set& pages, + StringResourceMap* string_resource_map) + : pages_(pages), string_resource_map_(string_resource_map) { + DCHECK(!pages.empty()); + } + + bool OnRequest(scoped_refptr request) override { + CEF_REQUIRE_IO_THREAD(); + + const std::string& url = request->url(); + if (url.find(kTestOrigin) != 0U) { + // Not handled by this provider. + return false; + } + + const std::string& page = url.substr(strlen(kTestOrigin)); + if (pages_.find(page) == pages_.end()) { + // Not handled by this provider. + return false; + } + + std::string value; + StringResourceMap::const_iterator it = string_resource_map_->find(page); + if (it != string_resource_map_->end()) { + value = it->second; + } else { + value = "No data available"; + } + + CefRefPtr response = CefStreamReader::CreateForData( + static_cast(const_cast(value.c_str())), value.size()); + + request->Continue(new CefStreamResourceHandler( + 200, "OK", "text/html", CefResponse::HeaderMap(), response)); + return true; + } + + private: + const std::set pages_; + + // Only accessed on the IO thread. + StringResourceMap* string_resource_map_; + + DISALLOW_COPY_AND_ASSIGN(StringResourceProvider); +}; + +// Add a file extension to |url| if none is currently specified. +std::string RequestUrlFilter(const std::string& url) { + if (url.find(kTestOrigin) != 0U) { + // Don't filter anything outside of the test origin. + return url; + } + + // Identify where the query or fragment component, if any, begins. + size_t suffix_pos = url.find('?'); + if (suffix_pos == std::string::npos) + suffix_pos = url.find('#'); + + std::string url_base, url_suffix; + if (suffix_pos == std::string::npos) { + url_base = url; + } else { + url_base = url.substr(0, suffix_pos); + url_suffix = url.substr(suffix_pos); + } + + // Identify the last path component. + size_t path_pos = url_base.rfind('/'); + if (path_pos == std::string::npos) + return url; + + const std::string& path_component = url_base.substr(path_pos); + + // Identify if a file extension is currently specified. + size_t ext_pos = path_component.rfind("."); + if (ext_pos != std::string::npos) + return url; + + // Rebuild the URL with a file extension. + 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; + } +} + +std::string DumpRequestContents(CefRefPtr request) { + std::stringstream ss; + + ss << "URL: " << std::string(request->GetURL()); + ss << "\nMethod: " << std::string(request->GetMethod()); + + CefRequest::HeaderMap headerMap; + request->GetHeaderMap(headerMap); + if (headerMap.size() > 0) { + ss << "\nHeaders:"; + CefRequest::HeaderMap::const_iterator it = headerMap.begin(); + for (; it != headerMap.end(); ++it) { + ss << "\n\t" << std::string((*it).first) << ": " + << std::string((*it).second); + } + } + + CefRefPtr postData = request->GetPostData(); + if (postData.get()) { + CefPostData::ElementVector elements; + postData->GetElements(elements); + if (elements.size() > 0) { + ss << "\nPost Data:"; + CefRefPtr element; + CefPostData::ElementVector::const_iterator it = elements.begin(); + for (; it != elements.end(); ++it) { + element = (*it); + if (element->GetType() == PDE_TYPE_BYTES) { + // the element is composed of bytes + ss << "\n\tBytes: "; + if (element->GetBytesCount() == 0) { + ss << "(empty)"; + } else { + // retrieve the data. + size_t size = element->GetBytesCount(); + char* bytes = new char[size]; + element->GetBytes(size, bytes); + ss << std::string(bytes, size); + delete[] bytes; + } + } else if (element->GetType() == PDE_TYPE_FILE) { + ss << "\n\tFile: " << std::string(element->GetFile()); + } + } + } + } + + return ss.str(); +} + +CefRefPtr GetDumpResponse( + CefRefPtr request, + CefResponse::HeaderMap& response_headers) { + std::string origin; + + // Extract the origin request header, if any. It will be specified for + // cross-origin requests. + { + CefRequest::HeaderMap requestMap; + request->GetHeaderMap(requestMap); + + CefRequest::HeaderMap::const_iterator it = requestMap.begin(); + for (; it != requestMap.end(); ++it) { + std::string key = it->first; + std::transform(key.begin(), key.end(), key.begin(), ::tolower); + if (key == "origin") { + origin = it->second; + break; + } + } + } + + if (!origin.empty() && + (origin.find("http://" + std::string(kTestHost)) == 0 || + origin.find("http://" + std::string(kLocalHost)) == 0)) { + // Allow cross-origin XMLHttpRequests from test origins. + response_headers.insert( + std::make_pair("Access-Control-Allow-Origin", origin)); + + // Allow the custom header from the xmlhttprequest.html example. + response_headers.insert( + std::make_pair("Access-Control-Allow-Headers", "My-Custom-Header")); + } + + const std::string& dump = DumpRequestContents(request); + std::string str = + "
" + dump + "
"; + CefRefPtr stream = CefStreamReader::CreateForData( + static_cast(const_cast(str.c_str())), str.size()); + DCHECK(stream); + return stream; +} + +std::string GetDataURI(const std::string& data, const std::string& mime_type) { + return "data:" + mime_type + ";base64," + + CefURIEncode(CefBase64Encode(data.data(), data.size()), false) + .ToString(); +} + +std::string GetErrorString(cef_errorcode_t code) { +// Case condition that returns |code| as a string. +#define CASE(code) \ + case code: \ + return #code + + switch (code) { + CASE(ERR_NONE); + CASE(ERR_FAILED); + CASE(ERR_ABORTED); + CASE(ERR_INVALID_ARGUMENT); + CASE(ERR_INVALID_HANDLE); + CASE(ERR_FILE_NOT_FOUND); + CASE(ERR_TIMED_OUT); + CASE(ERR_FILE_TOO_BIG); + CASE(ERR_UNEXPECTED); + CASE(ERR_ACCESS_DENIED); + CASE(ERR_NOT_IMPLEMENTED); + CASE(ERR_CONNECTION_CLOSED); + CASE(ERR_CONNECTION_RESET); + CASE(ERR_CONNECTION_REFUSED); + CASE(ERR_CONNECTION_ABORTED); + CASE(ERR_CONNECTION_FAILED); + CASE(ERR_NAME_NOT_RESOLVED); + CASE(ERR_INTERNET_DISCONNECTED); + CASE(ERR_SSL_PROTOCOL_ERROR); + CASE(ERR_ADDRESS_INVALID); + CASE(ERR_ADDRESS_UNREACHABLE); + CASE(ERR_SSL_CLIENT_AUTH_CERT_NEEDED); + CASE(ERR_TUNNEL_CONNECTION_FAILED); + CASE(ERR_NO_SSL_VERSIONS_ENABLED); + CASE(ERR_SSL_VERSION_OR_CIPHER_MISMATCH); + CASE(ERR_SSL_RENEGOTIATION_REQUESTED); + CASE(ERR_CERT_COMMON_NAME_INVALID); + CASE(ERR_CERT_DATE_INVALID); + CASE(ERR_CERT_AUTHORITY_INVALID); + CASE(ERR_CERT_CONTAINS_ERRORS); + CASE(ERR_CERT_NO_REVOCATION_MECHANISM); + CASE(ERR_CERT_UNABLE_TO_CHECK_REVOCATION); + CASE(ERR_CERT_REVOKED); + CASE(ERR_CERT_INVALID); + CASE(ERR_CERT_END); + CASE(ERR_INVALID_URL); + CASE(ERR_DISALLOWED_URL_SCHEME); + CASE(ERR_UNKNOWN_URL_SCHEME); + CASE(ERR_TOO_MANY_REDIRECTS); + CASE(ERR_UNSAFE_REDIRECT); + CASE(ERR_UNSAFE_PORT); + CASE(ERR_INVALID_RESPONSE); + CASE(ERR_INVALID_CHUNKED_ENCODING); + CASE(ERR_METHOD_NOT_SUPPORTED); + CASE(ERR_UNEXPECTED_PROXY_AUTH); + CASE(ERR_EMPTY_RESPONSE); + CASE(ERR_RESPONSE_HEADERS_TOO_BIG); + CASE(ERR_CACHE_MISS); + CASE(ERR_INSECURE_RESPONSE); + default: + return "UNKNOWN"; + } +} + +void SetupResourceManager(CefRefPtr resource_manager, + StringResourceMap* string_resource_map) { + if (!CefCurrentlyOn(TID_IO)) { + // Execute on the browser IO thread. + CefPostTask(TID_IO, base::BindOnce(SetupResourceManager, resource_manager, + string_resource_map)); + return; + } + + const std::string& test_origin = kTestOrigin; + + // Add the URL filter. + resource_manager->SetUrlFilter(base::BindRepeating(RequestUrlFilter)); + + // Add provider for resource dumps. + resource_manager->AddProvider( + new RequestDumpResourceProvider(test_origin + "request.html"), 0, + std::string()); + + // Set of supported string pages. + std::set string_pages; + string_pages.insert(kTestGetSourcePage); + string_pages.insert(kTestGetTextPage); + + // Add provider for string resources. + resource_manager->AddProvider( + new StringResourceProvider(string_pages, string_resource_map), 0, + std::string()); + +// Add provider for bundled resource files. +#if defined(OS_WIN) + // Read resources from the binary. + resource_manager->AddProvider( + CreateBinaryResourceProvider(test_origin, std::string()), 100, + std::string()); +#elif defined(OS_POSIX) + // Read resources from a directory on disk. + std::string resource_dir; + if (GetResourceDir(resource_dir)) { + resource_manager->AddDirectoryProvider(test_origin, resource_dir, 100, + std::string()); + } +#endif +} + +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(); + if (!browser) + return; + } + + // Escape special characters in the message. + std::string msg = StringReplace(message, "\\", "\\\\"); + msg = StringReplace(msg, "'", "\\'"); + + // Execute a JavaScript alert(). + CefRefPtr frame = browser->GetMainFrame(); + frame->ExecuteJavaScript("alert('" + msg + "');", frame->GetURL(), 0); +} + +bool IsTestURL(const std::string& url, const std::string& path) { + CefURLParts parts; + CefParseURL(url, parts); + + const std::string& url_host = CefString(&parts.host); + if (url_host != kTestHost && url_host != kLocalHost) + return false; + + const std::string& url_path = CefString(&parts.path); + return url_path.find(path) == 0; +} + +void CreateMessageHandlers(MessageHandlerSet& handlers) { + handlers.insert(new PromptHandler); + + // Create the binding test handlers. + binding_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 preferences test handlers. + preferences_test::CreateMessageHandlers(handlers); + + // Create the server test handlers. + server_test::CreateMessageHandlers(handlers); + + // Create the urlrequest test handlers. + urlrequest_test::CreateMessageHandlers(handlers); + + // Create the window test handlers. + window_test::CreateMessageHandlers(handlers); +} + +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, + request, response); +} + +} // namespace test_runner +} // namespace client diff --git a/src/CEF/TestRunner.h b/src/CEF/TestRunner.h new file mode 100644 index 0000000..ed0172d --- /dev/null +++ b/src/CEF/TestRunner.h @@ -0,0 +1,69 @@ +// 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_TEST_RUNNER_H_ +#define CEF_TESTS_CEFCLIENT_BROWSER_TEST_RUNNER_H_ +#pragma once + +#include +#include + +#include "include/cef_browser.h" +#include "include/cef_request.h" +#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); + +// Returns the contents of the CefRequest as a string. +std::string DumpRequestContents(CefRefPtr request); + +// Returns the dump response as a stream. |request| is the request. +// |response_headers| will be populated with extra response headers, if any. +CefRefPtr GetDumpResponse( + CefRefPtr request, + CefResponse::HeaderMap& response_headers); + +// Returns a data: URI with the specified contents. +std::string GetDataURI(const std::string& data, const std::string& mime_type); + +// Returns the string representation of the specified error code. +std::string GetErrorString(cef_errorcode_t code); + +typedef std::map StringResourceMap; + +// Set up the resource manager for tests. +void SetupResourceManager(CefRefPtr resource_manager, + StringResourceMap* string_resource_map); + +// Show a JS alert message. +void Alert(CefRefPtr browser, const std::string& message); + +// Returns true if |url| is a test URL with the specified |path|. This matches +// both http://tests/ and http://localhost:xxxx/. +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); + +// Register scheme handlers for tests. +void RegisterSchemeHandlers(); + +// Create a resource response filter for tests. +CefRefPtr GetResourceResponseFilter( + CefRefPtr browser, + CefRefPtr frame, + CefRefPtr request, + CefRefPtr response); + +} // namespace test_runner +} // namespace client + +#endif // CEF_TESTS_CEFCLIENT_BROWSER_TEST_RUNNER_H_