// Copyright 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 "libcef/browser/views/browser_view_impl.h" #include #include #include "libcef/browser/browser_host_base.h" #include "libcef/browser/browser_util.h" #include "libcef/browser/chrome/views/chrome_browser_view.h" #include "libcef/browser/context.h" #include "libcef/browser/request_context_impl.h" #include "libcef/browser/thread_util.h" #include "libcef/browser/views/widget.h" #include "libcef/browser/views/window_impl.h" #include "libcef/features/runtime.h" #include "chrome/browser/profiles/profile.h" #include "content/public/common/input/native_web_keyboard_event.h" #include "ui/content_accelerators/accelerator_util.h" namespace { std::optional GetGestureCommand( ui::GestureEvent* event) { #if BUILDFLAG(IS_MAC) if (event->details().type() == ui::ET_GESTURE_SWIPE) { if (event->details().swipe_left()) { return CEF_GESTURE_COMMAND_BACK; } else if (event->details().swipe_right()) { return CEF_GESTURE_COMMAND_FORWARD; } } #endif return std::nullopt; } bool ComputeAlloyStyle(CefBrowserViewDelegate* cef_delegate, bool is_devtools_popup) { // Alloy style is not supported with Chrome DevTools popups. const bool supports_alloy_style = cef::IsAlloyRuntimeEnabled() || !is_devtools_popup; const bool supports_chrome_style = cef::IsChromeRuntimeEnabled(); const auto default_style = cef::IsAlloyRuntimeEnabled() ? CEF_RUNTIME_STYLE_ALLOY : CEF_RUNTIME_STYLE_CHROME; auto result_style = default_style; if (cef_delegate) { auto requested_style = cef_delegate->GetBrowserRuntimeStyle(); if (requested_style == CEF_RUNTIME_STYLE_ALLOY) { if (supports_alloy_style) { result_style = requested_style; } else { LOG(ERROR) << "GetBrowserRuntimeStyle() requested Alloy style; only " "Chrome style is supported"; } } else if (requested_style == CEF_RUNTIME_STYLE_CHROME) { if (supports_chrome_style) { result_style = requested_style; } else { LOG(ERROR) << "GetBrowserRuntimeStyle() requested Chrome style; only " "Alloy style is supported"; } } } return result_style == CEF_RUNTIME_STYLE_ALLOY; } } // namespace // static CefRefPtr CefBrowserView::CreateBrowserView( CefRefPtr client, const CefString& url, const CefBrowserSettings& settings, CefRefPtr extra_info, CefRefPtr request_context, CefRefPtr delegate) { return CefBrowserViewImpl::Create(CefWindowInfo(), client, url, settings, extra_info, request_context, delegate); } // static CefRefPtr CefBrowserView::GetForBrowser( CefRefPtr browser) { CEF_REQUIRE_UIT_RETURN(nullptr); auto browser_impl = CefBrowserHostBase::FromBrowser(browser); if (browser_impl && browser_impl->is_views_hosted()) { return browser_impl->GetBrowserView(); } return nullptr; } // static CefRefPtr CefBrowserViewImpl::Create( const CefWindowInfo& window_info, CefRefPtr client, const CefString& url, const CefBrowserSettings& settings, CefRefPtr extra_info, CefRefPtr request_context, CefRefPtr delegate) { CEF_REQUIRE_UIT_RETURN(nullptr); if (!request_context) { request_context = CefRequestContext::GetGlobalContext(); } // Verify that the browser context is valid. Do this here instead of risking // potential browser creation failure when this view is added to the window. auto request_context_impl = static_cast(request_context.get()); if (!request_context_impl->VerifyBrowserContext()) { return nullptr; } CefRefPtr browser_view = new CefBrowserViewImpl(delegate, /*is_devtools_popup=*/false); browser_view->SetPendingBrowserCreateParams( window_info, client, url, settings, extra_info, request_context); browser_view->Initialize(); browser_view->SetDefaults(settings); return browser_view; } // static CefRefPtr CefBrowserViewImpl::CreateForPopup( const CefBrowserSettings& settings, CefRefPtr delegate, bool is_devtools) { CEF_REQUIRE_UIT_RETURN(nullptr); CefRefPtr browser_view = new CefBrowserViewImpl(delegate, is_devtools); browser_view->Initialize(); browser_view->SetDefaults(settings); return browser_view; } void CefBrowserViewImpl::WebContentsCreated( content::WebContents* web_contents) { if (web_view()) { web_view()->SetWebContents(web_contents); } } void CefBrowserViewImpl::WebContentsDestroyed( content::WebContents* web_contents) { // This will always be called before BrowserDestroyed(). DisassociateFromWidget(); if (web_view()) { web_view()->SetWebContents(nullptr); } } void CefBrowserViewImpl::BrowserCreated( CefBrowserHostBase* browser, base::RepeatingClosure on_bounds_changed) { browser_ = browser; on_bounds_changed_ = on_bounds_changed; } void CefBrowserViewImpl::BrowserDestroyed(CefBrowserHostBase* browser) { DCHECK_EQ(browser, browser_); browser_ = nullptr; // If this BrowserView belonged to a Widget then we expect to have received a // call to DisassociateFromWidget(). DCHECK(!cef_widget_); } bool CefBrowserViewImpl::HandleKeyboardEvent( const content::NativeWebKeyboardEvent& event) { if (!root_view()) { return false; } views::FocusManager* focus_manager = root_view()->GetFocusManager(); if (!focus_manager) { return false; } if (HandleAccelerator(event, focus_manager)) { return true; } // Give the CefWindowDelegate a chance to handle the event. if (auto* window_impl = cef_window_impl()) { CefKeyEvent cef_event; if (browser_util::GetCefKeyEvent(event, cef_event) && window_impl->OnKeyEvent(cef_event)) { return true; } } // Proceed with default native handling. return unhandled_keyboard_event_handler_.HandleKeyboardEvent(event, focus_manager); } CefRefPtr CefBrowserViewImpl::GetBrowser() { CEF_REQUIRE_VALID_RETURN(nullptr); return browser_; } CefRefPtr CefBrowserViewImpl::GetChromeToolbar() { CEF_REQUIRE_VALID_RETURN(nullptr); if (!is_alloy_style_) { return chrome_browser_view()->cef_toolbar(); } return nullptr; } void CefBrowserViewImpl::SetPreferAccelerators(bool prefer_accelerators) { CEF_REQUIRE_VALID_RETURN_VOID(); if (web_view()) { web_view()->set_allow_accelerators(prefer_accelerators); } } cef_runtime_style_t CefBrowserViewImpl::GetRuntimeStyle() { CEF_REQUIRE_VALID_RETURN(CEF_RUNTIME_STYLE_DEFAULT); return IsAlloyStyle() ? CEF_RUNTIME_STYLE_ALLOY : CEF_RUNTIME_STYLE_CHROME; } void CefBrowserViewImpl::RequestFocus() { CEF_REQUIRE_VALID_RETURN_VOID(); // Always execute asynchronously to work around issue #3040. CEF_POST_TASK(CEF_UIT, base::BindOnce(&CefBrowserViewImpl::RequestFocusInternal, weak_ptr_factory_.GetWeakPtr())); } void CefBrowserViewImpl::SetBackgroundColor(cef_color_t color) { CEF_REQUIRE_VALID_RETURN_VOID(); ParentClass::SetBackgroundColor(color); if (web_view()) { web_view()->SetResizeBackgroundColor(color); } } void CefBrowserViewImpl::Detach() { ParentClass::Detach(); // root_view() will be nullptr now. DCHECK(!root_view()); if (browser_) { // With the Alloy runtime |browser_| will disappear when WindowDestroyed() // indirectly calls BrowserDestroyed() so keep a reference. CefRefPtr browser = browser_; // Force the browser to be destroyed. browser->WindowDestroyed(); } } void CefBrowserViewImpl::GetDebugInfo(base::Value::Dict* info, bool include_children) { ParentClass::GetDebugInfo(info, include_children); if (browser_) { info->Set("url", browser_->GetMainFrame()->GetURL().ToString()); } } void CefBrowserViewImpl::AddedToWidget() { DCHECK(!cef_widget_); views::Widget* widget = root_view()->GetWidget(); DCHECK(widget); CefWidget* cef_widget = CefWidget::GetForWidget(widget); DCHECK(cef_widget); if (!browser_) { if (cef_widget->IsAlloyStyle() && !is_alloy_style_) { LOG(ERROR) << "Cannot add Chrome style BrowserView to Alloy style Window"; return; } if (cef_widget->IsChromeStyle() && cef_widget->GetThemeProfile()) { LOG(ERROR) << "Cannot add multiple Chrome style BrowserViews"; return; } } if (!browser_ && pending_browser_create_params_) { // Top-level browsers will be created when this view is added to the views // hierarchy. pending_browser_create_params_->browser_view = this; CefBrowserHostBase::Create(*pending_browser_create_params_); DCHECK(browser_); pending_browser_create_params_.reset(nullptr); } cef_widget_ = cef_widget; profile_ = Profile::FromBrowserContext(browser_->GetBrowserContext()); DCHECK(profile_); // May call Widget::ThemeChanged(). cef_widget_->AddAssociatedProfile(profile_); } void CefBrowserViewImpl::RemovedFromWidget() { // With Chrome runtime this may be called after BrowserDestroyed(), in which // case the following call will be a no-op. DisassociateFromWidget(); } void CefBrowserViewImpl::OnBoundsChanged() { if (!on_bounds_changed_.is_null()) { on_bounds_changed_.Run(); } } bool CefBrowserViewImpl::OnGestureEvent(ui::GestureEvent* event) { if (auto command = GetGestureCommand(event)) { if (delegate() && delegate()->OnGestureCommand(this, *command)) { return true; } if (is_alloy_style_ && browser_) { // Default handling for the Alloy runtime. switch (*command) { case CEF_GESTURE_COMMAND_BACK: browser_->GoBack(); break; case CEF_GESTURE_COMMAND_FORWARD: browser_->GoForward(); break; } return true; } } return false; } CefBrowserViewImpl::CefBrowserViewImpl( CefRefPtr delegate, bool is_devtools_popup) : ParentClass(delegate), is_alloy_style_(ComputeAlloyStyle(delegate.get(), is_devtools_popup)), weak_ptr_factory_(this) {} void CefBrowserViewImpl::SetPendingBrowserCreateParams( const CefWindowInfo& window_info, CefRefPtr client, const CefString& url, const CefBrowserSettings& settings, CefRefPtr extra_info, CefRefPtr request_context) { DCHECK(!pending_browser_create_params_); pending_browser_create_params_ = std::make_unique(); pending_browser_create_params_->MaybeSetWindowInfo( window_info, /*allow_alloy_style=*/true, /*allow_chrome_style=*/true); pending_browser_create_params_->client = client; pending_browser_create_params_->url = url; pending_browser_create_params_->settings = settings; pending_browser_create_params_->extra_info = extra_info; pending_browser_create_params_->request_context = request_context; } void CefBrowserViewImpl::SetDefaults(const CefBrowserSettings& settings) { SetBackgroundColor( CefContext::Get()->GetBackgroundColor(&settings, STATE_DISABLED)); } views::View* CefBrowserViewImpl::CreateRootView() { if (!is_alloy_style_) { return new ChromeBrowserView(this); } return new CefBrowserViewView(delegate(), this); } void CefBrowserViewImpl::InitializeRootView() { if (!is_alloy_style_) { chrome_browser_view()->Initialize(); } else { static_cast(root_view())->Initialize(); } } views::WebView* CefBrowserViewImpl::web_view() const { if (!root_view()) { return nullptr; } if (!is_alloy_style_) { return chrome_browser_view()->contents_web_view(); } return static_cast(root_view()); } ChromeBrowserView* CefBrowserViewImpl::chrome_browser_view() const { CHECK(!is_alloy_style_); return static_cast(root_view()); } CefWindowImpl* CefBrowserViewImpl::cef_window_impl() const { // Same implementation as GetWindow(). if (!root_view()) { return nullptr; } CefRefPtr window = view_util::GetWindowFor(root_view()->GetWidget()); return static_cast(window.get()); } bool CefBrowserViewImpl::HandleAccelerator( const content::NativeWebKeyboardEvent& event, views::FocusManager* focus_manager) { // Previous calls to TranslateMessage can generate Char events as well as // RawKeyDown events, even if the latter triggered an accelerator. In these // cases, we discard the Char events. if (event.GetType() == blink::WebInputEvent::Type::kChar && ignore_next_char_event_) { ignore_next_char_event_ = false; return true; } // It's necessary to reset this flag, because a RawKeyDown event may not // always generate a Char event. ignore_next_char_event_ = false; if (event.GetType() == blink::WebInputEvent::Type::kRawKeyDown) { ui::Accelerator accelerator = ui::GetAcceleratorFromNativeWebKeyboardEvent(event); // This is tricky: we want to set ignore_next_char_event_ if // ProcessAccelerator returns true. But ProcessAccelerator might delete // |this| if the accelerator is a "close tab" one. So we speculatively // set the flag and fix it if no event was handled. ignore_next_char_event_ = true; if (focus_manager->ProcessAccelerator(accelerator)) { return true; } // ProcessAccelerator didn't handle the accelerator, so we know both // that |this| is still valid, and that we didn't want to set the flag. ignore_next_char_event_ = false; } return false; } void CefBrowserViewImpl::RequestFocusInternal() { ParentClass::RequestFocus(); } void CefBrowserViewImpl::DisassociateFromWidget() { if (!cef_widget_) { return; } // May call Widget::ThemeChanged(). cef_widget_->RemoveAssociatedProfile(profile_); cef_widget_ = nullptr; profile_ = nullptr; }