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/heap/member.h"
15 #include "third_party/blink/renderer/platform/wtf/cross_thread_functional.h"
16
17 namespace blink {
18
19 class Image;
20 class ImagePaintTimingDetector;
21 class ImageResourceContent;
22 class LargestContentfulPaintCalculator;
23 class LayoutObject;
24 class LocalFrameView;
25 class PropertyTreeState;
26 class StyleFetchedImage;
27 class TextPaintTimingDetector;
28 struct WebFloatRect;
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 USING_GARBAGE_COLLECTED_MIXIN(PaintTimingCallbackManagerImpl);
63
64 public:
PaintTimingCallbackManagerImpl(LocalFrameView * frame_view)65 PaintTimingCallbackManagerImpl(LocalFrameView* frame_view)
66 : frame_view_(frame_view),
67 frame_callbacks_(
68 std::make_unique<std::queue<
69 PaintTimingCallbackManager::LocalThreadCallback>>()) {}
~PaintTimingCallbackManagerImpl()70 ~PaintTimingCallbackManagerImpl() { frame_callbacks_.reset(); }
71
72 // Instead of registering the callback right away, this impl of the interface
73 // combine the callback into |frame_callbacks_| before registering a separate
74 // swap-time callback for the combined callbacks. When the swap-time callback
75 // is invoked, the swap-time is then assigned to each callback of
76 // |frame_callbacks_|.
RegisterCallback(PaintTimingCallbackManager::LocalThreadCallback callback)77 void RegisterCallback(
78 PaintTimingCallbackManager::LocalThreadCallback callback) override {
79 frame_callbacks_->push(std::move(callback));
80 }
81
82 void RegisterPaintTimeCallbackForCombinedCallbacks();
83
CountCallbacks()84 inline size_t CountCallbacks() { return frame_callbacks_->size(); }
85
86 void ReportPaintTime(
87 std::unique_ptr<std::queue<
88 PaintTimingCallbackManager::LocalThreadCallback>> frame_callbacks,
89 WebSwapResult,
90 base::TimeTicks paint_time);
91
92 void Trace(Visitor* visitor) override;
93
94 private:
95 Member<LocalFrameView> frame_view_;
96 // |frame_callbacks_| stores the callbacks of |TextPaintTimingDetector| and
97 // |ImagePaintTimingDetector| in an (animated) frame. It is passed as an
98 // argument of a swap-time callback which once is invoked, invokes every
99 // callback in |frame_callbacks_|. This hierarchical callback design is to
100 // reduce the need of calling ChromeClient to register swap-time callbacks for
101 // both detectos.
102 // Although |frame_callbacks_| intends to store callbacks
103 // of a frame, it occasionally has to do that for more than one frame, when it
104 // fails to register a swap-time callback.
105 std::unique_ptr<PaintTimingCallbackManager::CallbackQueue> frame_callbacks_;
106 };
107
108 // PaintTimingDetector contains some of paint metric detectors,
109 // providing common infrastructure for these detectors.
110 //
111 // Users has to enable 'loading' trace category to enable the metrics.
112 //
113 // See also:
114 // https://docs.google.com/document/d/1DRVd4a2VU8-yyWftgOparZF-sf16daf0vfbsHuz2rws/edit
115 class CORE_EXPORT PaintTimingDetector
116 : public GarbageCollected<PaintTimingDetector> {
117 friend class ImagePaintTimingDetectorTest;
118 friend class TextPaintTimingDetectorTest;
119
120 public:
121 PaintTimingDetector(LocalFrameView*);
122
123 static void NotifyBackgroundImagePaint(
124 const Node*,
125 const Image*,
126 const StyleFetchedImage*,
127 const PropertyTreeState& current_paint_chunk_properties,
128 const IntRect& image_border);
129 static void NotifyImagePaint(
130 const LayoutObject&,
131 const IntSize& intrinsic_size,
132 const ImageResourceContent* cached_image,
133 const PropertyTreeState& current_paint_chunk_properties);
134 inline static void NotifyTextPaint(const IntRect& text_visual_rect);
135
136 void NotifyImageFinished(const LayoutObject&, const ImageResourceContent*);
137 void LayoutObjectWillBeDestroyed(const LayoutObject&);
138 void NotifyImageRemoved(const LayoutObject&, const ImageResourceContent*);
139 void NotifyPaintFinished();
140 void NotifyInputEvent(WebInputEvent::Type);
141 bool NeedToNotifyInputOrScroll() const;
142 void NotifyScroll(mojom::blink::ScrollType);
143 // The returned value indicates whether the candidates have changed.
144 bool NotifyIfChangedLargestImagePaint(base::TimeTicks, uint64_t size);
145 bool NotifyIfChangedLargestTextPaint(base::TimeTicks, uint64_t size);
146
147 void DidChangePerformanceTiming();
148
IsTracing()149 inline static bool IsTracing() {
150 bool tracing_enabled;
151 TRACE_EVENT_CATEGORY_GROUP_ENABLED("loading", &tracing_enabled);
152 return tracing_enabled;
153 }
154
155 void ConvertViewportToWindow(WebFloatRect* float_rect) const;
156 FloatRect CalculateVisualRect(const IntRect& visual_rect,
157 const PropertyTreeState&) const;
158
GetTextPaintTimingDetector()159 TextPaintTimingDetector* GetTextPaintTimingDetector() const {
160 DCHECK(text_paint_timing_detector_);
161 return text_paint_timing_detector_;
162 }
GetImagePaintTimingDetector()163 ImagePaintTimingDetector* GetImagePaintTimingDetector() const {
164 return image_paint_timing_detector_;
165 }
166
167 LargestContentfulPaintCalculator* GetLargestContentfulPaintCalculator();
168
LargestImagePaint()169 base::TimeTicks LargestImagePaint() const {
170 return largest_image_paint_time_;
171 }
LargestImagePaintSize()172 uint64_t LargestImagePaintSize() const { return largest_image_paint_size_; }
LargestTextPaint()173 base::TimeTicks LargestTextPaint() const { return largest_text_paint_time_; }
LargestTextPaintSize()174 uint64_t LargestTextPaintSize() const { return largest_text_paint_size_; }
FirstInputOrScrollNotifiedTimestamp()175 base::TimeTicks FirstInputOrScrollNotifiedTimestamp() const {
176 return first_input_or_scroll_notified_timestamp_;
177 }
178
179 void UpdateLargestContentfulPaintCandidate();
180
Visualizer()181 base::Optional<PaintTimingVisualizer>& Visualizer() { return visualizer_; }
182 void Trace(Visitor* visitor);
183
184 private:
185 // Method called to stop recording the Largest Contentful Paint.
186 void OnInputOrScroll();
187 bool HasLargestImagePaintChanged(base::TimeTicks, uint64_t size) const;
188 bool HasLargestTextPaintChanged(base::TimeTicks, uint64_t size) const;
189 Member<LocalFrameView> frame_view_;
190 // This member lives forever because it is also used for Text Element Timing.
191 Member<TextPaintTimingDetector> text_paint_timing_detector_;
192 // This member lives until the end of the paint phase after the largest
193 // image paint is found.
194 Member<ImagePaintTimingDetector> image_paint_timing_detector_;
195
196 // This member lives for as long as the largest contentful paint is being
197 // computed. However, it is initialized lazily, so it may be nullptr because
198 // it has not yet been initialized or because we have stopped computing LCP.
199 Member<LargestContentfulPaintCalculator> largest_contentful_paint_calculator_;
200 // Time at which the first input or scroll is notified to PaintTimingDetector,
201 // hence causing LCP to stop being recorded. This is the same time at which
202 // |largest_contentful_paint_calculator_| is set to nullptr.
203 base::TimeTicks first_input_or_scroll_notified_timestamp_;
204
205 Member<PaintTimingCallbackManagerImpl> callback_manager_;
206
207 base::Optional<PaintTimingVisualizer> visualizer_;
208
209 // Largest image information.
210 base::TimeTicks largest_image_paint_time_;
211 uint64_t largest_image_paint_size_ = 0;
212 // Largest text information.
213 base::TimeTicks largest_text_paint_time_;
214 uint64_t largest_text_paint_size_ = 0;
215 bool is_recording_largest_contentful_paint_ = true;
216 };
217
218 // Largest Text Paint and Text Element Timing aggregate text nodes by these
219 // text nodes' ancestors. In order to tell whether a text node is contained by
220 // another node efficiently, The aggregation relies on the paint order of the
221 // rendering tree (https://www.w3.org/TR/CSS21/zindex.html). Because of the
222 // paint order, we can assume that if a text node T is visited during the visit
223 // of another node B, then B contains T. This class acts as the hook to certain
224 // container nodes (block object or inline object) to tell whether a text node
225 // is their descendant. The hook should be placed right before visiting the
226 // subtree of an container node, so that the constructor and the destructor can
227 // tell the start and end of the visit.
228 // TODO(crbug.com/960946): we should document the text aggregation.
229 class ScopedPaintTimingDetectorBlockPaintHook {
230 STACK_ALLOCATED();
231
232 public:
233 // This constructor does nothing by itself. It will only set relevant
234 // variables when EmplaceIfNeeded() is called successfully. The lifetime of
235 // the object helps keeping the lifetime of |reset_top_| and |data_| to the
236 // appropriate scope.
ScopedPaintTimingDetectorBlockPaintHook()237 ScopedPaintTimingDetectorBlockPaintHook() {}
238
239 void EmplaceIfNeeded(const LayoutBoxModelObject&, const PropertyTreeState&);
240 ~ScopedPaintTimingDetectorBlockPaintHook();
241
242 private:
243 friend class PaintTimingDetector;
AggregateTextPaint(const IntRect & visual_rect)244 inline static void AggregateTextPaint(const IntRect& visual_rect) {
245 // Ideally we'd assert that |top_| exists, but there may be text nodes that
246 // do not have an ancestor non-anonymous block layout objects in the layout
247 // tree. An example of this is a multicol div, since the
248 // LayoutMultiColumnFlowThread is in a different layer from the DIV. In
249 // these cases, |top_| will be null. This is a known bug, see the related
250 // crbug.com/933479.
251 if (top_ && top_->data_)
252 top_->data_->aggregated_visual_rect_.Unite(visual_rect);
253 }
254
255 base::Optional<base::AutoReset<ScopedPaintTimingDetectorBlockPaintHook*>>
256 reset_top_;
257 struct Data {
258 STACK_ALLOCATED();
259
260 public:
261 Data(const LayoutBoxModelObject& aggregator,
262 const PropertyTreeState&,
263 TextPaintTimingDetector*);
264
265 const LayoutBoxModelObject& aggregator_;
266 const PropertyTreeState& property_tree_state_;
267 TextPaintTimingDetector* detector_;
268 IntRect aggregated_visual_rect_;
269 };
270 base::Optional<Data> data_;
271 static ScopedPaintTimingDetectorBlockPaintHook* top_;
272
273 DISALLOW_COPY_AND_ASSIGN(ScopedPaintTimingDetectorBlockPaintHook);
274 };
275
276 // Creates a scope to ignore paint timing, e.g. when we are painting contents
277 // under opacity:0.
278 class IgnorePaintTimingScope {
279 STACK_ALLOCATED();
280
281 public:
IgnorePaintTimingScope()282 IgnorePaintTimingScope() : auto_reset_(&should_ignore_, true) {}
283 ~IgnorePaintTimingScope() = default;
284
ShouldIgnore()285 static bool ShouldIgnore() { return should_ignore_; }
286
287 private:
288 base::AutoReset<bool> auto_reset_;
289 static bool should_ignore_;
290 };
291
292 // static
NotifyTextPaint(const IntRect & text_visual_rect)293 inline void PaintTimingDetector::NotifyTextPaint(
294 const IntRect& text_visual_rect) {
295 if (IgnorePaintTimingScope::ShouldIgnore())
296 return;
297 ScopedPaintTimingDetectorBlockPaintHook::AggregateTextPaint(text_visual_rect);
298 }
299
300 } // namespace blink
301
302 #endif // THIRD_PARTY_BLINK_RENDERER_CORE_PAINT_PAINT_TIMING_DETECTOR_H_
303