| // Copyright 2018 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_PAINT_PAINT_TIMING_DETECTOR_H_ |
| #define THIRD_PARTY_BLINK_RENDERER_CORE_PAINT_PAINT_TIMING_DETECTOR_H_ |
| |
| #include "third_party/blink/public/common/input/web_input_event.h" |
| #include "third_party/blink/public/web/web_swap_result.h" |
| #include "third_party/blink/renderer/core/core_export.h" |
| #include "third_party/blink/renderer/core/layout/layout_box_model_object.h" |
| #include "third_party/blink/renderer/core/paint/paint_timing_visualizer.h" |
| #include "third_party/blink/renderer/core/scroll/scroll_types.h" |
| #include "third_party/blink/renderer/platform/graphics/paint/ignore_paint_timing_scope.h" |
| #include "third_party/blink/renderer/platform/heap/member.h" |
| #include "third_party/blink/renderer/platform/wtf/cross_thread_functional.h" |
| |
| namespace blink { |
| |
| class Image; |
| class ImagePaintTimingDetector; |
| class ImageResourceContent; |
| class LargestContentfulPaintCalculator; |
| class LayoutObject; |
| class LocalFrameView; |
| class PropertyTreeStateOrAlias; |
| class StyleFetchedImage; |
| class TextPaintTimingDetector; |
| |
| // |PaintTimingCallbackManager| is an interface between |
| // |ImagePaintTimingDetector|/|TextPaintTimingDetector| and |ChromeClient|. |
| // As |ChromeClient| is shared among the paint-timing-detecters, it |
| // makes it hard to test each detector without being affected other detectors. |
| // The interface, however, allows unit tests to mock |ChromeClient| for each |
| // detector. With the mock, |ImagePaintTimingDetector|'s callback does not need |
| // to store in the same queue as |TextPaintTimingDetector|'s. The separate |
| // queue makes it possible to pop an |ImagePaintTimingDetector|'s callback |
| // without having to popping the |TextPaintTimingDetector|'s. |
| class PaintTimingCallbackManager : public GarbageCollectedMixin { |
| public: |
| using LocalThreadCallback = base::OnceCallback<void(base::TimeTicks)>; |
| using CallbackQueue = std::queue<LocalThreadCallback>; |
| |
| virtual void RegisterCallback( |
| PaintTimingCallbackManager::LocalThreadCallback) = 0; |
| }; |
| |
| // This class is responsible for managing the swap-time callback for Largest |
| // Image Paint and Largest Text Paint. In frames where both text and image are |
| // painted, Largest Image Paint and Largest Text Paint need to assign the same |
| // paint-time for their records. In this case, |PaintTimeCallbackManager| |
| // requests a swap-time callback and share the swap-time with LIP and LTP. |
| // Otherwise LIP and LTP would have to request their own swap-time callbacks. |
| // An extra benefit of this design is that |LargestContentfulPaintCalculator| |
| // can thus hook to the end of the LIP and LTP's record assignments. |
| // |
| // |GarbageCollected| inheritance is required by the swap-time callback |
| // registration. |
| class PaintTimingCallbackManagerImpl final |
| : public GarbageCollected<PaintTimingCallbackManagerImpl>, |
| public PaintTimingCallbackManager { |
| public: |
| PaintTimingCallbackManagerImpl(LocalFrameView* frame_view) |
| : frame_view_(frame_view), |
| frame_callbacks_( |
| std::make_unique<std::queue< |
| PaintTimingCallbackManager::LocalThreadCallback>>()) {} |
| ~PaintTimingCallbackManagerImpl() { frame_callbacks_.reset(); } |
| |
| // Instead of registering the callback right away, this impl of the interface |
| // combine the callback into |frame_callbacks_| before registering a separate |
| // swap-time callback for the combined callbacks. When the swap-time callback |
| // is invoked, the swap-time is then assigned to each callback of |
| // |frame_callbacks_|. |
| void RegisterCallback( |
| PaintTimingCallbackManager::LocalThreadCallback callback) override { |
| frame_callbacks_->push(std::move(callback)); |
| } |
| |
| void RegisterPaintTimeCallbackForCombinedCallbacks(); |
| |
| inline size_t CountCallbacks() { return frame_callbacks_->size(); } |
| |
| void ReportPaintTime( |
| std::unique_ptr<std::queue< |
| PaintTimingCallbackManager::LocalThreadCallback>> frame_callbacks, |
| WebSwapResult, |
| base::TimeTicks paint_time); |
| |
| void Trace(Visitor* visitor) const override; |
| |
| private: |
| Member<LocalFrameView> frame_view_; |
| // |frame_callbacks_| stores the callbacks of |TextPaintTimingDetector| and |
| // |ImagePaintTimingDetector| in an (animated) frame. It is passed as an |
| // argument of a swap-time callback which once is invoked, invokes every |
| // callback in |frame_callbacks_|. This hierarchical callback design is to |
| // reduce the need of calling ChromeClient to register swap-time callbacks for |
| // both detectos. |
| // Although |frame_callbacks_| intends to store callbacks |
| // of a frame, it occasionally has to do that for more than one frame, when it |
| // fails to register a swap-time callback. |
| std::unique_ptr<PaintTimingCallbackManager::CallbackQueue> frame_callbacks_; |
| }; |
| |
| // PaintTimingDetector contains some of paint metric detectors, |
| // providing common infrastructure for these detectors. |
| // |
| // Users has to enable 'loading' trace category to enable the metrics. |
| // |
| // See also: |
| // https://docs.google.com/document/d/1DRVd4a2VU8-yyWftgOparZF-sf16daf0vfbsHuz2rws/edit |
| class CORE_EXPORT PaintTimingDetector |
| : public GarbageCollected<PaintTimingDetector> { |
| friend class ImagePaintTimingDetectorTest; |
| friend class TextPaintTimingDetectorTest; |
| |
| public: |
| PaintTimingDetector(LocalFrameView*); |
| |
| static void NotifyBackgroundImagePaint( |
| const Node&, |
| const Image&, |
| const StyleFetchedImage&, |
| const PropertyTreeStateOrAlias& current_paint_chunk_properties, |
| const IntRect& image_border); |
| static void NotifyImagePaint( |
| const LayoutObject&, |
| const IntSize& intrinsic_size, |
| const ImageResourceContent& cached_image, |
| const PropertyTreeStateOrAlias& current_paint_chunk_properties, |
| const IntRect& image_border); |
| inline static void NotifyTextPaint(const IntRect& text_visual_rect); |
| |
| void NotifyImageFinished(const LayoutObject&, const ImageResourceContent*); |
| void LayoutObjectWillBeDestroyed(const LayoutObject&); |
| void NotifyImageRemoved(const LayoutObject&, const ImageResourceContent*); |
| void NotifyPaintFinished(); |
| void NotifyInputEvent(WebInputEvent::Type); |
| bool NeedToNotifyInputOrScroll() const; |
| void NotifyScroll(mojom::blink::ScrollType); |
| |
| // The returned value indicates whether the candidates have changed. |
| // To compute experimental LCP (including removals) for images we need to know |
| // the time and size of removed images in order to account for cases where the |
| // largest image is removed while it is still loading: in this case, we would |
| // first update the experimental LCP size to be the image size, so we need to |
| // be able to decrease the size. To do this, the simplest way to achieve the |
| // correct results is to store the largest image removed which did receive a |
| // paint time. |
| bool NotifyIfChangedLargestImagePaint(base::TimeTicks image_paint_time, |
| uint64_t image_size, |
| base::TimeTicks removed_image_time, |
| uint64_t removed_image_size); |
| bool NotifyIfChangedLargestTextPaint(base::TimeTicks, uint64_t size); |
| |
| void DidChangePerformanceTiming(); |
| |
| inline static bool IsTracing() { |
| bool tracing_enabled; |
| TRACE_EVENT_CATEGORY_GROUP_ENABLED("loading", &tracing_enabled); |
| return tracing_enabled; |
| } |
| |
| FloatRect BlinkSpaceToDIPs(const FloatRect& float_rect) const; |
| FloatRect CalculateVisualRect(const IntRect& visual_rect, |
| const PropertyTreeStateOrAlias&) const; |
| |
| TextPaintTimingDetector* GetTextPaintTimingDetector() const { |
| DCHECK(text_paint_timing_detector_); |
| return text_paint_timing_detector_; |
| } |
| ImagePaintTimingDetector* GetImagePaintTimingDetector() const { |
| return image_paint_timing_detector_; |
| } |
| |
| LargestContentfulPaintCalculator* GetLargestContentfulPaintCalculator(); |
| |
| base::TimeTicks LargestImagePaint() const { |
| return largest_image_paint_time_; |
| } |
| uint64_t LargestImagePaintSize() const { return largest_image_paint_size_; } |
| base::TimeTicks LargestTextPaint() const { return largest_text_paint_time_; } |
| uint64_t LargestTextPaintSize() const { return largest_text_paint_size_; } |
| |
| base::TimeTicks LargestContentfulPaint() const { |
| return largest_contentful_paint_time_; |
| } |
| |
| // Experimental counterparts of the above methods. Currently these values are |
| // computed by looking at the largest content seen so far, but excluding |
| // content that is removed. |
| base::TimeTicks ExperimentalLargestImagePaint() const { |
| return experimental_largest_image_paint_time_; |
| } |
| uint64_t ExperimentalLargestImagePaintSize() const { |
| return experimental_largest_image_paint_size_; |
| } |
| base::TimeTicks ExperimentalLargestTextPaint() const { |
| return experimental_largest_text_paint_time_; |
| } |
| uint64_t ExperimentalLargestTextPaintSize() const { |
| return experimental_largest_text_paint_size_; |
| } |
| |
| base::TimeTicks FirstInputOrScrollNotifiedTimestamp() const { |
| return first_input_or_scroll_notified_timestamp_; |
| } |
| |
| void UpdateLargestContentfulPaintCandidate(); |
| |
| // Reports the largest image and text candidates painted under non-nested 0 |
| // opacity layer. |
| void ReportIgnoredContent(); |
| |
| base::Optional<PaintTimingVisualizer>& Visualizer() { return visualizer_; } |
| void Trace(Visitor* visitor) const; |
| |
| private: |
| // Method called to stop recording the Largest Contentful Paint. |
| void OnInputOrScroll(); |
| bool HasLargestImagePaintChanged(base::TimeTicks, uint64_t size) const; |
| bool HasLargestTextPaintChanged(base::TimeTicks, uint64_t size) const; |
| void UpdateLargestContentfulPaintTime(); |
| Member<LocalFrameView> frame_view_; |
| // This member lives forever because it is also used for Text Element Timing. |
| Member<TextPaintTimingDetector> text_paint_timing_detector_; |
| // This member lives until the end of the paint phase after the largest |
| // image paint is found. |
| Member<ImagePaintTimingDetector> image_paint_timing_detector_; |
| |
| // This member lives for as long as the largest contentful paint is being |
| // computed. However, it is initialized lazily, so it may be nullptr because |
| // it has not yet been initialized or because we have stopped computing LCP. |
| Member<LargestContentfulPaintCalculator> largest_contentful_paint_calculator_; |
| // Time at which the first input or scroll is notified to PaintTimingDetector, |
| // hence causing LCP to stop being recorded. This is the same time at which |
| // |largest_contentful_paint_calculator_| is set to nullptr. |
| base::TimeTicks first_input_or_scroll_notified_timestamp_; |
| |
| Member<PaintTimingCallbackManagerImpl> callback_manager_; |
| |
| base::Optional<PaintTimingVisualizer> visualizer_; |
| |
| base::TimeTicks largest_image_paint_time_; |
| uint64_t largest_image_paint_size_ = 0; |
| base::TimeTicks largest_text_paint_time_; |
| uint64_t largest_text_paint_size_ = 0; |
| base::TimeTicks largest_contentful_paint_time_; |
| |
| base::TimeTicks experimental_largest_image_paint_time_; |
| uint64_t experimental_largest_image_paint_size_ = 0; |
| base::TimeTicks experimental_largest_text_paint_time_; |
| uint64_t experimental_largest_text_paint_size_ = 0; |
| |
| bool is_recording_largest_contentful_paint_ = true; |
| }; |
| |
| // Largest Text Paint and Text Element Timing aggregate text nodes by these |
| // text nodes' ancestors. In order to tell whether a text node is contained by |
| // another node efficiently, The aggregation relies on the paint order of the |
| // rendering tree (https://www.w3.org/TR/CSS21/zindex.html). Because of the |
| // paint order, we can assume that if a text node T is visited during the visit |
| // of another node B, then B contains T. This class acts as the hook to certain |
| // container nodes (block object or inline object) to tell whether a text node |
| // is their descendant. The hook should be placed right before visiting the |
| // subtree of an container node, so that the constructor and the destructor can |
| // tell the start and end of the visit. |
| // TODO(crbug.com/960946): we should document the text aggregation. |
| class ScopedPaintTimingDetectorBlockPaintHook { |
| STACK_ALLOCATED(); |
| |
| public: |
| // This constructor does nothing by itself. It will only set relevant |
| // variables when EmplaceIfNeeded() is called successfully. The lifetime of |
| // the object helps keeping the lifetime of |reset_top_| and |data_| to the |
| // appropriate scope. |
| ScopedPaintTimingDetectorBlockPaintHook() {} |
| ScopedPaintTimingDetectorBlockPaintHook( |
| const ScopedPaintTimingDetectorBlockPaintHook&) = delete; |
| ScopedPaintTimingDetectorBlockPaintHook& operator=( |
| const ScopedPaintTimingDetectorBlockPaintHook&) = delete; |
| |
| void EmplaceIfNeeded(const LayoutBoxModelObject&, |
| const PropertyTreeStateOrAlias&); |
| ~ScopedPaintTimingDetectorBlockPaintHook(); |
| |
| private: |
| friend class PaintTimingDetector; |
| inline static void AggregateTextPaint(const IntRect& visual_rect) { |
| // Ideally we'd assert that |top_| exists, but there may be text nodes that |
| // do not have an ancestor non-anonymous block layout objects in the layout |
| // tree. An example of this is a multicol div, since the |
| // LayoutMultiColumnFlowThread is in a different layer from the DIV. In |
| // these cases, |top_| will be null. This is a known bug, see the related |
| // crbug.com/933479. |
| if (top_ && top_->data_) |
| top_->data_->aggregated_visual_rect_.Unite(visual_rect); |
| } |
| |
| base::Optional<base::AutoReset<ScopedPaintTimingDetectorBlockPaintHook*>> |
| reset_top_; |
| struct Data { |
| STACK_ALLOCATED(); |
| |
| public: |
| Data(const LayoutBoxModelObject& aggregator, |
| const PropertyTreeStateOrAlias&, |
| TextPaintTimingDetector*); |
| |
| const LayoutBoxModelObject& aggregator_; |
| const PropertyTreeStateOrAlias& property_tree_state_; |
| TextPaintTimingDetector* detector_; |
| IntRect aggregated_visual_rect_; |
| }; |
| base::Optional<Data> data_; |
| static ScopedPaintTimingDetectorBlockPaintHook* top_; |
| }; |
| |
| // static |
| inline void PaintTimingDetector::NotifyTextPaint( |
| const IntRect& text_visual_rect) { |
| if (IgnorePaintTimingScope::ShouldIgnore()) |
| return; |
| ScopedPaintTimingDetectorBlockPaintHook::AggregateTextPaint(text_visual_rect); |
| } |
| |
| class LCPRectInfo { |
| public: |
| LCPRectInfo(IntRect frame_rect_info, IntRect root_rect_info) |
| : frame_rect_info_(frame_rect_info), root_rect_info_(root_rect_info) {} |
| |
| void OutputToTraceValue(TracedValue& value) { |
| value.SetInteger("frame_x", frame_rect_info_.X()); |
| value.SetInteger("frame_y", frame_rect_info_.Y()); |
| value.SetInteger("frame_width", frame_rect_info_.Width()); |
| value.SetInteger("frame_height", frame_rect_info_.Height()); |
| value.SetInteger("root_x", root_rect_info_.X()); |
| value.SetInteger("root_y", root_rect_info_.Y()); |
| value.SetInteger("root_width", root_rect_info_.Width()); |
| value.SetInteger("root_height", root_rect_info_.Height()); |
| } |
| |
| private: |
| IntRect frame_rect_info_; |
| IntRect root_rect_info_; |
| }; |
| |
| } // namespace blink |
| |
| #endif // THIRD_PARTY_BLINK_RENDERER_CORE_PAINT_PAINT_TIMING_DETECTOR_H_ |