// 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 "cefclient/browser/client_handler.h" #include #include #include #include #include #include "include/base/cef_bind.h" #include "include/cef_browser.h" #include "include/cef_frame.h" #include "include/cef_url.h" #include "include/wrapper/cef_closure_task.h" #include "cefclient/browser/main_context.h" #include "cefclient/browser/resource_util.h" #include "cefclient/browser/root_window_manager.h" #include "cefclient/browser/test_runner.h" #include "cefclient/common/client_switches.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_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()); } // 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")); } } // namespace ClientHandler::ClientHandler(Delegate* delegate, bool is_osr, const std::string& startup_url) : is_osr_(is_osr), startup_url_(startup_url), delegate_(delegate), browser_count_(0), console_log_file_(MainContext::Get()->GetConsoleLogPath()), first_console_message_(true), focus_on_editable_field_(false) { DCHECK(!console_log_file_.empty()); #if defined(OS_LINUX) // Provide the GTK-based dialog implementation on Linux. dialog_handler_ = new ClientDialogHandlerGtk(); #endif // Read command line settings. CefRefPtr command_line = CefCommandLine::GetGlobalCommandLine(); mouse_cursor_change_disabled_ = command_line->HasSwitch(switches::kMouseCursorChangeDisabled); } void ClientHandler::DetachDelegate() { if (!CURRENTLY_ON_MAIN_THREAD()) { // Execute this method on the main thread. MAIN_POST_CLOSURE(base::Bind(&ClientHandler::DetachDelegate, this)); return; } DCHECK(delegate_); delegate_ = NULL; } bool ClientHandler::OnProcessMessageReceived( CefRefPtr browser, CefProcessId source_process, CefRefPtr message) { CEF_REQUIRE_UI_THREAD(); if (message_router_->OnProcessMessageReceived(browser, 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; } void ClientHandler::OnBeforeContextMenu( CefRefPtr browser, CefRefPtr frame, CefRefPtr params, CefRefPtr model) { CEF_REQUIRE_UI_THREAD(); 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"); model->AddSeparator(); model->AddItem(CLIENT_ID_INSPECT_ELEMENT, "Inspect Element"); // Test context menu features. BuildTestMenu(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; 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); } bool ClientHandler::OnConsoleMessage(CefRefPtr browser, 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 << "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; } 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 link URLs. if (mask & DRAG_OPERATION_LINK) return true; return false; } bool ClientHandler::OnRequestGeolocationPermission( CefRefPtr browser, const CefString& requesting_url, int request_id, CefRefPtr callback) { CEF_REQUIRE_UI_THREAD(); // Allow geolocation access from all websites. callback->Continue(true); return true; } 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, const CefPopupFeatures& popupFeatures, CefWindowInfo& windowInfo, CefRefPtr& client, CefBrowserSettings& settings, bool* no_javascript_access) { CEF_REQUIRE_IO_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); } // Disable mouse cursor change if requested via the command-line flag. if (mouse_cursor_change_disabled_) browser->GetHost()->SetMouseCursorChangeDisabled(true); 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_ = NULL; } NotifyBrowserClosed(browser); } void ClientHandler::OnLoadingStateChange(CefRefPtr browser, bool isLoading, bool canGoBack, bool canGoForward) { CEF_REQUIRE_UI_THREAD(); 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 is_redirect) { CEF_REQUIRE_UI_THREAD(); message_router_->OnBeforeBrowse(browser, frame); return false; } CefRefPtr ClientHandler::GetResourceHandler( CefRefPtr browser, CefRefPtr frame, CefRefPtr request) { CEF_REQUIRE_IO_THREAD(); return test_runner::GetResourceHandler(browser, frame, request); } 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. callback->Continue(new_size <= max_size); return true; } void ClientHandler::OnProtocolExecution(CefRefPtr browser, const CefString& url, bool& allow_os_execution) { CEF_REQUIRE_UI_THREAD(); std::string urlStr = url; // Allow OS execution of Spotify URIs. if (urlStr.find("spotify:") == 0) allow_os_execution = true; } bool ClientHandler::OnCertificateError( CefRefPtr browser, ErrorCode cert_error, const CefString& request_url, CefRefPtr ssl_info, CefRefPtr callback) { CEF_REQUIRE_UI_THREAD(); CefRefPtr subject = ssl_info->GetSubject(); CefRefPtr issuer = ssl_info->GetIssuer(); // Build a table showing certificate information. std::stringstream ss; ss << "X.509 Certificate Information:" "" << "" "" "" "" "" "" "" "" "" "
FieldValue
Subject" << (subject.get() ? subject->GetDisplayName().ToString() : " ") << "
Issuer" << (issuer.get() ? issuer->GetDisplayName().ToString() : " ") << "
Serial #*" << GetBinaryString(ssl_info->GetSerialNumber()) << "
Valid Start" << GetTimeString(ssl_info->GetValidStart()) << "
Valid Expiry" << GetTimeString(ssl_info->GetValidExpiry()) << "
DER Encoded*" << GetBinaryString(ssl_info->GetDEREncoded()) << "
PEM Encoded*" << GetBinaryString(ssl_info->GetPEMEncoded()) << "
* Displayed value is base64 encoded."; // Load the error page. LoadErrorPage(browser->GetMainFrame(), request_url, cert_error, ss.str()); return false; // Cancel the request. } 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_); } int ClientHandler::GetBrowserCount() const { CEF_REQUIRE_UI_THREAD(); return browser_count_; } void ClientHandler::ShowDevTools(CefRefPtr browser, const CefPoint& inspect_element_at) { CefWindowInfo windowInfo; CefRefPtr client; CefBrowserSettings settings; if (CreatePopupWindow(browser, true, CefPopupFeatures(), windowInfo, client, settings)) { browser->GetHost()->ShowDevTools(windowInfo, client, settings, inspect_element_at); } } void ClientHandler::CloseDevTools(CefRefPtr browser) { browser->GetHost()->CloseDevTools(); } bool ClientHandler::CreatePopupWindow( CefRefPtr browser, bool is_devtools, const CefPopupFeatures& popupFeatures, CefWindowInfo& windowInfo, CefRefPtr& client, CefBrowserSettings& settings) { // Note: This method will be called on multiple threads. // 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( !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::Bind(&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::Bind(&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::Bind(&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::Bind(&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::Bind(&ClientHandler::NotifyTitle, this, title)); return; } if (delegate_) delegate_->OnSetTitle(title); } 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::Bind(&ClientHandler::NotifyLoadingState, this, isLoading, canGoBack, canGoForward)); return; } if (delegate_) delegate_->OnSetLoadingState(isLoading, canGoBack, canGoForward); } 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; } } // namespace client