blob: 8dc0cd4eb6cf31f61e6ac1c573a5f2a9e3f4ffde [file] [log] [blame]
// Copyright 2017 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 <string>
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/strings/strcat.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "build/build_config.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/subresource_filter/subresource_filter_browser_test_harness.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/heavy_ad_intervention/heavy_ad_features.h"
#include "components/page_load_metrics/browser/ads_page_load_metrics_test_waiter.h"
#include "components/page_load_metrics/browser/observers/ad_metrics/ad_intervention_browser_test_utils.h"
#include "components/page_load_metrics/browser/observers/ad_metrics/ads_page_load_metrics_observer.h"
#include "components/page_load_metrics/browser/observers/ad_metrics/frame_tree_data.h"
#include "components/page_load_metrics/browser/observers/use_counter_page_load_metrics_observer.h"
#include "components/page_load_metrics/browser/page_load_metrics_memory_tracker.h"
#include "components/page_load_metrics/browser/page_load_metrics_test_waiter.h"
#include "components/performance_manager/public/v8_memory/v8_detailed_memory.h"
#include "components/subresource_filter/content/browser/content_subresource_filter_throttle_manager.h"
#include "components/subresource_filter/content/browser/ruleset_service.h"
#include "components/subresource_filter/core/browser/subresource_filter_features.h"
#include "components/subresource_filter/core/common/activation_scope.h"
#include "components/subresource_filter/core/common/common_features.h"
#include "components/subresource_filter/core/common/test_ruleset_utils.h"
#include "components/subresource_filter/core/mojom/subresource_filter.mojom.h"
#include "components/ukm/test_ukm_recorder.h"
#include "content/public/browser/back_forward_cache.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/common/content_features.h"
#include "content/public/common/url_constants.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/test_navigation_observer.h"
#include "media/base/media_switches.h"
#include "net/base/escape.h"
#include "net/base/net_errors.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/controllable_http_response.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "services/metrics/public/cpp/metrics_utils.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "services/metrics/public/cpp/ukm_source.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/mojom/web_feature/web_feature.mojom.h"
#include "ui/gfx/geometry/size.h"
#include "url/gurl.h"
#include "url/url_constants.h"
namespace {
using OriginStatus = page_load_metrics::OriginStatus;
using OriginStatusWithThrottling =
page_load_metrics::OriginStatusWithThrottling;
using FrameTreeNodeId = int;
const char kAdsInterventionRecordedHistogram[] =
"SubresourceFilter.PageLoad.AdsInterventionTriggered";
const char kCrossOriginHistogramId[] =
"PageLoad.Clients.Ads.FrameCounts.AdFrames.PerFrame."
"OriginStatus";
const char kCreativeOriginHistogramId[] =
"PageLoad.Clients.Ads.FrameCounts.AdFrames.PerFrame."
"CreativeOriginStatus";
const char kCreativeOriginWithThrottlingHistogramId[] =
"PageLoad.Clients.Ads.FrameCounts.AdFrames.PerFrame."
"CreativeOriginStatusWithThrottling";
const char kAdUserActivationHistogramId[] =
"PageLoad.Clients.Ads.FrameCounts.AdFrames.PerFrame."
"UserActivation";
const char kPeakWindowdPercentHistogramId[] =
"PageLoad.Clients.Ads.Cpu.FullPage.PeakWindowedPercent2";
const char kHeavyAdInterventionTypeHistogramId[] =
"PageLoad.Clients.Ads.HeavyAds.InterventionType2";
const char kMaxAdDensityByAreaHistogramId[] =
"PageLoad.Clients.Ads.AdDensity.MaxPercentByArea";
const char kMaxAdDensityByHeightHistogramId[] =
"PageLoad.Clients.Ads.AdDensity.MaxPercentByHeight";
const char kMaxAdDensityRecordedHistogramId[] =
"PageLoad.Clients.Ads.AdDensity.Recorded";
const char kMemoryPerFrameMaxHistogramId[] =
"PageLoad.Clients.Ads.Memory.PerFrame.Max";
const char kMemoryAggregateMaxHistogramId[] =
"PageLoad.Clients.Ads.Memory.Aggregate.Max";
const char kMemoryMainFrameMaxHistogramId[] =
"PageLoad.Clients.Ads.Memory.MainFrame.Max";
const char kMemoryUpdateCountHistogramId[] =
"PageLoad.Clients.Ads.Memory.UpdateCount";
} // namespace
class AdsPageLoadMetricsObserverBrowserTest
: public subresource_filter::SubresourceFilterBrowserTest {
public:
AdsPageLoadMetricsObserverBrowserTest()
: subresource_filter::SubresourceFilterBrowserTest() {}
AdsPageLoadMetricsObserverBrowserTest(
const AdsPageLoadMetricsObserverBrowserTest&) = delete;
AdsPageLoadMetricsObserverBrowserTest& operator=(
const AdsPageLoadMetricsObserverBrowserTest&) = delete;
~AdsPageLoadMetricsObserverBrowserTest() override {}
std::unique_ptr<page_load_metrics::PageLoadMetricsTestWaiter>
CreatePageLoadMetricsTestWaiter() {
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
return std::make_unique<page_load_metrics::PageLoadMetricsTestWaiter>(
web_contents);
}
void SetUp() override {
std::vector<base::Feature> enabled = {
subresource_filter::kAdTagging, features::kV8PerFrameMemoryMonitoring};
std::vector<base::Feature> disabled = {};
scoped_feature_list_.InitWithFeatures(enabled, disabled);
subresource_filter::SubresourceFilterBrowserTest::SetUp();
}
void SetUpOnMainThread() override {
SubresourceFilterBrowserTest::SetUpOnMainThread();
SetRulesetWithRules(
{subresource_filter::testing::CreateSuffixRule("ad_iframe_writer.js"),
subresource_filter::testing::CreateSuffixRule("ad_script.js"),
subresource_filter::testing::CreateSuffixRule(
"expensive_animation_frame.html*"),
subresource_filter::testing::CreateSuffixRule("ad.html")});
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
// Test that an embedded ad is same origin.
IN_PROC_BROWSER_TEST_F(AdsPageLoadMetricsObserverBrowserTest,
OriginStatusMetricEmbedded) {
base::HistogramTester histogram_tester;
auto waiter = CreatePageLoadMetricsTestWaiter();
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(),
embedded_test_server()->GetURL("/ads_observer/srcdoc_embedded_ad.html")));
waiter->AddMinimumCompleteResourcesExpectation(4);
waiter->Wait();
ASSERT_TRUE(
ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL)));
histogram_tester.ExpectUniqueSample(
kCrossOriginHistogramId, page_load_metrics::OriginStatus::kSame, 1);
}
// Test that an empty embedded ad isn't reported at all.
// TODO(crbug.com/1226500): This test is flaky.
IN_PROC_BROWSER_TEST_F(AdsPageLoadMetricsObserverBrowserTest,
DISABLED_OriginStatusMetricEmbeddedEmpty) {
base::HistogramTester histogram_tester;
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL(
"/ads_observer/srcdoc_embedded_ad_empty.html")));
ASSERT_TRUE(
ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL)));
histogram_tester.ExpectTotalCount(kCrossOriginHistogramId, 0);
}
// Test that an ad with the same origin as the main page is same origin.
IN_PROC_BROWSER_TEST_F(AdsPageLoadMetricsObserverBrowserTest,
OriginStatusMetricSame) {
// Set the frame's resource as a rule.
SetRulesetWithRules(
{subresource_filter::testing::CreateSuffixRule("pixel.png")});
base::HistogramTester histogram_tester;
ukm::TestAutoSetUkmRecorder ukm_recorder;
auto waiter = CreatePageLoadMetricsTestWaiter();
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(),
embedded_test_server()->GetURL("/ads_observer/same_origin_ad.html")));
waiter->AddMinimumCompleteResourcesExpectation(4);
waiter->Wait();
ASSERT_TRUE(
ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL)));
histogram_tester.ExpectUniqueSample(
kCrossOriginHistogramId, page_load_metrics::OriginStatus::kSame, 1);
auto entries =
ukm_recorder.GetEntriesByName(ukm::builders::AdFrameLoad::kEntryName);
EXPECT_EQ(1u, entries.size());
ukm_recorder.ExpectEntryMetric(
entries.front(), ukm::builders::AdFrameLoad::kStatus_CrossOriginName,
static_cast<int>(page_load_metrics::OriginStatus::kSame));
}
IN_PROC_BROWSER_TEST_F(AdsPageLoadMetricsObserverBrowserTest,
OriginStatusMetricCross) {
// Note: Cannot navigate cross-origin without dynamically generating the URL.
base::HistogramTester histogram_tester;
ukm::TestAutoSetUkmRecorder ukm_recorder;
auto waiter = CreatePageLoadMetricsTestWaiter();
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL("/iframe_blank.html")));
// Note that the initial iframe is not an ad, so the metric doesn't observe
// it initially as same origin. However, on re-navigating to a cross
// origin site that has an ad at its origin, the ad on that page is cross
// origin from the original page.
NavigateIframeToURL(web_contents(), "test",
embedded_test_server()->GetURL(
"a.com", "/ads_observer/same_origin_ad.html"));
// Wait until all resource data updates are sent. Note that there is one more
// than in the tests above due to the navigation to same_origin_ad.html being
// itself made in an iframe.
waiter->AddMinimumCompleteResourcesExpectation(5);
waiter->Wait();
ASSERT_TRUE(
ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL)));
histogram_tester.ExpectUniqueSample(
kCrossOriginHistogramId, page_load_metrics::OriginStatus::kCross, 1);
auto entries =
ukm_recorder.GetEntriesByName(ukm::builders::AdFrameLoad::kEntryName);
EXPECT_EQ(1u, entries.size());
ukm_recorder.ExpectEntryMetric(
entries.front(), ukm::builders::AdFrameLoad::kStatus_CrossOriginName,
static_cast<int>(page_load_metrics::OriginStatus::kCross));
}
// Verifies that the page ad density records the maximum value during
// a page's lifecycling by creating a large ad frame, destroying it, and
// creating a smaller iframe. The ad density recorded is the density with
// the first larger frame.
IN_PROC_BROWSER_TEST_F(AdsPageLoadMetricsObserverBrowserTest,
PageAdDensityRecordsPageMax) {
base::HistogramTester histogram_tester;
ukm::TestAutoSetUkmRecorder ukm_recorder;
auto waiter = CreatePageLoadMetricsTestWaiter();
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
// Evaluate the height and width of the page as the browser_test can
// vary the dimensions.
int document_height =
EvalJs(web_contents, "document.body.scrollHeight").ExtractInt();
int document_width =
EvalJs(web_contents, "document.body.scrollWidth").ExtractInt();
// Expectation is before NavigateToUrl for this test as the expectation can be
// met after NavigateToUrl and before the Wait.
waiter->AddMainFrameIntersectionExpectation(
gfx::Rect(0, 0, document_width,
document_height)); // Initial main frame rect.
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(),
embedded_test_server()->GetURL(
"a.com", "/ads_observer/blank_with_adiframe_writer.html")));
waiter->Wait();
web_contents = browser()->tab_strip_model()->GetActiveWebContents();
// Create a frame at 100,100 of size 200,200.
waiter->AddMainFrameIntersectionExpectation(gfx::Rect(100, 100, 200, 200));
// Create the frame with b.com as origin to not get caught by
// restricted ad tagging.
EXPECT_TRUE(ExecJs(
web_contents,
content::JsReplace(
"let frame = createAdIframeAtRect(100, 100, 200, 200); "
"frame.src = $1; ",
embedded_test_server()->GetURL("b.com", "/ads_observer/pixel.png"))));
waiter->Wait();
// Load should stop before we remove the frame.
EXPECT_TRUE(WaitForLoadStop(web_contents));
EXPECT_TRUE(ExecJs(web_contents,
"let frames = document.getElementsByTagName('iframe'); "
"frames[0].remove(); "));
waiter->AddMainFrameIntersectionExpectation(gfx::Rect(400, 400, 10, 10));
// Delete the frame and create a new frame at 400,400 of size 10x10. The
// ad density resulting from this frame is lower than the 200x200.
EXPECT_TRUE(ExecJs(
web_contents,
content::JsReplace("let frame = createAdIframeAtRect(400, 400, 10, 10); "
"frame.src = $1; ",
embedded_test_server()
->GetURL("b.com", "/ads_observer/pixel.png")
.spec())));
waiter->Wait();
// Evaluate the height and width of the page as the browser_test can
// vary the dimensions.
document_height =
EvalJs(web_contents, "document.body.scrollHeight").ExtractInt();
document_width =
EvalJs(web_contents, "document.body.scrollWidth").ExtractInt();
int page_area = document_width * document_height;
int ad_area = 200 * 200; // The area of the first larger ad iframe.
int expected_page_density_area = ad_area * 100 / page_area;
int expected_page_density_height = 200 * 100 / document_height;
ASSERT_TRUE(
ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL)));
histogram_tester.ExpectUniqueSample(kMaxAdDensityByAreaHistogramId,
expected_page_density_area, 1);
histogram_tester.ExpectUniqueSample(kMaxAdDensityByHeightHistogramId,
expected_page_density_height, 1);
histogram_tester.ExpectUniqueSample(kMaxAdDensityRecordedHistogramId, true,
1);
auto entries =
ukm_recorder.GetEntriesByName(ukm::builders::AdPageLoad::kEntryName);
EXPECT_EQ(1u, entries.size());
ukm_recorder.ExpectEntryMetric(
entries.front(), ukm::builders::AdPageLoad::kMaxAdDensityByAreaName,
expected_page_density_area);
ukm_recorder.ExpectEntryMetric(
entries.front(), ukm::builders::AdPageLoad::kMaxAdDensityByHeightName,
expected_page_density_height);
}
// Creates multiple overlapping frames and verifies the page ad density.
IN_PROC_BROWSER_TEST_F(AdsPageLoadMetricsObserverBrowserTest,
PageAdDensityMultipleFrames) {
base::HistogramTester histogram_tester;
ukm::TestAutoSetUkmRecorder ukm_recorder;
auto waiter = CreatePageLoadMetricsTestWaiter();
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
int document_height =
EvalJs(web_contents, "document.body.scrollHeight").ExtractInt();
int document_width =
EvalJs(web_contents, "document.body.scrollWidth").ExtractInt();
// Expectation is before NavigateToUrl for this test as the expectation can be
// met after NavigateToUrl and before the Wait.
waiter->AddMainFrameIntersectionExpectation(
gfx::Rect(0, 0, document_width,
document_height)); // Initial main frame rect.
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(),
embedded_test_server()->GetURL(
"a.com", "/ads_observer/blank_with_adiframe_writer.html")));
waiter->Wait();
web_contents = browser()->tab_strip_model()->GetActiveWebContents();
// Create a frame of size 400,400 at 100,100.
waiter->AddMainFrameIntersectionExpectation(gfx::Rect(400, 400, 100, 100));
// Create the frame with b.com as origin to not get caught by
// restricted ad tagging.
EXPECT_TRUE(ExecJs(
web_contents, content::JsReplace(
"let frame = createAdIframeAtRect(400, 400, 100, 100); "
"frame.src = $1",
embedded_test_server()
->GetURL("b.com", "/ads_observer/pixel.png")
.spec())));
waiter->Wait();
// Create a frame at of size 200,200 at 450,450.
waiter->AddMainFrameIntersectionExpectation(gfx::Rect(450, 450, 200, 200));
EXPECT_TRUE(ExecJs(
web_contents, content::JsReplace(
"let frame = createAdIframeAtRect(450, 450, 200, 200); "
"frame.src = $1",
embedded_test_server()
->GetURL("b.com", "/ads_observer/pixel.png")
.spec())));
waiter->Wait();
// Evaluate the height and width of the page as the browser_test can
// vary the dimensions.
document_height =
EvalJs(web_contents, "document.body.scrollHeight").ExtractInt();
document_width =
EvalJs(web_contents, "document.body.scrollWidth").ExtractInt();
ASSERT_TRUE(
ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL)));
int page_area = document_width * document_height;
// The area of the two iframes minus the area of the overlapping section.
int ad_area = 100 * 100 + 200 * 200 - 50 * 50;
int expected_page_density_area = ad_area * 100 / page_area;
int expected_page_density_height = 250 * 100 / document_height;
histogram_tester.ExpectUniqueSample(kMaxAdDensityByAreaHistogramId,
expected_page_density_area, 1);
histogram_tester.ExpectUniqueSample(kMaxAdDensityByHeightHistogramId,
expected_page_density_height, 1);
histogram_tester.ExpectUniqueSample(kMaxAdDensityRecordedHistogramId, true,
1);
auto entries =
ukm_recorder.GetEntriesByName(ukm::builders::AdPageLoad::kEntryName);
EXPECT_EQ(1u, entries.size());
ukm_recorder.ExpectEntryMetric(
entries.front(), ukm::builders::AdPageLoad::kMaxAdDensityByAreaName,
expected_page_density_area);
ukm_recorder.ExpectEntryMetric(
entries.front(), ukm::builders::AdPageLoad::kMaxAdDensityByHeightName,
expected_page_density_height);
}
// Creates a frame with display:none styling and verifies that it has an
// empty intersection with the main frame.
//
// TODO(crbug.com/1121444): This test is disabled due to flaky failures on
// multiple platforms.
IN_PROC_BROWSER_TEST_F(AdsPageLoadMetricsObserverBrowserTest,
DISABLED_PageAdDensityIgnoreDisplayNoneFrame) {
base::HistogramTester histogram_tester;
ukm::TestAutoSetUkmRecorder ukm_recorder;
auto waiter = CreatePageLoadMetricsTestWaiter();
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
// Evaluate the height and width of the page as the browser_test can
// vary the dimensions.
int document_height =
EvalJs(web_contents, "document.body.scrollHeight").ExtractInt();
int document_width =
EvalJs(web_contents, "document.body.scrollWidth").ExtractInt();
// Expectation is before NavigateToUrl for this test as the expectation can be
// met after NavigateToUrl and before the Wait.
waiter->AddMainFrameIntersectionExpectation(
gfx::Rect(0, 0, document_width,
document_height)); // Initial main frame rect.
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(),
embedded_test_server()->GetURL(
"a.com", "/ads_observer/blank_with_adiframe_writer.html")));
waiter->Wait();
web_contents = browser()->tab_strip_model()->GetActiveWebContents();
// Create a frame at 100,100 of size 200,200. The expectation is an empty rect
// as the frame is display:none and as a result has no main frame
// intersection.
waiter->AddMainFrameIntersectionExpectation(gfx::Rect(0, 0, 0, 0));
// Create the frame with b.com as origin to not get caught by
// restricted ad tagging.
EXPECT_TRUE(ExecJs(
web_contents, content::JsReplace(
"let frame = createAdIframeAtRect(100, 100, 200, 200); "
"frame.src = $1; "
"frame.style.display = \"none\";",
embedded_test_server()
->GetURL("b.com", "/ads_observer/pixel.png")
.spec())));
waiter->Wait();
ASSERT_TRUE(
ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL)));
histogram_tester.ExpectUniqueSample(kMaxAdDensityByAreaHistogramId, 0, 1);
histogram_tester.ExpectUniqueSample(kMaxAdDensityByHeightHistogramId, 0, 1);
histogram_tester.ExpectUniqueSample(kMaxAdDensityRecordedHistogramId, true,
1);
auto entries =
ukm_recorder.GetEntriesByName(ukm::builders::AdPageLoad::kEntryName);
EXPECT_EQ(1u, entries.size());
ukm_recorder.ExpectEntryMetric(
entries.front(), ukm::builders::AdPageLoad::kMaxAdDensityByAreaName, 0);
ukm_recorder.ExpectEntryMetric(
entries.front(), ukm::builders::AdPageLoad::kMaxAdDensityByHeightName, 0);
}
// Each CreativeOriginStatus* browser test inputs a pointer to a frame object
// representing the frame tree path of a website with with a (possibly null)
// ad subframe, which itself may have linearly nested subframes.
// Each test then queries cross_site_iframe_factory.html with the query string
// corresponding to the frame tree path, computes the actual creative origin
// status and compares it to the expected creative origin status.
class CreativeOriginAdsPageLoadMetricsObserverBrowserTest
: public AdsPageLoadMetricsObserverBrowserTest {
public:
CreativeOriginAdsPageLoadMetricsObserverBrowserTest() = default;
~CreativeOriginAdsPageLoadMetricsObserverBrowserTest() override = default;
// Data structure to store the frame tree path for the root/main frame and
// its single ad subframe (if such a child exists), as well as to keep track
// of whether or not to render text in the frame. A child frame may have at
// most one child frame of its own, and so forth.
class Frame {
public:
Frame(std::string origin,
std::unique_ptr<Frame> child,
bool has_text = false,
bool is_outside_view = false)
: origin_(origin),
child_(std::move(child)),
has_text_(has_text),
is_outside_view_(is_outside_view) {}
~Frame() = default;
bool HasChild() const { return child_ != nullptr; }
bool HasDescendantRenderingText(bool is_top_frame = true) const {
if (!is_top_frame && has_text_ && !is_outside_view_)
return true;
if (!is_top_frame && is_outside_view_)
return false;
if (!child_)
return false;
return child_->HasDescendantRenderingText(false);
}
std::string Hostname() const { return origin_ + ".com"; }
std::string Print(bool should_escape = false) const {
std::vector<std::string> query_pieces = {origin_};
if (!has_text_ && is_outside_view_)
query_pieces.push_back("{no-text-render,out-of-view}");
else if (!has_text_)
query_pieces.push_back("{no-text-render}");
else if (is_outside_view_)
query_pieces.push_back("{out-of-view}");
query_pieces.push_back("(");
if (child_)
query_pieces.push_back(child_->Print());
query_pieces.push_back(")");
std::string out = base::StrCat(query_pieces);
if (should_escape)
out = net::EscapeQueryParamValue(out, false /* use_plus */);
return out;
}
std::string PrintChild(bool should_escape = false) const {
return HasChild() ? child_->Print(should_escape) : "";
}
private:
std::string origin_;
std::unique_ptr<Frame> child_;
bool has_text_;
bool is_outside_view_;
};
// A convenience function to make frame creation less verbose.
std::unique_ptr<Frame> MakeFrame(std::string origin,
std::unique_ptr<Frame> child,
bool has_text = false,
bool is_outside_view = false) {
return std::make_unique<Frame>(origin, std::move(child), has_text,
is_outside_view);
}
void RecordCreativeOriginStatusHistograms(std::unique_ptr<Frame> frame) {
// The file cross_site_iframe_factory.html loads URLs like:
// http://a.com:40919/
// cross_site_iframe_factory.html?a{no-text-render}(b(c{no-text-render}))
// The frame thus intended as the creative will be the only one in which
// text renders.
std::string ad_suffix = frame->PrintChild(true /* should_escape */);
if (!ad_suffix.empty())
SetRulesetToDisallowURLsWithPathSuffix(ad_suffix);
std::string query = frame->Print();
std::string relative_url = "/cross_site_iframe_factory.html?" + query;
const GURL main_url(
embedded_test_server()->GetURL(frame->Hostname(), relative_url));
// If there is text to render in any subframe, wait until there is a first
// contentful paint. Load some bytes in any case.
auto waiter = CreatePageLoadMetricsTestWaiter();
if (frame->HasDescendantRenderingText()) {
waiter->AddSubFrameExpectation(
page_load_metrics::PageLoadMetricsTestWaiter::TimingField::
kFirstContentfulPaint);
} else if (frame->HasChild()) {
waiter->AddSubframeDataExpectation();
}
EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), main_url));
waiter->Wait();
// Navigate away to force the histogram recording.
EXPECT_TRUE(
ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL)));
}
void TestCreativeOriginStatus(
std::unique_ptr<Frame> main_frame,
page_load_metrics::OriginStatus expected_status,
absl::optional<page_load_metrics::OriginStatusWithThrottling>
expected_status_with_throttling) {
base::HistogramTester histogram_tester;
bool subframe_exists = main_frame->HasChild();
RecordCreativeOriginStatusHistograms(std::move(main_frame));
// Test histograms.
if (subframe_exists) {
histogram_tester.ExpectUniqueSample(kCreativeOriginHistogramId,
expected_status, 1);
if (expected_status_with_throttling.has_value()) {
histogram_tester.ExpectUniqueSample(
kCreativeOriginWithThrottlingHistogramId,
expected_status_with_throttling.value(), 1);
} else {
// the CreativeOriginStatusWithThrottling histogram is best-effort,
// and in the case where there is no content, multiple possible
// states are valid.
histogram_tester.ExpectTotalCount(
kCreativeOriginWithThrottlingHistogramId, 1);
}
} else {
// If no subframe exists, verify that each histogram is not set.
histogram_tester.ExpectTotalCount(kCreativeOriginHistogramId, 0);
histogram_tester.ExpectTotalCount(
kCreativeOriginWithThrottlingHistogramId, 0);
}
}
};
// Test that an ad with same origin as the main page is same-origin.
IN_PROC_BROWSER_TEST_F(CreativeOriginAdsPageLoadMetricsObserverBrowserTest,
CreativeOriginStatusSame) {
TestCreativeOriginStatus(
MakeFrame("a", MakeFrame("a", MakeFrame("b", MakeFrame("c", nullptr)),
true /* has_text */)),
OriginStatus::kSame, OriginStatusWithThrottling::kSameAndUnthrottled);
}
// Test that an ad with a different origin as the main page is cross-origin.
IN_PROC_BROWSER_TEST_F(CreativeOriginAdsPageLoadMetricsObserverBrowserTest,
CreativeOriginStatusCross) {
TestCreativeOriginStatus(
MakeFrame("a", MakeFrame("b", MakeFrame("c", nullptr), true)),
OriginStatus::kCross, OriginStatusWithThrottling::kCrossAndUnthrottled);
}
// Test that an ad creative with the same origin as the main page,
// but nested in a cross-origin root ad frame, is same-origin.
IN_PROC_BROWSER_TEST_F(CreativeOriginAdsPageLoadMetricsObserverBrowserTest,
CreativeOriginStatusSameNested) {
TestCreativeOriginStatus(
MakeFrame("a",
MakeFrame("b", MakeFrame("a", MakeFrame("c", nullptr), true))),
OriginStatus::kSame, OriginStatusWithThrottling::kSameAndUnthrottled);
}
// Test that an ad creative with a different origin as the main page,
// but nested in a same-origin root ad frame, is cross-origin.
IN_PROC_BROWSER_TEST_F(CreativeOriginAdsPageLoadMetricsObserverBrowserTest,
CreativeOriginStatusCrossNested) {
TestCreativeOriginStatus(
MakeFrame("a",
MakeFrame("a", MakeFrame("b", MakeFrame("c", nullptr), true))),
page_load_metrics::OriginStatus::kCross,
page_load_metrics::OriginStatusWithThrottling::kCrossAndUnthrottled);
}
// Test that an ad creative with a different origin as the main page,
// but nested two deep in a same-origin root ad frame, is cross-origin.
IN_PROC_BROWSER_TEST_F(CreativeOriginAdsPageLoadMetricsObserverBrowserTest,
CreativeOriginStatusCrossDoubleNested) {
TestCreativeOriginStatus(
MakeFrame("a",
MakeFrame("a", MakeFrame("a", MakeFrame("b", nullptr,
true /* has_text */)))),
OriginStatus::kCross, OriginStatusWithThrottling::kCrossAndUnthrottled);
}
// Test that if no iframe renders text, the creative origin status is
// indeterminate. The creative origin status with throttling can be
// either kUnknownAndThrottled or kUnknownAndUnthrottled in this case
// due to race conditions, as nothing is painted.
// TODO(cammie): Find a better workaround for testing COSwT in this
// edge case.
IN_PROC_BROWSER_TEST_F(CreativeOriginAdsPageLoadMetricsObserverBrowserTest,
CreativeOriginStatusNoCreativeDesignated) {
TestCreativeOriginStatus(
MakeFrame("a", MakeFrame("b", MakeFrame("c", nullptr))),
OriginStatus::kUnknown, absl::nullopt);
}
// Test that if no iframe is created, there is no histogram set.
IN_PROC_BROWSER_TEST_F(CreativeOriginAdsPageLoadMetricsObserverBrowserTest,
CreativeOriginStatusNoSubframes) {
TestCreativeOriginStatus(MakeFrame("a", nullptr), OriginStatus::kUnknown,
OriginStatusWithThrottling::kUnknownAndUnthrottled);
}
// Flakily fails (crbug.com/1099758)
// Test that a throttled ad with a different origin as the main page is
// marked as throttled, with indeterminate creative origin status.
IN_PROC_BROWSER_TEST_F(CreativeOriginAdsPageLoadMetricsObserverBrowserTest,
DISABLED_CreativeOriginStatusWithThrottlingUnknown) {
TestCreativeOriginStatus(
MakeFrame("a",
MakeFrame("b", MakeFrame("c", nullptr), true /* has_text */,
true /* is_outside_view */)),
OriginStatus::kUnknown, OriginStatusWithThrottling::kUnknownAndThrottled);
}
// Disabled due to flakiness https://crbug.com/1229601
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_WIN)
#define MAYBE_CreativeOriginStatusWithThrottlingNestedThrottled \
DISABLED_CreativeOriginStatusWithThrottlingNestedThrottled
#else
#define MAYBE_CreativeOriginStatusWithThrottlingNestedThrottled \
CreativeOriginStatusWithThrottlingNestedThrottled
#endif
// Test that an ad creative with the same origin as the main page,
// but nested in a throttled cross-origin root ad frame, is marked as
// throttled, with indeterminate creative origin status.
IN_PROC_BROWSER_TEST_F(
CreativeOriginAdsPageLoadMetricsObserverBrowserTest,
MAYBE_CreativeOriginStatusWithThrottlingNestedThrottled) {
TestCreativeOriginStatus(
MakeFrame(
"a",
MakeFrame(
"b", MakeFrame("a", MakeFrame("c", nullptr), true /* has_text */),
false /* has_text */, true /* is_outside_view */)),
OriginStatus::kUnknown, OriginStatusWithThrottling::kUnknownAndThrottled);
}
// Flakily fails. https://crbug.com/1099545
// Test that an ad creative with a different origin as the main page,
// but nested in a same-origin root ad frame, such that its root ad frame
// is outside the main frame but not throttled (because the root is
// same-origin), will be marked as having unknown creative origin status
// (since there will be no FCP) and being unthrottled.
IN_PROC_BROWSER_TEST_F(
CreativeOriginAdsPageLoadMetricsObserverBrowserTest,
DISABLED_CreativeOriginStatusWithThrottlingNestedUnthrottled) {
TestCreativeOriginStatus(
MakeFrame(
"a",
MakeFrame(
"a", MakeFrame("b", MakeFrame("c", nullptr), true /* has_text */),
false /* has_text */, true /* is_outside_view */)),
OriginStatus::kUnknown,
OriginStatusWithThrottling::kUnknownAndUnthrottled);
}
IN_PROC_BROWSER_TEST_F(AdsPageLoadMetricsObserverBrowserTest,
UserActivationSetOnFrame) {
base::HistogramTester histogram_tester;
ukm::TestAutoSetUkmRecorder ukm_recorder;
auto waiter = CreatePageLoadMetricsTestWaiter();
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL(
"foo.com", "/ad_tagging/frame_factory.html")));
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
// Create a second frame that will not receive activation.
EXPECT_TRUE(content::ExecuteScriptWithoutUserGesture(
web_contents, "createAdFrame('/ad_tagging/ad.html', '');"));
EXPECT_TRUE(content::ExecuteScriptWithoutUserGesture(
web_contents, "createAdFrame('/ad_tagging/ad.html', '');"));
// Wait for the frames resources to be loaded as we only log histograms for
// frames that have non-zero bytes. Four resources in the main frame and one
// favicon.
waiter->AddMinimumCompleteResourcesExpectation(7);
waiter->Wait();
// Activate one frame by executing a dummy script.
content::RenderFrameHost* ad_frame =
ChildFrameAt(web_contents->GetMainFrame(), 0);
const std::string no_op_script = "// No-op script";
EXPECT_TRUE(ExecuteScript(ad_frame, no_op_script));
ASSERT_TRUE(
ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL)));
histogram_tester.ExpectBucketCount(
kAdUserActivationHistogramId,
page_load_metrics::UserActivationStatus::kReceivedActivation, 1);
histogram_tester.ExpectBucketCount(
kAdUserActivationHistogramId,
page_load_metrics::UserActivationStatus::kNoActivation, 1);
auto entries =
ukm_recorder.GetEntriesByName(ukm::builders::AdFrameLoad::kEntryName);
EXPECT_EQ(2u, entries.size());
// Verify that one ad was reported to be activated and the other was not.
EXPECT_TRUE(*ukm_recorder.GetEntryMetric(
entries.front(),
ukm::builders::AdFrameLoad::kStatus_UserActivationName) !=
*ukm_recorder.GetEntryMetric(
entries.back(),
ukm::builders::AdFrameLoad::kStatus_UserActivationName));
}
// See https://crbug.com/1193885.
IN_PROC_BROWSER_TEST_F(AdsPageLoadMetricsObserverBrowserTest,
UserActivationSetOnFrameAfterSameOriginActivation) {
base::HistogramTester histogram_tester;
ukm::TestAutoSetUkmRecorder ukm_recorder;
auto waiter = CreatePageLoadMetricsTestWaiter();
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL(
"foo.com", "/ad_tagging/frame_factory.html")));
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
// Create two same-origin ad frames.
EXPECT_TRUE(content::ExecuteScriptWithoutUserGesture(
web_contents, "createAdFrame('/ad_tagging/ad.html', '');"));
EXPECT_TRUE(content::ExecuteScriptWithoutUserGesture(
web_contents, "createAdFrame('/ad_tagging/ad.html', '');"));
// Wait for the frames resources to be loaded as we only log histograms for
// frames that have non-zero bytes. Four resources in the main frame and one
// favicon.
waiter->AddMinimumCompleteResourcesExpectation(7);
waiter->Wait();
// Activate one frame by executing a dummy script. This will inherently
// activate the second frame due to same-origin visibility user activation.
// The activation of the second frame by this heuristic should be ignored.
content::RenderFrameHost* ad_frame =
ChildFrameAt(web_contents->GetMainFrame(), 0);
const std::string no_op_script = "// No-op script";
EXPECT_TRUE(ExecuteScript(ad_frame, no_op_script));
// Activate the other frame directly by executing a dummy script.
content::RenderFrameHost* ad_frame_2 =
ChildFrameAt(web_contents->GetMainFrame(), 1);
EXPECT_TRUE(ExecuteScript(ad_frame_2, no_op_script));
// Ensure both frames are marked active.
ASSERT_TRUE(
ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL)));
histogram_tester.ExpectUniqueSample(
kAdUserActivationHistogramId,
page_load_metrics::UserActivationStatus::kReceivedActivation, 2);
}
// TODO(https://crbug.com/1234339): Test is flaky.
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
#define MAYBE_DocOverwritesNavigation DISABLED_DocOverwritesNavigation
#else
#define MAYBE_DocOverwritesNavigation DocOverwritesNavigation
#endif
// Test that a subframe that aborts (due to doc.write) doesn't cause a crash
// if it continues to load resources.
IN_PROC_BROWSER_TEST_F(AdsPageLoadMetricsObserverBrowserTest,
MAYBE_DocOverwritesNavigation) {
// Ensure that the previous page won't be stored in the back/forward cache, so
// that the histogram will be recorded when the previous page is unloaded.
// TODO(https://crbug.com/1229122): Investigate if this needs further fix.
browser()
->tab_strip_model()
->GetActiveWebContents()
->GetController()
.GetBackForwardCache()
.DisableForTesting(content::BackForwardCache::TEST_ASSUMES_NO_CACHING);
content::DOMMessageQueue msg_queue;
ukm::TestAutoSetUkmRecorder ukm_recorder;
auto waiter = CreatePageLoadMetricsTestWaiter();
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL(
"/ads_observer/docwrite_provisional_frame.html")));
std::string status;
EXPECT_TRUE(msg_queue.WaitForMessage(&status));
EXPECT_EQ("\"loaded\"", status);
// Navigate away to force the histogram recording.
ASSERT_TRUE(
ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL)));
auto entries =
ukm_recorder.GetEntriesByName(ukm::builders::AdFrameLoad::kEntryName);
EXPECT_EQ(1u, entries.size());
ukm_recorder.ExpectEntryMetric(
entries.front(), ukm::builders::AdFrameLoad::kLoading_NumResourcesName,
3);
// TODO(https://crbug.com/): We should verify that we also receive FCP for
// frames that are loaded in this manner. Currently timing updates are not
// sent for aborted navigations due to doc.write.
}
// Test that a blank ad subframe that is docwritten correctly reports metrics.
IN_PROC_BROWSER_TEST_F(AdsPageLoadMetricsObserverBrowserTest,
DocWriteAboutBlankAdframe) {
base::HistogramTester histogram_tester;
ukm::TestAutoSetUkmRecorder ukm_recorder;
auto waiter = CreatePageLoadMetricsTestWaiter();
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL(
"/ads_observer/docwrite_blank_frame.html")));
waiter->AddMinimumCompleteResourcesExpectation(5);
waiter->Wait();
// Navigate away to force the histogram recording.
ASSERT_TRUE(
ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL)));
histogram_tester.ExpectUniqueSample(
"PageLoad.Clients.Ads.FrameCounts.AdFrames.Total", 1, 1);
histogram_tester.ExpectUniqueSample(
"PageLoad.Clients.Ads.Bytes.AdFrames.Aggregate.Total2", 0 /* < 1 KB */,
1);
auto entries =
ukm_recorder.GetEntriesByName(ukm::builders::AdFrameLoad::kEntryName);
EXPECT_EQ(1u, entries.size());
EXPECT_GT(*ukm_recorder.GetEntryMetric(
entries.front(),
ukm::builders::AdFrameLoad::kLoading_NetworkBytesName),
0);
}
IN_PROC_BROWSER_TEST_F(AdsPageLoadMetricsObserverBrowserTest,
SubresourceFilter) {
base::HistogramTester histogram_tester;
ukm::TestAutoSetUkmRecorder ukm_recorder;
// cross_site_iframe_factory loads URLs like:
// http://b.com:40919/cross_site_iframe_factory.html?b()
SetRulesetToDisallowURLsWithPathSuffix("b()");
const GURL main_url(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(b,b,c,d)"));
auto waiter = CreatePageLoadMetricsTestWaiter();
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), main_url));
// One favicon resource and 2 resources for each frame.
waiter->AddMinimumCompleteResourcesExpectation(11);
waiter->Wait();
// Navigate away to force the histogram recording.
ASSERT_TRUE(
ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL)));
histogram_tester.ExpectUniqueSample(
"PageLoad.Clients.Ads.FrameCounts.AdFrames.Total", 2, 1);
auto entries =
ukm_recorder.GetEntriesByName(ukm::builders::AdFrameLoad::kEntryName);
EXPECT_EQ(2u, entries.size());
}
IN_PROC_BROWSER_TEST_F(AdsPageLoadMetricsObserverBrowserTest, FrameDepth) {
base::HistogramTester histogram_tester;
ukm::TestAutoSetUkmRecorder ukm_recorder;
// cross_site_iframe_factory loads URLs like:
// http://b.com:40919/cross_site_iframe_factory.html?b()
SetRulesetToDisallowURLsWithPathSuffix("b()))");
const GURL main_url(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(b(b(b())))"));
auto waiter = CreatePageLoadMetricsTestWaiter();
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), main_url));
// One favicon resource and 2 resources for each frame.
waiter->AddMinimumCompleteResourcesExpectation(9);
waiter->Wait();
// Navigate away to force the histogram recording.
ASSERT_TRUE(
ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL)));
auto entries =
ukm_recorder.GetEntriesByName(ukm::builders::AdFrameLoad::kEntryName);
EXPECT_EQ(1u, entries.size());
ukm_recorder.ExpectEntryMetric(
entries.front(), ukm::builders::AdFrameLoad::kFrameDepthName, 2);
}
// Test that an ad frame with visible resource gets a FCP.
IN_PROC_BROWSER_TEST_F(AdsPageLoadMetricsObserverBrowserTest,
FirstContentfulPaintRecorded) {
SetRulesetWithRules(
{subresource_filter::testing::CreateSuffixRule("pixel.png")});
base::HistogramTester histogram_tester;
auto waiter = CreatePageLoadMetricsTestWaiter();
waiter->AddSubFrameExpectation(page_load_metrics::PageLoadMetricsTestWaiter::
TimingField::kFirstContentfulPaint);
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL(
"/ads_observer/display_block_adframe.html")));
// Wait for FirstContentfulPaint in a subframe.
waiter->Wait();
// Navigate away so that it records the metric.
ASSERT_TRUE(
ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL)));
histogram_tester.ExpectTotalCount(
"PageLoad.Clients.Ads.AdPaintTiming.NavigationToFirstContentfulPaint3",
1);
histogram_tester.ExpectTotalCount(
"PageLoad.Clients.Ads.Visible.AdPaintTiming."
"NavigationToFirstContentfulPaint3",
1);
histogram_tester.ExpectTotalCount(
"PageLoad.Clients.Ads.NonVisible.AdPaintTiming."
"NavigationToFirstContentfulPaint3",
0);
}
// Test that a frame without display:none is reported as visible.
IN_PROC_BROWSER_TEST_F(AdsPageLoadMetricsObserverBrowserTest,
VisibleAdframeRecorded) {
SetRulesetWithRules(
{subresource_filter::testing::CreateSuffixRule("pixel.png")});
base::HistogramTester histogram_tester;
ukm::TestAutoSetUkmRecorder ukm_recorder;
auto waiter = CreatePageLoadMetricsTestWaiter();
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL(
"/ads_observer/display_block_adframe.html")));
waiter->AddMinimumCompleteResourcesExpectation(4);
waiter->Wait();
ASSERT_TRUE(
ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL)));
histogram_tester.ExpectTotalCount(
"PageLoad.Clients.Ads.Visible.Bytes.AdFrames.PerFrame.Total2", 1);
histogram_tester.ExpectTotalCount(
"PageLoad.Clients.Ads.Bytes.AdFrames.PerFrame.Total2", 1);
histogram_tester.ExpectTotalCount(
"PageLoad.Clients.Ads.NonVisible.Bytes.AdFrames.PerFrame.Total2", 0);
auto entries =
ukm_recorder.GetEntriesByName(ukm::builders::AdFrameLoad::kEntryName);
EXPECT_EQ(1u, entries.size());
ukm_recorder.ExpectEntryMetric(
entries.front(), ukm::builders::AdFrameLoad::kVisibility_HiddenName,
false);
}
IN_PROC_BROWSER_TEST_F(AdsPageLoadMetricsObserverBrowserTest,
DisplayNoneAdframeRecorded) {
base::HistogramTester histogram_tester;
ukm::TestAutoSetUkmRecorder ukm_recorder;
auto waiter = CreatePageLoadMetricsTestWaiter();
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL(
"/ads_observer/display_none_adframe.html")));
waiter->AddMinimumCompleteResourcesExpectation(4);
waiter->Wait();
// Navigate away to force the histogram recording.
ASSERT_TRUE(
ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL)));
histogram_tester.ExpectTotalCount(
"PageLoad.Clients.Ads.NonVisible.Bytes.AdFrames.PerFrame.Total2", 1);
histogram_tester.ExpectTotalCount(
"PageLoad.Clients.Ads.Bytes.AdFrames.PerFrame.Total2", 1);
histogram_tester.ExpectTotalCount(
"PageLoad.Clients.Ads.Visible.Bytes.AdFrames.PerFrame.Total2", 0);
auto entries =
ukm_recorder.GetEntriesByName(ukm::builders::AdFrameLoad::kEntryName);
EXPECT_EQ(1u, entries.size());
ukm_recorder.ExpectEntryMetric(
entries.front(), ukm::builders::AdFrameLoad::kVisibility_HiddenName,
true);
}
// TODO(https://crbug.com/929136): Investigate why setting display: none on the
// frame will cause size updates to not be received. Verify that we record the
// correct sizes for display: none iframes.
IN_PROC_BROWSER_TEST_F(AdsPageLoadMetricsObserverBrowserTest, FramePixelSize) {
SetRulesetWithRules(
{subresource_filter::testing::CreateSuffixRule("pixel.png")});
base::HistogramTester histogram_tester;
ukm::TestAutoSetUkmRecorder ukm_recorder;
auto waiter = CreatePageLoadMetricsTestWaiter();
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL(
"/ads_observer/blank_with_adiframe_writer.html")));
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
std::map<std::pair<int, int>, int> expected_dimension_counts;
std::map<std::pair<int, int>, int> expected_bucketed_dimension_counts;
expected_dimension_counts[std::make_pair(100, 100)] = 1;
expected_dimension_counts[std::make_pair(0, 0)] = 1;
expected_dimension_counts[std::make_pair(10, 1000)] = 1;
for (auto const& dimension_and_count : expected_dimension_counts) {
// Convert the expected dimensions into exponential buckets.
expected_bucketed_dimension_counts[std::make_pair(
ukm::GetExponentialBucketMinForCounts1000(
dimension_and_count.first.first),
ukm::GetExponentialBucketMinForCounts1000(
dimension_and_count.first.second))] = 1;
// Create an iframe with the given dimensions.
ASSERT_TRUE(
ExecJs(web_contents,
"let frame = createAdIframe(); frame.width=" +
base::NumberToString(dimension_and_count.first.first) +
"; frame.height = " +
base::NumberToString(dimension_and_count.first.second) +
"; "
"frame.src = '/ads_observer/pixel.png';"));
}
// Wait for each frames resource to load so that they will have non-zero
// bytes.
waiter->AddMinimumCompleteResourcesExpectation(6);
waiter->AddFrameSizeExpectation(gfx::Size(0, 0));
waiter->AddFrameSizeExpectation(gfx::Size(10, 1000));
waiter->AddFrameSizeExpectation(gfx::Size(100, 100));
waiter->Wait();
// Navigate away to force the histogram recording.
ASSERT_TRUE(
ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL)));
// Verify each UKM entry has a corresponding, unique size.
auto entries =
ukm_recorder.GetEntriesByName(ukm::builders::AdFrameLoad::kEntryName);
EXPECT_EQ(3u, entries.size());
for (auto* const entry : entries) {
auto dimension = std::make_pair(
*ukm_recorder.GetEntryMetric(
entry, ukm::builders::AdFrameLoad::kVisibility_FrameWidthName),
*ukm_recorder.GetEntryMetric(
entry, ukm::builders::AdFrameLoad::kVisibility_FrameHeightName));
EXPECT_EQ(1u, expected_bucketed_dimension_counts.erase(dimension));
}
}
IN_PROC_BROWSER_TEST_F(AdsPageLoadMetricsObserverBrowserTest,
FrameWithSmallAreaNotConsideredVisible) {
base::HistogramTester histogram_tester;
auto waiter = CreatePageLoadMetricsTestWaiter();
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL(
"/ads_observer/blank_with_adiframe_writer.html")));
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
// Create a 4x4 iframe. The threshold for visibility is an area of 25 pixels
// or more.
ASSERT_TRUE(
ExecJs(web_contents,
"let frame = createAdIframe(); frame.width=4; frame.height = 4; "
"frame.src = '/ads_observer/ad.html';"));
// Wait for each frames resource to load so that they will have non-zero
// bytes.
waiter->AddMinimumCompleteResourcesExpectation(4);
waiter->AddFrameSizeExpectation(gfx::Size(4, 4));
waiter->Wait();
// Navigate away to force the histogram recording.
ASSERT_TRUE(
ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL)));
histogram_tester.ExpectUniqueSample(
"PageLoad.Clients.Ads.Visible.FrameCounts.AdFrames.Total", 0, 1);
histogram_tester.ExpectUniqueSample(
"PageLoad.Clients.Ads.NonVisible.FrameCounts.AdFrames.Total", 1, 1);
}
IN_PROC_BROWSER_TEST_F(AdsPageLoadMetricsObserverBrowserTest,
AdFrameRecordMediaStatusNotPlayed) {
SetRulesetWithRules(
{subresource_filter::testing::CreateSuffixRule("pixel.png")});
ukm::TestAutoSetUkmRecorder ukm_recorder;
auto waiter = CreatePageLoadMetricsTestWaiter();
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(),
embedded_test_server()->GetURL("/ads_observer/same_origin_ad.html")));
waiter->AddMinimumCompleteResourcesExpectation(4);
waiter->Wait();
ASSERT_TRUE(
ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL)));
auto entries =
ukm_recorder.GetEntriesByName(ukm::builders::AdFrameLoad::kEntryName);
EXPECT_EQ(1u, entries.size());
ukm_recorder.ExpectEntryMetric(
entries.front(), ukm::builders::AdFrameLoad::kStatus_MediaName,
static_cast<int>(page_load_metrics::MediaStatus::kNotPlayed));
}
// Flaky on all platforms, http://crbug.com/972822.
IN_PROC_BROWSER_TEST_F(AdsPageLoadMetricsObserverBrowserTest,
DISABLED_AdFrameRecordMediaStatusPlayed) {
base::HistogramTester histogram_tester;
ukm::TestAutoSetUkmRecorder ukm_recorder;
auto waiter = CreatePageLoadMetricsTestWaiter();
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(),
embedded_test_server()->GetURL("/ad_tagging/frame_factory.html")));
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
// Create a second frame that will not receive activation.
EXPECT_TRUE(content::ExecuteScriptWithoutUserGesture(
web_contents,
"createAdFrame('/ad_tagging/multiple_mimes.html', 'test');"));
waiter->AddMinimumCompleteResourcesExpectation(8);
waiter->Wait();
// Wait for the video to autoplay in the frame.
content::RenderFrameHost* ad_frame =
ChildFrameAt(web_contents->GetMainFrame(), 0);
const std::string play_script =
"var video = document.getElementsByTagName('video')[0];"
"video.onplaying = () => { "
"window.domAutomationController.send('true'); };"
"video.play();";
EXPECT_EQ("true", content::EvalJs(ad_frame, play_script,
content::EXECUTE_SCRIPT_USE_MANUAL_REPLY));
ASSERT_TRUE(
ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL)));
auto entries =
ukm_recorder.GetEntriesByName(ukm::builders::AdFrameLoad::kEntryName);
EXPECT_EQ(1u, entries.size());
ukm_recorder.ExpectEntryMetric(
entries.front(), ukm::builders::AdFrameLoad::kStatus_MediaName,
static_cast<int>(page_load_metrics::MediaStatus::kPlayed));
}
IN_PROC_BROWSER_TEST_F(AdsPageLoadMetricsObserverBrowserTest,
SameDomainFrameCreatedByAdScript_NotRecorddedAsAd) {
base::HistogramTester histogram_tester;
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
auto waiter = CreatePageLoadMetricsTestWaiter();
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(),
embedded_test_server()->GetURL(
"a.com", "/ads_observer/blank_with_adiframe_writer.html")));
waiter->AddSubframeDataExpectation();
EXPECT_TRUE(
ExecJs(web_contents,
content::JsReplace("createAdIframeWithSrc($1);",
embedded_test_server()
->GetURL("a.com", "/ads_observer/pixel.png")
.spec())));
waiter->Wait();
// Re-navigate to record histograms.
ASSERT_TRUE(
ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL)));
// There should be no observed ads because the ad iframe was same domain.
histogram_tester.ExpectUniqueSample(
"PageLoad.Clients.Ads.FrameCounts.AdFrames.Total", 0, 1);
histogram_tester.ExpectUniqueSample(
"PageLoad.Clients.Ads.FrameCounts.IgnoredByRestrictedAdTagging", 1, 1);
waiter = CreatePageLoadMetricsTestWaiter();
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(),
embedded_test_server()->GetURL(
"a.com", "/ads_observer/blank_with_adiframe_writer.html")));
// This time create a frame that is not same-domain.
waiter->AddSubframeDataExpectation();
EXPECT_TRUE(
ExecJs(web_contents,
content::JsReplace("createAdIframeWithSrc($1);",
embedded_test_server()
->GetURL("b.com", "/ads_observer/pixel.png")
.spec())));
waiter->Wait();
// The frame should be tagged an ad.
ASSERT_TRUE(
ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL)));
histogram_tester.ExpectBucketCount(
"PageLoad.Clients.Ads.FrameCounts.AdFrames.Total", 1, 1);
histogram_tester.ExpectBucketCount(
"PageLoad.Clients.Ads.FrameCounts.IgnoredByRestrictedAdTagging", 0, 1);
}
IN_PROC_BROWSER_TEST_F(
AdsPageLoadMetricsObserverBrowserTest,
FrameCreatedByAdScriptNavigatedToAllowListRule_NotRecorddedAsAd) {
// Subdocument resources should always check allowlist rules, even if
// there is not matching blocklist rule.
SetRulesetWithRules(
{subresource_filter::testing::CreateSuffixRule("ad_iframe_writer.js"),
subresource_filter::testing::CreateAllowlistSuffixRule("xel.png")});
base::HistogramTester histogram_tester;
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
auto waiter = CreatePageLoadMetricsTestWaiter();
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(),
embedded_test_server()->GetURL(
"a.com", "/ads_observer/blank_with_adiframe_writer.html")));
waiter->AddSubframeDataExpectation();
EXPECT_TRUE(
ExecJs(web_contents,
content::JsReplace("createAdIframeWithSrc($1);",
embedded_test_server()
->GetURL("b.com", "/ads_observer/pixel.png")
.spec())));
waiter->Wait();
// Re-navigate to record histograms.
ASSERT_TRUE(
ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL)));
// There should be no observed ads because the ad iframe was navigated to an
// allowlist rule.
histogram_tester.ExpectUniqueSample(
"PageLoad.Clients.Ads.FrameCounts.AdFrames.Total", 0, 1);
histogram_tester.ExpectUniqueSample(
"PageLoad.Clients.Ads.FrameCounts.IgnoredByRestrictedAdTagging", 1, 1);
}
// This test harness does not start the test server and allows
// ControllableHttpResponses to be declared.
class AdsPageLoadMetricsObserverResourceBrowserTest
: public subresource_filter::SubresourceFilterBrowserTest {
public:
AdsPageLoadMetricsObserverResourceBrowserTest() {
scoped_feature_list_.InitWithFeaturesAndParameters(
{{subresource_filter::kAdTagging, {}},
{subresource_filter::kAdsInterventionsEnforced, {}},
{heavy_ad_intervention::features::kHeavyAdIntervention, {}},
{heavy_ad_intervention::features::kHeavyAdPrivacyMitigations,
{{"host-threshold", "3"}}}},
{});
}
~AdsPageLoadMetricsObserverResourceBrowserTest() override {}
void SetUpOnMainThread() override {
host_resolver()->AddRule("*", "127.0.0.1");
embedded_test_server()->ServeFilesFromSourceDirectory(
"components/test/data");
content::SetupCrossSiteRedirector(embedded_test_server());
SetRulesetWithRules(
{subresource_filter::testing::CreateSuffixRule("ad_script.js"),
subresource_filter::testing::CreateSuffixRule("ad_script_2.js"),
subresource_filter::testing::CreateSuffixRule("ad_iframe_writer.js")});
}
void SetUpCommandLine(base::CommandLine* command_line) override {
command_line->AppendSwitchASCII(
switches::kAutoplayPolicy,
switches::autoplay::kNoUserGestureRequiredPolicy);
}
// This function loads a |large_resource| and if |will_block| is set, then
// checks to see the resource is blocked, otherwise, it uses the |waiter| to
// wait until the resource is loaded.
void LoadHeavyAdResourceAndWaitOrError(
net::test_server::ControllableHttpResponse* large_resource,
page_load_metrics::PageLoadMetricsTestWaiter* waiter,
bool will_block) {
// Create a frame for the large resource.
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
EXPECT_TRUE(ExecJs(web_contents,
"createAdFrame('/ads_observer/"
"ad_with_incomplete_resource.html', '');"));
if (will_block) {
// If we expect the resource to be blocked, load a resource large enough
// to trigger the intervention and ensure that the navigation failed.
content::TestNavigationObserver error_observer(
web_contents, net::ERR_BLOCKED_BY_CLIENT);
page_load_metrics::LoadLargeResource(
large_resource, page_load_metrics::kMaxHeavyAdNetworkSize);
error_observer.WaitForNavigationFinished();
EXPECT_FALSE(error_observer.last_navigation_succeeded());
} else {
// Otherwise load the resource, ensuring enough bytes were loaded.
int64_t current_network_bytes = waiter->current_network_bytes();
page_load_metrics::LoadLargeResource(
large_resource, page_load_metrics::kMaxHeavyAdNetworkSize);
waiter->AddMinimumNetworkBytesExpectation(
current_network_bytes + page_load_metrics::kMaxHeavyAdNetworkSize);
waiter->Wait();
}
}
protected:
std::unique_ptr<page_load_metrics::AdsPageLoadMetricsTestWaiter>
CreateAdsPageLoadMetricsTestWaiter() {
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
return std::make_unique<page_load_metrics::AdsPageLoadMetricsTestWaiter>(
web_contents);
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
IN_PROC_BROWSER_TEST_F(AdsPageLoadMetricsObserverResourceBrowserTest,
ReceivedAdResources) {
ASSERT_TRUE(embedded_test_server()->Start());
auto waiter = CreateAdsPageLoadMetricsTestWaiter();
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL(
"foo.com", "/ad_tagging/frame_factory.html")));
// Two subresources should have been reported as ads.
waiter->AddMinimumAdResourceExpectation(2);
waiter->Wait();
}
// Main resources for adframes are counted as ad resources.
IN_PROC_BROWSER_TEST_F(AdsPageLoadMetricsObserverResourceBrowserTest,
ReceivedMainResourceAds) {
ASSERT_TRUE(embedded_test_server()->Start());
auto waiter = CreateAdsPageLoadMetricsTestWaiter();
content::WebContents* contents =
browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL(
"foo.com", "/ad_tagging/frame_factory.html")));
contents->GetMainFrame()->ExecuteJavaScriptForTests(
u"createAdFrame('frame_factory.html', '');", base::NullCallback());
// Two pages subresources should have been reported as ad. The iframe resource
// and its three subresources should also be reported as ads.
waiter->AddMinimumAdResourceExpectation(6);
waiter->Wait();
}
// Subframe navigations report ad resources correctly.
IN_PROC_BROWSER_TEST_F(AdsPageLoadMetricsObserverResourceBrowserTest,
ReceivedSubframeNavigationAds) {
ASSERT_TRUE(embedded_test_server()->Start());
auto waiter = CreateAdsPageLoadMetricsTestWaiter();
content::WebContents* contents =
browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL(
"foo.com", "/ad_tagging/frame_factory.html")));
contents->GetMainFrame()->ExecuteJavaScriptForTests(
u"createAdFrame('frame_factory.html', 'test');", base::NullCallback());
waiter->AddMinimumAdResourceExpectation(6);
waiter->Wait();
NavigateIframeToURL(web_contents(), "test",
embedded_test_server()->GetURL(
"foo.com", "/ad_tagging/frame_factory.html"));
// The new subframe and its three subresources should be reported
// as ads.
waiter->AddMinimumAdResourceExpectation(10);
waiter->Wait();
}
// Verify that per-resource metrics are recorded correctly.
IN_PROC_BROWSER_TEST_F(AdsPageLoadMetricsObserverResourceBrowserTest,
ReceivedAdResourceMetrics) {
SetRulesetWithRules(
{subresource_filter::testing::CreateSuffixRule("ad.html"),
subresource_filter::testing::CreateSuffixRule("ad_script.js")});
base::HistogramTester histogram_tester;
auto main_html_response =
std::make_unique<net::test_server::ControllableHttpResponse>(
embedded_test_server(), "/mock_page.html",
true /*relative_url_is_prefix*/);
auto ad_script_response =
std::make_unique<net::test_server::ControllableHttpResponse>(
embedded_test_server(), "/ad_script.js",
true /*relative_url_is_prefix*/);
auto iframe_response =
std::make_unique<net::test_server::ControllableHttpResponse>(
embedded_test_server(), "/ad.html", true /*relative_url_is_prefix*/);
auto vanilla_script_response =
std::make_unique<net::test_server::ControllableHttpResponse>(
embedded_test_server(), "/vanilla_script.js",
true /*relative_url_is_prefix*/);
ASSERT_TRUE(embedded_test_server()->Start());
auto waiter = CreateAdsPageLoadMetricsTestWaiter();
browser()->OpenURL(content::OpenURLParams(
embedded_test_server()->GetURL("/mock_page.html"), content::Referrer(),
WindowOpenDisposition::CURRENT_TAB, ui::PAGE_TRANSITION_TYPED, false));
main_html_response->WaitForRequest();
main_html_response->Send(page_load_metrics::kHttpOkResponseHeader);
main_html_response->Send(
"<html><body></body><script src=\"ad_script.js\"></script></html>");
main_html_response->Send(std::string(1024, ' '));
main_html_response->Done();
ad_script_response->WaitForRequest();
ad_script_response->Send(page_load_metrics::kHttpOkResponseHeader);
ad_script_response->Send(
"var iframe = document.createElement(\"iframe\");"
"iframe.src =\"ad.html\";"
"document.body.appendChild(iframe);");
ad_script_response->Send(std::string(1000, ' '));
ad_script_response->Done();
iframe_response->WaitForRequest();
iframe_response->Send(page_load_metrics::kHttpOkResponseHeader);
iframe_response->Send("<html><script src=\"vanilla_script.js\"></script>");
iframe_response->Send(std::string(2000, ' '));
iframe_response->Send("</html>");
iframe_response->Done();
vanilla_script_response->WaitForRequest();
vanilla_script_response->Send(page_load_metrics::kHttpOkResponseHeader);
vanilla_script_response->Send(std::string(1024, ' '));
waiter->AddMinimumNetworkBytesExpectation(5000);
waiter->Wait();
// Close all tabs instead of navigating as the embedded_test_server will
// hang waiting for loads to finish when we have an unfinished
// ControllableHttpResponse.
browser()->tab_strip_model()->CloseAllTabs();
// We have received 4 KB of ads, including 1 KB of mainframe ads, plus 1 KB of
// mainframe content.
histogram_tester.ExpectBucketCount(
"PageLoad.Clients.Ads.Bytes.FullPage.Network", 5, 1);
histogram_tester.ExpectBucketCount(
"PageLoad.Clients.Ads.Resources.Bytes.Ads2", 4, 1);
histogram_tester.ExpectBucketCount(
"PageLoad.Clients.Ads.AllPages.NonAdNetworkBytes", 1, 1);
histogram_tester.ExpectBucketCount(
"PageLoad.Clients.Ads.Bytes.MainFrame.Ads.Total2", 1, 1);
histogram_tester.ExpectBucketCount(
"PageLoad.Clients.Ads.Bytes.MainFrame.Total2", 2, 1);
}
IN_PROC_BROWSER_TEST_F(AdsPageLoadMetricsObserverResourceBrowserTest,
IncompleteResourcesRecordedToFrameMetrics) {
base::HistogramTester histogram_tester;
ukm::TestAutoSetUkmRecorder ukm_recorder;
auto incomplete_resource_response =
std::make_unique<net::test_server::ControllableHttpResponse>(
embedded_test_server(), "/ads_observer/incomplete_resource.js",
true /*relative_url_is_prefix*/);
ASSERT_TRUE(embedded_test_server()->Start());
auto waiter = CreateAdsPageLoadMetricsTestWaiter();
browser()->OpenURL(content::OpenURLParams(
embedded_test_server()->GetURL(
"/ads_observer/ad_with_incomplete_resource.html"),
content::Referrer(), WindowOpenDisposition::CURRENT_TAB,
ui::PAGE_TRANSITION_TYPED, false));
waiter->AddMinimumCompleteResourcesExpectation(3);
waiter->Wait();
int64_t initial_page_bytes = waiter->current_network_bytes();
// Make the response large enough so that normal editing to the resource files
// won't interfere with the test expectations.
const int response_kilobytes = 64;
const int response_bytes = response_kilobytes * 1024;
// Ad resource will not finish loading but should be reported to metrics.
incomplete_resource_response->WaitForRequest();
incomplete_resource_response->Send(page_load_metrics::kHttpOkResponseHeader);
incomplete_resource_response->Send(std::string(response_bytes, ' '));
// Wait for the resource update to be received for the incomplete response.
waiter->AddMinimumNetworkBytesExpectation(response_bytes);
waiter->Wait();
// Close all tabs instead of navigating as the embedded_test_server will
// hang waiting for loads to finish when we have an unfinished
// ControllableHttpResponse.
browser()->tab_strip_model()->CloseAllTabs();
int expected_page_kilobytes = (initial_page_bytes + response_bytes) / 1024;
histogram_tester.ExpectBucketCount(
"PageLoad.Clients.Ads.Bytes.FullPage.Network", expected_page_kilobytes,
1);
histogram_tester.ExpectBucketCount(
"PageLoad.Clients.Ads.Bytes.AdFrames.Aggregate.Network",
response_kilobytes, 1);
histogram_tester.ExpectBucketCount(
"PageLoad.Clients.Ads.Bytes.AdFrames.Aggregate.Total2",
response_kilobytes, 1);
histogram_tester.ExpectBucketCount(
"PageLoad.Clients.Ads.Bytes.AdFrames.PerFrame.Network",
response_kilobytes, 1);
histogram_tester.ExpectBucketCount(
"PageLoad.Clients.Ads.Bytes.AdFrames.PerFrame.Total2", response_kilobytes,
1);
auto entries =
ukm_recorder.GetEntriesByName(ukm::builders::AdFrameLoad::kEntryName);
EXPECT_EQ(1u, entries.size());
ukm_recorder.ExpectEntryMetric(
entries.front(), ukm::builders::AdFrameLoad::kLoading_NetworkBytesName,
ukm::GetExponentialBucketMinForBytes(response_bytes));
ukm_recorder.ExpectEntryMetric(
entries.front(), ukm::builders::AdFrameLoad::kLoading_CacheBytes2Name, 0);
}
// Verifies that the ad unloaded by the heavy ad intervention receives an
// intervention report prior to being unloaded.
IN_PROC_BROWSER_TEST_F(AdsPageLoadMetricsObserverResourceBrowserTest,
HeavyAdInterventionFired_ReportSent) {
base::HistogramTester histogram_tester;
auto incomplete_resource_response =
std::make_unique<net::test_server::ControllableHttpResponse>(
embedded_test_server(), "/ads_observer/incomplete_resource.js",
true /*relative_url_is_prefix*/);
ASSERT_TRUE(embedded_test_server()->Start());
// Create a navigation observer that will watch for the intervention to
// navigate the frame.
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
auto waiter = CreateAdsPageLoadMetricsTestWaiter();
GURL url = embedded_test_server()->GetURL(
"/ads_observer/ad_with_incomplete_resource.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
content::RenderFrameHost* ad_frame =
ChildFrameAt(web_contents->GetMainFrame(), 0);
content::DOMMessageQueue message_queue(ad_frame);
const std::string report_script = R"(
function process(report) {
if (report.body.id === 'HeavyAdIntervention')
window.domAutomationController.send('REPORT');
}
let observer = new ReportingObserver((reports, observer) => {
reports.forEach(process);
});
observer.observe();
window.addEventListener('unload', function(event) {
observer.takeRecords().forEach(process);
window.domAutomationController.send('END');
});
)";
EXPECT_TRUE(content::ExecJs(ad_frame, report_script,
content::EXECUTE_SCRIPT_NO_USER_GESTURE));
// Load a resource large enough to trigger the intervention.
page_load_metrics::LoadLargeResource(
incomplete_resource_response.get(),
page_load_metrics::kMaxHeavyAdNetworkSize);
std::string message;
bool got_report = false;
while (message_queue.WaitForMessage(&message)) {
if (message == "\"REPORT\"") {
got_report = true;
break;
}
if (message == "\"END\"")
break;
}
EXPECT_TRUE(got_report);
}
// Verifies that reports are sent to all children.
// crbug.com/1189635: flaky on win and linux.
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_WIN)
#define MAYBE_HeavyAdInterventionFired_ReportsToAllChildren \
DISABLED_HeavyAdInterventionFired_ReportsToAllChildren
#else
#define MAYBE_HeavyAdInterventionFired_ReportsToAllChildren \
HeavyAdInterventionFired_ReportsToAllChildren
#endif
IN_PROC_BROWSER_TEST_F(AdsPageLoadMetricsObserverResourceBrowserTest,
MAYBE_HeavyAdInterventionFired_ReportsToAllChildren) {
SetRulesetWithRules(
{subresource_filter::testing::CreateSuffixRule("frame_factory.html")});
base::HistogramTester histogram_tester;
auto large_resource =
std::make_unique<net::test_server::ControllableHttpResponse>(
embedded_test_server(), "/ads_observer/incomplete_resource.js",
false /*relative_url_is_prefix*/);
ASSERT_TRUE(embedded_test_server()->Start());
// Create a navigation observer that will watch for the intervention to
// navigate the frame.
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
content::TestNavigationObserver child_observer(web_contents, 2);
content::TestNavigationObserver error_observer(web_contents,
net::ERR_BLOCKED_BY_CLIENT);
auto waiter = CreateAdsPageLoadMetricsTestWaiter();
content::WebContentsConsoleObserver console_observer(web_contents);
console_observer.SetPattern("Ad was removed*");
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL(
"foo.com", "/ad_tagging/frame_factory.html")));
EXPECT_TRUE(ExecJs(web_contents,
"createAdFrame('/ad_tagging/frame_factory.html', '');"));
child_observer.Wait();
content::RenderFrameHost* ad_frame =
ChildFrameAt(web_contents->GetMainFrame(), 0);
auto cross_origin_ad_url = embedded_test_server()->GetURL(
"xyz.com", "/ad_tagging/frame_factory.html");
EXPECT_TRUE(ExecJs(
ad_frame,
"createAdFrame('/ads_observer/ad_with_incomplete_resource.html', '');",
content::EXECUTE_SCRIPT_NO_USER_GESTURE));
EXPECT_TRUE(ExecJs(ad_frame,
"createAdFrame('" + cross_origin_ad_url.spec() + "', '');",
content::EXECUTE_SCRIPT_NO_USER_GESTURE));
// Load a resource large enough to trigger the intervention.
page_load_metrics::LoadLargeResource(
large_resource.get(), page_load_metrics::kMaxHeavyAdNetworkSize);
error_observer.WaitForNavigationFinished();
// Every frame should get a report (ad_with_incomplete_resource.html loads two
// frames).
EXPECT_EQ(4u, console_observer.messages().size());
}
// Verifies that the frame is navigated to the intervention page when a
// heavy ad intervention triggers.
IN_PROC_BROWSER_TEST_F(AdsPageLoadMetricsObserverResourceBrowserTest,
HeavyAdInterventionEnabled_ErrorPageLoaded) {
base::HistogramTester histogram_tester;
auto incomplete_resource_response =
std::make_unique<net::test_server::ControllableHttpResponse>(
embedded_test_server(), "/ads_observer/incomplete_resource.js",
true /*relative_url_is_prefix*/);
ASSERT_TRUE(embedded_test_server()->Start());
// Create a navigation observer that will watch for the intervention to
// navigate the frame.
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
content::TestNavigationObserver error_observer(web_contents,
net::ERR_BLOCKED_BY_CLIENT);
auto waiter = CreateAdsPageLoadMetricsTestWaiter();
GURL url = embedded_test_server()->GetURL(
"/ads_observer/ad_with_incomplete_resource.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
// Load a resource large enough to trigger the intervention.
page_load_metrics::LoadLargeResource(
incomplete_resource_response.get(),
page_load_metrics::kMaxHeavyAdNetworkSize);
// Wait for the intervention page navigation to finish on the frame.
error_observer.WaitForNavigationFinished();
histogram_tester.ExpectUniqueSample(
kHeavyAdInterventionTypeHistogramId,
page_load_metrics::HeavyAdStatus::kNetwork, 1);
// Check that the ad frame was navigated to the intervention page.
EXPECT_FALSE(error_observer.last_navigation_succeeded());
histogram_tester.ExpectUniqueSample(
kHeavyAdInterventionTypeHistogramId,
page_load_metrics::HeavyAdStatus::kNetwork, 1);
histogram_tester.ExpectBucketCount(
"Blink.UseCounter.Features",
blink::mojom::WebFeature::kHeavyAdIntervention, 1);
}
class AdsPageLoadMetricsObserverResourceBrowserTestWithoutHeavyAdIntervention
: public AdsPageLoadMetricsObserverResourceBrowserTest {
public:
AdsPageLoadMetricsObserverResourceBrowserTestWithoutHeavyAdIntervention() {
// The experiment is "on" if either intervention or reporting is active.
feature_list_.InitWithFeatures(
{}, {heavy_ad_intervention::features::kHeavyAdIntervention,
heavy_ad_intervention::features::kHeavyAdInterventionWarning});
}
private:
base::test::ScopedFeatureList feature_list_;
};
// Check that when the heavy ad feature is disabled we don't navigate
// the frame.
IN_PROC_BROWSER_TEST_F(
AdsPageLoadMetricsObserverResourceBrowserTestWithoutHeavyAdIntervention,
ErrorPageNotLoaded) {
base::HistogramTester histogram_tester;
auto incomplete_resource_response =
std::make_unique<net::test_server::ControllableHttpResponse>(
embedded_test_server(), "/ads_observer/incomplete_resource.js",
true /*relative_url_is_prefix*/);
ASSERT_TRUE(embedded_test_server()->Start());
auto waiter = CreateAdsPageLoadMetricsTestWaiter();
GURL url = embedded_test_server()->GetURL(
"/ads_observer/ad_with_incomplete_resource.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
// Load a resource large enough to trigger the intervention.
page_load_metrics::LoadLargeResource(
incomplete_resource_response.get(),
page_load_metrics::kMaxHeavyAdNetworkSize);
// Wait for the resource update to be received for the large resource.
waiter->AddMinimumNetworkBytesExpectation(
page_load_metrics::kMaxHeavyAdNetworkSize);
waiter->Wait();
// We can't check whether the navigation didn't occur because the error page
// load is not synchronous. Instead check that we didn't log intervention UMA
// that is always recorded when the intervention occurs.
histogram_tester.ExpectTotalCount(kHeavyAdInterventionTypeHistogramId, 0);
histogram_tester.ExpectBucketCount(
"Blink.UseCounter.Features",
blink::mojom::WebFeature::kHeavyAdIntervention, 0);
}
// Check that we don't activate a HeavyAdIntervention field trial if we don't
// have a heavy ad.
IN_PROC_BROWSER_TEST_F(AdsPageLoadMetricsObserverResourceBrowserTest,
HeavyAdInterventionNoHeavyAd_FieldTrialNotActive) {
base::HistogramTester histogram_tester;
auto incomplete_resource_response =
std::make_unique<net::test_server::ControllableHttpResponse>(
embedded_test_server(), "/ads_observer/incomplete_resource.js",
true /*relative_url_is_prefix*/);
ASSERT_TRUE(embedded_test_server()->Start());
auto waiter = CreateAdsPageLoadMetricsTestWaiter();
GURL url = embedded_test_server()->GetURL(
"/ads_observer/ad_with_incomplete_resource.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
// Load a resource not large enough to trigger the intervention.
page_load_metrics::LoadLargeResource(
incomplete_resource_response.get(),
page_load_metrics::kMaxHeavyAdNetworkSize / 2);
// Wait for the resource update to be received for the large resource.
waiter->AddMinimumNetworkBytesExpectation(
page_load_metrics::kMaxHeavyAdNetworkSize / 2);
waiter->Wait();
histogram_tester.ExpectTotalCount(kHeavyAdInterventionTypeHistogramId, 0);
// Verify that the trial is not activated if no heavy ads are seen.
EXPECT_FALSE(base::FieldTrialList::IsTrialActive(
base::FeatureList::GetFieldTrial(
heavy_ad_intervention::features::kHeavyAdIntervention)
->trial_name()));
}
// Check that the Heavy Ad Intervention fires the correct number of times to
// protect privacy, and that after that limit is hit, the Ads Intervention
// Framework takes over for future navigations.
IN_PROC_BROWSER_TEST_F(AdsPageLoadMetricsObserverResourceBrowserTest,
HeavyAdInterventionBlocklistFull_InterventionBlocked) {
std::vector<std::unique_ptr<net::test_server::ControllableHttpResponse>>
http_responses(4);
for (auto& http_response : http_responses) {
http_response =
std::make_unique<net::test_server::ControllableHttpResponse>(
embedded_test_server(), "/ads_observer/incomplete_resource.js",
false /*relative_url_is_prefix*/);
}
base::HistogramTester histogram_tester;
ASSERT_TRUE(embedded_test_server()->Start());
// Create a waiter for the navigation and navigate.
auto waiter = CreateAdsPageLoadMetricsTestWaiter();
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL(
"foo.com", "/ad_tagging/frame_factory.html")));
// Load and block the resource. The ads intervention framework should not
// be triggered at this point.
LoadHeavyAdResourceAndWaitOrError(http_responses[0].get(), waiter.get(),
/*will_block=*/true);
histogram_tester.ExpectUniqueSample(
kHeavyAdInterventionTypeHistogramId,
page_load_metrics::HeavyAdStatus::kNetwork, 1);
histogram_tester.ExpectTotalCount(kAdsInterventionRecordedHistogram, 0);
histogram_tester.ExpectBucketCount(
"SubresourceFilter.Actions2",
subresource_filter::SubresourceFilterAction::kUIShown, 0);
// Block a second resource on the page. The ads intervention framework should
// not be triggered at this point.
LoadHeavyAdResourceAndWaitOrError(http_responses[1].get(), waiter.get(),
/*will_block=*/true);
histogram_tester.ExpectUniqueSample(
kHeavyAdInterventionTypeHistogramId,
page_load_metrics::HeavyAdStatus::kNetwork, 2);
histogram_tester.ExpectTotalCount(kAdsInterventionRecordedHistogram, 0);
histogram_tester.ExpectBucketCount(
"SubresourceFilter.Actions2",
subresource_filter::SubresourceFilterAction::kUIShown, 0);
// Create a new waiter for the next navigation and navigate.
waiter = CreateAdsPageLoadMetricsTestWaiter();
// Set query to ensure that it's not treated as a reload as preview metrics
// are not recorded for reloads.
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(),
embedded_test_server()->GetURL(
"foo.com", "/ad_tagging/frame_factory.html?avoid_reload")));
// Load and block the resource. The ads intervention framework should
// be triggered at this point.
LoadHeavyAdResourceAndWaitOrError(http_responses[2].get(), waiter.get(),
/*will_block=*/true);
histogram_tester.ExpectUniqueSample(
kHeavyAdInterventionTypeHistogramId,
page_load_metrics::HeavyAdStatus::kNetwork, 3);
histogram_tester.ExpectUniqueSample(
kAdsInterventionRecordedHistogram,
subresource_filter::mojom::AdsViolation::kHeavyAdsInterventionAtHostLimit,
1);
histogram_tester.ExpectBucketCount(
"SubresourceFilter.Actions2",
subresource_filter::SubresourceFilterAction::kUIShown, 0);
// Allow a second resource on the page. The ads intervention shouldn't fire a
// second time.
LoadHeavyAdResourceAndWaitOrError(http_responses[3].get(), waiter.get(),
/*will_block=*/false);
histogram_tester.ExpectUniqueSample(
kHeavyAdInterventionTypeHistogramId,
page_load_metrics::HeavyAdStatus::kNetwork, 3);
histogram_tester.ExpectUniqueSample(
kAdsInterventionRecordedHistogram,
subresource_filter::mojom::AdsViolation::kHeavyAdsInterventionAtHostLimit,
1);
histogram_tester.ExpectBucketCount(
"SubresourceFilter.Actions2",
subresource_filter::SubresourceFilterAction::kUIShown, 0);
// Reset the waiter and navigate again. Check we show the Ads Intervention UI.
waiter.reset();
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL(
"foo.com", "/ad_tagging/frame_factory.html")));
histogram_tester.ExpectUniqueSample(
kAdsInterventionRecordedHistogram,
subresource_filter::mojom::AdsViolation::kHeavyAdsInterventionAtHostLimit,
1);
histogram_tester.ExpectBucketCount(
"SubresourceFilter.Actions2",
subresource_filter::SubresourceFilterAction::kUIShown, 1);
}
// Verifies that the blocklist is setup correctly and the intervention triggers
// in incognito mode.
IN_PROC_BROWSER_TEST_F(AdsPageLoadMetricsObserverResourceBrowserTest,
HeavyAdInterventionIncognitoMode_InterventionFired) {
base::HistogramTester histogram_tester;
auto incomplete_resource_response =
std::make_unique<net::test_server::ControllableHttpResponse>(
embedded_test_server(), "/ads_observer/incomplete_resource.js",
true /*relative_url_is_prefix*/);
ASSERT_TRUE(embedded_test_server()->Start());
Browser* incognito_browser = CreateIncognitoBrowser();
content::WebContents* web_contents =
incognito_browser->tab_strip_model()->GetActiveWebContents();
// Create a navigation observer that will watch for the intervention to
// navigate the frame.
content::TestNavigationObserver error_observer(web_contents,
net::ERR_BLOCKED_BY_CLIENT);
// Create a waiter for the incognito contents.
auto waiter = std::make_unique<page_load_metrics::PageLoadMetricsTestWaiter>(
web_contents);
GURL url = embedded_test_server()->GetURL(
"/ads_observer/ad_with_incomplete_resource.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(incognito_browser, url));
// Load a resource large enough to trigger the intervention.
page_load_metrics::LoadLargeResource(
incomplete_resource_response.get(),
page_load_metrics::kMaxHeavyAdNetworkSize);
// Wait for the intervention page navigation to finish on the frame.
error_observer.WaitForNavigationFinished();
// Check that the ad frame was navigated to the intervention page.
EXPECT_FALSE(error_observer.last_navigation_succeeded());
}
// Verify that UKM metrics are recorded correctly.
IN_PROC_BROWSER_TEST_F(AdsPageLoadMetricsObserverResourceBrowserTest,
RecordedUKMMetrics) {
base::HistogramTester histogram_tester;
ukm::TestAutoSetUkmRecorder ukm_recorder;
ASSERT_TRUE(embedded_test_server()->Start());
auto waiter = CreateAdsPageLoadMetricsTestWaiter();
content::WebContents* contents =
browser()->tab_strip_model()->GetActiveWebContents();
GURL url = embedded_test_server()->GetURL("foo.com",
"/ad_tagging/frame_factory.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
contents->GetMainFrame()->ExecuteJavaScriptForTests(
u"createAdFrame('multiple_mimes.html', 'test');", base::NullCallback());
waiter->AddMinimumAdResourceExpectation(8);
waiter->Wait();
// Close all tabs to report metrics.
browser()->tab_strip_model()->CloseAllTabs();
// Verify UKM Metrics recorded.
auto entries =
ukm_recorder.GetEntriesByName(ukm::builders::AdPageLoad::kEntryName);
EXPECT_EQ(1u, entries.size());
ukm_recorder.ExpectEntrySourceHasUrl(entries.front(), url);
EXPECT_GT(*ukm_recorder.GetEntryMetric(
entries.front(), ukm::builders::AdPageLoad::kAdBytesName),
0);
EXPECT_GT(
*ukm_recorder.GetEntryMetric(
entries.front(), ukm::builders::AdPageLoad::kMainframeAdBytesName),
0);
EXPECT_GT(
*ukm_recorder.GetEntryMetric(
entries.front(), ukm::builders::AdPageLoad::kAdJavascriptBytesName),
0);
EXPECT_GT(*ukm_recorder.GetEntryMetric(
entries.front(), ukm::builders::AdPageLoad::kAdVideoBytesName),
0);
}
void WaitForRAF(content::DOMMessageQueue* message_queue) {
std::string message;
while (message_queue->WaitForMessage(&message)) {
if (message == "\"RAF DONE\"")
break;
}
EXPECT_EQ("\"RAF DONE\"", message);
}
// Test that rAF events are measured as part of the cpu metrics.
IN_PROC_BROWSER_TEST_F(AdsPageLoadMetricsObserverBrowserTest,
FrameRAFTriggersCpuUpdate) {
base::HistogramTester histogram_tester;
auto waiter = CreatePageLoadMetricsTestWaiter();
// Navigate to the page and set up the waiter.
base::TimeTicks start_time = base::TimeTicks::Now();
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL("/iframe_blank.html")));
waiter->AddMinimumAggregateCpuTimeExpectation(base::Milliseconds(300));
// Navigate the iframe to a page with a delayed rAF, waiting for it to
// complete. Long enough to guarantee the frame client sees a cpu time
// update. (See: LocalFrame::AddTaskTime kTaskDurationSendThreshold).
NavigateIframeToURL(
web_contents(), "test",
embedded_test_server()->GetURL(
"a.com", "/ads_observer/expensive_animation_frame.html?delay=300"));
// Wait until we've received the cpu update and navigate away.
waiter->Wait();
ASSERT_TRUE(
ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL)));
// The elapsed_time is an upper bound on the overall page time, as it runs
// from just before to just after activation. The task itself is guaranteed
// to have run at least 300ms, so we can derive a minimum percent of cpu time
// that the task should have taken.
base::TimeDelta elapsed_time = base::TimeTicks::Now() - start_time;
EXPECT_GE(elapsed_time.InMilliseconds(), 300);
// Ensure that there is a single entry that is at least the percent specified.
int min_percent =
100 * 300 /
page_load_metrics::PeakCpuAggregator::kWindowSize.InMilliseconds();
auto samples = histogram_tester.GetAllSamples(kPeakWindowdPercentHistogramId);
EXPECT_EQ(1u, samples.size());
EXPECT_EQ(1, samples.front().count);
EXPECT_LE(min_percent, samples.front().min);
}
// Test that rAF events are measured as part of the cpu metrics.
IN_PROC_BROWSER_TEST_F(AdsPageLoadMetricsObserverBrowserTest,
TwoRAFFramesTriggerCpuUpdates) {
base::HistogramTester histogram_tester;
auto waiter = CreatePageLoadMetricsTestWaiter();
// Navigate to the page and set up the waiter.
content::DOMMessageQueue message_queue(web_contents());
base::TimeTicks start_time = base::TimeTicks::Now();
// Each rAF frame in two_raf_frames delays for 200ms.
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(),
embedded_test_server()->GetURL("/ads_observer/two_raf_frames.html")));
// Wait for both RAF calls to finish
WaitForRAF(&message_queue);
WaitForRAF(&message_queue);
ASSERT_TRUE(
ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL)));
// The elapsed_time is an upper bound on the overall page time, as it runs
// from just before to just after activation. The task itself is guaranteed
// to have run at least 200ms, so we can derive a minimum percent of cpu time
// that the task should have taken.
base::TimeDelta elapsed_time = base::TimeTicks::Now() - start_time;
EXPECT_GE(elapsed_time.InMilliseconds(), 200);
// Ensure that there is a single entry that is at least the peak windowed
// percent of 400ms.
int min_percent =
100 * 400 /
page_load_metrics::PeakCpuAggregator::kWindowSize.InMilliseconds();
auto samples = histogram_tester.GetAllSamples(kPeakWindowdPercentHistogramId);
EXPECT_EQ(1u, samples.size());
EXPECT_EQ(1, samples.front().count);
EXPECT_LE(min_percent, samples.front().min);
}
// Test that cpu time aggregation across a subframe navigation is cumulative.
IN_PROC_BROWSER_TEST_F(AdsPageLoadMetricsObserverBrowserTest,
AggregateCpuTriggersCpuUpdateOverSubframeNavigate) {
base::HistogramTester histogram_tester;
auto waiter = CreatePageLoadMetricsTestWaiter();
// Navigate to the page and set up the waiter.
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL("/iframe_blank.html")));
waiter->AddMinimumAggregateCpuTimeExpectation(base::Milliseconds(100));
// Navigate twice to a page delaying 50ms. The first and second navigations
// by themselves aren't enough to trigger a cpu update, but when combined an
// update fires. (See: LocalFrame::AddTaskTime kTaskDurationSendThreshold).
// Navigate the first time, waiting for it to complete so that the work is
// observed, then renavigate.
content::DOMMessageQueue message_queue(web_contents());
NavigateIframeToURL(
web_contents(), "test",
embedded_test_server()->GetURL(
"a.com", "/ads_observer/expensive_animation_frame.html?delay=50"));
WaitForRAF(&message_queue);
NavigateIframeToURL(
web_contents(), "test",
embedded_test_server()->GetURL(
"a.com", "/ads_observer/expensive_animation_frame.html?delay=50"));
// Wait until we've received the cpu update and navigate away. If CPU is
// not cumulative, this hangs waiting for a CPU update indefinitely.
waiter->Wait();
ASSERT_TRUE(
ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL)));
}
// Test that cpu metrics are cumulative across subframe navigations.
IN_PROC_BROWSER_TEST_F(AdsPageLoadMetricsObserverBrowserTest,
SubframeNavigate_CpuTimesCumulative) {
base::HistogramTester histogram_tester;
auto waiter = CreatePageLoadMetricsTestWaiter();
// Navigate to the page and set up the waiter.
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL("/iframe_blank.html")));
waiter->AddMinimumAggregateCpuTimeExpectation(base::Milliseconds(300));
// Navigate twice to a page with enough cumulative time to measure
// at least 1% peak windowed percent (300ms), either individually leads to 0
// % peak windowed percent.
base::TimeTicks start_time = base::TimeTicks::Now();
content::DOMMessageQueue message_queue(web_contents());
NavigateIframeToURL(
web_contents(), "test",
embedded_test_server()->GetURL(
"a.com", "/ads_observer/expensive_animation_frame.html?delay=200"));
WaitForRAF(&message_queue);
NavigateIframeToURL(
web_contents(), "test",
embedded_test_server()->GetURL(
"a.com", "/ads_observer/expensive_animation_frame.html?delay=100"));
// Wait until we've received the cpu update and navigate away.
waiter->Wait();
ASSERT_TRUE(
ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL)));
// The elapsed_time is an upper bound on the overall page time, as it runs
// from just before to just after activation. The tasks in aggregate are
// guaranteed to have run for at least 300ms, so we can derive a minimum
// percent of cpu time that the tasks should have taken.
base::TimeDelta elapsed_time = base::TimeTicks::Now() - start_time;
EXPECT_GE(elapsed_time.InMilliseconds(), 300);
// Ensure that there is a single entry that is at least the peak windowed
// percent of 400ms.
int min_percent =
100 * 300 /
page_load_metrics::PeakCpuAggregator::kWindowSize.InMilliseconds();
auto samples = histogram_tester.GetAllSamples(kPeakWindowdPercentHistogramId);
EXPECT_EQ(1u, samples.size());
EXPECT_EQ(1, samples.front().count);
EXPECT_LE(min_percent, samples.front().min);
}
IN_PROC_BROWSER_TEST_F(AdsPageLoadMetricsObserverBrowserTest,
DisallowedAdFrames_NotMeasured) {
base::HistogramTester histogram_tester;
ukm::TestAutoSetUkmRecorder ukm_recorder;
ResetConfiguration(subresource_filter::Configuration(
subresource_filter::mojom::ActivationLevel::kEnabled,
subresource_filter::ActivationScope::ALL_SITES));
// cross_site_iframe_factory loads URLs like:
// http://b.com:40919/cross_site_iframe_factory.html?b()
SetRulesetToDisallowURLsWithPathSuffix("b()");
const GURL main_url(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(b,b,c,d)"));
auto waiter = CreatePageLoadMetricsTestWaiter();
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), main_url));
// One favicon resource and 2 resources for frames a,c,d
waiter->AddPageExpectation(
page_load_metrics::PageLoadMetricsTestWaiter::TimingField::kLoadEvent);
waiter->Wait();
// Navigate away to force the histogram recording.
ASSERT_TRUE(
ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL)));
// Check that adframes are not included in UKM's or UMA metrics.
auto entries =
ukm_recorder.GetEntriesByName(ukm::builders::AdFrameLoad::kEntryName);
EXPECT_EQ(0u, entries.size());
histogram_tester.ExpectTotalCount(
"PageLoad.Clients.Ads.Bytes.AdFrames.Aggregate.Total2", 0);
histogram_tester.ExpectTotalCount(
"PageLoad.Clients.Ads.FrameCounts.AdFrames.Total", 0);
}
// DummyMemoryObserver is a subclass of V8DetailedMemoryObserverAnySeq so
// that we can spin up a request in the AdsMemoryMeasurementBrowserTest with
// MeasurementMode::kEagerForTesting, which will make measurements available
// to the PageLoadMetricsMemoryTracker much more quickly than they would be
// otherwise.
class DummyMemoryObserver
: public performance_manager::v8_memory::V8DetailedMemoryObserverAnySeq {
public:
DummyMemoryObserver() = default;
~DummyMemoryObserver() override = default;
void OnV8MemoryMeasurementAvailable(
performance_manager::RenderProcessHostId process_id,
const performance_manager::v8_memory::V8DetailedMemoryProcessData&
process_data,
const performance_manager::v8_memory::V8DetailedMemoryObserverAnySeq::
FrameDataMap& frame_data) override {}
};
class AdsMemoryMeasurementBrowserTest
: public subresource_filter::SubresourceFilterBrowserTest {
public:
AdsMemoryMeasurementBrowserTest() = default;
AdsMemoryMeasurementBrowserTest(const AdsMemoryMeasurementBrowserTest&) =
delete;
AdsMemoryMeasurementBrowserTest& operator=(
const AdsMemoryMeasurementBrowserTest&) = delete;
~AdsMemoryMeasurementBrowserTest() override = default;
void SetUp() override {
performance_manager::v8_memory::internal::
SetEagerMemoryMeasurementEnabledForTesting(true);
std::vector<base::Feature> enabled = {
subresource_filter::kAdTagging, features::kV8PerFrameMemoryMonitoring};
std::vector<base::Feature> disabled = {};
scoped_feature_list_.InitWithFeatures(enabled, disabled);
subresource_filter::SubresourceFilterBrowserTest::SetUp();
}
std::unique_ptr<page_load_metrics::PageLoadMetricsTestWaiter>
CreatePageLoadMetricsTestWaiter() {
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
return std::make_unique<page_load_metrics::PageLoadMetricsTestWaiter>(
web_contents);
}
std::unordered_set<content::GlobalRenderFrameHostId,
content::GlobalRenderFrameHostIdHasher>
GetFrameRoutingIds() {
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
std::unordered_set<content::GlobalRenderFrameHostId,
content::GlobalRenderFrameHostIdHasher>
frame_routing_ids;
web_contents->GetMainFrame()->ForEachRenderFrameHost(base::BindRepeating(
[](std::unordered_set<content::GlobalRenderFrameHostId,
content::GlobalRenderFrameHostIdHasher>*
frame_routing_ids,
content::RenderFrameHost* frame) {
frame_routing_ids->insert(frame->GetGlobalId());
},
&frame_routing_ids));
return frame_routing_ids;
}
private:
std::unique_ptr<page_load_metrics::PageLoadMetricsTestWaiter> waiter_;
base::test::ScopedFeatureList scoped_feature_list_;
};
IN_PROC_BROWSER_TEST_F(AdsMemoryMeasurementBrowserTest,
SingleAdFrame_MaxMemoryBytesRecorded) {
base::HistogramTester histogram_tester;
// Instantiate a memory request and observer to set memory measurement
// polling parameters.
std::unique_ptr<performance_manager::v8_memory::V8DetailedMemoryRequestAnySeq>
memory_request = std::make_unique<
performance_manager::v8_memory::V8DetailedMemoryRequestAnySeq>(
base::Seconds(1),
performance_manager::v8_memory::V8DetailedMemoryRequest::
MeasurementMode::kEagerForTesting);
auto memory_observer = std::make_unique<DummyMemoryObserver>();
memory_request->AddObserver(memory_observer.get());
// cross_site_iframe_factory loads URLs like:
// http://b.com:40919/cross_site_iframe_factory.html?b()
SetRulesetWithRules({subresource_filter::testing::CreateSuffixRule("b()")});
const GURL main_url(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(b)"));
// Create a waiter, navigate the mainframe to "about:blank", and prime the
// waiter with the mainframe's routing ID.
auto waiter = CreatePageLoadMetricsTestWaiter();
ASSERT_TRUE(
ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL)));
waiter->AddMemoryUpdateExpectation(browser()
->tab_strip_model()
->GetActiveWebContents()
->GetMainFrame()
->GetGlobalId());
// Navigate to the main URL.
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), main_url));
// Add any additional frame routing IDs and wait until we get positive
// memory measurements for each frame.
for (content::GlobalRenderFrameHostId id : GetFrameRoutingIds())
waiter->AddMemoryUpdateExpectation(id);
waiter->Wait();
// Navigate away to force the histogram recording.
ASSERT_TRUE(
ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL)));
histogram_tester.ExpectTotalCount(kMemoryPerFrameMaxHistogramId, 1);
EXPECT_GT(
histogram_tester.GetAllSamples(kMemoryPerFrameMaxHistogramId)[0].min, 0);
histogram_tester.ExpectTotalCount(kMemoryAggregateMaxHistogramId, 1);
EXPECT_GT(
histogram_tester.GetAllSamples(kMemoryAggregateMaxHistogramId)[0].min, 0);
histogram_tester.ExpectTotalCount(kMemoryMainFrameMaxHistogramId, 1);
EXPECT_GT(
histogram_tester.GetAllSamples(kMemoryMainFrameMaxHistogramId)[0].min, 0);
histogram_tester.ExpectTotalCount(kMemoryUpdateCountHistogramId, 1);
EXPECT_GE(
histogram_tester.GetAllSamples(kMemoryUpdateCountHistogramId)[0].min, 1);
memory_request->RemoveObserver(memory_observer.get());
}