Chrome · PerformanceAPIs › PaintTiming
CVE-2026-113091
UAF in PerformanceAPIs (PaintTiming)
Overview
Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
ForEachPaintTimingClientthird_party/blink/renderer/core/paint/timing/image_paint_timing_detector.cc |
modified | |
ifthird_party/blink/renderer/core/paint/timing/paint_timing_utils.cc |
modified | |
Documentthird_party/blink/renderer/core/paint/timing/paint_timing_utils.h |
modified | |
LocalDOMWindowthird_party/blink/renderer/core/paint/timing/paint_timing_utils.h |
modified | |
LocalFrameViewthird_party/blink/renderer/core/paint/timing/paint_timing_utils.h |
modified | |
MediaTimingthird_party/blink/renderer/core/paint/timing/paint_timing_utils.h |
modified | |
TEST_Pthird_party/blink/renderer/core/paint/timing/image_element_timing_test.cc |
modified | |
TEST_Pthird_party/blink/renderer/core/paint/timing/image_paint_timing_detector_test.cc |
modified |
Files Changed
third_party/blink/web_tests/media/video-default-poster-paint-timing.htmlthird_party/blink/renderer/core/paint/timing/paint_timing_utils.hthird_party/blink/renderer/core/paint/timing/image_element_timing.ccthird_party/blink/renderer/core/paint/timing/paint_timing_test_base.hthird_party/blink/renderer/core/paint/timing/image_paint_timing_detector_test.ccthird_party/blink/renderer/core/paint/timing/paint_timing_utils.ccthird_party/blink/renderer/core/paint/timing/image_element_timing_test.ccthird_party/blink/renderer/core/paint/timing/image_paint_timing_detector.cc
Patch
From c89f1510200fbae83fe42fef454de7c50991214e Mon Sep 17 00:00:00 2001
From: Scott Haseley <shaseley@chromium.org>
Date: Wed, 23 Sep 2026 18:14:54 -0700
Subject: [PATCH] PaintTiming: Ignore default poster images
Default poster images on WebView are not good LCP candidates since they
come from internal resources, and their URLs can be privacy-sensitive.
This CL filters out these out from Paint Timing so they are not
considered by LCP, ICP, or Element Timing.
Fixed: 497433155
Change-Id: I52f633827abbe8b269f1b074ebe6d5a1819d62f8
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8422585
Reviewed-by: Michal Mocny <mmocny@chromium.org>
Commit-Queue: Scott Haseley <shaseley@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1703980}
---
diff --git a/third_party/blink/renderer/core/paint/timing/image_element_timing.cc b/third_party/blink/renderer/core/paint/timing/image_element_timing.cc
index 73efdf1..fd81140 100644
--- a/third_party/blink/renderer/core/paint/timing/image_element_timing.cc
+++ b/third_party/blink/renderer/core/paint/timing/image_element_timing.cc
@@ -22,6 +22,7 @@
#include "third_party/blink/renderer/core/paint/timing/image_paint_timing_detector.h"
#include "third_party/blink/renderer/core/paint/timing/paint_timing.h"
#include "third_party/blink/renderer/core/paint/timing/paint_timing_detector.h"
+#include "third_party/blink/renderer/core/paint/timing/paint_timing_utils.h"
#include "third_party/blink/renderer/core/style/style_fetched_image.h"
#include "third_party/blink/renderer/core/svg/svg_image_element.h"
#include "third_party/blink/renderer/core/timing/dom_window_performance.h"
@@ -61,7 +62,6 @@
}
} // namespace internal
-
AtomicString ImagePaintString() {
DEFINE_STATIC_LOCAL(const AtomicString, kImagePaint, ("image-paint"));
return kImagePaint;
@@ -82,6 +82,10 @@
const MediaTiming& media_timing,
const PropertyTreeStateOrAlias& current_paint_chunk_properties,
const gfx::Rect& image_border) {
+ if (paint_timing::ShouldIgnoreImageContentForPaintTiming(layout_object,
+ &media_timing)) {
+ return;
+ }
Node* node = layout_object.GetNode();
bool is_image_or_video_element = IsA<HTMLImageElement>(node) ||
IsA<HTMLVideoElement>(node) ||
@@ -258,6 +262,10 @@
void ImageElementTiming::NotifyImageRemoved(const LayoutObject& layout_object,
const ImageResourceContent* image) {
+ if (paint_timing::ShouldIgnoreImageContentForPaintTiming(layout_object,
+ image)) {
+ return;
+ }
recorded_images_.erase(MediaRecordId::GenerateHash(&layout_object, image));
}
diff --git a/third_party/blink/renderer/core/paint/timing/image_paint_timing_detector.cc b/third_party/blink/renderer/core/paint/timing/image_paint_timing_detector.cc
index 64f506cf..a9794531 100644
--- a/third_party/blink/renderer/core/paint/timing/image_paint_timing_detector.cc
+++ b/third_party/blink/renderer/core/paint/timing/image_paint_timing_detector.cc
@@ -97,6 +97,10 @@
void ImagePaintTimingDetector::NotifyImageRemoved(
const LayoutObject& object,
const MediaTiming* media_timing) {
+ if (paint_timing::ShouldIgnoreImageContentForPaintTiming(object,
+ media_timing)) {
+ return;
+ }
RemoveRecord(MediaRecordId::GenerateHash(&object, media_timing));
ForEachPaintTimingClient([&](PaintTimingClient* client) {
client->OnImageRemoved(object, media_timing);
@@ -129,6 +133,11 @@
return;
}
+ if (paint_timing::ShouldIgnoreImageContentForPaintTiming(object,
+ &media_timing)) {
+ return;
+ }
+
// Before the image resource starts loading, <img> has no size info. We wait
// until the size is known.
if (image_border.IsEmpty()) {
@@ -295,6 +304,10 @@
void ImagePaintTimingDetector::NotifyImageFinished(
const LayoutObject& object,
const MediaTiming* media_timing) {
+ if (paint_timing::ShouldIgnoreImageContentForPaintTiming(object,
+ media_timing)) {
+ return;
+ }
auto hash(MediaRecordId::GenerateHash(&object, media_timing));
const auto& insertion_result =
image_finished_times_.insert(hash, base::TimeTicks());
diff --git a/third_party/blink/renderer/core/paint/timing/paint_timing_test_base.h b/third_party/blink/renderer/core/paint/timing/paint_timing_test_base.h
index a518640..513ff1d 100644
--- a/third_party/blink/renderer/core/paint/timing/paint_timing_test_base.h
+++ b/third_party/blink/renderer/core/paint/timing/paint_timing_test_base.h
@@ -25,6 +25,23 @@
namespace blink {
+#define SIMPLE_IMAGE \
+ "data:image/gif;base64," \
+ "R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="
+
+#define LARGE_IMAGE \
+ "data:image/gif;base64," \
+ "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSF" \
+ "lzAAAN1wAADdcBQiibeAAAAb5JREFUOMulkr1KA0EQgGdvTwwnYmER0gQsrFKmSy+pLESw9Qm0" \
+ "F/ICNnba+h6iEOuAEWslKJKTOyJJvIT72d1xZuOFC0giOLA77O7Mt/PnNptN+I+49Xr9GhH3f3" \
+ "mb0v1ht9vtLAUYYw5ItkgDL3KyD8PhcLvdbl/WarXT3DjLMnAcR/f7/YfxeKwtgC5RKQVhGILW" \
+ "eg4hQ6hUKjWyucmhLFEUuWR3QYBWAZABQ9i5CCmXy16pVALP80BKaaG+70MQBLvzFMjRKKXh8j" \
+ "6FSYKF7ITdEWLa4/ktokN74wiqjSMpnVcbQZqmEJHz+ckeCPFjWKwULpyspAqhdXVXdcnZcPjs" \
+ "Ign+2BsVA8jVYuWlgJ3yBj0icgq2uoK+lg4t+ZvLomSKamSQ4AI5BcMADtMhyNoSgNIISUaFNt" \
+ "wlazcDcBc4gjjVwCWid2usCWroYEhnaqbzFJLUzAHIXRDChXCcQP8zhkSZ5eNLgHAUzwDcRu4C" \
+ "oIRn/wsGUQIIy4Vr9TH6SYFCNzw4nALn5627K4vIttOUOwfa5YnrDYzt/9OLv9I5l8kk5hZ3XL" \
+ "O20b7tbR7zHLy/BX8G0IeBEM7ZN1NGIaFUaKLgAAAAAElFTkSuQmCC"
+
// Mock platform support to provide localized string resources (such as button
// labels) during tests so shadow DOM form controls (e.g. <input type="file">)
// are rendered with non-zero dimensions and recorded by paint timing detectors.
diff --git a/third_party/blink/renderer/core/paint/timing/paint_timing_utils.cc b/third_party/blink/renderer/core/paint/timing/paint_timing_utils.cc
index 598d223..ca9e268 100644
--- a/third_party/blink/renderer/core/paint/timing/paint_timing_utils.cc
+++ b/third_party/blink/renderer/core/paint/timing/paint_timing_utils.cc
@@ -4,16 +4,25 @@
#include "third_party/blink/renderer/core/paint/timing/paint_timing_utils.h"
+#include "base/feature_list.h"
#include "cc/trees/layer_tree_host.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/frame/local_dom_window.h"
#include "third_party/blink/renderer/core/frame/local_frame_view.h"
+#include "third_party/blink/renderer/core/frame/settings.h"
+#include "third_party/blink/renderer/core/html/media/html_video_element.h"
+#include "third_party/blink/renderer/core/html/parser/html_parser_idioms.h"
#include "third_party/blink/renderer/core/loader/document_loader.h"
+#include "third_party/blink/renderer/platform/loader/fetch/media_timing.h"
+#include "third_party/blink/renderer/platform/weborigin/kurl.h"
namespace blink::paint_timing {
namespace {
+BASE_FEATURE(kIgnoreDefaultVideoPosterForPaintTiming,
+ base::FEATURE_ENABLED_BY_DEFAULT);
+
cc::HeadsUpDisplayLayer* GetHUDLayer(LocalFrameView* frame_view) {
if (!frame_view) {
return nullptr;
@@ -53,6 +62,28 @@
return nullptr;
}
+bool ShouldIgnoreImageContentForPaintTiming(const LayoutObject& object,
+ const MediaTiming* media_timing) {
+ if (!base::FeatureList::IsEnabled(kIgnoreDefaultVideoPosterForPaintTiming)) {
+ return false;
+ }
+ if (!media_timing || media_timing->IsVideo() ||
+ !IsA<HTMLVideoElement>(object.GetNode())) {
+ return false;
+ }
+ const Settings* settings = object.GetDocument().GetSettings();
+ if (!settings) {
+ return false;
+ }
+ StringView default_poster =
+ StripLeadingAndTrailingHtmlSpaces(settings->GetDefaultVideoPosterURL());
+ if (default_poster.empty()) {
+ return false;
+ }
+ return object.GetDocument().CompleteURL(default_poster) ==
+ media_timing->Url();
+}
+
void NotifyLoaderPerformanceTimingChanged(LocalDOMWindow* window) {
if (!window) {
return;
diff --git a/third_party/blink/renderer/core/paint/timing/paint_timing_utils.h b/third_party/blink/renderer/core/paint/timing/paint_timing_utils.h
index a48da10..802458fb3 100644
--- a/third_party/blink/renderer/core/paint/timing/paint_timing_utils.h
+++ b/third_party/blink/renderer/core/paint/timing/paint_timing_utils.h
@@ -17,6 +17,7 @@
class Document;
class LocalDOMWindow;
class LocalFrameView;
+class MediaTiming;
} // namespace blink
namespace blink::paint_timing {
@@ -39,6 +40,12 @@
CORE_EXPORT cc::HeadsUpDisplayLayer* GetHUDLayerIfLayoutShiftRectsEnabled(
LocalFrameView* frame_view);
+// Returns whether or not the `media_timing` should be ignored. Currently only
+// `MediaTiming` objects representing the default poster image are ignored.
+CORE_EXPORT bool ShouldIgnoreImageContentForPaintTiming(
+ const LayoutObject& object,
+ const MediaTiming* media_timing);
+
// Notifies the document loader that performance timing has changed in some way
// if the window, document, and loader are non-null. Causes the current
// performance timing values to be sent to UKM.
diff --git a/third_party/blink/renderer/core/paint/timing/image_element_timing_test.cc b/third_party/blink/renderer/core/paint/timing/image_element_timing_test.cc
index e5ff2aa..5dc4a4af 100644
--- a/third_party/blink/renderer/core/paint/timing/image_element_timing_test.cc
+++ b/third_party/blink/renderer/core/paint/timing/image_element_timing_test.cc
@@ -6,6 +6,7 @@
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/renderer/core/frame/settings.h"
#include "third_party/blink/renderer/core/layout/layout_image.h"
#include "third_party/blink/renderer/core/layout/svg/layout_svg_image.h"
#include "third_party/blink/renderer/core/loader/resource/image_resource_content.h"
@@ -15,6 +16,7 @@
#include "third_party/blink/renderer/core/timing/performance.h"
#include "third_party/blink/renderer/core/timing/performance_element_timing.h"
#include "third_party/blink/renderer/platform/testing/paint_test_configurations.h"
+#include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
using testing::ElementsAre;
using testing::IsEmpty;
@@ -198,7 +200,7 @@
#target {
width: 100px;
height: 100px;
- background: url(data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==);
+ background: url()HTML" SIMPLE_IMAGE R"HTML();
}
</style>
<div elementtiming="time-my-background-image" id="target"></div>
@@ -261,4 +263,48 @@
EXPECT_THAT(GetElementTimingEntries(), IsEmpty());
}
+TEST_P(ImageElementTimingTest, VideoImage_DefaultPosterIgnored) {
+ GetDocument().GetSettings()->SetDefaultVideoPosterURL(
+ AtomicString(SIMPLE_IMAGE));
+ SetBodyInnerHTML(R"HTML(
+ <video id="target" elementtiming="video-et" width="100" height="100"></video>
+ )HTML");
+ test::RunPendingTasks();
+ SimulateRenderingAndPresentationTime();
+ EXPECT_EQ(RecordedImagesSize(), 0u);
+ EXPECT_THAT(GetElementTimingEntries(), IsEmpty());
+}
+
+TEST_P(ImageElementTimingTest,
+ VideoImage_ExplicitPosterRecordedWhenDefaultSet) {
+ GetDocument().GetSettings()->SetDefaultVideoPosterURL(
+ AtomicString(SIMPLE_IMAGE));
+ SetBodyInnerHTML(R"HTML(
+ <video id="target" elementtiming="video-et"
+ width="100" height="100"></video>
+ )HTML");
+ test::RunPendingTasks();
+ SimulateRenderingAndPresentationTime();
+ EXPECT_EQ(RecordedImagesSize(), 0u);
+ EXPECT_THAT(GetElementTimingEntries(), IsEmpty());
+
+ // Changing the poster to an explicit image after the default poster loaded
+ // should record the new explicit poster.
+ Element* video_element = GetElementById("target");
+ ASSERT_TRUE(video_element);
+ video_element->setAttribute(html_names::kPosterAttr,
+ AtomicString(LARGE_IMAGE));
+ test::RunPendingTasks();
+ SimulateRenderingAndPresentationTime();
+ EXPECT_EQ(RecordedImagesSize(), 1u);
+ EXPECT_THAT(GetElementTimingEntries(), ElementsAre(ForId("target")));
+
+ // Removing the explicit poster attribute reverts to the default poster,
+ // which should remove the explicit poster record and not record the default.
+ video_element->removeAttribute(html_names::kPosterAttr);
+ test::RunPendingTasks();
+ SimulateRenderingAndPresentationTime();
+ EXPECT_EQ(RecordedImagesSize(), 0u);
+}
+
} // namespace blink
diff --git a/third_party/blink/renderer/core/paint/timing/image_paint_timing_detector_test.cc b/third_party/blink/renderer/core/paint/timing/image_paint_timing_detector_test.cc
index 2c0382f..af0fbc0 100644
--- a/third_party/blink/renderer/core/paint/timing/image_paint_timing_detector_test.cc
+++ b/third_party/blink/renderer/core/paint/timing/image_paint_timing_detector_test.cc
@@ -46,23 +46,6 @@
namespace blink {
-#define SIMPLE_IMAGE \
- "data:image/gif;base64," \
- "R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="
-
-#define LARGE_IMAGE \
- "data:image/gif;base64," \
- "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSF" \
- "lzAAAN1wAADdcBQiibeAAAAb5JREFUOMulkr1KA0EQgGdvTwwnYmER0gQsrFKmSy+pLESw9Qm0" \
- "F/ICNnba+h6iEOuAEWslKJKTOyJJvIT72d1xZuOFC0giOLA77O7Mt/PnNptN+I+49Xr9GhH3f3" \
- "mb0v1ht9vtLAUYYw5ItkgDL3KyD8PhcLvdbl/WarXT3DjLMnAcR/f7/YfxeKwtgC5RKQVhGILW" \
- "eg4hQ6hUKjWyucmhLFEUuWR3QYBWAZABQ9i5CCmXy16pVALP80BKaaG+70MQBLvzFMjRKKXh8j" \
- "6FSYKF7ITdEWLa4/ktokN74wiqjSMpnVcbQZqmEJHz+ckeCPFjWKwULpyspAqhdXVXdcnZcPjs" \
- "Ign+2BsVA8jVYuWlgJ3yBj0icgq2uoK+lg4t+ZvLomSKamSQ4AI5BcMADtMhyNoSgNIISUaFNt" \
- "wlazcDcBc4gjjVwCWid2usCWroYEhnaqbzFJLUzAHIXRDChXCcQP8zhkSZ5eNLgHAUzwDcRu4C" \
- "oIRn/wsGUQIIy4Vr9TH6SYFCNzw4nALn5627K4vIttOUOwfa5YnrDYzt/9OLv9I5l8kk5hZ3XL" \
- "O20b7tbR7zHLy/BX8G0IeBEM7ZN1NGIaFUaKLgAAAAAElFTkSuQmCC"
-
#define TRANSPARENT_PLACEHOLDER_IMAGE \
"data:image/gif;base64," \
"R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="
@@ -825,6 +808,69 @@
EXPECT_FALSE(record);
}
+TEST_P(ImagePaintTimingDetectorTest, VideoImage_DefaultPosterIgnored) {
+ GetDocument().GetSettings()->SetDefaultVideoPosterURL(
+ AtomicString(LARGE_IMAGE));
+ SetMainFrameBodyContent(R"HTML(
+ <video id="target" width="300" height="200"></video>
+ )HTML");
+ test::RunPendingTasks();
+ SimulateRenderingAndPresentationTime();
+ EXPECT_FALSE(LargestImage());
+ EXPECT_EQ(CountImageRecords(), 0u);
+
+ // Verify that a subsequent first video frame is not blocked by the ignored
+ // default poster image.
+ Element* video_element = GetDocument().getElementById(AtomicString("target"));
+ ASSERT_TRUE(video_element);
+ VideoTiming* video_timing = MakeGarbageCollected<VideoTiming>();
+ video_timing->SetFirstVideoFrameTime(NowTicks());
+ video_timing->SetIsSufficientContentLoadedForPaint();
+ video_timing->SetUrl(KURL("http://test.com/video.mp4"));
+ video_timing->SetContentSizeForEntropy(1024 * 1024);
+
+ SimulateFirstVideoFrame(video_element, video_timing, 300, 200);
+ SimulateRenderingAndPresentationTime();
+ ImageRecord* record = LargestImage();
+ ASSERT_TRUE(record);
+ EXPECT_EQ(record->GetMediaTiming(), video_timing);
+ EXPECT_TRUE(record->HasPaintTime());
+}
+
+TEST_P(ImagePaintTimingDetectorTest,
+ VideoImage_ExplicitPosterRecordedWhenDefaultSet) {
+ GetDocument().GetSettings()->SetDefaultVideoPosterURL(
+ AtomicString(SIMPLE_IMAGE));
+ SetMainFrameBodyContent(R"HTML(
+ <video id="target" width="300" height="200"></video>
+ )HTML");
+ test::RunPendingTasks();
+ SimulateRenderingAndPresentationTime();
+ EXPECT_FALSE(LargestImage());
+ EXPECT_EQ(CountImageRecords(), 0u);
+
+ // Changing the poster to an explicit image after the default poster loaded
+ // should record the new explicit poster.
+ Element* video_element = GetDocument().getElementById(AtomicString("target"));
+ ASSERT_TRUE(video_element);
+ video_element->setAttribute(html_names::kPosterAttr,
+ AtomicString(LARGE_IMAGE));
+ test::RunPendingTasks();
+ SimulateRenderingAndPresentationTime();
+ ImageRecord* record = LargestImage();
+ ASSERT_TRUE(record);
+ EXPECT_GT(record->EffectiveVisualSize(), 0ul);
+ EXPECT_TRUE(record->HasPaintTime());
+
+ // Removing the explicit poster attribute reverts to the default poster,
+ // which should remove the explicit poster record and not record the default.
+ video_element->removeAttribute(html_names::kPosterAttr);
+ test::RunPendingTasks();
+ SimulateRenderingAndPresentationTime();
+ EXPECT_EQ(CountImageRecords(), 0u);
+ EXPECT_EQ(ContainerTotalSize(), 0u);
+}
+
TEST_P(ImagePaintTimingDetectorTest, SVGImage) {
SetMainFrameBodyContent(R"HTML(
<svg>
diff --git a/third_party/blink/web_tests/media/video-default-poster-paint-timing.html b/third_party/blink/web_tests/media/video-default-poster-paint-timing.html
new file mode 100644
index 0000000..954ed08
--- /dev/null
+++ b/third_party/blink/web_tests/media/video-default-poster-paint-timing.html
@@ -0,0 +1,77 @@
+<!DOCTYPE html>
+<script src="../resources/testharness.js"></script>
+<script src="../resources/testharnessreport.js"></script>
+<body>
+<script>
+promise_test(async t => {
+ assert_implements(window.internals, 'window.internals is required');
+ internals.settings.setDefaultVideoPosterURL('content/abe.png');
+
... (file diff truncated)
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/third_party/blink/renderer/core/paint/timing/image_element_timing_test.cc b/third_party/blink/renderer/core/paint/timing/image_element_timing_test.cc
index e5ff2aa..5dc4a4af 100644
--- a/third_party/blink/renderer/core/paint/timing/image_element_timing_test.cc
+++ b/third_party/blink/renderer/core/paint/timing/image_element_timing_test.cc
@@ -6,6 +6,7 @@
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/renderer/core/frame/settings.h"
#include "third_party/blink/renderer/core/layout/layout_image.h"
#include "third_party/blink/renderer/core/layout/svg/layout_svg_image.h"
#include "third_party/blink/renderer/core/loader/resource/image_resource_content.h"
@@ -15,6 +16,7 @@
#include "third_party/blink/renderer/core/timing/performance.h"
#include "third_party/blink/renderer/core/timing/performance_element_timing.h"
#include "third_party/blink/renderer/platform/testing/paint_test_configurations.h"
+#include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
using testing::ElementsAre;
using testing::IsEmpty;
@@ -198,7 +200,7 @@
#target {
width: 100px;
height: 100px;
- background: url(data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==);
+ background: url()HTML" SIMPLE_IMAGE R"HTML();
}
</style>
<div elementtiming="time-my-background-image" id="target"></div>
@@ -261,4 +263,48 @@
EXPECT_THAT(GetElementTimingEntries(), IsEmpty());
}
+TEST_P(ImageElementTimingTest, VideoImage_DefaultPosterIgnored) {
+ GetDocument().GetSettings()->SetDefaultVideoPosterURL(
+ AtomicString(SIMPLE_IMAGE));
+ SetBodyInnerHTML(R"HTML(
+ <video id="target" elementtiming="video-et" width="100" height="100"></video>
+ )HTML");
+ test::RunPendingTasks();
+ SimulateRenderingAndPresentationTime();
+ EXPECT_EQ(RecordedImagesSize(), 0u);
+ EXPECT_THAT(GetElementTimingEntries(), IsEmpty());
+}
+
+TEST_P(ImageElementTimingTest,
+ VideoImage_ExplicitPosterRecordedWhenDefaultSet) {
+ GetDocument().GetSettings()->SetDefaultVideoPosterURL(
+ AtomicString(SIMPLE_IMAGE));
+ SetBodyInnerHTML(R"HTML(
+ <video id="target" elementtiming="video-et"
+ width="100" height="100"></video>
+ )HTML");
+ test::RunPendingTasks();
+ SimulateRenderingAndPresentationTime();
+ EXPECT_EQ(RecordedImagesSize(), 0u);
+ EXPECT_THAT(GetElementTimingEntries(), IsEmpty());
+
+ // Changing the poster to an explicit image after the default poster loaded
+ // should record the new explicit poster.
+ Element* video_element = GetElementById("target");
+ ASSERT_TRUE(video_element);
+ video_element->setAttribute(html_names::kPosterAttr,
+ AtomicString(LARGE_IMAGE));
+ test::RunPendingTasks();
+ SimulateRenderingAndPresentationTime();
+ EXPECT_EQ(RecordedImagesSize(), 1u);
+ EXPECT_THAT(GetElementTimingEntries(), ElementsAre(ForId("target")));
+
+ // Removing the explicit poster attribute reverts to the default poster,
+ // which should remove the explicit poster record and not record the default.
+ video_element->removeAttribute(html_names::kPosterAttr);
+ test::RunPendingTasks();
+ SimulateRenderingAndPresentationTime();
+ EXPECT_EQ(RecordedImagesSize(), 0u);
+}
+
} // namespace blink
diff --git a/third_party/blink/renderer/core/paint/timing/image_paint_timing_detector_test.cc b/third_party/blink/renderer/core/paint/timing/image_paint_timing_detector_test.cc
index 2c0382f..af0fbc0 100644
--- a/third_party/blink/renderer/core/paint/timing/image_paint_timing_detector_test.cc
+++ b/third_party/blink/renderer/core/paint/timing/image_paint_timing_detector_test.cc
@@ -46,23 +46,6 @@
namespace blink {
-#define SIMPLE_IMAGE \
- "data:image/gif;base64," \
- "R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="
-
-#define LARGE_IMAGE \
- "data:image/gif;base64," \
- "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSF" \
- "lzAAAN1wAADdcBQiibeAAAAb5JREFUOMulkr1KA0EQgGdvTwwnYmER0gQsrFKmSy+pLESw9Qm0" \
- "F/ICNnba+h6iEOuAEWslKJKTOyJJvIT72d1xZuOFC0giOLA77O7Mt/PnNptN+I+49Xr9GhH3f3" \
- "mb0v1ht9vtLAUYYw5ItkgDL3KyD8PhcLvdbl/WarXT3DjLMnAcR/f7/YfxeKwtgC5RKQVhGILW" \
- "eg4hQ6hUKjWyucmhLFEUuWR3QYBWAZABQ9i5CCmXy16pVALP80BKaaG+70MQBLvzFMjRKKXh8j" \
- "6FSYKF7ITdEWLa4/ktokN74wiqjSMpnVcbQZqmEJHz+ckeCPFjWKwULpyspAqhdXVXdcnZcPjs" \
- "Ign+2BsVA8jVYuWlgJ3yBj0icgq2uoK+lg4t+ZvLomSKamSQ4AI5BcMADtMhyNoSgNIISUaFNt" \
- "wlazcDcBc4gjjVwCWid2usCWroYEhnaqbzFJLUzAHIXRDChXCcQP8zhkSZ5eNLgHAUzwDcRu4C" \
- "oIRn/wsGUQIIy4Vr9TH6SYFCNzw4nALn5627K4vIttOUOwfa5YnrDYzt/9OLv9I5l8kk5hZ3XL" \
- "O20b7tbR7zHLy/BX8G0IeBEM7ZN1NGIaFUaKLgAAAAAElFTkSuQmCC"
-
#define TRANSPARENT_PLACEHOLDER_IMAGE \
"data:image/gif;base64," \
"R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="
@@ -825,6 +808,69 @@
EXPECT_FALSE(record);
}
+TEST_P(ImagePaintTimingDetectorTest, VideoImage_DefaultPosterIgnored) {
+ GetDocument().GetSettings()->SetDefaultVideoPosterURL(
+ AtomicString(LARGE_IMAGE));
+ SetMainFrameBodyContent(R"HTML(
+ <video id="target" width="300" height="200"></video>
+ )HTML");
+ test::RunPendingTasks();
+ SimulateRenderingAndPresentationTime();
+ EXPECT_FALSE(LargestImage());
+ EXPECT_EQ(CountImageRecords(), 0u);
+
+ // Verify that a subsequent first video frame is not blocked by the ignored
+ // default poster image.
+ Element* video_element = GetDocument().getElementById(AtomicString("target"));
+ ASSERT_TRUE(video_element);
+ VideoTiming* video_timing = MakeGarbageCollected<VideoTiming>();
+ video_timing->SetFirstVideoFrameTime(NowTicks());
+ video_timing->SetIsSufficientContentLoadedForPaint();
+ video_timing->SetUrl(KURL("http://test.com/video.mp4"));
+ video_timing->SetContentSizeForEntropy(1024 * 1024);
+
+ SimulateFirstVideoFrame(video_element, video_timing, 300, 200);
+ SimulateRenderingAndPresentationTime();
+ ImageRecord* record = LargestImage();
+ ASSERT_TRUE(record);
+ EXPECT_EQ(record->GetMediaTiming(), video_timing);
+ EXPECT_TRUE(record->HasPaintTime());
+}
+
+TEST_P(ImagePaintTimingDetectorTest,
+ VideoImage_ExplicitPosterRecordedWhenDefaultSet) {
+ GetDocument().GetSettings()->SetDefaultVideoPosterURL(
+ AtomicString(SIMPLE_IMAGE));
+ SetMainFrameBodyContent(R"HTML(
+ <video id="target" width="300" height="200"></video>
+ )HTML");
+ test::RunPendingTasks();
+ SimulateRenderingAndPresentationTime();
+ EXPECT_FALSE(LargestImage());
+ EXPECT_EQ(CountImageRecords(), 0u);
+
+ // Changing the poster to an explicit image after the default poster loaded
+ // should record the new explicit poster.
+ Element* video_element = GetDocument().getElementById(AtomicString("target"));
+ ASSERT_TRUE(video_element);
+ video_element->setAttribute(html_names::kPosterAttr,
+ AtomicString(LARGE_IMAGE));
+ test::RunPendingTasks();
+ SimulateRenderingAndPresentationTime();
+ ImageRecord* record = LargestImage();
+ ASSERT_TRUE(record);
+ EXPECT_GT(record->EffectiveVisualSize(), 0ul);
+ EXPECT_TRUE(record->HasPaintTime());
+
+ // Removing the explicit poster attribute reverts to the default poster,
+ // which should remove the explicit poster record and not record the default.
+ video_element->removeAttribute(html_names::kPosterAttr);
+ test::RunPendingTasks();
+ SimulateRenderingAndPresentationTime();
+ EXPECT_EQ(CountImageRecords(), 0u);
+ EXPECT_EQ(ContainerTotalSize(), 0u);
+}
+
TEST_P(ImagePaintTimingDetectorTest, SVGImage) {
SetMainFrameBodyContent(R"HTML(
<svg>
diff --git a/third_party/blink/web_tests/media/video-default-poster-paint-timing.html b/third_party/blink/web_tests/media/video-default-poster-paint-timing.html
new file mode 100644
index 0000000..954ed08
--- /dev/null
+++ b/third_party/blink/web_tests/media/video-default-poster-paint-timing.html
@@ -0,0 +1,77 @@
+<!DOCTYPE html>
+<script src="../resources/testharness.js"></script>
+<script src="../resources/testharnessreport.js"></script>
+<body>
+<script>
+promise_test(async t => {
+ assert_implements(window.internals, 'window.internals is required');
+ internals.settings.setDefaultVideoPosterURL('content/abe.png');
+
+ // Preload both images so their resource loads don't race.
+ await Promise.all(['content/abe.png', 'content/greenbox.png'].map(src => {
+ return new Promise((resolve, reject) => {
+ const img = new Image();
+ img.onload = resolve;
+ img.onerror = reject;
+ img.src = src;
+ });
+ }));
+
+ const entries = [];
+ const observer = new PerformanceObserver(list => {
+ entries.push(...list.getEntries());
+ });
+ observer.observe({entryTypes: ['largest-contentful-paint', 'element']});
+
+ // 1. Video with default poster (no poster attribute) and elementtiming.
+ // Sized larger (300x300) than videoExplicit so it would win LCP if not filtered.
+ const videoDefault = document.createElement('video');
+ videoDefault.id = 'video-default';
+ videoDefault.setAttribute('elementtiming', 'default-et');
+ videoDefault.width = 300;
+ videoDefault.height = 300;
+ document.body.appendChild(videoDefault);
+
+ // Ensure videoDefault has gone through a rendering update before adding videoExplicit.
+ await new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)));
+
+ // 2. Video with explicit poster and elementtiming.
+ const videoExplicit = document.createElement('video');
+ videoExplicit.id = 'video-explicit';
+ videoExplicit.poster = 'content/greenbox.png';
+ videoExplicit.setAttribute('elementtiming', 'explicit-et');
+ videoExplicit.width = 100;
+ videoExplicit.height = 100;
+ document.body.appendChild(videoExplicit);
+
+ // Wait for an LCP entry and the explicit ElementTiming entry.
+ // Do not filter LCP by id here so that if video-default wrongly wins LCP,
+ // step_wait resolves immediately and fails the assertion below instead of timing out.
+ await t.step_wait(() => {
+ const hasLcp = entries.some(e => e.entryType === 'largest-contentful-paint');
+ const hasEt = entries.some(
+ e => e.entryType === 'element' && e.identifier === 'explicit-et');
+ return hasLcp && hasEt;
+ }, 'Wait for LCP and ElementTiming entries');
+
+ // Verify that the default poster was never reported to LCP or ElementTiming,
+ // and that the explicit poster was reported to LCP.
+ const defaultLcp = entries.find(
+ e => e.entryType === 'largest-contentful-paint' &&
+ (e.id === 'video-default' || e.url.includes('abe.png')));
+ const defaultEt = entries.find(
+ e => e.entryType === 'element' &&
+ (e.identifier === 'default-et' || e.url.includes('abe.png')));
+ const explicitLcp = entries.find(
+ e => e.entryType === 'largest-contentful-paint' &&
+ e.id === 'video-explicit');
+
+ assert_equals(defaultLcp, undefined,
+ 'Default video poster should not be reported to LargestContentfulPaint');
+ assert_equals(defaultEt, undefined,
+ 'Default video poster should not be reported to ElementTiming');
+ assert_not_equals(explicitLcp, undefined,
+ 'Explicit video poster should be reported to LargestContentfulPaint');
+}, 'Default video poster image is ignored by LargestContentfulPaint and ElementTiming');
+</script>
+</body>
Loading diff…
Original Bug Report
The reporter's bug is still restricted on the tracker. Chrome de-restricts security bugs ~30–90 days after the fix ships; a later run will backfill it here.
References
On This Page