summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/element-timing
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /testing/web-platform/tests/element-timing
parentInitial commit. (diff)
downloadfirefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz
firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/element-timing')
-rw-r--r--testing/web-platform/tests/element-timing/META.yml3
-rw-r--r--testing/web-platform/tests/element-timing/background-image-data-uri.html39
-rw-r--r--testing/web-platform/tests/element-timing/background-image-multiple-elements.html88
-rw-r--r--testing/web-platform/tests/element-timing/background-image-stretched.html39
-rw-r--r--testing/web-platform/tests/element-timing/buffer-before-onload.html44
-rw-r--r--testing/web-platform/tests/element-timing/buffered-flag.html40
-rw-r--r--testing/web-platform/tests/element-timing/cross-origin-element.sub.html45
-rw-r--r--testing/web-platform/tests/element-timing/cross-origin-iframe-element.sub.html36
-rw-r--r--testing/web-platform/tests/element-timing/css-generated-text.html36
-rw-r--r--testing/web-platform/tests/element-timing/disconnect-image.html41
-rw-r--r--testing/web-platform/tests/element-timing/element-only-when-fully-active.html20
-rw-r--r--testing/web-platform/tests/element-timing/first-letter-background.html57
-rw-r--r--testing/web-platform/tests/element-timing/fixed-id-identifier.html28
-rw-r--r--testing/web-platform/tests/element-timing/idlharness.window.js17
-rw-r--r--testing/web-platform/tests/element-timing/image-TAO.sub.html54
-rw-r--r--testing/web-platform/tests/element-timing/image-carousel.html75
-rw-r--r--testing/web-platform/tests/element-timing/image-clipped-svg.html34
-rw-r--r--testing/web-platform/tests/element-timing/image-data-uri.html37
-rw-r--r--testing/web-platform/tests/element-timing/image-not-added.html31
-rw-r--r--testing/web-platform/tests/element-timing/image-not-fully-visible.html47
-rw-r--r--testing/web-platform/tests/element-timing/image-rect-iframe.html33
-rw-r--r--testing/web-platform/tests/element-timing/image-src-change.html86
-rw-r--r--testing/web-platform/tests/element-timing/image-with-css-scale.html42
-rw-r--r--testing/web-platform/tests/element-timing/image-with-rotation.html50
-rw-r--r--testing/web-platform/tests/element-timing/images-repeated-resource.html73
-rw-r--r--testing/web-platform/tests/element-timing/invisible-images.html79
-rw-r--r--testing/web-platform/tests/element-timing/multiple-background-images.html58
-rw-r--r--testing/web-platform/tests/element-timing/multiple-redirects-TAO.html62
-rw-r--r--testing/web-platform/tests/element-timing/observe-background-image.html37
-rw-r--r--testing/web-platform/tests/element-timing/observe-child-element.html41
-rw-r--r--testing/web-platform/tests/element-timing/observe-elementtiming.html44
-rw-r--r--testing/web-platform/tests/element-timing/observe-empty-attribute.html38
-rw-r--r--testing/web-platform/tests/element-timing/observe-multiple-images.html122
-rw-r--r--testing/web-platform/tests/element-timing/observe-shadow-image.html38
-rw-r--r--testing/web-platform/tests/element-timing/observe-shadow-text.html37
-rw-r--r--testing/web-platform/tests/element-timing/observe-svg-image.html34
-rw-r--r--testing/web-platform/tests/element-timing/observe-text.html48
-rw-r--r--testing/web-platform/tests/element-timing/observe-video-poster.html32
-rw-r--r--testing/web-platform/tests/element-timing/progressively-loaded-image.html40
-rw-r--r--testing/web-platform/tests/element-timing/rectangular-image.html43
-rw-r--r--testing/web-platform/tests/element-timing/redirects-tao-star.html53
-rw-r--r--testing/web-platform/tests/element-timing/resources/TAOImage.py47
-rw-r--r--testing/web-platform/tests/element-timing/resources/circle-tao.svg6
-rw-r--r--testing/web-platform/tests/element-timing/resources/circle-tao.svg.headers1
-rw-r--r--testing/web-platform/tests/element-timing/resources/circle.svg6
-rw-r--r--testing/web-platform/tests/element-timing/resources/element-timing-helpers.js74
-rw-r--r--testing/web-platform/tests/element-timing/resources/iframe-stores-entry.html12
-rw-r--r--testing/web-platform/tests/element-timing/resources/iframe-with-content.html5
-rw-r--r--testing/web-platform/tests/element-timing/resources/iframe-with-square-sends-entry.html26
-rw-r--r--testing/web-platform/tests/element-timing/resources/iframe-with-square.html10
-rw-r--r--testing/web-platform/tests/element-timing/resources/multiple-redirects.py59
-rw-r--r--testing/web-platform/tests/element-timing/resources/progressive-image.py27
-rw-r--r--testing/web-platform/tests/element-timing/resources/slow-image.py19
-rw-r--r--testing/web-platform/tests/element-timing/resources/square100.pngbin0 -> 12940 bytes
-rw-r--r--testing/web-platform/tests/element-timing/resources/square20.jpgbin0 -> 529 bytes
-rw-r--r--testing/web-platform/tests/element-timing/resources/square20.pngbin0 -> 150 bytes
-rw-r--r--testing/web-platform/tests/element-timing/retrievability.html29
-rw-r--r--testing/web-platform/tests/element-timing/same-origin-redirects.html37
-rw-r--r--testing/web-platform/tests/element-timing/scroll-to-text.html30
-rw-r--r--testing/web-platform/tests/element-timing/supported-element-type.html17
-rw-r--r--testing/web-platform/tests/element-timing/text-with-display-style.html54
-rw-r--r--testing/web-platform/tests/element-timing/toJSON.html45
62 files changed, 2405 insertions, 0 deletions
diff --git a/testing/web-platform/tests/element-timing/META.yml b/testing/web-platform/tests/element-timing/META.yml
new file mode 100644
index 0000000000..15510df7e5
--- /dev/null
+++ b/testing/web-platform/tests/element-timing/META.yml
@@ -0,0 +1,3 @@
+spec: https://wicg.github.io/element-timing/
+suggested_reviewers:
+ - npm1
diff --git a/testing/web-platform/tests/element-timing/background-image-data-uri.html b/testing/web-platform/tests/element-timing/background-image-data-uri.html
new file mode 100644
index 0000000000..9722463742
--- /dev/null
+++ b/testing/web-platform/tests/element-timing/background-image-data-uri.html
@@ -0,0 +1,39 @@
+<!DOCTYPE HTML>
+<meta charset=utf-8>
+<title>Element Timing: observe element with inline background image</title>
+<body>
+<style>
+body {
+ margin: 0;
+}
+#target {
+ width: 100px;
+ height: 50px;
+ background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKAQMAAAC3/F3+AAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAABlBMVEX/AAD///9BHTQRAAAAAWJLR0QB/wIt3gAAAAtJREFUCNdjYMAHAAAeAAFuhUcyAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE5LTA0LTA1VDIwOjA4OjQxKzAyOjAwPa6EZwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxOS0wNC0wNVQyMDowODo0MSswMjowMEzzPNsAAAAASUVORK5CYII=);
+}
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/element-timing-helpers.js"></script>
+<script>
+ let beforeRender = performance.now();
+ async_test(function (t) {
+ assert_implements(window.PerformanceElementTiming, "PerformanceElementTiming is not implemented");
+ const observer = new PerformanceObserver(
+ t.step_func_done(function(entryList) {
+ assert_equals(entryList.getEntries().length, 1);
+ const entry = entryList.getEntries()[0];
+ // Only the first characters of the data URI are included in the entry.
+ const uriPrefix = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKAQMAAAC3/F3+AAAABGdBTUEAALGPC/xhBQAAACBjSFJNAA';
+ checkElementWithoutResourceTiming(entry, uriPrefix, 'my_div', 'target',
+ beforeRender, document.getElementById('target'));
+ // The background image is a red square of length 10.
+ checkRect(entry, [0, 100, 0, 50]);
+ checkNaturalSize(entry, 10, 10);
+ })
+ );
+ observer.observe({entryTypes: ['element']});
+ }, 'Element with elementtiming attribute and inline background image is observable.');
+</script>
+<div id='target' elementtiming='my_div'></div>
+</body>
diff --git a/testing/web-platform/tests/element-timing/background-image-multiple-elements.html b/testing/web-platform/tests/element-timing/background-image-multiple-elements.html
new file mode 100644
index 0000000000..11f8c05f96
--- /dev/null
+++ b/testing/web-platform/tests/element-timing/background-image-multiple-elements.html
@@ -0,0 +1,88 @@
+<!DOCTYPE HTML>
+<meta charset=utf-8>
+<title>Element Timing: background image affecting multiple elements</title>
+<body>
+<style>
+body {
+ margin: 0;
+}
+.my_div {
+ background-image: url('resources/square100.png');
+}
+#div1 {
+ width: 100px;
+ height: 100px;
+}
+#div2 {
+ width: 200px;
+ height: 100px;
+}
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/element-timing-helpers.js"></script>
+<script>
+ async_test(function (t) {
+ assert_implements(window.PerformanceElementTiming, "PerformanceElementTiming is not implemented");
+ let beforeRender = performance.now();
+ let numObservedElements = 0;
+ let observedDiv1 = false;
+ let observedDiv2Img = false;
+ let observedDiv2Txt = false;
+ const pathname = window.location.origin + '/element-timing/resources/square100.png';
+ const observer = new PerformanceObserver(
+ t.step_func(function(entryList) {
+ entryList.getEntries().forEach(entry => {
+ numObservedElements++;
+ if (entry.id == 'div1') {
+ observedDiv1 = true;
+ checkElement(entry, pathname, 'et1', 'div1', beforeRender,
+ document.getElementById('div1'));
+ // Div is in the top left corner.
+ checkRect(entry, [0, 100, 0, 100]);
+ checkNaturalSize(entry, 100, 100);
+ }
+ else if (entry.id == 'div2') {
+ // Check image entry.
+ if (entry.name !== 'text-paint') {
+ observedDiv2Img = true;
+ checkElement(entry, pathname, 'et2', 'div2', beforeRender,
+ document.getElementById('div2'));
+ // Div is below div1, on the left.
+ checkRect(entry, [0, 200, 100, 200]);
+ checkNaturalSize(entry, 100, 100);
+ }
+ // Check the text entry.
+ else {
+ observedDiv2Txt = true;
+ checkTextElement(entry, 'et2', 'div2', beforeRender,
+ document.getElementById('div2'));
+ assert_greater_than_equal(entry.intersectionRect.right - entry.intersectionRect.left, 50);
+ assert_greater_than_equal(entry.intersectionRect.bottom - entry.intersectionRect.top, 10);
+ }
+ }
+ else {
+ assert_unreached("Should not observe other elements!");
+ }
+ if (numObservedElements === 3) {
+ assert_true(observedDiv1);
+ assert_true(observedDiv2Img);
+ assert_true(observedDiv2Txt);
+ t.done();
+ }
+ });
+ })
+ );
+ observer.observe({entryTypes: ['element']});
+ }, 'Background image affecting various elements is observed.');
+</script>
+<div id="div1" class="my_div" elementtiming="et1">
+ <img width=50 height=50 src='resources/circle.svg'/>
+</div>
+<div width=200 height=100 id="div2" class="my_div" elementtiming="et2">
+ Sample text inside div.
+</div>
+<div id="div3"/>
+ I am a div that should not be observed!
+</div>
+</body>
diff --git a/testing/web-platform/tests/element-timing/background-image-stretched.html b/testing/web-platform/tests/element-timing/background-image-stretched.html
new file mode 100644
index 0000000000..3ad8976ccf
--- /dev/null
+++ b/testing/web-platform/tests/element-timing/background-image-stretched.html
@@ -0,0 +1,39 @@
+<!DOCTYPE HTML>
+<meta charset=utf-8>
+<title>Element Timing: observe larger element with stretched background image</title>
+<body>
+<style>
+body {
+ margin: 0;
+}
+#target {
+ width: 200px;
+ height: 150px;
+ background-image: url('resources/square100.png');
+}
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/element-timing-helpers.js"></script>
+<script>
+ let beforeRender = performance.now();
+ async_test(function (t) {
+ assert_implements(window.PerformanceElementTiming, "PerformanceElementTiming is not implemented");
+ const observer = new PerformanceObserver(
+ t.step_func_done(function(entryList) {
+ assert_equals(entryList.getEntries().length, 1);
+ const entry = entryList.getEntries()[0];
+ const pathname = window.location.origin + '/element-timing/resources/square100.png';
+ checkElement(entry, pathname, 'my_div', 'target', beforeRender,
+ document.getElementById('target'));
+ // The background image extends to occupy to full size of the div.
+ checkRect(entry, [0, 200, 0, 150]);
+ // The natural size of the square remains unchanged.
+ checkNaturalSize(entry, 100, 100);
+ })
+ );
+ observer.observe({entryTypes: ['element']});
+ }, 'Element with background image shows correct image size.');
+</script>
+<div id='target' elementtiming='my_div'></div>
+</body>
diff --git a/testing/web-platform/tests/element-timing/buffer-before-onload.html b/testing/web-platform/tests/element-timing/buffer-before-onload.html
new file mode 100644
index 0000000000..17c8238f6e
--- /dev/null
+++ b/testing/web-platform/tests/element-timing/buffer-before-onload.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<html>
+<meta charset=utf-8 />
+<title>Element Timing: buffer elements before onload</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src="resources/element-timing-helpers.js"></script>
+<body>
+<img src=resources/slow-image.py?name=square20.png&sleep=500>
+<script>
+ /*
+ In this test, a slow image is added to the frame to delay onload. The entry
+ is available from the observer with the buffered flag set to true.
+ */
+ async_test(function(t) {
+ assert_implements(window.PerformanceElementTiming, "PerformanceElementTiming is not implemented");
+ beforeRender = performance.now();
+ const img = document.createElement('img');
+ img.src = 'resources/square20.jpg';
+ img.setAttribute('elementtiming', 'my_image');
+ img.setAttribute('id', 'my_id');
+ document.body.appendChild(img);
+
+ // this PerformanceObserver should be notified about the previously
+ // buffered element entry
+ new PerformanceObserver(t.step_func((entryList, observer) => {
+ assert_equals(entryList.getEntries().length, 1);
+ entryList.getEntries().forEach(entry => {
+ assert_equals(entry.entryType, "element");
+ const pathname = window.location.origin + '/element-timing/resources/square20.jpg';
+ checkElement(entry, pathname, 'my_image', 'my_id', beforeRender, img);
+ checkNaturalSize(entry, 20, 20);
+ observer.disconnect();
+ t.done();
+ });
+ })).observe({
+ type: "element",
+ buffered: true
+ });
+ }, "Element Timing: image loads before onload available from buffered flag.");
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/element-timing/buffered-flag.html b/testing/web-platform/tests/element-timing/buffered-flag.html
new file mode 100644
index 0000000000..e7fcf059fb
--- /dev/null
+++ b/testing/web-platform/tests/element-timing/buffered-flag.html
@@ -0,0 +1,40 @@
+<!DOCTYPE HTML>
+<meta charset=utf-8>
+<title>Element Timing: observe element with buffered flag</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>
+ async_test(t => {
+ assert_implements(window.PerformanceElementTiming, "PerformanceElementTiming is not implemented");
+ const beforeRender = performance.now();
+ const img = document.createElement('img');
+ // Initial observer used to know when entry has been dispatched
+ new PerformanceObserver(() => {
+ // Second observer should require buffered flag to receive the already-dispatched entry.
+ new PerformanceObserver(t.step_func_done(entryList => {
+ assert_equals(entryList.getEntries().length, 1);
+ const entry = entryList.getEntries()[0];
+ const pathname = window.location.origin + '/images/black-rectangle.png';
+ checkElement(entry, pathname, 'my_image', 'my_id', beforeRender, document.getElementById('my_id'));
+ // Test that viewport size is at least 100, so element is fully visible.
+ assert_greater_than_equal(document.documentElement.clientWidth, 100, 'Width should be >= 100');
+ assert_greater_than_equal(document.documentElement.clientHeight, 100, 'Height should be >= 100');
+
+ checkRect(entry, [0, 100, 0, 50]);
+ checkNaturalSize(entry, 100, 50);
+ })).observe({type: 'element', buffered: true});
+ }).observe({type: 'element'});
+ img.src = '/images/black-rectangle.png';
+ img.setAttribute('elementtiming', 'my_image');
+ img.setAttribute('id', 'my_id');
+ document.body.appendChild(img);
+ }, 'Element Timing entries are observable via buffered flag.');
+</script>
+</body>
diff --git a/testing/web-platform/tests/element-timing/cross-origin-element.sub.html b/testing/web-platform/tests/element-timing/cross-origin-element.sub.html
new file mode 100644
index 0000000000..1052bf246d
--- /dev/null
+++ b/testing/web-platform/tests/element-timing/cross-origin-element.sub.html
@@ -0,0 +1,45 @@
+<!DOCTYPE HTML>
+<meta charset=utf-8>
+<title>Element Timing: observe cross-origin images but without renderTime</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>
+ async_test((t) => {
+ assert_implements(window.PerformanceElementTiming, "PerformanceElementTiming is not implemented");
+ let img;
+ const pathname = 'http://{{domains[www]}}:{{ports[http][1]}}'
+ + '/element-timing/resources/square100.png';
+ const observer = new PerformanceObserver(
+ t.step_func_done((entryList) => {
+ assert_equals(entryList.getEntries().length, 1);
+ const entry = entryList.getEntries()[0];
+ checkElement(entry, pathname, 'my_image', 'the_id', 0, img);
+ assert_equals(entry.renderTime, 0,
+ 'The renderTime of a cross-origin image should be 0.');
+ checkRect(entry, [0, 100, 0, 100]);
+ checkNaturalSize(entry, 100, 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.
+ // TODO(npm): change observer to use buffered flag.
+ window.onload = t.step_func(() => {
+ // Add a cross origin image resource.
+ img = document.createElement('img');
+ img.src = pathname;
+ img.setAttribute('elementtiming', 'my_image');
+ img.setAttribute('id', 'the_id');
+ document.body.appendChild(img);
+ });
+ }, 'Cross-origin image element is NOT observable.');
+</script>
+
+</body>
diff --git a/testing/web-platform/tests/element-timing/cross-origin-iframe-element.sub.html b/testing/web-platform/tests/element-timing/cross-origin-iframe-element.sub.html
new file mode 100644
index 0000000000..2f49933ab9
--- /dev/null
+++ b/testing/web-platform/tests/element-timing/cross-origin-iframe-element.sub.html
@@ -0,0 +1,36 @@
+<!DOCTYPE HTML>
+<meta charset=utf-8>
+<title>Element Timing: do NOT observe elements from cross-origin iframes</title>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/element-timing-helpers.js"></script>
+<script>
+ async_test((t) => {
+ assert_implements(window.PerformanceElementTiming, "PerformanceElementTiming is not implemented");
+ const observer = new PerformanceObserver(
+ t.step_func_done((entryList) => {
+ assert_unreached("We should not observe a cross origin element.");
+ })
+ );
+ observer.observe({entryTypes: ['element']});
+ // We add the iframe during onload to be sure that the observer is registered
+ // in time for it to observe the element timing.
+ // TODO(npm): change observer to use buffered flag.
+ window.onload = t.step_func(() => {
+ // Add a cross origin iframe with an image.
+ const iframe = document.createElement('iframe');
+ iframe.src = 'http://{{domains[www]}}:{{ports[http][1]}}'
+ + '/element-timing/resources/iframe-with-square.html';
+ document.body.appendChild(iframe);
+ iframe.onload = t.step_func(() => {
+ t.step_timeout( () => {
+ // After some wait, assume observer did not receive the entry, so the test passes.
+ t.done();
+ }, 100);
+ });
+ });
+ }, 'Element from cross origin iframe is NOT observable.');
+</script>
+
+</body>
diff --git a/testing/web-platform/tests/element-timing/css-generated-text.html b/testing/web-platform/tests/element-timing/css-generated-text.html
new file mode 100644
index 0000000000..d1bbf5a7ee
--- /dev/null
+++ b/testing/web-platform/tests/element-timing/css-generated-text.html
@@ -0,0 +1,36 @@
+<!DOCTYPE HTML>
+<meta charset=utf-8>
+<title>Element Timing: observe generated CSS text</title>
+<style>
+p::before {
+ content: "Generated text prefix";
+}
+body {
+ margin: 20px;
+}
+</style>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/element-timing-helpers.js"></script>
+<p elementtiming='my_text' id='text_id'></p>
+<script>
+ async_test(function (t) {
+ const beforeRender = performance.now();
+ assert_implements(window.PerformanceElementTiming, "PerformanceElementTiming is not implemented");
+ const observer = new PerformanceObserver(
+ t.step_func_done(function(entryList) {
+ assert_equals(entryList.getEntries().length, 1);
+ const entry = entryList.getEntries()[0];
+ checkTextElement(entry, 'my_text', 'text_id', beforeRender, document.getElementById('text_id'));
+ assert_equals(entry.intersectionRect.left, 20, 'left should be 20.');
+ assert_equals(entry.intersectionRect.top, 20, 'top should be 20.');
+ // Try a lower bound of height=10, width=80 for the generated text.
+ assert_greater_than_equal(entry.intersectionRect.right, 100);
+ assert_greater_than_equal(entry.intersectionRect.bottom, 30);
+ })
+ );
+ observer.observe({type: 'element', buffered: true});
+ }, 'Generated text content is observable.');
+</script>
+</body>
diff --git a/testing/web-platform/tests/element-timing/disconnect-image.html b/testing/web-platform/tests/element-timing/disconnect-image.html
new file mode 100644
index 0000000000..6f9f2ce7b9
--- /dev/null
+++ b/testing/web-platform/tests/element-timing/disconnect-image.html
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML>
+<meta charset=utf-8>
+<title>Element Timing: element attribute returns null when element is disconnected</title>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/element-timing-helpers.js"></script>
+<script>
+ let beforeRender;
+ let img;
+ async_test(function (t) {
+ assert_implements(window.PerformanceElementTiming, "PerformanceElementTiming is not implemented");
+ const observer = new PerformanceObserver(
+ t.step_func_done(function(entryList) {
+ assert_equals(entryList.getEntries().length, 1);
+ const entry = entryList.getEntries()[0];
+ const pathname = window.location.origin + '/element-timing/resources/square100.png';
+ // This method will check that entry.element is |img|.
+ checkElement(entry, pathname, 'my_image', 'my_id', beforeRender, img);
+
+ img.parentNode.removeChild(img);
+ // After removing image, entry.element should return null.
+ assert_equals(entry.element, null);
+ })
+ );
+ 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 equal to 100 and height equal to 100.
+ img = document.createElement('img');
+ img.src = 'resources/square100.png';
+ img.setAttribute('elementtiming', 'my_image');
+ img.setAttribute('id', 'my_id');
+ document.body.appendChild(img);
+ beforeRender = performance.now();
+ };
+ }, 'Disconnected elements have null as their |element| attribute.');
+</script>
+
+</body>
diff --git a/testing/web-platform/tests/element-timing/element-only-when-fully-active.html b/testing/web-platform/tests/element-timing/element-only-when-fully-active.html
new file mode 100644
index 0000000000..ff08074d57
--- /dev/null
+++ b/testing/web-platform/tests/element-timing/element-only-when-fully-active.html
@@ -0,0 +1,20 @@
+<!DOCTYPE HTML>
+<meta charset=utf-8>
+<title>ElementTiming: element is only exposed for fully active documents.</title>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<iframe src="resources/iframe-stores-entry.html" id="ifr"></iframe>
+<script>
+ let t = async_test('Only expose element attribute for fully active documents');
+ t.step(() => {
+ assert_implements(window.PerformanceElementTiming, "PerformanceElementTiming is not implemented");
+ });
+ window.triggerTest = t.step_func_done(elementEntry => {
+ assert_not_equals(elementEntry.element, null);
+ const iframe = document.getElementById('ifr');
+ iframe.remove();
+ assert_equals(elementEntry.element, null);
+ });
+</script>
+</body>
diff --git a/testing/web-platform/tests/element-timing/first-letter-background.html b/testing/web-platform/tests/element-timing/first-letter-background.html
new file mode 100644
index 0000000000..f05f2f9223
--- /dev/null
+++ b/testing/web-platform/tests/element-timing/first-letter-background.html
@@ -0,0 +1,57 @@
+<!DOCTYPE HTML>
+<meta charset=utf-8>
+<title>Element Timing: observe element with background image in its first letter</title>
+<body>
+<style>
+#target::first-letter {
+ background-image: url('/images/black-rectangle.png');
+}
+#target {
+ font-size: 12px;
+}
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/element-timing-helpers.js"></script>
+<script>
+ let beforeRender = performance.now();
+ async_test(function (t) {
+ assert_implements(window.PerformanceElementTiming, "PerformanceElementTiming is not implemented");
+ const div = document.getElementById('target');
+ let textObserved = false;
+ let imageObserved = false;
+ function calculateSize(entry) {
+ const ir = entry.intersectionRect;
+ return (ir.right - ir.left) * (ir.bottom - ir.top);
+ }
+ const observer = new PerformanceObserver(
+ t.step_func(function(entryList) {
+ entryList.getEntries().forEach(entry => {
+ if (entry.name === 'text-paint') {
+ checkTextElement(entry, 'my_div', 'target', beforeRender, div);
+ textObserved = true;
+ const size = calculateSize(entry);
+ // Assume average width is between 4px and 15px.
+ // Therefore, text size must be between 12 * (35*4) and 12 * (35*15).
+ assert_between_inclusive(size, 1680, 6300);
+ }
+ else {
+ assert_equals(entry.name, 'image-paint');
+ const pathname = window.location.origin + '/images/black-rectangle.png';
+ checkElement(entry, pathname, 'my_div', 'target', beforeRender, div);
+ checkNaturalSize(entry, 100, 50);
+ imageObserved = true;
+ const size = calculateSize(entry);
+ // Background image size should only be approximately the size of a single letter.
+ assert_between_inclusive(size, 48, 180);
+ }
+ });
+ if (textObserved && imageObserved)
+ t.done();
+ })
+ );
+ observer.observe({entryTypes: ['element']});
+ }, 'Element with elementtiming attribute and background image in first-letter is observable.');
+</script>
+<div id='target' elementtiming='my_div'>This is some text that I care about</div>
+</body>
diff --git a/testing/web-platform/tests/element-timing/fixed-id-identifier.html b/testing/web-platform/tests/element-timing/fixed-id-identifier.html
new file mode 100644
index 0000000000..749b9ada2f
--- /dev/null
+++ b/testing/web-platform/tests/element-timing/fixed-id-identifier.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML>
+<meta charset=utf-8>
+<title>Element Timing: entry does not change its id or identifier value</title>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/element-timing-helpers.js"></script>
+<p elementtiming='my_identifier' id='my_id'>Text</p>
+<script>
+ async_test(function (t) {
+ assert_implements(window.PerformanceElementTiming, "PerformanceElementTiming is not implemented");
+ const observer = new PerformanceObserver(
+ t.step_func_done(function(entryList) {
+ assert_equals(entryList.getEntries().length, 1);
+ const entry = entryList.getEntries()[0];
+ assert_equals(entry.id, 'my_id');
+ assert_equals(entry.identifier, 'my_identifier');
+ const element = document.getElementById('my_id');
+ element.id = 'other_id';
+ element.setAttribute('elementtiming', 'other_identifier');
+ assert_equals(entry.id, 'my_id');
+ assert_equals(entry.identifier, 'my_identifier');
+ })
+ );
+ observer.observe({type: 'element', buffered: true});
+ }, 'PerformanceElementTiming id and identifier do not change when Element changes.');
+</script>
+</body>
diff --git a/testing/web-platform/tests/element-timing/idlharness.window.js b/testing/web-platform/tests/element-timing/idlharness.window.js
new file mode 100644
index 0000000000..a3189c3679
--- /dev/null
+++ b/testing/web-platform/tests/element-timing/idlharness.window.js
@@ -0,0 +1,17 @@
+// META: script=/resources/WebIDLParser.js
+// META: script=/resources/idlharness.js
+// META: timeout=long
+
+// https://wicg.github.io/element-timing/
+
+'use strict';
+
+idl_test(
+ ['element-timing'],
+ ['performance-timeline', 'dom'],
+ idl_array => {
+ idl_array.add_objects({
+ // PerformanceElementTiming: [ TODO ]
+ });
+ }
+);
diff --git a/testing/web-platform/tests/element-timing/image-TAO.sub.html b/testing/web-platform/tests/element-timing/image-TAO.sub.html
new file mode 100644
index 0000000000..7b455dfa4c
--- /dev/null
+++ b/testing/web-platform/tests/element-timing/image-TAO.sub.html
@@ -0,0 +1,54 @@
+<!DOCTYPE HTML>
+<meta charset=utf-8>
+<title>Element Timing: observe cross origin images with various Timing-Allow-Origin headers</title>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/element-timing-helpers.js"></script>
+<script>
+ async_test(t => {
+ assert_implements(window.PerformanceElementTiming, "PerformanceElementTiming is not implemented");
+ const beforeRender = performance.now();
+ const remote_img = 'http://{{domains[www]}}:{{ports[http][1]}}/element-timing/resources/TAOImage.py?'
+ + 'origin=' + window.location.origin +'&tao=';
+ const valid_tao = ['wildcard', 'origin', 'multi', 'multi_wildcard', 'match_origin', 'match_wildcard'];
+ function addImage(tao) {
+ const img = document.createElement('img');
+ img.src = remote_img + tao;
+ img.setAttribute('elementtiming', tao);
+ img.id = 'id_' + tao;
+ document.body.appendChild(img);
+ }
+ valid_tao.forEach(tao => {
+ addImage(tao);
+ });
+ const invalid_tao = ['null', 'space', 'uppercase'];
+ invalid_tao.forEach(tao => {
+ addImage(tao);
+ });
+ let img_count = 0;
+ const total_images = valid_tao.length + invalid_tao.length;
+ new PerformanceObserver(
+ t.step_func(entryList => {
+ entryList.getEntries().forEach(entry => {
+ img_count++;
+ const tao = entry.identifier;
+ const img = document.getElementById('id_' + tao);
+ if (valid_tao.includes(tao)) {
+ checkElement(entry, remote_img + tao, tao, 'id_' + tao, beforeRender, img);
+ } else if (invalid_tao.includes(tao)) {
+ assert_equals(entry.renderTime, 0, 'Entry with tao=' + tao + ' must have a renderTime of 0');
+ checkElement(entry, remote_img + tao, tao, 'id_' + tao, 0, img);
+ }
+ else {
+ assert_unreached('Should be in one of valid_tao OR invalid_tao');
+ }
+ checkNaturalSize(entry, 100, 100);
+ if (img_count == total_images)
+ t.done();
+ });
+ })
+ ).observe({type: 'element', buffered: true});
+ }, 'Cross-origin elements with valid TAO have correct renderTime, with invalid TAO have renderTime set to 0.');
+</script>
+</body>
diff --git a/testing/web-platform/tests/element-timing/image-carousel.html b/testing/web-platform/tests/element-timing/image-carousel.html
new file mode 100644
index 0000000000..2b3b618f8c
--- /dev/null
+++ b/testing/web-platform/tests/element-timing/image-carousel.html
@@ -0,0 +1,75 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Element Timing: observe images in carousel</title>
+<style>
+body {
+ margin: 0;
+}
+/* Do not display images by default */
+.carousel-image {
+ display: none;
+}
+</style>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/element-timing-helpers.js"></script>
+
+<div class="slideshow-container">
+ <div class='carousel-image'>
+ <img src="resources/circle.svg" elementtiming='image0' id='image0'>
+ </div>
+ <div class='carousel-image'>
+ <img src="resources/square100.png" elementtiming='image1' id='image1'>
+ </div>
+</div>
+
+<script>
+ async_test(function (t) {
+ assert_implements(window.PerformanceElementTiming, "PerformanceElementTiming is not implemented");
+ const beforeRenderTimes = [];
+ let entry_count = 0;
+ const entry_count_per_element = [0, 0];
+ const pathname0 = window.location.origin + '/element-timing/resources/circle.svg';
+ const pathname1 = window.location.origin + '/element-timing/resources/square100.png';
+ const observer = new PerformanceObserver(t.step_func(list => {
+ list.getEntries().forEach(entry => {
+ if (entry_count % 2 == 0) {
+ checkElement(entry, pathname0, 'image0', 'image0', beforeRenderTimes[entry_count],
+ document.getElementById('image0'));
+ checkRect(entry, [0, 200, 0, 200]);
+ checkNaturalSize(entry, 200, 200);
+ entry_count_per_element[0]++;
+ }
+ else {
+ checkElement(entry, pathname1, 'image1', 'image1', beforeRenderTimes[entry_count],
+ document.getElementById('image1'));
+ checkRect(entry, [0, 100, 0, 100]);
+ checkNaturalSize(entry, 100, 100);
+ entry_count_per_element[1]++;
+ }
+ entry_count++;
+ // Check each image twice before ending the test.
+ if (entry_count == 4) {
+ assert_equals(entry_count_per_element[0], 2);
+ assert_equals(entry_count_per_element[1], 2);
+ t.done();
+ }
+ })
+ }));
+ observer.observe({entryTypes: ['element']});
+ let slideIndex = 0;
+ showCarousel();
+
+ function showCarousel() {
+ beforeRenderTimes.push(performance.now());
+ const slides = document.getElementsByClassName("carousel-image");
+ slides[slideIndex].style.display = "block";
+ slides[1 - slideIndex].style.display = "none";
+ slideIndex = 1 - slideIndex;
+ t.step_timeout(showCarousel, 50); // Change image every 50 ms.
+ }
+ }, 'Entries for elements within an image carousel are dispatched when the elements are redrawn.');
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/element-timing/image-clipped-svg.html b/testing/web-platform/tests/element-timing/image-clipped-svg.html
new file mode 100644
index 0000000000..4c2bb36079
--- /dev/null
+++ b/testing/web-platform/tests/element-timing/image-clipped-svg.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<meta charset=utf-8>
+<title>Element Timing: observe image inside SVG with small dimensions</title>
+<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) {
+ assert_implements(window.PerformanceElementTiming, "PerformanceElementTiming is not implemented");
+ const observer = new PerformanceObserver(
+ t.step_func_done(function(entryList) {
+ assert_equals(entryList.getEntries().length, 1);
+ const entry = entryList.getEntries()[0];
+ const pathname = window.location.origin + '/element-timing/resources/circle.svg';
+ checkElement(entry, pathname, 'my_svg', 'SVG', beforeRender,
+ document.getElementById('SVG'));
+ // Image size is 200x200 but SVG size is 100x100 so it is clipped.
+ checkRect(entry, [0, 100, 0, 100]);
+ checkNaturalSize(entry, 200, 200);
+ })
+ );
+ observer.observe({entryTypes: ['element']});
+ beforeRender = performance.now();
+}, "Able to observe svg image.");
+</script>
+<style>
+body {
+ margin: 0;
+}
+</style>
+<svg width="100" height="100">
+ <image href='resources/circle.svg' elementtiming='my_svg' id='SVG'/>
+</svg>
diff --git a/testing/web-platform/tests/element-timing/image-data-uri.html b/testing/web-platform/tests/element-timing/image-data-uri.html
new file mode 100644
index 0000000000..02d88fb244
--- /dev/null
+++ b/testing/web-platform/tests/element-timing/image-data-uri.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML>
+<meta charset=utf-8>
+<title>Element Timing: observe inline image</title>
+<body>
+<style>
+body {
+ margin: 0;
+}
+#inline_wee {
+ display: block;
+}
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/element-timing-helpers.js"></script>
+<script>
+ let beforeRender = performance.now();
+ async_test(function (t) {
+ assert_implements(window.PerformanceElementTiming, "PerformanceElementTiming is not implemented");
+ const observer = new PerformanceObserver(
+ t.step_func_done(function(entryList) {
+ assert_equals(entryList.getEntries().length, 1);
+ const entry = entryList.getEntries()[0];
+ // Only the first characters of the data URI are included in the entry.
+ const uriPrefix = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKAQMAAAC3/F3+AAAABGdBTUEAALGPC/xhBQAAACBjSFJNAA';
+ checkElementWithoutResourceTiming(entry, uriPrefix, 'my_img', 'inline_wee',
+ beforeRender, document.getElementById('inline_wee'));
+ // The image is a red square of length 10.
+ checkRect(entry, [0, 10, 0, 10]);
+ checkNaturalSize(entry, 10, 10);
+ })
+ );
+ observer.observe({entryTypes: ['element']});
+ }, 'Inline image is observable via Element Timing.');
+</script>
+<img elementtiming='my_img' id='inline_wee' src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKAQMAAAC3/F3+AAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAABlBMVEX/AAD///9BHTQRAAAAAWJLR0QB/wIt3gAAAAtJREFUCNdjYMAHAAAeAAFuhUcyAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE5LTA0LTA1VDIwOjA4OjQxKzAyOjAwPa6EZwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxOS0wNC0wNVQyMDowODo0MSswMjowMEzzPNsAAAAASUVORK5CYII="/>
+</body>
diff --git a/testing/web-platform/tests/element-timing/image-not-added.html b/testing/web-platform/tests/element-timing/image-not-added.html
new file mode 100644
index 0000000000..d77049ecd4
--- /dev/null
+++ b/testing/web-platform/tests/element-timing/image-not-added.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML>
+<meta charset=utf-8>
+<title>Element Timing: do not observe a disconnected image</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ async_test(function (t) {
+ assert_implements(window.PerformanceElementTiming, "PerformanceElementTiming is not implemented");
+ const observer = new PerformanceObserver(
+ t.step_func_done(() => {
+ // The image should not have caused an entry, so fail test.
+ assert_unreached('Should not have received an entry!');
+ })
+ );
+ 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 equal to 100 and height equal to 100.
+ const img = document.createElement('img');
+ img.src = 'resources/square100.png';
+ img.setAttribute('elementtiming', 'my_image');
+ img.setAttribute('id', 'my_id');
+ // Image has been created but not added.
+ // Wait for 500ms and end test, ensuring no entry was created.
+ t.step_timeout(() => {
+ t.done();
+ }, 500);
+ };
+ }, 'Image which is not added to DOM tree is not observable.');
+</script>
diff --git a/testing/web-platform/tests/element-timing/image-not-fully-visible.html b/testing/web-platform/tests/element-timing/image-not-fully-visible.html
new file mode 100644
index 0000000000..504d17592f
--- /dev/null
+++ b/testing/web-platform/tests/element-timing/image-not-fully-visible.html
@@ -0,0 +1,47 @@
+<!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;
+ let img;
+ async_test(function (t) {
+ assert_implements(window.PerformanceElementTiming, "PerformanceElementTiming is not implemented");
+ const observer = new PerformanceObserver(
+ t.step_func_done(function(entryList) {
+ assert_equals(entryList.getEntries().length, 1);
+ const entry = entryList.getEntries()[0];
+ const pathname = window.location.origin + '/element-timing/resources/square20.png';
+ checkElement(entry, pathname, 'not_fully_visible', '', beforeRender, img);
+ // 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]);
+ checkNaturalSize(entry, 20, 20);
+ })
+ );
+ 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.
+ img = document.createElement('img');
+ img.src = 'resources/square20.png';
+ 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>
diff --git a/testing/web-platform/tests/element-timing/image-rect-iframe.html b/testing/web-platform/tests/element-timing/image-rect-iframe.html
new file mode 100644
index 0000000000..00986366e6
--- /dev/null
+++ b/testing/web-platform/tests/element-timing/image-rect-iframe.html
@@ -0,0 +1,33 @@
+<!DOCTYPE HTML>
+<meta charset=utf-8>
+<title>Element Timing: check intersectionRect for element in iframe</title>
+<body>
+<style>
+body {
+ margin: 50px;
+}
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ async_test((t) => {
+ assert_implements(window.PerformanceElementTiming, "PerformanceElementTiming is not implemented");
+ on_event(window, 'message', e => {
+ assert_equals(e.data.length, 1);
+ assert_equals(e.data.entryType, 'element');
+ const rect = e.data.rect;
+ // rect should start at (0,0) even though main frame has a margin.
+ assert_equals(rect.left, 0);
+ assert_equals(rect.right, 100);
+ assert_equals(rect.top, 0);
+ assert_equals(rect.bottom, 100);
+ assert_equals(e.data.naturalWidth, 100);
+ assert_equals(e.data.naturalHeight, 100);
+ assert_equals(e.data.id, 'iframe_img_id');
+ assert_equals(e.data.elementId, 'iframe_img_id');
+ t.done();
+ });
+ }, 'Element Timing entry in iframe has coordinates relative to the iframe.');
+</script>
+<iframe src="resources/iframe-with-square-sends-entry.html"/>
+</body>
diff --git a/testing/web-platform/tests/element-timing/image-src-change.html b/testing/web-platform/tests/element-timing/image-src-change.html
new file mode 100644
index 0000000000..7416a3f365
--- /dev/null
+++ b/testing/web-platform/tests/element-timing/image-src-change.html
@@ -0,0 +1,86 @@
+<!DOCTYPE HTML>
+<meta charset=utf-8>
+<title>Element Timing: src change triggers new entry</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>
+ <img elementtiming='my_image' id='my_id' />
+ <script>
+ setup({"hide_test_state": true});
+
+ const performanceEntryPromise = (pathname) => {
+ return new Promise(resolve => {
+ new PerformanceObserver((entryList, observer) => {
+ assert_equals(entryList.getEntries().length, 1);
+ if (entryList.getEntries()[0].url == pathname) {
+ observer.disconnect();
+ resolve(entryList.getEntries()[0]);
+ }
+ }).observe({ type: 'element' });
+ });
+ }
+
+ promise_test(async (t) => {
+ assert_implements(window.PerformanceElementTiming, "PerformanceElementTiming is not implemented");
+
+ // Take beforeRender timestamp.
+ const beforeRender1 = performance.now();
+
+ const img = document.getElementById('my_id');
+
+ const url1 = 'resources/square100.png';
+
+ const pathname1 = (new URL(url1, document.location)).href
+
+ // Register performance observer.
+ const promise1 = performanceEntryPromise(pathname1);
+
+ //Load image
+ await new Promise(resolve => {
+ img.addEventListener('load', resolve);
+ img.src = url1;
+ });
+
+ // Get element entry.
+ const entry1 = await promise1;
+
+ // Check entry.
+ checkElement(entry1, pathname1, 'my_image', 'my_id', beforeRender1, img);
+ checkRect(entry1, [0, 100, 0, 100]);
+ checkNaturalSize(entry1, 100, 100);
+
+ // Take beforeRender timestamp before changing image src.
+ const beforeRender2 = performance.now();
+
+ // Set the src to trigger another entry.
+ const url2 = '/images/black-rectangle.png';
+
+ const pathname2 = (new URL(url2, document.location)).href;
+
+ const promise2 = performanceEntryPromise(pathname2);
+
+ //Load image with changed src.
+ await new Promise(resolve => {
+ img.addEventListener('load', resolve);
+ img.src = url2;
+ });
+
+ // Get the corresponding element entry.
+ const entry2 = await promise2;
+
+ // Check entry.
+ checkElement(entry2, pathname2, 'my_image', 'my_id', beforeRender2, img);
+ checkRect(entry2, [0, 100, 0, 50]);
+ checkNaturalSize(entry2, 100, 50);
+ }, 'Element Timing: changing src causes a new entry to be dispatched.')
+ </script>
+
+</body>
diff --git a/testing/web-platform/tests/element-timing/image-with-css-scale.html b/testing/web-platform/tests/element-timing/image-with-css-scale.html
new file mode 100644
index 0000000000..a0490f375c
--- /dev/null
+++ b/testing/web-platform/tests/element-timing/image-with-css-scale.html
@@ -0,0 +1,42 @@
+<!DOCTYPE HTML>
+<meta charset=utf-8>
+<title>Element Timing: image with scaling.</title>
+<head>
+<style>
+/* The margin of 50px allows the rect to be fully shown when the div is scaled. */
+body {
+ margin: 50px;
+}
+.my_div {
+ width: 100px;
+ height: 50px;
+ transform: scale(2);
+}
+</style>
+</head>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/element-timing-helpers.js"></script>
+<script>
+ const beforeRender = performance.now();
+ async_test(function (t) {
+ assert_implements(window.PerformanceElementTiming, "PerformanceElementTiming is not implemented");
+ const observer = new PerformanceObserver(
+ t.step_func_done(function(entryList) {
+ assert_equals(entryList.getEntries().length, 1);
+ const entry = entryList.getEntries()[0];
+ const pathname = window.location.origin + '/images/black-rectangle.png';
+ checkElement(entry, pathname, 'rectangle', 'rect_id', beforeRender,
+ document.getElementById('rect_id'));
+ checkRect(entry, [0, 200, 25, 125]);
+ checkNaturalSize(entry, 100, 50);
+ })
+ );
+ observer.observe({entryTypes: ['element']});
+ }, 'Image intersectionRect is affected by scaling, but not its intrinsic size.');
+</script>
+<div class="my_div" id='div_id'>
+ <img src="/images/black-rectangle.png" elementtiming="rectangle" id='rect_id'/>
+</div>
+</body>
diff --git a/testing/web-platform/tests/element-timing/image-with-rotation.html b/testing/web-platform/tests/element-timing/image-with-rotation.html
new file mode 100644
index 0000000000..229e9ae96c
--- /dev/null
+++ b/testing/web-platform/tests/element-timing/image-with-rotation.html
@@ -0,0 +1,50 @@
+<!DOCTYPE HTML>
+<meta charset=utf-8>
+<title>Element Timing: image with rotation.</title>
+<head>
+<style>
+body {
+ margin: 0px;
+}
+.my_div {
+ width: 100px;
+ height: 50px;
+ transform: rotate(45deg);
+ transform-origin: top left;
+}
+</style>
+</head>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/element-timing-helpers.js"></script>
+<script>
+ const beforeRender = performance.now();
+ async_test(function (t) {
+ assert_implements(window.PerformanceElementTiming, "PerformanceElementTiming is not implemented");
+ const observer = new PerformanceObserver(
+ t.step_func_done(function(entryList) {
+ assert_equals(entryList.getEntries().length, 1);
+ const entry = entryList.getEntries()[0];
+ const pathname = window.location.origin + '/images/black-rectangle.png';
+ checkElement(entry, pathname, 'rectangle', 'rect_id', beforeRender,
+ document.getElementById('rect_id'));
+ checkNaturalSize(entry, 100, 50);
+ const rect = entry.intersectionRect;
+ // The div rotates with respect to the origin, so part of it will be invisible.
+ // The width of the visible part will be 100 / sqrt(2) and the height will be
+ // 100 / sqrt(2) + 50 / sqrt(2).
+ assert_equals(rect.left, 0);
+ // Checking precision only to the nearest integer.
+ assert_equals(Math.round(rect.right), 71);
+ assert_equals(rect.top, 0);
+ assert_equals(Math.round(rect.bottom), 106);
+ })
+ );
+ observer.observe({entryTypes: ['element']});
+ }, 'Image intersectionRect is affected by rotation, but not its intrinsic size.');
+</script>
+<div class="my_div" id="div_id">
+ <img src="/images/black-rectangle.png" elementtiming="rectangle" id="rect_id"/>
+</div>
+</body>
diff --git a/testing/web-platform/tests/element-timing/images-repeated-resource.html b/testing/web-platform/tests/element-timing/images-repeated-resource.html
new file mode 100644
index 0000000000..f7296e05e7
--- /dev/null
+++ b/testing/web-platform/tests/element-timing/images-repeated-resource.html
@@ -0,0 +1,73 @@
+<!DOCTYPE HTML>
+<meta charset=utf-8>
+<title>Element Timing: observe elements with the same resource</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;
+ let numEntries = 0;
+ let loadTime1;
+ let loadTime2;
+ let renderTime1;
+ let renderTime2;
+ let img;
+ let img2;
+ async_test(function (t) {
+ assert_implements(window.PerformanceElementTiming, "PerformanceElementTiming is not implemented");
+ const observer = new PerformanceObserver(
+ t.step_func(function(entryList) {
+ assert_equals(entryList.getEntries().length, 1);
+ const entry = entryList.getEntries()[0];
+ const pathname = window.location.origin + '/element-timing/resources/square100.png';
+ // Easier to check the |element| attribute here since element ID is the same for both images.
+ checkElement(entry, pathname, entry.identifier, 'image_id', beforeRender, null);
+ checkNaturalSize(entry, 100, 100);
+ if (entry.identifier === 'my_image') {
+ ++numEntries;
+ loadTime1 = entry.loadTime;
+ renderTime1 = entry.renderTime;
+ assert_equals(entry.element, img);
+
+ img2 = document.createElement('img');
+ img2.src = 'resources/square100.png';
+ img2.setAttribute('elementtiming', 'my_image2');
+ img2.setAttribute('id', 'image_id');
+ document.body.appendChild(img2);
+ beforeRender = performance.now();
+ }
+ else if (entry.identifier === 'my_image2') {
+ ++numEntries;
+ loadTime2 = entry.loadTime;
+ renderTime2 = entry.renderTime;
+ assert_equals(entry.element, img2);
+ }
+ if (numEntries == 2) {
+ assert_greater_than(loadTime2, loadTime1, 'Second image loads after first.');
+ assert_greater_than(renderTime2, renderTime1, 'Second image renders after first');
+ t.done();
+ }
+ })
+ );
+ observer.observe({entryTypes: ['element']});
+ // We add the images 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.
+ img = document.createElement('img');
+ img.src = 'resources/square100.png';
+ img.setAttribute('elementtiming', 'my_image');
+ img.setAttribute('id', 'image_id');
+ document.body.appendChild(img);
+ beforeRender = performance.now();
+ };
+ }, 'Elements with elementtiming and same src are observable.');
+</script>
+
+</body>
diff --git a/testing/web-platform/tests/element-timing/invisible-images.html b/testing/web-platform/tests/element-timing/invisible-images.html
new file mode 100644
index 0000000000..ffde3ce2f6
--- /dev/null
+++ b/testing/web-platform/tests/element-timing/invisible-images.html
@@ -0,0 +1,79 @@
+<!DOCTYPE HTML>
+<meta charset=utf-8>
+<title>Element Timing: invisible images are not observable</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+ #opacity0 {
+ opacity: 0;
+ }
+ #visibilityHidden {
+ visibility: hidden;
+ }
+ #displayNone {
+ display: none;
+ }
+</style>
+<script>
+ async_test(function (t) {
+ assert_implements(window.PerformanceElementTiming, "PerformanceElementTiming is not implemented");
+ const observer = new PerformanceObserver(
+ t.step_func_done((entries) => {
+ // The image should not have caused an entry, so fail test.
+ assert_unreached('Should not have received an entry! Received one with identifier '
+ + entries.getEntries()[0].identifier);
+ })
+ );
+ observer.observe({entryTypes: ['element']});
+ // We add the images during onload to be sure that the observer is registered
+ // in time for it to observe the element timing.
+ window.onload = () => {
+ const img = document.createElement('img');
+ img.src = 'resources/square100.png';
+ img.setAttribute('elementtiming', 'my_image');
+ img.setAttribute('id', 'opacity0');
+ document.body.appendChild(img);
+
+ const img2 = document.createElement('img');
+ img2.src = 'resources/square20.png';
+ img2.setAttribute('elementtiming', 'my_image2');
+ img2.setAttribute('id', 'visibilityHidden');
+ document.body.appendChild(img2);
+
+ const img3 = document.createElement('img');
+ img3.src = 'resources/circle.svg';
+ img3.setAttribute('elementtiming', 'my_image3');
+ img3.setAttribute('id', 'displayNone');
+ document.body.appendChild(img3);
+
+ const div = document.createElement('div');
+ div.setAttribute('id', 'opacity0');
+ const img4 = document.createElement('img');
+ img4.src = '/images/blue.png';
+ img4.setAttribute('elementtiming', 'my_image4');
+ div.appendChild(img4);
+ document.body.appendChild(div);
+
+ const div2 = document.createElement('div');
+ div2.setAttribute('id', 'visibilityHidden');
+ const img5 = document.createElement('img');
+ img5.src = '/images/blue.png';
+ img5.setAttribute('elementtiming', 'my_image5');
+ div.appendChild(img5);
+ document.body.appendChild(div2);
+
+ const div3 = document.createElement('div');
+ div3.setAttribute('id', 'displayNone');
+ const img6 = document.createElement('img');
+ img6.src = '/images/blue.png';
+ img6.setAttribute('elementtiming', 'my_image6');
+ div.appendChild(img6);
+ document.body.appendChild(div3);
+ // Images have been added but should not cause entries to be dispatched.
+ // Wait for 500ms and end test, ensuring no entry was created.
+ t.step_timeout(() => {
+ t.done();
+ }, 500);
+ };
+ }, 'Images with opacity: 0, visibility: hidden, or display: none are not observable.');
+</script>
diff --git a/testing/web-platform/tests/element-timing/multiple-background-images.html b/testing/web-platform/tests/element-timing/multiple-background-images.html
new file mode 100644
index 0000000000..380e5e825e
--- /dev/null
+++ b/testing/web-platform/tests/element-timing/multiple-background-images.html
@@ -0,0 +1,58 @@
+<!DOCTYPE HTML>
+<meta charset=utf-8>
+<title>Element Timing: observe element with multiple background images</title>
+<body>
+<style>
+body {
+ margin: 0;
+}
+#target {
+ width: 200px;
+ height: 200px;
+ background-image: url('resources/circle.svg'), url('resources/square100.png');
+}
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/element-timing-helpers.js"></script>
+<script>
+ let beforeRender = performance.now();
+ async_test(function (t) {
+ assert_implements(window.PerformanceElementTiming, "PerformanceElementTiming is not implemented");
+ let numObservedElements = 0;
+ let observedCircle = false;
+ let observedSquare = false;
+ const pathPrefix = window.location.origin + '/element-timing/resources/';
+ let div = document.getElementById('target');
+ const observer = new PerformanceObserver(
+ t.step_func(entryList => {
+ entryList.getEntries().forEach(entry => {
+ numObservedElements++;
+ if (entry.url.endsWith('square100.png')) {
+ observedSquare = true;
+ checkElement(entry, pathPrefix + 'square100.png', 'multi', 'target', beforeRender, div);
+ checkRect(entry, [0, 200, 0, 200]);
+ checkNaturalSize(entry, 100, 100);
+ }
+ else if (entry.url.endsWith('circle.svg')) {
+ observedCircle = true;
+ checkElement(entry, pathPrefix + 'circle.svg', 'multi', 'target', beforeRender, div);
+ checkRect(entry, [0, 200, 0, 200]);
+ checkNaturalSize(entry, 200, 200);
+ }
+ else {
+ assert_unreached("Should not have observed an entry with different url!");
+ }
+ if (numObservedElements === 2) {
+ assert_true(observedCircle);
+ assert_true(observedSquare);
+ t.done();
+ }
+ });
+ })
+ );
+ observer.observe({entryTypes: ['element']});
+ }, 'Element with two background images receives both.');
+</script>
+<div id='target' elementtiming='multi'></div>
+</body>
diff --git a/testing/web-platform/tests/element-timing/multiple-redirects-TAO.html b/testing/web-platform/tests/element-timing/multiple-redirects-TAO.html
new file mode 100644
index 0000000000..3a45b552be
--- /dev/null
+++ b/testing/web-platform/tests/element-timing/multiple-redirects-TAO.html
@@ -0,0 +1,62 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>This test validates some Timing-Allow-Origin header usage in multiple redirects.</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/element-timing-helpers.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+</head>
+<body>
+<script>
+async_test(t => {
+ assert_implements(window.PerformanceElementTiming, "PerformanceElementTiming is not implemented");
+ let destUrl = get_host_info().HTTP_REMOTE_ORIGIN
+ + '/element-timing/resources/multiple-redirects.py?';
+ destUrl += 'redirect_count=2';
+ // The final resource has '*' in TAO header, so will not affect the result.
+ destUrl += '&final_resource=/element-timing/resources/circle-tao.svg';
+ destUrl += '&origin1=' + get_host_info().UNAUTHENTICATED_ORIGIN;
+ destUrl += '&origin2=' + get_host_info().HTTP_REMOTE_ORIGIN;
+ const taoCombinations = [
+ {tao1: location.origin, tao2: location.origin, passes: false},
+ {tao1: location.origin, tao2: get_host_info().HTTP_REMOTE_ORIGIN, passes: false},
+ {tao1: location.origin, tao2: 'null', passes: true},
+ {tao1: location.origin, tao2: '*', passes: true},
+ {tao1: location.origin, tao2: location.origin, passes: false},
+ {tao1: 'null', tao2: '*', passes: false},
+ {tao1: '*', tao2: 'null', passes: true},
+ ];
+ function getURL(item) {
+ return destUrl + '&tao1=' + item.tao1 + '&tao2=' + item.tao2;
+ }
+ taoCombinations.forEach((item, index) => {
+ const image = document.createElement('img');
+ image.src = getURL(item);
+ image.setAttribute('elementtiming', item.passes.toString());
+ image.setAttribute('id', index);
+ document.body.appendChild(image);
+ });
+ let observed = new Array(taoCombinations.length).fill(false);
+ let observedCount = 0;
+ const beforeRender = performance.now();
+ new PerformanceObserver(t.step_func(entries => {
+ entries.getEntries().forEach(e => {
+ const index = parseInt(e.id);
+ assert_false(observed[index]);
+ const item = taoCombinations[index];
+ const url = getURL(item);
+ checkElement(e, url, item.passes.toString(), e.id, item.passes ? beforeRender : 0,
+ document.getElementById(e.id));
+ observed[index] = true;
+ observedCount++;
+ if (observedCount === taoCombinations.length)
+ t.done();
+ });
+ })).observe({entryTypes: ['element']});
+}, 'Cross-origin images with passing/failing TAO should/shouldn\'t have its renderTime set.');
+</script>
+</body>
+</html>
+
diff --git a/testing/web-platform/tests/element-timing/observe-background-image.html b/testing/web-platform/tests/element-timing/observe-background-image.html
new file mode 100644
index 0000000000..6a43401cd0
--- /dev/null
+++ b/testing/web-platform/tests/element-timing/observe-background-image.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML>
+<meta charset=utf-8>
+<title>Element Timing: observe element with background image</title>
+<body>
+<style>
+body {
+ margin: 0;
+}
+#target {
+ width: 100px;
+ height: 50px;
+ background-image: url('/images/black-rectangle.png');
+}
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/element-timing-helpers.js"></script>
+<script>
+ let beforeRender = performance.now();
+ async_test(function (t) {
+ assert_implements(window.PerformanceElementTiming, "PerformanceElementTiming is not implemented");
+ const observer = new PerformanceObserver(
+ t.step_func_done(function(entryList) {
+ assert_equals(entryList.getEntries().length, 1);
+ const entry = entryList.getEntries()[0];
+ const pathname = window.location.origin + '/images/black-rectangle.png';
+ checkElement(entry, pathname, 'my_div', 'target', beforeRender,
+ document.getElementById('target'));
+ checkRect(entry, [0, 100, 0, 50]);
+ checkNaturalSize(entry, 100, 50);
+ })
+ );
+ observer.observe({entryTypes: ['element']});
+ }, 'Element with elementtiming attribute and background image is observable.');
+</script>
+<div id='target' elementtiming='my_div'></div>
+</body>
diff --git a/testing/web-platform/tests/element-timing/observe-child-element.html b/testing/web-platform/tests/element-timing/observe-child-element.html
new file mode 100644
index 0000000000..c8071998c5
--- /dev/null
+++ b/testing/web-platform/tests/element-timing/observe-child-element.html
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML>
+<meta charset=utf-8>
+<title>Element Timing: do NOT observe elements from same-origin iframes</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>
+ async_test((t) => {
+ assert_implements(window.PerformanceElementTiming, "PerformanceElementTiming is not implemented");
+ const observer = new PerformanceObserver(
+ t.step_func_done((entryList) => {
+ assert_unreached("Should not have received an entry!");
+ })
+ );
+ observer.observe({entryTypes: ['element']});
+ // We add the iframe during onload to be sure that the observer is registered
+ // in time for it to observe the element timing.
+ // TODO(npm): change observer to use buffered flag.
+ window.onload = () => {
+ // Add iframe with an image of width and height equal to 100.
+ const iframe = document.createElement('iframe');
+ iframe.src = 'resources/iframe-with-square.html';
+ iframe.onload = () => {
+ // After a short delay, assume that the entry was not dispatched to the
+ // parent frame.
+ t.step_timeout(() => {
+ t.done();
+ }, 100);
+ }
+ document.body.appendChild(iframe);
+ };
+ }, 'Element in child iframe is not observed, even if same-origin.');
+</script>
+
+</body>
diff --git a/testing/web-platform/tests/element-timing/observe-elementtiming.html b/testing/web-platform/tests/element-timing/observe-elementtiming.html
new file mode 100644
index 0000000000..a204f0d677
--- /dev/null
+++ b/testing/web-platform/tests/element-timing/observe-elementtiming.html
@@ -0,0 +1,44 @@
+<!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;
+ let img;
+ async_test(function (t) {
+ assert_implements(window.PerformanceElementTiming, "PerformanceElementTiming is not implemented");
+ const observer = new PerformanceObserver(
+ t.step_func_done(function(entryList) {
+ assert_equals(entryList.getEntries().length, 1);
+ const entry = entryList.getEntries()[0];
+ const pathname = window.location.origin + '/element-timing/resources/square100.png';
+ checkElement(entry, pathname, 'my_image', 'my_id', beforeRender, img);
+ // Assume viewport has size at least 100, so the element is fully visible.
+ checkRect(entry, [0, 100, 0, 100]);
+ checkNaturalSize(entry, 100, 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 equal to 100 and height equal to 100.
+ img = document.createElement('img');
+ img.src = 'resources/square100.png';
+ img.setAttribute('elementtiming', 'my_image');
+ img.setAttribute('id', 'my_id');
+ document.body.appendChild(img);
+ beforeRender = performance.now();
+ };
+ }, 'Element with elementtiming attribute is observable.');
+</script>
+
+</body>
diff --git a/testing/web-platform/tests/element-timing/observe-empty-attribute.html b/testing/web-platform/tests/element-timing/observe-empty-attribute.html
new file mode 100644
index 0000000000..baec6ee48d
--- /dev/null
+++ b/testing/web-platform/tests/element-timing/observe-empty-attribute.html
@@ -0,0 +1,38 @@
+<!DOCTYPE HTML>
+<meta charset=utf-8>
+<title>Element Timing: observe with empty elementtiming attribute</title>
+<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) {
+ assert_implements(window.PerformanceElementTiming, "PerformanceElementTiming is not implemented");
+ let observedImage = false;
+ let observedText = false;
+ const observer = new PerformanceObserver(
+ t.step_func(function(entryList) {
+ entryList.getEntries().forEach(entry => {
+ if (entry.name === 'image-paint') {
+ assert_false(observedImage, 'Image should only be observed once.');
+ const pathname = window.location.origin + '/element-timing/resources/square20.png';
+ checkElement(entry, pathname, '', 'square', beforeRender,
+ document.getElementById('square'));
+ checkNaturalSize(entry, 20, 20);
+ observedImage = true;
+ } else {
+ assert_false(observedText, 'Text should only be observed once.');
+ checkTextElement(entry, '', 'text', beforeRender, document.getElementById('text'));
+ observedText = true;
+ }
+ });
+ if (observedImage && observedText)
+ t.done();
+ })
+ );
+ observer.observe({type: 'element', buffered: true});
+ beforeRender = performance.now();
+}, "Able to observe image and text with empty elementtiming attribute.");
+</script>
+<img id='square' src='resources/square20.png' elementtiming/>
+<p id='text' elementtiming>Text!</p>
diff --git a/testing/web-platform/tests/element-timing/observe-multiple-images.html b/testing/web-platform/tests/element-timing/observe-multiple-images.html
new file mode 100644
index 0000000000..c5ea700553
--- /dev/null
+++ b/testing/web-platform/tests/element-timing/observe-multiple-images.html
@@ -0,0 +1,122 @@
+<!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) {
+ assert_implements(window.PerformanceElementTiming, "PerformanceElementTiming is not implemented");
+ const observer = new PerformanceObserver(
+ t.step_func(function(entryList) {
+ entryList.getEntries().forEach( entry => {
+ if (entry.identifier === 'image1') {
+ if (image1Observed) {
+ assert_unreached("Observer received image1 more than once");
+ t.done();
+ }
+ image1Observed = 1;
+ const pathname1 = window.location.origin + '/element-timing/resources/square100.png';
+ // The images do not contain ID, so expect an empty ID.
+ checkElement(entry, pathname1, 'image1', 'img1', beforeRender,
+ document.getElementById('img1'));
+ // 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');
+ checkNaturalSize(entry, 100, 100);
+ }
+ else if (entry.identifier === 'image2') {
+ if (image2Observed) {
+ assert_unreached("Observer received image2 more than once");
+ t.done();
+ }
+ image2Observed = 1;
+ const pathname2 = window.location.origin + '/element-timing/resources/square20.png';
+ checkElement(entry, pathname2, 'image2', 'img2', beforeRender,
+ document.getElementById('img2'));
+ // This image should be below image 1, and should respect the margin.
+ checkRect(entry, [50, 250, 250, 450], "of image2");
+ checkNaturalSize(entry, 20, 20);
+ }
+ else if (entry.identifier === 'image3') {
+ if (image3Observed) {
+ assert_unreached("Observer received image3 more than once");
+ t.done();
+ }
+ image3Observed = 1;
+ const pathname3 = window.location.origin + '/element-timing/resources/circle.svg';
+ checkElement(entry, pathname3, 'image3', 'img3', beforeRender,
+ document.getElementById('img3'));
+ // This image is just to the right of image2.
+ checkRect(entry, [250, 450, 250, 450], "of image3");
+ checkNaturalSize(entry, 200, 200);
+ }
+ else {
+ assert_unreached("Received an unexpected identifier.");
+ 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;
+ // Set a different id and elementtiming value for each image.
+ 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.png', 200);
+ requestAnimationFrame( () => {
+ t.step_timeout( () => {
+ addImage(3, 'resources/circle.svg');
+ }, 0);
+ });
+ }, 0);
+ });
+ beforeRender = performance.now();
+ };
+ }, 'PerformanceObserver can observe multiple image elements.');
+</script>
+</body>
diff --git a/testing/web-platform/tests/element-timing/observe-shadow-image.html b/testing/web-platform/tests/element-timing/observe-shadow-image.html
new file mode 100644
index 0000000000..e2a81d6244
--- /dev/null
+++ b/testing/web-platform/tests/element-timing/observe-shadow-image.html
@@ -0,0 +1,38 @@
+<!DOCTYPE HTML>
+<meta charset=utf-8>
+<title>Element Timing: do not observe image in shadow tree</title>
+<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>
+<div id='target'></div>
+<script>
+ async_test(function (t) {
+ assert_implements(window.PerformanceElementTiming, "PerformanceElementTiming is not implemented");
+ const observer = new PerformanceObserver(
+ t.step_func_done(function(entryList) {
+ assert_unreached('Should not observe elements in shadow trees!');
+ })
+ );
+ 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 equal to 100 and height equal to 100.
+ const img = document.createElement('img');
+ img.src = 'resources/square100.png';
+ img.setAttribute('elementtiming', 'my_image');
+ img.setAttribute('id', 'my_id');
+ const shadowRoot = document.getElementById('target').attachShadow({mode: 'open'});
+ shadowRoot.appendChild(img);
+ t.step_timeout(() => {
+ // Assume entry was not dispatched, so test passes.
+ t.done();
+ }, 500);
+ };
+ }, 'Image in shadow tree with elementtiming attribute is not observable.');
+</script>
diff --git a/testing/web-platform/tests/element-timing/observe-shadow-text.html b/testing/web-platform/tests/element-timing/observe-shadow-text.html
new file mode 100644
index 0000000000..6e6347e60d
--- /dev/null
+++ b/testing/web-platform/tests/element-timing/observe-shadow-text.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML>
+<meta charset=utf-8>
+<title>Element Timing: do not observe text in shadow tree</title>
+<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>
+<div id='target'></div>
+<script>
+ async_test(function (t) {
+ assert_implements(window.PerformanceElementTiming, "PerformanceElementTiming is not implemented");
+ const observer = new PerformanceObserver(
+ t.step_func_done(function(entryList) {
+ assert_unreached('Should not observe text elements in shadow trees!');
+ })
+ );
+ observer.observe({entryTypes: ['element']});
+ // We add the text during onload to be sure that the observer is registered
+ // in time for it to observe the element timing.
+ window.onload = () => {
+ // Add text of width equal to 100 and height equal to 100.
+ const text = document.createElement('p');
+ text.innerHTML = 'Text';
+ text.setAttribute('elementtiming', 'my_text');
+ const shadowRoot = document.getElementById('target').attachShadow({mode: 'open'});
+ shadowRoot.appendChild(text);
+ t.step_timeout(() => {
+ // Assume entry was not dispatched, so test passes.
+ t.done();
+ }, 500);
+ };
+ }, 'Text in shadow tree with elementtiming attribute is not observable.');
+</script>
diff --git a/testing/web-platform/tests/element-timing/observe-svg-image.html b/testing/web-platform/tests/element-timing/observe-svg-image.html
new file mode 100644
index 0000000000..737f94f92b
--- /dev/null
+++ b/testing/web-platform/tests/element-timing/observe-svg-image.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<meta charset=utf-8>
+<title>Element Timing: observe image inside SVG</title>
+<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) {
+ assert_implements(window.PerformanceElementTiming, "PerformanceElementTiming is not implemented");
+ const observer = new PerformanceObserver(
+ t.step_func_done(function(entryList) {
+ assert_equals(entryList.getEntries().length, 1);
+ const entry = entryList.getEntries()[0];
+ const pathname = window.location.origin + '/element-timing/resources/circle.svg';
+ checkElement(entry, pathname, 'my_svg', 'svg_id', beforeRender,
+ document.getElementById('svg_id'));
+ // Assume viewport has size at least 200, so the element is fully visible.
+ checkRect(entry, [0, 200, 0, 200]);
+ checkNaturalSize(entry, 200, 200);
+ })
+ );
+ observer.observe({entryTypes: ['element']});
+ beforeRender = performance.now();
+}, "Able to observe svg image.");
+</script>
+<style>
+body {
+ margin: 0;
+}
+</style>
+<svg width="300" height="300">
+ <image href='resources/circle.svg' elementtiming='my_svg' id='svg_id'/>
+</svg>
diff --git a/testing/web-platform/tests/element-timing/observe-text.html b/testing/web-platform/tests/element-timing/observe-text.html
new file mode 100644
index 0000000000..5d8955269f
--- /dev/null
+++ b/testing/web-platform/tests/element-timing/observe-text.html
@@ -0,0 +1,48 @@
+<!DOCTYPE HTML>
+<meta charset=utf-8>
+<title>Element Timing: observe text</title>
+<body>
+<style>
+body {
+ margin: 20px;
+}
+p {
+ font-size: 12px;
+}
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/element-timing-helpers.js"></script>
+<script>
+ async_test((t) => {
+ assert_implements(window.PerformanceElementTiming, "PerformanceElementTiming is not implemented");
+ let paragraph;
+ let beforeRender;
+ const observer = new PerformanceObserver(
+ t.step_func_done((entryList) => {
+ assert_equals(entryList.getEntries().length, 1);
+ const entry = entryList.getEntries()[0];
+ checkTextElement(entry, 'my_text', 'text_id', beforeRender, paragraph);
+ assert_equals(entry.intersectionRect.left, 20, 'left should be 20.');
+ // Text box size will vary from device to device, so try lower bounding height by 12, width by 100.
+ assert_greater_than_equal(entry.intersectionRect.right, 120);
+
+ assert_equals(entry.intersectionRect.top, 20, 'top should be 20.');
+ assert_greater_than_equal(entry.intersectionRect.bottom, 32);
+ })
+ );
+ observer.observe({entryTypes: ['element']});
+ // We add the iframe during onload to be sure that the observer is registered
+ // in time for it to observe the element timing.
+ window.onload = () => {
+ paragraph = document.createElement('p');
+ paragraph.innerHTML = 'This is text I care about';
+ paragraph.setAttribute('elementtiming', 'my_text');
+ paragraph.setAttribute('id', 'text_id');
+ document.body.appendChild(paragraph);
+ beforeRender = performance.now();
+ };
+ }, 'Paragraph with elementtiming attribute is observed.');
+</script>
+
+</body>
diff --git a/testing/web-platform/tests/element-timing/observe-video-poster.html b/testing/web-platform/tests/element-timing/observe-video-poster.html
new file mode 100644
index 0000000000..5607733529
--- /dev/null
+++ b/testing/web-platform/tests/element-timing/observe-video-poster.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<meta charset=utf-8>
+<title>Element Timing: observe video poster image</title>
+<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) {
+ assert_implements(window.PerformanceElementTiming, "PerformanceElementTiming is not implemented");
+ const observer = new PerformanceObserver(
+ t.step_func_done(function(entryList) {
+ assert_equals(entryList.getEntries().length, 1);
+ const entry = entryList.getEntries()[0];
+ const pathname = window.location.origin + '/element-timing/resources/circle.svg';
+ checkElement(entry, pathname, 'my_poster', 'the_poster', beforeRender,
+ document.getElementById('the_poster'));
+ // Assume viewport has size at least 200, so the element is fully visible.
+ checkRect(entry, [0, 200, 0, 200]);
+ checkNaturalSize(entry, 200, 200);
+ })
+ );
+ observer.observe({entryTypes: ['element']});
+ beforeRender = performance.now();
+}, "Able to observe a video's poster image.");
+</script>
+<style>
+body {
+ margin: 0;
+}
+</style>
+<video elementtiming='my_poster' id='the_poster' src='/media/test.mp4' poster='resources/circle.svg'/>
diff --git a/testing/web-platform/tests/element-timing/progressively-loaded-image.html b/testing/web-platform/tests/element-timing/progressively-loaded-image.html
new file mode 100644
index 0000000000..6695d8f9c3
--- /dev/null
+++ b/testing/web-platform/tests/element-timing/progressively-loaded-image.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<html>
+<meta charset=utf-8 />
+<title>Element Timing: buffer elements before onload</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src="resources/element-timing-helpers.js"></script>
+<body>
+<script>
+ let beforeRender;
+ let img;
+ // Number of characters to be read on the initial read, before sleeping.
+ // Should be sufficient to do at least a first scan.
+ let numInitial = 75;
+ let sleep = 500;
+ async_test(function(t) {
+ assert_implements(window.PerformanceElementTiming, "PerformanceElementTiming is not implemented");
+ const img_src = 'resources/progressive-image.py?name=square20.jpg&numInitial='
+ + numInitial + '&sleep=' + sleep;
+ const observer = new PerformanceObserver(
+ t.step_func_done(function(entryList) {
+ assert_equals(entryList.getEntries().length, 1);
+ const entry = entryList.getEntries()[0];
+ const pathname = window.location.origin + '/element-timing/' + img_src;
+ // Since the image is only fully loaded after the sleep, the render timestamp
+ // must be greater than |beforeRender| + |sleep|.
+ checkElement(entry, pathname, 'my_image', '', beforeRender + sleep, img);
+ checkNaturalSize(entry, 20, 20);
+ })
+ );
+ observer.observe({entryTypes: ['element']});
+
+ img = document.createElement('img');
+ img.src = img_src;
+ img.setAttribute('elementtiming', 'my_image');
+ document.body.appendChild(img);
+ beforeRender = performance.now();
+ t.step_timeout(() => {assert_true(0);}, 2000);
+ }, "Element Timing: image render timestamp occurs after it is fully loaded.");
+</script>
diff --git a/testing/web-platform/tests/element-timing/rectangular-image.html b/testing/web-platform/tests/element-timing/rectangular-image.html
new file mode 100644
index 0000000000..65f190e753
--- /dev/null
+++ b/testing/web-platform/tests/element-timing/rectangular-image.html
@@ -0,0 +1,43 @@
+<!DOCTYPE HTML>
+<meta charset=utf-8>
+<title>Element Timing: observe a rectangular image</title>
+<body>
+<style>
+body {
+ margin: 20px;
+}
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/element-timing-helpers.js"></script>
+<script>
+ let beforeRender;
+ let img;
+ async_test(function (t) {
+ assert_implements(window.PerformanceElementTiming, "PerformanceElementTiming is not implemented");
+ const observer = new PerformanceObserver(
+ t.step_func_done(function(entryList) {
+ assert_equals(entryList.getEntries().length, 1);
+ const entry = entryList.getEntries()[0];
+ const pathname = window.location.origin + '/images/black-rectangle.png';
+ checkElement(entry, pathname, 'my_image', 'rectangle', beforeRender, img);
+ // Assume viewport has size at least 100, so the element is fully visible.
+ checkRect(entry, [20, 120, 20, 70]);
+ checkNaturalSize(entry, 100, 50);
+ })
+ );
+ 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 equal to 100 and height equal to 50.
+ img = document.createElement('img');
+ img.src = '/images/black-rectangle.png';
+ img.id = 'rectangle';
+ img.setAttribute('elementtiming', 'my_image');
+ document.body.appendChild(img);
+ beforeRender = performance.now();
+ };
+ }, 'Element with rectangular image has correct rect and instrinsic size.');
+</script>
+</body>
diff --git a/testing/web-platform/tests/element-timing/redirects-tao-star.html b/testing/web-platform/tests/element-timing/redirects-tao-star.html
new file mode 100644
index 0000000000..e5067d3d6b
--- /dev/null
+++ b/testing/web-platform/tests/element-timing/redirects-tao-star.html
@@ -0,0 +1,53 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>This test validates element timing information for cross-origin redirect chain images.</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/element-timing-helpers.js"></script>
+<script src=/common/get-host-info.sub.js></script>
+</head>
+<body>
+<script>
+async_test(t => {
+ assert_implements(window.PerformanceElementTiming, "PerformanceElementTiming is not implemented");
+ let destUrl = get_host_info().HTTP_REMOTE_ORIGIN
+ + '/resource-timing/resources/multi_redirect.py?';
+ destUrl += 'page_origin=' + get_host_info().HTTP_ORIGIN;
+ destUrl += '&cross_origin=' + get_host_info().HTTP_REMOTE_ORIGIN;
+ destUrl += '&final_resource=' + '/element-timing/resources/circle-tao.svg';
+ destUrl += '&timing_allow=1';
+ destUrl += '&tao_steps=';
+ for (let taoSteps=0; taoSteps < 4; taoSteps++) {
+ const image = document.createElement('img');
+ image.src = destUrl + taoSteps;
+ image.setAttribute('elementtiming', taoSteps);
+ image.setAttribute('id', 'id' + taoSteps)
+ document.body.appendChild(image);
+ }
+ let numObserved = 0;
+ let observedMap = [false, false, false, false];
+ const beforeRender = performance.now();
+ new PerformanceObserver(t.step_func(entries => {
+ entries.getEntries().forEach(entry => {
+ const taoSteps = entry.identifier;
+ assert_false(observedMap[taoSteps], 'Should observe each image once');
+ observedMap[taoSteps] = true;
+ if (taoSteps !== '3') {
+ assert_equals(entry.renderTime, 0,
+ 'renderTime should be 0 when there is ' + taoSteps + ' tao steps');
+ }
+ const bound = taoSteps === '3' ? beforeRender : 0;
+ checkElement(entry, destUrl + taoSteps, taoSteps, 'id' + taoSteps, bound,
+ document.getElementById(taoSteps));
+ });
+ numObserved += entries.getEntries().length;
+ if (numObserved === 4)
+ t.done();
+ })).observe({type: 'element', buffered: true});
+}, 'Cross-origin image without TAO should not have its renderTime set, with full TAO it should.');
+</script>
+</body>
+</html>
+
diff --git a/testing/web-platform/tests/element-timing/resources/TAOImage.py b/testing/web-platform/tests/element-timing/resources/TAOImage.py
new file mode 100644
index 0000000000..0e1c481489
--- /dev/null
+++ b/testing/web-platform/tests/element-timing/resources/TAOImage.py
@@ -0,0 +1,47 @@
+import os
+
+from wptserve.utils import isomorphic_encode
+
+def main(request, response):
+ origin = request.GET.first(b'origin')
+ if origin:
+ response.headers.set(b'Access-Control-Allow-Origin', origin)
+
+ tao = request.GET.first(b'tao')
+
+ if tao == b'wildcard':
+ # wildcard, pass
+ response.headers.set(b'Timing-Allow-Origin', b'*')
+ elif tao == b'null':
+ # null, fail
+ response.headers.set(b'Timing-Allow-Origin', b'null')
+ elif tao == b'origin':
+ # case-sensitive match for origin, pass
+ response.headers.set(b'Timing-Allow-Origin', origin)
+ elif tao == b'space':
+ # space separated list of origin and wildcard, fail
+ response.headers.set(b'Timing-Allow-Origin', (origin + b' *'))
+ elif tao == b'multi':
+ # more than one TAO values, separated by comma, pass
+ response.headers.set(b'Timing-Allow-Origin', origin)
+ response.headers.append(b'Timing-Allow-Origin', b'*')
+ elif tao == b'multi_wildcard':
+ # multiple wildcards, separated by comma, pass
+ response.headers.set(b'Timing-Allow-Origin', b'*')
+ response.headers.append(b'Timing-Allow-Origin', b'*')
+ elif tao == b'match_origin':
+ # contains a match of origin, separated by comma, pass
+ response.headers.set(b'Timing-Allow-Origin', origin)
+ response.headers.append(b'Timing-Allow-Origin', b"fake")
+ elif tao == b'match_wildcard':
+ # contains a wildcard, separated by comma, pass
+ response.headers.set(b'Timing-Allow-Origin', b"fake")
+ response.headers.append(b'Timing-Allow-Origin', b'*')
+ elif tao == b'uppercase':
+ # non-case-sensitive match for origin, fail
+ response.headers.set(b'Timing-Allow-Origin', origin.upper())
+ else:
+ pass
+ response.headers.set(b"Cache-Control", b"no-cache, must-revalidate");
+ image_path = os.path.join(os.path.dirname(isomorphic_encode(__file__)), b"square100.png");
+ response.content = open(image_path, mode=u'rb').read();
diff --git a/testing/web-platform/tests/element-timing/resources/circle-tao.svg b/testing/web-platform/tests/element-timing/resources/circle-tao.svg
new file mode 100644
index 0000000000..209b9f4e5b
--- /dev/null
+++ b/testing/web-platform/tests/element-timing/resources/circle-tao.svg
@@ -0,0 +1,6 @@
+<?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>
diff --git a/testing/web-platform/tests/element-timing/resources/circle-tao.svg.headers b/testing/web-platform/tests/element-timing/resources/circle-tao.svg.headers
new file mode 100644
index 0000000000..7296361df3
--- /dev/null
+++ b/testing/web-platform/tests/element-timing/resources/circle-tao.svg.headers
@@ -0,0 +1 @@
+Timing-Allow-Origin: *
diff --git a/testing/web-platform/tests/element-timing/resources/circle.svg b/testing/web-platform/tests/element-timing/resources/circle.svg
new file mode 100644
index 0000000000..209b9f4e5b
--- /dev/null
+++ b/testing/web-platform/tests/element-timing/resources/circle.svg
@@ -0,0 +1,6 @@
+<?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>
diff --git a/testing/web-platform/tests/element-timing/resources/element-timing-helpers.js b/testing/web-platform/tests/element-timing/resources/element-timing-helpers.js
new file mode 100644
index 0000000000..8175b0f385
--- /dev/null
+++ b/testing/web-platform/tests/element-timing/resources/element-timing-helpers.js
@@ -0,0 +1,74 @@
+// Common checks between checkElement() and checkElementWithoutResourceTiming().
+function checkElementInternal(entry, expectedUrl, expectedIdentifier, expectedID, beforeRender,
+ expectedElement) {
+ assert_equals(entry.entryType, 'element', 'entryType does not match');
+ assert_equals(entry.url, expectedUrl, 'url does not match');
+ assert_equals(entry.identifier, expectedIdentifier, 'identifier does not match');
+ if (beforeRender != 0) {
+ // In this case, renderTime is not 0.
+ assert_greater_than(entry.renderTime, 0, 'renderTime should be nonzero');
+ assert_equals(entry.startTime, entry.renderTime, 'startTime should equal renderTime');
+ } else {
+ // In this case, renderTime is 0, so compare to loadTime.
+ assert_equals(entry.renderTime, 0, 'renderTime should be zero');
+ assert_equals(entry.startTime, entry.loadTime, 'startTime should equal loadTime');
+ }
+ assert_equals(entry.duration, 0, 'duration should be 0');
+ assert_equals(entry.id, expectedID, 'id does not match');
+ assert_greater_than_equal(entry.renderTime, beforeRender, 'renderTime greater than beforeRender');
+ assert_greater_than_equal(performance.now(), entry.renderTime, 'renderTime bounded by now()');
+ if (expectedElement !== null) {
+ assert_equals(entry.element, expectedElement, 'element does not match');
+ assert_equals(entry.identifier, expectedElement.elementTiming,
+ 'identifier must be the elementtiming of the element');
+ assert_equals(entry.id, expectedElement.id, 'id must be the id of the element');
+ }
+}
+
+// Checks that this is an ElementTiming entry with url |expectedUrl|. It also
+// does a very basic check on |renderTime|: after |beforeRender| and before now().
+function checkElement(entry, expectedUrl, expectedIdentifier, expectedID, beforeRender,
+ expectedElement) {
+ checkElementInternal(entry, expectedUrl, expectedIdentifier, expectedID, beforeRender,
+ expectedElement);
+ assert_equals(entry.name, 'image-paint', 'The entry name should be image-paint.');
+ const rt_entries = performance.getEntriesByName(expectedUrl, 'resource');
+ assert_equals(rt_entries.length, 1, 'There should be only 1 resource entry.');
+ assert_greater_than_equal(entry.loadTime, rt_entries[0].responseEnd,
+ 'Image loadTime is after the resource responseEnd');
+}
+
+function checkElementWithoutResourceTiming(entry, expectedUrl, expectedIdentifier,
+ expectedID, beforeRender, expectedElement) {
+ checkElementInternal(entry, expectedUrl, expectedIdentifier, expectedID, beforeRender,
+ expectedElement);
+ assert_equals(entry.name, 'image-paint');
+ // No associated resource from ResourceTiming, so not much to compare loadTime with.
+ assert_greater_than(entry.loadTime, 0, 'The entry loadTime should be greater than 0.');
+}
+
+// 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);
+}
+
+// Checks that the intrinsic size matches the desired values.
+function checkNaturalSize(entry, width, height) {
+ assert_equals(entry.naturalWidth, width, 'The entry naturalWidth is not as expected.');
+ assert_equals(entry.naturalHeight, height, 'The entry naturalHeight is not as expected.');
+}
+
+function checkTextElement(entry, expectedIdentifier, expectedID, beforeRender,
+ expectedElement) {
+ checkElementInternal(entry, '', expectedIdentifier, expectedID, beforeRender,
+ expectedElement);
+ assert_equals(entry.name, 'text-paint', 'The entry name should be text-paint.');
+ assert_equals(entry.loadTime, 0, 'The entry loadTime should be 0.');
+}
diff --git a/testing/web-platform/tests/element-timing/resources/iframe-stores-entry.html b/testing/web-platform/tests/element-timing/resources/iframe-stores-entry.html
new file mode 100644
index 0000000000..2fa2476972
--- /dev/null
+++ b/testing/web-platform/tests/element-timing/resources/iframe-stores-entry.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<body>
+<p elementtiming='text'>Text</p>
+<script>
+ const observer = new PerformanceObserver(entryList => {
+ window.parent.triggerTest(entryList.getEntries()[0]);
+ });
+ observer.observe({type: 'element', buffered: true});
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/element-timing/resources/iframe-with-content.html b/testing/web-platform/tests/element-timing/resources/iframe-with-content.html
new file mode 100644
index 0000000000..ab8cdfde01
--- /dev/null
+++ b/testing/web-platform/tests/element-timing/resources/iframe-with-content.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<body>
+<p>Text</p>
+<img src='/images/blue.png'/>
+</body>
diff --git a/testing/web-platform/tests/element-timing/resources/iframe-with-square-sends-entry.html b/testing/web-platform/tests/element-timing/resources/iframe-with-square-sends-entry.html
new file mode 100644
index 0000000000..b8af505d32
--- /dev/null
+++ b/testing/web-platform/tests/element-timing/resources/iframe-with-square-sends-entry.html
@@ -0,0 +1,26 @@
+<!DOCType html>
+<html>
+<style>
+body {
+ margin: 0;
+}
+</style>
+<body>
+<script>
+ const observer = new PerformanceObserver(entryList => {
+ top.postMessage({
+ 'length' : entryList.getEntries().length,
+ 'entryType' : entryList.getEntries()[0].entryType,
+ 'rect' : entryList.getEntries()[0].intersectionRect,
+ 'naturalWidth' : entryList.getEntries()[0].naturalWidth,
+ 'naturalHeight' : entryList.getEntries()[0].naturalHeight,
+ 'id': entryList.getEntries()[0].id,
+ // Elements cannot be cloned, so just send the element ID.
+ 'elementId' : entryList.getEntries()[0].element.id,
+ }, '*');
+ });
+ observer.observe({entryTypes: ['element']});
+</script>
+<img src=square100.png id=iframe_img_id elementtiming=my_image/>
+</body>
+</html>
diff --git a/testing/web-platform/tests/element-timing/resources/iframe-with-square.html b/testing/web-platform/tests/element-timing/resources/iframe-with-square.html
new file mode 100644
index 0000000000..0a905d22b9
--- /dev/null
+++ b/testing/web-platform/tests/element-timing/resources/iframe-with-square.html
@@ -0,0 +1,10 @@
+<!DOCTYPE HTML>
+<meta charset=utf-8>
+<body>
+<style>
+body {
+ margin: 0;
+}
+</style>
+<img src='square100.png' elementtiming="my_image">
+</body>
diff --git a/testing/web-platform/tests/element-timing/resources/multiple-redirects.py b/testing/web-platform/tests/element-timing/resources/multiple-redirects.py
new file mode 100644
index 0000000000..3efc634b99
--- /dev/null
+++ b/testing/web-platform/tests/element-timing/resources/multiple-redirects.py
@@ -0,0 +1,59 @@
+from wptserve.utils import isomorphic_encode
+
+def main(request, response):
+ """Handler that causes multiple redirections.
+
+ Mandatory parameters:
+ redirect_count - A number which is at least 1 (number of redirects).
+ final_resource - The location of the last redirect.
+
+ For each number i between 1 and |redirect_count| we have the following optional parameters:
+ tao{{i}} - The Timing-Allow-Origin header of the ith response. Default is no header.
+ origin{{i}} - The origin of the ith redirect (i+1 response). Default is location.origin.
+ Note that the origin of the initial request cannot be controlled here
+ and the Timing-Allow-Origin header of the final response cannot be controlled here.
+
+ Example: redirect_count=2&final_resource=miau.png&tao1=*
+
+ Note: |step| is used internally to track the current redirect number.
+ """
+ step = 1
+ if b"step" in request.GET:
+ try:
+ step = int(request.GET.first(b"step"))
+ except ValueError:
+ pass
+
+ redirect_count = int(request.GET.first(b"redirect_count"))
+ final_resource = request.GET.first(b"final_resource")
+
+ tao_value = None
+ tao = b"tao" + isomorphic_encode(str(step))
+ if tao in request.GET:
+ tao_value = request.GET.first(tao)
+
+ redirect_url = b""
+ origin = b"origin" + isomorphic_encode(str(step))
+ if origin in request.GET:
+ redirect_url = request.GET.first(origin)
+
+ if step == redirect_count:
+ redirect_url += final_resource
+ else:
+ redirect_url += b"/element-timing/resources/multiple-redirects.py?"
+ redirect_url += b"redirect_count=" + isomorphic_encode(str(redirect_count))
+ redirect_url += b"&final_resource=" + final_resource
+ for i in range(1, redirect_count + 1):
+ tao = b"tao" + isomorphic_encode(str(i))
+ if tao in request.GET:
+ redirect_url += b"&" + tao + b"=" + request.GET.first(tao)
+ origin = b"origin" + isomorphic_encode(str(i))
+ if origin in request.GET:
+ redirect_url += b"&" + origin + b"=" + request.GET.first(origin)
+ redirect_url += b"&step=" + isomorphic_encode(str(step + 1))
+
+ if tao_value:
+ response.headers.set(b"timing-allow-origin", tao_value)
+
+ response.status = 302
+ response.headers.set(b"Location", redirect_url)
diff --git a/testing/web-platform/tests/element-timing/resources/progressive-image.py b/testing/web-platform/tests/element-timing/resources/progressive-image.py
new file mode 100644
index 0000000000..84e98c8451
--- /dev/null
+++ b/testing/web-platform/tests/element-timing/resources/progressive-image.py
@@ -0,0 +1,27 @@
+import os.path
+import time
+
+from wptserve.utils import isomorphic_encode
+
+def main(request, response):
+ name = request.GET.first(b"name")
+ sleepTime = float(request.GET.first(b"sleep")) / 1E3
+ numInitial = int(request.GET.first(b"numInitial"))
+
+ path = os.path.join(os.path.dirname(isomorphic_encode(__file__)), name)
+ body = open(path, u"rb").read()
+
+ response.headers.set(b"Content-Type", b"image")
+ response.headers.set(b"Content-Length", len(body))
+ response.headers.set(b"Cache-control", b"no-cache, must-revalidate")
+ response.write_status_headers()
+
+ # Read from the beginning, |numInitial| bytes.
+ first = body[:numInitial]
+ response.writer.write_content(first)
+
+ time.sleep(sleepTime)
+
+ # Read the remainder after having slept.
+ second = body[numInitial:]
+ response.writer.write_content(second)
diff --git a/testing/web-platform/tests/element-timing/resources/slow-image.py b/testing/web-platform/tests/element-timing/resources/slow-image.py
new file mode 100644
index 0000000000..10172fd35a
--- /dev/null
+++ b/testing/web-platform/tests/element-timing/resources/slow-image.py
@@ -0,0 +1,19 @@
+import os.path
+import time
+
+from wptserve.utils import isomorphic_encode
+
+def main(request, response):
+ name = request.GET.first(b"name")
+ sleepTime = float(request.GET.first(b"sleep")) / 1E3
+
+ time.sleep(sleepTime)
+
+ path = os.path.join(os.path.dirname(isomorphic_encode(__file__)), name)
+ body = open(path, u"rb").read()
+
+ response.headers.set(b"Content-Type", b"image")
+ response.headers.set(b"Content-Length", len(body))
+ response.headers.set(b"Cache-control", b"no-cache, must-revalidate")
+
+ response.content = body;
diff --git a/testing/web-platform/tests/element-timing/resources/square100.png b/testing/web-platform/tests/element-timing/resources/square100.png
new file mode 100644
index 0000000000..567babb96d
--- /dev/null
+++ b/testing/web-platform/tests/element-timing/resources/square100.png
Binary files differ
diff --git a/testing/web-platform/tests/element-timing/resources/square20.jpg b/testing/web-platform/tests/element-timing/resources/square20.jpg
new file mode 100644
index 0000000000..83ed4914bb
--- /dev/null
+++ b/testing/web-platform/tests/element-timing/resources/square20.jpg
Binary files differ
diff --git a/testing/web-platform/tests/element-timing/resources/square20.png b/testing/web-platform/tests/element-timing/resources/square20.png
new file mode 100644
index 0000000000..4d51ac4b46
--- /dev/null
+++ b/testing/web-platform/tests/element-timing/resources/square20.png
Binary files differ
diff --git a/testing/web-platform/tests/element-timing/retrievability.html b/testing/web-platform/tests/element-timing/retrievability.html
new file mode 100644
index 0000000000..cd2c2a956e
--- /dev/null
+++ b/testing/web-platform/tests/element-timing/retrievability.html
@@ -0,0 +1,29 @@
+<!DOCTYPE HTML>
+<meta charset=utf-8>
+<title>Element Timing: 'element' entries are not accessible via performance timeline</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/element-timing-helpers.js"></script>
+<script>
+ let img;
+ async_test(function (t) {
+ assert_implements(window.PerformanceElementTiming, "PerformanceElementTiming is not implemented");
+ const beforeRender = performance.now();
+ new PerformanceObserver(
+ t.step_func_done(function(entryList) {
+ assert_equals(entryList.getEntries().length, 1);
+ const entry = entryList.getEntries()[0];
+ assert_equals(entry.entryType, 'element');
+ assert_equals(entry.name, 'image-paint');
+
+ const entriesByName = performance.getEntriesByName('image-paint', 'element');
+ const entriesByType = performance.getEntriesByType('element');
+ const allEntries = performance.getEntries();
+ assert_equals(entriesByName.length, 0, 'Element Timing entry should not be retrievable by getEntriesByName');
+ assert_equals(entriesByType.length, 0, 'Element Timing entry should not be retrievable by getEntriesByType');
+ assert_equals(allEntries.filter(e => e.entryType === 'element').length, 0, 'Element Timing entry should not be retrievable by getEntries');
+ })
+ ).observe({type: 'element', buffered: true});
+ }, 'Element Timing entries are not accessible via performance.getEntries*');
+</script>
+<img src='resources/square100.png' elementtiming='my_image' id='my_id'/>
diff --git a/testing/web-platform/tests/element-timing/same-origin-redirects.html b/testing/web-platform/tests/element-timing/same-origin-redirects.html
new file mode 100644
index 0000000000..e52fcecc1a
--- /dev/null
+++ b/testing/web-platform/tests/element-timing/same-origin-redirects.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>This test validates element timing information for same-origin redirect chain.</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/element-timing-helpers.js"></script>
+</head>
+<body>
+<script>
+async_test(t => {
+ assert_implements(window.PerformanceElementTiming, "PerformanceElementTiming is not implemented");
+ // First redirect
+ let destUrl = '/common/redirect.py?location='
+ // Second redirect
+ destUrl += '/common/redirect.py?location='
+ // Image without TAO headers.
+ destUrl += '/element-timing/resources/square20.png';
+ let beforeRender;
+ new PerformanceObserver(t.step_func_done(entries => {
+ assert_equals(entries.getEntries().length, 1, 'There should be one entry');
+ const entry = entries.getEntries()[0];
+ checkElement(entry, location.origin + destUrl, 'et', 'id', beforeRender,
+ document.getElementById('id'));
+ })).observe({entryTypes: ['element']});
+ const image = document.createElement('img');
+ image.src = destUrl;
+ image.setAttribute('elementtiming', 'et');
+ image.setAttribute('id', 'id')
+ document.body.appendChild(image);
+ beforeRender = performance.now();
+}, 'Same-origin image redirect without TAO should have its renderTime set.');
+</script>
+</body>
+</html>
+
diff --git a/testing/web-platform/tests/element-timing/scroll-to-text.html b/testing/web-platform/tests/element-timing/scroll-to-text.html
new file mode 100644
index 0000000000..0508d2bcf9
--- /dev/null
+++ b/testing/web-platform/tests/element-timing/scroll-to-text.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML>
+<meta charset=utf-8>
+<title>Element Timing: observe text that is initially not visible</title>
+<body>
+<style>
+.big {
+ width: 100%;
+ height: 100vh;
+}
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/element-timing-helpers.js"></script>
+<div class='big'></div>
+<p elementtiming='observeMe'>Test text</p>
+<script>
+ async_test((t) => {
+ assert_implements(window.PerformanceElementTiming, "PerformanceElementTiming is not implemented");
+ const observer = new PerformanceObserver(t.step_func_done(() => {}));
+ observer.observe({type: 'element', buffered: true});
+ window.onload = () => {
+ // The div occupies the whole screen because it occupies 100% of the height.
+ // We scroll to the end of the document so that the paragraph becomes visible.
+ // A user agent could paint the text before or after scrolling, but either way
+ // it must produce an entry for it.
+ window.scrollTo(0,document.scrollingElement.scrollHeight);
+ };
+ }, 'Paragraph with elementtiming attribute is observed even when not initially visible.');
+</script>
+</body>
diff --git a/testing/web-platform/tests/element-timing/supported-element-type.html b/testing/web-platform/tests/element-timing/supported-element-type.html
new file mode 100644
index 0000000000..15733d46f9
--- /dev/null
+++ b/testing/web-platform/tests/element-timing/supported-element-type.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<head>
+<title>PerformanceObserver.supportedEntryTypes contains "element"</title>
+</head>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+test(() => {
+ if (typeof PerformanceObserver.supportedEntryTypes === "undefined")
+ assert_unreached("supportedEntryTypes is not supported.");
+ assert_greater_than(PerformanceObserver.supportedEntryTypes.indexOf("element"), -1,
+ "There should be an entry 'element' in PerformanceObserver.supportedEntryTypes");
+}, "supportedEntryTypes contains 'element'.");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/element-timing/text-with-display-style.html b/testing/web-platform/tests/element-timing/text-with-display-style.html
new file mode 100644
index 0000000000..94e89fcf72
--- /dev/null
+++ b/testing/web-platform/tests/element-timing/text-with-display-style.html
@@ -0,0 +1,54 @@
+<!DOCTYPE HTML>
+<meta charset=utf-8>
+<title>Element Timing: observe text with display style.</title>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/element-timing-helpers.js"></script>
+<style>
+h1 {
+ display: flex;
+}
+h2 {
+ display: grid;
+}
+h3 {
+ display: block;
+}
+</style>
+<h1 id='title1' elementtiming='h1'>I am h1</h1>
+<h2 id='title2' elementtiming='h2'>I am h2</h2>
+<h3 id='title3' elementtiming='h3'>I am h3</h3>
+<script>
+ async_test(function (t) {
+ assert_implements(window.PerformanceElementTiming, "PerformanceElementTiming is not implemented");
+ const beforeRender = performance.now();
+ let observedFlex = false;
+ let observedGrid = false;
+ let observedBlock = false;
+ const observer = new PerformanceObserver(
+ t.step_func(function(entryList) {
+ entryList.getEntries().forEach(entry => {
+ if (entry.id === 'title1') {
+ assert_false(observedFlex, 'Should observe flex once');
+ observedFlex = true;
+ checkTextElement(entry, 'h1', 'title1', beforeRender, document.getElementById('title1'));
+ } else if (entry.id === 'title2') {
+ assert_false(observedGrid, 'Should observe grid once');
+ observedGrid = true;
+ checkTextElement(entry, 'h2', 'title2', beforeRender, document.getElementById('title2'));
+ } else {
+ assert_equals(entry.id, 'title3', 'ID should be title1, title2, or title3');
+ assert_false(observedBlock, 'Should observe block once');
+ observedBlock = true;
+ checkTextElement(entry, 'h3', 'title3', beforeRender, document.getElementById('title3'));
+ }
+ });
+ if (observedFlex && observedGrid && observedBlock)
+ t.done();
+ })
+ );
+ observer.observe({type: 'element', buffered: true});
+ }, 'Text with display style is observable.');
+</script>
+</body>
diff --git a/testing/web-platform/tests/element-timing/toJSON.html b/testing/web-platform/tests/element-timing/toJSON.html
new file mode 100644
index 0000000000..a9f6a5180a
--- /dev/null
+++ b/testing/web-platform/tests/element-timing/toJSON.html
@@ -0,0 +1,45 @@
+<!DOCTYPE HTML>
+<meta charset=utf-8>
+<title>Element Timing: toJSON</title>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/element-timing-helpers.js"></script>
+<img elementtiming='img' src="resources/square100.png"/>
+<script>
+ async_test(function (t) {
+ assert_implements(window.PerformanceElementTiming, "PerformanceElementTiming is not implemented");
+ const observer = new PerformanceObserver(
+ t.step_func_done(function(entryList) {
+ assert_equals(entryList.getEntries().length, 1);
+ const entry = entryList.getEntries()[0];
+ assert_equals(typeof(entry.toJSON), 'function');
+ const json = entry.toJSON();
+ assert_equals(typeof(json), 'object');
+ const keys = [
+ // PerformanceEntry
+ 'name',
+ 'entryType',
+ 'startTime',
+ 'duration',
+ // PerformanceElementTiming
+ 'renderTime',
+ 'loadTime',
+ 'intersectionRect',
+ 'identifier',
+ 'naturalWidth',
+ 'naturalHeight',
+ 'id',
+ 'url',
+ ];
+ for (const key of keys) {
+ assert_equals(json[key], entry[key],
+ 'PerformanceElementTiming ${key} entry does not match its toJSON value');
+ }
+ assert_equals(json['element'], undefined, 'toJSON should not include element');
+ })
+ );
+ observer.observe({type: 'element', buffered: true});
+ }, 'Test toJSON() in PerformanceElementTiming.');
+</script>
+</body>