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