Commit 150777e3 authored by Nicolas Pena's avatar Nicolas Pena Committed by Commit Bot

[Element Timing] Implement Image Element Timing

This CL adds support to observe the first time an <img> element paints
via the PerformanceObserver (no support for performance timeline yet)
and under a flag that is disabled by default.

Implementation proposal:
https://docs.google.com/document/d/1BvHknbj3T-fUuj4RxHF8qGRNxbswtBqUjKiPx7iLXCc

Intent to Implement:
https://groups.google.com/a/chromium.org/forum/#!topic/blink-dev/2twP4Xdd1VI

Bug: 879270
Cq-Include-Trybots: luci.chromium.try:linux_layout_tests_slimming_paint_v2;master.tryserver.blink:linux_trusty_blink_rel
Change-Id: I715bd71be088f8055ece493db208caa9b97d736b
Reviewed-on: https://chromium-review.googlesource.com/1197349
Commit-Queue: Nicolás Peña Moreno <npm@chromium.org>
Reviewed-by: default avatarChris Harrelson <chrishtr@chromium.org>
Reviewed-by: default avatarSteve Kobes <skobes@chromium.org>
Cr-Commit-Position: refs/heads/master@{#589712}
parent e4aec5d4
<!DOCTYPE HTML>
<meta charset=utf-8>
<title>Element Timing: intersectionRect when image overflows</title>
<body>
<style>
body {
margin: 200px 100px;
}
</style>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="resources/element-timing-helpers.js"></script>
<script>
let beforeRender;
async_test(function (t) {
const observer = new PerformanceObserver(
t.step_func_done(function(entryList) {
assert_equals(entryList.getEntries().length, 1);
const entry = entryList.getEntries()[0];
checkElement(entry, 'not_fully_visible', beforeRender);
// Image will not be fully visible. It should start from the top left part
// of the document, excluding the margin, and then overflow.
checkRect(entry,
[100, document.documentElement.clientWidth, 200, document.documentElement.clientHeight]);
})
);
observer.observe({entryTypes: ['element']});
// We add the image during onload to be sure that the observer is registered
// in time for it to observe the element timing.
window.onload = () => {
// Add an image setting width and height equal to viewport.
const img = document.createElement('img');
img.src = '/resources/square20.bmp';
img.setAttribute('elementtiming', 'not_fully_visible');
img.width = document.documentElement.clientWidth;
img.height = document.documentElement.clientHeight;
document.body.appendChild(img);
beforeRender = performance.now();
};
}, 'The intersectionRect of an img element overflowing is computed correctly');
</script>
</body>
<!DOCTYPE HTML>
<meta charset=utf-8>
<title>Element Timing: observe elements with elementtiming attribute</title>
<body>
<style>
body {
margin: 0;
}
</style>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="resources/element-timing-helpers.js"></script>
<script>
let beforeRender;
async_test(function (t) {
const observer = new PerformanceObserver(
t.step_func_done(function(entryList) {
assert_equals(entryList.getEntries().length, 1);
const entry = entryList.getEntries()[0];
checkElement(entry, 'my_image', beforeRender);
// Assume viewport has size at least 100, so the element is fully visible.
checkRect(entry, [0, 100, 0, 100]);
})
);
observer.observe({entryTypes: ['element']});
// We add the image during onload to be sure that the observer is registered
// in time for it to observe the element timing.
window.onload = () => {
// Add image of width and height equal to 100.
const img = document.createElement('img');
img.src = '/resources/square.png';
img.setAttribute('elementtiming', 'my_image');
document.body.appendChild(img);
beforeRender = performance.now();
};
}, 'Element with elementtiming attribute is observable.');
</script>
</body>
<!DOCTYPE HTML>
<meta charset=utf-8>
<title>Element Timing: observe large elements</title>
<body>
<style>
body {
margin: 0;
}
</style>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="resources/element-timing-helpers.js"></script>
<script>
let beforeRender;
async_test(function (t) {
const observer = new PerformanceObserver(
t.step_func_done(function(entryList) {
assert_equals(entryList.getEntries().length, 1);
const entry = entryList.getEntries()[0];
checkElement(entry, 'img', beforeRender)
// Assume viewport hasn't changed, so the element occupies all of it.
checkRect(entry,
[0, document.documentElement.clientWidth, 0, document.documentElement.clientHeight]);
})
);
observer.observe({entryTypes: ['element']});
// We add the image during onload to be sure that the observer is registered
// in time for it to observe the element timing.
window.onload = () => {
// Add an image setting width and height equal to viewport.
const img = document.createElement('img');
img.src = '/resources/square20.jpg';
img.width = document.documentElement.clientWidth;
img.height = document.documentElement.clientHeight;
document.body.appendChild(img);
beforeRender = performance.now();
};
}, 'Large img element is observable.');
</script>
</body>
<!DOCTYPE HTML>
<meta charset=utf-8>
<title>Element Timing: multiple images</title>
<body>
<style>
body {
margin: 0;
}
#img1 {
display: block;
margin-left: auto;
margin-right: auto;
}
#img2 {
margin-top:150px;
margin-left:50px;
}
</style>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="resources/element-timing-helpers.js"></script>
<script>
let beforeRender, image1Observed=0, image2Observed=0, image3Observed=0;
async_test(function (t) {
const observer = new PerformanceObserver(
t.step_func(function(entryList) {
entryList.getEntries().forEach( entry => {
if (entry.name === 'image1') {
if (image1Observed) {
assert_unreached("Observer received image1 more than once");
t.done();
}
image1Observed = 1;
checkElement(entry, 'image1', beforeRender);
// This image is horizontally centered.
// Using abs and comparing to 1 because the viewport sizes could be odd.
// If a size is odd, then image cannot be in the pure center, but left
// and right should still be very close to their estimated coordinates.
assert_less_than_equal(Math.abs(entry.intersectionRect.left -
(document.documentElement.clientWidth / 2 - 50)), 1,
'left of rect for image1');
assert_less_than_equal(Math.abs(entry.intersectionRect.right -
(document.documentElement.clientWidth / 2 + 50)), 1,
'right of rect for image1');
assert_equals(entry.intersectionRect.top, 0, 'top of rect for image1');
assert_equals(entry.intersectionRect.bottom,
100, 'bottom of rect for image1');
}
else if (entry.name === 'image2') {
if (image2Observed) {
assert_unreached("Observer received image2 more than once");
t.done();
}
image2Observed = 1;
checkElement(entry, 'image2', beforeRender);
// This image should be below image 1, and should respect the margin.
checkRect(entry, [50, 250, 250, 450], "of image2");
}
else if (entry.name === 'image3') {
if (image3Observed) {
assert_unreached("Observer received image3 more than once");
t.done();
}
image3Observed = 1;
checkElement(entry, 'image3', beforeRender);
// This image is just to the right of image2.
checkRect(entry, [250, 450, 250, 450], "of image3");
}
else {
assert_unreached("Received an unexpected name.");
t.done();
}
if (image1Observed && image2Observed && image3Observed) {
t.done();
}
});
})
);
observer.observe({entryTypes: ['element']});
function addImage(number, source, width=0) {
const img = document.createElement('img');
img.src = source;
img.id = 'img' + number;
img.setAttribute('elementtiming', 'image' + number);
if (width !== 0)
img.width = width;
document.body.appendChild(img);
}
// Add the images during onload to be sure that the observer is registered in
// time to observe the element timing.
window.onload = () => {
addImage(1, '/resources/square100.png');
// Use requestAnimationFrame and a timeout to ensure that the images are
// processed in the order we want.
requestAnimationFrame( () => {
t.step_timeout( () => {
// Set the size equal to that of image3 to make positioning easier.
addImage(2, '/resources/square20.gif', 200);
requestAnimationFrame( () => {
t.step_timeout( () => {
addImage(3, '/resources/circle.svg');
}, 0);
});
}, 0);
});
beforeRender = performance.now();
};
}, 'PerformanceObserver can observe multiple image elements.');
</script>
</body>
// Checks that this is an ElementTiming entry with name |expectedName|. It also
// does a very basic check on |startTime|: after |beforeRender| and before now().
function checkElement(entry, expectedName, beforeRender) {
assert_equals(entry.entryType, 'element');
assert_equals(entry.name, expectedName);
assert_equals(entry.duration, 0);
assert_greater_than_equal(entry.startTime, beforeRender);
assert_greater_than_equal(performance.now(), entry.startTime);
}
// Checks that the rect matches the desired values [left right top bottom]
function checkRect(entry, expected, description="") {
assert_equals(entry.intersectionRect.left, expected[0],
'left of rect ' + description);
assert_equals(entry.intersectionRect.right, expected[1],
'right of rect ' + description);
assert_equals(entry.intersectionRect.top, expected[2],
'top of rect ' + description);
assert_equals(entry.intersectionRect.bottom, expected[3],
'bottom of rect ' + description);
}
<?xml version="1.0"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200">
<circle cx="50%" cy="50%" r="80" style="fill:blue;" />
</svg>
...@@ -5096,6 +5096,11 @@ interface Performance : EventTarget ...@@ -5096,6 +5096,11 @@ interface Performance : EventTarget
method toJSON method toJSON
setter oneventtimingbufferfull setter oneventtimingbufferfull
setter onresourcetimingbufferfull setter onresourcetimingbufferfull
interface PerformanceElementTiming : PerformanceEntry
attribute @@toStringTag
getter intersectionRect
method constructor
method toJSON
interface PerformanceEntry interface PerformanceEntry
attribute @@toStringTag attribute @@toStringTag
getter duration getter duration
......
...@@ -417,6 +417,7 @@ core_idl_files = ...@@ -417,6 +417,7 @@ core_idl_files =
"svg/svg_view_element.idl", "svg/svg_view_element.idl",
"timing/memory_info.idl", "timing/memory_info.idl",
"timing/performance.idl", "timing/performance.idl",
"timing/performance_element_timing.idl",
"timing/performance_entry.idl", "timing/performance_entry.idl",
"timing/performance_event_timing.idl", "timing/performance_event_timing.idl",
"timing/performance_long_task_timing.idl", "timing/performance_long_task_timing.idl",
......
...@@ -73,6 +73,7 @@ ...@@ -73,6 +73,7 @@
"disableremoteplayback", "disableremoteplayback",
"download", "download",
"draggable", "draggable",
"elementtiming",
"enctype", "enctype",
"end", "end",
"event", "event",
......
...@@ -39,6 +39,7 @@ ...@@ -39,6 +39,7 @@
#include "third_party/blink/renderer/core/layout/intrinsic_sizing_info.h" #include "third_party/blink/renderer/core/layout/intrinsic_sizing_info.h"
#include "third_party/blink/renderer/core/layout/layout_view.h" #include "third_party/blink/renderer/core/layout/layout_view.h"
#include "third_party/blink/renderer/core/loader/resource/image_resource_content.h" #include "third_party/blink/renderer/core/loader/resource/image_resource_content.h"
#include "third_party/blink/renderer/core/paint/image_element_timing.h"
#include "third_party/blink/renderer/core/paint/image_painter.h" #include "third_party/blink/renderer/core/paint/image_painter.h"
#include "third_party/blink/renderer/core/paint/paint_layer.h" #include "third_party/blink/renderer/core/paint/paint_layer.h"
#include "third_party/blink/renderer/core/svg/graphics/svg_image.h" #include "third_party/blink/renderer/core/svg/graphics/svg_image.h"
...@@ -119,6 +120,11 @@ LayoutImage::~LayoutImage() = default; ...@@ -119,6 +120,11 @@ LayoutImage::~LayoutImage() = default;
void LayoutImage::WillBeDestroyed() { void LayoutImage::WillBeDestroyed() {
DCHECK(image_resource_); DCHECK(image_resource_);
image_resource_->Shutdown(); image_resource_->Shutdown();
if (RuntimeEnabledFeatures::ElementTimingEnabled()) {
if (LocalDOMWindow* window = GetDocument().domWindow())
ImageElementTiming::From(*window).NotifyWillBeDestroyed(this);
}
LayoutReplaced::WillBeDestroyed(); LayoutReplaced::WillBeDestroyed();
} }
......
...@@ -96,6 +96,8 @@ blink_core_sources("paint") { ...@@ -96,6 +96,8 @@ blink_core_sources("paint") {
"grid_painter.h", "grid_painter.h",
"html_canvas_painter.cc", "html_canvas_painter.cc",
"html_canvas_painter.h", "html_canvas_painter.h",
"image_element_timing.cc",
"image_element_timing.h",
"image_painter.cc", "image_painter.cc",
"image_painter.h", "image_painter.h",
"inline_box_painter_base.cc", "inline_box_painter_base.cc",
......
// 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/html/html_image_element.h"
#include "third_party/blink/renderer/core/layout/layout_image.h"
#include "third_party/blink/renderer/core/layout/layout_object.h"
#include "third_party/blink/renderer/core/layout/layout_replaced.h"
#include "third_party/blink/renderer/core/layout/layout_view.h"
#include "third_party/blink/renderer/core/page/chrome_client.h"
#include "third_party/blink/renderer/core/paint/paint_layer.h"
#include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.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/cross_thread_functional.h"
#include "third_party/blink/renderer/platform/graphics/paint/geometry_mapper.h"
#include "third_party/blink/renderer/platform/wtf/text/atomic_string.h"
namespace blink {
// TODO(npm): decide on a reasonable value for the threshold.
constexpr const float kImageTimingSizeThreshold = 0.15f;
// static
const char ImageElementTiming::kSupplementName[] = "ImageElementTiming";
// static
ImageElementTiming& ImageElementTiming::From(LocalDOMWindow& window) {
ImageElementTiming* timing =
Supplement<LocalDOMWindow>::From<ImageElementTiming>(window);
if (!timing) {
timing = new ImageElementTiming(window);
ProvideTo(window, timing);
}
return *timing;
}
ImageElementTiming::ImageElementTiming(LocalDOMWindow& window)
: Supplement<LocalDOMWindow>(window) {
DCHECK(RuntimeEnabledFeatures::ElementTimingEnabled());
}
void ImageElementTiming::NotifyImagePainted(const HTMLImageElement* element,
const LayoutImage* layout_image,
const PaintLayer* painting_layer) {
if (images_notified_.find(layout_image) != images_notified_.end())
return;
images_notified_.insert(layout_image);
// Compute the viewport rect.
LocalFrame* frame = GetSupplementable()->GetFrame();
DCHECK(frame == layout_image->GetDocument().GetFrame());
if (!frame)
return;
WebLayerTreeView* layerTreeView =
frame->GetChromeClient().GetWebLayerTreeView(frame);
if (!layerTreeView)
return;
IntRect viewport = frame->View()->LayoutViewport()->VisibleContentRect();
// Compute the visible part of the image rect.
LayoutRect image_visual_rect = layout_image->FirstFragment().VisualRect();
const auto* local_transform = painting_layer->GetLayoutObject()
.FirstFragment()
.LocalBorderBoxProperties()
.Transform();
const auto* ancestor_transform = painting_layer->GetLayoutObject()
.View()
->FirstFragment()
.LocalBorderBoxProperties()
.Transform();
FloatRect new_visual_rect_abs = FloatRect(image_visual_rect);
GeometryMapper::SourceToDestinationRect(local_transform, ancestor_transform,
new_visual_rect_abs);
IntRect visible_new_visual_rect = RoundedIntRect(new_visual_rect_abs);
visible_new_visual_rect.Intersect(viewport);
const AtomicString attr =
element->FastGetAttribute(HTMLNames::elementtimingAttr);
// Do not create an entry if 'elementtiming' is not present or the image is
// below a certain size threshold.
if (attr.IsEmpty() &&
visible_new_visual_rect.Size().Area() <=
viewport.Size().Area() * kImageTimingSizeThreshold) {
return;
}
// Compute the |name| for the entry. Use the 'elementtiming' attribute. If
// empty, use the ID. If empty, use 'img'.
AtomicString name = attr;
if (name.IsEmpty())
name = element->FastGetAttribute(HTMLNames::idAttr);
if (name.IsEmpty())
name = "img";
element_timings_.emplace_back(name, visible_new_visual_rect);
// 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) {
layerTreeView->NotifySwapTime(ConvertToBaseCallback(
CrossThreadBind(&ImageElementTiming::ReportImagePaintSwapTime,
WrapCrossThreadWeakPersistent(this))));
}
}
void ImageElementTiming::ReportImagePaintSwapTime(WebLayerTreeView::SwapResult,
base::TimeTicks timestamp) {
WindowPerformance* performance =
DOMWindowPerformance::performance(*GetSupplementable());
if (!performance || !performance->HasObserverFor(PerformanceEntry::kElement))
return;
for (const auto& element_timing : element_timings_) {
performance->AddElementTiming(element_timing.name, element_timing.rect,
timestamp);
}
element_timings_.clear();
}
void ImageElementTiming::NotifyWillBeDestroyed(const LayoutImage* image) {
images_notified_.erase(image);
}
void ImageElementTiming::Trace(blink::Visitor* visitor) {
Supplement<LocalDOMWindow>::Trace(visitor);
}
} // namespace blink
// 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_ELEMENT_TIMING_H_
#define THIRD_PARTY_BLINK_RENDERER_CORE_PAINT_IMAGE_ELEMENT_TIMING_H_
#include "third_party/blink/public/platform/web_layer_tree_view.h"
#include "third_party/blink/renderer/core/frame/local_dom_window.h"
#include "third_party/blink/renderer/platform/supplementable.h"
#include "third_party/blink/renderer/platform/wtf/functional.h"
#include "third_party/blink/renderer/platform/wtf/hash_set.h"
#include "third_party/blink/renderer/platform/wtf/time.h"
#include "third_party/blink/renderer/platform/wtf/vector.h"
namespace blink {
class HTMLImageElement;
class LayoutImage;
// ImageElementTiming is responsible for tracking the paint timings for <img>
// elements for a given window.
class CORE_EXPORT ImageElementTiming final
: public GarbageCollectedFinalized<ImageElementTiming>,
public Supplement<LocalDOMWindow> {
USING_GARBAGE_COLLECTED_MIXIN(ImageElementTiming);
public:
static const char kSupplementName[];
virtual ~ImageElementTiming() = default;
static ImageElementTiming& From(LocalDOMWindow&);
// Called when the LayoutImage has been painted. This method might queue a
// swap promise to compute and report paint timestamps.
void NotifyImagePainted(const HTMLImageElement*,
const LayoutImage*,
const PaintLayer*);
// Called when the LayoutImage will be destroyed.
void NotifyWillBeDestroyed(const LayoutImage*);
void Trace(blink::Visitor*) override;
private:
explicit ImageElementTiming(LocalDOMWindow&);
// Callback for the swap promise. Reports paint timestamps.
void ReportImagePaintSwapTime(WebLayerTreeView::SwapResult,
base::TimeTicks timestamp);
// Struct containing information about image element timing.
struct ElementTimingInfo {
AtomicString name;
IntRect rect;
ElementTimingInfo(AtomicString name, IntRect rect)
: name(name), rect(rect) {}
};
// Vector containing the element timing infos that will be reported during the
// next swap promise callback.
WTF::Vector<ElementTimingInfo> element_timings_;
// Hashmap of LayoutImage objects for which paint has already been notified.
WTF::HashSet<const LayoutImage*> images_notified_;
DISALLOW_COPY_AND_ASSIGN(ImageElementTiming);
};
} // namespace blink
#endif
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
#include "third_party/blink/renderer/core/layout/text_run_constructor.h" #include "third_party/blink/renderer/core/layout/text_run_constructor.h"
#include "third_party/blink/renderer/core/page/chrome_client.h" #include "third_party/blink/renderer/core/page/chrome_client.h"
#include "third_party/blink/renderer/core/page/page.h" #include "third_party/blink/renderer/core/page/page.h"
#include "third_party/blink/renderer/core/paint/image_element_timing.h"
#include "third_party/blink/renderer/core/paint/paint_info.h" #include "third_party/blink/renderer/core/paint/paint_info.h"
#include "third_party/blink/renderer/core/paint/paint_info_with_offset.h" #include "third_party/blink/renderer/core/paint/paint_info_with_offset.h"
#include "third_party/blink/renderer/platform/geometry/layout_point.h" #include "third_party/blink/renderer/platform/geometry/layout_point.h"
...@@ -133,12 +134,15 @@ void ImagePainter::PaintReplaced(const PaintInfo& paint_info, ...@@ -133,12 +134,15 @@ void ImagePainter::PaintReplaced(const PaintInfo& paint_info,
paint_rect.MoveBy(paint_offset); paint_rect.MoveBy(paint_offset);
DrawingRecorder recorder(context, layout_image_, paint_info.phase); DrawingRecorder recorder(context, layout_image_, paint_info.phase);
PaintIntoRect(context, paint_rect, content_rect); DCHECK(paint_info.PaintContainer());
PaintIntoRect(context, paint_rect, content_rect,
paint_info.PaintContainer()->Layer());
} }
void ImagePainter::PaintIntoRect(GraphicsContext& context, void ImagePainter::PaintIntoRect(GraphicsContext& context,
const LayoutRect& dest_rect, const LayoutRect& dest_rect,
const LayoutRect& content_rect) { const LayoutRect& content_rect,
const PaintLayer* painting_layer) {
if (!layout_image_.ImageResource()->HasImage() || if (!layout_image_.ImageResource()->HasImage() ||
layout_image_.ImageResource()->ErrorOccurred()) layout_image_.ImageResource()->ErrorOccurred())
return; // FIXME: should we just ASSERT these conditions? (audit all return; // FIXME: should we just ASSERT these conditions? (audit all
...@@ -184,6 +188,13 @@ void ImagePainter::PaintIntoRect(GraphicsContext& context, ...@@ -184,6 +188,13 @@ void ImagePainter::PaintIntoRect(GraphicsContext& context,
image.get(), decode_mode, FloatRect(pixel_snapped_dest_rect), &src_rect, image.get(), decode_mode, FloatRect(pixel_snapped_dest_rect), &src_rect,
SkBlendMode::kSrcOver, SkBlendMode::kSrcOver,
LayoutObject::ShouldRespectImageOrientation(&layout_image_)); LayoutObject::ShouldRespectImageOrientation(&layout_image_));
if (RuntimeEnabledFeatures::ElementTimingEnabled() &&
IsHTMLImageElement(node) && !context.ContextDisabled()) {
LocalDOMWindow* window = layout_image_.GetDocument().domWindow();
DCHECK(window);
ImageElementTiming::From(*window).NotifyImagePainted(
ToHTMLImageElement(node), &layout_image_, painting_layer);
}
} }
} // namespace blink } // namespace blink
...@@ -14,6 +14,7 @@ class GraphicsContext; ...@@ -14,6 +14,7 @@ class GraphicsContext;
class LayoutPoint; class LayoutPoint;
class LayoutRect; class LayoutRect;
class LayoutImage; class LayoutImage;
class PaintLayer;
class ImagePainter { class ImagePainter {
STACK_ALLOCATED(); STACK_ALLOCATED();
...@@ -29,7 +30,8 @@ class ImagePainter { ...@@ -29,7 +30,8 @@ class ImagePainter {
// offset. // offset.
void PaintIntoRect(GraphicsContext&, void PaintIntoRect(GraphicsContext&,
const LayoutRect& dest_rect, const LayoutRect& dest_rect,
const LayoutRect& content_rect); const LayoutRect& content_rect,
const PaintLayer*);
private: private:
void PaintAreaElementFocusRing(const PaintInfo&); void PaintAreaElementFocusRing(const PaintInfo&);
......
...@@ -68,8 +68,10 @@ void VideoPainter::PaintReplaced(const PaintInfo& paint_info, ...@@ -68,8 +68,10 @@ void VideoPainter::PaintReplaced(const PaintInfo& paint_info,
if (displaying_poster || !force_software_video_paint) { if (displaying_poster || !force_software_video_paint) {
// This will display the poster image, if one is present, and otherwise // This will display the poster image, if one is present, and otherwise
// paint nothing. // paint nothing.
DCHECK(paint_info.PaintContainer());
ImagePainter(layout_video_) ImagePainter(layout_video_)
.PaintIntoRect(context, replaced_rect, content_rect); .PaintIntoRect(context, replaced_rect, content_rect,
paint_info.PaintContainer()->Layer());
} else { } else {
PaintFlags video_flags = context.FillFlags(); PaintFlags video_flags = context.FillFlags();
video_flags.setColor(SK_ColorBLACK); video_flags.setColor(SK_ColorBLACK);
......
...@@ -14,6 +14,8 @@ blink_core_sources("timing") { ...@@ -14,6 +14,8 @@ blink_core_sources("timing") {
"memory_info.h", "memory_info.h",
"performance.cc", "performance.cc",
"performance.h", "performance.h",
"performance_element_timing.cc",
"performance_element_timing.h",
"performance_entry.cc", "performance_entry.cc",
"performance_entry.h", "performance_entry.h",
"performance_event_timing.cc", "performance_event_timing.cc",
......
...@@ -37,7 +37,7 @@ void EventTiming::WillDispatchEvent(const Event* event) { ...@@ -37,7 +37,7 @@ void EventTiming::WillDispatchEvent(const Event* event) {
// dispatched. // dispatched.
if ((performance_->ShouldBufferEventTiming() && if ((performance_->ShouldBufferEventTiming() &&
!performance_->IsEventTimingBufferFull()) || !performance_->IsEventTimingBufferFull()) ||
performance_->ObservingEventTimingEntries()) { performance_->HasObserverFor(PerformanceEntry::kEvent)) {
processing_start_ = CurrentTimeTicks(); processing_start_ = CurrentTimeTicks();
finished_will_dispatch_event_ = true; finished_will_dispatch_event_ = true;
} }
......
...@@ -209,6 +209,10 @@ PerformanceEntryVector Performance::getEntriesByType( ...@@ -209,6 +209,10 @@ PerformanceEntryVector Performance::getEntriesByType(
break; break;
case PerformanceEntry::kTaskAttribution: case PerformanceEntry::kTaskAttribution:
break; break;
// TODO(npm): decide which element timing entries are accessible via the
// performance buffer.
case PerformanceEntry::kElement:
break;
case PerformanceEntry::kInvalid: case PerformanceEntry::kInvalid:
break; break;
} }
......
...@@ -198,6 +198,8 @@ class CORE_EXPORT Performance : public EventTargetWithInlineData { ...@@ -198,6 +198,8 @@ class CORE_EXPORT Performance : public EventTargetWithInlineData {
void ActivateObserver(PerformanceObserver&); void ActivateObserver(PerformanceObserver&);
void ResumeSuspendedObservers(); void ResumeSuspendedObservers();
bool HasObserverFor(PerformanceEntry::EntryType) const;
static bool AllowsTimingRedirect(const Vector<ResourceResponse>&, static bool AllowsTimingRedirect(const Vector<ResourceResponse>&,
const ResourceResponse&, const ResourceResponse&,
const SecurityOrigin&, const SecurityOrigin&,
...@@ -244,7 +246,6 @@ class CORE_EXPORT Performance : public EventTargetWithInlineData { ...@@ -244,7 +246,6 @@ class CORE_EXPORT Performance : public EventTargetWithInlineData {
void NotifyObserversOfEntry(PerformanceEntry&) const; void NotifyObserversOfEntry(PerformanceEntry&) const;
void NotifyObserversOfEntries(PerformanceEntryVector&); void NotifyObserversOfEntries(PerformanceEntryVector&);
bool HasObserverFor(PerformanceEntry::EntryType) const;
void DeliverObservationsTimerFired(TimerBase*); void DeliverObservationsTimerFired(TimerBase*);
......
// 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/timing/performance_element_timing.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_object_builder.h"
#include "third_party/blink/renderer/core/performance_entry_names.h"
namespace blink {
// static
PerformanceElementTiming* PerformanceElementTiming::Create(
const AtomicString& name,
const IntRect& intersection_rect,
DOMHighResTimeStamp start_time) {
return new PerformanceElementTiming(name, intersection_rect, start_time);
}
PerformanceElementTiming::PerformanceElementTiming(
const AtomicString& name,
const IntRect& intersection_rect,
DOMHighResTimeStamp start_time)
: PerformanceEntry(name, start_time, start_time),
intersection_rect_(DOMRectReadOnly::FromIntRect(intersection_rect)) {}
PerformanceElementTiming::~PerformanceElementTiming() = default;
AtomicString PerformanceElementTiming::entryType() const {
return PerformanceEntryNames::element;
}
PerformanceEntryType PerformanceElementTiming::EntryTypeEnum() const {
return PerformanceEntry::EntryType::kElement;
}
void PerformanceElementTiming::BuildJSONValue(V8ObjectBuilder& builder) const {
PerformanceEntry::BuildJSONValue(builder);
builder.Add("intersectionRect", intersection_rect_);
}
void PerformanceElementTiming::Trace(blink::Visitor* visitor) {
visitor->Trace(intersection_rect_);
PerformanceEntry::Trace(visitor);
}
} // namespace blink
// 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_TIMING_PERFORMANCE_ELEMENT_TIMING_H_
#define THIRD_PARTY_BLINK_RENDERER_CORE_TIMING_PERFORMANCE_ELEMENT_TIMING_H_
#include "third_party/blink/renderer/core/core_export.h"
#include "third_party/blink/renderer/core/dom/dom_high_res_time_stamp.h"
#include "third_party/blink/renderer/core/geometry/dom_rect_read_only.h"
#include "third_party/blink/renderer/core/timing/performance_entry.h"
namespace blink {
// The PerformanceElementTiming object exposes the time in which an element is
// first rendered on the screen and its intersection with the viewport at the
// time it is painted. Currently this is only done for <img> elements but other
// element types should be supported in the future.
class CORE_EXPORT PerformanceElementTiming final : public PerformanceEntry {
DEFINE_WRAPPERTYPEINFO();
public:
static PerformanceElementTiming* Create(const AtomicString& name,
const IntRect& intersection_rect,
DOMHighResTimeStamp start_time);
~PerformanceElementTiming() override;
AtomicString entryType() const override;
PerformanceEntryType EntryTypeEnum() const override;
DOMRectReadOnly* intersectionRect() const { return intersection_rect_; }
void Trace(blink::Visitor*) override;
private:
PerformanceElementTiming(const AtomicString& name,
const IntRect& intersection_rect,
DOMHighResTimeStamp start_time);
void BuildJSONValue(V8ObjectBuilder&) const override;
Member<DOMRectReadOnly> intersection_rect_;
};
} // namespace blink
#endif // THIRD_PARTY_BLINK_RENDERER_CORE_TIMING_PERFORMANCE_ELEMENT_TIMING_H_
// 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.
// hhttps://docs.google.com/document/d/1blFeMVdqxB0V3BAJh60ptOBFY7cJSXnf7VyW3wspbZ8
[RuntimeEnabled=ElementTiming]
interface PerformanceElementTiming : PerformanceEntry {
readonly attribute DOMRectReadOnly intersectionRect;
serializer = {inherit, attribute};
};
...@@ -79,6 +79,8 @@ PerformanceEntry::EntryType PerformanceEntry::ToEntryTypeEnum( ...@@ -79,6 +79,8 @@ PerformanceEntry::EntryType PerformanceEntry::ToEntryTypeEnum(
return kEvent; return kEvent;
if (entry_type == PerformanceEntryNames::firstInput) if (entry_type == PerformanceEntryNames::firstInput)
return kFirstInput; return kFirstInput;
if (entry_type == PerformanceEntryNames::element)
return kElement;
return kInvalid; return kInvalid;
} }
......
...@@ -64,6 +64,7 @@ class CORE_EXPORT PerformanceEntry : public ScriptWrappable { ...@@ -64,6 +64,7 @@ class CORE_EXPORT PerformanceEntry : public ScriptWrappable {
kPaint = 1 << 6, kPaint = 1 << 6,
kEvent = 1 << 7, kEvent = 1 << 7,
kFirstInput = 1 << 8, kFirstInput = 1 << 8,
kElement = 1 << 9,
}; };
const AtomicString& name() const { return name_; } const AtomicString& name() const { return name_; }
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
}, },
data: [ data: [
"element",
"event", "event",
"firstInput", "firstInput",
"longtask", "longtask",
......
...@@ -45,6 +45,7 @@ ...@@ -45,6 +45,7 @@
#include "third_party/blink/renderer/core/loader/document_loader.h" #include "third_party/blink/renderer/core/loader/document_loader.h"
#include "third_party/blink/renderer/core/origin_trials/origin_trials.h" #include "third_party/blink/renderer/core/origin_trials/origin_trials.h"
#include "third_party/blink/renderer/core/page/chrome_client.h" #include "third_party/blink/renderer/core/page/chrome_client.h"
#include "third_party/blink/renderer/core/timing/performance_element_timing.h"
#include "third_party/blink/renderer/core/timing/performance_event_timing.h" #include "third_party/blink/renderer/core/timing/performance_event_timing.h"
#include "third_party/blink/renderer/core/timing/performance_timing.h" #include "third_party/blink/renderer/core/timing/performance_timing.h"
#include "third_party/blink/renderer/platform/cross_thread_functional.h" #include "third_party/blink/renderer/platform/cross_thread_functional.h"
...@@ -335,10 +336,6 @@ bool WindowPerformance::ShouldBufferEventTiming() { ...@@ -335,10 +336,6 @@ bool WindowPerformance::ShouldBufferEventTiming() {
return !timing() || !timing()->loadEventStart(); return !timing() || !timing()->loadEventStart();
} }
bool WindowPerformance::ObservingEventTimingEntries() {
return HasObserverFor(PerformanceEntry::kEvent);
}
void WindowPerformance::RegisterEventTiming(const AtomicString& event_type, void WindowPerformance::RegisterEventTiming(const AtomicString& event_type,
TimeTicks start_time, TimeTicks start_time,
TimeTicks processing_start, TimeTicks processing_start,
...@@ -395,7 +392,7 @@ void WindowPerformance::ReportEventTimings(WebLayerTreeView::SwapResult result, ...@@ -395,7 +392,7 @@ void WindowPerformance::ReportEventTimings(WebLayerTreeView::SwapResult result,
if (duration_in_ms <= kEventTimingDurationThresholdInMs) if (duration_in_ms <= kEventTimingDurationThresholdInMs)
continue; continue;
if (ObservingEventTimingEntries()) { if (HasObserverFor(PerformanceEntry::kEvent)) {
UseCounter::Count(GetFrame(), UseCounter::Count(GetFrame(),
WebFeature::kEventTimingExplicitlyRequested); WebFeature::kEventTimingExplicitlyRequested);
NotifyObserversOfEntry(*entry); NotifyObserversOfEntry(*entry);
...@@ -407,6 +404,15 @@ void WindowPerformance::ReportEventTimings(WebLayerTreeView::SwapResult result, ...@@ -407,6 +404,15 @@ void WindowPerformance::ReportEventTimings(WebLayerTreeView::SwapResult result,
event_timings_.clear(); event_timings_.clear();
} }
void WindowPerformance::AddElementTiming(const AtomicString& name,
const IntRect& rect,
TimeTicks timestamp) {
DCHECK(RuntimeEnabledFeatures::ElementTimingEnabled());
PerformanceEntry* entry = PerformanceElementTiming::Create(
name, rect, MonotonicTimeToDOMHighResTimeStamp(timestamp));
NotifyObserversOfEntry(*entry);
}
void WindowPerformance::DispatchFirstInputTiming( void WindowPerformance::DispatchFirstInputTiming(
PerformanceEventTiming* entry) { PerformanceEventTiming* entry) {
DCHECK(OriginTrials::EventTimingEnabled(GetExecutionContext())); DCHECK(OriginTrials::EventTimingEnabled(GetExecutionContext()));
......
...@@ -67,7 +67,6 @@ class CORE_EXPORT WindowPerformance final : public Performance, ...@@ -67,7 +67,6 @@ class CORE_EXPORT WindowPerformance final : public Performance,
void UpdateLongTaskInstrumentation() override; void UpdateLongTaskInstrumentation() override;
bool ObservingEventTimingEntries();
bool ShouldBufferEventTiming(); bool ShouldBufferEventTiming();
// This method creates a PerformanceEventTiming and if needed creates a swap // This method creates a PerformanceEventTiming and if needed creates a swap
...@@ -79,6 +78,10 @@ class CORE_EXPORT WindowPerformance final : public Performance, ...@@ -79,6 +78,10 @@ class CORE_EXPORT WindowPerformance final : public Performance,
TimeTicks processing_end, TimeTicks processing_end,
bool cancelable); bool cancelable);
void AddElementTiming(const AtomicString& name,
const IntRect& rect,
TimeTicks timestamp);
void Trace(blink::Visitor*) override; void Trace(blink::Visitor*) override;
private: private:
......
...@@ -385,6 +385,11 @@ ...@@ -385,6 +385,11 @@
// http://crbug.com/707656 content editable in LayoutNG. // http://crbug.com/707656 content editable in LayoutNG.
name: "EditingNG", name: "EditingNG",
}, },
{
// https://crbug.com/879270
name: "ElementTiming",
status: "test",
},
{ {
name: "EncodingStreams", name: "EncodingStreams",
status: "experimental", status: "experimental",
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment