// Copyright (c) 2012 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_tab_helper.h" #include "base/bind.h" #include "base/metrics/histogram.h" #include "base/strings/string_number_conversions.h" #include "base/time/time.h" #include "chrome/browser/password_manager/password_manager.h" #include "chrome/browser/predictors/logged_in_predictor_table.h" #include "chrome/browser/prerender/prerender_histograms.h" #include "chrome/browser/prerender/prerender_local_predictor.h" #include "chrome/browser/prerender/prerender_manager.h" #include "chrome/browser/prerender/prerender_manager_factory.h" #include "chrome/browser/profiles/profile.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/navigation_details.h" #include "content/public/browser/navigation_entry.h" #include "content/public/browser/render_view_host.h" #include "content/public/browser/web_contents.h" #include "content/public/browser/web_contents_view.h" #include "content/public/common/frame_navigate_params.h" #include "skia/ext/platform_canvas.h" #include "third_party/skia/include/core/SkBitmap.h" #include "ui/gfx/rect.h" using content::WebContents; DEFINE_WEB_CONTENTS_USER_DATA_KEY(prerender::PrerenderTabHelper); namespace prerender { namespace { void ReportTabHelperURLSeenToLocalPredictor( PrerenderManager* prerender_manager, const GURL& url, WebContents* web_contents) { if (!prerender_manager) return; PrerenderLocalPredictor* local_predictor = prerender_manager->local_predictor(); if (!local_predictor) return; local_predictor->OnTabHelperURLSeen(url, web_contents); } } // namespace // Helper class to compute pixel-based stats on the paint progress // between when a prerendered page is swapped in and when the onload event // fires. class PrerenderTabHelper::PixelStats { public: explicit PixelStats(PrerenderTabHelper* tab_helper) : bitmap_web_contents_(NULL), weak_factory_(this), tab_helper_(tab_helper) { } // Reasons why we need to fetch bitmaps: either a prerender was swapped in, // or a prerendered page has finished loading. enum BitmapType { BITMAP_SWAP_IN, BITMAP_ON_LOAD }; void GetBitmap(BitmapType bitmap_type, WebContents* web_contents) { if (bitmap_type == BITMAP_SWAP_IN) { bitmap_.reset(); bitmap_web_contents_ = web_contents; } if (bitmap_type == BITMAP_ON_LOAD && bitmap_web_contents_ != web_contents) return; if (!web_contents || !web_contents->GetView() || !web_contents->GetRenderViewHost()) { return; } web_contents->GetRenderViewHost()->CopyFromBackingStore( gfx::Rect(), gfx::Size(), base::Bind(&PrerenderTabHelper::PixelStats::HandleBitmapResult, weak_factory_.GetWeakPtr(), bitmap_type, web_contents)); } private: void HandleBitmapResult(BitmapType bitmap_type, WebContents* web_contents, bool succeeded, const SkBitmap& canvas_bitmap) { scoped_ptr bitmap; if (succeeded) { // TODO(nick): This copy may now be unnecessary. bitmap.reset(new SkBitmap()); canvas_bitmap.copyTo(bitmap.get(), SkBitmap::kARGB_8888_Config); } if (bitmap_web_contents_ != web_contents) return; if (bitmap_type == BITMAP_SWAP_IN) bitmap_.swap(bitmap); if (bitmap_type == BITMAP_ON_LOAD) { PrerenderManager* prerender_manager = tab_helper_->MaybeGetPrerenderManager(); if (prerender_manager) { prerender_manager->RecordFractionPixelsFinalAtSwapin( web_contents, CompareBitmaps(bitmap_.get(), bitmap.get())); } bitmap_.reset(); bitmap_web_contents_ = NULL; } } // Helper comparing two bitmaps of identical size. // Returns a value < 0.0 if there is an error, and otherwise, a double in // [0, 1] indicating the fraction of pixels that are the same. double CompareBitmaps(SkBitmap* bitmap1, SkBitmap* bitmap2) { if (!bitmap1 || !bitmap2) { return -2.0; } if (bitmap1->width() != bitmap2->width() || bitmap1->height() != bitmap2->height()) { return -1.0; } int pixels = bitmap1->width() * bitmap1->height(); int same_pixels = 0; for (int y = 0; y < bitmap1->height(); ++y) { for (int x = 0; x < bitmap1->width(); ++x) { if (bitmap1->getColor(x, y) == bitmap2->getColor(x, y)) same_pixels++; } } return static_cast(same_pixels) / static_cast(pixels); } // Bitmap of what the last swapped in prerendered tab looked like at swapin, // and the WebContents that it was swapped into. scoped_ptr bitmap_; WebContents* bitmap_web_contents_; base::WeakPtrFactory weak_factory_; PrerenderTabHelper* tab_helper_; }; // static void PrerenderTabHelper::CreateForWebContentsWithPasswordManager( content::WebContents* web_contents, PasswordManager* password_manager) { if (!FromWebContents(web_contents)) { web_contents->SetUserData(UserDataKey(), new PrerenderTabHelper(web_contents, password_manager)); } } PrerenderTabHelper::PrerenderTabHelper(content::WebContents* web_contents, PasswordManager* password_manager) : content::WebContentsObserver(web_contents), weak_factory_(this) { password_manager->AddSubmissionCallback( base::Bind(&PrerenderTabHelper::PasswordSubmitted, weak_factory_.GetWeakPtr())); } PrerenderTabHelper::~PrerenderTabHelper() { } void PrerenderTabHelper::ProvisionalChangeToMainFrameUrl( const GURL& url, content::RenderViewHost* render_view_host) { url_ = url; RecordEvent(EVENT_MAINFRAME_CHANGE); RecordEventIfLoggedInURL(EVENT_MAINFRAME_CHANGE_DOMAIN_LOGGED_IN, url); PrerenderManager* prerender_manager = MaybeGetPrerenderManager(); if (!prerender_manager) return; if (prerender_manager->IsWebContentsPrerendering(web_contents(), NULL)) return; prerender_manager->MarkWebContentsAsNotPrerendered(web_contents()); ReportTabHelperURLSeenToLocalPredictor(prerender_manager, url, web_contents()); } void PrerenderTabHelper::DidCommitProvisionalLoadForFrame( int64 frame_id, const string16& frame_unique_name, bool is_main_frame, const GURL& validated_url, content::PageTransition transition_type, content::RenderViewHost* render_view_host) { if (!is_main_frame) return; RecordEvent(EVENT_MAINFRAME_COMMIT); RecordEventIfLoggedInURL(EVENT_MAINFRAME_COMMIT_DOMAIN_LOGGED_IN, validated_url); url_ = validated_url; PrerenderManager* prerender_manager = MaybeGetPrerenderManager(); if (!prerender_manager) return; if (prerender_manager->IsWebContentsPrerendering(web_contents(), NULL)) return; prerender_manager->RecordNavigation(validated_url); ReportTabHelperURLSeenToLocalPredictor(prerender_manager, validated_url, web_contents()); } void PrerenderTabHelper::DidStopLoading( content::RenderViewHost* render_view_host) { // Compute the PPLT metric and report it in a histogram, if needed. // We include pages that are still prerendering and have just finished // loading -- PrerenderManager will sort this out and handle it correctly // (putting those times into a separate histogram). if (!pplt_load_start_.is_null()) { double fraction_elapsed_at_swapin = -1.0; base::TimeTicks now = base::TimeTicks::Now(); if (!actual_load_start_.is_null()) { double plt = (now - actual_load_start_).InMillisecondsF(); if (plt > 0.0) { fraction_elapsed_at_swapin = 1.0 - (now - pplt_load_start_).InMillisecondsF() / plt; } else { fraction_elapsed_at_swapin = 1.0; } DCHECK_GE(fraction_elapsed_at_swapin, 0.0); DCHECK_LE(fraction_elapsed_at_swapin, 1.0); } PrerenderManager::RecordPerceivedPageLoadTime( now - pplt_load_start_, fraction_elapsed_at_swapin, web_contents(), url_); if (IsPrerendered() && pixel_stats_.get()) pixel_stats_->GetBitmap(PixelStats::BITMAP_ON_LOAD, web_contents()); } // Reset the PPLT metric. pplt_load_start_ = base::TimeTicks(); actual_load_start_ = base::TimeTicks(); } void PrerenderTabHelper::DidStartProvisionalLoadForFrame( int64 frame_id, int64 parent_frame_id, bool is_main_frame, const GURL& validated_url, bool is_error_page, bool is_iframe_srcdoc, content::RenderViewHost* render_view_host) { if (is_main_frame) { // Record the beginning of a new PPLT navigation. pplt_load_start_ = base::TimeTicks::Now(); actual_load_start_ = base::TimeTicks(); } } void PrerenderTabHelper::PasswordSubmitted(const autofill::PasswordForm& form) { PrerenderManager* prerender_manager = MaybeGetPrerenderManager(); if (prerender_manager) { prerender_manager->RecordLikelyLoginOnURL(form.origin); RecordEvent(EVENT_LOGIN_ACTION_ADDED); if (form.password_value.empty()) RecordEvent(EVENT_LOGIN_ACTION_ADDED_PW_EMPTY); } } PrerenderManager* PrerenderTabHelper::MaybeGetPrerenderManager() const { return PrerenderManagerFactory::GetForProfile( Profile::FromBrowserContext(web_contents()->GetBrowserContext())); } bool PrerenderTabHelper::IsPrerendering() { PrerenderManager* prerender_manager = MaybeGetPrerenderManager(); if (!prerender_manager) return false; return prerender_manager->IsWebContentsPrerendering(web_contents(), NULL); } bool PrerenderTabHelper::IsPrerendered() { PrerenderManager* prerender_manager = MaybeGetPrerenderManager(); if (!prerender_manager) return false; return prerender_manager->IsWebContentsPrerendered(web_contents(), NULL); } void PrerenderTabHelper::PrerenderSwappedIn() { // Ensure we are not prerendering any more. DCHECK(!IsPrerendering()); if (pplt_load_start_.is_null()) { // If we have already finished loading, report a 0 PPLT. PrerenderManager::RecordPerceivedPageLoadTime(base::TimeDelta(), 1.0, web_contents(), url_); PrerenderManager* prerender_manager = MaybeGetPrerenderManager(); if (prerender_manager) prerender_manager->RecordFractionPixelsFinalAtSwapin(web_contents(), 1.0); } else { // If we have not finished loading yet, record the actual load start, and // rebase the start time to now. actual_load_start_ = pplt_load_start_; pplt_load_start_ = base::TimeTicks::Now(); if (pixel_stats_.get()) pixel_stats_->GetBitmap(PixelStats::BITMAP_SWAP_IN, web_contents()); } } void PrerenderTabHelper::RecordEvent(PrerenderTabHelper::Event event) const { UMA_HISTOGRAM_ENUMERATION("Prerender.TabHelperEvent", event, PrerenderTabHelper::EVENT_MAX_VALUE); } void PrerenderTabHelper::RecordEventIfLoggedInURL( PrerenderTabHelper::Event event, const GURL& url) { PrerenderManager* prerender_manager = MaybeGetPrerenderManager(); if (!prerender_manager) return; scoped_ptr is_present(new bool); scoped_ptr lookup_succeeded(new bool); bool* is_present_ptr = is_present.get(); bool* lookup_succeeded_ptr = lookup_succeeded.get(); prerender_manager->CheckIfLikelyLoggedInOnURL( url, is_present_ptr, lookup_succeeded_ptr, base::Bind(&PrerenderTabHelper::RecordEventIfLoggedInURLResult, weak_factory_.GetWeakPtr(), event, base::Passed(&is_present), base::Passed(&lookup_succeeded))); } void PrerenderTabHelper::RecordEventIfLoggedInURLResult( PrerenderTabHelper::Event event, scoped_ptr is_present, scoped_ptr lookup_succeeded) { if (*lookup_succeeded && *is_present) RecordEvent(event); } } // namespace prerender