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 {
virtual void SetActive(bool active);
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:
const ElementData* GetElementData() const { return element_data_.Get(); }
UniqueElementData& EnsureUniqueElementData();
......
......@@ -10,7 +10,9 @@
#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_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"
......@@ -23,6 +25,11 @@ 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};
class TestAdTracker : public AdTracker {
public:
explicit TestAdTracker(LocalFrame* frame) : AdTracker(frame) {}
......@@ -474,9 +481,9 @@ TEST_F(AdTrackerSimTest, ImageLoadedWhileExecutingAdScriptAsyncEnabled) {
GetDocument().GetFrame()->SetAdTrackerForTesting(ad_tracker_);
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 vanilla_image(kVanillaUrl, "image/jpeg");
SimSubresourceRequest vanilla_image(kVanillaUrl, "image/gif");
ad_tracker_->SetAdSuffix("ad_script.js");
......@@ -484,14 +491,19 @@ TEST_F(AdTrackerSimTest, ImageLoadedWhileExecutingAdScriptAsyncEnabled) {
ad_resource.Complete(R"SCRIPT(
image = document.createElement("img");
image.src = "vanilla_image.jpg";
image.src = "vanilla_image.gif";
document.body.appendChild(image);
)SCRIPT");
// Wait for script to run.
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(ad_tracker_->RequestWithUrlTaggedAsAd(kAdUrl));
......@@ -499,6 +511,17 @@ TEST_F(AdTrackerSimTest, ImageLoadedWhileExecutingAdScriptAsyncEnabled) {
// 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.
......@@ -512,9 +535,9 @@ TEST_F(AdTrackerSimTest, ImageLoadedWhileExecutingAdScriptAsyncDisabled) {
GetDocument().GetFrame()->SetAdTrackerForTesting(ad_tracker_);
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 vanilla_image(kVanillaUrl, "image/jpeg");
SimSubresourceRequest vanilla_image(kVanillaUrl, "image/gif");
ad_tracker_->SetAdSuffix("ad_script.js");
......@@ -522,14 +545,19 @@ TEST_F(AdTrackerSimTest, ImageLoadedWhileExecutingAdScriptAsyncDisabled) {
ad_resource.Complete(R"SCRIPT(
image = document.createElement("img");
image.src = "vanilla_image.jpg";
image.src = "vanilla_image.gif";
document.body.appendChild(image);
)SCRIPT");
// Wait for script to run.
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(ad_tracker_->RequestWithUrlTaggedAsAd(kAdUrl));
......@@ -537,6 +565,58 @@ TEST_F(AdTrackerSimTest, ImageLoadedWhileExecutingAdScriptAsyncDisabled) {
// 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().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.
......
......@@ -177,6 +177,10 @@ class CORE_EXPORT HTMLImageElement final
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:
// Controls how an image element appears in the layout. See:
// https://html.spec.whatwg.org/C/#image-request
......@@ -257,6 +261,8 @@ class CORE_EXPORT HTMLImageElement final
std::unique_ptr<LazyLoadImageObserver::VisibleLoadTimeMetrics>
visible_load_time_metrics_;
bool is_ad_related_ = false;
};
} // namespace blink
......
......@@ -72,10 +72,13 @@ void HTMLImageLoader::ImageNotifyFinished(ImageResourceContent*) {
bool load_error = cached_image->ErrorOccurred();
if (auto* image = DynamicTo<HTMLImageElement>(*element)) {
if (load_error)
if (load_error) {
image->EnsureCollapsedOrFallbackContent();
else
} else {
if (cached_image->IsAdResource())
image->SetIsAdRelated();
image->EnsurePrimaryContent();
}
}
if (auto* input = DynamicTo<HTMLInputElement>(*element)) {
......
......@@ -146,6 +146,10 @@ class ImageResource::ImageResourceInfoImpl final
}
}
bool IsAdResource() const override {
return resource_->GetResourceRequest().IsAdResource();
}
const Member<ImageResource> resource_;
};
......
......@@ -69,6 +69,8 @@ class NullImageResourceInfo final
void LoadDeferredImage(ResourceFetcher* fetcher) override {}
bool IsAdResource() const override { return false; }
const KURL url_;
const ResourceResponse response_;
};
......@@ -685,4 +687,8 @@ void ImageResourceContent::LoadDeferredImage(ResourceFetcher* fetcher) {
info_->LoadDeferredImage(fetcher);
}
bool ImageResourceContent::IsAdResource() const {
return info_->IsAdResource();
}
} // namespace blink
......@@ -186,6 +186,9 @@ class CORE_EXPORT ImageResourceContent final
void LoadDeferredImage(ResourceFetcher* fetcher);
// Returns whether the resource request has been tagged as an ad.
bool IsAdResource() const;
private:
using CanDeferInvalidation = ImageResourceObserver::CanDeferInvalidation;
......
......@@ -60,6 +60,8 @@ class CORE_EXPORT ImageResourceInfo : public GarbageCollectedMixin {
virtual void LoadDeferredImage(ResourceFetcher* fetcher) = 0;
virtual bool IsAdResource() const = 0;
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