| Index: chrome/browser/page_load_metrics/page_load_metrics_update_dispatcher.cc
|
| diff --git a/chrome/browser/page_load_metrics/page_load_tracker.cc b/chrome/browser/page_load_metrics/page_load_metrics_update_dispatcher.cc
|
| similarity index 35%
|
| copy from chrome/browser/page_load_metrics/page_load_tracker.cc
|
| copy to chrome/browser/page_load_metrics/page_load_metrics_update_dispatcher.cc
|
| index dbbfff0fc38cd36c7d3043d3bc820b7fd8e1257c..1b9898e77c4872c9d25b8bde45c9e3424aeaada0 100644
|
| --- a/chrome/browser/page_load_metrics/page_load_tracker.cc
|
| +++ b/chrome/browser/page_load_metrics/page_load_metrics_update_dispatcher.cc
|
| @@ -1,114 +1,33 @@
|
| -// Copyright 2016 The Chromium Authors. All rights reserved.
|
| +// Copyright 2017 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 "chrome/browser/page_load_metrics/page_load_tracker.h"
|
| +#include "chrome/browser/page_load_metrics/page_load_metrics_update_dispatcher.h"
|
|
|
| -#include <algorithm>
|
| #include <ostream>
|
| -#include <string>
|
| #include <utility>
|
|
|
| #include "base/logging.h"
|
| -#include "base/memory/ptr_util.h"
|
| #include "base/metrics/histogram_macros.h"
|
| +#include "base/optional.h"
|
| #include "chrome/browser/page_load_metrics/browser_page_track_decider.h"
|
| #include "chrome/browser/page_load_metrics/page_load_metrics_embedder_interface.h"
|
| #include "chrome/browser/page_load_metrics/page_load_metrics_util.h"
|
| -#include "chrome/browser/prerender/prerender_contents.h"
|
| -#include "chrome/common/page_load_metrics/page_load_timing.h"
|
| -#include "content/public/browser/navigation_details.h"
|
| +#include "chrome/browser/page_load_metrics/page_load_tracker.h"
|
| +#include "chrome/common/page_load_metrics/page_load_metrics_constants.h"
|
| #include "content/public/browser/navigation_handle.h"
|
| #include "content/public/browser/render_frame_host.h"
|
| -#include "content/public/browser/web_contents.h"
|
| -#include "content/public/browser/web_contents_observer.h"
|
| -#include "content/public/common/browser_side_navigation_policy.h"
|
| -#include "ui/base/page_transition_types.h"
|
| -
|
| -// This macro invokes the specified method on each observer, passing the
|
| -// variable length arguments as the method's arguments, and removes the observer
|
| -// from the list of observers if the given method returns STOP_OBSERVING.
|
| -#define INVOKE_AND_PRUNE_OBSERVERS(observers, Method, ...) \
|
| - for (auto it = observers.begin(); it != observers.end();) { \
|
| - if ((*it)->Method(__VA_ARGS__) == \
|
| - PageLoadMetricsObserver::STOP_OBSERVING) { \
|
| - it = observers.erase(it); \
|
| - } else { \
|
| - ++it; \
|
| - } \
|
| - }
|
|
|
| namespace page_load_metrics {
|
|
|
| namespace internal {
|
|
|
| -const char kErrorEvents[] = "PageLoad.Internal.ErrorCode";
|
| -const char kAbortChainSizeReload[] =
|
| - "PageLoad.Internal.ProvisionalAbortChainSize.Reload";
|
| -const char kAbortChainSizeForwardBack[] =
|
| - "PageLoad.Internal.ProvisionalAbortChainSize.ForwardBack";
|
| -const char kAbortChainSizeNewNavigation[] =
|
| - "PageLoad.Internal.ProvisionalAbortChainSize.NewNavigation";
|
| -const char kAbortChainSizeSameURL[] =
|
| - "PageLoad.Internal.ProvisionalAbortChainSize.SameURL";
|
| -const char kAbortChainSizeNoCommit[] =
|
| - "PageLoad.Internal.ProvisionalAbortChainSize.NoCommit";
|
| -const char kClientRedirectFirstPaintToNavigation[] =
|
| - "PageLoad.Internal.ClientRedirect.FirstPaintToNavigation";
|
| -const char kClientRedirectWithoutPaint[] =
|
| - "PageLoad.Internal.ClientRedirect.NavigationWithoutPaint";
|
| -const char kPageLoadCompletedAfterAppBackground[] =
|
| - "PageLoad.Internal.PageLoadCompleted.AfterAppBackground";
|
| -const char kPageLoadStartedInForeground[] =
|
| - "PageLoad.Internal.NavigationStartedInForeground";
|
| -const char kPageLoadPrerender[] = "PageLoad.Internal.Prerender";
|
| const char kPageLoadTimingStatus[] = "PageLoad.Internal.PageLoadTimingStatus";
|
| const char kPageLoadTimingDispatchStatus[] =
|
| "PageLoad.Internal.PageLoadTimingStatus.AtTimingCallbackDispatch";
|
|
|
| } // namespace internal
|
|
|
| -void RecordInternalError(InternalErrorLoadEvent event) {
|
| - UMA_HISTOGRAM_ENUMERATION(internal::kErrorEvents, event, ERR_LAST_ENTRY);
|
| -}
|
| -
|
| -// TODO(csharrison): Add a case for client side redirects, which is what JS
|
| -// initiated window.location / window.history navigations get set to.
|
| -PageEndReason EndReasonForPageTransition(ui::PageTransition transition) {
|
| - if (transition & ui::PAGE_TRANSITION_CLIENT_REDIRECT) {
|
| - return END_CLIENT_REDIRECT;
|
| - }
|
| - if (ui::PageTransitionCoreTypeIs(transition, ui::PAGE_TRANSITION_RELOAD))
|
| - return END_RELOAD;
|
| - if (transition & ui::PAGE_TRANSITION_FORWARD_BACK)
|
| - return END_FORWARD_BACK;
|
| - if (ui::PageTransitionIsNewNavigation(transition))
|
| - return END_NEW_NAVIGATION;
|
| - NOTREACHED()
|
| - << "EndReasonForPageTransition received unexpected ui::PageTransition: "
|
| - << transition;
|
| - return END_OTHER;
|
| -}
|
| -
|
| -void LogAbortChainSameURLHistogram(int aborted_chain_size_same_url) {
|
| - if (aborted_chain_size_same_url > 0) {
|
| - UMA_HISTOGRAM_COUNTS(internal::kAbortChainSizeSameURL,
|
| - aborted_chain_size_same_url);
|
| - }
|
| -}
|
| -
|
| -bool IsNavigationUserInitiated(content::NavigationHandle* handle) {
|
| - // TODO(crbug.com/617904): Browser initiated navigations should have
|
| - // HasUserGesture() set to true. In the meantime, we consider all
|
| - // browser-initiated navigations to be user initiated.
|
| - //
|
| - // TODO(crbug.com/637345): Some browser-initiated navigations incorrectly
|
| - // report that they are renderer-initiated. We will currently report that
|
| - // these navigations are not user initiated, when in fact they are user
|
| - // initiated.
|
| - return handle->HasUserGesture() || !handle->IsRendererInitiated();
|
| -}
|
| -
|
| namespace {
|
|
|
| // Helper to allow use of Optional<> values in LOG() messages.
|
| @@ -316,251 +235,37 @@ void MaybeUpdateTimeDelta(
|
| *inout_existing_value = candidate_new_value;
|
| }
|
|
|
| -void RecordAppBackgroundPageLoadCompleted(bool completed_after_background) {
|
| - UMA_HISTOGRAM_BOOLEAN(internal::kPageLoadCompletedAfterAppBackground,
|
| - completed_after_background);
|
| -}
|
| -
|
| -void DispatchObserverTimingCallbacks(
|
| - PageLoadMetricsObserver* observer,
|
| - const mojom::PageLoadTiming& last_timing,
|
| - const mojom::PageLoadTiming& new_timing,
|
| - const mojom::PageLoadMetadata& last_metadata,
|
| - const PageLoadExtraInfo& extra_info) {
|
| - if (extra_info.main_frame_metadata.behavior_flags !=
|
| - last_metadata.behavior_flags)
|
| - observer->OnLoadingBehaviorObserved(extra_info);
|
| - if (!last_timing.Equals(new_timing))
|
| - observer->OnTimingUpdate(new_timing, extra_info);
|
| - if (new_timing.document_timing->dom_content_loaded_event_start &&
|
| - !last_timing.document_timing->dom_content_loaded_event_start)
|
| - observer->OnDomContentLoadedEventStart(new_timing, extra_info);
|
| - if (new_timing.document_timing->load_event_start &&
|
| - !last_timing.document_timing->load_event_start)
|
| - observer->OnLoadEventStart(new_timing, extra_info);
|
| - if (new_timing.document_timing->first_layout &&
|
| - !last_timing.document_timing->first_layout)
|
| - observer->OnFirstLayout(new_timing, extra_info);
|
| - if (new_timing.paint_timing->first_paint &&
|
| - !last_timing.paint_timing->first_paint)
|
| - observer->OnFirstPaintInPage(new_timing, extra_info);
|
| - if (new_timing.paint_timing->first_text_paint &&
|
| - !last_timing.paint_timing->first_text_paint)
|
| - observer->OnFirstTextPaintInPage(new_timing, extra_info);
|
| - if (new_timing.paint_timing->first_image_paint &&
|
| - !last_timing.paint_timing->first_image_paint)
|
| - observer->OnFirstImagePaintInPage(new_timing, extra_info);
|
| - if (new_timing.paint_timing->first_contentful_paint &&
|
| - !last_timing.paint_timing->first_contentful_paint)
|
| - observer->OnFirstContentfulPaintInPage(new_timing, extra_info);
|
| - if (new_timing.paint_timing->first_meaningful_paint &&
|
| - !last_timing.paint_timing->first_meaningful_paint)
|
| - observer->OnFirstMeaningfulPaintInMainFrameDocument(new_timing, extra_info);
|
| - if (new_timing.parse_timing->parse_start &&
|
| - !last_timing.parse_timing->parse_start)
|
| - observer->OnParseStart(new_timing, extra_info);
|
| - if (new_timing.parse_timing->parse_stop &&
|
| - !last_timing.parse_timing->parse_stop)
|
| - observer->OnParseStop(new_timing, extra_info);
|
| -}
|
| -
|
| } // namespace
|
|
|
| -PageLoadTracker::PageLoadTracker(
|
| - bool in_foreground,
|
| - PageLoadMetricsEmbedderInterface* embedder_interface,
|
| - const GURL& currently_committed_url,
|
| +PageLoadMetricsUpdateDispatcher::PageLoadMetricsUpdateDispatcher(
|
| + PageLoadMetricsUpdateDispatcher::Client* client,
|
| content::NavigationHandle* navigation_handle,
|
| - UserInitiatedInfo user_initiated_info,
|
| - int aborted_chain_size,
|
| - int aborted_chain_size_same_url)
|
| - : did_stop_tracking_(false),
|
| - app_entered_background_(false),
|
| + PageLoadMetricsEmbedderInterface* embedder_interface)
|
| + : client_(client),
|
| + embedder_interface_(embedder_interface),
|
| navigation_start_(navigation_handle->NavigationStart()),
|
| - url_(navigation_handle->GetURL()),
|
| - start_url_(navigation_handle->GetURL()),
|
| - did_commit_(false),
|
| - page_end_reason_(END_NONE),
|
| - page_end_user_initiated_info_(UserInitiatedInfo::NotUserInitiated()),
|
| - started_in_foreground_(in_foreground),
|
| - merged_page_timing_(CreatePageLoadTiming()),
|
| - last_dispatched_merged_page_timing_(CreatePageLoadTiming()),
|
| - last_dispatched_main_frame_metadata_(mojom::PageLoadMetadata::New()),
|
| - page_transition_(navigation_handle->GetPageTransition()),
|
| - user_initiated_info_(user_initiated_info),
|
| - aborted_chain_size_(aborted_chain_size),
|
| - aborted_chain_size_same_url_(aborted_chain_size_same_url),
|
| - embedder_interface_(embedder_interface) {
|
| - DCHECK(!navigation_handle->HasCommitted());
|
| - embedder_interface_->RegisterObservers(this);
|
| - INVOKE_AND_PRUNE_OBSERVERS(observers_, OnStart, navigation_handle,
|
| - currently_committed_url, started_in_foreground_);
|
| -
|
| - UMA_HISTOGRAM_BOOLEAN(internal::kPageLoadStartedInForeground,
|
| - started_in_foreground_);
|
| - const bool is_prerender = prerender::PrerenderContents::FromWebContents(
|
| - navigation_handle->GetWebContents()) != nullptr;
|
| - if (is_prerender) {
|
| - UMA_HISTOGRAM_BOOLEAN(internal::kPageLoadPrerender, true);
|
| - }
|
| -}
|
| -
|
| -PageLoadTracker::~PageLoadTracker() {
|
| - if (app_entered_background_) {
|
| - RecordAppBackgroundPageLoadCompleted(true);
|
| - }
|
| -
|
| - if (did_stop_tracking_)
|
| - return;
|
| -
|
| - if (page_end_time_.is_null()) {
|
| - // page_end_time_ can be unset in some cases, such as when a navigation is
|
| - // aborted by a navigation that started before it. In these cases, set the
|
| - // end time to the current time.
|
| - RecordInternalError(ERR_NO_PAGE_LOAD_END_TIME);
|
| - NotifyPageEnd(END_OTHER, UserInitiatedInfo::NotUserInitiated(),
|
| - base::TimeTicks::Now(), true);
|
| - }
|
| -
|
| - if (!did_commit_) {
|
| - if (!failed_provisional_load_info_)
|
| - RecordInternalError(ERR_NO_COMMIT_OR_FAILED_PROVISIONAL_LOAD);
|
| -
|
| - // Don't include any aborts that resulted in a new navigation, as the chain
|
| - // length will be included in the aborter PageLoadTracker.
|
| - if (page_end_reason_ != END_RELOAD &&
|
| - page_end_reason_ != END_FORWARD_BACK &&
|
| - page_end_reason_ != END_NEW_NAVIGATION) {
|
| - LogAbortChainHistograms(nullptr);
|
| - }
|
| - } else if (page_load_metrics::IsEmpty(*merged_page_timing_)) {
|
| - RecordInternalError(ERR_NO_IPCS_RECEIVED);
|
| - }
|
| -
|
| - const PageLoadExtraInfo info = ComputePageLoadExtraInfo();
|
| - for (const auto& observer : observers_) {
|
| - if (failed_provisional_load_info_) {
|
| - observer->OnFailedProvisionalLoad(*failed_provisional_load_info_, info);
|
| - } else if (did_commit_) {
|
| - observer->OnComplete(*merged_page_timing_, info);
|
| - }
|
| - }
|
| -}
|
| -
|
| -void PageLoadTracker::LogAbortChainHistograms(
|
| - content::NavigationHandle* final_navigation) {
|
| - if (aborted_chain_size_ == 0)
|
| - return;
|
| - // Note that this could be broken out by this navigation's abort type, if more
|
| - // granularity is needed. Add one to the chain size to count the current
|
| - // navigation. In the other cases, the current navigation is the final
|
| - // navigation (which commits).
|
| - if (!final_navigation) {
|
| - UMA_HISTOGRAM_COUNTS(internal::kAbortChainSizeNoCommit,
|
| - aborted_chain_size_ + 1);
|
| - LogAbortChainSameURLHistogram(aborted_chain_size_same_url_ + 1);
|
| - return;
|
| - }
|
| -
|
| - // The following is only executed for committing trackers.
|
| - DCHECK(did_commit_);
|
| -
|
| - // Note that histograms could be separated out by this commit's transition
|
| - // type, but for simplicity they will all be bucketed together.
|
| - LogAbortChainSameURLHistogram(aborted_chain_size_same_url_);
|
| + current_merged_page_timing_(CreatePageLoadTiming()),
|
| + pending_merged_page_timing_(CreatePageLoadTiming()),
|
| + main_frame_metadata_(mojom::PageLoadMetadata::New()),
|
| + subframe_metadata_(mojom::PageLoadMetadata::New()) {}
|
|
|
| - ui::PageTransition committed_transition =
|
| - final_navigation->GetPageTransition();
|
| - switch (EndReasonForPageTransition(committed_transition)) {
|
| - case END_RELOAD:
|
| - UMA_HISTOGRAM_COUNTS(internal::kAbortChainSizeReload,
|
| - aborted_chain_size_);
|
| - return;
|
| - case END_FORWARD_BACK:
|
| - UMA_HISTOGRAM_COUNTS(internal::kAbortChainSizeForwardBack,
|
| - aborted_chain_size_);
|
| - return;
|
| - // TODO(csharrison): Refactor this code so it is based on the WillStart*
|
| - // code path instead of the committed load code path. Then, for every abort
|
| - // chain, log a histogram of the counts of each of these metrics. For now,
|
| - // merge client redirects with new navigations, which was (basically) the
|
| - // previous behavior.
|
| - case END_CLIENT_REDIRECT:
|
| - case END_NEW_NAVIGATION:
|
| - UMA_HISTOGRAM_COUNTS(internal::kAbortChainSizeNewNavigation,
|
| - aborted_chain_size_);
|
| - return;
|
| - default:
|
| - NOTREACHED()
|
| - << "LogAbortChainHistograms received unexpected ui::PageTransition: "
|
| - << committed_transition;
|
| - return;
|
| - }
|
| -}
|
| -
|
| -void PageLoadTracker::WebContentsHidden() {
|
| - // Only log the first time we background in a given page load.
|
| - if (background_time_.is_null()) {
|
| - // Make sure we either started in the foreground and haven't been
|
| - // foregrounded yet, or started in the background and have already been
|
| - // foregrounded.
|
| - DCHECK_EQ(started_in_foreground_, foreground_time_.is_null());
|
| - background_time_ = base::TimeTicks::Now();
|
| - ClampBrowserTimestampIfInterProcessTimeTickSkew(&background_time_);
|
| - }
|
| - const PageLoadExtraInfo info = ComputePageLoadExtraInfo();
|
| - INVOKE_AND_PRUNE_OBSERVERS(observers_, OnHidden, *merged_page_timing_, info);
|
| -}
|
| -
|
| -void PageLoadTracker::WebContentsShown() {
|
| - // Only log the first time we foreground in a given page load.
|
| - if (foreground_time_.is_null()) {
|
| - // Make sure we either started in the background and haven't been
|
| - // backgrounded yet, or started in the foreground and have already been
|
| - // backgrounded.
|
| - DCHECK_NE(started_in_foreground_, background_time_.is_null());
|
| - foreground_time_ = base::TimeTicks::Now();
|
| - ClampBrowserTimestampIfInterProcessTimeTickSkew(&foreground_time_);
|
| - }
|
| -
|
| - INVOKE_AND_PRUNE_OBSERVERS(observers_, OnShown);
|
| -}
|
| -
|
| -void PageLoadTracker::WillProcessNavigationResponse(
|
| - content::NavigationHandle* navigation_handle) {
|
| - DCHECK(!navigation_request_id_.has_value());
|
| - navigation_request_id_ = navigation_handle->GetGlobalRequestID();
|
| - DCHECK(navigation_request_id_.value() != content::GlobalRequestID());
|
| -}
|
| -
|
| -void PageLoadTracker::Commit(content::NavigationHandle* navigation_handle) {
|
| - did_commit_ = true;
|
| - url_ = navigation_handle->GetURL();
|
| - // Some transitions (like CLIENT_REDIRECT) are only known at commit time.
|
| - page_transition_ = navigation_handle->GetPageTransition();
|
| - user_initiated_info_.user_gesture = navigation_handle->HasUserGesture();
|
| -
|
| - INVOKE_AND_PRUNE_OBSERVERS(
|
| - observers_, ShouldObserveMimeType,
|
| - navigation_handle->GetWebContents()->GetContentsMimeType());
|
| -
|
| - INVOKE_AND_PRUNE_OBSERVERS(observers_, OnCommit, navigation_handle);
|
| - LogAbortChainHistograms(navigation_handle);
|
| -}
|
| +PageLoadMetricsUpdateDispatcher::~PageLoadMetricsUpdateDispatcher() {}
|
|
|
| -void PageLoadTracker::DidCommitSameDocumentNavigation(
|
| - content::NavigationHandle* navigation_handle) {
|
| - for (const auto& observer : observers_) {
|
| - observer->OnCommitSameDocumentNavigation(navigation_handle);
|
| +void PageLoadMetricsUpdateDispatcher::UpdateMetrics(
|
| + content::RenderFrameHost* render_frame_host,
|
| + const mojom::PageLoadTiming& new_timing,
|
| + const mojom::PageLoadMetadata& new_metadata) {
|
| + if (render_frame_host->GetParent() == nullptr) {
|
| + UpdateMainFrameMetadata(new_metadata);
|
| + UpdateMainFrameTiming(new_timing);
|
| + } else {
|
| + UpdateSubFrameMetadata(new_metadata);
|
| + UpdateSubFrameTiming(render_frame_host, new_timing);
|
| }
|
| }
|
|
|
| -void PageLoadTracker::DidFinishSubFrameNavigation(
|
| +void PageLoadMetricsUpdateDispatcher::DidFinishSubFrameNavigation(
|
| content::NavigationHandle* navigation_handle) {
|
| - for (const auto& observer : observers_) {
|
| - observer->OnDidFinishSubFrameNavigation(navigation_handle);
|
| - }
|
| -
|
| if (!navigation_handle->HasCommitted())
|
| return;
|
|
|
| @@ -585,60 +290,9 @@ void PageLoadTracker::DidFinishSubFrameNavigation(
|
| navigation_handle->GetFrameTreeNodeId(), navigation_delta));
|
| }
|
|
|
| -void PageLoadTracker::FailedProvisionalLoad(
|
| - content::NavigationHandle* navigation_handle,
|
| - base::TimeTicks failed_load_time) {
|
| - DCHECK(!failed_provisional_load_info_);
|
| - failed_provisional_load_info_.reset(new FailedProvisionalLoadInfo(
|
| - failed_load_time - navigation_handle->NavigationStart(),
|
| - navigation_handle->GetNetErrorCode()));
|
| -}
|
| -
|
| -void PageLoadTracker::Redirect(content::NavigationHandle* navigation_handle) {
|
| - url_ = navigation_handle->GetURL();
|
| - INVOKE_AND_PRUNE_OBSERVERS(observers_, OnRedirect, navigation_handle);
|
| -}
|
| -
|
| -void PageLoadTracker::OnInputEvent(const blink::WebInputEvent& event) {
|
| - input_tracker_.OnInputEvent(event);
|
| - for (const auto& observer : observers_) {
|
| - observer->OnUserInput(event);
|
| - }
|
| -}
|
| -
|
| -void PageLoadTracker::FlushMetricsOnAppEnterBackground() {
|
| - if (!app_entered_background_) {
|
| - RecordAppBackgroundPageLoadCompleted(false);
|
| - app_entered_background_ = true;
|
| - }
|
| -
|
| - const PageLoadExtraInfo info = ComputePageLoadExtraInfo();
|
| - INVOKE_AND_PRUNE_OBSERVERS(observers_, FlushMetricsOnAppEnterBackground,
|
| - *merged_page_timing_, info);
|
| -}
|
| -
|
| -void PageLoadTracker::NotifyClientRedirectTo(
|
| - const PageLoadTracker& destination) {
|
| - if (merged_page_timing_->paint_timing->first_paint) {
|
| - base::TimeTicks first_paint_time =
|
| - navigation_start() +
|
| - merged_page_timing_->paint_timing->first_paint.value();
|
| - base::TimeDelta first_paint_to_navigation;
|
| - if (destination.navigation_start() > first_paint_time)
|
| - first_paint_to_navigation =
|
| - destination.navigation_start() - first_paint_time;
|
| - PAGE_LOAD_HISTOGRAM(internal::kClientRedirectFirstPaintToNavigation,
|
| - first_paint_to_navigation);
|
| - } else {
|
| - UMA_HISTOGRAM_BOOLEAN(internal::kClientRedirectWithoutPaint, true);
|
| - }
|
| -}
|
| -
|
| -void PageLoadTracker::UpdateSubFrameTiming(
|
| +void PageLoadMetricsUpdateDispatcher::UpdateSubFrameTiming(
|
| content::RenderFrameHost* render_frame_host,
|
| - const mojom::PageLoadTiming& new_timing,
|
| - const mojom::PageLoadMetadata& new_metadata) {
|
| - UpdateSubFrameMetadata(new_metadata);
|
| + const mojom::PageLoadTiming& new_timing) {
|
| const auto it = subframe_navigation_start_offset_.find(
|
| render_frame_host->GetFrameTreeNodeId());
|
| if (it == subframe_navigation_start_offset_.end()) {
|
| @@ -653,67 +307,57 @@ void PageLoadTracker::UpdateSubFrameTiming(
|
| DispatchTimingUpdates();
|
| }
|
|
|
| -void PageLoadTracker::MergePaintTiming(
|
| +void PageLoadMetricsUpdateDispatcher::MergePaintTiming(
|
| base::TimeDelta navigation_start_offset,
|
| const mojom::PaintTiming& new_paint_timing,
|
| bool is_main_frame) {
|
| - MaybeUpdateTimeDelta(&merged_page_timing_->paint_timing->first_paint,
|
| + MaybeUpdateTimeDelta(&pending_merged_page_timing_->paint_timing->first_paint,
|
| navigation_start_offset, new_paint_timing.first_paint);
|
| - MaybeUpdateTimeDelta(&merged_page_timing_->paint_timing->first_text_paint,
|
| - navigation_start_offset,
|
| - new_paint_timing.first_text_paint);
|
| - MaybeUpdateTimeDelta(&merged_page_timing_->paint_timing->first_image_paint,
|
| - navigation_start_offset,
|
| - new_paint_timing.first_image_paint);
|
| MaybeUpdateTimeDelta(
|
| - &merged_page_timing_->paint_timing->first_contentful_paint,
|
| + &pending_merged_page_timing_->paint_timing->first_text_paint,
|
| + navigation_start_offset, new_paint_timing.first_text_paint);
|
| + MaybeUpdateTimeDelta(
|
| + &pending_merged_page_timing_->paint_timing->first_image_paint,
|
| + navigation_start_offset, new_paint_timing.first_image_paint);
|
| + MaybeUpdateTimeDelta(
|
| + &pending_merged_page_timing_->paint_timing->first_contentful_paint,
|
| navigation_start_offset, new_paint_timing.first_contentful_paint);
|
| if (is_main_frame) {
|
| // first meaningful paint is only tracked in the main frame.
|
| - merged_page_timing_->paint_timing->first_meaningful_paint =
|
| + pending_merged_page_timing_->paint_timing->first_meaningful_paint =
|
| new_paint_timing.first_meaningful_paint;
|
| }
|
| }
|
|
|
| -void PageLoadTracker::UpdateSubFrameMetadata(
|
| +void PageLoadMetricsUpdateDispatcher::UpdateSubFrameMetadata(
|
| const mojom::PageLoadMetadata& subframe_metadata) {
|
| // Merge the subframe loading behavior flags with any we've already observed,
|
| // possibly from other subframes.
|
| const int last_subframe_loading_behavior_flags =
|
| - subframe_metadata_.behavior_flags;
|
| - subframe_metadata_.behavior_flags |= subframe_metadata.behavior_flags;
|
| - if (last_subframe_loading_behavior_flags == subframe_metadata_.behavior_flags)
|
| + subframe_metadata_->behavior_flags;
|
| + subframe_metadata_->behavior_flags |= subframe_metadata.behavior_flags;
|
| + if (last_subframe_loading_behavior_flags ==
|
| + subframe_metadata_->behavior_flags)
|
| return;
|
|
|
| - PageLoadExtraInfo extra_info(ComputePageLoadExtraInfo());
|
| - for (const auto& observer : observers_) {
|
| - observer->OnLoadingBehaviorObserved(extra_info);
|
| - }
|
| + client_->OnSubframeMetadataChanged();
|
| }
|
|
|
| -void PageLoadTracker::UpdateTiming(
|
| - const mojom::PageLoadTiming& new_timing,
|
| - const mojom::PageLoadMetadata& new_metadata) {
|
| +void PageLoadMetricsUpdateDispatcher::UpdateMainFrameTiming(
|
| + const mojom::PageLoadTiming& new_timing) {
|
| // Throw away IPCs that are not relevant to the current navigation.
|
| // Two timing structures cannot refer to the same navigation if they indicate
|
| // that a navigation started at different times, so a new timing struct with a
|
| // different start time from an earlier struct is considered invalid.
|
| const bool valid_timing_descendent =
|
| - merged_page_timing_->navigation_start.is_null() ||
|
| - merged_page_timing_->navigation_start == new_timing.navigation_start;
|
| + pending_merged_page_timing_->navigation_start.is_null() ||
|
| + pending_merged_page_timing_->navigation_start ==
|
| + new_timing.navigation_start;
|
| if (!valid_timing_descendent) {
|
| RecordInternalError(ERR_BAD_TIMING_IPC_INVALID_TIMING_DESCENDENT);
|
| return;
|
| }
|
|
|
| - // Ensure flags sent previously are still present in the new metadata fields.
|
| - const bool valid_behavior_descendent =
|
| - (main_frame_metadata_.behavior_flags & new_metadata.behavior_flags) ==
|
| - main_frame_metadata_.behavior_flags;
|
| - if (!valid_behavior_descendent) {
|
| - RecordInternalError(ERR_BAD_TIMING_IPC_INVALID_BEHAVIOR_DESCENDENT);
|
| - return;
|
| - }
|
| internal::PageLoadTimingStatus status = IsValidPageLoadTiming(new_timing);
|
| UMA_HISTOGRAM_ENUMERATION(internal::kPageLoadTimingStatus, status,
|
| internal::LAST_PAGE_LOAD_TIMING_STATUS);
|
| @@ -722,38 +366,40 @@ void PageLoadTracker::UpdateTiming(
|
| return;
|
| }
|
|
|
| - DCHECK(did_commit_); // OnCommit() must be called first.
|
| - // There are some subtle ordering constraints here. GetPageLoadMetricsInfo()
|
| - // must be called before DispatchObserverTimingCallbacks, but its
|
| - // implementation depends on the state of main_frame_metadata_, so we need
|
| - // to update main_frame_metadata_ before calling GetPageLoadMetricsInfo.
|
| - // Thus, we make a copy of timing here, update merged_page_timing_ and
|
| - // main_frame_metadata_, and then proceed to dispatch the observer timing
|
| - // callbacks.
|
| - const mojom::PaintTimingPtr last_paint_timing =
|
| - std::move(merged_page_timing_->paint_timing);
|
| -
|
| - // Update the merged_page_timing_, making sure to merge the previously
|
| + mojom::PaintTimingPtr last_paint_timing =
|
| + std::move(pending_merged_page_timing_->paint_timing);
|
| + // Update the pending_merged_page_timing_, making sure to merge the previously
|
| // observed |paint_timing|, which is tracked across all frames in the page.
|
| - merged_page_timing_ = new_timing.Clone();
|
| - merged_page_timing_->paint_timing = last_paint_timing.Clone();
|
| + pending_merged_page_timing_ = new_timing.Clone();
|
| + pending_merged_page_timing_->paint_timing = std::move(last_paint_timing);
|
| MergePaintTiming(base::TimeDelta(), *new_timing.paint_timing,
|
| true /* is_main_frame */);
|
|
|
| - main_frame_metadata_ = new_metadata;
|
| -
|
| DispatchTimingUpdates();
|
| }
|
|
|
| -void PageLoadTracker::DispatchTimingUpdates() {
|
| - if (last_dispatched_merged_page_timing_->Equals(*merged_page_timing_) &&
|
| - last_dispatched_main_frame_metadata_->Equals(main_frame_metadata_)) {
|
| +void PageLoadMetricsUpdateDispatcher::UpdateMainFrameMetadata(
|
| + const mojom::PageLoadMetadata& new_metadata) {
|
| + if (main_frame_metadata_->Equals(new_metadata))
|
| + return;
|
| +
|
| + // Ensure flags sent previously are still present in the new metadata fields.
|
| + const bool valid_behavior_descendent =
|
| + (main_frame_metadata_->behavior_flags & new_metadata.behavior_flags) ==
|
| + main_frame_metadata_->behavior_flags;
|
| + if (!valid_behavior_descendent) {
|
| + RecordInternalError(ERR_BAD_TIMING_IPC_INVALID_BEHAVIOR_DESCENDENT);
|
| return;
|
| }
|
|
|
| - if (merged_page_timing_->paint_timing->first_paint) {
|
| - if (!merged_page_timing_->parse_timing->parse_start ||
|
| - !merged_page_timing_->document_timing->first_layout) {
|
| + main_frame_metadata_ = new_metadata.Clone();
|
| + client_->OnMainFrameMetadataChanged();
|
| +}
|
| +
|
| +void PageLoadMetricsUpdateDispatcher::DispatchTimingUpdates() {
|
| + if (pending_merged_page_timing_->paint_timing->first_paint) {
|
| + if (!pending_merged_page_timing_->parse_timing->parse_start ||
|
| + !pending_merged_page_timing_->document_timing->first_layout) {
|
| // When merging paint events across frames, we can sometimes encounter
|
| // cases where we've received a first paint event for a child frame before
|
| // receiving required earlier events in the main frame, due to buffering
|
| @@ -768,210 +414,16 @@ void PageLoadTracker::DispatchTimingUpdates() {
|
| }
|
| }
|
|
|
| + if (current_merged_page_timing_->Equals(*pending_merged_page_timing_))
|
| + return;
|
| + current_merged_page_timing_ = pending_merged_page_timing_->Clone();
|
| +
|
| internal::PageLoadTimingStatus status =
|
| - IsValidPageLoadTiming(*merged_page_timing_);
|
| + IsValidPageLoadTiming(*pending_merged_page_timing_);
|
| UMA_HISTOGRAM_ENUMERATION(internal::kPageLoadTimingDispatchStatus, status,
|
| internal::LAST_PAGE_LOAD_TIMING_STATUS);
|
|
|
| - const PageLoadExtraInfo info = ComputePageLoadExtraInfo();
|
| - for (const auto& observer : observers_) {
|
| - DispatchObserverTimingCallbacks(
|
| - observer.get(), *last_dispatched_merged_page_timing_,
|
| - *merged_page_timing_, *last_dispatched_main_frame_metadata_, info);
|
| - }
|
| - last_dispatched_merged_page_timing_ = merged_page_timing_->Clone();
|
| - last_dispatched_main_frame_metadata_ = main_frame_metadata_.Clone();
|
| -}
|
| -
|
| -void PageLoadTracker::OnStartedResource(
|
| - const ExtraRequestStartInfo& extra_request_start_info) {
|
| - for (const auto& observer : observers_) {
|
| - observer->OnStartedResource(extra_request_start_info);
|
| - }
|
| -}
|
| -
|
| -void PageLoadTracker::OnLoadedResource(
|
| - const ExtraRequestCompleteInfo& extra_request_complete_info) {
|
| - for (const auto& observer : observers_) {
|
| - observer->OnLoadedResource(extra_request_complete_info);
|
| - }
|
| -}
|
| -
|
| -void PageLoadTracker::StopTracking() {
|
| - did_stop_tracking_ = true;
|
| - observers_.clear();
|
| -}
|
| -
|
| -void PageLoadTracker::AddObserver(
|
| - std::unique_ptr<PageLoadMetricsObserver> observer) {
|
| - observers_.push_back(std::move(observer));
|
| -}
|
| -
|
| -void PageLoadTracker::ClampBrowserTimestampIfInterProcessTimeTickSkew(
|
| - base::TimeTicks* event_time) {
|
| - DCHECK(event_time != nullptr);
|
| - // Windows 10 GCE bot non-deterministically failed because TimeTicks::Now()
|
| - // called in the browser process e.g. commit_time was less than
|
| - // navigation_start_ that was populated in the renderer process because the
|
| - // clock was not system-wide monotonic.
|
| - // Note that navigation_start_ can also be set in the browser process in
|
| - // some cases and in those cases event_time should never be <
|
| - // navigation_start_. If it is due to a code error and it gets clamped in this
|
| - // function, on high resolution systems it should lead to a dcheck failure.
|
| -
|
| - // TODO(shivanisha): Currently IsHighResolution is the best way to check
|
| - // if the clock is system-wide monotonic. However IsHighResolution
|
| - // does a broader check to see if the clock in use is high resolution
|
| - // which also implies it is system-wide monotonic (on Windows).
|
| - if (base::TimeTicks::IsHighResolution()) {
|
| - DCHECK(event_time->is_null() || *event_time >= navigation_start_);
|
| - return;
|
| - }
|
| -
|
| - if (!event_time->is_null() && *event_time < navigation_start_) {
|
| - RecordInternalError(ERR_INTER_PROCESS_TIME_TICK_SKEW);
|
| - *event_time = navigation_start_;
|
| - }
|
| -}
|
| -
|
| -PageLoadExtraInfo PageLoadTracker::ComputePageLoadExtraInfo() {
|
| - base::Optional<base::TimeDelta> first_background_time;
|
| - base::Optional<base::TimeDelta> first_foreground_time;
|
| - base::Optional<base::TimeDelta> page_end_time;
|
| -
|
| - if (!background_time_.is_null()) {
|
| - DCHECK_GE(background_time_, navigation_start_);
|
| - first_background_time = background_time_ - navigation_start_;
|
| - }
|
| -
|
| - if (!foreground_time_.is_null()) {
|
| - DCHECK_GE(foreground_time_, navigation_start_);
|
| - first_foreground_time = foreground_time_ - navigation_start_;
|
| - }
|
| -
|
| - if (page_end_reason_ != END_NONE) {
|
| - DCHECK_GE(page_end_time_, navigation_start_);
|
| - page_end_time = page_end_time_ - navigation_start_;
|
| - } else {
|
| - DCHECK(page_end_time_.is_null());
|
| - }
|
| -
|
| - // page_end_reason_ == END_NONE implies page_end_user_initiated_info_ is not
|
| - // user initiated.
|
| - DCHECK(page_end_reason_ != END_NONE ||
|
| - (!page_end_user_initiated_info_.browser_initiated &&
|
| - !page_end_user_initiated_info_.user_gesture &&
|
| - !page_end_user_initiated_info_.user_input_event));
|
| - return PageLoadExtraInfo(
|
| - navigation_start_, first_background_time, first_foreground_time,
|
| - started_in_foreground_, user_initiated_info_, url(), start_url_,
|
| - did_commit_, page_end_reason_, page_end_user_initiated_info_,
|
| - page_end_time, main_frame_metadata_, subframe_metadata_);
|
| -}
|
| -
|
| -bool PageLoadTracker::HasMatchingNavigationRequestID(
|
| - const content::GlobalRequestID& request_id) const {
|
| - DCHECK(request_id != content::GlobalRequestID());
|
| - return navigation_request_id_.has_value() &&
|
| - navigation_request_id_.value() == request_id;
|
| -}
|
| -
|
| -void PageLoadTracker::NotifyPageEnd(PageEndReason page_end_reason,
|
| - UserInitiatedInfo user_initiated_info,
|
| - base::TimeTicks timestamp,
|
| - bool is_certainly_browser_timestamp) {
|
| - DCHECK_NE(page_end_reason, END_NONE);
|
| - // Use UpdatePageEnd to update an already notified PageLoadTracker.
|
| - if (page_end_reason_ != END_NONE)
|
| - return;
|
| -
|
| - UpdatePageEndInternal(page_end_reason, user_initiated_info, timestamp,
|
| - is_certainly_browser_timestamp);
|
| -}
|
| -
|
| -void PageLoadTracker::UpdatePageEnd(PageEndReason page_end_reason,
|
| - UserInitiatedInfo user_initiated_info,
|
| - base::TimeTicks timestamp,
|
| - bool is_certainly_browser_timestamp) {
|
| - DCHECK_NE(page_end_reason, END_NONE);
|
| - DCHECK_NE(page_end_reason, END_OTHER);
|
| - DCHECK_EQ(page_end_reason_, END_OTHER);
|
| - DCHECK(!page_end_time_.is_null());
|
| - if (page_end_time_.is_null() || page_end_reason_ != END_OTHER)
|
| - return;
|
| -
|
| - // For some aborts (e.g. navigations), the initiated timestamp can be earlier
|
| - // than the timestamp that aborted the load. Taking the minimum gives the
|
| - // closest user initiated time known.
|
| - UpdatePageEndInternal(page_end_reason, user_initiated_info,
|
| - std::min(page_end_time_, timestamp),
|
| - is_certainly_browser_timestamp);
|
| -}
|
| -
|
| -bool PageLoadTracker::IsLikelyProvisionalAbort(
|
| - base::TimeTicks abort_cause_time) const {
|
| - // Note that |abort_cause_time - page_end_time_| can be negative.
|
| - return page_end_reason_ == END_OTHER &&
|
| - (abort_cause_time - page_end_time_).InMilliseconds() < 100;
|
| -}
|
| -
|
| -bool PageLoadTracker::MatchesOriginalNavigation(
|
| - content::NavigationHandle* navigation_handle) {
|
| - // Neither navigation should have committed.
|
| - DCHECK(!navigation_handle->HasCommitted());
|
| - DCHECK(!did_commit_);
|
| - return navigation_handle->GetURL() == start_url_;
|
| -}
|
| -
|
| -void PageLoadTracker::UpdatePageEndInternal(
|
| - PageEndReason page_end_reason,
|
| - UserInitiatedInfo user_initiated_info,
|
| - base::TimeTicks timestamp,
|
| - bool is_certainly_browser_timestamp) {
|
| - // When a provisional navigation commits, that navigation's start time is
|
| - // interpreted as the abort time for other provisional loads in the tab.
|
| - // However, this only makes sense if the committed load started after the
|
| - // aborted provisional loads started. Thus we ignore cases where the committed
|
| - // load started before the aborted provisional load, as this would result in
|
| - // recording a negative time-to-abort. The real issue here is that we have to
|
| - // infer the cause of aborts. It would be better if the navigation code could
|
| - // instead report the actual cause of an aborted navigation. See crbug/571647
|
| - // for details.
|
| - if (timestamp < navigation_start_) {
|
| - RecordInternalError(ERR_END_BEFORE_NAVIGATION_START);
|
| - page_end_reason_ = END_NONE;
|
| - page_end_time_ = base::TimeTicks();
|
| - return;
|
| - }
|
| - page_end_reason_ = page_end_reason;
|
| - page_end_time_ = timestamp;
|
| - // A client redirect can never be user initiated. Due to the way Blink
|
| - // implements user gesture tracking, where all events that occur within 1
|
| - // second after a user interaction are considered to be triggered by user
|
| - // activation (based on HTML spec:
|
| - // https://html.spec.whatwg.org/multipage/interaction.html#triggered-by-user-activation),
|
| - // these navs may sometimes be reported as user initiated by Blink. Thus, we
|
| - // explicitly filter these types of aborts out when deciding if the abort was
|
| - // user initiated.
|
| - if (page_end_reason != END_CLIENT_REDIRECT)
|
| - page_end_user_initiated_info_ = user_initiated_info;
|
| -
|
| - if (is_certainly_browser_timestamp) {
|
| - ClampBrowserTimestampIfInterProcessTimeTickSkew(&page_end_time_);
|
| - }
|
| -}
|
| -
|
| -void PageLoadTracker::MediaStartedPlaying(
|
| - const content::WebContentsObserver::MediaPlayerInfo& video_type,
|
| - bool is_in_main_frame) {
|
| - for (const auto& observer : observers_)
|
| - observer->MediaStartedPlaying(video_type, is_in_main_frame);
|
| -}
|
| -
|
| -void PageLoadTracker::OnNavigationDelayComplete(base::TimeDelta scheduled_delay,
|
| - base::TimeDelta actual_delay) {
|
| - for (const auto& observer : observers_)
|
| - observer->OnNavigationDelayComplete(scheduled_delay, actual_delay);
|
| + client_->OnTimingChanged();
|
| }
|
|
|
| } // namespace page_load_metrics
|
|
|