| // 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 "third_party/blink/renderer/core/frame/ad_tracker.h" |
| |
| #include <memory> |
| |
| #include "base/run_loop.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/blink/public/common/features.h" |
| #include "third_party/blink/renderer/core/dom/element_traversal.h" |
| #include "third_party/blink/renderer/core/frame/local_dom_window.h" |
| #include "third_party/blink/renderer/core/frame/local_frame.h" |
| #include "third_party/blink/renderer/core/html/html_image_element.h" |
| #include "third_party/blink/renderer/core/probe/async_task_id.h" |
| #include "third_party/blink/renderer/core/probe/core_probes.h" |
| #include "third_party/blink/renderer/core/testing/dummy_page_holder.h" |
| #include "third_party/blink/renderer/core/testing/sim/sim_request.h" |
| #include "third_party/blink/renderer/core/testing/sim/sim_test.h" |
| #include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h" |
| #include "third_party/blink/renderer/platform/testing/unit_test_helpers.h" |
| |
| namespace blink { |
| |
| namespace { |
| |
| const unsigned char kSmallGifData[] = {0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0x01, |
| 0x00, 0x01, 0x00, 0x00, 0xff, 0x00, 0x2c, |
| 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, |
| 0x00, 0x00, 0x02, 0x00, 0x3b}; |
| |
| // The pages include a div with class="test" to ensure the resources in the |
| // stylesheet are loaded. |
| const char kPageWithVanillaExternalStylesheet[] = R"HTML( |
| <head><link rel="stylesheet" href="style.css"></head> |
| <body><div class="test">Test</div></body> |
| )HTML"; |
| const char kPageWithAdExternalStylesheet[] = R"HTML( |
| <head><link rel="stylesheet" href="style.css?ad=true"></head> |
| <body><div class="test">Test</div></body> |
| )HTML"; |
| const char kPageWithVanillaScript[] = R"HTML( |
| <head><script defer src="script.js"></script></head> |
| <body><div class="test">Test</div></body> |
| )HTML"; |
| const char kPageWithAdScript[] = R"HTML( |
| <head><script defer src="script.js?ad=true"></script></head> |
| <body><div class="test">Test</div></body> |
| )HTML"; |
| const char kPageWithFrame[] = R"HTML( |
| <head></head> |
| <body><div class="test">Test</div><iframe src="frame.html"></iframe></body> |
| )HTML"; |
| const char kPageWithStyleTagLoadingVanillaResources[] = R"HTML( |
| <head><style> |
| @font-face { |
| font-family: "Vanilla"; |
| src: url("font.woff2") format("woff2"); |
| } |
| .test { |
| font-family: "Vanilla"; |
| background-image: url("pixel.png"); |
| } |
| </style></head> |
| <body><div class="test">Test</div></body> |
| )HTML"; |
| |
| const char kStylesheetWithVanillaResources[] = R"CSS( |
| @font-face { |
| font-family: "Vanilla"; |
| src: url("font.woff2") format("woff2"); |
| } |
| .test { |
| font-family: "Vanilla"; |
| background-image: url("pixel.png"); |
| } |
| )CSS"; |
| const char kStylesheetWithAdResources[] = R"CSS( |
| @font-face { |
| font-family: "Ad"; |
| src: url("font.woff2?ad=true") format("woff2"); |
| } |
| .test { |
| font-family: "Ad"; |
| background-image: url("pixel.png?ad=true"); |
| } |
| )CSS"; |
| |
| class TestAdTracker : public AdTracker { |
| public: |
| explicit TestAdTracker(LocalFrame* frame) : AdTracker(frame) {} |
| void SetScriptAtTopOfStack(const String& url) { script_at_top_ = url; } |
| void SetExecutionContext(ExecutionContext* execution_context) { |
| execution_context_ = execution_context; |
| } |
| |
| void SetAdSuffix(const String& ad_suffix) { ad_suffix_ = ad_suffix; } |
| ~TestAdTracker() override {} |
| |
| void Trace(Visitor* visitor) const override { |
| visitor->Trace(execution_context_); |
| AdTracker::Trace(visitor); |
| } |
| |
| bool RequestWithUrlTaggedAsAd(const String& url) const { |
| DCHECK(is_ad_.Contains(url)); |
| return is_ad_.at(url); |
| } |
| |
| bool UrlHasBeenRequested(const String& url) const { |
| return is_ad_.Contains(url); |
| } |
| |
| void SetSimTest() { sim_test_ = true; } |
| |
| void WaitForSubresource(const String& url) { |
| if (base::Contains(is_ad_, url)) { |
| return; |
| } |
| url_to_wait_for_ = url; |
| base::RunLoop run_loop; |
| quit_closure_ = run_loop.QuitClosure(); |
| run_loop.Run(); |
| } |
| |
| protected: |
| String ScriptAtTopOfStack() override { |
| if (sim_test_ && !script_at_top_) |
| return AdTracker::ScriptAtTopOfStack(); |
| return script_at_top_; |
| } |
| |
| ExecutionContext* GetCurrentExecutionContext() override { |
| if (!execution_context_) |
| return AdTracker::GetCurrentExecutionContext(); |
| |
| return execution_context_; |
| } |
| |
| bool CalculateIfAdSubresource(ExecutionContext* execution_context, |
| const KURL& request_url, |
| ResourceType resource_type, |
| const FetchInitiatorInfo& initiator_info, |
| bool ad_request) override { |
| if (!ad_suffix_.IsEmpty() && request_url.GetString().EndsWith(ad_suffix_)) { |
| ad_request = true; |
| } |
| |
| ad_request = AdTracker::CalculateIfAdSubresource( |
| execution_context, request_url, resource_type, initiator_info, |
| ad_request); |
| |
| String resource_url = request_url.GetString(); |
| is_ad_.insert(resource_url, ad_request); |
| |
| if (quit_closure_ && url_to_wait_for_ == resource_url) { |
| std::move(quit_closure_).Run(); |
| } |
| return ad_request; |
| } |
| |
| private: |
| HashMap<String, bool> is_ad_; |
| String script_at_top_; |
| Member<ExecutionContext> execution_context_; |
| String ad_suffix_; |
| bool sim_test_ = false; |
| |
| base::OnceClosure quit_closure_; |
| String url_to_wait_for_; |
| }; |
| |
| } // namespace |
| |
| class AdTrackerTest : public testing::Test { |
| protected: |
| void SetUp() override; |
| void TearDown() override; |
| LocalFrame* GetFrame() const { |
| return page_holder_->GetDocument().GetFrame(); |
| } |
| |
| void CreateAdTracker() { |
| if (ad_tracker_) |
| ad_tracker_->Shutdown(); |
| ad_tracker_ = MakeGarbageCollected<TestAdTracker>(GetFrame()); |
| ad_tracker_->SetExecutionContext(ExecutionContext()); |
| } |
| |
| void WillExecuteScript(const String& script_url) { |
| ad_tracker_->WillExecuteScript(ExecutionContext(), String(script_url), |
| v8::Message::kNoScriptIdInfo); |
| } |
| |
| ExecutionContext* ExecutionContext() { |
| return page_holder_->GetFrame().DomWindow(); |
| } |
| |
| void DidExecuteScript() { ad_tracker_->DidExecuteScript(); } |
| |
| bool AnyExecutingScriptsTaggedAsAdResource() { |
| return AnyExecutingScriptsTaggedAsAdResourceWithStackType( |
| AdTracker::StackType::kBottomAndTop); |
| } |
| |
| bool AnyExecutingScriptsTaggedAsAdResourceWithStackType( |
| AdTracker::StackType stack_type) { |
| return ad_tracker_->IsAdScriptInStack(stack_type); |
| } |
| |
| void AppendToKnownAdScripts(const String& url) { |
| ad_tracker_->AppendToKnownAdScripts(*ExecutionContext(), url); |
| } |
| |
| Persistent<TestAdTracker> ad_tracker_; |
| std::unique_ptr<DummyPageHolder> page_holder_; |
| }; |
| |
| void AdTrackerTest::SetUp() { |
| page_holder_ = std::make_unique<DummyPageHolder>(IntSize(800, 600)); |
| page_holder_->GetDocument().SetURL(KURL("https://example.com/foo")); |
| CreateAdTracker(); |
| } |
| |
| void AdTrackerTest::TearDown() { |
| ad_tracker_->Shutdown(); |
| } |
| |
| TEST_F(AdTrackerTest, AnyExecutingScriptsTaggedAsAdResource) { |
| String ad_script_url("https://example.com/bar.js"); |
| AppendToKnownAdScripts(ad_script_url); |
| |
| WillExecuteScript("https://example.com/foo.js"); |
| WillExecuteScript("https://example.com/bar.js"); |
| EXPECT_TRUE(AnyExecutingScriptsTaggedAsAdResource()); |
| } |
| |
| TEST_F(AdTrackerTest, BottomScriptTaggedAsAdResource) { |
| AppendToKnownAdScripts("https://example.com/ad.js"); |
| |
| WillExecuteScript("https://example.com/ad.js"); |
| ad_tracker_->SetScriptAtTopOfStack("https://example.com/vanilla.js"); |
| EXPECT_TRUE(AnyExecutingScriptsTaggedAsAdResourceWithStackType( |
| AdTracker::StackType::kBottomAndTop)); |
| EXPECT_TRUE(AnyExecutingScriptsTaggedAsAdResourceWithStackType( |
| AdTracker::StackType::kBottomOnly)); |
| } |
| |
| TEST_F(AdTrackerTest, TopScriptTaggedAsAdResource) { |
| AppendToKnownAdScripts("https://example.com/ad.js"); |
| |
| WillExecuteScript("https://example.com/vanilla.js"); |
| ad_tracker_->SetScriptAtTopOfStack("https://example.com/ad.js"); |
| |
| EXPECT_TRUE(AnyExecutingScriptsTaggedAsAdResourceWithStackType( |
| AdTracker::StackType::kBottomAndTop)); |
| EXPECT_FALSE(AnyExecutingScriptsTaggedAsAdResourceWithStackType( |
| AdTracker::StackType::kBottomOnly)); |
| } |
| |
| // Tests that if neither script in the stack is an ad, |
| // AnyExecutingScriptsTaggedAsAdResource should return false. |
| TEST_F(AdTrackerTest, AnyExecutingScriptsTaggedAsAdResource_False) { |
| WillExecuteScript("https://example.com/foo.js"); |
| WillExecuteScript("https://example.com/bar.js"); |
| EXPECT_FALSE(AnyExecutingScriptsTaggedAsAdResource()); |
| } |
| |
| TEST_F(AdTrackerTest, TopOfStackIncluded) { |
| String ad_script_url("https://example.com/ad.js"); |
| AppendToKnownAdScripts(ad_script_url); |
| |
| WillExecuteScript("https://example.com/foo.js"); |
| WillExecuteScript("https://example.com/bar.js"); |
| EXPECT_FALSE(AnyExecutingScriptsTaggedAsAdResource()); |
| |
| ad_tracker_->SetScriptAtTopOfStack("https://www.example.com/baz.js"); |
| EXPECT_FALSE(AnyExecutingScriptsTaggedAsAdResource()); |
| |
| ad_tracker_->SetScriptAtTopOfStack(ad_script_url); |
| EXPECT_TRUE(AnyExecutingScriptsTaggedAsAdResource()); |
| EXPECT_FALSE(AnyExecutingScriptsTaggedAsAdResourceWithStackType( |
| AdTracker::StackType::kBottomOnly)); |
| |
| ad_tracker_->SetScriptAtTopOfStack("https://www.example.com/baz.js"); |
| EXPECT_FALSE(AnyExecutingScriptsTaggedAsAdResource()); |
| |
| ad_tracker_->SetScriptAtTopOfStack(""); |
| EXPECT_FALSE(AnyExecutingScriptsTaggedAsAdResource()); |
| |
| ad_tracker_->SetScriptAtTopOfStack(String()); |
| EXPECT_FALSE(AnyExecutingScriptsTaggedAsAdResource()); |
| |
| WillExecuteScript(ad_script_url); |
| EXPECT_TRUE(AnyExecutingScriptsTaggedAsAdResource()); |
| } |
| |
| TEST_F(AdTrackerTest, AdStackFrameCounting) { |
| AppendToKnownAdScripts("https://example.com/ad.js"); |
| |
| WillExecuteScript("https://example.com/vanilla.js"); |
| WillExecuteScript("https://example.com/vanilla.js"); |
| EXPECT_FALSE(AnyExecutingScriptsTaggedAsAdResource()); |
| |
| WillExecuteScript("https://example.com/ad.js"); |
| EXPECT_TRUE(AnyExecutingScriptsTaggedAsAdResource()); |
| |
| DidExecuteScript(); |
| EXPECT_FALSE(AnyExecutingScriptsTaggedAsAdResource()); |
| |
| WillExecuteScript("https://example.com/ad.js"); |
| WillExecuteScript("https://example.com/ad.js"); |
| WillExecuteScript("https://example.com/vanilla.js"); |
| EXPECT_TRUE(AnyExecutingScriptsTaggedAsAdResource()); |
| |
| DidExecuteScript(); |
| DidExecuteScript(); |
| EXPECT_TRUE(AnyExecutingScriptsTaggedAsAdResource()); |
| |
| DidExecuteScript(); |
| EXPECT_FALSE(AnyExecutingScriptsTaggedAsAdResource()); |
| |
| DidExecuteScript(); |
| DidExecuteScript(); |
| EXPECT_FALSE(AnyExecutingScriptsTaggedAsAdResource()); |
| |
| WillExecuteScript("https://example.com/ad.js"); |
| EXPECT_TRUE(AnyExecutingScriptsTaggedAsAdResource()); |
| } |
| |
| TEST_F(AdTrackerTest, AsyncTagging) { |
| base::test::ScopedFeatureList feature_list; |
| feature_list.InitAndEnableFeature(features::kAsyncStackAdTagging); |
| CreateAdTracker(); |
| |
| // Put an ad script on the stack. |
| AppendToKnownAdScripts("https://example.com/ad.js"); |
| WillExecuteScript("https://example.com/ad.js"); |
| EXPECT_TRUE(AnyExecutingScriptsTaggedAsAdResource()); |
| |
| // Create a fake task void*. |
| probe::AsyncTaskId async_task; |
| |
| // Create an async task while ad script is running. |
| ad_tracker_->DidCreateAsyncTask(&async_task); |
| |
| // Finish executing the ad script. |
| DidExecuteScript(); |
| EXPECT_FALSE(AnyExecutingScriptsTaggedAsAdResource()); |
| |
| // Start and stop the async task created by the ad script. |
| ad_tracker_->DidStartAsyncTask(&async_task); |
| EXPECT_TRUE(AnyExecutingScriptsTaggedAsAdResource()); |
| ad_tracker_->DidFinishAsyncTask(&async_task); |
| EXPECT_FALSE(AnyExecutingScriptsTaggedAsAdResource()); |
| |
| // Do it again. |
| ad_tracker_->DidStartAsyncTask(&async_task); |
| EXPECT_TRUE(AnyExecutingScriptsTaggedAsAdResource()); |
| ad_tracker_->DidFinishAsyncTask(&async_task); |
| EXPECT_FALSE(AnyExecutingScriptsTaggedAsAdResource()); |
| |
| // Call the task recursively. |
| ad_tracker_->DidStartAsyncTask(&async_task); |
| EXPECT_TRUE(AnyExecutingScriptsTaggedAsAdResource()); |
| ad_tracker_->DidStartAsyncTask(&async_task); |
| EXPECT_TRUE(AnyExecutingScriptsTaggedAsAdResource()); |
| ad_tracker_->DidFinishAsyncTask(&async_task); |
| EXPECT_TRUE(AnyExecutingScriptsTaggedAsAdResource()); |
| ad_tracker_->DidFinishAsyncTask(&async_task); |
| EXPECT_FALSE(AnyExecutingScriptsTaggedAsAdResource()); |
| } |
| |
| class AdTrackerSimTest : public SimTest { |
| protected: |
| void SetUp() override { |
| SimTest::SetUp(); |
| main_resource_ = std::make_unique<SimRequest>( |
| "https://example.com/test.html", "text/html"); |
| |
| LoadURL("https://example.com/test.html"); |
| ad_tracker_ = MakeGarbageCollected<TestAdTracker>(GetDocument().GetFrame()); |
| ad_tracker_->SetSimTest(); |
| GetDocument().GetFrame()->SetAdTrackerForTesting(ad_tracker_); |
| } |
| |
| void TearDown() override { |
| ad_tracker_->Shutdown(); |
| SimTest::TearDown(); |
| } |
| |
| bool IsKnownAdScript(ExecutionContext* execution_context, const String& url) { |
| return ad_tracker_->IsKnownAdScript(execution_context, url); |
| } |
| |
| std::unique_ptr<SimRequest> main_resource_; |
| Persistent<TestAdTracker> ad_tracker_; |
| }; |
| |
| // Script loaded by ad script is tagged as ad. |
| TEST_F(AdTrackerSimTest, ScriptLoadedWhileExecutingAdScript) { |
| const char kAdUrl[] = "https://example.com/ad_script.js"; |
| const char kVanillaUrl[] = "https://example.com/vanilla_script.js"; |
| SimSubresourceRequest ad_resource(kAdUrl, "text/javascript"); |
| SimSubresourceRequest vanilla_script(kVanillaUrl, "text/javascript"); |
| |
| ad_tracker_->SetAdSuffix("ad_script.js"); |
| |
| main_resource_->Complete("<body></body><script src=ad_script.js></script>"); |
| |
| ad_resource.Complete(R"SCRIPT( |
| script = document.createElement("script"); |
| script.src = "vanilla_script.js"; |
| document.body.appendChild(script); |
| )SCRIPT"); |
| |
| // Wait for script to run. |
| base::RunLoop().RunUntilIdle(); |
| |
| vanilla_script.Complete(""); |
| |
| EXPECT_TRUE(IsKnownAdScript(GetDocument().GetExecutionContext(), kAdUrl)); |
| EXPECT_TRUE( |
| IsKnownAdScript(GetDocument().GetExecutionContext(), kVanillaUrl)); |
| EXPECT_TRUE(ad_tracker_->RequestWithUrlTaggedAsAd(kAdUrl)); |
| EXPECT_TRUE(ad_tracker_->RequestWithUrlTaggedAsAd(kVanillaUrl)); |
| } |
| |
| // Unknown script running in an ad context should be labeled as ad script. |
| TEST_F(AdTrackerSimTest, ScriptDetectedByContext) { |
| // Create an iframe that's considered an ad. |
| main_resource_->Complete("<body><iframe></iframe></body>"); |
| auto* child_frame = |
| To<LocalFrame>(GetDocument().GetFrame()->Tree().FirstChild()); |
| child_frame->SetIsAdSubframe(blink::mojom::AdFrameType::kRootAd); |
| |
| // Now run unknown script in the child's context. It should be considered an |
| // ad based on context alone. |
| ad_tracker_->SetExecutionContext(child_frame->DomWindow()); |
| ad_tracker_->SetScriptAtTopOfStack("foo.js"); |
| EXPECT_TRUE( |
| ad_tracker_->IsAdScriptInStack(AdTracker::StackType::kBottomAndTop)); |
| } |
| |
| TEST_F(AdTrackerSimTest, EventHandlerForPostMessageFromAdFrame_NoAdInStack) { |
| const char kAdScriptUrl[] = "https://example.com/ad_script.js"; |
| SimSubresourceRequest ad_script(kAdScriptUrl, "text/javascript"); |
| const char kVanillaUrl[] = "https://example.com/vanilla_script.js"; |
| SimSubresourceRequest vanilla_script(kVanillaUrl, "text/javascript"); |
| |
| SimSubresourceRequest image_resource("https://example.com/image.gif", |
| "image/gif"); |
| |
| ad_tracker_->SetAdSuffix("ad_script.js"); |
| |
| // Create an iframe that's considered an ad. |
| main_resource_->Complete(R"(<body> |
| <script src='vanilla_script.js'></script> |
| <script src='ad_script.js'></script> |
| </body>)"); |
| |
| // Register a postMessage handler which is not considered to be ad script, |
| // which loads an image. |
| vanilla_script.Complete(R"SCRIPT( |
| window.addEventListener('message', e => { |
| image = document.createElement("img"); |
| image.src = "image.gif"; |
| document.body.appendChild(image); |
| });)SCRIPT"); |
| |
| // Post message from an ad iframe to the non-ad script in the parent frame. |
| ad_script.Complete(R"SCRIPT( |
| frame = document.createElement("iframe"); |
| document.body.appendChild(frame); |
| iframeDocument = frame.contentWindow.document; |
| iframeDocument.open(); |
| iframeDocument.write( |
| "<html><script>window.parent.postMessage('a', '*');</script></html>"); |
| iframeDocument.close(); |
| )SCRIPT"); |
| |
| // Wait for script to run. |
| base::RunLoop().RunUntilIdle(); |
| |
| image_resource.Complete("data"); |
| |
| // The image should not be considered an ad even if it was loaded in response |
| // to an ad initiated postMessage. |
| EXPECT_FALSE( |
| ad_tracker_->RequestWithUrlTaggedAsAd("https://example.com/image.gif")); |
| } |
| |
| TEST_F(AdTrackerSimTest, RedirectToAdUrl) { |
| SimRequest::Params params; |
| params.redirect_url = "https://example.com/ad_script.js"; |
| SimSubresourceRequest redirect_script( |
| "https://example.com/redirect_script.js", "text/javascript", params); |
| SimSubresourceRequest ad_script("https://example.com/ad_script.js", |
| "text/javascript"); |
| |
| ad_tracker_->SetAdSuffix("ad_script.js"); |
| |
| main_resource_->Complete( |
| "<body><script src='redirect_script.js'></script></body>"); |
| |
| ad_script.Complete(""); |
| |
| EXPECT_FALSE(ad_tracker_->RequestWithUrlTaggedAsAd( |
| "https://example.com/redirect_script.js")); |
| EXPECT_TRUE(ad_tracker_->RequestWithUrlTaggedAsAd( |
| "https://example.com/ad_script.js")); |
| } |
| |
| TEST_F(AdTrackerSimTest, AdResourceDetectedByContext) { |
| SimRequest ad_frame("https://example.com/ad_frame.html", "text/html"); |
| SimSubresourceRequest foo_css("https://example.com/foo.css", "text/style"); |
| |
| // Create an iframe that's considered an ad. |
| main_resource_->Complete( |
| "<body><iframe src='ad_frame.html'></iframe></body>"); |
| auto* child_frame = |
| To<LocalFrame>(GetDocument().GetFrame()->Tree().FirstChild()); |
| child_frame->SetIsAdSubframe(blink::mojom::AdFrameType::kRootAd); |
| |
| // Load a resource from the frame. It should be detected as an ad resource due |
| // to its context. |
| ad_frame.Complete(R"HTML( |
| <link rel="stylesheet" href="foo.css"> |
| )HTML"); |
| |
| foo_css.Complete(""); |
| |
| EXPECT_TRUE( |
| ad_tracker_->RequestWithUrlTaggedAsAd("https://example.com/foo.css")); |
| } |
| |
| // When inline script in an ad frame inserts an iframe into a non-ad frame, the |
| // new frame should be considered as created by ad script (and would therefore |
| // be tagged as an ad). |
| TEST_F(AdTrackerSimTest, InlineAdScriptRunningInNonAdContext) { |
| SimSubresourceRequest ad_script("https://example.com/ad_script.js", |
| "text/javascript"); |
| SimRequest ad_iframe("https://example.com/ad_frame.html", "text/html"); |
| ad_tracker_->SetAdSuffix("ad_script.js"); |
| |
| main_resource_->Complete("<body><script src='ad_script.js'></script></body>"); |
| ad_script.Complete(R"SCRIPT( |
| frame = document.createElement("iframe"); |
| frame.src = "ad_frame.html"; |
| document.body.appendChild(frame); |
| )SCRIPT"); |
| |
| // Wait for script to run. |
| base::RunLoop().RunUntilIdle(); |
| |
| auto* child_frame = |
| To<LocalFrame>(GetDocument().GetFrame()->Tree().FirstChild()); |
| |
| // Verify that the new frame is considered created by ad script then set it |
| // as an ad subframe. This emulates the embedder tagging a frame as an ad. |
| EXPECT_TRUE(child_frame->IsSubframeCreatedByAdScript()); |
| child_frame->SetIsAdSubframe(blink::mojom::AdFrameType::kRootAd); |
| |
| // Create a new sibling frame to the ad frame. The ad context calls the non-ad |
| // context's (top frame) appendChild. |
| ad_iframe.Complete(R"HTML( |
| <script> |
| frame = document.createElement("iframe"); |
| frame.name = "ad_sibling"; |
| parent.document.body.appendChild(frame); |
| </script> |
| )HTML"); |
| |
| // The new sibling frame should also be identified as created by ad script. |
| EXPECT_TRUE( |
| To<LocalFrame>(GetDocument().GetFrame()->Tree().ScopedChild("ad_sibling")) |
| ->IsSubframeCreatedByAdScript()); |
| } |
| |
| // Image loaded by ad script is tagged as ad. |
| TEST_F(AdTrackerSimTest, ImageLoadedWhileExecutingAdScriptAsyncEnabled) { |
| base::test::ScopedFeatureList feature_list; |
| feature_list.InitAndEnableFeature(features::kAsyncStackAdTagging); |
| |
| // Reset the AdTracker so that it gets the latest base::Feature value on |
| // construction. |
| ad_tracker_ = MakeGarbageCollected<TestAdTracker>(GetDocument().GetFrame()); |
| GetDocument().GetFrame()->SetAdTrackerForTesting(ad_tracker_); |
| |
| const char kAdUrl[] = "https://example.com/ad_script.js"; |
| const char kVanillaUrl[] = "https://example.com/vanilla_image.gif"; |
| SimSubresourceRequest ad_resource(kAdUrl, "text/javascript"); |
| SimSubresourceRequest vanilla_image(kVanillaUrl, "image/gif"); |
| |
| ad_tracker_->SetAdSuffix("ad_script.js"); |
| |
| main_resource_->Complete("<body></body><script src=ad_script.js></script>"); |
| |
| ad_resource.Complete(R"SCRIPT( |
| image = document.createElement("img"); |
| image.src = "vanilla_image.gif"; |
| document.body.appendChild(image); |
| )SCRIPT"); |
| |
| // Wait for script to run. |
| base::RunLoop().RunUntilIdle(); |
| |
| // Put the gif bytes in a Vector to avoid difficulty with |
| // non null-terminated char*. |
| Vector<char> gif; |
| gif.Append(kSmallGifData, sizeof(kSmallGifData)); |
| |
| vanilla_image.Complete(gif); |
| |
| EXPECT_TRUE(IsKnownAdScript(GetDocument().GetExecutionContext(), kAdUrl)); |
| EXPECT_TRUE(ad_tracker_->RequestWithUrlTaggedAsAd(kAdUrl)); |
| |
| // Image loading is async, so we should catch this when async stacks are |
| // monitored. |
| EXPECT_TRUE(ad_tracker_->RequestWithUrlTaggedAsAd(kVanillaUrl)); |
| |
| // Walk through the DOM to get the image element. |
| Element* doc_element = GetDocument().documentElement(); |
| Element* body_element = Traversal<Element>::LastChild(*doc_element); |
| HTMLImageElement* image_element = |
| Traversal<HTMLImageElement>::FirstChild(*body_element); |
| |
| // When async stacks are monitored, we should also tag the |
| // HTMLImageElement as ad-related. |
| ASSERT_TRUE(image_element); |
| EXPECT_TRUE(image_element->IsAdRelated()); |
| } |
| |
| // Image loaded by ad script is tagged as ad. |
| TEST_F(AdTrackerSimTest, ImageLoadedWhileExecutingAdScriptAsyncDisabled) { |
| base::test::ScopedFeatureList feature_list; |
| feature_list.InitAndDisableFeature(features::kAsyncStackAdTagging); |
| |
| // Reset the AdTracker so that it gets the latest base::Feature value on |
| // construction. |
| ad_tracker_ = MakeGarbageCollected<TestAdTracker>(GetDocument().GetFrame()); |
| GetDocument().GetFrame()->SetAdTrackerForTesting(ad_tracker_); |
| |
| const char kAdUrl[] = "https://example.com/ad_script.js"; |
| const char kVanillaUrl[] = "https://example.com/vanilla_image.gif"; |
| SimSubresourceRequest ad_resource(kAdUrl, "text/javascript"); |
| SimSubresourceRequest vanilla_image(kVanillaUrl, "image/gif"); |
| |
| ad_tracker_->SetAdSuffix("ad_script.js"); |
| |
| main_resource_->Complete("<body></body><script src=ad_script.js></script>"); |
| |
| ad_resource.Complete(R"SCRIPT( |
| image = document.createElement("img"); |
| image.src = "vanilla_image.gif"; |
| document.body.appendChild(image); |
| )SCRIPT"); |
| |
| // Wait for script to run. |
| base::RunLoop().RunUntilIdle(); |
| |
| // Put the gif bytes in a Vector to avoid difficulty with |
| // non null-terminated char*. |
| Vector<char> gif; |
| gif.Append(kSmallGifData, sizeof(kSmallGifData)); |
| |
| vanilla_image.Complete(gif); |
| |
| EXPECT_TRUE(IsKnownAdScript(GetDocument().GetExecutionContext(), kAdUrl)); |
| EXPECT_TRUE(ad_tracker_->RequestWithUrlTaggedAsAd(kAdUrl)); |
| |
| // Image loading is async, so we won't catch this when async stacks aren't |
| // monitored. |
| EXPECT_FALSE(ad_tracker_->RequestWithUrlTaggedAsAd(kVanillaUrl)); |
| |
| // Walk through the DOM to get the image element. |
| Element* doc_element = GetDocument().documentElement(); |
| Element* body_element = Traversal<Element>::LastChild(*doc_element); |
| HTMLImageElement* image_element = |
| Traversal<HTMLImageElement>::FirstChild(*body_element); |
| |
| // When async stacks are not monitored, we do not tag the |
| // HTMLImageElement as ad-related. |
| ASSERT_TRUE(image_element); |
| EXPECT_FALSE(image_element->IsAdRelated()); |
| } |
| |
| // Image loaded by ad script is tagged as ad. |
| TEST_F(AdTrackerSimTest, DataURLImageLoadedWhileExecutingAdScriptAsyncEnabled) { |
| base::test::ScopedFeatureList feature_list; |
| feature_list.InitAndEnableFeature(features::kAsyncStackAdTagging); |
| |
| // Reset the AdTracker so that it gets the latest base::Feature value on |
| // construction. |
| ad_tracker_ = MakeGarbageCollected<TestAdTracker>(GetDocument().GetFrame()); |
| GetDocument().GetFrame()->SetAdTrackerForTesting(ad_tracker_); |
| |
| const char kAdUrl[] = "https://example.com/ad_script.js"; |
| SimSubresourceRequest ad_resource(kAdUrl, "text/javascript"); |
| |
| ad_tracker_->SetAdSuffix("ad_script.js"); |
| |
| main_resource_->Complete("<body></body><script src=ad_script.js></script>"); |
| |
| ad_resource.Complete(R"SCRIPT( |
| image = document.createElement("img"); |
| image.src = "data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs="; |
| document.body.appendChild(image); |
| )SCRIPT"); |
| |
| // Wait for script to run. |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_TRUE(IsKnownAdScript(GetDocument().GetExecutionContext(), kAdUrl)); |
| EXPECT_TRUE(ad_tracker_->RequestWithUrlTaggedAsAd(kAdUrl)); |
| |
| // Walk through the DOM to get the image element. |
| Element* doc_element = GetDocument().documentElement(); |
| Element* body_element = Traversal<Element>::LastChild(*doc_element); |
| HTMLImageElement* image_element = |
| Traversal<HTMLImageElement>::FirstChild(*body_element); |
| |
| // When async stacks are monitored, we should also tag the |
| // HTMLImageElement as ad-related. |
| ASSERT_TRUE(image_element); |
| EXPECT_TRUE(image_element->IsAdRelated()); |
| } |
| |
| // Frame loaded by ad script is considered created by ad script. |
| TEST_F(AdTrackerSimTest, FrameLoadedWhileExecutingAdScript) { |
| const char kAdUrl[] = "https://example.com/ad_script.js"; |
| const char kVanillaUrl[] = "https://example.com/vanilla_page.html"; |
| const char kVanillaImgUrl[] = "https://example.com/vanilla_img.jpg"; |
| SimSubresourceRequest ad_resource(kAdUrl, "text/javascript"); |
| SimRequest vanilla_page(kVanillaUrl, "text/html"); |
| SimSubresourceRequest vanilla_image(kVanillaImgUrl, "image/jpeg"); |
| |
| ad_tracker_->SetAdSuffix("ad_script.js"); |
| |
| main_resource_->Complete("<body></body><script src=ad_script.js></script>"); |
| |
| ad_resource.Complete(R"SCRIPT( |
| iframe = document.createElement("iframe"); |
| iframe.src = "vanilla_page.html"; |
| document.body.appendChild(iframe); |
| )SCRIPT"); |
| |
| // Wait for script to run. |
| base::RunLoop().RunUntilIdle(); |
| |
| auto* child_frame = |
| To<LocalFrame>(GetDocument().GetFrame()->Tree().FirstChild()); |
| |
| // Verify that the new frame is considered created by ad script then set it |
| // as an ad subframe. This emulates the SubresourceFilterAgent's tagging. |
| EXPECT_TRUE(child_frame->IsSubframeCreatedByAdScript()); |
| child_frame->SetIsAdSubframe(blink::mojom::AdFrameType::kRootAd); |
| |
| vanilla_page.Complete("<img src=vanilla_img.jpg></img>"); |
| vanilla_image.Complete(""); |
| |
| EXPECT_TRUE(IsKnownAdScript(GetDocument().GetExecutionContext(), kAdUrl)); |
| EXPECT_TRUE(ad_tracker_->RequestWithUrlTaggedAsAd(kAdUrl)); |
| EXPECT_TRUE(ad_tracker_->RequestWithUrlTaggedAsAd(kVanillaImgUrl)); |
| } |
| |
| // A script tagged as an ad in one frame shouldn't cause it to be considered |
| // an ad when executed in another frame. |
| TEST_F(AdTrackerSimTest, Contexts) { |
| // Load a page that loads library.js. It also creates an iframe that also |
| // loads library.js (where it gets tagged as an ad). Even though library.js |
| // gets tagged as an ad script in the subframe, that shouldn't cause it to |
| // be treated as an ad in the main frame. |
| SimRequest iframe_resource("https://example.com/iframe.html", "text/html"); |
| SimSubresourceRequest library_resource("https://example.com/library.js", |
| "text/javascript"); |
| |
| main_resource_->Complete(R"HTML( |
| <script src=library.js></script> |
| <iframe src=iframe.html></iframe> |
| )HTML"); |
| |
| // Complete the main frame's library.js. |
| library_resource.Complete(""); |
| |
| // The library script is loaded for a second time, this time in the |
| // subframe. Mark it as an ad. |
| SimSubresourceRequest library_resource_for_subframe( |
| "https://example.com/library.js", "text/javascript"); |
| ad_tracker_->SetAdSuffix("library.js"); |
| |
| iframe_resource.Complete(R"HTML( |
| <script src="library.js"></script> |
| )HTML"); |
| library_resource_for_subframe.Complete(""); |
| |
| // Verify that library.js is an ad script in the subframe's context but not |
| // in the main frame's context. |
| Frame* subframe = GetDocument().GetFrame()->Tree().FirstChild(); |
| auto* local_subframe = To<LocalFrame>(subframe); |
| EXPECT_TRUE( |
| IsKnownAdScript(local_subframe->GetDocument()->GetExecutionContext(), |
| String("https://example.com/library.js"))); |
| |
| EXPECT_FALSE(IsKnownAdScript(GetDocument().GetExecutionContext(), |
| String("https://example.com/library.js"))); |
| } |
| |
| TEST_F(AdTrackerSimTest, SameOriginSubframeFromAdScript) { |
| SimSubresourceRequest ad_resource("https://example.com/ad_script.js", |
| "text/javascript"); |
| SimRequest iframe_resource("https://example.com/iframe.html", "text/html"); |
| ad_tracker_->SetAdSuffix("ad_script.js"); |
| |
| main_resource_->Complete(R"HTML( |
| <body></body><script src=ad_script.js></script> |
| )HTML"); |
| ad_resource.Complete(R"SCRIPT( |
| var iframe = document.createElement("iframe"); |
| iframe.src = "iframe.html"; |
| document.body.appendChild(iframe); |
| )SCRIPT"); |
| |
| // Wait for script to run. |
| base::RunLoop().RunUntilIdle(); |
| |
| iframe_resource.Complete("iframe data"); |
| |
| auto* subframe = |
| To<LocalFrame>(GetDocument().GetFrame()->Tree().FirstChild()); |
| EXPECT_TRUE(subframe->IsSubframeCreatedByAdScript()); |
| } |
| |
| TEST_F(AdTrackerSimTest, SameOriginDocWrittenSubframeFromAdScript) { |
| SimSubresourceRequest ad_resource("https://example.com/ad_script.js", |
| "text/javascript"); |
| ad_tracker_->SetAdSuffix("ad_script.js"); |
| |
| main_resource_->Complete(R"HTML( |
| <body></body><script src=ad_script.js></script> |
| )HTML"); |
| ad_resource.Complete(R"SCRIPT( |
| var iframe = document.createElement("iframe"); |
| document.body.appendChild(iframe); |
| var iframeDocument = iframe.contentWindow.document; |
| iframeDocument.open(); |
| iframeDocument.write("iframe data"); |
| iframeDocument.close(); |
| )SCRIPT"); |
| |
| // Wait for script to run. |
| base::RunLoop().RunUntilIdle(); |
| |
| auto* subframe = |
| To<LocalFrame>(GetDocument().GetFrame()->Tree().FirstChild()); |
| EXPECT_TRUE(subframe->IsSubframeCreatedByAdScript()); |
| } |
| |
| // This test class allows easy running of tests that only differ by whether |
| // one resource (or a set of resources) is vanilla or an ad. |
| class AdTrackerVanillaOrAdSimTest : public AdTrackerSimTest, |
| public ::testing::WithParamInterface<bool> { |
| public: |
| bool IsAdRun() { return GetParam(); } |
| |
| String FlipURLOnAdRun(String vanilla_url) { |
| return IsAdRun() ? vanilla_url + "?ad=true" : vanilla_url; |
| } |
| }; |
| |
| TEST_P(AdTrackerVanillaOrAdSimTest, VanillaExternalStylesheetLoadsResources) { |
| String vanilla_stylesheet_url = "https://example.com/style.css"; |
| String font_url = FlipURLOnAdRun("https://example.com/font.woff2"); |
| String image_url = FlipURLOnAdRun("https://example.com/pixel.png"); |
| SimSubresourceRequest stylesheet(vanilla_stylesheet_url, "text/css"); |
| SimSubresourceRequest font(font_url, "font/woff2"); |
| SimSubresourceRequest image(image_url, "image/png"); |
| |
| ad_tracker_->SetAdSuffix("ad=true"); |
| |
| main_resource_->Complete(kPageWithVanillaExternalStylesheet); |
| stylesheet.Complete(IsAdRun() ? kStylesheetWithAdResources |
| : kStylesheetWithVanillaResources); |
| |
| // Wait for stylesheet to fetch resources. |
| ad_tracker_->WaitForSubresource(font_url); |
| ad_tracker_->WaitForSubresource(image_url); |
| |
| font.Complete(); |
| image.Complete(); |
| |
| EXPECT_FALSE(ad_tracker_->RequestWithUrlTaggedAsAd(vanilla_stylesheet_url)); |
| EXPECT_EQ(ad_tracker_->RequestWithUrlTaggedAsAd(font_url), IsAdRun()); |
| EXPECT_EQ(ad_tracker_->RequestWithUrlTaggedAsAd(image_url), IsAdRun()); |
| } |
| |
| TEST_P(AdTrackerVanillaOrAdSimTest, AdExternalStylesheetLoadsResources) { |
| String ad_stylesheet_url = "https://example.com/style.css?ad=true"; |
| String font_url = FlipURLOnAdRun("https://example.com/font.woff2"); |
| String image_url = FlipURLOnAdRun("https://example.com/pixel.png"); |
| SimSubresourceRequest stylesheet(ad_stylesheet_url, "text/css"); |
| SimSubresourceRequest font(font_url, "font/woff2"); |
| SimSubresourceRequest image(image_url, "image/png"); |
| |
| ad_tracker_->SetAdSuffix("ad=true"); |
| |
| main_resource_->Complete(kPageWithAdExternalStylesheet); |
| stylesheet.Complete(IsAdRun() ? kStylesheetWithAdResources |
| : kStylesheetWithVanillaResources); |
| |
| // Wait for stylesheet to fetch resources. |
| ad_tracker_->WaitForSubresource(font_url); |
| ad_tracker_->WaitForSubresource(image_url); |
| |
| font.Complete(); |
| image.Complete(); |
| |
| EXPECT_TRUE(ad_tracker_->RequestWithUrlTaggedAsAd(ad_stylesheet_url)); |
| EXPECT_TRUE(ad_tracker_->RequestWithUrlTaggedAsAd(font_url)); |
| EXPECT_TRUE(ad_tracker_->RequestWithUrlTaggedAsAd(image_url)); |
| } |
| |
| TEST_P(AdTrackerVanillaOrAdSimTest, LinkRelStylesheetAddedByScript) { |
| String script_url = FlipURLOnAdRun("https://example.com/script.js"); |
| String vanilla_stylesheet_url = "https://example.com/style.css"; |
| String vanilla_font_url = "https://example.com/font.woff2"; |
| String vanilla_image_url = "https://example.com/pixel.png"; |
| SimSubresourceRequest script(script_url, "text/javascript"); |
| SimSubresourceRequest stylesheet(vanilla_stylesheet_url, "text/css"); |
| SimSubresourceRequest font(vanilla_font_url, "font/woff2"); |
| SimSubresourceRequest image(vanilla_image_url, "image/png"); |
| |
| ad_tracker_->SetAdSuffix("ad=true"); |
| |
| main_resource_->Complete(IsAdRun() ? kPageWithAdScript |
| : kPageWithVanillaScript); |
| script.Complete(R"SCRIPT( |
| let link = document.createElement("link"); |
| link.rel = "stylesheet"; |
| link.href = "style.css"; |
| document.head.appendChild(link); |
| )SCRIPT"); |
| |
| // Wait for script to run. |
| ad_tracker_->WaitForSubresource(vanilla_stylesheet_url); |
| |
| stylesheet.Complete(kStylesheetWithVanillaResources); |
| |
| // Wait for stylesheet to fetch resources. |
| ad_tracker_->WaitForSubresource(vanilla_font_url); |
| ad_tracker_->WaitForSubresource(vanilla_image_url); |
| |
| font.Complete(); |
| image.Complete(); |
| |
| EXPECT_EQ(ad_tracker_->RequestWithUrlTaggedAsAd(script_url), IsAdRun()); |
| EXPECT_EQ(ad_tracker_->RequestWithUrlTaggedAsAd(vanilla_stylesheet_url), |
| IsAdRun()); |
| EXPECT_EQ(ad_tracker_->RequestWithUrlTaggedAsAd(vanilla_font_url), IsAdRun()); |
| EXPECT_EQ(ad_tracker_->RequestWithUrlTaggedAsAd(vanilla_image_url), |
| IsAdRun()); |
| } |
| |
| TEST_P(AdTrackerVanillaOrAdSimTest, ExternalStylesheetInFrame) { |
| String vanilla_stylesheet_url = "https://example.com/style.css"; |
| String vanilla_font_url = "https://example.com/font.woff2"; |
| String vanilla_image_url = "https://example.com/pixel.png"; |
| SimRequest frame("https://example.com/frame.html", "text/html"); |
| SimSubresourceRequest stylesheet(vanilla_stylesheet_url, "text/css"); |
| SimSubresourceRequest font(vanilla_font_url, "font/woff2"); |
| SimSubresourceRequest image(vanilla_image_url, "image/png"); |
| |
| ad_tracker_->SetAdSuffix("ad=true"); |
| |
| main_resource_->Complete(kPageWithFrame); |
| if (IsAdRun()) { |
| auto* subframe = |
| To<LocalFrame>(GetDocument().GetFrame()->Tree().FirstChild()); |
| subframe->SetIsAdSubframe(blink::mojom::AdFrameType::kRootAd); |
| } |
| |
| frame.Complete(kPageWithVanillaExternalStylesheet); |
| stylesheet.Complete(kStylesheetWithVanillaResources); |
| |
| // Wait for stylesheet to fetch resources. |
| ad_tracker_->WaitForSubresource(vanilla_font_url); |
| ad_tracker_->WaitForSubresource(vanilla_image_url); |
| |
| font.Complete(); |
| image.Complete(); |
| |
| EXPECT_EQ(ad_tracker_->RequestWithUrlTaggedAsAd(vanilla_stylesheet_url), |
| IsAdRun()); |
| EXPECT_EQ(ad_tracker_->RequestWithUrlTaggedAsAd(vanilla_font_url), IsAdRun()); |
| EXPECT_EQ(ad_tracker_->RequestWithUrlTaggedAsAd(vanilla_image_url), |
| IsAdRun()); |
| } |
| |
| // Note that we skip fonts as at rules aren't valid in inline CSS. |
| TEST_P(AdTrackerVanillaOrAdSimTest, InlineCSSSetByScript) { |
| String script_url = FlipURLOnAdRun("https://example.com/script.js"); |
| String vanilla_image_url = "https://example.com/pixel.png"; |
| SimSubresourceRequest script(script_url, "text/javascript"); |
| SimSubresourceRequest image(vanilla_image_url, "image/png"); |
| |
| ad_tracker_->SetAdSuffix("ad=true"); |
| |
| main_resource_->Complete(IsAdRun() ? kPageWithAdScript |
| : kPageWithVanillaScript); |
| script.Complete(R"SCRIPT( |
| let div = document.getElementsByClassName("test")[0]; |
| div.style = "background-image: url('pixel.png');"; |
| )SCRIPT"); |
| |
| ad_tracker_->WaitForSubresource(vanilla_image_url); |
| |
| image.Complete(); |
| |
| EXPECT_EQ(ad_tracker_->RequestWithUrlTaggedAsAd(script_url), IsAdRun()); |
| EXPECT_EQ(ad_tracker_->RequestWithUrlTaggedAsAd(vanilla_image_url), |
| IsAdRun()); |
| } |
| |
| TEST_F(AdTrackerSimTest, StyleTagInMainframe) { |
| String vanilla_font_url = "https://example.com/font.woff2"; |
| String vanilla_image_url = "https://example.com/pixel.png"; |
| SimSubresourceRequest font(vanilla_font_url, "font/woff2"); |
| SimSubresourceRequest image(vanilla_image_url, "image/png"); |
| |
| ad_tracker_->SetAdSuffix("ad=true"); |
| |
| main_resource_->Complete(kPageWithStyleTagLoadingVanillaResources); |
| |
| // Wait for stylesheet to fetch resources. |
| ad_tracker_->WaitForSubresource(vanilla_font_url); |
| ad_tracker_->WaitForSubresource(vanilla_image_url); |
| |
| font.Complete(); |
| image.Complete(); |
| |
| EXPECT_FALSE(ad_tracker_->RequestWithUrlTaggedAsAd(vanilla_font_url)); |
| EXPECT_FALSE(ad_tracker_->RequestWithUrlTaggedAsAd(vanilla_image_url)); |
| } |
| |
| // This verifies that style tag resources in ad frames are correctly tagged |
| // according to the heuristic that all requests from an ad frame should also be |
| // tagged as ads. |
| TEST_P(AdTrackerVanillaOrAdSimTest, StyleTagInSubframe) { |
| String vanilla_font_url = "https://example.com/font.woff2"; |
| String vanilla_image_url = "https://example.com/pixel.png"; |
| SimRequest frame("https://example.com/frame.html", "text/html"); |
| SimSubresourceRequest font(vanilla_font_url, "font/woff2"); |
| SimSubresourceRequest image(vanilla_image_url, "image/png"); |
| |
| ad_tracker_->SetAdSuffix("ad=true"); |
| |
| main_resource_->Complete(kPageWithFrame); |
| if (IsAdRun()) { |
| auto* subframe = |
| To<LocalFrame>(GetDocument().GetFrame()->Tree().FirstChild()); |
| subframe->SetIsAdSubframe(blink::mojom::AdFrameType::kRootAd); |
| } |
| |
| frame.Complete(kPageWithStyleTagLoadingVanillaResources); |
| |
| // Wait for stylesheet to fetch resources. |
| ad_tracker_->WaitForSubresource(vanilla_font_url); |
| ad_tracker_->WaitForSubresource(vanilla_image_url); |
| |
| font.Complete(); |
| image.Complete(); |
| |
| EXPECT_EQ(ad_tracker_->RequestWithUrlTaggedAsAd(vanilla_font_url), IsAdRun()); |
| EXPECT_EQ(ad_tracker_->RequestWithUrlTaggedAsAd(vanilla_image_url), |
| IsAdRun()); |
| } |
| |
| TEST_P(AdTrackerVanillaOrAdSimTest, StyleTagAddedByScript) { |
| String script_url = FlipURLOnAdRun("https://example.com/script.js"); |
| String vanilla_font_url = "https://example.com/font.woff2"; |
| String vanilla_image_url = "https://example.com/pixel.png"; |
| SimSubresourceRequest script(script_url, "text/javascript"); |
| SimSubresourceRequest font(vanilla_font_url, "font/woff2"); |
| SimSubresourceRequest image(vanilla_image_url, "image/png"); |
| |
| ad_tracker_->SetAdSuffix("ad=true"); |
| |
| main_resource_->Complete(IsAdRun() ? kPageWithAdScript |
| : kPageWithVanillaScript); |
| script.Complete(String::Format( |
| R"SCRIPT( |
| let style = document.createElement("style"); |
| let text = document.createTextNode(`%s`); |
| style.appendChild(text); |
| document.head.appendChild(style); |
| )SCRIPT", |
| kStylesheetWithVanillaResources)); |
| |
| // Wait for stylesheet to fetch resources. |
| ad_tracker_->WaitForSubresource(vanilla_font_url); |
| ad_tracker_->WaitForSubresource(vanilla_image_url); |
| |
| font.Complete(); |
| image.Complete(); |
| |
| EXPECT_EQ(ad_tracker_->RequestWithUrlTaggedAsAd(script_url), IsAdRun()); |
| EXPECT_EQ(ad_tracker_->RequestWithUrlTaggedAsAd(vanilla_font_url), IsAdRun()); |
| EXPECT_EQ(ad_tracker_->RequestWithUrlTaggedAsAd(vanilla_image_url), |
| IsAdRun()); |
| } |
| |
| TEST_P(AdTrackerVanillaOrAdSimTest, VanillaImportInStylesheet) { |
| String stylesheet_url = FlipURLOnAdRun("https://example.com/style.css"); |
| String vanilla_imported_stylesheet_url = "https://example.com/imported.css"; |
| String vanilla_font_url = "https://example.com/font.woff2"; |
| String vanilla_image_url = "https://example.com/pixel.png"; |
| SimSubresourceRequest stylesheet(stylesheet_url, "text/css"); |
| SimSubresourceRequest imported_stylesheet(vanilla_imported_stylesheet_url, |
| "text/css"); |
| SimSubresourceRequest font(vanilla_font_url, "font/woff2"); |
| SimSubresourceRequest image(vanilla_image_url, "image/png"); |
| |
| ad_tracker_->SetAdSuffix("ad=true"); |
| |
| main_resource_->Complete(IsAdRun() ? kPageWithAdExternalStylesheet |
| : kPageWithVanillaExternalStylesheet); |
| stylesheet.Complete(R"CSS( |
| @import url(imported.css); |
| )CSS"); |
| imported_stylesheet.Complete(kStylesheetWithVanillaResources); |
| |
| // Wait for stylesheets to fetch resources. |
| ad_tracker_->WaitForSubresource(vanilla_font_url); |
| ad_tracker_->WaitForSubresource(vanilla_image_url); |
| |
| font.Complete(); |
| image.Complete(); |
| |
| EXPECT_EQ(ad_tracker_->RequestWithUrlTaggedAsAd(stylesheet_url), IsAdRun()); |
| EXPECT_EQ( |
| ad_tracker_->RequestWithUrlTaggedAsAd(vanilla_imported_stylesheet_url), |
| IsAdRun()); |
| EXPECT_EQ(ad_tracker_->RequestWithUrlTaggedAsAd(vanilla_font_url), IsAdRun()); |
| EXPECT_EQ(ad_tracker_->RequestWithUrlTaggedAsAd(vanilla_image_url), |
| IsAdRun()); |
| } |
| |
| TEST_P(AdTrackerVanillaOrAdSimTest, AdImportInStylesheet) { |
| String stylesheet_url = FlipURLOnAdRun("https://example.com/style.css"); |
| String ad_imported_stylesheet_url = |
| "https://example.com/imported.css?ad=true"; |
| String vanilla_font_url = "https://example.com/font.woff2"; |
| String vanilla_image_url = "https://example.com/pixel.png"; |
| SimSubresourceRequest stylesheet(stylesheet_url, "text/css"); |
| SimSubresourceRequest imported_stylesheet(ad_imported_stylesheet_url, |
| "text/css"); |
| SimSubresourceRequest font(vanilla_font_url, "font/woff2"); |
| SimSubresourceRequest image(vanilla_image_url, "image/png"); |
| |
| ad_tracker_->SetAdSuffix("ad=true"); |
| |
| main_resource_->Complete(IsAdRun() ? kPageWithAdExternalStylesheet |
| : kPageWithVanillaExternalStylesheet); |
| stylesheet.Complete(R"CSS( |
| @import url(imported.css?ad=true); |
| )CSS"); |
| imported_stylesheet.Complete(kStylesheetWithVanillaResources); |
| |
| // Wait for stylesheets to fetch resources. |
| ad_tracker_->WaitForSubresource(vanilla_font_url); |
| ad_tracker_->WaitForSubresource(vanilla_image_url); |
| |
| font.Complete(); |
| image.Complete(); |
| |
| EXPECT_EQ(ad_tracker_->RequestWithUrlTaggedAsAd(stylesheet_url), IsAdRun()); |
| EXPECT_TRUE( |
| ad_tracker_->RequestWithUrlTaggedAsAd(ad_imported_stylesheet_url)); |
| EXPECT_TRUE(ad_tracker_->RequestWithUrlTaggedAsAd(vanilla_font_url)); |
| EXPECT_TRUE(ad_tracker_->RequestWithUrlTaggedAsAd(vanilla_image_url)); |
| } |
| |
| TEST_P(AdTrackerVanillaOrAdSimTest, ImageSetInStylesheet) { |
| String stylesheet_url = FlipURLOnAdRun("https://example.com/style.css"); |
| String vanilla_image_url = "https://example.com/pixel.png"; |
| SimSubresourceRequest stylesheet(stylesheet_url, "text/css"); |
| SimSubresourceRequest image(vanilla_image_url, "image/png"); |
| |
| ad_tracker_->SetAdSuffix("ad=true"); |
| |
| main_resource_->Complete(IsAdRun() ? kPageWithAdExternalStylesheet |
| : kPageWithVanillaExternalStylesheet); |
| |
| // The image with the lowest scale factor that is still larger than the |
| // device's scale factor is used. |
| stylesheet.Complete(R"CSS( |
| .test { |
| background-image: -webkit-image-set( url("pixel.png") 100x, |
| url("too_high.png") 999x); |
| } |
| )CSS"); |
| |
| // Wait for stylesheet to fetch resource. |
| ad_tracker_->WaitForSubresource(vanilla_image_url); |
| |
| image.Complete(); |
| |
| EXPECT_EQ(ad_tracker_->RequestWithUrlTaggedAsAd(stylesheet_url), IsAdRun()); |
| EXPECT_EQ(ad_tracker_->RequestWithUrlTaggedAsAd(vanilla_image_url), |
| IsAdRun()); |
| } |
| |
| TEST_P(AdTrackerVanillaOrAdSimTest, ConstructableCSSCreatedByScript) { |
| String script_url = FlipURLOnAdRun("https://example.com/script.js"); |
| String vanilla_font_url = "https://example.com/font.woff2"; |
| String vanilla_image_url = "https://example.com/pixel.png"; |
| SimSubresourceRequest script(script_url, "text/javascript"); |
| SimSubresourceRequest font(vanilla_font_url, "font/woff2"); |
| SimSubresourceRequest image(vanilla_image_url, "image/png"); |
| |
| ad_tracker_->SetAdSuffix("ad=true"); |
| |
| main_resource_->Complete(IsAdRun() ? kPageWithAdScript |
| : kPageWithVanillaScript); |
| script.Complete(R"SCRIPT( |
| const sheet = new CSSStyleSheet(); |
| sheet.insertRule(` |
| @font-face { |
| font-family: "Vanilla"; |
| src: url("font.woff2") format("woff2"); |
| }`); |
| sheet.insertRule(` |
| .test { |
| font-family: "Vanilla"; |
| background-image: url("pixel.png"); |
| }`); |
| document.adoptedStyleSheets = [sheet]; |
| )SCRIPT"); |
| |
| // Wait for stylesheet to fetch resources. |
| ad_tracker_->WaitForSubresource(vanilla_font_url); |
| ad_tracker_->WaitForSubresource(vanilla_image_url); |
| |
| font.Complete(); |
| image.Complete(); |
| |
| EXPECT_EQ(ad_tracker_->RequestWithUrlTaggedAsAd(script_url), IsAdRun()); |
| EXPECT_EQ(ad_tracker_->RequestWithUrlTaggedAsAd(vanilla_font_url), IsAdRun()); |
| EXPECT_EQ(ad_tracker_->RequestWithUrlTaggedAsAd(vanilla_image_url), |
| IsAdRun()); |
| } |
| |
| // Vanilla resources loaded due to an ad's script's style recalculation |
| // shouldn't be tagged. |
| TEST_F(AdTrackerSimTest, StyleRecalcCausedByAdScript) { |
| String ad_script_url = "https://example.com/script.js?ad=true"; |
| String vanilla_stylesheet_url = "https://example.com/style.css"; |
| String vanilla_font_url = "https://example.com/font.woff2"; |
| String vanilla_image_url = "https://example.com/pixel.png"; |
| SimSubresourceRequest script(ad_script_url, "text/javascript"); |
| SimSubresourceRequest stylesheet(vanilla_stylesheet_url, "text/css"); |
| SimSubresourceRequest font(vanilla_font_url, "font/woff2"); |
| SimSubresourceRequest image(vanilla_image_url, "image/png"); |
| |
| ad_tracker_->SetAdSuffix("ad=true"); |
| |
| main_resource_->Complete(R"HTML( |
| <head><link rel="stylesheet" href="style.css"> |
| <script async src="script.js?ad=true"></script></head> |
| <body><div>Test</div></body> |
| )HTML"); |
| stylesheet.Complete(kStylesheetWithVanillaResources); |
| |
| Compositor().BeginFrame(); |
| base::RunLoop().RunUntilIdle(); |
| // @font-face rules have fetches set up for src descriptors when the font face |
| // is initialized in FontFace::InitCSSFontFace(). The fetch is not actually |
| // performed, but the AdTracker is notified. |
| EXPECT_TRUE(ad_tracker_->UrlHasBeenRequested(vanilla_font_url)); |
| EXPECT_FALSE(ad_tracker_->UrlHasBeenRequested(vanilla_image_url)); |
| |
| // We override these to ensure the ad script appears on top of the stack when |
| // the requests are made. |
| ad_tracker_->SetExecutionContext(GetDocument().GetExecutionContext()); |
| ad_tracker_->SetScriptAtTopOfStack(ad_script_url); |
| |
| script.Complete(R"SCRIPT( |
| let div = document.getElementsByTagName("div")[0]; |
| div.className = "test"; |
| )SCRIPT"); |
| |
| // Wait for stylesheets to fetch resources. |
| ad_tracker_->WaitForSubresource(vanilla_font_url); |
| ad_tracker_->WaitForSubresource(vanilla_image_url); |
| |
| font.Complete(); |
| image.Complete(); |
| |
| EXPECT_TRUE(ad_tracker_->RequestWithUrlTaggedAsAd(ad_script_url)); |
| EXPECT_FALSE(ad_tracker_->RequestWithUrlTaggedAsAd(vanilla_stylesheet_url)); |
| EXPECT_FALSE(ad_tracker_->RequestWithUrlTaggedAsAd(vanilla_font_url)); |
| EXPECT_FALSE(ad_tracker_->RequestWithUrlTaggedAsAd(vanilla_image_url)); |
| } |
| |
| // A dynamically added script with no src is still tagged as an ad if created |
| // by an ad script. |
| TEST_F(AdTrackerSimTest, DynamicallyAddedScriptNoSrc_StillTagged) { |
| String ad_script_url = "https://example.com/script.js?ad=true"; |
| String vanilla_script_url = "https://example.com/script.js"; |
| String vanilla_image_url = "https://example.com/pixel.png"; |
| SimSubresourceRequest ad_script(ad_script_url, "text/javascript"); |
| SimSubresourceRequest vanilla_script(vanilla_script_url, "text/javascript"); |
| SimSubresourceRequest image(vanilla_image_url, "image/png"); |
| |
| ad_tracker_->SetAdSuffix("ad=true"); |
| |
| main_resource_->Complete(R"HTML( |
| <body><script src="script.js?ad=true"></script> |
| <script src="script.js"></script></body> |
| )HTML"); |
| |
| ad_script.Complete(R"SCRIPT( |
| let script = document.createElement("script"); |
| let text = document.createTextNode( |
| "function getImage() { fetch('pixel.png'); }"); |
| script.appendChild(text); |
| document.body.appendChild(script); |
| )SCRIPT"); |
| |
| // Fetch a resource using the function defined by dynamically added ad script. |
| vanilla_script.Complete(R"SCRIPT( |
| getImage(); |
| )SCRIPT"); |
| |
| ad_tracker_->WaitForSubresource(vanilla_image_url); |
| |
| EXPECT_TRUE(ad_tracker_->RequestWithUrlTaggedAsAd(ad_script_url)); |
| EXPECT_FALSE(ad_tracker_->RequestWithUrlTaggedAsAd(vanilla_script_url)); |
| EXPECT_TRUE(ad_tracker_->RequestWithUrlTaggedAsAd(vanilla_image_url)); |
| } |
| |
| // A dynamically added script with no src isn't tagged as an ad if not created |
| // by an ad script, even if it's later used by an ad script. |
| TEST_F(AdTrackerSimTest, |
| DynamicallyAddedScriptNoSrc_NotTaggedBasedOnUseByAdScript) { |
| String vanilla_script_url = "https://example.com/script.js"; |
| String ad_script_url = "https://example.com/script.js?ad=true"; |
| String vanilla_script2_url = "https://example.com/script2.js"; |
| String vanilla_image_url = "https://example.com/pixel.png"; |
| SimSubresourceRequest vanilla_script(vanilla_script_url, "text/javascript"); |
| SimSubresourceRequest ad_script(ad_script_url, "text/javascript"); |
| SimSubresourceRequest vanilla_script2(vanilla_script2_url, "text/javascript"); |
| SimSubresourceRequest image(vanilla_image_url, "image/png"); |
| |
| ad_tracker_->SetAdSuffix("ad=true"); |
| |
| main_resource_->Complete(R"HTML( |
| <body><script src="script.js"></script> |
| <script src="script.js?ad=true"></script> |
| <script src="script2.js"></script></body> |
| )HTML"); |
| |
| vanilla_script.Complete(R"SCRIPT( |
| let script = document.createElement("script"); |
| let text = document.createTextNode( |
| "function doNothing() {} " + |
| "function getImage() { fetch('pixel.png'); }"); |
| script.appendChild(text); |
| document.body.appendChild(script); |
| )SCRIPT"); |
| |
| ad_script.Complete(R"SCRIPT( |
| doNothing(); |
| )SCRIPT"); |
| |
| vanilla_script2.Complete(R"SCRIPT( |
| getImage(); |
| )SCRIPT"); |
| |
| ad_tracker_->WaitForSubresource(vanilla_image_url); |
| |
| EXPECT_FALSE(ad_tracker_->RequestWithUrlTaggedAsAd(vanilla_script_url)); |
| EXPECT_TRUE(ad_tracker_->RequestWithUrlTaggedAsAd(ad_script_url)); |
| EXPECT_FALSE(ad_tracker_->RequestWithUrlTaggedAsAd(vanilla_script2_url)); |
| EXPECT_FALSE(ad_tracker_->RequestWithUrlTaggedAsAd(vanilla_image_url)); |
| } |
| |
| TEST_F(AdTrackerSimTest, VanillaModuleScript_ResourceNotTagged) { |
| String vanilla_script_url = "https://example.com/script.js"; |
| String vanilla_image_url = "https://example.com/pixel.png"; |
| SimSubresourceRequest vanilla_script(vanilla_script_url, "text/javascript"); |
| SimSubresourceRequest image(vanilla_image_url, "image/png"); |
| |
| ad_tracker_->SetAdSuffix("ad=true"); |
| |
| main_resource_->Complete(R"HTML( |
| <head><script type="module" src="script.js"></script></head> |
| <body><div>Test</div></body> |
| )HTML"); |
| |
| vanilla_script.Complete(R"SCRIPT( |
| fetch('pixel.png'); |
| )SCRIPT"); |
| |
| ad_tracker_->WaitForSubresource(vanilla_image_url); |
| |
| EXPECT_FALSE(ad_tracker_->RequestWithUrlTaggedAsAd(vanilla_script_url)); |
| EXPECT_FALSE(ad_tracker_->RequestWithUrlTaggedAsAd(vanilla_image_url)); |
| } |
| |
| TEST_F(AdTrackerSimTest, AdModuleScript_ResourceTagged) { |
| String ad_script_url = "https://example.com/script.js?ad=true"; |
| String vanilla_image_url = "https://example.com/pixel.png"; |
| SimSubresourceRequest ad_script(ad_script_url, "text/javascript"); |
| SimSubresourceRequest image(vanilla_image_url, "image/png"); |
| |
| ad_tracker_->SetAdSuffix("ad=true"); |
| |
| main_resource_->Complete(R"HTML( |
| <head><script type="module" src="script.js?ad=true"></script></head> |
| <body><div>Test</div></body> |
| )HTML"); |
| |
| ad_script.Complete(R"SCRIPT( |
| fetch('pixel.png'); |
| )SCRIPT"); |
| |
| ad_tracker_->WaitForSubresource(vanilla_image_url); |
| |
| EXPECT_TRUE(ad_tracker_->RequestWithUrlTaggedAsAd(ad_script_url)); |
| EXPECT_TRUE(ad_tracker_->RequestWithUrlTaggedAsAd(vanilla_image_url)); |
| } |
| |
| // A resource fetched with ad script at top of stack is still tagged as an ad |
| // when the ad script defines a sourceURL. |
| TEST_F(AdTrackerSimTest, AdScriptWithSourceURLAtTopOfStack_StillTagged) { |
| String vanilla_script_url = "https://example.com/script.js"; |
| String ad_script_url = "https://example.com/script.js?ad=true"; |
| String vanilla_image_url = "https://example.com/pixel.png"; |
| SimSubresourceRequest vanilla_script(vanilla_script_url, "text/javascript"); |
| SimSubresourceRequest ad_script(ad_script_url, "text/javascript"); |
| SimSubresourceRequest image(vanilla_image_url, "image/png"); |
| |
| ad_tracker_->SetAdSuffix("ad=true"); |
| |
| main_resource_->Complete(R"HTML( |
| <head><script src="script.js?ad=true"></script> |
| <script src="script.js"></script></head> |
| <body><div>Test</div></body> |
| )HTML"); |
| |
| // We don't directly fetch in ad script as we aim to test ScriptAtTopOfStack() |
| // not WillExecuteScript(). |
| ad_script.Complete(R"SCRIPT( |
| function getImage() { fetch('pixel.png'); } |
| //# sourceURL=source.js |
| )SCRIPT"); |
| |
| vanilla_script.Complete(R"SCRIPT( |
| getImage(); |
| )SCRIPT"); |
| |
| ad_tracker_->WaitForSubresource(vanilla_image_url); |
| |
| EXPECT_TRUE(ad_tracker_->RequestWithUrlTaggedAsAd(ad_script_url)); |
| EXPECT_FALSE(ad_tracker_->RequestWithUrlTaggedAsAd(vanilla_script_url)); |
| EXPECT_TRUE(ad_tracker_->RequestWithUrlTaggedAsAd(vanilla_image_url)); |
| } |
| |
| // A dynamically added script with no src is still tagged as an ad if created |
| // by an ad script even if it defines a sourceURL. |
| TEST_F(AdTrackerSimTest, InlineAdScriptWithSourceURLAtTopOfStack_StillTagged) { |
| String ad_script_url = "https://example.com/script.js?ad=true"; |
| String vanilla_script_url = "https://example.com/script.js"; |
| String vanilla_image_url = "https://example.com/pixel.png"; |
| SimSubresourceRequest ad_script(ad_script_url, "text/javascript"); |
| SimSubresourceRequest vanilla_script(vanilla_script_url, "text/javascript"); |
| SimSubresourceRequest image(vanilla_image_url, "image/png"); |
| |
| ad_tracker_->SetAdSuffix("ad=true"); |
| |
| main_resource_->Complete(R"HTML( |
| <body><script src="script.js?ad=true"></script> |
| <script src="script.js"></script></body> |
| )HTML"); |
| |
| ad_script.Complete(R"SCRIPT( |
| let script = document.createElement("script"); |
| let text = document.createTextNode( |
| "function getImage() { fetch('pixel.png'); } \n" |
| + "//# sourceURL=source.js"); |
| script.appendChild(text); |
| document.body.appendChild(script); |
| )SCRIPT"); |
| |
| // Fetch a resource using the function defined by dynamically added ad script. |
| vanilla_script.Complete(R"SCRIPT( |
| getImage(); |
| )SCRIPT"); |
| |
| ad_tracker_->WaitForSubresource(vanilla_image_url); |
| |
| EXPECT_TRUE(ad_tracker_->RequestWithUrlTaggedAsAd(ad_script_url)); |
| EXPECT_FALSE(ad_tracker_->RequestWithUrlTaggedAsAd(vanilla_script_url)); |
| EXPECT_TRUE(ad_tracker_->RequestWithUrlTaggedAsAd(vanilla_image_url)); |
| } |
| |
| class AdTrackerDisabledSimTest : public SimTest, |
| private ScopedAdTaggingForTest { |
| protected: |
| AdTrackerDisabledSimTest() : ScopedAdTaggingForTest(false) {} |
| void SetUp() override { |
| SimTest::SetUp(); |
| main_resource_ = std::make_unique<SimRequest>( |
| "https://example.com/test.html", "text/html"); |
| |
| LoadURL("https://example.com/test.html"); |
| } |
| |
| std::unique_ptr<SimRequest> main_resource_; |
| }; |
| |
| TEST_F(AdTrackerDisabledSimTest, VerifyAdTrackingDisabled) { |
| main_resource_->Complete("<body></body>"); |
| EXPECT_FALSE(GetDocument().GetFrame()->GetAdTracker()); |
| EXPECT_FALSE(GetDocument().GetFrame()->IsAdSubframe()); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(All, |
| AdTrackerVanillaOrAdSimTest, |
| ::testing::Values(true, false)); |
| |
| } // namespace blink |