| // Copyright 2018 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <string> |
| |
| #include "base/bind.h" |
| #include "base/callback.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "chrome/browser/content_settings/host_content_settings_map_factory.h" |
| #include "chrome/browser/subresource_filter/subresource_filter_browser_test_harness.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/browser/ui/tabs/tab_strip_model.h" |
| #include "chrome/test/base/ui_test_utils.h" |
| #include "components/content_settings/core/browser/host_content_settings_map.h" |
| #include "components/content_settings/core/common/content_settings.h" |
| #include "components/content_settings/core/common/content_settings_types.h" |
| #include "components/embedder_support/switches.h" |
| #include "components/metrics/content/subprocess_metrics_provider.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/page_load_metrics_test_waiter.h" |
| #include "components/subresource_filter/content/browser/ad_tagging_browser_test_utils.h" |
| #include "components/subresource_filter/content/browser/subresource_filter_observer_test_utils.h" |
| #include "components/subresource_filter/core/common/test_ruleset_utils.h" |
| #include "components/ukm/content/source_url_recorder.h" |
| #include "components/ukm/test_ukm_recorder.h" |
| #include "content/public/test/back_forward_cache_util.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 "services/metrics/public/cpp/ukm_builders.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/blink/public/common/frame/from_ad_state.h" |
| #include "url/gurl.h" |
| |
| namespace subresource_filter { |
| |
| namespace { |
| |
| using content::RenderFrameHost; |
| using testing::CreateAllowlistSuffixRule; |
| using testing::CreateSuffixRule; |
| |
| // Creates a link element with rel="stylesheet" and href equal to the specified |
| // url. Returns immediately after adding the element to the DOM. |
| void AddExternalStylesheet(const content::ToRenderFrameHost& adapter, |
| const GURL& url) { |
| std::string script_template = R"( |
| link = document.createElement("link"); |
| link.rel = "stylesheet"; |
| link.href = $1; |
| document.head.appendChild(link); |
| )"; |
| EXPECT_TRUE(content::ExecJs(adapter.render_frame_host(), |
| content::JsReplace(script_template, url))); |
| } |
| |
| class AdTaggingPageLoadMetricsTestWaiter |
| : public page_load_metrics::PageLoadMetricsTestWaiter { |
| public: |
| explicit AdTaggingPageLoadMetricsTestWaiter( |
| content::WebContents* web_contents) |
| : page_load_metrics::PageLoadMetricsTestWaiter(web_contents) {} |
| |
| void AddMinimumCompleteAdResourcesExpectation(int num_ad_resources) { |
| expected_minimum_complete_ad_resources_ = num_ad_resources; |
| } |
| |
| void AddMinimumCompleteVanillaResourcesExpectation( |
| int num_vanilla_resources) { |
| expected_minimum_complete_vanilla_resources_ = num_vanilla_resources; |
| } |
| |
| int current_complete_ad_resources() { return current_complete_ad_resources_; } |
| |
| int current_complete_vanilla_resources() { |
| return current_complete_vanilla_resources_; |
| } |
| |
| protected: |
| bool ExpectationsSatisfied() const override { |
| return current_complete_ad_resources_ >= |
| expected_minimum_complete_ad_resources_ && |
| current_complete_vanilla_resources_ >= |
| expected_minimum_complete_vanilla_resources_ && |
| PageLoadMetricsTestWaiter::ExpectationsSatisfied(); |
| } |
| |
| void HandleResourceUpdate( |
| const page_load_metrics::mojom::ResourceDataUpdatePtr& resource) |
| override { |
| // Due to cache-aware loading of font resources, we see two resource loads |
| // for resources not in the cache. We ignore the first request, which |
| // results in a cache miss, by using the fact that it will have no received |
| // data. Note that this only impacts this class's expectations, not those in |
| // PageLoadMetricsTestWaiter itself. |
| if (resource->is_complete && resource->received_data_length != 0) { |
| if (resource->reported_as_ad_resource) { |
| current_complete_ad_resources_++; |
| } else { |
| current_complete_vanilla_resources_++; |
| } |
| } |
| } |
| |
| private: |
| int current_complete_ad_resources_ = 0; |
| int expected_minimum_complete_ad_resources_ = 0; |
| |
| int current_complete_vanilla_resources_ = 0; |
| int expected_minimum_complete_vanilla_resources_ = 0; |
| }; |
| |
| class AdTaggingBrowserTest : public SubresourceFilterBrowserTest { |
| public: |
| AdTaggingBrowserTest() : SubresourceFilterBrowserTest() {} |
| |
| AdTaggingBrowserTest(const AdTaggingBrowserTest&) = delete; |
| AdTaggingBrowserTest& operator=(const AdTaggingBrowserTest&) = delete; |
| |
| ~AdTaggingBrowserTest() override {} |
| |
| void SetUpOnMainThread() override { |
| SubresourceFilterBrowserTest::SetUpOnMainThread(); |
| // For subdocument resources, allowlist rules are always checked. |
| SetRulesetWithRules({CreateSuffixRule("ad_script.js"), |
| CreateSuffixRule("ad=true"), |
| CreateAllowlistSuffixRule("allowed=true")}); |
| } |
| |
| content::WebContents* GetWebContents() { |
| return browser()->tab_strip_model()->GetActiveWebContents(); |
| } |
| |
| // Creates a frame and doc.writes the frame into it. Returns after |
| // navigation has completed. |
| content::RenderFrameHost* CreateDocWrittenFrame( |
| const content::ToRenderFrameHost& adapter) { |
| return CreateDocWrittenFrameImpl(adapter, false /* ad_script */); |
| } |
| |
| // Creates a frame and doc.writes the frame into it. The script creating the |
| // frame is an ad script. Returns after navigation has completed. |
| content::RenderFrameHost* CreateDocWrittenFrameFromAdScript( |
| const content::ToRenderFrameHost& adapter) { |
| return CreateDocWrittenFrameImpl(adapter, true /* ad_script */); |
| } |
| |
| // Creates a frame and aborts the initial load with a doc.write. Returns after |
| // navigation has completed. |
| content::RenderFrameHost* CreateFrameWithDocWriteAbortedLoad( |
| const content::ToRenderFrameHost& adapter) { |
| return CreateFrameWithDocWriteAbortedLoadImpl(adapter, |
| false /* ad_script */); |
| } |
| |
| // Creates a frame and aborts the initial load with a doc.write. The script |
| // creating the frame is an ad script. Returns after navigation has completed. |
| content::RenderFrameHost* CreateFrameWithDocWriteAbortedLoadFromAdScript( |
| const content::ToRenderFrameHost& adapter) { |
| return CreateFrameWithDocWriteAbortedLoadImpl(adapter, |
| true /* ad_script */); |
| } |
| |
| // Creates a frame and aborts the initial load with a window.stop. Returns |
| // after navigation has completed. |
| content::RenderFrameHost* CreateFrameWithWindowStopAbortedLoad( |
| const content::ToRenderFrameHost& adapter) { |
| return CreateFrameWithWindowStopAbortedLoadImpl(adapter, |
| false /* ad_script */); |
| } |
| |
| // Creates a frame and aborts the initial load with a window.stop. The script |
| // creating the frame is an ad script. Returns after navigation has completed. |
| content::RenderFrameHost* CreateFrameWithWindowStopAbortedLoadFromAdScript( |
| const content::ToRenderFrameHost& adapter) { |
| return CreateFrameWithWindowStopAbortedLoadImpl(adapter, |
| true /* ad_script */); |
| } |
| |
| // Given a RenderFrameHost, navigates the page to the given |url| and waits |
| // for the navigation to complete before returning. |
| void NavigateFrame(content::RenderFrameHost* render_frame_host, |
| const GURL& url); |
| |
| GURL GetURL(const std::string& page) { |
| return embedded_test_server()->GetURL("/ad_tagging/" + page); |
| } |
| |
| std::unique_ptr<AdTaggingPageLoadMetricsTestWaiter> |
| CreateAdTaggingPageLoadMetricsTestWaiter() { |
| return std::make_unique<AdTaggingPageLoadMetricsTestWaiter>( |
| GetWebContents()); |
| } |
| |
| private: |
| content::RenderFrameHost* CreateDocWrittenFrameImpl( |
| const content::ToRenderFrameHost& adapter, |
| bool ad_script); |
| |
| content::RenderFrameHost* CreateFrameWithDocWriteAbortedLoadImpl( |
| const content::ToRenderFrameHost& adapter, |
| bool ad_script); |
| |
| content::RenderFrameHost* CreateFrameWithWindowStopAbortedLoadImpl( |
| const content::ToRenderFrameHost& adapter, |
| bool ad_script); |
| }; |
| |
| content::RenderFrameHost* AdTaggingBrowserTest::CreateDocWrittenFrameImpl( |
| const content::ToRenderFrameHost& adapter, |
| bool ad_script) { |
| content::RenderFrameHost* rfh = adapter.render_frame_host(); |
| std::string name = GetUniqueFrameName(); |
| std::string script = base::StringPrintf( |
| "%s('%s', '%s');", |
| ad_script ? "createDocWrittenAdFrame" : "createDocWrittenFrame", |
| name.c_str(), GetURL("").spec().c_str()); |
| content::WebContents* web_contents = |
| content::WebContents::FromRenderFrameHost(rfh); |
| content::TestNavigationObserver navigation_observer(web_contents, 1); |
| EXPECT_EQ(true, content::EvalJs(rfh, script, |
| content::EXECUTE_SCRIPT_USE_MANUAL_REPLY)); |
| navigation_observer.Wait(); |
| EXPECT_TRUE(navigation_observer.last_navigation_succeeded()) |
| << navigation_observer.last_net_error_code(); |
| return content::FrameMatchingPredicate( |
| rfh->GetPage(), base::BindRepeating(&content::FrameMatchesName, name)); |
| } |
| |
| content::RenderFrameHost* |
| AdTaggingBrowserTest::CreateFrameWithDocWriteAbortedLoadImpl( |
| const content::ToRenderFrameHost& adapter, |
| bool ad_script) { |
| content::RenderFrameHost* rfh = adapter.render_frame_host(); |
| std::string name = GetUniqueFrameName(); |
| std::string script = |
| base::StringPrintf("%s('%s');", |
| ad_script ? "createAdFrameWithDocWriteAbortedLoad" |
| : "createFrameWithDocWriteAbortedLoad", |
| name.c_str()); |
| // The executed scripts set the title to be the frame name when they have |
| // finished loading. |
| content::TitleWatcher title_watcher(GetWebContents(), |
| base::ASCIIToUTF16(name)); |
| EXPECT_TRUE(content::ExecJs(rfh, script)); |
| EXPECT_EQ(base::ASCIIToUTF16(name), title_watcher.WaitAndGetTitle()); |
| return content::FrameMatchingPredicate( |
| rfh->GetPage(), base::BindRepeating(&content::FrameMatchesName, name)); |
| } |
| |
| content::RenderFrameHost* |
| AdTaggingBrowserTest::CreateFrameWithWindowStopAbortedLoadImpl( |
| const content::ToRenderFrameHost& adapter, |
| bool ad_script) { |
| content::RenderFrameHost* rfh = adapter.render_frame_host(); |
| std::string name = GetUniqueFrameName(); |
| std::string script = |
| base::StringPrintf("%s('%s');", |
| ad_script ? "createAdFrameWithWindowStopAbortedLoad" |
| : "createFrameWithWindowStopAbortedLoad", |
| name.c_str()); |
| // The executed scripts set the title to be the frame name when they have |
| // finished loading. |
| content::TitleWatcher title_watcher(GetWebContents(), |
| base::ASCIIToUTF16(name)); |
| EXPECT_TRUE(content::ExecJs(rfh, script)); |
| EXPECT_EQ(base::ASCIIToUTF16(name), title_watcher.WaitAndGetTitle()); |
| return content::FrameMatchingPredicate( |
| rfh->GetPage(), base::BindRepeating(&content::FrameMatchesName, name)); |
| } |
| |
| // Given a RenderFrameHost, navigates the page to the given |url| and waits |
| // for the navigation to complete before returning. |
| void AdTaggingBrowserTest::NavigateFrame( |
| content::RenderFrameHost* render_frame_host, |
| const GURL& url) { |
| std::string script = |
| base::StringPrintf(R"(window.location='%s')", url.spec().c_str()); |
| content::TestNavigationObserver navigation_observer(GetWebContents(), 1); |
| EXPECT_TRUE(content::ExecJs(render_frame_host, script)); |
| navigation_observer.Wait(); |
| EXPECT_TRUE(navigation_observer.last_navigation_succeeded()) |
| << navigation_observer.last_net_error_code(); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AdTaggingBrowserTest, |
| AdContentSettingAllowed_AdTaggingDisabled) { |
| HostContentSettingsMapFactory::GetForProfile( |
| web_contents()->GetBrowserContext()) |
| ->SetDefaultContentSetting(ContentSettingsType::ADS, |
| CONTENT_SETTING_ALLOW); |
| |
| TestSubresourceFilterObserver observer(web_contents()); |
| ASSERT_TRUE( |
| ui_test_utils::NavigateToURL(browser(), GetURL("frame_factory.html"))); |
| |
| // Create an ad frame. |
| GURL ad_url = GetURL("frame_factory.html?2&ad=true"); |
| RenderFrameHost* ad_frame = CreateSrcFrame(GetWebContents(), ad_url); |
| |
| // Verify that we are not evaluating subframe loads. |
| EXPECT_FALSE(observer.GetSubframeLoadPolicy(ad_url).has_value()); |
| EXPECT_FALSE(observer.GetIsAdSubframe(ad_frame->GetFrameTreeNodeId())); |
| |
| // Child frame created by ad script. |
| RenderFrameHost* ad_frame_tagged_by_script = CreateSrcFrameFromAdScript( |
| GetWebContents(), GetURL("frame_factory.html?1")); |
| |
| // No frames should be detected by script heuristics. |
| EXPECT_FALSE(observer.GetIsAdSubframe( |
| ad_frame_tagged_by_script->GetFrameTreeNodeId())); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AdTaggingBrowserTest, |
| AdContentSettingBlocked_AdTaggingEnabled) { |
| HostContentSettingsMapFactory::GetForProfile( |
| web_contents()->GetBrowserContext()) |
| ->SetDefaultContentSetting(ContentSettingsType::ADS, |
| CONTENT_SETTING_BLOCK); |
| |
| TestSubresourceFilterObserver observer(web_contents()); |
| ASSERT_TRUE( |
| ui_test_utils::NavigateToURL(browser(), GetURL("frame_factory.html"))); |
| |
| // Create an ad frame. |
| GURL ad_url = GetURL("frame_factory.html?2&ad=true"); |
| RenderFrameHost* ad_frame = CreateSrcFrame(GetWebContents(), ad_url); |
| |
| // Verify that we are evaluating subframe loads. |
| EXPECT_TRUE(observer.GetSubframeLoadPolicy(ad_url).has_value()); |
| EXPECT_TRUE(observer.GetIsAdSubframe(ad_frame->GetFrameTreeNodeId())); |
| |
| // Child frame created by ad script. |
| RenderFrameHost* ad_frame_tagged_by_script = CreateSrcFrameFromAdScript( |
| GetWebContents(), GetURL("frame_factory.html?1")); |
| |
| // Frames should be detected by script heuristics. |
| EXPECT_TRUE(observer.GetIsAdSubframe( |
| ad_frame_tagged_by_script->GetFrameTreeNodeId())); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AdTaggingBrowserTest, FramesByURL) { |
| TestSubresourceFilterObserver observer(web_contents()); |
| |
| // Main frame. |
| ASSERT_TRUE( |
| ui_test_utils::NavigateToURL(browser(), GetURL("frame_factory.html"))); |
| EXPECT_FALSE(observer.GetIsAdSubframe( |
| GetWebContents()->GetMainFrame()->GetFrameTreeNodeId())); |
| |
| // (1) Vanilla child. |
| content::RenderFrameHost* vanilla_child = |
| CreateSrcFrame(GetWebContents(), GetURL("frame_factory.html?1")); |
| EXPECT_FALSE(observer.GetIsAdSubframe(vanilla_child->GetFrameTreeNodeId())); |
| |
| // (2) Ad child. |
| RenderFrameHost* ad_child = |
| CreateSrcFrame(GetWebContents(), GetURL("frame_factory.html?2&ad=true")); |
| EXPECT_TRUE(observer.GetIsAdSubframe(ad_child->GetFrameTreeNodeId())); |
| EXPECT_TRUE(EvidenceForFrameComprises( |
| ad_child, /*parent_is_ad=*/false, |
| blink::mojom::FilterListResult::kMatchedBlockingRule, |
| blink::mojom::FrameCreationStackEvidence::kNotCreatedByAdScript)); |
| |
| // (3) Ad child of 2. |
| RenderFrameHost* ad_child_2 = |
| CreateSrcFrame(ad_child, GetURL("frame_factory.html?sub=1&3&ad=true")); |
| EXPECT_TRUE(observer.GetIsAdSubframe(ad_child_2->GetFrameTreeNodeId())); |
| EXPECT_TRUE(EvidenceForFrameComprises( |
| ad_child_2, /*parent_is_ad=*/true, |
| blink::mojom::FilterListResult::kMatchedBlockingRule, |
| blink::mojom::FrameCreationStackEvidence::kCreatedByAdScript)); |
| |
| // (4) Vanilla child of 2. |
| RenderFrameHost* vanilla_child_2 = |
| CreateSrcFrame(ad_child, GetURL("frame_factory.html?4")); |
| EXPECT_TRUE(observer.GetIsAdSubframe(vanilla_child_2->GetFrameTreeNodeId())); |
| EXPECT_TRUE(EvidenceForFrameComprises( |
| vanilla_child_2, /*parent_is_ad=*/true, |
| blink::mojom::FilterListResult::kMatchedNoRules, |
| blink::mojom::FrameCreationStackEvidence::kCreatedByAdScript)); |
| |
| // (5) Vanilla child of 1. This tests something subtle. |
| // frame_factory.html?ad=true loads the same script that frame_factory.html |
| // uses to load frames. This tests that even though the script is tagged as an |
| // ad in the ad iframe, it's not considered an ad in the main frame, hence |
| // it's able to create an iframe that's not labeled as an ad. |
| RenderFrameHost* vanilla_child_3 = |
| CreateSrcFrame(vanilla_child, GetURL("frame_factory.html?5")); |
| EXPECT_FALSE(observer.GetIsAdSubframe(vanilla_child_3->GetFrameTreeNodeId())); |
| } |
| |
| const char kSubresourceFilterOriginStatusHistogram[] = |
| "PageLoad.Clients.Ads.FrameCounts.AdFrames.PerFrame." |
| "OriginStatus"; |
| const char kWindowOpenFromAdStateHistogram[] = "Blink.WindowOpen.FromAdState"; |
| |
| IN_PROC_BROWSER_TEST_F(AdTaggingBrowserTest, VerifySameOriginWithoutNavigate) { |
| // The test assumes pages gets deleted after navigation, triggering histogram |
| // recording. Disable back/forward cache to ensure that pages don't get |
| // preserved in the cache. |
| // TODO(https://crbug.com/1229122): Investigate if this needs further fix. |
| content::DisableBackForwardCacheForTesting( |
| browser()->tab_strip_model()->GetActiveWebContents(), |
| content::BackForwardCache::TEST_ASSUMES_NO_CACHING); |
| |
| base::HistogramTester histogram_tester; |
| |
| // Main frame. |
| ASSERT_TRUE( |
| ui_test_utils::NavigateToURL(browser(), GetURL("frame_factory.html"))); |
| |
| // Ad frame via doc write. |
| CreateDocWrittenFrameFromAdScript(GetWebContents()); |
| |
| // Navigate away and ensure we report same origin. |
| ASSERT_TRUE( |
| ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL))); |
| histogram_tester.ExpectUniqueSample(kSubresourceFilterOriginStatusHistogram, |
| page_load_metrics::OriginStatus::kSame, |
| 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AdTaggingBrowserTest, VerifyCrossOriginWithoutNavigate) { |
| base::HistogramTester histogram_tester; |
| |
| // Main frame. |
| ASSERT_TRUE( |
| ui_test_utils::NavigateToURL(browser(), GetURL("frame_factory.html"))); |
| |
| // Regular frame that's cross origin and has a doc write ad of its own. |
| RenderFrameHost* regular_child = CreateSrcFrame( |
| GetWebContents(), embedded_test_server()->GetURL( |
| "b.com", "/ad_tagging/frame_factory.html")); |
| CreateDocWrittenFrameFromAdScript(regular_child); |
| |
| // Navigate away and ensure we report cross origin. |
| ASSERT_TRUE( |
| ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL))); |
| |
| // TODO(johnidel): Check that frame was reported properly. See |
| // crbug.com/914893. |
| } |
| |
| // Ad script creates a frame and navigates it cross origin. |
| IN_PROC_BROWSER_TEST_F(AdTaggingBrowserTest, |
| VerifyCrossOriginWithImmediateNavigate) { |
| base::HistogramTester histogram_tester; |
| |
| auto waiter = CreateAdTaggingPageLoadMetricsTestWaiter(); |
| // Create the main frame and cross origin subframe from an ad script. |
| ASSERT_TRUE( |
| ui_test_utils::NavigateToURL(browser(), GetURL("frame_factory.html"))); |
| CreateSrcFrameFromAdScript(GetWebContents(), |
| embedded_test_server()->GetURL( |
| "b.com", "/ads_observer/same_origin_ad.html")); |
| // Wait for all of the subresources to finish loading (includes favicon). |
| // Waiting for the navigation to finish is not sufficient, as it blocks on the |
| // main resource load finishing, not the iframe resource. Page loads 4 |
| // resources, a favicon, and 2 resources for the iframe. |
| waiter->AddMinimumCompleteResourcesExpectation(8); |
| waiter->Wait(); |
| |
| // Navigate away and ensure we report cross origin. |
| ASSERT_TRUE( |
| ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL))); |
| histogram_tester.ExpectUniqueSample(kSubresourceFilterOriginStatusHistogram, |
| page_load_metrics::OriginStatus::kCross, |
| 1); |
| } |
| |
| // Ad script creates a frame and navigates it same origin. |
| // It is then renavigated cross origin. |
| IN_PROC_BROWSER_TEST_F(AdTaggingBrowserTest, |
| VerifySameOriginWithCrossOriginRenavigate) { |
| base::HistogramTester histogram_tester; |
| |
| // Create the main frame and same origin subframe from an ad script. |
| // This triggers the subresource_filter ad detection. |
| ASSERT_TRUE( |
| ui_test_utils::NavigateToURL(browser(), GetURL("frame_factory.html"))); |
| RenderFrameHost* ad_child = CreateSrcFrameFromAdScript( |
| GetWebContents(), GetURL("frame_factory.html?ad=true")); |
| |
| // Navigate the subframe to a cross origin site. |
| NavigateFrame(ad_child, embedded_test_server()->GetURL( |
| "b.com", "/ad_tagging/frame_factory.html")); |
| |
| // Navigate away and ensure we report same origin. |
| ASSERT_TRUE( |
| ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL))); |
| histogram_tester.ExpectUniqueSample(kSubresourceFilterOriginStatusHistogram, |
| page_load_metrics::OriginStatus::kSame, |
| 1); |
| } |
| |
| // Test that a subframe with a non-ad url but loaded by ad script is an ad. |
| IN_PROC_BROWSER_TEST_F(AdTaggingBrowserTest, FrameLoadedByAdScript) { |
| TestSubresourceFilterObserver observer(web_contents()); |
| |
| // Main frame. |
| ASSERT_TRUE( |
| ui_test_utils::NavigateToURL(browser(), GetURL("frame_factory.html"))); |
| |
| // Child frame created by ad script. |
| RenderFrameHost* ad_child = CreateSrcFrameFromAdScript( |
| GetWebContents(), GetURL("frame_factory.html?1")); |
| EXPECT_TRUE(observer.GetIsAdSubframe(ad_child->GetFrameTreeNodeId())); |
| EXPECT_TRUE(EvidenceForFrameComprises( |
| ad_child, /*parent_is_ad=*/false, |
| blink::mojom::FilterListResult::kMatchedNoRules, |
| blink::mojom::FrameCreationStackEvidence::kCreatedByAdScript)); |
| } |
| |
| // Test that same-origin doc.write created iframes are tagged as ads. |
| IN_PROC_BROWSER_TEST_F(AdTaggingBrowserTest, SameOriginFrameTagging) { |
| TestSubresourceFilterObserver observer(web_contents()); |
| |
| // Main frame. |
| ASSERT_TRUE( |
| ui_test_utils::NavigateToURL(browser(), GetURL("frame_factory.html"))); |
| |
| // (1) Vanilla child. |
| content::RenderFrameHost* vanilla_frame = |
| CreateDocWrittenFrame(GetWebContents()); |
| EXPECT_FALSE(observer.GetIsAdSubframe(vanilla_frame->GetFrameTreeNodeId())); |
| |
| // (2) Ad child. |
| content::RenderFrameHost* ad_frame = |
| CreateDocWrittenFrameFromAdScript(GetWebContents()); |
| EXPECT_TRUE(EvidenceForFrameComprises( |
| ad_frame, /*parent_is_ad=*/false, |
| blink::mojom::FilterListResult::kNotChecked, |
| blink::mojom::FrameCreationStackEvidence::kCreatedByAdScript)); |
| } |
| |
| // Test that the children same-origin doc.write created iframes are tagged as |
| // ads where appropriate. |
| IN_PROC_BROWSER_TEST_F(AdTaggingBrowserTest, |
| ChildrenOfSameOriginFrames_CorrectlyTagged) { |
| TestSubresourceFilterObserver observer(web_contents()); |
| |
| // Main frame. |
| ASSERT_TRUE( |
| ui_test_utils::NavigateToURL(browser(), GetURL("frame_factory.html"))); |
| |
| // Vanilla frame and descendants |
| content::RenderFrameHost* vanilla_frame = |
| CreateDocWrittenFrame(GetWebContents()); |
| EXPECT_FALSE(observer.GetIsAdSubframe(vanilla_frame->GetFrameTreeNodeId())); |
| |
| content::RenderFrameHost* vanilla_child_of_vanilla = |
| CreateSrcFrame(vanilla_frame, GetURL("frame_factory.html")); |
| EXPECT_FALSE( |
| observer.GetIsAdSubframe(vanilla_child_of_vanilla->GetFrameTreeNodeId())); |
| |
| content::RenderFrameHost* ad_child_of_vanilla = |
| CreateSrcFrameFromAdScript(vanilla_frame, GetURL("frame_factory.html")); |
| EXPECT_TRUE( |
| observer.GetIsAdSubframe(ad_child_of_vanilla->GetFrameTreeNodeId())); |
| EXPECT_TRUE(EvidenceForFrameComprises( |
| ad_child_of_vanilla, /*parent_is_ad=*/false, |
| blink::mojom::FilterListResult::kMatchedNoRules, |
| blink::mojom::FrameCreationStackEvidence::kCreatedByAdScript)); |
| |
| // Ad frame and descendants |
| content::RenderFrameHost* ad_frame = |
| CreateDocWrittenFrameFromAdScript(GetWebContents()); |
| EXPECT_TRUE(EvidenceForFrameComprises( |
| ad_frame, /*parent_is_ad=*/false, |
| blink::mojom::FilterListResult::kNotChecked, |
| blink::mojom::FrameCreationStackEvidence::kCreatedByAdScript)); |
| |
| content::RenderFrameHost* vanilla_child_of_ad = |
| CreateSrcFrame(ad_frame, GetURL("frame_factory.html")); |
| EXPECT_TRUE( |
| observer.GetIsAdSubframe(vanilla_child_of_ad->GetFrameTreeNodeId())); |
| EXPECT_TRUE(EvidenceForFrameComprises( |
| vanilla_child_of_ad, /*parent_is_ad=*/true, |
| blink::mojom::FilterListResult::kMatchedNoRules, |
| blink::mojom::FrameCreationStackEvidence::kCreatedByAdScript)); |
| |
| content::RenderFrameHost* ad_child_of_ad = |
| CreateSrcFrameFromAdScript(ad_frame, GetURL("frame_factory.html")); |
| EXPECT_TRUE(observer.GetIsAdSubframe(ad_child_of_ad->GetFrameTreeNodeId())); |
| EXPECT_TRUE(EvidenceForFrameComprises( |
| ad_child_of_ad, /*parent_is_ad=*/true, |
| blink::mojom::FilterListResult::kMatchedNoRules, |
| blink::mojom::FrameCreationStackEvidence::kCreatedByAdScript)); |
| } |
| |
| // Test that frames with an aborted initial load due to a doc.write are still |
| // correctly tagged as ads or not. |
| IN_PROC_BROWSER_TEST_F(AdTaggingBrowserTest, |
| FramesWithDocWriteAbortedLoad_StillCorrectlyTagged) { |
| TestSubresourceFilterObserver observer(web_contents()); |
| |
| // Main frame. |
| ASSERT_TRUE( |
| ui_test_utils::NavigateToURL(browser(), GetURL("frame_factory.html"))); |
| |
| // Vanilla child. |
| content::RenderFrameHost* vanilla_frame_with_aborted_load = |
| CreateFrameWithDocWriteAbortedLoad(GetWebContents()); |
| EXPECT_FALSE(observer.GetIsAdSubframe( |
| vanilla_frame_with_aborted_load->GetFrameTreeNodeId())); |
| |
| // Child created by ad script. |
| content::RenderFrameHost* ad_frame_with_aborted_load = |
| CreateFrameWithDocWriteAbortedLoadFromAdScript(GetWebContents()); |
| EXPECT_TRUE(observer.GetIsAdSubframe( |
| ad_frame_with_aborted_load->GetFrameTreeNodeId())); |
| EXPECT_TRUE(EvidenceForFrameComprises( |
| ad_frame_with_aborted_load, |
| /*parent_is_ad=*/false, blink::mojom::FilterListResult::kNotChecked, |
| blink::mojom::FrameCreationStackEvidence::kCreatedByAdScript)); |
| |
| // Child with ad parent. |
| content::RenderFrameHost* ad_frame = CreateSrcFrameFromAdScript( |
| GetWebContents(), GetURL("frame_factory.html")); |
| content::RenderFrameHost* child_frame_of_ad_with_aborted_load = |
| CreateFrameWithDocWriteAbortedLoad(ad_frame); |
| EXPECT_TRUE(observer.GetIsAdSubframe(ad_frame->GetFrameTreeNodeId())); |
| EXPECT_TRUE(observer.GetIsAdSubframe( |
| child_frame_of_ad_with_aborted_load->GetFrameTreeNodeId())); |
| EXPECT_TRUE(EvidenceForFrameComprises( |
| child_frame_of_ad_with_aborted_load, |
| /*parent_is_ad=*/true, blink::mojom::FilterListResult::kNotChecked, |
| blink::mojom::FrameCreationStackEvidence::kCreatedByAdScript)); |
| } |
| |
| // Test that frames with an aborted initial load due to a window.stop are still |
| // correctly tagged as ads or not. |
| IN_PROC_BROWSER_TEST_F(AdTaggingBrowserTest, |
| FramesWithWindowStopAbortedLoad_StillCorrectlyTagged) { |
| TestSubresourceFilterObserver observer(web_contents()); |
| |
| // Main frame. |
| ASSERT_TRUE( |
| ui_test_utils::NavigateToURL(browser(), GetURL("frame_factory.html"))); |
| |
| // Vanilla child. |
| content::RenderFrameHost* vanilla_frame_with_aborted_load = |
| CreateFrameWithWindowStopAbortedLoad(GetWebContents()); |
| EXPECT_FALSE(observer.GetIsAdSubframe( |
| vanilla_frame_with_aborted_load->GetFrameTreeNodeId())); |
| |
| // Child created by ad script. |
| content::RenderFrameHost* ad_frame_with_aborted_load = |
| CreateFrameWithWindowStopAbortedLoadFromAdScript(GetWebContents()); |
| EXPECT_TRUE(observer.GetIsAdSubframe( |
| ad_frame_with_aborted_load->GetFrameTreeNodeId())); |
| EXPECT_TRUE(EvidenceForFrameComprises( |
| ad_frame_with_aborted_load, |
| /*parent_is_ad=*/false, blink::mojom::FilterListResult::kNotChecked, |
| blink::mojom::FrameCreationStackEvidence::kCreatedByAdScript)); |
| |
| // Child with ad parent. |
| content::RenderFrameHost* ad_frame = CreateSrcFrameFromAdScript( |
| GetWebContents(), GetURL("frame_factory.html")); |
| content::RenderFrameHost* child_frame_of_ad_with_aborted_load = |
| CreateFrameWithWindowStopAbortedLoad(ad_frame); |
| EXPECT_TRUE(observer.GetIsAdSubframe(ad_frame->GetFrameTreeNodeId())); |
| EXPECT_TRUE(observer.GetIsAdSubframe( |
| child_frame_of_ad_with_aborted_load->GetFrameTreeNodeId())); |
| EXPECT_TRUE(EvidenceForFrameComprises( |
| child_frame_of_ad_with_aborted_load, |
| /*parent_is_ad=*/true, blink::mojom::FilterListResult::kNotChecked, |
| blink::mojom::FrameCreationStackEvidence::kCreatedByAdScript)); |
| } |
| |
| // Test that the children of a frame with its initial load aborted due to a |
| // doc.write are reported correctly as vanilla or ad frames. |
| IN_PROC_BROWSER_TEST_F( |
| AdTaggingBrowserTest, |
| ChildrenOfFrameWithDocWriteAbortedLoad_StillCorrectlyTagged) { |
| TestSubresourceFilterObserver observer(web_contents()); |
| |
| // Main frame. |
| ASSERT_TRUE( |
| ui_test_utils::NavigateToURL(browser(), GetURL("frame_factory.html"))); |
| |
| // Create a frame and abort its initial load in vanilla script. The children |
| // of this vanilla frame should be taggged correctly. |
| content::RenderFrameHost* vanilla_frame_with_aborted_load = |
| CreateFrameWithDocWriteAbortedLoad(GetWebContents()); |
| |
| content::RenderFrameHost* vanilla_child_of_vanilla = CreateSrcFrame( |
| vanilla_frame_with_aborted_load, GetURL("frame_factory.html")); |
| EXPECT_FALSE( |
| observer.GetIsAdSubframe(vanilla_child_of_vanilla->GetFrameTreeNodeId())); |
| |
| content::RenderFrameHost* ad_child_of_vanilla = CreateSrcFrameFromAdScript( |
| vanilla_frame_with_aborted_load, GetURL("frame_factory.html")); |
| EXPECT_TRUE( |
| observer.GetIsAdSubframe(ad_child_of_vanilla->GetFrameTreeNodeId())); |
| EXPECT_TRUE(EvidenceForFrameComprises( |
| ad_child_of_vanilla, /*parent_is_ad=*/false, |
| blink::mojom::FilterListResult::kMatchedNoRules, |
| blink::mojom::FrameCreationStackEvidence::kCreatedByAdScript)); |
| |
| // Create a frame and abort its initial load in ad script. The children of |
| // this ad frame should be tagged as ads. |
| content::RenderFrameHost* ad_frame_with_aborted_load = |
| CreateFrameWithDocWriteAbortedLoadFromAdScript(GetWebContents()); |
| EXPECT_TRUE(observer.GetIsAdSubframe( |
| ad_frame_with_aborted_load->GetFrameTreeNodeId())); |
| EXPECT_TRUE(EvidenceForFrameComprises( |
| ad_frame_with_aborted_load, |
| /*parent_is_ad=*/false, blink::mojom::FilterListResult::kNotChecked, |
| blink::mojom::FrameCreationStackEvidence::kCreatedByAdScript)); |
| |
| content::RenderFrameHost* vanilla_child_of_ad = |
| CreateSrcFrame(ad_frame_with_aborted_load, GetURL("frame_factory.html")); |
| EXPECT_TRUE( |
| observer.GetIsAdSubframe(vanilla_child_of_ad->GetFrameTreeNodeId())); |
| EXPECT_TRUE(EvidenceForFrameComprises( |
| vanilla_child_of_ad, /*parent_is_ad=*/true, |
| blink::mojom::FilterListResult::kMatchedNoRules, |
| blink::mojom::FrameCreationStackEvidence::kCreatedByAdScript)); |
| |
| content::RenderFrameHost* ad_child_of_ad = CreateSrcFrameFromAdScript( |
| ad_frame_with_aborted_load, GetURL("frame_factory.html")); |
| EXPECT_TRUE(observer.GetIsAdSubframe(ad_child_of_ad->GetFrameTreeNodeId())); |
| EXPECT_TRUE(EvidenceForFrameComprises( |
| ad_child_of_ad, /*parent_is_ad=*/true, |
| blink::mojom::FilterListResult::kMatchedNoRules, |
| blink::mojom::FrameCreationStackEvidence::kCreatedByAdScript)); |
| } |
| |
| // Test that the children of a frame with its initial load aborted due to a |
| // window.stop are reported correctly as vanilla or ad frames. |
| // This test is flaky. See crbug.com/1069346. |
| IN_PROC_BROWSER_TEST_F( |
| AdTaggingBrowserTest, |
| ChildrenOfFrameWithWindowStopAbortedLoad_StillCorrectlyTagged) { |
| TestSubresourceFilterObserver observer(web_contents()); |
| |
| // Main frame. |
| ASSERT_TRUE( |
| ui_test_utils::NavigateToURL(browser(), GetURL("frame_factory.html"))); |
| |
| // Create a frame and abort its initial load in vanilla script. The children |
| // of this vanilla frame should be taggged correctly. |
| content::RenderFrameHost* vanilla_frame_with_aborted_load = |
| CreateFrameWithWindowStopAbortedLoad(GetWebContents()); |
| |
| content::RenderFrameHost* vanilla_child_of_vanilla = CreateSrcFrame( |
| vanilla_frame_with_aborted_load, GetURL("frame_factory.html")); |
| EXPECT_FALSE( |
| observer.GetIsAdSubframe(vanilla_child_of_vanilla->GetFrameTreeNodeId())); |
| |
| content::RenderFrameHost* ad_child_of_vanilla = CreateSrcFrameFromAdScript( |
| vanilla_frame_with_aborted_load, GetURL("frame_factory.html")); |
| EXPECT_TRUE( |
| observer.GetIsAdSubframe(ad_child_of_vanilla->GetFrameTreeNodeId())); |
| EXPECT_TRUE(EvidenceForFrameComprises( |
| ad_child_of_vanilla, /*parent_is_ad=*/false, |
| blink::mojom::FilterListResult::kMatchedNoRules, |
| blink::mojom::FrameCreationStackEvidence::kCreatedByAdScript)); |
| |
| // Create a frame and abort its initial load in ad script. The children of |
| // this ad frame should be tagged as ads. |
| content::RenderFrameHost* ad_frame_with_aborted_load = |
| CreateFrameWithWindowStopAbortedLoadFromAdScript(GetWebContents()); |
| EXPECT_TRUE(observer.GetIsAdSubframe( |
| ad_frame_with_aborted_load->GetFrameTreeNodeId())); |
| EXPECT_TRUE(EvidenceForFrameComprises( |
| ad_frame_with_aborted_load, |
| /*parent_is_ad=*/false, blink::mojom::FilterListResult::kNotChecked, |
| blink::mojom::FrameCreationStackEvidence::kCreatedByAdScript)); |
| |
| content::RenderFrameHost* vanilla_child_of_ad = |
| CreateSrcFrame(ad_frame_with_aborted_load, GetURL("frame_factory.html")); |
| EXPECT_TRUE( |
| observer.GetIsAdSubframe(vanilla_child_of_ad->GetFrameTreeNodeId())); |
| EXPECT_TRUE(EvidenceForFrameComprises( |
| vanilla_child_of_ad, /*parent_is_ad=*/true, |
| blink::mojom::FilterListResult::kMatchedNoRules, |
| blink::mojom::FrameCreationStackEvidence::kCreatedByAdScript)); |
| |
| content::RenderFrameHost* ad_child_of_ad = CreateSrcFrameFromAdScript( |
| ad_frame_with_aborted_load, GetURL("frame_factory.html")); |
| EXPECT_TRUE(observer.GetIsAdSubframe(ad_child_of_ad->GetFrameTreeNodeId())); |
| EXPECT_TRUE(EvidenceForFrameComprises( |
| vanilla_child_of_ad, /*parent_is_ad=*/true, |
| blink::mojom::FilterListResult::kMatchedNoRules, |
| blink::mojom::FrameCreationStackEvidence::kCreatedByAdScript)); |
| } |
| |
| // Test that navigating a frame to a URL with a less restrictive load policy |
| // updates the latest filter list result, but not the most-restrictive one. |
| IN_PROC_BROWSER_TEST_F( |
| AdTaggingBrowserTest, |
| NavigationToLessRestrictiveUrl_MostRestrictiveLoadPolicyUnchanged) { |
| TestSubresourceFilterObserver observer(web_contents()); |
| |
| // Main frame. |
| ASSERT_TRUE( |
| ui_test_utils::NavigateToURL(browser(), GetURL("frame_factory.html"))); |
| |
| content::RenderFrameHost* test_frame = CreateSrcFrame( |
| GetWebContents(), GetURL("frame_factory.html?allowed=true")); |
| EXPECT_FALSE(observer.GetIsAdSubframe(test_frame->GetFrameTreeNodeId())); |
| EXPECT_TRUE(EvidenceForFrameComprises( |
| test_frame, /*parent_is_ad=*/false, |
| /*latest_filter_list_result=*/ |
| blink::mojom::FilterListResult::kMatchedAllowingRule, |
| /*most_restrictive_filter_list_result=*/ |
| blink::mojom::FilterListResult::kMatchedAllowingRule, |
| blink::mojom::FrameCreationStackEvidence::kNotCreatedByAdScript)); |
| |
| NavigateFrame(test_frame, GetURL("frame_factory.html")); |
| EXPECT_FALSE(observer.GetIsAdSubframe(test_frame->GetFrameTreeNodeId())); |
| EXPECT_TRUE(EvidenceForFrameComprises( |
| test_frame, /*parent_is_ad=*/false, |
| /*latest_filter_list_result=*/ |
| blink::mojom::FilterListResult::kMatchedNoRules, |
| /*most_restrictive_filter_list_result=*/ |
| blink::mojom::FilterListResult::kMatchedNoRules, |
| blink::mojom::FrameCreationStackEvidence::kNotCreatedByAdScript)); |
| |
| NavigateFrame(test_frame, GetURL("frame_factory.html?allowed=true")); |
| EXPECT_FALSE(observer.GetIsAdSubframe(test_frame->GetFrameTreeNodeId())); |
| EXPECT_TRUE(EvidenceForFrameComprises( |
| test_frame, /*parent_is_ad=*/false, |
| /*latest_filter_list_result=*/ |
| blink::mojom::FilterListResult::kMatchedAllowingRule, |
| /*most_restrictive_filter_list_result=*/ |
| blink::mojom::FilterListResult::kMatchedNoRules, |
| blink::mojom::FrameCreationStackEvidence::kNotCreatedByAdScript)); |
| |
| NavigateFrame(test_frame, GetURL("frame_factory.html?ad=true")); |
| EXPECT_TRUE(observer.GetIsAdSubframe(test_frame->GetFrameTreeNodeId())); |
| EXPECT_TRUE(EvidenceForFrameComprises( |
| test_frame, /*parent_is_ad=*/false, |
| /*latest_filter_list_result=*/ |
| blink::mojom::FilterListResult::kMatchedBlockingRule, |
| /*most_restrictive_filter_list_result=*/ |
| blink::mojom::FilterListResult::kMatchedBlockingRule, |
| blink::mojom::FrameCreationStackEvidence::kNotCreatedByAdScript)); |
| |
| NavigateFrame(test_frame, GetURL("frame_factory.html")); |
| EXPECT_TRUE(observer.GetIsAdSubframe(test_frame->GetFrameTreeNodeId())); |
| EXPECT_TRUE(EvidenceForFrameComprises( |
| test_frame, /*parent_is_ad=*/false, |
| /*latest_filter_list_result=*/ |
| blink::mojom::FilterListResult::kMatchedNoRules, |
| /*most_restrictive_filter_list_result=*/ |
| blink::mojom::FilterListResult::kMatchedBlockingRule, |
| blink::mojom::FrameCreationStackEvidence::kNotCreatedByAdScript)); |
| } |
| |
| // Basic vanilla stylesheet with vanilla font and image. |
| IN_PROC_BROWSER_TEST_F(AdTaggingBrowserTest, |
| VanillaExternalStylesheet_ResourcesNotTagged) { |
| auto waiter = CreateAdTaggingPageLoadMetricsTestWaiter(); |
| |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GetURL("test_div.html"))); |
| AddExternalStylesheet(GetWebContents(), |
| GetURL("sheet_with_vanilla_resources.css")); |
| |
| // Document, favicon, stylesheet, font, background-image |
| waiter->AddMinimumCompleteVanillaResourcesExpectation(5); |
| waiter->Wait(); |
| |
| EXPECT_EQ(waiter->current_complete_vanilla_resources(), 5); |
| EXPECT_EQ(waiter->current_complete_ad_resources(), 0); |
| } |
| |
| // Basic ad stylesheet with vanilla font and image. |
| IN_PROC_BROWSER_TEST_F(AdTaggingBrowserTest, |
| AdExternalStylesheet_ResourcesTagged) { |
| auto waiter = CreateAdTaggingPageLoadMetricsTestWaiter(); |
| |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GetURL("test_div.html"))); |
| AddExternalStylesheet(GetWebContents(), |
| GetURL("sheet_with_vanilla_resources.css?ad=true")); |
| |
| // Document, favicon |
| waiter->AddMinimumCompleteVanillaResourcesExpectation(2); |
| // Stylesheet, font, background-image |
| waiter->AddMinimumCompleteAdResourcesExpectation(3); |
| waiter->Wait(); |
| |
| EXPECT_EQ(waiter->current_complete_vanilla_resources(), 2); |
| EXPECT_EQ(waiter->current_complete_ad_resources(), 3); |
| } |
| |
| void ExpectWindowOpenUkmEntry(const ukm::TestUkmRecorder& ukm_recorder, |
| bool from_main_frame, |
| const GURL& main_frame_url, |
| bool from_ad_subframe, |
| bool from_ad_script) { |
| auto entries = ukm_recorder.GetEntriesByName( |
| ukm::builders::AbusiveExperienceHeuristic_WindowOpen::kEntryName); |
| EXPECT_EQ(1u, entries.size()); |
| |
| // Check that the event is keyed to |main_frame_url| only if it was from the |
| // top frame. |
| if (from_main_frame) { |
| ukm_recorder.ExpectEntrySourceHasUrl(entries.back(), main_frame_url); |
| } else { |
| EXPECT_FALSE(ukm_recorder.GetSourceForSourceId(entries.back()->source_id)); |
| } |
| |
| // Check that a DocumentCreated entry was created, and it's keyed to |
| // |main_frame_url| only if it was from the top frame. However, we can always |
| // use the navigation source ID to link this source to |main_frame_url|. |
| const ukm::mojom::UkmEntry* dc_entry = |
| ukm_recorder.GetDocumentCreatedEntryForSourceId( |
| entries.back()->source_id); |
| EXPECT_TRUE(dc_entry); |
| EXPECT_EQ(entries.back()->source_id, dc_entry->source_id); |
| if (from_main_frame) { |
| ukm_recorder.ExpectEntrySourceHasUrl(dc_entry, main_frame_url); |
| } else { |
| EXPECT_FALSE(ukm_recorder.GetSourceForSourceId(dc_entry->source_id)); |
| } |
| |
| const ukm::UkmSource* navigation_source = |
| ukm_recorder.GetSourceForSourceId(*ukm_recorder.GetEntryMetric( |
| dc_entry, ukm::builders::DocumentCreated::kNavigationSourceIdName)); |
| EXPECT_EQ(main_frame_url, navigation_source->url()); |
| |
| EXPECT_EQ(from_main_frame, |
| *ukm_recorder.GetEntryMetric( |
| dc_entry, ukm::builders::DocumentCreated::kIsMainFrameName)); |
| |
| ukm_recorder.ExpectEntryMetric( |
| entries.back(), |
| ukm::builders::AbusiveExperienceHeuristic_WindowOpen::kFromAdSubframeName, |
| from_ad_subframe); |
| ukm_recorder.ExpectEntryMetric( |
| entries.back(), |
| ukm::builders::AbusiveExperienceHeuristic_WindowOpen::kFromAdScriptName, |
| from_ad_script); |
| } |
| |
| void ExpectWindowOpenUmaEntry(const base::HistogramTester& histogram_tester, |
| bool from_ad_subframe, |
| bool from_ad_script) { |
| metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting(); |
| blink::FromAdState state = |
| blink::GetFromAdState(from_ad_subframe, from_ad_script); |
| histogram_tester.ExpectBucketCount(kWindowOpenFromAdStateHistogram, state, |
| 1 /* expected_count */); |
| } |
| |
| enum class NavigationInitiationType { |
| kWindowOpen, |
| kSetLocation, |
| kAnchorLinkActivate, |
| }; |
| |
| class AdClickNavigationBrowserTest |
| : public AdTaggingBrowserTest, |
| public ::testing::WithParamInterface< |
| std::tuple<NavigationInitiationType, bool /* gesture */>> { |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| // Popups without user gesture is blocked by default. Turn off the switch |
| // here to unblock that, so as to be able to test that the UseCounter not |
| // being recorded was due to the filtering in our recording function, rather |
| // than the default popup blocking. |
| command_line->AppendSwitch(embedder_support::kDisablePopupBlocking); |
| } |
| }; |
| |
| IN_PROC_BROWSER_TEST_P(AdClickNavigationBrowserTest, UseCounter) { |
| NavigationInitiationType type; |
| bool gesture; |
| std::tie(type, gesture) = GetParam(); |
| auto web_feature_waiter = |
| std::make_unique<page_load_metrics::PageLoadMetricsTestWaiter>( |
| GetWebContents()); |
| blink::mojom::WebFeature ad_click_navigation_feature = |
| blink::mojom::WebFeature::kAdClickNavigation; |
| web_feature_waiter->AddWebFeatureExpectation(ad_click_navigation_feature); |
| GURL url = |
| embedded_test_server()->GetURL("a.com", "/ad_tagging/frame_factory.html"); |
| |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url)); |
| content::WebContents* main_tab = GetWebContents(); |
| RenderFrameHost* child = CreateSrcFrame( |
| main_tab, embedded_test_server()->GetURL( |
| "a.com", "/ad_tagging/frame_factory.html?1&ad=true")); |
| |
| std::string script; |
| switch (type) { |
| case NavigationInitiationType::kWindowOpen: |
| script = "window.open('frame_factory.html')"; |
| break; |
| case NavigationInitiationType::kSetLocation: |
| script = "location='frame_factory.html'"; |
| break; |
| case NavigationInitiationType::kAnchorLinkActivate: |
| script = |
| "var a = document.createElement('a');" |
| "a.setAttribute('href', 'frame_factory.html');" |
| "a.click();"; |
| break; |
| } |
| |
| if (gesture) { |
| EXPECT_TRUE(ExecJs(child, script)); |
| web_feature_waiter->Wait(); |
| } else { |
| switch (type) { |
| case NavigationInitiationType::kSetLocation: |
| case NavigationInitiationType::kAnchorLinkActivate: { |
| content::TestNavigationObserver navigation_observer(web_contents()); |
| EXPECT_TRUE(content::ExecJs(child, script, |
| content::EXECUTE_SCRIPT_NO_USER_GESTURE)); |
| // To report metrics. |
| navigation_observer.Wait(); |
| break; |
| } |
| case NavigationInitiationType::kWindowOpen: { |
| EXPECT_TRUE(content::ExecJs(child, script, |
| content::EXECUTE_SCRIPT_NO_USER_GESTURE)); |
| // To report metrics. |
| ASSERT_EQ(2, browser()->tab_strip_model()->count()); |
| browser()->tab_strip_model()->MoveSelectedTabsTo(0); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url)); |
| break; |
| } |
| } |
| EXPECT_FALSE( |
| web_feature_waiter->DidObserveWebFeature(ad_click_navigation_feature)); |
| } |
| } |
| |
| INSTANTIATE_TEST_SUITE_P( |
| All, |
| AdClickNavigationBrowserTest, |
| ::testing::Combine( |
| ::testing::Values(NavigationInitiationType::kWindowOpen, |
| NavigationInitiationType::kSetLocation, |
| NavigationInitiationType::kAnchorLinkActivate), |
| ::testing::Bool())); |
| |
| class AdTaggingEventFromSubframeBrowserTest |
| : public AdTaggingBrowserTest, |
| public ::testing::WithParamInterface< |
| std::tuple<bool /* cross_origin */, bool /* from_ad_subframe */>> {}; |
| |
| // crbug.com/997410. The test is flaky on multiple platforms. |
| IN_PROC_BROWSER_TEST_P(AdTaggingEventFromSubframeBrowserTest, |
| DISABLED_WindowOpenFromSubframe) { |
| bool cross_origin; |
| bool from_ad_subframe; |
| std::tie(cross_origin, from_ad_subframe) = GetParam(); |
| SCOPED_TRACE(::testing::Message() |
| << "cross_origin = " << cross_origin << ", " |
| << "from_ad_subframe = " << from_ad_subframe); |
| |
| ukm::TestAutoSetUkmRecorder ukm_recorder; |
| base::HistogramTester histogram_tester; |
| GURL main_frame_url = |
| embedded_test_server()->GetURL("a.com", "/ad_tagging/frame_factory.html"); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), main_frame_url)); |
| content::WebContents* main_tab = GetWebContents(); |
| |
| std::string hostname = cross_origin ? "b.com" : "a.com"; |
| std::string suffix = from_ad_subframe ? "&ad=true" : ""; |
| RenderFrameHost* child = CreateSrcFrame( |
| main_tab, embedded_test_server()->GetURL( |
| hostname, "/ad_tagging/frame_factory.html?1" + suffix)); |
| |
| EXPECT_TRUE(content::ExecJs(child, "window.open();")); |
| |
| bool from_ad_script = from_ad_subframe; |
| ExpectWindowOpenUkmEntry(ukm_recorder, false /* from_main_frame */, |
| main_frame_url, from_ad_subframe, from_ad_script); |
| ExpectWindowOpenUmaEntry(histogram_tester, from_ad_subframe, from_ad_script); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P( |
| All, |
| AdTaggingEventFromSubframeBrowserTest, |
| ::testing::Combine(::testing::Bool(), ::testing::Bool())); |
| |
| class AdTaggingEventWithScriptInStackBrowserTest |
| : public AdTaggingBrowserTest, |
| public ::testing::WithParamInterface<bool /* from_ad_script */> {}; |
| |
| // crbug.com/998405. The test is flaky on multiple platforms. |
| IN_PROC_BROWSER_TEST_P(AdTaggingEventWithScriptInStackBrowserTest, |
| DISABLED_WindowOpenWithScriptInStack) { |
| bool from_ad_script = GetParam(); |
| SCOPED_TRACE(::testing::Message() << "from_ad_script = " << from_ad_script); |
| |
| ukm::TestAutoSetUkmRecorder ukm_recorder; |
| base::HistogramTester histogram_tester; |
| GURL main_frame_url = GetURL("frame_factory.html"); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), main_frame_url)); |
| content::WebContents* main_tab = GetWebContents(); |
| |
| std::string script = from_ad_script ? "windowOpenFromAdScript();" |
| : "windowOpenFromNonAdScript();"; |
| |
| EXPECT_TRUE(content::ExecJs(main_tab, script)); |
| |
| bool from_ad_subframe = false; |
| ExpectWindowOpenUkmEntry(ukm_recorder, true /* from_main_frame */, |
| main_frame_url, from_ad_subframe, from_ad_script); |
| ExpectWindowOpenUmaEntry(histogram_tester, from_ad_subframe, from_ad_script); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P( |
| All, |
| AdTaggingEventWithScriptInStackBrowserTest, |
| ::testing::Bool()); |
| |
| } // namespace |
| |
| } // namespace subresource_filter |