// 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 "libcef/browser/request_context_impl.h" #include "libcef/browser/browser_context.h" #include "libcef/browser/content_browser_client.h" #include "libcef/browser/context.h" #include "libcef/browser/extensions/extension_system.h" #include "libcef/browser/thread_util.h" #include "libcef/common/extensions/extensions_util.h" #include "libcef/common/task_runner_impl.h" #include "libcef/common/values_impl.h" #include "base/atomic_sequence_num.h" #include "base/logging.h" #include "base/strings/stringprintf.h" #include "components/prefs/pref_service.h" #include "content/public/browser/browser_task_traits.h" #include "content/public/browser/plugin_service.h" #include "content/public/browser/ssl_host_state_delegate.h" #include "mojo/public/cpp/bindings/binding.h" #include "mojo/public/cpp/bindings/pending_receiver.h" #include "mojo/public/cpp/bindings/remote.h" #include "services/network/public/cpp/resolve_host_client_base.h" #include "services/network/public/mojom/network_context.mojom.h" using content::BrowserThread; namespace { base::AtomicSequenceNumber g_next_id; const char* GetTypeString(base::Value::Type type) { switch (type) { case base::Value::Type::NONE: return "NULL"; case base::Value::Type::BOOLEAN: return "BOOLEAN"; case base::Value::Type::INTEGER: return "INTEGER"; case base::Value::Type::DOUBLE: return "DOUBLE"; case base::Value::Type::STRING: return "STRING"; case base::Value::Type::BINARY: return "BINARY"; case base::Value::Type::DICTIONARY: return "DICTIONARY"; case base::Value::Type::LIST: return "LIST"; case base::Value::Type::DEAD: return "DEAD"; } NOTREACHED(); return "UNKNOWN"; } // Helper for HostResolver::Resolve. struct ResolveHostHelperOld { explicit ResolveHostHelperOld(CefRefPtr callback) : callback_(callback) {} void OnResolveCompleted(int result) { std::vector resolved_ips; base::Optional maybe_address_list = request_->GetAddressResults(); if (maybe_address_list) { net::AddressList::const_iterator iter = maybe_address_list->begin(); for (; iter != maybe_address_list->end(); ++iter) resolved_ips.push_back(iter->ToStringWithoutPort()); } CEF_POST_TASK( CEF_UIT, base::Bind(&CefResolveCallback::OnResolveCompleted, callback_, static_cast(result), resolved_ips)); delete this; } CefRefPtr callback_; std::unique_ptr request_; }; class ResolveHostHelper : public network::ResolveHostClientBase { public: explicit ResolveHostHelper(CefRefPtr callback) : callback_(callback), receiver_(this) {} void Start(CefBrowserContext* browser_context, const CefString& origin) { CEF_REQUIRE_UIT(); browser_context->GetNetworkContext()->CreateHostResolver( base::nullopt, host_resolver_.BindNewPipeAndPassReceiver()); host_resolver_.set_disconnect_handler(base::BindOnce( &ResolveHostHelper::OnComplete, base::Unretained(this), net::ERR_FAILED, net::ResolveErrorInfo(net::ERR_FAILED), base::nullopt)); host_resolver_->ResolveHost( net::HostPortPair::FromURL(GURL(origin.ToString())), net::NetworkIsolationKey::CreateTransient(), nullptr, receiver_.BindNewPipeAndPassRemote()); } private: void OnComplete( int32_t result, const ::net::ResolveErrorInfo& resolve_error_info, const base::Optional& resolved_addresses) override { CEF_REQUIRE_UIT(); host_resolver_.reset(); receiver_.reset(); std::vector resolved_ips; if (result == net::OK) { DCHECK(resolved_addresses && !resolved_addresses->empty()); for (const auto& value : resolved_addresses.value()) { resolved_ips.push_back(value.ToStringWithoutPort()); } } callback_->OnResolveCompleted(static_cast(result), resolved_ips); delete this; } CefRefPtr callback_; mojo::Remote host_resolver_; mojo::Receiver receiver_; DISALLOW_COPY_AND_ASSIGN(ResolveHostHelper); }; } // namespace // CefBrowserContext // static CefRefPtr CefRequestContext::GetGlobalContext() { // Verify that the context is in a valid state. if (!CONTEXT_STATE_VALID()) { NOTREACHED() << "context not valid"; return nullptr; } CefRequestContextImpl::Config config; config.is_global = true; return CefRequestContextImpl::GetOrCreateRequestContext(config); } // static CefRefPtr CefRequestContext::CreateContext( const CefRequestContextSettings& settings, CefRefPtr handler) { // Verify that the context is in a valid state. if (!CONTEXT_STATE_VALID()) { NOTREACHED() << "context not valid"; return nullptr; } CefRequestContextImpl::Config config; config.settings = settings; config.handler = handler; config.unique_id = g_next_id.GetNext(); return CefRequestContextImpl::GetOrCreateRequestContext(config); } // static CefRefPtr CefRequestContext::CreateContext( CefRefPtr other, CefRefPtr handler) { // Verify that the context is in a valid state. if (!CONTEXT_STATE_VALID()) { NOTREACHED() << "context not valid"; return nullptr; } if (!other.get()) return nullptr; CefRequestContextImpl::Config config; config.other = static_cast(other.get()); config.handler = handler; config.unique_id = g_next_id.GetNext(); return CefRequestContextImpl::GetOrCreateRequestContext(config); } // CefRequestContextImpl CefRequestContextImpl::~CefRequestContextImpl() { CEF_REQUIRE_UIT(); if (browser_context_) { // May result in |browser_context_| being deleted if no other // CefRequestContextImpl are referencing it. browser_context_->RemoveCefRequestContext(this); } } // static CefRefPtr CefRequestContextImpl::CreateGlobalRequestContext( const CefRequestContextSettings& settings) { // Create and initialize the global context immediately. Config config; config.is_global = true; config.settings = settings; CefRefPtr impl = new CefRequestContextImpl(config); impl->Initialize(); return impl; } // static CefRefPtr CefRequestContextImpl::GetOrCreateForRequestContext( CefRefPtr request_context) { if (request_context.get()) { // Use the context from the provided CefRequestContext. return static_cast(request_context.get()); } // Use the global context. Config config; config.is_global = true; return CefRequestContextImpl::GetOrCreateRequestContext(config); } CefBrowserContext* CefRequestContextImpl::GetBrowserContext() { EnsureBrowserContext(); return browser_context(); } void CefRequestContextImpl::GetBrowserContext( scoped_refptr task_runner, const BrowserContextCallback& callback) { if (!task_runner.get()) task_runner = CefTaskRunnerImpl::GetCurrentTaskRunner(); GetBrowserContextOnUIThread(task_runner, callback); } bool CefRequestContextImpl::IsSame(CefRefPtr other) { CefRequestContextImpl* other_impl = static_cast(other.get()); if (!other_impl) return false; // Compare whether both are the global context. if (config_.is_global && other_impl->config_.is_global) return true; // Compare CefBrowserContext pointers if one has been associated. if (browser_context() && other_impl->browser_context()) { return (browser_context() == other_impl->browser_context()); } else if (browser_context() || other_impl->browser_context()) { return false; } // Otherwise compare unique IDs. return (config_.unique_id == other_impl->config_.unique_id); } bool CefRequestContextImpl::IsSharingWith(CefRefPtr other) { CefRequestContextImpl* other_impl = static_cast(other.get()); if (!other_impl) return false; if (IsSame(other)) return true; CefRefPtr pending_other = config_.other; if (pending_other.get()) { // This object is not initialized but we know what context this object will // share with. Compare to that other context instead. return pending_other->IsSharingWith(other); } pending_other = other_impl->config_.other; if (pending_other.get()) { // The other object is not initialized but we know what context that object // will share with. Compare to that other context instead. return pending_other->IsSharingWith(this); } // This or the other object is not initialized. Compare the cache path values. // If both are non-empty and the same then they'll share the same storage. if (config_.settings.cache_path.length > 0 && other_impl->config_.settings.cache_path.length > 0) { return ( base::FilePath(CefString(&config_.settings.cache_path)) == base::FilePath(CefString(&other_impl->config_.settings.cache_path))); } return false; } bool CefRequestContextImpl::IsGlobal() { return config_.is_global; } CefRefPtr CefRequestContextImpl::GetHandler() { return config_.handler; } CefString CefRequestContextImpl::GetCachePath() { return CefString(&config_.settings.cache_path); } CefRefPtr CefRequestContextImpl::GetCookieManager( CefRefPtr callback) { CefRefPtr cookie_manager = new CefCookieManagerImpl(); InitializeCookieManagerOnUIThread(cookie_manager, callback); return cookie_manager.get(); } bool CefRequestContextImpl::RegisterSchemeHandlerFactory( const CefString& scheme_name, const CefString& domain_name, CefRefPtr factory) { if (!CEF_CURRENTLY_ON_UIT()) { CEF_POST_TASK(CEF_UIT, base::BindOnce( base::IgnoreResult( &CefRequestContextImpl::RegisterSchemeHandlerFactory), this, scheme_name, domain_name, factory)); return true; } // Make sure the browser context exists. EnsureBrowserContext(); browser_context()->RegisterSchemeHandlerFactory(scheme_name, domain_name, factory); return true; } bool CefRequestContextImpl::ClearSchemeHandlerFactories() { if (!CEF_CURRENTLY_ON_UIT()) { CEF_POST_TASK( CEF_UIT, base::BindOnce(base::IgnoreResult( &CefRequestContextImpl::ClearSchemeHandlerFactories), this)); return true; } // Make sure the browser context exists. EnsureBrowserContext(); browser_context()->ClearSchemeHandlerFactories(); return true; } void CefRequestContextImpl::PurgePluginListCache(bool reload_pages) { GetBrowserContext( base::CreateSingleThreadTaskRunner({BrowserThread::UI}), base::Bind(&CefRequestContextImpl::PurgePluginListCacheInternal, this, reload_pages)); } bool CefRequestContextImpl::HasPreference(const CefString& name) { // Verify that this method is being called on the UI thread. if (!CEF_CURRENTLY_ON_UIT()) { NOTREACHED() << "called on invalid thread"; return false; } // Make sure the browser context exists. EnsureBrowserContext(); PrefService* pref_service = browser_context()->GetPrefs(); return (pref_service->FindPreference(name) != nullptr); } CefRefPtr CefRequestContextImpl::GetPreference( const CefString& name) { // Verify that this method is being called on the UI thread. if (!CEF_CURRENTLY_ON_UIT()) { NOTREACHED() << "called on invalid thread"; return nullptr; } // Make sure the browser context exists. EnsureBrowserContext(); PrefService* pref_service = browser_context()->GetPrefs(); const PrefService::Preference* pref = pref_service->FindPreference(name); if (!pref) return nullptr; return new CefValueImpl(pref->GetValue()->DeepCopy()); } CefRefPtr CefRequestContextImpl::GetAllPreferences( bool include_defaults) { // Verify that this method is being called on the UI thread. if (!CEF_CURRENTLY_ON_UIT()) { NOTREACHED() << "called on invalid thread"; return nullptr; } // Make sure the browser context exists. EnsureBrowserContext(); PrefService* pref_service = browser_context()->GetPrefs(); std::unique_ptr values = pref_service->GetPreferenceValues(include_defaults ? PrefService::INCLUDE_DEFAULTS : PrefService::EXCLUDE_DEFAULTS); // CefDictionaryValueImpl takes ownership of |values|. return new CefDictionaryValueImpl(values.release(), true, false); } bool CefRequestContextImpl::CanSetPreference(const CefString& name) { // Verify that this method is being called on the UI thread. if (!CEF_CURRENTLY_ON_UIT()) { NOTREACHED() << "called on invalid thread"; return false; } // Make sure the browser context exists. EnsureBrowserContext(); PrefService* pref_service = browser_context()->GetPrefs(); const PrefService::Preference* pref = pref_service->FindPreference(name); return (pref && pref->IsUserModifiable()); } bool CefRequestContextImpl::SetPreference(const CefString& name, CefRefPtr value, CefString& error) { // Verify that this method is being called on the UI thread. if (!CEF_CURRENTLY_ON_UIT()) { NOTREACHED() << "called on invalid thread"; return false; } // Make sure the browser context exists. EnsureBrowserContext(); PrefService* pref_service = browser_context()->GetPrefs(); // The below validation logic should match PrefService::SetUserPrefValue. const PrefService::Preference* pref = pref_service->FindPreference(name); if (!pref) { error = "Trying to modify an unregistered preference"; return false; } if (!pref->IsUserModifiable()) { error = "Trying to modify a preference that is not user modifiable"; return false; } if (!value.get()) { // Reset the preference to its default value. pref_service->ClearPref(name); return true; } if (!value->IsValid()) { error = "A valid value is required"; return false; } CefValueImpl* impl = static_cast(value.get()); CefValueImpl::ScopedLockedValue scoped_locked_value(impl); base::Value* impl_value = impl->GetValueUnsafe(); if (pref->GetType() != impl_value->type()) { error = base::StringPrintf( "Trying to set a preference of type %s to value of type %s", GetTypeString(pref->GetType()), GetTypeString(impl_value->type())); return false; } // PrefService will make a DeepCopy of |impl_value|. pref_service->Set(name, *impl_value); return true; } void CefRequestContextImpl::ClearCertificateExceptions( CefRefPtr callback) { GetBrowserContext( base::CreateSingleThreadTaskRunner({BrowserThread::UI}), base::Bind(&CefRequestContextImpl::ClearCertificateExceptionsInternal, this, callback)); } void CefRequestContextImpl::ClearHttpAuthCredentials( CefRefPtr callback) { GetBrowserContext( base::CreateSingleThreadTaskRunner({BrowserThread::UI}), base::Bind(&CefRequestContextImpl::ClearHttpAuthCredentialsInternal, this, callback)); } void CefRequestContextImpl::CloseAllConnections( CefRefPtr callback) { GetBrowserContext( base::CreateSingleThreadTaskRunner({BrowserThread::UI}), base::Bind(&CefRequestContextImpl::CloseAllConnectionsInternal, this, callback)); } void CefRequestContextImpl::ResolveHost( const CefString& origin, CefRefPtr callback) { GetBrowserContext(base::CreateSingleThreadTaskRunner({BrowserThread::UI}), base::Bind(&CefRequestContextImpl::ResolveHostInternal, this, origin, callback)); } void CefRequestContextImpl::LoadExtension( const CefString& root_directory, CefRefPtr manifest, CefRefPtr handler) { if (!CEF_CURRENTLY_ON_UIT()) { CEF_POST_TASK(CEF_UIT, base::BindOnce(&CefRequestContextImpl::LoadExtension, this, root_directory, manifest, handler)); return; } if (!extensions::ExtensionsEnabled()) { if (handler) handler->OnExtensionLoadFailed(ERR_ABORTED); return; } if (manifest && manifest->GetSize() > 0) { CefDictionaryValueImpl* value_impl = static_cast(manifest.get()); GetBrowserContext()->extension_system()->LoadExtension( base::WrapUnique(value_impl->CopyValue()), root_directory, false /* builtin */, this, handler); } else { GetBrowserContext()->extension_system()->LoadExtension( root_directory, false /* builtin */, this, handler); } } bool CefRequestContextImpl::DidLoadExtension(const CefString& extension_id) { CefRefPtr extension = GetExtension(extension_id); // GetLoaderContext() will return NULL for internal extensions. return extension && IsSame(extension->GetLoaderContext()); } bool CefRequestContextImpl::HasExtension(const CefString& extension_id) { return !!GetExtension(extension_id); } bool CefRequestContextImpl::GetExtensions( std::vector& extension_ids) { extension_ids.clear(); if (!CEF_CURRENTLY_ON_UIT()) { NOTREACHED() << "called on invalid thread"; return false; } if (!extensions::ExtensionsEnabled()) return false; extensions::CefExtensionSystem::ExtensionMap extension_map = GetBrowserContext()->extension_system()->GetExtensions(); extensions::CefExtensionSystem::ExtensionMap::const_iterator it = extension_map.begin(); for (; it != extension_map.end(); ++it) extension_ids.push_back(it->second->GetIdentifier()); return true; } CefRefPtr CefRequestContextImpl::GetExtension( const CefString& extension_id) { if (!CEF_CURRENTLY_ON_UIT()) { NOTREACHED() << "called on invalid thread"; return nullptr; } if (!extensions::ExtensionsEnabled()) return nullptr; return GetBrowserContext()->extension_system()->GetExtension(extension_id); } CefRefPtr CefRequestContextImpl::GetMediaRouter() { CefRefPtr media_router = new CefMediaRouterImpl(); InitializeMediaRouterOnUIThread(media_router); return media_router.get(); } void CefRequestContextImpl::OnRenderFrameCreated(int render_process_id, int render_frame_id, int frame_tree_node_id, bool is_main_frame, bool is_guest_view) { browser_context_->OnRenderFrameCreated(this, render_process_id, render_frame_id, frame_tree_node_id, is_main_frame, is_guest_view); } void CefRequestContextImpl::OnRenderFrameDeleted(int render_process_id, int render_frame_id, int frame_tree_node_id, bool is_main_frame, bool is_guest_view) { browser_context_->OnRenderFrameDeleted(this, render_process_id, render_frame_id, frame_tree_node_id, is_main_frame, is_guest_view); } // static CefRefPtr CefRequestContextImpl::GetOrCreateRequestContext(const Config& config) { if (config.is_global || (config.other && config.other->IsGlobal() && !config.handler)) { // Return the singleton global context. return CefContentBrowserClient::Get()->request_context(); } // The new context will be initialized later by EnsureBrowserContext(). return new CefRequestContextImpl(config); } CefRequestContextImpl::CefRequestContextImpl( const CefRequestContextImpl::Config& config) : config_(config) {} void CefRequestContextImpl::Initialize() { CEF_REQUIRE_UIT(); DCHECK(!browser_context_); if (config_.other) { // Share storage with |config_.other|. browser_context_ = CefBrowserContext::GetForContext(config_.other->GetBrowserContext()); DCHECK(browser_context_); } if (!browser_context_) { if (!config_.is_global) { // User-specified settings need to be normalized. CefContext::Get()->NormalizeRequestContextSettings(&config_.settings); } const base::FilePath& cache_path = base::FilePath(CefString(&config_.settings.cache_path)); if (!cache_path.empty()) { // Check if a CefBrowserContext is already globally registered for // the specified cache path. If so then use it. browser_context_ = CefBrowserContext::GetForCachePath(cache_path); } } if (!browser_context_) { // Create a new CefBrowserContext instance. If the cache path is non- // empty then this new instance will become the globally registered // CefBrowserContext for that path. Otherwise, this new instance will // be a completely isolated "incognito mode" context. browser_context_ = new CefBrowserContext(config_.settings); browser_context_->Initialize(); } else { // Share the same settings as the existing context. config_.settings = browser_context_->GetSettings(); } // We'll disassociate from |browser_context_| on destruction. browser_context_->AddCefRequestContext(this); if (config_.other) { // Clear the reference to |config_.other| after setting // |request_context_getter_|. This is the reverse order of checks in // IsSharedWith(). config_.other = nullptr; } if (config_.handler) config_.handler->OnRequestContextInitialized(this); } void CefRequestContextImpl::EnsureBrowserContext() { CEF_REQUIRE_UIT(); if (!browser_context()) Initialize(); DCHECK(browser_context()); } void CefRequestContextImpl::GetBrowserContextOnUIThread( scoped_refptr task_runner, const BrowserContextCallback& callback) { if (!CEF_CURRENTLY_ON_UIT()) { CEF_POST_TASK( CEF_UIT, base::Bind(&CefRequestContextImpl::GetBrowserContextOnUIThread, this, task_runner, callback)); return; } // Make sure the browser context exists. EnsureBrowserContext(); if (task_runner->BelongsToCurrentThread()) { // Execute the callback immediately. callback.Run(browser_context()); } else { // Execute the callback on the target thread. task_runner->PostTask(FROM_HERE, base::Bind(callback, browser_context())); } } void CefRequestContextImpl::PurgePluginListCacheInternal( bool reload_pages, CefBrowserContext* browser_context) { CEF_REQUIRE_UIT(); browser_context->ClearPluginLoadDecision(-1); content::PluginService::GetInstance()->PurgePluginListCache(browser_context, false); } void CefRequestContextImpl::ClearCertificateExceptionsInternal( CefRefPtr callback, CefBrowserContext* browser_context) { CEF_REQUIRE_UIT(); content::SSLHostStateDelegate* ssl_delegate = browser_context->GetSSLHostStateDelegate(); if (ssl_delegate) ssl_delegate->Clear(base::Callback()); if (callback) { CEF_POST_TASK(CEF_UIT, base::Bind(&CefCompletionCallback::OnComplete, callback)); } } void CefRequestContextImpl::ClearHttpAuthCredentialsInternal( CefRefPtr callback, CefBrowserContext* browser_context) { CEF_REQUIRE_UIT(); browser_context->GetNetworkContext()->ClearHttpAuthCache( base::Time(), base::Bind(&CefCompletionCallback::OnComplete, callback)); } void CefRequestContextImpl::CloseAllConnectionsInternal( CefRefPtr callback, CefBrowserContext* browser_context) { CEF_REQUIRE_UIT(); browser_context->GetNetworkContext()->CloseAllConnections( base::Bind(&CefCompletionCallback::OnComplete, callback)); } void CefRequestContextImpl::ResolveHostInternal( const CefString& origin, CefRefPtr callback, CefBrowserContext* browser_context) { CEF_REQUIRE_UIT(); // |helper| will be deleted in ResolveHostHelper::OnComplete(). ResolveHostHelper* helper = new ResolveHostHelper(callback); helper->Start(browser_context, origin); } void CefRequestContextImpl::InitializeCookieManagerOnUIThread( CefRefPtr cookie_manager, CefRefPtr callback) { if (!CEF_CURRENTLY_ON_UIT()) { CEF_POST_TASK( CEF_UIT, base::Bind(&CefRequestContextImpl::InitializeCookieManagerOnUIThread, this, cookie_manager, callback)); return; } auto browser_context = GetBrowserContext(); cookie_manager->Initialize(browser_context->getter(), callback); } void CefRequestContextImpl::InitializeMediaRouterOnUIThread( CefRefPtr media_router) { if (!CEF_CURRENTLY_ON_UIT()) { CEF_POST_TASK( CEF_UIT, base::Bind(&CefRequestContextImpl::InitializeMediaRouterOnUIThread, this, media_router)); return; } auto browser_context = GetBrowserContext(); media_router->Initialize(browser_context->getter()); } CefBrowserContext* CefRequestContextImpl::browser_context() const { return browser_context_; }