// Copyright (c) 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 "libcef/browser/content_browser_client.h" #include #include #include "include/cef_version.h" #include "libcef/browser/browser_context.h" #include "libcef/browser/browser_host_impl.h" #include "libcef/browser/browser_info.h" #include "libcef/browser/browser_info_manager.h" #include "libcef/browser/browser_main.h" #include "libcef/browser/browser_message_filter.h" #include "libcef/browser/browser_platform_delegate.h" #include "libcef/browser/context.h" #include "libcef/browser/devtools/devtools_manager_delegate.h" #include "libcef/browser/extensions/extension_system.h" #include "libcef/browser/extensions/extension_web_contents_observer.h" #include "libcef/browser/media_capture_devices_dispatcher.h" #include "libcef/browser/net/chrome_scheme_handler.h" #include "libcef/browser/net_service/login_delegate.h" #include "libcef/browser/net_service/proxy_url_loader_factory.h" #include "libcef/browser/net_service/resource_request_handler_wrapper.h" #include "libcef/browser/plugins/plugin_service_filter.h" #include "libcef/browser/prefs/renderer_prefs.h" #include "libcef/browser/printing/printing_message_filter.h" #include "libcef/browser/speech_recognition_manager_delegate.h" #include "libcef/browser/ssl_info_impl.h" #include "libcef/browser/thread_util.h" #include "libcef/browser/x509_certificate_impl.h" #include "libcef/common/cef_messages.h" #include "libcef/common/cef_switches.h" #include "libcef/common/command_line_impl.h" #include "libcef/common/content_client.h" #include "libcef/common/extensions/extensions_util.h" #include "libcef/common/net/scheme_registration.h" #include "libcef/common/request_impl.h" #include "libcef/common/service_manifests/cef_content_browser_overlay_manifest.h" #include "base/base_switches.h" #include "base/command_line.h" #include "base/files/file_path.h" #include "base/json/json_reader.h" #include "base/path_service.h" #include "base/stl_util.h" #include "base/threading/thread_restrictions.h" #include "cef/grit/cef_resources.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/chrome_content_browser_client.h" #include "chrome/browser/net/system_network_context_manager.h" #include "chrome/browser/plugins/plugin_info_host_impl.h" #include "chrome/browser/plugins/plugin_response_interceptor_url_loader_throttle.h" #include "chrome/browser/plugins/plugin_utils.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/profiles/renderer_updater.h" #include "chrome/browser/profiles/renderer_updater_factory.h" #include "chrome/browser/renderer_host/pepper/chrome_browser_pepper_host_factory.h" #include "chrome/browser/spellchecker/spell_check_host_chrome_impl.h" #include "chrome/common/chrome_paths.h" #include "chrome/common/chrome_switches.h" #include "chrome/common/google_url_loader_throttle.h" #include "chrome/common/pref_names.h" #include "chrome/common/webui_url_constants.h" #include "chrome/grit/browser_resources.h" #include "chrome/grit/generated_resources.h" #include "chrome/services/printing/printing_service.h" #include "components/navigation_interception/intercept_navigation_throttle.h" #include "components/navigation_interception/navigation_params.h" #include "components/spellcheck/common/spellcheck.mojom.h" #include "components/variations/variations_http_header_provider.h" #include "components/version_info/version_info.h" #include "content/browser/frame_host/render_frame_host_impl.h" #include "content/browser/plugin_service_impl.h" #include "content/public/browser/browser_context.h" #include "content/public/browser/browser_ppapi_host.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/child_process_security_policy.h" #include "content/public/browser/client_certificate_delegate.h" #include "content/public/browser/navigation_handle.h" #include "content/public/browser/overlay_window.h" #include "content/public/browser/page_navigator.h" #include "content/public/browser/quota_permission_context.h" #include "content/public/browser/render_frame_host.h" #include "content/public/browser/render_process_host.h" #include "content/public/browser/render_view_host.h" #include "content/public/browser/render_widget_host.h" #include "content/public/browser/render_widget_host_view.h" #include "content/public/browser/storage_partition.h" #include "content/public/browser/web_ui_url_loader_factory.h" #include "content/public/common/content_switches.h" #include "content/public/common/service_names.mojom.h" #include "content/public/common/storage_quota_params.h" #include "content/public/common/url_constants.h" #include "content/public/common/user_agent.h" #include "content/public/common/web_preferences.h" #include "extensions/browser/extension_message_filter.h" #include "extensions/browser/extension_protocols.h" #include "extensions/browser/extension_registry.h" #include "extensions/browser/extensions_browser_client.h" #include "extensions/browser/guest_view/extensions_guest_view_message_filter.h" #include "extensions/browser/guest_view/web_view/web_view_guest.h" #include "extensions/browser/url_loader_factory_manager.h" #include "extensions/common/constants.h" #include "extensions/common/switches.h" #include "mojo/public/cpp/bindings/remote.h" #include "mojo/public/cpp/bindings/self_owned_associated_receiver.h" #include "net/base/auth.h" #include "net/ssl/ssl_cert_request_info.h" #include "ppapi/host/ppapi_host.h" #include "services/network/public/cpp/network_switches.h" #include "services/proxy_resolver/public/mojom/proxy_resolver.mojom.h" #include "services/service_manager/embedder/switches.h" #include "services/service_manager/public/mojom/connector.mojom.h" #include "services/service_manager/sandbox/switches.h" #include "storage/browser/quota/quota_settings.h" #include "third_party/blink/public/mojom/insecure_input/insecure_input_service.mojom.h" #include "third_party/blink/public/mojom/prerender/prerender.mojom.h" #include "third_party/blink/public/web/web_window_features.h" #include "third_party/widevine/cdm/buildflags.h" #include "ui/base/l10n/l10n_util.h" #include "ui/base/resource/resource_bundle.h" #include "ui/base/ui_base_switches.h" #include "url/gurl.h" #if defined(OS_LINUX) #include "libcef/common/widevine_loader.h" #endif #if defined(OS_POSIX) && !defined(OS_MACOSX) #include "base/debug/leak_annotations.h" #include "chrome/common/chrome_paths.h" #include "components/crash/content/browser/crash_handler_host_linux.h" #include "components/crash/core/app/breakpad_linux.h" #include "content/public/common/content_descriptors.h" #endif #if defined(OS_MACOSX) #include "net/ssl/client_cert_store_mac.h" #include "services/video_capture/public/mojom/constants.mojom.h" #endif #if defined(OS_WIN) #include "net/ssl/client_cert_store_win.h" #include "sandbox/win/src/sandbox_policy.h" #endif #if defined(USE_NSS_CERTS) #include "net/ssl/client_cert_store_nss.h" #endif #if BUILDFLAG(HAS_SPELLCHECK_PANEL) #include "chrome/browser/spellchecker/spell_check_panel_host_impl.h" #endif namespace { class CefQuotaCallbackImpl : public CefRequestCallback { public: using CallbackType = content::QuotaPermissionContext::PermissionCallback; explicit CefQuotaCallbackImpl(CallbackType callback) : callback_(std::move(callback)) {} ~CefQuotaCallbackImpl() { if (!callback_.is_null()) { // The callback is still pending. Cancel it now. if (CEF_CURRENTLY_ON_IOT()) { RunNow(std::move(callback_), false); } else { CEF_POST_TASK(CEF_IOT, base::BindOnce(&CefQuotaCallbackImpl::RunNow, std::move(callback_), false)); } } } void Continue(bool allow) override { if (CEF_CURRENTLY_ON_IOT()) { if (!callback_.is_null()) { RunNow(std::move(callback_), allow); } } else { CEF_POST_TASK(CEF_IOT, base::BindOnce(&CefQuotaCallbackImpl::Continue, this, allow)); } } void Cancel() override { Continue(false); } CallbackType Disconnect() WARN_UNUSED_RESULT { return std::move(callback_); } private: static void RunNow(CallbackType callback, bool allow) { CEF_REQUIRE_IOT(); std::move(callback).Run( allow ? content::QuotaPermissionContext::QUOTA_PERMISSION_RESPONSE_ALLOW : content::QuotaPermissionContext:: QUOTA_PERMISSION_RESPONSE_DISALLOW); } CallbackType callback_; IMPLEMENT_REFCOUNTING(CefQuotaCallbackImpl); DISALLOW_COPY_AND_ASSIGN(CefQuotaCallbackImpl); }; class CefAllowCertificateErrorCallbackImpl : public CefRequestCallback { public: typedef base::OnceCallback CallbackType; explicit CefAllowCertificateErrorCallbackImpl(CallbackType callback) : callback_(std::move(callback)) {} ~CefAllowCertificateErrorCallbackImpl() { if (!callback_.is_null()) { // The callback is still pending. Cancel it now. if (CEF_CURRENTLY_ON_UIT()) { RunNow(std::move(callback_), false); } else { CEF_POST_TASK( CEF_UIT, base::BindOnce(&CefAllowCertificateErrorCallbackImpl::RunNow, std::move(callback_), false)); } } } void Continue(bool allow) override { if (CEF_CURRENTLY_ON_UIT()) { if (!callback_.is_null()) { RunNow(std::move(callback_), allow); } } else { CEF_POST_TASK(CEF_UIT, base::Bind(&CefAllowCertificateErrorCallbackImpl::Continue, this, allow)); } } void Cancel() override { Continue(false); } CallbackType Disconnect() WARN_UNUSED_RESULT { return std::move(callback_); } private: static void RunNow(CallbackType callback, bool allow) { CEF_REQUIRE_UIT(); std::move(callback).Run( allow ? content::CERTIFICATE_REQUEST_RESULT_TYPE_CONTINUE : content::CERTIFICATE_REQUEST_RESULT_TYPE_CANCEL); } CallbackType callback_; IMPLEMENT_REFCOUNTING(CefAllowCertificateErrorCallbackImpl); DISALLOW_COPY_AND_ASSIGN(CefAllowCertificateErrorCallbackImpl); }; class CefSelectClientCertificateCallbackImpl : public CefSelectClientCertificateCallback { public: explicit CefSelectClientCertificateCallbackImpl( std::unique_ptr delegate) : delegate_(std::move(delegate)) {} ~CefSelectClientCertificateCallbackImpl() { // If Select has not been called, call it with NULL to continue without any // client certificate. if (delegate_) DoSelect(nullptr); } void Select(CefRefPtr cert) override { if (delegate_) DoSelect(cert); } private: void DoSelect(CefRefPtr cert) { if (CEF_CURRENTLY_ON_UIT()) { RunNow(std::move(delegate_), cert); } else { CEF_POST_TASK( CEF_UIT, base::BindOnce(&CefSelectClientCertificateCallbackImpl::RunNow, std::move(delegate_), cert)); } } static void RunNow( std::unique_ptr delegate, CefRefPtr cert) { CEF_REQUIRE_UIT(); if (cert) { CefX509CertificateImpl* certImpl = static_cast(cert.get()); certImpl->AcquirePrivateKey(base::BindOnce( &CefSelectClientCertificateCallbackImpl::RunWithPrivateKey, std::move(delegate), cert)); return; } delegate->ContinueWithCertificate(nullptr, nullptr); } static void RunWithPrivateKey( std::unique_ptr delegate, CefRefPtr cert, scoped_refptr key) { CEF_REQUIRE_UIT(); DCHECK(cert); if (key) { CefX509CertificateImpl* certImpl = static_cast(cert.get()); delegate->ContinueWithCertificate(certImpl->GetInternalCertObject(), key); } else { delegate->ContinueWithCertificate(nullptr, nullptr); } } std::unique_ptr delegate_; IMPLEMENT_REFCOUNTING(CefSelectClientCertificateCallbackImpl); DISALLOW_COPY_AND_ASSIGN(CefSelectClientCertificateCallbackImpl); }; class CefQuotaPermissionContext : public content::QuotaPermissionContext { public: CefQuotaPermissionContext() {} // The callback will be dispatched on the IO thread. void RequestQuotaPermission(const content::StorageQuotaParams& params, int render_process_id, PermissionCallback callback) override { if (params.storage_type != blink::mojom::StorageType::kPersistent) { // To match Chrome behavior we only support requesting quota with this // interface for Persistent storage type. std::move(callback).Run(QUOTA_PERMISSION_RESPONSE_DISALLOW); return; } bool handled = false; CefRefPtr browser = CefBrowserHostImpl::GetBrowserForFrameRoute(render_process_id, params.render_frame_id); if (browser.get()) { CefRefPtr client = browser->GetClient(); if (client.get()) { CefRefPtr handler = client->GetRequestHandler(); if (handler.get()) { CefRefPtr callbackImpl( new CefQuotaCallbackImpl(std::move(callback))); handled = handler->OnQuotaRequest( browser.get(), params.origin_url.spec(), params.requested_size, callbackImpl.get()); if (!handled) { // May return nullptr if the client has already executed the // callback. callback = callbackImpl->Disconnect(); } } } } if (!handled && !callback.is_null()) { // Disallow the request by default. std::move(callback).Run(QUOTA_PERMISSION_RESPONSE_DISALLOW); } } private: ~CefQuotaPermissionContext() override {} DISALLOW_COPY_AND_ASSIGN(CefQuotaPermissionContext); }; #if defined(OS_POSIX) && !defined(OS_MACOSX) breakpad::CrashHandlerHostLinux* CreateCrashHandlerHost( const std::string& process_type) { base::FilePath dumps_path; base::PathService::Get(chrome::DIR_CRASH_DUMPS, &dumps_path); { ANNOTATE_SCOPED_MEMORY_LEAK; // Uploads will only occur if a non-empty crash URL is specified in // CefMainDelegate::InitCrashReporter. breakpad::CrashHandlerHostLinux* crash_handler = new breakpad::CrashHandlerHostLinux(process_type, dumps_path, true /* upload */); crash_handler->StartUploaderThread(); return crash_handler; } } int GetCrashSignalFD(const base::CommandLine& command_line) { if (!breakpad::IsCrashReporterEnabled()) return -1; // Extensions have the same process type as renderers. if (command_line.HasSwitch(extensions::switches::kExtensionProcess)) { static breakpad::CrashHandlerHostLinux* crash_handler = nullptr; if (!crash_handler) crash_handler = CreateCrashHandlerHost("extension"); return crash_handler->GetDeathSignalSocket(); } std::string process_type = command_line.GetSwitchValueASCII(switches::kProcessType); if (process_type == switches::kRendererProcess) { static breakpad::CrashHandlerHostLinux* crash_handler = nullptr; if (!crash_handler) crash_handler = CreateCrashHandlerHost(process_type); return crash_handler->GetDeathSignalSocket(); } if (process_type == switches::kPpapiPluginProcess) { static breakpad::CrashHandlerHostLinux* crash_handler = nullptr; if (!crash_handler) crash_handler = CreateCrashHandlerHost(process_type); return crash_handler->GetDeathSignalSocket(); } if (process_type == switches::kGpuProcess) { static breakpad::CrashHandlerHostLinux* crash_handler = nullptr; if (!crash_handler) crash_handler = CreateCrashHandlerHost(process_type); return crash_handler->GetDeathSignalSocket(); } return -1; } #endif // defined(OS_POSIX) && !defined(OS_MACOSX) // TODO(cef): We can't currently trust NavigationParams::is_main_frame() because // it's always set to true in // InterceptNavigationThrottle::CheckIfShouldIgnoreNavigation. Remove the // |is_main_frame| argument once this problem is fixed. bool NavigationOnUIThread( bool is_main_frame, int64_t frame_id, int64_t parent_frame_id, int frame_tree_node_id, content::WebContents* source, const navigation_interception::NavigationParams& params) { CEF_REQUIRE_UIT(); bool ignore_navigation = false; CefRefPtr browser = CefBrowserHostImpl::GetBrowserForContents(source); if (browser.get()) { CefRefPtr client = browser->GetClient(); if (client.get()) { CefRefPtr handler = client->GetRequestHandler(); if (handler.get()) { CefRefPtr frame; if (is_main_frame) { frame = browser->GetMainFrame(); } else if (frame_id >= 0) { frame = browser->GetFrame(frame_id); } if (!frame && frame_tree_node_id >= 0) { frame = browser->GetFrameForFrameTreeNode(frame_tree_node_id); } if (!frame) { // Create a temporary frame object for navigation of sub-frames that // don't yet exist. frame = browser->browser_info()->CreateTempSubFrame(parent_frame_id); } CefRefPtr request = new CefRequestImpl(); request->Set(params, is_main_frame); request->SetReadOnly(true); // Initiating a new navigation in OnBeforeBrowse will delete the // InterceptNavigationThrottle that currently owns this callback, // resulting in a crash. Use the lock to prevent that. std::unique_ptr navigation_lock = browser->CreateNavigationLock(); ignore_navigation = handler->OnBeforeBrowse( browser.get(), frame, request.get(), params.has_user_gesture(), params.is_redirect()); } } } return ignore_navigation; } // From chrome/browser/plugins/chrome_content_browser_client_plugins_part.cc. void BindPluginInfoHost( int render_process_id, mojo::PendingAssociatedReceiver receiver) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); content::RenderProcessHost* host = content::RenderProcessHost::FromID(render_process_id); if (!host) return; Profile* profile = Profile::FromBrowserContext(host->GetBrowserContext()); mojo::MakeSelfOwnedAssociatedReceiver( std::make_unique(render_process_id, profile), std::move(receiver)); } base::FilePath GetRootCachePath() { // The CefContext::ValidateCachePath method enforces the requirement that all // cache_path values be either equal to or a child of root_cache_path. return base::FilePath( CefString(&CefContext::Get()->settings().root_cache_path)); } // Register BrowserInterfaceBroker's GetInterface() handler callbacks for // chrome-specific document-scoped interfaces. // Stub implementations to silence "Empty binder for interface // blink.mojom.[Name] for the frame/document scope" errors. // Based on chrome/browser/chrome_browser_interface_binders.cc. void PopulateChromeFrameBinders( service_manager::BinderMapWithContext* map) { map->Add(base::BindRepeating( [](content::RenderFrameHost* frame_host, mojo::PendingReceiver receiver) { })); map->Add(base::BindRepeating( [](content::RenderFrameHost* frame_host, mojo::PendingReceiver receiver) {})); } } // namespace CefContentBrowserClient::CefContentBrowserClient() : browser_main_parts_(nullptr) { plugin_service_filter_.reset(new CefPluginServiceFilter); content::PluginServiceImpl::GetInstance()->SetFilter( plugin_service_filter_.get()); } CefContentBrowserClient::~CefContentBrowserClient() {} // static CefContentBrowserClient* CefContentBrowserClient::Get() { if (!CefContentClient::Get()) return nullptr; return static_cast( CefContentClient::Get()->browser()); } std::unique_ptr CefContentBrowserClient::CreateBrowserMainParts( const content::MainFunctionParams& parameters) { browser_main_parts_ = new CefBrowserMainParts(parameters); return base::WrapUnique(browser_main_parts_); } void CefContentBrowserClient::RenderProcessWillLaunch( content::RenderProcessHost* host) { const int id = host->GetID(); Profile* profile = Profile::FromBrowserContext(host->GetBrowserContext()); host->AddFilter(new CefBrowserMessageFilter(id)); host->AddFilter(new printing::CefPrintingMessageFilter(id, profile)); if (extensions::ExtensionsEnabled()) { host->AddFilter(new extensions::ExtensionMessageFilter(id, profile)); host->AddFilter( new extensions::ExtensionsGuestViewMessageFilter(id, profile)); } // If the renderer process crashes then the host may already have // CefBrowserInfoManager as an observer. Try to remove it first before adding // to avoid DCHECKs. host->RemoveObserver(CefBrowserInfoManager::GetInstance()); host->AddObserver(CefBrowserInfoManager::GetInstance()); // Forwards dynamic parameters to CefRenderThreadObserver. Profile* original_profile = profile->GetOriginalProfile(); RendererUpdaterFactory::GetForProfile(original_profile) ->InitializeRenderer(host); } bool CefContentBrowserClient::ShouldUseProcessPerSite( content::BrowserContext* browser_context, const GURL& effective_url) { if (!extensions::ExtensionsEnabled()) return false; if (!effective_url.SchemeIs(extensions::kExtensionScheme)) return false; extensions::ExtensionRegistry* registry = extensions::ExtensionRegistry::Get(browser_context); if (!registry) return false; const extensions::Extension* extension = registry->enabled_extensions().GetByID(effective_url.host()); if (!extension) return false; // TODO(extensions): Extra checks required if type is TYPE_HOSTED_APP. // Hosted apps that have script access to their background page must use // process per site, since all instances can make synchronous calls to the // background window. Other extensions should use process per site as well. return true; } // Based on // ChromeContentBrowserClientExtensionsPart::DoesSiteRequireDedicatedProcess. bool CefContentBrowserClient::DoesSiteRequireDedicatedProcess( content::BrowserContext* browser_context, const GURL& effective_site_url) { if (!extensions::ExtensionsEnabled()) return false; const extensions::Extension* extension = extensions::ExtensionRegistry::Get(browser_context) ->enabled_extensions() .GetExtensionOrAppByURL(effective_site_url); // Isolate all extensions. return extension != nullptr; } void CefContentBrowserClient::OverrideURLLoaderFactoryParams( content::BrowserContext* browser_context, const url::Origin& origin, bool is_for_isolated_world, network::mojom::URLLoaderFactoryParams* factory_params) { if (extensions::ExtensionsEnabled()) { extensions::URLLoaderFactoryManager::OverrideURLLoaderFactoryParams( browser_context, origin, is_for_isolated_world, factory_params); } } void CefContentBrowserClient::GetAdditionalWebUISchemes( std::vector* additional_schemes) { // Any schemes listed here are treated as WebUI schemes but do not get WebUI // bindings. Also, view-source is allowed for these schemes. WebUI schemes // will not be passed to HandleExternalProtocol. } void CefContentBrowserClient::GetAdditionalViewSourceSchemes( std::vector* additional_schemes) { GetAdditionalWebUISchemes(additional_schemes); additional_schemes->push_back(extensions::kExtensionScheme); } void CefContentBrowserClient::GetAdditionalAllowedSchemesForFileSystem( std::vector* additional_allowed_schemes) { ContentBrowserClient::GetAdditionalAllowedSchemesForFileSystem( additional_allowed_schemes); additional_allowed_schemes->push_back(content::kChromeDevToolsScheme); additional_allowed_schemes->push_back(content::kChromeUIScheme); } bool CefContentBrowserClient::IsWebUIAllowedToMakeNetworkRequests( const url::Origin& origin) { return scheme::IsWebUIAllowedToMakeNetworkRequests(origin); } bool CefContentBrowserClient::IsHandledURL(const GURL& url) { if (!url.is_valid()) return false; const std::string& scheme = url.scheme(); DCHECK_EQ(scheme, base::ToLowerASCII(scheme)); if (scheme::IsInternalHandledScheme(scheme)) return true; return CefContentClient::Get()->HasCustomScheme(scheme); } void CefContentBrowserClient::SiteInstanceGotProcess( content::SiteInstance* site_instance) { if (!extensions::ExtensionsEnabled()) return; // If this isn't an extension renderer there's nothing to do. const extensions::Extension* extension = GetExtension(site_instance); if (!extension) return; CefBrowserContext* browser_context = static_cast(site_instance->GetBrowserContext()); extensions::ProcessMap::Get(browser_context) ->Insert(extension->id(), site_instance->GetProcess()->GetID(), site_instance->GetId()); CEF_POST_TASK( CEF_IOT, base::Bind(&extensions::InfoMap::RegisterExtensionProcess, browser_context->extension_system()->info_map(), extension->id(), site_instance->GetProcess()->GetID(), site_instance->GetId())); } void CefContentBrowserClient::SiteInstanceDeleting( content::SiteInstance* site_instance) { if (!extensions::ExtensionsEnabled()) return; // May be NULL during shutdown. if (!extensions::ExtensionsBrowserClient::Get()) return; // May be NULL during shutdown. if (!site_instance->HasProcess()) return; // If this isn't an extension renderer there's nothing to do. const extensions::Extension* extension = GetExtension(site_instance); if (!extension) return; CefBrowserContext* browser_context = static_cast(site_instance->GetBrowserContext()); extensions::ProcessMap::Get(browser_context) ->Remove(extension->id(), site_instance->GetProcess()->GetID(), site_instance->GetId()); CEF_POST_TASK( CEF_IOT, base::Bind(&extensions::InfoMap::UnregisterExtensionProcess, browser_context->extension_system()->info_map(), extension->id(), site_instance->GetProcess()->GetID(), site_instance->GetId())); } void CefContentBrowserClient::BindHostReceiverForRenderer( content::RenderProcessHost* render_process_host, mojo::GenericPendingReceiver receiver) { if (auto host_receiver = receiver.As()) { SpellCheckHostChromeImpl::Create(render_process_host->GetID(), std::move(host_receiver)); return; } #if BUILDFLAG(HAS_SPELLCHECK_PANEL) if (auto panel_host_receiver = receiver.As()) { SpellCheckPanelHostImpl::Create(render_process_host->GetID(), std::move(panel_host_receiver)); return; } #endif // BUILDFLAG(HAS_SPELLCHECK_PANEL) } base::Optional CefContentBrowserClient::GetServiceManifestOverlay(base::StringPiece name) { if (name == content::mojom::kBrowserServiceName) { return GetCefContentBrowserOverlayManifest(); } return base::nullopt; } void CefContentBrowserClient::AppendExtraCommandLineSwitches( base::CommandLine* command_line, int child_process_id) { const base::CommandLine* browser_cmd = base::CommandLine::ForCurrentProcess(); { // Propagate the following switches to all command lines (along with any // associated values) if present in the browser command line. static const char* const kSwitchNames[] = { switches::kDisablePackLoading, #if defined(OS_MACOSX) switches::kFrameworkDirPath, switches::kMainBundlePath, #endif switches::kLocalesDirPath, switches::kLogFile, switches::kLogSeverity, switches::kProductVersion, switches::kResourcesDirPath, switches::kUserAgent, }; command_line->CopySwitchesFrom(*browser_cmd, kSwitchNames, base::size(kSwitchNames)); } const std::string& process_type = command_line->GetSwitchValueASCII(switches::kProcessType); if (process_type == switches::kRendererProcess) { // Propagate the following switches to the renderer command line (along with // any associated values) if present in the browser command line. static const char* const kSwitchNames[] = { switches::kDisableExtensions, switches::kDisablePdfExtension, switches::kDisablePlugins, switches::kDisablePrintPreview, switches::kDisableScrollBounce, switches::kDisableSpellChecking, switches::kEnableSpeechInput, switches::kEnableSystemFlash, switches::kPpapiFlashArgs, switches::kPpapiFlashPath, switches::kPpapiFlashVersion, switches::kUncaughtExceptionStackSize, network::switches::kUnsafelyTreatInsecureOriginAsSecure, }; command_line->CopySwitchesFrom(*browser_cmd, kSwitchNames, base::size(kSwitchNames)); if (extensions::ExtensionsEnabled()) { content::RenderProcessHost* process = content::RenderProcessHost::FromID(child_process_id); CefBrowserContext* context = process ? CefBrowserContext::GetForContext(process->GetBrowserContext()) : nullptr; if (context) { if (context->IsPrintPreviewSupported()) { command_line->AppendSwitch(switches::kEnablePrintPreview); } // Based on ChromeContentBrowserClientExtensionsPart:: // AppendExtraRendererCommandLineSwitches if (extensions::ProcessMap::Get(context)->Contains(process->GetID())) { command_line->AppendSwitch(extensions::switches::kExtensionProcess); } } } } else { // Propagate the following switches to non-renderer command line (along with // any associated values) if present in the browser command line. static const char* const kSwitchNames[] = { switches::kLang, }; command_line->CopySwitchesFrom(*browser_cmd, kSwitchNames, base::size(kSwitchNames)); } #if defined(OS_LINUX) if (process_type == service_manager::switches::kZygoteProcess) { // Propagate the following switches to the zygote command line (along with // any associated values) if present in the browser command line. static const char* const kSwitchNames[] = { switches::kPpapiFlashPath, switches::kPpapiFlashVersion, }; command_line->CopySwitchesFrom(*browser_cmd, kSwitchNames, base::size(kSwitchNames)); #if BUILDFLAG(ENABLE_WIDEVINE) && BUILDFLAG(ENABLE_LIBRARY_CDMS) if (!browser_cmd->HasSwitch(service_manager::switches::kNoSandbox)) { // Pass the Widevine CDM path to the Zygote process. See comments in // CefWidevineLoader::AddContentDecryptionModules. const base::FilePath& cdm_path = CefWidevineLoader::GetInstance()->path(); if (!cdm_path.empty()) command_line->AppendSwitchPath(switches::kWidevineCdmPath, cdm_path); } #endif if (browser_cmd->HasSwitch(switches::kBrowserSubprocessPath)) { // Force use of the sub-process executable path for the zygote process. const base::FilePath& subprocess_path = browser_cmd->GetSwitchValuePath(switches::kBrowserSubprocessPath); if (!subprocess_path.empty()) command_line->SetProgram(subprocess_path); } } #endif // defined(OS_LINUX) CefRefPtr app = CefContentClient::Get()->application(); if (app.get()) { CefRefPtr handler = app->GetBrowserProcessHandler(); if (handler.get()) { CefRefPtr commandLinePtr( new CefCommandLineImpl(command_line, false, false)); handler->OnBeforeChildProcessLaunch(commandLinePtr.get()); commandLinePtr->Detach(nullptr); } } } std::string CefContentBrowserClient::GetApplicationLocale() { return g_browser_process->GetApplicationLocale(); } scoped_refptr CefContentBrowserClient::GetSystemSharedURLLoaderFactory() { DCHECK( content::BrowserThread::CurrentlyOn(content::BrowserThread::UI) || !content::BrowserThread::IsThreadInitialized(content::BrowserThread::UI)); if (!SystemNetworkContextManager::GetInstance()) return nullptr; return SystemNetworkContextManager::GetInstance() ->GetSharedURLLoaderFactory(); } network::mojom::NetworkContext* CefContentBrowserClient::GetSystemNetworkContext() { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); DCHECK(SystemNetworkContextManager::GetInstance()); return SystemNetworkContextManager::GetInstance()->GetContext(); } scoped_refptr CefContentBrowserClient::CreateQuotaPermissionContext() { return new CefQuotaPermissionContext(); } content::MediaObserver* CefContentBrowserClient::GetMediaObserver() { return CefMediaCaptureDevicesDispatcher::GetInstance(); } content::SpeechRecognitionManagerDelegate* CefContentBrowserClient::CreateSpeechRecognitionManagerDelegate() { const base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); if (command_line->HasSwitch(switches::kEnableSpeechInput)) return new CefSpeechRecognitionManagerDelegate(); return nullptr; } content::GeneratedCodeCacheSettings CefContentBrowserClient::GetGeneratedCodeCacheSettings( content::BrowserContext* context) { // If we pass 0 for size, disk_cache will pick a default size using the // heuristics based on available disk size. These are implemented in // disk_cache::PreferredCacheSize in net/disk_cache/cache_util.cc. const base::FilePath& cache_path = context->GetPath(); return content::GeneratedCodeCacheSettings(!cache_path.empty() /* enabled */, 0 /* size */, cache_path); } void CefContentBrowserClient::AllowCertificateError( content::WebContents* web_contents, int cert_error, const net::SSLInfo& ssl_info, const GURL& request_url, bool is_main_frame_request, bool strict_enforcement, base::OnceCallback callback) { CEF_REQUIRE_UIT(); if (!is_main_frame_request) { // A sub-resource has a certificate error. The user doesn't really // have a context for making the right decision, so block the request // hard. std::move(callback).Run(content::CERTIFICATE_REQUEST_RESULT_TYPE_CANCEL); return; } CefRefPtr browser = CefBrowserHostImpl::GetBrowserForContents(web_contents); if (!browser.get()) return; CefRefPtr client = browser->GetClient(); if (!client.get()) return; CefRefPtr handler = client->GetRequestHandler(); if (!handler.get()) return; CefRefPtr cef_ssl_info = new CefSSLInfoImpl(ssl_info); CefRefPtr callbackImpl( new CefAllowCertificateErrorCallbackImpl(std::move(callback))); bool proceed = handler->OnCertificateError( browser.get(), static_cast(cert_error), request_url.spec(), cef_ssl_info, callbackImpl.get()); if (!proceed) { // |callback| may be null if the user executed it despite returning false. callback = callbackImpl->Disconnect(); if (!callback.is_null()) { std::move(callback).Run(content::CERTIFICATE_REQUEST_RESULT_TYPE_CANCEL); } } } base::OnceClosure CefContentBrowserClient::SelectClientCertificate( content::WebContents* web_contents, net::SSLCertRequestInfo* cert_request_info, net::ClientCertIdentityList client_certs, std::unique_ptr delegate) { CEF_REQUIRE_UIT(); CefRefPtr handler; CefRefPtr browser = CefBrowserHostImpl::GetBrowserForContents(web_contents); if (browser.get()) { CefRefPtr client = browser->GetClient(); if (client.get()) handler = client->GetRequestHandler(); } if (!handler.get()) { delegate->ContinueWithCertificate(nullptr, nullptr); return base::OnceClosure(); } CefRequestHandler::X509CertificateList certs; for (net::ClientCertIdentityList::iterator iter = client_certs.begin(); iter != client_certs.end(); iter++) { certs.push_back(new CefX509CertificateImpl(std::move(*iter))); } CefRefPtr callbackImpl( new CefSelectClientCertificateCallbackImpl(std::move(delegate))); bool proceed = handler->OnSelectClientCertificate( browser.get(), cert_request_info->is_proxy, cert_request_info->host_and_port.host(), cert_request_info->host_and_port.port(), certs, callbackImpl.get()); if (!proceed && !certs.empty()) { callbackImpl->Select(certs[0]); } return base::OnceClosure(); } bool CefContentBrowserClient::CanCreateWindow( content::RenderFrameHost* opener, const GURL& opener_url, const GURL& opener_top_level_frame_url, const url::Origin& source_origin, content::mojom::WindowContainerType container_type, const GURL& target_url, const content::Referrer& referrer, const std::string& frame_name, WindowOpenDisposition disposition, const blink::mojom::WindowFeatures& features, bool user_gesture, bool opener_suppressed, bool* no_javascript_access) { CEF_REQUIRE_UIT(); *no_javascript_access = false; return CefBrowserInfoManager::GetInstance()->CanCreateWindow( opener, target_url, referrer, frame_name, disposition, features, user_gesture, opener_suppressed, no_javascript_access); } void CefContentBrowserClient::OverrideWebkitPrefs( content::RenderViewHost* rvh, content::WebPreferences* prefs) { // Using RVH instead of RFH here because rvh->GetMainFrame() may be nullptr // when this method is called. renderer_prefs::PopulateWebPreferences(rvh, *prefs); } void CefContentBrowserClient::BrowserURLHandlerCreated( content::BrowserURLHandler* handler) { scheme::BrowserURLHandlerCreated(handler); } std::string CefContentBrowserClient::GetDefaultDownloadName() { return "download"; } void CefContentBrowserClient::DidCreatePpapiPlugin( content::BrowserPpapiHost* browser_host) { browser_host->GetPpapiHost()->AddHostFactoryFilter( std::unique_ptr( new ChromeBrowserPepperHostFactory(browser_host))); } content::DevToolsManagerDelegate* CefContentBrowserClient::GetDevToolsManagerDelegate() { return new CefDevToolsManagerDelegate(); } std::vector> CefContentBrowserClient::CreateThrottlesForNavigation( content::NavigationHandle* navigation_handle) { CEF_REQUIRE_UIT(); std::vector> throttles; const bool is_main_frame = navigation_handle->IsInMainFrame(); // Identify the RenderFrameHost that originated the navigation. const int64_t parent_frame_id = !is_main_frame ? CefFrameHostImpl::MakeFrameId(navigation_handle->GetParentFrame()) : CefFrameHostImpl::kInvalidFrameId; const int64_t frame_id = !is_main_frame && navigation_handle->HasCommitted() ? CefFrameHostImpl::MakeFrameId( navigation_handle->GetRenderFrameHost()) : CefFrameHostImpl::kInvalidFrameId; // Must use SynchronyMode::kSync to ensure that OnBeforeBrowse is always // called before OnBeforeResourceLoad. std::unique_ptr throttle = std::make_unique( navigation_handle, base::Bind(&NavigationOnUIThread, is_main_frame, frame_id, parent_frame_id, navigation_handle->GetFrameTreeNodeId()), navigation_interception::SynchronyMode::kSync); throttles.push_back(std::move(throttle)); return throttles; } std::vector> CefContentBrowserClient::CreateURLLoaderThrottles( const network::ResourceRequest& request, content::BrowserContext* browser_context, const base::RepeatingCallback& wc_getter, content::NavigationUIData* navigation_ui_data, int frame_tree_node_id) { std::vector> result; // Used to substitute View ID for PDF contents when using the PDF plugin. result.push_back(std::make_unique( request.resource_type, frame_tree_node_id)); Profile* profile = Profile::FromBrowserContext(browser_context); chrome::mojom::DynamicParams dynamic_params = { profile->GetPrefs()->GetBoolean(prefs::kForceGoogleSafeSearch), profile->GetPrefs()->GetInteger(prefs::kForceYouTubeRestrict), profile->GetPrefs()->GetString(prefs::kAllowedDomainsForApps)}; result.push_back( std::make_unique(std::move(dynamic_params))); return result; } #if defined(OS_LINUX) void CefContentBrowserClient::GetAdditionalMappedFilesForChildProcess( const base::CommandLine& command_line, int child_process_id, content::PosixFileDescriptorInfo* mappings) { int crash_signal_fd = GetCrashSignalFD(command_line); if (crash_signal_fd >= 0) { mappings->Share(service_manager::kCrashDumpSignal, crash_signal_fd); } } #endif // defined(OS_LINUX) #if defined(OS_WIN) const wchar_t* CefContentBrowserClient::GetResourceDllName() { static wchar_t file_path[MAX_PATH + 1] = {0}; if (file_path[0] == 0) { // Retrieve the module path (usually libcef.dll). base::FilePath module; base::PathService::Get(base::FILE_MODULE, &module); const std::wstring wstr = module.value(); size_t count = std::min(static_cast(MAX_PATH), wstr.size()); wcsncpy(file_path, wstr.c_str(), count); file_path[count] = 0; } return file_path; } bool CefContentBrowserClient::PreSpawnRenderer(sandbox::TargetPolicy* policy, RendererSpawnFlags flags) { return true; } #endif // defined(OS_WIN) void CefContentBrowserClient::ExposeInterfacesToRenderer( service_manager::BinderRegistry* registry, blink::AssociatedInterfaceRegistry* associated_registry, content::RenderProcessHost* host) { associated_registry->AddInterface( base::BindRepeating(&BindPluginInfoHost, host->GetID())); } std::unique_ptr CefContentBrowserClient::CreateClientCertStore( content::BrowserContext* browser_context) { // Match the logic in ProfileNetworkContextService::CreateClientCertStore. #if defined(USE_NSS_CERTS) // TODO: Add support for client implementation of crypto password dialog. return std::unique_ptr(new net::ClientCertStoreNSS( net::ClientCertStoreNSS::PasswordDelegateFactory())); #elif defined(OS_WIN) return std::unique_ptr(new net::ClientCertStoreWin()); #elif defined(OS_MACOSX) return std::unique_ptr(new net::ClientCertStoreMac()); #else #error Unknown platform. #endif } std::unique_ptr CefContentBrowserClient::CreateLoginDelegate( const net::AuthChallengeInfo& auth_info, content::WebContents* web_contents, const content::GlobalRequestID& request_id, bool is_request_for_main_frame, const GURL& url, scoped_refptr response_headers, bool first_auth_attempt, LoginAuthRequiredCallback auth_required_callback) { return std::make_unique( auth_info, web_contents, request_id, url, std::move(auth_required_callback)); } void CefContentBrowserClient::RegisterNonNetworkNavigationURLLoaderFactories( int frame_tree_node_id, NonNetworkURLLoaderFactoryMap* factories) { if (!extensions::ExtensionsEnabled()) return; content::WebContents* web_contents = content::WebContents::FromFrameTreeNodeId(frame_tree_node_id); factories->emplace( extensions::kExtensionScheme, extensions::CreateExtensionNavigationURLLoaderFactory( web_contents->GetBrowserContext(), !!extensions::WebViewGuest::FromWebContents(web_contents))); } void CefContentBrowserClient::RegisterNonNetworkSubresourceURLLoaderFactories( int render_process_id, int render_frame_id, NonNetworkURLLoaderFactoryMap* factories) { if (!extensions::ExtensionsEnabled()) return; auto factory = extensions::CreateExtensionURLLoaderFactory(render_process_id, render_frame_id); if (factory) factories->emplace(extensions::kExtensionScheme, std::move(factory)); content::RenderFrameHost* frame_host = content::RenderFrameHost::FromID(render_process_id, render_frame_id); content::WebContents* web_contents = content::WebContents::FromRenderFrameHost(frame_host); if (!web_contents) return; extensions::CefExtensionWebContentsObserver* web_observer = extensions::CefExtensionWebContentsObserver::FromWebContents( web_contents); // There is nothing to do if no CefExtensionWebContentsObserver is attached // to the |web_contents|. if (!web_observer) return; const extensions::Extension* extension = web_observer->GetExtensionFromFrame(frame_host, false); if (!extension) return; std::vector allowed_webui_hosts; // Support for chrome:// scheme if appropriate. if ((extension->is_extension() || extension->is_platform_app()) && extensions::Manifest::IsComponentLocation(extension->location())) { // Components of chrome that are implemented as extensions or platform apps // are allowed to use chrome://resources/ and chrome://theme/ URLs. allowed_webui_hosts.emplace_back(content::kChromeUIResourcesHost); allowed_webui_hosts.emplace_back(chrome::kChromeUIThemeHost); } if (!allowed_webui_hosts.empty()) { factories->emplace( content::kChromeUIScheme, content::CreateWebUIURLLoader(frame_host, content::kChromeUIScheme, std::move(allowed_webui_hosts))); } } bool CefContentBrowserClient::WillCreateURLLoaderFactory( content::BrowserContext* browser_context, content::RenderFrameHost* frame, int render_process_id, URLLoaderFactoryType type, const url::Origin& request_initiator, base::Optional navigation_id, mojo::PendingReceiver* factory_receiver, mojo::PendingRemote* header_client, bool* bypass_redirect_checks, bool* disable_secure_dns, network::mojom::URLLoaderFactoryOverridePtr* factory_override) { auto request_handler = net_service::CreateInterceptedRequestHandler( browser_context, frame, render_process_id, type == URLLoaderFactoryType::kNavigation, type == URLLoaderFactoryType::kDownload, request_initiator); net_service::ProxyURLLoaderFactory::CreateProxy( browser_context, factory_receiver, header_client, std::move(request_handler)); return true; } void CefContentBrowserClient::OnNetworkServiceCreated( network::mojom::NetworkService* network_service) { DCHECK(g_browser_process); PrefService* local_state = g_browser_process->local_state(); DCHECK(local_state); // Need to set up global NetworkService state before anything else uses it. DCHECK(SystemNetworkContextManager::GetInstance()); SystemNetworkContextManager::GetInstance()->OnNetworkServiceCreated( network_service); } mojo::Remote CefContentBrowserClient::CreateNetworkContext( content::BrowserContext* context, bool in_memory, const base::FilePath& relative_partition_path) { // This method may be called during shutdown when using multi-threaded // message loop mode. In that case exit early to avoid crashes. if (!SystemNetworkContextManager::GetInstance()) return mojo::Remote(); Profile* profile = Profile::FromBrowserContext(context); return profile->CreateNetworkContext(in_memory, relative_partition_path); } // The sandbox may block read/write access from the NetworkService to // directories that are not returned by this method. std::vector CefContentBrowserClient::GetNetworkContextsParentDirectory() { base::FilePath user_data_path; base::PathService::Get(chrome::DIR_USER_DATA, &user_data_path); DCHECK(!user_data_path.empty()); const auto& root_cache_path = GetRootCachePath(); // root_cache_path may sometimes be empty or a child of user_data_path, so // only return the one path in that case. if (root_cache_path.empty() || user_data_path.IsParent(root_cache_path)) { return {user_data_path}; } return {user_data_path, root_cache_path}; } bool CefContentBrowserClient::HandleExternalProtocol( const GURL& url, base::OnceCallback web_contents_getter, int child_id, content::NavigationUIData* navigation_data, bool is_main_frame, ui::PageTransition page_transition, bool has_user_gesture, const base::Optional& initiating_origin, mojo::PendingRemote* out_factory) { // Call the other HandleExternalProtocol variant. return false; } bool CefContentBrowserClient::HandleExternalProtocol( content::WebContents::Getter web_contents_getter, int frame_tree_node_id, content::NavigationUIData* navigation_data, const network::ResourceRequest& resource_request, mojo::PendingRemote* out_factory) { mojo::PendingReceiver receiver = out_factory->InitWithNewPipeAndPassReceiver(); // CefBrowserPlatformDelegate::HandleExternalProtocol may be called if // nothing handles the request. if (CEF_CURRENTLY_ON_IOT()) { auto request_handler = net_service::CreateInterceptedRequestHandler( web_contents_getter, frame_tree_node_id, resource_request); net_service::ProxyURLLoaderFactory::CreateProxy( web_contents_getter, std::move(receiver), std::move(request_handler)); } else { auto request_handler = net_service::CreateInterceptedRequestHandler( web_contents_getter, frame_tree_node_id, resource_request); CEF_POST_TASK( CEF_IOT, base::BindOnce( [](mojo::PendingReceiver receiver, std::unique_ptr request_handler, content::WebContents::Getter web_contents_getter) { // Manages its own lifetime. net_service::ProxyURLLoaderFactory::CreateProxy( web_contents_getter, std::move(receiver), std::move(request_handler)); }, std::move(receiver), std::move(request_handler), std::move(web_contents_getter))); } return true; } std::unique_ptr CefContentBrowserClient::CreateWindowForPictureInPicture( content::PictureInPictureWindowController* controller) { // Note: content::OverlayWindow::Create() is defined by platform-specific // implementation in chrome/browser/ui/views. This layering hack, which goes // through //content and ContentBrowserClient, allows us to work around the // dependency constraints that disallow directly calling // chrome/browser/ui/views code either from here or from other code in // chrome/browser. return content::OverlayWindow::Create(controller); } void CefContentBrowserClient::RegisterBrowserInterfaceBindersForFrame( content::RenderFrameHost* render_frame_host, service_manager::BinderMapWithContext* map) { PopulateChromeFrameBinders(map); if (!extensions::ExtensionsEnabled()) return; content::WebContents* web_contents = content::WebContents::FromRenderFrameHost(render_frame_host); if (!web_contents) return; const GURL& site = render_frame_host->GetSiteInstance()->GetSiteURL(); if (!site.SchemeIs(extensions::kExtensionScheme)) return; content::BrowserContext* browser_context = render_frame_host->GetProcess()->GetBrowserContext(); auto* extension = extensions::ExtensionRegistry::Get(browser_context) ->enabled_extensions() .GetByID(site.host()); if (!extension) return; extensions::ExtensionsBrowserClient::Get() ->RegisterBrowserInterfaceBindersForFrame(map, render_frame_host, extension); } base::FilePath CefContentBrowserClient::GetSandboxedStorageServiceDataDirectory() { return GetRootCachePath(); } std::string CefContentBrowserClient::GetProduct() { // Match the logic in chrome_content_browser_client.cc GetProduct(). return ::GetProduct(); } std::string CefContentBrowserClient::GetChromeProduct() { return version_info::GetProductNameAndVersionForUserAgent(); } std::string CefContentBrowserClient::GetUserAgent() { // Match the logic in chrome_content_browser_client.cc GetUserAgent(). return ::GetUserAgent(); } blink::UserAgentMetadata CefContentBrowserClient::GetUserAgentMetadata() { blink::UserAgentMetadata metadata; metadata.brand = version_info::GetProductName(); metadata.full_version = version_info::GetVersionNumber(); metadata.platform = version_info::GetOSType(); // TODO(mkwst): Poke at BuildUserAgentFromProduct to split out these pieces. metadata.architecture = ""; metadata.model = ""; return metadata; } base::flat_set CefContentBrowserClient::GetPluginMimeTypesWithExternalHandlers( content::BrowserContext* browser_context) { base::flat_set mime_types; auto map = PluginUtils::GetMimeTypeToExtensionIdMap(browser_context); for (const auto& pair : map) mime_types.insert(pair.first); return mime_types; } void CefContentBrowserClient::RegisterCustomScheme(const std::string& scheme) { // Register as a Web-safe scheme so that requests for the scheme from a // render process will be allowed in resource_dispatcher_host_impl.cc // ShouldServiceRequest. content::ChildProcessSecurityPolicy* policy = content::ChildProcessSecurityPolicy::GetInstance(); if (!policy->IsWebSafeScheme(scheme)) policy->RegisterWebSafeScheme(scheme); } CefRefPtr CefContentBrowserClient::request_context() const { return browser_main_parts_->request_context(); } CefDevToolsDelegate* CefContentBrowserClient::devtools_delegate() const { return browser_main_parts_->devtools_delegate(); } scoped_refptr CefContentBrowserClient::background_task_runner() const { return browser_main_parts_->background_task_runner(); } scoped_refptr CefContentBrowserClient::user_visible_task_runner() const { return browser_main_parts_->user_visible_task_runner(); } scoped_refptr CefContentBrowserClient::user_blocking_task_runner() const { return browser_main_parts_->user_blocking_task_runner(); } const extensions::Extension* CefContentBrowserClient::GetExtension( content::SiteInstance* site_instance) { extensions::ExtensionRegistry* registry = extensions::ExtensionRegistry::Get(site_instance->GetBrowserContext()); if (!registry) return nullptr; return registry->enabled_extensions().GetExtensionOrAppByURL( site_instance->GetSiteURL()); }