diff --git a/chrome/android/java/src/org/chromium/chrome/browser/prerender/ExternalPrerenderHandler.java b/chrome/android/java/src/org/chromium/chrome/browser/prerender/ExternalPrerenderHandler.java index 3783554d6062..b96062e9f402 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/prerender/ExternalPrerenderHandler.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/prerender/ExternalPrerenderHandler.java @@ -38,12 +38,18 @@ public static boolean hasPrerenderedUrl(Profile profile, String url, long webCon return nativeHasPrerenderedUrl(profile, url, webContentsPtr); } + public static boolean hasCookieStoreLoaded(Profile profile) { + return nativeHasCookieStoreLoaded(profile); + } + private static native long nativeInit(); private static native boolean nativeAddPrerender( long nativeExternalPrerenderHandlerAndroid, Profile profile, long webContentsPtr, String url, String referrer, int width, int height); private static native boolean nativeHasPrerenderedUrl( Profile profile, String url, long webContentsPtr); + private static native boolean nativeHasCookieStoreLoaded( + Profile profile); private static native void nativeCancelCurrentPrerender( long nativeExternalPrerenderHandlerAndroid); } diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/prerender/ExternalPrerenderRequestTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/prerender/ExternalPrerenderRequestTest.java index dcbb9fc91cb5..4ef0054439df 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/prerender/ExternalPrerenderRequestTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/prerender/ExternalPrerenderRequestTest.java @@ -21,11 +21,14 @@ * Tests for adding and removing prerenders using the {@link ExternalPrerenderHandler} */ public class ExternalPrerenderRequestTest extends ChromeShellTestBase { + private static final String HOMEPAGE_URL = + TestHttpServerClient.getUrl("chrome/test/data/android/prerender/homepage.html"); private static final String GOOGLE_URL = TestHttpServerClient.getUrl("chrome/test/data/android/prerender/google.html"); private static final String YOUTUBE_URL = TestHttpServerClient.getUrl("chrome/test/data/android/prerender/youtube.html"); private static final int PRERENDER_DELAY_MS = 500; + private static final int CHECK_COOKIE_STORE_FREQUENCY_MS = 200; private ExternalPrerenderHandler mHandler; private Profile mProfile; @@ -34,7 +37,8 @@ public class ExternalPrerenderRequestTest extends ChromeShellTestBase { public void setUp() throws Exception { super.setUp(); clearAppData(); - launchChromeShellWithBlankPage(); + // Launch with a non-blank homepage, to trigger cookie store loading. + launchChromeShellWithUrl(HOMEPAGE_URL); assertTrue(waitForActiveShellToBeDoneLoading()); mHandler = new ExternalPrerenderHandler(); final Callable profileCallable = new Callable() { @@ -44,6 +48,8 @@ public Profile call() throws Exception { } }; mProfile = ThreadUtils.runOnUiThreadBlocking(profileCallable); + while (!ExternalPrerenderHandler.hasCookieStoreLoaded(mProfile)) + Thread.sleep(CHECK_COOKIE_STORE_FREQUENCY_MS); } @MediumTest diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc index 06b97e7f9bce..678948c0941b 100644 --- a/chrome/browser/chrome_content_browser_client.cc +++ b/chrome/browser/chrome_content_browser_client.cc @@ -630,7 +630,8 @@ float GetDeviceScaleAdjustment() { namespace chrome { -ChromeContentBrowserClient::ChromeContentBrowserClient() { +ChromeContentBrowserClient::ChromeContentBrowserClient() + : prerender_tracker_(NULL) { #if defined(ENABLE_PLUGINS) for (size_t i = 0; i < arraysize(kPredefinedAllowedFileHandleOrigins); ++i) allowed_file_handle_origins_.insert(kPredefinedAllowedFileHandleOrigins[i]); @@ -1214,6 +1215,24 @@ bool ChromeContentBrowserClient::IsSuitableHost( privilege_required; } +bool ChromeContentBrowserClient::MayReuseHost( + content::RenderProcessHost* process_host) { + // If there is currently a prerender in progress for the host provided, + // it may not be shared. We require prerenders to be by themselves in a + // separate process, so that we can monitor their resource usage, and so that + // we can track the cookies that they change. + Profile* profile = Profile::FromBrowserContext( + process_host->GetBrowserContext()); + prerender::PrerenderManager* prerender_manager = + prerender::PrerenderManagerFactory::GetForProfile(profile); + if (prerender_manager && + prerender_manager->IsProcessPrerendering(process_host)) { + return false; + } + + return true; +} + // This function is trying to limit the amount of processes used by extensions // with background pages. It uses a globally set percentage of processes to // run such extensions and if the limit is exceeded, it returns true, to @@ -1765,6 +1784,13 @@ bool ChromeContentBrowserClient::AllowSetCookie( CookieSettings* cookie_settings = io_data->GetCookieSettings(); bool allow = cookie_settings->IsSettingCookieAllowed(url, first_party); + if (prerender_tracker_) { + prerender_tracker_->OnCookieChangedForURL( + render_process_id, + context->GetRequestContext()->cookie_store()->GetCookieMonster(), + url); + } + BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, base::Bind(&TabSpecificContentSettings::CookieChanged, render_process_id, @@ -2211,6 +2237,8 @@ std::string ChromeContentBrowserClient::GetWorkerProcessTitle( } void ChromeContentBrowserClient::ResourceDispatcherHostCreated() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + prerender_tracker_ = g_browser_process->prerender_tracker(); return g_browser_process->ResourceDispatcherHostCreated(); } @@ -2698,6 +2726,16 @@ bool ChromeContentBrowserClient::IsPluginAllowedToUseDevChannelAPIs() { #endif } +net::CookieStore* +ChromeContentBrowserClient::OverrideCookieStoreForRenderProcess( + int render_process_id) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + if (!prerender_tracker_) + return NULL; + return prerender_tracker_-> + GetPrerenderCookieStoreForRenderProcess(render_process_id); +} + #if defined(ENABLE_WEBRTC) void ChromeContentBrowserClient::MaybeCopyDisableWebRtcEncryptionSwitch( CommandLine* to_command_line, diff --git a/chrome/browser/chrome_content_browser_client.h b/chrome/browser/chrome_content_browser_client.h index 9432b5ecdddf..b8b9af1ef812 100644 --- a/chrome/browser/chrome_content_browser_client.h +++ b/chrome/browser/chrome_content_browser_client.h @@ -31,6 +31,10 @@ namespace extensions { class BrowserPermissionsPolicyDelegate; } +namespace prerender { +class PrerenderTracker; +} + namespace user_prefs { class PrefRegistrySyncable; } @@ -102,6 +106,7 @@ class ChromeContentBrowserClient : public content::ContentBrowserClient { const GURL& url) OVERRIDE; virtual bool IsSuitableHost(content::RenderProcessHost* process_host, const GURL& site_url) OVERRIDE; + virtual bool MayReuseHost(content::RenderProcessHost* process_host) OVERRIDE; virtual bool ShouldTryToUseExistingProcessHost( content::BrowserContext* browser_context, const GURL& url) OVERRIDE; virtual void SiteInstanceGotProcess( @@ -278,6 +283,9 @@ class ChromeContentBrowserClient : public content::ContentBrowserClient { virtual bool IsPluginAllowedToUseDevChannelAPIs() OVERRIDE; + virtual net::CookieStore* OverrideCookieStoreForRenderProcess( + int render_process_id) OVERRIDE; + private: #if defined(ENABLE_WEBRTC) // Copies disable WebRTC encryption switch depending on the channel. @@ -296,6 +304,14 @@ class ChromeContentBrowserClient : public content::ContentBrowserClient { scoped_ptr permissions_policy_delegate_; + // The prerender tracker used to determine whether a render process is used + // for prerendering and an override cookie store must be provided. + // This needs to be kept as a member rather than just looked up from + // the profile due to initialization ordering, as well as due to threading. + // It is initialized on the UI thread when the ResoureDispatcherHost is + // created. It is used only the IO thread. + prerender::PrerenderTracker* prerender_tracker_; + friend class DisableWebRtcEncryptionFlagTest; DISALLOW_COPY_AND_ASSIGN(ChromeContentBrowserClient); diff --git a/chrome/browser/extensions/activity_log/activity_log_browsertest.cc b/chrome/browser/extensions/activity_log/activity_log_browsertest.cc index a78d30a5eb1c..32a175efff95 100644 --- a/chrome/browser/extensions/activity_log/activity_log_browsertest.cc +++ b/chrome/browser/extensions/activity_log/activity_log_browsertest.cc @@ -12,7 +12,10 @@ #include "chrome/browser/ui/tabs/tab_strip_model.h" #include "chrome/common/chrome_switches.h" #include "chrome/test/base/ui_test_utils.h" +#include "content/public/browser/browser_context.h" +#include "content/public/browser/browser_thread.h" #include "content/public/browser/notification_service.h" +#include "content/public/browser/storage_partition.h" #include "content/public/test/browser_test_utils.h" #include "net/dns/mock_host_resolver.h" #include "net/test/embedded_test_server/embedded_test_server.h" @@ -99,6 +102,13 @@ IN_PROC_BROWSER_TEST_F(ActivityLogPrerenderTest, TestScriptInjected) { "http://www.google.com.bo:%d/test.html", port)); + if (!prerender_manager->cookie_store_loaded()) { + base::RunLoop loop; + prerender_manager->set_on_cookie_store_loaded_cb_for_testing( + loop.QuitClosure()); + loop.Run(); + } + const gfx::Size kSize(640, 480); scoped_ptr prerender_handle( prerender_manager->AddPrerenderFromLocalPredictor( diff --git a/chrome/browser/extensions/activity_log/activity_log_unittest.cc b/chrome/browser/extensions/activity_log/activity_log_unittest.cc index 7f1e530bf6f1..5c8fda02319c 100644 --- a/chrome/browser/extensions/activity_log/activity_log_unittest.cc +++ b/chrome/browser/extensions/activity_log/activity_log_unittest.cc @@ -244,6 +244,8 @@ TEST_F(ActivityLogTest, LogPrerender) { prerender::PrerenderManagerFactory::GetForProfile( Profile::FromBrowserContext(profile())); + prerender_manager->OnCookieStoreLoaded(); + const gfx::Size kSize(640, 480); scoped_ptr prerender_handle( prerender_manager->AddPrerenderFromLocalPredictor( diff --git a/chrome/browser/net/chrome_network_delegate.cc b/chrome/browser/net/chrome_network_delegate.cc index 1cab93d7654e..bafd11fa1731 100644 --- a/chrome/browser/net/chrome_network_delegate.cc +++ b/chrome/browser/net/chrome_network_delegate.cc @@ -29,6 +29,7 @@ #include "chrome/browser/net/client_hints.h" #include "chrome/browser/net/connect_interceptor.h" #include "chrome/browser/performance_monitor/performance_monitor.h" +#include "chrome/browser/prerender/prerender_tracker.h" #include "chrome/browser/profiles/profile_manager.h" #include "chrome/browser/task_manager/task_manager.h" #include "chrome/common/pref_names.h" @@ -52,6 +53,7 @@ #include "net/http/http_response_headers.h" #include "net/socket_stream/socket_stream.h" #include "net/url_request/url_request.h" +#include "net/url_request/url_request_context.h" #if defined(OS_CHROMEOS) #include "base/command_line.h" @@ -358,7 +360,8 @@ ChromeNetworkDelegate::ChromeNetworkDelegate( domain_reliability_monitor_(NULL), received_content_length_(0), original_content_length_(0), - first_request_(true) { + first_request_(true), + prerender_tracker_(NULL) { DCHECK(event_router); DCHECK(enable_referrers); } @@ -742,6 +745,13 @@ bool ChromeNetworkDelegate::OnCanSetCookie(const net::URLRequest& request, cookie_line, *options, !allow)); } + if (prerender_tracker_) { + prerender_tracker_->OnCookieChangedForURL( + render_process_id, + request.context()->cookie_store()->GetCookieMonster(), + request.url()); + } + return allow; } diff --git a/chrome/browser/net/chrome_network_delegate.h b/chrome/browser/net/chrome_network_delegate.h index 20531805c7f3..608ba05234c8 100644 --- a/chrome/browser/net/chrome_network_delegate.h +++ b/chrome/browser/net/chrome_network_delegate.h @@ -49,6 +49,10 @@ namespace policy { class URLBlacklistManager; } +namespace prerender { +class PrerenderTracker; +} + // ChromeNetworkDelegate is the central point from within the chrome code to // add hooks into the network stack. class ChromeNetworkDelegate : public net::NetworkDelegate { @@ -106,6 +110,10 @@ class ChromeNetworkDelegate : public net::NetworkDelegate { domain_reliability_monitor_ = domain_reliability_monitor; } + void set_prerender_tracker(prerender::PrerenderTracker* prerender_tracker) { + prerender_tracker_ = prerender_tracker; + } + // Adds the Client Hints header to HTTP requests. void SetEnableClientHints(); @@ -228,6 +236,8 @@ class ChromeNetworkDelegate : public net::NetworkDelegate { bool first_request_; + prerender::PrerenderTracker* prerender_tracker_; + DISALLOW_COPY_AND_ASSIGN(ChromeNetworkDelegate); }; diff --git a/chrome/browser/net/cookie_store_util.cc b/chrome/browser/net/cookie_store_util.cc index 924c9b0fbe95..5e5d780f909f 100644 --- a/chrome/browser/net/cookie_store_util.cc +++ b/chrome/browser/net/cookie_store_util.cc @@ -12,6 +12,8 @@ #include "chrome/browser/chrome_notification_types.h" #include "chrome/browser/net/chrome_cookie_notification_details.h" #include "chrome/browser/net/evicted_domain_cookie_counter.h" +#include "chrome/browser/prerender/prerender_manager.h" +#include "chrome/browser/prerender/prerender_manager_factory.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/profiles/profile_manager.h" #include "chrome/common/chrome_constants.h" @@ -49,6 +51,13 @@ class ChromeCookieMonsterDelegate : public net::CookieMonsterDelegate { this, cookie, removed, cause)); } + virtual void OnLoaded() OVERRIDE { + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + base::Bind(&ChromeCookieMonsterDelegate::OnLoadedAsyncHelper, + this)); + } + private: virtual ~ChromeCookieMonsterDelegate() {} @@ -73,6 +82,16 @@ class ChromeCookieMonsterDelegate : public net::CookieMonsterDelegate { } } + void OnLoadedAsyncHelper() { + Profile* profile = profile_getter_.Run(); + if (profile) { + prerender::PrerenderManager* prerender_manager = + prerender::PrerenderManagerFactory::GetForProfile(profile); + if (prerender_manager) + prerender_manager->OnCookieStoreLoaded(); + } + } + const base::Callback profile_getter_; }; diff --git a/chrome/browser/net/evicted_domain_cookie_counter.cc b/chrome/browser/net/evicted_domain_cookie_counter.cc index 2d9590aecfea..71139a58a500 100644 --- a/chrome/browser/net/evicted_domain_cookie_counter.cc +++ b/chrome/browser/net/evicted_domain_cookie_counter.cc @@ -111,6 +111,11 @@ void EvictedDomainCookieCounter::OnCookieChanged( next_cookie_monster_delegate_->OnCookieChanged(cookie, removed, cause); } +void EvictedDomainCookieCounter::OnLoaded() { + if (next_cookie_monster_delegate_.get()) + next_cookie_monster_delegate_->OnLoaded(); +} + // static EvictedDomainCookieCounter::EvictedCookieKey EvictedDomainCookieCounter::GetKey(const net::CanonicalCookie& cookie) { diff --git a/chrome/browser/net/evicted_domain_cookie_counter.h b/chrome/browser/net/evicted_domain_cookie_counter.h index 5cf87973a67b..5731fa91ade4 100644 --- a/chrome/browser/net/evicted_domain_cookie_counter.h +++ b/chrome/browser/net/evicted_domain_cookie_counter.h @@ -90,6 +90,7 @@ class EvictedDomainCookieCounter : public net::CookieMonster::Delegate { virtual void OnCookieChanged(const net::CanonicalCookie& cookie, bool removed, ChangeCause cause) OVERRIDE; + virtual void OnLoaded() OVERRIDE; private: // Identifier of an evicted cookie. diff --git a/chrome/browser/net/evicted_domain_cookie_counter_unittest.cc b/chrome/browser/net/evicted_domain_cookie_counter_unittest.cc index ca2597d487dd..1a735894be75 100644 --- a/chrome/browser/net/evicted_domain_cookie_counter_unittest.cc +++ b/chrome/browser/net/evicted_domain_cookie_counter_unittest.cc @@ -193,6 +193,8 @@ TEST_F(EvictedDomainCookieCounterTest, TestChain) { ++(*result_); } + virtual void OnLoaded() OVERRIDE {} + private: virtual ~ChangedDelegateDummy() {} diff --git a/chrome/browser/prerender/external_prerender_handler_android.cc b/chrome/browser/prerender/external_prerender_handler_android.cc index 013873bf886b..0be6463ba364 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 111181c46edb..6009ddfbd154 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 bbfba0dc05dd..75ffbc4dadad 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(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 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 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 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 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(); diff --git a/chrome/browser/prerender/prerender_contents.cc b/chrome/browser/prerender/prerender_contents.cc index 35f45a4de882..8e209e10ee02 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 d60602f9c4a7..3b8ba1de1936 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 { 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 000000000000..3f94efc95bcf --- /dev/null +++ b/chrome/browser/prerender/prerender_cookie_store.cc @@ -0,0 +1,241 @@ +// Copyright (c) 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 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* cookie_change_urls) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + + if (in_forwarding_mode_) + return; + + // Apply all changes to the underlying cookie store. + for (std::vector::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 000000000000..229048626070 --- /dev/null +++ b/chrome/browser/prerender/prerender_cookie_store.h @@ -0,0 +1,164 @@ +// Copyright (c) 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 +#include +#include + +#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 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* 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 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 changes_cookie_monster_; + + // Log of cookie operations performed + std::vector cookie_ops_; + + // The keys which have been copied on write to |changes_cookie_monster_|. + std::set copied_keys_; + + // Keys which have been read (but not necessarily been modified). + std::set 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 ac0526de29eb..adeaf4920f44 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 8551c104b207..412be21ad036 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 d36a502a10ff..e8344718b33b 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, @@ -1280,6 +1303,13 @@ 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. + RecordFinalStatus(origin, experiment, FINAL_STATUS_COOKIE_STORE_NOT_LOADED); + return NULL; + } + PrerenderContents* prerender_contents = CreatePrerenderContents( url, referrer, origin, experiment); DCHECK(prerender_contents); @@ -1304,11 +1334,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()); @@ -1826,4 +1861,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 6861b7ba39e4..c91322d2a658 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, public base::NonThreadSafe, public content::NotificationObserver, + public content::RenderProcessHostObserver, public KeyedService, public MediaCaptureDevicesDispatcher::Observer { public: @@ -295,6 +297,8 @@ class PrerenderManager : public base::SupportsWeakPtr, 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, // 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 { @@ -510,6 +533,11 @@ class PrerenderManager : public base::SupportsWeakPtr, // 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; @@ -719,6 +747,15 @@ class PrerenderManager : public base::SupportsWeakPtr, // 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 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 b44c8d3430df..cabb35b44b09 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 +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 cookie_change_urls; + if (was_swapped) + it->second->ApplyChanges(&cookie_change_urls); + + scoped_refptr 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::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 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 f9dd17beebae..6020658f0fae 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 +#include #include +#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 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 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 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 > + 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 1af24ea4ab96..6a8a16de78a5 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 diff --git a/chrome/browser/profiles/profile_io_data.cc b/chrome/browser/profiles/profile_io_data.cc index ba8b91e3a189..9a4234f032ce 100644 --- a/chrome/browser/profiles/profile_io_data.cc +++ b/chrome/browser/profiles/profile_io_data.cc @@ -366,6 +366,7 @@ void ProfileIOData::InitializeOnUIThread(Profile* profile) { #endif params->profile = profile; + params->prerender_tracker = g_browser_process->prerender_tracker(); profile_params_.reset(params.release()); ChromeNetworkDelegate::InitializePrefsOnUIThread( @@ -948,6 +949,7 @@ void ProfileIOData::Init( network_delegate->set_cookie_settings(profile_params_->cookie_settings.get()); network_delegate->set_enable_do_not_track(&enable_do_not_track_); network_delegate->set_force_google_safe_search(&force_safesearch_); + network_delegate->set_prerender_tracker(profile_params_->prerender_tracker); network_delegate_.reset(network_delegate); fraudulent_certificate_reporter_.reset( diff --git a/chrome/browser/profiles/profile_io_data.h b/chrome/browser/profiles/profile_io_data.h index 43b46d59cf76..aee3ea046120 100644 --- a/chrome/browser/profiles/profile_io_data.h +++ b/chrome/browser/profiles/profile_io_data.h @@ -61,6 +61,10 @@ class PolicyHeaderIOHelper; class URLBlacklistManager; } // namespace policy +namespace prerender { +class PrerenderTracker; +} + // Conceptually speaking, the ProfileIOData represents data that lives on the IO // thread that is owned by a Profile, such as, but not limited to, network // objects like CookieMonster, HttpTransactionFactory, etc. Profile owns @@ -298,6 +302,8 @@ class ProfileIOData { // ensure it's not accidently used on the IO thread. Before using it on the // UI thread, call ProfileManager::IsValidProfile to ensure it's alive. void* profile; + + prerender::PrerenderTracker* prerender_tracker; }; explicit ProfileIOData(Profile::ProfileType profile_type); diff --git a/chrome/browser/ui/search/instant_search_prerenderer_unittest.cc b/chrome/browser/ui/search/instant_search_prerenderer_unittest.cc index f4ba748015fa..cc8ca4929c84 100644 --- a/chrome/browser/ui/search/instant_search_prerenderer_unittest.cc +++ b/chrome/browser/ui/search/instant_search_prerenderer_unittest.cc @@ -59,7 +59,8 @@ class DummyPrerenderContents : public PrerenderContents { virtual void StartPrerendering( int ALLOW_UNUSED creator_child_id, const gfx::Size& ALLOW_UNUSED size, - content::SessionStorageNamespace* session_storage_namespace) OVERRIDE; + content::SessionStorageNamespace* session_storage_namespace, + net::URLRequestContextGetter* request_context) OVERRIDE; virtual bool GetChildId(int* child_id) const OVERRIDE; virtual bool GetRouteId(int* route_id) const OVERRIDE; @@ -115,7 +116,8 @@ DummyPrerenderContents::DummyPrerenderContents( void DummyPrerenderContents::StartPrerendering( int ALLOW_UNUSED creator_child_id, const gfx::Size& ALLOW_UNUSED size, - content::SessionStorageNamespace* session_storage_namespace) { + content::SessionStorageNamespace* session_storage_namespace, + net::URLRequestContextGetter* request_context) { prerender_contents_.reset(content::WebContents::CreateWithSessionStorage( content::WebContents::CreateParams(profile_), session_storage_namespace_map_)); @@ -181,7 +183,8 @@ class InstantSearchPrerendererTest : public InstantUnitTestBase { SetPrerenderContentsFactory( new DummyPrerenderContentsFactory(call_did_finish_load, session_storage_namespace_map)); - + PrerenderManagerFactory::GetForProfile(browser()->profile())-> + OnCookieStoreLoaded(); if (prerender_search_results_base_page) { InstantSearchPrerenderer* prerenderer = GetInstantSearchPrerenderer(); prerenderer->Init(session_storage_namespace_map, gfx::Size(640, 480)); diff --git a/chrome/browser/ui/webui/net_internals/net_internals_ui_browsertest.cc b/chrome/browser/ui/webui/net_internals/net_internals_ui_browsertest.cc index 6f62e8d659f1..3d60cc015019 100644 --- a/chrome/browser/ui/webui/net_internals/net_internals_ui_browsertest.cc +++ b/chrome/browser/ui/webui/net_internals/net_internals_ui_browsertest.cc @@ -398,6 +398,12 @@ void NetInternalsTest::SetUpOnMainThread() { prerender::PrerenderManager* prerender_manager = prerender::PrerenderManagerFactory::GetForProfile(profile); prerender_manager->mutable_config().max_bytes = 1000 * 1024 * 1024; + if (!prerender_manager->cookie_store_loaded()) { + base::RunLoop loop; + prerender_manager->set_on_cookie_store_loaded_cb_for_testing( + loop.QuitClosure()); + loop.Run(); + } } content::WebUIMessageHandler* NetInternalsTest::GetMockMessageHandler() { diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi index 3e59c741911b..2d344468c239 100644 --- a/chrome/chrome_browser.gypi +++ b/chrome/chrome_browser.gypi @@ -1591,6 +1591,8 @@ 'browser/prefs/tracked/tracked_split_preference.h', 'browser/prerender/external_prerender_handler_android.cc', 'browser/prerender/external_prerender_handler_android.h', + 'browser/prerender/prerender_cookie_store.cc', + 'browser/prerender/prerender_cookie_store.h', 'browser/prerender/prerender_condition.h', 'browser/prerender/prerender_config.cc', 'browser/prerender/prerender_config.h', diff --git a/chrome/test/data/android/prerender/homepage.html b/chrome/test/data/android/prerender/homepage.html new file mode 100644 index 000000000000..5f9c68beb92c --- /dev/null +++ b/chrome/test/data/android/prerender/homepage.html @@ -0,0 +1,12 @@ + + + + Homepage + + + +

Dummy Homepage

+ + + + diff --git a/chrome/test/data/prerender/prerender_cookie.html b/chrome/test/data/prerender/prerender_cookie.html new file mode 100644 index 000000000000..d57433b10fa1 --- /dev/null +++ b/chrome/test/data/prerender/prerender_cookie.html @@ -0,0 +1,41 @@ + + + + + diff --git a/content/browser/renderer_host/render_process_host_impl.cc b/content/browser/renderer_host/render_process_host_impl.cc index d5870eb88042..26039a4aa685 100644 --- a/content/browser/renderer_host/render_process_host_impl.cc +++ b/content/browser/renderer_host/render_process_host_impl.cc @@ -1711,11 +1711,12 @@ RenderProcessHost* RenderProcessHost::GetExistingProcessHost( iterator iter(AllHostsIterator()); while (!iter.IsAtEnd()) { - if (RenderProcessHostImpl::IsSuitableHost( + if (GetContentClient()->browser()->MayReuseHost(iter.GetCurrentValue()) && + RenderProcessHostImpl::IsSuitableHost( iter.GetCurrentValue(), - browser_context, site_url)) + browser_context, site_url)) { suitable_renderers.push_back(iter.GetCurrentValue()); - + } iter.Advance(); } @@ -1768,7 +1769,8 @@ RenderProcessHost* RenderProcessHostImpl::GetProcessHostForSite( std::string site = SiteInstance::GetSiteForURL(browser_context, url) .possibly_invalid_spec(); RenderProcessHost* host = map->FindProcess(site); - if (host && !IsSuitableHost(host, browser_context, url)) { + if (host && (!GetContentClient()->browser()->MayReuseHost(host) || + !IsSuitableHost(host, browser_context, url))) { // The registered process does not have an appropriate set of bindings for // the url. Remove it from the map so we can register a better one. RecordAction( diff --git a/content/public/browser/content_browser_client.cc b/content/public/browser/content_browser_client.cc index fb8279bc8628..6afbfd876836 100644 --- a/content/public/browser/content_browser_client.cc +++ b/content/public/browser/content_browser_client.cc @@ -66,6 +66,10 @@ bool ContentBrowserClient::IsSuitableHost(RenderProcessHost* process_host, return true; } +bool ContentBrowserClient::MayReuseHost(RenderProcessHost* process_host) { + return true; +} + bool ContentBrowserClient::ShouldTryToUseExistingProcessHost( BrowserContext* browser_context, const GURL& url) { return false; diff --git a/content/public/browser/content_browser_client.h b/content/public/browser/content_browser_client.h index 5f0473ba8649..cc7a4954a083 100644 --- a/content/public/browser/content_browser_client.h +++ b/content/public/browser/content_browser_client.h @@ -234,6 +234,10 @@ class CONTENT_EXPORT ContentBrowserClient { virtual bool IsSuitableHost(RenderProcessHost* process_host, const GURL& site_url); + // Returns whether a new view for a new site instance can be added to a + // given |process_host|. + virtual bool MayReuseHost(RenderProcessHost* process_host); + // Returns whether a new process should be created or an existing one should // be reused based on the URL we want to load. This should return false, // unless there is a good reason otherwise. @@ -626,7 +630,7 @@ class CONTENT_EXPORT ContentBrowserClient { // if the default cookie store should be used // This is called on the IO thread. virtual net::CookieStore* OverrideCookieStoreForRenderProcess( - int render_process_id_); + int render_process_id); #if defined(VIDEO_HOLE) // Allows an embedder to provide its own ExternalVideoSurfaceContainer diff --git a/net/cookies/canonical_cookie.cc b/net/cookies/canonical_cookie.cc index 5d67ebc21828..54a9565a9c68 100644 --- a/net/cookies/canonical_cookie.cc +++ b/net/cookies/canonical_cookie.cc @@ -394,4 +394,20 @@ std::string CanonicalCookie::DebugString() const { static_cast(creation_date_.ToTimeT())); } +CanonicalCookie* CanonicalCookie::Duplicate() { + CanonicalCookie* cc = new CanonicalCookie(); + cc->source_ = source_; + cc->name_ = name_; + cc->value_ = value_; + cc->domain_ = domain_; + cc->path_ = path_; + cc->creation_date_ = creation_date_; + cc->expiry_date_ = expiry_date_; + cc->last_access_date_ = last_access_date_; + cc->secure_ = secure_; + cc->httponly_ = httponly_; + cc->priority_ = priority_; + return cc; +} + } // namespace net diff --git a/net/cookies/canonical_cookie.h b/net/cookies/canonical_cookie.h index a78eece41988..a55674052422 100644 --- a/net/cookies/canonical_cookie.h +++ b/net/cookies/canonical_cookie.h @@ -125,6 +125,9 @@ class NET_EXPORT CanonicalCookie { std::string DebugString() const; + // Returns a duplicate of this cookie. + CanonicalCookie* Duplicate(); + // Returns the cookie source when cookies are set for |url|. This function // is public for unit test purposes only. static std::string GetCookieSourceFromURL(const GURL& url); @@ -134,6 +137,9 @@ class NET_EXPORT CanonicalCookie { const base::Time& server_time); private: + // NOTE: When any new members are added below, the implementation of + // Duplicate() must be updated to copy the new member accordingly. + // The source member of a canonical cookie is the origin of the URL that tried // to set this cookie, minus the port number if any. This field is not // persistent though; its only used in the in-tab cookies dialog to show the @@ -153,6 +159,9 @@ class NET_EXPORT CanonicalCookie { bool secure_; bool httponly_; CookiePriority priority_; + // NOTE: When any new members are added above this comment, the + // implementation of Duplicate() must be updated to copy the new member + // accordingly. }; typedef std::vector CookieList; diff --git a/net/cookies/cookie_monster.cc b/net/cookies/cookie_monster.cc index f2023b6caa89..7aed8f8d0f76 100644 --- a/net/cookies/cookie_monster.cc +++ b/net/cookies/cookie_monster.cc @@ -53,6 +53,7 @@ #include "base/callback.h" #include "base/logging.h" #include "base/memory/scoped_ptr.h" +#include "base/memory/scoped_vector.h" #include "base/message_loop/message_loop.h" #include "base/message_loop/message_loop_proxy.h" #include "base/metrics/histogram.h" @@ -311,7 +312,7 @@ std::string BuildCookieLine(const CanonicalCookieVector& cookies) { CookieMonster::CookieMonster(PersistentCookieStore* store, CookieMonsterDelegate* delegate) : initialized_(false), - loaded_(false), + loaded_(store == NULL), store_(store), last_access_threshold_( TimeDelta::FromSeconds(kDefaultAccessUpdateThresholdSeconds)), @@ -327,7 +328,7 @@ CookieMonster::CookieMonster(PersistentCookieStore* store, CookieMonsterDelegate* delegate, int last_access_threshold_milliseconds) : initialized_(false), - loaded_(false), + loaded_(store == NULL), store_(store), last_access_threshold_(base::TimeDelta::FromMilliseconds( last_access_threshold_milliseconds)), @@ -1433,6 +1434,11 @@ void CookieMonster::InitStore() { store_->Load(base::Bind(&CookieMonster::OnLoaded, this, TimeTicks::Now())); } +void CookieMonster::ReportLoaded() { + if (delegate_.get()) + delegate_->OnLoaded(); +} + void CookieMonster::OnLoaded(TimeTicks beginning_time, const std::vector& cookies) { StoreLoadedCookies(cookies); @@ -1440,6 +1446,8 @@ void CookieMonster::OnLoaded(TimeTicks beginning_time, // Invoke the task queue of cookie request. InvokeQueue(); + + ReportLoaded(); } void CookieMonster::OnKeyLoaded(const std::string& key, @@ -2245,4 +2253,55 @@ Time CookieMonster::CurrentTime() { Time::FromInternalValue(last_time_seen_.ToInternalValue() + 1)); } +bool CookieMonster::CopyCookiesForKeyToOtherCookieMonster( + std::string key, + CookieMonster* other) { + ScopedVector duplicated_cookies; + + { + base::AutoLock autolock(lock_); + DCHECK(other); + if (!loaded_) + return false; + + for (CookieMapItPair its = cookies_.equal_range(key); + its.first != its.second; + ++its.first) { + CookieMap::iterator curit = its.first; + CanonicalCookie* cc = curit->second; + + duplicated_cookies.push_back(cc->Duplicate()); + } + } + + { + base::AutoLock autolock(other->lock_); + if (!other->loaded_) + return false; + + // There must not exist any entries for the key to be copied in |other|. + CookieMapItPair its = other->cookies_.equal_range(key); + if (its.first != its.second) + return false; + + // Store the copied cookies in |other|. + for (ScopedVector::const_iterator it = + duplicated_cookies.begin(); + it != duplicated_cookies.end(); + ++it) { + other->InternalInsertCookie(key, *it, true); + } + + // Since the cookies are owned by |other| now, weak clear must be used. + duplicated_cookies.weak_clear(); + } + + return true; +} + +bool CookieMonster::loaded() { + base::AutoLock autolock(lock_); + return loaded_; +} + } // namespace net diff --git a/net/cookies/cookie_monster.h b/net/cookies/cookie_monster.h index 5a0692239162..b719c32b5956 100644 --- a/net/cookies/cookie_monster.h +++ b/net/cookies/cookie_monster.h @@ -307,6 +307,19 @@ class NET_EXPORT CookieMonster : public CookieStore { static const char* kDefaultCookieableSchemes[]; static const int kDefaultCookieableSchemesCount; + // Copies all keys for the given |key| to another cookie monster |other|. + // Both |other| and |this| must be loaded for this operation to succeed. + // Furthermore, there may not be any cookies stored in |other| for |key|. + // Returns false if any of these conditions is not met. + bool CopyCookiesForKeyToOtherCookieMonster(std::string key, + CookieMonster* other); + + // Find the key (for lookup in cookies_) based on the given domain. + // See comment on keys before the CookieMap typedef. + std::string GetKey(const std::string& domain) const; + + bool loaded(); + private: // For queueing the cookie monster calls. class CookieMonsterTask; @@ -455,6 +468,7 @@ class NET_EXPORT CookieMonster : public CookieStore { InitStore(); } else { loaded_ = true; + ReportLoaded(); } initialized_ = true; } @@ -464,6 +478,9 @@ class NET_EXPORT CookieMonster : public CookieStore { // Should only be called by InitIfNecessary(). void InitStore(); + // Reports to the delegate that the cookie monster was loaded. + void ReportLoaded(); + // Stores cookies loaded from the backing store and invokes any deferred // calls. |beginning_time| should be the moment PersistentCookieStore::Load // was invoked and is used for reporting histogram_time_blocked_on_load_. @@ -576,10 +593,6 @@ class NET_EXPORT CookieMonster : public CookieStore { CookieItVector::iterator cookie_its_begin, CookieItVector::iterator cookie_its_end); - // Find the key (for lookup in cookies_) based on the given domain. - // See comment on keys before the CookieMap typedef. - std::string GetKey(const std::string& domain) const; - bool HasCookieableScheme(const GURL& url); // Statistics support @@ -720,6 +733,9 @@ class NET_EXPORT CookieMonsterDelegate virtual void OnCookieChanged(const CanonicalCookie& cookie, bool removed, ChangeCause cause) = 0; + // Indicates that the cookie store has fully loaded. + virtual void OnLoaded() = 0; + protected: friend class base::RefCountedThreadSafe; virtual ~CookieMonsterDelegate() {} diff --git a/net/cookies/cookie_monster_store_test.cc b/net/cookies/cookie_monster_store_test.cc index 350a0159b900..226242a6e503 100644 --- a/net/cookies/cookie_monster_store_test.cc +++ b/net/cookies/cookie_monster_store_test.cc @@ -97,6 +97,8 @@ void MockCookieMonsterDelegate::OnCookieChanged( changes_.push_back(notification); } +void MockCookieMonsterDelegate::OnLoaded() {} + MockCookieMonsterDelegate::~MockCookieMonsterDelegate() {} CanonicalCookie BuildCanonicalCookie(const std::string& key, diff --git a/net/cookies/cookie_monster_store_test.h b/net/cookies/cookie_monster_store_test.h index ec40de625d98..efbcbe5bd569 100644 --- a/net/cookies/cookie_monster_store_test.h +++ b/net/cookies/cookie_monster_store_test.h @@ -132,6 +132,8 @@ class MockCookieMonsterDelegate : public CookieMonsterDelegate { bool removed, CookieMonsterDelegate::ChangeCause cause) OVERRIDE; + virtual void OnLoaded() OVERRIDE; + private: virtual ~MockCookieMonsterDelegate();