// Copyright 2020 The Chromium Embedded Framework Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "libcef/browser/browser_contents_delegate.h" #include "libcef/browser/browser_host_base.h" #include "libcef/browser/browser_platform_delegate.h" #include "libcef/browser/browser_util.h" #include "content/public/browser/focused_node_details.h" #include "content/public/browser/keyboard_event_processing_result.h" #include "content/public/browser/native_web_keyboard_event.h" #include "content/public/browser/navigation_entry.h" #include "content/public/browser/navigation_handle.h" #include "content/public/browser/notification_details.h" #include "content/public/browser/notification_source.h" #include "content/public/browser/notification_types.h" #include "content/public/browser/render_view_host.h" #include "third_party/blink/public/mojom/favicon/favicon_url.mojom.h" #include "third_party/blink/public/mojom/input/focus_type.mojom-blink.h" using content::KeyboardEventProcessingResult; CefBrowserContentsDelegate::CefBrowserContentsDelegate( scoped_refptr browser_info) : browser_info_(browser_info) { DCHECK(browser_info_->browser()); } void CefBrowserContentsDelegate::ObserveWebContents( content::WebContents* new_contents) { WebContentsObserver::Observe(new_contents); if (new_contents) { registrar_.reset(new content::NotificationRegistrar); // When navigating through the history, the restored NavigationEntry's title // will be used. If the entry ends up having the same title after we return // to it, as will usually be the case, the // NOTIFICATION_WEB_CONTENTS_TITLE_UPDATED will then be suppressed, since // the NavigationEntry's title hasn't changed. registrar_->Add(this, content::NOTIFICATION_LOAD_STOP, content::Source( &new_contents->GetController())); // Make sure RenderFrameCreated is called at least one time. // Create the frame representation before OnAfterCreated is called for a new // browser. Additionally, RenderFrameCreated is otherwise not called at all // for new popup browsers. RenderFrameCreated(new_contents->GetMainFrame()); } else { registrar_.reset(); } } void CefBrowserContentsDelegate::AddObserver(Observer* observer) { observers_.AddObserver(observer); } void CefBrowserContentsDelegate::RemoveObserver(Observer* observer) { observers_.RemoveObserver(observer); } // |source| may be NULL for navigations in the current tab, or if the // navigation originates from a guest view via MaybeAllowNavigation. content::WebContents* CefBrowserContentsDelegate::OpenURLFromTab( content::WebContents* source, const content::OpenURLParams& params) { bool cancel = false; if (auto c = client()) { if (auto handler = c->GetRequestHandler()) { // May return nullptr for omnibox navigations. auto frame = browser()->GetFrame(params.frame_tree_node_id); if (!frame) frame = browser()->GetMainFrame(); cancel = handler->OnOpenURLFromTab( browser(), frame, params.url.spec(), static_cast(params.disposition), params.user_gesture); } } // Returning nullptr will cancel the navigation. return cancel ? nullptr : web_contents(); } void CefBrowserContentsDelegate::LoadingStateChanged( content::WebContents* source, bool to_different_document) { const int current_index = source->GetController().GetLastCommittedEntryIndex(); const int max_index = source->GetController().GetEntryCount() - 1; const bool is_loading = source->IsLoading(); const bool can_go_back = (current_index > 0); const bool can_go_forward = (current_index < max_index); // This method may be called multiple times in a row with |is_loading| // true as a result of https://crrev.com/5e750ad0. Ignore the 2nd+ times. if (is_loading_ == is_loading && can_go_back_ == can_go_back && can_go_forward_ == can_go_forward) { return; } is_loading_ = is_loading; can_go_back_ = can_go_back; can_go_forward_ = can_go_forward; OnStateChanged(State::kNavigation); if (auto c = client()) { if (auto handler = c->GetLoadHandler()) { auto navigation_lock = browser_info_->CreateNavigationLock(); handler->OnLoadingStateChange(browser(), is_loading, can_go_back, can_go_forward); } } } void CefBrowserContentsDelegate::UpdateTargetURL(content::WebContents* source, const GURL& url) { if (auto c = client()) { if (auto handler = c->GetDisplayHandler()) { handler->OnStatusMessage(browser(), url.spec()); } } } bool CefBrowserContentsDelegate::DidAddMessageToConsole( content::WebContents* source, blink::mojom::ConsoleMessageLevel log_level, const std::u16string& message, int32_t line_no, const std::u16string& source_id) { if (auto c = client()) { if (auto handler = c->GetDisplayHandler()) { // Use LOGSEVERITY_DEBUG for unrecognized |level| values. cef_log_severity_t cef_level = LOGSEVERITY_DEBUG; switch (log_level) { case blink::mojom::ConsoleMessageLevel::kVerbose: cef_level = LOGSEVERITY_DEBUG; break; case blink::mojom::ConsoleMessageLevel::kInfo: cef_level = LOGSEVERITY_INFO; break; case blink::mojom::ConsoleMessageLevel::kWarning: cef_level = LOGSEVERITY_WARNING; break; case blink::mojom::ConsoleMessageLevel::kError: cef_level = LOGSEVERITY_ERROR; break; } return handler->OnConsoleMessage(browser(), cef_level, message, source_id, line_no); } } return false; } void CefBrowserContentsDelegate::DidNavigateMainFramePostCommit( content::WebContents* web_contents) { has_document_ = false; OnStateChanged(State::kDocument); } void CefBrowserContentsDelegate::EnterFullscreenModeForTab( content::RenderFrameHost* requesting_frame, const blink::mojom::FullscreenOptions& options) { OnFullscreenModeChange(/*fullscreen=*/true); } void CefBrowserContentsDelegate::ExitFullscreenModeForTab( content::WebContents* web_contents) { OnFullscreenModeChange(/*fullscreen=*/false); } KeyboardEventProcessingResult CefBrowserContentsDelegate::PreHandleKeyboardEvent( content::WebContents* source, const content::NativeWebKeyboardEvent& event) { if (auto delegate = platform_delegate()) { if (auto c = client()) { if (auto handler = c->GetKeyboardHandler()) { CefKeyEvent cef_event; if (browser_util::GetCefKeyEvent(event, cef_event)) { cef_event.focus_on_editable_field = focus_on_editable_field_; auto event_handle = delegate->GetEventHandle(event); bool is_keyboard_shortcut = false; bool result = handler->OnPreKeyEvent( browser(), cef_event, event_handle, &is_keyboard_shortcut); if (result) { return KeyboardEventProcessingResult::HANDLED; } else if (is_keyboard_shortcut) { return KeyboardEventProcessingResult::NOT_HANDLED_IS_SHORTCUT; } } } } } return KeyboardEventProcessingResult::NOT_HANDLED; } bool CefBrowserContentsDelegate::HandleKeyboardEvent( content::WebContents* source, const content::NativeWebKeyboardEvent& event) { // Check to see if event should be ignored. if (event.skip_in_browser) return false; if (auto delegate = platform_delegate()) { if (auto c = client()) { if (auto handler = c->GetKeyboardHandler()) { CefKeyEvent cef_event; if (browser_util::GetCefKeyEvent(event, cef_event)) { cef_event.focus_on_editable_field = focus_on_editable_field_; auto event_handle = delegate->GetEventHandle(event); if (handler->OnKeyEvent(browser(), cef_event, event_handle)) { return true; } } } } } return false; } void CefBrowserContentsDelegate::RenderFrameCreated( content::RenderFrameHost* render_frame_host) { browser_info_->MaybeCreateFrame(render_frame_host, false /* is_guest_view */); } void CefBrowserContentsDelegate::RenderFrameHostChanged( content::RenderFrameHost* old_host, content::RenderFrameHost* new_host) { // Just in case RenderFrameCreated wasn't called for some reason. RenderFrameCreated(new_host); } void CefBrowserContentsDelegate::RenderFrameDeleted( content::RenderFrameHost* render_frame_host) { const auto frame_id = CefFrameHostImpl::MakeFrameId(render_frame_host); browser_info_->RemoveFrame(render_frame_host); if (focused_frame_ && focused_frame_->GetIdentifier() == frame_id) { focused_frame_ = nullptr; OnStateChanged(State::kFocusedFrame); } } void CefBrowserContentsDelegate::RenderViewReady() { if (auto c = client()) { if (auto handler = c->GetRequestHandler()) { handler->OnRenderViewReady(browser()); } } } void CefBrowserContentsDelegate::RenderProcessGone( base::TerminationStatus status) { cef_termination_status_t ts = TS_ABNORMAL_TERMINATION; if (status == base::TERMINATION_STATUS_PROCESS_WAS_KILLED) ts = TS_PROCESS_WAS_KILLED; else if (status == base::TERMINATION_STATUS_PROCESS_CRASHED) ts = TS_PROCESS_CRASHED; else if (status == base::TERMINATION_STATUS_OOM) ts = TS_PROCESS_OOM; else if (status != base::TERMINATION_STATUS_ABNORMAL_TERMINATION) return; if (auto c = client()) { if (auto handler = c->GetRequestHandler()) { auto navigation_lock = browser_info_->CreateNavigationLock(); handler->OnRenderProcessTerminated(browser(), ts); } } } void CefBrowserContentsDelegate::OnFrameFocused( content::RenderFrameHost* render_frame_host) { CefRefPtr frame = static_cast( browser_info_->GetFrameForHost(render_frame_host).get()); if (!frame || frame->IsFocused()) return; CefRefPtr previous_frame = focused_frame_; if (frame->IsMain()) focused_frame_ = nullptr; else focused_frame_ = frame; if (!previous_frame) { // The main frame is focused by default. previous_frame = browser_info_->GetMainFrame(); } if (previous_frame->GetIdentifier() != frame->GetIdentifier()) { previous_frame->SetFocused(false); frame->SetFocused(true); } OnStateChanged(State::kFocusedFrame); } void CefBrowserContentsDelegate::DocumentAvailableInMainFrame( content::RenderFrameHost* render_frame_host) { has_document_ = true; OnStateChanged(State::kDocument); if (auto c = client()) { if (auto handler = c->GetRequestHandler()) { handler->OnDocumentAvailableInMainFrame(browser()); } } } void CefBrowserContentsDelegate::LoadProgressChanged(double progress) { if (auto c = client()) { if (auto handler = c->GetDisplayHandler()) { handler->OnLoadingProgressChange(browser(), progress); } } } void CefBrowserContentsDelegate::DidStopLoading() { // Notify all renderers that loading has stopped. We used to use // RenderFrameObserver::DidStopLoading in the renderer process but that was // removed in https://crrev.com/3e37dd0ead. However, that callback wasn't // necessarily accurate because it wasn't called in all of the cases where // RenderFrameImpl sends the FrameHostMsg_DidStopLoading message. This adds // an additional round trip but should provide the same or improved // functionality. for (const auto& frame : browser_info_->GetAllFrames()) { frame->MaybeSendDidStopLoading(); } } void CefBrowserContentsDelegate::DidFinishNavigation( content::NavigationHandle* navigation_handle) { const net::Error error_code = navigation_handle->GetNetErrorCode(); // Skip calls where the navigation has not yet committed and there is no // error code. For example, when creating a browser without loading a URL. if (!navigation_handle->HasCommitted() && error_code == net::OK) return; const bool is_main_frame = navigation_handle->IsInMainFrame(); const GURL& url = (error_code == net::OK ? navigation_handle->GetURL() : GURL()); auto browser_info = browser_info_; // May return NULL when starting a new navigation if the previous navigation // caused the renderer process to crash during load. CefRefPtr frame = browser_info->GetFrameForFrameTreeNode( navigation_handle->GetFrameTreeNodeId()); if (!frame) { if (is_main_frame) { frame = browser_info->GetMainFrame(); } else { frame = browser_info->CreateTempSubFrame(CefFrameHostImpl::kInvalidFrameId); } } frame->RefreshAttributes(); if (error_code == net::OK) { // The navigation has been committed and there is no error. DCHECK(navigation_handle->HasCommitted()); // Don't call OnLoadStart for same page navigations (fragments, // history state). if (!navigation_handle->IsSameDocument()) { OnLoadStart(frame.get(), navigation_handle->GetPageTransition()); } if (is_main_frame) { OnAddressChange(url); } } else { // The navigation failed with an error. This may happen before commit // (e.g. network error) or after commit (e.g. response filter error). // If the error happened before commit then this call will originate from // RenderFrameHostImpl::OnDidFailProvisionalLoadWithError. // OnLoadStart/OnLoadEnd will not be called. OnLoadError(frame.get(), navigation_handle->GetURL(), error_code); } } void CefBrowserContentsDelegate::DidFailLoad( content::RenderFrameHost* render_frame_host, const GURL& validated_url, int error_code) { // The navigation failed after commit. OnLoadStart was called so we also // call OnLoadEnd. auto frame = browser_info_->GetFrameForHost(render_frame_host); frame->RefreshAttributes(); OnLoadError(frame, validated_url, error_code); OnLoadEnd(frame, validated_url, error_code); } void CefBrowserContentsDelegate::TitleWasSet(content::NavigationEntry* entry) { // |entry| may be NULL if a popup is created via window.open and never // navigated. if (entry) OnTitleChange(entry->GetTitle()); else if (web_contents()) OnTitleChange(web_contents()->GetTitle()); } void CefBrowserContentsDelegate::PluginCrashed( const base::FilePath& plugin_path, base::ProcessId plugin_pid) { if (auto c = client()) { if (auto handler = c->GetRequestHandler()) { handler->OnPluginCrashed(browser(), plugin_path.value()); } } } void CefBrowserContentsDelegate::DidUpdateFaviconURL( content::RenderFrameHost* render_frame_host, const std::vector& candidates) { if (auto c = client()) { if (auto handler = c->GetDisplayHandler()) { std::vector icon_urls; for (const auto& icon : candidates) { if (icon->icon_type == blink::mojom::FaviconIconType::kFavicon) { icon_urls.push_back(icon->icon_url.spec()); } } if (!icon_urls.empty()) { handler->OnFaviconURLChange(browser(), icon_urls); } } } } void CefBrowserContentsDelegate::OnWebContentsFocused( content::RenderWidgetHost* render_widget_host) { if (auto c = client()) { if (auto handler = c->GetFocusHandler()) { handler->OnGotFocus(browser()); } } } void CefBrowserContentsDelegate::OnFocusChangedInPage( content::FocusedNodeDetails* details) { focus_on_editable_field_ = details->focus_type != blink::mojom::blink::FocusType::kNone && details->is_editable_node; } void CefBrowserContentsDelegate::WebContentsDestroyed() { auto wc = web_contents(); ObserveWebContents(nullptr); for (auto& observer : observers_) { observer.OnWebContentsDestroyed(wc); } } void CefBrowserContentsDelegate::Observe( int type, const content::NotificationSource& source, const content::NotificationDetails& details) { DCHECK_EQ(type, content::NOTIFICATION_LOAD_STOP); if (type == content::NOTIFICATION_LOAD_STOP) { content::NavigationController* controller = content::Source(source).ptr(); OnTitleChange(controller->GetWebContents()->GetTitle()); } } void CefBrowserContentsDelegate::OnLoadEnd(CefRefPtr frame, const GURL& url, int http_status_code) { if (auto c = client()) { if (auto handler = c->GetLoadHandler()) { auto navigation_lock = browser_info_->CreateNavigationLock(); handler->OnLoadEnd(browser(), frame, http_status_code); } } } bool CefBrowserContentsDelegate::OnSetFocus(cef_focus_source_t source) { // SetFocus() might be called while inside the OnSetFocus() callback. If // so, don't re-enter the callback. if (is_in_onsetfocus_) return true; if (auto c = client()) { if (auto handler = c->GetFocusHandler()) { is_in_onsetfocus_ = true; bool handled = handler->OnSetFocus(browser(), source); is_in_onsetfocus_ = false; return handled; } } return false; } CefRefPtr CefBrowserContentsDelegate::client() const { if (auto b = browser()) { return b->GetHost()->GetClient(); } return nullptr; } CefRefPtr CefBrowserContentsDelegate::browser() const { return browser_info_->browser(); } CefBrowserPlatformDelegate* CefBrowserContentsDelegate::platform_delegate() const { auto browser = browser_info_->browser(); if (browser) return browser->platform_delegate(); return nullptr; } void CefBrowserContentsDelegate::OnAddressChange(const GURL& url) { if (auto c = client()) { if (auto handler = c->GetDisplayHandler()) { // On the handler of an address change. handler->OnAddressChange(browser(), browser_info_->GetMainFrame(), url.spec()); } } } void CefBrowserContentsDelegate::OnLoadStart( CefRefPtr frame, ui::PageTransition transition_type) { if (auto c = client()) { if (auto handler = c->GetLoadHandler()) { auto navigation_lock = browser_info_->CreateNavigationLock(); // On the handler that loading has started. handler->OnLoadStart(browser(), frame, static_cast(transition_type)); } } } void CefBrowserContentsDelegate::OnLoadError(CefRefPtr frame, const GURL& url, int error_code) { if (auto c = client()) { if (auto handler = c->GetLoadHandler()) { auto navigation_lock = browser_info_->CreateNavigationLock(); // On the handler that loading has failed. handler->OnLoadError(browser(), frame, static_cast(error_code), net::ErrorToShortString(error_code), url.spec()); } } } void CefBrowserContentsDelegate::OnTitleChange(const std::u16string& title) { if (auto c = client()) { if (auto handler = c->GetDisplayHandler()) { handler->OnTitleChange(browser(), title); } } } void CefBrowserContentsDelegate::OnFullscreenModeChange(bool fullscreen) { if (fullscreen == is_fullscreen_) return; is_fullscreen_ = fullscreen; OnStateChanged(State::kFullscreen); if (auto c = client()) { if (auto handler = c->GetDisplayHandler()) { handler->OnFullscreenModeChange(browser(), fullscreen); } } } void CefBrowserContentsDelegate::OnStateChanged(State state_changed) { for (auto& observer : observers_) { observer.OnStateChanged(state_changed); } }