summaryrefslogtreecommitdiff
path: root/chromium/third_party/blink/renderer/core/paint/image_paint_timing_detector.h
blob: 802c124f9c2235e8a37f6ad04c948d66c40aaee2 (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
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
// 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.

// 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_IMAGE_PAINT_TIMING_DETECTOR_H_
#define THIRD_PARTY_BLINK_RENDERER_CORE_PAINT_IMAGE_PAINT_TIMING_DETECTOR_H_

#include "base/memory/weak_ptr.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "third_party/blink/renderer/core/dom/dom_node_ids.h"
#include "third_party/blink/renderer/core/loader/resource/image_resource_content.h"
#include "third_party/blink/renderer/core/paint/paint_timing_detector.h"
#include "third_party/blink/renderer/platform/wtf/cross_thread_functional.h"
#include "third_party/blink/renderer/platform/wtf/hash_set.h"

namespace blink {

class LayoutObject;
class LocalFrameView;
class PropertyTreeStateOrAlias;
class TracedValue;
class Image;

// TODO(crbug/960502): we should limit the access of these properties.
class ImageRecord : public base::SupportsWeakPtr<ImageRecord> {
 public:
  ImageRecord(DOMNodeId new_node_id,
              const ImageResourceContent* new_cached_image,
              uint64_t new_first_size,
              const IntRect& frame_visual_rect,
              const FloatRect& root_visual_rect)
      : node_id(new_node_id),
        cached_image(new_cached_image),
        first_size(new_first_size) {
    static unsigned next_insertion_index_ = 1;
    insertion_index = next_insertion_index_++;
    if (PaintTimingVisualizer::IsTracingEnabled()) {
      lcp_rect_info_ = std::make_unique<LCPRectInfo>(
          frame_visual_rect, RoundedIntRect(root_visual_rect));
    }
  }

  ImageRecord() {}

  DOMNodeId node_id = kInvalidDOMNodeId;
  WeakPersistent<const ImageResourceContent> cached_image;
  // Mind that |first_size| has to be assigned before pusing to
  // |size_ordered_set_| since it's the sorting key.
  uint64_t first_size = 0;
  unsigned frame_index = 0;
  unsigned insertion_index;
  // The time of the first paint after fully loaded. 0 means not painted yet.
  base::TimeTicks paint_time = base::TimeTicks();
  base::TimeTicks load_time = base::TimeTicks();
  bool loaded = false;
  // LCP rect information, only populated when tracing is enabled.
  std::unique_ptr<LCPRectInfo> lcp_rect_info_;
};

typedef std::pair<const LayoutObject*, const ImageResourceContent*> RecordId;

// |ImageRecordsManager| is the manager of all of the images that Largest Image
// Paint cares about. Note that an image does not necessarily correspond to a
// node; it can also be one of the background images attached to a node.
// |ImageRecordsManager| encapsulates the logic of |ImageRecord| handling,
// providing interface for the external world to handle it in the language of
// Node, LayoutObject, etc.
class CORE_EXPORT ImageRecordsManager {
  friend class ImagePaintTimingDetectorTest;
  DISALLOW_NEW();

  using NodesQueueComparator = bool (*)(const base::WeakPtr<ImageRecord>&,
                                        const base::WeakPtr<ImageRecord>&);
  using ImageRecordSet =
      std::set<base::WeakPtr<ImageRecord>, NodesQueueComparator>;

 public:
  explicit ImageRecordsManager(LocalFrameView*);
  ImageRecordsManager(const ImageRecordsManager&) = delete;
  ImageRecordsManager& operator=(const ImageRecordsManager&) = delete;
  ImageRecord* FindLargestPaintCandidate() const;

  inline void RemoveInvisibleRecordIfNeeded(const RecordId& record_id) {
    invisible_images_.erase(record_id);
  }

  inline void RemoveImageFinishedRecord(const RecordId& record_id) {
    image_finished_times_.erase(record_id);
  }

  inline void RemoveVisibleRecord(const RecordId& record_id) {
    base::WeakPtr<ImageRecord> record =
        visible_images_.find(record_id)->value->AsWeakPtr();
    if (!record->paint_time.is_null()) {
      DCHECK_GT(record->first_size, 0u);
      if (record->first_size > largest_removed_image_size_) {
        largest_removed_image_size_ = record->first_size;
        largest_removed_image_paint_time_ = record->paint_time;
      } else if (record->first_size == largest_removed_image_size_) {
        // Ensure we use the lower timestamp in the case of a tie.
        DCHECK(!largest_removed_image_paint_time_.is_null());
        largest_removed_image_paint_time_ =
            std::min(largest_removed_image_paint_time_, record->paint_time);
      }
    }
    size_ordered_set_.erase(record);
    visible_images_.erase(record_id);
    // Leave out |images_queued_for_paint_time_| intentionally because the null
    // record will be removed in |AssignPaintTimeToRegisteredQueuedRecords|.
  }

  inline void RecordInvisible(const RecordId& record_id) {
    invisible_images_.insert(record_id);
  }
  void RecordVisible(const RecordId& record_id,
                     const uint64_t& visual_size,
                     const IntRect& frame_visual_rect,
                     const FloatRect& root_visual_rect);
  bool IsRecordedVisibleImage(const RecordId& record_id) const {
    return visible_images_.Contains(record_id);
  }
  bool IsRecordedInvisibleImage(const RecordId& record_id) const {
    return invisible_images_.Contains(record_id);
  }

  void NotifyImageFinished(const RecordId& record_id) {
    // TODO(npm): Ideally NotifyImageFinished() would only be called when the
    // record has not yet been inserted in |image_finished_times_| but that's
    // not currently the case. If we plumb some information from
    // ImageResourceContent we may be able to ensure that this call does not
    // require the Contains() check, which would save time.
    if (!image_finished_times_.Contains(record_id))
      image_finished_times_.insert(record_id, base::TimeTicks::Now());
  }

  inline bool IsVisibleImageLoaded(const RecordId& record_id) const {
    DCHECK(visible_images_.Contains(record_id));
    return visible_images_.at(record_id)->loaded;
  }
  void OnImageLoaded(const RecordId&,
                     unsigned current_frame_index,
                     const StyleFetchedImage*);
  void OnImageLoadedInternal(base::WeakPtr<ImageRecord>&,
                             unsigned current_frame_index);

  // Receives a candidate image painted under opacity 0 but without nested
  // opacity. May update |largest_ignored_image_| if the new candidate has a
  // larger size.
  void MaybeUpdateLargestIgnoredImage(const RecordId&,
                                      const uint64_t& visual_size,
                                      const IntRect& frame_visual_rect,
                                      const FloatRect& root_visual_rect);
  void ReportLargestIgnoredImage(unsigned current_frame_index);

  // Compare the last frame index in queue with the last frame index that has
  // registered for assigning paint time.
  inline bool HasUnregisteredRecordsInQueue(
      unsigned last_registered_frame_index) {
    while (!images_queued_for_paint_time_.IsEmpty() &&
           !images_queued_for_paint_time_.back()) {
      images_queued_for_paint_time_.pop_back();
    }
    if (images_queued_for_paint_time_.IsEmpty())
      return false;
    return last_registered_frame_index < LastQueuedFrameIndex();
  }
  void AssignPaintTimeToRegisteredQueuedRecords(
      const base::TimeTicks&,
      unsigned last_queued_frame_index);
  inline unsigned LastQueuedFrameIndex() const {
    DCHECK(images_queued_for_paint_time_.back());
    return images_queued_for_paint_time_.back()->frame_index;
  }

  uint64_t LargestRemovedImageSize() const {
    return largest_removed_image_size_;
  }
  base::TimeTicks LargestRemovedImagePaintTime() const {
    return largest_removed_image_paint_time_;
  }

  void Trace(Visitor* visitor) const;

 private:
  // Find the image record of an visible image.
  inline base::WeakPtr<ImageRecord> FindVisibleRecord(
      const RecordId& record_id) const {
    DCHECK(visible_images_.Contains(record_id));
    return visible_images_.find(record_id)->value->AsWeakPtr();
  }
  std::unique_ptr<ImageRecord> CreateImageRecord(
      const LayoutObject& object,
      const ImageResourceContent* cached_image,
      const uint64_t& visual_size,
      const IntRect& frame_visual_rect,
      const FloatRect& root_visual_rect);
  inline void QueueToMeasurePaintTime(base::WeakPtr<ImageRecord>& record,
                                      unsigned current_frame_index) {
    images_queued_for_paint_time_.push_back(record);
    record->frame_index = current_frame_index;
  }
  inline void SetLoaded(base::WeakPtr<ImageRecord>& record) {
    record->loaded = true;
  }

  HashMap<RecordId, std::unique_ptr<ImageRecord>> visible_images_;
  HashSet<RecordId> invisible_images_;

  // This stores the image records, which are ordered by size.
  ImageRecordSet size_ordered_set_;
  // |ImageRecord|s waiting for paint time are stored in this queue
  // until they get a presentation time.
  Deque<base::WeakPtr<ImageRecord>> images_queued_for_paint_time_;
  // Map containing timestamps of when LayoutObject::ImageNotifyFinished is
  // first called.
  HashMap<RecordId, base::TimeTicks> image_finished_times_;

  Member<LocalFrameView> frame_view_;

  // We store the size and paint time of the largest removed image in order to
  // compute experimental LCP correctly.
  uint64_t largest_removed_image_size_ = 0u;
  base::TimeTicks largest_removed_image_paint_time_;

  // Image paints are ignored when they (or an ancestor) have opacity 0. This
  // can be a problem later on if the opacity changes to nonzero but this change
  // is composited. We solve this for the special case of documentElement by
  // storing a record for the largest ignored image without nested opacity. We
  // consider this an LCP candidate when the documentElement's opacity changes
  // from zero to nonzero.
  std::unique_ptr<ImageRecord> largest_ignored_image_;
};

// ImagePaintTimingDetector contains Largest Image Paint.
//
// Largest Image Paint timing measures when the largest image element within
// viewport finishes painting. Specifically, it:
// 1. Tracks all images' first invalidation, recording their visual size, if
// this image is within viewport.
// 2. When an image finishes loading, record its paint time.
// 3. At the end of each frame, if new images are added and loaded, the
// algorithm will start an analysis.
//
// In the analysis:
// 3.1 Largest Image Paint finds the largest image by the first visual size. If
// it has finished loading, reports a candidate result as its first paint time
// since loaded.
//
// For all these candidate results, Telemetry picks the lastly reported
// Largest Image Paint candidate as its final result.
//
// See also:
// https://docs.google.com/document/d/1DRVd4a2VU8-yyWftgOparZF-sf16daf0vfbsHuz2rws/edit#heading=h.1k2rnrs6mdmt
class CORE_EXPORT ImagePaintTimingDetector final
    : public GarbageCollected<ImagePaintTimingDetector> {
  friend class ImagePaintTimingDetectorTest;

 public:
  ImagePaintTimingDetector(LocalFrameView*, PaintTimingCallbackManager*);
  // Record an image paint. This method covers both img and background image. In
  // the case of a normal img, the last parameter will be nullptr. This
  // parameter is needed only for the purposes of plumbing the correct loadTime
  // value to the ImageRecord.
  void RecordImage(const LayoutObject&,
                   const IntSize& intrinsic_size,
                   const ImageResourceContent&,
                   const PropertyTreeStateOrAlias& current_paint_properties,
                   const StyleFetchedImage*,
                   const IntRect& image_border);
  void NotifyImageFinished(const LayoutObject&, const ImageResourceContent*);
  void OnPaintFinished();
  void NotifyImageRemoved(const LayoutObject&, const ImageResourceContent*);
  // After the method being called, the detector stops to record new entries and
  // node removal. But it still observe the loading status. In other words, if
  // an image is recorded before stopping recording, and finish loading after
  // stopping recording, the detector can still observe the loading being
  // finished.
  void StopRecordEntries();
  inline bool IsRecording() const { return is_recording_; }
  inline bool FinishedReportingImages() const {
    return !is_recording_ && num_pending_presentation_callbacks_ == 0;
  }
  void ResetCallbackManager(PaintTimingCallbackManager* manager) {
    callback_manager_ = manager;
  }
  void ReportPresentationTime(unsigned last_queued_frame_index,
                              base::TimeTicks);

  // Return the candidate.
  ImageRecord* UpdateCandidate();

  // Called when documentElement changes from zero to nonzero opacity. Makes the
  // largest image that was hidden due to this a Largest Contentful Paint
  // candidate.
  void ReportLargestIgnoredImage();

  void Trace(Visitor*) const;

 private:
  friend class LargestContentfulPaintCalculatorTest;

  void PopulateTraceValue(TracedValue&, const ImageRecord& first_image_paint);
  void RegisterNotifyPresentationTime();
  void ReportCandidateToTrace(ImageRecord&);
  void ReportNoCandidateToTrace();
  // Computes the size of an image for the purpose of LargestContentfulPaint,
  // downsizing the size of images with low intrinsic size. Images that occupy
  // the full viewport are special-cased and this method returns 0 for them so
  // that they are not considered valid candidates.
  uint64_t ComputeImageRectSize(const IntRect& image_border,
                                const FloatRect& mapped_visual_rect,
                                const IntSize&,
                                const PropertyTreeStateOrAlias&,
                                const LayoutObject&,
                                const ImageResourceContent&);

  // Used to find the last candidate.
  unsigned count_candidates_ = 0;

  // Used to decide which frame a record belongs to, monotonically increasing.
  unsigned frame_index_ = 1;
  unsigned last_registered_frame_index_ = 0;

  // Used to control if we record new image entries and image removal, but has
  // no effect on recording the loading status.
  bool is_recording_ = true;

  // Used to determine how many presentation callbacks are pending. In
  // combination with |is_recording|, helps determine whether this detector can
  // be destroyed.
  int num_pending_presentation_callbacks_ = 0;

  // This need to be set whenever changes that can affect the output of
  // |FindLargestPaintCandidate| occur during the paint tree walk.
  bool need_update_timing_at_frame_end_ = false;

  bool contains_full_viewport_image_ = false;

  // We cache the viewport size computation to avoid performing it on every
  // image. This value is reset when paint is finished and is computed if unset
  // when needed. 0 means that the size has not been computed.
  absl::optional<uint64_t> viewport_size_;

  ImageRecordsManager records_manager_;
  Member<LocalFrameView> frame_view_;
  Member<PaintTimingCallbackManager> callback_manager_;
};
}  // namespace blink

#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_PAINT_IMAGE_PAINT_TIMING_DETECTOR_H_