blob: d58cfb003b594b0b803b1b3b7fa8befb7d2354ab [file] [log] [blame]
// Copyright 2015 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.
#ifndef COMPONENTS_PAGE_LOAD_METRICS_BROWSER_METRICS_WEB_CONTENTS_OBSERVER_H_
#define COMPONENTS_PAGE_LOAD_METRICS_BROWSER_METRICS_WEB_CONTENTS_OBSERVER_H_
#include <map>
#include <memory>
#include <vector>
#include "base/macros.h"
#include "base/time/time.h"
#include "components/page_load_metrics/browser/page_load_metrics_observer.h"
#include "components/page_load_metrics/common/page_load_timing.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_observer.h"
#include "content/public/browser/web_contents_user_data.h"
#include "net/base/net_errors.h"
namespace content {
class NavigationHandle;
class RenderFrameHost;
} // namespace content
namespace IPC {
class Message;
} // namespace IPC
namespace page_load_metrics {
class PageLoadTracker;
namespace internal {
extern const char kErrorEvents[];
extern const char kAbortChainSizeReload[];
extern const char kAbortChainSizeForwardBack[];
extern const char kAbortChainSizeNewNavigation[];
extern const char kAbortChainSizeNoCommit[];
extern const char kAbortChainSizeSameURL[];
} // namespace internal
// These errors are internal to the page_load_metrics subsystem and do not
// reflect actual errors that occur during a page load.
//
// If you add elements to this enum, make sure you update the enum
// value in histograms.xml. Only add elements to the end to prevent
// inconsistencies between versions.
enum InternalErrorLoadEvent {
// A timing IPC was sent from the renderer that did not line up with previous
// data we've received (i.e. navigation start is different or the timing
// struct is somehow invalid). This error can only occur once the IPC is
// vetted in other ways (see other errors).
ERR_BAD_TIMING_IPC,
// The following IPCs are not mutually exclusive.
//
// We received an IPC when we weren't tracking a committed load. This will
// often happen if we get an IPC from a bad URL scheme (that is, the renderer
// sent us an IPC from a navigation we don't care about).
ERR_IPC_WITH_NO_RELEVANT_LOAD,
// Received a notification from a frame that has been navigated away from.
ERR_IPC_FROM_WRONG_FRAME,
// We received an IPC even through the last committed url from the browser
// was not http/s. This can happen with the renderer sending IPCs for the
// new tab page. This will often come paired with
// ERR_IPC_WITH_NO_RELEVANT_LOAD.
ERR_IPC_FROM_BAD_URL_SCHEME,
// If we track a navigation, but the renderer sends us no IPCs. This could
// occur if the browser filters loads less aggressively than the renderer.
ERR_NO_IPCS_RECEIVED,
// Tracks frequency with which we record an abort time that occurred before
// navigation start. This is expected to happen in some cases (see comments in
// cc file for details). We use this error counter to understand how often it
// happens.
ERR_ABORT_BEFORE_NAVIGATION_START,
// A new navigation triggers abort updates in multiple trackers in
// |aborted_provisional_loads_|, when usually there should only be one (the
// navigation that just aborted because of this one). If this happens, the
// latest aborted load is used to track the chain size.
ERR_NAVIGATION_SIGNALS_MULIPLE_ABORTED_LOADS,
// Add values before this final count.
ERR_LAST_ENTRY
};
// This class serves as a functional interface to various chrome// features.
// Impl version is defined in chrome/browser/page_load_metrics.
class PageLoadMetricsEmbedderInterface {
public:
virtual ~PageLoadMetricsEmbedderInterface() {}
virtual bool IsPrerendering(content::WebContents* web_contents) = 0;
virtual void RegisterObservers(PageLoadTracker* metrics) = 0;
};
// This class tracks a given page load, starting from navigation start /
// provisional load, until a new navigation commits or the navigation fails.
// MetricsWebContentsObserver manages a set of provisional PageLoadTrackers, as
// well as a committed PageLoadTracker.
class PageLoadTracker {
public:
// Caller must guarantee that the embedder_interface pointer outlives this
// class. The PageLoadTracker must not hold on to
// currently_committed_load_or_null or navigation_handle beyond the scope of
// the constructor.
PageLoadTracker(bool in_foreground,
PageLoadMetricsEmbedderInterface* embedder_interface,
const GURL& currently_committed_url,
content::NavigationHandle* navigation_handle,
int aborted_chain_size,
int aborted_chain_size_same_url);
~PageLoadTracker();
void Redirect(content::NavigationHandle* navigation_handle);
void Commit(content::NavigationHandle* navigation_handle);
void FailedProvisionalLoad(content::NavigationHandle* navigation_handle);
void WebContentsHidden();
void WebContentsShown();
// Returns true if the timing was successfully updated.
bool UpdateTiming(const PageLoadTiming& timing,
const PageLoadMetadata& metadata);
void set_renderer_tracked(bool renderer_tracked);
bool renderer_tracked() const { return renderer_tracked_; }
int aborted_chain_size() const { return aborted_chain_size_; }
int aborted_chain_size_same_url() const {
return aborted_chain_size_same_url_;
}
UserAbortType abort_type() const { return abort_type_; }
base::TimeTicks abort_time() const { return abort_time_; }
void AddObserver(std::unique_ptr<PageLoadMetricsObserver> observer);
// If the user performs some abort-like action while we are tracking this page
// load, notify the tracker. Note that we may not classify this as an abort if
// we've already performed a first paint.
void NotifyAbort(UserAbortType abort_type, base::TimeTicks timestamp);
void UpdateAbort(UserAbortType abort_type, base::TimeTicks timestamp);
// This method returns true if this page load has been aborted with type of
// ABORT_OTHER, and the |abort_cause_time| is within a sufficiently close
// delta to when it was aborted. Note that only provisional loads can be
// aborted with ABORT_OTHER. While this heuristic is coarse, it works better
// and is simpler than other feasible methods. See https://goo.gl/WKRG98.
bool IsLikelyProvisionalAbort(base::TimeTicks abort_cause_time);
bool MatchesOriginalNavigation(content::NavigationHandle* navigation_handle);
// Only valid to call post-commit.
const GURL& committed_url() const {
DCHECK(!commit_time_.is_null());
return url_;
}
private:
PageLoadExtraInfo GetPageLoadMetricsInfo();
void UpdateAbortInternal(UserAbortType abort_type,
base::TimeTicks timestamp);
// If |final_navigation| is null, then this is an "unparented" abort chain,
// and represents a sequence of provisional aborts that never ends with a
// committed load.
void LogAbortChainHistograms(content::NavigationHandle* final_navigation);
// Whether the renderer should be sending timing IPCs to this page load.
bool renderer_tracked_;
// The navigation start in TimeTicks, not the wall time reported by Blink.
const base::TimeTicks navigation_start_;
// Time this page load was committed. If this page load hasn't committed,
// |commit_time_| will be zero.
base::TimeTicks commit_time_;
// The URL of this page load. This is the provisional url before commit
// (before redirects), and the committed url after commit.
GURL url_;
// Will be ABORT_NONE if we have not aborted this load yet. Otherwise will
// be the first abort action the user performed.
UserAbortType abort_type_;
base::TimeTicks abort_time_;
// We record separate metrics for events that occur after a background,
// because metrics like layout/paint are delayed artificially
// when they occur in the background.
base::TimeTicks background_time_;
base::TimeTicks foreground_time_;
bool started_in_foreground_;
PageLoadTiming timing_;
PageLoadMetadata metadata_;
// This is a subtle member. If a provisional load A gets aborted by
// provisional load B, which gets aborted by C that eventually commits, then
// there exists an abort chain of length 2, starting at A's navigation_start.
// This is useful because it allows histograming abort chain lengths based on
// what the last load's transition type is. i.e. holding down F-5 to spam
// reload will produce a long chain with the RELOAD transition.
const int aborted_chain_size_;
// This member counts consecutive provisional aborts that share a url. It will
// always be less than or equal to |aborted_chain_size_|.
const int aborted_chain_size_same_url_;
// Interface to chrome features. Must outlive the class.
PageLoadMetricsEmbedderInterface* const embedder_interface_;
std::vector<std::unique_ptr<PageLoadMetricsObserver>> observers_;
DISALLOW_COPY_AND_ASSIGN(PageLoadTracker);
};
// MetricsWebContentsObserver tracks page loads and loading metrics
// related data based on IPC messages received from a
// MetricsRenderFrameObserver.
class MetricsWebContentsObserver
: public content::WebContentsObserver,
public content::WebContentsUserData<MetricsWebContentsObserver> {
public:
// Note that the returned metrics is owned by the web contents.
static MetricsWebContentsObserver* CreateForWebContents(
content::WebContents* web_contents,
std::unique_ptr<PageLoadMetricsEmbedderInterface> embedder_interface);
MetricsWebContentsObserver(
content::WebContents* web_contents,
std::unique_ptr<PageLoadMetricsEmbedderInterface> embedder_interface);
~MetricsWebContentsObserver() override;
// content::WebContentsObserver implementation:
bool OnMessageReceived(const IPC::Message& message,
content::RenderFrameHost* render_frame_host) override;
void DidStartNavigation(
content::NavigationHandle* navigation_handle) override;
void DidFinishNavigation(
content::NavigationHandle* navigation_handle) override;
void DidRedirectNavigation(
content::NavigationHandle* navigation_handle) override;
void NavigationStopped() override;
void WasShown() override;
void WasHidden() override;
void RenderProcessGone(base::TerminationStatus status) override;
private:
friend class content::WebContentsUserData<MetricsWebContentsObserver>;
// Notify all loads, provisional and committed, that we performed an action
// that might abort them.
void NotifyAbortAllLoads(UserAbortType abort_type);
void NotifyAbortAllLoadsWithTimestamp(UserAbortType abort_type,
base::TimeTicks timestamp);
// Notify aborted provisional loads that a new navigation occurred. This is
// used for more consistent attribution tracking for aborted provisional
// loads. This method returns the provisional load that was likely aborted by
// this navigation, to help instantiate the new PageLoadTracker.
std::unique_ptr<PageLoadTracker> NotifyAbortedProvisionalLoadsNewNavigation(
content::NavigationHandle* new_navigation);
void OnTimingUpdated(content::RenderFrameHost*,
const PageLoadTiming& timing,
const PageLoadMetadata& metadata);
// True if the web contents is currently in the foreground.
bool in_foreground_;
// The PageLoadTrackers must be deleted before the |embedded_interface_|,
// because they hold a pointer to the |embedder_interface_|.
std::unique_ptr<PageLoadMetricsEmbedderInterface> embedder_interface_;
// This map tracks all of the navigations ongoing that are not committed
// yet. Once a navigation is committed, it moves from the map to
// committed_load_. Note that a PageLoadTrackers NavigationHandle is only
// valid until commit time, when we remove it from the map.
std::map<content::NavigationHandle*, std::unique_ptr<PageLoadTracker>>
provisional_loads_;
// Tracks aborted provisional loads for a little bit longer than usual (one
// more navigation commit at the max), in order to better understand how the
// navigation failed. This is because most provisional loads are destroyed and
// vanish before we get signal about what caused the abort (new navigation,
// stop button, etc.).
std::vector<std::unique_ptr<PageLoadTracker>> aborted_provisional_loads_;
std::unique_ptr<PageLoadTracker> committed_load_;
// Has the MWCO observed at least one navigation?
bool has_navigated_;
DISALLOW_COPY_AND_ASSIGN(MetricsWebContentsObserver);
};
} // namespace page_load_metrics
#endif // COMPONENTS_PAGE_LOAD_METRICS_BROWSER_METRICS_WEB_CONTENTS_OBSERVER_H_