blob: 9943ed9e447b480f81ea39f4e4e88f6e695a9b89 [file] [log] [blame]
// Copyright 2018 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 "chrome/browser/page_load_metrics/page_load_metrics_test_waiter.h"
#include "base/logging.h"
#include "chrome/browser/page_load_metrics/page_load_metrics_observer.h"
#include "chrome/common/page_load_metrics/page_load_metrics.mojom.h"
#include "content/public/common/resource_type.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace page_load_metrics {
PageLoadMetricsTestWaiter::PageLoadMetricsTestWaiter(
content::WebContents* web_contents)
: TestingObserver(web_contents), weak_factory_(this) {}
PageLoadMetricsTestWaiter::~PageLoadMetricsTestWaiter() {
CHECK(did_add_observer_);
CHECK_EQ(nullptr, run_loop_.get());
}
void PageLoadMetricsTestWaiter::AddPageExpectation(TimingField field) {
page_expected_fields_.Set(field);
if (field == TimingField::kLoadTimingInfo) {
attach_on_tracker_creation_ = true;
}
}
void PageLoadMetricsTestWaiter::AddSubFrameExpectation(TimingField field) {
CHECK_NE(field, TimingField::kLoadTimingInfo)
<< "LOAD_TIMING_INFO should only be used as a page-level expectation";
subframe_expected_fields_.Set(field);
// If the given field is also a page-level field, then add a page-level
// expectation as well
if (IsPageLevelField(field))
page_expected_fields_.Set(field);
}
void PageLoadMetricsTestWaiter::AddWebFeatureExpectation(
blink::mojom::WebFeature web_feature) {
size_t feature_idx = static_cast<size_t>(web_feature);
if (!expected_web_features_.test(feature_idx)) {
expected_web_features_.set(feature_idx);
}
}
void PageLoadMetricsTestWaiter::AddSubframeNavigationExpectation(
size_t expected_subframe_navigations) {
expected_subframe_navigations_ = expected_subframe_navigations;
}
void PageLoadMetricsTestWaiter::AddMinimumCompleteResourcesExpectation(
int expected_minimum_complete_resources) {
expected_minimum_complete_resources_ = expected_minimum_complete_resources;
}
void PageLoadMetricsTestWaiter::AddMinimumNetworkBytesExpectation(
int expected_minimum_network_bytes) {
expected_minimum_network_bytes_ = expected_minimum_network_bytes;
}
bool PageLoadMetricsTestWaiter::DidObserveInPage(TimingField field) const {
return observed_page_fields_.IsSet(field);
}
void PageLoadMetricsTestWaiter::Wait() {
if (ExpectationsSatisfied())
return;
run_loop_ = std::make_unique<base::RunLoop>();
run_loop_->Run();
run_loop_ = nullptr;
EXPECT_TRUE(ExpectationsSatisfied());
}
void PageLoadMetricsTestWaiter::OnTimingUpdated(
content::RenderFrameHost* subframe_rfh,
const page_load_metrics::mojom::PageLoadTiming& timing,
const page_load_metrics::PageLoadExtraInfo& extra_info) {
if (ExpectationsSatisfied())
return;
const page_load_metrics::mojom::PageLoadMetadata& metadata =
subframe_rfh ? extra_info.subframe_metadata
: extra_info.main_frame_metadata;
TimingFieldBitSet matched_bits = GetMatchedBits(timing, metadata);
if (subframe_rfh) {
subframe_expected_fields_.ClearMatching(matched_bits);
} else {
page_expected_fields_.ClearMatching(matched_bits);
observed_page_fields_.Merge(matched_bits);
}
if (ExpectationsSatisfied() && run_loop_)
run_loop_->Quit();
}
void PageLoadMetricsTestWaiter::OnLoadedResource(
const page_load_metrics::ExtraRequestCompleteInfo&
extra_request_complete_info) {
if (ExpectationsSatisfied())
return;
if (extra_request_complete_info.resource_type !=
content::RESOURCE_TYPE_MAIN_FRAME) {
// The waiter confirms loading timing for the main frame only.
return;
}
if (!extra_request_complete_info.load_timing_info->send_start.is_null() &&
!extra_request_complete_info.load_timing_info->send_end.is_null() &&
!extra_request_complete_info.load_timing_info->request_start.is_null()) {
page_expected_fields_.Clear(TimingField::kLoadTimingInfo);
observed_page_fields_.Set(TimingField::kLoadTimingInfo);
}
if (ExpectationsSatisfied() && run_loop_)
run_loop_->Quit();
}
void PageLoadMetricsTestWaiter::OnResourceDataUseObserved(
FrameTreeNodeId frame_tree_node_id,
const std::vector<page_load_metrics::mojom::ResourceDataUpdatePtr>&
resources) {
for (auto const& resource : resources) {
auto it = page_resources_.find(resource->request_id);
if (it != page_resources_.end()) {
it->second = resource.Clone();
} else {
page_resources_.emplace(std::piecewise_construct,
std::forward_as_tuple(resource->request_id),
std::forward_as_tuple(resource->Clone()));
}
if (resource->is_complete) {
current_complete_resources_++;
if (!resource->was_fetched_via_cache)
current_network_body_bytes_ += resource->encoded_body_length;
}
current_network_bytes_ += resource->delta_bytes;
}
if (ExpectationsSatisfied() && run_loop_)
run_loop_->Quit();
}
void PageLoadMetricsTestWaiter::OnFeaturesUsageObserved(
content::RenderFrameHost* rfh,
const mojom::PageLoadFeatures& features,
const PageLoadExtraInfo& extra_info) {
if (WebFeaturesExpectationsSatisfied())
return;
for (blink::mojom::WebFeature feature : features.features) {
size_t feature_idx = static_cast<size_t>(feature);
if (observed_web_features_.test(feature_idx))
continue;
observed_web_features_.set(feature_idx);
}
if (ExpectationsSatisfied() && run_loop_)
run_loop_->Quit();
}
void PageLoadMetricsTestWaiter::OnDidFinishSubFrameNavigation(
content::NavigationHandle* navigation_handle) {
if (SubframeNavigationExpectationsSatisfied())
return;
++observed_subframe_navigations_;
if (ExpectationsSatisfied() && run_loop_)
run_loop_->Quit();
}
bool PageLoadMetricsTestWaiter::IsPageLevelField(TimingField field) {
switch (field) {
case TimingField::kFirstPaint:
case TimingField::kFirstContentfulPaint:
case TimingField::kFirstMeaningfulPaint:
return true;
default:
return false;
}
}
PageLoadMetricsTestWaiter::TimingFieldBitSet
PageLoadMetricsTestWaiter::GetMatchedBits(
const page_load_metrics::mojom::PageLoadTiming& timing,
const page_load_metrics::mojom::PageLoadMetadata& metadata) {
PageLoadMetricsTestWaiter::TimingFieldBitSet matched_bits;
if (timing.document_timing->first_layout)
matched_bits.Set(TimingField::kFirstLayout);
if (timing.document_timing->load_event_start)
matched_bits.Set(TimingField::kLoadEvent);
if (timing.paint_timing->first_paint)
matched_bits.Set(TimingField::kFirstPaint);
if (timing.paint_timing->first_contentful_paint)
matched_bits.Set(TimingField::kFirstContentfulPaint);
if (timing.paint_timing->first_meaningful_paint)
matched_bits.Set(TimingField::kFirstMeaningfulPaint);
if (metadata.behavior_flags & blink::WebLoadingBehaviorFlag::
kWebLoadingBehaviorDocumentWriteBlockReload)
matched_bits.Set(TimingField::kDocumentWriteBlockReload);
return matched_bits;
}
void PageLoadMetricsTestWaiter::OnTrackerCreated(
page_load_metrics::PageLoadTracker* tracker) {
if (!attach_on_tracker_creation_)
return;
// A PageLoadMetricsWaiter should only wait for events from a single page
// load.
ASSERT_FALSE(did_add_observer_);
tracker->AddObserver(
std::make_unique<WaiterMetricsObserver>(weak_factory_.GetWeakPtr()));
did_add_observer_ = true;
}
void PageLoadMetricsTestWaiter::OnCommit(
page_load_metrics::PageLoadTracker* tracker) {
if (attach_on_tracker_creation_)
return;
// A PageLoadMetricsWaiter should only wait for events from a single page
// load.
ASSERT_FALSE(did_add_observer_);
tracker->AddObserver(
std::make_unique<WaiterMetricsObserver>(weak_factory_.GetWeakPtr()));
did_add_observer_ = true;
}
bool PageLoadMetricsTestWaiter::ResourceUseExpectationsSatisfied() const {
return (expected_minimum_complete_resources_ == 0 ||
current_complete_resources_ >=
expected_minimum_complete_resources_) &&
(expected_minimum_network_bytes_ == 0 ||
current_network_bytes_ >= expected_minimum_network_bytes_);
}
bool PageLoadMetricsTestWaiter::WebFeaturesExpectationsSatisfied() const {
// We are only interested to see if all features being set in
// |expected_web_features_| are observed, but don't care about whether extra
// features are observed.
return (expected_web_features_ & observed_web_features_ ^
expected_web_features_)
.none();
}
bool PageLoadMetricsTestWaiter::SubframeNavigationExpectationsSatisfied()
const {
return observed_subframe_navigations_ >= expected_subframe_navigations_;
}
bool PageLoadMetricsTestWaiter::ExpectationsSatisfied() const {
return subframe_expected_fields_.Empty() && page_expected_fields_.Empty() &&
ResourceUseExpectationsSatisfied() &&
WebFeaturesExpectationsSatisfied() &&
SubframeNavigationExpectationsSatisfied();
}
PageLoadMetricsTestWaiter::WaiterMetricsObserver::~WaiterMetricsObserver() {}
PageLoadMetricsTestWaiter::WaiterMetricsObserver::WaiterMetricsObserver(
base::WeakPtr<PageLoadMetricsTestWaiter> waiter)
: waiter_(waiter) {}
void PageLoadMetricsTestWaiter::WaiterMetricsObserver::OnTimingUpdate(
content::RenderFrameHost* subframe_rfh,
const page_load_metrics::mojom::PageLoadTiming& timing,
const page_load_metrics::PageLoadExtraInfo& extra_info) {
if (waiter_)
waiter_->OnTimingUpdated(subframe_rfh, timing, extra_info);
}
void PageLoadMetricsTestWaiter::WaiterMetricsObserver::OnLoadedResource(
const page_load_metrics::ExtraRequestCompleteInfo&
extra_request_complete_info) {
if (waiter_)
waiter_->OnLoadedResource(extra_request_complete_info);
}
void PageLoadMetricsTestWaiter::WaiterMetricsObserver::
OnResourceDataUseObserved(
FrameTreeNodeId frame_tree_node_id,
const std::vector<page_load_metrics::mojom::ResourceDataUpdatePtr>&
resources) {
if (waiter_)
waiter_->OnResourceDataUseObserved(frame_tree_node_id, resources);
}
void PageLoadMetricsTestWaiter::WaiterMetricsObserver::OnFeaturesUsageObserved(
content::RenderFrameHost* rfh,
const mojom::PageLoadFeatures& features,
const PageLoadExtraInfo& extra_info) {
if (waiter_)
waiter_->OnFeaturesUsageObserved(nullptr, features, extra_info);
}
void PageLoadMetricsTestWaiter::WaiterMetricsObserver::
OnDidFinishSubFrameNavigation(
content::NavigationHandle* navigation_handle) {
if (waiter_)
waiter_->OnDidFinishSubFrameNavigation(navigation_handle);
}
} // namespace page_load_metrics