summaryrefslogtreecommitdiff
path: root/chromium/third_party/blink/renderer/core/paint/paint_timing_detector.h
blob: 701deaf780b2223ca5a81e62b412da99af731a75 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
// 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_; }
  // 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;
  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 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);
}

}  // namespace blink

#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_PAINT_PAINT_TIMING_DETECTOR_H_