// Copyright (c) 2015 GitHub, Inc. // Use of this source code is governed by the MIT license that can be // found in the LICENSE file. #include "atom/browser/api/atom_api_session.h" #include #include #include "atom/browser/api/atom_api_cookies.h" #include "atom/browser/api/atom_api_download_item.h" #include "atom/browser/api/atom_api_web_contents.h" #include "atom/browser/api/save_page_handler.h" #include "atom/browser/atom_browser_context.h" #include "atom/browser/atom_browser_main_parts.h" #include "atom/browser/browser.h" #include "atom/common/native_mate_converters/callback.h" #include "atom/common/native_mate_converters/gurl_converter.h" #include "atom/common/native_mate_converters/file_path_converter.h" #include "atom/common/native_mate_converters/net_converter.h" #include "atom/common/node_includes.h" #include "base/files/file_path.h" #include "base/prefs/pref_service.h" #include "base/strings/string_util.h" #include "base/thread_task_runner_handle.h" #include "brightray/browser/net/devtools_network_conditions.h" #include "brightray/browser/net/devtools_network_controller.h" #include "chrome/common/pref_names.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/storage_partition.h" #include "native_mate/dictionary.h" #include "native_mate/object_template_builder.h" #include "net/base/load_flags.h" #include "net/disk_cache/disk_cache.h" #include "net/proxy/proxy_service.h" #include "net/proxy/proxy_config_service_fixed.h" #include "net/url_request/url_request_context.h" #include "net/url_request/url_request_context_getter.h" using content::BrowserThread; using content::StoragePartition; namespace { struct ClearStorageDataOptions { GURL origin; uint32 storage_types = StoragePartition::REMOVE_DATA_MASK_ALL; uint32 quota_types = StoragePartition::QUOTA_MANAGED_STORAGE_MASK_ALL; }; uint32 GetStorageMask(const std::vector& storage_types) { uint32 storage_mask = 0; for (const auto& it : storage_types) { auto type = base::StringToLowerASCII(it); if (type == "appcache") storage_mask |= StoragePartition::REMOVE_DATA_MASK_APPCACHE; else if (type == "cookies") storage_mask |= StoragePartition::REMOVE_DATA_MASK_COOKIES; else if (type == "filesystem") storage_mask |= StoragePartition::REMOVE_DATA_MASK_FILE_SYSTEMS; else if (type == "indexdb") storage_mask |= StoragePartition::REMOVE_DATA_MASK_INDEXEDDB; else if (type == "localstorage") storage_mask |= StoragePartition::REMOVE_DATA_MASK_LOCAL_STORAGE; else if (type == "shadercache") storage_mask |= StoragePartition::REMOVE_DATA_MASK_SHADER_CACHE; else if (type == "websql") storage_mask |= StoragePartition::REMOVE_DATA_MASK_WEBSQL; else if (type == "serviceworkers") storage_mask |= StoragePartition::REMOVE_DATA_MASK_SERVICE_WORKERS; } return storage_mask; } uint32 GetQuotaMask(const std::vector& quota_types) { uint32 quota_mask = 0; for (const auto& it : quota_types) { auto type = base::StringToLowerASCII(it); if (type == "temporary") quota_mask |= StoragePartition::QUOTA_MANAGED_STORAGE_MASK_TEMPORARY; else if (type == "persistent") quota_mask |= StoragePartition::QUOTA_MANAGED_STORAGE_MASK_PERSISTENT; else if (type == "syncable") quota_mask |= StoragePartition::QUOTA_MANAGED_STORAGE_MASK_SYNCABLE; } return quota_mask; } } // namespace namespace mate { template<> struct Converter { static bool FromV8(v8::Isolate* isolate, v8::Local val, ClearStorageDataOptions* out) { mate::Dictionary options; if (!ConvertFromV8(isolate, val, &options)) return false; options.Get("origin", &out->origin); std::vector types; if (options.Get("storages", &types)) out->storage_types = GetStorageMask(types); if (options.Get("quotas", &types)) out->quota_types = GetQuotaMask(types); return true; } }; template<> struct Converter { static bool FromV8(v8::Isolate* isolate, v8::Local val, net::ProxyConfig* out) { std::string proxy; if (!ConvertFromV8(isolate, val, &proxy)) return false; auto pac_url = GURL(proxy); if (pac_url.is_valid()) { out->set_pac_url(pac_url); } else { out->proxy_rules().ParseFromString(proxy); } return true; } }; } // namespace mate namespace atom { namespace api { namespace { // The wrapSession funtion which is implemented in JavaScript using WrapSessionCallback = base::Callback)>; WrapSessionCallback g_wrap_session; class ResolveProxyHelper { public: ResolveProxyHelper(AtomBrowserContext* browser_context, const GURL& url, Session::ResolveProxyCallback callback) : callback_(callback), original_thread_(base::ThreadTaskRunnerHandle::Get()) { scoped_refptr context_getter = browser_context->GetRequestContext(); context_getter->GetNetworkTaskRunner()->PostTask( FROM_HERE, base::Bind(&ResolveProxyHelper::ResolveProxy, base::Unretained(this), context_getter, url)); } void OnResolveProxyCompleted(int result) { std::string proxy; if (result == net::OK) proxy = proxy_info_.ToPacString(); original_thread_->PostTask(FROM_HERE, base::Bind(callback_, proxy)); delete this; } private: void ResolveProxy(scoped_refptr context_getter, const GURL& url) { DCHECK_CURRENTLY_ON(content::BrowserThread::IO); net::ProxyService* proxy_service = context_getter->GetURLRequestContext()->proxy_service(); net::CompletionCallback completion_callback = base::Bind(&ResolveProxyHelper::OnResolveProxyCompleted, base::Unretained(this)); // Start the request. int result = proxy_service->ResolveProxy( url, net::LOAD_NORMAL, &proxy_info_, completion_callback, &pac_req_, nullptr, net::BoundNetLog()); // Completed synchronously. if (result != net::ERR_IO_PENDING) completion_callback.Run(result); } Session::ResolveProxyCallback callback_; net::ProxyInfo proxy_info_; net::ProxyService::PacRequest* pac_req_; scoped_refptr original_thread_; DISALLOW_COPY_AND_ASSIGN(ResolveProxyHelper); }; // Runs the callback in UI thread. template void RunCallbackInUI(const base::Callback& callback, T... result) { BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, base::Bind(callback, result...)); } // Callback of HttpCache::GetBackend. void OnGetBackend(disk_cache::Backend** backend_ptr, const net::CompletionCallback& callback, int result) { if (result != net::OK) { RunCallbackInUI(callback, result); } else if (backend_ptr && *backend_ptr) { (*backend_ptr)->DoomAllEntries(base::Bind(&RunCallbackInUI, callback)); } else { RunCallbackInUI(callback, net::ERR_FAILED); } } void ClearHttpCacheInIO( const scoped_refptr& context_getter, const net::CompletionCallback& callback) { auto request_context = context_getter->GetURLRequestContext(); auto http_cache = request_context->http_transaction_factory()->GetCache(); if (!http_cache) RunCallbackInUI(callback, net::ERR_FAILED); // Call GetBackend and make the backend's ptr accessable in OnGetBackend. using BackendPtr = disk_cache::Backend*; BackendPtr* backend_ptr = new BackendPtr(nullptr); net::CompletionCallback on_get_backend = base::Bind(&OnGetBackend, base::Owned(backend_ptr), callback); int rv = http_cache->GetBackend(backend_ptr, on_get_backend); if (rv != net::ERR_IO_PENDING) on_get_backend.Run(net::OK); } void SetProxyInIO(net::URLRequestContextGetter* getter, const net::ProxyConfig& config, const base::Closure& callback) { auto proxy_service = getter->GetURLRequestContext()->proxy_service(); proxy_service->ResetConfigService(new net::ProxyConfigServiceFixed(config)); // Refetches and applies the new pac script if provided. proxy_service->ForceReloadProxyConfig(); RunCallbackInUI(callback); } } // namespace Session::Session(AtomBrowserContext* browser_context) : browser_context_(browser_context) { AttachAsUserData(browser_context); // Observe DownloadManger to get download notifications. content::BrowserContext::GetDownloadManager(browser_context)-> AddObserver(this); } Session::~Session() { content::BrowserContext::GetDownloadManager(browser_context())-> RemoveObserver(this); Destroy(); } void Session::OnDownloadCreated(content::DownloadManager* manager, content::DownloadItem* item) { auto web_contents = item->GetWebContents(); if (SavePageHandler::IsSavePageTypes(item->GetMimeType())) return; bool prevent_default = Emit( "will-download", DownloadItem::Create(isolate(), item), api::WebContents::CreateFrom(isolate(), web_contents)); if (prevent_default) { item->Cancel(true); item->Remove(); } } bool Session::IsDestroyed() const { return !browser_context_; } void Session::Destroy() { browser_context_ = nullptr; } void Session::ResolveProxy(const GURL& url, ResolveProxyCallback callback) { new ResolveProxyHelper(browser_context(), url, callback); } void Session::ClearCache(const net::CompletionCallback& callback) { BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, base::Bind(&ClearHttpCacheInIO, make_scoped_refptr(browser_context_->GetRequestContext()), callback)); } void Session::ClearStorageData(mate::Arguments* args) { // clearStorageData([options, ]callback) ClearStorageDataOptions options; args->GetNext(&options); base::Closure callback; if (!args->GetNext(&callback)) { args->ThrowError(); return; } auto storage_partition = content::BrowserContext::GetStoragePartition(browser_context(), nullptr); storage_partition->ClearData( options.storage_types, options.quota_types, options.origin, content::StoragePartition::OriginMatcherFunction(), base::Time(), base::Time::Max(), callback); } void Session::SetProxy(const net::ProxyConfig& config, const base::Closure& callback) { auto getter = browser_context_->GetRequestContext(); BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, base::Bind(&SetProxyInIO, base::Unretained(getter), config, callback)); } void Session::SetDownloadPath(const base::FilePath& path) { browser_context_->prefs()->SetFilePath( prefs::kDownloadDefaultDirectory, path); } void Session::EnableNetworkEmulation(const mate::Dictionary& options) { scoped_ptr conditions; bool offline = false; double latency, download_throughput, upload_throughput; if (options.Get("offline", &offline) && offline) { conditions.reset(new brightray::DevToolsNetworkConditions(offline)); } else { options.Get("latency", &latency); options.Get("downloadThroughput", &download_throughput); options.Get("uploadThroughput", &upload_throughput); conditions.reset( new brightray::DevToolsNetworkConditions(false, latency, download_throughput, upload_throughput)); } auto controller = browser_context_->GetDevToolsNetworkController(); BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, base::Bind(&brightray::DevToolsNetworkController::SetNetworkState, base::Unretained(controller), std::string(), base::Passed(&conditions))); } void Session::DisableNetworkEmulation() { scoped_ptr conditions( new brightray::DevToolsNetworkConditions(false)); auto controller = browser_context_->GetDevToolsNetworkController(); BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, base::Bind(&brightray::DevToolsNetworkController::SetNetworkState, base::Unretained(controller), std::string(), base::Passed(&conditions))); } v8::Local Session::Cookies(v8::Isolate* isolate) { if (cookies_.IsEmpty()) { auto handle = atom::api::Cookies::Create(isolate, browser_context()); cookies_.Reset(isolate, handle.ToV8()); } return v8::Local::New(isolate, cookies_); } mate::ObjectTemplateBuilder Session::GetObjectTemplateBuilder( v8::Isolate* isolate) { auto browser = base::Unretained(Browser::Get()); return mate::ObjectTemplateBuilder(isolate) .SetMethod("resolveProxy", &Session::ResolveProxy) .SetMethod("clearCache", &Session::ClearCache) .SetMethod("clearStorageData", &Session::ClearStorageData) .SetMethod("setProxy", &Session::SetProxy) .SetMethod("setDownloadPath", &Session::SetDownloadPath) .SetMethod("enableNetworkEmulation", &Session::EnableNetworkEmulation) .SetMethod("disableNetworkEmulation", &Session::DisableNetworkEmulation) .SetMethod("setCertificateVerifier", base::Bind(&Browser::SetCertificateVerifier, browser)) .SetMethod("removeCertificateVerifier", base::Bind(&Browser::RemoveCertificateVerifier, browser)) .SetProperty("cookies", &Session::Cookies); } // static mate::Handle Session::CreateFrom( v8::Isolate* isolate, AtomBrowserContext* browser_context) { auto existing = TrackableObject::FromWrappedClass(isolate, browser_context); if (existing) return mate::CreateHandle(isolate, static_cast(existing)); auto handle = mate::CreateHandle(isolate, new Session(browser_context)); g_wrap_session.Run(handle.ToV8()); return handle; } // static mate::Handle Session::FromPartition( v8::Isolate* isolate, const std::string& partition, bool in_memory) { auto browser_context = brightray::BrowserContext::From(partition, in_memory); return CreateFrom(isolate, static_cast(browser_context.get())); } void ClearWrapSession() { g_wrap_session.Reset(); } void SetWrapSession(const WrapSessionCallback& callback) { g_wrap_session = callback; // Cleanup the wrapper on exit. atom::AtomBrowserMainParts::Get()->RegisterDestructionCallback( base::Bind(ClearWrapSession)); } } // namespace api } // namespace atom namespace { void Initialize(v8::Local exports, v8::Local unused, v8::Local context, void* priv) { v8::Isolate* isolate = context->GetIsolate(); mate::Dictionary dict(isolate, exports); dict.SetMethod("fromPartition", &atom::api::Session::FromPartition); dict.SetMethod("_setWrapSession", &atom::api::SetWrapSession); } } // namespace NODE_MODULE_CONTEXT_AWARE_BUILTIN(atom_browser_session, Initialize)