// 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 "tests/cefclient/browser/client_handler.h" #include #include #include #include #include #include "include/base/cef_callback.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_shared_process_message_builder.h" #include "include/cef_ssl_status.h" #include "include/cef_x509_certificate.h" #include "include/wrapper/cef_closure_task.h" #include "tests/cefclient/browser/main_context.h" #include "tests/cefclient/browser/root_window_manager.h" #include "tests/cefclient/browser/test_runner.h" #include "tests/shared/browser/extension_util.h" #include "tests/shared/browser/resource_util.h" #include "tests/shared/common/binary_value_utils.h" #include "tests/shared/common/client_switches.h" #include "tests/shared/common/string_util.h" namespace client { #if defined(OS_WIN) #define NEWLINE "\r\n" #else #define NEWLINE "\n" #endif namespace { // 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_MEDIA_HANDLING_DISABLED, CLIENT_ID_OFFLINE, CLIENT_ID_TESTMENU_SUBMENU, CLIENT_ID_TESTMENU_CHECKITEM, CLIENT_ID_TESTMENU_RADIOITEM1, CLIENT_ID_TESTMENU_RADIOITEM2, CLIENT_ID_TESTMENU_RADIOITEM3, }; // Must 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 GetTimeString(const CefBaseTime& value) { CefTime time; if (cef_time_from_basetime(value, &time)) { return GetTimeString(time); } else { return "Invalid"; } } 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(); } void OnTestProcessMessageReceived( const CefRefPtr& frame, const CefRefPtr& process_message, const bv_utils::TimePoint& finish_time) { DCHECK(process_message->IsValid()); CefRefPtr input_args = process_message->GetArgumentList(); DCHECK_EQ(input_args->GetSize(), 1U); const auto renderer_msg = bv_utils::GetRendererMsgFromBinary(input_args->GetBinary(0)); CefRefPtr response = CefProcessMessage::Create(bv_utils::kTestSendProcessMessage); CefRefPtr args = response->GetArgumentList(); const auto message_size = std::max(input_args->GetBinary(0)->GetSize(), sizeof(bv_utils::BrowserMessage)); std::vector data(message_size); const auto browser_msg = reinterpret_cast(data.data()); browser_msg->test_id = renderer_msg.test_id; browser_msg->duration = finish_time - renderer_msg.start_time; browser_msg->start_time = bv_utils::Now(); args->SetBinary(0, bv_utils::CreateCefBinaryValue(data)); frame->SendProcessMessage(PID_RENDERER, response); } void OnTestSMRProcessMessageReceived( const CefRefPtr& frame, const CefRefPtr& process_message, const bv_utils::TimePoint& finish_time) { DCHECK(process_message->IsValid()); CefRefPtr region = process_message->GetSharedMemoryRegion(); DCHECK_GE(region->Size(), sizeof(bv_utils::RendererMessage)); const auto renderer_msg = static_cast(region->Memory()); const auto message_size = std::max(region->Size(), sizeof(bv_utils::BrowserMessage)); std::vector data(message_size); const auto browser_msg = reinterpret_cast(data.data()); browser_msg->test_id = renderer_msg->test_id; browser_msg->duration = finish_time - renderer_msg->start_time; browser_msg->start_time = bv_utils::Now(); auto builder = CefSharedProcessMessageBuilder::Create( bv_utils::kTestSendSMRProcessMessage, message_size); bv_utils::CopyDataIntoMemory(data, builder->Memory()); frame->SendProcessMessage(PID_RENDERER, builder->Build()); } bool IsAllowedPageActionIcon(cef_chrome_page_action_icon_type_t icon_type) { // Only the specified icons will be allowed. switch (icon_type) { case CEF_CPAIT_FIND: case CEF_CPAIT_ZOOM: return true; default: break; } return false; } bool IsAllowedToolbarButton(cef_chrome_toolbar_button_type_t button_type) { // All configurable buttons will be disabled. return false; } bool IsAllowedAppMenuCommandId(int command_id) { // Only the commands in this array will be allowed. static const int kAllowedCommandIds[] = { IDC_NEW_WINDOW, IDC_NEW_INCOGNITO_WINDOW, // Zoom buttons. IDC_ZOOM_MENU, IDC_ZOOM_PLUS, IDC_ZOOM_NORMAL, IDC_ZOOM_MINUS, IDC_FULLSCREEN, IDC_PRINT, IDC_FIND, IDC_FIND_NEXT, IDC_FIND_PREVIOUS, // "More tools" sub-menu and contents. IDC_MORE_TOOLS_MENU, IDC_CLEAR_BROWSING_DATA, IDC_MANAGE_EXTENSIONS, IDC_PERFORMANCE, IDC_TASK_MANAGER, IDC_DEV_TOOLS, // Edit buttons. IDC_EDIT_MENU, IDC_CUT, IDC_COPY, IDC_PASTE, IDC_OPTIONS, IDC_EXIT, }; for (size_t i = 0; i < std::size(kAllowedCommandIds); ++i) { if (command_id == kAllowedCommandIds[i]) { return true; } } return false; } bool IsAllowedContextMenuCommandId(int command_id) { // Allow commands added by web content. if (command_id >= IDC_CONTENT_CONTEXT_CUSTOM_FIRST && command_id <= IDC_CONTENT_CONTEXT_CUSTOM_LAST) { return true; } // Allow commands added by extensions. if (command_id >= IDC_EXTENSIONS_CONTEXT_CUSTOM_FIRST && command_id <= IDC_EXTENSIONS_CONTEXT_CUSTOM_LAST) { return true; } // 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 < std::size(kAllowedCommandIds); ++i) { if (command_id == kAllowedCommandIds[i]) { return true; } } return false; } void FilterContextMenuModel(CefRefPtr model) { // Evaluate from the bottom to the top because we'll be removing menu items. for (size_t x = model->GetCount(); x > 0; --x) { const auto i = x - 1; const auto type = model->GetTypeAt(i); if (type == MENUITEMTYPE_SUBMENU) { // Filter sub-menu and remove if empty. auto sub_model = model->GetSubMenuAt(i); FilterContextMenuModel(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 (!IsAllowedContextMenuCommandId(model->GetCommandIdAt(i))) { model->RemoveAt(i); } } } } // 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), delegate_(delegate), console_log_file_(MainContext::Get()->GetConsoleLogPath()) { 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); filter_chrome_commands_ = command_line->HasSwitch(switches::kFilterChromeCommands); #if defined(OS_LINUX) // Optionally use the client-provided GTK dialogs. const bool use_client_dialogs = command_line->HasSwitch(switches::kUseClientDialogs); // Determine if the client-provided GTK dialogs can/should be used. bool require_client_dialogs = false; bool support_client_dialogs = true; if (command_line->HasSwitch(switches::kMultiThreadedMessageLoop)) { // Default/internal GTK dialogs are not supported in combination with // multi-threaded-message-loop because Chromium doesn't support GDK threads. // This does not apply to the JS dialogs which use Views instead of GTK. if (!use_client_dialogs) { LOG(WARNING) << "Client dialogs must be used in combination with " "multi-threaded-message-loop."; } require_client_dialogs = true; } if (MainContext::Get()->UseViews()) { // Client-provided GTK dialogs cannot be used in combination with Views // because the implementation of ClientDialogHandlerGtk requires a top-level // GtkWindow. if (use_client_dialogs) { LOG(ERROR) << "Client dialogs cannot be used in combination with Views."; } support_client_dialogs = false; } if (support_client_dialogs) { if (use_client_dialogs) { js_dialog_handler_ = new ClientDialogHandlerGtk(); } if (use_client_dialogs || require_client_dialogs) { file_dialog_handler_ = js_dialog_handler_ ? js_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(); const auto finish_time = bv_utils::Now(); if (message_router_->OnProcessMessageReceived(browser, frame, source_process, message)) { return true; } // Check for messages from the client renderer. const 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; } if (message_name == bv_utils::kTestSendProcessMessage) { OnTestProcessMessageReceived(frame, message, finish_time); return true; } if (message_name == bv_utils::kTestSendSMRProcessMessage) { OnTestSMRProcessMessageReceived(frame, message, finish_time); 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()); const bool allowed = IsAllowedAppMenuCommandId(command_id) || IsAllowedContextMenuCommandId(command_id); bool block = false; if (filter_chrome_commands_) { // Block all commands that aren't specifically allowed. block = !allowed; } else if (!with_controls_) { // If controls are hidden, block all commands that don't target the current // tab or aren't specifically allowed. block = disposition != CEF_WOD_CURRENT_TAB || !allowed; } if (block) { LOG(INFO) << "Blocking command " << command_id << " with disposition " << disposition; return true; } // Default handling. return false; } bool ClientHandler::IsChromeAppMenuItemVisible(CefRefPtr browser, int command_id) { CEF_REQUIRE_UI_THREAD(); DCHECK(MainContext::Get()->UseChromeRuntime()); if (!filter_chrome_commands_) { return true; } return IsAllowedAppMenuCommandId(command_id); } bool ClientHandler::IsChromePageActionIconVisible( cef_chrome_page_action_icon_type_t icon_type) { CEF_REQUIRE_UI_THREAD(); DCHECK(MainContext::Get()->UseChromeRuntime()); if (!filter_chrome_commands_) { return true; } return IsAllowedPageActionIcon(icon_type); } bool ClientHandler::IsChromeToolbarButtonVisible( cef_chrome_toolbar_button_type_t button_type) { CEF_REQUIRE_UI_THREAD(); DCHECK(MainContext::Get()->UseChromeRuntime()); if (!filter_chrome_commands_) { return true; } return IsAllowedToolbarButton(button_type); } 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_ || filter_chrome_commands_)) { // Remove all disallowed menu items. FilterContextMenuModel(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(); } // Add DevTools items to all context menus. model->AddItem(CLIENT_ID_SHOW_DEVTOOLS, "&Show DevTools"); model->AddItem(CLIENT_ID_CLOSE_DEVTOOLS, "Close DevTools"); if (!use_chrome_runtime) { // Chrome runtime already gives us an "Inspect" menu item. 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_MEDIA_HANDLING_DISABLED, "Media handling disabled"); if (media_handling_disabled_) { model->SetChecked(CLIENT_ID_MEDIA_HANDLING_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_MEDIA_HANDLING_DISABLED: media_handling_disabled_ = !media_handling_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); } 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(); if (target_disposition == CEF_WOD_NEW_PICTURE_IN_PICTURE) { // Use default handling for document picture-in-picture popups. client = nullptr; return false; } // Potentially create a new RootWindow for the popup browser that will be // created asynchronously. CreatePopupWindow(browser, /*is_devtools=*/false, popupFeatures, windowInfo, client, settings); // Allow popup creation. return false; } void ClientHandler::OnBeforeDevToolsPopup( CefRefPtr browser, CefWindowInfo& windowInfo, CefRefPtr& client, CefBrowserSettings& settings, CefRefPtr& extra_info, bool* use_default_window) { CEF_REQUIRE_UI_THREAD(); // Potentially create a new RootWindow for the DevTools popup browser that // will be created immediately after this method returns. if (!CreatePopupWindow(browser, /*is_devtools=*/true, CefPopupFeatures(), windowInfo, client, settings)) { *use_default_window = true; } } 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::OnRequestMediaAccessPermission( CefRefPtr browser, CefRefPtr frame, const CefString& requesting_origin, uint32_t requested_permissions, CefRefPtr callback) { callback->Continue(media_handling_disabled_ ? CEF_MEDIA_PERMISSION_NONE : requested_permissions); return true; } 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 == CEF_WOD_NEW_BACKGROUND_TAB || target_disposition == CEF_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::OnCertificateError(CefRefPtr browser, ErrorCode cert_error, const CefString& request_url, CefRefPtr ssl_info, CefRefPtr callback) { CEF_REQUIRE_UI_THREAD(); if (cert_error == ERR_CERT_COMMON_NAME_INVALID && request_url.ToString().find("https://www.magpcss.com/") == 0U) { // Allow magpcss.com to load despite having a certificate common name of // magpcss.org. 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; } // Convert URLs to lowercase for easier comparison. url = AsciiStrToLower(url); const std::string& start_url = AsciiStrToLower(startup_url_); // 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; CefRefPtr host = browser->GetHost(); // Test if the DevTools browser already exists. if (!MainContext::Get()->UseChromeRuntime() && !host->HasDevTools()) { // Potentially create a new RootWindow for the DevTools browser that will be // created by ShowDevTools(). For Chrome runtime this occurs in // OnBeforeDevToolsPopup instead. CreatePopupWindow(browser, /*is_devtools=*/true, CefPopupFeatures(), windowInfo, client, settings); } // 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. // May return nullptr if UseDefaultPopup() returns true. return !!MainContext::Get()->GetRootWindowManager()->CreateRootWindowAsPopup( with_controls_ && !is_devtools, is_osr_, popupFeatures, windowInfo, client, settings); } 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); } } // namespace client