1 // Copyright 2018 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_PAINT_PAINT_TIMING_DETECTOR_H_
6 #define THIRD_PARTY_BLINK_RENDERER_CORE_PAINT_PAINT_TIMING_DETECTOR_H_
7 
8 #include "third_party/blink/public/common/input/web_input_event.h"
9 #include "third_party/blink/public/web/web_swap_result.h"
10 #include "third_party/blink/renderer/core/core_export.h"
11 #include "third_party/blink/renderer/core/layout/layout_box_model_object.h"
12 #include "third_party/blink/renderer/core/paint/paint_timing_visualizer.h"
13 #include "third_party/blink/renderer/core/scroll/scroll_types.h"
14 #include "third_party/blink/renderer/platform/graphics/paint/ignore_paint_timing_scope.h"
15 #include "third_party/blink/renderer/platform/heap/member.h"
16 #include "third_party/blink/renderer/platform/wtf/cross_thread_functional.h"
17 
18 namespace blink {
19 
20 class Image;
21 class ImagePaintTimingDetector;
22 class ImageResourceContent;
23 class LargestContentfulPaintCalculator;
24 class LayoutObject;
25 class LocalFrameView;
26 class PropertyTreeStateOrAlias;
27 class StyleFetchedImage;
28 class TextPaintTimingDetector;
29 
30 // |PaintTimingCallbackManager| is an interface between
31 // |ImagePaintTimingDetector|/|TextPaintTimingDetector| and |ChromeClient|.
32 // As |ChromeClient| is shared among the paint-timing-detecters, it
33 // makes it hard to test each detector without being affected other detectors.
34 // The interface, however, allows unit tests to mock |ChromeClient| for each
35 // detector. With the mock, |ImagePaintTimingDetector|'s callback does not need
36 // to store in the same queue as |TextPaintTimingDetector|'s. The separate
37 // queue makes it possible to pop an |ImagePaintTimingDetector|'s callback
38 // without having to popping the |TextPaintTimingDetector|'s.
39 class PaintTimingCallbackManager : public GarbageCollectedMixin {
40  public:
41   using LocalThreadCallback = base::OnceCallback<void(base::TimeTicks)>;
42   using CallbackQueue = std::queue<LocalThreadCallback>;
43 
44   virtual void RegisterCallback(
45       PaintTimingCallbackManager::LocalThreadCallback) = 0;
46 };
47 
48 // This class is responsible for managing the swap-time callback for Largest
49 // Image Paint and Largest Text Paint. In frames where both text and image are
50 // painted, Largest Image Paint and Largest Text Paint need to assign the same
51 // paint-time for their records. In this case, |PaintTimeCallbackManager|
52 // requests a swap-time callback and share the swap-time with LIP and LTP.
53 // Otherwise LIP and LTP would have to request their own swap-time callbacks.
54 // An extra benefit of this design is that |LargestContentfulPaintCalculator|
55 // can thus hook to the end of the LIP and LTP's record assignments.
56 //
57 // |GarbageCollected| inheritance is required by the swap-time callback
58 // registration.
59 class PaintTimingCallbackManagerImpl final
60     : public GarbageCollected<PaintTimingCallbackManagerImpl>,
61       public PaintTimingCallbackManager {
62  public:
PaintTimingCallbackManagerImpl(LocalFrameView * frame_view)63   PaintTimingCallbackManagerImpl(LocalFrameView* frame_view)
64       : frame_view_(frame_view),
65         frame_callbacks_(
66             std::make_unique<std::queue<
67                 PaintTimingCallbackManager::LocalThreadCallback>>()) {}
~PaintTimingCallbackManagerImpl()68   ~PaintTimingCallbackManagerImpl() { frame_callbacks_.reset(); }
69 
70   // Instead of registering the callback right away, this impl of the interface
71   // combine the callback into |frame_callbacks_| before registering a separate
72   // swap-time callback for the combined callbacks. When the swap-time callback
73   // is invoked, the swap-time is then assigned to each callback of
74   // |frame_callbacks_|.
RegisterCallback(PaintTimingCallbackManager::LocalThreadCallback callback)75   void RegisterCallback(
76       PaintTimingCallbackManager::LocalThreadCallback callback) override {
77     frame_callbacks_->push(std::move(callback));
78   }
79 
80   void RegisterPaintTimeCallbackForCombinedCallbacks();
81 
CountCallbacks()82   inline size_t CountCallbacks() { return frame_callbacks_->size(); }
83 
84   void ReportPaintTime(
85       std::unique_ptr<std::queue<
86           PaintTimingCallbackManager::LocalThreadCallback>> frame_callbacks,
87       WebSwapResult,
88       base::TimeTicks paint_time);
89 
90   void Trace(Visitor* visitor) const override;
91 
92  private:
93   Member<LocalFrameView> frame_view_;
94   // |frame_callbacks_| stores the callbacks of |TextPaintTimingDetector| and
95   // |ImagePaintTimingDetector| in an (animated) frame. It is passed as an
96   // argument of a swap-time callback which once is invoked, invokes every
97   // callback in |frame_callbacks_|. This hierarchical callback design is to
98   // reduce the need of calling ChromeClient to register swap-time callbacks for
99   // both detectos.
100   // Although |frame_callbacks_| intends to store callbacks
101   // of a frame, it occasionally has to do that for more than one frame, when it
102   // fails to register a swap-time callback.
103   std::unique_ptr<PaintTimingCallbackManager::CallbackQueue> frame_callbacks_;
104 };
105 
106 // PaintTimingDetector contains some of paint metric detectors,
107 // providing common infrastructure for these detectors.
108 //
109 // Users has to enable 'loading' trace category to enable the metrics.
110 //
111 // See also:
112 // https://docs.google.com/document/d/1DRVd4a2VU8-yyWftgOparZF-sf16daf0vfbsHuz2rws/edit
113 class CORE_EXPORT PaintTimingDetector
114     : public GarbageCollected<PaintTimingDetector> {
115   friend class ImagePaintTimingDetectorTest;
116   friend class TextPaintTimingDetectorTest;
117 
118  public:
119   PaintTimingDetector(LocalFrameView*);
120 
121   static void NotifyBackgroundImagePaint(
122       const Node*,
123       const Image*,
124       const StyleFetchedImage*,
125       const PropertyTreeStateOrAlias& current_paint_chunk_properties,
126       const IntRect& image_border);
127   static void NotifyImagePaint(
128       const LayoutObject&,
129       const IntSize& intrinsic_size,
130       const ImageResourceContent* cached_image,
131       const PropertyTreeStateOrAlias& current_paint_chunk_properties,
132       const IntRect& image_border);
133   inline static void NotifyTextPaint(const IntRect& text_visual_rect);
134 
135   void NotifyImageFinished(const LayoutObject&, const ImageResourceContent*);
136   void LayoutObjectWillBeDestroyed(const LayoutObject&);
137   void NotifyImageRemoved(const LayoutObject&, const ImageResourceContent*);
138   void NotifyPaintFinished();
139   void NotifyInputEvent(WebInputEvent::Type);
140   bool NeedToNotifyInputOrScroll() const;
141   void NotifyScroll(mojom::blink::ScrollType);
142 
143   // The returned value indicates whether the candidates have changed.
144   // To compute experimental LCP (including removals) for images we need to know
145   // the time and size of removed images in order to account for cases where the
146   // largest image is removed while it is still loading: in this case, we would
147   // first update the experimental LCP size to be the image size, so we need to
148   // be able to decrease the size. To do this, the simplest way to achieve the
149   // correct results is to store the largest image removed which did receive a
150   // paint time.
151   bool NotifyIfChangedLargestImagePaint(base::TimeTicks image_paint_time,
152                                         uint64_t image_size,
153                                         base::TimeTicks removed_image_time,
154                                         uint64_t removed_image_size);
155   bool NotifyIfChangedLargestTextPaint(base::TimeTicks, uint64_t size);
156 
157   void DidChangePerformanceTiming();
158 
IsTracing()159   inline static bool IsTracing() {
160     bool tracing_enabled;
161     TRACE_EVENT_CATEGORY_GROUP_ENABLED("loading", &tracing_enabled);
162     return tracing_enabled;
163   }
164 
165   FloatRect BlinkSpaceToDIPs(const FloatRect& float_rect) const;
166   FloatRect CalculateVisualRect(const IntRect& visual_rect,
167                                 const PropertyTreeStateOrAlias&) const;
168 
GetTextPaintTimingDetector()169   TextPaintTimingDetector* GetTextPaintTimingDetector() const {
170     DCHECK(text_paint_timing_detector_);
171     return text_paint_timing_detector_;
172   }
GetImagePaintTimingDetector()173   ImagePaintTimingDetector* GetImagePaintTimingDetector() const {
174     return image_paint_timing_detector_;
175   }
176 
177   LargestContentfulPaintCalculator* GetLargestContentfulPaintCalculator();
178 
LargestImagePaint()179   base::TimeTicks LargestImagePaint() const {
180     return largest_image_paint_time_;
181   }
LargestImagePaintSize()182   uint64_t LargestImagePaintSize() const { return largest_image_paint_size_; }
LargestTextPaint()183   base::TimeTicks LargestTextPaint() const { return largest_text_paint_time_; }
LargestTextPaintSize()184   uint64_t LargestTextPaintSize() const { return largest_text_paint_size_; }
185   // Experimental counterparts of the above methods. Currently these values are
186   // computed by looking at the largest content seen so far, but excluding
187   // content that is removed.
ExperimentalLargestImagePaint()188   base::TimeTicks ExperimentalLargestImagePaint() const {
189     return experimental_largest_image_paint_time_;
190   }
ExperimentalLargestImagePaintSize()191   uint64_t ExperimentalLargestImagePaintSize() const {
192     return experimental_largest_image_paint_size_;
193   }
ExperimentalLargestTextPaint()194   base::TimeTicks ExperimentalLargestTextPaint() const {
195     return experimental_largest_text_paint_time_;
196   }
ExperimentalLargestTextPaintSize()197   uint64_t ExperimentalLargestTextPaintSize() const {
198     return experimental_largest_text_paint_size_;
199   }
200 
FirstInputOrScrollNotifiedTimestamp()201   base::TimeTicks FirstInputOrScrollNotifiedTimestamp() const {
202     return first_input_or_scroll_notified_timestamp_;
203   }
204 
205   void UpdateLargestContentfulPaintCandidate();
206 
207   // Reports the largest image and text candidates painted under non-nested 0
208   // opacity layer.
209   void ReportIgnoredContent();
210 
Visualizer()211   base::Optional<PaintTimingVisualizer>& Visualizer() { return visualizer_; }
212   void Trace(Visitor* visitor) const;
213 
214  private:
215   // Method called to stop recording the Largest Contentful Paint.
216   void OnInputOrScroll();
217   bool HasLargestImagePaintChanged(base::TimeTicks, uint64_t size) const;
218   bool HasLargestTextPaintChanged(base::TimeTicks, uint64_t size) const;
219   Member<LocalFrameView> frame_view_;
220   // This member lives forever because it is also used for Text Element Timing.
221   Member<TextPaintTimingDetector> text_paint_timing_detector_;
222   // This member lives until the end of the paint phase after the largest
223   // image paint is found.
224   Member<ImagePaintTimingDetector> image_paint_timing_detector_;
225 
226   // This member lives for as long as the largest contentful paint is being
227   // computed. However, it is initialized lazily, so it may be nullptr because
228   // it has not yet been initialized or because we have stopped computing LCP.
229   Member<LargestContentfulPaintCalculator> largest_contentful_paint_calculator_;
230   // Time at which the first input or scroll is notified to PaintTimingDetector,
231   // hence causing LCP to stop being recorded. This is the same time at which
232   // |largest_contentful_paint_calculator_| is set to nullptr.
233   base::TimeTicks first_input_or_scroll_notified_timestamp_;
234 
235   Member<PaintTimingCallbackManagerImpl> callback_manager_;
236 
237   base::Optional<PaintTimingVisualizer> visualizer_;
238 
239   base::TimeTicks largest_image_paint_time_;
240   uint64_t largest_image_paint_size_ = 0;
241   base::TimeTicks largest_text_paint_time_;
242   uint64_t largest_text_paint_size_ = 0;
243 
244   base::TimeTicks experimental_largest_image_paint_time_;
245   uint64_t experimental_largest_image_paint_size_ = 0;
246   base::TimeTicks experimental_largest_text_paint_time_;
247   uint64_t experimental_largest_text_paint_size_ = 0;
248 
249   bool is_recording_largest_contentful_paint_ = true;
250 };
251 
252 // Largest Text Paint and Text Element Timing aggregate text nodes by these
253 // text nodes' ancestors. In order to tell whether a text node is contained by
254 // another node efficiently, The aggregation relies on the paint order of the
255 // rendering tree (https://www.w3.org/TR/CSS21/zindex.html). Because of the
256 // paint order, we can assume that if a text node T is visited during the visit
257 // of another node B, then B contains T. This class acts as the hook to certain
258 // container nodes (block object or inline object) to tell whether a text node
259 // is their descendant. The hook should be placed right before visiting the
260 // subtree of an container node, so that the constructor and the destructor can
261 // tell the start and end of the visit.
262 // TODO(crbug.com/960946): we should document the text aggregation.
263 class ScopedPaintTimingDetectorBlockPaintHook {
264   STACK_ALLOCATED();
265 
266  public:
267   // This constructor does nothing by itself. It will only set relevant
268   // variables when EmplaceIfNeeded() is called successfully. The lifetime of
269   // the object helps keeping the lifetime of |reset_top_| and |data_| to the
270   // appropriate scope.
ScopedPaintTimingDetectorBlockPaintHook()271   ScopedPaintTimingDetectorBlockPaintHook() {}
272   ScopedPaintTimingDetectorBlockPaintHook(
273       const ScopedPaintTimingDetectorBlockPaintHook&) = delete;
274   ScopedPaintTimingDetectorBlockPaintHook& operator=(
275       const ScopedPaintTimingDetectorBlockPaintHook&) = delete;
276 
277   void EmplaceIfNeeded(const LayoutBoxModelObject&,
278                        const PropertyTreeStateOrAlias&);
279   ~ScopedPaintTimingDetectorBlockPaintHook();
280 
281  private:
282   friend class PaintTimingDetector;
AggregateTextPaint(const IntRect & visual_rect)283   inline static void AggregateTextPaint(const IntRect& visual_rect) {
284     // Ideally we'd assert that |top_| exists, but there may be text nodes that
285     // do not have an ancestor non-anonymous block layout objects in the layout
286     // tree. An example of this is a multicol div, since the
287     // LayoutMultiColumnFlowThread is in a different layer from the DIV. In
288     // these cases, |top_| will be null. This is a known bug, see the related
289     // crbug.com/933479.
290     if (top_ && top_->data_)
291       top_->data_->aggregated_visual_rect_.Unite(visual_rect);
292   }
293 
294   base::Optional<base::AutoReset<ScopedPaintTimingDetectorBlockPaintHook*>>
295       reset_top_;
296   struct Data {
297     STACK_ALLOCATED();
298 
299    public:
300     Data(const LayoutBoxModelObject& aggregator,
301          const PropertyTreeStateOrAlias&,
302          TextPaintTimingDetector*);
303 
304     const LayoutBoxModelObject& aggregator_;
305     const PropertyTreeStateOrAlias& property_tree_state_;
306     TextPaintTimingDetector* detector_;
307     IntRect aggregated_visual_rect_;
308   };
309   base::Optional<Data> data_;
310   static ScopedPaintTimingDetectorBlockPaintHook* top_;
311 };
312 
313 // static
NotifyTextPaint(const IntRect & text_visual_rect)314 inline void PaintTimingDetector::NotifyTextPaint(
315     const IntRect& text_visual_rect) {
316   if (IgnorePaintTimingScope::ShouldIgnore())
317     return;
318   ScopedPaintTimingDetectorBlockPaintHook::AggregateTextPaint(text_visual_rect);
319 }
320 
321 }  // namespace blink
322 
323 #endif  // THIRD_PARTY_BLINK_RENDERER_CORE_PAINT_PAINT_TIMING_DETECTOR_H_
324