#include "chrome/browser/browser_process.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/common/cancelable_request.h"
-#include "chrome/browser/favicon/favicon_tab_helper.h"
#include "chrome/browser/history/history_service_factory.h"
#include "chrome/browser/net/chrome_cookie_notification_details.h"
#include "chrome/browser/predictors/predictor_database.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/prerender_messages.h"
+#include "chrome/common/prerender_types.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/devtools_agent_host.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_delegate.h"
#include "content/public/browser/web_contents_view.h"
-#include "content/public/common/favicon_url.h"
#include "content/public/common/url_constants.h"
#include "extensions/common/constants.h"
#include "net/url_request/url_request_context.h"
// Timeout, in ms, for a session storage namespace merge.
const int kSessionStorageNamespaceMergeTimeoutMs = 500;
+// If true, all session storage merges hang indefinitely.
+bool g_hang_session_storage_merges_for_testing = false;
+
// Indicates whether a Prerender has been cancelled such that we need
// a dummy replacement for the purpose of recording the correct PPLT for
// the Match Complete case.
base::TimeTicks time;
};
-PrerenderManager::PrerenderedWebContentsData::
-PrerenderedWebContentsData(Origin origin) : origin(origin) {
-}
-
-PrerenderManager::WouldBePrerenderedWebContentsData::
-WouldBePrerenderedWebContentsData(Origin origin)
- : origin(origin),
- state(WAITING_FOR_PROVISIONAL_LOAD) {
-}
-
PrerenderManager::PrerenderManager(Profile* profile,
PrerenderTracker* prerender_tracker)
: enabled_(profile && profile->GetPrefs() &&
last_prerender_start_time_(GetCurrentTimeTicks() -
base::TimeDelta::FromMilliseconds(kMinTimeBetweenPrerendersMs)),
prerender_history_(new PrerenderHistory(kHistoryLength)),
- histograms_(new PrerenderHistograms()) {
+ histograms_(new PrerenderHistograms()),
+ profile_network_bytes_(0),
+ last_recorded_profile_network_bytes_(0) {
// 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.
int process_id,
int route_id,
const GURL& url,
+ const uint32 rel_types,
const content::Referrer& referrer,
const gfx::Size& size) {
- Origin origin = ORIGIN_LINK_REL_PRERENDER_CROSSDOMAIN;
+ Origin origin = rel_types & PrerenderRelTypePrerender ?
+ ORIGIN_LINK_REL_PRERENDER_CROSSDOMAIN :
+ ORIGIN_LINK_REL_NEXT;
SessionStorageNamespace* session_storage_namespace = NULL;
// Unit tests pass in a process_id == -1.
if (process_id != -1) {
WebContents::FromRenderViewHost(source_render_view_host);
if (!source_web_contents)
return NULL;
- if (source_web_contents->GetURL().host() == url.host())
+ if (origin == ORIGIN_LINK_REL_PRERENDER_CROSSDOMAIN &&
+ source_web_contents->GetURL().host() == url.host()) {
origin = ORIGIN_LINK_REL_PRERENDER_SAMEDOMAIN;
+ }
// TODO(ajwong): This does not correctly handle storage for isolated apps.
session_storage_namespace =
source_web_contents->GetController()
.GetDefaultSessionStorageNamespace();
}
- // If the prerender request comes from a recently cancelled prerender that
- // |this| still owns, then abort the prerender.
- for (ScopedVector<PrerenderData>::iterator it = to_delete_prerenders_.begin();
- it != to_delete_prerenders_.end(); ++it) {
- PrerenderContents* prerender_contents = (*it)->contents();
- int contents_child_id;
- int contents_route_id;
- if (prerender_contents->GetChildId(&contents_child_id) &&
- prerender_contents->GetRouteId(&contents_route_id)) {
- if (contents_child_id == process_id && contents_route_id == route_id)
- return NULL;
- }
- }
-
- if (PrerenderData* parent_prerender_data =
- FindPrerenderDataForChildAndRoute(process_id, route_id)) {
- // Instead of prerendering from inside of a running prerender, we will defer
- // this request until its launcher is made visible.
- if (PrerenderContents* contents = parent_prerender_data->contents()) {
- PrerenderHandle* prerender_handle =
- new PrerenderHandle(static_cast<PrerenderData*>(NULL));
- scoped_ptr<PrerenderContents::PendingPrerenderInfo>
- pending_prerender_info(new PrerenderContents::PendingPrerenderInfo(
- prerender_handle->weak_ptr_factory_.GetWeakPtr(),
- origin, url, referrer, size));
-
- contents->AddPendingPrerender(pending_prerender_info.Pass());
- return prerender_handle;
- }
- }
-
return AddPrerender(origin, process_id, url, referrer, size,
session_storage_namespace);
}
RecordEvent(prerender_data->contents(), PRERENDER_EVENT_SWAPIN_CANDIDATE);
DCHECK(prerender_data->contents());
- // If there is currently a merge pending for this prerender data,
- // or this webcontents, do not swap in, but give the merge a chance to
- // finish and swap into the intended target webcontents.
+ // If there is currently a merge pending for this prerender data, don't swap.
if (prerender_data->pending_swap())
return false;
+ // Abort any existing pending swap on the target contents.
+ PrerenderData* pending_swap =
+ FindPrerenderDataForTargetContents(web_contents);
+ if (pending_swap) {
+ pending_swap->ClearPendingSwap();
+ DCHECK(FindPrerenderDataForTargetContents(web_contents) == NULL);
+ }
+
RecordEvent(prerender_data->contents(),
PRERENDER_EVENT_SWAPIN_NO_MERGE_PENDING);
SessionStorageNamespace* target_namespace =
return NULL;
}
+ PrerenderTabHelper* target_tab_helper =
+ PrerenderTabHelper::FromWebContents(web_contents);
+ if (!target_tab_helper) {
+ NOTREACHED();
+ return NULL;
+ }
+
if (IsNoSwapInExperiment(prerender_data->contents()->experiment_id()))
return NULL;
// that prerendering hasn't even started yet), record that |web_contents| now
// would be showing a prerendered contents, but otherwise, don't do anything.
if (!prerender_data->contents()->prerendering_has_started()) {
- MarkWebContentsAsWouldBePrerendered(web_contents,
- prerender_data->contents()->origin());
+ target_tab_helper->WouldHavePrerenderedNextLoad(
+ prerender_data->contents()->origin());
prerender_data->contents()->Destroy(FINAL_STATUS_WOULD_HAVE_BEEN_USED);
return NULL;
}
// For bookkeeping purposes, we need to mark this WebContents to
// reflect that it would have been prerendered.
if (GetMode() == PRERENDER_MODE_EXPERIMENT_NO_USE_GROUP) {
- MarkWebContentsAsWouldBePrerendered(web_contents,
- prerender_data->contents()->origin());
+ target_tab_helper->WouldHavePrerenderedNextLoad(
+ prerender_data->contents()->origin());
prerender_data->contents()->Destroy(FINAL_STATUS_WOULD_HAVE_BEEN_USED);
return NULL;
}
- int child_id, route_id;
- CHECK(prerender_data->contents()->GetChildId(&child_id));
- CHECK(prerender_data->contents()->GetRouteId(&route_id));
-
// At this point, we've determined that we will use the prerender.
if (prerender_data->pending_swap())
prerender_data->pending_swap()->set_swap_successful(true);
histograms_->RecordPerSessionCount(prerender_contents->origin(),
++prerenders_per_session_count_);
histograms_->RecordUsedPrerender(prerender_contents->origin());
- prerender_contents->SetFinalStatus(FINAL_STATUS_USED);
- // Start pending prerender requests from the PrerenderContents, if there are
- // any.
+ // Mark prerender as used.
prerender_contents->PrepareForUse();
WebContents* new_web_contents =
DCHECK(new_web_contents);
DCHECK(old_web_contents);
- MarkWebContentsAsPrerendered(new_web_contents, prerender_contents->origin());
-
// Merge the browsing history.
new_web_contents->GetController().CopyStateFromAndPrune(
&old_web_contents->GetController(),
should_replace_current_entry);
CoreTabHelper::FromWebContents(old_web_contents)->delegate()->
- SwapTabContents(old_web_contents, new_web_contents);
+ SwapTabContents(old_web_contents,
+ new_web_contents,
+ true,
+ prerender_contents->has_finished_loading());
prerender_contents->CommitHistory(new_web_contents);
- GURL icon_url = prerender_contents->icon_url();
-
- if (!icon_url.is_empty()) {
-#if defined(OS_ANDROID)
- // Do the delayed icon fetch since we didn't download
- // the favicon during prerendering on mobile devices.
- FaviconTabHelper * favicon_tap_helper =
- FaviconTabHelper::FromWebContents(new_web_contents);
- favicon_tap_helper->set_should_fetch_icons(true);
- favicon_tap_helper->FetchFavicon(icon_url);
-#endif // defined(OS_ANDROID)
-
- std::vector<content::FaviconURL> urls;
- urls.push_back(content::FaviconURL(icon_url, content::FaviconURL::FAVICON));
- FaviconTabHelper::FromWebContents(new_web_contents)->
- DidUpdateFaviconURL(prerender_contents->page_id(), urls);
- }
-
// Update PPLT metrics:
// If the tab has finished loading, record a PPLT of 0.
// If the tab is still loading, reset its start time to the current time.
(*it)->MakeIntoMatchCompleteReplacement();
} else {
to_delete_prerenders_.push_back(*it);
+ (*it)->ClearPendingSwap();
active_prerenders_.weak_erase(it);
}
PostCleanupTask();
}
-// static
+void PrerenderManager::RecordPageLoadTimeNotSwappedIn(
+ Origin origin,
+ base::TimeDelta page_load_time,
+ const GURL& url) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ histograms_->RecordPageLoadTimeNotSwappedIn(origin, page_load_time, url);
+}
+
void PrerenderManager::RecordPerceivedPageLoadTime(
+ Origin origin,
+ NavigationType navigation_type,
base::TimeDelta perceived_page_load_time,
double fraction_plt_elapsed_at_swap_in,
- WebContents* web_contents,
const GURL& url) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
- PrerenderManager* prerender_manager =
- PrerenderManagerFactory::GetForProfile(
- Profile::FromBrowserContext(web_contents->GetBrowserContext()));
- if (!prerender_manager)
- return;
- if (!prerender_manager->IsEnabled())
+ if (!IsEnabled())
return;
- Origin prerender_origin = ORIGIN_NONE;
- if (prerender_manager->IsWebContentsPrerendering(web_contents,
- &prerender_origin)) {
- prerender_manager->histograms_->RecordPageLoadTimeNotSwappedIn(
- prerender_origin, perceived_page_load_time, url);
- return;
- }
+ histograms_->RecordPerceivedPageLoadTime(
+ origin, perceived_page_load_time, navigation_type, url);
- bool was_prerender = prerender_manager->IsWebContentsPrerendered(
- web_contents, &prerender_origin);
- bool was_complete_prerender = was_prerender ||
- prerender_manager->WouldWebContentsBePrerendered(web_contents,
- &prerender_origin);
- prerender_manager->histograms_->RecordPerceivedPageLoadTime(
- prerender_origin, perceived_page_load_time, was_prerender,
- was_complete_prerender, url);
-
- if (was_prerender) {
- prerender_manager->histograms_->RecordPercentLoadDoneAtSwapin(
- prerender_origin, fraction_plt_elapsed_at_swap_in);
+ if (navigation_type == NAVIGATION_TYPE_PRERENDERED) {
+ histograms_->RecordPercentLoadDoneAtSwapin(
+ origin, fraction_plt_elapsed_at_swap_in);
}
- if (prerender_manager->local_predictor_.get()) {
- prerender_manager->local_predictor_->
- OnPLTEventForURL(url, perceived_page_load_time);
+ if (local_predictor_) {
+ local_predictor_->OnPLTEventForURL(url, perceived_page_load_time);
}
}
return NULL;
}
+PrerenderContents* PrerenderManager::GetPrerenderContentsForRoute(
+ int child_id,
+ int route_id) const {
+ content::WebContents* web_contents =
+ tab_util::GetWebContentsByID(child_id, route_id);
+ if (web_contents == NULL)
+ return NULL;
+ return GetPrerenderContents(web_contents);
+}
+
const std::vector<WebContents*>
PrerenderManager::GetAllPrerenderingContents() const {
DCHECK(CalledOnValidThread());
return result;
}
-void PrerenderManager::MarkWebContentsAsPrerendered(WebContents* web_contents,
- Origin origin) {
- DCHECK(CalledOnValidThread());
- prerendered_web_contents_data_.insert(
- base::hash_map<content::WebContents*,
- PrerenderedWebContentsData>::value_type(
- web_contents, PrerenderedWebContentsData(origin)));
-}
-
-void PrerenderManager::MarkWebContentsAsWouldBePrerendered(
- WebContents* web_contents,
- Origin origin) {
- DCHECK(CalledOnValidThread());
- would_be_prerendered_map_.insert(
- base::hash_map<content::WebContents*,
- WouldBePrerenderedWebContentsData>::value_type(
- web_contents,
- WouldBePrerenderedWebContentsData(origin)));
-}
-
-void PrerenderManager::MarkWebContentsAsNotPrerendered(
- WebContents* web_contents) {
- DCHECK(CalledOnValidThread());
- prerendered_web_contents_data_.erase(web_contents);
- base::hash_map<content::WebContents*, WouldBePrerenderedWebContentsData>::
- iterator it = would_be_prerendered_map_.find(web_contents);
- if (it != would_be_prerendered_map_.end()) {
- if (it->second.state ==
- WouldBePrerenderedWebContentsData::WAITING_FOR_PROVISIONAL_LOAD) {
- it->second.state =
- WouldBePrerenderedWebContentsData::SEEN_PROVISIONAL_LOAD;
- } else {
- would_be_prerendered_map_.erase(it);
- }
- }
-}
-
-bool PrerenderManager::IsWebContentsPrerendered(
- content::WebContents* web_contents,
- Origin* origin) const {
- DCHECK(CalledOnValidThread());
- base::hash_map<content::WebContents*, PrerenderedWebContentsData>::
- const_iterator it = prerendered_web_contents_data_.find(web_contents);
- if (it == prerendered_web_contents_data_.end())
- return false;
- if (origin)
- *origin = it->second.origin;
- return true;
-}
-
-bool PrerenderManager::WouldWebContentsBePrerendered(
- WebContents* web_contents,
- Origin* origin) const {
- DCHECK(CalledOnValidThread());
- base::hash_map<content::WebContents*, WouldBePrerenderedWebContentsData>::
- const_iterator it = would_be_prerendered_map_.find(web_contents);
- if (it == would_be_prerendered_map_.end())
- return false;
- if (origin)
- *origin = it->second.origin;
- return true;
-}
-
bool PrerenderManager::HasRecentlyBeenNavigatedTo(Origin origin,
const GURL& url) {
DCHECK(CalledOnValidThread());
bool should_replace_current_entry)
: content::WebContentsObserver(target_contents),
manager_(manager),
- target_contents_(target_contents),
prerender_data_(prerender_data),
url_(url),
should_replace_current_entry_(should_replace_current_entry),
target_route_id_, swap_successful_);
}
+WebContents* PrerenderManager::PendingSwap::target_contents() const {
+ return web_contents();
+}
+
void PrerenderManager::PendingSwap::BeginSwap() {
+ if (g_hang_session_storage_merges_for_testing)
+ return;
+
SessionStorageNamespace* target_namespace =
- target_contents_->GetController().GetDefaultSessionStorageNamespace();
+ target_contents()->GetController().GetDefaultSessionStorageNamespace();
SessionStorageNamespace* prerender_namespace =
prerender_data_->contents()->GetSessionStorageNamespace();
RecordEvent(PRERENDER_EVENT_MERGE_RESULT_SWAPPING_IN);
- WebContents* new_web_contents = NULL;
- // Ensure that the prerendering hasn't been destroyed in the meantime.
- if (prerender_data_->contents()->final_status() == FINAL_STATUS_MAX) {
- // Note that SwapInternal, on success, will delete |prerender_data_| and
- // |this|. Pass in a new GURL object rather than a reference to |url_|.
- //
- // TODO(davidben): See about deleting PrerenderData asynchronously so this
- // behavior is more reasonable.
-
- new_web_contents = manager_->SwapInternal(
- GURL(url_), target_contents_, prerender_data_,
+ // Note that SwapInternal will, on success, delete |prerender_data_| and
+ // |this|. It will also delete |this| in some failure cases. Pass in a new
+ // GURL object rather than a reference to |url_|. Also hold on to |manager_|
+ // and |prerender_data_|.
+ //
+ // TODO(davidben): Can we make this less fragile?
+ PrerenderManager* manager = manager_;
+ PrerenderData* prerender_data = prerender_data_;
+ WebContents* new_web_contents = manager_->SwapInternal(
+ GURL(url_), target_contents(), prerender_data_,
should_replace_current_entry_);
- }
-
if (!new_web_contents) {
- RecordEvent(PRERENDER_EVENT_MERGE_RESULT_SWAPIN_FAILED);
- prerender_data_->ClearPendingSwap();
+ manager->RecordEvent(prerender_data->contents(),
+ PRERENDER_EVENT_MERGE_RESULT_SWAPIN_FAILED);
+ // Depending on whether SwapInternal called Destroy() or simply failed to
+ // swap, |this| may or may not be deleted. Either way, if the swap failed,
+ // |prerender_data| is deleted asynchronously, so this call is a no-op if
+ // |this| is already gone.
+ prerender_data->ClearPendingSwap();
}
}
prerender_contents_factory_.reset(prerender_contents_factory);
}
-
-void PrerenderManager::StartPendingPrerenders(
- const int process_id,
- ScopedVector<PrerenderContents::PendingPrerenderInfo>* pending_prerenders,
- content::SessionStorageNamespace* session_storage_namespace) {
- for (ScopedVector<PrerenderContents::PendingPrerenderInfo>::iterator
- it = pending_prerenders->begin();
- it != pending_prerenders->end(); ++it) {
- PrerenderContents::PendingPrerenderInfo* info = *it;
- PrerenderHandle* existing_prerender_handle =
- info->weak_prerender_handle.get();
- if (!existing_prerender_handle)
- continue;
-
- DCHECK(!existing_prerender_handle->IsPrerendering());
- DCHECK(process_id == -1 || session_storage_namespace);
-
- scoped_ptr<PrerenderHandle> new_prerender_handle(AddPrerender(
- info->origin, process_id,
- info->url, info->referrer, info->size,
- session_storage_namespace));
- if (new_prerender_handle) {
- // AddPrerender has returned a new prerender handle to us. We want to make
- // |existing_prerender_handle| active, so move the underlying
- // PrerenderData to our new handle.
- existing_prerender_handle->AdoptPrerenderDataFrom(
- new_prerender_handle.get());
- continue;
- }
- }
-}
-
void PrerenderManager::SourceNavigatedAway(PrerenderData* prerender_data) {
// The expiry time of our prerender data will likely change because of
// this navigation. This requires a resort of active_prerenders_.
}
PrerenderManager::PrerenderData*
-PrerenderManager::FindPrerenderDataForChildAndRoute(
- const int child_id, const int route_id) {
+PrerenderManager::FindPrerenderDataForTargetContents(
+ WebContents* target_contents) {
for (ScopedVector<PrerenderData>::iterator it = active_prerenders_.begin();
it != active_prerenders_.end(); ++it) {
- PrerenderContents* prerender_contents = (*it)->contents();
-
- int contents_child_id;
- if (!prerender_contents->GetChildId(&contents_child_id))
- continue;
- int contents_route_id;
- if (!prerender_contents->GetRouteId(&contents_route_id))
- continue;
-
- if (contents_child_id == child_id && contents_route_id == route_id)
+ if ((*it)->pending_swap() &&
+ (*it)->pending_swap()->target_contents() == target_contents)
return *it;
}
return NULL;
return true;
}
-PrerenderManager* FindPrerenderManagerUsingRenderProcessId(
- int render_process_id) {
- DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
- content::RenderProcessHost* render_process_host =
- content::RenderProcessHost::FromID(render_process_id);
- // Each render process is guaranteed to only hold RenderViews owned by the
- // same BrowserContext. This is enforced by
- // RenderProcessHost::GetExistingProcessHost.
- if (!render_process_host || !render_process_host->GetBrowserContext())
- return NULL;
- Profile* profile = Profile::FromBrowserContext(
- render_process_host->GetBrowserContext());
- if (!profile)
- return NULL;
- return PrerenderManagerFactory::GetInstance()->GetForProfile(profile);
-}
-
void PrerenderManager::Observe(int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) {
histograms_->RecordPrerenderPageVisitedStatus(origin, experiment_id, success);
}
+// static
+void PrerenderManager::HangSessionStorageMergesForTesting() {
+ g_hang_session_storage_merges_for_testing = true;
+}
+
+void PrerenderManager::RecordNetworkBytes(bool used, int64 prerender_bytes) {
+ if (!ActuallyPrerendering())
+ return;
+ int64 recent_profile_bytes =
+ profile_network_bytes_ - last_recorded_profile_network_bytes_;
+ last_recorded_profile_network_bytes_ = profile_network_bytes_;
+ DCHECK_GE(recent_profile_bytes, 0);
+ histograms_->RecordNetworkBytes(used, prerender_bytes, recent_profile_bytes);
+}
+
+void PrerenderManager::AddProfileNetworkBytesIfEnabled(int64 bytes) {
+ DCHECK_GE(bytes, 0);
+ if (IsEnabled() && ActuallyPrerendering())
+ profile_network_bytes_ += bytes;
+}
+
} // namespace prerender