diff options
Diffstat (limited to '')
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 Binary files differnew file mode 100644 index 0000000000..567babb96d --- /dev/null +++ b/testing/web-platform/tests/element-timing/resources/square100.png diff --git a/testing/web-platform/tests/element-timing/resources/square20.jpg b/testing/web-platform/tests/element-timing/resources/square20.jpg Binary files differnew file mode 100644 index 0000000000..83ed4914bb --- /dev/null +++ b/testing/web-platform/tests/element-timing/resources/square20.jpg diff --git a/testing/web-platform/tests/element-timing/resources/square20.png b/testing/web-platform/tests/element-timing/resources/square20.png Binary files differnew file mode 100644 index 0000000000..4d51ac4b46 --- /dev/null +++ b/testing/web-platform/tests/element-timing/resources/square20.png 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> |