// 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/time/time.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 "components/password_manager/core/browser/password_manager.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/common/frame_navigate_params.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

// static
void PrerenderTabHelper::CreateForWebContentsWithPasswordManager(
    content::WebContents* web_contents,
    password_manager::PasswordManager* password_manager) {
  if (!FromWebContents(web_contents)) {
    web_contents->SetUserData(UserDataKey(),
                              new PrerenderTabHelper(web_contents,
                                                     password_manager));
  }
}

PrerenderTabHelper::PrerenderTabHelper(
    content::WebContents* web_contents,
    password_manager::PasswordManager* password_manager)
    : content::WebContentsObserver(web_contents),
      origin_(ORIGIN_NONE),
      next_load_is_control_prerender_(false),
      next_load_origin_(ORIGIN_NONE),
      weak_factory_(this) {
  if (password_manager) {
    // May be NULL in testing.
    password_manager->AddSubmissionCallback(
        base::Bind(&PrerenderTabHelper::PasswordSubmitted,
                   weak_factory_.GetWeakPtr()));
  }

  // Determine if this is a prerender.
  PrerenderManager* prerender_manager = MaybeGetPrerenderManager();
  if (prerender_manager &&
      prerender_manager->IsWebContentsPrerendering(web_contents, &origin_)) {
    navigation_type_ = NAVIGATION_TYPE_PRERENDERED;
  } else {
    navigation_type_ = NAVIGATION_TYPE_NORMAL;
  }
}

PrerenderTabHelper::~PrerenderTabHelper() {
}

void PrerenderTabHelper::ProvisionalChangeToMainFrameUrl(
    const GURL& url,
    content::RenderFrameHost* render_frame_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;
  ReportTabHelperURLSeenToLocalPredictor(prerender_manager, url,
                                         web_contents());
}

void PrerenderTabHelper::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) {
  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. If the
  // page is still prerendering, record the not swapped in page load time
  // instead.
  if (!pplt_load_start_.is_null()) {
    base::TimeTicks now = base::TimeTicks::Now();
    if (IsPrerendering()) {
      PrerenderManager* prerender_manager = MaybeGetPrerenderManager();
      if (prerender_manager) {
        prerender_manager->RecordPageLoadTimeNotSwappedIn(
            origin_, now - pplt_load_start_, url_);
      } else {
        NOTREACHED();
      }
    } else {
      double fraction_elapsed_at_swapin = -1.0;
      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);
      }

      RecordPerceivedPageLoadTime(
          now - pplt_load_start_, fraction_elapsed_at_swapin);
    }
  }

  // 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)
    return;

  // Record PPLT state for the beginning of a new navigation.
  pplt_load_start_ = base::TimeTicks::Now();
  actual_load_start_ = base::TimeTicks();

  if (next_load_is_control_prerender_) {
    DCHECK_EQ(NAVIGATION_TYPE_NORMAL, navigation_type_);
    navigation_type_ = NAVIGATION_TYPE_WOULD_HAVE_BEEN_PRERENDERED;
    origin_ = next_load_origin_;
    next_load_is_control_prerender_ = false;
    next_load_origin_ = ORIGIN_NONE;
  }
}

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);
}

void PrerenderTabHelper::PrerenderSwappedIn() {
  // Ensure we are not prerendering any more.
  DCHECK_EQ(NAVIGATION_TYPE_PRERENDERED, navigation_type_);
  DCHECK(!IsPrerendering());
  if (pplt_load_start_.is_null()) {
    // If we have already finished loading, report a 0 PPLT.
    RecordPerceivedPageLoadTime(base::TimeDelta(), 1.0);
    DCHECK_EQ(NAVIGATION_TYPE_NORMAL, navigation_type_);
  } 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();
  }
}

void PrerenderTabHelper::WouldHavePrerenderedNextLoad(Origin origin) {
  next_load_is_control_prerender_ = true;
  next_load_origin_ = origin;
}

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<bool> is_present(new bool);
  scoped_ptr<bool> 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<bool> is_present,
    scoped_ptr<bool> lookup_succeeded) {
  if (*lookup_succeeded && *is_present)
    RecordEvent(event);
}

void PrerenderTabHelper::RecordPerceivedPageLoadTime(
    base::TimeDelta perceived_page_load_time,
    double fraction_plt_elapsed_at_swap_in) {
  DCHECK(!IsPrerendering());
  PrerenderManager* prerender_manager = MaybeGetPrerenderManager();
  if (!prerender_manager)
    return;

  // Note: it is possible for |next_load_is_control_prerender_| to be true at
  // this point. This does not affect the classification of the current load,
  // but only the next load. (This occurs if a WOULD_HAVE_BEEN_PRERENDERED
  // navigation interrupts and aborts another navigation.)
  prerender_manager->RecordPerceivedPageLoadTime(
      origin_, navigation_type_, perceived_page_load_time,
      fraction_plt_elapsed_at_swap_in, url_);

  // Reset state for the next navigation.
  navigation_type_ = NAVIGATION_TYPE_NORMAL;
  origin_ = ORIGIN_NONE;
}

}  // namespace prerender