1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "chrome/browser/prerender/prerender_tab_helper.h" 6 7 #include "base/metrics/histogram.h" 8 #include "base/strings/string_number_conversions.h" 9 #include "base/time/time.h" 10 #include "chrome/browser/predictors/logged_in_predictor_table.h" 11 #include "chrome/browser/prerender/prerender_histograms.h" 12 #include "chrome/browser/prerender/prerender_local_predictor.h" 13 #include "chrome/browser/prerender/prerender_manager.h" 14 #include "chrome/browser/prerender/prerender_manager_factory.h" 15 #include "chrome/browser/profiles/profile.h" 16 #include "content/public/browser/browser_thread.h" 17 #include "content/public/browser/navigation_details.h" 18 #include "content/public/browser/navigation_entry.h" 19 #include "content/public/browser/render_view_host.h" 20 #include "content/public/browser/web_contents.h" 21 #include "content/public/browser/web_contents_view.h" 22 #include "content/public/common/frame_navigate_params.h" 23 #include "skia/ext/platform_canvas.h" 24 #include "third_party/skia/include/core/SkBitmap.h" 25 #include "ui/gfx/rect.h" 26 27 using content::WebContents; 28 29 DEFINE_WEB_CONTENTS_USER_DATA_KEY(prerender::PrerenderTabHelper); 30 31 namespace prerender { 32 33 namespace { 34 35 void ReportTabHelperURLSeenToLocalPredictor( 36 PrerenderManager* prerender_manager, 37 const GURL& url, 38 WebContents* web_contents) { 39 if (!prerender_manager) 40 return; 41 PrerenderLocalPredictor* local_predictor = 42 prerender_manager->local_predictor(); 43 if (!local_predictor) 44 return; 45 local_predictor->OnTabHelperURLSeen(url, web_contents); 46 } 47 48 } // namespace 49 50 // Helper class to compute pixel-based stats on the paint progress 51 // between when a prerendered page is swapped in and when the onload event 52 // fires. 53 class PrerenderTabHelper::PixelStats { 54 public: 55 explicit PixelStats(PrerenderTabHelper* tab_helper) : 56 bitmap_web_contents_(NULL), 57 weak_factory_(this), 58 tab_helper_(tab_helper) { 59 } 60 61 // Reasons why we need to fetch bitmaps: either a prerender was swapped in, 62 // or a prerendered page has finished loading. 63 enum BitmapType { 64 BITMAP_SWAP_IN, 65 BITMAP_ON_LOAD 66 }; 67 68 void GetBitmap(BitmapType bitmap_type, WebContents* web_contents) { 69 if (bitmap_type == BITMAP_SWAP_IN) { 70 bitmap_.reset(); 71 bitmap_web_contents_ = web_contents; 72 } 73 74 if (bitmap_type == BITMAP_ON_LOAD && bitmap_web_contents_ != web_contents) 75 return; 76 77 if (!web_contents || !web_contents->GetView() || 78 !web_contents->GetRenderViewHost()) { 79 return; 80 } 81 82 web_contents->GetRenderViewHost()->CopyFromBackingStore( 83 gfx::Rect(), 84 gfx::Size(), 85 base::Bind(&PrerenderTabHelper::PixelStats::HandleBitmapResult, 86 weak_factory_.GetWeakPtr(), 87 bitmap_type, 88 web_contents)); 89 } 90 91 private: 92 void HandleBitmapResult(BitmapType bitmap_type, 93 WebContents* web_contents, 94 bool succeeded, 95 const SkBitmap& canvas_bitmap) { 96 scoped_ptr<SkBitmap> bitmap; 97 if (succeeded) { 98 // TODO(nick): This copy may now be unnecessary. 99 bitmap.reset(new SkBitmap()); 100 canvas_bitmap.copyTo(bitmap.get(), SkBitmap::kARGB_8888_Config); 101 } 102 103 if (bitmap_web_contents_ != web_contents) 104 return; 105 106 if (bitmap_type == BITMAP_SWAP_IN) 107 bitmap_.swap(bitmap); 108 109 if (bitmap_type == BITMAP_ON_LOAD) { 110 PrerenderManager* prerender_manager = 111 tab_helper_->MaybeGetPrerenderManager(); 112 if (prerender_manager) { 113 prerender_manager->RecordFractionPixelsFinalAtSwapin( 114 web_contents, CompareBitmaps(bitmap_.get(), bitmap.get())); 115 } 116 bitmap_.reset(); 117 bitmap_web_contents_ = NULL; 118 } 119 } 120 121 // Helper comparing two bitmaps of identical size. 122 // Returns a value < 0.0 if there is an error, and otherwise, a double in 123 // [0, 1] indicating the fraction of pixels that are the same. 124 double CompareBitmaps(SkBitmap* bitmap1, SkBitmap* bitmap2) { 125 if (!bitmap1 || !bitmap2) { 126 return -2.0; 127 } 128 if (bitmap1->width() != bitmap2->width() || 129 bitmap1->height() != bitmap2->height()) { 130 return -1.0; 131 } 132 int pixels = bitmap1->width() * bitmap1->height(); 133 int same_pixels = 0; 134 for (int y = 0; y < bitmap1->height(); ++y) { 135 for (int x = 0; x < bitmap1->width(); ++x) { 136 if (bitmap1->getColor(x, y) == bitmap2->getColor(x, y)) 137 same_pixels++; 138 } 139 } 140 return static_cast<double>(same_pixels) / static_cast<double>(pixels); 141 } 142 143 // Bitmap of what the last swapped in prerendered tab looked like at swapin, 144 // and the WebContents that it was swapped into. 145 scoped_ptr<SkBitmap> bitmap_; 146 WebContents* bitmap_web_contents_; 147 148 base::WeakPtrFactory<PixelStats> weak_factory_; 149 150 PrerenderTabHelper* tab_helper_; 151 }; 152 153 PrerenderTabHelper::PrerenderTabHelper(content::WebContents* web_contents) 154 : content::WebContentsObserver(web_contents), 155 weak_factory_(this) { 156 } 157 158 PrerenderTabHelper::~PrerenderTabHelper() { 159 } 160 161 void PrerenderTabHelper::ProvisionalChangeToMainFrameUrl( 162 const GURL& url, 163 content::RenderViewHost* render_view_host) { 164 url_ = url; 165 RecordEvent(EVENT_MAINFRAME_CHANGE); 166 RecordEventIfLoggedInURL(EVENT_MAINFRAME_CHANGE_DOMAIN_LOGGED_IN, url); 167 PrerenderManager* prerender_manager = MaybeGetPrerenderManager(); 168 if (!prerender_manager) 169 return; 170 if (prerender_manager->IsWebContentsPrerendering(web_contents(), NULL)) 171 return; 172 prerender_manager->MarkWebContentsAsNotPrerendered(web_contents()); 173 ReportTabHelperURLSeenToLocalPredictor(prerender_manager, url, 174 web_contents()); 175 } 176 177 void PrerenderTabHelper::DidCommitProvisionalLoadForFrame( 178 int64 frame_id, 179 bool is_main_frame, 180 const GURL& validated_url, 181 content::PageTransition transition_type, 182 content::RenderViewHost* render_view_host) { 183 if (!is_main_frame) 184 return; 185 RecordEvent(EVENT_MAINFRAME_COMMIT); 186 RecordEventIfLoggedInURL(EVENT_MAINFRAME_COMMIT_DOMAIN_LOGGED_IN, 187 validated_url); 188 url_ = validated_url; 189 PrerenderManager* prerender_manager = MaybeGetPrerenderManager(); 190 if (!prerender_manager) 191 return; 192 if (prerender_manager->IsWebContentsPrerendering(web_contents(), NULL)) 193 return; 194 prerender_manager->RecordNavigation(validated_url); 195 ReportTabHelperURLSeenToLocalPredictor(prerender_manager, validated_url, 196 web_contents()); 197 } 198 199 void PrerenderTabHelper::DidStopLoading( 200 content::RenderViewHost* render_view_host) { 201 // Compute the PPLT metric and report it in a histogram, if needed. 202 // We include pages that are still prerendering and have just finished 203 // loading -- PrerenderManager will sort this out and handle it correctly 204 // (putting those times into a separate histogram). 205 if (!pplt_load_start_.is_null()) { 206 double fraction_elapsed_at_swapin = -1.0; 207 base::TimeTicks now = base::TimeTicks::Now(); 208 if (!actual_load_start_.is_null()) { 209 double plt = (now - actual_load_start_).InMillisecondsF(); 210 if (plt > 0.0) { 211 fraction_elapsed_at_swapin = 1.0 - 212 (now - pplt_load_start_).InMillisecondsF() / plt; 213 } else { 214 fraction_elapsed_at_swapin = 1.0; 215 } 216 DCHECK_GE(fraction_elapsed_at_swapin, 0.0); 217 DCHECK_LE(fraction_elapsed_at_swapin, 1.0); 218 } 219 PrerenderManager::RecordPerceivedPageLoadTime( 220 now - pplt_load_start_, fraction_elapsed_at_swapin, web_contents(), 221 url_); 222 if (IsPrerendered() && pixel_stats_.get()) 223 pixel_stats_->GetBitmap(PixelStats::BITMAP_ON_LOAD, web_contents()); 224 } 225 226 // Reset the PPLT metric. 227 pplt_load_start_ = base::TimeTicks(); 228 actual_load_start_ = base::TimeTicks(); 229 } 230 231 void PrerenderTabHelper::DidStartProvisionalLoadForFrame( 232 int64 frame_id, 233 int64 parent_frame_id, 234 bool is_main_frame, 235 const GURL& validated_url, 236 bool is_error_page, 237 bool is_iframe_srcdoc, 238 content::RenderViewHost* render_view_host) { 239 if (is_main_frame) { 240 // Record the beginning of a new PPLT navigation. 241 pplt_load_start_ = base::TimeTicks::Now(); 242 actual_load_start_ = base::TimeTicks(); 243 } 244 } 245 246 void PrerenderTabHelper::DidNavigateAnyFrame( 247 const content::LoadCommittedDetails& details, 248 const content::FrameNavigateParams& params) { 249 PrerenderManager* prerender_manager = MaybeGetPrerenderManager(); 250 if (params.password_form.origin.is_valid() && prerender_manager) { 251 prerender_manager->RecordLikelyLoginOnURL(params.url); 252 RecordEvent(EVENT_LOGIN_ACTION_ADDED); 253 if (details.is_main_frame) { 254 RecordEvent(EVENT_LOGIN_ACTION_ADDED_MAINFRAME); 255 if (params.password_form.password_value.empty()) 256 RecordEvent(EVENT_LOGIN_ACTION_ADDED_MAINFRAME_PW_EMPTY); 257 } else { 258 RecordEvent(EVENT_LOGIN_ACTION_ADDED_SUBFRAME); 259 if (params.password_form.password_value.empty()) 260 RecordEvent(EVENT_LOGIN_ACTION_ADDED_SUBFRAME_PW_EMPTY); 261 } 262 } 263 } 264 265 PrerenderManager* PrerenderTabHelper::MaybeGetPrerenderManager() const { 266 return PrerenderManagerFactory::GetForProfile( 267 Profile::FromBrowserContext(web_contents()->GetBrowserContext())); 268 } 269 270 bool PrerenderTabHelper::IsPrerendering() { 271 PrerenderManager* prerender_manager = MaybeGetPrerenderManager(); 272 if (!prerender_manager) 273 return false; 274 return prerender_manager->IsWebContentsPrerendering(web_contents(), NULL); 275 } 276 277 bool PrerenderTabHelper::IsPrerendered() { 278 PrerenderManager* prerender_manager = MaybeGetPrerenderManager(); 279 if (!prerender_manager) 280 return false; 281 return prerender_manager->IsWebContentsPrerendered(web_contents(), NULL); 282 } 283 284 void PrerenderTabHelper::PrerenderSwappedIn() { 285 // Ensure we are not prerendering any more. 286 DCHECK(!IsPrerendering()); 287 if (pplt_load_start_.is_null()) { 288 // If we have already finished loading, report a 0 PPLT. 289 PrerenderManager::RecordPerceivedPageLoadTime(base::TimeDelta(), 1.0, 290 web_contents(), url_); 291 PrerenderManager* prerender_manager = MaybeGetPrerenderManager(); 292 if (prerender_manager) 293 prerender_manager->RecordFractionPixelsFinalAtSwapin(web_contents(), 1.0); 294 } else { 295 // If we have not finished loading yet, record the actual load start, and 296 // rebase the start time to now. 297 actual_load_start_ = pplt_load_start_; 298 pplt_load_start_ = base::TimeTicks::Now(); 299 if (pixel_stats_.get()) 300 pixel_stats_->GetBitmap(PixelStats::BITMAP_SWAP_IN, web_contents()); 301 } 302 } 303 304 void PrerenderTabHelper::RecordEvent(PrerenderTabHelper::Event event) const { 305 UMA_HISTOGRAM_ENUMERATION("Prerender.TabHelperEvent", 306 event, PrerenderTabHelper::EVENT_MAX_VALUE); 307 } 308 309 void PrerenderTabHelper::RecordEventIfLoggedInURL( 310 PrerenderTabHelper::Event event, const GURL& url) { 311 PrerenderManager* prerender_manager = MaybeGetPrerenderManager(); 312 if (!prerender_manager) 313 return; 314 scoped_ptr<bool> is_present(new bool); 315 scoped_ptr<bool> lookup_succeeded(new bool); 316 bool* is_present_ptr = is_present.get(); 317 bool* lookup_succeeded_ptr = lookup_succeeded.get(); 318 prerender_manager->CheckIfLikelyLoggedInOnURL( 319 url, 320 is_present_ptr, 321 lookup_succeeded_ptr, 322 base::Bind(&PrerenderTabHelper::RecordEventIfLoggedInURLResult, 323 weak_factory_.GetWeakPtr(), 324 event, 325 base::Passed(&is_present), 326 base::Passed(&lookup_succeeded))); 327 } 328 329 void PrerenderTabHelper::RecordEventIfLoggedInURLResult( 330 PrerenderTabHelper::Event event, 331 scoped_ptr<bool> is_present, 332 scoped_ptr<bool> lookup_succeeded) { 333 if (*lookup_succeeded && *is_present) 334 RecordEvent(event); 335 } 336 337 } // namespace prerender 338