Commit fe0d4f2d authored by Cammie Smith Barnes's avatar Cammie Smith Barnes Committed by Commit Bot

Ad tagging: Add functionality to tag elements as ads.

When an HTMLImageElement is created by an ad script, if
async ad tagging is enabled, the HTMLImageElement will be
tagged as ad-related.  When the image finishes loading,
the HTMLImageLoader will check the ResourceRequest via
the ImageResourceContent and ImageResourceInfo to see
whether the resource was tagged as an ad resource.

Eventually we would like to expand the ad-tagging
functionality to some other types of elements, notably
HTMLDivElements.

Tests: blink_unittests --gtest_filter=*ImageLoaded*Async*

Change-Id: I5c6115f0b0be180a4647389096983378191cd0be
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2031086Reviewed-by: default avatarHiroshige Hayashizaki <hiroshige@chromium.org>
Reviewed-by: default avatarJosh Karlin <jkarlin@chromium.org>
Commit-Queue: Cammie Smith Barnes <cammie@chromium.org>
Cr-Commit-Position: refs/heads/master@{#750956}
parent 8919bfac
...@@ -936,6 +936,10 @@ class CORE_EXPORT Element : public ContainerNode, public Animatable { ...@@ -936,6 +936,10 @@ class CORE_EXPORT Element : public ContainerNode, public Animatable {
virtual void SetActive(bool active); virtual void SetActive(bool active);
virtual void SetHovered(bool hovered); virtual void SetHovered(bool hovered);
// Classes overriding this method can return true when an element has
// been determined to be from an ad. Returns false by default.
virtual bool IsAdRelated() const { return false; }
protected: protected:
const ElementData* GetElementData() const { return element_data_.Get(); } const ElementData* GetElementData() const { return element_data_.Get(); }
UniqueElementData& EnsureUniqueElementData(); UniqueElementData& EnsureUniqueElementData();
......
...@@ -10,7 +10,9 @@ ...@@ -10,7 +10,9 @@
#include "base/test/scoped_feature_list.h" #include "base/test/scoped_feature_list.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/features.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_frame.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/async_task_id.h"
#include "third_party/blink/renderer/core/probe/core_probes.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/dummy_page_holder.h"
...@@ -23,6 +25,11 @@ namespace blink { ...@@ -23,6 +25,11 @@ namespace blink {
namespace { 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};
class TestAdTracker : public AdTracker { class TestAdTracker : public AdTracker {
public: public:
explicit TestAdTracker(LocalFrame* frame) : AdTracker(frame) {} explicit TestAdTracker(LocalFrame* frame) : AdTracker(frame) {}
...@@ -474,9 +481,9 @@ TEST_F(AdTrackerSimTest, ImageLoadedWhileExecutingAdScriptAsyncEnabled) { ...@@ -474,9 +481,9 @@ TEST_F(AdTrackerSimTest, ImageLoadedWhileExecutingAdScriptAsyncEnabled) {
GetDocument().GetFrame()->SetAdTrackerForTesting(ad_tracker_); GetDocument().GetFrame()->SetAdTrackerForTesting(ad_tracker_);
const char kAdUrl[] = "https://example.com/ad_script.js"; const char kAdUrl[] = "https://example.com/ad_script.js";
const char kVanillaUrl[] = "https://example.com/vanilla_image.jpg"; const char kVanillaUrl[] = "https://example.com/vanilla_image.gif";
SimSubresourceRequest ad_resource(kAdUrl, "text/javascript"); SimSubresourceRequest ad_resource(kAdUrl, "text/javascript");
SimSubresourceRequest vanilla_image(kVanillaUrl, "image/jpeg"); SimSubresourceRequest vanilla_image(kVanillaUrl, "image/gif");
ad_tracker_->SetAdSuffix("ad_script.js"); ad_tracker_->SetAdSuffix("ad_script.js");
...@@ -484,14 +491,19 @@ TEST_F(AdTrackerSimTest, ImageLoadedWhileExecutingAdScriptAsyncEnabled) { ...@@ -484,14 +491,19 @@ TEST_F(AdTrackerSimTest, ImageLoadedWhileExecutingAdScriptAsyncEnabled) {
ad_resource.Complete(R"SCRIPT( ad_resource.Complete(R"SCRIPT(
image = document.createElement("img"); image = document.createElement("img");
image.src = "vanilla_image.jpg"; image.src = "vanilla_image.gif";
document.body.appendChild(image); document.body.appendChild(image);
)SCRIPT"); )SCRIPT");
// Wait for script to run. // Wait for script to run.
base::RunLoop().RunUntilIdle(); base::RunLoop().RunUntilIdle();
vanilla_image.Complete(""); // 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().ToExecutionContext(), kAdUrl)); EXPECT_TRUE(IsKnownAdScript(GetDocument().ToExecutionContext(), kAdUrl));
EXPECT_TRUE(ad_tracker_->RequestWithUrlTaggedAsAd(kAdUrl)); EXPECT_TRUE(ad_tracker_->RequestWithUrlTaggedAsAd(kAdUrl));
...@@ -499,6 +511,17 @@ TEST_F(AdTrackerSimTest, ImageLoadedWhileExecutingAdScriptAsyncEnabled) { ...@@ -499,6 +511,17 @@ TEST_F(AdTrackerSimTest, ImageLoadedWhileExecutingAdScriptAsyncEnabled) {
// Image loading is async, so we should catch this when async stacks are // Image loading is async, so we should catch this when async stacks are
// monitored. // monitored.
EXPECT_TRUE(ad_tracker_->RequestWithUrlTaggedAsAd(kVanillaUrl)); 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. // Image loaded by ad script is tagged as ad.
...@@ -512,9 +535,9 @@ TEST_F(AdTrackerSimTest, ImageLoadedWhileExecutingAdScriptAsyncDisabled) { ...@@ -512,9 +535,9 @@ TEST_F(AdTrackerSimTest, ImageLoadedWhileExecutingAdScriptAsyncDisabled) {
GetDocument().GetFrame()->SetAdTrackerForTesting(ad_tracker_); GetDocument().GetFrame()->SetAdTrackerForTesting(ad_tracker_);
const char kAdUrl[] = "https://example.com/ad_script.js"; const char kAdUrl[] = "https://example.com/ad_script.js";
const char kVanillaUrl[] = "https://example.com/vanilla_image.jpg"; const char kVanillaUrl[] = "https://example.com/vanilla_image.gif";
SimSubresourceRequest ad_resource(kAdUrl, "text/javascript"); SimSubresourceRequest ad_resource(kAdUrl, "text/javascript");
SimSubresourceRequest vanilla_image(kVanillaUrl, "image/jpeg"); SimSubresourceRequest vanilla_image(kVanillaUrl, "image/gif");
ad_tracker_->SetAdSuffix("ad_script.js"); ad_tracker_->SetAdSuffix("ad_script.js");
...@@ -522,14 +545,19 @@ TEST_F(AdTrackerSimTest, ImageLoadedWhileExecutingAdScriptAsyncDisabled) { ...@@ -522,14 +545,19 @@ TEST_F(AdTrackerSimTest, ImageLoadedWhileExecutingAdScriptAsyncDisabled) {
ad_resource.Complete(R"SCRIPT( ad_resource.Complete(R"SCRIPT(
image = document.createElement("img"); image = document.createElement("img");
image.src = "vanilla_image.jpg"; image.src = "vanilla_image.gif";
document.body.appendChild(image); document.body.appendChild(image);
)SCRIPT"); )SCRIPT");
// Wait for script to run. // Wait for script to run.
base::RunLoop().RunUntilIdle(); base::RunLoop().RunUntilIdle();
vanilla_image.Complete(""); // 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().ToExecutionContext(), kAdUrl)); EXPECT_TRUE(IsKnownAdScript(GetDocument().ToExecutionContext(), kAdUrl));
EXPECT_TRUE(ad_tracker_->RequestWithUrlTaggedAsAd(kAdUrl)); EXPECT_TRUE(ad_tracker_->RequestWithUrlTaggedAsAd(kAdUrl));
...@@ -537,6 +565,58 @@ TEST_F(AdTrackerSimTest, ImageLoadedWhileExecutingAdScriptAsyncDisabled) { ...@@ -537,6 +565,58 @@ TEST_F(AdTrackerSimTest, ImageLoadedWhileExecutingAdScriptAsyncDisabled) {
// Image loading is async, so we won't catch this when async stacks aren't // Image loading is async, so we won't catch this when async stacks aren't
// monitored. // monitored.
EXPECT_FALSE(ad_tracker_->RequestWithUrlTaggedAsAd(kVanillaUrl)); 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().ToExecutionContext(), 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 tagged as ad. // Frame loaded by ad script is tagged as ad.
......
...@@ -177,6 +177,10 @@ class CORE_EXPORT HTMLImageElement final ...@@ -177,6 +177,10 @@ class CORE_EXPORT HTMLImageElement final
return is_legacy_format_or_unoptimized_image_; return is_legacy_format_or_unoptimized_image_;
} }
// Keeps track of whether the image comes from an ad.
void SetIsAdRelated() { is_ad_related_ = true; }
bool IsAdRelated() const override { return is_ad_related_; }
protected: protected:
// Controls how an image element appears in the layout. See: // Controls how an image element appears in the layout. See:
// https://html.spec.whatwg.org/C/#image-request // https://html.spec.whatwg.org/C/#image-request
...@@ -257,6 +261,8 @@ class CORE_EXPORT HTMLImageElement final ...@@ -257,6 +261,8 @@ class CORE_EXPORT HTMLImageElement final
std::unique_ptr<LazyLoadImageObserver::VisibleLoadTimeMetrics> std::unique_ptr<LazyLoadImageObserver::VisibleLoadTimeMetrics>
visible_load_time_metrics_; visible_load_time_metrics_;
bool is_ad_related_ = false;
}; };
} // namespace blink } // namespace blink
......
...@@ -72,10 +72,13 @@ void HTMLImageLoader::ImageNotifyFinished(ImageResourceContent*) { ...@@ -72,10 +72,13 @@ void HTMLImageLoader::ImageNotifyFinished(ImageResourceContent*) {
bool load_error = cached_image->ErrorOccurred(); bool load_error = cached_image->ErrorOccurred();
if (auto* image = DynamicTo<HTMLImageElement>(*element)) { if (auto* image = DynamicTo<HTMLImageElement>(*element)) {
if (load_error) if (load_error) {
image->EnsureCollapsedOrFallbackContent(); image->EnsureCollapsedOrFallbackContent();
else } else {
if (cached_image->IsAdResource())
image->SetIsAdRelated();
image->EnsurePrimaryContent(); image->EnsurePrimaryContent();
}
} }
if (auto* input = DynamicTo<HTMLInputElement>(*element)) { if (auto* input = DynamicTo<HTMLInputElement>(*element)) {
......
...@@ -146,6 +146,10 @@ class ImageResource::ImageResourceInfoImpl final ...@@ -146,6 +146,10 @@ class ImageResource::ImageResourceInfoImpl final
} }
} }
bool IsAdResource() const override {
return resource_->GetResourceRequest().IsAdResource();
}
const Member<ImageResource> resource_; const Member<ImageResource> resource_;
}; };
......
...@@ -69,6 +69,8 @@ class NullImageResourceInfo final ...@@ -69,6 +69,8 @@ class NullImageResourceInfo final
void LoadDeferredImage(ResourceFetcher* fetcher) override {} void LoadDeferredImage(ResourceFetcher* fetcher) override {}
bool IsAdResource() const override { return false; }
const KURL url_; const KURL url_;
const ResourceResponse response_; const ResourceResponse response_;
}; };
...@@ -685,4 +687,8 @@ void ImageResourceContent::LoadDeferredImage(ResourceFetcher* fetcher) { ...@@ -685,4 +687,8 @@ void ImageResourceContent::LoadDeferredImage(ResourceFetcher* fetcher) {
info_->LoadDeferredImage(fetcher); info_->LoadDeferredImage(fetcher);
} }
bool ImageResourceContent::IsAdResource() const {
return info_->IsAdResource();
}
} // namespace blink } // namespace blink
...@@ -186,6 +186,9 @@ class CORE_EXPORT ImageResourceContent final ...@@ -186,6 +186,9 @@ class CORE_EXPORT ImageResourceContent final
void LoadDeferredImage(ResourceFetcher* fetcher); void LoadDeferredImage(ResourceFetcher* fetcher);
// Returns whether the resource request has been tagged as an ad.
bool IsAdResource() const;
private: private:
using CanDeferInvalidation = ImageResourceObserver::CanDeferInvalidation; using CanDeferInvalidation = ImageResourceObserver::CanDeferInvalidation;
......
...@@ -60,6 +60,8 @@ class CORE_EXPORT ImageResourceInfo : public GarbageCollectedMixin { ...@@ -60,6 +60,8 @@ class CORE_EXPORT ImageResourceInfo : public GarbageCollectedMixin {
virtual void LoadDeferredImage(ResourceFetcher* fetcher) = 0; virtual void LoadDeferredImage(ResourceFetcher* fetcher) = 0;
virtual bool IsAdResource() const = 0;
void Trace(Visitor* visitor) override {} void Trace(Visitor* visitor) override {}
}; };
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment