Index: components/page_load_metrics/browser/metrics_web_contents_observer.cc |
diff --git a/components/page_load_metrics/browser/metrics_web_contents_observer.cc b/components/page_load_metrics/browser/metrics_web_contents_observer.cc |
deleted file mode 100644 |
index 0c586b7c65e6a9842bfa0a0f6fced893633f73f1..0000000000000000000000000000000000000000 |
--- a/components/page_load_metrics/browser/metrics_web_contents_observer.cc |
+++ /dev/null |
@@ -1,960 +0,0 @@ |
-// Copyright 2015 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/metrics_web_contents_observer.h" |
- |
-#include <algorithm> |
-#include <ostream> |
-#include <string> |
-#include <utility> |
- |
-#include "base/location.h" |
-#include "base/logging.h" |
-#include "base/memory/ptr_util.h" |
-#include "base/metrics/histogram.h" |
-#include "base/metrics/user_metrics.h" |
-#include "components/page_load_metrics/browser/page_load_metrics_util.h" |
-#include "components/page_load_metrics/common/page_load_metrics_messages.h" |
-#include "components/page_load_metrics/common/page_load_timing.h" |
-#include "content/public/browser/browser_thread.h" |
-#include "content/public/browser/navigation_details.h" |
-#include "content/public/browser/navigation_handle.h" |
-#include "content/public/browser/render_frame_host.h" |
-#include "content/public/browser/render_view_host.h" |
-#include "content/public/browser/web_contents.h" |
-#include "content/public/browser/web_contents_observer.h" |
-#include "content/public/browser/web_contents_user_data.h" |
-#include "ipc/ipc_message.h" |
-#include "ipc/ipc_message_macros.h" |
-#include "ui/base/page_transition_types.h" |
- |
-DEFINE_WEB_CONTENTS_USER_DATA_KEY( |
- page_load_metrics::MetricsWebContentsObserver); |
- |
-namespace page_load_metrics { |
- |
-namespace internal { |
- |
-const char kErrorEvents[] = "PageLoad.Events.InternalError"; |
-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 kCommitToCompleteNoTimingIPCs[] = |
- "PageLoad.Internal.CommitToComplete.NoTimingIPCs"; |
- |
-} // namespace internal |
- |
-namespace { |
- |
-// Helper to allow use of Optional<> values in LOG() messages. |
-std::ostream& operator<<(std::ostream& os, |
- const base::Optional<base::TimeDelta>& opt) { |
- if (opt) |
- os << opt.value(); |
- else |
- os << "(unset)"; |
- return os; |
-} |
- |
-// If second is non-zero, first must also be non-zero and less than or equal to |
-// second. |
-bool EventsInOrder(const base::Optional<base::TimeDelta>& first, |
- const base::Optional<base::TimeDelta>& second) { |
- if (!second) { |
- return true; |
- } |
- return first && first <= second; |
-} |
- |
-bool IsValidPageLoadTiming(const PageLoadTiming& timing) { |
- if (timing.IsEmpty()) |
- return false; |
- |
- // If we have a non-empty timing, it should always have a navigation start. |
- if (timing.navigation_start.is_null()) { |
- NOTREACHED() << "Received null navigation_start."; |
- return false; |
- } |
- |
- // Verify proper ordering between the various timings. |
- |
- if (!EventsInOrder(timing.response_start, timing.dom_loading)) { |
- // We sometimes get a zero response_start with a non-zero DOM loading. See |
- // crbug.com/590212. |
- DLOG(ERROR) << "Invalid response_start " << timing.response_start |
- << " for dom_loading " << timing.dom_loading; |
- return false; |
- } |
- |
- if (!EventsInOrder(timing.response_start, timing.parse_start)) { |
- // We sometimes get a zero response_start with a non-zero parse start. See |
- // crbug.com/590212. |
- DLOG(ERROR) << "Invalid response_start " << timing.response_start |
- << " for parse_start " << timing.parse_start; |
- return false; |
- } |
- |
- if (!EventsInOrder(timing.parse_start, timing.parse_stop)) { |
- NOTREACHED() << "Invalid parse_start " << timing.parse_start |
- << " for parse_stop " << timing.parse_stop; |
- return false; |
- } |
- |
- if (timing.parse_stop) { |
- const base::TimeDelta parse_duration = |
- timing.parse_stop.value() - timing.parse_start.value(); |
- if (timing.parse_blocked_on_script_load_duration > parse_duration) { |
- NOTREACHED() << "Invalid parse_blocked_on_script_load_duration " |
- << timing.parse_blocked_on_script_load_duration |
- << " for parse duration " << parse_duration; |
- return false; |
- } |
- } |
- |
- if (timing.parse_blocked_on_script_load_from_document_write_duration > |
- timing.parse_blocked_on_script_load_duration) { |
- NOTREACHED() |
- << "Invalid parse_blocked_on_script_load_from_document_write_duration " |
- << timing.parse_blocked_on_script_load_from_document_write_duration |
- << " for parse_blocked_on_script_load_duration " |
- << timing.parse_blocked_on_script_load_duration; |
- return false; |
- } |
- |
- if (!EventsInOrder(timing.dom_loading, |
- timing.dom_content_loaded_event_start)) { |
- NOTREACHED() << "Invalid dom_loading " << timing.dom_loading |
- << " for dom_content_loaded_event_start " |
- << timing.dom_content_loaded_event_start; |
- return false; |
- } |
- |
- if (!EventsInOrder(timing.dom_content_loaded_event_start, |
- timing.load_event_start)) { |
- NOTREACHED() << "Invalid dom_content_loaded_event_start " |
- << timing.dom_content_loaded_event_start |
- << " for load_event_start " << timing.load_event_start; |
- return false; |
- } |
- |
- if (!EventsInOrder(timing.dom_loading, timing.first_layout)) { |
- NOTREACHED() << "Invalid dom_loading " << timing.dom_loading |
- << " for first_layout " << timing.first_layout; |
- return false; |
- } |
- |
- if (!EventsInOrder(timing.first_layout, timing.first_paint)) { |
- NOTREACHED() << "Invalid first_layout " << timing.first_layout |
- << " for first_paint " << timing.first_paint; |
- return false; |
- } |
- |
- if (!EventsInOrder(timing.first_paint, timing.first_text_paint)) { |
- NOTREACHED() << "Invalid first_paint " << timing.first_paint |
- << " for first_text_paint " << timing.first_text_paint; |
- return false; |
- } |
- |
- if (!EventsInOrder(timing.first_paint, timing.first_image_paint)) { |
- NOTREACHED() << "Invalid first_paint " << timing.first_paint |
- << " for first_image_paint " << timing.first_image_paint; |
- return false; |
- } |
- |
- if (!EventsInOrder(timing.first_paint, timing.first_contentful_paint)) { |
- NOTREACHED() << "Invalid first_paint " << timing.first_paint |
- << " for first_contentful_paint " |
- << timing.first_contentful_paint; |
- return false; |
- } |
- |
- return true; |
-} |
- |
-void RecordInternalError(InternalErrorLoadEvent event) { |
- UMA_HISTOGRAM_ENUMERATION(internal::kErrorEvents, event, ERR_LAST_ENTRY); |
-} |
- |
-UserAbortType AbortTypeForPageTransition(ui::PageTransition transition) { |
- if (ui::PageTransitionCoreTypeIs(transition, ui::PAGE_TRANSITION_RELOAD)) |
- return ABORT_RELOAD; |
- if (transition & ui::PAGE_TRANSITION_FORWARD_BACK) |
- return ABORT_FORWARD_BACK; |
- if (ui::PageTransitionIsNewNavigation(transition)) |
- return ABORT_NEW_NAVIGATION; |
- NOTREACHED() |
- << "AbortTypeForPageTransition received unexpected ui::PageTransition: " |
- << transition; |
- return ABORT_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); |
- } |
-} |
- |
-void DispatchObserverTimingCallbacks(PageLoadMetricsObserver* observer, |
- const PageLoadTiming& last_timing, |
- const PageLoadTiming& new_timing, |
- const PageLoadMetadata& last_metadata, |
- const PageLoadExtraInfo& extra_info) { |
- if (last_timing != new_timing) |
- observer->OnTimingUpdate(new_timing, extra_info); |
- if (new_timing.dom_content_loaded_event_start && |
- !last_timing.dom_content_loaded_event_start) |
- observer->OnDomContentLoadedEventStart(new_timing, extra_info); |
- if (new_timing.load_event_start && !last_timing.load_event_start) |
- observer->OnLoadEventStart(new_timing, extra_info); |
- if (new_timing.first_layout && !last_timing.first_layout) |
- observer->OnFirstLayout(new_timing, extra_info); |
- if (new_timing.first_paint && !last_timing.first_paint) |
- observer->OnFirstPaint(new_timing, extra_info); |
- if (new_timing.first_text_paint && !last_timing.first_text_paint) |
- observer->OnFirstTextPaint(new_timing, extra_info); |
- if (new_timing.first_image_paint && !last_timing.first_image_paint) |
- observer->OnFirstImagePaint(new_timing, extra_info); |
- if (new_timing.first_contentful_paint && !last_timing.first_contentful_paint) |
- observer->OnFirstContentfulPaint(new_timing, extra_info); |
- if (new_timing.parse_start && !last_timing.parse_start) |
- observer->OnParseStart(new_timing, extra_info); |
- if (new_timing.parse_stop && !last_timing.parse_stop) |
- observer->OnParseStop(new_timing, extra_info); |
- if (extra_info.metadata.behavior_flags != last_metadata.behavior_flags) |
- observer->OnLoadingBehaviorObserved(extra_info); |
-} |
- |
-} // namespace |
- |
-PageLoadTracker::PageLoadTracker( |
- bool in_foreground, |
- PageLoadMetricsEmbedderInterface* embedder_interface, |
- const GURL& currently_committed_url, |
- content::NavigationHandle* navigation_handle, |
- int aborted_chain_size, |
- int aborted_chain_size_same_url) |
- : did_stop_tracking_(false), |
- navigation_start_(navigation_handle->NavigationStart()), |
- url_(navigation_handle->GetURL()), |
- abort_type_(ABORT_NONE), |
- started_in_foreground_(in_foreground), |
- 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); |
- for (const auto& observer : observers_) { |
- observer->OnStart(navigation_handle, currently_committed_url, |
- started_in_foreground_); |
- } |
-} |
- |
-PageLoadTracker::~PageLoadTracker() { |
- if (did_stop_tracking_) |
- return; |
- |
- const PageLoadExtraInfo info = ComputePageLoadExtraInfo(); |
- DCHECK_NE(static_cast<bool>(info.time_to_commit), |
- static_cast<bool>(failed_provisional_load_info_)); |
- if (info.time_to_commit && timing_.IsEmpty()) { |
- RecordInternalError(ERR_NO_IPCS_RECEIVED); |
- const base::TimeTicks commit_time = |
- navigation_start_ + info.time_to_commit.value(); |
- PAGE_LOAD_HISTOGRAM(internal::kCommitToCompleteNoTimingIPCs, |
- base::TimeTicks::Now() - commit_time); |
- } |
- // Recall that trackers that are given ABORT_UNKNOWN_NAVIGATION have their |
- // chain length added to the next navigation. Take care not to double count |
- // them. Also do not double count committed loads, which call this already. |
- if (commit_time_.is_null() && abort_type_ != ABORT_UNKNOWN_NAVIGATION) |
- LogAbortChainHistograms(nullptr); |
- |
- for (const auto& observer : observers_) { |
- if (failed_provisional_load_info_) { |
- observer->OnFailedProvisionalLoad(*failed_provisional_load_info_, info); |
- } else { |
- observer->OnComplete(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(!commit_time_.is_null()); |
- |
- // 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_); |
- |
- ui::PageTransition committed_transition = |
- final_navigation->GetPageTransition(); |
- switch (AbortTypeForPageTransition(committed_transition)) { |
- case ABORT_RELOAD: |
- UMA_HISTOGRAM_COUNTS(internal::kAbortChainSizeReload, |
- aborted_chain_size_); |
- return; |
- case ABORT_FORWARD_BACK: |
- UMA_HISTOGRAM_COUNTS(internal::kAbortChainSizeForwardBack, |
- aborted_chain_size_); |
- return; |
- case ABORT_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_); |
- } |
- |
- for (const auto& observer : observers_) |
- observer->OnHidden(); |
-} |
- |
-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_); |
- } |
- |
- for (const auto& observer : observers_) |
- observer->OnShown(); |
-} |
- |
-void PageLoadTracker::Commit(content::NavigationHandle* navigation_handle) { |
- // TODO(bmcquade): To improve accuracy, consider adding commit time to |
- // NavigationHandle. Taking a timestamp here should be close enough for now. |
- commit_time_ = base::TimeTicks::Now(); |
- ClampBrowserTimestampIfInterProcessTimeTickSkew(&commit_time_); |
- url_ = navigation_handle->GetURL(); |
- for (const auto& observer : observers_) { |
- observer->OnCommit(navigation_handle); |
- } |
- LogAbortChainHistograms(navigation_handle); |
-} |
- |
-void PageLoadTracker::FailedProvisionalLoad( |
- content::NavigationHandle* navigation_handle) { |
- DCHECK(!failed_provisional_load_info_); |
- failed_provisional_load_info_.reset(new FailedProvisionalLoadInfo( |
- base::TimeTicks::Now() - navigation_handle->NavigationStart(), |
- navigation_handle->GetNetErrorCode())); |
-} |
- |
-void PageLoadTracker::Redirect(content::NavigationHandle* navigation_handle) { |
- for (const auto& observer : observers_) { |
- observer->OnRedirect(navigation_handle); |
- } |
-} |
- |
-void PageLoadTracker::OnInputEvent(const blink::WebInputEvent& event) { |
- for (const auto& observer : observers_) { |
- observer->OnUserInput(event); |
- } |
-} |
- |
-void PageLoadTracker::NotifyClientRedirectTo( |
- const PageLoadTracker& destination) { |
- if (timing_.first_paint) { |
- base::TimeTicks first_paint_time = |
- navigation_start() + 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); |
- } |
-} |
- |
-bool PageLoadTracker::UpdateTiming(const PageLoadTiming& new_timing, |
- const PageLoadMetadata& new_metadata) { |
- // 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. |
- bool valid_timing_descendent = |
- timing_.navigation_start.is_null() || |
- timing_.navigation_start == new_timing.navigation_start; |
- // Ensure flags sent previously are still present in the new metadata fields. |
- bool valid_behavior_descendent = |
- (metadata_.behavior_flags & new_metadata.behavior_flags) == |
- metadata_.behavior_flags; |
- if (IsValidPageLoadTiming(new_timing) && valid_timing_descendent && |
- valid_behavior_descendent) { |
- // There are some subtle ordering constraints here. GetPageLoadMetricsInfo() |
- // must be called before DispatchObserverTimingCallbacks, but its |
- // implementation depends on the state of metadata_, so we need to update |
- // metadata_ before calling GetPageLoadMetricsInfo. Thus, we make a copy of |
- // timing here, update timing_ and metadata_, and then proceed to dispatch |
- // the observer timing callbacks. |
- const PageLoadTiming last_timing = timing_; |
- timing_ = new_timing; |
- |
- const PageLoadMetadata last_metadata = metadata_; |
- metadata_ = new_metadata; |
- const PageLoadExtraInfo info = ComputePageLoadExtraInfo(); |
- for (const auto& observer : observers_) { |
- DispatchObserverTimingCallbacks(observer.get(), last_timing, new_timing, |
- last_metadata, info); |
- } |
- return true; |
- } |
- return false; |
-} |
- |
-void PageLoadTracker::StopTracking() { |
- did_stop_tracking_ = true; |
-} |
- |
-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> time_to_abort; |
- base::Optional<base::TimeDelta> time_to_commit; |
- |
- 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 (abort_type_ != ABORT_NONE) { |
- DCHECK_GE(abort_time_, navigation_start_); |
- time_to_abort = abort_time_ - navigation_start_; |
- } else { |
- DCHECK(abort_time_.is_null()); |
- } |
- |
- if (!commit_time_.is_null()) { |
- DCHECK_GE(commit_time_, navigation_start_); |
- time_to_commit = commit_time_ - navigation_start_; |
- } |
- |
- return PageLoadExtraInfo( |
- first_background_time, first_foreground_time, started_in_foreground_, |
- commit_time_.is_null() ? GURL() : url_, time_to_commit, abort_type_, |
- time_to_abort, metadata_); |
-} |
- |
-void PageLoadTracker::NotifyAbort(UserAbortType abort_type, |
- base::TimeTicks timestamp, |
- bool is_certainly_browser_timestamp) { |
- DCHECK_NE(abort_type, ABORT_NONE); |
- // Use UpdateAbort to update an already notified PageLoadTracker. |
- if (abort_type_ != ABORT_NONE) |
- return; |
- |
- UpdateAbortInternal(abort_type, timestamp, is_certainly_browser_timestamp); |
-} |
- |
-void PageLoadTracker::UpdateAbort(UserAbortType abort_type, |
- base::TimeTicks timestamp, |
- bool is_certainly_browser_timestamp) { |
- DCHECK_NE(abort_type, ABORT_NONE); |
- DCHECK_NE(abort_type, ABORT_OTHER); |
- DCHECK_EQ(abort_type_, ABORT_OTHER); |
- |
- // 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. |
- UpdateAbortInternal(abort_type, std::min(abort_time_, timestamp), |
- is_certainly_browser_timestamp); |
-} |
- |
-bool PageLoadTracker::IsLikelyProvisionalAbort( |
- base::TimeTicks abort_cause_time) { |
- // Note that |abort_cause_time - abort_time| can be negative. |
- return abort_type_ == ABORT_OTHER && |
- (abort_cause_time - abort_time_).InMilliseconds() < 100; |
-} |
- |
-bool PageLoadTracker::MatchesOriginalNavigation( |
- content::NavigationHandle* navigation_handle) { |
- // Neither navigation should have committed. |
- DCHECK(!navigation_handle->HasCommitted()); |
- DCHECK(commit_time_.is_null()); |
- return navigation_handle->GetURL() == url_; |
-} |
- |
-void PageLoadTracker::UpdateAbortInternal(UserAbortType abort_type, |
- 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_ABORT_BEFORE_NAVIGATION_START); |
- abort_type_ = ABORT_NONE; |
- abort_time_ = base::TimeTicks(); |
- return; |
- } |
- abort_type_ = abort_type; |
- abort_time_ = timestamp; |
- |
- if (is_certainly_browser_timestamp) { |
- ClampBrowserTimestampIfInterProcessTimeTickSkew(&abort_time_); |
- } |
-} |
- |
-// static |
-MetricsWebContentsObserver::MetricsWebContentsObserver( |
- content::WebContents* web_contents, |
- std::unique_ptr<PageLoadMetricsEmbedderInterface> embedder_interface) |
- : content::WebContentsObserver(web_contents), |
- in_foreground_(false), |
- embedder_interface_(std::move(embedder_interface)), |
- has_navigated_(false) { |
- RegisterInputEventObserver(web_contents->GetRenderViewHost()); |
-} |
- |
-MetricsWebContentsObserver* MetricsWebContentsObserver::CreateForWebContents( |
- content::WebContents* web_contents, |
- std::unique_ptr<PageLoadMetricsEmbedderInterface> embedder_interface) { |
- DCHECK(web_contents); |
- |
- MetricsWebContentsObserver* metrics = FromWebContents(web_contents); |
- if (!metrics) { |
- metrics = new MetricsWebContentsObserver(web_contents, |
- std::move(embedder_interface)); |
- web_contents->SetUserData(UserDataKey(), metrics); |
- } |
- return metrics; |
-} |
- |
-MetricsWebContentsObserver::~MetricsWebContentsObserver() { |
- NotifyAbortAllLoads(ABORT_CLOSE); |
-} |
- |
-void MetricsWebContentsObserver::RegisterInputEventObserver( |
- content::RenderViewHost* host) { |
- if (host != nullptr) |
- host->GetWidget()->AddInputEventObserver(this); |
-} |
- |
-void MetricsWebContentsObserver::UnregisterInputEventObserver( |
- content::RenderViewHost* host) { |
- if (host != nullptr) |
- host->GetWidget()->RemoveInputEventObserver(this); |
-} |
- |
-void MetricsWebContentsObserver::RenderViewHostChanged( |
- content::RenderViewHost* old_host, |
- content::RenderViewHost* new_host) { |
- UnregisterInputEventObserver(old_host); |
- RegisterInputEventObserver(new_host); |
-} |
- |
-bool MetricsWebContentsObserver::OnMessageReceived( |
- const IPC::Message& message, |
- content::RenderFrameHost* render_frame_host) { |
- DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
- bool handled = true; |
- IPC_BEGIN_MESSAGE_MAP_WITH_PARAM(MetricsWebContentsObserver, message, |
- render_frame_host) |
- IPC_MESSAGE_HANDLER(PageLoadMetricsMsg_TimingUpdated, OnTimingUpdated) |
- IPC_MESSAGE_UNHANDLED(handled = false) |
- IPC_END_MESSAGE_MAP() |
- return handled; |
-} |
- |
-void MetricsWebContentsObserver::DidStartNavigation( |
- content::NavigationHandle* navigation_handle) { |
- if (!navigation_handle->IsInMainFrame()) |
- return; |
- |
- std::unique_ptr<PageLoadTracker> last_aborted = |
- NotifyAbortedProvisionalLoadsNewNavigation(navigation_handle); |
- |
- int chain_size_same_url = 0; |
- int chain_size = 0; |
- if (last_aborted) { |
- if (last_aborted->MatchesOriginalNavigation(navigation_handle)) { |
- chain_size_same_url = last_aborted->aborted_chain_size_same_url() + 1; |
- } else if (last_aborted->aborted_chain_size_same_url() > 0) { |
- LogAbortChainSameURLHistogram( |
- last_aborted->aborted_chain_size_same_url()); |
- } |
- chain_size = last_aborted->aborted_chain_size() + 1; |
- } |
- |
- if (!ShouldTrackNavigation(navigation_handle)) |
- return; |
- |
- // TODO(bmcquade): add support for tracking prerendered pages when they become |
- // visible. |
- if (embedder_interface_->IsPrerendering(web_contents())) |
- return; |
- |
- // Pass in the last committed url to the PageLoadTracker. If the MWCO has |
- // never observed a committed load, use the last committed url from this |
- // WebContent's opener. This is more accurate than using referrers due to |
- // referrer sanitizing and origin referrers. Note that this could potentially |
- // be inaccurate if the opener has since navigated. |
- content::WebContents* opener = web_contents()->GetOpener(); |
- const GURL& opener_url = |
- !has_navigated_ && opener |
- ? web_contents()->GetOpener()->GetLastCommittedURL() |
- : GURL::EmptyGURL(); |
- const GURL& currently_committed_url = |
- committed_load_ ? committed_load_->committed_url() : opener_url; |
- has_navigated_ = true; |
- |
- // We can have two provisional loads in some cases. E.g. a same-site |
- // navigation can have a concurrent cross-process navigation started |
- // from the omnibox. |
- DCHECK_GT(2ul, provisional_loads_.size()); |
- // Passing raw pointers to observers_ and embedder_interface_ is safe because |
- // the MetricsWebContentsObserver owns them both list and they are torn down |
- // after the PageLoadTracker. The PageLoadTracker does not hold on to |
- // committed_load_ or navigation_handle beyond the scope of the constructor. |
- provisional_loads_.insert(std::make_pair( |
- navigation_handle, |
- base::WrapUnique(new PageLoadTracker( |
- in_foreground_, embedder_interface_.get(), currently_committed_url, |
- navigation_handle, chain_size, chain_size_same_url)))); |
-} |
- |
-const PageLoadExtraInfo |
-MetricsWebContentsObserver::GetPageLoadExtraInfoForCommittedLoad() { |
- DCHECK(committed_load_); |
- return committed_load_->ComputePageLoadExtraInfo(); |
-} |
- |
-void MetricsWebContentsObserver::DidFinishNavigation( |
- content::NavigationHandle* navigation_handle) { |
- if (!navigation_handle->IsInMainFrame()) |
- return; |
- |
- std::unique_ptr<PageLoadTracker> finished_nav( |
- std::move(provisional_loads_[navigation_handle])); |
- provisional_loads_.erase(navigation_handle); |
- |
- const bool should_track = |
- finished_nav && ShouldTrackNavigation(navigation_handle); |
- |
- if (finished_nav && !should_track) |
- finished_nav->StopTracking(); |
- |
- if (navigation_handle->HasCommitted()) { |
- // Ignore same-page navigations. |
- if (navigation_handle->IsSamePage()) |
- return; |
- |
- // Notify other loads that they may have been aborted by this committed |
- // load. Note that by using the committed navigation start as the abort |
- // cause, we lose data on provisional loads that were aborted by other |
- // provisional loads. Those will either be listed as ABORT_OTHER or as being |
- // aborted by this load. |
- // is_certainly_browser_timestamp is set to false because NavigationStart() |
- // could be set in either the renderer or browser process. |
- NotifyAbortAllLoadsWithTimestamp( |
- AbortTypeForPageTransition(navigation_handle->GetPageTransition()), |
- navigation_handle->NavigationStart(), false); |
- |
- if (should_track) { |
- HandleCommittedNavigationForTrackedLoad(navigation_handle, |
- std::move(finished_nav)); |
- } else { |
- committed_load_.reset(); |
- } |
- } else if (should_track) { |
- HandleFailedNavigationForTrackedLoad(navigation_handle, |
- std::move(finished_nav)); |
- } |
-} |
- |
-// Handle a pre-commit error. Navigations that result in an error page will be |
-// ignored. Note that downloads/204s will result in HasCommitted() returning |
-// false. |
-// TODO(csharrison): Track changes to NavigationHandle for signals when this is |
-// the case (HTTP response headers). |
-void MetricsWebContentsObserver::HandleFailedNavigationForTrackedLoad( |
- content::NavigationHandle* navigation_handle, |
- std::unique_ptr<PageLoadTracker> tracker) { |
- tracker->FailedProvisionalLoad(navigation_handle); |
- |
- net::Error error = navigation_handle->GetNetErrorCode(); |
- |
- // net::OK: This case occurs when the NavigationHandle finishes and reports |
- // !HasCommitted(), but reports no net::Error. This should not occur |
- // pre-PlzNavigate, but afterwards it should represent the navigation stopped |
- // by the user before it was ready to commit. |
- // net::ERR_ABORTED: An aborted provisional load has error |
- // net::ERR_ABORTED. Note that this can come from some non user-initiated |
- // errors, such as downloads, or 204 responses. See crbug.com/542369. |
- if ((error == net::OK) || (error == net::ERR_ABORTED)) { |
- tracker->NotifyAbort(ABORT_OTHER, base::TimeTicks::Now(), true); |
- aborted_provisional_loads_.push_back(std::move(tracker)); |
- } |
-} |
- |
-void MetricsWebContentsObserver::HandleCommittedNavigationForTrackedLoad( |
- content::NavigationHandle* navigation_handle, |
- std::unique_ptr<PageLoadTracker> tracker) { |
- if (!navigation_handle->HasUserGesture() && |
- (navigation_handle->GetPageTransition() & |
- ui::PAGE_TRANSITION_CLIENT_REDIRECT) != 0 && |
- committed_load_) |
- committed_load_->NotifyClientRedirectTo(*tracker); |
- |
- committed_load_ = std::move(tracker); |
- committed_load_->Commit(navigation_handle); |
-} |
- |
-void MetricsWebContentsObserver::NavigationStopped() { |
- NotifyAbortAllLoads(ABORT_STOP); |
-} |
- |
-void MetricsWebContentsObserver::OnInputEvent( |
- const blink::WebInputEvent& event) { |
- // Ignore browser navigation or reload which comes with type Undefined. |
- if (event.type == blink::WebInputEvent::Type::Undefined) |
- return; |
- |
- if (committed_load_) |
- committed_load_->OnInputEvent(event); |
-} |
- |
-void MetricsWebContentsObserver::DidRedirectNavigation( |
- content::NavigationHandle* navigation_handle) { |
- if (!navigation_handle->IsInMainFrame()) |
- return; |
- auto it = provisional_loads_.find(navigation_handle); |
- if (it == provisional_loads_.end()) |
- return; |
- it->second->Redirect(navigation_handle); |
-} |
- |
-void MetricsWebContentsObserver::WasShown() { |
- if (in_foreground_) |
- return; |
- in_foreground_ = true; |
- if (committed_load_) |
- committed_load_->WebContentsShown(); |
- for (const auto& kv : provisional_loads_) { |
- kv.second->WebContentsShown(); |
- } |
-} |
- |
-void MetricsWebContentsObserver::WasHidden() { |
- if (!in_foreground_) |
- return; |
- in_foreground_ = false; |
- if (committed_load_) |
- committed_load_->WebContentsHidden(); |
- for (const auto& kv : provisional_loads_) { |
- kv.second->WebContentsHidden(); |
- } |
-} |
- |
-// This will occur when the process for the main RenderFrameHost exits, either |
-// normally or from a crash. We eagerly log data from the last committed load if |
-// we have one. Don't notify aborts here because this is probably not user |
-// initiated. If it is (e.g. browser shutdown), other code paths will take care |
-// of notifying. |
-void MetricsWebContentsObserver::RenderProcessGone( |
- base::TerminationStatus status) { |
- // Other code paths will be run for normal renderer shutdown. Note that we |
- // sometimes get the STILL_RUNNING value on fast shutdown. |
- if (status == base::TERMINATION_STATUS_NORMAL_TERMINATION || |
- status == base::TERMINATION_STATUS_STILL_RUNNING) { |
- return; |
- } |
- |
- // If this is a crash, eagerly log the aborted provisional loads and the |
- // committed load. |provisional_loads_| don't need to be destroyed here |
- // because their lifetime is tied to the NavigationHandle. |
- committed_load_.reset(); |
- aborted_provisional_loads_.clear(); |
-} |
- |
-void MetricsWebContentsObserver::NotifyAbortAllLoads(UserAbortType abort_type) { |
- NotifyAbortAllLoadsWithTimestamp(abort_type, base::TimeTicks::Now(), true); |
-} |
- |
-void MetricsWebContentsObserver::NotifyAbortAllLoadsWithTimestamp( |
- UserAbortType abort_type, |
- base::TimeTicks timestamp, |
- bool is_certainly_browser_timestamp) { |
- if (committed_load_) |
- committed_load_->NotifyAbort(abort_type, timestamp, |
- is_certainly_browser_timestamp); |
- for (const auto& kv : provisional_loads_) { |
- kv.second->NotifyAbort(abort_type, timestamp, |
- is_certainly_browser_timestamp); |
- } |
- for (const auto& tracker : aborted_provisional_loads_) { |
- if (tracker->IsLikelyProvisionalAbort(timestamp)) |
- tracker->UpdateAbort(abort_type, timestamp, |
- is_certainly_browser_timestamp); |
- } |
- aborted_provisional_loads_.clear(); |
-} |
- |
-std::unique_ptr<PageLoadTracker> |
-MetricsWebContentsObserver::NotifyAbortedProvisionalLoadsNewNavigation( |
- content::NavigationHandle* new_navigation) { |
- // If there are multiple aborted loads that can be attributed to this one, |
- // just count the latest one for simplicity. Other loads will fall into the |
- // OTHER bucket, though there shouldn't be very many. |
- if (aborted_provisional_loads_.size() == 0) |
- return nullptr; |
- if (aborted_provisional_loads_.size() > 1) |
- RecordInternalError(ERR_NAVIGATION_SIGNALS_MULIPLE_ABORTED_LOADS); |
- |
- std::unique_ptr<PageLoadTracker> last_aborted_load = |
- std::move(aborted_provisional_loads_.back()); |
- aborted_provisional_loads_.pop_back(); |
- |
- base::TimeTicks timestamp = new_navigation->NavigationStart(); |
- if (last_aborted_load->IsLikelyProvisionalAbort(timestamp)) |
- last_aborted_load->UpdateAbort(ABORT_UNKNOWN_NAVIGATION, timestamp, false); |
- |
- aborted_provisional_loads_.clear(); |
- return last_aborted_load; |
-} |
- |
-void MetricsWebContentsObserver::OnTimingUpdated( |
- content::RenderFrameHost* render_frame_host, |
- const PageLoadTiming& timing, |
- const PageLoadMetadata& metadata) { |
- bool error = false; |
- if (!committed_load_) { |
- RecordInternalError(ERR_IPC_WITH_NO_RELEVANT_LOAD); |
- error = true; |
- } |
- |
- // We may receive notifications from frames that have been navigated away |
- // from. We simply ignore them. |
- if (render_frame_host != web_contents()->GetMainFrame()) { |
- RecordInternalError(ERR_IPC_FROM_WRONG_FRAME); |
- error = true; |
- } |
- |
- // For urls like chrome://newtab, the renderer and browser disagree, |
- // so we have to double check that the renderer isn't sending data from a |
- // bad url like https://www.google.com/_/chrome/newtab. |
- if (!web_contents()->GetLastCommittedURL().SchemeIsHTTPOrHTTPS()) { |
- RecordInternalError(ERR_IPC_FROM_BAD_URL_SCHEME); |
- error = true; |
- } |
- |
- if (error) |
- return; |
- |
- if (!committed_load_->UpdateTiming(timing, metadata)) { |
- // If the page load tracker cannot update its timing, something is wrong |
- // with the IPC (it's from another load, or it's invalid in some other way). |
- // We expect this to be a rare occurrence. |
- RecordInternalError(ERR_BAD_TIMING_IPC); |
- } |
-} |
- |
-bool MetricsWebContentsObserver::ShouldTrackNavigation( |
- content::NavigationHandle* navigation_handle) const { |
- DCHECK(navigation_handle->IsInMainFrame()); |
- if (!navigation_handle->GetURL().SchemeIsHTTPOrHTTPS()) |
- return false; |
- if (embedder_interface_->IsNewTabPageUrl(navigation_handle->GetURL())) |
- return false; |
- if (navigation_handle->HasCommitted()) { |
- if (navigation_handle->IsSamePage() || navigation_handle->IsErrorPage()) |
- return false; |
- const std::string& mime_type = web_contents()->GetContentsMimeType(); |
- if (mime_type != "text/html" && mime_type != "application/xhtml+xml") |
- return false; |
- } |
- return true; |
-} |
- |
-} // namespace page_load_metrics |