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
3 // found in the LICENSE file.
4 
5 #include "components/page_load_metrics/browser/metrics_web_contents_observer.h"
6 
7 #include <algorithm>
8 #include <ostream>
9 #include <string>
10 #include <utility>
11 
12 #include "base/location.h"
13 #include "base/memory/ptr_util.h"
14 #include "base/metrics/histogram_macros.h"
15 #include "components/page_load_metrics/browser/page_load_metrics_embedder_interface.h"
16 #include "components/page_load_metrics/browser/page_load_metrics_update_dispatcher.h"
17 #include "components/page_load_metrics/browser/page_load_metrics_util.h"
18 #include "components/page_load_metrics/browser/page_load_tracker.h"
19 #include "components/page_load_metrics/common/page_load_timing.h"
20 #include "content/public/browser/back_forward_cache.h"
21 #include "content/public/browser/browser_thread.h"
22 #include "content/public/browser/cookie_access_details.h"
23 #include "content/public/browser/global_request_id.h"
24 #include "content/public/browser/global_routing_id.h"
25 #include "content/public/browser/media_player_id.h"
26 #include "content/public/browser/navigation_details.h"
27 #include "content/public/browser/navigation_handle.h"
28 #include "content/public/browser/render_frame_host.h"
29 #include "content/public/browser/render_process_host.h"
30 #include "content/public/browser/render_view_host.h"
31 #include "content/public/browser/web_contents.h"
32 #include "content/public/browser/web_contents_observer.h"
33 #include "content/public/browser/web_contents_user_data.h"
34 #include "net/base/ip_endpoint.h"
35 #include "net/base/net_errors.h"
36 #include "services/network/public/mojom/fetch_api.mojom-shared.h"
37 #include "third_party/blink/public/common/loader/resource_type_util.h"
38 #include "third_party/blink/public/common/mobile_metrics/mobile_friendliness.h"
39 #include "ui/base/page_transition_types.h"
40 
41 namespace page_load_metrics {
42 
43 namespace {
44 
45 // Returns the HTTP status code for the current page, or -1 if no status code
46 // is available. Can only be called if the |navigation_handle| has committed.
GetHttpStatusCode(content::NavigationHandle * navigation_handle)47 int GetHttpStatusCode(content::NavigationHandle* navigation_handle) {
48   DCHECK(navigation_handle->HasCommitted());
49   const net::HttpResponseHeaders* response_headers =
50       navigation_handle->GetResponseHeaders();
51   if (!response_headers)
52     return -1;
53   return response_headers->response_code();
54 }
55 
CreateUserInitiatedInfo(content::NavigationHandle * navigation_handle)56 UserInitiatedInfo CreateUserInitiatedInfo(
57     content::NavigationHandle* navigation_handle) {
58   if (!navigation_handle->IsRendererInitiated())
59     return UserInitiatedInfo::BrowserInitiated();
60 
61   return UserInitiatedInfo::RenderInitiated(
62       navigation_handle->HasUserGesture(),
63       !navigation_handle->NavigationInputStart().is_null());
64 }
65 
66 }  // namespace
67 
68 // static
RecordFeatureUsage(content::RenderFrameHost * render_frame_host,const mojom::PageLoadFeatures & new_features)69 void MetricsWebContentsObserver::RecordFeatureUsage(
70     content::RenderFrameHost* render_frame_host,
71     const mojom::PageLoadFeatures& new_features) {
72   content::WebContents* web_contents =
73       content::WebContents::FromRenderFrameHost(render_frame_host);
74   MetricsWebContentsObserver* observer =
75       MetricsWebContentsObserver::FromWebContents(web_contents);
76   if (observer)
77     observer->OnBrowserFeatureUsage(render_frame_host, new_features);
78 }
79 
80 // static
CreateForWebContents(content::WebContents * web_contents,std::unique_ptr<PageLoadMetricsEmbedderInterface> embedder_interface)81 MetricsWebContentsObserver* MetricsWebContentsObserver::CreateForWebContents(
82     content::WebContents* web_contents,
83     std::unique_ptr<PageLoadMetricsEmbedderInterface> embedder_interface) {
84   DCHECK(web_contents);
85 
86   MetricsWebContentsObserver* metrics = FromWebContents(web_contents);
87   if (!metrics) {
88     metrics = new MetricsWebContentsObserver(web_contents,
89                                              std::move(embedder_interface));
90     web_contents->SetUserData(UserDataKey(), base::WrapUnique(metrics));
91   }
92   return metrics;
93 }
94 
~MetricsWebContentsObserver()95 MetricsWebContentsObserver::~MetricsWebContentsObserver() {}
96 
WebContentsWillSoonBeDestroyed()97 void MetricsWebContentsObserver::WebContentsWillSoonBeDestroyed() {
98   web_contents_will_soon_be_destroyed_ = true;
99 }
100 
WebContentsDestroyed()101 void MetricsWebContentsObserver::WebContentsDestroyed() {
102   // TODO(csharrison): Use a more user-initiated signal for CLOSE.
103   NotifyPageEndAllLoads(END_CLOSE, UserInitiatedInfo::NotUserInitiated());
104 
105   // We tear down PageLoadTrackers in WebContentsDestroyed, rather than in the
106   // destructor, since |web_contents()| returns nullptr in the destructor, and
107   // PageLoadMetricsObservers can cause code to execute that wants to be able to
108   // access the current WebContents.
109   committed_load_ = nullptr;
110   ukm_smoothness_data_ = {};
111   provisional_loads_.clear();
112   aborted_provisional_loads_.clear();
113 
114   for (auto& observer : testing_observers_)
115     observer.OnGoingAway();
116 }
117 
RegisterInputEventObserver(content::RenderViewHost * host)118 void MetricsWebContentsObserver::RegisterInputEventObserver(
119     content::RenderViewHost* host) {
120   if (host != nullptr)
121     host->GetWidget()->AddInputEventObserver(this);
122 }
123 
UnregisterInputEventObserver(content::RenderViewHost * host)124 void MetricsWebContentsObserver::UnregisterInputEventObserver(
125     content::RenderViewHost* host) {
126   if (host != nullptr)
127     host->GetWidget()->RemoveInputEventObserver(this);
128 }
129 
RenderViewHostChanged(content::RenderViewHost * old_host,content::RenderViewHost * new_host)130 void MetricsWebContentsObserver::RenderViewHostChanged(
131     content::RenderViewHost* old_host,
132     content::RenderViewHost* new_host) {
133   UnregisterInputEventObserver(old_host);
134   RegisterInputEventObserver(new_host);
135 }
136 
FrameDeleted(content::RenderFrameHost * rfh)137 void MetricsWebContentsObserver::FrameDeleted(content::RenderFrameHost* rfh) {
138   if (committed_load_)
139     committed_load_->FrameDeleted(rfh);
140 }
141 
RenderFrameDeleted(content::RenderFrameHost * rfh)142 void MetricsWebContentsObserver::RenderFrameDeleted(
143     content::RenderFrameHost* rfh) {
144   // PageLoadTracker can be associated only with a main frame.
145   if (rfh->GetParent())
146     return;
147   back_forward_cached_pages_.erase(rfh);
148 }
149 
MediaStartedPlaying(const content::WebContentsObserver::MediaPlayerInfo & video_type,const content::MediaPlayerId & id)150 void MetricsWebContentsObserver::MediaStartedPlaying(
151     const content::WebContentsObserver::MediaPlayerInfo& video_type,
152     const content::MediaPlayerId& id) {
153   if (!id.render_frame_host->GetMainFrame()->IsCurrent()) {
154     // Ignore media that starts playing in a page that was navigated away
155     // from.
156     return;
157   }
158 
159   if (committed_load_)
160     committed_load_->MediaStartedPlaying(video_type, id.render_frame_host);
161 }
162 
WillStartNavigationRequest(content::NavigationHandle * navigation_handle)163 void MetricsWebContentsObserver::WillStartNavigationRequest(
164     content::NavigationHandle* navigation_handle) {
165   // Same-document navigations should never go through
166   // WillStartNavigationRequest.
167   DCHECK(!navigation_handle->IsSameDocument());
168 
169   if (!navigation_handle->IsInMainFrame())
170     return;
171 
172   WillStartNavigationRequestImpl(navigation_handle);
173   has_navigated_ = true;
174 }
175 
MetricsWebContentsObserver(content::WebContents * web_contents,std::unique_ptr<PageLoadMetricsEmbedderInterface> embedder_interface)176 MetricsWebContentsObserver::MetricsWebContentsObserver(
177     content::WebContents* web_contents,
178     std::unique_ptr<PageLoadMetricsEmbedderInterface> embedder_interface)
179     : content::WebContentsObserver(web_contents),
180       in_foreground_(web_contents->GetVisibility() !=
181                      content::Visibility::HIDDEN),
182       embedder_interface_(std::move(embedder_interface)),
183       has_navigated_(false),
184       page_load_metrics_receiver_(web_contents, this) {
185   // Prerenders erroneously report that they are initially visible, so we
186   // manually override visibility state for prerender.
187   if (embedder_interface_->IsPrerender(web_contents))
188     in_foreground_ = false;
189 
190   RegisterInputEventObserver(web_contents->GetRenderViewHost());
191 }
192 
WillStartNavigationRequestImpl(content::NavigationHandle * navigation_handle)193 void MetricsWebContentsObserver::WillStartNavigationRequestImpl(
194     content::NavigationHandle* navigation_handle) {
195   UserInitiatedInfo user_initiated_info(
196       CreateUserInitiatedInfo(navigation_handle));
197   std::unique_ptr<PageLoadTracker> last_aborted =
198       NotifyAbortedProvisionalLoadsNewNavigation(navigation_handle,
199                                                  user_initiated_info);
200 
201   int chain_size_same_url = 0;
202   int chain_size = 0;
203   if (last_aborted) {
204     if (last_aborted->MatchesOriginalNavigation(navigation_handle)) {
205       chain_size_same_url = last_aborted->aborted_chain_size_same_url() + 1;
206     } else if (last_aborted->aborted_chain_size_same_url() > 0) {
207       LogAbortChainSameURLHistogram(
208           last_aborted->aborted_chain_size_same_url());
209     }
210     chain_size = last_aborted->aborted_chain_size() + 1;
211   }
212 
213   if (!ShouldTrackMainFrameNavigation(navigation_handle))
214     return;
215 
216   // Pass in the last committed url to the PageLoadTracker. If the MWCO has
217   // never observed a committed load, use the last committed url from this
218   // WebContent's opener. This is more accurate than using referrers due to
219   // referrer sanitizing and origin referrers. Note that this could potentially
220   // be inaccurate if the opener has since navigated.
221   content::RenderFrameHost* opener = web_contents()->GetOpener();
222   const GURL& opener_url = !has_navigated_ && opener
223                                ? opener->GetLastCommittedURL()
224                                : GURL::EmptyGURL();
225   const GURL& currently_committed_url =
226       committed_load_ ? committed_load_->url() : opener_url;
227 
228   // Passing raw pointers to |observers_| and |embedder_interface_| is safe
229   // because the MetricsWebContentsObserver owns them both list and they are
230   // torn down after the PageLoadTracker. The PageLoadTracker does not hold on
231   // to |committed_load_| or |navigation_handle| beyond the scope of the
232   // constructor.
233   auto insertion_result = provisional_loads_.insert(std::make_pair(
234       navigation_handle,
235       std::make_unique<PageLoadTracker>(
236           in_foreground_, embedder_interface_.get(), currently_committed_url,
237           !has_navigated_, navigation_handle, user_initiated_info, chain_size,
238           chain_size_same_url)));
239   DCHECK(insertion_result.second)
240       << "provisional_loads_ already contains NavigationHandle.";
241   for (auto& observer : testing_observers_)
242     observer.OnTrackerCreated(insertion_result.first->second.get());
243 }
244 
WillProcessNavigationResponse(content::NavigationHandle * navigation_handle)245 void MetricsWebContentsObserver::WillProcessNavigationResponse(
246     content::NavigationHandle* navigation_handle) {
247   auto it = provisional_loads_.find(navigation_handle);
248   if (it == provisional_loads_.end())
249     return;
250   it->second->WillProcessNavigationResponse(navigation_handle);
251 }
252 
GetTrackerOrNullForRequest(const content::GlobalRequestID & request_id,content::RenderFrameHost * render_frame_host_or_null,network::mojom::RequestDestination request_destination,base::TimeTicks creation_time)253 PageLoadTracker* MetricsWebContentsObserver::GetTrackerOrNullForRequest(
254     const content::GlobalRequestID& request_id,
255     content::RenderFrameHost* render_frame_host_or_null,
256     network::mojom::RequestDestination request_destination,
257     base::TimeTicks creation_time) {
258   if (request_destination == network::mojom::RequestDestination::kDocument) {
259     DCHECK(request_id != content::GlobalRequestID());
260     // The main frame request can complete either before or after commit, so we
261     // look at both provisional loads and the committed load to find a
262     // PageLoadTracker with a matching request id. See https://goo.gl/6TzCYN for
263     // more details.
264     for (const auto& kv : provisional_loads_) {
265       PageLoadTracker* candidate = kv.second.get();
266       if (candidate->HasMatchingNavigationRequestID(request_id)) {
267         return candidate;
268       }
269     }
270     if (committed_load_ &&
271         committed_load_->HasMatchingNavigationRequestID(request_id)) {
272       return committed_load_.get();
273     }
274   } else {
275     // Non main frame resources are always associated with the currently
276     // committed load. If the resource request was started before this
277     // navigation then it should be ignored.
278     if (!committed_load_ || creation_time < committed_load_->navigation_start())
279       return nullptr;
280 
281     // Sub-frame resources have a null RFH when browser-side navigation is
282     // enabled, so we can't perform the RFH check below for them.
283     //
284     // TODO(bmcquade): consider tracking GlobalRequestIDs for sub-frame
285     // navigations in each PageLoadTracker, and performing a lookup for
286     // sub-frames similar to the main-frame lookup above.
287     if (blink::IsRequestDestinationFrame(request_destination))
288       return committed_load_.get();
289 
290     // This was originally a DCHECK but it fails when the document load happened
291     // after client certificate selection.
292     if (!render_frame_host_or_null)
293       return nullptr;
294 
295     // There is a race here: a completed resource for the previously committed
296     // page can arrive after the new page has committed. In this case, we may
297     // attribute the resource to the wrong page load. We do our best to guard
298     // against this by verifying that the RFH for the resource matches the RFH
299     // for the currently committed load, however there are cases where the same
300     // RFH is used across page loads (same origin navigations, as well as some
301     // cross-origin render-initiated navigations).
302     //
303     // TODO(crbug.com/738577): use a DocumentId here instead, to eliminate this
304     // race.
305     if (render_frame_host_or_null->GetMainFrame()->IsCurrent()) {
306       return committed_load_.get();
307     }
308   }
309   return nullptr;
310 }
311 
ResourceLoadComplete(content::RenderFrameHost * render_frame_host,const content::GlobalRequestID & request_id,const blink::mojom::ResourceLoadInfo & resource_load_info)312 void MetricsWebContentsObserver::ResourceLoadComplete(
313     content::RenderFrameHost* render_frame_host,
314     const content::GlobalRequestID& request_id,
315     const blink::mojom::ResourceLoadInfo& resource_load_info) {
316   if (!resource_load_info.final_url.SchemeIsHTTPOrHTTPS())
317     return;
318 
319   PageLoadTracker* tracker = GetTrackerOrNullForRequest(
320       request_id, render_frame_host, resource_load_info.request_destination,
321       resource_load_info.load_timing_info.request_start);
322   if (tracker) {
323     // TODO(crbug.com/721403): Fill in data reduction proxy fields when this is
324     // available in the network service.
325     // int original_content_length =
326     //     was_cached ? 0
327     //                : data_reduction_proxy::util::EstimateOriginalBodySize(
328     //                      request, lofi_decider);
329     int original_content_length = 0;
330     std::unique_ptr<data_reduction_proxy::DataReductionProxyData>
331         data_reduction_proxy_data;
332 
333     const blink::mojom::CommonNetworkInfoPtr& network_info =
334         resource_load_info.network_info;
335     ExtraRequestCompleteInfo extra_request_complete_info(
336         url::Origin::Create(resource_load_info.final_url),
337         network_info->remote_endpoint.value(),
338         render_frame_host->GetFrameTreeNodeId(), resource_load_info.was_cached,
339         resource_load_info.raw_body_bytes, original_content_length,
340         std::move(data_reduction_proxy_data),
341         resource_load_info.request_destination, resource_load_info.net_error,
342         std::make_unique<net::LoadTimingInfo>(
343             resource_load_info.load_timing_info));
344     tracker->OnLoadedResource(extra_request_complete_info);
345   }
346 }
347 
FrameReceivedFirstUserActivation(content::RenderFrameHost * render_frame_host)348 void MetricsWebContentsObserver::FrameReceivedFirstUserActivation(
349     content::RenderFrameHost* render_frame_host) {
350   if (committed_load_)
351     committed_load_->FrameReceivedFirstUserActivation(render_frame_host);
352 }
353 
FrameDisplayStateChanged(content::RenderFrameHost * render_frame_host,bool is_display_none)354 void MetricsWebContentsObserver::FrameDisplayStateChanged(
355     content::RenderFrameHost* render_frame_host,
356     bool is_display_none) {
357   if (committed_load_)
358     committed_load_->FrameDisplayStateChanged(render_frame_host,
359                                               is_display_none);
360 }
361 
FrameSizeChanged(content::RenderFrameHost * render_frame_host,const gfx::Size & frame_size)362 void MetricsWebContentsObserver::FrameSizeChanged(
363     content::RenderFrameHost* render_frame_host,
364     const gfx::Size& frame_size) {
365   if (committed_load_)
366     committed_load_->FrameSizeChanged(render_frame_host, frame_size);
367 }
368 
OnCookiesAccessed(content::NavigationHandle * navigation,const content::CookieAccessDetails & details)369 void MetricsWebContentsObserver::OnCookiesAccessed(
370     content::NavigationHandle* navigation,
371     const content::CookieAccessDetails& details) {
372   OnCookiesAccessedImpl(details);
373 }
374 
OnCookiesAccessed(content::RenderFrameHost * rfh,const content::CookieAccessDetails & details)375 void MetricsWebContentsObserver::OnCookiesAccessed(
376     content::RenderFrameHost* rfh,
377     const content::CookieAccessDetails& details) {
378   OnCookiesAccessedImpl(details);
379 }
380 
OnCookiesAccessedImpl(const content::CookieAccessDetails & details)381 void MetricsWebContentsObserver::OnCookiesAccessedImpl(
382     const content::CookieAccessDetails& details) {
383   if (!committed_load_)
384     return;
385 
386   // TODO(altimin): Propagate |CookieAccessDetails| further.
387   switch (details.type) {
388     case content::CookieAccessDetails::Type::kRead:
389       committed_load_->OnCookiesRead(details.url, details.first_party_url,
390                                      details.cookie_list,
391                                      details.blocked_by_policy);
392       break;
393     case content::CookieAccessDetails::Type::kChange:
394       for (const auto& cookie : details.cookie_list) {
395         committed_load_->OnCookieChange(details.url, details.first_party_url,
396                                         cookie, details.blocked_by_policy);
397       }
398       break;
399   }
400 }
401 
DidActivatePortal(content::WebContents * predecessor_web_contents,base::TimeTicks activation_time)402 void MetricsWebContentsObserver::DidActivatePortal(
403     content::WebContents* predecessor_web_contents,
404     base::TimeTicks activation_time) {
405   // The |predecessor_web_contents| is the WebContents that instantiated the
406   // portal.
407   MetricsWebContentsObserver* predecessor_observer =
408       MetricsWebContentsObserver::FromWebContents(predecessor_web_contents);
409   // We only track the portal activation if the predecessor is also being
410   // tracked.
411   if (!committed_load_ || !predecessor_observer ||
412       !predecessor_observer->committed_load_) {
413     return;
414   }
415   committed_load_->DidActivatePortal(activation_time);
416 }
417 
OnStorageAccessed(const GURL & url,const GURL & first_party_url,bool blocked_by_policy,StorageType storage_type)418 void MetricsWebContentsObserver::OnStorageAccessed(const GURL& url,
419                                                    const GURL& first_party_url,
420                                                    bool blocked_by_policy,
421                                                    StorageType storage_type) {
422   if (committed_load_)
423     committed_load_->OnStorageAccessed(url, first_party_url, blocked_by_policy,
424                                        storage_type);
425 }
426 
427 const PageLoadMetricsObserverDelegate&
GetDelegateForCommittedLoad()428 MetricsWebContentsObserver::GetDelegateForCommittedLoad() {
429   DCHECK(committed_load_);
430   return *committed_load_.get();
431 }
432 
ReadyToCommitNavigation(content::NavigationHandle * navigation_handle)433 void MetricsWebContentsObserver::ReadyToCommitNavigation(
434     content::NavigationHandle* navigation_handle) {
435   if (committed_load_)
436     committed_load_->ReadyToCommitNavigation(navigation_handle);
437 }
438 
DidFinishNavigation(content::NavigationHandle * navigation_handle)439 void MetricsWebContentsObserver::DidFinishNavigation(
440     content::NavigationHandle* navigation_handle) {
441   if (!navigation_handle->IsInMainFrame()) {
442     if (committed_load_ && navigation_handle->GetParentFrame() &&
443         navigation_handle->GetParentFrame()->GetMainFrame()->IsCurrent()) {
444       committed_load_->DidFinishSubFrameNavigation(navigation_handle);
445       committed_load_->metrics_update_dispatcher()->DidFinishSubFrameNavigation(
446           navigation_handle);
447     }
448     return;
449   }
450 
451   // Not all navigations trigger the WillStartNavigationRequest callback (for
452   // example, navigations to about:blank). DidFinishNavigation is guaranteed to
453   // be called for every navigation, so we also update has_navigated_ here, to
454   // ensure it is set consistently for all navigations.
455   has_navigated_ = true;
456 
457   std::unique_ptr<PageLoadTracker> navigation_handle_tracker(
458       std::move(provisional_loads_[navigation_handle]));
459   provisional_loads_.erase(navigation_handle);
460 
461   // Ignore same-document navigations.
462   if (navigation_handle->HasCommitted() &&
463       navigation_handle->IsSameDocument()) {
464     if (navigation_handle_tracker)
465       navigation_handle_tracker->StopTracking();
466     if (committed_load_)
467       committed_load_->DidCommitSameDocumentNavigation(navigation_handle);
468     return;
469   }
470 
471   // Ignore internally generated aborts for navigations with HTTP responses that
472   // don't commit, such as HTTP 204 responses and downloads.
473   if (!navigation_handle->HasCommitted() &&
474       navigation_handle->GetNetErrorCode() == net::ERR_ABORTED &&
475       navigation_handle->GetResponseHeaders()) {
476     if (navigation_handle_tracker) {
477       navigation_handle_tracker->DidInternalNavigationAbort(navigation_handle);
478       navigation_handle_tracker->StopTracking();
479     }
480     return;
481   }
482 
483   if (navigation_handle->HasCommitted()) {
484     // A new navigation is committing, so finalize and destroy the tracker for
485     // the currently committed navigation.
486     FinalizeCurrentlyCommittedLoad(navigation_handle,
487                                    navigation_handle_tracker.get());
488     // Transfers the ownership of |committed_load_|. This |committed_load_|
489     // might be reused later when restoring the page from the cache.
490     MaybeStorePageLoadTrackerForBackForwardCache(navigation_handle,
491                                                  std::move(committed_load_));
492     // If |navigation_handle| is a back-forward cache navigation, an associated
493     // PageLoadTracker is restored into |committed_load_|.
494     if (MaybeRestorePageLoadTrackerForBackForwardCache(navigation_handle))
495       return;
496   }
497 
498   if (!navigation_handle_tracker)
499     return;
500 
501   if (!ShouldTrackMainFrameNavigation(navigation_handle)) {
502     navigation_handle_tracker->StopTracking();
503     return;
504   }
505 
506   if (navigation_handle->HasCommitted()) {
507     HandleCommittedNavigationForTrackedLoad(
508         navigation_handle, std::move(navigation_handle_tracker));
509   } else {
510     HandleFailedNavigationForTrackedLoad(navigation_handle,
511                                          std::move(navigation_handle_tracker));
512   }
513 }
514 
515 // Handle a pre-commit error. Navigations that result in an error page will be
516 // ignored.
HandleFailedNavigationForTrackedLoad(content::NavigationHandle * navigation_handle,std::unique_ptr<PageLoadTracker> tracker)517 void MetricsWebContentsObserver::HandleFailedNavigationForTrackedLoad(
518     content::NavigationHandle* navigation_handle,
519     std::unique_ptr<PageLoadTracker> tracker) {
520   const base::TimeTicks now = base::TimeTicks::Now();
521   tracker->FailedProvisionalLoad(navigation_handle, now);
522 
523   const net::Error error = navigation_handle->GetNetErrorCode();
524 
525   // net::OK: This case occurs when the NavigationHandle finishes and reports
526   // !HasCommitted(), but reports no net::Error. This represents the navigation
527   // being stopped by the user before it was ready to commit.
528   // net::ERR_ABORTED: An aborted provisional load has error net::ERR_ABORTED.
529   const bool is_aborted_provisional_load =
530       error == net::OK || error == net::ERR_ABORTED;
531 
532   // If is_aborted_provisional_load, the page end reason is not yet known, and
533   // will be updated as additional information is available from subsequent
534   // navigations.
535   tracker->NotifyPageEnd(
536       is_aborted_provisional_load ? END_OTHER : END_PROVISIONAL_LOAD_FAILED,
537       UserInitiatedInfo::NotUserInitiated(), now, true);
538 
539   if (is_aborted_provisional_load)
540     aborted_provisional_loads_.push_back(std::move(tracker));
541 }
542 
HandleCommittedNavigationForTrackedLoad(content::NavigationHandle * navigation_handle,std::unique_ptr<PageLoadTracker> tracker)543 void MetricsWebContentsObserver::HandleCommittedNavigationForTrackedLoad(
544     content::NavigationHandle* navigation_handle,
545     std::unique_ptr<PageLoadTracker> tracker) {
546   committed_load_ = std::move(tracker);
547   committed_load_->Commit(navigation_handle);
548   DCHECK(committed_load_->did_commit());
549 
550   for (auto& observer : testing_observers_)
551     observer.OnCommit(committed_load_.get());
552 
553   if (ukm_smoothness_data_.IsValid()) {
554     auto* render_frame_host = navigation_handle->GetRenderFrameHost();
555     const bool is_main_frame =
556         render_frame_host && render_frame_host->GetParent() == nullptr;
557     if (is_main_frame) {
558       committed_load_->metrics_update_dispatcher()
559           ->SetUpSharedMemoryForSmoothness(render_frame_host,
560                                            std::move(ukm_smoothness_data_));
561     }
562   }
563 }
564 
MaybeStorePageLoadTrackerForBackForwardCache(content::NavigationHandle * next_navigation_handle,std::unique_ptr<PageLoadTracker> previously_committed_load)565 void MetricsWebContentsObserver::MaybeStorePageLoadTrackerForBackForwardCache(
566     content::NavigationHandle* next_navigation_handle,
567     std::unique_ptr<PageLoadTracker> previously_committed_load) {
568   if (!previously_committed_load)
569     return;
570 
571   content::RenderFrameHost* previous_frame = content::RenderFrameHost::FromID(
572       next_navigation_handle->GetPreviousRenderFrameHostId());
573 
574   // The PageLoadTracker is associated with a bfcached document if:
575   bool is_back_forward_cache =
576       // 1. the frame being navigated away from was not already deleted
577       previous_frame &&
578       // 2. the previous frame is in the BFCache
579       previous_frame->IsInBackForwardCache();
580 
581   if (!is_back_forward_cache)
582     return;
583 
584   previously_committed_load->OnEnterBackForwardCache();
585 
586   back_forward_cached_pages_.emplace(previous_frame,
587                                      std::move(previously_committed_load));
588 }
589 
MaybeRestorePageLoadTrackerForBackForwardCache(content::NavigationHandle * navigation_handle)590 bool MetricsWebContentsObserver::MaybeRestorePageLoadTrackerForBackForwardCache(
591     content::NavigationHandle* navigation_handle) {
592   if (!navigation_handle->IsServedFromBackForwardCache())
593     return false;
594 
595   auto it =
596       back_forward_cached_pages_.find(navigation_handle->GetRenderFrameHost());
597 
598   // There are some cases that the PageLoadTracker does not exist. For example,
599   // if a page is put into the cache before MetricsWebContents is created,
600   // |back_forward_cached_pages_| is empty.
601   if (it == back_forward_cached_pages_.end())
602     return false;
603 
604   committed_load_ = std::move(it->second);
605   back_forward_cached_pages_.erase(it);
606   committed_load_->OnRestoreFromBackForwardCache(navigation_handle);
607 
608   for (auto& observer : testing_observers_)
609     observer.OnRestoredFromBackForwardCache(committed_load_.get());
610 
611   return true;
612 }
613 
FinalizeCurrentlyCommittedLoad(content::NavigationHandle * newly_committed_navigation,PageLoadTracker * newly_committed_navigation_tracker)614 void MetricsWebContentsObserver::FinalizeCurrentlyCommittedLoad(
615     content::NavigationHandle* newly_committed_navigation,
616     PageLoadTracker* newly_committed_navigation_tracker) {
617   UserInitiatedInfo user_initiated_info =
618       newly_committed_navigation_tracker
619           ? newly_committed_navigation_tracker->user_initiated_info()
620           : CreateUserInitiatedInfo(newly_committed_navigation);
621 
622   // Notify other loads that they may have been aborted by this committed
623   // load. is_certainly_browser_timestamp is set to false because
624   // NavigationStart() could be set in either the renderer or browser process.
625   NotifyPageEndAllLoadsWithTimestamp(
626       EndReasonForPageTransition(
627           newly_committed_navigation->GetPageTransition()),
628       user_initiated_info, newly_committed_navigation->NavigationStart(),
629       /*is_certainly_browser_timestamp=*/false);
630 
631   if (committed_load_) {
632     bool is_non_user_initiated_client_redirect =
633         !IsNavigationUserInitiated(newly_committed_navigation) &&
634         (newly_committed_navigation->GetPageTransition() &
635          ui::PAGE_TRANSITION_CLIENT_REDIRECT) != 0;
636     if (is_non_user_initiated_client_redirect) {
637       committed_load_->NotifyClientRedirectTo(newly_committed_navigation);
638     }
639 
640     // Ensure that any pending update gets dispatched.
641     committed_load_->metrics_update_dispatcher()->FlushPendingTimingUpdates();
642   }
643 }
644 
NavigationStopped()645 void MetricsWebContentsObserver::NavigationStopped() {
646   // TODO(csharrison): Use a more user-initiated signal for STOP.
647   NotifyPageEndAllLoads(END_STOP, UserInitiatedInfo::NotUserInitiated());
648 }
649 
OnInputEvent(const blink::WebInputEvent & event)650 void MetricsWebContentsObserver::OnInputEvent(
651     const blink::WebInputEvent& event) {
652   // Ignore browser navigation or reload which comes with type Undefined.
653   if (event.GetType() == blink::WebInputEvent::Type::kUndefined)
654     return;
655 
656   if (committed_load_)
657     committed_load_->OnInputEvent(event);
658 }
659 
FlushMetricsOnAppEnterBackground()660 void MetricsWebContentsObserver::FlushMetricsOnAppEnterBackground() {
661   // Note that, while a call to FlushMetricsOnAppEnterBackground usually
662   // indicates that the app is about to be backgrounded, there are cases where
663   // the app may not end up getting backgrounded. Thus, we should not assume
664   // anything about foreground / background state of the associated tab as part
665   // of this method call.
666 
667   if (committed_load_)
668     committed_load_->FlushMetricsOnAppEnterBackground();
669   for (const auto& kv : provisional_loads_) {
670     kv.second->FlushMetricsOnAppEnterBackground();
671   }
672   for (const auto& tracker : aborted_provisional_loads_) {
673     tracker->FlushMetricsOnAppEnterBackground();
674   }
675 }
676 
DidRedirectNavigation(content::NavigationHandle * navigation_handle)677 void MetricsWebContentsObserver::DidRedirectNavigation(
678     content::NavigationHandle* navigation_handle) {
679   if (!navigation_handle->IsInMainFrame())
680     return;
681   auto it = provisional_loads_.find(navigation_handle);
682   if (it == provisional_loads_.end())
683     return;
684   it->second->Redirect(navigation_handle);
685 }
686 
OnVisibilityChanged(content::Visibility visibility)687 void MetricsWebContentsObserver::OnVisibilityChanged(
688     content::Visibility visibility) {
689   if (web_contents_will_soon_be_destroyed_)
690     return;
691 
692   bool was_in_foreground = in_foreground_;
693   in_foreground_ = visibility == content::Visibility::VISIBLE;
694   if (in_foreground_ == was_in_foreground)
695     return;
696 
697   if (in_foreground_) {
698     if (committed_load_)
699       committed_load_->PageShown();
700     for (const auto& kv : provisional_loads_) {
701       kv.second->PageShown();
702     }
703   } else {
704     if (committed_load_)
705       committed_load_->PageHidden();
706     for (const auto& kv : provisional_loads_) {
707       kv.second->PageHidden();
708     }
709   }
710 
711   // As pages in back-forward cache are frozen, |back_forward_cached_pages_|
712   // don't have to be iterated here.
713 }
714 
715 // This will occur when the process for the main RenderFrameHost exits, either
716 // normally or from a crash. We eagerly log data from the last committed load if
717 // we have one.
RenderProcessGone(base::TerminationStatus status)718 void MetricsWebContentsObserver::RenderProcessGone(
719     base::TerminationStatus status) {
720   // Other code paths will be run for normal renderer shutdown. Note that we
721   // sometimes get the STILL_RUNNING value on fast shutdown.
722   if (status == base::TERMINATION_STATUS_NORMAL_TERMINATION ||
723       status == base::TERMINATION_STATUS_STILL_RUNNING) {
724     return;
725   }
726 
727   // RenderProcessGone is associated with the render frame host for the
728   // currently committed load. We don't know if the pending navs or aborted
729   // pending navs are associated w/ the render process that died, so we can't be
730   // sure the info should propagate to them.
731   if (committed_load_) {
732     committed_load_->NotifyPageEnd(END_RENDER_PROCESS_GONE,
733                                    UserInitiatedInfo::NotUserInitiated(),
734                                    base::TimeTicks::Now(), true);
735   }
736 
737   // If this is a crash, eagerly log the aborted provisional loads and the
738   // committed load. |provisional_loads_| don't need to be destroyed here
739   // because their lifetime is tied to the NavigationHandle.
740   committed_load_.reset();
741   aborted_provisional_loads_.clear();
742 }
743 
NotifyPageEndAllLoads(PageEndReason page_end_reason,UserInitiatedInfo user_initiated_info)744 void MetricsWebContentsObserver::NotifyPageEndAllLoads(
745     PageEndReason page_end_reason,
746     UserInitiatedInfo user_initiated_info) {
747   NotifyPageEndAllLoadsWithTimestamp(page_end_reason, user_initiated_info,
748                                      base::TimeTicks::Now(),
749                                      /*is_certainly_browser_timestamp=*/true);
750 }
751 
NotifyPageEndAllLoadsWithTimestamp(PageEndReason page_end_reason,UserInitiatedInfo user_initiated_info,base::TimeTicks timestamp,bool is_certainly_browser_timestamp)752 void MetricsWebContentsObserver::NotifyPageEndAllLoadsWithTimestamp(
753     PageEndReason page_end_reason,
754     UserInitiatedInfo user_initiated_info,
755     base::TimeTicks timestamp,
756     bool is_certainly_browser_timestamp) {
757   if (committed_load_) {
758     committed_load_->NotifyPageEnd(page_end_reason, user_initiated_info,
759                                    timestamp, is_certainly_browser_timestamp);
760   }
761   for (const auto& kv : provisional_loads_) {
762     kv.second->NotifyPageEnd(page_end_reason, user_initiated_info, timestamp,
763                              is_certainly_browser_timestamp);
764   }
765   for (const auto& tracker : aborted_provisional_loads_) {
766     if (tracker->IsLikelyProvisionalAbort(timestamp)) {
767       tracker->UpdatePageEnd(page_end_reason, user_initiated_info, timestamp,
768                              is_certainly_browser_timestamp);
769     }
770   }
771   aborted_provisional_loads_.clear();
772 }
773 
774 std::unique_ptr<PageLoadTracker>
NotifyAbortedProvisionalLoadsNewNavigation(content::NavigationHandle * new_navigation,UserInitiatedInfo user_initiated_info)775 MetricsWebContentsObserver::NotifyAbortedProvisionalLoadsNewNavigation(
776     content::NavigationHandle* new_navigation,
777     UserInitiatedInfo user_initiated_info) {
778   // If there are multiple aborted loads that can be attributed to this one,
779   // just count the latest one for simplicity. Other loads will fall into the
780   // OTHER bucket, though there shouldn't be very many.
781   if (aborted_provisional_loads_.empty())
782     return nullptr;
783   if (aborted_provisional_loads_.size() > 1)
784     RecordInternalError(ERR_NAVIGATION_SIGNALS_MULIPLE_ABORTED_LOADS);
785 
786   std::unique_ptr<PageLoadTracker> last_aborted_load =
787       std::move(aborted_provisional_loads_.back());
788   aborted_provisional_loads_.pop_back();
789 
790   base::TimeTicks timestamp = new_navigation->NavigationStart();
791   if (last_aborted_load->IsLikelyProvisionalAbort(timestamp)) {
792     last_aborted_load->UpdatePageEnd(
793         EndReasonForPageTransition(new_navigation->GetPageTransition()),
794         user_initiated_info, timestamp, false);
795   }
796 
797   aborted_provisional_loads_.clear();
798   return last_aborted_load;
799 }
800 
OnTimingUpdated(content::RenderFrameHost * render_frame_host,mojom::PageLoadTimingPtr timing,mojom::FrameMetadataPtr metadata,mojom::PageLoadFeaturesPtr new_features,const std::vector<mojom::ResourceDataUpdatePtr> & resources,mojom::FrameRenderDataUpdatePtr render_data,mojom::CpuTimingPtr cpu_timing,mojom::DeferredResourceCountsPtr new_deferred_resource_data,mojom::InputTimingPtr input_timing_delta,const blink::MobileFriendliness & mobile_friendliness)801 void MetricsWebContentsObserver::OnTimingUpdated(
802     content::RenderFrameHost* render_frame_host,
803     mojom::PageLoadTimingPtr timing,
804     mojom::FrameMetadataPtr metadata,
805     mojom::PageLoadFeaturesPtr new_features,
806     const std::vector<mojom::ResourceDataUpdatePtr>& resources,
807     mojom::FrameRenderDataUpdatePtr render_data,
808     mojom::CpuTimingPtr cpu_timing,
809     mojom::DeferredResourceCountsPtr new_deferred_resource_data,
810     mojom::InputTimingPtr input_timing_delta,
811     const blink::MobileFriendliness& mobile_friendliness) {
812   // We may receive notifications from frames that have been navigated away
813   // from. We simply ignore them.
814   // TODO(crbug.com/1061060): We should not ignore page timings if the page is
815   // in bfcache.
816   if (!render_frame_host->GetMainFrame()->IsCurrent()) {
817     RecordInternalError(ERR_IPC_FROM_WRONG_FRAME);
818     return;
819   }
820 
821   const bool is_main_frame = (render_frame_host->GetParent() == nullptr);
822   if (is_main_frame) {
823     if (DoesTimingUpdateHaveError())
824       return;
825   } else if (!committed_load_) {
826     RecordInternalError(ERR_SUBFRAME_IPC_WITH_NO_RELEVANT_LOAD);
827   }
828 
829   if (committed_load_) {
830     committed_load_->metrics_update_dispatcher()->UpdateMetrics(
831         render_frame_host, std::move(timing), std::move(metadata),
832         std::move(new_features), resources, std::move(render_data),
833         std::move(cpu_timing), std::move(new_deferred_resource_data),
834         std::move(input_timing_delta), std::move(mobile_friendliness));
835   }
836 }
837 
DoesTimingUpdateHaveError()838 bool MetricsWebContentsObserver::DoesTimingUpdateHaveError() {
839   // While timings arriving for the wrong frame are expected, we do not expect
840   // any of the errors below for main frames. Thus, we track occurrences of
841   // all errors below, rather than returning early after encountering an
842   // error.
843   // TODO(crbug/1061090): Update page load metrics IPC validation to ues
844   // mojo::ReportBadMessage.
845   bool error = false;
846   if (!committed_load_) {
847     RecordInternalError(ERR_IPC_WITH_NO_RELEVANT_LOAD);
848     error = true;
849   }
850 
851   if (!web_contents()->GetLastCommittedURL().SchemeIsHTTPOrHTTPS()) {
852     RecordInternalError(ERR_IPC_FROM_BAD_URL_SCHEME);
853     error = true;
854   }
855   return error;
856 }
857 
UpdateTiming(mojom::PageLoadTimingPtr timing,mojom::FrameMetadataPtr metadata,mojom::PageLoadFeaturesPtr new_features,std::vector<mojom::ResourceDataUpdatePtr> resources,mojom::FrameRenderDataUpdatePtr render_data,mojom::CpuTimingPtr cpu_timing,mojom::DeferredResourceCountsPtr new_deferred_resource_data,mojom::InputTimingPtr input_timing_delta,const blink::MobileFriendliness & mobile_friendliness)858 void MetricsWebContentsObserver::UpdateTiming(
859     mojom::PageLoadTimingPtr timing,
860     mojom::FrameMetadataPtr metadata,
861     mojom::PageLoadFeaturesPtr new_features,
862     std::vector<mojom::ResourceDataUpdatePtr> resources,
863     mojom::FrameRenderDataUpdatePtr render_data,
864     mojom::CpuTimingPtr cpu_timing,
865     mojom::DeferredResourceCountsPtr new_deferred_resource_data,
866     mojom::InputTimingPtr input_timing_delta,
867     const blink::MobileFriendliness& mobile_friendliness) {
868   content::RenderFrameHost* render_frame_host =
869       page_load_metrics_receiver_.GetCurrentTargetFrame();
870   OnTimingUpdated(render_frame_host, std::move(timing), std::move(metadata),
871                   std::move(new_features), resources, std::move(render_data),
872                   std::move(cpu_timing), std::move(new_deferred_resource_data),
873                   std::move(input_timing_delta),
874                   std::move(mobile_friendliness));
875 }
876 
SetUpSharedMemoryForSmoothness(base::ReadOnlySharedMemoryRegion shared_memory)877 void MetricsWebContentsObserver::SetUpSharedMemoryForSmoothness(
878     base::ReadOnlySharedMemoryRegion shared_memory) {
879   content::RenderFrameHost* render_frame_host =
880       page_load_metrics_receiver_.GetCurrentTargetFrame();
881   const bool is_main_frame = render_frame_host->GetParent() == nullptr;
882   if (!is_main_frame) {
883     // TODO(1115136): Merge smoothness metrics from OOPIFs with the main-frame.
884     return;
885   }
886 
887   if (committed_load_) {
888     committed_load_->metrics_update_dispatcher()
889         ->SetUpSharedMemoryForSmoothness(render_frame_host,
890                                          std::move(shared_memory));
891   } else {
892     ukm_smoothness_data_ = std::move(shared_memory);
893   }
894 }
895 
ShouldTrackMainFrameNavigation(content::NavigationHandle * navigation_handle) const896 bool MetricsWebContentsObserver::ShouldTrackMainFrameNavigation(
897     content::NavigationHandle* navigation_handle) const {
898   DCHECK(navigation_handle->IsInMainFrame());
899   DCHECK(!navigation_handle->HasCommitted() ||
900          !navigation_handle->IsSameDocument());
901   // Ignore non-HTTP schemes (e.g. chrome://).
902   if (!navigation_handle->GetURL().SchemeIsHTTPOrHTTPS())
903     return false;
904 
905   // Ignore NTP loads.
906   if (embedder_interface_->IsNewTabPageUrl(navigation_handle->GetURL()))
907     return false;
908 
909   // The navigation served from the back-forward cache will use the previously
910   // created tracker for the document.
911   if (navigation_handle->IsServedFromBackForwardCache())
912     return false;
913 
914   if (navigation_handle->HasCommitted()) {
915     // Ignore Chrome error pages (e.g. No Internet connection).
916     if (navigation_handle->IsErrorPage())
917       return false;
918 
919     // Ignore network error pages (e.g. 4xx, 5xx).
920     int http_status_code = GetHttpStatusCode(navigation_handle);
921     if (http_status_code > 0 &&
922         (http_status_code < 200 || http_status_code >= 400))
923       return false;
924   }
925 
926   return true;
927 }
928 
OnBrowserFeatureUsage(content::RenderFrameHost * render_frame_host,const mojom::PageLoadFeatures & new_features)929 void MetricsWebContentsObserver::OnBrowserFeatureUsage(
930     content::RenderFrameHost* render_frame_host,
931     const mojom::PageLoadFeatures& new_features) {
932   // Since this call is coming directly from the browser, it should not pass us
933   // data from frames that have already been navigated away from.
934   DCHECK(render_frame_host->GetMainFrame()->IsCurrent());
935 
936   if (!committed_load_) {
937     RecordInternalError(ERR_BROWSER_USAGE_WITH_NO_RELEVANT_LOAD);
938     return;
939   }
940 
941   committed_load_->metrics_update_dispatcher()->UpdateFeatures(
942       render_frame_host, new_features);
943 }
944 
AddTestingObserver(TestingObserver * observer)945 void MetricsWebContentsObserver::AddTestingObserver(TestingObserver* observer) {
946   if (!testing_observers_.HasObserver(observer))
947     testing_observers_.AddObserver(observer);
948 }
949 
RemoveTestingObserver(TestingObserver * observer)950 void MetricsWebContentsObserver::RemoveTestingObserver(
951     TestingObserver* observer) {
952   testing_observers_.RemoveObserver(observer);
953 }
954 
TestingObserver(content::WebContents * web_contents)955 MetricsWebContentsObserver::TestingObserver::TestingObserver(
956     content::WebContents* web_contents)
957     : observer_(page_load_metrics::MetricsWebContentsObserver::FromWebContents(
958           web_contents)) {
959   observer_->AddTestingObserver(this);
960 }
961 
~TestingObserver()962 MetricsWebContentsObserver::TestingObserver::~TestingObserver() {
963   if (observer_) {
964     observer_->RemoveTestingObserver(this);
965     observer_ = nullptr;
966   }
967 }
968 
OnGoingAway()969 void MetricsWebContentsObserver::TestingObserver::OnGoingAway() {
970   observer_ = nullptr;
971 }
972 
973 const PageLoadMetricsObserverDelegate&
GetDelegateForCommittedLoad()974 MetricsWebContentsObserver::TestingObserver::GetDelegateForCommittedLoad() {
975   return observer_->GetDelegateForCommittedLoad();
976 }
977 
BroadcastEventToObservers(const void * const event_key)978 void MetricsWebContentsObserver::BroadcastEventToObservers(
979     const void* const event_key) {
980   if (committed_load_)
981     committed_load_->BroadcastEventToObservers(event_key);
982 }
983 
984 WEB_CONTENTS_USER_DATA_KEY_IMPL(MetricsWebContentsObserver)
985 
986 }  // namespace page_load_metrics
987