| Index: chrome/browser/page_load_metrics/metrics_web_contents_observer_unittest.cc
|
| diff --git a/chrome/browser/page_load_metrics/metrics_web_contents_observer_unittest.cc b/chrome/browser/page_load_metrics/metrics_web_contents_observer_unittest.cc
|
| index 8951471af54af426f030b319af0a22148a64d840..e14d7dcad4b9e2c6b4957a7da3dde623ae5c39b2 100644
|
| --- a/chrome/browser/page_load_metrics/metrics_web_contents_observer_unittest.cc
|
| +++ b/chrome/browser/page_load_metrics/metrics_web_contents_observer_unittest.cc
|
| @@ -9,12 +9,15 @@
|
|
|
| #include "base/macros.h"
|
| #include "base/memory/ptr_util.h"
|
| +#include "base/memory/weak_ptr.h"
|
| #include "base/process/kill.h"
|
| #include "base/test/histogram_tester.h"
|
| #include "base/time/time.h"
|
| +#include "base/timer/mock_timer.h"
|
| #include "chrome/browser/page_load_metrics/metrics_navigation_throttle.h"
|
| #include "chrome/browser/page_load_metrics/page_load_metrics_embedder_interface.h"
|
| #include "chrome/browser/page_load_metrics/page_load_metrics_observer.h"
|
| +#include "chrome/browser/page_load_metrics/page_load_metrics_update_dispatcher.h"
|
| #include "chrome/browser/page_load_metrics/page_load_tracker.h"
|
| #include "chrome/common/page_load_metrics/page_load_metrics_messages.h"
|
| #include "chrome/common/url_constants.h"
|
| @@ -36,6 +39,13 @@ const char kDefaultTestUrl2[] = "https://whatever.com/";
|
| const char kFilteredStartUrl[] = "https://whatever.com/ignore-on-start";
|
| const char kFilteredCommitUrl[] = "https://whatever.com/ignore-on-commit";
|
|
|
| +class WeakMockTimer : public base::MockTimer,
|
| + public base::SupportsWeakPtr<WeakMockTimer> {
|
| + public:
|
| + WeakMockTimer()
|
| + : MockTimer(false /* retain_user_task */, false /* is_repeating */) {}
|
| +};
|
| +
|
| // Simple PageLoadMetricsObserver that copies observed PageLoadTimings into the
|
| // provided std::vector, so they can be analyzed by unit tests.
|
| class TestPageLoadMetricsObserver : public PageLoadMetricsObserver {
|
| @@ -129,6 +139,11 @@ class TestPageLoadMetricsEmbedderInterface
|
| tracker->AddObserver(base::MakeUnique<FilteringPageLoadMetricsObserver>(
|
| &completed_filtered_urls_));
|
| }
|
| + std::unique_ptr<base::Timer> CreateTimer() override {
|
| + std::unique_ptr<WeakMockTimer> timer = base::MakeUnique<WeakMockTimer>();
|
| + most_recent_timer_ = timer->AsWeakPtr();
|
| + return std::move(timer);
|
| + }
|
| const std::vector<mojom::PageLoadTimingPtr>& updated_timings() const {
|
| return updated_timings_;
|
| }
|
| @@ -149,6 +164,9 @@ class TestPageLoadMetricsEmbedderInterface
|
| const std::vector<GURL>& completed_filtered_urls() const {
|
| return completed_filtered_urls_;
|
| }
|
| + base::MockTimer* GetMostRecentTimer() const {
|
| + return most_recent_timer_.get();
|
| + }
|
|
|
| private:
|
| std::vector<mojom::PageLoadTimingPtr> updated_timings_;
|
| @@ -156,9 +174,18 @@ class TestPageLoadMetricsEmbedderInterface
|
| std::vector<mojom::PageLoadTimingPtr> complete_timings_;
|
| std::vector<GURL> observed_committed_urls_;
|
| std::vector<GURL> completed_filtered_urls_;
|
| + base::WeakPtr<WeakMockTimer> most_recent_timer_;
|
| bool is_ntp_;
|
| };
|
|
|
| +void PopulatePageLoadTiming(mojom::PageLoadTiming* timing) {
|
| + page_load_metrics::InitPageLoadTimingForTest(timing);
|
| + timing->navigation_start = base::Time::FromDoubleT(1);
|
| + timing->response_start = base::TimeDelta::FromMilliseconds(10);
|
| + timing->parse_timing->parse_start = base::TimeDelta::FromMilliseconds(20);
|
| + timing->document_timing->first_layout = base::TimeDelta::FromMilliseconds(30);
|
| +}
|
| +
|
| } // namespace
|
|
|
| class MetricsWebContentsObserverTest : public ChromeRenderViewHostTestHarness {
|
| @@ -175,12 +202,30 @@ class MetricsWebContentsObserverTest : public ChromeRenderViewHostTestHarness {
|
| ->NavigateAndCommit(GURL(url::kAboutBlankURL));
|
| }
|
|
|
| + // Returns the mock timer used for buffering updates in the
|
| + // PageLoadMetricsUpdateDispatcher.
|
| + base::MockTimer* GetMostRecentTimer() {
|
| + return embedder_interface_->GetMostRecentTimer();
|
| + }
|
| +
|
| void SimulateTimingUpdate(const mojom::PageLoadTiming& timing) {
|
| SimulateTimingUpdate(timing, web_contents()->GetMainFrame());
|
| }
|
|
|
| void SimulateTimingUpdate(const mojom::PageLoadTiming& timing,
|
| content::RenderFrameHost* render_frame_host) {
|
| + SimulateTimingUpdateWithoutFiringDispatchTimer(timing, render_frame_host);
|
| + // If sending the timing update caused the PageLoadMetricsUpdateDispatcher
|
| + // to schedule a buffering timer, then fire it now so metrics are dispatched
|
| + // to observers.
|
| + base::MockTimer* mock_timer = GetMostRecentTimer();
|
| + if (mock_timer && mock_timer->IsRunning())
|
| + mock_timer->Fire();
|
| + }
|
| +
|
| + void SimulateTimingUpdateWithoutFiringDispatchTimer(
|
| + const mojom::PageLoadTiming& timing,
|
| + content::RenderFrameHost* render_frame_host) {
|
| observer()->OnTimingUpdated(render_frame_host, timing,
|
| mojom::PageLoadMetadata());
|
| }
|
| @@ -735,13 +780,7 @@ TEST_F(MetricsWebContentsObserverTest, OutOfOrderCrossFrameTiming) {
|
|
|
| // Dispatch a timing update for the child frame that includes a first paint.
|
| mojom::PageLoadTiming subframe_timing;
|
| - page_load_metrics::InitPageLoadTimingForTest(&subframe_timing);
|
| - subframe_timing.navigation_start = base::Time::FromDoubleT(2);
|
| - subframe_timing.response_start = base::TimeDelta::FromMilliseconds(10);
|
| - subframe_timing.parse_timing->parse_start =
|
| - base::TimeDelta::FromMilliseconds(20);
|
| - subframe_timing.document_timing->first_layout =
|
| - base::TimeDelta::FromMilliseconds(30);
|
| + PopulatePageLoadTiming(&subframe_timing);
|
| subframe_timing.paint_timing->first_paint =
|
| base::TimeDelta::FromMilliseconds(40);
|
| content::RenderFrameHostTester* subframe_tester =
|
| @@ -797,4 +836,100 @@ TEST_F(MetricsWebContentsObserverTest, OutOfOrderCrossFrameTiming) {
|
| CheckNoErrorEvents();
|
| }
|
|
|
| +// We buffer cross-frame paint updates to account for paint timings from
|
| +// different frames arriving out of order.
|
| +TEST_F(MetricsWebContentsObserverTest, OutOfOrderCrossFrameTiming2) {
|
| + // Dispatch a timing update for the main frame that includes a first
|
| + // paint. This should be buffered, with the dispatch timer running.
|
| + mojom::PageLoadTiming timing;
|
| + PopulatePageLoadTiming(&timing);
|
| + timing.paint_timing->first_paint = base::TimeDelta::FromMilliseconds(1000);
|
| + content::WebContentsTester* web_contents_tester =
|
| + content::WebContentsTester::For(web_contents());
|
| + web_contents_tester->NavigateAndCommit(GURL(kDefaultTestUrl));
|
| + SimulateTimingUpdateWithoutFiringDispatchTimer(timing, main_rfh());
|
| +
|
| + EXPECT_TRUE(GetMostRecentTimer()->IsRunning());
|
| + ASSERT_EQ(0, CountUpdatedTimingReported());
|
| +
|
| + content::RenderFrameHostTester* rfh_tester =
|
| + content::RenderFrameHostTester::For(main_rfh());
|
| +
|
| + // Dispatch a timing update for a child frame that includes a first paint.
|
| + mojom::PageLoadTiming subframe_timing;
|
| + PopulatePageLoadTiming(&subframe_timing);
|
| + subframe_timing.paint_timing->first_paint =
|
| + base::TimeDelta::FromMilliseconds(500);
|
| + content::RenderFrameHost* subframe = rfh_tester->AppendChild("subframe");
|
| + content::RenderFrameHostTester* subframe_tester =
|
| + content::RenderFrameHostTester::For(subframe);
|
| + subframe_tester->SimulateNavigationStart(GURL(kDefaultTestUrl2));
|
| + subframe_tester->SimulateNavigationCommit(GURL(kDefaultTestUrl2));
|
| + SimulateTimingUpdateWithoutFiringDispatchTimer(subframe_timing, subframe);
|
| + subframe_tester->SimulateNavigationStop();
|
| +
|
| + histogram_tester_.ExpectTotalCount(
|
| + page_load_metrics::internal::kHistogramOutOfOrderTiming, 1);
|
| +
|
| + EXPECT_TRUE(GetMostRecentTimer()->IsRunning());
|
| + ASSERT_EQ(0, CountUpdatedTimingReported());
|
| +
|
| + // At this point, the timing update is buffered, waiting for the timer to
|
| + // fire.
|
| + GetMostRecentTimer()->Fire();
|
| +
|
| + // Firing the timer should produce a timing update. The update should be a
|
| + // merged view of the main frame timing, with a first paint timestamp from the
|
| + // subframe.
|
| + ASSERT_EQ(1, CountUpdatedTimingReported());
|
| + EXPECT_FALSE(timing.Equals(*updated_timings().back()));
|
| + EXPECT_TRUE(
|
| + updated_timings().back()->parse_timing->Equals(*timing.parse_timing));
|
| + EXPECT_TRUE(updated_timings().back()->document_timing->Equals(
|
| + *timing.document_timing));
|
| + EXPECT_FALSE(
|
| + updated_timings().back()->paint_timing->Equals(*timing.paint_timing));
|
| + EXPECT_TRUE(updated_timings().back()->paint_timing->first_paint);
|
| +
|
| + // The first paint value should be the min of all received first paints, wich
|
| + // in this case is the first paint from the subframe. Since it is offset by
|
| + // the subframe's navigation start, the received value should be >= the first
|
| + // paint value specified in the subframe.
|
| + EXPECT_GE(updated_timings().back()->paint_timing->first_paint,
|
| + subframe_timing.paint_timing->first_paint);
|
| + EXPECT_LT(updated_timings().back()->paint_timing->first_paint,
|
| + timing.paint_timing->first_paint);
|
| +
|
| + base::TimeDelta initial_first_paint =
|
| + updated_timings().back()->paint_timing->first_paint.value();
|
| +
|
| + // Dispatch a timing update for an additional child frame, with an earlier
|
| + // first paint time. This should cause an immediate update, without a timer
|
| + // delay.
|
| + subframe_timing.paint_timing->first_paint =
|
| + base::TimeDelta::FromMilliseconds(50);
|
| + content::RenderFrameHost* subframe2 = rfh_tester->AppendChild("subframe");
|
| + content::RenderFrameHostTester* subframe2_tester =
|
| + content::RenderFrameHostTester::For(subframe2);
|
| + subframe2_tester->SimulateNavigationStart(GURL(kDefaultTestUrl2));
|
| + subframe2_tester->SimulateNavigationCommit(GURL(kDefaultTestUrl2));
|
| + SimulateTimingUpdateWithoutFiringDispatchTimer(subframe_timing, subframe2);
|
| + subframe2_tester->SimulateNavigationStop();
|
| +
|
| + base::TimeDelta updated_first_paint =
|
| + updated_timings().back()->paint_timing->first_paint.value();
|
| +
|
| + EXPECT_FALSE(GetMostRecentTimer()->IsRunning());
|
| + ASSERT_EQ(2, CountUpdatedTimingReported());
|
| + EXPECT_LT(updated_first_paint, initial_first_paint);
|
| +
|
| + histogram_tester_.ExpectTotalCount(
|
| + page_load_metrics::internal::kHistogramOutOfOrderTimingBuffered, 1);
|
| + histogram_tester_.ExpectBucketCount(
|
| + page_load_metrics::internal::kHistogramOutOfOrderTimingBuffered,
|
| + (initial_first_paint - updated_first_paint).InMilliseconds(), 1);
|
| +
|
| + CheckNoErrorEvents();
|
| +}
|
| +
|
| } // namespace page_load_metrics
|
|
|