blob: f8f0be2fb27e02b0d957d73da77206efb0f626b2 [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/macros.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/metrics/subprocess_metrics_provider.h"
#include "chrome/browser/page_load_metrics/observers/ad_metrics/ads_page_load_metrics_observer.h"
#include "chrome/browser/page_load_metrics/observers/ad_metrics/frame_data.h"
#include "chrome/browser/page_load_metrics/observers/use_counter_page_load_metrics_observer.h"
#include "chrome/browser/page_load_metrics/page_load_metrics_test_waiter.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/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/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/download_test_observer.h"
#include "content/public/test/test_navigation_observer.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/ukm_builders.h"
#include "services/metrics/public/cpp/ukm_source.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/download/download_stats.h"
#include "url/gurl.h"
#include "url/url_constants.h"
namespace {
const char kCrossOriginHistogramId[] =
"PageLoad.Clients.Ads.FrameCounts.AdFrames.PerFrame."
"OriginStatus";
const char kAdUserActivationHistogramId[] =
"PageLoad.Clients.Ads.FrameCounts.AdFrames.PerFrame."
"UserActivation";
const char kSqrtNumberOfPixelsHistogramId[] =
"PageLoad.Clients.Ads.FrameCounts.AdFrames.PerFrame."
"SqrtNumberOfPixels";
const char kSmallestDimensionHistogramId[] =
"PageLoad.Clients.Ads.FrameCounts.AdFrames.PerFrame."
"SmallestDimension";
const char kAdFrameSizeInterventionHistogramId[] =
"PageLoad.Clients.Ads.FrameCounts.AdFrames.PerFrame."
"SizeIntervention";
enum class Origin {
kNavigation,
kAnchorAttribute,
};
std::ostream& operator<<(std::ostream& os, Origin origin) {
switch (origin) {
case Origin::kNavigation:
return os << "Navigation";
case Origin::kAnchorAttribute:
return os << "AnchorAttribute";
}
}
enum class SandboxOption {
kNoSandbox,
kDisallowDownloadsWithoutUserActivation,
kAllowDownloadsWithoutUserActivation,
};
std::ostream& operator<<(std::ostream& os, SandboxOption sandbox_option) {
switch (sandbox_option) {
case SandboxOption::kNoSandbox:
return os << "NoSandbox";
case SandboxOption::kDisallowDownloadsWithoutUserActivation:
return os << "DisallowDownloadsWithoutUserActivation";
case SandboxOption::kAllowDownloadsWithoutUserActivation:
return os << "AllowDownloadsWithoutUserActivation";
}
}
// Allow PageLoadMetricsTestWaiter to be initialized for a new web content
// before the first commit.
class PopupPageLoadMetricsWaiterInitializer : public TabStripModelObserver {
public:
PopupPageLoadMetricsWaiterInitializer(
TabStripModel* tab_strip_model,
std::unique_ptr<page_load_metrics::PageLoadMetricsTestWaiter>* waiter)
: waiter_(waiter), scoped_observer_(this) {
scoped_observer_.Add(tab_strip_model);
}
void OnTabStripModelChanged(
TabStripModel* tab_strip_model,
const TabStripModelChange& change,
const TabStripSelectionChange& selection) override {
if (change.type() == TabStripModelChange::kInserted &&
selection.active_tab_changed()) {
DCHECK(waiter_ && !(*waiter_));
*waiter_ = std::make_unique<page_load_metrics::PageLoadMetricsTestWaiter>(
tab_strip_model->GetActiveWebContents());
}
}
private:
std::unique_ptr<page_load_metrics::PageLoadMetricsTestWaiter>* waiter_ =
nullptr;
ScopedObserver<TabStripModel, PopupPageLoadMetricsWaiterInitializer>
scoped_observer_;
DISALLOW_COPY_AND_ASSIGN(PopupPageLoadMetricsWaiterInitializer);
};
} // namespace
class AdsPageLoadMetricsObserverBrowserTest
: public subresource_filter::SubresourceFilterBrowserTest {
public:
AdsPageLoadMetricsObserverBrowserTest()
: subresource_filter::SubresourceFilterBrowserTest() {
scoped_feature_list_.InitAndEnableFeature(subresource_filter::kAdTagging);
}
~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 SetUpOnMainThread() override {
SubresourceFilterBrowserTest::SetUpOnMainThread();
SetRulesetWithRules(
{subresource_filter::testing::CreateSuffixRule("ad_iframe_writer.js"),
subresource_filter::testing::CreateSuffixRule("ad_script.js")});
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
DISALLOW_COPY_AND_ASSIGN(AdsPageLoadMetricsObserverBrowserTest);
};
// Test that an embedded ad is same origin.
IN_PROC_BROWSER_TEST_F(AdsPageLoadMetricsObserverBrowserTest,
OriginStatusMetricEmbedded) {
base::HistogramTester histogram_tester;
auto waiter = CreatePageLoadMetricsTestWaiter();
ui_test_utils::NavigateToURL(
browser(),
embedded_test_server()->GetURL("/ads_observer/srcdoc_embedded_ad.html"));
waiter->AddMinimumCompleteResourcesExpectation(4);
waiter->Wait();
ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL));
histogram_tester.ExpectUniqueSample(kCrossOriginHistogramId,
FrameData::OriginStatus::kSame, 1);
}
// Test that an empty embedded ad isn't reported at all.
IN_PROC_BROWSER_TEST_F(AdsPageLoadMetricsObserverBrowserTest,
OriginStatusMetricEmbeddedEmpty) {
base::HistogramTester histogram_tester;
ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL(
"/ads_observer/srcdoc_embedded_ad_empty.html"));
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) {
base::HistogramTester histogram_tester;
auto waiter = CreatePageLoadMetricsTestWaiter();
ui_test_utils::NavigateToURL(
browser(),
embedded_test_server()->GetURL("/ads_observer/same_origin_ad.html"));
waiter->AddMinimumCompleteResourcesExpectation(4);
waiter->Wait();
ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL));
histogram_tester.ExpectUniqueSample(kCrossOriginHistogramId,
FrameData::OriginStatus::kSame, 1);
}
// Test that an ad with a different origin as the main page is cross origin.
IN_PROC_BROWSER_TEST_F(AdsPageLoadMetricsObserverBrowserTest,
OriginStatusMetricCross) {
// Note: Cannot navigate cross-origin without dynamically generating the URL.
base::HistogramTester histogram_tester;
auto waiter = CreatePageLoadMetricsTestWaiter();
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.
waiter->AddPageExpectation(
page_load_metrics::PageLoadMetricsTestWaiter::TimingField::kLoadEvent);
waiter->Wait();
ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL));
histogram_tester.ExpectUniqueSample(kCrossOriginHistogramId,
FrameData::OriginStatus::kCross, 1);
}
IN_PROC_BROWSER_TEST_F(AdsPageLoadMetricsObserverBrowserTest,
UserActivationSetOnFrame) {
base::HistogramTester histogram_tester;
auto waiter = CreatePageLoadMetricsTestWaiter();
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/frame_factory.html', '');"));
EXPECT_TRUE(content::ExecuteScriptWithoutUserGesture(
web_contents, "createAdFrame('/ad_tagging/frame_factory.html', '');"));
// Wait for the frames resources to be loaded as we only log histograms for
// frames that have non-zero bytes. Four resources per frame and one favicon.
waiter->AddMinimumCompleteResourcesExpectation(13);
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));
ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL));
histogram_tester.ExpectBucketCount(
kAdUserActivationHistogramId,
FrameData::UserActivationStatus::kReceivedActivation, 1);
histogram_tester.ExpectBucketCount(
kAdUserActivationHistogramId,
FrameData::UserActivationStatus::kNoActivation, 1);
}
// 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,
DocOverwritesNavigation) {
content::DOMMessageQueue msg_queue;
base::HistogramTester histogram_tester;
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.
ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL));
// TODO(johnidel): Check that the subresources of the new frame are reported
// correctly. Resources from a failed provisional load are not reported to
// resource data updates, causing this adframe to not be recorded. This is an
// uncommon case but should be reported. See crbug.com/914893.
}
// Test that a blank ad subframe that is docwritten correctly reports metrics.
IN_PROC_BROWSER_TEST_F(AdsPageLoadMetricsObserverBrowserTest,
DocWriteAboutBlankAdframe) {
base::HistogramTester histogram_tester;
auto waiter = CreatePageLoadMetricsTestWaiter();
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.
ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL));
histogram_tester.ExpectUniqueSample(
"PageLoad.Clients.Ads.FrameCounts.AnyParentFrame."
"AdFrames",
1, 1);
histogram_tester.ExpectUniqueSample(
"PageLoad.Clients.Ads.Bytes.AdFrames.Aggregate.Total", 0 /* < 1 KB */, 1);
}
IN_PROC_BROWSER_TEST_F(AdsPageLoadMetricsObserverBrowserTest,
SubresourceFilter) {
base::HistogramTester histogram_tester;
// 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();
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.
ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL));
histogram_tester.ExpectUniqueSample(
"PageLoad.Clients.Ads.FrameCounts.AnyParentFrame."
"AdFrames",
2, 1);
}
// Test that a frame without display:none is reported as visible.
IN_PROC_BROWSER_TEST_F(AdsPageLoadMetricsObserverBrowserTest,
VisibleAdframeRecorded) {
base::HistogramTester histogram_tester;
auto waiter = CreatePageLoadMetricsTestWaiter();
ui_test_utils::NavigateToURL(browser(),
embedded_test_server()->GetURL(
"/ads_observer/display_block_adframe.html"));
waiter->AddMinimumCompleteResourcesExpectation(4);
waiter->Wait();
ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL));
histogram_tester.ExpectTotalCount(
"PageLoad.Clients.Ads.Visible.Bytes.AdFrames.PerFrame.Total", 1);
histogram_tester.ExpectTotalCount(
"PageLoad.Clients.Ads.Bytes.AdFrames.PerFrame.Total", 1);
histogram_tester.ExpectTotalCount(
"PageLoad.Clients.Ads.NonVisible.Bytes.AdFrames.PerFrame.Total", 0);
}
IN_PROC_BROWSER_TEST_F(AdsPageLoadMetricsObserverBrowserTest,
DisplayNoneAdframeRecorded) {
base::HistogramTester histogram_tester;
auto waiter = CreatePageLoadMetricsTestWaiter();
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.
ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL));
histogram_tester.ExpectTotalCount(
"PageLoad.Clients.Ads.NonVisible.Bytes.AdFrames.PerFrame.Total", 1);
histogram_tester.ExpectTotalCount(
"PageLoad.Clients.Ads.Bytes.AdFrames.PerFrame.Total", 1);
histogram_tester.ExpectTotalCount(
"PageLoad.Clients.Ads.Visible.Bytes.AdFrames.PerFrame.Total", 0);
}
// 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) {
base::HistogramTester histogram_tester;
auto waiter = CreatePageLoadMetricsTestWaiter();
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 100x100 iframe.
ASSERT_TRUE(ExecJs(
web_contents,
"let frame = createAdIframe(); frame.width=100; frame.height = 100; "
"frame.src = '/ads_observer/pixel.png';"));
// Create a 0x0 iframe.
ASSERT_TRUE(ExecJs(
web_contents,
"frame = createAdIframe(); frame.width=0; frame.height = 0; frame.src = "
"'/ads_observer/pixel.png';"));
// Create a 10 x 1000 iframe.
ASSERT_TRUE(
ExecJs(web_contents,
"frame = createAdIframe(); frame.width=10; frame.height = 1000; "
"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->Wait();
// Navigate away to force the histogram recording.
ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL));
histogram_tester.ExpectBucketCount(kSqrtNumberOfPixelsHistogramId, 100, 2);
histogram_tester.ExpectBucketCount(kSqrtNumberOfPixelsHistogramId, 0, 1);
histogram_tester.ExpectBucketCount(kSmallestDimensionHistogramId, 0, 1);
histogram_tester.ExpectBucketCount(kSmallestDimensionHistogramId, 10, 1);
histogram_tester.ExpectBucketCount(kSmallestDimensionHistogramId, 100, 1);
}
IN_PROC_BROWSER_TEST_F(AdsPageLoadMetricsObserverBrowserTest,
FrameWithSmallAreaNotConsideredVisible) {
base::HistogramTester histogram_tester;
auto waiter = CreatePageLoadMetricsTestWaiter();
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/pixel.png';"));
// Wait for each frames resource to load so that they will have non-zero
// bytes.
waiter->AddMinimumCompleteResourcesExpectation(4);
waiter->Wait();
// Navigate away to force the histogram recording.
ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL));
histogram_tester.ExpectTotalCount(
"PageLoad.Clients.Ads.Visible.FrameCounts.AdFrames.PerFrame."
"SmallestDimension",
0);
histogram_tester.ExpectUniqueSample(
"PageLoad.Clients.Ads.NonVisible.FrameCounts.AdFrames.PerFrame."
"SmallestDimension",
4, 1);
}
IN_PROC_BROWSER_TEST_F(AdsPageLoadMetricsObserverBrowserTest,
AdFrameSameOriginByteMetrics) {
base::HistogramTester histogram_tester;
// cross_site_iframe_factory loads URLs like:
// http://b.com:40919/cross_site_iframe_factory.html?b()
SetRulesetWithRules({subresource_filter::testing::CreateSuffixRule("b()))"),
subresource_filter::testing::CreateSuffixRule("e())")});
const GURL main_url(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(b(c(),d(b())),e(e,e()))"));
auto waiter = CreatePageLoadMetricsTestWaiter();
ui_test_utils::NavigateToURL(browser(), main_url);
// One favicon resource and 2 resources for each frame.
waiter->AddMinimumCompleteResourcesExpectation(17);
waiter->Wait();
// Navigate away to force the histogram recording.
ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL));
// Verify that iframe e is only same origin.
histogram_tester.ExpectBucketCount(
"PageLoad.Clients.Ads.Bytes.AdFrames.PerFrame.PercentSameOrigin", 100, 1);
// Verify that iframe b counts subframes as cross origin and a nested same
// origin subframe as same origin.
histogram_tester.ExpectBucketCount(
"PageLoad.Clients.Ads.Bytes.AdFrames.PerFrame.PercentSameOrigin", 50, 1);
// Verify that all iframe are treated as cross-origin to the page. Only 1/8 of
// resources are on origin a.com.
histogram_tester.ExpectBucketCount(
"PageLoad.Clients.Ads.Bytes.FullPage.PercentSameOrigin", 12.5, 1);
}
class AdsPageLoadMetricsTestWaiter
: public page_load_metrics::PageLoadMetricsTestWaiter {
public:
explicit AdsPageLoadMetricsTestWaiter(content::WebContents* web_contents)
: page_load_metrics::PageLoadMetricsTestWaiter(web_contents) {}
void AddMinimumAdResourceExpectation(int num_ad_resources) {
expected_minimum_num_ad_resources_ = num_ad_resources;
}
protected:
bool ExpectationsSatisfied() const override {
int num_ad_resources = 0;
for (auto& kv : page_resources_) {
if (kv.second->reported_as_ad_resource)
num_ad_resources++;
}
return num_ad_resources >= expected_minimum_num_ad_resources_ &&
PageLoadMetricsTestWaiter::ExpectationsSatisfied();
}
private:
int expected_minimum_num_ad_resources_ = 0;
};
class AdsPageLoadMetricsObserverResourceBrowserTest
: public subresource_filter::SubresourceFilterBrowserTest {
public:
AdsPageLoadMetricsObserverResourceBrowserTest() {
scoped_feature_list_.InitAndEnableFeature(subresource_filter::kAdTagging);
}
~AdsPageLoadMetricsObserverResourceBrowserTest() override {}
void SetUpOnMainThread() override {
host_resolver()->AddRule("*", "127.0.0.1");
SetRulesetWithRules(
{subresource_filter::testing::CreateSuffixRule("ad_script.js"),
subresource_filter::testing::CreateSuffixRule("ad_script_2.js"),
subresource_filter::testing::CreateSuffixRule("disallow.zip")});
}
void OpenLinkInFrame(const content::ToRenderFrameHost& adapter,
const std::string& link_id,
bool has_gesture) {
std::string open_link_script = base::StringPrintf(
R"(
var evt = document.createEvent("MouseEvent");
evt.initMouseEvent('click', true, true);
document.getElementById('%s').dispatchEvent(evt);
)",
link_id.c_str());
if (has_gesture) {
EXPECT_TRUE(ExecuteScript(adapter, open_link_script));
} else {
EXPECT_TRUE(ExecuteScriptWithoutUserGesture(adapter, open_link_script));
}
}
protected:
std::unique_ptr<AdsPageLoadMetricsTestWaiter>
CreateAdsPageLoadMetricsTestWaiter() {
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
return std::make_unique<AdsPageLoadMetricsTestWaiter>(web_contents);
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
IN_PROC_BROWSER_TEST_F(AdsPageLoadMetricsObserverResourceBrowserTest,
ReceivedAdResources) {
embedded_test_server()->ServeFilesFromSourceDirectory(
"chrome/test/data/ad_tagging");
content::SetupCrossSiteRedirector(embedded_test_server());
ASSERT_TRUE(embedded_test_server()->Start());
auto waiter = CreateAdsPageLoadMetricsTestWaiter();
ui_test_utils::NavigateToURL(
browser(),
embedded_test_server()->GetURL("foo.com", "/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) {
embedded_test_server()->ServeFilesFromSourceDirectory(
"chrome/test/data/ad_tagging");
content::SetupCrossSiteRedirector(embedded_test_server());
ASSERT_TRUE(embedded_test_server()->Start());
auto waiter = CreateAdsPageLoadMetricsTestWaiter();
content::WebContents* contents =
browser()->tab_strip_model()->GetActiveWebContents();
ui_test_utils::NavigateToURL(
browser(),
embedded_test_server()->GetURL("foo.com", "/frame_factory.html"));
contents->GetMainFrame()->ExecuteJavaScriptForTests(
base::ASCIIToUTF16("createAdFrame('frame_factory.html', '');"));
// 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) {
embedded_test_server()->ServeFilesFromSourceDirectory(
"chrome/test/data/ad_tagging");
content::SetupCrossSiteRedirector(embedded_test_server());
ASSERT_TRUE(embedded_test_server()->Start());
auto waiter = CreateAdsPageLoadMetricsTestWaiter();
content::WebContents* contents =
browser()->tab_strip_model()->GetActiveWebContents();
ui_test_utils::NavigateToURL(
browser(),
embedded_test_server()->GetURL("foo.com", "/frame_factory.html"));
contents->GetMainFrame()->ExecuteJavaScriptForTests(
base::ASCIIToUTF16("createAdFrame('frame_factory.html', 'test');"));
waiter->AddMinimumAdResourceExpectation(6);
waiter->Wait();
NavigateIframeToURL(
web_contents(), "test",
embedded_test_server()->GetURL("foo.com", "/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) {
base::HistogramTester histogram_tester;
const char kHttpResponseHeader[] =
"HTTP/1.1 200 OK\r\n"
"Content-Type: text/html; charset=utf-8\r\n"
"\r\n";
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(), "/iframe.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(kHttpResponseHeader);
main_html_response->Send(
"<html><body></body><script src=\"ad_script.js\"></script></html>");
main_html_response->Done();
ad_script_response->WaitForRequest();
ad_script_response->Send(kHttpResponseHeader);
ad_script_response->Send(
"var iframe = document.createElement(\"iframe\");"
"iframe.src =\"iframe.html\";"
"document.body.appendChild(iframe);");
ad_script_response->Send(std::string(1000, ' '));
ad_script_response->Done();
iframe_response->WaitForRequest();
iframe_response->Send(kHttpResponseHeader);
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(kHttpResponseHeader);
vanilla_script_response->Send(std::string(1024, ' '));
waiter->AddMinimumNetworkBytesExpectation(4000);
waiter->Wait();
// Verify correct numbers of resources are recorded.
histogram_tester.ExpectTotalCount(
"Ads.ResourceUsage.Size.Network.Mainframe.VanillaResource", 1);
histogram_tester.ExpectTotalCount(
"Ads.ResourceUsage.Size.Network.Mainframe.AdResource", 1);
histogram_tester.ExpectTotalCount(
"Ads.ResourceUsage.Size.Network.Subframe.AdResource", 1);
// Verify unfinished resource not yet recorded.
histogram_tester.ExpectTotalCount(
"Ads.ResourceUsage.Size.Network.Subframe.VanillaResource", 0);
// Close all tabs instead of navigating as the embedded_test_server will
// hang waiting for loads to finish when we have an unfinished
// ControlledHttpReseonse.
browser()->tab_strip_model()->CloseAllTabs();
// Verify unfinished resource recorded when page is destroyed.
histogram_tester.ExpectTotalCount(
"Ads.ResourceUsage.Size.Network.Subframe.AdResource", 2);
histogram_tester.ExpectBucketCount(
"PageLoad.Clients.Ads.Bytes.FullPage.Network", 4, 1);
// We have received 4 KB of ads and 1 KB of toplevel ads.
histogram_tester.ExpectBucketCount("PageLoad.Clients.Ads.Resources.Bytes.Ads",
4, 1);
histogram_tester.ExpectBucketCount(
"PageLoad.Clients.Ads.Resources.Bytes.TopLevelAds", 1, 1);
// 4 resources loaded, one unfinished.
histogram_tester.ExpectBucketCount(
"PageLoad.Clients.Ads.Resources.Bytes.Unfinished", 1, 1);
}
IN_PROC_BROWSER_TEST_F(AdsPageLoadMetricsObserverResourceBrowserTest,
IncompleteResourcesRecordedToFrameMetrics) {
base::HistogramTester histogram_tester;
SetRulesetWithRules(
{subresource_filter::testing::CreateSuffixRule("ad_iframe_writer.js")});
embedded_test_server()->ServeFilesFromSourceDirectory(
"chrome/test/data/ads_observer");
content::SetupCrossSiteRedirector(embedded_test_server());
const char kHttpResponseHeader[] =
"HTTP/1.1 200 OK\r\n"
"Content-Type: text/html; charset=utf-8\r\n"
"\r\n";
auto incomplete_resource_response =
std::make_unique<net::test_server::ControllableHttpResponse>(
embedded_test_server(), "/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("/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();
// Ad resource will not finish loading but should be reported to metrics.
incomplete_resource_response->WaitForRequest();
incomplete_resource_response->Send(kHttpResponseHeader);
incomplete_resource_response->Send(std::string(2048, ' '));
// Wait for the resource update to be received for the incomplete response.
waiter->AddMinimumNetworkBytesExpectation(2048);
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
// ControlledHttpResponse.
browser()->tab_strip_model()->CloseAllTabs();
int expected_page_kilobytes = (initial_page_bytes + 2048) / 1024;
histogram_tester.ExpectBucketCount(
"PageLoad.Clients.Ads.Bytes.FullPage.Network", expected_page_kilobytes,
1);
histogram_tester.ExpectBucketCount(
"PageLoad.Clients.Ads.Bytes.AdFrames.Aggregate.Network", 2, 1);
histogram_tester.ExpectBucketCount(
"PageLoad.Clients.Ads.Bytes.AdFrames.Aggregate.Total", 2, 1);
histogram_tester.ExpectBucketCount(
"PageLoad.Clients.Ads.Bytes.AdFrames.PerFrame.Network", 2, 1);
histogram_tester.ExpectBucketCount(
"PageLoad.Clients.Ads.Bytes.AdFrames.PerFrame.Total", 2, 1);
}
IN_PROC_BROWSER_TEST_F(AdsPageLoadMetricsObserverResourceBrowserTest,
AdFrameSizeInterventionTriggered) {
base::HistogramTester histogram_tester;
SetRulesetWithRules(
{subresource_filter::testing::CreateSuffixRule("ad_iframe_writer.js")});
embedded_test_server()->ServeFilesFromSourceDirectory(
"chrome/test/data/ads_observer");
content::SetupCrossSiteRedirector(embedded_test_server());
const char kHttpResponseHeader[] =
"HTTP/1.1 200 OK\r\n"
"Content-Type: text/html; charset=utf-8\r\n"
"\r\n";
auto resource_response =
std::make_unique<net::test_server::ControllableHttpResponse>(
embedded_test_server(), "/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("/ad_with_incomplete_resource.html"),
content::Referrer(), WindowOpenDisposition::CURRENT_TAB,
ui::PAGE_TRANSITION_TYPED, false));
waiter->AddMinimumCompleteResourcesExpectation(3);
waiter->Wait();
// Load a resource large enough to trigger intervention.
resource_response->WaitForRequest();
resource_response->Send(kHttpResponseHeader);
resource_response->Send(
std::string(FrameData::kFrameSizeInterventionByteThreshold, ' '));
resource_response->Done();
// Wait for the resource to finish loading.
waiter->AddMinimumCompleteResourcesExpectation(4);
waiter->Wait();
// Close all tabs to report metrics.
browser()->tab_strip_model()->CloseAllTabs();
histogram_tester.ExpectBucketCount(
kAdFrameSizeInterventionHistogramId,
FrameData::FrameSizeInterventionStatus::kTriggered, 1);
histogram_tester.ExpectBucketCount(
"Blink.UseCounter.Features",
blink::mojom::WebFeature::kAdFrameSizeIntervention, 1);
}
IN_PROC_BROWSER_TEST_F(AdsPageLoadMetricsObserverResourceBrowserTest,
AdFrameSizeInterventionNotActivatedOnFrameWithGesture) {
base::HistogramTester histogram_tester;
SetRulesetWithRules(
{subresource_filter::testing::CreateSuffixRule("ad_iframe_writer.js")});
embedded_test_server()->ServeFilesFromSourceDirectory(
"chrome/test/data/ads_observer");
content::SetupCrossSiteRedirector(embedded_test_server());
const char kHttpResponseHeader[] =
"HTTP/1.1 200 OK\r\n"
"Content-Type: text/html; charset=utf-8\r\n"
"\r\n";
auto resource_response =
std::make_unique<net::test_server::ControllableHttpResponse>(
embedded_test_server(), "/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("/ad_with_incomplete_resource.html"),
content::Referrer(), WindowOpenDisposition::CURRENT_TAB,
ui::PAGE_TRANSITION_TYPED, false));
waiter->AddMinimumCompleteResourcesExpectation(3);
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));
// Load a resource large enough to trigger intervention.
resource_response->WaitForRequest();
resource_response->Send(kHttpResponseHeader);
resource_response->Send(
std::string(FrameData::kFrameSizeInterventionByteThreshold, ' '));
resource_response->Done();
// Wait for the resource to finish loading.
waiter->AddMinimumCompleteResourcesExpectation(4);
waiter->Wait();
// Close all tabs to report metrics.
browser()->tab_strip_model()->CloseAllTabs();
histogram_tester.ExpectBucketCount(
kAdFrameSizeInterventionHistogramId,
FrameData::FrameSizeInterventionStatus::kNone, 1);
histogram_tester.ExpectBucketCount(
"Blink.UseCounter.Features",
blink::mojom::WebFeature::kAdFrameSizeIntervention, 0);
}
// Verify that per-resource metrics are reported for cached resources and
// resources loaded by the network.
IN_PROC_BROWSER_TEST_F(AdsPageLoadMetricsObserverResourceBrowserTest,
RecordedCacheResourceMetrics) {
base::HistogramTester histogram_tester;
SetRulesetWithRules(
{subresource_filter::testing::CreateSuffixRule("create_frame.js")});
embedded_test_server()->ServeFilesFromSourceDirectory(
"chrome/test/data/ad_tagging");
content::SetupCrossSiteRedirector(embedded_test_server());
ASSERT_TRUE(embedded_test_server()->Start());
auto waiter = CreateAdsPageLoadMetricsTestWaiter();
ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL("foo.com", "/cachetime"));
// Wait for the favicon to be fetched.
waiter->AddMinimumCompleteResourcesExpectation(2);
waiter->Wait();
// All resources should have been loaded by network.
histogram_tester.ExpectTotalCount(
"Ads.ResourceUsage.Size.Network.Mainframe.VanillaResource", 2);
// Open a new tab and navigate so that resources are fetched via the disk
// cache. Navigating to the same URL in the same tab triggers a refresh which
// will not check the disk cache.
ui_test_utils::NavigateToURLWithDisposition(
browser(), GURL("about:blank"), WindowOpenDisposition::NEW_FOREGROUND_TAB,
ui_test_utils::BROWSER_TEST_WAIT_FOR_TAB |
ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION);
waiter = CreateAdsPageLoadMetricsTestWaiter();
ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL("foo.com", "/cachetime"));
// Wait for the resource to be fetched.
waiter->AddMinimumCompleteResourcesExpectation(1);
waiter->Wait();
// Resource should be recorded as loaded from the cache. Favicon not
// fetched this time.
histogram_tester.ExpectTotalCount(
"Ads.ResourceUsage.Size.Cache.Mainframe.VanillaResource", 1);
}
// Verify that Mime type metrics are recorded correctly.
IN_PROC_BROWSER_TEST_F(AdsPageLoadMetricsObserverResourceBrowserTest,
RecordedMimeMetrics) {
base::HistogramTester histogram_tester;
ukm::TestAutoSetUkmRecorder ukm_recorder;
embedded_test_server()->ServeFilesFromSourceDirectory(
"chrome/test/data/ad_tagging");
content::SetupCrossSiteRedirector(embedded_test_server());
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", "/frame_factory.html");
ui_test_utils::NavigateToURL(browser(), url);
contents->GetMainFrame()->ExecuteJavaScriptForTests(
base::ASCIIToUTF16("createAdFrame('multiple_mimes.html', 'test');"));
waiter->AddMinimumAdResourceExpectation(8);
waiter->Wait();
// Close all tabs to log metrics, as the video resource request is incomplete.
browser()->tab_strip_model()->CloseAllTabs();
histogram_tester.ExpectTotalCount("Ads.ResourceUsage.Size.Network.Mime.HTML",
1);
histogram_tester.ExpectTotalCount("Ads.ResourceUsage.Size.Network.Mime.CSS",
1);
histogram_tester.ExpectTotalCount("Ads.ResourceUsage.Size.Network.Mime.JS",
3);
// Note: png and video/webm mime types are not set explicitly by the
// embedded_test_server.
histogram_tester.ExpectTotalCount("Ads.ResourceUsage.Size.Network.Mime.Image",
1);
histogram_tester.ExpectTotalCount("Ads.ResourceUsage.Size.Network.Mime.Video",
1);
histogram_tester.ExpectTotalCount("Ads.ResourceUsage.Size.Network.Mime.Other",
1);
// 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::kAdBytesPerSecondName),
0);
// TTI is not reached by this page and thus should not have this recorded.
EXPECT_FALSE(ukm_recorder.EntryHasMetric(
entries.front(),
ukm::builders::AdPageLoad::kAdBytesPerSecondAfterInteractiveName));
EXPECT_GT(
*ukm_recorder.GetEntryMetric(
entries.front(), ukm::builders::AdPageLoad::kAdJavascriptBytesName),
0);
EXPECT_GT(*ukm_recorder.GetEntryMetric(
entries.front(), ukm::builders::AdPageLoad::kAdVideoBytesName),
0);
}
// Download gets blocked when LoadPolicy is DISALLOW for the navigation
// to download.
IN_PROC_BROWSER_TEST_F(AdsPageLoadMetricsObserverResourceBrowserTest,
SubframeNavigationDownloadBlockedByLoadPolicy) {
ResetConfiguration(subresource_filter::Configuration(
subresource_filter::mojom::ActivationLevel::kEnabled,
subresource_filter::ActivationScope::ALL_SITES));
base::HistogramTester histogram_tester;
embedded_test_server()->ServeFilesFromSourceDirectory(
"chrome/test/data/ad_tagging");
content::SetupCrossSiteRedirector(embedded_test_server());
ASSERT_TRUE(embedded_test_server()->Start());
content::WebContents* contents =
browser()->tab_strip_model()->GetActiveWebContents();
std::string host_name = "foo.com";
ui_test_utils::NavigateToURL(
browser(),
embedded_test_server()->GetURL(host_name, "/frame_factory.html"));
content::TestNavigationObserver navigation_observer(web_contents());
contents->GetMainFrame()->ExecuteJavaScriptForTests(
base::ASCIIToUTF16("createFrame('download.html', 'test');"));
navigation_observer.Wait();
content::RenderFrameHost* rfh = content::FrameMatchingPredicate(
web_contents(), base::BindRepeating(&content::FrameMatchesName, "test"));
OpenLinkInFrame(rfh, "blocked_nav_download_id", false /* gesture*/);
SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
histogram_tester.ExpectTotalCount("Download.Subframe.SandboxOriginAdGesture",
0);
}
class RemoteFrameNavigationBrowserTest
: public AdsPageLoadMetricsObserverResourceBrowserTest {
void SetUpCommandLine(base::CommandLine* command_line) override {
command_line->AppendSwitchASCII(
"enable-blink-features",
"BlockingDownloadsInSandboxWithoutUserActivation");
}
};
IN_PROC_BROWSER_TEST_F(RemoteFrameNavigationBrowserTest,
DownloadsBlockedInSandbox) {
embedded_test_server()->ServeFilesFromSourceDirectory(
"chrome/test/data/ad_tagging");
content::SetupCrossSiteRedirector(embedded_test_server());
ASSERT_TRUE(embedded_test_server()->Start());
base::HistogramTester histogram_tester;
std::string origin1 = "foo.com";
std::string origin2 = "bar.com";
GURL tab1_url =
embedded_test_server()->GetURL(origin1, "/frame_factory.html");
auto subframe_navigation_waiter =
std::make_unique<page_load_metrics::PageLoadMetricsTestWaiter>(
web_contents());
subframe_navigation_waiter->AddSubframeNavigationExpectation(2);
ui_test_utils::NavigateToURL(browser(), tab1_url);
std::string subframe_url =
embedded_test_server()->GetURL(origin2, "/frame_factory.html").spec();
content::TestNavigationObserver new_subframe_waiter(web_contents());
std::string script =
base::StringPrintf("createFrame('%s','test','');", subframe_url.c_str());
web_contents()->GetMainFrame()->ExecuteJavaScriptForTests(
base::ASCIIToUTF16(script));
new_subframe_waiter.Wait();
GURL dld_url = embedded_test_server()->GetURL(origin1, "/allow.zip");
EXPECT_TRUE(ExecuteScriptWithoutUserGesture(
web_contents(),
"document.getElementById('test').src = \"" + dld_url.spec() + "\";"));
subframe_navigation_waiter->Wait();
SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
histogram_tester.ExpectTotalCount("Download.Subframe.SandboxOriginAdGesture",
0 /* expected_count */);
}
class MainFrameDownloadFlagsBrowserTest
: public AdsPageLoadMetricsObserverResourceBrowserTest,
public ::testing::WithParamInterface<std::tuple<
Origin,
bool /* enable_blocking_downloads_in_sandbox_without_user_activation
*/
,
SandboxOption,
bool /* has_gesture */>> {
void SetUpCommandLine(base::CommandLine* command_line) override {
bool enable_blocking_downloads_in_sandbox_without_user_activation;
std::tie(std::ignore,
enable_blocking_downloads_in_sandbox_without_user_activation,
std::ignore, std::ignore) = GetParam();
std::string cmd =
enable_blocking_downloads_in_sandbox_without_user_activation
? "enable-blink-features"
: "disable-blink-features";
command_line->AppendSwitchASCII(
cmd, "BlockingDownloadsInSandboxWithoutUserActivation");
}
};
// Main frame download events are reported correctly.
IN_PROC_BROWSER_TEST_P(MainFrameDownloadFlagsBrowserTest, Download) {
Origin origin;
bool enable_blocking_downloads_in_sandbox_without_user_activation;
SandboxOption sandbox_option;
bool has_gesture;
std::tie(origin, enable_blocking_downloads_in_sandbox_without_user_activation,
sandbox_option, has_gesture) = GetParam();
SCOPED_TRACE(
::testing::Message()
<< "origin = " << origin << ", "
<< "enable_blocking_downloads_in_sandbox_without_user_activation = "
<< enable_blocking_downloads_in_sandbox_without_user_activation << ", "
<< "sandbox_option = " << sandbox_option << ", "
<< "has_gesture = " << has_gesture);
bool expected_download =
!enable_blocking_downloads_in_sandbox_without_user_activation ||
has_gesture ||
sandbox_option != SandboxOption::kDisallowDownloadsWithoutUserActivation;
bool expected_sandbox_bit =
expected_download &&
sandbox_option == SandboxOption::kDisallowDownloadsWithoutUserActivation;
base::HistogramTester histogram_tester;
ukm::TestAutoSetUkmRecorder ukm_recorder;
embedded_test_server()->ServeFilesFromSourceDirectory(
"chrome/test/data/ad_tagging");
content::SetupCrossSiteRedirector(embedded_test_server());
ASSERT_TRUE(embedded_test_server()->Start());
std::string host_name = "foo.com";
GURL main_url = embedded_test_server()->GetURL(host_name, "/download.html");
std::unique_ptr<page_load_metrics::PageLoadMetricsTestWaiter>
web_feature_waiter;
if (sandbox_option == SandboxOption::kNoSandbox) {
ui_test_utils::NavigateToURL(browser(), main_url);
} else {
GURL first_tab_url =
embedded_test_server()->GetURL(host_name, "/frame_factory.html");
ui_test_utils::NavigateToURL(browser(), first_tab_url);
const char* method = "createFrame";
std::string subframe_url =
embedded_test_server()->GetURL(host_name, "/frame_factory.html").spec();
const char* id = "test";
const char* sandbox_param =
sandbox_option == SandboxOption::kDisallowDownloadsWithoutUserActivation
? "'allow-scripts allow-same-origin allow-popups'"
: "'allow-scripts allow-same-origin allow-popups "
"allow-downloads-without-user-activation'";
content::TestNavigationObserver navigation_observer(web_contents());
std::string script = base::StringPrintf(
"%s('%s','%s',%s);", method, subframe_url.c_str(), id, sandbox_param);
web_contents()->GetMainFrame()->ExecuteJavaScriptForTests(
base::ASCIIToUTF16(script));
navigation_observer.Wait();
content::RenderFrameHost* child = content::FrameMatchingPredicate(
web_contents(), base::BindRepeating(&content::FrameMatchesName, id));
std::unique_ptr<PopupPageLoadMetricsWaiterInitializer> waiter_initializer;
if (expected_sandbox_bit) {
waiter_initializer =
std::make_unique<PopupPageLoadMetricsWaiterInitializer>(
browser()->tab_strip_model(), &web_feature_waiter);
}
content::TestNavigationObserver popup_observer(main_url);
popup_observer.StartWatchingNewWebContents();
EXPECT_TRUE(
ExecuteScript(child, "window.open(\"" + main_url.spec() + "\");"));
popup_observer.Wait();
ASSERT_EQ(2, browser()->tab_strip_model()->count());
}
DCHECK(!expected_sandbox_bit || web_feature_waiter);
if (expected_sandbox_bit) {
blink::mojom::WebFeature feature =
origin == Origin::kNavigation
? has_gesture ? blink::mojom::WebFeature::
kNavigationDownloadInSandboxWithUserGesture
: blink::mojom::WebFeature::
kNavigationDownloadInSandboxWithoutUserGesture
: has_gesture
? blink::mojom::WebFeature::
kHTMLAnchorElementDownloadInSandboxWithUserGesture
: blink::mojom::WebFeature::
kHTMLAnchorElementDownloadInSandboxWithoutUserGesture;
web_feature_waiter->AddWebFeatureExpectation(feature);
}
std::string link_id =
origin == Origin::kNavigation ? "nav_download_id" : "anchor_download_id";
std::unique_ptr<content::DownloadTestObserver> download_observer(
new content::DownloadTestObserverTerminal(
content::BrowserContext::GetDownloadManager(browser()->profile()),
expected_download /* wait_count */,
content::DownloadTestObserver::ON_DANGEROUS_DOWNLOAD_FAIL));
OpenLinkInFrame(web_contents(), link_id, has_gesture);
download_observer->WaitForFinished();
if (web_feature_waiter)
web_feature_waiter->Wait();
SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
if (!expected_download) {
histogram_tester.ExpectTotalCount("Download.MainFrame.SandboxGesture",
0 /* expected_count */);
return;
}
blink::DownloadStats::MainFrameDownloadFlags expected_flags;
expected_flags.has_sandbox = expected_sandbox_bit;
expected_flags.has_gesture = has_gesture;
histogram_tester.ExpectUniqueSample("Download.MainFrame.SandboxGesture",
expected_flags.ToUmaValue(),
1 /* expected_count */);
auto entries = ukm_recorder.GetEntriesByName(
ukm::builders::MainFrameDownload::kEntryName);
EXPECT_EQ(1u, entries.size());
ukm_recorder.ExpectEntrySourceHasUrl(entries.back(), main_url);
ukm_recorder.ExpectEntryMetric(
entries.back(), ukm::builders::MainFrameDownload::kHasSandboxName,
expected_sandbox_bit);
ukm_recorder.ExpectEntryMetric(
entries.back(), ukm::builders::MainFrameDownload::kHasGestureName,
has_gesture);
}
INSTANTIATE_TEST_SUITE_P(
/* no prefix */,
MainFrameDownloadFlagsBrowserTest,
::testing::Combine(
::testing::Values(Origin::kNavigation, Origin::kAnchorAttribute),
::testing::Bool(),
::testing::Values(
SandboxOption::kNoSandbox,
SandboxOption::kDisallowDownloadsWithoutUserActivation,
SandboxOption::kAllowDownloadsWithoutUserActivation),
::testing::Bool()));
class SubframeDownloadFlagsBrowserTest
: public AdsPageLoadMetricsObserverResourceBrowserTest,
public ::testing::WithParamInterface<std::tuple<
Origin,
bool /* enable_blocking_downloads_in_sandbox_without_user_activation
*/
,
SandboxOption,
bool /* is_cross_origin */,
bool /* is_ad_frame */,
bool /* has_gesture */>> {
void SetUpCommandLine(base::CommandLine* command_line) override {
bool enable_blocking_downloads_in_sandbox_without_user_activation;
std::tie(std::ignore,
enable_blocking_downloads_in_sandbox_without_user_activation,
std::ignore, std::ignore, std::ignore, std::ignore) = GetParam();
std::string cmd =
enable_blocking_downloads_in_sandbox_without_user_activation
? "enable-blink-features"
: "disable-blink-features";
command_line->AppendSwitchASCII(
cmd, "BlockingDownloadsInSandboxWithoutUserActivation");
}
};
// Subframe download events are reported correctly.
IN_PROC_BROWSER_TEST_P(SubframeDownloadFlagsBrowserTest, Download) {
Origin origin;
bool enable_blocking_downloads_in_sandbox_without_user_activation;
SandboxOption sandbox_option;
bool is_cross_origin;
bool is_ad_frame;
bool has_gesture;
std::tie(origin, enable_blocking_downloads_in_sandbox_without_user_activation,
sandbox_option, is_cross_origin, is_ad_frame, has_gesture) =
GetParam();
SCOPED_TRACE(
::testing::Message()
<< "origin = " << origin << ", "
<< "enable_blocking_downloads_in_sandbox_without_user_activation = "
<< enable_blocking_downloads_in_sandbox_without_user_activation << ", "
<< "sandbox_option = " << sandbox_option << ", "
<< "is_cross_origin = " << is_cross_origin << ", "
<< "is_ad_frame = " << is_ad_frame << ", "
<< "has_gesture = " << has_gesture);
base::HistogramTester histogram_tester;
ukm::TestAutoSetUkmRecorder ukm_recorder;
bool expected_download =
!enable_blocking_downloads_in_sandbox_without_user_activation ||
has_gesture ||
sandbox_option != SandboxOption::kDisallowDownloadsWithoutUserActivation;
bool expected_sandbox_bit =
expected_download &&
sandbox_option == SandboxOption::kDisallowDownloadsWithoutUserActivation;
std::unique_ptr<content::DownloadTestObserver> download_observer(
new content::DownloadTestObserverTerminal(
content::BrowserContext::GetDownloadManager(browser()->profile()),
expected_download /* wait_count */,
content::DownloadTestObserver::ON_DANGEROUS_DOWNLOAD_FAIL));
embedded_test_server()->ServeFilesFromSourceDirectory(
"chrome/test/data/ad_tagging");
content::SetupCrossSiteRedirector(embedded_test_server());
ASSERT_TRUE(embedded_test_server()->Start());
content::WebContents* contents =
browser()->tab_strip_model()->GetActiveWebContents();
std::unique_ptr<AdsPageLoadMetricsTestWaiter> waiter;
if (origin == Origin::kNavigation) {
waiter = std::make_unique<AdsPageLoadMetricsTestWaiter>(contents);
waiter->AddSubframeNavigationExpectation(2);
}
if (expected_download && is_ad_frame) {
if (!waiter)
waiter = std::make_unique<AdsPageLoadMetricsTestWaiter>(contents);
blink::mojom::WebFeature feature =
has_gesture
? blink::mojom::WebFeature::kDownloadInAdFrameWithUserGesture
: blink::mojom::WebFeature::kDownloadInAdFrameWithoutUserGesture;
waiter->AddWebFeatureExpectation(feature);
}
if (expected_sandbox_bit) {
if (!waiter)
waiter = std::make_unique<AdsPageLoadMetricsTestWaiter>(contents);
blink::mojom::WebFeature feature =
origin == Origin::kNavigation
? has_gesture ? blink::mojom::WebFeature::
kNavigationDownloadInSandboxWithUserGesture
: blink::mojom::WebFeature::
kNavigationDownloadInSandboxWithoutUserGesture
: has_gesture
? blink::mojom::WebFeature::
kHTMLAnchorElementDownloadInSandboxWithUserGesture
: blink::mojom::WebFeature::
kHTMLAnchorElementDownloadInSandboxWithoutUserGesture;
waiter->AddWebFeatureExpectation(feature);
}
std::string host_name = "foo.com";
GURL main_url =
embedded_test_server()->GetURL(host_name, "/frame_factory.html");
ui_test_utils::NavigateToURL(browser(), main_url);
std::string link_id =
origin == Origin::kNavigation ? "nav_download_id" : "anchor_download_id";
const char* method = is_ad_frame ? "createAdFrame" : "createFrame";
std::string url =
embedded_test_server()
->GetURL(is_cross_origin ? "bar.com" : host_name, "/download.html")
.spec();
const char* id = "test";
const char* sandbox_param =
sandbox_option == SandboxOption::kNoSandbox
? "undefined"
: sandbox_option ==
SandboxOption::kDisallowDownloadsWithoutUserActivation
? "'allow-scripts allow-same-origin'"
: "'allow-scripts allow-same-origin "
"allow-downloads-without-user-activation'";
content::TestNavigationObserver navigation_observer(web_contents());
std::string script = base::StringPrintf("%s('%s','%s',%s);", method,
url.c_str(), id, sandbox_param);
contents->GetMainFrame()->ExecuteJavaScriptForTests(
base::ASCIIToUTF16(script));
navigation_observer.Wait();
content::RenderFrameHost* rfh = content::FrameMatchingPredicate(
web_contents(), base::BindRepeating(&content::FrameMatchesName, id));
OpenLinkInFrame(rfh, link_id, has_gesture);
download_observer->WaitForFinished();
if (waiter)
waiter->Wait();
SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
if (!expected_download) {
histogram_tester.ExpectTotalCount(
"Download.Subframe.SandboxOriginAdGesture", 0 /* expected_count */);
return;
}
blink::DownloadStats::SubframeDownloadFlags expected_flags;
expected_flags.has_sandbox = expected_sandbox_bit;
expected_flags.is_cross_origin = is_cross_origin;
expected_flags.is_ad_frame = is_ad_frame;
expected_flags.has_gesture = has_gesture;
histogram_tester.ExpectUniqueSample(
"Download.Subframe.SandboxOriginAdGesture", expected_flags.ToUmaValue(),
1 /* expected_count */);
auto entries = ukm_recorder.GetEntriesByName(
ukm::builders::SubframeDownload::kEntryName);
EXPECT_EQ(1u, entries.size());
switch (origin) {
case Origin::kAnchorAttribute: {
const ukm::mojom::UkmEntry* dc_entry =
ukm_recorder.GetDocumentCreatedEntryForSourceId(
entries.back()->source_id);
const ukm::UkmSource* navigation_source =
ukm_recorder.GetSourceForSourceId(*ukm_recorder.GetEntryMetric(
dc_entry,
ukm::builders::DocumentCreated::kNavigationSourceIdName));
EXPECT_EQ(main_url, navigation_source->url());
} break;
case Origin::kNavigation: {
ukm_recorder.ExpectEntrySourceHasUrl(entries.back(), main_url);
} break;
}
ukm_recorder.ExpectEntryMetric(
entries.back(), ukm::builders::SubframeDownload::kHasSandboxName,
expected_flags.has_sandbox);
ukm_recorder.ExpectEntryMetric(
entries.back(), ukm::builders::SubframeDownload::kIsCrossOriginName,
is_cross_origin);
ukm_recorder.ExpectEntryMetric(
entries.back(), ukm::builders::SubframeDownload::kIsAdFrameName,
is_ad_frame);
ukm_recorder.ExpectEntryMetric(
entries.back(), ukm::builders::SubframeDownload::kHasGestureName,
has_gesture);
}
INSTANTIATE_TEST_SUITE_P(
/* no prefix */,
SubframeDownloadFlagsBrowserTest,
::testing::Combine(
::testing::Values(Origin::kNavigation, Origin::kAnchorAttribute),
::testing::Bool(),
::testing::Values(
SandboxOption::kNoSandbox,
SandboxOption::kDisallowDownloadsWithoutUserActivation,
SandboxOption::kAllowDownloadsWithoutUserActivation),
::testing::Bool(),
::testing::Bool(),
::testing::Bool()));