// 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/metrics/histogram.h" #include "base/string_number_conversions.h" #include "base/time.h" #include "chrome/browser/prerender/prerender_histograms.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/ui/tab_contents/core_tab_helper.h" #include "chrome/browser/ui/tab_contents/tab_contents.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 "skia/ext/platform_canvas.h" #include "third_party/skia/include/core/SkBitmap.h" #include "ui/gfx/rect.h" using content::WebContents; namespace prerender { // 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) : 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; } skia::PlatformCanvas* temp_canvas = new skia::PlatformCanvas; web_contents->GetRenderViewHost()->CopyFromBackingStore( gfx::Rect(), gfx::Size(), temp_canvas, base::Bind(&PrerenderTabHelper::PixelStats::HandleBitmapResult, weak_factory_.GetWeakPtr(), bitmap_type, web_contents, base::Owned(temp_canvas))); } private: void HandleBitmapResult(BitmapType bitmap_type, WebContents* web_contents, skia::PlatformCanvas* temp_canvas, bool succeeded) { scoped_ptr bitmap; if (succeeded) { const SkBitmap& canvas_bitmap = skia::GetTopDevice(*temp_canvas)->accessBitmap(false); 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->histograms()->RecordFractionPixelsFinalAtSwapin( 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_; }; PrerenderTabHelper::PrerenderTabHelper(TabContents* tab) : content::WebContentsObserver(tab->web_contents()), tab_(tab) { } PrerenderTabHelper::~PrerenderTabHelper() { } void PrerenderTabHelper::ProvisionalChangeToMainFrameUrl( const GURL& url, const GURL& opener_url, content::RenderViewHost* render_view_host) { url_ = url; PrerenderManager* prerender_manager = MaybeGetPrerenderManager(); if (!prerender_manager) return; if (prerender_manager->IsWebContentsPrerendering(web_contents())) return; prerender_manager->MarkWebContentsAsNotPrerendered(web_contents()); } void PrerenderTabHelper::DidCommitProvisionalLoadForFrame( int64 frame_id, bool is_main_frame, const GURL& validated_url, content::PageTransition transition_type, content::RenderViewHost* render_view_host) { if (!is_main_frame) return; url_ = validated_url; PrerenderManager* prerender_manager = MaybeGetPrerenderManager(); if (!prerender_manager) return; if (prerender_manager->IsWebContentsPrerendering(web_contents())) return; prerender_manager->RecordNavigation(validated_url); } void PrerenderTabHelper::DidStopLoading() { // 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, bool is_main_frame, const GURL& validated_url, bool is_error_page, 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(); } } 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()); } bool PrerenderTabHelper::IsPrerendered() { PrerenderManager* prerender_manager = MaybeGetPrerenderManager(); if (!prerender_manager) return false; return prerender_manager->IsWebContentsPrerendered(web_contents()); } 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->histograms()->RecordFractionPixelsFinalAtSwapin(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()); } } } // namespace prerender