diff options
-rw-r--r-- | android_webview/BUILD.gn | 2 | ||||
-rw-r--r-- | android_webview/android_webview.gyp | 2 | ||||
-rw-r--r-- | android_webview/android_webview_tests.gypi | 1 | ||||
-rw-r--r-- | android_webview/browser/aw_browser_context.cc | 6 | ||||
-rw-r--r-- | android_webview/browser/aw_browser_context.h | 5 | ||||
-rw-r--r-- | android_webview/browser/net/aw_cookie_store_wrapper.cc | 357 | ||||
-rw-r--r-- | android_webview/browser/net/aw_cookie_store_wrapper.h | 132 | ||||
-rw-r--r-- | android_webview/browser/net/aw_cookie_store_wrapper_unittest.cc | 49 | ||||
-rw-r--r-- | android_webview/browser/net/aw_url_request_context_getter.cc | 5 | ||||
-rw-r--r-- | android_webview/browser/net/aw_url_request_context_getter.h | 1 | ||||
-rw-r--r-- | android_webview/browser/net/init_native_callback.h | 14 | ||||
-rw-r--r-- | android_webview/native/cookie_manager.cc | 257 | ||||
-rw-r--r-- | android_webview/test/BUILD.gn | 1 |
13 files changed, 681 insertions, 151 deletions
diff --git a/android_webview/BUILD.gn b/android_webview/BUILD.gn index c9505c3..d3efe00 100644 --- a/android_webview/BUILD.gn +++ b/android_webview/BUILD.gn @@ -446,6 +446,8 @@ source_set("common") { "browser/jni_dependency_factory.h", "browser/net/android_stream_reader_url_request_job.cc", "browser/net/android_stream_reader_url_request_job.h", + "browser/net/aw_cookie_store_wrapper.cc", + "browser/net/aw_cookie_store_wrapper.h", "browser/net/aw_http_user_agent_settings.cc", "browser/net/aw_http_user_agent_settings.h", "browser/net/aw_network_change_notifier.cc", diff --git a/android_webview/android_webview.gyp b/android_webview/android_webview.gyp index f9cd183..8378d0e 100644 --- a/android_webview/android_webview.gyp +++ b/android_webview/android_webview.gyp @@ -350,6 +350,8 @@ 'browser/gl_view_renderer_manager.h', 'browser/net/android_stream_reader_url_request_job.cc', 'browser/net/android_stream_reader_url_request_job.h', + 'browser/net/aw_cookie_store_wrapper.cc', + 'browser/net/aw_cookie_store_wrapper.h', 'browser/net/aw_http_user_agent_settings.h', 'browser/net/aw_http_user_agent_settings.cc', 'browser/net/aw_network_change_notifier.cc', diff --git a/android_webview/android_webview_tests.gypi b/android_webview/android_webview_tests.gypi index 6a2cf65..ab92ffc 100644 --- a/android_webview/android_webview_tests.gypi +++ b/android_webview/android_webview_tests.gypi @@ -146,6 +146,7 @@ 'browser/aw_form_database_service_unittest.cc', 'browser/browser_view_renderer_unittest.cc', 'browser/net/android_stream_reader_url_request_job_unittest.cc', + 'browser/net/aw_cookie_store_wrapper_unittest.cc', 'browser/net/input_stream_reader_unittest.cc', 'browser/test/fake_window.cc', 'browser/test/fake_window.h', diff --git a/android_webview/browser/aw_browser_context.cc b/android_webview/browser/aw_browser_context.cc index a5e985f..02aa751 100644 --- a/android_webview/browser/aw_browser_context.cc +++ b/android_webview/browser/aw_browser_context.cc @@ -15,7 +15,6 @@ #include "android_webview/browser/aw_resource_context.h" #include "android_webview/browser/jni_dependency_factory.h" #include "android_webview/browser/net/aw_url_request_context_getter.h" -#include "android_webview/browser/net/init_native_callback.h" #include "android_webview/common/aw_content_client.h" #include "base/base_paths_android.h" #include "base/bind.h" @@ -42,7 +41,6 @@ #include "content/public/browser/ssl_host_state_delegate.h" #include "content/public/browser/storage_partition.h" #include "content/public/browser/web_contents.h" -#include "net/cookies/cookie_store.h" #include "net/proxy/proxy_config_service_android.h" #include "net/proxy/proxy_service.h" @@ -182,7 +180,6 @@ void AwBrowserContext::SetLegacyCacheRemovalDelayForTest(int delay_ms) { } void AwBrowserContext::PreMainMessageLoopRun() { - cookie_store_ = CreateCookieStore(this); FilePath cache_path; const FilePath fallback_cache_dir = GetPath().Append(FILE_PATH_LITERAL("Cache")); @@ -205,8 +202,7 @@ void AwBrowserContext::PreMainMessageLoopRun() { InitUserPrefService(); url_request_context_getter_ = new AwURLRequestContextGetter( - cache_path, cookie_store_.get(), CreateProxyConfigService(), - user_pref_service_.get()); + cache_path, CreateProxyConfigService(), user_pref_service_.get()); data_reduction_proxy_io_data_.reset( new data_reduction_proxy::DataReductionProxyIOData( diff --git a/android_webview/browser/aw_browser_context.h b/android_webview/browser/aw_browser_context.h index 94f9f57..84f477d 100644 --- a/android_webview/browser/aw_browser_context.h +++ b/android_webview/browser/aw_browser_context.h @@ -37,10 +37,6 @@ class DataReductionProxyService; class DataReductionProxySettings; } -namespace net { -class CookieStore; -} - namespace policy { class URLBlacklistManager; class BrowserPolicyConnectorBase; @@ -149,7 +145,6 @@ class AwBrowserContext : public content::BrowserContext, base::FilePath context_storage_path_; JniDependencyFactory* native_factory_; - scoped_refptr<net::CookieStore> cookie_store_; scoped_refptr<AwURLRequestContextGetter> url_request_context_getter_; scoped_refptr<AwQuotaManagerBridge> quota_manager_bridge_; scoped_ptr<AwFormDatabaseService> form_database_service_; diff --git a/android_webview/browser/net/aw_cookie_store_wrapper.cc b/android_webview/browser/net/aw_cookie_store_wrapper.cc new file mode 100644 index 0000000..6b9b063 --- /dev/null +++ b/android_webview/browser/net/aw_cookie_store_wrapper.cc @@ -0,0 +1,357 @@ +// Copyright 2016 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 "android_webview/browser/net/aw_cookie_store_wrapper.h" + +#include <string> + +#include "android_webview/browser/net/init_native_callback.h" +#include "base/memory/ref_counted_delete_on_message_loop.h" +#include "base/thread_task_runner_handle.h" +#include "url/gurl.h" + +namespace android_webview { + +namespace { + +// Posts |task| to the thread that the global CookieStore lives on. +void PostTaskToCookieStoreTaskRunner(const base::Closure& task) { + GetCookieStoreTaskRunner()->PostTask(FROM_HERE, task); +} + +// Wraps a subscription to cookie change notifications for the global +// CookieStore for a consumer that lives on another thread. Handles passing +// messages between thread, and destroys itself when the consumer unsubscribes. +// Must be created on the consumer's thread. Each instance only supports a +// single subscription. +class SubscriptionWrapper { + public: + SubscriptionWrapper() : weak_factory_(this) {} + + scoped_ptr<net::CookieStore::CookieChangedSubscription> Subscribe( + const GURL& url, + const std::string& name, + const net::CookieStore::CookieChangedCallback& callback) { + // This class is only intended to be used for a single subscription. + DCHECK(callback_list_.empty()); + + nested_subscription_ = + new NestedSubscription(url, name, weak_factory_.GetWeakPtr()); + return callback_list_.Add(callback); + } + + private: + // The NestedSubscription is responsible for creating and managing the + // underlying subscription to the real CookieStore, and posting notifications + // back to |callback_list_|. + class NestedSubscription + : public base::RefCountedDeleteOnMessageLoop<NestedSubscription> { + public: + NestedSubscription(const GURL& url, + const std::string& name, + base::WeakPtr<SubscriptionWrapper> subscription_wrapper) + : base::RefCountedDeleteOnMessageLoop<NestedSubscription>( + GetCookieStoreTaskRunner()), + subscription_wrapper_(subscription_wrapper), + client_task_runner_(base::ThreadTaskRunnerHandle::Get()) { + PostTaskToCookieStoreTaskRunner( + base::Bind(&NestedSubscription::Subscribe, this, url, name)); + } + + private: + friend class base::RefCountedDeleteOnMessageLoop<NestedSubscription>; + friend class base::DeleteHelper<NestedSubscription>; + + ~NestedSubscription() {} + + void Subscribe(const GURL& url, const std::string& name) { + GetCookieStore()->AddCallbackForCookie( + url, name, base::Bind(&NestedSubscription::OnChanged, this)); + } + + void OnChanged(const net::CanonicalCookie& cookie, bool removed) { + client_task_runner_->PostTask( + FROM_HERE, base::Bind(&SubscriptionWrapper::OnChanged, + subscription_wrapper_, cookie, removed)); + } + + base::WeakPtr<SubscriptionWrapper> subscription_wrapper_; + scoped_refptr<base::TaskRunner> client_task_runner_; + + scoped_ptr<net::CookieStore::CookieChangedSubscription> subscription_; + + DISALLOW_COPY_AND_ASSIGN(NestedSubscription); + }; + + void OnChanged(const net::CanonicalCookie& cookie, bool removed) { + callback_list_.Notify(cookie, removed); + } + + // The "list" only had one entry, so can just clean up now. + void OnUnsubscribe() { delete this; } + + scoped_refptr<NestedSubscription> nested_subscription_; + net::CookieStore::CookieChangedCallbackList callback_list_; + base::WeakPtrFactory<SubscriptionWrapper> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(SubscriptionWrapper); +}; + +void SetCookieWithOptionsAsyncOnCookieThread( + const GURL& url, + const std::string& cookie_line, + const net::CookieOptions& options, + const net::CookieStore::SetCookiesCallback& callback) { + GetCookieStore()->SetCookieWithOptionsAsync(url, cookie_line, options, + callback); +} + +void SetCookieWithDetailsAsyncOnCookieThread( + const GURL& url, + const std::string& name, + const std::string& value, + const std::string& domain, + const std::string& path, + base::Time creation_time, + base::Time expiration_time, + base::Time last_access_time, + bool secure, + bool http_only, + bool same_site, + bool enforce_strict_secure, + net::CookiePriority priority, + const net::CookieStore::SetCookiesCallback& callback) { + GetCookieStore()->SetCookieWithDetailsAsync( + url, name, value, domain, path, creation_time, expiration_time, + last_access_time, secure, http_only, same_site, enforce_strict_secure, + priority, callback); +} + +void GetCookiesWithOptionsAsyncOnCookieThread( + const GURL& url, + const net::CookieOptions& options, + const net::CookieStore::GetCookiesCallback& callback) { + GetCookieStore()->GetCookiesWithOptionsAsync(url, options, callback); +} + +void GetCookieListWithOptionsAsyncOnCookieThread( + const GURL& url, + const net::CookieOptions& options, + const net::CookieStore::GetCookieListCallback& callback) { + GetCookieStore()->GetCookieListWithOptionsAsync(url, options, callback); +} + +void GetAllCookiesAsyncOnCookieThread( + const net::CookieStore::GetCookieListCallback& callback) { + GetCookieStore()->GetAllCookiesAsync(callback); +} + +void DeleteCookieAsyncOnCookieThread(const GURL& url, + const std::string& cookie_name, + const base::Closure& callback) { + GetCookieStore()->DeleteCookieAsync(url, cookie_name, callback); +} + +void DeleteCanonicalCookieAsyncOnCookieThread( + const net::CanonicalCookie& cookie, + const net::CookieStore::DeleteCallback& callback) { + GetCookieStore()->DeleteCanonicalCookieAsync(cookie, callback); +} + +void DeleteAllCreatedBetweenAsyncOnCookieThread( + const base::Time& delete_begin, + const base::Time& delete_end, + const net::CookieStore::DeleteCallback& callback) { + GetCookieStore()->DeleteAllCreatedBetweenAsync(delete_begin, delete_end, + callback); +} + +void DeleteAllCreatedBetweenForHostAsyncOnCookieThread( + const base::Time delete_begin, + const base::Time delete_end, + const GURL& url, + const net::CookieStore::DeleteCallback& callback) { + GetCookieStore()->DeleteAllCreatedBetweenForHostAsync( + delete_begin, delete_end, url, callback); +} + +void DeleteSessionCookiesAsyncOnCookieThread( + const net::CookieStore::DeleteCallback& callback) { + GetCookieStore()->DeleteSessionCookiesAsync(callback); +} + +void FlushStoreOnCookieThread(const base::Closure& callback) { + GetCookieStore()->FlushStore(callback); +} + +void SetForceKeepSessionStateOnCookieThread() { + GetCookieStore()->SetForceKeepSessionState(); +} + +} // namespace + +AwCookieStoreWrapper::AwCookieStoreWrapper() + : client_task_runner_(base::ThreadTaskRunnerHandle::Get()), + weak_factory_(this) {} + +void AwCookieStoreWrapper::SetCookieWithOptionsAsync( + const GURL& url, + const std::string& cookie_line, + const net::CookieOptions& options, + const net::CookieStore::SetCookiesCallback& callback) { + DCHECK(client_task_runner_->RunsTasksOnCurrentThread()); + PostTaskToCookieStoreTaskRunner( + base::Bind(&SetCookieWithOptionsAsyncOnCookieThread, url, cookie_line, + options, CreateWrappedCallback<bool>(callback))); +} + +void AwCookieStoreWrapper::SetCookieWithDetailsAsync( + const GURL& url, + const std::string& name, + const std::string& value, + const std::string& domain, + const std::string& path, + base::Time creation_time, + base::Time expiration_time, + base::Time last_access_time, + bool secure, + bool http_only, + bool same_site, + bool enforce_strict_secure, + net::CookiePriority priority, + const SetCookiesCallback& callback) { + DCHECK(client_task_runner_->RunsTasksOnCurrentThread()); + PostTaskToCookieStoreTaskRunner( + base::Bind(&SetCookieWithDetailsAsyncOnCookieThread, url, name, value, + domain, path, creation_time, expiration_time, last_access_time, + secure, http_only, same_site, enforce_strict_secure, priority, + CreateWrappedCallback<bool>(callback))); +} + +void AwCookieStoreWrapper::GetCookiesWithOptionsAsync( + const GURL& url, + const net::CookieOptions& options, + const GetCookiesCallback& callback) { + DCHECK(client_task_runner_->RunsTasksOnCurrentThread()); + PostTaskToCookieStoreTaskRunner( + base::Bind(&GetCookiesWithOptionsAsyncOnCookieThread, url, options, + CreateWrappedCallback<const std::string&>(callback))); +} + +void AwCookieStoreWrapper::GetCookieListWithOptionsAsync( + const GURL& url, + const net::CookieOptions& options, + const GetCookieListCallback& callback) { + DCHECK(client_task_runner_->RunsTasksOnCurrentThread()); + PostTaskToCookieStoreTaskRunner( + base::Bind(&GetCookieListWithOptionsAsyncOnCookieThread, url, options, + CreateWrappedCallback<const net::CookieList&>(callback))); +} + +void AwCookieStoreWrapper::GetAllCookiesAsync( + const GetCookieListCallback& callback) { + DCHECK(client_task_runner_->RunsTasksOnCurrentThread()); + PostTaskToCookieStoreTaskRunner( + base::Bind(&GetAllCookiesAsyncOnCookieThread, + CreateWrappedCallback<const net::CookieList&>(callback))); +} + +void AwCookieStoreWrapper::DeleteCookieAsync(const GURL& url, + const std::string& cookie_name, + const base::Closure& callback) { + DCHECK(client_task_runner_->RunsTasksOnCurrentThread()); + PostTaskToCookieStoreTaskRunner( + base::Bind(&DeleteCookieAsyncOnCookieThread, url, cookie_name, + CreateWrappedClosureCallback(callback))); +} + +void AwCookieStoreWrapper::DeleteCanonicalCookieAsync( + const net::CanonicalCookie& cookie, + const DeleteCallback& callback) { + DCHECK(client_task_runner_->RunsTasksOnCurrentThread()); + PostTaskToCookieStoreTaskRunner( + base::Bind(&DeleteCanonicalCookieAsyncOnCookieThread, cookie, + CreateWrappedCallback<int>(callback))); +} + +void AwCookieStoreWrapper::DeleteAllCreatedBetweenAsync( + const base::Time& delete_begin, + const base::Time& delete_end, + const DeleteCallback& callback) { + DCHECK(client_task_runner_->RunsTasksOnCurrentThread()); + PostTaskToCookieStoreTaskRunner( + base::Bind(&DeleteAllCreatedBetweenAsyncOnCookieThread, delete_begin, + delete_end, CreateWrappedCallback<int>(callback))); +} + +void AwCookieStoreWrapper::DeleteAllCreatedBetweenForHostAsync( + const base::Time delete_begin, + const base::Time delete_end, + const GURL& url, + const DeleteCallback& callback) { + DCHECK(client_task_runner_->RunsTasksOnCurrentThread()); + PostTaskToCookieStoreTaskRunner(base::Bind( + &DeleteAllCreatedBetweenForHostAsyncOnCookieThread, delete_begin, + delete_end, url, CreateWrappedCallback<int>(callback))); +} + +void AwCookieStoreWrapper::DeleteSessionCookiesAsync( + const DeleteCallback& callback) { + DCHECK(client_task_runner_->RunsTasksOnCurrentThread()); + PostTaskToCookieStoreTaskRunner( + base::Bind(&DeleteSessionCookiesAsyncOnCookieThread, + CreateWrappedCallback<int>(callback))); +} + +void AwCookieStoreWrapper::FlushStore(const base::Closure& callback) { + DCHECK(client_task_runner_->RunsTasksOnCurrentThread()); + PostTaskToCookieStoreTaskRunner(base::Bind( + &FlushStoreOnCookieThread, CreateWrappedClosureCallback(callback))); +} + +void AwCookieStoreWrapper::SetForceKeepSessionState() { + DCHECK(client_task_runner_->RunsTasksOnCurrentThread()); + PostTaskToCookieStoreTaskRunner( + base::Bind(&SetForceKeepSessionStateOnCookieThread)); +} + +net::CookieMonster* AwCookieStoreWrapper::GetCookieMonster() { + return nullptr; +} + +scoped_ptr<net::CookieStore::CookieChangedSubscription> +AwCookieStoreWrapper::AddCallbackForCookie( + const GURL& url, + const std::string& name, + const CookieChangedCallback& callback) { + DCHECK(client_task_runner_->RunsTasksOnCurrentThread()); + + // The SubscriptionWrapper is owned by the subscription itself, and has no + // connection to the AwCookieStoreWrapper after creation. Other CookieStore + // implementations DCHECK if a subscription outlasts the cookie store, + // unfortunately, this design makes DCHECKing if there's an outstanding + // subscription when the AwCookieStoreWrapper is destroyed a bit ugly. + // TODO(mmenke): Still worth adding a DCHECK? + SubscriptionWrapper* subscription = new SubscriptionWrapper(); + return subscription->Subscribe(url, name, callback); +} + +AwCookieStoreWrapper::~AwCookieStoreWrapper() {} + +base::Closure AwCookieStoreWrapper::CreateWrappedClosureCallback( + const base::Closure& callback) { + if (callback.is_null()) + return callback; + return base::Bind(base::IgnoreResult(&base::TaskRunner::PostTask), + client_task_runner_, FROM_HERE, + base::Bind(&AwCookieStoreWrapper::RunClosureCallback, + weak_factory_.GetWeakPtr(), callback)); +} + +void AwCookieStoreWrapper::RunClosureCallback(const base::Closure& callback) { + DCHECK(client_task_runner_->RunsTasksOnCurrentThread()); + callback.Run(); +} + +} // namespace android_webview diff --git a/android_webview/browser/net/aw_cookie_store_wrapper.h b/android_webview/browser/net/aw_cookie_store_wrapper.h new file mode 100644 index 0000000..e308fb1 --- /dev/null +++ b/android_webview/browser/net/aw_cookie_store_wrapper.h @@ -0,0 +1,132 @@ +// Copyright 2016 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 <string> + +#include "base/bind.h" +#include "base/location.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "base/single_thread_task_runner.h" +#include "base/task_runner.h" +#include "base/time/time.h" +#include "net/cookies/canonical_cookie.h" +#include "net/cookies/cookie_constants.h" +#include "net/cookies/cookie_store.h" + +namespace android_webview { + +// A cross-threaded cookie store implementation that wraps the CookieManager's +// CookieStore. It posts tasks to the CookieStore's thread, and invokes +// callbacks on the originating thread. Deleting it cancels pending callbacks. +// This is needed to allow Webview to run the CookieStore on its own thread, to +// enable synchronous calls into the store on the IO Thread from Java. +// +// AwCookieStoreWrapper will only grab the CookieStore pointer from the +// CookieManager when it's needed, allowing for lazy creation of the +// CookieStore. +// +// AwCookieStoreWrapper may only be called from the thread on which it's +// created. +// +// The CookieManager must outlive the AwCookieStoreWrapper. +class AwCookieStoreWrapper : public net::CookieStore { + public: + AwCookieStoreWrapper(); + + // CookieStore implementation: + void SetCookieWithOptionsAsync(const GURL& url, + const std::string& cookie_line, + const net::CookieOptions& options, + const SetCookiesCallback& callback) override; + void SetCookieWithDetailsAsync(const GURL& url, + const std::string& name, + const std::string& value, + const std::string& domain, + const std::string& path, + base::Time creation_time, + base::Time expiration_time, + base::Time last_access_time, + bool secure, + bool http_only, + bool same_site, + bool enforce_strict_secure, + net::CookiePriority priority, + const SetCookiesCallback& callback) override; + void GetCookiesWithOptionsAsync(const GURL& url, + const net::CookieOptions& options, + const GetCookiesCallback& callback) override; + void GetCookieListWithOptionsAsync( + const GURL& url, + const net::CookieOptions& options, + const GetCookieListCallback& callback) override; + void GetAllCookiesAsync(const GetCookieListCallback& callback) override; + void DeleteCookieAsync(const GURL& url, + const std::string& cookie_name, + const base::Closure& callback) override; + void DeleteCanonicalCookieAsync(const net::CanonicalCookie& cookie, + const DeleteCallback& callback) override; + void DeleteAllCreatedBetweenAsync(const base::Time& delete_begin, + const base::Time& delete_end, + const DeleteCallback& callback) override; + void DeleteAllCreatedBetweenForHostAsync( + const base::Time delete_begin, + const base::Time delete_end, + const GURL& url, + const DeleteCallback& callback) override; + void DeleteSessionCookiesAsync(const DeleteCallback& callback) override; + void FlushStore(const base::Closure& callback) override; + void SetForceKeepSessionState() override; + net::CookieMonster* GetCookieMonster() override; + scoped_ptr<CookieChangedSubscription> AddCallbackForCookie( + const GURL& url, + const std::string& name, + const CookieChangedCallback& callback) override; + + private: + ~AwCookieStoreWrapper() override; + + // Used by CreateWrappedCallback below. Takes an arugment of Type and posts + // a task to |task_runner| to invoke |callback| with that argument. If + // |weak_cookie_store| is deleted before the task is run, the task will not + // be run. + template <class Type> + static void RunCallbackOnClientThread( + base::TaskRunner* task_runner, + base::WeakPtr<AwCookieStoreWrapper> weak_cookie_store, + base::Callback<void(Type)> callback, + Type argument) { + task_runner->PostTask( + FROM_HERE, + base::Bind(&AwCookieStoreWrapper::RunClosureCallback, weak_cookie_store, + base::Bind(callback, argument))); + } + + // Returns a base::Callback that takes an argument of Type and posts a task to + // the |client_task_runner_| to invoke |callback| with that argument. + template <class Type> + base::Callback<void(Type)> CreateWrappedCallback( + base::Callback<void(Type)> callback) { + if (callback.is_null()) + return callback; + return base::Bind(&AwCookieStoreWrapper::RunCallbackOnClientThread<Type>, + client_task_runner_, weak_factory_.GetWeakPtr(), + callback); + } + + // Returns a base::Closure that posts a task to the |client_task_runner_| to + // invoke |callback|. + base::Closure CreateWrappedClosureCallback(const base::Closure& callback); + + // Runs |callback|. Used to prevent callbacks from being invoked after the + // AwCookieStoreWrapper has been destroyed. + void RunClosureCallback(const base::Closure& callback); + + scoped_refptr<base::SingleThreadTaskRunner> client_task_runner_; + + base::WeakPtrFactory<AwCookieStoreWrapper> weak_factory_; +}; + +} // namesspace android_webview diff --git a/android_webview/browser/net/aw_cookie_store_wrapper_unittest.cc b/android_webview/browser/net/aw_cookie_store_wrapper_unittest.cc new file mode 100644 index 0000000..3567afc --- /dev/null +++ b/android_webview/browser/net/aw_cookie_store_wrapper_unittest.cc @@ -0,0 +1,49 @@ +// Copyright 2016 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 "android_webview/browser/net/aw_cookie_store_wrapper.h" + +#include "base/memory/ref_counted.h" +#include "net/cookies/cookie_store.h" +#include "net/cookies/cookie_store_test_callbacks.h" +#include "net/cookies/cookie_store_unittest.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace android_webview { + +struct AwCookieStoreWrapperTestTraits { + static scoped_refptr<net::CookieStore> Create() { + scoped_refptr<net::CookieStore> cookie_store(new AwCookieStoreWrapper()); + + // Android Webview can run multiple tests without restarting the binary, + // so have to delete any cookies the global store may have from an earlier + // test. + net::ResultSavingCookieCallback<int> callback; + cookie_store->DeleteAllAsync( + base::Bind(&net::ResultSavingCookieCallback<int>::Run, + base::Unretained(&callback))); + callback.WaitUntilDone(); + + return cookie_store; + } + + static const bool is_cookie_monster = false; + static const bool supports_http_only = true; + static const bool supports_non_dotted_domains = true; + static const bool preserves_trailing_dots = true; + static const bool filters_schemes = true; + static const bool has_path_prefix_bug = false; + static const int creation_time_granularity_in_ms = 0; + static const bool enforce_strict_secure = false; +}; + +} // namespace android_webview + +// Run the standard cookie tests with AwCookieStoreWrapper. Macro must be in +// net namespace. +namespace net { +INSTANTIATE_TYPED_TEST_CASE_P(AwCookieStoreWrapper, + CookieStoreTest, + android_webview::AwCookieStoreWrapperTestTraits); +} // namespace net diff --git a/android_webview/browser/net/aw_url_request_context_getter.cc b/android_webview/browser/net/aw_url_request_context_getter.cc index 7b54fa9..600ea17 100644 --- a/android_webview/browser/net/aw_url_request_context_getter.cc +++ b/android_webview/browser/net/aw_url_request_context_getter.cc @@ -9,6 +9,7 @@ #include "android_webview/browser/aw_browser_context.h" #include "android_webview/browser/aw_content_browser_client.h" +#include "android_webview/browser/net/aw_cookie_store_wrapper.h" #include "android_webview/browser/net/aw_http_user_agent_settings.h" #include "android_webview/browser/net/aw_network_delegate.h" #include "android_webview/browser/net/aw_request_interceptor.h" @@ -166,13 +167,11 @@ scoped_ptr<net::URLRequestJobFactory> CreateJobFactory( AwURLRequestContextGetter::AwURLRequestContextGetter( const base::FilePath& cache_path, - net::CookieStore* cookie_store, scoped_ptr<net::ProxyConfigService> config_service, PrefService* user_pref_service) : cache_path_(cache_path), net_log_(new net::NetLog()), proxy_config_service_(std::move(config_service)), - cookie_store_(cookie_store), http_user_agent_settings_(new AwHttpUserAgentSettings()) { // CreateSystemProxyConfigService for Android must be called on main thread. DCHECK_CURRENTLY_ON(BrowserThread::UI); @@ -201,6 +200,8 @@ void AwURLRequestContextGetter::InitializeURLRequestContext() { DCHECK_CURRENTLY_ON(BrowserThread::IO); DCHECK(!url_request_context_); + cookie_store_ = new AwCookieStoreWrapper(); + net::URLRequestContextBuilder builder; scoped_ptr<AwNetworkDelegate> aw_network_delegate(new AwNetworkDelegate()); diff --git a/android_webview/browser/net/aw_url_request_context_getter.h b/android_webview/browser/net/aw_url_request_context_getter.h index 46c2146..763a45c 100644 --- a/android_webview/browser/net/aw_url_request_context_getter.h +++ b/android_webview/browser/net/aw_url_request_context_getter.h @@ -36,7 +36,6 @@ class AwURLRequestContextGetter : public net::URLRequestContextGetter { public: AwURLRequestContextGetter( const base::FilePath& cache_path, - net::CookieStore* cookie_store, scoped_ptr<net::ProxyConfigService> config_service, PrefService* pref_service); diff --git a/android_webview/browser/net/init_native_callback.h b/android_webview/browser/net/init_native_callback.h index d98867c..34a65c6 100644 --- a/android_webview/browser/net/init_native_callback.h +++ b/android_webview/browser/net/init_native_callback.h @@ -8,6 +8,10 @@ #include "base/memory/ref_counted.h" #include "base/memory/scoped_ptr.h" +namespace base { +class SingleThreadTaskRunner; +} + namespace net { class CookieStore; class URLRequestInterceptor; @@ -16,9 +20,13 @@ class URLRequestInterceptor; namespace android_webview { class AwBrowserContext; -// Called when the CookieMonster needs to be created. -scoped_refptr<net::CookieStore> CreateCookieStore( - AwBrowserContext* browser_context); +// Gets the TaskRunner that the CookieStore must be called on. +scoped_refptr<base::SingleThreadTaskRunner> GetCookieStoreTaskRunner(); + +// Gets a pointer to the CookieStore managed by the CookieManager. +// The CookieStore is never deleted. May only be called on the +// CookieStore's TaskRunner. +net::CookieStore* GetCookieStore(); // Called lazily when the job factory is being constructed. scoped_ptr<net::URLRequestInterceptor> diff --git a/android_webview/native/cookie_manager.cc b/android_webview/native/cookie_manager.cc index 08f8246..ebbc67f 100644 --- a/android_webview/native/cookie_manager.cc +++ b/android_webview/native/cookie_manager.cc @@ -20,6 +20,7 @@ #include "base/files/file_util.h" #include "base/lazy_instance.h" #include "base/location.h" +#include "base/logging.h" #include "base/path_service.h" #include "base/single_thread_task_runner.h" #include "base/synchronization/lock.h" @@ -33,6 +34,7 @@ #include "jni/AwCookieManager_jni.h" #include "net/cookies/cookie_monster.h" #include "net/cookies/cookie_options.h" +#include "net/cookies/cookie_store.h" #include "net/extras/sqlite/cookie_crypto_delegate.h" #include "net/url_request/url_request_context.h" #include "url/url_constants.h" @@ -44,7 +46,6 @@ using base::android::ConvertJavaStringToUTF16; using base::android::ScopedJavaGlobalRef; using content::BrowserThread; using net::CookieList; -using net::CookieMonster; // In the future, we may instead want to inject an explicit CookieStore // dependency into this object during process initialization to avoid @@ -74,8 +75,8 @@ class BoolCookieCallbackHolder { void Invoke(bool result) { if (!callback_.is_null()) { JNIEnv* env = base::android::AttachCurrentThread(); - Java_AwCookieManager_invokeBooleanCookieCallback( - env, callback_.obj(), result); + Java_AwCookieManager_invokeBooleanCookieCallback(env, callback_.obj(), + result); } } @@ -144,19 +145,28 @@ void GetUserDataDir(FilePath* user_data_dir) { } } +// CookieManager creates and owns Webview's CookieStore, in addition to handling +// calls into the CookieStore from Java. +// +// Since Java calls can be made on the IO Thread, and must synchronously return +// a result, and the CookieStore API allows it to asynchronously return results, +// the CookieStore must be run on its own thread, to prevent deadlock. class CookieManager { public: static CookieManager* GetInstance(); - scoped_refptr<net::CookieStore> GetCookieStore(); + // Returns the TaskRunner on which the CookieStore lives. + base::SingleThreadTaskRunner* GetCookieStoreTaskRunner(); + // Returns the CookieStore, creating it if necessary. This must only be called + // on the CookieStore TaskRunner. + net::CookieStore* GetCookieStore(); void SetShouldAcceptCookies(bool accept); bool GetShouldAcceptCookies(); void SetCookie(const GURL& host, const std::string& cookie_value, scoped_ptr<BoolCookieCallbackHolder> callback); - void SetCookieSync(const GURL& host, - const std::string& cookie_value); + void SetCookieSync(const GURL& host, const std::string& cookie_value); std::string GetCookie(const GURL& host); void RemoveSessionCookies(scoped_ptr<BoolCookieCallbackHolder> callback); void RemoveAllCookies(scoped_ptr<BoolCookieCallbackHolder> callback); @@ -179,10 +189,9 @@ class CookieManager { void ExecCookieTaskSync(const base::Callback<void(base::Closure)>& task); void ExecCookieTask(const base::Closure& task); - void SetCookieHelper( - const GURL& host, - const std::string& value, - BoolCallback callback); + void SetCookieHelper(const GURL& host, + const std::string& value, + BoolCallback callback); void GetCookieValueAsyncHelper(const GURL& host, std::string* result, @@ -200,93 +209,58 @@ class CookieManager { void HasCookiesAsyncHelper(bool* result, base::Closure complete); void HasCookiesCompleted(base::Closure complete, bool* result, - const CookieList& cookies); + const net::CookieList& cookies); - void CreateCookieMonster( - const FilePath& user_data_dir, - const scoped_refptr<base::SequencedTaskRunner>& client_task_runner, - const scoped_refptr<base::SequencedTaskRunner>& background_task_runner); - void EnsureCookieMonsterExistsLocked(); - bool AllowFileSchemeCookiesLocked(); - void SetAcceptFileSchemeCookiesLocked(bool accept); + // This protects the following two bools, as they're used on multiple threads. + base::Lock accept_file_scheme_cookies_lock_; + // True if cookies should be allowed for file URLs. Can only be changed prior + // to creating the CookieStore. + bool accept_file_scheme_cookies_; + // True once the cookie store has been created. Just used to track when + // |accept_file_scheme_cookies_| can no longer be modified. + bool cookie_store_created_; - scoped_refptr<net::CookieMonster> cookie_monster_; - scoped_refptr<base::SingleThreadTaskRunner> cookie_monster_task_runner_; - base::Lock cookie_monster_lock_; + base::Thread cookie_store_client_thread_; + base::Thread cookie_store_backend_thread_; - scoped_ptr<base::Thread> cookie_monster_client_thread_; - scoped_ptr<base::Thread> cookie_monster_backend_thread_; + scoped_refptr<base::SingleThreadTaskRunner> cookie_store_task_runner_; + scoped_refptr<net::CookieStore> cookie_store_; DISALLOW_COPY_AND_ASSIGN(CookieManager); }; base::LazyInstance<CookieManager>::Leaky g_lazy_instance; +} // namespace + // static CookieManager* CookieManager::GetInstance() { return g_lazy_instance.Pointer(); } -CookieManager::CookieManager() { +CookieManager::CookieManager() + : accept_file_scheme_cookies_(kDefaultFileSchemeAllowed), + cookie_store_created_(false), + cookie_store_client_thread_("CookieMonsterClient"), + cookie_store_backend_thread_("CookieMonsterBackend") { + cookie_store_client_thread_.Start(); + cookie_store_backend_thread_.Start(); + cookie_store_task_runner_ = cookie_store_client_thread_.task_runner(); } CookieManager::~CookieManager() { } -void CookieManager::CreateCookieMonster( - const FilePath& user_data_dir, - const scoped_refptr<base::SequencedTaskRunner>& client_task_runner, - const scoped_refptr<base::SequencedTaskRunner>& background_task_runner) { - FilePath cookie_store_path = - user_data_dir.Append(FILE_PATH_LITERAL("Cookies")); - - background_task_runner->PostTask( - FROM_HERE, - base::Bind(ImportLegacyCookieStore, cookie_store_path)); - - content::CookieStoreConfig cookie_config( - cookie_store_path, - content::CookieStoreConfig::RESTORED_SESSION_COOKIES, - NULL, NULL); - cookie_config.client_task_runner = client_task_runner; - cookie_config.background_task_runner = background_task_runner; - net::CookieStore* cookie_store = content::CreateCookieStore(cookie_config); - cookie_monster_ = cookie_store->GetCookieMonster(); - SetAcceptFileSchemeCookiesLocked(kDefaultFileSchemeAllowed); -} - -void CookieManager::EnsureCookieMonsterExistsLocked() { - cookie_monster_lock_.AssertAcquired(); - if (cookie_monster_.get()) { - return; - } - - // Create cookie monster using WebView-specific threads, as the rest of the - // browser has not been started yet. - FilePath user_data_dir; - GetUserDataDir(&user_data_dir); - cookie_monster_client_thread_.reset( - new base::Thread("CookieMonsterClient")); - cookie_monster_client_thread_->Start(); - cookie_monster_task_runner_ = cookie_monster_client_thread_->task_runner(); - cookie_monster_backend_thread_.reset( - new base::Thread("CookieMonsterBackend")); - cookie_monster_backend_thread_->Start(); - - CreateCookieMonster(user_data_dir, cookie_monster_task_runner_, - cookie_monster_backend_thread_->task_runner()); -} - -// Executes the |task| on the |cookie_monster_proxy_| message loop and -// waits for it to complete before returning. - +// Executes the |task| on |cookie_store_task_runner_| and waits for it to +// complete before returning. +// // To execute a CookieTask synchronously you must arrange for Signal to be // called on the waitable event at some point. You can call the bool or int // versions of ExecCookieTaskSync, these will supply the caller with a dummy // callback which takes an int/bool, throws it away and calls Signal. // Alternatively you can call the version which supplies a Closure in which // case you must call Run on it when you want the unblock the calling code. - +// // Ignore a bool callback. void CookieManager::ExecCookieTaskSync( const base::Callback<void(BoolCallback)>& task) { @@ -317,17 +291,57 @@ void CookieManager::ExecCookieTaskSync( completion.Wait(); } -// Executes the |task| on the |cookie_monster_proxy_| message loop. +// Executes the |task| using |cookie_store_task_runner_|. void CookieManager::ExecCookieTask(const base::Closure& task) { - base::AutoLock lock(cookie_monster_lock_); - EnsureCookieMonsterExistsLocked(); - cookie_monster_task_runner_->PostTask(FROM_HERE, task); -} + cookie_store_task_runner_->PostTask(FROM_HERE, task); +} + +base::SingleThreadTaskRunner* CookieManager::GetCookieStoreTaskRunner() { + return cookie_store_task_runner_.get(); +} + +net::CookieStore* CookieManager::GetCookieStore() { + DCHECK(cookie_store_task_runner_->RunsTasksOnCurrentThread()); + + if (!cookie_store_) { + FilePath user_data_dir; + GetUserDataDir(&user_data_dir); + FilePath cookie_store_path = + user_data_dir.Append(FILE_PATH_LITERAL("Cookies")); + + cookie_store_backend_thread_.task_runner()->PostTask( + FROM_HERE, base::Bind(ImportLegacyCookieStore, cookie_store_path)); + + content::CookieStoreConfig cookie_config( + cookie_store_path, content::CookieStoreConfig::RESTORED_SESSION_COOKIES, + nullptr, nullptr); + cookie_config.client_task_runner = cookie_store_task_runner_; + cookie_config.background_task_runner = + cookie_store_backend_thread_.task_runner(); + + { + base::AutoLock lock(accept_file_scheme_cookies_lock_); + + // There are some unknowns about how to correctly handle file:// cookies, + // and our implementation for this is not robust. http://crbug.com/582985 + // + // TODO(mmenke): This call should be removed once we can deprecate and + // remove the Android WebView 'CookieManager::setAcceptFileSchemeCookies' + // method. Until then, note that this is just not a great idea. + cookie_config.cookieable_schemes.insert( + cookie_config.cookieable_schemes.begin(), + net::CookieMonster::kDefaultCookieableSchemes, + net::CookieMonster::kDefaultCookieableSchemes + + net::CookieMonster::kDefaultCookieableSchemesCount); + if (accept_file_scheme_cookies_) + cookie_config.cookieable_schemes.push_back(url::kFileScheme); + cookie_store_created_ = true; + } + + cookie_store_ = content::CreateCookieStore(cookie_config); + } -scoped_refptr<net::CookieStore> CookieManager::GetCookieStore() { - base::AutoLock lock(cookie_monster_lock_); - EnsureCookieMonsterExistsLocked(); - return cookie_monster_; + return cookie_store_.get(); } void CookieManager::SetShouldAcceptCookies(bool accept) { @@ -366,8 +380,7 @@ void CookieManager::SetCookieHelper( net::CookieOptions options; options.set_include_httponly(); - cookie_monster_->SetCookieWithOptionsAsync( - host, value, options, callback); + GetCookieStore()->SetCookieWithOptionsAsync(host, value, options, callback); } std::string CookieManager::GetCookie(const GURL& host) { @@ -386,13 +399,9 @@ void CookieManager::GetCookieValueAsyncHelper( net::CookieOptions options; options.set_include_httponly(); - cookie_monster_->GetCookiesWithOptionsAsync( - host, - options, - base::Bind(&CookieManager::GetCookieValueCompleted, - base::Unretained(this), - complete, - result)); + GetCookieStore()->GetCookiesWithOptionsAsync( + host, options, base::Bind(&CookieManager::GetCookieValueCompleted, + base::Unretained(this), complete, result)); } void CookieManager::GetCookieValueCompleted(base::Closure complete, @@ -418,9 +427,8 @@ void CookieManager::RemoveSessionCookiesSync() { void CookieManager::RemoveSessionCookiesHelper( BoolCallback callback) { - cookie_monster_->DeleteSessionCookiesAsync( - base::Bind(&CookieManager::RemoveCookiesCompleted, - base::Unretained(this), + GetCookieStore()->DeleteSessionCookiesAsync( + base::Bind(&CookieManager::RemoveCookiesCompleted, base::Unretained(this), callback)); } @@ -446,9 +454,8 @@ void CookieManager::RemoveAllCookiesSync() { void CookieManager::RemoveAllCookiesHelper( const BoolCallback callback) { - cookie_monster_->DeleteAllAsync( - base::Bind(&CookieManager::RemoveCookiesCompleted, - base::Unretained(this), + GetCookieStore()->DeleteAllAsync( + base::Bind(&CookieManager::RemoveCookiesCompleted, base::Unretained(this), callback)); } @@ -464,7 +471,7 @@ void CookieManager::FlushCookieStore() { void CookieManager::FlushCookieStoreAsyncHelper( base::Closure complete) { - cookie_monster_->FlushStore(complete); + GetCookieStore()->FlushStore(complete); } bool CookieManager::HasCookies() { @@ -479,11 +486,9 @@ bool CookieManager::HasCookies() { // should not be needed. void CookieManager::HasCookiesAsyncHelper(bool* result, base::Closure complete) { - cookie_monster_->GetAllCookiesAsync( - base::Bind(&CookieManager::HasCookiesCompleted, - base::Unretained(this), - complete, - result)); + GetCookieStore()->GetAllCookiesAsync( + base::Bind(&CookieManager::HasCookiesCompleted, base::Unretained(this), + complete, result)); } void CookieManager::HasCookiesCompleted(base::Closure complete, @@ -494,42 +499,17 @@ void CookieManager::HasCookiesCompleted(base::Closure complete, } bool CookieManager::AllowFileSchemeCookies() { - base::AutoLock lock(cookie_monster_lock_); - EnsureCookieMonsterExistsLocked(); - return AllowFileSchemeCookiesLocked(); -} - -bool CookieManager::AllowFileSchemeCookiesLocked() { - return cookie_monster_->IsCookieableScheme(url::kFileScheme); + base::AutoLock lock(accept_file_scheme_cookies_lock_); + return accept_file_scheme_cookies_; } void CookieManager::SetAcceptFileSchemeCookies(bool accept) { - base::AutoLock lock(cookie_monster_lock_); - EnsureCookieMonsterExistsLocked(); - SetAcceptFileSchemeCookiesLocked(accept); -} - -void CookieManager::SetAcceptFileSchemeCookiesLocked(bool accept) { - // There are some unknowns about how to correctly handle file:// cookies, - // and our implementation for this is not robust. http://crbug.com/582985 - // - // TODO(mmenke): This call should be removed once we can deprecate and remove - // the Android WebView 'CookieManager::setAcceptFileSchemeCookies' method. - // Until then, note that this is just not a great idea. - std::vector<std::string> schemes; - schemes.insert(schemes.begin(), - net::CookieMonster::kDefaultCookieableSchemes, - net::CookieMonster::kDefaultCookieableSchemes + - net::CookieMonster::kDefaultCookieableSchemesCount); - - if (accept) - schemes.push_back(url::kFileScheme); - - cookie_monster_->SetCookieableSchemes(schemes); + base::AutoLock lock(accept_file_scheme_cookies_lock_); + // Can only modify this before the cookie store is created. + if (!cookie_store_created_) + accept_file_scheme_cookies_ = accept; } -} // namespace - static void SetShouldAcceptCookies(JNIEnv* env, const JavaParamRef<jobject>& obj, jboolean accept) { @@ -623,8 +603,15 @@ static void SetAcceptFileSchemeCookies(JNIEnv* env, return CookieManager::GetInstance()->SetAcceptFileSchemeCookies(accept); } -scoped_refptr<net::CookieStore> CreateCookieStore( - AwBrowserContext* browser_context) { +// The following two methods are used to avoid a circular project dependency. +// TODO(mmenke): This is weird. Maybe there should be a leaky Singleton in +// browser/net that creates and owns there? + +scoped_refptr<base::SingleThreadTaskRunner> GetCookieStoreTaskRunner() { + return CookieManager::GetInstance()->GetCookieStoreTaskRunner(); +} + +net::CookieStore* GetCookieStore() { return CookieManager::GetInstance()->GetCookieStore(); } diff --git a/android_webview/test/BUILD.gn b/android_webview/test/BUILD.gn index 72f4dd6..92b46fa 100644 --- a/android_webview/test/BUILD.gn +++ b/android_webview/test/BUILD.gn @@ -129,6 +129,7 @@ test("android_webview_unittests") { "../browser/aw_static_cookie_policy_unittest.cc", "../browser/browser_view_renderer_unittest.cc", "../browser/net/android_stream_reader_url_request_job_unittest.cc", + "../browser/net/aw_cookie_store_wrapper_unittest.cc", "../browser/net/input_stream_reader_unittest.cc", "../browser/test/fake_window.cc", "../browser/test/fake_window.h", |