diff --git a/blink/common/features.cc b/blink/common/features.cc index c922937b008a..32ff7ec489f4 100644 --- a/blink/common/features.cc +++ b/blink/common/features.cc @@ -1101,5 +1101,10 @@ const base::Feature kDeprecationWillLogToConsole{ const base::Feature kDeprecationWillLogToDevToolsIssue{ "DeprecationWillLogToDevToolsIssue", base::FEATURE_DISABLED_BY_DEFAULT}; +// Enables reporting and web-exposure (respectively) of the time the first frame +// of an animated image was painted. +const base::Feature kLCPAnimatedImagesReporting{ + "LCPAnimatedImagesReporting", base::FEATURE_DISABLED_BY_DEFAULT}; + } // namespace features } // namespace blink diff --git a/blink/public/common/features.h b/blink/public/common/features.h index 3ed34baf4477..7c1a892a7b74 100644 --- a/blink/public/common/features.h +++ b/blink/public/common/features.h @@ -518,6 +518,8 @@ BLINK_COMMON_EXPORT extern const base::Feature kDeprecationWillLogToConsole; BLINK_COMMON_EXPORT extern const base::Feature kDeprecationWillLogToDevToolsIssue; +BLINK_COMMON_EXPORT extern const base::Feature kLCPAnimatedImagesReporting; + } // namespace features } // namespace blink diff --git a/blink/renderer/core/loader/resource/image_resource_content.cc b/blink/renderer/core/loader/resource/image_resource_content.cc index bc333d664bb9..6662e6857d7e 100644 --- a/blink/renderer/core/loader/resource/image_resource_content.cc +++ b/blink/renderer/core/loader/resource/image_resource_content.cc @@ -626,7 +626,12 @@ ResourceStatus ImageResourceContent::GetContentStatus() const { return content_status_; } -// TODO(hiroshige): Consider removing the following methods, or stoping +bool ImageResourceContent::IsAnimatedImageWithPaintedFirstFrame() const { + return (image_ && !image_->IsNull() && image_->MaybeAnimated() && + image_->CurrentFrameIsComplete()); +} + +// TODO(hiroshige): Consider removing the following methods, or stopping // redirecting to ImageResource. const KURL& ImageResourceContent::Url() const { return info_->Url(); diff --git a/blink/renderer/core/loader/resource/image_resource_content.h b/blink/renderer/core/loader/resource/image_resource_content.h index 572c7369976f..57f333c400ae 100644 --- a/blink/renderer/core/loader/resource/image_resource_content.h +++ b/blink/renderer/core/loader/resource/image_resource_content.h @@ -109,6 +109,7 @@ class CORE_EXPORT ImageResourceContent final bool IsLoading() const; bool ErrorOccurred() const; bool LoadFailedOrCanceled() const; + bool IsAnimatedImageWithPaintedFirstFrame() const; // Redirecting methods to Resource. const KURL& Url() const; diff --git a/blink/renderer/core/paint/image_paint_timing_detector.cc b/blink/renderer/core/paint/image_paint_timing_detector.cc index 2d1a2418339f..24a895df72a7 100644 --- a/blink/renderer/core/paint/image_paint_timing_detector.cc +++ b/blink/renderer/core/paint/image_paint_timing_detector.cc @@ -53,6 +53,11 @@ uint64_t DownScaleIfIntrinsicSizeIsSmaller( return visual_size; } +bool ShouldReportAnimatedImages() { + return (RuntimeEnabledFeatures::LCPAnimatedImagesWebExposedEnabled() || + base::FeatureList::IsEnabled(features::kLCPAnimatedImagesReporting)); +} + } // namespace static bool LargeImageFirst(const base::WeakPtr& a, @@ -101,6 +106,7 @@ void ImagePaintTimingDetector::ReportCandidateToTrace( DCHECK(!largest_image_record.paint_time.is_null()); auto value = std::make_unique(); PopulateTraceValue(*value, largest_image_record); + // TODO(yoav): Report first animated frame times as well. TRACE_EVENT_MARK_WITH_TIMESTAMP2("loading", "LargestImagePaint::Candidate", largest_image_record.paint_time, "data", std::move(value), "frame", @@ -123,20 +129,29 @@ void ImagePaintTimingDetector::ReportNoCandidateToTrace() { ImageRecord* ImagePaintTimingDetector::UpdateCandidate() { ImageRecord* largest_image_record = records_manager_.FindLargestPaintCandidate(); - const base::TimeTicks time = largest_image_record - ? largest_image_record->paint_time - : base::TimeTicks(); + base::TimeTicks time = largest_image_record ? largest_image_record->paint_time + : base::TimeTicks(); + // This doesn't use ShouldReportAnimatedImages(), as it should only update the + // record when the base::Feature is enabled, regardless of the runtime-enabled + // flag. + if (base::FeatureList::IsEnabled(features::kLCPAnimatedImagesReporting) && + largest_image_record && + !largest_image_record->first_animated_frame_time.is_null()) { + time = largest_image_record->first_animated_frame_time; + } const uint64_t size = largest_image_record ? largest_image_record->first_size : 0; PaintTimingDetector& detector = frame_view_->GetPaintTimingDetector(); + // Calling NotifyIfChangedLargestImagePaint only has an impact on + // PageLoadMetrics, and not on the web exposed metrics. + // // Two different candidates are rare to have the same time and size. // So when they are unchanged, the candidate is considered unchanged. bool changed = detector.NotifyIfChangedLargestImagePaint( time, size, records_manager_.LargestRemovedImagePaintTime(), records_manager_.LargestRemovedImageSize()); if (changed) { - if (!time.is_null()) { - DCHECK(largest_image_record->loaded); + if (!time.is_null() && largest_image_record->loaded) { ReportCandidateToTrace(*largest_image_record); } else { ReportNoCandidateToTrace(); @@ -166,7 +181,7 @@ void ImagePaintTimingDetector::NotifyImageRemoved( const LayoutObject& object, const ImageResourceContent* cached_image) { RecordId record_id = std::make_pair(&object, cached_image); - records_manager_.RemoveImageFinishedRecord(record_id); + records_manager_.RemoveImageTimeRecords(record_id); records_manager_.RemoveInvisibleRecordIfNeeded(record_id); if (!records_manager_.IsRecordedVisibleImage(record_id)) return; @@ -215,7 +230,13 @@ void ImageRecordsManager::AssignPaintTimeToRegisteredQueuedRecords( } if (record->frame_index > last_queued_frame_index) break; - record->paint_time = timestamp; + if (record->loaded) { + record->paint_time = timestamp; + } + if (record->queue_animated_paint) { + record->first_animated_frame_time = timestamp; + record->queue_animated_paint = false; + } images_queued_for_paint_time_.pop_front(); } } @@ -228,6 +249,7 @@ void ImagePaintTimingDetector::RecordImage( const StyleFetchedImage* style_image, const IntRect& image_border) { Node* node = object.GetNode(); + if (!node) return; @@ -269,25 +291,28 @@ void ImagePaintTimingDetector::RecordImage( return; } - if (is_recorded_visible_image && - !records_manager_.IsVisibleImageLoaded(record_id) && - cached_image.IsLoaded()) { - records_manager_.OnImageLoaded(record_id, frame_index_, style_image); - need_update_timing_at_frame_end_ = true; - if (absl::optional& visualizer = - frame_view_->GetPaintTimingDetector().Visualizer()) { - FloatRect mapped_visual_rect = - frame_view_->GetPaintTimingDetector().CalculateVisualRect( - image_border, current_paint_chunk_properties); - visualizer->DumpImageDebuggingRect(object, mapped_visual_rect, - cached_image); + if (is_recorded_visible_image) { + if (ShouldReportAnimatedImages() && + cached_image.IsAnimatedImageWithPaintedFirstFrame()) { + need_update_timing_at_frame_end_ |= + records_manager_.OnFirstAnimatedFramePainted(record_id, frame_index_); + } + if (!records_manager_.IsVisibleImageLoaded(record_id) && + cached_image.IsLoaded()) { + records_manager_.OnImageLoaded(record_id, frame_index_, style_image); + need_update_timing_at_frame_end_ = true; + if (absl::optional& visualizer = + frame_view_->GetPaintTimingDetector().Visualizer()) { + FloatRect mapped_visual_rect = + frame_view_->GetPaintTimingDetector().CalculateVisualRect( + image_border, current_paint_chunk_properties); + visualizer->DumpImageDebuggingRect(object, mapped_visual_rect, + cached_image); + } } return; } - if (is_recorded_visible_image) - return; - FloatRect mapped_visual_rect = frame_view_->GetPaintTimingDetector().CalculateVisualRect( image_border, current_paint_chunk_properties); @@ -299,6 +324,11 @@ void ImagePaintTimingDetector::RecordImage( } else { records_manager_.RecordVisible(record_id, rect_size, image_border, mapped_visual_rect); + if (ShouldReportAnimatedImages() && + cached_image.IsAnimatedImageWithPaintedFirstFrame()) { + need_update_timing_at_frame_end_ |= + records_manager_.OnFirstAnimatedFramePainted(record_id, frame_index_); + } if (cached_image.IsLoaded()) { records_manager_.OnImageLoaded(record_id, frame_index_, style_image); need_update_timing_at_frame_end_ = true; @@ -366,6 +396,19 @@ void ImagePaintTimingDetector::ReportLargestIgnoredImage() { ImageRecordsManager::ImageRecordsManager(LocalFrameView* frame_view) : size_ordered_set_(&LargeImageFirst), frame_view_(frame_view) {} +bool ImageRecordsManager::OnFirstAnimatedFramePainted( + const RecordId& record_id, + unsigned current_frame_index) { + base::WeakPtr record = FindVisibleRecord(record_id); + DCHECK(record); + if (record->first_animated_frame_time.is_null()) { + record->queue_animated_paint = true; + QueueToMeasurePaintTime(record, current_frame_index); + return true; + } + return false; +} + void ImageRecordsManager::OnImageLoaded(const RecordId& record_id, unsigned current_frame_index, const StyleFetchedImage* style_image) { diff --git a/blink/renderer/core/paint/image_paint_timing_detector.h b/blink/renderer/core/paint/image_paint_timing_detector.h index c9bedf793782..6da959eafc64 100644 --- a/blink/renderer/core/paint/image_paint_timing_detector.h +++ b/blink/renderer/core/paint/image_paint_timing_detector.h @@ -56,7 +56,10 @@ class ImageRecord : public base::SupportsWeakPtr { // The time of the first paint after fully loaded. 0 means not painted yet. base::TimeTicks paint_time = base::TimeTicks(); base::TimeTicks load_time = base::TimeTicks(); + base::TimeTicks first_animated_frame_time = base::TimeTicks(); bool loaded = false; + // An animated frame is queued for paint timing. + bool queue_animated_paint = false; // LCP rect information, only populated when tracing is enabled. std::unique_ptr lcp_rect_info_; }; @@ -88,7 +91,7 @@ class CORE_EXPORT ImageRecordsManager { invisible_images_.erase(record_id); } - inline void RemoveImageFinishedRecord(const RecordId& record_id) { + inline void RemoveImageTimeRecords(const RecordId& record_id) { image_finished_times_.erase(record_id); } @@ -133,14 +136,17 @@ class CORE_EXPORT ImageRecordsManager { // not currently the case. If we plumb some information from // ImageResourceContent we may be able to ensure that this call does not // require the Contains() check, which would save time. - if (!image_finished_times_.Contains(record_id)) + if (!image_finished_times_.Contains(record_id)) { image_finished_times_.insert(record_id, base::TimeTicks::Now()); + } } inline bool IsVisibleImageLoaded(const RecordId& record_id) const { DCHECK(visible_images_.Contains(record_id)); return visible_images_.at(record_id)->loaded; } + bool OnFirstAnimatedFramePainted(const RecordId&, + unsigned current_frame_index); void OnImageLoaded(const RecordId&, unsigned current_frame_index, const StyleFetchedImage*); diff --git a/blink/renderer/core/paint/largest_contentful_paint_calculator.cc b/blink/renderer/core/paint/largest_contentful_paint_calculator.cc index 3c97f1992f7a..267ff338a071 100644 --- a/blink/renderer/core/paint/largest_contentful_paint_calculator.cc +++ b/blink/renderer/core/paint/largest_contentful_paint_calculator.cc @@ -22,7 +22,7 @@ LargestContentfulPaintCalculator::LargestContentfulPaintCalculator( WindowPerformance* window_performance) : window_performance_(window_performance) {} -void LargestContentfulPaintCalculator::UpdateLargestContentPaintIfNeeded( +void LargestContentfulPaintCalculator::UpdateLargestContentfulPaintIfNeeded( const TextRecord* largest_text, const ImageRecord* largest_image) { uint64_t text_size = largest_text ? largest_text->first_size : 0u; @@ -72,9 +72,12 @@ void LargestContentfulPaintCalculator::UpdateLargestContentfulImage( image_element ? image_element->GetIdAttribute() : AtomicString(); window_performance_->OnLargestContentfulPaintUpdated( expose_paint_time_to_api ? largest_image->paint_time : base::TimeTicks(), - largest_image->first_size, largest_image->load_time, image_id, image_url, - image_element); + largest_image->first_size, largest_image->load_time, + expose_paint_time_to_api ? largest_image->first_animated_frame_time + : base::TimeTicks(), + image_id, image_url, image_element); + // TODO: update trace value with animated frame data if (LocalDOMWindow* window = window_performance_->DomWindow()) { TRACE_EVENT_MARK_WITH_TIMESTAMP2(kTraceCategories, kLCPCandidate, largest_image->paint_time, "data", @@ -100,7 +103,7 @@ void LargestContentfulPaintCalculator::UpdateLargestContentfulText( text_element ? text_element->GetIdAttribute() : AtomicString(); window_performance_->OnLargestContentfulPaintUpdated( largest_text.paint_time, largest_text.first_size, base::TimeTicks(), - text_id, g_empty_string, text_element); + base::TimeTicks(), text_id, g_empty_string, text_element); if (LocalDOMWindow* window = window_performance_->DomWindow()) { TRACE_EVENT_MARK_WITH_TIMESTAMP2(kTraceCategories, kLCPCandidate, diff --git a/blink/renderer/core/paint/largest_contentful_paint_calculator.h b/blink/renderer/core/paint/largest_contentful_paint_calculator.h index e0a32e261629..fd2991736901 100644 --- a/blink/renderer/core/paint/largest_contentful_paint_calculator.h +++ b/blink/renderer/core/paint/largest_contentful_paint_calculator.h @@ -23,8 +23,8 @@ class CORE_EXPORT LargestContentfulPaintCalculator final LargestContentfulPaintCalculator& operator=( const LargestContentfulPaintCalculator&) = delete; - void UpdateLargestContentPaintIfNeeded(const TextRecord* largest_text, - const ImageRecord* largest_image); + void UpdateLargestContentfulPaintIfNeeded(const TextRecord* largest_text, + const ImageRecord* largest_image); void Trace(Visitor* visitor) const; diff --git a/blink/renderer/core/paint/paint_timing_detector.cc b/blink/renderer/core/paint/paint_timing_detector.cc index 27ded06284bd..4686e5b26aef 100644 --- a/blink/renderer/core/paint/paint_timing_detector.cc +++ b/blink/renderer/core/paint/paint_timing_detector.cc @@ -33,6 +33,7 @@ #include "third_party/blink/renderer/platform/graphics/paint/property_tree_state.h" #include "third_party/blink/renderer/platform/graphics/paint/scoped_paint_chunk_properties.h" #include "third_party/blink/renderer/platform/graphics/static_bitmap_image.h" +#include "third_party/blink/renderer/platform/runtime_enabled_features.h" #include "third_party/blink/renderer/platform/wtf/cross_thread_functional.h" #include "third_party/blink/renderer/platform/wtf/functional.h" @@ -122,14 +123,23 @@ void PaintTimingDetector::NotifyBackgroundImagePaint( LocalFrameView* frame_view = object->GetFrameView(); if (!frame_view) return; - PaintTimingDetector& detector = frame_view->GetPaintTimingDetector(); - if (!detector.GetImagePaintTimingDetector()) + + ImagePaintTimingDetector* detector = + frame_view->GetPaintTimingDetector().GetImagePaintTimingDetector(); + if (!detector) return; + if (!IsBackgroundImageContentful(*object, image)) return; - detector.GetImagePaintTimingDetector()->RecordImage( - *object, image.Size(), *style_image.CachedImage(), - current_paint_chunk_properties, &style_image, image_border); + + ImageResourceContent* cached_image = style_image.CachedImage(); + DCHECK(cached_image); + // TODO(yoav): |image| and |cached_image.GetImage()| are not the same here in + // the case of SVGs. Figure out why and if we can remove this footgun. + + detector->RecordImage(*object, image.Size(), *cached_image, + current_paint_chunk_properties, &style_image, + image_border); } // static @@ -144,12 +154,13 @@ void PaintTimingDetector::NotifyImagePaint( LocalFrameView* frame_view = object.GetFrameView(); if (!frame_view) return; - PaintTimingDetector& detector = frame_view->GetPaintTimingDetector(); - if (!detector.GetImagePaintTimingDetector()) + ImagePaintTimingDetector* detector = + frame_view->GetPaintTimingDetector().GetImagePaintTimingDetector(); + if (!detector) return; - detector.GetImagePaintTimingDetector()->RecordImage( - object, intrinsic_size, cached_image, current_paint_chunk_properties, - nullptr, image_border); + + detector->RecordImage(object, intrinsic_size, cached_image, + current_paint_chunk_properties, nullptr, image_border); } void PaintTimingDetector::NotifyImageFinished( @@ -386,8 +397,8 @@ void PaintTimingDetector::UpdateLargestContentfulPaintCandidate() { largest_image_record = image_timing_detector->UpdateCandidate(); } - lcp_calculator->UpdateLargestContentPaintIfNeeded(largest_text_record, - largest_image_record); + lcp_calculator->UpdateLargestContentfulPaintIfNeeded(largest_text_record, + largest_image_record); } void PaintTimingDetector::ReportIgnoredContent() { diff --git a/blink/renderer/core/timing/largest_contentful_paint.cc b/blink/renderer/core/timing/largest_contentful_paint.cc index fca205e81c52..e3d3dfd2eef6 100644 --- a/blink/renderer/core/timing/largest_contentful_paint.cc +++ b/blink/renderer/core/timing/largest_contentful_paint.cc @@ -11,17 +11,20 @@ namespace blink { -LargestContentfulPaint::LargestContentfulPaint(double start_time, - base::TimeDelta render_time, - uint64_t size, - base::TimeDelta load_time, - const AtomicString& id, - const String& url, - Element* element) +LargestContentfulPaint::LargestContentfulPaint( + double start_time, + base::TimeDelta render_time, + uint64_t size, + base::TimeDelta load_time, + base::TimeDelta first_animated_frame_time, + const AtomicString& id, + const String& url, + Element* element) : PerformanceEntry(g_empty_atom, start_time, start_time), size_(size), render_time_(render_time), load_time_(load_time), + first_animated_frame_time_(first_animated_frame_time), id_(id), url_(url), element_(element) {} @@ -53,6 +56,8 @@ void LargestContentfulPaint::BuildJSONValue(V8ObjectBuilder& builder) const { builder.Add("size", size_); builder.Add("renderTime", render_time_.InMillisecondsF()); builder.Add("loadTime", load_time_.InMillisecondsF()); + builder.Add("firstAnimatedFrameTime", + first_animated_frame_time_.InMillisecondsF()); builder.Add("id", id_); builder.Add("url", url_); } diff --git a/blink/renderer/core/timing/largest_contentful_paint.h b/blink/renderer/core/timing/largest_contentful_paint.h index 97b66eb1e53c..915e1b029f5e 100644 --- a/blink/renderer/core/timing/largest_contentful_paint.h +++ b/blink/renderer/core/timing/largest_contentful_paint.h @@ -22,6 +22,7 @@ class CORE_EXPORT LargestContentfulPaint final : public PerformanceEntry { base::TimeDelta render_time, uint64_t size, base::TimeDelta load_time, + base::TimeDelta first_animated_frame_time, const AtomicString& id, const String& url, Element*); @@ -35,6 +36,9 @@ class CORE_EXPORT LargestContentfulPaint final : public PerformanceEntry { return render_time_.InMillisecondsF(); } DOMHighResTimeStamp loadTime() const { return load_time_.InMillisecondsF(); } + DOMHighResTimeStamp firstAnimatedFrameTime() const { + return first_animated_frame_time_.InMillisecondsF(); + } const AtomicString& id() const { return id_; } const String& url() const { return url_; } Element* element() const; @@ -47,6 +51,7 @@ class CORE_EXPORT LargestContentfulPaint final : public PerformanceEntry { uint64_t size_; base::TimeDelta render_time_; base::TimeDelta load_time_; + base::TimeDelta first_animated_frame_time_; AtomicString id_; String url_; WeakMember element_; diff --git a/blink/renderer/core/timing/largest_contentful_paint.idl b/blink/renderer/core/timing/largest_contentful_paint.idl index 0d3ae7b0cb1e..41e7faf9b029 100644 --- a/blink/renderer/core/timing/largest_contentful_paint.idl +++ b/blink/renderer/core/timing/largest_contentful_paint.idl @@ -7,6 +7,7 @@ interface LargestContentfulPaint : PerformanceEntry { readonly attribute DOMHighResTimeStamp renderTime; readonly attribute DOMHighResTimeStamp loadTime; + [RuntimeEnabled=LCPAnimatedImagesWebExposed] readonly attribute DOMHighResTimeStamp firstAnimatedFrameTime; readonly attribute unsigned long long size; readonly attribute DOMString id; readonly attribute DOMString url; diff --git a/blink/renderer/core/timing/window_performance.cc b/blink/renderer/core/timing/window_performance.cc index 1a227a2b4c24..dd15fdf3e657 100644 --- a/blink/renderer/core/timing/window_performance.cc +++ b/blink/renderer/core/timing/window_performance.cc @@ -638,16 +638,20 @@ void WindowPerformance::OnLargestContentfulPaintUpdated( base::TimeTicks paint_time, uint64_t paint_size, base::TimeTicks load_time, + base::TimeTicks first_animated_frame_time, const AtomicString& id, const String& url, Element* element) { base::TimeDelta render_timestamp = MonotonicTimeToTimeDelta(paint_time); base::TimeDelta load_timestamp = MonotonicTimeToTimeDelta(load_time); + base::TimeDelta first_animated_frame_timestamp = + MonotonicTimeToTimeDelta(first_animated_frame_time); + // TODO(yoav): Should we modify start to represent the animated frame? base::TimeDelta start_timestamp = render_timestamp.is_zero() ? load_timestamp : render_timestamp; auto* entry = MakeGarbageCollected( start_timestamp.InMillisecondsF(), render_timestamp, paint_size, - load_timestamp, id, url, element); + load_timestamp, first_animated_frame_timestamp, id, url, element); if (HasObserverFor(PerformanceEntry::kLargestContentfulPaint)) NotifyObserversOfEntry(*entry); AddLargestContentfulPaint(entry); diff --git a/blink/renderer/core/timing/window_performance.h b/blink/renderer/core/timing/window_performance.h index 4362b11cf9b6..e3a4432b7749 100644 --- a/blink/renderer/core/timing/window_performance.h +++ b/blink/renderer/core/timing/window_performance.h @@ -146,12 +146,14 @@ class CORE_EXPORT WindowPerformance final : public Performance, // PageVisibilityObserver void PageVisibilityChanged() override; - void OnLargestContentfulPaintUpdated(base::TimeTicks paint_time, - uint64_t paint_size, - base::TimeTicks load_time, - const AtomicString& id, - const String& url, - Element*); + void OnLargestContentfulPaintUpdated( + base::TimeTicks paint_time, + uint64_t paint_size, + base::TimeTicks load_time, + base::TimeTicks first_animated_frame_time, + const AtomicString& id, + const String& url, + Element*); void Trace(Visitor*) const override; diff --git a/blink/renderer/platform/runtime_enabled_features.json5 b/blink/renderer/platform/runtime_enabled_features.json5 index af662fcf06cb..979732506c39 100644 --- a/blink/renderer/platform/runtime_enabled_features.json5 +++ b/blink/renderer/platform/runtime_enabled_features.json5 @@ -1336,6 +1336,10 @@ name: "LazyInitializeMediaControls", // This is enabled by features::kLazyInitializeMediaControls. }, + { + name: "LCPAnimatedImagesWebExposed", + status: "test", + }, { name: "LegacyWindowsDWriteFontFallback", // Enabled by features::kLegacyWindowsDWriteFontFallback; diff --git a/blink/web_tests/external/wpt/images/anim-tao.png b/blink/web_tests/external/wpt/images/anim-tao.png new file mode 100644 index 000000000000..925e2efc9a97 Binary files /dev/null and b/blink/web_tests/external/wpt/images/anim-tao.png differ diff --git a/blink/web_tests/external/wpt/images/anim-tao.png.headers b/blink/web_tests/external/wpt/images/anim-tao.png.headers new file mode 100644 index 000000000000..0230e176e445 --- /dev/null +++ b/blink/web_tests/external/wpt/images/anim-tao.png.headers @@ -0,0 +1,2 @@ +Timing-Allow-Origin: * + diff --git a/blink/web_tests/external/wpt/images/webp-animated.webp b/blink/web_tests/external/wpt/images/webp-animated.webp new file mode 100644 index 000000000000..35a8dfcf34d5 Binary files /dev/null and b/blink/web_tests/external/wpt/images/webp-animated.webp differ diff --git a/blink/web_tests/external/wpt/largest-contentful-paint/animated/observe-animated-image-gif.tentative.html b/blink/web_tests/external/wpt/largest-contentful-paint/animated/observe-animated-image-gif.tentative.html new file mode 100644 index 000000000000..a2c0d7975abe --- /dev/null +++ b/blink/web_tests/external/wpt/largest-contentful-paint/animated/observe-animated-image-gif.tentative.html @@ -0,0 +1,27 @@ + + + + + Largest Contentful Paint: observe image. + + + + + + + + diff --git a/blink/web_tests/external/wpt/largest-contentful-paint/animated/observe-animated-image-webp.tentative.html b/blink/web_tests/external/wpt/largest-contentful-paint/animated/observe-animated-image-webp.tentative.html new file mode 100644 index 000000000000..de59d5c5f78c --- /dev/null +++ b/blink/web_tests/external/wpt/largest-contentful-paint/animated/observe-animated-image-webp.tentative.html @@ -0,0 +1,27 @@ + + + + + Largest Contentful Paint: observe image. + + + + + + + + diff --git a/blink/web_tests/external/wpt/largest-contentful-paint/animated/observe-animated-image.tentative.html b/blink/web_tests/external/wpt/largest-contentful-paint/animated/observe-animated-image.tentative.html new file mode 100644 index 000000000000..cf7d262b0f84 --- /dev/null +++ b/blink/web_tests/external/wpt/largest-contentful-paint/animated/observe-animated-image.tentative.html @@ -0,0 +1,29 @@ + + + + + Largest Contentful Paint: observe image. + + + + + + + + diff --git a/blink/web_tests/external/wpt/largest-contentful-paint/animated/observe-cross-origin-animated-image.tentative.html b/blink/web_tests/external/wpt/largest-contentful-paint/animated/observe-cross-origin-animated-image.tentative.html new file mode 100644 index 000000000000..993883c607b8 --- /dev/null +++ b/blink/web_tests/external/wpt/largest-contentful-paint/animated/observe-cross-origin-animated-image.tentative.html @@ -0,0 +1,30 @@ + + + + + Largest Contentful Paint: observe image. + + + + + + + + + diff --git a/blink/web_tests/external/wpt/largest-contentful-paint/animated/observe-cross-origin-tao-animated-image.tentative.html b/blink/web_tests/external/wpt/largest-contentful-paint/animated/observe-cross-origin-tao-animated-image.tentative.html new file mode 100644 index 000000000000..137dde66383f --- /dev/null +++ b/blink/web_tests/external/wpt/largest-contentful-paint/animated/observe-cross-origin-tao-animated-image.tentative.html @@ -0,0 +1,30 @@ + + + + + Largest Contentful Paint: observe image. + + + + + + + + + diff --git a/blink/web_tests/external/wpt/largest-contentful-paint/animated/observe-non-animated-image.tentative.html b/blink/web_tests/external/wpt/largest-contentful-paint/animated/observe-non-animated-image.tentative.html new file mode 100644 index 000000000000..6bbc0958b1de --- /dev/null +++ b/blink/web_tests/external/wpt/largest-contentful-paint/animated/observe-non-animated-image.tentative.html @@ -0,0 +1,27 @@ + + + + + Largest Contentful Paint: observe image. + + + + + + + + diff --git a/blink/web_tests/external/wpt/largest-contentful-paint/resources/largest-contentful-paint-helpers.js b/blink/web_tests/external/wpt/largest-contentful-paint/resources/largest-contentful-paint-helpers.js index e12ece0a7561..5012faf3b1be 100644 --- a/blink/web_tests/external/wpt/largest-contentful-paint/resources/largest-contentful-paint-helpers.js +++ b/blink/web_tests/external/wpt/largest-contentful-paint/resources/largest-contentful-paint-helpers.js @@ -1,3 +1,6 @@ +const image_delay = 1000; +const delay_pipe_value = image_delay / 1000; + // Receives an image LargestContentfulPaint |entry| and checks |entry|'s attribute values. // The |timeLowerBound| parameter is a lower bound on the loadTime value of the entry. // The |options| parameter may contain some string values specifying the following: @@ -33,4 +36,33 @@ function checkImage(entry, expectedUrl, expectedID, expectedSize, timeLowerBound } else { assert_equals(entry.size, expectedSize); } + if (options.includes('animated')) { + assert_greater_than(entry.loadTime, entry.firstAnimatedFrameTime, + 'firstAnimatedFrameTime should be smaller than loadTime'); + assert_greater_than(entry.renderTime, entry.firstAnimatedFrameTime, + 'firstAnimatedFrameTime should be smaller than renderTime'); + assert_less_than(entry.firstAnimatedFrameTime, image_delay, + 'firstAnimatedFrameTime should be smaller than the delay applied to the second frame'); + assert_greater_than(entry.firstAnimatedFrameTime, 0, + 'firstAnimatedFrameTime should be larger than 0'); + } + if (options.includes('animated-zero')) { + assert_equals(entry.firstAnimatedFrameTime, 0, 'firstAnimatedFrameTime should be 0'); + } } + +const load_and_observe = url => { + return new Promise(resolve => { + (new PerformanceObserver(entryList => { + for (let entry of entryList.getEntries()) { + if (entry.url == url) { + resolve(entryList.getEntries()[0]); + } + } + })).observe({type: 'largest-contentful-paint', buffered: true}); + const img = new Image(); + img.id = 'image_id'; + img.src = url; + document.body.appendChild(img); + }); +}; diff --git a/blink/web_tests/webexposed/global-interface-listing-expected.txt b/blink/web_tests/webexposed/global-interface-listing-expected.txt index 87a9dce9b39e..51c58da6c2e7 100644 --- a/blink/web_tests/webexposed/global-interface-listing-expected.txt +++ b/blink/web_tests/webexposed/global-interface-listing-expected.txt @@ -4944,6 +4944,7 @@ interface KeyframeEffect : AnimationEffect interface LargestContentfulPaint : PerformanceEntry attribute @@toStringTag getter element + getter firstAnimatedFrameTime getter id getter loadTime getter renderTime