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 <[email protected]> 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 <[email protected]> Commit-Queue: Scott Haseley <[email protected]> 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