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 3a1fbd9302c38026492c1c708a2043fa0efe8827..fdde938812b67c813317dfc9afa98533d253dd93 100644 |
--- a/components/page_load_metrics/browser/metrics_web_contents_observer.cc |
+++ b/components/page_load_metrics/browser/metrics_web_contents_observer.cc |
@@ -47,16 +47,99 @@ bool IsValidPageLoadTiming(const PageLoadTiming& timing) { |
return true; |
} |
+base::Time WallTimeFromTimeTicks(base::TimeTicks time) { |
+ return base::Time::FromDoubleT( |
+ (time - base::TimeTicks::UnixEpoch()).InSecondsF()); |
+} |
+ |
} // namespace |
+#define PAGE_LOAD_HISTOGRAM(name, sample) \ |
+ UMA_HISTOGRAM_CUSTOM_TIMES(name, sample, \ |
+ base::TimeDelta::FromMilliseconds(10), \ |
+ base::TimeDelta::FromMinutes(10), 100) |
+ |
+PageLoadTracker::PageLoadTracker(bool in_foreground) |
+ : has_commit_(false), started_in_foreground_(in_foreground) {} |
+ |
+PageLoadTracker::~PageLoadTracker() { |
+ if (has_commit_) |
+ RecordTimingHistograms(); |
+} |
+ |
+void PageLoadTracker::WebContentsHidden() { |
+ // Only log the first time we background in a given page load. |
+ if (background_time_.is_null()) { |
+ background_time_ = base::TimeTicks::Now(); |
+ } |
+} |
+ |
+void PageLoadTracker::Commit() { |
+ has_commit_ = true; |
+} |
+ |
+bool PageLoadTracker::UpdateTiming(const PageLoadTiming& timing) { |
+ // Throw away IPCs that are not relevant to the current navigation. |
+ if (!timing_.navigation_start.is_null() && |
+ timing_.navigation_start != timing.navigation_start) { |
+ // TODO(csharrison) uma log a counter here |
+ return false; |
+ } |
+ if (IsValidPageLoadTiming(timing)) { |
+ timing_ = timing; |
+ return true; |
+ } |
+ return false; |
+} |
+ |
+void PageLoadTracker::RecordTimingHistograms() { |
+ DCHECK(has_commit_); |
+ // This method is similar to how blink converts TimeTicks to epoch time. |
+ // There may be slight inaccuracies due to inter-process timestamps, but |
+ // this solution is the best we have right now. |
+ base::TimeDelta background_delta; |
+ if (started_in_foreground_) { |
+ background_delta = background_time_.is_null() |
+ ? base::TimeDelta::Max() |
+ : WallTimeFromTimeTicks(background_time_) - timing_.navigation_start; |
+ } |
+ |
+ if (!timing_.dom_content_loaded_event_start.is_zero()) { |
+ if (timing_.dom_content_loaded_event_start < background_delta) { |
+ PAGE_LOAD_HISTOGRAM( |
+ "PageLoad.Timing2.NavigationToDOMContentLoadedEventFired", |
+ timing_.dom_content_loaded_event_start); |
+ } else { |
+ PAGE_LOAD_HISTOGRAM( |
+ "PageLoad.Timing2.NavigationToDOMContentLoadedEventFired.BG", |
+ timing_.dom_content_loaded_event_start); |
+ } |
+ } |
+ if (!timing_.load_event_start.is_zero()) { |
+ if (timing_.load_event_start < background_delta) { |
+ PAGE_LOAD_HISTOGRAM("PageLoad.Timing2.NavigationToLoadEventFired", |
+ timing_.load_event_start); |
+ } else { |
+ PAGE_LOAD_HISTOGRAM("PageLoad.Timing2.NavigationToLoadEventFired.BG", |
+ timing_.load_event_start); |
+ } |
+ } |
+ if (!timing_.first_layout.is_zero()) { |
+ if (timing_.first_layout < background_delta) { |
+ PAGE_LOAD_HISTOGRAM("PageLoad.Timing2.NavigationToFirstLayout", |
+ timing_.first_layout); |
+ } else { |
+ PAGE_LOAD_HISTOGRAM("PageLoad.Timing2.NavigationToFirstLayout.BG", |
+ timing_.first_layout); |
+ } |
+ } |
+} |
+ |
MetricsWebContentsObserver::MetricsWebContentsObserver( |
content::WebContents* web_contents) |
- : content::WebContentsObserver(web_contents) {} |
+ : content::WebContentsObserver(web_contents), in_foreground_(false) {} |
-// This object is tied to a single WebContents for its entire lifetime. |
-MetricsWebContentsObserver::~MetricsWebContentsObserver() { |
- RecordTimingHistograms(); |
-} |
+MetricsWebContentsObserver::~MetricsWebContentsObserver() {} |
bool MetricsWebContentsObserver::OnMessageReceived( |
const IPC::Message& message, |
@@ -71,32 +154,74 @@ bool MetricsWebContentsObserver::OnMessageReceived( |
return handled; |
} |
+void MetricsWebContentsObserver::DidStartNavigation( |
+ content::NavigationHandle* navigation_handle) { |
+ if (!navigation_handle->IsInMainFrame()) |
+ return; |
+ // We can have two provisional loads in some cases. E.g. a same-site |
+ // navigation can have a concurrent cross-process navigation started |
+ // from the omnibox. |
+ DCHECK_GT(2ul, provisional_loads_.size()); |
+ provisional_loads_.insert( |
+ navigation_handle, make_scoped_ptr(new PageLoadTracker(in_foreground_))); |
+} |
+ |
void MetricsWebContentsObserver::DidFinishNavigation( |
content::NavigationHandle* navigation_handle) { |
+ if (!navigation_handle->IsInMainFrame()) |
+ return; |
+ |
+ scoped_ptr<PageLoadTracker> finished_nav( |
+ provisional_loads_.take_and_erase(navigation_handle)); |
+ DCHECK(finished_nav); |
+ |
+ // TODO(csharrison) handle the two error cases: |
+ // 1. Error pages that replace the previous page. |
+ // 2. Error pages that leave the user on the previous page. |
+ // For now these cases will be ignored. |
if (!navigation_handle->HasCommitted()) |
return; |
- if (navigation_handle->IsInMainFrame() && !navigation_handle->IsSamePage()) |
- RecordTimingHistograms(); |
- if (IsRelevantNavigation(navigation_handle)) |
- current_timing_.reset(new PageLoadTiming()); |
+ |
+ // Don't treat a same-page nav as a new page load. |
+ if (navigation_handle->IsSamePage()) |
+ return; |
+ |
+ // Eagerly log the previous UMA even if we don't care about the current |
+ // navigation. |
+ committed_load_.reset(); |
+ |
+ if (!IsRelevantNavigation(navigation_handle)) |
+ return; |
+ |
+ committed_load_ = finished_nav.Pass(); |
+ committed_load_->Commit(); |
+} |
+ |
+void MetricsWebContentsObserver::WasShown() { |
+ in_foreground_ = true; |
+} |
+ |
+void MetricsWebContentsObserver::WasHidden() { |
+ in_foreground_ = false; |
+ if (committed_load_) |
+ committed_load_->WebContentsHidden(); |
+ for (const auto& kv : provisional_loads_) { |
+ kv.second->WebContentsHidden(); |
+ } |
} |
-// This will occur when the process for the main RenderFrameHost exits. |
-// This will happen with a normal exit or a crash. |
+// This will occur when the process for the main RenderFrameHost exits, either |
+// normally or from a crash. We eagerly log data from the last committed load if |
+// we have one. |
void MetricsWebContentsObserver::RenderProcessGone( |
base::TerminationStatus status) { |
- RecordTimingHistograms(); |
+ committed_load_.reset(); |
} |
-#define PAGE_LOAD_HISTOGRAM(name, sample) \ |
- UMA_HISTOGRAM_CUSTOM_TIMES(name, sample, \ |
- base::TimeDelta::FromMilliseconds(10), \ |
- base::TimeDelta::FromMinutes(10), 100); |
- |
void MetricsWebContentsObserver::OnTimingUpdated( |
content::RenderFrameHost* render_frame_host, |
const PageLoadTiming& timing) { |
- if (!current_timing_) |
+ if (!committed_load_) |
return; |
// We may receive notifications from frames that have been navigated away |
@@ -110,36 +235,7 @@ void MetricsWebContentsObserver::OnTimingUpdated( |
if (!web_contents()->GetLastCommittedURL().SchemeIsHTTPOrHTTPS()) |
return; |
- // Throw away IPCs that are not relevant to the current navigation. |
- if (!current_timing_->navigation_start.is_null() && |
- timing.navigation_start != current_timing_->navigation_start) { |
- // TODO(csharrison) uma log a counter here |
- return; |
- } |
- |
- *current_timing_ = timing; |
-} |
- |
-void MetricsWebContentsObserver::RecordTimingHistograms() { |
- if (!current_timing_ || !IsValidPageLoadTiming(*current_timing_)) |
- return; |
- |
- if (!current_timing_->dom_content_loaded_event_start.is_zero()) { |
- PAGE_LOAD_HISTOGRAM( |
- "PageLoad.Timing.NavigationToDOMContentLoadedEventFired", |
- current_timing_->dom_content_loaded_event_start); |
- } |
- |
- if (!current_timing_->load_event_start.is_zero()) { |
- PAGE_LOAD_HISTOGRAM("PageLoad.Timing.NavigationToLoadEventFired", |
- current_timing_->load_event_start); |
- } |
- |
- if (!current_timing_->first_layout.is_zero()) { |
- PAGE_LOAD_HISTOGRAM("PageLoad.Timing.NavigationToFirstLayout", |
- current_timing_->first_layout); |
- } |
- current_timing_.reset(); |
+ committed_load_->UpdateTiming(timing); |
} |
bool MetricsWebContentsObserver::IsRelevantNavigation( |