Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. | 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "components/page_load_metrics/browser/metrics_web_contents_observer.h" | 5 #include "components/page_load_metrics/browser/metrics_web_contents_observer.h" |
| 6 | 6 |
| 7 #include "base/logging.h" | 7 #include "base/logging.h" |
| 8 #include "base/metrics/histogram.h" | 8 #include "base/metrics/histogram.h" |
| 9 #include "components/page_load_metrics/common/page_load_metrics_messages.h" | 9 #include "components/page_load_metrics/common/page_load_metrics_messages.h" |
| 10 #include "components/page_load_metrics/common/page_load_timing.h" | 10 #include "components/page_load_metrics/common/page_load_timing.h" |
| (...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 42 !timing.load_event_start.is_zero(), | 42 !timing.load_event_start.is_zero(), |
| 43 !timing.dom_content_loaded_event_start.is_zero() && | 43 !timing.dom_content_loaded_event_start.is_zero() && |
| 44 timing.response_start <= timing.load_event_start && | 44 timing.response_start <= timing.load_event_start && |
| 45 timing.dom_content_loaded_event_start <= timing.load_event_start); | 45 timing.dom_content_loaded_event_start <= timing.load_event_start); |
| 46 | 46 |
| 47 return true; | 47 return true; |
| 48 } | 48 } |
| 49 | 49 |
| 50 } // namespace | 50 } // namespace |
| 51 | 51 |
| 52 #define PAGE_LOAD_HISTOGRAM(name, sample) \ | |
| 53 UMA_HISTOGRAM_CUSTOM_TIMES(name, sample, \ | |
| 54 base::TimeDelta::FromMilliseconds(10), \ | |
| 55 base::TimeDelta::FromMinutes(10), 100); | |
| 56 | |
| 57 PageLoadTracker::PageLoadTracker(bool in_foreground) | |
| 58 : has_commit_(false), | |
| 59 has_finished_(false), | |
| 60 page_load_stayed_in_foreground_(in_foreground) {} | |
| 61 | |
| 62 PageLoadTracker::~PageLoadTracker() { | |
| 63 if (has_commit_) | |
| 64 RecordTimingHistograms(); | |
| 65 } | |
| 66 | |
| 67 void PageLoadTracker::WebContentsHidden() { | |
| 68 if (!has_finished_) | |
|
Bryan McQuade
2015/09/24 18:21:52
let's chat about this and some possible alternativ
Charlie Harrison
2015/09/24 18:49:07
Sounds good. I'm hesitant about logging partial st
| |
| 69 page_load_stayed_in_foreground_ = false; | |
| 70 } | |
| 71 | |
| 72 void PageLoadTracker::Commit() { | |
| 73 has_commit_ = true; | |
| 74 } | |
| 75 | |
| 76 void PageLoadTracker::Finish() { | |
| 77 has_finished_ = true; | |
| 78 } | |
| 79 | |
| 80 bool PageLoadTracker::UpdateTiming(const PageLoadTiming& timing) { | |
| 81 // Throw away IPCs that are not relevant to the current navigation. | |
| 82 if (!timing_.navigation_start.is_null() && | |
| 83 timing_.navigation_start != timing.navigation_start) { | |
| 84 // TODO(csharrison) uma log a counter here | |
| 85 return false; | |
| 86 } | |
| 87 if (IsValidPageLoadTiming(timing)) { | |
| 88 timing_ = timing; | |
| 89 return true; | |
| 90 } | |
| 91 return false; | |
| 92 } | |
| 93 | |
| 94 void PageLoadTracker::RecordTimingHistograms() { | |
| 95 DCHECK(has_commit_); | |
| 96 if (!timing_.dom_content_loaded_event_start.is_zero()) { | |
| 97 if (page_load_stayed_in_foreground_) { | |
| 98 PAGE_LOAD_HISTOGRAM( | |
| 99 "PageLoad.Timing.NavigationToDOMContentLoadedEventFired", | |
| 100 timing_.dom_content_loaded_event_start); | |
| 101 } else { | |
| 102 PAGE_LOAD_HISTOGRAM( | |
| 103 "PageLoad.Timing.BG.NavigationToDOMContentLoadedEventFired", | |
| 104 timing_.dom_content_loaded_event_start); | |
| 105 } | |
| 106 } | |
| 107 if (!timing_.load_event_start.is_zero()) { | |
| 108 if (page_load_stayed_in_foreground_) { | |
| 109 PAGE_LOAD_HISTOGRAM("PageLoad.Timing.NavigationToLoadEventFired", | |
| 110 timing_.load_event_start); | |
| 111 } else { | |
| 112 PAGE_LOAD_HISTOGRAM("PageLoad.Timing.BG.NavigationToLoadEventFired", | |
| 113 timing_.load_event_start); | |
| 114 } | |
| 115 } | |
| 116 if (!timing_.first_layout.is_zero()) { | |
| 117 if (page_load_stayed_in_foreground_) { | |
| 118 PAGE_LOAD_HISTOGRAM("PageLoad.Timing.NavigationToFirstLayout", | |
| 119 timing_.first_layout); | |
| 120 } else { | |
| 121 PAGE_LOAD_HISTOGRAM("PageLoad.Timing.BG.NavigationToFirstLayout", | |
| 122 timing_.first_layout); | |
| 123 } | |
| 124 } | |
| 125 } | |
| 126 | |
| 52 MetricsWebContentsObserver::MetricsWebContentsObserver( | 127 MetricsWebContentsObserver::MetricsWebContentsObserver( |
| 53 content::WebContents* web_contents) | 128 content::WebContents* web_contents) |
| 54 : content::WebContentsObserver(web_contents) {} | 129 : content::WebContentsObserver(web_contents), in_foreground_(false) {} |
| 55 | 130 |
| 56 // This object is tied to a single WebContents for its entire lifetime. | 131 MetricsWebContentsObserver::~MetricsWebContentsObserver() {} |
| 57 MetricsWebContentsObserver::~MetricsWebContentsObserver() { | |
| 58 RecordTimingHistograms(); | |
| 59 } | |
| 60 | 132 |
| 61 bool MetricsWebContentsObserver::OnMessageReceived( | 133 bool MetricsWebContentsObserver::OnMessageReceived( |
| 62 const IPC::Message& message, | 134 const IPC::Message& message, |
| 63 content::RenderFrameHost* render_frame_host) { | 135 content::RenderFrameHost* render_frame_host) { |
| 64 DCHECK_CURRENTLY_ON(content::BrowserThread::UI); | 136 DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| 65 bool handled = true; | 137 bool handled = true; |
| 66 IPC_BEGIN_MESSAGE_MAP_WITH_PARAM(MetricsWebContentsObserver, message, | 138 IPC_BEGIN_MESSAGE_MAP_WITH_PARAM(MetricsWebContentsObserver, message, |
| 67 render_frame_host) | 139 render_frame_host) |
| 68 IPC_MESSAGE_HANDLER(PageLoadMetricsMsg_TimingUpdated, OnTimingUpdated) | 140 IPC_MESSAGE_HANDLER(PageLoadMetricsMsg_TimingUpdated, OnTimingUpdated) |
| 69 IPC_MESSAGE_UNHANDLED(handled = false) | 141 IPC_MESSAGE_UNHANDLED(handled = false) |
| 70 IPC_END_MESSAGE_MAP() | 142 IPC_END_MESSAGE_MAP() |
| 71 return handled; | 143 return handled; |
| 72 } | 144 } |
| 73 | 145 |
| 74 void MetricsWebContentsObserver::DidCommitNavigation( | 146 void MetricsWebContentsObserver::DidCommitNavigation( |
| 75 content::NavigationHandle* navigation_handle) { | 147 content::NavigationHandle* navigation_handle) { |
| 76 if (navigation_handle->IsInMainFrame() && !navigation_handle->IsSamePage()) | 148 if (!navigation_handle->IsInMainFrame()) |
| 77 RecordTimingHistograms(); | 149 return; |
| 78 if (IsRelevantNavigation(navigation_handle)) | 150 |
| 79 current_timing_.reset(new PageLoadTiming()); | 151 // If the provisional load is a relevant navigation (which we only know for |
| 152 // sure after commit), then we update |committed_load_|. We always remove a | |
| 153 // provisional navigation from |provisional_loads_| on commit. | |
| 154 if (IsRelevantNavigation(navigation_handle)) { | |
| 155 // Updating |committed_load_| will trigger RecordTimingHistograms on a | |
| 156 // previous PageLoadTracker. | |
| 157 committed_load_ = provisional_loads_.take_and_erase(navigation_handle); | |
| 158 DCHECK(committed_load_); | |
| 159 committed_load_->Commit(); | |
| 160 } else { | |
| 161 provisional_loads_.erase(navigation_handle); | |
|
Bryan McQuade
2015/09/24 18:21:52
should we also call
committed_load_.reset();
here
Charlie Harrison
2015/09/24 18:49:07
I'm not sure. Can't a same-page navigation commit
| |
| 162 } | |
| 80 } | 163 } |
| 81 | 164 |
| 82 // This will occur when the process for the main RenderFrameHost exits. | 165 void MetricsWebContentsObserver::DidStartNavigation( |
| 83 // This will happen with a normal exit or a crash. | 166 content::NavigationHandle* navigation_handle) { |
| 84 void MetricsWebContentsObserver::RenderProcessGone( | 167 if (!navigation_handle->IsInMainFrame()) |
| 85 base::TerminationStatus status) { | 168 return; |
| 86 RecordTimingHistograms(); | 169 // We can have two provisional loads in some cases. E.g. a same-site |
| 170 // navigation can have a concurrent cross-process navigation started | |
| 171 // from the omnibox. | |
| 172 DCHECK(provisional_loads_.size() < 2); | |
| 173 provisional_loads_.insert( | |
| 174 navigation_handle, make_scoped_ptr(new PageLoadTracker(in_foreground_))); | |
| 87 } | 175 } |
| 88 | 176 |
| 89 #define PAGE_LOAD_HISTOGRAM(name, sample) \ | 177 void MetricsWebContentsObserver::DidFinishLoad( |
| 90 UMA_HISTOGRAM_CUSTOM_TIMES(name, sample, \ | 178 content::RenderFrameHost* render_frame_host, |
| 91 base::TimeDelta::FromMilliseconds(10), \ | 179 const GURL& validated_url) { |
| 92 base::TimeDelta::FromMinutes(10), 100); | 180 if (render_frame_host == web_contents()->GetMainFrame() && committed_load_) |
| 181 committed_load_->Finish(); | |
| 182 } | |
| 183 | |
| 184 void MetricsWebContentsObserver::DidFailLoad( | |
| 185 content::RenderFrameHost* render_frame_host, | |
| 186 const GURL& validated_url, | |
| 187 int error_code, | |
| 188 const base::string16& error_description, | |
| 189 bool was_ignored_by_handler) { | |
| 190 if (render_frame_host == web_contents()->GetMainFrame() && committed_load_) | |
| 191 committed_load_->Finish(); | |
| 192 } | |
| 193 | |
| 194 void MetricsWebContentsObserver::WasShown() { | |
| 195 in_foreground_ = true; | |
| 196 } | |
| 197 void MetricsWebContentsObserver::WasHidden() { | |
| 198 in_foreground_ = false; | |
| 199 if (committed_load_) | |
| 200 committed_load_->WebContentsHidden(); | |
| 201 for (auto kv : provisional_loads_) { | |
|
Bryan McQuade
2015/09/24 18:21:52
auto is awesome but subtle. if you just say 'auto'
Charlie Harrison
2015/09/24 18:49:07
Yeah good idea.
| |
| 202 kv.second->WebContentsHidden(); | |
| 203 } | |
| 204 } | |
| 93 | 205 |
| 94 void MetricsWebContentsObserver::OnTimingUpdated( | 206 void MetricsWebContentsObserver::OnTimingUpdated( |
| 95 content::RenderFrameHost* render_frame_host, | 207 content::RenderFrameHost* render_frame_host, |
| 96 const PageLoadTiming& timing) { | 208 const PageLoadTiming& timing) { |
| 97 if (!current_timing_) | |
|
Bryan McQuade
2015/09/24 18:21:52
should we still continue to return if committed_lo
| |
| 98 return; | |
| 99 | |
| 100 // We may receive notifications from frames that have been navigated away | 209 // We may receive notifications from frames that have been navigated away |
| 101 // from. We simply ignore them. | 210 // from. We simply ignore them. |
| 102 if (render_frame_host != web_contents()->GetMainFrame()) | 211 if (render_frame_host != web_contents()->GetMainFrame()) |
| 103 return; | 212 return; |
| 104 | 213 |
| 105 // For urls like chrome://newtab, the renderer and browser disagree, | 214 // For urls like chrome://newtab, the renderer and browser disagree, |
| 106 // so we have to double check that the renderer isn't sending data from a | 215 // so we have to double check that the renderer isn't sending data from a |
| 107 // bad url like https://www.google.com/_/chrome/newtab. | 216 // bad url like https://www.google.com/_/chrome/newtab. |
| 108 if (!web_contents()->GetLastCommittedURL().SchemeIsHTTPOrHTTPS()) | 217 if (!web_contents()->GetLastCommittedURL().SchemeIsHTTPOrHTTPS()) |
| 109 return; | 218 return; |
| 110 | 219 |
| 111 // Throw away IPCs that are not relevant to the current navigation. | 220 committed_load_->UpdateTiming(timing); |
| 112 if (!current_timing_->navigation_start.is_null() && | |
| 113 timing.navigation_start != current_timing_->navigation_start) { | |
| 114 // TODO(csharrison) uma log a counter here | |
| 115 return; | |
| 116 } | |
| 117 | |
| 118 *current_timing_ = timing; | |
| 119 } | |
| 120 | |
| 121 void MetricsWebContentsObserver::RecordTimingHistograms() { | |
| 122 if (!current_timing_ || !IsValidPageLoadTiming(*current_timing_)) | |
| 123 return; | |
| 124 | |
| 125 if (!current_timing_->dom_content_loaded_event_start.is_zero()) { | |
| 126 PAGE_LOAD_HISTOGRAM( | |
| 127 "PageLoad.Timing.NavigationToDOMContentLoadedEventFired", | |
| 128 current_timing_->dom_content_loaded_event_start); | |
| 129 } | |
| 130 | |
| 131 if (!current_timing_->load_event_start.is_zero()) { | |
| 132 PAGE_LOAD_HISTOGRAM("PageLoad.Timing.NavigationToLoadEventFired", | |
| 133 current_timing_->load_event_start); | |
| 134 } | |
| 135 | |
| 136 if (!current_timing_->first_layout.is_zero()) { | |
| 137 PAGE_LOAD_HISTOGRAM("PageLoad.Timing.NavigationToFirstLayout", | |
| 138 current_timing_->first_layout); | |
| 139 } | |
| 140 current_timing_.reset(); | |
| 141 } | 221 } |
| 142 | 222 |
| 143 bool MetricsWebContentsObserver::IsRelevantNavigation( | 223 bool MetricsWebContentsObserver::IsRelevantNavigation( |
| 144 content::NavigationHandle* navigation_handle) { | 224 content::NavigationHandle* navigation_handle) { |
| 145 // The url we see from the renderer side is not always the same as what | 225 // The url we see from the renderer side is not always the same as what |
| 146 // we see from the browser side (e.g. chrome://newtab). We want to be | 226 // we see from the browser side (e.g. chrome://newtab). We want to be |
| 147 // sure here that we aren't logging UMA for internal pages. | 227 // sure here that we aren't logging UMA for internal pages. |
| 148 const GURL& browser_url = web_contents()->GetLastCommittedURL(); | 228 const GURL& browser_url = web_contents()->GetLastCommittedURL(); |
| 149 return navigation_handle->IsInMainFrame() && | 229 return navigation_handle->IsInMainFrame() && |
| 150 !navigation_handle->IsSamePage() && | 230 !navigation_handle->IsSamePage() && |
| 151 navigation_handle->HasCommittedDocument() && | 231 navigation_handle->HasCommittedDocument() && |
| 152 navigation_handle->GetURL().SchemeIsHTTPOrHTTPS() && | 232 navigation_handle->GetURL().SchemeIsHTTPOrHTTPS() && |
| 153 browser_url.SchemeIsHTTPOrHTTPS(); | 233 browser_url.SchemeIsHTTPOrHTTPS(); |
| 154 } | 234 } |
| 155 | 235 |
| 156 } // namespace page_load_metrics | 236 } // namespace page_load_metrics |
| OLD | NEW |