summaryrefslogtreecommitdiff
path: root/chromium/third_party/blink/renderer/core/paint/image_element_timing.cc
blob: 771c75aa95bf79354e5bcb42b4e7f96d0f7df301 (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
// 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.

#include "third_party/blink/renderer/core/paint/image_element_timing.h"

#include "third_party/blink/renderer/core/layout/layout_object.h"
#include "third_party/blink/renderer/core/loader/resource/image_resource_content.h"
#include "third_party/blink/renderer/core/page/chrome_client.h"
#include "third_party/blink/renderer/core/paint/element_timing_utils.h"
#include "third_party/blink/renderer/core/style/style_fetched_image.h"
#include "third_party/blink/renderer/core/timing/dom_window_performance.h"
#include "third_party/blink/renderer/core/timing/window_performance.h"
#include "third_party/blink/renderer/platform/graphics/paint/property_tree_state.h"
#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
#include "third_party/blink/renderer/platform/weborigin/security_origin.h"
#include "third_party/blink/renderer/platform/wtf/cross_thread_functional.h"
#include "third_party/blink/renderer/platform/wtf/text/atomic_string.h"

namespace blink {

namespace internal {

// "CORE_EXPORT" is needed to make this function visible to tests.
bool CORE_EXPORT
IsExplicitlyRegisteredForTiming(const LayoutObject* layout_object) {
  DCHECK(layout_object);
  const auto* element = DynamicTo<Element>(layout_object->GetNode());
  if (!element)
    return false;

  // If the element has no 'elementtiming' attribute, do not
  // generate timing entries for the element. See
  // https://wicg.github.io/element-timing/#sec-modifications-DOM for report
  // vs. ignore criteria.
  return element->FastHasAttribute(html_names::kElementtimingAttr);
}

}  // namespace internal

// static
const char ImageElementTiming::kSupplementName[] = "ImageElementTiming";

AtomicString ImagePaintString() {
  DEFINE_STATIC_LOCAL(const AtomicString, kImagePaint, ("image-paint"));
  return kImagePaint;
}

// static
ImageElementTiming& ImageElementTiming::From(LocalDOMWindow& window) {
  ImageElementTiming* timing =
      Supplement<LocalDOMWindow>::From<ImageElementTiming>(window);
  if (!timing) {
    timing = MakeGarbageCollected<ImageElementTiming>(window);
    ProvideTo(window, timing);
  }
  return *timing;
}

ImageElementTiming::ImageElementTiming(LocalDOMWindow& window)
    : Supplement<LocalDOMWindow>(window) {}

void ImageElementTiming::NotifyImageFinished(
    const LayoutObject& layout_object,
    const ImageResourceContent* cached_image) {
  if (!internal::IsExplicitlyRegisteredForTiming(&layout_object))
    return;

  const auto& insertion_result = images_notified_.insert(
      std::make_pair(&layout_object, cached_image), ImageInfo());
  if (insertion_result.is_new_entry)
    insertion_result.stored_value->value.load_time_ = base::TimeTicks::Now();
}

void ImageElementTiming::NotifyBackgroundImageFinished(
    const StyleFetchedImage* style_image) {
  const auto& insertion_result =
      background_image_timestamps_.insert(style_image, base::TimeTicks());
  if (insertion_result.is_new_entry)
    insertion_result.stored_value->value = base::TimeTicks::Now();
}

base::TimeTicks ImageElementTiming::GetBackgroundImageLoadTime(
    const StyleFetchedImage* style_image) {
  return background_image_timestamps_.at(style_image);
}

void ImageElementTiming::NotifyImagePainted(
    const LayoutObject* layout_object,
    const ImageResourceContent* cached_image,
    const PropertyTreeState& current_paint_chunk_properties) {
  DCHECK(layout_object);

  if (!internal::IsExplicitlyRegisteredForTiming(layout_object))
    return;

  auto it = images_notified_.find(std::make_pair(layout_object, cached_image));
  // It is possible that the pair is not in |images_notified_|. See
  // https://crbug.com/1027948
  if (it != images_notified_.end() && !it->value.is_painted_ && cached_image) {
    it->value.is_painted_ = true;
    NotifyImagePaintedInternal(layout_object->GetNode(), *layout_object,
                               *cached_image, current_paint_chunk_properties,
                               it->value.load_time_, nullptr);
  }
}

void ImageElementTiming::NotifyImagePaintedInternal(
    Node* node,
    const LayoutObject& layout_object,
    const ImageResourceContent& cached_image,
    const PropertyTreeState& current_paint_chunk_properties,
    base::TimeTicks load_time,
    const IntRect* image_border) {
  LocalFrame* frame = GetSupplementable()->GetFrame();
  DCHECK(frame == layout_object.GetDocument().GetFrame());
  DCHECK(node);
  // Background images could cause |node| to not be an element. For example,
  // style applied to body causes this node to be a Document Node. Therefore,
  // bail out if that is the case.
  auto* element = DynamicTo<Element>(node);
  if (!frame || !element)
    return;

  // We do not expose elements in shadow trees, for now. We might expose
  // something once the discussions at
  // https://github.com/WICG/element-timing/issues/3 and
  // https://github.com/w3c/webcomponents/issues/816 have been resolved.
  if (node->IsInShadowTree())
    return;

  // Do not expose elements which should have effective zero opacity.
  // We can afford to call this expensive method because this is only called
  // once per image annotated with the elementtiming attribute.
  if (!layout_object.HasNonZeroEffectiveOpacity())
    return;

  RespectImageOrientationEnum respect_orientation =
      LayoutObject::ShouldRespectImageOrientation(&layout_object);

  FloatRect intersection_rect = ElementTimingUtils::ComputeIntersectionRect(
      frame,
      image_border ? *image_border
                   : layout_object.FragmentsVisualRectBoundingBox(),
      current_paint_chunk_properties);
  const AtomicString attr =
      element->FastGetAttribute(html_names::kElementtimingAttr);

  const AtomicString& id = element->GetIdAttribute();

  const KURL& url = cached_image.Url();
  DCHECK(GetSupplementable()->document() == &layout_object.GetDocument());
  DCHECK(layout_object.GetDocument().GetSecurityOrigin());
  // It's ok to expose rendering timestamp for data URIs so exclude those from
  // the Timing-Allow-Origin check.
  if (!url.ProtocolIsData()) {
    bool timing_allow_check = false;
    // Use the TimingAllowPassed() check from the response if OutOfBlinkCors is
    // enabled. If it is not enabled then that flag is not computed, so use to
    // the single PassesTimingAllowCheck(), which is incorrect because it does
    // not check the full redirect chain. See crbug.com/1003943.
    if (RuntimeEnabledFeatures::OutOfBlinkCorsEnabled()) {
      timing_allow_check = cached_image.GetResponse().TimingAllowPassed();
    } else {
      bool response_tainting_not_basic = false;
      bool tainted_origin_flag = false;
      timing_allow_check = Performance::PassesTimingAllowCheck(
          cached_image.GetResponse(), cached_image.GetResponse(),
          *layout_object.GetDocument().GetSecurityOrigin(),
          layout_object.GetDocument().GetExecutionContext(),
          &response_tainting_not_basic, &tainted_origin_flag);
    }
    if (!timing_allow_check) {
      WindowPerformance* performance =
          DOMWindowPerformance::performance(*GetSupplementable());
      if (performance) {
        // Create an entry with a |startTime| of 0.
        performance->AddElementTiming(
            ImagePaintString(), url.GetString(), intersection_rect,
            base::TimeTicks(), load_time, attr,
            cached_image.IntrinsicSize(respect_orientation), id, element);
      }
      return;
    }
  }

  // If the image URL is a data URL ("data:image/..."), then the |name| of the
  // PerformanceElementTiming entry should be the URL trimmed to 100 characters.
  // If it is not, then pass in the full URL regardless of the length to be
  // consistent with Resource Timing.
  const String& image_url = url.ProtocolIsData()
                                ? url.GetString().Left(kInlineImageMaxChars)
                                : url.GetString();
  element_timings_.emplace_back(MakeGarbageCollected<ElementTimingInfo>(
      image_url, intersection_rect, load_time, attr,
      cached_image.IntrinsicSize(respect_orientation), id, element));
  // Only queue a swap promise when |element_timings_| was empty. All of the
  // records in |element_timings_| will be processed when the promise succeeds
  // or fails, and at that time the vector is cleared.
  if (element_timings_.size() == 1) {
    frame->GetChromeClient().NotifySwapTime(
        *frame,
        CrossThreadBindOnce(&ImageElementTiming::ReportImagePaintSwapTime,
                            WrapCrossThreadWeakPersistent(this)));
  }
}

void ImageElementTiming::NotifyBackgroundImagePainted(
    Node* node,
    const StyleFetchedImage* background_image,
    const PropertyTreeState& current_paint_chunk_properties,
    const IntRect& image_border) {
  DCHECK(node);
  DCHECK(background_image);

  const LayoutObject* layout_object = node->GetLayoutObject();
  if (!layout_object)
    return;

  if (!internal::IsExplicitlyRegisteredForTiming(layout_object))
    return;

  const ImageResourceContent* cached_image = background_image->CachedImage();
  if (!cached_image || !cached_image->IsLoaded())
    return;

  auto it = background_image_timestamps_.find(background_image);
  DCHECK(it != background_image_timestamps_.end());

  ImageInfo& info =
      images_notified_
          .insert(std::make_pair(layout_object, cached_image), ImageInfo())
          .stored_value->value;
  if (!info.is_painted_) {
    info.is_painted_ = true;
    NotifyImagePaintedInternal(layout_object->GetNode(), *layout_object,
                               *cached_image, current_paint_chunk_properties,
                               it->value, &image_border);
  }
}

void ImageElementTiming::ReportImagePaintSwapTime(WebSwapResult,
                                                  base::TimeTicks timestamp) {
  WindowPerformance* performance =
      DOMWindowPerformance::performance(*GetSupplementable());
  if (performance) {
    for (const auto& element_timing : element_timings_) {
      performance->AddElementTiming(
          ImagePaintString(), element_timing->url, element_timing->rect,
          timestamp, element_timing->response_end, element_timing->identifier,
          element_timing->intrinsic_size, element_timing->id,
          element_timing->element);
    }
  }
  element_timings_.clear();
}

void ImageElementTiming::NotifyImageRemoved(const LayoutObject* layout_object,
                                            const ImageResourceContent* image) {
  images_notified_.erase(std::make_pair(layout_object, image));
}

void ImageElementTiming::Trace(Visitor* visitor) {
  visitor->Trace(element_timings_);
  visitor->Trace(background_image_timestamps_);
  Supplement<LocalDOMWindow>::Trace(visitor);
}

}  // namespace blink