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