diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2020-10-12 14:27:29 +0200 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2020-10-13 09:35:20 +0000 |
commit | c30a6232df03e1efbd9f3b226777b07e087a1122 (patch) | |
tree | e992f45784689f373bcc38d1b79a239ebe17ee23 /chromium/components/page_load_metrics | |
parent | 7b5b123ac58f58ffde0f4f6e488bcd09aa4decd3 (diff) | |
download | qtwebengine-chromium-85-based.tar.gz |
BASELINE: Update Chromium to 85.0.4183.14085-based
Change-Id: Iaa42f4680837c57725b1344f108c0196741f6057
Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
Diffstat (limited to 'chromium/components/page_load_metrics')
32 files changed, 1482 insertions, 478 deletions
diff --git a/chromium/components/page_load_metrics/browser/BUILD.gn b/chromium/components/page_load_metrics/browser/BUILD.gn index 8383f36b9af..c27d2e44573 100644 --- a/chromium/components/page_load_metrics/browser/BUILD.gn +++ b/chromium/components/page_load_metrics/browser/BUILD.gn @@ -10,6 +10,8 @@ source_set("browser") { "metrics_navigation_throttle.h", "metrics_web_contents_observer.cc", "metrics_web_contents_observer.h", + "observers/back_forward_cache_page_load_metrics_observer.cc", + "observers/back_forward_cache_page_load_metrics_observer.h", "observers/click_input_tracker.cc", "observers/click_input_tracker.h", "observers/core_page_load_metrics_observer.cc", @@ -26,6 +28,7 @@ source_set("browser") { "page_load_metrics_embedder_interface.h", "page_load_metrics_observer.cc", "page_load_metrics_observer.h", + "page_load_metrics_observer_delegate.cc", "page_load_metrics_observer_delegate.h", "page_load_metrics_update_dispatcher.cc", "page_load_metrics_update_dispatcher.h", diff --git a/chromium/components/page_load_metrics/browser/metrics_web_contents_observer.cc b/chromium/components/page_load_metrics/browser/metrics_web_contents_observer.cc index c718da71648..2bade0dc409 100644 --- a/chromium/components/page_load_metrics/browser/metrics_web_contents_observer.cc +++ b/chromium/components/page_load_metrics/browser/metrics_web_contents_observer.cc @@ -76,23 +76,6 @@ void MetricsWebContentsObserver::RecordFeatureUsage( observer->OnBrowserFeatureUsage(render_frame_host, new_features); } -MetricsWebContentsObserver::MetricsWebContentsObserver( - content::WebContents* web_contents, - std::unique_ptr<PageLoadMetricsEmbedderInterface> embedder_interface) - : content::WebContentsObserver(web_contents), - in_foreground_(web_contents->GetVisibility() != - content::Visibility::HIDDEN), - embedder_interface_(std::move(embedder_interface)), - has_navigated_(false), - page_load_metrics_receiver_(web_contents, this) { - // Prerenders erroneously report that they are initially visible, so we - // manually override visibility state for prerender. - if (embedder_interface_->IsPrerender(web_contents)) - in_foreground_ = false; - - RegisterInputEventObserver(web_contents->GetRenderViewHost()); -} - // static MetricsWebContentsObserver* MetricsWebContentsObserver::CreateForWebContents( content::WebContents* web_contents, @@ -188,6 +171,23 @@ void MetricsWebContentsObserver::WillStartNavigationRequest( has_navigated_ = true; } +MetricsWebContentsObserver::MetricsWebContentsObserver( + content::WebContents* web_contents, + std::unique_ptr<PageLoadMetricsEmbedderInterface> embedder_interface) + : content::WebContentsObserver(web_contents), + in_foreground_(web_contents->GetVisibility() != + content::Visibility::HIDDEN), + embedder_interface_(std::move(embedder_interface)), + has_navigated_(false), + page_load_metrics_receiver_(web_contents, this) { + // Prerenders erroneously report that they are initially visible, so we + // manually override visibility state for prerender. + if (embedder_interface_->IsPrerender(web_contents)) + in_foreground_ = false; + + RegisterInputEventObserver(web_contents->GetRenderViewHost()); +} + void MetricsWebContentsObserver::WillStartNavigationRequestImpl( content::NavigationHandle* navigation_handle) { UserInitiatedInfo user_initiated_info( @@ -397,6 +397,22 @@ void MetricsWebContentsObserver::OnCookiesAccessedImpl( } } +void MetricsWebContentsObserver::DidActivatePortal( + content::WebContents* predecessor_web_contents, + base::TimeTicks activation_time) { + // The |predecessor_web_contents| is the WebContents that instantiated the + // portal. + MetricsWebContentsObserver* predecessor_observer = + MetricsWebContentsObserver::FromWebContents(predecessor_web_contents); + // We only track the portal activation if the predecessor is also being + // tracked. + if (!committed_load_ || !predecessor_observer || + !predecessor_observer->committed_load_) { + return; + } + committed_load_->DidActivatePortal(activation_time); +} + void MetricsWebContentsObserver::OnStorageAccessed(const GURL& url, const GURL& first_party_url, bool blocked_by_policy, @@ -574,7 +590,11 @@ bool MetricsWebContentsObserver::MaybeRestorePageLoadTrackerForBackForwardCache( committed_load_ = std::move(it->second); back_forward_cached_pages_.erase(it); - committed_load_->OnRestoreFromBackForwardCache(); + committed_load_->OnRestoreFromBackForwardCache(navigation_handle); + + for (auto& observer : testing_observers_) + observer.OnRestoredFromBackForwardCache(committed_load_.get()); + return true; } @@ -783,24 +803,7 @@ void MetricsWebContentsObserver::OnTimingUpdated( const bool is_main_frame = (render_frame_host->GetParent() == nullptr); if (is_main_frame) { - // While timings arriving for the wrong frame are expected, we do not expect - // any of the errors below for main frames. Thus, we track occurrences of - // all errors below, rather than returning early after encountering an - // error. - // TODO(crbug/1061090): Update page load metrics IPC validation to ues - // mojo::ReportBadMessage. - bool error = false; - if (!committed_load_) { - RecordInternalError(ERR_IPC_WITH_NO_RELEVANT_LOAD); - error = true; - } - - if (!web_contents()->GetLastCommittedURL().SchemeIsHTTPOrHTTPS()) { - RecordInternalError(ERR_IPC_FROM_BAD_URL_SCHEME); - error = true; - } - - if (error) + if (DoesTimingUpdateHaveError()) return; } else if (!committed_load_) { RecordInternalError(ERR_SUBFRAME_IPC_WITH_NO_RELEVANT_LOAD); @@ -815,6 +818,26 @@ void MetricsWebContentsObserver::OnTimingUpdated( } } +bool MetricsWebContentsObserver::DoesTimingUpdateHaveError() { + // While timings arriving for the wrong frame are expected, we do not expect + // any of the errors below for main frames. Thus, we track occurrences of + // all errors below, rather than returning early after encountering an + // error. + // TODO(crbug/1061090): Update page load metrics IPC validation to ues + // mojo::ReportBadMessage. + bool error = false; + if (!committed_load_) { + RecordInternalError(ERR_IPC_WITH_NO_RELEVANT_LOAD); + error = true; + } + + if (!web_contents()->GetLastCommittedURL().SchemeIsHTTPOrHTTPS()) { + RecordInternalError(ERR_IPC_FROM_BAD_URL_SCHEME); + error = true; + } + return error; +} + void MetricsWebContentsObserver::UpdateTiming( mojom::PageLoadTimingPtr timing, mojom::FrameMetadataPtr metadata, @@ -832,17 +855,22 @@ void MetricsWebContentsObserver::UpdateTiming( std::move(input_timing_delta)); } +void MetricsWebContentsObserver::SubmitThroughputData( + mojom::ThroughputUkmDataPtr throughput_data) { + if (DoesTimingUpdateHaveError()) + return; + + content::RenderFrameHost* render_frame_host = + page_load_metrics_receiver_.GetCurrentTargetFrame(); + committed_load_->metrics_update_dispatcher()->UpdateThroughput( + render_frame_host, std::move(throughput_data)); +} + bool MetricsWebContentsObserver::ShouldTrackMainFrameNavigation( content::NavigationHandle* navigation_handle) const { DCHECK(navigation_handle->IsInMainFrame()); DCHECK(!navigation_handle->HasCommitted() || !navigation_handle->IsSameDocument()); - // If there is an outer WebContents, then this WebContents is embedded into - // another one (it is either a portal or a Chrome App <webview>). Ignore these - // navigations for now. - if (web_contents()->GetOuterWebContents()) - return false; - // Ignore non-HTTP schemes (e.g. chrome://). if (!navigation_handle->GetURL().SchemeIsHTTPOrHTTPS()) return false; diff --git a/chromium/components/page_load_metrics/browser/metrics_web_contents_observer.h b/chromium/components/page_load_metrics/browser/metrics_web_contents_observer.h index e511b3f562f..3fedb707723 100644 --- a/chromium/components/page_load_metrics/browser/metrics_web_contents_observer.h +++ b/chromium/components/page_load_metrics/browser/metrics_web_contents_observer.h @@ -65,6 +65,8 @@ class MetricsWebContentsObserver // fine. virtual void OnCommit(PageLoadTracker* tracker) {} + virtual void OnRestoredFromBackForwardCache(PageLoadTracker* tracker) {} + // Returns the observer delegate for the committed load associated with // the MetricsWebContentsObserver. const PageLoadMetricsObserverDelegate& GetDelegateForCommittedLoad(); @@ -85,9 +87,6 @@ class MetricsWebContentsObserver static MetricsWebContentsObserver* CreateForWebContents( content::WebContents* web_contents, std::unique_ptr<PageLoadMetricsEmbedderInterface> embedder_interface); - MetricsWebContentsObserver( - content::WebContents* web_contents, - std::unique_ptr<PageLoadMetricsEmbedderInterface> embedder_interface); ~MetricsWebContentsObserver() override; // Any visibility changes that occur after this method should be ignored since @@ -131,6 +130,8 @@ class MetricsWebContentsObserver const GURL& first_party_url, bool blocked_by_policy, StorageType storage_type); + void DidActivatePortal(content::WebContents* predecessor_web_contents, + base::TimeTicks activation_time) override; // These methods are forwarded from the MetricsNavigationThrottle. void WillStartNavigationRequest(content::NavigationHandle* navigation_handle); @@ -171,6 +172,10 @@ class MetricsWebContentsObserver private: friend class content::WebContentsUserData<MetricsWebContentsObserver>; + MetricsWebContentsObserver( + content::WebContents* web_contents, + std::unique_ptr<PageLoadMetricsEmbedderInterface> embedder_interface); + void WillStartNavigationRequestImpl( content::NavigationHandle* navigation_handle); @@ -184,6 +189,14 @@ class MetricsWebContentsObserver mojom::DeferredResourceCountsPtr new_deferred_resource_data, mojom::InputTimingPtr input_timing) override; + // Update the throughput samples on the browser side, and report it to UKM at + // page shuts down / nagivates away, report it to UKM. + void SubmitThroughputData( + mojom::ThroughputUkmDataPtr throughput_data) override; + + // Common part for UpdateThroughput and OnTimingUpdated. + bool DoesTimingUpdateHaveError(); + void HandleFailedNavigationForTrackedLoad( content::NavigationHandle* navigation_handle, std::unique_ptr<PageLoadTracker> tracker); diff --git a/chromium/components/page_load_metrics/browser/observers/back_forward_cache_page_load_metrics_observer.cc b/chromium/components/page_load_metrics/browser/observers/back_forward_cache_page_load_metrics_observer.cc new file mode 100644 index 00000000000..f055e06017a --- /dev/null +++ b/chromium/components/page_load_metrics/browser/observers/back_forward_cache_page_load_metrics_observer.cc @@ -0,0 +1,60 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/page_load_metrics/browser/observers/back_forward_cache_page_load_metrics_observer.h" + +#include "components/page_load_metrics/browser/page_load_metrics_util.h" + +namespace internal { + +const char kHistogramFirstPaintAfterBackForwardCacheRestore[] = + "PageLoad.PaintTiming.NavigationToFirstPaint.AfterBackForwardCacheRestore"; +const char kHistogramFirstInputDelayAfterBackForwardCacheRestore[] = + "PageLoad.InteractiveTiming.FirstInputDelay.AfterBackForwardCacheRestore"; + +} // namespace internal + +BackForwardCachePageLoadMetricsObserver:: + BackForwardCachePageLoadMetricsObserver() = default; + +BackForwardCachePageLoadMetricsObserver:: + ~BackForwardCachePageLoadMetricsObserver() = default; + +page_load_metrics::PageLoadMetricsObserver::ObservePolicy +BackForwardCachePageLoadMetricsObserver::OnEnterBackForwardCache( + const page_load_metrics::mojom::PageLoadTiming& timing) { + return CONTINUE_OBSERVING; +} + +void BackForwardCachePageLoadMetricsObserver:: + OnFirstPaintAfterBackForwardCacheRestoreInPage( + const page_load_metrics::mojom::BackForwardCacheTiming& timing, + size_t index) { + auto first_paint = timing.first_paint_after_back_forward_cache_restore; + DCHECK(!first_paint.is_zero()); + if (page_load_metrics:: + WasStartedInForegroundOptionalEventInForegroundAfterBackForwardCacheRestore( + first_paint, GetDelegate(), index)) { + PAGE_LOAD_HISTOGRAM( + internal::kHistogramFirstPaintAfterBackForwardCacheRestore, + first_paint); + } +} + +void BackForwardCachePageLoadMetricsObserver:: + OnFirstInputAfterBackForwardCacheRestoreInPage( + const page_load_metrics::mojom::BackForwardCacheTiming& timing, + size_t index) { + auto first_input_delay = + timing.first_input_delay_after_back_forward_cache_restore; + DCHECK(first_input_delay.has_value()); + if (page_load_metrics:: + WasStartedInForegroundOptionalEventInForegroundAfterBackForwardCacheRestore( + first_input_delay, GetDelegate(), index)) { + UMA_HISTOGRAM_CUSTOM_TIMES( + internal::kHistogramFirstInputDelayAfterBackForwardCacheRestore, + *first_input_delay, base::TimeDelta::FromMilliseconds(1), + base::TimeDelta::FromSeconds(60), 50); + } +} diff --git a/chromium/components/page_load_metrics/browser/observers/back_forward_cache_page_load_metrics_observer.h b/chromium/components/page_load_metrics/browser/observers/back_forward_cache_page_load_metrics_observer.h new file mode 100644 index 00000000000..ddcb5ede44f --- /dev/null +++ b/chromium/components/page_load_metrics/browser/observers/back_forward_cache_page_load_metrics_observer.h @@ -0,0 +1,38 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_PAGE_LOAD_METRICS_BROWSER_OBSERVERS_BACK_FORWARD_CACHE_PAGE_LOAD_METRICS_OBSERVER_H_ +#define COMPONENTS_PAGE_LOAD_METRICS_BROWSER_OBSERVERS_BACK_FORWARD_CACHE_PAGE_LOAD_METRICS_OBSERVER_H_ + +#include "components/page_load_metrics/browser/page_load_metrics_observer.h" + +namespace internal { + +extern const char kHistogramFirstPaintAfterBackForwardCacheRestore[]; +extern const char kHistogramFirstInputDelayAfterBackForwardCacheRestore[]; + +} // namespace internal + +class BackForwardCachePageLoadMetricsObserver + : public page_load_metrics::PageLoadMetricsObserver { + public: + BackForwardCachePageLoadMetricsObserver(); + ~BackForwardCachePageLoadMetricsObserver() override; + + // page_load_metrics::PageLoadMetricsObserver: + page_load_metrics::PageLoadMetricsObserver::ObservePolicy + OnEnterBackForwardCache( + const page_load_metrics::mojom::PageLoadTiming& timing) override; + void OnFirstPaintAfterBackForwardCacheRestoreInPage( + const page_load_metrics::mojom::BackForwardCacheTiming& timing, + size_t index) override; + void OnFirstInputAfterBackForwardCacheRestoreInPage( + const page_load_metrics::mojom::BackForwardCacheTiming& timing, + size_t index) override; + + private: + DISALLOW_COPY_AND_ASSIGN(BackForwardCachePageLoadMetricsObserver); +}; + +#endif // COMPONENTS_PAGE_LOAD_METRICS_BROWSER_OBSERVERS_BACK_FORWARD_CACHE_PAGE_LOAD_METRICS_OBSERVER_H_ diff --git a/chromium/components/page_load_metrics/browser/observers/core_page_load_metrics_observer.cc b/chromium/components/page_load_metrics/browser/observers/core_page_load_metrics_observer.cc index a885037b114..a6f18f2ca29 100644 --- a/chromium/components/page_load_metrics/browser/observers/core_page_load_metrics_observer.cc +++ b/chromium/components/page_load_metrics/browser/observers/core_page_load_metrics_observer.cc @@ -12,6 +12,7 @@ #include "base/feature_list.h" #include "base/metrics/histogram_macros.h" #include "base/power_monitor/power_monitor.h" +#include "components/page_load_metrics/browser/observers/largest_contentful_paint_handler.h" #include "components/page_load_metrics/browser/page_load_metrics_util.h" #include "content/public/common/process_type.h" #include "net/http/http_response_headers.h" @@ -112,6 +113,18 @@ const char kHistogramLargestContentfulPaintMainFrame[] = const char kHistogramLargestContentfulPaintMainFrameContentType[] = "PageLoad.Internal.PaintTiming.LargestContentfulPaint.MainFrame." "ContentType"; +const char kHistogramExperimentalLargestContentfulPaint[] = + "PageLoad.PaintTiming.NavigationToExperimentalLargestContentfulPaint"; +const char kHistogramExperimentalLargestContentfulPaintContentType[] = + "PageLoad.Internal.PaintTiming.ExperimentalLargestContentfulPaint." + "ContentType"; +const char kHistogramExperimentalLargestContentfulPaintMainFrame[] = + "PageLoad.PaintTiming.NavigationToExperimentalLargestContentfulPaint." + "MainFrame"; +const char kHistogramExperimentalLargestContentfulPaintMainFrameContentType[] = + "PageLoad.Internal.PaintTiming.ExperimentalLargestContentfulPaint." + "MainFrame." + "ContentType"; const char kHistogramFirstInputDelay[] = "PageLoad.InteractiveTiming.FirstInputDelay4"; const char kHistogramFirstInputTimestamp[] = @@ -291,6 +304,12 @@ const char kHistogramFontPreloadFirstContentfulPaint[] = const char kHistogramFontPreloadLargestContentfulPaint[] = "PageLoad.Clients.FontPreload.PaintTiming." "NavigationToLargestContentfulPaint"; +const char kHistogramFontPreloadLargestImagePaint[] = + "PageLoad.Clients.FontPreload.PaintTiming." + "NavigationToLargestImagePaint"; +const char kHistogramFontPreloadLargestTextPaint[] = + "PageLoad.Clients.FontPreload.PaintTiming." + "NavigationToLargestTextPaint"; // Navigation metrics from the navigation start. const char kHistogramNavigationTimingNavigationStartToFirstRequestStart[] = @@ -298,11 +317,46 @@ const char kHistogramNavigationTimingNavigationStartToFirstRequestStart[] = const char kHistogramNavigationTimingNavigationStartToFirstResponseStart[] = "PageLoad.Experimental.NavigationTiming." "NavigationStartToFirstResponseStart"; +const char kHistogramNavigationTimingNavigationStartToFirstLoaderCallback[] = + "PageLoad.Experimental.NavigationTiming." + "NavigationStartToFirstLoaderCallback"; +const char kHistogramNavigationTimingNavigationStartToFinalRequestStart[] = + "PageLoad.Experimental.NavigationTiming.NavigationStartToFinalRequestStart"; +const char kHistogramNavigationTimingNavigationStartToFinalResponseStart[] = + "PageLoad.Experimental.NavigationTiming." + "NavigationStartToFinalResponseStart"; +const char kHistogramNavigationTimingNavigationStartToFinalLoaderCallback[] = + "PageLoad.Experimental.NavigationTiming." + "NavigationStartToFinalLoaderCallback"; +const char kHistogramNavigationTimingNavigationStartToNavigationCommitSent[] = + "PageLoad.Experimental.NavigationTiming." + "NavigationStartToNavigationCommitSent"; // Navigation metrics between milestones. const char kHistogramNavigationTimingFirstRequestStartToFirstResponseStart[] = "PageLoad.Experimental.NavigationTiming." "FirstRequestStartToFirstResponseStart"; +const char kHistogramNavigationTimingFirstResponseStartToFirstLoaderCallback[] = + "PageLoad.Experimental.NavigationTiming." + "FirstResponseStartToFirstLoaderCallback"; +const char kHistogramNavigationTimingFinalRequestStartToFinalResponseStart[] = + "PageLoad.Experimental.NavigationTiming." + "FinalRequestStartToFinalResponseStart"; +const char kHistogramNavigationTimingFinalResponseStartToFinalLoaderCallback[] = + "PageLoad.Experimental.NavigationTiming." + "FinalResponseStartToFinalLoaderCallback"; +const char + kHistogramNavigationTimingFinalLoaderCallbackToNavigationCommitSent[] = + "PageLoad.Experimental.NavigationTiming." + "FinalLoaderCallbackToNavigationCommitSent"; + +// 103 Early Hints metrics for experiment (https://crbug.com/1093693). +const char kHistogramEarlyHintsFirstRequestStartToEarlyHints[] = + "PageLoad.Experimental.EarlyHints.FirstRequestStartToEarlyHints"; +const char kHistogramEarlyHintsFinalRequestStartToEarlyHints[] = + "PageLoad.Experimental.EarlyHints.FinalRequestStartToEarlyHints"; +const char kHistogramEarlyHintsEarlyHintsToFinalResponseStart[] = + "PageLoad.Experimental.EarlyHints.EarlyHintsToFinalResponseStart"; } // namespace internal @@ -314,8 +368,7 @@ CorePageLoadMetricsObserver::CorePageLoadMetricsObserver() cache_bytes_(0), network_bytes_(0), network_bytes_including_headers_(0), - redirect_chain_size_(0), - largest_contentful_paint_handler_() {} + redirect_chain_size_(0) {} CorePageLoadMetricsObserver::~CorePageLoadMetricsObserver() {} @@ -339,7 +392,7 @@ CorePageLoadMetricsObserver::OnCommit( } UMA_HISTOGRAM_COUNTS_100("PageLoad.Navigation.RedirectChainLength", redirect_chain_size_); - RecordNavigationTimingHistograms(navigation_handle); + navigation_handle_timing_ = navigation_handle->GetNavigationHandleTiming(); return CONTINUE_OBSERVING; } @@ -678,6 +731,7 @@ void CorePageLoadMetricsObserver::OnParseStop( void CorePageLoadMetricsObserver::OnComplete( const page_load_metrics::mojom::PageLoadTiming& timing) { + RecordNavigationTimingHistograms(); RecordTimingHistograms(timing); RecordByteAndResourceHistograms(timing); RecordCpuUsageHistograms(); @@ -692,6 +746,7 @@ CorePageLoadMetricsObserver::FlushMetricsOnAppEnterBackground( // flow. After this method is invoked, Chrome may be killed without further // notification, so we record final metrics collected up to this point. if (GetDelegate().DidCommit()) { + RecordNavigationTimingHistograms(); RecordTimingHistograms(timing); RecordByteAndResourceHistograms(timing); RecordCpuUsageHistograms(); @@ -770,42 +825,113 @@ void CorePageLoadMetricsObserver::OnResourceDataUseObserved( } } -void CorePageLoadMetricsObserver::RecordNavigationTimingHistograms( - content::NavigationHandle* navigation_handle) { - // Record only main frame navigation. - DCHECK(navigation_handle->IsInMainFrame()); +void CorePageLoadMetricsObserver::RecordNavigationTimingHistograms() { + const base::TimeTicks navigation_start_time = + GetDelegate().GetNavigationStart(); + const content::NavigationHandleTiming& timing = navigation_handle_timing_; // Record metrics for navigation only when all relevant milestones are // recorded and in the expected order. It is allowed that they have the same // value for some cases (e.g., internal redirection for HSTS). - if (navigation_handle->NavigationStart().is_null() || - navigation_handle->FirstRequestStart().is_null() || - navigation_handle->FirstResponseStart().is_null()) + if (navigation_start_time.is_null() || + timing.first_request_start_time.is_null() || + timing.first_response_start_time.is_null() || + timing.first_loader_callback_time.is_null() || + timing.final_request_start_time.is_null() || + timing.final_response_start_time.is_null() || + timing.final_loader_callback_time.is_null() || + timing.navigation_commit_sent_time.is_null()) { return; + } // TODO(https://crbug.com/1076710): Change these early-returns to DCHECKs // after the issue 1076710 is fixed. - if (navigation_handle->NavigationStart() > - navigation_handle->FirstRequestStart() || - navigation_handle->FirstRequestStart() > - navigation_handle->FirstResponseStart()) { + if (navigation_start_time > timing.first_request_start_time || + timing.first_request_start_time > timing.first_response_start_time || + timing.first_response_start_time > timing.first_loader_callback_time || + timing.first_loader_callback_time > timing.navigation_commit_sent_time) { + return; + } + if (navigation_start_time > timing.final_request_start_time || + timing.final_request_start_time > timing.final_response_start_time || + timing.final_response_start_time > timing.final_loader_callback_time || + timing.final_loader_callback_time > timing.navigation_commit_sent_time) { return; } + DCHECK_LE(timing.first_request_start_time, timing.final_request_start_time); + DCHECK_LE(timing.first_response_start_time, timing.final_response_start_time); + DCHECK_LE(timing.first_loader_callback_time, + timing.final_loader_callback_time); // Record the elapsed time from the navigation start milestone. PAGE_LOAD_HISTOGRAM( internal::kHistogramNavigationTimingNavigationStartToFirstRequestStart, - navigation_handle->FirstRequestStart() - - navigation_handle->NavigationStart()); + timing.first_request_start_time - navigation_start_time); PAGE_LOAD_HISTOGRAM( internal::kHistogramNavigationTimingNavigationStartToFirstResponseStart, - navigation_handle->FirstResponseStart() - - navigation_handle->NavigationStart()); + timing.first_response_start_time - navigation_start_time); + PAGE_LOAD_HISTOGRAM( + internal::kHistogramNavigationTimingNavigationStartToFirstLoaderCallback, + timing.first_loader_callback_time - navigation_start_time); + + PAGE_LOAD_HISTOGRAM( + internal::kHistogramNavigationTimingNavigationStartToFinalRequestStart, + timing.final_request_start_time - navigation_start_time); + PAGE_LOAD_HISTOGRAM( + internal::kHistogramNavigationTimingNavigationStartToFinalResponseStart, + timing.final_response_start_time - navigation_start_time); + PAGE_LOAD_HISTOGRAM( + internal::kHistogramNavigationTimingNavigationStartToFinalLoaderCallback, + timing.final_loader_callback_time - navigation_start_time); + + PAGE_LOAD_HISTOGRAM( + internal::kHistogramNavigationTimingNavigationStartToNavigationCommitSent, + timing.navigation_commit_sent_time - navigation_start_time); // Record the intervals between milestones. PAGE_LOAD_HISTOGRAM( internal::kHistogramNavigationTimingFirstRequestStartToFirstResponseStart, - navigation_handle->FirstResponseStart() - - navigation_handle->FirstRequestStart()); + timing.first_response_start_time - timing.first_request_start_time); + PAGE_LOAD_HISTOGRAM( + internal:: + kHistogramNavigationTimingFirstResponseStartToFirstLoaderCallback, + timing.first_loader_callback_time - timing.first_response_start_time); + + PAGE_LOAD_HISTOGRAM( + internal::kHistogramNavigationTimingFinalRequestStartToFinalResponseStart, + timing.final_response_start_time - timing.final_request_start_time); + PAGE_LOAD_HISTOGRAM( + internal:: + kHistogramNavigationTimingFinalResponseStartToFinalLoaderCallback, + timing.final_loader_callback_time - timing.final_response_start_time); + + PAGE_LOAD_HISTOGRAM( + internal:: + kHistogramNavigationTimingFinalLoaderCallbackToNavigationCommitSent, + timing.navigation_commit_sent_time - timing.final_loader_callback_time); + + // Record the following intervals for the 103 Early Hints experiment + // (https://crbug.com/1093693). + // - The first request start to the 103 response, + // - The final request start to the 103 response, and the 103 response to the + // final response, + // Note that multiple 103 responses can be served per request. These metrics + // use the first 103 response as the timing. + if (!timing.early_hints_for_first_request_time.is_null()) { + PAGE_LOAD_HISTOGRAM( + internal::kHistogramEarlyHintsFirstRequestStartToEarlyHints, + timing.first_request_start_time - + timing.early_hints_for_first_request_time); + } + if (!timing.early_hints_for_final_request_time.is_null()) { + PAGE_LOAD_HISTOGRAM( + internal::kHistogramEarlyHintsFinalRequestStartToEarlyHints, + timing.final_request_start_time - + timing.early_hints_for_final_request_time); + PAGE_LOAD_HISTOGRAM( + internal::kHistogramEarlyHintsEarlyHintsToFinalResponseStart, + timing.early_hints_for_final_request_time - + timing.final_response_start_time); + } } // This method records values for metrics that were not recorded during any @@ -823,8 +949,34 @@ void CorePageLoadMetricsObserver::RecordTimingHistograms( } const page_load_metrics::ContentfulPaintTimingInfo& + main_frame_largest_image_paint = GetDelegate() + .GetLargestContentfulPaintHandler() + .MainFrameLargestImagePaint(); + if (main_frame_largest_image_paint.ContainsValidTime() && + WasStartedInForegroundOptionalEventInForeground( + main_frame_largest_image_paint.Time(), GetDelegate()) && + font_preload_started_before_rendering_observed_) { + PAGE_LOAD_HISTOGRAM(internal::kHistogramFontPreloadLargestImagePaint, + main_frame_largest_image_paint.Time().value()); + } + + const page_load_metrics::ContentfulPaintTimingInfo& + main_frame_largest_text_paint = GetDelegate() + .GetLargestContentfulPaintHandler() + .MainFrameLargestTextPaint(); + if (main_frame_largest_text_paint.ContainsValidTime() && + WasStartedInForegroundOptionalEventInForeground( + main_frame_largest_text_paint.Time(), GetDelegate()) && + font_preload_started_before_rendering_observed_) { + PAGE_LOAD_HISTOGRAM(internal::kHistogramFontPreloadLargestTextPaint, + main_frame_largest_text_paint.Time().value()); + } + + const page_load_metrics::ContentfulPaintTimingInfo& main_frame_largest_contentful_paint = - largest_contentful_paint_handler_.MainFrameLargestContentfulPaint(); + GetDelegate() + .GetLargestContentfulPaintHandler() + .MainFrameLargestContentfulPaint(); if (main_frame_largest_contentful_paint.ContainsValidTime() && WasStartedInForegroundOptionalEventInForeground( main_frame_largest_contentful_paint.Time(), GetDelegate())) { @@ -837,7 +989,9 @@ void CorePageLoadMetricsObserver::RecordTimingHistograms( const page_load_metrics::ContentfulPaintTimingInfo& all_frames_largest_contentful_paint = - largest_contentful_paint_handler_.MergeMainFrameAndSubframes(); + GetDelegate() + .GetLargestContentfulPaintHandler() + .MergeMainFrameAndSubframes(); if (all_frames_largest_contentful_paint.ContainsValidTime() && WasStartedInForegroundOptionalEventInForeground( all_frames_largest_contentful_paint.Time(), GetDelegate())) { @@ -860,6 +1014,48 @@ void CorePageLoadMetricsObserver::RecordTimingHistograms( } } + const page_load_metrics::ContentfulPaintTimingInfo& + main_frame_experimental_largest_contentful_paint = + GetDelegate() + .GetExperimentalLargestContentfulPaintHandler() + .MainFrameLargestContentfulPaint(); + if (main_frame_experimental_largest_contentful_paint.ContainsValidTime() && + WasStartedInForegroundOptionalEventInForeground( + main_frame_experimental_largest_contentful_paint.Time(), + GetDelegate())) { + PAGE_LOAD_HISTOGRAM( + internal::kHistogramExperimentalLargestContentfulPaintMainFrame, + main_frame_experimental_largest_contentful_paint.Time().value()); + UMA_HISTOGRAM_ENUMERATION( + internal:: + kHistogramExperimentalLargestContentfulPaintMainFrameContentType, + main_frame_experimental_largest_contentful_paint.Type()); + } + + const page_load_metrics::ContentfulPaintTimingInfo& + all_frames_experimental_largest_contentful_paint = + GetDelegate() + .GetExperimentalLargestContentfulPaintHandler() + .MergeMainFrameAndSubframes(); + if (all_frames_experimental_largest_contentful_paint.ContainsValidTime() && + WasStartedInForegroundOptionalEventInForeground( + all_frames_experimental_largest_contentful_paint.Time(), + GetDelegate())) { + PAGE_LOAD_HISTOGRAM( + internal::kHistogramExperimentalLargestContentfulPaint, + all_frames_experimental_largest_contentful_paint.Time().value()); + UMA_HISTOGRAM_ENUMERATION( + internal::kHistogramExperimentalLargestContentfulPaintContentType, + all_frames_experimental_largest_contentful_paint.Type()); + TRACE_EVENT_MARK_WITH_TIMESTAMP1( + "loading", + "NavStartToExperimentalLargestContentfulPaint::AllFrames::UMA", + GetDelegate().GetNavigationStart() + + all_frames_experimental_largest_contentful_paint.Time().value(), + "data", + all_frames_experimental_largest_contentful_paint.DataAsTraceValue()); + } + if (main_frame_timing.paint_timing->first_paint && !main_frame_timing.paint_timing->first_meaningful_paint) { RecordFirstMeaningfulPaintStatus( @@ -1001,19 +1197,6 @@ void CorePageLoadMetricsObserver::RecordCpuUsageHistograms() { foreground_cpu_usage_); } -void CorePageLoadMetricsObserver::OnTimingUpdate( - content::RenderFrameHost* subframe_rfh, - const page_load_metrics::mojom::PageLoadTiming& timing) { - largest_contentful_paint_handler_.RecordTiming(timing.paint_timing, - subframe_rfh); -} - -void CorePageLoadMetricsObserver::OnDidFinishSubFrameNavigation( - content::NavigationHandle* navigation_handle) { - largest_contentful_paint_handler_.OnDidFinishSubFrameNavigation( - navigation_handle, GetDelegate()); -} - page_load_metrics::PageLoadMetricsObserver::ObservePolicy CorePageLoadMetricsObserver::OnEnterBackForwardCache( const page_load_metrics::mojom::PageLoadTiming& timing) { diff --git a/chromium/components/page_load_metrics/browser/observers/core_page_load_metrics_observer.h b/chromium/components/page_load_metrics/browser/observers/core_page_load_metrics_observer.h index 0ffaa0c8704..3fb6e3c42ec 100644 --- a/chromium/components/page_load_metrics/browser/observers/core_page_load_metrics_observer.h +++ b/chromium/components/page_load_metrics/browser/observers/core_page_load_metrics_observer.h @@ -6,7 +6,6 @@ #define COMPONENTS_PAGE_LOAD_METRICS_BROWSER_OBSERVERS_CORE_PAGE_LOAD_METRICS_OBSERVER_H_ #include "components/page_load_metrics/browser/observers/click_input_tracker.h" -#include "components/page_load_metrics/browser/observers/largest_contentful_paint_handler.h" #include "components/page_load_metrics/browser/page_load_metrics_observer.h" #include "services/metrics/public/cpp/ukm_source.h" @@ -32,6 +31,11 @@ extern const char kHistogramLargestContentfulPaint[]; extern const char kHistogramLargestContentfulPaintContentType[]; extern const char kHistogramLargestContentfulPaintMainFrame[]; extern const char kHistogramLargestContentfulPaintMainFrameContentType[]; +extern const char kHistogramExperimentalLargestContentfulPaint[]; +extern const char kHistogramExperimentalLargestContentfulPaintContentType[]; +extern const char kHistogramExperimentalLargestContentfulPaintMainFrame[]; +extern const char + kHistogramExperimentalLargestContentfulPaintMainFrameContentType[]; extern const char kHistogramParseDuration[]; extern const char kHistogramParseBlockedOnScriptLoad[]; extern const char kHistogramParseBlockedOnScriptExecution[]; @@ -105,16 +109,41 @@ extern const char kHistogramBackForwardCacheEvent[]; extern const char kHistogramFontPreloadFirstPaint[]; extern const char kHistogramFontPreloadFirstContentfulPaint[]; extern const char kHistogramFontPreloadLargestContentfulPaint[]; +extern const char kHistogramFontPreloadLargestImagePaint[]; +extern const char kHistogramFontPreloadLargestTextPaint[]; // Navigation metrics from the navigation start. extern const char kHistogramNavigationTimingNavigationStartToFirstRequestStart[]; extern const char kHistogramNavigationTimingNavigationStartToFirstResponseStart[]; +extern const char + kHistogramNavigationTimingNavigationStartToFirstLoaderCallback[]; +extern const char + kHistogramNavigationTimingNavigationStartToFinalRequestStart[]; +extern const char + kHistogramNavigationTimingNavigationStartToFinalResponseStart[]; +extern const char + kHistogramNavigationTimingNavigationStartToFinalLoaderCallback[]; +extern const char + kHistogramNavigationTimingNavigationStartToNavigationCommitSent[]; // Navigation metrics between milestones. extern const char kHistogramNavigationTimingFirstRequestStartToFirstResponseStart[]; +extern const char + kHistogramNavigationTimingFirstResponseStartToFirstLoaderCallback[]; +extern const char + kHistogramNavigationTimingFinalRequestStartToFinalResponseStart[]; +extern const char + kHistogramNavigationTimingFinalResponseStartToFinalLoaderCallback[]; +extern const char + kHistogramNavigationTimingFinalLoaderCallbackToNavigationCommitSent[]; + +// 103 Early Hints metrics for experiment (https://crbug.com/1093693). +extern const char kHistogramEarlyHintsFirstRequestStartToEarlyHints[]; +extern const char kHistogramEarlyHintsFinalRequestStartToEarlyHints[]; +extern const char kHistogramEarlyHintsEarlyHintsToFinalResponseStart[]; enum FirstMeaningfulPaintStatus { FIRST_MEANINGFUL_PAINT_RECORDED, @@ -181,14 +210,9 @@ class CorePageLoadMetricsObserver content::RenderFrameHost* rfh, const std::vector<page_load_metrics::mojom::ResourceDataUpdatePtr>& resources) override; - void OnTimingUpdate( - content::RenderFrameHost* subframe_rfh, - const page_load_metrics::mojom::PageLoadTiming& timing) override; void OnCpuTimingUpdate( content::RenderFrameHost* subframe_rfh, const page_load_metrics::mojom::CpuTiming& timing) override; - void OnDidFinishSubFrameNavigation( - content::NavigationHandle* navigation_handle) override; ObservePolicy OnEnterBackForwardCache( const page_load_metrics::mojom::PageLoadTiming& timing) override; void OnRestoreFromBackForwardCache( @@ -197,8 +221,7 @@ class CorePageLoadMetricsObserver int behavior_flags) override; private: - void RecordNavigationTimingHistograms( - content::NavigationHandle* navigation_handle); + void RecordNavigationTimingHistograms(); void RecordTimingHistograms( const page_load_metrics::mojom::PageLoadTiming& main_frame_timing); void RecordByteAndResourceHistograms( @@ -208,6 +231,8 @@ class CorePageLoadMetricsObserver const page_load_metrics::mojom::PageLoadTiming& timing, base::TimeTicks app_background_time); + content::NavigationHandleTiming navigation_handle_timing_; + ui::PageTransition transition_; bool was_no_store_main_resource_; @@ -244,9 +269,6 @@ class CorePageLoadMetricsObserver base::TimeTicks first_paint_; - page_load_metrics::LargestContentfulPaintHandler - largest_contentful_paint_handler_; - // Tracks user input clicks for possible click burst. page_load_metrics::ClickInputTracker click_tracker_; diff --git a/chromium/components/page_load_metrics/browser/observers/core_page_load_metrics_observer_unittest.cc b/chromium/components/page_load_metrics/browser/observers/core_page_load_metrics_observer_unittest.cc index f9e7623e6b0..123f97f986e 100644 --- a/chromium/components/page_load_metrics/browser/observers/core_page_load_metrics_observer_unittest.cc +++ b/chromium/components/page_load_metrics/browser/observers/core_page_load_metrics_observer_unittest.cc @@ -8,6 +8,7 @@ #include "base/test/power_monitor_test_base.h" #include "components/page_load_metrics/browser/metrics_web_contents_observer.h" +#include "components/page_load_metrics/browser/observers/largest_contentful_paint_handler.h" #include "components/page_load_metrics/browser/observers/page_load_metrics_observer_content_test_harness.h" #include "components/page_load_metrics/browser/page_load_metrics_util.h" #include "components/page_load_metrics/browser/page_load_tracker.h" @@ -22,7 +23,7 @@ using content::NavigationSimulator; using content::RenderFrameHost; using content::RenderFrameHostTester; using LargestContentType = - page_load_metrics::PageLoadMetricsObserver::LargestContentType; + page_load_metrics::ContentfulPaintTimingInfo::LargestContentType; namespace { @@ -57,6 +58,101 @@ class CorePageLoadMetricsObserverTest page_load_metrics::mojom::CpuTiming cpu_timing(cpu_time_spent); tester()->SimulateCpuTimingUpdate(cpu_timing, render_frame_host); } + + void TestNoLCP() { + tester()->histogram_tester().ExpectTotalCount( + internal::kHistogramLargestContentfulPaint, 0); + tester()->histogram_tester().ExpectTotalCount( + internal::kHistogramLargestContentfulPaintContentType, 0); + tester()->histogram_tester().ExpectTotalCount( + internal::kHistogramLargestContentfulPaintMainFrame, 0); + tester()->histogram_tester().ExpectTotalCount( + internal::kHistogramLargestContentfulPaintMainFrameContentType, 0); + + // Experimental values + tester()->histogram_tester().ExpectTotalCount( + internal::kHistogramExperimentalLargestContentfulPaint, 0); + tester()->histogram_tester().ExpectTotalCount( + internal::kHistogramExperimentalLargestContentfulPaintContentType, 0); + tester()->histogram_tester().ExpectTotalCount( + internal::kHistogramExperimentalLargestContentfulPaintMainFrame, 0); + tester()->histogram_tester().ExpectTotalCount( + internal:: + kHistogramExperimentalLargestContentfulPaintMainFrameContentType, + 0); + } + + void TestAllFramesLCP(int value, LargestContentType type) { + EXPECT_THAT(tester()->histogram_tester().GetAllSamples( + internal::kHistogramLargestContentfulPaint), + testing::ElementsAre(base::Bucket(value, 1))); + EXPECT_THAT(tester()->histogram_tester().GetAllSamples( + internal::kHistogramLargestContentfulPaintContentType), + testing::ElementsAre(base::Bucket( + static_cast<base::HistogramBase::Sample>(type), 1))); + + // Experimental values + EXPECT_THAT(tester()->histogram_tester().GetAllSamples( + internal::kHistogramExperimentalLargestContentfulPaint), + testing::ElementsAre(base::Bucket(value, 1))); + EXPECT_THAT( + tester()->histogram_tester().GetAllSamples( + internal::kHistogramExperimentalLargestContentfulPaintContentType), + testing::ElementsAre( + base::Bucket(static_cast<base::HistogramBase::Sample>(type), 1))); + } + + void TestMainFrameLCP(int value, LargestContentType type) { + EXPECT_THAT(tester()->histogram_tester().GetAllSamples( + internal::kHistogramLargestContentfulPaintMainFrame), + testing::ElementsAre(base::Bucket(value, 1))); + EXPECT_THAT( + tester()->histogram_tester().GetAllSamples( + internal::kHistogramLargestContentfulPaintMainFrameContentType), + testing::ElementsAre( + base::Bucket(static_cast<base::HistogramBase::Sample>(type), 1))); + + // Experimental values + EXPECT_THAT( + tester()->histogram_tester().GetAllSamples( + internal::kHistogramExperimentalLargestContentfulPaintMainFrame), + testing::ElementsAre(base::Bucket(value, 1))); + EXPECT_THAT( + tester()->histogram_tester().GetAllSamples( + internal:: + kHistogramExperimentalLargestContentfulPaintMainFrameContentType), + testing::ElementsAre( + base::Bucket(static_cast<base::HistogramBase::Sample>(type), 1))); + } + + void TestEmptyMainFrameLCP() { + EXPECT_TRUE( + tester() + ->histogram_tester() + .GetAllSamples(internal::kHistogramLargestContentfulPaintMainFrame) + .empty()); + EXPECT_TRUE( + tester() + ->histogram_tester() + .GetAllSamples( + internal::kHistogramLargestContentfulPaintMainFrameContentType) + .empty()); + + // Experimental LCP histograms + EXPECT_TRUE( + tester() + ->histogram_tester() + .GetAllSamples( + internal::kHistogramExperimentalLargestContentfulPaintMainFrame) + .empty()); + EXPECT_TRUE( + tester() + ->histogram_tester() + .GetAllSamples( + internal:: + kHistogramExperimentalLargestContentfulPaintMainFrameContentType) + .empty()); + } }; TEST_F(CorePageLoadMetricsObserverTest, NoMetrics) { @@ -508,18 +604,31 @@ TEST_F(CorePageLoadMetricsObserverTest, ForwardBack) { TEST_F(CorePageLoadMetricsObserverTest, NavigationTiming) { GURL url(kDefaultTestUrl); tester()->NavigateWithPageTransitionAndCommit(url, ui::PAGE_TRANSITION_LINK); + tester()->NavigateToUntrackedUrl(); // Verify if the elapsed times from the navigation start are recorded. std::vector<const char*> metrics_from_navigation_start = { internal::kHistogramNavigationTimingNavigationStartToFirstRequestStart, - internal::kHistogramNavigationTimingNavigationStartToFirstResponseStart}; + internal::kHistogramNavigationTimingNavigationStartToFirstResponseStart, + internal::kHistogramNavigationTimingNavigationStartToFirstLoaderCallback, + internal::kHistogramNavigationTimingNavigationStartToFinalRequestStart, + internal::kHistogramNavigationTimingNavigationStartToFinalResponseStart, + internal::kHistogramNavigationTimingNavigationStartToFinalLoaderCallback, + internal:: + kHistogramNavigationTimingNavigationStartToNavigationCommitSent}; for (const char* metric : metrics_from_navigation_start) tester()->histogram_tester().ExpectTotalCount(metric, 1); // Verify if the intervals between adjacent milestones are recorded. std::vector<const char*> metrics_between_milestones = { + internal::kHistogramNavigationTimingFirstRequestStartToFirstResponseStart, + internal:: + kHistogramNavigationTimingFirstResponseStartToFirstLoaderCallback, + internal::kHistogramNavigationTimingFinalRequestStartToFinalResponseStart, internal:: - kHistogramNavigationTimingFirstRequestStartToFirstResponseStart}; + kHistogramNavigationTimingFinalResponseStartToFinalLoaderCallback, + internal:: + kHistogramNavigationTimingFinalLoaderCallbackToNavigationCommitSent}; for (const char* metric : metrics_between_milestones) tester()->histogram_tester().ExpectTotalCount(metric, 1); } @@ -659,13 +768,16 @@ TEST_F(CorePageLoadMetricsObserverTest, LargestImageLoading) { page_load_metrics::InitPageLoadTimingForTest(&timing); timing.navigation_start = base::Time::FromDoubleT(1); // Largest image is loading so its timestamp is TimeDelta(). - timing.paint_timing->largest_image_paint = base::TimeDelta(); - timing.paint_timing->largest_image_paint_size = 100u; + timing.paint_timing->largest_contentful_paint->largest_image_paint = + base::TimeDelta(); + timing.paint_timing->largest_contentful_paint->largest_image_paint_size = + 100u; // There is a text paint but it's smaller than image. Pick a value that lines // up with a histogram bucket. - timing.paint_timing->largest_text_paint = + timing.paint_timing->largest_contentful_paint->largest_text_paint = base::TimeDelta::FromMilliseconds(4780); - timing.paint_timing->largest_text_paint_size = 70u; + timing.paint_timing->largest_contentful_paint->largest_text_paint_size = 70u; + PopulateExperimentalLCP(timing.paint_timing); PopulateRequiredTimingFields(&timing); NavigateAndCommit(GURL(kDefaultTestUrl)); @@ -674,8 +786,7 @@ TEST_F(CorePageLoadMetricsObserverTest, LargestImageLoading) { NavigateAndCommit(GURL(kDefaultTestUrl2)); // The image was larger so LCP should NOT be reported. - tester()->histogram_tester().ExpectTotalCount( - internal::kHistogramLargestContentfulPaint, 0); + TestNoLCP(); } TEST_F(CorePageLoadMetricsObserverTest, LargestImageLoadingSmallerThanText) { @@ -683,13 +794,16 @@ TEST_F(CorePageLoadMetricsObserverTest, LargestImageLoadingSmallerThanText) { page_load_metrics::InitPageLoadTimingForTest(&timing); timing.navigation_start = base::Time::FromDoubleT(1); // Largest image is loading so its timestamp is TimeDelta(). - timing.paint_timing->largest_image_paint = base::TimeDelta(); - timing.paint_timing->largest_image_paint_size = 100u; + timing.paint_timing->largest_contentful_paint->largest_image_paint = + base::TimeDelta(); + timing.paint_timing->largest_contentful_paint->largest_image_paint_size = + 100u; // There is a text paint but it's smaller than image. Pick a value that lines // up with a histogram bucket. - timing.paint_timing->largest_text_paint = + timing.paint_timing->largest_contentful_paint->largest_text_paint = base::TimeDelta::FromMilliseconds(4780); - timing.paint_timing->largest_text_paint_size = 120u; + timing.paint_timing->largest_contentful_paint->largest_text_paint_size = 120u; + PopulateExperimentalLCP(timing.paint_timing); PopulateRequiredTimingFields(&timing); NavigateAndCommit(GURL(kDefaultTestUrl)); @@ -697,9 +811,7 @@ TEST_F(CorePageLoadMetricsObserverTest, LargestImageLoadingSmallerThanText) { // Navigate again to force histogram recording. NavigateAndCommit(GURL(kDefaultTestUrl2)); - EXPECT_THAT(tester()->histogram_tester().GetAllSamples( - internal::kHistogramLargestContentfulPaint), - testing::ElementsAre(base::Bucket(4780, 1))); + TestAllFramesLCP(4780, LargestContentType::kText); } TEST_F(CorePageLoadMetricsObserverTest, @@ -716,9 +828,11 @@ TEST_F(CorePageLoadMetricsObserverTest, page_load_metrics::mojom::PageLoadTiming subframe_timing; page_load_metrics::InitPageLoadTimingForTest(&subframe_timing); subframe_timing.navigation_start = base::Time::FromDoubleT(200); - subframe_timing.paint_timing->largest_image_paint = + subframe_timing.paint_timing->largest_contentful_paint->largest_image_paint = base::TimeDelta::FromMilliseconds(4780); - subframe_timing.paint_timing->largest_image_paint_size = 100u; + subframe_timing.paint_timing->largest_contentful_paint + ->largest_image_paint_size = 100u; + PopulateExperimentalLCP(subframe_timing.paint_timing); PopulateRequiredTimingFields(&subframe_timing); // Commit the main frame and a subframe. @@ -736,26 +850,8 @@ TEST_F(CorePageLoadMetricsObserverTest, // Navigate again to force histogram recording in the main frame. NavigateAndCommit(GURL(kDefaultTestUrl2)); - EXPECT_THAT(tester()->histogram_tester().GetAllSamples( - internal::kHistogramLargestContentfulPaint), - testing::ElementsAre(base::Bucket(4780, 1))); - EXPECT_THAT( - tester()->histogram_tester().GetAllSamples( - internal::kHistogramLargestContentfulPaintContentType), - testing::ElementsAre(base::Bucket( - static_cast<base::HistogramBase::Sample>(LargestContentType::kImage), - 1))); - EXPECT_TRUE( - tester() - ->histogram_tester() - .GetAllSamples(internal::kHistogramLargestContentfulPaintMainFrame) - .empty()); - EXPECT_TRUE( - tester() - ->histogram_tester() - .GetAllSamples( - internal::kHistogramLargestContentfulPaintMainFrameContentType) - .empty()); + TestAllFramesLCP(4780, LargestContentType::kImage); + TestEmptyMainFrameLCP(); } TEST_F(CorePageLoadMetricsObserverTest, @@ -772,11 +868,15 @@ TEST_F(CorePageLoadMetricsObserverTest, page_load_metrics::mojom::PageLoadTiming subframe_timing; page_load_metrics::InitPageLoadTimingForTest(&subframe_timing); subframe_timing.navigation_start = base::Time::FromDoubleT(200); - subframe_timing.paint_timing->largest_image_paint = base::TimeDelta(); - subframe_timing.paint_timing->largest_image_paint_size = 100u; - subframe_timing.paint_timing->largest_text_paint = + subframe_timing.paint_timing->largest_contentful_paint->largest_image_paint = + base::TimeDelta(); + subframe_timing.paint_timing->largest_contentful_paint + ->largest_image_paint_size = 100u; + subframe_timing.paint_timing->largest_contentful_paint->largest_text_paint = base::TimeDelta::FromMilliseconds(500); - subframe_timing.paint_timing->largest_text_paint_size = 80u; + subframe_timing.paint_timing->largest_contentful_paint + ->largest_text_paint_size = 80u; + PopulateExperimentalLCP(subframe_timing.paint_timing); PopulateRequiredTimingFields(&subframe_timing); // Commit the main frame and a subframe. @@ -794,14 +894,7 @@ TEST_F(CorePageLoadMetricsObserverTest, // Navigate again to force histogram recording in the main frame. NavigateAndCommit(GURL(kDefaultTestUrl2)); - tester()->histogram_tester().ExpectTotalCount( - internal::kHistogramLargestContentfulPaint, 0); - tester()->histogram_tester().ExpectTotalCount( - internal::kHistogramLargestContentfulPaintContentType, 0); - tester()->histogram_tester().ExpectTotalCount( - internal::kHistogramLargestContentfulPaintMainFrame, 0); - tester()->histogram_tester().ExpectTotalCount( - internal::kHistogramLargestContentfulPaintMainFrameContentType, 0); + TestNoLCP(); } TEST_F(CorePageLoadMetricsObserverTest, @@ -812,9 +905,10 @@ TEST_F(CorePageLoadMetricsObserverTest, page_load_metrics::InitPageLoadTimingForTest(&timing); timing.navigation_start = base::Time::FromDoubleT(1); // Pick a value that lines up with a histogram bucket. - timing.paint_timing->largest_image_paint = + timing.paint_timing->largest_contentful_paint->largest_image_paint = base::TimeDelta::FromMilliseconds(4780); - timing.paint_timing->largest_image_paint_size = 50u; + timing.paint_timing->largest_contentful_paint->largest_image_paint_size = 50u; + PopulateExperimentalLCP(timing.paint_timing); PopulateRequiredTimingFields(&timing); page_load_metrics::mojom::PageLoadTiming subframe_timing; @@ -838,24 +932,8 @@ TEST_F(CorePageLoadMetricsObserverTest, // Navigate again to force histogram recording in the main frame. NavigateAndCommit(GURL(kDefaultTestUrl2)); - EXPECT_THAT(tester()->histogram_tester().GetAllSamples( - internal::kHistogramLargestContentfulPaint), - testing::ElementsAre(base::Bucket(4780, 1))); - EXPECT_THAT( - tester()->histogram_tester().GetAllSamples( - internal::kHistogramLargestContentfulPaintContentType), - testing::ElementsAre(base::Bucket( - static_cast<base::HistogramBase::Sample>(LargestContentType::kImage), - 1))); - EXPECT_THAT(tester()->histogram_tester().GetAllSamples( - internal::kHistogramLargestContentfulPaintMainFrame), - testing::ElementsAre(base::Bucket(4780, 1))); - EXPECT_THAT( - tester()->histogram_tester().GetAllSamples( - internal::kHistogramLargestContentfulPaintMainFrameContentType), - testing::ElementsAre(base::Bucket( - static_cast<base::HistogramBase::Sample>(LargestContentType::kImage), - 1))); + TestAllFramesLCP(4780, LargestContentType::kImage); + TestMainFrameLCP(4780, LargestContentType::kImage); } // This is to test whether LargestContentfulPaintAllFrames could merge @@ -870,18 +948,21 @@ TEST_F(CorePageLoadMetricsObserverTest, page_load_metrics::InitPageLoadTimingForTest(&timing); timing.navigation_start = base::Time::FromDoubleT(1); // Pick a value that lines up with a histogram bucket. - timing.paint_timing->largest_image_paint = + timing.paint_timing->largest_contentful_paint->largest_image_paint = base::TimeDelta::FromMilliseconds(9382); - timing.paint_timing->largest_image_paint_size = 50u; + timing.paint_timing->largest_contentful_paint->largest_image_paint_size = 50u; + PopulateExperimentalLCP(timing.paint_timing); PopulateRequiredTimingFields(&timing); // Create a candidate in subframe with a larger size. page_load_metrics::mojom::PageLoadTiming subframe_timing; page_load_metrics::InitPageLoadTimingForTest(&subframe_timing); subframe_timing.navigation_start = base::Time::FromDoubleT(2); - subframe_timing.paint_timing->largest_image_paint = + subframe_timing.paint_timing->largest_contentful_paint->largest_image_paint = base::TimeDelta::FromMilliseconds(4780); - subframe_timing.paint_timing->largest_image_paint_size = 100u; + subframe_timing.paint_timing->largest_contentful_paint + ->largest_image_paint_size = 100u; + PopulateExperimentalLCP(subframe_timing.paint_timing); PopulateRequiredTimingFields(&subframe_timing); // Commit the main frame and a subframe. @@ -899,25 +980,8 @@ TEST_F(CorePageLoadMetricsObserverTest, // Navigate again to force histogram recording in the main frame. NavigateAndCommit(GURL(kDefaultTestUrl2)); - EXPECT_THAT(tester()->histogram_tester().GetAllSamples( - internal::kHistogramLargestContentfulPaint), - testing::ElementsAre(base::Bucket(4780, 1))); - EXPECT_THAT( - tester()->histogram_tester().GetAllSamples( - internal::kHistogramLargestContentfulPaintContentType), - testing::ElementsAre(base::Bucket( - static_cast<base::HistogramBase::Sample>(LargestContentType::kImage), - 1))); - - EXPECT_THAT(tester()->histogram_tester().GetAllSamples( - internal::kHistogramLargestContentfulPaintMainFrame), - testing::ElementsAre(base::Bucket(9382, 1))); - EXPECT_THAT( - tester()->histogram_tester().GetAllSamples( - internal::kHistogramLargestContentfulPaintMainFrameContentType), - testing::ElementsAre(base::Bucket( - static_cast<base::HistogramBase::Sample>(LargestContentType::kImage), - 1))); + TestAllFramesLCP(4780, LargestContentType::kImage); + TestMainFrameLCP(9382, LargestContentType::kImage); } // This is to test whether LargestContentfulPaintAllFrames could merge @@ -931,18 +995,21 @@ TEST_F(CorePageLoadMetricsObserverTest, page_load_metrics::InitPageLoadTimingForTest(&timing); timing.navigation_start = base::Time::FromDoubleT(1); // Pick a value that lines up with a histogram bucket. - timing.paint_timing->largest_text_paint = + timing.paint_timing->largest_contentful_paint->largest_text_paint = base::TimeDelta::FromMilliseconds(4780); - timing.paint_timing->largest_text_paint_size = 100u; + timing.paint_timing->largest_contentful_paint->largest_text_paint_size = 100u; + PopulateExperimentalLCP(timing.paint_timing); PopulateRequiredTimingFields(&timing); // Create a candidate in subframe with a smaller size. page_load_metrics::mojom::PageLoadTiming subframe_timing; page_load_metrics::InitPageLoadTimingForTest(&subframe_timing); subframe_timing.navigation_start = base::Time::FromDoubleT(2); - subframe_timing.paint_timing->largest_text_paint = + subframe_timing.paint_timing->largest_contentful_paint->largest_text_paint = base::TimeDelta::FromMilliseconds(300); - subframe_timing.paint_timing->largest_text_paint_size = 50u; + subframe_timing.paint_timing->largest_contentful_paint + ->largest_text_paint_size = 50u; + PopulateExperimentalLCP(subframe_timing.paint_timing); PopulateRequiredTimingFields(&subframe_timing); // Commit the main frame and a subframe. @@ -960,25 +1027,8 @@ TEST_F(CorePageLoadMetricsObserverTest, // Navigate again to force histogram recording in the main frame. NavigateAndCommit(GURL(kDefaultTestUrl2)); - EXPECT_THAT(tester()->histogram_tester().GetAllSamples( - internal::kHistogramLargestContentfulPaint), - testing::ElementsAre(base::Bucket(4780, 1))); - EXPECT_THAT( - tester()->histogram_tester().GetAllSamples( - internal::kHistogramLargestContentfulPaintContentType), - testing::ElementsAre(base::Bucket( - static_cast<base::HistogramBase::Sample>(LargestContentType::kText), - 1))); - - EXPECT_THAT(tester()->histogram_tester().GetAllSamples( - internal::kHistogramLargestContentfulPaintMainFrame), - testing::ElementsAre(base::Bucket(4780, 1))); - EXPECT_THAT( - tester()->histogram_tester().GetAllSamples( - internal::kHistogramLargestContentfulPaintMainFrameContentType), - testing::ElementsAre(base::Bucket( - static_cast<base::HistogramBase::Sample>(LargestContentType::kText), - 1))); + TestAllFramesLCP(4780, LargestContentType::kText); + TestMainFrameLCP(4780, LargestContentType::kText); } // This tests a trade-off we have made - aggregating all subframe candidates, @@ -997,9 +1047,11 @@ TEST_F(CorePageLoadMetricsObserverTest, page_load_metrics::mojom::PageLoadTiming subframe_timing; page_load_metrics::InitPageLoadTimingForTest(&subframe_timing); subframe_timing.navigation_start = base::Time::FromDoubleT(2); - subframe_timing.paint_timing->largest_image_paint = + subframe_timing.paint_timing->largest_contentful_paint->largest_image_paint = base::TimeDelta::FromMilliseconds(4780); - subframe_timing.paint_timing->largest_image_paint_size = 50u; + subframe_timing.paint_timing->largest_contentful_paint + ->largest_image_paint_size = 50u; + PopulateExperimentalLCP(subframe_timing.paint_timing); PopulateRequiredTimingFields(&subframe_timing); // Commit the main frame and a subframe. @@ -1014,24 +1066,18 @@ TEST_F(CorePageLoadMetricsObserverTest, tester()->SimulateTimingUpdate(timing); tester()->SimulateTimingUpdate(subframe_timing, subframe); - subframe_timing.paint_timing->largest_image_paint = + subframe_timing.paint_timing->largest_contentful_paint->largest_image_paint = base::TimeDelta::FromMilliseconds(300); - subframe_timing.paint_timing->largest_image_paint_size = 10u; + subframe_timing.paint_timing->largest_contentful_paint + ->largest_image_paint_size = 10u; + PopulateExperimentalLCP(subframe_timing.paint_timing); tester()->SimulateTimingUpdate(subframe_timing, subframe); // Navigate again to force histogram recording in the main frame. NavigateAndCommit(GURL(kDefaultTestUrl2)); // Ensure that the largest_image_paint timing for the main frame is recorded. - EXPECT_THAT(tester()->histogram_tester().GetAllSamples( - internal::kHistogramLargestContentfulPaint), - testing::ElementsAre(base::Bucket(4780, 1))); - EXPECT_THAT( - tester()->histogram_tester().GetAllSamples( - internal::kHistogramLargestContentfulPaintContentType), - testing::ElementsAre(base::Bucket( - static_cast<base::HistogramBase::Sample>(LargestContentType::kImage), - 1))); + TestAllFramesLCP(4780, LargestContentType::kImage); } // This tests a trade-off we have made - aggregating all subframe candidates, @@ -1051,9 +1097,11 @@ TEST_F( page_load_metrics::mojom::PageLoadTiming subframe_timing; page_load_metrics::InitPageLoadTimingForTest(&subframe_timing); subframe_timing.navigation_start = base::Time::FromDoubleT(2); - subframe_timing.paint_timing->largest_image_paint = + subframe_timing.paint_timing->largest_contentful_paint->largest_image_paint = base::TimeDelta::FromMilliseconds(4780); - subframe_timing.paint_timing->largest_image_paint_size = 10u; + subframe_timing.paint_timing->largest_contentful_paint + ->largest_image_paint_size = 10u; + PopulateExperimentalLCP(subframe_timing.paint_timing); PopulateRequiredTimingFields(&subframe_timing); // Commit the main frame and a subframe. @@ -1068,24 +1116,18 @@ TEST_F( tester()->SimulateTimingUpdate(timing); tester()->SimulateTimingUpdate(subframe_timing, subframe); - subframe_timing.paint_timing->largest_image_paint = + subframe_timing.paint_timing->largest_contentful_paint->largest_image_paint = base::TimeDelta::FromMilliseconds(990); - subframe_timing.paint_timing->largest_image_paint_size = 50u; + subframe_timing.paint_timing->largest_contentful_paint + ->largest_image_paint_size = 50u; + PopulateExperimentalLCP(subframe_timing.paint_timing); tester()->SimulateTimingUpdate(subframe_timing, subframe); // Navigate again to force histogram recording in the main frame. NavigateAndCommit(GURL(kDefaultTestUrl2)); // Ensure that the largest_image_paint timing for the main frame is recorded. - EXPECT_THAT(tester()->histogram_tester().GetAllSamples( - internal::kHistogramLargestContentfulPaint), - testing::ElementsAre(base::Bucket(990, 1))); - EXPECT_THAT( - tester()->histogram_tester().GetAllSamples( - internal::kHistogramLargestContentfulPaintContentType), - testing::ElementsAre(base::Bucket( - static_cast<base::HistogramBase::Sample>(LargestContentType::kImage), - 1))); + TestAllFramesLCP(990, LargestContentType::kImage); } TEST_F(CorePageLoadMetricsObserverTest, LargestContentfulPaint_NoTextOrImage) { @@ -1094,7 +1136,8 @@ TEST_F(CorePageLoadMetricsObserverTest, LargestContentfulPaint_NoTextOrImage) { timing.navigation_start = base::Time::FromDoubleT(1); // When the size is 0, the timing is regarded as not set and should be // excluded from recording to UMA. - timing.paint_timing->largest_text_paint_size = 0u; + timing.paint_timing->largest_contentful_paint->largest_text_paint_size = 0u; + PopulateExperimentalLCP(timing.paint_timing); PopulateRequiredTimingFields(&timing); NavigateAndCommit(GURL(kDefaultTestUrl)); @@ -1102,14 +1145,7 @@ TEST_F(CorePageLoadMetricsObserverTest, LargestContentfulPaint_NoTextOrImage) { // Navigate again to force histogram recording. NavigateAndCommit(GURL(kDefaultTestUrl2)); - tester()->histogram_tester().ExpectTotalCount( - internal::kHistogramLargestContentfulPaint, 0); - tester()->histogram_tester().ExpectTotalCount( - internal::kHistogramLargestContentfulPaintContentType, 0); - tester()->histogram_tester().ExpectTotalCount( - internal::kHistogramLargestContentfulPaintMainFrame, 0); - tester()->histogram_tester().ExpectTotalCount( - internal::kHistogramLargestContentfulPaintMainFrameContentType, 0); + TestNoLCP(); } TEST_F(CorePageLoadMetricsObserverTest, LargestContentfulPaint_OnlyText) { @@ -1117,9 +1153,10 @@ TEST_F(CorePageLoadMetricsObserverTest, LargestContentfulPaint_OnlyText) { page_load_metrics::InitPageLoadTimingForTest(&timing); timing.navigation_start = base::Time::FromDoubleT(1); // Pick a value that lines up with a histogram bucket. - timing.paint_timing->largest_text_paint = + timing.paint_timing->largest_contentful_paint->largest_text_paint = base::TimeDelta::FromMilliseconds(4780); - timing.paint_timing->largest_text_paint_size = 100; + timing.paint_timing->largest_contentful_paint->largest_text_paint_size = 100; + PopulateExperimentalLCP(timing.paint_timing); PopulateRequiredTimingFields(&timing); NavigateAndCommit(GURL(kDefaultTestUrl)); @@ -1127,15 +1164,7 @@ TEST_F(CorePageLoadMetricsObserverTest, LargestContentfulPaint_OnlyText) { // Navigate again to force histogram recording. NavigateAndCommit(GURL(kDefaultTestUrl2)); - EXPECT_THAT(tester()->histogram_tester().GetAllSamples( - internal::kHistogramLargestContentfulPaint), - testing::ElementsAre(base::Bucket(4780, 1))); - EXPECT_THAT( - tester()->histogram_tester().GetAllSamples( - internal::kHistogramLargestContentfulPaintContentType), - testing::ElementsAre(base::Bucket( - static_cast<base::HistogramBase::Sample>(LargestContentType::kText), - 1))); + TestAllFramesLCP(4780, LargestContentType::kText); } TEST_F(CorePageLoadMetricsObserverTest, LargestContentfulPaint_OnlyImage) { @@ -1143,9 +1172,10 @@ TEST_F(CorePageLoadMetricsObserverTest, LargestContentfulPaint_OnlyImage) { page_load_metrics::InitPageLoadTimingForTest(&timing); timing.navigation_start = base::Time::FromDoubleT(1); // Pick a value that lines up with a histogram bucket. - timing.paint_timing->largest_image_paint = + timing.paint_timing->largest_contentful_paint->largest_image_paint = base::TimeDelta::FromMilliseconds(4780); - timing.paint_timing->largest_image_paint_size = 100; + timing.paint_timing->largest_contentful_paint->largest_image_paint_size = 100; + PopulateExperimentalLCP(timing.paint_timing); PopulateRequiredTimingFields(&timing); NavigateAndCommit(GURL(kDefaultTestUrl)); @@ -1153,15 +1183,7 @@ TEST_F(CorePageLoadMetricsObserverTest, LargestContentfulPaint_OnlyImage) { // Navigate again to force histogram recording. NavigateAndCommit(GURL(kDefaultTestUrl2)); - EXPECT_THAT(tester()->histogram_tester().GetAllSamples( - internal::kHistogramLargestContentfulPaint), - testing::ElementsAre(base::Bucket(4780, 1))); - EXPECT_THAT( - tester()->histogram_tester().GetAllSamples( - internal::kHistogramLargestContentfulPaintContentType), - testing::ElementsAre(base::Bucket( - static_cast<base::HistogramBase::Sample>(LargestContentType::kImage), - 1))); + TestAllFramesLCP(4780, LargestContentType::kImage); } TEST_F(CorePageLoadMetricsObserverTest, @@ -1170,12 +1192,13 @@ TEST_F(CorePageLoadMetricsObserverTest, page_load_metrics::InitPageLoadTimingForTest(&timing); timing.navigation_start = base::Time::FromDoubleT(1); // Pick a value that lines up with a histogram bucket. - timing.paint_timing->largest_image_paint = + timing.paint_timing->largest_contentful_paint->largest_image_paint = base::TimeDelta::FromMilliseconds(4780); - timing.paint_timing->largest_image_paint_size = 100; - timing.paint_timing->largest_text_paint = + timing.paint_timing->largest_contentful_paint->largest_image_paint_size = 100; + timing.paint_timing->largest_contentful_paint->largest_text_paint = base::TimeDelta::FromMilliseconds(1000); - timing.paint_timing->largest_text_paint_size = 10; + timing.paint_timing->largest_contentful_paint->largest_text_paint_size = 10; + PopulateExperimentalLCP(timing.paint_timing); PopulateRequiredTimingFields(&timing); NavigateAndCommit(GURL(kDefaultTestUrl)); @@ -1183,15 +1206,7 @@ TEST_F(CorePageLoadMetricsObserverTest, // Navigate again to force histogram recording. NavigateAndCommit(GURL(kDefaultTestUrl2)); - EXPECT_THAT(tester()->histogram_tester().GetAllSamples( - internal::kHistogramLargestContentfulPaint), - testing::ElementsAre(base::Bucket(4780, 1))); - EXPECT_THAT( - tester()->histogram_tester().GetAllSamples( - internal::kHistogramLargestContentfulPaintContentType), - testing::ElementsAre(base::Bucket( - static_cast<base::HistogramBase::Sample>(LargestContentType::kImage), - 1))); + TestAllFramesLCP(4780, LargestContentType::kImage); } TEST_F(CorePageLoadMetricsObserverTest, @@ -1199,13 +1214,14 @@ TEST_F(CorePageLoadMetricsObserverTest, page_load_metrics::mojom::PageLoadTiming timing; page_load_metrics::InitPageLoadTimingForTest(&timing); timing.navigation_start = base::Time::FromDoubleT(1); - timing.paint_timing->largest_image_paint = + timing.paint_timing->largest_contentful_paint->largest_image_paint = base::TimeDelta::FromMilliseconds(4780); - timing.paint_timing->largest_image_paint_size = 10; + timing.paint_timing->largest_contentful_paint->largest_image_paint_size = 10; // Pick a value that lines up with a histogram bucket. - timing.paint_timing->largest_text_paint = + timing.paint_timing->largest_contentful_paint->largest_text_paint = base::TimeDelta::FromMilliseconds(990); - timing.paint_timing->largest_text_paint_size = 100; + timing.paint_timing->largest_contentful_paint->largest_text_paint_size = 100; + PopulateExperimentalLCP(timing.paint_timing); PopulateRequiredTimingFields(&timing); NavigateAndCommit(GURL(kDefaultTestUrl)); @@ -1213,15 +1229,7 @@ TEST_F(CorePageLoadMetricsObserverTest, // Navigate again to force histogram recording. NavigateAndCommit(GURL(kDefaultTestUrl2)); - EXPECT_THAT(tester()->histogram_tester().GetAllSamples( - internal::kHistogramLargestContentfulPaint), - testing::ElementsAre(base::Bucket(990, 1))); - EXPECT_THAT( - tester()->histogram_tester().GetAllSamples( - internal::kHistogramLargestContentfulPaintContentType), - testing::ElementsAre(base::Bucket( - static_cast<base::HistogramBase::Sample>(LargestContentType::kText), - 1))); + TestAllFramesLCP(990, LargestContentType::kText); } TEST_F(CorePageLoadMetricsObserverTest, ForegroundToFirstMeaningfulPaint) { @@ -1423,12 +1431,13 @@ TEST_F(CorePageLoadMetricsObserverTest, FontPreloadHistogramsNotObserved) { timing.paint_timing->first_paint = base::TimeDelta::FromMilliseconds(150); timing.paint_timing->first_contentful_paint = base::TimeDelta::FromMilliseconds(200); - timing.paint_timing->largest_text_paint = + timing.paint_timing->largest_contentful_paint->largest_text_paint = base::TimeDelta::FromMilliseconds(250); - timing.paint_timing->largest_text_paint_size = 100u; - timing.paint_timing->largest_image_paint = + timing.paint_timing->largest_contentful_paint->largest_text_paint_size = 100u; + timing.paint_timing->largest_contentful_paint->largest_image_paint = base::TimeDelta::FromMilliseconds(250); - timing.paint_timing->largest_image_paint_size = 100u; + timing.paint_timing->largest_contentful_paint->largest_image_paint_size = + 100u; PopulateRequiredTimingFields(&timing); NavigateAndCommit(GURL("https://www.google.com/")); @@ -1443,6 +1452,10 @@ TEST_F(CorePageLoadMetricsObserverTest, FontPreloadHistogramsNotObserved) { internal::kHistogramFontPreloadFirstContentfulPaint, 0); tester()->histogram_tester().ExpectTotalCount( internal::kHistogramFontPreloadLargestContentfulPaint, 0); + tester()->histogram_tester().ExpectTotalCount( + internal::kHistogramFontPreloadLargestImagePaint, 0); + tester()->histogram_tester().ExpectTotalCount( + internal::kHistogramFontPreloadLargestTextPaint, 0); } // PageLoad.Clients.FontPreload.* should be recorded when the behavior is @@ -1456,12 +1469,13 @@ TEST_F(CorePageLoadMetricsObserverTest, FontPreloadHistogramsObserved) { timing.paint_timing->first_paint = base::TimeDelta::FromMilliseconds(150); timing.paint_timing->first_contentful_paint = base::TimeDelta::FromMilliseconds(200); - timing.paint_timing->largest_text_paint = + timing.paint_timing->largest_contentful_paint->largest_text_paint = base::TimeDelta::FromMilliseconds(250); - timing.paint_timing->largest_text_paint_size = 100u; - timing.paint_timing->largest_image_paint = + timing.paint_timing->largest_contentful_paint->largest_text_paint_size = 100u; + timing.paint_timing->largest_contentful_paint->largest_image_paint = base::TimeDelta::FromMilliseconds(250); - timing.paint_timing->largest_image_paint_size = 100u; + timing.paint_timing->largest_contentful_paint->largest_image_paint_size = + 100u; PopulateRequiredTimingFields(&timing); // Note: PageLoadMetrics internally processes loading behavior before timing @@ -1481,6 +1495,10 @@ TEST_F(CorePageLoadMetricsObserverTest, FontPreloadHistogramsObserved) { internal::kHistogramFontPreloadFirstContentfulPaint, 1); tester()->histogram_tester().ExpectTotalCount( internal::kHistogramFontPreloadLargestContentfulPaint, 1); + tester()->histogram_tester().ExpectTotalCount( + internal::kHistogramFontPreloadLargestImagePaint, 1); + tester()->histogram_tester().ExpectTotalCount( + internal::kHistogramFontPreloadLargestTextPaint, 1); } // PageLoad.Clients.FontPreload.* depends on the order that, we need to first @@ -1497,12 +1515,13 @@ TEST_F(CorePageLoadMetricsObserverTest, timing.paint_timing->first_paint = base::TimeDelta::FromMilliseconds(150); timing.paint_timing->first_contentful_paint = base::TimeDelta::FromMilliseconds(200); - timing.paint_timing->largest_text_paint = + timing.paint_timing->largest_contentful_paint->largest_text_paint = base::TimeDelta::FromMilliseconds(250); - timing.paint_timing->largest_text_paint_size = 100u; - timing.paint_timing->largest_image_paint = + timing.paint_timing->largest_contentful_paint->largest_text_paint_size = 100u; + timing.paint_timing->largest_contentful_paint->largest_image_paint = base::TimeDelta::FromMilliseconds(250); - timing.paint_timing->largest_image_paint_size = 100u; + timing.paint_timing->largest_contentful_paint->largest_image_paint_size = + 100u; PopulateRequiredTimingFields(&timing); // Note: PageLoadMetrics internally processes loading behavior before timing @@ -1523,7 +1542,12 @@ TEST_F(CorePageLoadMetricsObserverTest, tester()->histogram_tester().ExpectTotalCount( internal::kHistogramFontPreloadFirstContentfulPaint, 0); - // LCP is recorded on page complete/closing, so it can still be observed. + // Largest*Paint is recorded on page complete/closing, so they can still be + // observed. tester()->histogram_tester().ExpectTotalCount( internal::kHistogramFontPreloadLargestContentfulPaint, 1); + tester()->histogram_tester().ExpectTotalCount( + internal::kHistogramFontPreloadLargestImagePaint, 1); + tester()->histogram_tester().ExpectTotalCount( + internal::kHistogramFontPreloadLargestTextPaint, 1); } diff --git a/chromium/components/page_load_metrics/browser/observers/largest_contentful_paint_handler.cc b/chromium/components/page_load_metrics/browser/observers/largest_contentful_paint_handler.cc index c338e901f66..ea407015ecb 100644 --- a/chromium/components/page_load_metrics/browser/observers/largest_contentful_paint_handler.cc +++ b/chromium/components/page_load_metrics/browser/observers/largest_contentful_paint_handler.cc @@ -6,6 +6,7 @@ #include "components/page_load_metrics/browser/page_load_metrics_observer_delegate.h" #include "components/page_load_metrics/common/page_load_metrics.mojom.h" +#include "content/public/browser/navigation_handle.h" #include "content/public/browser/render_frame_host.h" namespace page_load_metrics { @@ -63,9 +64,8 @@ void Reset(ContentfulPaintTimingInfo& timing) { } // namespace -ContentfulPaintTimingInfo::ContentfulPaintTimingInfo( - PageLoadMetricsObserver::LargestContentType type, - bool in_main_frame) +ContentfulPaintTimingInfo::ContentfulPaintTimingInfo(LargestContentType type, + bool in_main_frame) : time_(base::Optional<base::TimeDelta>()), size_(0), type_(type), @@ -73,7 +73,7 @@ ContentfulPaintTimingInfo::ContentfulPaintTimingInfo( ContentfulPaintTimingInfo::ContentfulPaintTimingInfo( const base::Optional<base::TimeDelta>& time, const uint64_t& size, - const page_load_metrics::PageLoadMetricsObserver::LargestContentType type, + const LargestContentType type, bool in_main_frame) : time_(time), size_(size), type_(type), in_main_frame_(in_main_frame) {} @@ -93,9 +93,9 @@ ContentfulPaintTimingInfo::DataAsTraceValue() const { std::string ContentfulPaintTimingInfo::TypeInString() const { switch (Type()) { - case page_load_metrics::PageLoadMetricsObserver::LargestContentType::kText: + case LargestContentType::kText: return "text"; - case page_load_metrics::PageLoadMetricsObserver::LargestContentType::kImage: + case LargestContentType::kImage: return "image"; default: NOTREACHED(); @@ -114,16 +114,50 @@ void ContentfulPaintTimingInfo::Reset( size_ = size; time_ = time; } - ContentfulPaint::ContentfulPaint(bool in_main_frame) - : text_(PageLoadMetricsObserver::LargestContentType::kText, in_main_frame), - image_(PageLoadMetricsObserver::LargestContentType::kImage, + : text_(ContentfulPaintTimingInfo::LargestContentType::kText, + in_main_frame), + image_(ContentfulPaintTimingInfo::LargestContentType::kImage, in_main_frame) {} -const ContentfulPaintTimingInfo& ContentfulPaint::MergeTextAndImageTiming() { +const ContentfulPaintTimingInfo& ContentfulPaint::MergeTextAndImageTiming() + const { return MergeTimingsBySizeAndTime(text_, image_); } +// static +bool LargestContentfulPaintHandler::AssignTimeAndSizeForLargestContentfulPaint( + const page_load_metrics::mojom::LargestContentfulPaintTiming& + largest_contentful_paint, + base::Optional<base::TimeDelta>* largest_content_paint_time, + uint64_t* largest_content_paint_size, + ContentfulPaintTimingInfo::LargestContentType* largest_content_type) { + // Size being 0 means the paint time is not recorded. + if (!largest_contentful_paint.largest_text_paint_size && + !largest_contentful_paint.largest_image_paint_size) + return false; + + if ((largest_contentful_paint.largest_text_paint_size > + largest_contentful_paint.largest_image_paint_size) || + (largest_contentful_paint.largest_text_paint_size == + largest_contentful_paint.largest_image_paint_size && + largest_contentful_paint.largest_text_paint < + largest_contentful_paint.largest_image_paint)) { + *largest_content_paint_time = largest_contentful_paint.largest_text_paint; + *largest_content_paint_size = + largest_contentful_paint.largest_text_paint_size; + *largest_content_type = + ContentfulPaintTimingInfo::LargestContentType::kText; + } else { + *largest_content_paint_time = largest_contentful_paint.largest_image_paint; + *largest_content_paint_size = + largest_contentful_paint.largest_image_paint_size; + *largest_content_type = + ContentfulPaintTimingInfo::LargestContentType::kImage; + } + return true; +} + LargestContentfulPaintHandler::LargestContentfulPaintHandler() : main_frame_contentful_paint_(true /*in_main_frame*/), subframe_contentful_paint_(false /*in_main_frame*/) {} @@ -131,10 +165,14 @@ LargestContentfulPaintHandler::LargestContentfulPaintHandler() LargestContentfulPaintHandler::~LargestContentfulPaintHandler() = default; void LargestContentfulPaintHandler::RecordTiming( - const mojom::PaintTimingPtr& timing, + const page_load_metrics::mojom::LargestContentfulPaintTiming& + largest_contentful_paint, + const base::Optional<base::TimeDelta>& + first_input_or_scroll_notified_timestamp, content::RenderFrameHost* subframe_rfh) { if (!IsSubframe(subframe_rfh)) { - RecordMainFrameTiming(timing); + RecordMainFrameTiming(largest_contentful_paint, + first_input_or_scroll_notified_timestamp); return; } // For subframes @@ -144,11 +182,12 @@ void LargestContentfulPaintHandler::RecordTiming( // We received timing information for an untracked load. Ignore it. return; } - RecordSubframeTiming(timing, it->second); + RecordSubframeTiming(largest_contentful_paint, + first_input_or_scroll_notified_timestamp, it->second); } const ContentfulPaintTimingInfo& -LargestContentfulPaintHandler::MergeMainFrameAndSubframes() { +LargestContentfulPaintHandler::MergeMainFrameAndSubframes() const { const ContentfulPaintTimingInfo& main_frame_timing = main_frame_contentful_paint_.MergeTextAndImageTiming(); const ContentfulPaintTimingInfo& subframe_timing = @@ -165,31 +204,40 @@ LargestContentfulPaintHandler::MergeMainFrameAndSubframes() { // trade-off we make to keep a simple algorithm, otherwise we will have to // track one candidate per subframe. void LargestContentfulPaintHandler::RecordSubframeTiming( - const mojom::PaintTimingPtr& timing, + const page_load_metrics::mojom::LargestContentfulPaintTiming& + largest_contentful_paint, + const base::Optional<base::TimeDelta>& + first_input_or_scroll_notified_timestamp, const base::TimeDelta& navigation_start_offset) { - UpdateFirstInputOrScrollNotified( - timing->first_input_or_scroll_notified_timestamp, - navigation_start_offset); + UpdateFirstInputOrScrollNotified(first_input_or_scroll_notified_timestamp, + navigation_start_offset); MergeForSubframes(&subframe_contentful_paint_.Text(), - timing->largest_text_paint, timing->largest_text_paint_size, + largest_contentful_paint.largest_text_paint, + largest_contentful_paint.largest_text_paint_size, navigation_start_offset); MergeForSubframes(&subframe_contentful_paint_.Image(), - timing->largest_image_paint, - timing->largest_image_paint_size, navigation_start_offset); + largest_contentful_paint.largest_image_paint, + largest_contentful_paint.largest_image_paint_size, + navigation_start_offset); } void LargestContentfulPaintHandler::RecordMainFrameTiming( - const mojom::PaintTimingPtr& timing) { + const page_load_metrics::mojom::LargestContentfulPaintTiming& + largest_contentful_paint, + const base::Optional<base::TimeDelta>& + first_input_or_scroll_notified_timestamp) { UpdateFirstInputOrScrollNotified( - timing->first_input_or_scroll_notified_timestamp, + first_input_or_scroll_notified_timestamp, /* navigation_start_offset */ base::TimeDelta()); - if (IsValid(timing->largest_text_paint)) { - main_frame_contentful_paint_.Text().Reset(timing->largest_text_paint, - timing->largest_text_paint_size); + if (IsValid(largest_contentful_paint.largest_text_paint)) { + main_frame_contentful_paint_.Text().Reset( + largest_contentful_paint.largest_text_paint, + largest_contentful_paint.largest_text_paint_size); } - if (IsValid(timing->largest_image_paint)) { + if (IsValid(largest_contentful_paint.largest_image_paint)) { main_frame_contentful_paint_.Image().Reset( - timing->largest_image_paint, timing->largest_image_paint_size); + largest_contentful_paint.largest_image_paint, + largest_contentful_paint.largest_image_paint_size); } } @@ -220,7 +268,7 @@ void LargestContentfulPaintHandler::UpdateFirstInputOrScrollNotified( void LargestContentfulPaintHandler::OnDidFinishSubFrameNavigation( content::NavigationHandle* navigation_handle, - const PageLoadMetricsObserverDelegate& delegate) { + base::TimeTicks navigation_start) { if (!navigation_handle->HasCommitted()) return; @@ -229,7 +277,7 @@ void LargestContentfulPaintHandler::OnDidFinishSubFrameNavigation( subframe_navigation_start_offset_.erase( navigation_handle->GetFrameTreeNodeId()); - if (delegate.GetNavigationStart() > navigation_handle->NavigationStart()) + if (navigation_start > navigation_handle->NavigationStart()) return; base::TimeDelta navigation_delta; // If navigation start offset tracking has been disabled for tests, then @@ -238,13 +286,18 @@ void LargestContentfulPaintHandler::OnDidFinishSubFrameNavigation( // See crbug/616901 for more details on why navigation start offset tracking // is disabled in tests. if (!g_disable_subframe_navigation_start_offset) { - navigation_delta = - navigation_handle->NavigationStart() - delegate.GetNavigationStart(); + navigation_delta = navigation_handle->NavigationStart() - navigation_start; } subframe_navigation_start_offset_.insert(std::make_pair( navigation_handle->GetFrameTreeNodeId(), navigation_delta)); } +void LargestContentfulPaintHandler::OnFrameDeleted( + content::RenderFrameHost* render_frame_host) { + subframe_navigation_start_offset_.erase( + render_frame_host->GetFrameTreeNodeId()); +} + void LargestContentfulPaintHandler::MergeForSubframes( ContentfulPaintTimingInfo* inout_timing, const base::Optional<base::TimeDelta>& candidate_new_time, diff --git a/chromium/components/page_load_metrics/browser/observers/largest_contentful_paint_handler.h b/chromium/components/page_load_metrics/browser/observers/largest_contentful_paint_handler.h index b0f87192f12..0ac2bcbd5eb 100644 --- a/chromium/components/page_load_metrics/browser/observers/largest_contentful_paint_handler.h +++ b/chromium/components/page_load_metrics/browser/observers/largest_contentful_paint_handler.h @@ -6,31 +6,43 @@ #include <map> +#include "base/time/time.h" #include "base/trace_event/traced_value.h" -#include "components/page_load_metrics/browser/page_load_metrics_observer.h" #include "components/page_load_metrics/common/page_load_metrics.mojom.h" #include "components/page_load_metrics/common/page_load_timing.h" +namespace content { + +class NavigationHandle; +class RenderFrameHost; + +} // namespace content + namespace page_load_metrics { class ContentfulPaintTimingInfo { public: - explicit ContentfulPaintTimingInfo( - page_load_metrics::PageLoadMetricsObserver::LargestContentType, - bool in_main_frame); + // These values are persisted to logs. Entries should not be renumbered and + // numeric values should never be reused. + enum class LargestContentType { + kImage = 0, + kText = 1, + kMaxValue = kText, + }; + + explicit ContentfulPaintTimingInfo(LargestContentType largest_content_type, + bool in_main_frame); explicit ContentfulPaintTimingInfo( const base::Optional<base::TimeDelta>&, const uint64_t& size, - const page_load_metrics::PageLoadMetricsObserver::LargestContentType, + const LargestContentType largest_content_type, bool in_main_frame); explicit ContentfulPaintTimingInfo(const ContentfulPaintTimingInfo& other); void Reset(const base::Optional<base::TimeDelta>&, const uint64_t& size); base::Optional<base::TimeDelta> Time() const { return time_; } bool InMainFrame() const { return in_main_frame_; } uint64_t Size() const { return size_; } - page_load_metrics::PageLoadMetricsObserver::LargestContentType Type() const { - return type_; - } + LargestContentType Type() const { return type_; } // Returns true iff this object does not represent any paint. bool Empty() const { @@ -52,7 +64,7 @@ class ContentfulPaintTimingInfo { std::string TypeInString() const; base::Optional<base::TimeDelta> time_; uint64_t size_; - page_load_metrics::PageLoadMetricsObserver::LargestContentType type_; + LargestContentType type_; bool in_main_frame_; }; @@ -60,8 +72,10 @@ class ContentfulPaint { public: explicit ContentfulPaint(bool in_main_frame); ContentfulPaintTimingInfo& Text() { return text_; } + const ContentfulPaintTimingInfo& Text() const { return text_; } ContentfulPaintTimingInfo& Image() { return image_; } - const ContentfulPaintTimingInfo& MergeTextAndImageTiming(); + const ContentfulPaintTimingInfo& Image() const { return image_; } + const ContentfulPaintTimingInfo& MergeTextAndImageTiming() const; private: ContentfulPaintTimingInfo text_; @@ -70,13 +84,25 @@ class ContentfulPaint { class LargestContentfulPaintHandler { public: - using FrameTreeNodeId = - page_load_metrics::PageLoadMetricsObserver::FrameTreeNodeId; + using FrameTreeNodeId = int; static void SetTestMode(bool enabled); LargestContentfulPaintHandler(); ~LargestContentfulPaintHandler(); - void RecordTiming(const page_load_metrics::mojom::PaintTimingPtr&, - content::RenderFrameHost* subframe_rfh); + + // Returns true if the out parameters are assigned values. + static bool AssignTimeAndSizeForLargestContentfulPaint( + const page_load_metrics::mojom::LargestContentfulPaintTiming& + largest_contentful_paint, + base::Optional<base::TimeDelta>* largest_content_paint_time, + uint64_t* largest_content_paint_size, + ContentfulPaintTimingInfo::LargestContentType* largest_content_type); + + void RecordTiming( + const page_load_metrics::mojom::LargestContentfulPaintTiming& + largest_contentful_paint, + const base::Optional<base::TimeDelta>& + first_input_or_scroll_notified_timestamp, + content::RenderFrameHost* subframe_rfh); inline void RecordMainFrameTreeNodeId(int main_frame_tree_node_id) { main_frame_tree_node_id_.emplace(main_frame_tree_node_id); } @@ -87,24 +113,39 @@ class LargestContentfulPaintHandler { // We merge the candidates from text side and image side to get the largest // candidate across both types of content. - const ContentfulPaintTimingInfo& MainFrameLargestContentfulPaint() { + const ContentfulPaintTimingInfo& MainFrameLargestContentfulPaint() const { return main_frame_contentful_paint_.MergeTextAndImageTiming(); } - const ContentfulPaintTimingInfo& SubframesLargestContentfulPaint() { + const ContentfulPaintTimingInfo& SubframesLargestContentfulPaint() const { return subframe_contentful_paint_.MergeTextAndImageTiming(); } + const ContentfulPaintTimingInfo& MainFrameLargestImagePaint() const { + return main_frame_contentful_paint_.Image(); + } + const ContentfulPaintTimingInfo& MainFrameLargestTextPaint() const { + return main_frame_contentful_paint_.Text(); + } // We merge the candidates from main frame and subframe to get the largest // candidate across all frames. - const ContentfulPaintTimingInfo& MergeMainFrameAndSubframes(); + const ContentfulPaintTimingInfo& MergeMainFrameAndSubframes() const; void OnDidFinishSubFrameNavigation( content::NavigationHandle* navigation_handle, - const PageLoadMetricsObserverDelegate& delegate); + base::TimeTicks navigation_start); + void OnFrameDeleted(content::RenderFrameHost* render_frame_host); private: - void RecordSubframeTiming(const mojom::PaintTimingPtr& timing, - const base::TimeDelta& navigation_start_offset); - void RecordMainFrameTiming(const page_load_metrics::mojom::PaintTimingPtr&); + void RecordSubframeTiming( + const page_load_metrics::mojom::LargestContentfulPaintTiming& + largest_contentful_paint, + const base::Optional<base::TimeDelta>& + first_input_or_scroll_notified_timestamp, + const base::TimeDelta& navigation_start_offset); + void RecordMainFrameTiming( + const page_load_metrics::mojom::LargestContentfulPaintTiming& + largest_contentful_paint, + const base::Optional<base::TimeDelta>& + first_input_or_scroll_notified_timestamp); void UpdateFirstInputOrScrollNotified( const base::Optional<base::TimeDelta>& candidate_new_time, const base::TimeDelta& navigation_start_offset); diff --git a/chromium/components/page_load_metrics/browser/observers/use_counter/ukm_features.cc b/chromium/components/page_load_metrics/browser/observers/use_counter/ukm_features.cc index 2d822f9eacf..8c4045c51a5 100644 --- a/chromium/components/page_load_metrics/browser/observers/use_counter/ukm_features.cc +++ b/chromium/components/page_load_metrics/browser/observers/use_counter/ukm_features.cc @@ -160,6 +160,7 @@ UseCounterPageLoadMetricsObserver::GetAllowedUkmFeatures() { WebFeature::kThirdPartyCacheStorage, WebFeature::kThirdPartyLocalStorage, WebFeature::kThirdPartySessionStorage, + WebFeature::kOverlayPopup, WebFeature::kOverlayPopupAd, WebFeature::kTrustTokenXhr, WebFeature::kTrustTokenFetch, @@ -169,6 +170,9 @@ UseCounterPageLoadMetricsObserver::GetAllowedUkmFeatures() { WebFeature::kV8HTMLVideoElement_CancelVideoFrameCallback_Method, WebFeature::kSchemefulSameSiteContextDowngrade, WebFeature::kIdleDetectionStart, + WebFeature::kPerformanceObserverEntryTypesAndBuffered, + WebFeature::kStorageAccessAPI_HasStorageAccess_Method, + WebFeature::kStorageAccessAPI_requestStorageAccess_Method, })); return *opt_in_features; } diff --git a/chromium/components/page_load_metrics/browser/observers/use_counter_page_load_metrics_observer.h b/chromium/components/page_load_metrics/browser/observers/use_counter_page_load_metrics_observer.h index 542c1dcec8e..d0aca784cea 100644 --- a/chromium/components/page_load_metrics/browser/observers/use_counter_page_load_metrics_observer.h +++ b/chromium/components/page_load_metrics/browser/observers/use_counter_page_load_metrics_observer.h @@ -7,7 +7,6 @@ #include <bitset> #include "base/containers/flat_set.h" -#include "base/logging.h" #include "base/macros.h" #include "components/page_load_metrics/browser/page_load_metrics_observer.h" #include "third_party/blink/public/mojom/use_counter/css_property_id.mojom.h" diff --git a/chromium/components/page_load_metrics/browser/page_load_metrics_embedder_base.cc b/chromium/components/page_load_metrics/browser/page_load_metrics_embedder_base.cc index 2a31766142e..84b6e992068 100644 --- a/chromium/components/page_load_metrics/browser/page_load_metrics_embedder_base.cc +++ b/chromium/components/page_load_metrics/browser/page_load_metrics_embedder_base.cc @@ -6,6 +6,7 @@ #include "base/timer/timer.h" +#include "components/page_load_metrics/browser/observers/back_forward_cache_page_load_metrics_observer.h" #include "components/page_load_metrics/browser/observers/core_page_load_metrics_observer.h" #include "components/page_load_metrics/browser/observers/layout_page_load_metrics_observer.h" #include "components/page_load_metrics/browser/observers/use_counter_page_load_metrics_observer.h" @@ -22,6 +23,8 @@ PageLoadMetricsEmbedderBase::~PageLoadMetricsEmbedderBase() = default; void PageLoadMetricsEmbedderBase::RegisterObservers(PageLoadTracker* tracker) { // Register observers used by all embedders if (!IsPrerendering()) { + tracker->AddObserver( + std::make_unique<BackForwardCachePageLoadMetricsObserver>()); tracker->AddObserver(std::make_unique<CorePageLoadMetricsObserver>()); tracker->AddObserver(std::make_unique<LayoutPageLoadMetricsObserver>()); tracker->AddObserver(std::make_unique<UseCounterPageLoadMetricsObserver>()); diff --git a/chromium/components/page_load_metrics/browser/page_load_metrics_observer.cc b/chromium/components/page_load_metrics/browser/page_load_metrics_observer.cc index 758bae3025e..148a3b4b151 100644 --- a/chromium/components/page_load_metrics/browser/page_load_metrics_observer.cc +++ b/chromium/components/page_load_metrics/browser/page_load_metrics_observer.cc @@ -112,35 +112,6 @@ bool PageLoadMetricsObserver::IsStandardWebPageMimeType( return mime_type == "text/html" || mime_type == "application/xhtml+xml"; } -// static -bool PageLoadMetricsObserver::AssignTimeAndSizeForLargestContentfulPaint( - const page_load_metrics::mojom::PaintTimingPtr& paint_timing, - base::Optional<base::TimeDelta>* largest_content_paint_time, - uint64_t* largest_content_paint_size, - LargestContentType* largest_content_type) { - base::Optional<base::TimeDelta>& text_time = paint_timing->largest_text_paint; - base::Optional<base::TimeDelta>& image_time = - paint_timing->largest_image_paint; - uint64_t text_size = paint_timing->largest_text_paint_size; - uint64_t image_size = paint_timing->largest_image_paint_size; - - // Size being 0 means the paint time is not recorded. - if (!text_size && !image_size) - return false; - - if ((text_size > image_size) || - (text_size == image_size && text_time < image_time)) { - *largest_content_paint_time = text_time; - *largest_content_paint_size = text_size; - *largest_content_type = LargestContentType::kText; - } else { - *largest_content_paint_time = image_time; - *largest_content_paint_size = image_size; - *largest_content_type = LargestContentType::kImage; - } - return true; -} - const PageLoadMetricsObserverDelegate& PageLoadMetricsObserver::GetDelegate() const { // The delegate must exist and outlive the page load metrics observer. diff --git a/chromium/components/page_load_metrics/browser/page_load_metrics_observer.h b/chromium/components/page_load_metrics/browser/page_load_metrics_observer.h index baecda6e2e8..7aab9839073 100644 --- a/chromium/components/page_load_metrics/browser/page_load_metrics_observer.h +++ b/chromium/components/page_load_metrics/browser/page_load_metrics_observer.h @@ -195,10 +195,12 @@ class PageLoadMetricsObserver { // These values are persisted to logs. Entries should not be renumbered and // numeric values should never be reused. - enum class LargestContentType { - kImage = 0, - kText = 1, - kMaxValue = kText, + enum class LargestContentState { + kReported = 0, + kLargestImageLoading = 1, + kNotFound = 2, + kFoundButNotReported = 3, + kMaxValue = kFoundButNotReported, }; using FrameTreeNodeId = int; @@ -207,13 +209,6 @@ class PageLoadMetricsObserver { static bool IsStandardWebPageMimeType(const std::string& mime_type); - // Returns true if the out parameters are assigned values. - static bool AssignTimeAndSizeForLargestContentfulPaint( - const page_load_metrics::mojom::PaintTimingPtr& paint_timing, - base::Optional<base::TimeDelta>* largest_content_paint_time, - uint64_t* largest_content_paint_size, - LargestContentType* largest_content_type); - // Gets/Sets the delegate. The delegate must outlive the observer and is // normally set when the observer is first registered for the page load. The // delegate can only be set once. @@ -369,6 +364,15 @@ class PageLoadMetricsObserver { virtual void OnFirstContentfulPaintInPage( const mojom::PageLoadTiming& timing) {} + // These are called once every time when the page is restored from the + // back-forward cache. |index| indicates |index|-th restore. + virtual void OnFirstPaintAfterBackForwardCacheRestoreInPage( + const mojom::BackForwardCacheTiming& timing, + size_t index) {} + virtual void OnFirstInputAfterBackForwardCacheRestoreInPage( + const mojom::BackForwardCacheTiming& timing, + size_t index) {} + // Unlike other paint callbacks, OnFirstMeaningfulPaintInMainFrameDocument is // tracked per document, and is reported for the main frame document only. virtual void OnFirstMeaningfulPaintInMainFrameDocument( @@ -386,6 +390,9 @@ class PageLoadMetricsObserver { content::RenderFrameHost* rfh, const mojom::PageLoadFeatures& features) {} + virtual void OnThroughputUpdate( + const mojom::ThroughputUkmDataPtr& throughput_data) {} + // Invoked when there is data use for loading a resource on the page // for a given render frame host. This only contains resources that have had // new data use since the last callback. Resources loaded from the cache only @@ -495,6 +502,10 @@ class PageLoadMetricsObserver { // load. virtual void OnEventOccurred(const void* const event_key) {} + // Called when the page tracked was just activated after being loaded inside a + // portal. + virtual void DidActivatePortal(base::TimeTicks activation_time) {} + private: PageLoadMetricsObserverDelegate* delegate_ = nullptr; }; diff --git a/chromium/components/page_load_metrics/browser/page_load_metrics_observer_delegate.cc b/chromium/components/page_load_metrics/browser/page_load_metrics_observer_delegate.cc new file mode 100644 index 00000000000..6084579a962 --- /dev/null +++ b/chromium/components/page_load_metrics/browser/page_load_metrics_observer_delegate.cc @@ -0,0 +1,16 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/page_load_metrics/browser/page_load_metrics_observer_delegate.h" + +namespace page_load_metrics { + +PageLoadMetricsObserverDelegate::BackForwardCacheRestore:: + BackForwardCacheRestore(bool was_in_foreground) + : was_in_foreground(was_in_foreground) {} + +PageLoadMetricsObserverDelegate::BackForwardCacheRestore:: + BackForwardCacheRestore(const BackForwardCacheRestore&) = default; + +} // namespace page_load_metrics diff --git a/chromium/components/page_load_metrics/browser/page_load_metrics_observer_delegate.h b/chromium/components/page_load_metrics/browser/page_load_metrics_observer_delegate.h index 00199f5f775..cec06c91ed6 100644 --- a/chromium/components/page_load_metrics/browser/page_load_metrics_observer_delegate.h +++ b/chromium/components/page_load_metrics/browser/page_load_metrics_observer_delegate.h @@ -7,6 +7,7 @@ #include "base/optional.h" #include "base/time/time.h" +#include "components/page_load_metrics/browser/observers/largest_contentful_paint_handler.h" #include "components/page_load_metrics/browser/resource_tracker.h" #include "components/page_load_metrics/common/page_end_reason.h" #include "services/metrics/public/cpp/ukm_source_id.h" @@ -29,6 +30,20 @@ struct PageRenderData; // from any PageLoadMetricsObserver. class PageLoadMetricsObserverDelegate { public: + // States when the page is restored from the back-forward cache. + struct BackForwardCacheRestore { + explicit BackForwardCacheRestore(bool was_in_foreground); + BackForwardCacheRestore(const BackForwardCacheRestore&); + + // The first time when the page becomes backgrounded after the page is + // restored. The time is relative to the navigation start of bfcache restore + // avigation. + base::Optional<base::TimeDelta> first_background_time; + + // True if the page was in foreground when the page is restored. + bool was_in_foreground = false; + }; + virtual content::WebContents* GetWebContents() const = 0; // The time the navigation was initiated. @@ -42,6 +57,10 @@ class PageLoadMetricsObserverDelegate { virtual const base::Optional<base::TimeDelta>& GetFirstForegroundTime() const = 0; + // The state of index-th restore from the back-forward cache. + virtual const BackForwardCacheRestore& GetBackForwardCacheRestore( + size_t index) const = 0; + // True if the page load started in the foreground. virtual bool StartedInForeground() const = 0; @@ -104,6 +123,14 @@ class PageLoadMetricsObserverDelegate { virtual const ui::ScopedVisibilityTracker& GetVisibilityTracker() const = 0; virtual const ResourceTracker& GetResourceTracker() const = 0; + // Returns a shared LargestContentfulPaintHandler for page load metrics. + virtual const LargestContentfulPaintHandler& + GetLargestContentfulPaintHandler() const = 0; + // Returns a LargestContentfulPaintHandler for the experimental version of + // LCP. + virtual const LargestContentfulPaintHandler& + GetExperimentalLargestContentfulPaintHandler() const = 0; + // UKM SourceId for the current page load. virtual ukm::SourceId GetSourceId() const = 0; diff --git a/chromium/components/page_load_metrics/browser/page_load_metrics_test_waiter.cc b/chromium/components/page_load_metrics/browser/page_load_metrics_test_waiter.cc index 6b858e895fb..c509c63db4d 100644 --- a/chromium/components/page_load_metrics/browser/page_load_metrics_test_waiter.cc +++ b/chromium/components/page_load_metrics/browser/page_load_metrics_test_waiter.cc @@ -238,36 +238,58 @@ PageLoadMetricsTestWaiter::GetMatchedBits( blink::LoadingBehaviorFlag::kLoadingBehaviorDocumentWriteBlockReload) { matched_bits.Set(TimingField::kDocumentWriteBlockReload); } - if (timing.paint_timing->largest_image_paint || - timing.paint_timing->largest_text_paint) { + if (timing.paint_timing->largest_contentful_paint->largest_image_paint || + timing.paint_timing->largest_contentful_paint->largest_text_paint) { matched_bits.Set(TimingField::kLargestContentfulPaint); } if (timing.paint_timing->first_input_or_scroll_notified_timestamp) matched_bits.Set(TimingField::kFirstInputOrScroll); if (timing.interactive_timing->first_input_delay) matched_bits.Set(TimingField::kFirstInputDelay); + if (!timing.back_forward_cache_timings.empty()) { + if (!timing.back_forward_cache_timings.back() + ->first_paint_after_back_forward_cache_restore.is_zero()) { + matched_bits.Set(TimingField::kFirstPaintAfterBackForwardCacheRestore); + } + if (timing.back_forward_cache_timings.back() + ->first_input_delay_after_back_forward_cache_restore.has_value()) { + matched_bits.Set( + TimingField::kFirstInputDelayAfterBackForwardCacheRestore); + } + } return matched_bits; } void PageLoadMetricsTestWaiter::OnTrackerCreated( page_load_metrics::PageLoadTracker* tracker) { - if (!attach_on_tracker_creation_) - return; // A PageLoadMetricsWaiter should only wait for events from a single page // load. - ASSERT_FALSE(did_add_observer_); - tracker->AddObserver( - std::make_unique<WaiterMetricsObserver>(weak_factory_.GetWeakPtr())); - did_add_observer_ = true; + if (!attach_on_tracker_creation_) + return; + AddObserver(tracker); } void PageLoadMetricsTestWaiter::OnCommit( page_load_metrics::PageLoadTracker* tracker) { + // A PageLoadMetricsWaiter should only wait for events from a single page + // load. if (attach_on_tracker_creation_) return; + AddObserver(tracker); +} + +void PageLoadMetricsTestWaiter::OnRestoredFromBackForwardCache( + page_load_metrics::PageLoadTracker* tracker) { // A PageLoadMetricsWaiter should only wait for events from a single page // load. + if (attach_on_tracker_creation_) + return; + AddObserver(tracker); +} + +void PageLoadMetricsTestWaiter::AddObserver( + page_load_metrics::PageLoadTracker* tracker) { ASSERT_FALSE(did_add_observer_); tracker->AddObserver( std::make_unique<WaiterMetricsObserver>(weak_factory_.GetWeakPtr())); diff --git a/chromium/components/page_load_metrics/browser/page_load_metrics_test_waiter.h b/chromium/components/page_load_metrics/browser/page_load_metrics_test_waiter.h index 6456c2643b9..cfff1e12a14 100644 --- a/chromium/components/page_load_metrics/browser/page_load_metrics_test_waiter.h +++ b/chromium/components/page_load_metrics/browser/page_load_metrics_test_waiter.h @@ -31,6 +31,8 @@ class PageLoadMetricsTestWaiter kLargestContentfulPaint = 1 << 6, kFirstInputOrScroll = 1 << 7, kFirstInputDelay = 1 << 8, + kFirstPaintAfterBackForwardCacheRestore = 1 << 9, + kFirstInputDelayAfterBackForwardCacheRestore = 1 << 10, }; using FrameTreeNodeId = page_load_metrics::PageLoadMetricsObserver::FrameTreeNodeId; @@ -228,6 +230,9 @@ class PageLoadMetricsTestWaiter void OnCommit(page_load_metrics::PageLoadTracker* tracker) override; + void OnRestoredFromBackForwardCache( + page_load_metrics::PageLoadTracker* tracker) override; + bool CpuTimeExpectationsSatisfied() const; bool ResourceUseExpectationsSatisfied() const; @@ -238,6 +243,8 @@ class PageLoadMetricsTestWaiter bool SubframeDataExpectationsSatisfied() const; + void AddObserver(page_load_metrics::PageLoadTracker* tracker); + std::unique_ptr<base::RunLoop> run_loop_; TimingFieldBitSet page_expected_fields_; diff --git a/chromium/components/page_load_metrics/browser/page_load_metrics_update_dispatcher.cc b/chromium/components/page_load_metrics/browser/page_load_metrics_update_dispatcher.cc index d9f76dd0953..eb2dfaeaf1b 100644 --- a/chromium/components/page_load_metrics/browser/page_load_metrics_update_dispatcher.cc +++ b/chromium/components/page_load_metrics/browser/page_load_metrics_update_dispatcher.cc @@ -194,6 +194,16 @@ internal::PageLoadTimingStatus IsValidPageLoadTiming( return internal::INVALID_NULL_FIRST_INPUT_DELAY; } + if (timing.interactive_timing->first_scroll_delay.has_value() && + !timing.interactive_timing->first_scroll_timestamp.has_value()) { + return internal::INVALID_NULL_FIRST_SCROLL_TIMESTAMP; + } + + if (!timing.interactive_timing->first_scroll_delay.has_value() && + timing.interactive_timing->first_scroll_timestamp.has_value()) { + return internal::INVALID_NULL_FIRST_SCROLL_DELAY; + } + if (timing.interactive_timing->longest_input_delay.has_value() && !timing.interactive_timing->longest_input_timestamp.has_value()) { return internal::INVALID_NULL_LONGEST_INPUT_TIMESTAMP; @@ -252,6 +262,9 @@ class PageLoadTimingMerger { MergeInteractiveTiming(navigation_start_offset, *new_page_load_timing.interactive_timing, is_main_frame); + MergeBackForwardCacheTiming(navigation_start_offset, + new_page_load_timing.back_forward_cache_timings, + is_main_frame); } // Whether we merged a new value. @@ -327,16 +340,14 @@ class PageLoadTimingMerger { target_paint_timing->first_meaningful_paint = new_paint_timing.first_meaningful_paint; - target_paint_timing->largest_image_paint = - new_paint_timing.largest_image_paint; - target_paint_timing->largest_image_paint_size = - new_paint_timing.largest_image_paint_size; - target_paint_timing->largest_text_paint = - new_paint_timing.largest_text_paint; - target_paint_timing->largest_text_paint_size = - new_paint_timing.largest_text_paint_size; + target_paint_timing->largest_contentful_paint = + new_paint_timing.largest_contentful_paint->Clone(); + target_paint_timing->experimental_largest_contentful_paint = + new_paint_timing.experimental_largest_contentful_paint.Clone(); target_paint_timing->first_input_or_scroll_notified_timestamp = new_paint_timing.first_input_or_scroll_notified_timestamp; + target_paint_timing->portal_activated_paint = + new_paint_timing.portal_activated_paint; } } @@ -354,6 +365,10 @@ class PageLoadTimingMerger { // associated first input delay. target_interactive_timing->first_input_delay = new_interactive_timing.first_input_delay; + if (new_interactive_timing.first_input_processing_time.has_value()) { + target_interactive_timing->first_input_processing_time = + new_interactive_timing.first_input_processing_time; + } } if (new_interactive_timing.longest_input_delay.has_value()) { @@ -369,6 +384,28 @@ class PageLoadTimingMerger { new_longest_input_timestamp; } } + + // Update First Scroll Delay. + if (MaybeUpdateTimeDelta(&target_interactive_timing->first_scroll_timestamp, + navigation_start_offset, + new_interactive_timing.first_scroll_timestamp)) { + target_interactive_timing->first_scroll_delay = + new_interactive_timing.first_scroll_delay; + } + } + + void MergeBackForwardCacheTiming( + base::TimeDelta navigation_start_offset, + const std::vector<mojo::StructPtr<mojom::BackForwardCacheTiming>>& + new_back_forward_cache_timings, + bool is_main_frame) { + if (is_main_frame) { + target_->back_forward_cache_timings.clear(); + target_->back_forward_cache_timings.reserve( + new_back_forward_cache_timings.size()); + for (const auto& timing : new_back_forward_cache_timings) + target_->back_forward_cache_timings.push_back(timing.Clone()); + } } // The target PageLoadTiming we are merging values into. @@ -470,6 +507,18 @@ void PageLoadMetricsUpdateDispatcher::UpdateFeatures( client_->UpdateFeaturesUsage(render_frame_host, new_features); } +void PageLoadMetricsUpdateDispatcher::UpdateThroughput( + content::RenderFrameHost* render_frame_host, + mojom::ThroughputUkmDataPtr throughput_data) { + if (embedder_interface_->IsExtensionUrl( + render_frame_host->GetLastCommittedURL())) { + // Extensions can inject child frames into a page. We don't want to track + // these as they could skew metrics. See http://crbug.com/761037 + return; + } + client_->UpdateThroughput(std::move(throughput_data)); +} + void PageLoadMetricsUpdateDispatcher::DidFinishSubFrameNavigation( content::NavigationHandle* navigation_handle) { if (!navigation_handle->HasCommitted()) @@ -490,6 +539,12 @@ void PageLoadMetricsUpdateDispatcher::DidFinishSubFrameNavigation( navigation_handle->GetFrameTreeNodeId(), navigation_delta)); } +void PageLoadMetricsUpdateDispatcher::OnFrameDeleted( + content::RenderFrameHost* render_frame_host) { + subframe_navigation_start_offset_.erase( + render_frame_host->GetFrameTreeNodeId()); +} + void PageLoadMetricsUpdateDispatcher::UpdateSubFrameTiming( content::RenderFrameHost* render_frame_host, mojom::PageLoadTimingPtr new_timing) { diff --git a/chromium/components/page_load_metrics/browser/page_load_metrics_update_dispatcher.h b/chromium/components/page_load_metrics/browser/page_load_metrics_update_dispatcher.h index 0fbc534a82c..912c1bc27ab 100644 --- a/chromium/components/page_load_metrics/browser/page_load_metrics_update_dispatcher.h +++ b/chromium/components/page_load_metrics/browser/page_load_metrics_update_dispatcher.h @@ -75,6 +75,11 @@ enum PageLoadTimingStatus { // We received a longest input timestamp without a longest input delay. INVALID_NULL_LONGEST_INPUT_DELAY, + // We received a first scroll delay without a first scroll timestamp. + INVALID_NULL_FIRST_SCROLL_TIMESTAMP, + // We received a first scroll timestamp without a first scroll delay. + INVALID_NULL_FIRST_SCROLL_DELAY, + // Longest input delay cannot happen before first input delay. INVALID_LONGEST_INPUT_TIMESTAMP_LESS_THAN_FIRST_INPUT_TIMESTAMP, @@ -127,6 +132,8 @@ class PageLoadMetricsUpdateDispatcher { const mojom::FrameIntersectionUpdate& frame_intersection_update) = 0; virtual void OnNewDeferredResourceCounts( const mojom::DeferredResourceCounts& new_deferred_resource_data) = 0; + virtual void UpdateThroughput( + mojom::ThroughputUkmDataPtr throughput_data) = 0; }; // The |client| instance must outlive this object. @@ -147,6 +154,9 @@ class PageLoadMetricsUpdateDispatcher { mojom::DeferredResourceCountsPtr new_deferred_resource_data, mojom::InputTimingPtr input_timing_delta); + void UpdateThroughput(content::RenderFrameHost* render_frame_host, + mojom::ThroughputUkmDataPtr throughput_data); + // This method is only intended to be called for PageLoadFeatures being // recorded directly from the browser process. Features coming from the // renderer process should use the main flow into |UpdateMetrics|. @@ -156,6 +166,8 @@ class PageLoadMetricsUpdateDispatcher { void DidFinishSubFrameNavigation( content::NavigationHandle* navigation_handle); + void OnFrameDeleted(content::RenderFrameHost* render_frame_host); + void ShutDown(); const mojom::PageLoadTiming& timing() const { diff --git a/chromium/components/page_load_metrics/browser/page_load_metrics_util.cc b/chromium/components/page_load_metrics/browser/page_load_metrics_util.cc index 9e33a8c8f13..5bcb45f80b3 100644 --- a/chromium/components/page_load_metrics/browser/page_load_metrics_util.cc +++ b/chromium/components/page_load_metrics/browser/page_load_metrics_util.cc @@ -109,6 +109,19 @@ bool WasStartedInForegroundOptionalEventInForeground( event.value() <= delegate.GetFirstBackgroundTime().value()); } +bool WasStartedInForegroundOptionalEventInForegroundAfterBackForwardCacheRestore( + const base::Optional<base::TimeDelta>& event, + const PageLoadMetricsObserverDelegate& delegate, + size_t index) { + const auto& back_forward_cache_restore = + delegate.GetBackForwardCacheRestore(index); + base::Optional<base::TimeDelta> first_background_time = + back_forward_cache_restore.first_background_time; + return back_forward_cache_restore.was_in_foreground && event && + (!first_background_time || + event.value() <= first_background_time.value()); +} + bool WasStartedInBackgroundOptionalEventInForeground( const base::Optional<base::TimeDelta>& event, const PageLoadMetricsObserverDelegate& delegate) { diff --git a/chromium/components/page_load_metrics/browser/page_load_metrics_util.h b/chromium/components/page_load_metrics/browser/page_load_metrics_util.h index bee6462ef5c..51cb8de9a77 100644 --- a/chromium/components/page_load_metrics/browser/page_load_metrics_util.h +++ b/chromium/components/page_load_metrics/browser/page_load_metrics_util.h @@ -103,6 +103,11 @@ bool WasStartedInForegroundOptionalEventInForeground( const base::Optional<base::TimeDelta>& event, const PageLoadMetricsObserverDelegate& delegate); +bool WasStartedInForegroundOptionalEventInForegroundAfterBackForwardCacheRestore( + const base::Optional<base::TimeDelta>& event, + const PageLoadMetricsObserverDelegate& delegate, + size_t index); + // Returns true if: // - We have timing information for the event. // - The page load started in the background. diff --git a/chromium/components/page_load_metrics/browser/page_load_tracker.cc b/chromium/components/page_load_metrics/browser/page_load_tracker.cc index b9cd0830760..40a6f4844a2 100644 --- a/chromium/components/page_load_metrics/browser/page_load_tracker.cc +++ b/chromium/components/page_load_metrics/browser/page_load_tracker.cc @@ -122,38 +122,82 @@ void RecordAppBackgroundPageLoadCompleted(bool completed_after_background) { completed_after_background); } +void DispatchEventsAfterBackForwardCacheRestore( + PageLoadMetricsObserver* observer, + const std::vector<mojo::StructPtr<mojom::BackForwardCacheTiming>>& + last_timings, + const std::vector<mojo::StructPtr<mojom::BackForwardCacheTiming>>& + new_timings) { + DCHECK_GE(new_timings.size(), last_timings.size()); + + for (size_t i = 0; i < new_timings.size(); i++) { + auto first_paint = + new_timings[i]->first_paint_after_back_forward_cache_restore; + if (!first_paint.is_zero() && + (i >= last_timings.size() || + last_timings[i] + ->first_paint_after_back_forward_cache_restore.is_zero())) { + observer->OnFirstPaintAfterBackForwardCacheRestoreInPage(*new_timings[i], + i); + } + + auto first_input_delay = + new_timings[i]->first_input_delay_after_back_forward_cache_restore; + if (first_input_delay.has_value() && + (i >= last_timings.size() || + !last_timings[i] + ->first_input_delay_after_back_forward_cache_restore + .has_value())) { + observer->OnFirstInputAfterBackForwardCacheRestoreInPage(*new_timings[i], + i); + } + } +} + void DispatchObserverTimingCallbacks(PageLoadMetricsObserver* observer, const mojom::PageLoadTiming& last_timing, const mojom::PageLoadTiming& new_timing) { if (!last_timing.Equals(new_timing)) observer->OnTimingUpdate(nullptr, new_timing); if (new_timing.document_timing->dom_content_loaded_event_start && - !last_timing.document_timing->dom_content_loaded_event_start) + !last_timing.document_timing->dom_content_loaded_event_start) { observer->OnDomContentLoadedEventStart(new_timing); + } if (new_timing.document_timing->load_event_start && - !last_timing.document_timing->load_event_start) + !last_timing.document_timing->load_event_start) { observer->OnLoadEventStart(new_timing); + } if (new_timing.interactive_timing->first_input_delay && - !last_timing.interactive_timing->first_input_delay) + !last_timing.interactive_timing->first_input_delay) { observer->OnFirstInputInPage(new_timing); + } if (new_timing.paint_timing->first_paint && - !last_timing.paint_timing->first_paint) + !last_timing.paint_timing->first_paint) { observer->OnFirstPaintInPage(new_timing); + } + DispatchEventsAfterBackForwardCacheRestore( + observer, last_timing.back_forward_cache_timings, + new_timing.back_forward_cache_timings); if (new_timing.paint_timing->first_image_paint && - !last_timing.paint_timing->first_image_paint) + !last_timing.paint_timing->first_image_paint) { observer->OnFirstImagePaintInPage(new_timing); + } if (new_timing.paint_timing->first_contentful_paint && - !last_timing.paint_timing->first_contentful_paint) + !last_timing.paint_timing->first_contentful_paint) { observer->OnFirstContentfulPaintInPage(new_timing); + } if (new_timing.paint_timing->first_meaningful_paint && - !last_timing.paint_timing->first_meaningful_paint) + !last_timing.paint_timing->first_meaningful_paint) { observer->OnFirstMeaningfulPaintInMainFrameDocument(new_timing); + } if (new_timing.parse_timing->parse_start && - !last_timing.parse_timing->parse_start) + !last_timing.parse_timing->parse_start) { observer->OnParseStart(new_timing); + } if (new_timing.parse_timing->parse_stop && - !last_timing.parse_timing->parse_stop) + !last_timing.parse_timing->parse_stop) { observer->OnParseStop(new_timing); + } } } // namespace @@ -296,16 +340,31 @@ void PageLoadTracker::LogAbortChainHistograms( void PageLoadTracker::PageHidden() { // Only log the first time we background in a given page load. - if (!first_background_time_.has_value()) { + if (!first_background_time_.has_value() || + (!back_forward_cache_restores_.empty() && + !back_forward_cache_restores_.back() + .first_background_time.has_value())) { // Make sure we either started in the foreground and haven't been // foregrounded yet, or started in the background and have already been // foregrounded. base::TimeTicks background_time; - DCHECK_EQ(started_in_foreground_, !first_foreground_time_.has_value()); + + if (!first_background_time_.has_value()) + DCHECK_EQ(started_in_foreground_, !first_foreground_time_.has_value()); + background_time = base::TimeTicks::Now(); ClampBrowserTimestampIfInterProcessTimeTickSkew(&background_time); DCHECK_GE(background_time, navigation_start_); - first_background_time_ = background_time - navigation_start_; + + if (!first_background_time_.has_value()) + first_background_time_ = background_time - navigation_start_; + + if (!back_forward_cache_restores_.empty() && + !back_forward_cache_restores_.back() + .first_background_time.has_value()) { + back_forward_cache_restores_.back().first_background_time = + background_time - navigation_start_after_back_forward_cache_restore_; + } } visibility_tracker_.OnHidden(); INVOKE_AND_PRUNE_OBSERVERS(observers_, OnHidden, @@ -331,6 +390,8 @@ void PageLoadTracker::PageShown() { } void PageLoadTracker::FrameDeleted(content::RenderFrameHost* rfh) { + metrics_update_dispatcher_.OnFrameDeleted(rfh); + largest_contentful_paint_handler_.OnFrameDeleted(rfh); for (const auto& observer : observers_) { observer->OnFrameDeleted(rfh); } @@ -349,6 +410,13 @@ void PageLoadTracker::Commit(content::NavigationHandle* navigation_handle) { page_transition_ = navigation_handle->GetPageTransition(); user_initiated_info_.user_gesture = navigation_handle->HasUserGesture(); + if (navigation_handle->IsInMainFrame()) { + largest_contentful_paint_handler_.RecordMainFrameTreeNodeId( + navigation_handle->GetFrameTreeNodeId()); + experimental_largest_contentful_paint_handler_.RecordMainFrameTreeNodeId( + navigation_handle->GetFrameTreeNodeId()); + } + const std::string& mime_type = navigation_handle->GetWebContents()->GetContentsMimeType(); INVOKE_AND_PRUNE_OBSERVERS(observers_, ShouldObserveMimeType, mime_type); @@ -380,6 +448,10 @@ void PageLoadTracker::ReadyToCommitNavigation( void PageLoadTracker::DidFinishSubFrameNavigation( content::NavigationHandle* navigation_handle) { + largest_contentful_paint_handler_.OnDidFinishSubFrameNavigation( + navigation_handle, navigation_start_); + experimental_largest_contentful_paint_handler_.OnDidFinishSubFrameNavigation( + navigation_handle, navigation_start_); for (const auto& observer : observers_) { observer->OnDidFinishSubFrameNavigation(navigation_handle); } @@ -632,6 +704,17 @@ void PageLoadTracker::OnTimingChanged() { DCHECK(!last_dispatched_merged_page_timing_->Equals( metrics_update_dispatcher_.timing())); + const mojom::PaintTimingPtr& paint_timing = + metrics_update_dispatcher_.timing().paint_timing; + largest_contentful_paint_handler_.RecordTiming( + *paint_timing->largest_contentful_paint, + paint_timing->first_input_or_scroll_notified_timestamp, + nullptr /* subframe_rfh */); + experimental_largest_contentful_paint_handler_.RecordTiming( + *paint_timing->experimental_largest_contentful_paint, + paint_timing->first_input_or_scroll_notified_timestamp, + nullptr /* subframe_rfh */); + for (const auto& observer : observers_) { DispatchObserverTimingCallbacks(observer.get(), *last_dispatched_merged_page_timing_, @@ -645,6 +728,13 @@ void PageLoadTracker::OnSubFrameTimingChanged( content::RenderFrameHost* rfh, const mojom::PageLoadTiming& timing) { DCHECK(rfh->GetParent()); + const mojom::PaintTimingPtr& paint_timing = timing.paint_timing; + largest_contentful_paint_handler_.RecordTiming( + *paint_timing->largest_contentful_paint, + paint_timing->first_input_or_scroll_notified_timestamp, rfh); + experimental_largest_contentful_paint_handler_.RecordTiming( + *paint_timing->experimental_largest_contentful_paint, + paint_timing->first_input_or_scroll_notified_timestamp, rfh); for (const auto& observer : observers_) { observer->OnTimingUpdate(rfh, timing); } @@ -680,6 +770,11 @@ void PageLoadTracker::BroadcastEventToObservers(const void* const event_key) { } } +void PageLoadTracker::DidActivatePortal(base::TimeTicks activation_time) { + for (const auto& observer : observers_) + observer->DidActivatePortal(activation_time); +} + void PageLoadTracker::UpdateFeaturesUsage( content::RenderFrameHost* rfh, const mojom::PageLoadFeatures& new_features) { @@ -688,6 +783,14 @@ void PageLoadTracker::UpdateFeaturesUsage( } } +void PageLoadTracker::UpdateThroughput( + mojom::ThroughputUkmDataPtr throughput_data) { + if (!throughput_data) + return; + for (const auto& observer : observers_) + observer->OnThroughputUpdate(throughput_data); +} + void PageLoadTracker::UpdateResourceDataUse( content::RenderFrameHost* rfh, const std::vector<mojom::ResourceDataUpdatePtr>& resources) { @@ -738,6 +841,11 @@ const base::Optional<base::TimeDelta>& PageLoadTracker::GetFirstForegroundTime() return first_foreground_time_; } +const PageLoadMetricsObserverDelegate::BackForwardCacheRestore& +PageLoadTracker::GetBackForwardCacheRestore(size_t index) const { + return back_forward_cache_restores_[index]; +} + bool PageLoadTracker::StartedInForeground() const { return started_in_foreground_; } @@ -808,6 +916,16 @@ const ResourceTracker& PageLoadTracker::GetResourceTracker() const { return resource_tracker_; } +const LargestContentfulPaintHandler& +PageLoadTracker::GetLargestContentfulPaintHandler() const { + return largest_contentful_paint_handler_; +} + +const LargestContentfulPaintHandler& +PageLoadTracker::GetExperimentalLargestContentfulPaintHandler() const { + return experimental_largest_contentful_paint_handler_; +} + ukm::SourceId PageLoadTracker::GetSourceId() const { return source_id_; } @@ -817,7 +935,6 @@ bool PageLoadTracker::IsFirstNavigationInWebContents() const { } void PageLoadTracker::OnEnterBackForwardCache() { - DCHECK(visibility_tracker_.currently_in_foreground()); if (GetWebContents()->GetVisibility() == content::Visibility::VISIBLE) { PageHidden(); } @@ -826,11 +943,20 @@ void PageLoadTracker::OnEnterBackForwardCache() { metrics_update_dispatcher_.timing()); } -void PageLoadTracker::OnRestoreFromBackForwardCache() { +void PageLoadTracker::OnRestoreFromBackForwardCache( + content::NavigationHandle* navigation_handle) { + navigation_start_after_back_forward_cache_restore_ = + navigation_handle->NavigationStart(); + DCHECK(!visibility_tracker_.currently_in_foreground()); - if (GetWebContents()->GetVisibility() == content::Visibility::VISIBLE) { + bool visible = + GetWebContents()->GetVisibility() == content::Visibility::VISIBLE; + + BackForwardCacheRestore back_forward_cache_restore(visible); + back_forward_cache_restores_.push_back(back_forward_cache_restore); + + if (visible) PageShown(); - } for (const auto& observer : observers_) { observer->OnRestoreFromBackForwardCache( diff --git a/chromium/components/page_load_metrics/browser/page_load_tracker.h b/chromium/components/page_load_metrics/browser/page_load_tracker.h index 6e8ab12c4db..ffa0f1973c7 100644 --- a/chromium/components/page_load_metrics/browser/page_load_tracker.h +++ b/chromium/components/page_load_metrics/browser/page_load_tracker.h @@ -11,6 +11,7 @@ #include "base/macros.h" #include "base/optional.h" #include "base/time/time.h" +#include "components/page_load_metrics/browser/observers/largest_contentful_paint_handler.h" #include "components/page_load_metrics/browser/page_load_metrics_observer.h" #include "components/page_load_metrics/browser/page_load_metrics_observer_delegate.h" #include "components/page_load_metrics/browser/page_load_metrics_update_dispatcher.h" @@ -203,14 +204,17 @@ class PageLoadTracker : public PageLoadMetricsUpdateDispatcher::Client, void OnFrameIntersectionUpdate( content::RenderFrameHost* rfh, const mojom::FrameIntersectionUpdate& frame_intersection_update) override; + void UpdateThroughput(mojom::ThroughputUkmDataPtr throughput_data) override; - // PageLoadMetricsDelegate implementation: + // PageLoadMetricsObserverDelegate implementation: content::WebContents* GetWebContents() const override; base::TimeTicks GetNavigationStart() const override; const base::Optional<base::TimeDelta>& GetFirstBackgroundTime() const override; const base::Optional<base::TimeDelta>& GetFirstForegroundTime() const override; + const BackForwardCacheRestore& GetBackForwardCacheRestore( + size_t index) const override; bool StartedInForeground() const override; const UserInitiatedInfo& GetUserInitiatedInfo() const override; const GURL& GetUrl() const override; @@ -226,6 +230,10 @@ class PageLoadTracker : public PageLoadMetricsUpdateDispatcher::Client, const PageRenderData& GetMainFrameRenderData() const override; const ui::ScopedVisibilityTracker& GetVisibilityTracker() const override; const ResourceTracker& GetResourceTracker() const override; + const LargestContentfulPaintHandler& GetLargestContentfulPaintHandler() + const override; + const LargestContentfulPaintHandler& + GetExperimentalLargestContentfulPaintHandler() const override; ukm::SourceId GetSourceId() const override; bool IsFirstNavigationInWebContents() const override; @@ -356,7 +364,12 @@ class PageLoadTracker : public PageLoadMetricsUpdateDispatcher::Client, void BroadcastEventToObservers(const void* const event_key); void OnEnterBackForwardCache(); - void OnRestoreFromBackForwardCache(); + void OnRestoreFromBackForwardCache( + content::NavigationHandle* navigation_handle); + + // Called when the page tracked was just activated after being loaded inside a + // portal. + void DidActivatePortal(base::TimeTicks activation_time); private: // This function converts a TimeTicks value taken in the browser process @@ -389,6 +402,10 @@ class PageLoadTracker : public PageLoadMetricsUpdateDispatcher::Client, // The navigation start in TimeTicks, not the wall time reported by Blink. const base::TimeTicks navigation_start_; + // The navigation start after the last time when back-forward cache is + // restored. + base::TimeTicks navigation_start_after_back_forward_cache_restore_; + // The most recent URL of this page load. Updated at navigation start, upon // redirection, and at commit time. GURL url_; @@ -424,7 +441,8 @@ class PageLoadTracker : public PageLoadMetricsUpdateDispatcher::Client, // when they occur in the background. base::Optional<base::TimeDelta> first_background_time_; base::Optional<base::TimeDelta> first_foreground_time_; - bool started_in_foreground_; + std::vector<BackForwardCacheRestore> back_forward_cache_restores_; + const bool started_in_foreground_; mojom::PageLoadTimingPtr last_dispatched_merged_page_timing_; @@ -463,6 +481,11 @@ class PageLoadTracker : public PageLoadMetricsUpdateDispatcher::Client, const bool is_first_navigation_in_web_contents_; + page_load_metrics::LargestContentfulPaintHandler + largest_contentful_paint_handler_; + page_load_metrics::LargestContentfulPaintHandler + experimental_largest_contentful_paint_handler_; + DISALLOW_COPY_AND_ASSIGN(PageLoadTracker); }; diff --git a/chromium/components/page_load_metrics/common/page_end_reason.h b/chromium/components/page_load_metrics/common/page_end_reason.h index 94f3444267d..f6ab8eb2d36 100644 --- a/chromium/components/page_load_metrics/common/page_end_reason.h +++ b/chromium/components/page_load_metrics/common/page_end_reason.h @@ -15,7 +15,9 @@ namespace page_load_metrics { // numeric values should never be reused. For any additions, also update the // corresponding PageEndReason enum in enums.xml. enum PageEndReason { - // Page lifetime has not yet ended (page is still active). + // Page lifetime has not yet ended (page is still active). Pages that + // become hidden are logged in END_HIDDEN, so we expect low numbers in this + // bucket from the end of May 2020, i.e. in Navigation.PageEndReason2. END_NONE = 0, // The page was reloaded, possibly by the user. @@ -50,6 +52,10 @@ enum PageEndReason { // without committing, either without error or with net::ERR_ABORTED. END_OTHER = 9, + // The page became hidden but is still active. Added end of May 2020 for + // Navigation.PageEndReason2. + END_HIDDEN = 10, + PAGE_END_REASON_COUNT }; diff --git a/chromium/components/page_load_metrics/common/page_load_metrics.mojom b/chromium/components/page_load_metrics/common/page_load_metrics.mojom index c40451d875a..f4c41890dd2 100644 --- a/chromium/components/page_load_metrics/common/page_load_metrics.mojom +++ b/chromium/components/page_load_metrics/common/page_load_metrics.mojom @@ -19,6 +19,22 @@ struct DocumentTiming { mojo_base.mojom.TimeDelta? load_event_start; }; +struct LargestContentfulPaintTiming { + // Time when the page's largest image is painted. Removed images are excluded. + mojo_base.mojom.TimeDelta? largest_image_paint; + + // Size of the largest image of the largest image paint, by + // Size = Height * Width. Removed images are excluded. + uint64 largest_image_paint_size; + + // Time when the page's largest text is painted. Removed text is excluded. + mojo_base.mojom.TimeDelta? largest_text_paint; + + // Size of the largest text of the largest text paint, by + // Size = Height * Width. Removed text is excluded. + uint64 largest_text_paint_size; +}; + // TimeDeltas below relative to navigation start. struct PaintTiming { // Time when the first paint is performed. @@ -33,23 +49,23 @@ struct PaintTiming { // (Experimental) Time when the page's primary content is painted. mojo_base.mojom.TimeDelta? first_meaningful_paint; - // (Experimental) Time when the page's largest image is painted. - mojo_base.mojom.TimeDelta? largest_image_paint; + // Largest contentful paint, which excludes removed content. + LargestContentfulPaintTiming largest_contentful_paint; - // (Experimental) Size of the largest image of the largest image paint, by - // Size = Height * Width. - uint64 largest_image_paint_size; + // (Experimental) largest contentful paint including removed content. + LargestContentfulPaintTiming experimental_largest_contentful_paint; - // (Experimental) Time when the page's largest text is painted. - mojo_base.mojom.TimeDelta? largest_text_paint; - - // (Experimental) Size of the largest text of the largest text paint, by - // Size = Height * Width. - uint64 largest_text_paint_size; + // (Experimental) Time when the frame is first eligible to be painted, i.e. + // is first not render-throttled. Will be null if frame is throttled, + // unless there has already been a |first_paint|. + mojo_base.mojom.TimeDelta? first_eligible_to_paint; // (Experimental) Time when first input or scroll is received, causing the // largest contentful paint algorithm to stop. mojo_base.mojom.TimeDelta? first_input_or_scroll_notified_timestamp; + + // Time when the first paint happens after a portal activation. + mojo_base.mojom.TimeTicks? portal_activated_paint; }; // TimeDeltas below represent durations of time during the page load. @@ -105,6 +121,16 @@ struct InteractiveTiming { // The timestamp of the event whose delay is reported as longest_input_delay. mojo_base.mojom.TimeDelta? longest_input_timestamp; + + // The latency between user input and display update for the first scroll after + // a navigation. + mojo_base.mojom.TimeDelta? first_scroll_delay; + + // The timestamp of the user's first scroll after a navigation. + mojo_base.mojom.TimeDelta? first_scroll_timestamp; + + // The duration of event handlers processing the first input event. + mojo_base.mojom.TimeDelta? first_input_processing_time; }; // PageLoadTiming contains timing metrics associated with a page load. Many of @@ -124,6 +150,11 @@ struct PageLoadTiming { InteractiveTiming interactive_timing; PaintTiming paint_timing; ParseTiming parse_timing; + + // List of back-forward cache timings, one for each time a page was restored + // from the cache. + array<BackForwardCacheTiming> back_forward_cache_timings; + // Time between user input and navigation start. This is set for navigations // where the input start timing is known; currently when the navigation is // initiated by a link click in the renderer, or from the desktop omnibox. @@ -291,6 +322,21 @@ struct InputTiming { uint64 num_input_events = 0; }; +struct PercentOptional { + int8 percent; +}; + +// The throughput data for this page. +struct ThroughputUkmData { + // The source id for the ukm. + int64 source_id; + // The throughput of the page of different threads: main, compositor, and the + // aggregated throughput of main && compositor thread. + int8 aggregated_throughput_percent; + int8 impl_throughput_percent; + PercentOptional? main_throughput_percent; +}; + // Sent from renderer to browser process when the PageLoadTiming for the // associated frame changed. interface PageLoadMetrics { @@ -306,4 +352,24 @@ interface PageLoadMetrics { CpuTiming cpu_load_timing, DeferredResourceCounts new_deferred_resource_data, InputTiming input_timing_delta); + + // The renderer submit throughput data to the browser process. The browser + // process aggregates these data, and reports the aggregated value to UKM when + // the page shuts down or navigates away. + // TODO(xidachen): The interface exists for every frame, but this method is + // only called for LocalRoots. Should limit the visibility to LocalRoots only. + SubmitThroughputData(ThroughputUkmData throughput_data); +}; + +// TimeDelta below relative to the navigation start of the navigation restoring +// page from the back- forward cache. +struct BackForwardCacheTiming { + // Time when the first paint is performed after the time when the page + // is restored from the back-forward cache. + mojo_base.mojom.TimeDelta first_paint_after_back_forward_cache_restore; + + // Queueing Time of the first click, tap, key press, cancellable touchstart, + // or pointer down followed by a pointer up after the time when the page is + // restored from the back-forward cache. + mojo_base.mojom.TimeDelta? first_input_delay_after_back_forward_cache_restore; }; diff --git a/chromium/components/page_load_metrics/common/page_load_timing.cc b/chromium/components/page_load_metrics/common/page_load_timing.cc index 6ec07e64bb9..09eae81d8e9 100644 --- a/chromium/components/page_load_metrics/common/page_load_timing.cc +++ b/chromium/components/page_load_metrics/common/page_load_timing.cc @@ -10,7 +10,13 @@ mojom::PageLoadTimingPtr CreatePageLoadTiming() { return mojom::PageLoadTiming::New( base::Time(), base::Optional<base::TimeDelta>(), mojom::DocumentTiming::New(), mojom::InteractiveTiming::New(), - mojom::PaintTiming::New(), mojom::ParseTiming::New(), + mojom::PaintTiming::New(base::nullopt, base::nullopt, base::nullopt, + base::nullopt, + mojom::LargestContentfulPaintTiming::New(), + mojom::LargestContentfulPaintTiming::New(), + base::nullopt, base::nullopt, base::nullopt), + mojom::ParseTiming::New(), + std::vector<mojo::StructPtr<mojom::BackForwardCacheTiming>>{}, base::Optional<base::TimeDelta>()); } @@ -20,7 +26,9 @@ bool IsEmpty(const page_load_metrics::mojom::DocumentTiming& timing) { bool IsEmpty(const page_load_metrics::mojom::InteractiveTiming& timing) { return !timing.first_input_delay && !timing.first_input_timestamp && - !timing.longest_input_delay && !timing.longest_input_timestamp; + !timing.longest_input_delay && !timing.longest_input_timestamp && + !timing.first_scroll_delay && !timing.first_scroll_timestamp && + !timing.first_input_processing_time; } bool IsEmpty(const page_load_metrics::mojom::InputTiming& timing) { @@ -32,7 +40,11 @@ bool IsEmpty(const page_load_metrics::mojom::InputTiming& timing) { bool IsEmpty(const page_load_metrics::mojom::PaintTiming& timing) { return !timing.first_paint && !timing.first_image_paint && !timing.first_contentful_paint && !timing.first_meaningful_paint && - !timing.largest_image_paint && !timing.largest_text_paint; + !timing.largest_contentful_paint->largest_image_paint && + !timing.largest_contentful_paint->largest_text_paint && + !timing.experimental_largest_contentful_paint->largest_image_paint && + !timing.experimental_largest_contentful_paint->largest_text_paint && + !timing.first_eligible_to_paint; } bool IsEmpty(const page_load_metrics::mojom::ParseTiming& timing) { @@ -52,14 +64,20 @@ bool IsEmpty(const page_load_metrics::mojom::PageLoadTiming& timing) { (!timing.paint_timing || page_load_metrics::IsEmpty(*timing.paint_timing)) && (!timing.parse_timing || - page_load_metrics::IsEmpty(*timing.parse_timing)); + page_load_metrics::IsEmpty(*timing.parse_timing)) && + timing.back_forward_cache_timings.empty(); } void InitPageLoadTimingForTest(mojom::PageLoadTiming* timing) { timing->document_timing = mojom::DocumentTiming::New(); timing->interactive_timing = mojom::InteractiveTiming::New(); timing->paint_timing = mojom::PaintTiming::New(); + timing->paint_timing->largest_contentful_paint = + mojom::LargestContentfulPaintTiming::New(); + timing->paint_timing->experimental_largest_contentful_paint = + mojom::LargestContentfulPaintTiming::New(); timing->parse_timing = mojom::ParseTiming::New(); + timing->back_forward_cache_timings.clear(); } } // namespace page_load_metrics diff --git a/chromium/components/page_load_metrics/renderer/metrics_render_frame_observer.cc b/chromium/components/page_load_metrics/renderer/metrics_render_frame_observer.cc index f0d615cfac0..da17af31f96 100644 --- a/chromium/components/page_load_metrics/renderer/metrics_render_frame_observer.cc +++ b/chromium/components/page_load_metrics/renderer/metrics_render_frame_observer.cc @@ -39,12 +39,16 @@ base::TimeTicks ClampToStart(base::TimeTicks event, base::TimeTicks start) { class MojoPageTimingSender : public PageTimingSender { public: - explicit MojoPageTimingSender(content::RenderFrame* render_frame) { + explicit MojoPageTimingSender(content::RenderFrame* render_frame, + bool limited_sending_mode) + : limited_sending_mode_(limited_sending_mode) { DCHECK(render_frame); render_frame->GetRemoteAssociatedInterfaces()->GetInterface( &page_load_metrics_); } + ~MojoPageTimingSender() override = default; + void SendTiming(const mojom::PageLoadTimingPtr& timing, const mojom::FrameMetadataPtr& metadata, mojom::PageLoadFeaturesPtr new_features, @@ -55,12 +59,34 @@ class MojoPageTimingSender : public PageTimingSender { mojom::InputTimingPtr input_timing_delta) override { DCHECK(page_load_metrics_); page_load_metrics_->UpdateTiming( - timing->Clone(), metadata->Clone(), std::move(new_features), - std::move(resources), render_data.Clone(), cpu_timing->Clone(), + limited_sending_mode_ ? CreatePageLoadTiming() : timing->Clone(), + metadata->Clone(), std::move(new_features), std::move(resources), + render_data.Clone(), cpu_timing->Clone(), std::move(new_deferred_resource_data), std::move(input_timing_delta)); } + void SubmitThroughputData(ukm::SourceId source_id, + int aggregated_percent, + int impl_percent, + base::Optional<int> main_percent) { + DCHECK(page_load_metrics_); + mojom::PercentOptionalPtr main_ptr = + main_percent.has_value() + ? mojom::PercentOptional::New(main_percent.value()) + : nullptr; + mojom::ThroughputUkmDataPtr throughput_data = mojom::ThroughputUkmData::New( + source_id, aggregated_percent, impl_percent, std::move(main_ptr)); + page_load_metrics_->SubmitThroughputData(std::move(throughput_data)); + } + private: + // Indicates that this sender should not send timing updates or frame render + // data updates. + // TODO(https://crbug.com/1097127): When timing updates are handled for cases + // where we have a subframe document and no committed navigation, this can be + // removed. + bool limited_sending_mode_ = false; + // Use associated interface to make sure mojo messages are ordered with regard // to legacy IPC messages. mojo::AssociatedRemote<mojom::PageLoadMetrics> page_load_metrics_; @@ -246,17 +272,49 @@ void MetricsRenderFrameObserver::DidFailProvisionalLoad() { provisional_frame_resource_data_use_.reset(); } -void MetricsRenderFrameObserver::DidCommitProvisionalLoad( - bool is_same_document_navigation, - ui::PageTransition transition) { - // Same-document navigations (e.g. a navigation from a fragment link) aren't - // full page loads, since they don't go to network to load the main HTML - // resource. DidStartProvisionalLoad doesn't get invoked for same document - // navigations, so we may still have an active page_timing_metrics_sender_ at - // this point. - if (is_same_document_navigation) +void MetricsRenderFrameObserver::DidCreateDocumentElement() { + // If we do not have a render frame or are already tracking this frame, ignore + // the new document element. + if (HasNoRenderFrame() || page_timing_metrics_sender_) + return; + + // We should only track committed navigations for the main frame so ignore new + // document elements in the main frame. + if (render_frame()->IsMainFrame()) return; + // Every frame creates an initial about:blank document element prior to + // receiving a navigation to about:blank. Ignore this initial document + // element. + if (!first_document_observed_) { + first_document_observed_ = true; + return; + } + + // A new document element was created in a frame that did not commit a + // provisional load. This can be due to a doc.write in the frame that aborted + // a navigation. Create a page timing sender to track this load. This sender + // will only send resource usage updates to the browser process. There + // currently is not infrastructure in the browser process to monitor this case + // and properly handle timing updates without a committed load. + // TODO(https://crbug.com/1097127): Implement proper handling of timing + // updates in the browser process and create a normal page timing sender. + + // It should not be possible to have a |provisional_frame_resource_data_use_| + // object at this point. If we did, it means we reached + // ReadyToCommitNavigation() and aborted prior to load commit which should not + // be possible. + DCHECK(!provisional_frame_resource_data_use_); + + Timing timing = GetTiming(); + page_timing_metrics_sender_ = std::make_unique<PageTimingMetricsSender>( + CreatePageTimingSender(true /* limited_sending_mode */), CreateTimer(), + std::move(timing.relative_timing), timing.monotonic_timing, + std::make_unique<PageResourceDataUse>()); +} + +void MetricsRenderFrameObserver::DidCommitProvisionalLoad( + ui::PageTransition transition) { // Make sure to release the sender for a previous navigation, if we have one. page_timing_metrics_sender_.reset(); @@ -274,7 +332,7 @@ void MetricsRenderFrameObserver::DidCommitProvisionalLoad( Timing timing = GetTiming(); page_timing_metrics_sender_ = std::make_unique<PageTimingMetricsSender>( - CreatePageTimingSender(), CreateTimer(), + CreatePageTimingSender(false /* limited_sending_mode*/), CreateTimer(), std::move(timing.relative_timing), timing.monotonic_timing, std::move(provisional_frame_resource_data_use_)); } @@ -301,6 +359,18 @@ void MetricsRenderFrameObserver::OnMainFrameDocumentIntersectionChanged( main_frame_document_intersection); } +void MetricsRenderFrameObserver::OnThroughputDataAvailable( + ukm::SourceId source_id, + int aggregated_percent, + int impl_percent, + base::Optional<int> main_percent) { + std::unique_ptr<MojoPageTimingSender> sender = + std::make_unique<MojoPageTimingSender>(render_frame(), + false /* limited_sending_mode */); + sender->SubmitThroughputData(source_id, aggregated_percent, impl_percent, + main_percent); +} + void MetricsRenderFrameObserver::MaybeSetCompletedBeforeFCP(int request_id) { if (HasNoRenderFrame()) return; @@ -410,6 +480,17 @@ MetricsRenderFrameObserver::Timing MetricsRenderFrameObserver::GetTiming() timing->interactive_timing->longest_input_timestamp = ClampDelta((*perf.LongestInputTimestamp()).InSecondsF(), start); } + if (perf.FirstInputProcessingTime().has_value()) { + timing->interactive_timing->first_input_processing_time = + *perf.FirstInputProcessingTime(); + } + if (perf.FirstScrollDelay().has_value()) { + timing->interactive_timing->first_scroll_delay = *perf.FirstScrollDelay(); + } + if (perf.FirstScrollTimestamp().has_value()) { + timing->interactive_timing->first_scroll_timestamp = + ClampDelta((*perf.FirstScrollTimestamp()).InSecondsF(), start); + } if (perf.ResponseStart() > 0.0) timing->response_start = ClampDelta(perf.ResponseStart(), start); if (perf.DomContentLoadedEventStart() > 0.0) { @@ -422,11 +503,36 @@ MetricsRenderFrameObserver::Timing MetricsRenderFrameObserver::GetTiming() } if (perf.FirstPaint() > 0.0) timing->paint_timing->first_paint = ClampDelta(perf.FirstPaint(), start); + if (!perf.BackForwardCacheRestore().empty()) { + blink::WebPerformance::BackForwardCacheRestoreTimings restore_timings = + perf.BackForwardCacheRestore(); + for (const auto& restore_timing : restore_timings) { + double navigation_start = restore_timing.navigation_start; + double first_paint = restore_timing.first_paint; + base::Optional<base::TimeDelta> first_input_delay = + restore_timing.first_input_delay; + + auto back_forward_cache_timing = mojom::BackForwardCacheTiming::New(); + if (first_paint) { + back_forward_cache_timing + ->first_paint_after_back_forward_cache_restore = + ClampDelta(first_paint, navigation_start); + } + if (first_input_delay.has_value()) { + back_forward_cache_timing + ->first_input_delay_after_back_forward_cache_restore = + ClampDelta(first_input_delay->InSecondsF(), navigation_start); + } + timing->back_forward_cache_timings.push_back( + std::move(back_forward_cache_timing)); + } + } if (perf.FirstImagePaint() > 0.0) { timing->paint_timing->first_image_paint = ClampDelta(perf.FirstImagePaint(), start); } if (perf.FirstContentfulPaint() > 0.0) { + DCHECK(perf.FirstEligibleToPaint() > 0); timing->paint_timing->first_contentful_paint = ClampDelta(perf.FirstContentfulPaint(), start); monotonic_timing.first_contentful_paint = @@ -438,13 +544,13 @@ MetricsRenderFrameObserver::Timing MetricsRenderFrameObserver::GetTiming() ClampDelta(perf.FirstMeaningfulPaint(), start); } if (perf.LargestImagePaintSize() > 0) { - timing->paint_timing->largest_image_paint_size = + timing->paint_timing->largest_contentful_paint->largest_image_paint_size = perf.LargestImagePaintSize(); // Note that size can be nonzero while the time is 0 since a time of 0 is // sent when the image is painting. We assign the time even when it is 0 so // that it's not ignored, but need to be careful when doing operations on // the value. - timing->paint_timing->largest_image_paint = + timing->paint_timing->largest_contentful_paint->largest_image_paint = perf.LargestImagePaint() == 0.0 ? base::TimeDelta() : ClampDelta(perf.LargestImagePaint(), start); @@ -453,9 +559,43 @@ MetricsRenderFrameObserver::Timing MetricsRenderFrameObserver::GetTiming() // LargestTextPaint and LargestTextPaintSize should be available at the // same time. This is a renderer side DCHECK to ensure this. DCHECK(perf.LargestTextPaint()); - timing->paint_timing->largest_text_paint = + timing->paint_timing->largest_contentful_paint->largest_text_paint = ClampDelta(perf.LargestTextPaint(), start); - timing->paint_timing->largest_text_paint_size = perf.LargestTextPaintSize(); + timing->paint_timing->largest_contentful_paint->largest_text_paint_size = + perf.LargestTextPaintSize(); + } + if (perf.ExperimentalLargestImagePaintSize() > 0) { + timing->paint_timing->experimental_largest_contentful_paint + ->largest_image_paint_size = perf.ExperimentalLargestImagePaintSize(); + // Note that size can be nonzero while the time is 0 since a time of 0 is + // sent when the image is painting. We assign the time even when it is 0 so + // that it's not ignored, but need to be careful when doing operations on + // the value. + timing->paint_timing->experimental_largest_contentful_paint + ->largest_image_paint = + perf.ExperimentalLargestImagePaint() == 0.0 + ? base::TimeDelta() + : ClampDelta(perf.ExperimentalLargestImagePaint(), start); + } + if (perf.ExperimentalLargestTextPaintSize() > 0) { + // ExperimentalLargestTextPaint and ExperimentalLargestTextPaintSize should + // be available at the same time. This is a renderer side DCHECK to ensure + // this. + DCHECK(perf.ExperimentalLargestTextPaint()); + timing->paint_timing->experimental_largest_contentful_paint + ->largest_text_paint = + ClampDelta(perf.ExperimentalLargestTextPaint(), start); + timing->paint_timing->experimental_largest_contentful_paint + ->largest_text_paint_size = perf.ExperimentalLargestTextPaintSize(); + } + // It is possible for a frame to switch from eligible for painting to + // ineligible for it prior to the first paint. If this occurs, we need to + // propagate the null value. + if (perf.FirstEligibleToPaint() > 0) { + timing->paint_timing->first_eligible_to_paint = + ClampDelta(perf.FirstEligibleToPaint(), start); + } else { + timing->paint_timing->first_eligible_to_paint.reset(); } if (perf.FirstInputOrScrollNotifiedTimestamp() > 0) { timing->paint_timing->first_input_or_scroll_notified_timestamp = @@ -482,6 +622,10 @@ MetricsRenderFrameObserver::Timing MetricsRenderFrameObserver::GetTiming() base::TimeDelta::FromSecondsD( perf.ParseBlockedOnScriptExecutionFromDocumentWriteDuration()); } + if (perf.LastPortalActivatedPaint().has_value()) { + timing->paint_timing->portal_activated_paint = + *perf.LastPortalActivatedPaint(); + } return Timing(std::move(timing), monotonic_timing); } @@ -491,9 +635,9 @@ std::unique_ptr<base::OneShotTimer> MetricsRenderFrameObserver::CreateTimer() { } std::unique_ptr<PageTimingSender> -MetricsRenderFrameObserver::CreatePageTimingSender() { +MetricsRenderFrameObserver::CreatePageTimingSender(bool limited_sending_mode) { return base::WrapUnique<PageTimingSender>( - new MojoPageTimingSender(render_frame())); + new MojoPageTimingSender(render_frame(), limited_sending_mode)); } bool MetricsRenderFrameObserver::HasNoRenderFrame() const { diff --git a/chromium/components/page_load_metrics/renderer/metrics_render_frame_observer.h b/chromium/components/page_load_metrics/renderer/metrics_render_frame_observer.h index 9dc048cb1a4..c37e1cf91f9 100644 --- a/chromium/components/page_load_metrics/renderer/metrics_render_frame_observer.h +++ b/chromium/components/page_load_metrics/renderer/metrics_render_frame_observer.h @@ -77,8 +77,8 @@ class MetricsRenderFrameObserver void ReadyToCommitNavigation( blink::WebDocumentLoader* document_loader) override; void DidFailProvisionalLoad() override; - void DidCommitProvisionalLoad(bool is_same_document_navigation, - ui::PageTransition transition) override; + void DidCommitProvisionalLoad(ui::PageTransition transition) override; + void DidCreateDocumentElement() override; void OnDestruct() override; // Invoked when a frame is going away. This is our last chance to send IPCs @@ -96,6 +96,11 @@ class MetricsRenderFrameObserver void OnMainFrameDocumentIntersectionChanged( const blink::WebRect& main_frame_document_intersection) override; + void OnThroughputDataAvailable(ukm::SourceId source_id, + int aggregated_percent, + int impl_percent, + base::Optional<int> main_percent) override; + protected: // The relative and monotonic page load timings. struct Timing { @@ -125,9 +130,14 @@ class MetricsRenderFrameObserver void SendMetrics(); virtual Timing GetTiming() const; virtual std::unique_ptr<base::OneShotTimer> CreateTimer(); - virtual std::unique_ptr<PageTimingSender> CreatePageTimingSender(); + virtual std::unique_ptr<PageTimingSender> CreatePageTimingSender( + bool limited_sending_mode); virtual bool HasNoRenderFrame() const; + // Whether the initial about:blank document loaded into every frame was + // observed. + bool first_document_observed_ = false; + // Collects the data use of the frame request for a provisional load until the // load is committed. We want to collect data use for completed navigations in // this class, but the various navigation callbacks do not provide enough data diff --git a/chromium/components/page_load_metrics/renderer/metrics_render_frame_observer_unittest.cc b/chromium/components/page_load_metrics/renderer/metrics_render_frame_observer_unittest.cc index 6bc88c9ef83..a4789646bb0 100644 --- a/chromium/components/page_load_metrics/renderer/metrics_render_frame_observer_unittest.cc +++ b/chromium/components/page_load_metrics/renderer/metrics_render_frame_observer_unittest.cc @@ -32,7 +32,8 @@ class TestMetricsRenderFrameObserver : public MetricsRenderFrameObserver, return std::move(timer); } - std::unique_ptr<PageTimingSender> CreatePageTimingSender() override { + std::unique_ptr<PageTimingSender> CreatePageTimingSender( + bool limited_sending_mode) override { return base::WrapUnique<PageTimingSender>( new FakePageTimingSender(&validator_)); } @@ -88,7 +89,7 @@ TEST_F(MetricsRenderFrameObserverTest, SingleMetric) { observer.ExpectPageLoadTiming(timing); observer.DidStartNavigation(GURL(), base::nullopt); observer.ReadyToCommitNavigation(nullptr); - observer.DidCommitProvisionalLoad(false, ui::PAGE_TRANSITION_LINK); + observer.DidCommitProvisionalLoad(ui::PAGE_TRANSITION_LINK); observer.GetMockTimer()->Fire(); timing.parse_timing->parse_start = base::TimeDelta::FromMilliseconds(10); @@ -111,7 +112,7 @@ TEST_F(MetricsRenderFrameObserverTest, SingleCpuMetric) { observer.ExpectPageLoadTiming(timing); observer.DidStartNavigation(GURL(), base::nullopt); observer.ReadyToCommitNavigation(nullptr); - observer.DidCommitProvisionalLoad(false, ui::PAGE_TRANSITION_LINK); + observer.DidCommitProvisionalLoad(ui::PAGE_TRANSITION_LINK); // Send cpu timing updates and verify the expected result. observer.DidChangeCpuTiming(base::TimeDelta::FromMilliseconds(110)); @@ -133,7 +134,7 @@ TEST_F(MetricsRenderFrameObserverTest, MultipleMetrics) { observer.ExpectPageLoadTiming(timing); observer.DidStartNavigation(GURL(), base::nullopt); observer.ReadyToCommitNavigation(nullptr); - observer.DidCommitProvisionalLoad(false, ui::PAGE_TRANSITION_LINK); + observer.DidCommitProvisionalLoad(ui::PAGE_TRANSITION_LINK); observer.GetMockTimer()->Fire(); timing.document_timing->dom_content_loaded_event_start = dom_event; @@ -179,7 +180,7 @@ TEST_F(MetricsRenderFrameObserverTest, MultipleNavigations) { observer.ExpectPageLoadTiming(timing); observer.DidStartNavigation(GURL(), base::nullopt); observer.ReadyToCommitNavigation(nullptr); - observer.DidCommitProvisionalLoad(false, ui::PAGE_TRANSITION_LINK); + observer.DidCommitProvisionalLoad(ui::PAGE_TRANSITION_LINK); observer.GetMockTimer()->Fire(); timing.document_timing->dom_content_loaded_event_start = dom_event; @@ -205,7 +206,7 @@ TEST_F(MetricsRenderFrameObserverTest, MultipleNavigations) { observer.ExpectPageLoadTiming(timing_2); observer.DidStartNavigation(GURL(), base::nullopt); observer.ReadyToCommitNavigation(nullptr); - observer.DidCommitProvisionalLoad(false, ui::PAGE_TRANSITION_LINK); + observer.DidCommitProvisionalLoad(ui::PAGE_TRANSITION_LINK); observer.GetMockTimer()->Fire(); timing_2.document_timing->dom_content_loaded_event_start = dom_event_2; diff --git a/chromium/components/page_load_metrics/renderer/page_timing_metrics_sender.cc b/chromium/components/page_load_metrics/renderer/page_timing_metrics_sender.cc index 36625df9f45..3cd64fefc99 100644 --- a/chromium/components/page_load_metrics/renderer/page_timing_metrics_sender.cc +++ b/chromium/components/page_load_metrics/renderer/page_timing_metrics_sender.cc @@ -44,9 +44,9 @@ PageTimingMetricsSender::PageTimingMetricsSender( new_deferred_resource_data_(mojom::DeferredResourceCounts::New()), buffer_timer_delay_ms_(kBufferTimerDelayMillis), metadata_recorder_(initial_monotonic_timing) { + const auto resource_id = initial_request->resource_id(); page_resource_data_use_.emplace( - std::piecewise_construct, - std::forward_as_tuple(initial_request->resource_id()), + std::piecewise_construct, std::forward_as_tuple(resource_id), std::forward_as_tuple(std::move(initial_request))); buffer_timer_delay_ms_ = base::GetFieldTrialParamByFeatureAsInt( kPageLoadMetricsTimerDelayFeature, "BufferTimerDelayMillis", |