1 // Copyright 2016 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 #ifndef COMPONENTS_PAGE_LOAD_METRICS_BROWSER_PAGE_LOAD_TRACKER_H_
6 #define COMPONENTS_PAGE_LOAD_METRICS_BROWSER_PAGE_LOAD_TRACKER_H_
7 
8 #include <memory>
9 #include <vector>
10 
11 #include "base/macros.h"
12 #include "base/optional.h"
13 #include "base/time/time.h"
14 #include "components/page_load_metrics/browser/observers/core/largest_contentful_paint_handler.h"
15 #include "components/page_load_metrics/browser/page_load_metrics_observer.h"
16 #include "components/page_load_metrics/browser/page_load_metrics_observer_delegate.h"
17 #include "components/page_load_metrics/browser/page_load_metrics_update_dispatcher.h"
18 #include "components/page_load_metrics/browser/resource_tracker.h"
19 #include "components/page_load_metrics/common/page_end_reason.h"
20 #include "components/page_load_metrics/common/page_load_timing.h"
21 #include "content/public/browser/global_request_id.h"
22 #include "content/public/browser/web_contents_observer.h"
23 #include "net/cookies/canonical_cookie.h"
24 #include "services/metrics/public/cpp/ukm_source.h"
25 #include "ui/base/page_transition_types.h"
26 #include "ui/base/scoped_visibility_tracker.h"
27 #include "ui/gfx/geometry/size.h"
28 
29 class GURL;
30 
31 namespace blink {
32 class WebInputEvent;
33 }  // namespace blink
34 
35 namespace content {
36 class NavigationHandle;
37 class WebContents;
38 }  // namespace content
39 
40 namespace page_load_metrics {
41 
42 class PageLoadMetricsEmbedderInterface;
43 
44 namespace internal {
45 
46 extern const char kErrorEvents[];
47 extern const char kAbortChainSizeReload[];
48 extern const char kAbortChainSizeForwardBack[];
49 extern const char kAbortChainSizeNewNavigation[];
50 extern const char kAbortChainSizeNoCommit[];
51 extern const char kAbortChainSizeSameURL[];
52 extern const char kPageLoadCompletedAfterAppBackground[];
53 extern const char kPageLoadStartedInForeground[];
54 
55 }  // namespace internal
56 
57 // These errors are internal to the page_load_metrics subsystem and do not
58 // reflect actual errors that occur during a page load.
59 //
60 // If you add elements to this enum, make sure you update the enum
61 // value in histograms.xml. Only add elements to the end to prevent
62 // inconsistencies between versions.
63 enum InternalErrorLoadEvent {
64   // A timing IPC was sent from the renderer that did not line up with previous
65   // data we've received (i.e. navigation start is different or the timing
66   // struct is somehow invalid). This error can only occur once the IPC is
67   // vetted in other ways (see other errors). This error is deprecated as it has
68   // been replaced by the more detailed ERR_BAD_TIMING_IPC_* error codes.
69   DEPRECATED_ERR_BAD_TIMING_IPC,
70 
71   // The following IPCs are not mutually exclusive.
72   //
73   // We received an IPC when we weren't tracking a committed load. This will
74   // often happen if we get an IPC from a bad URL scheme (that is, the renderer
75   // sent us an IPC from a navigation we don't care about).
76   ERR_IPC_WITH_NO_RELEVANT_LOAD,
77 
78   // Received a notification from a frame that has been navigated away from.
79   ERR_IPC_FROM_WRONG_FRAME,
80 
81   // We received an IPC even through the last committed url from the browser
82   // was not http/s. This can happen with the renderer sending IPCs for the
83   // new tab page. This will often come paired with
84   // ERR_IPC_WITH_NO_RELEVANT_LOAD.
85   ERR_IPC_FROM_BAD_URL_SCHEME,
86 
87   // If we track a navigation, but the renderer sends us no IPCs. This could
88   // occur if the browser filters loads less aggressively than the renderer.
89   ERR_NO_IPCS_RECEIVED,
90 
91   // Tracks frequency with which we record an end time that occurred before
92   // navigation start. This is expected to happen in some cases (see comments in
93   // cc file for details). We use this error counter to understand how often it
94   // happens.
95   ERR_END_BEFORE_NAVIGATION_START,
96 
97   // A new navigation triggers abort updates in multiple trackers in
98   // |aborted_provisional_loads_|, when usually there should only be one (the
99   // navigation that just aborted because of this one). If this happens, the
100   // latest aborted load is used to track the chain size.
101   ERR_NAVIGATION_SIGNALS_MULIPLE_ABORTED_LOADS,
102 
103   // Received user input without a relevant load. This error type is deprecated,
104   // as it is valid to receive user input without a relevant load. We leave the
105   // enum value here since it's also used in histogram recording, so it's
106   // important that we not re-use this enum entry for a different value.
107   DEPRECATED_ERR_USER_INPUT_WITH_NO_RELEVANT_LOAD,
108 
109   // A TimeTicks value in the browser process has value less than
110   // navigation_start_. This could happen if navigation_start_ was computed in
111   // renderer process and the system clock has inter process time tick skew.
112   ERR_INTER_PROCESS_TIME_TICK_SKEW,
113 
114   // At the time a PageLoadTracker was destroyed, we had received neither a
115   // commit nor a failed provisional load.
116   ERR_NO_COMMIT_OR_FAILED_PROVISIONAL_LOAD,
117 
118   // No page load end time was recorded for this page load.
119   ERR_NO_PAGE_LOAD_END_TIME,
120 
121   // Received a timing update from a subframe (deprecated).
122   DEPRECATED_ERR_TIMING_IPC_FROM_SUBFRAME,
123 
124   // A timing IPC was sent from the renderer that contained timing data which
125   // was inconsistent with our timing data for the currently committed load.
126   ERR_BAD_TIMING_IPC_INVALID_TIMING_DESCENDENT,
127 
128   // A timing IPC was sent from the renderer that contained loading behavior
129   // data which was inconsistent with our loading behavior data for the
130   // currently committed load.
131   ERR_BAD_TIMING_IPC_INVALID_BEHAVIOR_DESCENDENT,
132 
133   // A timing IPC was sent from the renderer that contained invalid timing data
134   // (e.g. out of order timings, or other issues).
135   ERR_BAD_TIMING_IPC_INVALID_TIMING,
136 
137   // We received a navigation start for a child frame that is before the
138   // navigation start of the main frame.
139   ERR_SUBFRAME_NAVIGATION_START_BEFORE_MAIN_FRAME,
140 
141   // We received an IPC from a subframe when we weren't tracking a committed
142   // load. We expect this error to happen, and track it so we can understand how
143   // frequently this case is encountered.
144   ERR_SUBFRAME_IPC_WITH_NO_RELEVANT_LOAD,
145 
146   // We received browser-process reported metrics when we weren't tracking a
147   // committed load. We expect this error to happen, and track it so we can
148   // understand how frequently this case is encountered.
149   ERR_BROWSER_USAGE_WITH_NO_RELEVANT_LOAD,
150 
151   // Add values before this final count.
152   ERR_LAST_ENTRY,
153 };
154 
155 // NOTE: these functions are shared by page_load_tracker.cc and
156 // metrics_web_contents_observer.cc. They are declared here to allow both files
157 // to access them.
158 void RecordInternalError(InternalErrorLoadEvent event);
159 PageEndReason EndReasonForPageTransition(ui::PageTransition transition);
160 void LogAbortChainSameURLHistogram(int aborted_chain_size_same_url);
161 bool IsNavigationUserInitiated(content::NavigationHandle* handle);
162 
163 // This class tracks a given page load, starting from navigation start /
164 // provisional load, until a new navigation commits or the navigation fails.
165 // MetricsWebContentsObserver manages a set of provisional PageLoadTrackers, as
166 // well as a committed PageLoadTracker.
167 class PageLoadTracker : public PageLoadMetricsUpdateDispatcher::Client,
168                         public PageLoadMetricsObserverDelegate {
169  public:
170   // Caller must guarantee that the embedder_interface pointer outlives this
171   // class. The PageLoadTracker must not hold on to
172   // currently_committed_load_or_null or navigation_handle beyond the scope of
173   // the constructor.
174   PageLoadTracker(bool in_foreground,
175                   PageLoadMetricsEmbedderInterface* embedder_interface,
176                   const GURL& currently_committed_url,
177                   bool is_first_navigation_in_web_contents,
178                   content::NavigationHandle* navigation_handle,
179                   UserInitiatedInfo user_initiated_info,
180                   int aborted_chain_size,
181                   int aborted_chain_size_same_url);
182   ~PageLoadTracker() override;
183 
184   // PageLoadMetricsUpdateDispatcher::Client implementation:
185   void OnTimingChanged() override;
186   void OnSubFrameTimingChanged(content::RenderFrameHost* rfh,
187                                const mojom::PageLoadTiming& timing) override;
188   void OnSubFrameRenderDataChanged(
189       content::RenderFrameHost* rfh,
190       const mojom::FrameRenderDataUpdate& render_data) override;
191   void OnMainFrameMetadataChanged() override;
192   void OnSubframeMetadataChanged(content::RenderFrameHost* rfh,
193                                  const mojom::FrameMetadata& metadata) override;
194   void UpdateFeaturesUsage(
195       content::RenderFrameHost* rfh,
196       const mojom::PageLoadFeatures& new_features) override;
197   void UpdateResourceDataUse(
198       content::RenderFrameHost* rfh,
199       const std::vector<mojom::ResourceDataUpdatePtr>& resources) override;
200   void OnNewDeferredResourceCounts(
201       const mojom::DeferredResourceCounts& new_deferred_resource_data) override;
202   void UpdateFrameCpuTiming(content::RenderFrameHost* rfh,
203                             const mojom::CpuTiming& timing) override;
204   void OnFrameIntersectionUpdate(
205       content::RenderFrameHost* rfh,
206       const mojom::FrameIntersectionUpdate& frame_intersection_update) override;
207   void SetUpSharedMemoryForSmoothness(
208       base::ReadOnlySharedMemoryRegion shared_memory) override;
209 
210   // PageLoadMetricsObserverDelegate implementation:
211   content::WebContents* GetWebContents() const override;
212   base::TimeTicks GetNavigationStart() const override;
213   const base::Optional<base::TimeDelta>& GetFirstBackgroundTime()
214       const override;
215   const base::Optional<base::TimeDelta>& GetFirstForegroundTime()
216       const override;
217   const BackForwardCacheRestore& GetBackForwardCacheRestore(
218       size_t index) const override;
219   bool StartedInForeground() const override;
220   const UserInitiatedInfo& GetUserInitiatedInfo() const override;
221   const GURL& GetUrl() const override;
222   const GURL& GetStartUrl() const override;
223   bool DidCommit() const override;
224   PageEndReason GetPageEndReason() const override;
225   const UserInitiatedInfo& GetPageEndUserInitiatedInfo() const override;
226   base::Optional<base::TimeDelta> GetPageEndTime() const override;
227   const mojom::FrameMetadata& GetMainFrameMetadata() const override;
228   const mojom::FrameMetadata& GetSubframeMetadata() const override;
229   const PageRenderData& GetPageRenderData() const override;
230   const NormalizedCLSData& GetNormalizedCLSData() const override;
231   const mojom::InputTiming& GetPageInputTiming() const override;
232   const blink::MobileFriendliness& GetMobileFriendliness() const override;
233   const PageRenderData& GetMainFrameRenderData() const override;
234   const ui::ScopedVisibilityTracker& GetVisibilityTracker() const override;
235   const ResourceTracker& GetResourceTracker() const override;
236   const LargestContentfulPaintHandler& GetLargestContentfulPaintHandler()
237       const override;
238   const LargestContentfulPaintHandler&
239   GetExperimentalLargestContentfulPaintHandler() const override;
240   ukm::SourceId GetPageUkmSourceId() const override;
241   bool IsFirstNavigationInWebContents() const override;
242 
243   void Redirect(content::NavigationHandle* navigation_handle);
244   void WillProcessNavigationResponse(
245       content::NavigationHandle* navigation_handle);
246   void Commit(content::NavigationHandle* navigation_handle);
247   void DidCommitSameDocumentNavigation(
248       content::NavigationHandle* navigation_handle);
249   void DidInternalNavigationAbort(content::NavigationHandle* navigation_handle);
250   void ReadyToCommitNavigation(content::NavigationHandle* navigation_handle);
251   void DidFinishSubFrameNavigation(
252       content::NavigationHandle* navigation_handle);
253   void FailedProvisionalLoad(content::NavigationHandle* navigation_handle,
254                              base::TimeTicks failed_load_time);
255   void PageHidden();
256   void PageShown();
257   void FrameDeleted(content::RenderFrameHost* rfh);
258 
259   void OnInputEvent(const blink::WebInputEvent& event);
260 
261   // Flush any buffered metrics, as part of the metrics subsystem persisting
262   // metrics as the application goes into the background. The application may be
263   // killed at any time after this method is invoked without further
264   // notification.
265   void FlushMetricsOnAppEnterBackground();
266 
267   // Replaces the |visibility_tracker_| for testing, which can mock a clock.
SetVisibilityTrackerForTesting(const ui::ScopedVisibilityTracker & tracker)268   void SetVisibilityTrackerForTesting(
269       const ui::ScopedVisibilityTracker& tracker) {
270     visibility_tracker_ = tracker;
271   }
272 
273   void NotifyClientRedirectTo(content::NavigationHandle* destination);
274 
275   void OnLoadedResource(
276       const ExtraRequestCompleteInfo& extra_request_complete_info);
277 
278   void FrameReceivedFirstUserActivation(content::RenderFrameHost* rfh);
279   void FrameDisplayStateChanged(content::RenderFrameHost* render_frame_host,
280                                 bool is_display_none);
281   void FrameSizeChanged(content::RenderFrameHost* render_frame_host,
282                         const gfx::Size& frame_size);
283 
284   void OnCookiesRead(const GURL& url,
285                      const GURL& first_party_url,
286                      const net::CookieList& cookie_list,
287                      bool blocked_by_policy);
288 
289   void OnCookieChange(const GURL& url,
290                       const GURL& first_party_url,
291                       const net::CanonicalCookie& cookie,
292                       bool blocked_by_policy);
293 
294   void OnStorageAccessed(const GURL& url,
295                          const GURL& first_party_url,
296                          bool blocked_by_policy,
297                          StorageType access_type);
298 
299   // Signals that we should stop tracking metrics for the associated page load.
300   // We may stop tracking a page load if it doesn't meet the criteria for
301   // tracking metrics in DidFinishNavigation.
302   void StopTracking();
303 
aborted_chain_size()304   int aborted_chain_size() const { return aborted_chain_size_; }
aborted_chain_size_same_url()305   int aborted_chain_size_same_url() const {
306     return aborted_chain_size_same_url_;
307   }
308 
page_end_reason()309   PageEndReason page_end_reason() const { return page_end_reason_; }
page_end_time()310   base::TimeTicks page_end_time() const { return page_end_time_; }
311 
312   void AddObserver(std::unique_ptr<PageLoadMetricsObserver> observer);
313 
314   // If the user performs some abort-like action while we are tracking this page
315   // load, notify the tracker. Note that we may not classify this as an abort if
316   // we've already performed a first paint.
317   // is_certainly_browser_timestamp signifies if the timestamp passed is taken
318   // in the
319   // browser process or not. We need this to possibly clamp browser timestamp on
320   // a machine with inter process time tick skew.
321   void NotifyPageEnd(PageEndReason page_end_reason,
322                      UserInitiatedInfo user_initiated_info,
323                      base::TimeTicks timestamp,
324                      bool is_certainly_browser_timestamp);
325   void UpdatePageEnd(PageEndReason page_end_reason,
326                      UserInitiatedInfo user_initiated_info,
327                      base::TimeTicks timestamp,
328                      bool is_certainly_browser_timestamp);
329 
330   // This method returns true if this page load has been aborted with type of
331   // END_OTHER, and the |abort_cause_time| is within a sufficiently close
332   // delta to when it was aborted. Note that only provisional loads can be
333   // aborted with END_OTHER. While this heuristic is coarse, it works better
334   // and is simpler than other feasible methods. See https://goo.gl/WKRG98.
335   bool IsLikelyProvisionalAbort(base::TimeTicks abort_cause_time) const;
336 
337   bool MatchesOriginalNavigation(content::NavigationHandle* navigation_handle);
338 
did_commit()339   bool did_commit() const { return did_commit_; }
url()340   const GURL& url() const { return url_; }
341 
navigation_start()342   base::TimeTicks navigation_start() const { return navigation_start_; }
343 
page_transition()344   ui::PageTransition page_transition() const { return page_transition_; }
345 
user_initiated_info()346   UserInitiatedInfo user_initiated_info() const { return user_initiated_info_; }
347 
metrics_update_dispatcher()348   PageLoadMetricsUpdateDispatcher* metrics_update_dispatcher() {
349     return &metrics_update_dispatcher_;
350   }
351 
352   // Whether this PageLoadTracker has a navigation GlobalRequestID that matches
353   // the given request_id. This method will return false before
354   // WillProcessNavigationResponse has been invoked, as PageLoadTracker doesn't
355   // know its GlobalRequestID until WillProcessNavigationResponse has been
356   // invoked.
357   bool HasMatchingNavigationRequestID(
358       const content::GlobalRequestID& request_id) const;
359 
360   // Invoked when a media element starts playing.
361   void MediaStartedPlaying(
362       const content::WebContentsObserver::MediaPlayerInfo& video_type,
363       content::RenderFrameHost* render_frame_host);
364 
365   // Informs the observers that the event corresponding to |event_key| has
366   // occurred.
367   void BroadcastEventToObservers(const void* const event_key);
368 
369   void OnEnterBackForwardCache();
370   void OnRestoreFromBackForwardCache(
371       content::NavigationHandle* navigation_handle);
372 
373   // Called when the page tracked was just activated after being loaded inside a
374   // portal.
375   void DidActivatePortal(base::TimeTicks activation_time);
376 
377  private:
378   // This function converts a TimeTicks value taken in the browser process
379   // to navigation_start_ if:
380   // - base::TimeTicks is not comparable across processes because the clock
381   // is not system wide monotonic.
382   // - *event_time < navigation_start_
383   void ClampBrowserTimestampIfInterProcessTimeTickSkew(
384       base::TimeTicks* event_time);
385 
386   void UpdatePageEndInternal(PageEndReason page_end_reason,
387                              UserInitiatedInfo user_initiated_info,
388                              base::TimeTicks timestamp,
389                              bool is_certainly_browser_timestamp);
390   // If |final_navigation| is null, then this is an "unparented" abort chain,
391   // and represents a sequence of provisional aborts that never ends with a
392   // committed load.
393   void LogAbortChainHistograms(content::NavigationHandle* final_navigation);
394 
395   // Whether we stopped tracking this navigation after it was initiated. We may
396   // stop tracking a navigation if it doesn't meet the criteria for tracking
397   // metrics in DidFinishNavigation.
398   bool did_stop_tracking_;
399 
400   // Whether the application went into the background when this PageLoadTracker
401   // was active. This is a temporary boolean for UMA tracking.
402   bool app_entered_background_;
403 
404   // The navigation start in TimeTicks, not the wall time reported by Blink.
405   const base::TimeTicks navigation_start_;
406 
407   // The navigation start after the last time when back-forward cache is
408   // restored.
409   base::TimeTicks navigation_start_after_back_forward_cache_restore_;
410 
411   // The most recent URL of this page load. Updated at navigation start, upon
412   // redirection, and at commit time.
413   GURL url_;
414 
415   // The start URL for this page load (before redirects).
416   GURL start_url_;
417 
418   ui::ScopedVisibilityTracker visibility_tracker_;
419 
420   // Whether this page load committed.
421   bool did_commit_;
422 
423   std::unique_ptr<FailedProvisionalLoadInfo> failed_provisional_load_info_;
424 
425   // Will be END_NONE if we have not ended this load yet. Otherwise will
426   // be the first page end reason encountered.
427   PageEndReason page_end_reason_;
428 
429   // Whether the page end cause for this page load was user initiated. For
430   // example, if this page load was ended by a new navigation, this field tracks
431   // whether that new navigation was user-initiated. This field is only useful
432   // if this page load's end reason is a value other than END_NONE. Note that
433   // this value is currently experimental, and is subject to change. In
434   // particular, this field is never set to true for some page end reasons, such
435   // as stop and close, since we don't yet have sufficient instrumentation to
436   // know if a stop or close was caused by a user action.
437   UserInitiatedInfo page_end_user_initiated_info_;
438 
439   base::TimeTicks page_end_time_;
440 
441   // We record separate metrics for events that occur after a background,
442   // because metrics like layout/paint are delayed artificially
443   // when they occur in the background.
444   base::Optional<base::TimeDelta> first_background_time_;
445   base::Optional<base::TimeDelta> first_foreground_time_;
446   std::vector<BackForwardCacheRestore> back_forward_cache_restores_;
447   const bool started_in_foreground_;
448 
449   mojom::PageLoadTimingPtr last_dispatched_merged_page_timing_;
450   blink::MobileFriendliness latest_mobile_friendliness_;
451 
452   ui::PageTransition page_transition_;
453 
454   base::Optional<content::GlobalRequestID> navigation_request_id_;
455 
456   // Whether this page load was user initiated.
457   UserInitiatedInfo user_initiated_info_;
458 
459   // This is a subtle member. If a provisional load A gets aborted by
460   // provisional load B, which gets aborted by C that eventually commits, then
461   // there exists an abort chain of length 2, starting at A's navigation_start.
462   // This is useful because it allows histograming abort chain lengths based on
463   // what the last load's transition type is. i.e. holding down F-5 to spam
464   // reload will produce a long chain with the RELOAD transition.
465   const int aborted_chain_size_;
466 
467   // This member counts consecutive provisional aborts that share a url. It will
468   // always be less than or equal to |aborted_chain_size_|.
469   const int aborted_chain_size_same_url_;
470 
471   // Keeps track of actively loading resources on the page.
472   ResourceTracker resource_tracker_;
473 
474   // Interface to chrome features. Must outlive the class.
475   PageLoadMetricsEmbedderInterface* const embedder_interface_;
476 
477   std::vector<std::unique_ptr<PageLoadMetricsObserver>> observers_;
478 
479   PageLoadMetricsUpdateDispatcher metrics_update_dispatcher_;
480 
481   const ukm::SourceId source_id_;
482 
483   content::WebContents* const web_contents_;
484 
485   const bool is_first_navigation_in_web_contents_;
486 
487   page_load_metrics::LargestContentfulPaintHandler
488       largest_contentful_paint_handler_;
489   page_load_metrics::LargestContentfulPaintHandler
490       experimental_largest_contentful_paint_handler_;
491 
492   DISALLOW_COPY_AND_ASSIGN(PageLoadTracker);
493 };
494 
495 }  // namespace page_load_metrics
496 
497 #endif  // COMPONENTS_PAGE_LOAD_METRICS_BROWSER_PAGE_LOAD_TRACKER_H_
498