Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. | 1 // Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "chrome/browser/page_load_metrics/metrics_web_contents_observer.h" | 5 #include "chrome/browser/page_load_metrics/page_load_tracker.h" |
| 6 | 6 |
| 7 #include <algorithm> | 7 #include <algorithm> |
| 8 #include <ostream> | 8 #include <ostream> |
| 9 #include <string> | 9 #include <string> |
| 10 #include <utility> | 10 #include <utility> |
| 11 | 11 |
| 12 #include "base/location.h" | |
| 13 #include "base/logging.h" | 12 #include "base/logging.h" |
| 14 #include "base/memory/ptr_util.h" | 13 #include "base/memory/ptr_util.h" |
| 15 #include "base/metrics/histogram_macros.h" | 14 #include "base/metrics/histogram_macros.h" |
| 16 #include "base/metrics/user_metrics.h" | 15 #include "base/metrics/user_metrics.h" |
| 17 #include "chrome/browser/page_load_metrics/browser_page_track_decider.h" | 16 #include "chrome/browser/page_load_metrics/page_load_metrics_embedder_interface. h" |
| 18 #include "chrome/browser/page_load_metrics/page_load_metrics_util.h" | 17 #include "chrome/browser/page_load_metrics/page_load_metrics_util.h" |
| 19 #include "chrome/common/page_load_metrics/page_load_metrics_messages.h" | |
| 20 #include "chrome/common/page_load_metrics/page_load_timing.h" | 18 #include "chrome/common/page_load_metrics/page_load_timing.h" |
| 21 #include "content/public/browser/browser_thread.h" | |
| 22 #include "content/public/browser/navigation_details.h" | 19 #include "content/public/browser/navigation_details.h" |
| 23 #include "content/public/browser/navigation_handle.h" | 20 #include "content/public/browser/navigation_handle.h" |
| 24 #include "content/public/browser/render_frame_host.h" | |
| 25 #include "content/public/browser/render_view_host.h" | |
| 26 #include "content/public/browser/web_contents.h" | |
| 27 #include "content/public/browser/web_contents_observer.h" | |
| 28 #include "content/public/browser/web_contents_user_data.h" | |
| 29 #include "ipc/ipc_message.h" | |
| 30 #include "ipc/ipc_message_macros.h" | |
| 31 #include "ui/base/page_transition_types.h" | 21 #include "ui/base/page_transition_types.h" |
| 32 | 22 |
| 33 DEFINE_WEB_CONTENTS_USER_DATA_KEY( | |
| 34 page_load_metrics::MetricsWebContentsObserver); | |
| 35 | |
| 36 // This macro invokes the specified method on each observer, passing the | 23 // This macro invokes the specified method on each observer, passing the |
| 37 // variable length arguments as the method's arguments, and removes the observer | 24 // variable length arguments as the method's arguments, and removes the observer |
| 38 // from the list of observers if the given method returns STOP_OBSERVING. | 25 // from the list of observers if the given method returns STOP_OBSERVING. |
| 39 #define INVOKE_AND_PRUNE_OBSERVERS(observers, Method, ...) \ | 26 #define INVOKE_AND_PRUNE_OBSERVERS(observers, Method, ...) \ |
| 40 for (auto it = observers.begin(); it != observers.end();) { \ | 27 for (auto it = observers.begin(); it != observers.end();) { \ |
| 41 if ((*it)->Method(__VA_ARGS__) == \ | 28 if ((*it)->Method(__VA_ARGS__) == \ |
| 42 PageLoadMetricsObserver::STOP_OBSERVING) { \ | 29 PageLoadMetricsObserver::STOP_OBSERVING) { \ |
| 43 it = observers.erase(it); \ | 30 it = observers.erase(it); \ |
| 44 } else { \ | 31 } else { \ |
| 45 ++it; \ | 32 ++it; \ |
| (...skipping 17 matching lines...) Expand all Loading... | |
| 63 "PageLoad.Internal.ProvisionalAbortChainSize.NoCommit"; | 50 "PageLoad.Internal.ProvisionalAbortChainSize.NoCommit"; |
| 64 const char kClientRedirectFirstPaintToNavigation[] = | 51 const char kClientRedirectFirstPaintToNavigation[] = |
| 65 "PageLoad.Internal.ClientRedirect.FirstPaintToNavigation"; | 52 "PageLoad.Internal.ClientRedirect.FirstPaintToNavigation"; |
| 66 const char kClientRedirectWithoutPaint[] = | 53 const char kClientRedirectWithoutPaint[] = |
| 67 "PageLoad.Internal.ClientRedirect.NavigationWithoutPaint"; | 54 "PageLoad.Internal.ClientRedirect.NavigationWithoutPaint"; |
| 68 const char kPageLoadCompletedAfterAppBackground[] = | 55 const char kPageLoadCompletedAfterAppBackground[] = |
| 69 "PageLoad.Internal.PageLoadCompleted.AfterAppBackground"; | 56 "PageLoad.Internal.PageLoadCompleted.AfterAppBackground"; |
| 70 | 57 |
| 71 } // namespace internal | 58 } // namespace internal |
| 72 | 59 |
| 60 void RecordInternalError(InternalErrorLoadEvent event) { | |
| 61 UMA_HISTOGRAM_ENUMERATION(internal::kErrorEvents, event, ERR_LAST_ENTRY); | |
| 62 } | |
| 63 | |
| 64 // TODO(csharrison): Add a case for client side redirects, which is what JS | |
| 65 // initiated window.location / window.history navigations get set to. | |
| 66 UserAbortType AbortTypeForPageTransition(ui::PageTransition transition) { | |
| 67 if (transition & ui::PAGE_TRANSITION_CLIENT_REDIRECT) { | |
| 68 return ABORT_CLIENT_REDIRECT; | |
| 69 } | |
| 70 if (ui::PageTransitionCoreTypeIs(transition, ui::PAGE_TRANSITION_RELOAD)) | |
| 71 return ABORT_RELOAD; | |
| 72 if (transition & ui::PAGE_TRANSITION_FORWARD_BACK) | |
| 73 return ABORT_FORWARD_BACK; | |
| 74 if (ui::PageTransitionIsNewNavigation(transition)) | |
| 75 return ABORT_NEW_NAVIGATION; | |
| 76 NOTREACHED() | |
| 77 << "AbortTypeForPageTransition received unexpected ui::PageTransition: " | |
| 78 << transition; | |
| 79 return ABORT_OTHER; | |
| 80 } | |
| 81 | |
| 82 void LogAbortChainSameURLHistogram(int aborted_chain_size_same_url) { | |
| 83 if (aborted_chain_size_same_url > 0) { | |
| 84 UMA_HISTOGRAM_COUNTS(internal::kAbortChainSizeSameURL, | |
| 85 aborted_chain_size_same_url); | |
| 86 } | |
| 87 } | |
| 88 | |
| 73 namespace { | 89 namespace { |
| 74 | 90 |
| 75 // Helper to allow use of Optional<> values in LOG() messages. | 91 // Helper to allow use of Optional<> values in LOG() messages. |
| 76 std::ostream& operator<<(std::ostream& os, | 92 std::ostream& operator<<(std::ostream& os, |
| 77 const base::Optional<base::TimeDelta>& opt) { | 93 const base::Optional<base::TimeDelta>& opt) { |
| 78 if (opt) | 94 if (opt) |
| 79 os << opt.value(); | 95 os << opt.value(); |
| 80 else | 96 else |
| 81 os << "(unset)"; | 97 os << "(unset)"; |
| 82 return os; | 98 return os; |
| (...skipping 125 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 208 if (!EventsInOrder(timing.first_paint, timing.first_meaningful_paint)) { | 224 if (!EventsInOrder(timing.first_paint, timing.first_meaningful_paint)) { |
| 209 NOTREACHED() << "Invalid first_paint " << timing.first_paint | 225 NOTREACHED() << "Invalid first_paint " << timing.first_paint |
| 210 << " for first_meaningful_paint " | 226 << " for first_meaningful_paint " |
| 211 << timing.first_meaningful_paint; | 227 << timing.first_meaningful_paint; |
| 212 return false; | 228 return false; |
| 213 } | 229 } |
| 214 | 230 |
| 215 return true; | 231 return true; |
| 216 } | 232 } |
| 217 | 233 |
| 218 void RecordInternalError(InternalErrorLoadEvent event) { | |
| 219 UMA_HISTOGRAM_ENUMERATION(internal::kErrorEvents, event, ERR_LAST_ENTRY); | |
| 220 } | |
| 221 | |
| 222 void RecordAppBackgroundPageLoadCompleted(bool completed_after_background) { | 234 void RecordAppBackgroundPageLoadCompleted(bool completed_after_background) { |
| 223 UMA_HISTOGRAM_BOOLEAN(internal::kPageLoadCompletedAfterAppBackground, | 235 UMA_HISTOGRAM_BOOLEAN(internal::kPageLoadCompletedAfterAppBackground, |
| 224 completed_after_background); | 236 completed_after_background); |
| 225 } | 237 } |
| 226 | 238 |
| 227 // TODO(csharrison): Add a case for client side redirects, which is what JS | |
| 228 // initiated window.location / window.history navigations get set to. | |
| 229 UserAbortType AbortTypeForPageTransition(ui::PageTransition transition) { | |
| 230 if (transition & ui::PAGE_TRANSITION_CLIENT_REDIRECT) { | |
| 231 return ABORT_CLIENT_REDIRECT; | |
| 232 } | |
| 233 if (ui::PageTransitionCoreTypeIs(transition, ui::PAGE_TRANSITION_RELOAD)) | |
| 234 return ABORT_RELOAD; | |
| 235 if (transition & ui::PAGE_TRANSITION_FORWARD_BACK) | |
| 236 return ABORT_FORWARD_BACK; | |
| 237 if (ui::PageTransitionIsNewNavigation(transition)) | |
| 238 return ABORT_NEW_NAVIGATION; | |
| 239 NOTREACHED() | |
| 240 << "AbortTypeForPageTransition received unexpected ui::PageTransition: " | |
| 241 << transition; | |
| 242 return ABORT_OTHER; | |
| 243 } | |
| 244 | |
| 245 void LogAbortChainSameURLHistogram(int aborted_chain_size_same_url) { | |
| 246 if (aborted_chain_size_same_url > 0) { | |
| 247 UMA_HISTOGRAM_COUNTS(internal::kAbortChainSizeSameURL, | |
| 248 aborted_chain_size_same_url); | |
| 249 } | |
| 250 } | |
| 251 | |
| 252 void DispatchObserverTimingCallbacks(PageLoadMetricsObserver* observer, | 239 void DispatchObserverTimingCallbacks(PageLoadMetricsObserver* observer, |
| 253 const PageLoadTiming& last_timing, | 240 const PageLoadTiming& last_timing, |
| 254 const PageLoadTiming& new_timing, | 241 const PageLoadTiming& new_timing, |
| 255 const PageLoadMetadata& last_metadata, | 242 const PageLoadMetadata& last_metadata, |
| 256 const PageLoadExtraInfo& extra_info) { | 243 const PageLoadExtraInfo& extra_info) { |
| 257 if (last_timing != new_timing) | 244 if (last_timing != new_timing) |
| 258 observer->OnTimingUpdate(new_timing, extra_info); | 245 observer->OnTimingUpdate(new_timing, extra_info); |
| 259 if (new_timing.dom_content_loaded_event_start && | 246 if (new_timing.dom_content_loaded_event_start && |
| 260 !last_timing.dom_content_loaded_event_start) | 247 !last_timing.dom_content_loaded_event_start) |
| 261 observer->OnDomContentLoadedEventStart(new_timing, extra_info); | 248 observer->OnDomContentLoadedEventStart(new_timing, extra_info); |
| (...skipping 12 matching lines...) Expand all Loading... | |
| 274 if (new_timing.first_meaningful_paint && !last_timing.first_meaningful_paint) | 261 if (new_timing.first_meaningful_paint && !last_timing.first_meaningful_paint) |
| 275 observer->OnFirstMeaningfulPaint(new_timing, extra_info); | 262 observer->OnFirstMeaningfulPaint(new_timing, extra_info); |
| 276 if (new_timing.parse_start && !last_timing.parse_start) | 263 if (new_timing.parse_start && !last_timing.parse_start) |
| 277 observer->OnParseStart(new_timing, extra_info); | 264 observer->OnParseStart(new_timing, extra_info); |
| 278 if (new_timing.parse_stop && !last_timing.parse_stop) | 265 if (new_timing.parse_stop && !last_timing.parse_stop) |
| 279 observer->OnParseStop(new_timing, extra_info); | 266 observer->OnParseStop(new_timing, extra_info); |
| 280 if (extra_info.metadata.behavior_flags != last_metadata.behavior_flags) | 267 if (extra_info.metadata.behavior_flags != last_metadata.behavior_flags) |
| 281 observer->OnLoadingBehaviorObserved(extra_info); | 268 observer->OnLoadingBehaviorObserved(extra_info); |
| 282 } | 269 } |
| 283 | 270 |
| 284 // TODO(crbug.com/617904): Browser initiated navigations should have | |
| 285 // HasUserGesture() set to true. Update this once we get enough data from just | |
| 286 // renderer initiated aborts. | |
| 287 bool IsNavigationUserInitiated(content::NavigationHandle* handle) { | |
| 288 return handle->HasUserGesture(); | |
| 289 } | |
| 290 | |
| 291 } // namespace | 271 } // namespace |
| 292 | 272 |
| 293 PageLoadTracker::PageLoadTracker( | 273 PageLoadTracker::PageLoadTracker( |
| 294 bool in_foreground, | 274 bool in_foreground, |
| 295 PageLoadMetricsEmbedderInterface* embedder_interface, | 275 PageLoadMetricsEmbedderInterface* embedder_interface, |
| 296 const GURL& currently_committed_url, | 276 const GURL& currently_committed_url, |
| 297 content::NavigationHandle* navigation_handle, | 277 content::NavigationHandle* navigation_handle, |
| 298 int aborted_chain_size, | 278 int aborted_chain_size, |
| 299 int aborted_chain_size_same_url) | 279 int aborted_chain_size_same_url) |
| 300 : did_stop_tracking_(false), | 280 : did_stop_tracking_(false), |
| 301 app_entered_background_(false), | 281 app_entered_background_(false), |
| 302 navigation_start_(navigation_handle->NavigationStart()), | 282 navigation_start_(navigation_handle->NavigationStart()), |
| 303 start_url_(navigation_handle->GetURL()), | 283 start_url_(navigation_handle->GetURL()), |
| 304 abort_type_(ABORT_NONE), | 284 abort_type_(ABORT_NONE), |
| 305 abort_user_initiated_(false), | 285 abort_user_initiated_(false), |
| 306 started_in_foreground_(in_foreground), | 286 started_in_foreground_(in_foreground), |
| 307 page_transition_(navigation_handle->GetPageTransition()), | 287 page_transition_(navigation_handle->GetPageTransition()), |
| 308 num_cache_requests_(0), | 288 num_cache_requests_(0), |
| 309 num_network_requests_(0), | 289 num_network_requests_(0), |
|
Charlie Harrison
2016/10/24 16:32:39
Can you retain the comment above IsNavigationUserI
Bryan McQuade
2016/10/24 20:43:34
Ah, sorry, I made IsNavigationUserInitiated a func
| |
| 310 user_gesture_(IsNavigationUserInitiated(navigation_handle)), | 290 user_gesture_(navigation_handle->HasUserGesture()), |
| 311 aborted_chain_size_(aborted_chain_size), | 291 aborted_chain_size_(aborted_chain_size), |
| 312 aborted_chain_size_same_url_(aborted_chain_size_same_url), | 292 aborted_chain_size_same_url_(aborted_chain_size_same_url), |
| 313 embedder_interface_(embedder_interface) { | 293 embedder_interface_(embedder_interface) { |
| 314 DCHECK(!navigation_handle->HasCommitted()); | 294 DCHECK(!navigation_handle->HasCommitted()); |
| 315 if (embedder_interface_->IsPrerendering( | 295 if (embedder_interface_->IsPrerendering( |
| 316 navigation_handle->GetWebContents())) { | 296 navigation_handle->GetWebContents())) { |
| 317 DCHECK(!started_in_foreground_); | 297 DCHECK(!started_in_foreground_); |
| 318 // For the time being, we do not track prerenders. See crbug.com/648338 for | 298 // For the time being, we do not track prerenders. See crbug.com/648338 for |
| 319 // details. | 299 // details. |
| 320 StopTracking(); | 300 StopTracking(); |
| (...skipping 360 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 681 } | 661 } |
| 682 abort_type_ = abort_type; | 662 abort_type_ = abort_type; |
| 683 abort_time_ = timestamp; | 663 abort_time_ = timestamp; |
| 684 abort_user_initiated_ = user_initiated && abort_type != ABORT_CLIENT_REDIRECT; | 664 abort_user_initiated_ = user_initiated && abort_type != ABORT_CLIENT_REDIRECT; |
| 685 | 665 |
| 686 if (is_certainly_browser_timestamp) { | 666 if (is_certainly_browser_timestamp) { |
| 687 ClampBrowserTimestampIfInterProcessTimeTickSkew(&abort_time_); | 667 ClampBrowserTimestampIfInterProcessTimeTickSkew(&abort_time_); |
| 688 } | 668 } |
| 689 } | 669 } |
| 690 | 670 |
| 691 // static | |
| 692 MetricsWebContentsObserver::MetricsWebContentsObserver( | |
| 693 content::WebContents* web_contents, | |
| 694 std::unique_ptr<PageLoadMetricsEmbedderInterface> embedder_interface) | |
| 695 : content::WebContentsObserver(web_contents), | |
| 696 in_foreground_(false), | |
| 697 embedder_interface_(std::move(embedder_interface)), | |
| 698 has_navigated_(false) { | |
| 699 RegisterInputEventObserver(web_contents->GetRenderViewHost()); | |
| 700 } | |
| 701 | |
| 702 MetricsWebContentsObserver* MetricsWebContentsObserver::CreateForWebContents( | |
| 703 content::WebContents* web_contents, | |
| 704 std::unique_ptr<PageLoadMetricsEmbedderInterface> embedder_interface) { | |
| 705 DCHECK(web_contents); | |
| 706 | |
| 707 MetricsWebContentsObserver* metrics = FromWebContents(web_contents); | |
| 708 if (!metrics) { | |
| 709 metrics = new MetricsWebContentsObserver(web_contents, | |
| 710 std::move(embedder_interface)); | |
| 711 web_contents->SetUserData(UserDataKey(), metrics); | |
| 712 } | |
| 713 return metrics; | |
| 714 } | |
| 715 | |
| 716 MetricsWebContentsObserver::~MetricsWebContentsObserver() { | |
| 717 // TODO(csharrison): Use a more user-initiated signal for CLOSE. | |
| 718 NotifyAbortAllLoads(ABORT_CLOSE, false); | |
| 719 } | |
| 720 | |
| 721 void MetricsWebContentsObserver::RegisterInputEventObserver( | |
| 722 content::RenderViewHost* host) { | |
| 723 if (host != nullptr) | |
| 724 host->GetWidget()->AddInputEventObserver(this); | |
| 725 } | |
| 726 | |
| 727 void MetricsWebContentsObserver::UnregisterInputEventObserver( | |
| 728 content::RenderViewHost* host) { | |
| 729 if (host != nullptr) | |
| 730 host->GetWidget()->RemoveInputEventObserver(this); | |
| 731 } | |
| 732 | |
| 733 void MetricsWebContentsObserver::RenderViewHostChanged( | |
| 734 content::RenderViewHost* old_host, | |
| 735 content::RenderViewHost* new_host) { | |
| 736 UnregisterInputEventObserver(old_host); | |
| 737 RegisterInputEventObserver(new_host); | |
| 738 } | |
| 739 | |
| 740 bool MetricsWebContentsObserver::OnMessageReceived( | |
| 741 const IPC::Message& message, | |
| 742 content::RenderFrameHost* render_frame_host) { | |
| 743 DCHECK_CURRENTLY_ON(content::BrowserThread::UI); | |
| 744 bool handled = true; | |
| 745 IPC_BEGIN_MESSAGE_MAP_WITH_PARAM(MetricsWebContentsObserver, message, | |
| 746 render_frame_host) | |
| 747 IPC_MESSAGE_HANDLER(PageLoadMetricsMsg_TimingUpdated, OnTimingUpdated) | |
| 748 IPC_MESSAGE_UNHANDLED(handled = false) | |
| 749 IPC_END_MESSAGE_MAP() | |
| 750 return handled; | |
| 751 } | |
| 752 | |
| 753 void MetricsWebContentsObserver::WillStartNavigationRequest( | |
| 754 content::NavigationHandle* navigation_handle) { | |
| 755 if (!navigation_handle->IsInMainFrame()) | |
| 756 return; | |
| 757 | |
| 758 std::unique_ptr<PageLoadTracker> last_aborted = | |
| 759 NotifyAbortedProvisionalLoadsNewNavigation(navigation_handle); | |
| 760 | |
| 761 int chain_size_same_url = 0; | |
| 762 int chain_size = 0; | |
| 763 if (last_aborted) { | |
| 764 if (last_aborted->MatchesOriginalNavigation(navigation_handle)) { | |
| 765 chain_size_same_url = last_aborted->aborted_chain_size_same_url() + 1; | |
| 766 } else if (last_aborted->aborted_chain_size_same_url() > 0) { | |
| 767 LogAbortChainSameURLHistogram( | |
| 768 last_aborted->aborted_chain_size_same_url()); | |
| 769 } | |
| 770 chain_size = last_aborted->aborted_chain_size() + 1; | |
| 771 } | |
| 772 | |
| 773 if (!ShouldTrackNavigation(navigation_handle)) | |
| 774 return; | |
| 775 | |
| 776 // Pass in the last committed url to the PageLoadTracker. If the MWCO has | |
| 777 // never observed a committed load, use the last committed url from this | |
| 778 // WebContent's opener. This is more accurate than using referrers due to | |
| 779 // referrer sanitizing and origin referrers. Note that this could potentially | |
| 780 // be inaccurate if the opener has since navigated. | |
| 781 content::WebContents* opener = web_contents()->GetOpener(); | |
| 782 const GURL& opener_url = | |
| 783 !has_navigated_ && opener | |
| 784 ? web_contents()->GetOpener()->GetLastCommittedURL() | |
| 785 : GURL::EmptyGURL(); | |
| 786 const GURL& currently_committed_url = | |
| 787 committed_load_ ? committed_load_->committed_url() : opener_url; | |
| 788 has_navigated_ = true; | |
| 789 | |
| 790 // We can have two provisional loads in some cases. E.g. a same-site | |
| 791 // navigation can have a concurrent cross-process navigation started | |
| 792 // from the omnibox. | |
| 793 DCHECK_GT(2ul, provisional_loads_.size()); | |
| 794 // Passing raw pointers to observers_ and embedder_interface_ is safe because | |
| 795 // the MetricsWebContentsObserver owns them both list and they are torn down | |
| 796 // after the PageLoadTracker. The PageLoadTracker does not hold on to | |
| 797 // committed_load_ or navigation_handle beyond the scope of the constructor. | |
| 798 provisional_loads_.insert(std::make_pair( | |
| 799 navigation_handle, | |
| 800 base::MakeUnique<PageLoadTracker>( | |
| 801 in_foreground_, embedder_interface_.get(), currently_committed_url, | |
| 802 navigation_handle, chain_size, chain_size_same_url))); | |
| 803 } | |
| 804 | |
| 805 void MetricsWebContentsObserver::OnRequestComplete( | |
| 806 content::ResourceType resource_type, | |
| 807 bool was_cached, | |
| 808 int net_error) { | |
| 809 // For simplicity, only count subresources. Navigations are hard to attribute | |
| 810 // here because we won't have a committed load by the time data streams in | |
| 811 // from the IO thread. | |
| 812 if (resource_type == content::RESOURCE_TYPE_MAIN_FRAME && | |
| 813 net_error != net::OK) { | |
| 814 return; | |
| 815 } | |
| 816 if (!committed_load_) | |
| 817 return; | |
| 818 committed_load_->OnLoadedSubresource(was_cached); | |
| 819 } | |
| 820 | |
| 821 const PageLoadExtraInfo | |
| 822 MetricsWebContentsObserver::GetPageLoadExtraInfoForCommittedLoad() { | |
| 823 DCHECK(committed_load_); | |
| 824 return committed_load_->ComputePageLoadExtraInfo(); | |
| 825 } | |
| 826 | |
| 827 void MetricsWebContentsObserver::DidFinishNavigation( | |
| 828 content::NavigationHandle* navigation_handle) { | |
| 829 if (!navigation_handle->IsInMainFrame()) | |
| 830 return; | |
| 831 | |
| 832 std::unique_ptr<PageLoadTracker> finished_nav( | |
| 833 std::move(provisional_loads_[navigation_handle])); | |
| 834 provisional_loads_.erase(navigation_handle); | |
| 835 | |
| 836 // Ignore same-page navigations. | |
| 837 if (navigation_handle->HasCommitted() && navigation_handle->IsSamePage()) { | |
| 838 if (finished_nav) | |
| 839 finished_nav->StopTracking(); | |
| 840 return; | |
| 841 } | |
| 842 | |
| 843 // Ignore internally generated aborts for navigations with HTTP responses that | |
| 844 // don't commit, such as HTTP 204 responses and downloads. | |
| 845 if (!navigation_handle->HasCommitted() && | |
| 846 navigation_handle->GetNetErrorCode() == net::ERR_ABORTED && | |
| 847 navigation_handle->GetResponseHeaders()) { | |
| 848 if (finished_nav) | |
| 849 finished_nav->StopTracking(); | |
| 850 return; | |
| 851 } | |
| 852 | |
| 853 const bool should_track = | |
| 854 finished_nav && ShouldTrackNavigation(navigation_handle); | |
| 855 | |
| 856 if (finished_nav && !should_track) | |
| 857 finished_nav->StopTracking(); | |
| 858 | |
| 859 if (navigation_handle->HasCommitted()) { | |
| 860 // Notify other loads that they may have been aborted by this committed | |
| 861 // load. is_certainly_browser_timestamp is set to false because | |
| 862 // NavigationStart() could be set in either the renderer or browser process. | |
| 863 NotifyAbortAllLoadsWithTimestamp( | |
| 864 AbortTypeForPageTransition(navigation_handle->GetPageTransition()), | |
| 865 IsNavigationUserInitiated(navigation_handle), | |
| 866 navigation_handle->NavigationStart(), false); | |
| 867 | |
| 868 if (should_track) { | |
| 869 HandleCommittedNavigationForTrackedLoad(navigation_handle, | |
| 870 std::move(finished_nav)); | |
| 871 } else { | |
| 872 committed_load_.reset(); | |
| 873 } | |
| 874 } else if (should_track) { | |
| 875 HandleFailedNavigationForTrackedLoad(navigation_handle, | |
| 876 std::move(finished_nav)); | |
| 877 } | |
| 878 } | |
| 879 | |
| 880 // Handle a pre-commit error. Navigations that result in an error page will be | |
| 881 // ignored. | |
| 882 void MetricsWebContentsObserver::HandleFailedNavigationForTrackedLoad( | |
| 883 content::NavigationHandle* navigation_handle, | |
| 884 std::unique_ptr<PageLoadTracker> tracker) { | |
| 885 tracker->FailedProvisionalLoad(navigation_handle); | |
| 886 | |
| 887 net::Error error = navigation_handle->GetNetErrorCode(); | |
| 888 | |
| 889 // net::OK: This case occurs when the NavigationHandle finishes and reports | |
| 890 // !HasCommitted(), but reports no net::Error. This should not occur | |
| 891 // pre-PlzNavigate, but afterwards it should represent the navigation stopped | |
| 892 // by the user before it was ready to commit. | |
| 893 // net::ERR_ABORTED: An aborted provisional load has error | |
| 894 // net::ERR_ABORTED. | |
| 895 if ((error == net::OK) || (error == net::ERR_ABORTED)) { | |
| 896 tracker->NotifyAbort(ABORT_OTHER, false, base::TimeTicks::Now(), true); | |
| 897 aborted_provisional_loads_.push_back(std::move(tracker)); | |
| 898 } | |
| 899 } | |
| 900 | |
| 901 void MetricsWebContentsObserver::HandleCommittedNavigationForTrackedLoad( | |
| 902 content::NavigationHandle* navigation_handle, | |
| 903 std::unique_ptr<PageLoadTracker> tracker) { | |
| 904 if (!navigation_handle->HasUserGesture() && | |
| 905 (navigation_handle->GetPageTransition() & | |
| 906 ui::PAGE_TRANSITION_CLIENT_REDIRECT) != 0 && | |
| 907 committed_load_) | |
| 908 committed_load_->NotifyClientRedirectTo(*tracker); | |
| 909 | |
| 910 committed_load_ = std::move(tracker); | |
| 911 committed_load_->Commit(navigation_handle); | |
| 912 } | |
| 913 | |
| 914 void MetricsWebContentsObserver::NavigationStopped() { | |
| 915 // TODO(csharrison): Use a more user-initiated signal for STOP. | |
| 916 NotifyAbortAllLoads(ABORT_STOP, false); | |
| 917 } | |
| 918 | |
| 919 void MetricsWebContentsObserver::OnInputEvent( | |
| 920 const blink::WebInputEvent& event) { | |
| 921 // Ignore browser navigation or reload which comes with type Undefined. | |
| 922 if (event.type == blink::WebInputEvent::Type::Undefined) | |
| 923 return; | |
| 924 | |
| 925 if (committed_load_) | |
| 926 committed_load_->OnInputEvent(event); | |
| 927 } | |
| 928 | |
| 929 void MetricsWebContentsObserver::FlushMetricsOnAppEnterBackground() { | |
| 930 // Signal to observers that we've been backgrounded, in cases where the | |
| 931 // FlushMetricsOnAppEnterBackground callback gets invoked before the | |
| 932 // associated WasHidden callback. | |
| 933 WasHidden(); | |
| 934 | |
| 935 if (committed_load_) | |
| 936 committed_load_->FlushMetricsOnAppEnterBackground(); | |
| 937 for (const auto& kv : provisional_loads_) { | |
| 938 kv.second->FlushMetricsOnAppEnterBackground(); | |
| 939 } | |
| 940 for (const auto& tracker : aborted_provisional_loads_) { | |
| 941 tracker->FlushMetricsOnAppEnterBackground(); | |
| 942 } | |
| 943 } | |
| 944 | |
| 945 void MetricsWebContentsObserver::DidRedirectNavigation( | |
| 946 content::NavigationHandle* navigation_handle) { | |
| 947 if (!navigation_handle->IsInMainFrame()) | |
| 948 return; | |
| 949 auto it = provisional_loads_.find(navigation_handle); | |
| 950 if (it == provisional_loads_.end()) | |
| 951 return; | |
| 952 it->second->Redirect(navigation_handle); | |
| 953 } | |
| 954 | |
| 955 void MetricsWebContentsObserver::WasShown() { | |
| 956 if (in_foreground_) | |
| 957 return; | |
| 958 in_foreground_ = true; | |
| 959 if (committed_load_) | |
| 960 committed_load_->WebContentsShown(); | |
| 961 for (const auto& kv : provisional_loads_) { | |
| 962 kv.second->WebContentsShown(); | |
| 963 } | |
| 964 } | |
| 965 | |
| 966 void MetricsWebContentsObserver::WasHidden() { | |
| 967 if (!in_foreground_) | |
| 968 return; | |
| 969 in_foreground_ = false; | |
| 970 if (committed_load_) | |
| 971 committed_load_->WebContentsHidden(); | |
| 972 for (const auto& kv : provisional_loads_) { | |
| 973 kv.second->WebContentsHidden(); | |
| 974 } | |
| 975 } | |
| 976 | |
| 977 // This will occur when the process for the main RenderFrameHost exits, either | |
| 978 // normally or from a crash. We eagerly log data from the last committed load if | |
| 979 // we have one. Don't notify aborts here because this is probably not user | |
| 980 // initiated. If it is (e.g. browser shutdown), other code paths will take care | |
| 981 // of notifying. | |
| 982 void MetricsWebContentsObserver::RenderProcessGone( | |
| 983 base::TerminationStatus status) { | |
| 984 // Other code paths will be run for normal renderer shutdown. Note that we | |
| 985 // sometimes get the STILL_RUNNING value on fast shutdown. | |
| 986 if (status == base::TERMINATION_STATUS_NORMAL_TERMINATION || | |
| 987 status == base::TERMINATION_STATUS_STILL_RUNNING) { | |
| 988 return; | |
| 989 } | |
| 990 | |
| 991 // If this is a crash, eagerly log the aborted provisional loads and the | |
| 992 // committed load. |provisional_loads_| don't need to be destroyed here | |
| 993 // because their lifetime is tied to the NavigationHandle. | |
| 994 committed_load_.reset(); | |
| 995 aborted_provisional_loads_.clear(); | |
| 996 } | |
| 997 | |
| 998 void MetricsWebContentsObserver::NotifyAbortAllLoads(UserAbortType abort_type, | |
| 999 bool user_initiated) { | |
| 1000 NotifyAbortAllLoadsWithTimestamp(abort_type, user_initiated, | |
| 1001 base::TimeTicks::Now(), true); | |
| 1002 } | |
| 1003 | |
| 1004 void MetricsWebContentsObserver::NotifyAbortAllLoadsWithTimestamp( | |
| 1005 UserAbortType abort_type, | |
| 1006 bool user_initiated, | |
| 1007 base::TimeTicks timestamp, | |
| 1008 bool is_certainly_browser_timestamp) { | |
| 1009 if (committed_load_) { | |
| 1010 committed_load_->NotifyAbort(abort_type, user_initiated, timestamp, | |
| 1011 is_certainly_browser_timestamp); | |
| 1012 } | |
| 1013 for (const auto& kv : provisional_loads_) { | |
| 1014 kv.second->NotifyAbort(abort_type, user_initiated, timestamp, | |
| 1015 is_certainly_browser_timestamp); | |
| 1016 } | |
| 1017 for (const auto& tracker : aborted_provisional_loads_) { | |
| 1018 if (tracker->IsLikelyProvisionalAbort(timestamp)) { | |
| 1019 tracker->UpdateAbort(abort_type, user_initiated, timestamp, | |
| 1020 is_certainly_browser_timestamp); | |
| 1021 } | |
| 1022 } | |
| 1023 aborted_provisional_loads_.clear(); | |
| 1024 } | |
| 1025 | |
| 1026 std::unique_ptr<PageLoadTracker> | |
| 1027 MetricsWebContentsObserver::NotifyAbortedProvisionalLoadsNewNavigation( | |
| 1028 content::NavigationHandle* new_navigation) { | |
| 1029 // If there are multiple aborted loads that can be attributed to this one, | |
| 1030 // just count the latest one for simplicity. Other loads will fall into the | |
| 1031 // OTHER bucket, though there shouldn't be very many. | |
| 1032 if (aborted_provisional_loads_.size() == 0) | |
| 1033 return nullptr; | |
| 1034 if (aborted_provisional_loads_.size() > 1) | |
| 1035 RecordInternalError(ERR_NAVIGATION_SIGNALS_MULIPLE_ABORTED_LOADS); | |
| 1036 | |
| 1037 std::unique_ptr<PageLoadTracker> last_aborted_load = | |
| 1038 std::move(aborted_provisional_loads_.back()); | |
| 1039 aborted_provisional_loads_.pop_back(); | |
| 1040 | |
| 1041 base::TimeTicks timestamp = new_navigation->NavigationStart(); | |
| 1042 if (last_aborted_load->IsLikelyProvisionalAbort(timestamp)) { | |
| 1043 last_aborted_load->UpdateAbort( | |
| 1044 AbortTypeForPageTransition(new_navigation->GetPageTransition()), | |
| 1045 IsNavigationUserInitiated(new_navigation), timestamp, false); | |
| 1046 } | |
| 1047 | |
| 1048 aborted_provisional_loads_.clear(); | |
| 1049 return last_aborted_load; | |
| 1050 } | |
| 1051 | |
| 1052 void MetricsWebContentsObserver::OnTimingUpdated( | |
| 1053 content::RenderFrameHost* render_frame_host, | |
| 1054 const PageLoadTiming& timing, | |
| 1055 const PageLoadMetadata& metadata) { | |
| 1056 // We may receive notifications from frames that have been navigated away | |
| 1057 // from. We simply ignore them. | |
| 1058 if (render_frame_host != web_contents()->GetMainFrame()) { | |
| 1059 RecordInternalError(ERR_IPC_FROM_WRONG_FRAME); | |
| 1060 return; | |
| 1061 } | |
| 1062 | |
| 1063 // While timings arriving for the wrong frame are expected, we do not expect | |
| 1064 // any of the errors below. Thus, we track occurrences of all errors below, | |
| 1065 // rather than returning early after encountering an error. | |
| 1066 | |
| 1067 bool error = false; | |
| 1068 if (!committed_load_) { | |
| 1069 RecordInternalError(ERR_IPC_WITH_NO_RELEVANT_LOAD); | |
| 1070 error = true; | |
| 1071 } | |
| 1072 | |
| 1073 if (!web_contents()->GetLastCommittedURL().SchemeIsHTTPOrHTTPS()) { | |
| 1074 RecordInternalError(ERR_IPC_FROM_BAD_URL_SCHEME); | |
| 1075 error = true; | |
| 1076 } | |
| 1077 | |
| 1078 if (error) | |
| 1079 return; | |
| 1080 | |
| 1081 if (!committed_load_->UpdateTiming(timing, metadata)) { | |
| 1082 // If the page load tracker cannot update its timing, something is wrong | |
| 1083 // with the IPC (it's from another load, or it's invalid in some other way). | |
| 1084 // We expect this to be a rare occurrence. | |
| 1085 RecordInternalError(ERR_BAD_TIMING_IPC); | |
| 1086 } | |
| 1087 } | |
| 1088 | |
| 1089 bool MetricsWebContentsObserver::ShouldTrackNavigation( | |
| 1090 content::NavigationHandle* navigation_handle) const { | |
| 1091 DCHECK(navigation_handle->IsInMainFrame()); | |
| 1092 DCHECK(!navigation_handle->HasCommitted() || | |
| 1093 !navigation_handle->IsSamePage()); | |
| 1094 | |
| 1095 return BrowserPageTrackDecider(embedder_interface_.get(), web_contents(), | |
| 1096 navigation_handle).ShouldTrack(); | |
| 1097 } | |
| 1098 | |
| 1099 } // namespace page_load_metrics | 671 } // namespace page_load_metrics |
| OLD | NEW |