// Copyright 2014 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. #if !defined(OS_ANDROID) #include "chrome/browser/metrics/first_web_contents_profiler.h" #include <string> #include "base/location.h" #include "base/logging.h" #include "base/metrics/histogram_macros.h" #include "base/time/time.h" #include "build/build_config.h" #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/browser_list.h" #include "chrome/browser/ui/tabs/tab_strip_model.h" #include "components/metrics/profiler/tracking_synchronizer.h" #include "components/metrics/proto/profiler_event.pb.h" #include "components/startup_metric_utils/browser/startup_metric_utils.h" #include "content/public/browser/navigation_handle.h" // static void FirstWebContentsProfiler::Start() { for (auto* browser : *BrowserList::GetInstance()) { content::WebContents* web_contents = browser->tab_strip_model()->GetActiveWebContents(); if (web_contents) { // FirstWebContentsProfiler owns itself and is also bound to // |web_contents|'s lifetime by observing WebContentsDestroyed(). new FirstWebContentsProfiler(web_contents); return; } } } FirstWebContentsProfiler::FirstWebContentsProfiler( content::WebContents* web_contents) : content::WebContentsObserver(web_contents), collected_paint_metric_(false), collected_load_metric_(false), collected_main_navigation_start_metric_(false), collected_main_navigation_finished_metric_(false) {} void FirstWebContentsProfiler::DidFirstVisuallyNonEmptyPaint() { if (collected_paint_metric_) return; if (startup_metric_utils::WasNonBrowserUIDisplayed()) { FinishedCollectingMetrics(FinishReason::ABANDON_BLOCKING_UI); return; } collected_paint_metric_ = true; startup_metric_utils::RecordFirstWebContentsNonEmptyPaint( base::TimeTicks::Now()); metrics::TrackingSynchronizer::OnProfilingPhaseCompleted( metrics::ProfilerEventProto::EVENT_FIRST_NONEMPTY_PAINT); if (IsFinishedCollectingMetrics()) FinishedCollectingMetrics(FinishReason::DONE); } void FirstWebContentsProfiler::DocumentOnLoadCompletedInMainFrame() { if (collected_load_metric_) return; if (startup_metric_utils::WasNonBrowserUIDisplayed()) { FinishedCollectingMetrics(FinishReason::ABANDON_BLOCKING_UI); return; } collected_load_metric_ = true; startup_metric_utils::RecordFirstWebContentsMainFrameLoad( base::TimeTicks::Now()); if (IsFinishedCollectingMetrics()) FinishedCollectingMetrics(FinishReason::DONE); } void FirstWebContentsProfiler::DidStartNavigation( content::NavigationHandle* navigation_handle) { if (collected_main_navigation_start_metric_) return; if (startup_metric_utils::WasNonBrowserUIDisplayed()) { FinishedCollectingMetrics(FinishReason::ABANDON_BLOCKING_UI); return; } // The first navigation has to be the main frame's. DCHECK(navigation_handle->IsInMainFrame()); collected_main_navigation_start_metric_ = true; startup_metric_utils::RecordFirstWebContentsMainNavigationStart( base::TimeTicks::Now()); } void FirstWebContentsProfiler::DidFinishNavigation( content::NavigationHandle* navigation_handle) { if (collected_main_navigation_finished_metric_) { // Abandon profiling on a top-level navigation to a different page as it: // (1) is no longer a fair timing; and // (2) can cause http://crbug.com/525209 where one of the timing // heuristics (e.g. first paint) didn't fire for the initial content // but fires after a lot of idle time when the user finally navigates // to another page that does trigger it. if (navigation_handle->IsInMainFrame() && navigation_handle->HasCommitted() && !navigation_handle->IsSamePage()) { FinishedCollectingMetrics(FinishReason::ABANDON_NEW_NAVIGATION); } return; } if (startup_metric_utils::WasNonBrowserUIDisplayed()) { FinishedCollectingMetrics(FinishReason::ABANDON_BLOCKING_UI); return; } // The first navigation has to be the main frame's. DCHECK(navigation_handle->IsInMainFrame()); if (!navigation_handle->HasCommitted() || navigation_handle->IsErrorPage()) { FinishedCollectingMetrics(FinishReason::ABANDON_NAVIGATION_ERROR); return; } collected_main_navigation_finished_metric_ = true; startup_metric_utils::RecordFirstWebContentsMainNavigationFinished( base::TimeTicks::Now()); } void FirstWebContentsProfiler::WasHidden() { // Stop profiling if the content gets hidden as its load may be deprioritized // and timing it becomes meaningless. FinishedCollectingMetrics(FinishReason::ABANDON_CONTENT_HIDDEN); } void FirstWebContentsProfiler::WebContentsDestroyed() { FinishedCollectingMetrics(FinishReason::ABANDON_CONTENT_DESTROYED); } bool FirstWebContentsProfiler::IsFinishedCollectingMetrics() { return collected_paint_metric_ && collected_load_metric_; } void FirstWebContentsProfiler::FinishedCollectingMetrics( FinishReason finish_reason) { UMA_HISTOGRAM_ENUMERATION("Startup.FirstWebContents.FinishReason", finish_reason, FinishReason::ENUM_MAX); if (!collected_paint_metric_) { UMA_HISTOGRAM_ENUMERATION("Startup.FirstWebContents.FinishReason_NoPaint", finish_reason, FinishReason::ENUM_MAX); } if (!collected_load_metric_) { UMA_HISTOGRAM_ENUMERATION("Startup.FirstWebContents.FinishReason_NoLoad", finish_reason, FinishReason::ENUM_MAX); } delete this; } #endif // !defined(OS_ANDROID)