diff options
author | mmenke@chromium.org <mmenke@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-05-20 15:05:08 +0000 |
---|---|---|
committer | mmenke@chromium.org <mmenke@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-05-20 15:05:08 +0000 |
commit | 28a05f3af8c3031d23b55630d811fc5c18505a3c (patch) | |
tree | cb4691e3b846236408c45695b72051412f68dbf3 | |
parent | 2937a8c72bfd28188ccd0b3f3b54bc3303b5f537 (diff) | |
download | chromium_src-28a05f3af8c3031d23b55630d811fc5c18505a3c.zip chromium_src-28a05f3af8c3031d23b55630d811fc5c18505a3c.tar.gz chromium_src-28a05f3af8c3031d23b55630d811fc5c18505a3c.tar.bz2 |
Safely cancel prerenders on threads other than the UI thread.
Previously, prerendering was cancelled on the IO thread by not
doing something, and then passing a task to the UI thread
to abort the prerender. This resulted in a race which could
result in swapping in the prerender before the task was
executed. This fixes that.
BUG=83062
TEST=PrerenderStatusManagerTests, PrerenderBrowserTests
Review URL: http://codereview.chromium.org/7038012
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@86082 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | chrome/browser/prerender/prerender_contents.cc | 104 | ||||
-rw-r--r-- | chrome/browser/prerender/prerender_contents.h | 6 | ||||
-rw-r--r-- | chrome/browser/prerender/prerender_manager.cc | 35 | ||||
-rw-r--r-- | chrome/browser/prerender/prerender_manager.h | 11 | ||||
-rw-r--r-- | chrome/browser/prerender/prerender_tracker.cc | 202 | ||||
-rw-r--r-- | chrome/browser/prerender/prerender_tracker.h | 136 | ||||
-rw-r--r-- | chrome/browser/prerender/prerender_tracker_unittest.cc | 294 | ||||
-rw-r--r-- | chrome/chrome_browser.gypi | 2 | ||||
-rw-r--r-- | chrome/chrome_tests.gypi | 1 | ||||
-rw-r--r-- | content/browser/DEPS | 1 | ||||
-rw-r--r-- | content/browser/renderer_host/resource_dispatcher_host.cc | 42 | ||||
-rw-r--r-- | content/browser/renderer_host/resource_dispatcher_host.h | 11 |
12 files changed, 719 insertions, 126 deletions
diff --git a/chrome/browser/prerender/prerender_contents.cc b/chrome/browser/prerender/prerender_contents.cc index 0da2a6f..343c61a 100644 --- a/chrome/browser/prerender/prerender_contents.cc +++ b/chrome/browser/prerender/prerender_contents.cc @@ -16,6 +16,7 @@ #include "chrome/browser/prerender/prerender_final_status.h" #include "chrome/browser/prerender/prerender_manager.h" #include "chrome/browser/prerender/prerender_render_widget_host_view.h" +#include "chrome/browser/prerender/prerender_tracker.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/renderer_preferences_util.h" #include "chrome/browser/ui/login/login_prompt.h" @@ -61,18 +62,6 @@ struct PrerenderUrlPredicate { GURL url_; }; -void AddChildRoutePair(ResourceDispatcherHost* resource_dispatcher_host, - int child_id, int route_id) { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); - resource_dispatcher_host->AddPrerenderChildRoutePair(child_id, route_id); -} - -void RemoveChildRoutePair(ResourceDispatcherHost* resource_dispatcher_host, - int child_id, int route_id) { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); - resource_dispatcher_host->RemovePrerenderChildRoutePair(child_id, route_id); -} - } // end namespace class PrerenderContentsFactoryImpl : public PrerenderContents::Factory { @@ -146,7 +135,9 @@ PrerenderContents::PrerenderContents(PrerenderManager* prerender_manager, ALLOW_THIS_IN_INITIALIZER_LIST(tab_contents_observer_registrar_(this)), has_stopped_loading_(false), final_status_(FINAL_STATUS_MAX), - prerendering_has_started_(false) { + prerendering_has_started_(false), + child_id_(-1), + route_id_(-1) { DCHECK(prerender_manager != NULL); } @@ -187,18 +178,14 @@ void PrerenderContents::StartPrerenderingOld( new PrerenderRenderWidgetHostView(render_view_host_, this); view->Init(source_render_view_host->view()); - // Register this with the ResourceDispatcherHost as a prerender - // RenderViewHost. This must be done before the Navigate message to catch all - // resource requests, but as it is on the same thread as the Navigate message - // (IO) there is no race condition. - int process_id = render_view_host_->process()->id(); - int view_id = render_view_host_->routing_id(); + child_id_ = render_view_host_->process()->id(); + route_id_ = render_view_host_->routing_id(); - BrowserThread::PostTask( - BrowserThread::IO, FROM_HERE, - NewRunnableFunction(&AddChildRoutePair, - g_browser_process->resource_dispatcher_host(), - process_id, view_id)); + // Register this with the PrerenderTracker as a prerendering RenderViewHost. + // This must be done before the Navigate message to catch all resource + // requests. + PrerenderTracker::GetInstance()->OnPrerenderingStarted(child_id_, route_id_, + prerender_manager_); // Close ourselves when the application is shutting down. notification_registrar_.Add(this, NotificationType::APP_TERMINATING, @@ -292,20 +279,15 @@ void PrerenderContents::StartPrerendering( render_view_host_observer_.reset( new PrerenderRenderViewHostObserver(this, render_view_host_mutable())); - int process_id; - int view_id; - CHECK(GetChildId(&process_id)); - CHECK(GetRouteId(&view_id)); + child_id_ = render_view_host()->process()->id(); + route_id_ = render_view_host()->routing_id(); // Register this with the ResourceDispatcherHost as a prerender // RenderViewHost. This must be done before the Navigate message to catch all // resource requests, but as it is on the same thread as the Navigate message // (IO) there is no race condition. - BrowserThread::PostTask( - BrowserThread::IO, FROM_HERE, - NewRunnableFunction(&AddChildRoutePair, - g_browser_process->resource_dispatcher_host(), - process_id, view_id)); + PrerenderTracker::GetInstance()->OnPrerenderingStarted(child_id_, route_id_, + prerender_manager_); // Close ourselves when the application is shutting down. notification_registrar_.Add(this, NotificationType::APP_TERMINATING, @@ -353,22 +335,16 @@ void PrerenderContents::StartPrerendering( bool PrerenderContents::GetChildId(int* child_id) const { CHECK(child_id); - const RenderViewHost* prerender_render_view_host = render_view_host(); - if (prerender_render_view_host) { - *child_id = prerender_render_view_host->process()->id(); - return true; - } - return false; + DCHECK_GE(child_id_, -1); + *child_id = child_id_; + return child_id_ != -1; } bool PrerenderContents::GetRouteId(int* route_id) const { CHECK(route_id); - const RenderViewHost* prerender_render_view_host = render_view_host(); - if (prerender_render_view_host) { - *route_id = prerender_render_view_host->routing_id(); - return true; - } - return false; + DCHECK_GE(route_id_, -1); + *route_id = route_id_; + return route_id_ != -1; } void PrerenderContents::set_final_status(FinalStatus final_status) { @@ -396,21 +372,13 @@ PrerenderContents::~PrerenderContents() { if (prerendering_has_started()) RecordFinalStatus(final_status_); - if (render_view_host_ || prerender_contents_.get()) { - RenderViewHost* prerender_render_view_host = render_view_host_mutable(); - - int process_id = prerender_render_view_host->process()->id(); - int view_id = prerender_render_view_host->routing_id(); + // Only delete the RenderViewHost if we own it. + if (render_view_host_) + render_view_host_->Shutdown(); - BrowserThread::PostTask( - BrowserThread::IO, FROM_HERE, - NewRunnableFunction(&RemoveChildRoutePair, - g_browser_process->resource_dispatcher_host(), - process_id, view_id)); - - // Only delete the RenderViewHost if we own it. - if (render_view_host_) - render_view_host_->Shutdown(); + if (child_id_ != -1 && route_id_ != -1) { + PrerenderTracker::GetInstance()->OnPrerenderingFinished( + child_id_, route_id_); } } @@ -739,6 +707,24 @@ void PrerenderContents::Destroy(FinalStatus final_status) { if (prerender_manager_->IsPendingDelete(this)) return; + if (child_id_ != -1 && route_id_ != -1) { + // Cancel the prerender in the PrerenderTracker. This is needed + // because destroy may be called directly from the UI thread without calling + // TryCancel(). This is difficult to completely avoid, since prerendering + // can be cancelled before a RenderView is created. + bool is_cancelled = + PrerenderTracker::GetInstance()->TryCancel(child_id_, route_id_, + final_status); + CHECK(is_cancelled); + + // A different final status may have been set already from another thread. + // If so, use it instead. + if (!PrerenderTracker::GetInstance()->GetFinalStatus(child_id_, route_id_, + &final_status)) { + NOTREACHED(); + } + } + prerender_manager_->MoveEntryToPendingDelete(this); set_final_status(final_status); // We may destroy the PrerenderContents before we have initialized the diff --git a/chrome/browser/prerender/prerender_contents.h b/chrome/browser/prerender/prerender_contents.h index feaaab2..31c115e 100644 --- a/chrome/browser/prerender/prerender_contents.h +++ b/chrome/browser/prerender/prerender_contents.h @@ -323,6 +323,8 @@ class PrerenderContents : public RenderViewHostDelegate, bool has_stopped_loading_; + // This must be the same value as the PrerenderTracker has recorded for + // |this|, when |this| has a RenderView. FinalStatus final_status_; bool prerendering_has_started_; @@ -343,6 +345,10 @@ class PrerenderContents : public RenderViewHostDelegate, scoped_ptr<TabContentsDelegateImpl> tab_contents_delegate_; + // These are -1 before a RenderView is created. + int child_id_; + int route_id_; + DISALLOW_COPY_AND_ASSIGN(PrerenderContents); }; diff --git a/chrome/browser/prerender/prerender_manager.cc b/chrome/browser/prerender/prerender_manager.cc index 053de20..f5ccebe 100644 --- a/chrome/browser/prerender/prerender_manager.cc +++ b/chrome/browser/prerender/prerender_manager.cc @@ -14,6 +14,7 @@ #include "chrome/browser/favicon/favicon_tab_helper.h" #include "chrome/browser/prerender/prerender_contents.h" #include "chrome/browser/prerender/prerender_final_status.h" +#include "chrome/browser/prerender/prerender_tracker.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h" #include "chrome/browser/ui/tab_contents/tab_contents_wrapper_delegate.h" @@ -390,9 +391,7 @@ void PrerenderManager::DeleteOldEntries() { PrerenderContentsData data = prerender_list_.front(); if (IsPrerenderElementFresh(data.start_time_)) return; - prerender_list_.pop_front(); - data.contents_->set_final_status(FINAL_STATUS_TIMED_OUT); - delete data.contents_; + data.contents_->Destroy(FINAL_STATUS_TIMED_OUT); } MaybeStopSchedulingPeriodicCleanups(); } @@ -439,6 +438,16 @@ bool PrerenderManager::MaybeUsePreloadedPageOld(TabContents* tab_contents, return false; } + int child_id, route_id; + CHECK(prerender_contents->GetChildId(&child_id)); + CHECK(prerender_contents->GetRouteId(&route_id)); + + // Try to set the prerendered page as used, so any subsequent attempts to + // cancel on other threads will fail. If this fails because the prerender + // was already cancelled, possibly on another thread, fail. + if (!PrerenderTracker::GetInstance()->TryUse(child_id, route_id)) + return false; + if (!prerender_contents->load_start_time().is_null()) RecordTimeUntilUsed(GetCurrentTimeTicks() - prerender_contents->load_start_time()); @@ -447,11 +456,6 @@ bool PrerenderManager::MaybeUsePreloadedPageOld(TabContents* tab_contents, ++prerenders_per_session_count_); prerender_contents->set_final_status(FINAL_STATUS_USED); - int child_id; - int route_id; - CHECK(prerender_contents->GetChildId(&child_id)); - CHECK(prerender_contents->GetRouteId(&route_id)); - RenderViewHost* render_view_host = prerender_contents->render_view_host_mutable(); prerender_contents->set_render_view_host(NULL); @@ -531,6 +535,16 @@ bool PrerenderManager::MaybeUsePreloadedPage(TabContents* tab_contents, return false; } + int child_id, route_id; + CHECK(prerender_contents->GetChildId(&child_id)); + CHECK(prerender_contents->GetRouteId(&route_id)); + + // Try to set the prerendered page as used, so any subsequent attempts to + // cancel on other threads will fail. If this fails because the prerender + // was already cancelled, possibly on another thread, fail. + if (!PrerenderTracker::GetInstance()->TryUse(child_id, route_id)) + return false; + if (!prerender_contents->load_start_time().is_null()) RecordTimeUntilUsed(GetCurrentTimeTicks() - prerender_contents->load_start_time()); @@ -539,11 +553,6 @@ bool PrerenderManager::MaybeUsePreloadedPage(TabContents* tab_contents, ++prerenders_per_session_count_); prerender_contents->set_final_status(FINAL_STATUS_USED); - int child_id; - int route_id; - CHECK(prerender_contents->GetChildId(&child_id)); - CHECK(prerender_contents->GetRouteId(&route_id)); - RenderViewHost* render_view_host = prerender_contents->prerender_contents()->render_view_host(); DCHECK(render_view_host); diff --git a/chrome/browser/prerender/prerender_manager.h b/chrome/browser/prerender/prerender_manager.h index a5e60d7..8917abc 100644 --- a/chrome/browser/prerender/prerender_manager.h +++ b/chrome/browser/prerender/prerender_manager.h @@ -48,15 +48,6 @@ void HandlePrefetchTag( const GURL& referrer, bool make_pending); -// Given a renderer process id and view id, this will destroy any preloads and -// pending preloads than are using or originated in the given render view. -// Must be called on the UI thread. -void DestroyPreloadForRenderView( - const base::WeakPtr<PrerenderManager>& prerender_manager_weak_ptr, - int render_process_id, - int render_view_id, - FinalStatus final_status); - // PrerenderManager is responsible for initiating and keeping prerendered // views of webpages. All methods must be called on the UI thread unless // indicated otherwise. @@ -93,7 +84,7 @@ class PrerenderManager : public base::SupportsWeakPtr<PrerenderManager>, // Destroy all preloads for the given child route id pair and assign a final // status to them. - void DestroyPreloadForChildRouteIdPair( + virtual void DestroyPreloadForChildRouteIdPair( const std::pair<int, int>& child_route_id_pair, FinalStatus final_status); diff --git a/chrome/browser/prerender/prerender_tracker.cc b/chrome/browser/prerender/prerender_tracker.cc new file mode 100644 index 0000000..c82acd3 --- /dev/null +++ b/chrome/browser/prerender/prerender_tracker.cc @@ -0,0 +1,202 @@ +// Copyright (c) 2011 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 "base/logging.h" +#include "chrome/browser/prerender/prerender_manager.h" +#include "chrome/browser/prerender/prerender_tracker.h" +#include "content/browser/browser_thread.h" + +namespace prerender { + +namespace { + +void DestroyPreloadForRenderView( + const base::WeakPtr<PrerenderManager>& prerender_manager_weak_ptr, + int child_id, + int route_id, + FinalStatus final_status) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + PrerenderManager* prerender_manager = prerender_manager_weak_ptr.get(); + if (!prerender_manager) + return; + + prerender_manager->DestroyPreloadForChildRouteIdPair( + std::make_pair(child_id, route_id), + final_status); +} + +} // namespace + +struct RenderViewInfo { + explicit RenderViewInfo(PrerenderManager* prerender_manager) + : final_status(FINAL_STATUS_MAX), + prerender_manager(prerender_manager->AsWeakPtr()) { + } + + FinalStatus final_status; + base::WeakPtr<PrerenderManager> prerender_manager; +}; + +// static +PrerenderTracker* PrerenderTracker::GetInstance() { + return Singleton<PrerenderTracker>::get(); +} + +bool PrerenderTracker::TryUse(int child_id, int route_id) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + + FinalStatus final_status = SetFinalStatus(child_id, route_id, + FINAL_STATUS_USED); + return final_status == FINAL_STATUS_USED; +} + +bool PrerenderTracker::TryCancel( + int child_id, + int route_id, + FinalStatus final_status) { + DCHECK_NE(FINAL_STATUS_USED, final_status); + DCHECK(final_status >= 0 && final_status < FINAL_STATUS_MAX); + + final_status = SetFinalStatus(child_id, route_id, final_status); + return final_status != FINAL_STATUS_USED && final_status != FINAL_STATUS_MAX; +} + +bool PrerenderTracker::TryCancelOnIOThread( + int child_id, + int route_id, + FinalStatus final_status) { + DCHECK_NE(FINAL_STATUS_USED, final_status); + DCHECK(final_status >= 0 && final_status < FINAL_STATUS_MAX); + + if (!IsPrerenderingOnIOThread(child_id, route_id)) + return false; + return TryCancel(child_id, route_id, final_status); +} + +bool PrerenderTracker::IsPrerenderingOnIOThread(int child_id, + int route_id) const { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + + ChildRouteIdPair child_route_id_pair(child_id, route_id); + return possibly_prerendering_io_thread_set_.end() != + possibly_prerendering_io_thread_set_.find(child_route_id_pair); +} + +bool PrerenderTracker::GetFinalStatus(int child_id, int route_id, + FinalStatus* final_status) const { + ChildRouteIdPair child_route_id_pair(child_id, route_id); + + base::AutoLock lock(final_status_map_lock_); + FinalStatusMap::const_iterator final_status_it = + final_status_map_.find(child_route_id_pair); + if (final_status_map_.end() == final_status_map_.find(child_route_id_pair)) + return false; + *final_status = final_status_it->second.final_status; + return true; +} + +void PrerenderTracker::OnPrerenderingStarted( + int child_id, int route_id, PrerenderManager* prerender_manager) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + DCHECK_GE(child_id, 0); + DCHECK_GE(route_id, 0); + + ChildRouteIdPair child_route_id_pair(child_id, route_id); + + // The RenderView should not already be prerendering. + DCHECK(final_status_map_.end() == + final_status_map_.find(child_route_id_pair)); + + BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, + NewRunnableFunction(&AddPrerenderOnIOThreadTask, child_route_id_pair)); + + base::AutoLock lock(final_status_map_lock_); + + final_status_map_.insert( + std::make_pair(child_route_id_pair, RenderViewInfo(prerender_manager))); +} + +void PrerenderTracker::OnPrerenderingFinished(int child_id, int route_id) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + DCHECK_GE(child_id, 0); + DCHECK_GE(route_id, 0); + + ChildRouteIdPair child_route_id_pair(child_id, route_id); + + BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, + NewRunnableFunction(&RemovePrerenderOnIOThreadTask, child_route_id_pair)); + + base::AutoLock lock(final_status_map_lock_); + size_t num_erased = final_status_map_.erase(child_route_id_pair); + DCHECK_EQ(1u, num_erased); +} + +PrerenderTracker::PrerenderTracker() { +} + +PrerenderTracker::~PrerenderTracker() { +} + +FinalStatus PrerenderTracker::SetFinalStatus(int child_id, int route_id, + FinalStatus final_status) { + DCHECK(final_status >= FINAL_STATUS_USED && final_status < FINAL_STATUS_MAX); + + ChildRouteIdPair child_route_id_pair(child_id, route_id); + + base::AutoLock lock(final_status_map_lock_); + FinalStatusMap::iterator final_status_it = + final_status_map_.find(child_route_id_pair); + if (final_status_it == final_status_map_.end()) { + // The RenderView has already been either used or destroyed. + return FINAL_STATUS_MAX; + } + + if (final_status_it->second.final_status == FINAL_STATUS_MAX) { + final_status_it->second.final_status = final_status; + if (final_status != FINAL_STATUS_USED) { + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + NewRunnableFunction(&DestroyPreloadForRenderView, + final_status_it->second.prerender_manager, + child_id, + route_id, + final_status)); + } + } + return final_status_it->second.final_status; +} + +void PrerenderTracker::AddPrerenderOnIOThread( + const ChildRouteIdPair& child_route_id_pair) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + DCHECK(!IsPrerenderingOnIOThread(child_route_id_pair.first, + child_route_id_pair.second)); + + possibly_prerendering_io_thread_set_.insert(child_route_id_pair); +} + +void PrerenderTracker::RemovePrerenderOnIOThread( + const ChildRouteIdPair& child_route_id_pair) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + DCHECK(IsPrerenderingOnIOThread(child_route_id_pair.first, + child_route_id_pair.second)); + + possibly_prerendering_io_thread_set_.erase(child_route_id_pair); +} + +// static +void PrerenderTracker::AddPrerenderOnIOThreadTask( + const ChildRouteIdPair& child_route_id_pair) { + GetInstance()->AddPrerenderOnIOThread(child_route_id_pair); +} + +// static +void PrerenderTracker::RemovePrerenderOnIOThreadTask( + const ChildRouteIdPair& child_route_id_pair) { + GetInstance()->RemovePrerenderOnIOThread(child_route_id_pair); +} + +} // namespace prerender diff --git a/chrome/browser/prerender/prerender_tracker.h b/chrome/browser/prerender/prerender_tracker.h new file mode 100644 index 0000000..f263052 --- /dev/null +++ b/chrome/browser/prerender/prerender_tracker.h @@ -0,0 +1,136 @@ +// Copyright (c) 2011 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_TRACKER_H_ +#define CHROME_BROWSER_PRERENDER_PRERENDER_TRACKER_H_ +#pragma once + +#include <map> +#include <set> + +#include "base/memory/singleton.h" +#include "base/synchronization/lock.h" +#include "chrome/browser/prerender/prerender_final_status.h" + +namespace prerender { + +class PrerenderManager; +struct RenderViewInfo; + +// PrerenderTracker is responsible for keeping track of all prerendering +// RenderViews and their statuses. Its list is guaranteed to be up to date +// and can be modified on any thread. +class PrerenderTracker { + public: + // Returns the PrerenderTracker singleton. + static PrerenderTracker* GetInstance(); + + // Attempts to set the status of the specified RenderViewHost to + // FINAL_STATUS_USED. Returns true on success. Returns false if it has + // already been cancelled for any reason, or is no longer prerendering. + // Can only be called only on the IO thread. This method will not call + // PrerenderContents::set_final_status() on the corresponding + // PrerenderContents. + // + // If it returns true, all subsequent calls to TryCancel for the RenderView + // will return false. + bool TryUse(int child_id, int route_id); + + // Attempts to cancel prerendering by the specified RenderView, setting the + // FinalStatus to |final_status|. Returns true if the specified prerender has + // been cancelled, either as a result of this call or for any other reason. + // If the call results in cancelling a PrerenderContents, a task to destroy + // it is also posted to the UI thread. + // + // When true is returned, it is guaranteed that the RenderView will never + // be displayed. When false is returned, the RenderView has either been + // swapped into a tab or has already been destroyed. + bool TryCancel(int child_id, int route_id, FinalStatus final_status); + + // Same as above, but can only called on the IO Thread. Does not acquire a + // lock when the RenderView is not being prerendered. + bool TryCancelOnIOThread(int child_id, int route_id, + FinalStatus final_status); + + // Returns whether or not a RenderView is prerendering. Can only be called on + // the IO thread. Does not acquire a lock, so may claim a RenderView that has + // been displayed or destroyed is still prerendering. + // TODO(mmenke): Remove external use of this method and make it private. + bool IsPrerenderingOnIOThread(int child_id, int route_id) const; + + // Gets the FinalStatus of the specified prerendered RenderView. Returns + // |true| and sets |final_status| to the status of the RenderView if it + // is found, returns false otherwise. + bool GetFinalStatus(int child_id, int route_id, + FinalStatus* final_status) const; + + protected: + friend class PrerenderContents; + + FRIEND_TEST_ALL_PREFIXES(PrerenderTrackerTest, PrerenderTrackerUsed); + FRIEND_TEST_ALL_PREFIXES(PrerenderTrackerTest, PrerenderTrackerCancelled); + FRIEND_TEST_ALL_PREFIXES(PrerenderTrackerTest, PrerenderTrackerCancelledOnIO); + FRIEND_TEST_ALL_PREFIXES(PrerenderTrackerTest, PrerenderTrackerCancelledFast); + FRIEND_TEST_ALL_PREFIXES(PrerenderTrackerTest, PrerenderTrackerMultiple); + + // Must be called when a RenderView starts prerendering, before the first + // navigation starts to avoid any races. + void OnPrerenderingStarted(int child_id, int route_id, + PrerenderManager* prerender_manager); + + // Must be called when a RenderView stops prerendering, either because the + // RenderView was used or prerendering was cancelled and it is being + // destroyed. + void OnPrerenderingFinished(int child_id, int route_id); + + private: + friend struct DefaultSingletonTraits<PrerenderTracker>; + + typedef std::pair<int, int> ChildRouteIdPair; + + // Map of child/route id pairs to final statuses. + typedef std::map<ChildRouteIdPair, RenderViewInfo> FinalStatusMap; + // Set of child/route id pairs that may be prerendering. + typedef std::set<ChildRouteIdPair> PossiblyPrerenderingChildRouteIdPairs; + + PrerenderTracker(); + ~PrerenderTracker(); + + // Attempts to set the FinalStatus of the specified RenderView to + // |final_status|. If the FinalStatus of the RenderView has already been + // set, does nothing. Returns the resulting FinalStatus of that RenderView, + // regardless of success or failure. If the RenderView isn't currently + // prerendering, returns FINAL_STATUS_MAX. + FinalStatus SetFinalStatus(int child_id, int route_id, + FinalStatus final_status); + + // Add/remove the specified pair to |possibly_prerendering_io_thread_set_| on + // the IO Thread. + void AddPrerenderOnIOThread(const ChildRouteIdPair& child_route_id_pair); + void RemovePrerenderOnIOThread(const ChildRouteIdPair& child_route_id_pair); + + // Tasks posted to the IO Thread to call the above functions. + static void AddPrerenderOnIOThreadTask( + const ChildRouteIdPair& child_route_id_pair); + static void RemovePrerenderOnIOThreadTask( + const ChildRouteIdPair& child_route_id_pair); + + // |final_status_map_lock_| protects access to |final_status_map_|. + mutable base::Lock final_status_map_lock_; + // Map containing child/route id pairs and their final statuses. Must only be + // accessed while the lock is held. Values are always accurate and up to + // date. + FinalStatusMap final_status_map_; + + // Superset of child/route id pairs that are prerendering. Can only access on + // the IO thread. May contain entries that have since been displayed. Only + // used to prevent locking when not needed. + PossiblyPrerenderingChildRouteIdPairs possibly_prerendering_io_thread_set_; + + DISALLOW_COPY_AND_ASSIGN(PrerenderTracker); +}; + +} // namespace prerender + +#endif // CHROME_BROWSER_PRERENDER_PRERENDER_TRACKER_H_ diff --git a/chrome/browser/prerender/prerender_tracker_unittest.cc b/chrome/browser/prerender/prerender_tracker_unittest.cc new file mode 100644 index 0000000..4ed6f83 --- /dev/null +++ b/chrome/browser/prerender/prerender_tracker_unittest.cc @@ -0,0 +1,294 @@ +// Copyright (c) 2011 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 <set> + +#include "base/logging.h" +#include "chrome/browser/prerender/prerender_manager.h" +#include "chrome/browser/prerender/prerender_tracker.h" +#include "content/browser/browser_thread.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace prerender { + +namespace { + +class TestPrerenderManager : public PrerenderManager { + public: + TestPrerenderManager() : PrerenderManager(NULL) { + rate_limit_enabled_ = false; + } + + virtual void DestroyPreloadForChildRouteIdPair( + const std::pair<int, int>& child_route_id_pair, + FinalStatus final_status) OVERRIDE { + cancelled_id_pairs_.insert(child_route_id_pair); + } + + bool WasPrerenderCancelled(int child_id, int route_id) { + std::pair<int, int> child_route_id_pair(child_id, route_id); + return cancelled_id_pairs_.count(child_route_id_pair) != 0; + } + + // Set of all the RenderViews that have been cancelled. + std::set<std::pair<int, int> > cancelled_id_pairs_; +}; + +} // namespace + +class PrerenderTrackerTest : public testing::Test { + public: + PrerenderTrackerTest() : + ui_thread_(BrowserThread::UI, &message_loop_), + io_thread_(BrowserThread::IO, &message_loop_), + prerender_manager_(new TestPrerenderManager()) { + } + + TestPrerenderManager* prerender_manager() { + return prerender_manager_.get(); + } + + PrerenderTracker* prerender_tracker() { + return PrerenderTracker::GetInstance(); + } + + int GetCurrentStatus(int child_id, int route_id) { + FinalStatus final_status; + if (!prerender_tracker()->GetFinalStatus(child_id, route_id, + &final_status)) { + return -1; + } + return final_status; + } + + // Runs any tasks queued on either thread. + void RunEvents() { + message_loop_.RunAllPending(); + } + + private: + MessageLoop message_loop_; + BrowserThread ui_thread_; + BrowserThread io_thread_; + + scoped_ptr<TestPrerenderManager> prerender_manager_; +}; + +// Check that a non-existant RenderView is handled correctly. +TEST_F(PrerenderTrackerTest, PrerenderTrackerNull) { + FinalStatus final_status; + EXPECT_FALSE(prerender_tracker()->TryUse(0, 0)); + EXPECT_FALSE(prerender_tracker()->TryCancel(0, 0, FINAL_STATUS_HTTPS)); + EXPECT_FALSE(prerender_tracker()->TryCancelOnIOThread( + 0, 0, FINAL_STATUS_HTTPS)); + EXPECT_FALSE(prerender_tracker()->IsPrerenderingOnIOThread(0, 0)); + EXPECT_FALSE(prerender_tracker()->GetFinalStatus(0, 0, &final_status)); + EXPECT_FALSE(prerender_manager()->WasPrerenderCancelled(0, 0)); +} + +// Check that a page that is used is handled correctly. +TEST_F(PrerenderTrackerTest, PrerenderTrackerUsed) { + prerender_tracker()->OnPrerenderingStarted(0, 0, prerender_manager()); + EXPECT_EQ(FINAL_STATUS_MAX, GetCurrentStatus(0, 0)); + + // This calls AddPrerenderOnIOThreadTask(). + RunEvents(); + + EXPECT_TRUE(prerender_tracker()->IsPrerenderingOnIOThread(0, 0)); + EXPECT_EQ(FINAL_STATUS_MAX, GetCurrentStatus(0, 0)); + + // Display the prerendered RenderView. + EXPECT_TRUE(prerender_tracker()->TryUse(0, 0)); + + // Make sure the page can't be destroyed or claim it was destroyed after + // it's been used. + EXPECT_FALSE(prerender_tracker()->TryCancel(0, 0, FINAL_STATUS_HTTPS)); + EXPECT_FALSE(prerender_tracker()->TryCancelOnIOThread( + 0, 0, FINAL_STATUS_TIMED_OUT)); + EXPECT_EQ(FINAL_STATUS_USED, GetCurrentStatus(0, 0)); + + // This would call DestroyPreloadForChildRouteIdPair(), if the prerender were + // cancelled. + RunEvents(); + + // These functions should all behave as before. + EXPECT_FALSE(prerender_tracker()->TryCancel(0, 0, FINAL_STATUS_HTTPS)); + EXPECT_FALSE(prerender_tracker()->TryCancelOnIOThread( + 0, 0, FINAL_STATUS_TIMED_OUT)); + EXPECT_EQ(FINAL_STATUS_USED, GetCurrentStatus(0, 0)); + + // This calls DestroyPreloadForChildRouteIdPair(). + prerender_tracker()->OnPrerenderingFinished(0, 0); + EXPECT_TRUE(prerender_tracker()->IsPrerenderingOnIOThread(0, 0)); + + // This calls RemovePrerenderOnIOThreadTask(). + RunEvents(); + + FinalStatus final_status; + EXPECT_FALSE(prerender_tracker()->GetFinalStatus(0, 0, &final_status)); + EXPECT_FALSE(prerender_tracker()->IsPrerenderingOnIOThread(0, 0)); + EXPECT_FALSE(prerender_tracker()->TryCancel(0, 0, FINAL_STATUS_HTTPS)); + EXPECT_FALSE(prerender_manager()->WasPrerenderCancelled(0, 0)); +} + +// Check that a prerendered page cancelled by TryCancel() is handled correctly. +TEST_F(PrerenderTrackerTest, PrerenderTrackerCancelled) { + prerender_tracker()->OnPrerenderingStarted(0, 0, prerender_manager()); + EXPECT_EQ(FINAL_STATUS_MAX, GetCurrentStatus(0, 0)); + + // This calls AddPrerenderOnIOThreadTask(). + RunEvents(); + + // Cancel the prerender. + EXPECT_TRUE(prerender_tracker()->TryCancel(0, 0, FINAL_STATUS_HTTPS)); + + EXPECT_FALSE(prerender_tracker()->TryUse(0, 0)); + EXPECT_TRUE(prerender_tracker()->TryCancel(0, 0, FINAL_STATUS_TIMED_OUT)); + EXPECT_TRUE(prerender_tracker()->TryCancelOnIOThread( + 0, 0, FINAL_STATUS_TIMED_OUT)); + EXPECT_EQ(FINAL_STATUS_HTTPS, GetCurrentStatus(0, 0)); + + // This calls DestroyPreloadForChildRouteIdPair(). + RunEvents(); + EXPECT_TRUE(prerender_manager()->WasPrerenderCancelled(0, 0)); + + // These should all work until the prerendering RenderViewHost is destroyed. + EXPECT_TRUE(prerender_tracker()->TryCancel(0, 0, FINAL_STATUS_TIMED_OUT)); + EXPECT_TRUE(prerender_tracker()->TryCancelOnIOThread( + 0, 0, FINAL_STATUS_TIMED_OUT)); + EXPECT_EQ(FINAL_STATUS_HTTPS, GetCurrentStatus(0, 0)); + + prerender_tracker()->OnPrerenderingFinished(0, 0); + EXPECT_TRUE(prerender_tracker()->IsPrerenderingOnIOThread(0, 0)); + + // This calls RemovePrerenderOnIOThreadTask(). + RunEvents(); + + FinalStatus final_status; + EXPECT_FALSE(prerender_tracker()->GetFinalStatus(0, 0, &final_status)); + EXPECT_FALSE(prerender_tracker()->IsPrerenderingOnIOThread(0, 0)); +} + +// Check that a prerendered page cancelled on the IO thread by +// TryCancelOnIOThread() is handled correctly. +TEST_F(PrerenderTrackerTest, PrerenderTrackerCancelledOnIO) { + prerender_tracker()->OnPrerenderingStarted(0, 0, prerender_manager()); + EXPECT_EQ(FINAL_STATUS_MAX, GetCurrentStatus(0, 0)); + + // This calls AddPrerenderOnIOThreadTask(). + RunEvents(); + + // Cancel the prerender. + EXPECT_TRUE(prerender_tracker()->TryCancelOnIOThread( + 0, 0, FINAL_STATUS_TIMED_OUT)); + + EXPECT_FALSE(prerender_tracker()->TryUse(0, 0)); + EXPECT_TRUE(prerender_tracker()->TryCancel(0, 0, FINAL_STATUS_HTTPS)); + EXPECT_TRUE(prerender_tracker()->TryCancelOnIOThread( + 0, 0, FINAL_STATUS_HTTPS)); + EXPECT_EQ(FINAL_STATUS_TIMED_OUT, GetCurrentStatus(0, 0)); + + // This calls DestroyPreloadForChildRouteIdPair(). + RunEvents(); + EXPECT_TRUE(prerender_manager()->WasPrerenderCancelled(0, 0)); + + // These should all work until the prerendering RenderViewHost is destroyed. + EXPECT_TRUE(prerender_tracker()->TryCancel(0, 0, FINAL_STATUS_HTTPS)); + EXPECT_TRUE(prerender_tracker()->TryCancelOnIOThread( + 0, 0, FINAL_STATUS_HTTPS)); + EXPECT_EQ(FINAL_STATUS_TIMED_OUT, GetCurrentStatus(0, 0)); + + prerender_tracker()->OnPrerenderingFinished(0, 0); + EXPECT_TRUE(prerender_tracker()->IsPrerenderingOnIOThread(0, 0)); + + // This calls RemovePrerenderOnIOThreadTask(). + RunEvents(); + + FinalStatus final_status; + EXPECT_FALSE(prerender_tracker()->GetFinalStatus(0, 0, &final_status)); + EXPECT_FALSE(prerender_tracker()->IsPrerenderingOnIOThread(0, 0)); +} + +// Check that a prerendered page cancelled before it reaches the IO thread is +// handled correctly. +TEST_F(PrerenderTrackerTest, PrerenderTrackerCancelledFast) { + prerender_tracker()->OnPrerenderingStarted(0, 0, prerender_manager()); + // Cancel the prerender. + EXPECT_TRUE(prerender_tracker()->TryCancel(0, 0, FINAL_STATUS_HTTPS)); + + EXPECT_FALSE(prerender_tracker()->TryUse(0, 0)); + EXPECT_TRUE(prerender_tracker()->TryCancel(0, 0, FINAL_STATUS_TIMED_OUT)); + + // This calls AddPrerenderOnIOThreadTask() and + // DestroyPreloadForChildRouteIdPair(). + RunEvents(); + EXPECT_TRUE(prerender_manager()->WasPrerenderCancelled(0, 0)); + + EXPECT_TRUE(prerender_tracker()->TryCancelOnIOThread( + 0, 0, FINAL_STATUS_TIMED_OUT)); + EXPECT_TRUE(prerender_tracker()->TryCancel(0, 0, FINAL_STATUS_TIMED_OUT)); + EXPECT_EQ(FINAL_STATUS_HTTPS, GetCurrentStatus(0, 0)); + + prerender_tracker()->OnPrerenderingFinished(0, 0); + + // This calls RemovePrerenderOnIOThreadTask(). + RunEvents(); + + FinalStatus final_status; + EXPECT_FALSE(prerender_tracker()->GetFinalStatus(0, 0, &final_status)); + EXPECT_FALSE(prerender_tracker()->IsPrerenderingOnIOThread(0, 0)); +} + +// Check that handling two pages at once works. +TEST_F(PrerenderTrackerTest, PrerenderTrackerMultiple) { + prerender_tracker()->OnPrerenderingStarted(0, 0, prerender_manager()); + + // This calls AddPrerenderOnIOThreadTask(). + RunEvents(); + EXPECT_TRUE(prerender_tracker()->IsPrerenderingOnIOThread(0, 0)); + EXPECT_FALSE(prerender_tracker()->IsPrerenderingOnIOThread(1, 2)); + EXPECT_FALSE(prerender_tracker()->TryUse(1, 2)); + EXPECT_FALSE(prerender_tracker()->TryCancel(1, 2, FINAL_STATUS_HTTPS)); + + // Start second prerender. + prerender_tracker()->OnPrerenderingStarted(1, 2, prerender_manager()); + // This calls AddPrerenderOnIOThreadTask(). + RunEvents(); + + // Use (0, 0). + EXPECT_TRUE(prerender_tracker()->TryUse(0, 0)); + EXPECT_EQ(FINAL_STATUS_USED, GetCurrentStatus(0, 0)); + EXPECT_EQ(FINAL_STATUS_MAX, GetCurrentStatus(1, 2)); + + // Cancel (1, 2). + EXPECT_TRUE(prerender_tracker()->TryCancelOnIOThread( + 1, 2, FINAL_STATUS_HTTPS)); + + EXPECT_FALSE(prerender_tracker()->TryCancel(0, 0, FINAL_STATUS_HTTPS)); + EXPECT_EQ(FINAL_STATUS_USED, GetCurrentStatus(0, 0)); + + EXPECT_FALSE(prerender_tracker()->TryUse(1, 2)); + EXPECT_TRUE(prerender_tracker()->TryCancel(1, 2, FINAL_STATUS_HTTPS)); + EXPECT_EQ(FINAL_STATUS_HTTPS, GetCurrentStatus(1, 2)); + + // This calls DestroyPreloadForChildRouteIdPair(). + RunEvents(); + EXPECT_FALSE(prerender_manager()->WasPrerenderCancelled(0, 0)); + EXPECT_TRUE(prerender_manager()->WasPrerenderCancelled(1, 2)); + + prerender_tracker()->OnPrerenderingFinished(0, 0); + prerender_tracker()->OnPrerenderingFinished(1, 2); + + // This calls RemovePrerenderOnIOThreadTask(). + RunEvents(); + + FinalStatus final_status; + EXPECT_FALSE(prerender_tracker()->GetFinalStatus(0, 0, &final_status)); + EXPECT_FALSE(prerender_tracker()->IsPrerenderingOnIOThread(0, 0)); + + EXPECT_FALSE(prerender_tracker()->GetFinalStatus(1, 2, &final_status)); + EXPECT_FALSE(prerender_tracker()->IsPrerenderingOnIOThread(1, 2)); +} + +} // namespace prerender diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi index 5465fc8..d1c0605 100644 --- a/chrome/chrome_browser.gypi +++ b/chrome/chrome_browser.gypi @@ -1653,6 +1653,8 @@ 'browser/prerender/prerender_render_view_host_observer.h', 'browser/prerender/prerender_render_widget_host_view.cc', 'browser/prerender/prerender_render_widget_host_view.h', + 'browser/prerender/prerender_tracker.cc', + 'browser/prerender/prerender_tracker.h', 'browser/printing/background_printing_manager.cc', 'browser/printing/background_printing_manager.h', 'browser/printing/cloud_print/cloud_print_proxy_service.cc', diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi index 3fae730..b987f2c 100644 --- a/chrome/chrome_tests.gypi +++ b/chrome/chrome_tests.gypi @@ -1502,6 +1502,7 @@ 'browser/prefs/scoped_user_pref_update_unittest.cc', 'browser/prefs/session_startup_pref_unittest.cc', 'browser/prerender/prerender_manager_unittest.cc', + 'browser/prerender/prerender_tracker_unittest.cc', 'browser/printing/cloud_print/cloud_print_setup_source_unittest.cc', 'browser/printing/print_dialog_cloud_unittest.cc', 'browser/printing/print_job_unittest.cc', diff --git a/content/browser/DEPS b/content/browser/DEPS index 055ff1a..5678284 100644 --- a/content/browser/DEPS +++ b/content/browser/DEPS @@ -62,6 +62,7 @@ include_rules = [ # http://crbug.com/77090
"+chrome/browser/prerender/prerender_manager.h",
+ "+chrome/browser/prerender/prerender_tracker.h",
# http://crbug.com/76788
"+chrome/browser/profiles/profile.h",
diff --git a/content/browser/renderer_host/resource_dispatcher_host.cc b/content/browser/renderer_host/resource_dispatcher_host.cc index 8fdc0c3..451f99e 100644 --- a/content/browser/renderer_host/resource_dispatcher_host.cc +++ b/content/browser/renderer_host/resource_dispatcher_host.cc @@ -26,6 +26,7 @@ #include "chrome/browser/external_protocol_handler.h" #include "chrome/browser/net/url_request_tracking.h" #include "chrome/browser/prerender/prerender_manager.h" +#include "chrome/browser/prerender/prerender_tracker.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/renderer_host/download_resource_handler.h" #include "chrome/browser/renderer_host/safe_browsing_resource_handler.h" @@ -432,7 +433,9 @@ void ResourceDispatcherHost::BeginRequest( } const GURL referrer = MaybeStripReferrer(request_data.referrer); - const bool is_prerendering = IsPrerenderingChildRoutePair(child_id, route_id); + const bool is_prerendering = + prerender::PrerenderTracker::GetInstance()->IsPrerenderingOnIOThread( + child_id, route_id); // Handle a PREFETCH resource type. If prefetch is disabled, squelch the // request. If prerendering is enabled, trigger a prerender for the URL @@ -467,16 +470,11 @@ void ResourceDispatcherHost::BeginRequest( // Abort any prerenders that spawn requests that use invalid HTTP methods. if (is_prerendering && !prerender::PrerenderManager::IsValidHttpMethod(request_data.method)) { - BrowserThread::PostTask( - BrowserThread::UI, FROM_HERE, - NewRunnableFunction( - prerender::DestroyPreloadForRenderView, - resource_context.prerender_manager(), - child_id, - route_id, - prerender::FINAL_STATUS_INVALID_HTTP_METHOD)); - AbortRequestBeforeItStarts(filter_, sync_result, route_id, request_id); - return; + if (prerender::PrerenderTracker::GetInstance()->TryCancelOnIOThread( + child_id, route_id, prerender::FINAL_STATUS_INVALID_HTTP_METHOD)) { + AbortRequestBeforeItStarts(filter_, sync_result, route_id, request_id); + return; + } } // Construct the event handler. @@ -2036,28 +2034,6 @@ net::RequestPriority ResourceDispatcherHost::DetermineRequestPriority( } } -void ResourceDispatcherHost::AddPrerenderChildRoutePair(int child_id, - int route_id) { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); - DCHECK(!IsPrerenderingChildRoutePair(child_id, route_id)); - prerender_child_route_pairs_.insert(std::make_pair(child_id, route_id)); -} - -void ResourceDispatcherHost::RemovePrerenderChildRoutePair(int child_id, - int route_id) { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); - DCHECK(IsPrerenderingChildRoutePair(child_id, route_id)); - prerender_child_route_pairs_.erase(std::make_pair(child_id, route_id)); -} - -bool ResourceDispatcherHost::IsPrerenderingChildRoutePair(int child_id, - int route_id) const { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); - std::pair<int, int> c_r_pair = std::make_pair(child_id, route_id); - return (prerender_child_route_pairs_.find(c_r_pair) != - prerender_child_route_pairs_.end()); -} - // static bool ResourceDispatcherHost::is_prefetch_enabled() { diff --git a/content/browser/renderer_host/resource_dispatcher_host.h b/content/browser/renderer_host/resource_dispatcher_host.h index f7a5c64..f8643fe 100644 --- a/content/browser/renderer_host/resource_dispatcher_host.h +++ b/content/browser/renderer_host/resource_dispatcher_host.h @@ -244,14 +244,6 @@ class ResourceDispatcherHost : public net::URLRequest::Delegate { static bool is_prefetch_enabled(); static void set_is_prefetch_enabled(bool value); - void AddPrerenderChildRoutePair(int child_id, int route_id); - void RemovePrerenderChildRoutePair(int child_id, int route_id); - - typedef std::set<std::pair<int, int> > PrerenderChildRouteIdPairs; - const PrerenderChildRouteIdPairs& prerender_child_route_id_pairs() const { - return prerender_child_route_pairs_; - } - private: FRIEND_TEST_ALL_PREFIXES(ResourceDispatcherHostTest, TestBlockedRequestsProcessDies); @@ -384,8 +376,6 @@ class ResourceDispatcherHost : public net::URLRequest::Delegate { const GURL& new_first_party_for_cookies); void OnReleaseDownloadedFile(int request_id); - bool IsPrerenderingChildRoutePair(int child_id, int route_id) const; - ResourceHandler* CreateSafeBrowsingResourceHandler( ResourceHandler* handler, int child_id, int route_id, ResourceType::Type resource_type); @@ -487,7 +477,6 @@ class ResourceDispatcherHost : public net::URLRequest::Delegate { ResourceMessageFilter* filter_; static bool is_prefetch_enabled_; - PrerenderChildRouteIdPairs prerender_child_route_pairs_; DISALLOW_COPY_AND_ASSIGN(ResourceDispatcherHost); |