chromium/chrome/browser/page_load_metrics/observers/android_page_load_metrics_observer_unittest.cc

// Copyright 2017 The Chromium Authors
// 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/observers/android_page_load_metrics_observer.h"

#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/time/time.h"
#include "chrome/browser/page_load_metrics/observers/page_load_metrics_observer_test_harness.h"
#include "chrome/browser/profiles/profile.h"
#include "components/page_load_metrics/browser/page_load_tracker.h"
#include "components/page_load_metrics/common/test/page_load_metrics_test_util.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/navigation_simulator.h"
#include "net/base/ip_endpoint.h"
#include "services/network/public/cpp/network_quality_tracker.h"
#include "testing/gmock/include/gmock/gmock.h"

using testing::AnyNumber;
using testing::Return;

class MockNetworkQualityTracker : public network::NetworkQualityTracker {
 public:
  MOCK_CONST_METHOD0(GetEffectiveConnectionType,
                     net::EffectiveConnectionType());
  MOCK_CONST_METHOD0(GetHttpRTT, base::TimeDelta());
  MOCK_CONST_METHOD0(GetTransportRTT, base::TimeDelta());
  MOCK_CONST_METHOD0(GetDownstreamThroughputKbps, int32_t());
};

class TestAndroidPageLoadMetricsObserver
    : public AndroidPageLoadMetricsObserver {
 public:
  TestAndroidPageLoadMetricsObserver(
      network::NetworkQualityTracker* network_quality_tracker)
      : AndroidPageLoadMetricsObserver(network_quality_tracker) {}

  net::EffectiveConnectionType reported_connection_type() const {
    return reported_connection_type_;
  }

  int64_t reported_http_rtt_ms() const { return reported_http_rtt_ms_; }

  int64_t reported_transport_rtt_ms() const {
    return reported_transport_rtt_ms_;
  }

  int64_t reported_first_contentful_paint_ms() const {
    return reported_first_contentful_paint_ms_;
  }

  base::TimeTicks reported_navigation_start_tick_fcp() const {
    return reported_navigation_start_tick_fcp_;
  }

  base::TimeTicks reported_navigation_start_tick_load() const {
    return reported_navigation_start_tick_load_;
  }

  int64_t reported_load_event_start_ms() const {
    return reported_load_event_start_ms_;
  }

  int64_t reported_dns_start_ms() const { return reported_dns_start_ms_; }

 protected:
  void ReportNetworkQualityEstimate(
      net::EffectiveConnectionType connection_type,
      int64_t http_rtt_ms,
      int64_t transport_rtt_ms) override {
    reported_connection_type_ = connection_type;
    reported_http_rtt_ms_ = http_rtt_ms;
    reported_transport_rtt_ms_ = transport_rtt_ms;
  }

  void ReportFirstContentfulPaint(
      base::TimeTicks navigation_start_tick,
      base::TimeDelta first_contentful_paint) override {
    reported_navigation_start_tick_fcp_ = navigation_start_tick;
    reported_first_contentful_paint_ms_ =
        first_contentful_paint.InMilliseconds();
  }

  void ReportLoadEventStart(base::TimeTicks navigation_start_tick,
                            base::TimeDelta load_event_start) override {
    reported_navigation_start_tick_load_ = navigation_start_tick;
    reported_load_event_start_ms_ = load_event_start.InMilliseconds();
  }

  void ReportLoadedMainResource(int64_t dns_start_ms,
                                int64_t dns_end_ms,
                                int64_t connect_start_ms,
                                int64_t connect_end_ms,
                                int64_t request_start_ms,
                                int64_t send_start_ms,
                                int64_t send_end_ms) override {
    reported_dns_start_ms_ = dns_start_ms;
  }

 private:
  net::EffectiveConnectionType reported_connection_type_ =
      net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN;
  int64_t reported_http_rtt_ms_ = 0;
  int64_t reported_transport_rtt_ms_ = 0;
  int64_t reported_first_contentful_paint_ms_ = 0;
  base::TimeTicks reported_navigation_start_tick_fcp_;
  base::TimeTicks reported_navigation_start_tick_load_;
  int64_t reported_load_event_start_ms_ = 0;
  int64_t reported_dns_start_ms_ = 0;
};

class AndroidPageLoadMetricsObserverTest
    : public page_load_metrics::PageLoadMetricsObserverTestHarness {
 public:
  AndroidPageLoadMetricsObserverTest() {}

  void SetUp() override {
    PageLoadMetricsObserverTestHarness::SetUp();
    // Save observer_ptr_ so we can query for test results, while the
    // PageLoadTracker owns it.
    observer_ptr_ =
        new TestAndroidPageLoadMetricsObserver(&mock_network_quality_tracker_);
    observer_ = base::WrapUnique<page_load_metrics::PageLoadMetricsObserver>(
        observer_ptr_.get());
  }

  TestAndroidPageLoadMetricsObserver* observer() const { return observer_ptr_; }

  base::TimeTicks GetNavigationStart() const { return navigation_start_; }

  void SetNetworkQualityMock() {
    EXPECT_CALL(mock_network_quality_tracker(), GetEffectiveConnectionType())
        .Times(AnyNumber())
        .WillRepeatedly(Return(net::EFFECTIVE_CONNECTION_TYPE_3G));
    EXPECT_CALL(mock_network_quality_tracker(), GetHttpRTT())
        .Times(AnyNumber())
        .WillRepeatedly(Return(base::Milliseconds(3)));
    EXPECT_CALL(mock_network_quality_tracker(), GetTransportRTT())
        .Times(AnyNumber())
        .WillRepeatedly(Return(base::Milliseconds(4)));
  }

  MockNetworkQualityTracker& mock_network_quality_tracker() {
    return mock_network_quality_tracker_;
  }

  void RegisterObservers(page_load_metrics::PageLoadTracker* tracker) override {
    navigation_start_ = tracker->navigation_start();
    tracker->AddObserver(std::move(observer_));
  }

 private:
  std::unique_ptr<page_load_metrics::PageLoadMetricsObserver> observer_;
  raw_ptr<TestAndroidPageLoadMetricsObserver> observer_ptr_;
  MockNetworkQualityTracker mock_network_quality_tracker_;
  base::TimeTicks navigation_start_;
};

TEST_F(AndroidPageLoadMetricsObserverTest, NetworkQualityEstimate) {
  SetNetworkQualityMock();
  NavigateAndCommit(GURL("https://www.example.com"));
  EXPECT_EQ(net::EFFECTIVE_CONNECTION_TYPE_3G,
            observer()->reported_connection_type());
  EXPECT_EQ(3L, observer()->reported_http_rtt_ms());
  EXPECT_EQ(4L, observer()->reported_transport_rtt_ms());
}

TEST_F(AndroidPageLoadMetricsObserverTest, MissingNetworkQualityEstimate) {
  EXPECT_CALL(mock_network_quality_tracker(), GetEffectiveConnectionType())
      .Times(AnyNumber())
      .WillRepeatedly(Return(net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN));
  EXPECT_CALL(mock_network_quality_tracker(), GetHttpRTT())
      .Times(AnyNumber())
      .WillRepeatedly(Return(base::TimeDelta()));
  EXPECT_CALL(mock_network_quality_tracker(), GetTransportRTT())
      .Times(AnyNumber())
      .WillRepeatedly(Return(base::TimeDelta()));
  NavigateAndCommit(GURL("https://www.example.com"));
  EXPECT_EQ(net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN,
            observer()->reported_connection_type());
  EXPECT_EQ(0L, observer()->reported_http_rtt_ms());
  EXPECT_EQ(0L, observer()->reported_transport_rtt_ms());
}

TEST_F(AndroidPageLoadMetricsObserverTest, LoadTimingInfo) {
  SetNetworkQualityMock();
  auto navigation_simulator =
      content::NavigationSimulator::CreateRendererInitiated(
          GURL("https://www.example.com"),
          web_contents()->GetPrimaryMainFrame());
  navigation_simulator->Start();
  int frame_tree_node_id =
      navigation_simulator->GetNavigationHandle()->GetFrameTreeNodeId();
  navigation_simulator->Commit();

  auto load_timing_info = std::make_unique<net::LoadTimingInfo>();
  const base::TimeTicks kNow = base::TimeTicks::Now();
  load_timing_info->connect_timing.domain_lookup_start = kNow;
  page_load_metrics::ExtraRequestCompleteInfo info(
      url::SchemeHostPort(GURL("https://ignored.com")), net::IPEndPoint(),
      frame_tree_node_id, false, /* cached */
      10 * 1024 /* size */, 0 /* original_network_content_length */,
      network::mojom::RequestDestination::kDocument, 0,
      std::move(load_timing_info));
  tester()->SimulateLoadedResource(info,
                                   navigation_simulator->GetGlobalRequestID());
  EXPECT_EQ(kNow.since_origin().InMilliseconds(),
            observer()->reported_dns_start_ms());
}

TEST_F(AndroidPageLoadMetricsObserverTest, LoadEvents) {
  SetNetworkQualityMock();
  page_load_metrics::mojom::PageLoadTiming timing;
  page_load_metrics::InitPageLoadTimingForTest(&timing);
  // Note this navigation start does not effect the start that is reported to
  // us.
  timing.navigation_start = base::Time::FromSecondsSinceUnixEpoch(1);
  timing.document_timing->load_event_start = base::Milliseconds(30);
  timing.parse_timing->parse_start = base::Milliseconds(20);
  timing.paint_timing->first_contentful_paint = base::Milliseconds(20);
  PopulateRequiredTimingFields(&timing);
  NavigateAndCommit(GURL("https://www.example.com"));
  tester()->SimulateTimingUpdate(timing);
  EXPECT_EQ(30, observer()->reported_load_event_start_ms());
  EXPECT_EQ(GetNavigationStart(),
            observer()->reported_navigation_start_tick_load());
  EXPECT_EQ(20, observer()->reported_first_contentful_paint_ms());
  EXPECT_EQ(GetNavigationStart(),
            observer()->reported_navigation_start_tick_fcp());
}