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/time/time.h"
10#include "chrome/browser/prerender/prerender_histograms.h"
11#include "chrome/browser/prerender/prerender_local_predictor.h"
12#include "chrome/browser/prerender/prerender_manager.h"
13#include "chrome/browser/prerender/prerender_manager_factory.h"
14#include "chrome/browser/profiles/profile.h"
15#include "components/password_manager/core/browser/password_manager.h"
16#include "content/public/browser/navigation_details.h"
17#include "content/public/browser/navigation_entry.h"
18#include "content/public/browser/render_frame_host.h"
19#include "content/public/browser/resource_request_details.h"
20#include "content/public/browser/web_contents.h"
21#include "content/public/common/frame_navigate_params.h"
22
23using content::WebContents;
24
25DEFINE_WEB_CONTENTS_USER_DATA_KEY(prerender::PrerenderTabHelper);
26
27namespace prerender {
28
29namespace {
30
31void ReportTabHelperURLSeenToLocalPredictor(
32    PrerenderManager* prerender_manager,
33    const GURL& url,
34    WebContents* web_contents) {
35  if (!prerender_manager)
36    return;
37  PrerenderLocalPredictor* local_predictor =
38      prerender_manager->local_predictor();
39  if (!local_predictor)
40    return;
41  local_predictor->OnTabHelperURLSeen(url, web_contents);
42}
43
44}  // namespace
45
46// static
47void PrerenderTabHelper::CreateForWebContentsWithPasswordManager(
48    content::WebContents* web_contents,
49    password_manager::PasswordManager* password_manager) {
50  if (!FromWebContents(web_contents)) {
51    web_contents->SetUserData(UserDataKey(),
52                              new PrerenderTabHelper(web_contents,
53                                                     password_manager));
54  }
55}
56
57PrerenderTabHelper::PrerenderTabHelper(
58    content::WebContents* web_contents,
59    password_manager::PasswordManager* password_manager)
60    : content::WebContentsObserver(web_contents),
61      origin_(ORIGIN_NONE),
62      next_load_is_control_prerender_(false),
63      next_load_origin_(ORIGIN_NONE),
64      weak_factory_(this) {
65  if (password_manager) {
66    // May be NULL in testing.
67    password_manager->AddSubmissionCallback(
68        base::Bind(&PrerenderTabHelper::PasswordSubmitted,
69                   weak_factory_.GetWeakPtr()));
70  }
71
72  // Determine if this is a prerender.
73  PrerenderManager* prerender_manager = MaybeGetPrerenderManager();
74  if (prerender_manager &&
75      prerender_manager->IsWebContentsPrerendering(web_contents, &origin_)) {
76    navigation_type_ = NAVIGATION_TYPE_PRERENDERED;
77  } else {
78    navigation_type_ = NAVIGATION_TYPE_NORMAL;
79  }
80}
81
82PrerenderTabHelper::~PrerenderTabHelper() {
83}
84
85void PrerenderTabHelper::DidGetRedirectForResourceRequest(
86    content::RenderViewHost* render_view_host,
87    const content::ResourceRedirectDetails& details) {
88  if (details.resource_type != content::RESOURCE_TYPE_MAIN_FRAME)
89    return;
90
91  MainFrameUrlDidChange(details.new_url);
92}
93
94void PrerenderTabHelper::DidCommitProvisionalLoadForFrame(
95    content::RenderFrameHost* render_frame_host,
96    const GURL& validated_url,
97    ui::PageTransition transition_type) {
98  if (render_frame_host->GetParent())
99    return;
100  RecordEvent(EVENT_MAINFRAME_COMMIT);
101  RecordEventIfLoggedInURL(EVENT_MAINFRAME_COMMIT_DOMAIN_LOGGED_IN,
102                           validated_url);
103  url_ = validated_url;
104  PrerenderManager* prerender_manager = MaybeGetPrerenderManager();
105  if (!prerender_manager)
106    return;
107  if (prerender_manager->IsWebContentsPrerendering(web_contents(), NULL))
108    return;
109  prerender_manager->RecordNavigation(validated_url);
110  ReportTabHelperURLSeenToLocalPredictor(prerender_manager, validated_url,
111                                         web_contents());
112}
113
114void PrerenderTabHelper::DidStopLoading(
115    content::RenderViewHost* render_view_host) {
116  // Compute the PPLT metric and report it in a histogram, if needed. If the
117  // page is still prerendering, record the not swapped in page load time
118  // instead.
119  if (!pplt_load_start_.is_null()) {
120    base::TimeTicks now = base::TimeTicks::Now();
121    if (IsPrerendering()) {
122      PrerenderManager* prerender_manager = MaybeGetPrerenderManager();
123      if (prerender_manager) {
124        prerender_manager->RecordPageLoadTimeNotSwappedIn(
125            origin_, now - pplt_load_start_, url_);
126      } else {
127        NOTREACHED();
128      }
129    } else {
130      double fraction_elapsed_at_swapin = -1.0;
131      if (!actual_load_start_.is_null()) {
132        double plt = (now - actual_load_start_).InMillisecondsF();
133        if (plt > 0.0) {
134          fraction_elapsed_at_swapin = 1.0 -
135              (now - pplt_load_start_).InMillisecondsF() / plt;
136        } else {
137          fraction_elapsed_at_swapin = 1.0;
138        }
139        DCHECK_GE(fraction_elapsed_at_swapin, 0.0);
140        DCHECK_LE(fraction_elapsed_at_swapin, 1.0);
141      }
142
143      RecordPerceivedPageLoadTime(
144          now - pplt_load_start_, fraction_elapsed_at_swapin);
145    }
146  }
147
148  // Reset the PPLT metric.
149  pplt_load_start_ = base::TimeTicks();
150  actual_load_start_ = base::TimeTicks();
151}
152
153void PrerenderTabHelper::DidStartProvisionalLoadForFrame(
154    content::RenderFrameHost* render_frame_host,
155    const GURL& validated_url,
156    bool is_error_page,
157    bool is_iframe_srcdoc) {
158  if (render_frame_host->GetParent())
159    return;
160
161  // Record PPLT state for the beginning of a new navigation.
162  pplt_load_start_ = base::TimeTicks::Now();
163  actual_load_start_ = base::TimeTicks();
164
165  if (next_load_is_control_prerender_) {
166    DCHECK_EQ(NAVIGATION_TYPE_NORMAL, navigation_type_);
167    navigation_type_ = NAVIGATION_TYPE_WOULD_HAVE_BEEN_PRERENDERED;
168    origin_ = next_load_origin_;
169    next_load_is_control_prerender_ = false;
170    next_load_origin_ = ORIGIN_NONE;
171  }
172
173  MainFrameUrlDidChange(validated_url);
174}
175
176void PrerenderTabHelper::MainFrameUrlDidChange(const GURL& url) {
177  url_ = url;
178  RecordEvent(EVENT_MAINFRAME_CHANGE);
179  RecordEventIfLoggedInURL(EVENT_MAINFRAME_CHANGE_DOMAIN_LOGGED_IN, url);
180  PrerenderManager* prerender_manager = MaybeGetPrerenderManager();
181  if (!prerender_manager)
182    return;
183  if (prerender_manager->IsWebContentsPrerendering(web_contents(), NULL))
184    return;
185  ReportTabHelperURLSeenToLocalPredictor(prerender_manager, url,
186                                         web_contents());
187}
188
189void PrerenderTabHelper::PasswordSubmitted(const autofill::PasswordForm& form) {
190  PrerenderManager* prerender_manager = MaybeGetPrerenderManager();
191  if (prerender_manager) {
192    prerender_manager->RecordLikelyLoginOnURL(form.origin);
193    RecordEvent(EVENT_LOGIN_ACTION_ADDED);
194    if (form.password_value.empty())
195      RecordEvent(EVENT_LOGIN_ACTION_ADDED_PW_EMPTY);
196  }
197}
198
199PrerenderManager* PrerenderTabHelper::MaybeGetPrerenderManager() const {
200  return PrerenderManagerFactory::GetForProfile(
201      Profile::FromBrowserContext(web_contents()->GetBrowserContext()));
202}
203
204bool PrerenderTabHelper::IsPrerendering() {
205  PrerenderManager* prerender_manager = MaybeGetPrerenderManager();
206  if (!prerender_manager)
207    return false;
208  return prerender_manager->IsWebContentsPrerendering(web_contents(), NULL);
209}
210
211void PrerenderTabHelper::PrerenderSwappedIn() {
212  // Ensure we are not prerendering any more.
213  DCHECK_EQ(NAVIGATION_TYPE_PRERENDERED, navigation_type_);
214  DCHECK(!IsPrerendering());
215  if (pplt_load_start_.is_null()) {
216    // If we have already finished loading, report a 0 PPLT.
217    RecordPerceivedPageLoadTime(base::TimeDelta(), 1.0);
218    DCHECK_EQ(NAVIGATION_TYPE_NORMAL, navigation_type_);
219  } else {
220    // If we have not finished loading yet, record the actual load start, and
221    // rebase the start time to now.
222    actual_load_start_ = pplt_load_start_;
223    pplt_load_start_ = base::TimeTicks::Now();
224  }
225}
226
227void PrerenderTabHelper::WouldHavePrerenderedNextLoad(Origin origin) {
228  next_load_is_control_prerender_ = true;
229  next_load_origin_ = origin;
230}
231
232void PrerenderTabHelper::RecordEvent(PrerenderTabHelper::Event event) const {
233  UMA_HISTOGRAM_ENUMERATION("Prerender.TabHelperEvent",
234                            event, PrerenderTabHelper::EVENT_MAX_VALUE);
235}
236
237void PrerenderTabHelper::RecordEventIfLoggedInURL(
238    PrerenderTabHelper::Event event, const GURL& url) {
239  PrerenderManager* prerender_manager = MaybeGetPrerenderManager();
240  if (!prerender_manager)
241    return;
242  scoped_ptr<bool> is_present(new bool);
243  scoped_ptr<bool> lookup_succeeded(new bool);
244  bool* is_present_ptr = is_present.get();
245  bool* lookup_succeeded_ptr = lookup_succeeded.get();
246  prerender_manager->CheckIfLikelyLoggedInOnURL(
247      url,
248      is_present_ptr,
249      lookup_succeeded_ptr,
250      base::Bind(&PrerenderTabHelper::RecordEventIfLoggedInURLResult,
251                 weak_factory_.GetWeakPtr(),
252                 event,
253                 base::Passed(&is_present),
254                 base::Passed(&lookup_succeeded)));
255}
256
257void PrerenderTabHelper::RecordEventIfLoggedInURLResult(
258    PrerenderTabHelper::Event event,
259    scoped_ptr<bool> is_present,
260    scoped_ptr<bool> lookup_succeeded) {
261  if (*lookup_succeeded && *is_present)
262    RecordEvent(event);
263}
264
265void PrerenderTabHelper::RecordPerceivedPageLoadTime(
266    base::TimeDelta perceived_page_load_time,
267    double fraction_plt_elapsed_at_swap_in) {
268  DCHECK(!IsPrerendering());
269  PrerenderManager* prerender_manager = MaybeGetPrerenderManager();
270  if (!prerender_manager)
271    return;
272
273  // Note: it is possible for |next_load_is_control_prerender_| to be true at
274  // this point. This does not affect the classification of the current load,
275  // but only the next load. (This occurs if a WOULD_HAVE_BEEN_PRERENDERED
276  // navigation interrupts and aborts another navigation.)
277  prerender_manager->RecordPerceivedPageLoadTime(
278      origin_, navigation_type_, perceived_page_load_time,
279      fraction_plt_elapsed_at_swap_in, url_);
280
281  // Reset state for the next navigation.
282  navigation_type_ = NAVIGATION_TYPE_NORMAL;
283  origin_ = ORIGIN_NONE;
284}
285
286}  // namespace prerender
287