Chromium Code Reviews| Index: components/page_load_metrics/browser/metrics_web_contents_observer.cc |
| diff --git a/components/page_load_metrics/browser/metrics_web_contents_observer.cc b/components/page_load_metrics/browser/metrics_web_contents_observer.cc |
| index 3e1db269cbc05761a974dd871614369af70141f7..b92ae7ba0f8eed69abbb98819226823a9e1d3a91 100644 |
| --- a/components/page_load_metrics/browser/metrics_web_contents_observer.cc |
| +++ b/components/page_load_metrics/browser/metrics_web_contents_observer.cc |
| @@ -21,6 +21,7 @@ |
| #include "content/public/browser/web_contents_user_data.h" |
| #include "ipc/ipc_message.h" |
| #include "ipc/ipc_message_macros.h" |
| +#include "ui/base/page_transition_types.h" |
| DEFINE_WEB_CONTENTS_USER_DATA_KEY( |
| page_load_metrics::MetricsWebContentsObserver); |
| @@ -80,6 +81,15 @@ void RecordInternalError(InternalErrorLoadEvent event) { |
| UMA_HISTOGRAM_ENUMERATION(kErrorEvents, event, ERR_LAST_ENTRY); |
| } |
| +UserAbortType AbortTypeForPageTransition(ui::PageTransition transition) { |
| + if (ui::PageTransitionCoreTypeIs(transition, ui::PAGE_TRANSITION_RELOAD)) |
| + return ABORT_RELOAD; |
| + else if (transition & ui::PAGE_TRANSITION_FORWARD_BACK) |
| + return ABORT_FORWARD_BACK; |
| + else |
| + return ABORT_NEW_NAVIGATION; |
| +} |
| + |
| base::TimeDelta GetFirstContentfulPaint(const PageLoadTiming& timing) { |
| if (timing.first_text_paint.is_zero()) |
| return timing.first_image_paint; |
| @@ -120,6 +130,7 @@ PageLoadTracker::PageLoadTracker( |
| base::ObserverList<PageLoadMetricsObserver, true>* observers) |
| : has_commit_(false), |
| navigation_start_(navigation_handle->navigation_start()), |
| + abort_type_(ABORT_NULL), |
| started_in_foreground_(in_foreground), |
| embedder_interface_(embedder_interface), |
| observers_(observers) {} |
| @@ -129,6 +140,7 @@ PageLoadTracker::~PageLoadTracker() { |
| RecordTimingHistograms(); |
| RecordRappor(); |
| } |
| + RecordAbortTimingHistograms(); |
| } |
| void PageLoadTracker::WebContentsHidden() { |
| @@ -178,14 +190,90 @@ bool PageLoadTracker::HasBackgrounded() { |
| PageLoadExtraInfo PageLoadTracker::GetPageLoadMetricsInfo() { |
| base::TimeDelta first_background_time; |
| base::TimeDelta first_foreground_time; |
| - if (!background_time_.is_null() && started_in_foreground_) |
| + if (!background_time_.is_null()) |
| first_background_time = background_time_ - navigation_start_; |
| - if (!foreground_time_.is_null() && !started_in_foreground_) |
| + if (!foreground_time_.is_null()) |
| first_foreground_time = foreground_time_ - navigation_start_; |
| return PageLoadExtraInfo(first_background_time, first_foreground_time, |
| started_in_foreground_); |
| } |
| +// We consider the first abort passed here as the true abort, unless it is |
| +// ABORT_OTHER, so call this with decreasing specificity. |
| +void PageLoadTracker::NotifyAbort(UserAbortType abort_type, |
| + const base::TimeTicks& timestamp) { |
| + DCHECK(abort_type != ABORT_NULL); |
| + if (abort_type_ == ABORT_NULL) { |
| + abort_type_ = abort_type; |
| + abort_time_ = timestamp; |
| + } |
| + |
| + // If we got a better signal than ABORT_OTHER since the last navigation, treat |
| + // it as the cause of the abort (Some ABORT_OTHER signals occur before the |
| + // true signal). Note that this only occurs for provisional loads. While this |
| + // heuristic is coarse, it works better and is simpler than other feasible |
| + // methods. |
| + if (abort_type_ == ABORT_OTHER && abort_type != ABORT_OTHER) { |
| + abort_type_ = abort_type; |
| + abort_time_ = std::min(abort_time_, timestamp); |
| + } |
| +} |
| + |
| +void PageLoadTracker::RecordAbortTimingHistograms() { |
| + // Log the background "abort" if we backgrounded a foregrounded page before |
| + // its first paint. This is a slightly different notion of abort (we can still |
| + // get to the first paint later), but it is still worthwhile measure. |
| + if (!background_time_.is_null() && |
| + (timing_.first_paint.is_zero() || |
| + GetBackgroundDelta() < timing_.first_paint)) { |
| + if (has_commit_) { |
| + PAGE_LOAD_HISTOGRAM(kHistogramCommittedAbortBackground, |
|
Bryan McQuade
2015/11/17 13:33:08
this does feel like a user-initiated abort, but i
Charlie Harrison
2015/11/17 16:11:54
I could go either way. I only put it here because
|
| + background_time_ - navigation_start_); |
| + } else { |
| + PAGE_LOAD_HISTOGRAM(kHistogramProvisionalAbortBackground, |
| + background_time_ - navigation_start_); |
| + } |
| + } |
| + if (abort_type_ == ABORT_NULL) |
| + return; |
| + DCHECK(!abort_time_.is_null()); |
| + |
| + // Loads are not considered aborts if they painted before the abort event. |
|
Bryan McQuade
2015/11/17 13:33:08
i'm inclined to either ignore aborts when a tab is
Charlie Harrison
2015/11/17 16:11:54
I'm inclined to ignore them for now and add later
|
| + base::TimeDelta time_to_abort = abort_time_ - navigation_start_; |
| + if (!timing_.first_paint.is_zero() && timing_.first_paint < time_to_abort) |
| + return; |
| + if (has_commit_) { |
| + if (abort_type_ == ABORT_RELOAD) { |
| + PAGE_LOAD_HISTOGRAM(kHistogramCommittedAbortReload, time_to_abort); |
| + } else if (abort_type_ == ABORT_FORWARD_BACK) { |
| + PAGE_LOAD_HISTOGRAM(kHistogramCommittedAbortForwardBack, time_to_abort); |
| + } else if (abort_type_ == ABORT_NEW_NAVIGATION) { |
| + PAGE_LOAD_HISTOGRAM(kHistogramCommittedAbortNewNavigation, time_to_abort); |
| + } else if (abort_type_ == ABORT_STOP) { |
| + PAGE_LOAD_HISTOGRAM(kHistogramCommittedAbortStop, time_to_abort); |
| + } else if (abort_type_ == ABORT_CLOSE) { |
| + PAGE_LOAD_HISTOGRAM(kHistogramCommittedAbortClose, time_to_abort); |
| + } else if (abort_type_ == ABORT_OTHER) { |
| + PAGE_LOAD_HISTOGRAM(kHistogramCommittedAbortOther, time_to_abort); |
| + } |
| + } else { |
| + if (abort_type_ == ABORT_RELOAD) { |
| + PAGE_LOAD_HISTOGRAM(kHistogramProvisionalAbortReload, time_to_abort); |
| + } else if (abort_type_ == ABORT_FORWARD_BACK) { |
| + PAGE_LOAD_HISTOGRAM(kHistogramProvisionalAbortForwardBack, time_to_abort); |
| + } else if (abort_type_ == ABORT_NEW_NAVIGATION) { |
| + PAGE_LOAD_HISTOGRAM(kHistogramProvisionalAbortNewNavigation, |
| + time_to_abort); |
| + } else if (abort_type_ == ABORT_STOP) { |
| + PAGE_LOAD_HISTOGRAM(kHistogramProvisionalAbortStop, time_to_abort); |
| + } else if (abort_type_ == ABORT_CLOSE) { |
| + PAGE_LOAD_HISTOGRAM(kHistogramProvisionalAbortClose, time_to_abort); |
| + } else if (abort_type_ == ABORT_OTHER) { |
| + PAGE_LOAD_HISTOGRAM(kHistogramProvisionalAbortOther, time_to_abort); |
| + } |
| + } |
| +} |
| + |
| const GURL& PageLoadTracker::GetCommittedURL() { |
| DCHECK(has_commit_); |
| return url_; |
| @@ -370,8 +458,10 @@ MetricsWebContentsObserver* MetricsWebContentsObserver::CreateForWebContents( |
| MetricsWebContentsObserver::~MetricsWebContentsObserver() { |
| // Reset PageLoadTrackers so observers get final notifications. |
| + NotifyAbortAllLoads(ABORT_CLOSE); |
| committed_load_.reset(); |
| provisional_loads_.clear(); |
| + aborted_provisional_loads_.clear(); |
| FOR_EACH_OBSERVER(PageLoadMetricsObserver, observers_, |
| OnPageLoadMetricsGoingAway()); |
| } |
| @@ -433,12 +523,18 @@ void MetricsWebContentsObserver::DidFinishNavigation( |
| // Handle a pre-commit error here. Navigations that result in an error page |
| // will be ignored. Note that downloads/204s will result in HasCommitted() |
| // returning false. |
| + // TODO(csharrison): Track changes to NavigationHandle for signals when this |
| + // is the case (HTTP response headers). |
| if (!navigation_handle->HasCommitted()) { |
| net::Error error = navigation_handle->GetNetErrorCode(); |
| - finished_nav->RecordProvisionalEvent( |
| - error == net::OK ? PROVISIONAL_LOAD_STOPPED |
| + ProvisionalLoadEvent event = error == net::OK ? PROVISIONAL_LOAD_STOPPED |
| : error == net::ERR_ABORTED ? PROVISIONAL_LOAD_ERR_ABORTED |
|
Bryan McQuade
2015/11/17 13:33:08
IIRC downloads/204s report ERR_ABORTED, so we'll e
Charlie Harrison
2015/11/17 16:11:54
Let's see. It's possible clamy@ will land her chan
|
| - : PROVISIONAL_LOAD_ERR_FAILED_NON_ABORT); |
| + : PROVISIONAL_LOAD_ERR_FAILED_NON_ABORT; |
| + finished_nav->RecordProvisionalEvent(event); |
| + if (event != PROVISIONAL_LOAD_ERR_FAILED_NON_ABORT) { |
| + finished_nav->NotifyAbort(ABORT_OTHER, base::TimeTicks::Now()); |
| + aborted_provisional_loads_.push_back(finished_nav.Pass()); |
|
Bryan McQuade
2015/11/17 13:33:08
the aborted_provisional_loads_ member makes me a b
|
| + } |
| return; |
| } |
| finished_nav->RecordProvisionalEvent(PROVISIONAL_LOAD_COMMITTED); |
|
Bryan McQuade
2015/11/17 13:33:08
just noticing now - unrelated to this change - sho
Charlie Harrison
2015/11/17 16:11:54
This was intentional. If we want we can break up t
|
| @@ -447,6 +543,11 @@ void MetricsWebContentsObserver::DidFinishNavigation( |
| if (navigation_handle->IsSamePage()) |
| return; |
| + // Notify other loads that they may have been aborted by this committed load |
| + NotifyAbortAllLoadsWithTimestamp( |
| + AbortTypeForPageTransition(navigation_handle->GetPageTransition()), |
| + navigation_handle->navigation_start()); |
| + |
| // Eagerly log the previous UMA even if we don't care about the current |
| // navigation. |
| committed_load_.reset(); |
| @@ -460,6 +561,11 @@ void MetricsWebContentsObserver::DidFinishNavigation( |
| committed_load_ = finished_nav.Pass(); |
| committed_load_->Commit(navigation_handle); |
| + aborted_provisional_loads_.clear(); |
|
Bryan McQuade
2015/11/17 13:33:08
should we clear aborted_provisional_loads_ even if
Charlie Harrison
2015/11/17 16:11:54
Yupp good idea. Done.
|
| +} |
| + |
| +void MetricsWebContentsObserver::NavigationStopped() { |
| + NotifyAbortAllLoads(ABORT_STOP); |
| } |
| void MetricsWebContentsObserver::WasShown() { |
| @@ -485,7 +591,26 @@ void MetricsWebContentsObserver::WasHidden() { |
| // we have one. |
| void MetricsWebContentsObserver::RenderProcessGone( |
| base::TerminationStatus status) { |
| + NotifyAbortAllLoads(ABORT_CLOSE); |
| committed_load_.reset(); |
| + provisional_loads_.clear(); |
| + aborted_provisional_loads_.clear(); |
| +} |
| + |
| +void MetricsWebContentsObserver::NotifyAbortAllLoads(UserAbortType abort_type) { |
| + NotifyAbortAllLoadsWithTimestamp(abort_type, base::TimeTicks::Now()); |
| +} |
| + |
| +void MetricsWebContentsObserver::NotifyAbortAllLoadsWithTimestamp( |
| + UserAbortType abort_type, const base::TimeTicks& timestamp) { |
| + if (committed_load_) |
| + committed_load_->NotifyAbort(abort_type, timestamp); |
| + for (const auto& kv : provisional_loads_) { |
| + kv.second->NotifyAbort(abort_type, timestamp); |
| + } |
| + for (const auto& plt : aborted_provisional_loads_) { |
| + plt->NotifyAbort(abort_type, timestamp); |
| + } |
| } |
| void MetricsWebContentsObserver::OnTimingUpdated( |