diff options
author | tburkard@chromium.org <tburkard@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-05-13 08:19:59 +0000 |
---|---|---|
committer | tburkard@chromium.org <tburkard@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-05-13 08:19:59 +0000 |
commit | 28c5d0b7a83b11685698c5cd2ffb04c91d75a0c6 (patch) | |
tree | 949ca1fb18b48a01675ce6f62ecb0943a15bb292 /chrome/browser/prerender | |
parent | f73e1ca075924a6ca1125d5d7f96e374532852c3 (diff) | |
download | chromium_src-28c5d0b7a83b11685698c5cd2ffb04c91d75a0c6.zip chromium_src-28c5d0b7a83b11685698c5cd2ffb04c91d75a0c6.tar.gz chromium_src-28c5d0b7a83b11685698c5cd2ffb04c91d75a0c6.tar.bz2 |
Only commit cookie changes in prerenders after a prerender is shown
Will create a PrerenderCookieStore for each prerender, retaining all cookie
operations of a prerender until the prerender is shown to the user.
Forces prerenders to be in a new render process by themselves for this to work.
This is a resubmission of https://codereview.chromium.org/233353003, which had
to be reverted due to build breaks. See LGTM's there.
BUG=371003
TBR=jam@chromium.org
Review URL: https://codereview.chromium.org/280403002
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@270049 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/prerender')
-rw-r--r-- | chrome/browser/prerender/external_prerender_handler_android.cc | 11 | ||||
-rw-r--r-- | chrome/browser/prerender/external_prerender_handler_android.h | 3 | ||||
-rw-r--r-- | chrome/browser/prerender/prerender_browsertest.cc | 135 | ||||
-rw-r--r-- | chrome/browser/prerender/prerender_contents.cc | 27 | ||||
-rw-r--r-- | chrome/browser/prerender/prerender_contents.h | 10 | ||||
-rw-r--r-- | chrome/browser/prerender/prerender_cookie_store.cc | 241 | ||||
-rw-r--r-- | chrome/browser/prerender/prerender_cookie_store.h | 164 | ||||
-rw-r--r-- | chrome/browser/prerender/prerender_final_status.cc | 2 | ||||
-rw-r--r-- | chrome/browser/prerender/prerender_final_status.h | 2 | ||||
-rw-r--r-- | chrome/browser/prerender/prerender_manager.cc | 76 | ||||
-rw-r--r-- | chrome/browser/prerender/prerender_manager.h | 37 | ||||
-rw-r--r-- | chrome/browser/prerender/prerender_tracker.cc | 87 | ||||
-rw-r--r-- | chrome/browser/prerender/prerender_tracker.h | 37 | ||||
-rw-r--r-- | chrome/browser/prerender/prerender_unittest.cc | 12 |
14 files changed, 832 insertions, 12 deletions
diff --git a/chrome/browser/prerender/external_prerender_handler_android.cc b/chrome/browser/prerender/external_prerender_handler_android.cc index 013873b..0be6463 100644 --- a/chrome/browser/prerender/external_prerender_handler_android.cc +++ b/chrome/browser/prerender/external_prerender_handler_android.cc @@ -89,6 +89,17 @@ static jboolean HasPrerenderedUrl(JNIEnv* env, return prerender_manager->HasPrerenderedUrl(url, web_contents); } +static jboolean HasCookieStoreLoaded(JNIEnv* env, + jclass clazz, + jobject jprofile) { + Profile* profile = ProfileAndroid::FromProfileAndroid(jprofile); + prerender::PrerenderManager* prerender_manager = + prerender::PrerenderManagerFactory::GetForProfile(profile); + if (!prerender_manager) + return false; + return prerender_manager->cookie_store_loaded(); +} + ExternalPrerenderHandlerAndroid::ExternalPrerenderHandlerAndroid() {} ExternalPrerenderHandlerAndroid::~ExternalPrerenderHandlerAndroid() {} diff --git a/chrome/browser/prerender/external_prerender_handler_android.h b/chrome/browser/prerender/external_prerender_handler_android.h index 111181c..6009ddf 100644 --- a/chrome/browser/prerender/external_prerender_handler_android.h +++ b/chrome/browser/prerender/external_prerender_handler_android.h @@ -45,6 +45,9 @@ class ExternalPrerenderHandlerAndroid { GURL url, content::WebContents* web_contents); + // Whether the cookie store associated with this profile has been loaded. + static bool HasCookieStoreLoaded(Profile* profile); + static bool RegisterExternalPrerenderHandlerAndroid(JNIEnv* env); private: diff --git a/chrome/browser/prerender/prerender_browsertest.cc b/chrome/browser/prerender/prerender_browsertest.cc index bbfba0d..20b9df5 100644 --- a/chrome/browser/prerender/prerender_browsertest.cc +++ b/chrome/browser/prerender/prerender_browsertest.cc @@ -1124,6 +1124,12 @@ class PrerenderBrowserTest : virtual public InProcessBrowserTest { return scoped_ptr<TestPrerender>(prerenders[0]); } + // Navigates to a URL, unrelated to prerendering + void NavigateStraightToURL(const std::string dest_html_file) { + ui_test_utils::NavigateToURL(current_browser(), + test_server()->GetURL(dest_html_file)); + } + void NavigateToDestURL() const { NavigateToDestURLWithDisposition(CURRENT_TAB, true); } @@ -1484,6 +1490,22 @@ class PrerenderBrowserTest : virtual public InProcessBrowserTest { base::ASCIIToUTF16(page_title)); } + void RunJSReturningString(const char* js, std::string* result) { + ASSERT_TRUE( + content::ExecuteScriptAndExtractString( + GetActiveWebContents(), + base::StringPrintf("window.domAutomationController.send(%s)", + js).c_str(), + result)); + } + + void RunJS(const char* js) { + ASSERT_TRUE(content::ExecuteScript( + GetActiveWebContents(), + base::StringPrintf("window.domAutomationController.send(%s)", + js).c_str())); + } + protected: bool autostart_test_server_; @@ -4153,6 +4175,103 @@ IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, PrerenderPPLTNormalNavigation) { histograms.ExpectTotalCount("Prerender.none_PerceivedPLTMatchedComplete", 0); } +IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, + PrerenderCookieChangeConflictTest) { + NavigateStraightToURL( + "files/prerender/prerender_cookie.html?set=1&key=c&value=1"); + + GURL url = test_server()->GetURL( + "files/prerender/prerender_cookie.html?set=1&key=c&value=2"); + + scoped_ptr<TestPrerender> prerender = + ExpectPrerender(FINAL_STATUS_COOKIE_CONFLICT); + AddPrerender(url, 1); + prerender->WaitForStart(); + prerender->WaitForLoads(1); + // Ensure that in the prerendered page, querying the cookie again + // via javascript yields the same value that was set during load. + EXPECT_TRUE(DidPrerenderPass(prerender->contents()->prerender_contents())); + + // The prerender has loaded. Ensure that the change is not visible + // to visible tabs. + std::string value; + RunJSReturningString("GetCookie('c')", &value); + ASSERT_EQ(value, "1"); + + // Make a conflicting cookie change, which should cancel the prerender. + RunJS("SetCookie('c', '3')"); + prerender->WaitForStop(); +} + +IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, PrerenderCookieChangeUseTest) { + // Permit 2 concurrent prerenders. + GetPrerenderManager()->mutable_config().max_link_concurrency = 2; + GetPrerenderManager()->mutable_config().max_link_concurrency_per_launcher = 2; + + // Go to a first URL setting the cookie to value "1". + NavigateStraightToURL( + "files/prerender/prerender_cookie.html?set=1&key=c&value=1"); + + // Prerender a URL setting the cookie to value "2". + GURL url = test_server()->GetURL( + "files/prerender/prerender_cookie.html?set=1&key=c&value=2"); + + scoped_ptr<TestPrerender> prerender1 = ExpectPrerender(FINAL_STATUS_USED); + AddPrerender(url, 1); + prerender1->WaitForStart(); + prerender1->WaitForLoads(1); + + // Launch a second prerender, setting the cookie to value "3". + scoped_ptr<TestPrerender> prerender2 = + ExpectPrerender(FINAL_STATUS_COOKIE_CONFLICT); + AddPrerender(test_server()->GetURL( + "files/prerender/prerender_cookie.html?set=1&key=c&value=3"), 1); + prerender2->WaitForStart(); + prerender2->WaitForLoads(1); + + // Both prerenders have loaded. Ensure that the visible tab is still + // unchanged and cannot see their changes. + // to visible tabs. + std::string value; + RunJSReturningString("GetCookie('c')", &value); + ASSERT_EQ(value, "1"); + + // Navigate to the prerendered URL. The first prerender should be swapped in, + // and the changes should now be visible. The second prerender should + // be cancelled due to the conflict. + ui_test_utils::NavigateToURLWithDisposition( + current_browser(), + url, + CURRENT_TAB, + ui_test_utils::BROWSER_TEST_NONE); + RunJSReturningString("GetCookie('c')", &value); + ASSERT_EQ(value, "2"); + prerender2->WaitForStop(); +} + +IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, + PrerenderCookieChangeConflictHTTPHeaderTest) { + NavigateStraightToURL( + "files/prerender/prerender_cookie.html?set=1&key=c&value=1"); + + GURL url = test_server()->GetURL("set-cookie?c=2"); + scoped_ptr<TestPrerender> prerender = + ExpectPrerender(FINAL_STATUS_COOKIE_CONFLICT); + AddPrerender(url, 1); + prerender->WaitForStart(); + prerender->WaitForLoads(1); + + // The prerender has loaded. Ensure that the change is not visible + // to visible tabs. + std::string value; + RunJSReturningString("GetCookie('c')", &value); + ASSERT_EQ(value, "1"); + + // Make a conflicting cookie change, which should cancel the prerender. + RunJS("SetCookie('c', '3')"); + prerender->WaitForStop(); +} + // Checks that a prerender which calls window.close() on itself is aborted. IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, PrerenderWindowClose) { DisableLoadEventCheck(); @@ -4230,6 +4349,14 @@ class PrerenderOmniboxBrowserTest : public PrerenderBrowserTest { // Checks that closing the omnibox popup cancels an omnibox prerender. IN_PROC_BROWSER_TEST_F(PrerenderOmniboxBrowserTest, PrerenderOmniboxCancel) { + // Ensure the cookie store has been loaded. + if (!GetPrerenderManager()->cookie_store_loaded()) { + base::RunLoop loop; + GetPrerenderManager()->set_on_cookie_store_loaded_cb_for_testing( + loop.QuitClosure()); + loop.Run(); + } + // Fake an omnibox prerender. scoped_ptr<TestPrerender> prerender = StartOmniboxPrerender( test_server()->GetURL("files/empty.html"), @@ -4247,6 +4374,14 @@ IN_PROC_BROWSER_TEST_F(PrerenderOmniboxBrowserTest, PrerenderOmniboxAbandon) { GetPrerenderManager()->mutable_config().abandon_time_to_live = base::TimeDelta::FromDays(999); + // Ensure the cookie store has been loaded. + if (!GetPrerenderManager()->cookie_store_loaded()) { + base::RunLoop loop; + GetPrerenderManager()->set_on_cookie_store_loaded_cb_for_testing( + loop.QuitClosure()); + loop.Run(); + } + // Enter a URL into the Omnibox. OmniboxView* omnibox_view = GetOmniboxView(); omnibox_view->OnBeforePossibleChange(); diff --git a/chrome/browser/prerender/prerender_contents.cc b/chrome/browser/prerender/prerender_contents.cc index 35f45a4..8e209e1 100644 --- a/chrome/browser/prerender/prerender_contents.cc +++ b/chrome/browser/prerender/prerender_contents.cc @@ -39,8 +39,10 @@ #include "content/public/browser/web_contents_delegate.h" #include "content/public/common/frame_navigate_params.h" #include "content/public/common/page_transition_types.h" +#include "net/url_request/url_request_context_getter.h" #include "ui/gfx/rect.h" +using content::BrowserThread; using content::DownloadItem; using content::OpenURLParams; using content::RenderViewHost; @@ -291,7 +293,8 @@ PrerenderContents* PrerenderContents::FromWebContents( void PrerenderContents::StartPrerendering( int creator_child_id, const gfx::Size& size, - SessionStorageNamespace* session_storage_namespace) { + SessionStorageNamespace* session_storage_namespace, + net::URLRequestContextGetter* request_context) { DCHECK(profile_ != NULL); DCHECK(!size.IsEmpty()); DCHECK(!prerendering_has_started_); @@ -339,6 +342,24 @@ void PrerenderContents::StartPrerendering( // the event of a mismatch. alias_session_storage_namespace->AddTransactionLogProcessId(child_id_); + // Add the RenderProcessHost to the Prerender Manager. + prerender_manager()->AddPrerenderProcessHost( + GetRenderViewHost()->GetProcess()); + + // In the prerender tracker, create a Prerender Cookie Store to keep track of + // cookie changes performed by the prerender. Once the prerender is shown, + // the cookie changes will be committed to the actual cookie store, + // otherwise, they will be discarded. + BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, + base::Bind(&PrerenderTracker::AddPrerenderCookieStoreOnIOThread, + base::Unretained(prerender_manager()->prerender_tracker()), + GetRenderViewHost()->GetProcess()->GetID(), + make_scoped_refptr(request_context), + base::Bind(&PrerenderContents::Destroy, + AsWeakPtr(), + FINAL_STATUS_COOKIE_CONFLICT))); + NotifyPrerenderStart(); // Close ourselves when the application is shutting down. @@ -796,8 +817,8 @@ void PrerenderContents::PrepareForUse() { NotifyPrerenderStop(); - content::BrowserThread::PostTask( - content::BrowserThread::IO, + BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, base::Bind(&ResumeThrottles, resource_throttles_)); resource_throttles_.clear(); diff --git a/chrome/browser/prerender/prerender_contents.h b/chrome/browser/prerender/prerender_contents.h index d60602f..3b8ba1d 100644 --- a/chrome/browser/prerender/prerender_contents.h +++ b/chrome/browser/prerender/prerender_contents.h @@ -39,6 +39,10 @@ namespace history { struct HistoryAddPageArgs; } +namespace net { +class URLRequestContextGetter; +} + namespace prerender { class PrerenderHandle; @@ -46,7 +50,8 @@ class PrerenderManager; class PrerenderResourceThrottle; class PrerenderContents : public content::NotificationObserver, - public content::WebContentsObserver { + public content::WebContentsObserver, + public base::SupportsWeakPtr<PrerenderContents> { public: // PrerenderContents::Create uses the currently registered Factory to create // the PrerenderContents. Factory is intended for testing. @@ -151,7 +156,8 @@ class PrerenderContents : public content::NotificationObserver, virtual void StartPrerendering( int creator_child_id, const gfx::Size& size, - content::SessionStorageNamespace* session_storage_namespace); + content::SessionStorageNamespace* session_storage_namespace, + net::URLRequestContextGetter* request_context); // Verifies that the prerendering is not using too many resources, and kills // it if not. diff --git a/chrome/browser/prerender/prerender_cookie_store.cc b/chrome/browser/prerender/prerender_cookie_store.cc new file mode 100644 index 0000000..078c941 --- /dev/null +++ b/chrome/browser/prerender/prerender_cookie_store.cc @@ -0,0 +1,241 @@ +// Copyright 2014 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 "chrome/browser/prerender/prerender_cookie_store.h" + +#include "base/logging.h" +#include "base/stl_util.h" +#include "content/public/browser/browser_thread.h" + +using content::BrowserThread; + +namespace prerender { + +PrerenderCookieStore::PrerenderCookieStore( + scoped_refptr<net::CookieMonster> default_cookie_monster, + const base::Closure& cookie_conflict_cb) + : in_forwarding_mode_(false), + default_cookie_monster_(default_cookie_monster), + changes_cookie_monster_(new net::CookieMonster(NULL, NULL)), + cookie_conflict_cb_(cookie_conflict_cb), + cookie_conflict_(false) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + DCHECK(default_cookie_monster_ != NULL); + DCHECK(default_cookie_monster_->loaded()); +} + +PrerenderCookieStore::~PrerenderCookieStore() { +} + +void PrerenderCookieStore::SetCookieWithOptionsAsync( + const GURL& url, + const std::string& cookie_line, + const net::CookieOptions& options, + const SetCookiesCallback& callback) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + + CookieOperation op; + op.op = COOKIE_OP_SET_COOKIE_WITH_OPTIONS_ASYNC; + op.url = url; + op.cookie_line = cookie_line; + op.options = options; + + GetCookieStoreForCookieOpAndLog(op)-> + SetCookieWithOptionsAsync(url, cookie_line, options, callback); +} + +void PrerenderCookieStore::GetCookiesWithOptionsAsync( + const GURL& url, + const net::CookieOptions& options, + const GetCookiesCallback& callback) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + CookieOperation op; + op.op = COOKIE_OP_GET_COOKIES_WITH_OPTIONS_ASYNC; + op.url = url; + op.options = options; + + GetCookieStoreForCookieOpAndLog(op)-> + GetCookiesWithOptionsAsync(url, options, callback); +} + +void PrerenderCookieStore::GetAllCookiesForURLAsync( + const GURL& url, + const GetCookieListCallback& callback) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + + CookieOperation op; + op.op = COOKIE_OP_GET_ALL_COOKIES_FOR_URL_ASYNC; + op.url = url; + + GetCookieStoreForCookieOpAndLog(op)->GetAllCookiesForURLAsync(url, callback); +} + + +void PrerenderCookieStore::DeleteCookieAsync(const GURL& url, + const std::string& cookie_name, + const base::Closure& callback) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + + CookieOperation op; + op.op = COOKIE_OP_DELETE_COOKIE_ASYNC; + op.url = url; + op.cookie_name = cookie_name; + + GetCookieStoreForCookieOpAndLog(op)->DeleteCookieAsync(url, cookie_name, + callback); +} + +void PrerenderCookieStore::DeleteAllCreatedBetweenAsync( + const base::Time& delete_begin, + const base::Time& delete_end, + const DeleteCallback& callback) { + NOTREACHED(); +} + +void PrerenderCookieStore::DeleteAllCreatedBetweenForHostAsync( + const base::Time delete_begin, + const base::Time delete_end, + const GURL& url, + const DeleteCallback& callback) { + NOTREACHED(); +} + +void PrerenderCookieStore::DeleteSessionCookiesAsync(const DeleteCallback&) { + NOTREACHED(); +} + +net::CookieMonster* PrerenderCookieStore::GetCookieMonster() { + NOTREACHED(); + return NULL; +} + +net::CookieStore* PrerenderCookieStore::GetCookieStoreForCookieOpAndLog( + const CookieOperation& op) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + + std::string key = default_cookie_monster_->GetKey(op.url.host()); + bool is_read_only = (op.op == COOKIE_OP_GET_COOKIES_WITH_OPTIONS_ASYNC || + op.op == COOKIE_OP_GET_ALL_COOKIES_FOR_URL_ASYNC); + + if (in_forwarding_mode_) + return default_cookie_monster_; + + DCHECK(changes_cookie_monster_ != NULL); + + cookie_ops_.push_back(op); + + bool key_copied = ContainsKey(copied_keys_, key); + + if (key_copied) + return changes_cookie_monster_; + + if (is_read_only) { + // Insert this key into the set of read keys, if it doesn't exist yet. + if (!ContainsKey(read_keys_, key)) + read_keys_.insert(key); + return default_cookie_monster_; + } + + // If this method hasn't returned yet, the key has not been copied yet, + // and we must copy it due to the requested write operation. + + bool copy_success = default_cookie_monster_-> + CopyCookiesForKeyToOtherCookieMonster(key, changes_cookie_monster_); + + // The copy must succeed. + DCHECK(copy_success); + + copied_keys_.insert(key); + + return changes_cookie_monster_; +} + +void PrerenderCookieStore::ApplyChanges(std::vector<GURL>* cookie_change_urls) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + + if (in_forwarding_mode_) + return; + + // Apply all changes to the underlying cookie store. + for (std::vector<CookieOperation>::const_iterator it = cookie_ops_.begin(); + it != cookie_ops_.end(); + ++it) { + switch (it->op) { + case COOKIE_OP_SET_COOKIE_WITH_OPTIONS_ASYNC: + cookie_change_urls->push_back(it->url); + default_cookie_monster_->SetCookieWithOptionsAsync( + it->url, it->cookie_line, it->options, SetCookiesCallback()); + break; + case COOKIE_OP_GET_COOKIES_WITH_OPTIONS_ASYNC: + default_cookie_monster_->GetCookiesWithOptionsAsync( + it->url, it->options, GetCookiesCallback()); + break; + case COOKIE_OP_GET_ALL_COOKIES_FOR_URL_ASYNC: + default_cookie_monster_->GetAllCookiesForURLAsync( + it->url, GetCookieListCallback()); + break; + case COOKIE_OP_DELETE_COOKIE_ASYNC: + cookie_change_urls->push_back(it->url); + default_cookie_monster_->DeleteCookieAsync( + it->url, it->cookie_name, base::Closure()); + break; + case COOKIE_OP_MAX: + NOTREACHED(); + } + } + + in_forwarding_mode_ = true; + copied_keys_.clear(); + cookie_ops_.clear(); + changes_cookie_monster_ = NULL; +} + +void PrerenderCookieStore::OnCookieChangedForURL( + net::CookieMonster* cookie_monster, + const GURL& url) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + + // If the cookie was changed in a different cookie monster than the one + // being decorated, there is nothing to do). + if (cookie_monster != default_cookie_monster_) + return; + + if (in_forwarding_mode_) + return; + + // If we have encountered a conflict before, it has already been recorded + // and the cb has been issued, so nothing to do. + if (cookie_conflict_) + return; + + std::string key = default_cookie_monster_->GetKey(url.host()); + + // If the key for the cookie which was modified was neither read nor written, + // nothing to do. + if ((!ContainsKey(read_keys_, key)) && (!ContainsKey(copied_keys_, key))) + return; + + // There was a conflict in cookies. Call the conflict callback, which should + // cancel the prerender if necessary (i.e. if it hasn't already been + // cancelled for some other reason). + // Notice that there is a race here with swapping in the prerender, but this + // is the same issue that occurs when two tabs modify cookies for the + // same domain concurrently. Therefore, there is no need to do anything + // special to prevent this race condition. + cookie_conflict_ = true; + if (!cookie_conflict_cb_.is_null()) { + BrowserThread::PostTask( + BrowserThread::UI, + FROM_HERE, + cookie_conflict_cb_); + } +} + +PrerenderCookieStore::CookieOperation::CookieOperation() { +} + +PrerenderCookieStore::CookieOperation::~CookieOperation() { +} + +} // namespace prerender diff --git a/chrome/browser/prerender/prerender_cookie_store.h b/chrome/browser/prerender/prerender_cookie_store.h new file mode 100644 index 0000000..b42ef45 --- /dev/null +++ b/chrome/browser/prerender/prerender_cookie_store.h @@ -0,0 +1,164 @@ +// Copyright 2014 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. + +#ifndef CHROME_BROWSER_PRERENDER_PRERENDER_COOKIE_STORE_H_ +#define CHROME_BROWSER_PRERENDER_PRERENDER_COOKIE_STORE_H_ + +#include <set> +#include <string> +#include <vector> + +#include "base/callback.h" +#include "base/compiler_specific.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "net/cookies/cookie_monster.h" +#include "net/cookies/cookie_store.h" +#include "url/gurl.h" + +namespace prerender { + +// A cookie store which keeps track of provisional changes to the cookie monster +// of an underlying request context (called the default cookie monster). +// Initially, it will proxy read requests to the default cookie monster, and +// copy on write keys that are being modified into a private cookie monster. +// Reads for these will then happen from the private cookie monster. +// Should keys be modified in the default cookie store, the corresponding +// prerender should be aborted. +// This class also keeps a log of all cookie transactions. Once ApplyChanges +// is called, the changes will be applied to the default cookie monster, +// and any future requests to this object will simply be forwarded to the +// default cookie monster. After ApplyChanges is called, the prerender tracker, +// which "owns" the PrerenderCookieStore reference, will remove its entry for +// the PrerenderCookieStore. Therefore, after ApplyChanges is called, the +// object will only stick around (and exhibit forwarding mode) as long as +// eg pending requests hold on to its reference. +class PrerenderCookieStore : public net::CookieStore { + public: + // Creates a PrerenderCookieStore using the default cookie monster provided + // by the URLRequestContext. The underlying cookie store must be loaded, + // ie it's call to loaded() must return true. + // Otherwise, copying cookie data between the prerender cookie store + // (used to only commit cookie changes once a prerender is shown) would + // not work synchronously, which would complicate the code. + // |cookie_conflict_cb| will be called when a cookie conflict is detected. + // The callback will be run on the UI thread. + PrerenderCookieStore(scoped_refptr<net::CookieMonster> default_cookie_store_, + const base::Closure& cookie_conflict_cb); + + // CookieStore implementation + virtual void SetCookieWithOptionsAsync( + const GURL& url, + const std::string& cookie_line, + const net::CookieOptions& options, + const SetCookiesCallback& callback) OVERRIDE; + + virtual void GetCookiesWithOptionsAsync( + const GURL& url, + const net::CookieOptions& options, + const GetCookiesCallback& callback) OVERRIDE; + + virtual void GetAllCookiesForURLAsync( + const GURL& url, + const GetCookieListCallback& callback) OVERRIDE; + + virtual void DeleteCookieAsync(const GURL& url, + const std::string& cookie_name, + const base::Closure& callback) OVERRIDE; + + // All the following methods should not be used in the scenarios where + // a PrerenderCookieStore is used. This will be checked via NOTREACHED(). + // Should PrerenderCookieStore used in contexts requiring these, they will + // need to be implemented first. They are only intended to be called on the + // IO thread. + + virtual void DeleteAllCreatedBetweenAsync( + const base::Time& delete_begin, + const base::Time& delete_end, + const DeleteCallback& callback) OVERRIDE; + + virtual void DeleteAllCreatedBetweenForHostAsync( + const base::Time delete_begin, + const base::Time delete_end, + const GURL& url, + const DeleteCallback& callback) OVERRIDE; + + virtual void DeleteSessionCookiesAsync(const DeleteCallback&) OVERRIDE; + + virtual net::CookieMonster* GetCookieMonster() OVERRIDE; + + // Commits the changes made to the underlying cookie store, and switches + // into forwarding mode. To be called on the IO thread. + // |cookie_change_urls| will be populated with all URLs for which cookies + // were updated. + void ApplyChanges(std::vector<GURL>* cookie_change_urls); + + // Called when a cookie for a URL is changed in the underlying default cookie + // store. To be called on the IO thread. If the key corresponding to the URL + // was copied or read, the prerender will be cancelled. + void OnCookieChangedForURL(net::CookieMonster* cookie_monster, + const GURL& url); + + net::CookieMonster* default_cookie_monster() { + return default_cookie_monster_; + } + + private: + enum CookieOperationType { + COOKIE_OP_SET_COOKIE_WITH_OPTIONS_ASYNC, + COOKIE_OP_GET_COOKIES_WITH_OPTIONS_ASYNC, + COOKIE_OP_GET_ALL_COOKIES_FOR_URL_ASYNC, + COOKIE_OP_DELETE_COOKIE_ASYNC, + COOKIE_OP_MAX + }; + + struct CookieOperation { + CookieOperationType op; + GURL url; + net::CookieOptions options; + std::string cookie_line; + std::string cookie_name; + CookieOperation(); + ~CookieOperation(); + }; + + virtual ~PrerenderCookieStore(); + + // Gets the appropriate cookie store for the operation provided, and pushes + // it back on the log of cookie operations performed. + net::CookieStore* GetCookieStoreForCookieOpAndLog(const CookieOperation& op); + + // Indicates whether the changes have already been applied (ie the prerender + // has been shown), and we are merely in forwarding mode; + bool in_forwarding_mode_; + + // The default cookie monster. + scoped_refptr<net::CookieMonster> default_cookie_monster_; + + // A cookie monster storing changes made by the prerender. + // Entire keys are copied from default_cookie_monster_ on change, and then + // modified. + scoped_refptr<net::CookieMonster> changes_cookie_monster_; + + // Log of cookie operations performed + std::vector<CookieOperation> cookie_ops_; + + // The keys which have been copied on write to |changes_cookie_monster_|. + std::set<std::string> copied_keys_; + + // Keys which have been read (but not necessarily been modified). + std::set<std::string> read_keys_; + + // Callback when a cookie conflict was detected + base::Closure cookie_conflict_cb_; + + // Indicates whether a cookie conflict has been detected yet. + bool cookie_conflict_; + + DISALLOW_COPY_AND_ASSIGN(PrerenderCookieStore); +}; + +} // namespace prerender + +#endif // CHROME_BROWSER_PRERENDER_PRERENDER_COOKIE_STORE_H_ diff --git a/chrome/browser/prerender/prerender_final_status.cc b/chrome/browser/prerender/prerender_final_status.cc index ac0526d..adeaf49 100644 --- a/chrome/browser/prerender/prerender_final_status.cc +++ b/chrome/browser/prerender/prerender_final_status.cc @@ -59,6 +59,8 @@ const char* kFinalStatusNames[] = { "Bad Deferred Redirect", "Navigation Uncommitted", "New Navigation Entry", + "Cookie Store Not Loaded", + "Cookie Conflict", "Max", }; COMPILE_ASSERT(arraysize(kFinalStatusNames) == FINAL_STATUS_MAX + 1, diff --git a/chrome/browser/prerender/prerender_final_status.h b/chrome/browser/prerender/prerender_final_status.h index 8551c10..412be21 100644 --- a/chrome/browser/prerender/prerender_final_status.h +++ b/chrome/browser/prerender/prerender_final_status.h @@ -60,6 +60,8 @@ enum FinalStatus { FINAL_STATUS_BAD_DEFERRED_REDIRECT = 45, FINAL_STATUS_NAVIGATION_UNCOMMITTED = 46, FINAL_STATUS_NEW_NAVIGATION_ENTRY = 47, + FINAL_STATUS_COOKIE_STORE_NOT_LOADED = 48, + FINAL_STATUS_COOKIE_CONFLICT = 49, FINAL_STATUS_MAX, }; diff --git a/chrome/browser/prerender/prerender_manager.cc b/chrome/browser/prerender/prerender_manager.cc index 4f0c083..141afe7 100644 --- a/chrome/browser/prerender/prerender_manager.cc +++ b/chrome/browser/prerender/prerender_manager.cc @@ -58,6 +58,7 @@ #include "content/public/browser/render_process_host.h" #include "content/public/browser/render_view_host.h" #include "content/public/browser/session_storage_namespace.h" +#include "content/public/browser/storage_partition.h" #include "content/public/browser/web_contents.h" #include "content/public/browser/web_contents_delegate.h" #include "content/public/common/url_constants.h" @@ -245,7 +246,8 @@ PrerenderManager::PrerenderManager(Profile* profile, prerender_history_(new PrerenderHistory(kHistoryLength)), histograms_(new PrerenderHistograms()), profile_network_bytes_(0), - last_recorded_profile_network_bytes_(0) { + last_recorded_profile_network_bytes_(0), + cookie_store_loaded_(false) { // There are some assumptions that the PrerenderManager is on the UI thread. // Any other checks simply make sure that the PrerenderManager is accessed on // the same thread that it was created on. @@ -303,6 +305,13 @@ PrerenderManager::~PrerenderManager() { // emptied these vectors already. DCHECK(active_prerenders_.empty()); DCHECK(to_delete_prerenders_.empty()); + + for (PrerenderProcessSet::const_iterator it = + prerender_process_hosts_.begin(); + it != prerender_process_hosts_.end(); + ++it) { + (*it)->RemoveObserver(this); + } } void PrerenderManager::Shutdown() { @@ -577,6 +586,14 @@ WebContents* PrerenderManager::SwapInternal( } // At this point, we've determined that we will use the prerender. + content::RenderProcessHost* process_host = + prerender_data->contents()->GetRenderViewHost()->GetProcess(); + prerender_process_hosts_.erase(process_host); + BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, + base::Bind(&PrerenderTracker::RemovePrerenderCookieStoreOnIOThread, + base::Unretained(prerender_tracker()), process_host->GetID(), + true)); if (!prerender_data->contents()->load_start_time().is_null()) { histograms_->RecordTimeUntilUsed( prerender_data->contents()->origin(), @@ -748,7 +765,7 @@ const char* PrerenderManager::GetModeString() { default: NOTREACHED() << "Invalid PrerenderManager mode."; break; - }; + } return ""; } @@ -1216,6 +1233,12 @@ void PrerenderManager::SourceNavigatedAway(PrerenderData* prerender_data) { SortActivePrerenders(); } +net::URLRequestContextGetter* PrerenderManager::GetURLRequestContext() { + return content::BrowserContext::GetDefaultStoragePartition(profile_)-> + GetURLRequestContext(); +} + + // private PrerenderHandle* PrerenderManager::AddPrerender( Origin origin, @@ -1283,6 +1306,14 @@ PrerenderHandle* PrerenderManager::AddPrerender( return NULL; } + if (!cookie_store_loaded()) { + // Only prerender if the cookie store for this profile has been loaded. + // This is required by PrerenderCookieMonster. + RecordFinalStatusWithoutCreatingPrerenderContents( + url, origin, experiment, FINAL_STATUS_COOKIE_STORE_NOT_LOADED); + return NULL; + } + PrerenderContents* prerender_contents = CreatePrerenderContents( url, referrer, origin, experiment); DCHECK(prerender_contents); @@ -1307,11 +1338,16 @@ PrerenderHandle* PrerenderManager::AddPrerender( gfx::Size contents_size = size.IsEmpty() ? config_.default_tab_bounds.size() : size; + net::URLRequestContextGetter* request_context = GetURLRequestContext(); + prerender_contents->StartPrerendering(process_id, contents_size, - session_storage_namespace); + session_storage_namespace, + request_context); DCHECK(IsControlGroup(experiment) || - prerender_contents->prerendering_has_started()); + prerender_contents->prerendering_has_started() || + (origin == ORIGIN_LOCAL_PREDICTOR && + IsLocalPredictorPrerenderAlwaysControlEnabled())); if (GetMode() == PRERENDER_MODE_EXPERIMENT_MULTI_PRERENDER_GROUP) histograms_->RecordConcurrency(active_prerenders_.size()); @@ -1831,4 +1867,36 @@ void PrerenderManager::AddProfileNetworkBytesIfEnabled(int64 bytes) { profile_network_bytes_ += bytes; } +void PrerenderManager::OnCookieStoreLoaded() { + cookie_store_loaded_ = true; + if (!on_cookie_store_loaded_cb_for_testing_.is_null()) + on_cookie_store_loaded_cb_for_testing_.Run(); +} + +void PrerenderManager::AddPrerenderProcessHost( + content::RenderProcessHost* process_host) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + DCHECK(prerender_process_hosts_.find(process_host) == + prerender_process_hosts_.end()); + prerender_process_hosts_.insert(process_host); + process_host->AddObserver(this); +} + +bool PrerenderManager::IsProcessPrerendering( + content::RenderProcessHost* process_host) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + return (prerender_process_hosts_.find(process_host) != + prerender_process_hosts_.end()); +} + +void PrerenderManager::RenderProcessHostDestroyed( + content::RenderProcessHost* host) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + prerender_process_hosts_.erase(host); + BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, + base::Bind(&PrerenderTracker::RemovePrerenderCookieStoreOnIOThread, + base::Unretained(prerender_tracker()), host->GetID(), false)); +} + } // namespace prerender diff --git a/chrome/browser/prerender/prerender_manager.h b/chrome/browser/prerender/prerender_manager.h index aad510e..95d78d5 100644 --- a/chrome/browser/prerender/prerender_manager.h +++ b/chrome/browser/prerender/prerender_manager.h @@ -31,6 +31,7 @@ #include "components/keyed_service/core/keyed_service.h" #include "content/public/browser/notification_observer.h" #include "content/public/browser/notification_registrar.h" +#include "content/public/browser/render_process_host_observer.h" #include "content/public/browser/session_storage_namespace.h" #include "content/public/browser/web_contents_observer.h" #include "net/cookies/canonical_cookie.h" @@ -74,6 +75,7 @@ class PrerenderLocalPredictor; class PrerenderManager : public base::SupportsWeakPtr<PrerenderManager>, public base::NonThreadSafe, public content::NotificationObserver, + public content::RenderProcessHostObserver, public KeyedService, public MediaCaptureDevicesDispatcher::Observer { public: @@ -295,6 +297,8 @@ class PrerenderManager : public base::SupportsWeakPtr<PrerenderManager>, PrerenderTracker* prerender_tracker() { return prerender_tracker_; } + bool cookie_store_loaded() { return cookie_store_loaded_; } + // Adds a condition. This is owned by the PrerenderManager. void AddCondition(const PrerenderCondition* condition); @@ -360,6 +364,25 @@ class PrerenderManager : public base::SupportsWeakPtr<PrerenderManager>, // profile if prerendering is currently enabled. void AddProfileNetworkBytesIfEnabled(int64 bytes); + // Registers a new ProcessHost performing a prerender. Called by + // PrerenderContents. + void AddPrerenderProcessHost(content::RenderProcessHost* process_host); + + bool IsProcessPrerendering(content::RenderProcessHost* process_host); + + // content::RenderProcessHostObserver implementation. + virtual void RenderProcessHostDestroyed( + content::RenderProcessHost* host) OVERRIDE; + + // To be called once the cookie store for this profile has been loaded. + void OnCookieStoreLoaded(); + + // For testing purposes. Issues a callback once the cookie store has been + // loaded. + void set_on_cookie_store_loaded_cb_for_testing(base::Closure cb) { + on_cookie_store_loaded_cb_for_testing_ = cb; + } + protected: class PendingSwap; class PrerenderData : public base::SupportsWeakPtr<PrerenderData> { @@ -510,6 +533,11 @@ class PrerenderManager : public base::SupportsWeakPtr<PrerenderManager>, // shorten the TTL of the prerendered page. void SourceNavigatedAway(PrerenderData* prerender_data); + // Gets the request context for the profile. + // For unit tests, this will be overriden to return NULL, since it is not + // needed. + virtual net::URLRequestContextGetter* GetURLRequestContext(); + private: friend class ::InstantSearchPrerendererTest; friend class PrerenderBrowserTest; @@ -720,6 +748,15 @@ class PrerenderManager : public base::SupportsWeakPtr<PrerenderManager>, // The value of profile_network_bytes_ that was last recorded. int64 last_recorded_profile_network_bytes_; + // Set of process hosts being prerendered. + typedef std::set<content::RenderProcessHost*> PrerenderProcessSet; + PrerenderProcessSet prerender_process_hosts_; + + // Indicates whether the cookie store for this profile has fully loaded yet. + bool cookie_store_loaded_; + + base::Closure on_cookie_store_loaded_cb_for_testing_; + DISALLOW_COPY_AND_ASSIGN(PrerenderManager); }; diff --git a/chrome/browser/prerender/prerender_tracker.cc b/chrome/browser/prerender/prerender_tracker.cc index b44c8d3..cabb35b 100644 --- a/chrome/browser/prerender/prerender_tracker.cc +++ b/chrome/browser/prerender/prerender_tracker.cc @@ -8,6 +8,9 @@ #include "base/logging.h" #include "chrome/browser/prerender/prerender_pending_swap_throttle.h" #include "content/public/browser/browser_thread.h" +#include "content/public/browser/render_process_host.h" +#include "net/url_request/url_request_context.h" +#include "net/url_request/url_request_context_getter.h" using content::BrowserThread; @@ -103,4 +106,88 @@ PrerenderTracker::PendingSwapThrottleData::PendingSwapThrottleData( PrerenderTracker::PendingSwapThrottleData::~PendingSwapThrottleData() { } +scoped_refptr<PrerenderCookieStore> +PrerenderTracker::GetPrerenderCookieStoreForRenderProcess( + int process_id) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + PrerenderCookieStoreMap::const_iterator it = + prerender_cookie_store_map_.find(process_id); + + if (it == prerender_cookie_store_map_.end()) + return NULL; + + return it->second; +} + +void PrerenderTracker::OnCookieChangedForURL( + int process_id, + net::CookieMonster* cookie_monster, + const GURL& url) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + + // We only care about cookie changes by non-prerender tabs, since only those + // get applied to the underlying cookie store. Therefore, if a cookie change + // originated from a prerender, there is nothing to do. + if (ContainsKey(prerender_cookie_store_map_, process_id)) + return; + + // Since the cookie change did not come from a prerender, broadcast it too + // all prerenders so that they can be cancelled if there is a conflict. + for (PrerenderCookieStoreMap::iterator it = + prerender_cookie_store_map_.begin(); + it != prerender_cookie_store_map_.end(); + ++it) { + it->second->OnCookieChangedForURL(cookie_monster, url); + } +} + +void PrerenderTracker::RemovePrerenderCookieStoreOnIOThread(int process_id, + bool was_swapped) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + + PrerenderCookieStoreMap::iterator it = + prerender_cookie_store_map_.find(process_id); + + if (it == prerender_cookie_store_map_.end()) + return; + + std::vector<GURL> cookie_change_urls; + if (was_swapped) + it->second->ApplyChanges(&cookie_change_urls); + + scoped_refptr<net::CookieMonster> cookie_monster( + it->second->default_cookie_monster()); + + prerender_cookie_store_map_.erase(it); + + // For each cookie updated by ApplyChanges, we need to call + // OnCookieChangedForURL so that any potentially conflicting prerenders + // will be aborted. + for (std::vector<GURL>::const_iterator url_it = cookie_change_urls.begin(); + url_it != cookie_change_urls.end(); + ++url_it) { + OnCookieChangedForURL(process_id, cookie_monster, *url_it); + } +} + +void PrerenderTracker::AddPrerenderCookieStoreOnIOThread( + int process_id, + scoped_refptr<net::URLRequestContextGetter> request_context, + const base::Closure& cookie_conflict_cb) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + DCHECK(request_context != NULL); + net::CookieMonster* cookie_monster = + request_context->GetURLRequestContext()->cookie_store()-> + GetCookieMonster(); + DCHECK(cookie_monster != NULL); + bool exists = (prerender_cookie_store_map_.find(process_id) != + prerender_cookie_store_map_.end()); + DCHECK(!exists); + if (exists) + return; + prerender_cookie_store_map_[process_id] = + new PrerenderCookieStore(make_scoped_refptr(cookie_monster), + cookie_conflict_cb); +} + } // namespace prerender diff --git a/chrome/browser/prerender/prerender_tracker.h b/chrome/browser/prerender/prerender_tracker.h index f9dd17b..6020658 100644 --- a/chrome/browser/prerender/prerender_tracker.h +++ b/chrome/browser/prerender/prerender_tracker.h @@ -6,16 +6,26 @@ #define CHROME_BROWSER_PRERENDER_PRERENDER_TRACKER_H_ #include <map> +#include <set> #include <utility> +#include "base/containers/hash_tables.h" +#include "base/memory/ref_counted.h" #include "base/memory/weak_ptr.h" +#include "base/synchronization/lock.h" +#include "chrome/browser/prerender/prerender_cookie_store.h" +#include "content/public/browser/render_process_host_observer.h" #include "url/gurl.h" +namespace net { +class URLRequestContextGetter; +} + namespace prerender { class PrerenderPendingSwapThrottle; -// Global object for maintaining prerender state on the IO thread. +// Global object for maintaining various prerender state on the IO thread. class PrerenderTracker { public: typedef std::pair<int, int> ChildRouteIdPair; @@ -46,6 +56,25 @@ class PrerenderTracker { const ChildRouteIdPair& render_frame_route_id_pair, bool swap_successful); + // Gets the Prerender Cookie Store for a specific render process, if it + // is a prerender. Only to be called from the IO thread. + scoped_refptr<PrerenderCookieStore> GetPrerenderCookieStoreForRenderProcess( + int process_id); + + // Called when a given render process has changed a cookie for |url|, + // in |cookie_monster|. + // Only to be called from the IO thread. + void OnCookieChangedForURL(int process_id, + net::CookieMonster* cookie_monster, + const GURL& url); + + void AddPrerenderCookieStoreOnIOThread( + int process_id, + scoped_refptr<net::URLRequestContextGetter> request_context, + const base::Closure& cookie_conflict_cb); + + void RemovePrerenderCookieStoreOnIOThread(int process_id, bool was_swapped); + private: // Add/remove prerenders pending swap on the IO Thread. void AddPrerenderPendingSwapOnIOThread( @@ -68,6 +97,12 @@ class PrerenderTracker { PendingSwapThrottleMap; PendingSwapThrottleMap pending_swap_throttle_map_; + // Map of prerendering render process ids to PrerenderCookieStore used for + // the prerender. Only to be used on the IO thread. + typedef base::hash_map<int, scoped_refptr<PrerenderCookieStore> > + PrerenderCookieStoreMap; + PrerenderCookieStoreMap prerender_cookie_store_map_; + DISALLOW_COPY_AND_ASSIGN(PrerenderTracker); }; diff --git a/chrome/browser/prerender/prerender_unittest.cc b/chrome/browser/prerender/prerender_unittest.cc index 1af24ea..6a8a16d 100644 --- a/chrome/browser/prerender/prerender_unittest.cc +++ b/chrome/browser/prerender/prerender_unittest.cc @@ -52,7 +52,8 @@ class DummyPrerenderContents : public PrerenderContents { virtual void StartPrerendering( int ALLOW_UNUSED creator_child_id, const gfx::Size& ALLOW_UNUSED size, - content::SessionStorageNamespace* ALLOW_UNUSED session_storage_namespace) + content::SessionStorageNamespace* ALLOW_UNUSED session_storage_namespace, + net::URLRequestContextGetter* ALLOW_UNUSED request_context) OVERRIDE; virtual bool GetChildId(int* child_id) const OVERRIDE { @@ -101,6 +102,7 @@ class UnitTestPrerenderManager : public PrerenderManager { time_ticks_(TimeTicks::Now()), prerender_tracker_(prerender_tracker) { set_rate_limit_enabled(false); + OnCookieStoreLoaded(); } virtual ~UnitTestPrerenderManager() { @@ -229,6 +231,11 @@ class UnitTestPrerenderManager : public PrerenderManager { prerender_contents_map_.erase(std::make_pair(child_id, route_id)); } + protected: + virtual net::URLRequestContextGetter* GetURLRequestContext() OVERRIDE { + return NULL; + } + private: void SetNextPrerenderContents(DummyPrerenderContents* prerender_contents) { CHECK(!next_prerender_contents_.get()); @@ -296,7 +303,8 @@ DummyPrerenderContents::~DummyPrerenderContents() { void DummyPrerenderContents::StartPrerendering( int ALLOW_UNUSED creator_child_id, const gfx::Size& ALLOW_UNUSED size, - content::SessionStorageNamespace* ALLOW_UNUSED session_storage_namespace) { + content::SessionStorageNamespace* ALLOW_UNUSED session_storage_namespace, + net::URLRequestContextGetter* ALLOW_UNUSED request_context) { // In the base PrerenderContents implementation, StartPrerendering will // be called even when the PrerenderManager is part of the control group, // but it will early exit before actually creating a new RenderView if |