diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /testing/web-platform/tests/resize-observer | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/resize-observer')
26 files changed, 3484 insertions, 0 deletions
diff --git a/testing/web-platform/tests/resize-observer/META.yml b/testing/web-platform/tests/resize-observer/META.yml new file mode 100644 index 0000000000..40ebbe9662 --- /dev/null +++ b/testing/web-platform/tests/resize-observer/META.yml @@ -0,0 +1,4 @@ +spec: https://drafts.csswg.org/resize-observer/ +suggested_reviewers: + - atotic + - dholbert diff --git a/testing/web-platform/tests/resize-observer/calculate-depth-for-node.html b/testing/web-platform/tests/resize-observer/calculate-depth-for-node.html new file mode 100644 index 0000000000..339e52bb79 --- /dev/null +++ b/testing/web-platform/tests/resize-observer/calculate-depth-for-node.html @@ -0,0 +1,26 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<link rel="help" href="https://drafts.csswg.org/resize-observer/#calculate-depth-for-node"> +<body> +<div id="host"></div> +<script> +let didSeeError = false; +window.onerror = (message, source, lineno, colno, error) => { + didSeeError = true; +} + +async_test(t => { + let host = document.querySelector('#host'); + let observer = new ResizeObserver(t.step_func(() => { + let root = host.attachShadow({mode:'open'}); + let child = root.appendChild(document.createElement('div')); + new ResizeObserver(() => {}).observe(child); + requestAnimationFrame(t.step_func_done(() => { assert_false(didSeeError); })); + })); + observer.observe(host); +}, '"Calculate depth for node" algorithm with Shadow DOM'); + + +</script> +</body> diff --git a/testing/web-platform/tests/resize-observer/callback-cross-realm-report-exception.html b/testing/web-platform/tests/resize-observer/callback-cross-realm-report-exception.html new file mode 100644 index 0000000000..75a91ec0a1 --- /dev/null +++ b/testing/web-platform/tests/resize-observer/callback-cross-realm-report-exception.html @@ -0,0 +1,30 @@ +<!doctype html> +<meta charset=utf-8> +<title>ResizeObserver reports the exception from its callback in the callback's global object</title> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<iframe srcdoc='<div style="height: 100px;">foo</div>'></iframe> +<iframe></iframe> +<iframe></iframe> +<script> +setup({ allow_uncaught_exception: true }); + +const onerrorCalls = []; +window.onerror = () => { onerrorCalls.push("top"); }; +frames[0].onerror = () => { onerrorCalls.push("frame0"); }; +frames[1].onerror = () => { onerrorCalls.push("frame1"); }; +frames[2].onerror = () => { onerrorCalls.push("frame2"); }; + +async_test(t => { + window.onload = t.step_func(() => { + const target = frames[0].document.querySelector("div"); + const io = new frames[0].ResizeObserver(new frames[1].Function(`throw new parent.frames[2].Error("PASS");`)); + io.observe(target); + + t.step_timeout(() => { + assert_array_equals(onerrorCalls, ["frame1"]); + t.done(); + }, 25); + }); +}); +</script> diff --git a/testing/web-platform/tests/resize-observer/change-layout-in-error.html b/testing/web-platform/tests/resize-observer/change-layout-in-error.html new file mode 100644 index 0000000000..9083fb48f9 --- /dev/null +++ b/testing/web-platform/tests/resize-observer/change-layout-in-error.html @@ -0,0 +1,33 @@ +<!doctype HTML> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<style> + div { + width: 100px; + height: 100px; + } +</style> +<div id="observeme"></div> + +<script> +setup({allow_uncaught_exception: true}); + +async_test(a => { + let t = document.querySelector("#observeme"); + let i = 0; + window.onerror = function (err) { + t.style.height = "112px"; + i++; + requestAnimationFrame(a.step_func_done(() => { + assert_equals(i, 1); + }), 0); + }; + + new ResizeObserver(function() { + t.style.height = "111px"; + }).observe(observeme); + observeme.style.height = "110px"; + +}, "Changing layout in window error handler should not result in lifecyle loop when resize observer loop limit is reached."); +</script> diff --git a/testing/web-platform/tests/resize-observer/create-pattern-data-url.js b/testing/web-platform/tests/resize-observer/create-pattern-data-url.js new file mode 100644 index 0000000000..d3634dc424 --- /dev/null +++ b/testing/web-platform/tests/resize-observer/create-pattern-data-url.js @@ -0,0 +1,22 @@ +const patternSize = 4; + +export default function createPatternDataURL() { + const ctx = document.createElement('canvas').getContext('2d'); + ctx.canvas.width = patternSize; + ctx.canvas.height = patternSize; + + const b = [0, 0, 0, 255]; + const t = [0, 0, 0, 0]; + const r = [255, 0, 0, 255]; + const g = [0, 255, 0, 255]; + + const imageData = new ImageData(patternSize, patternSize); + imageData.data.set([ + b, t, t, r, + t, b, g, t, + t, r, b, t, + g, t, t, b, + ].flat()); + ctx.putImageData(imageData, 0, 0); + return {patternSize, dataURL: ctx.canvas.toDataURL()}; +} diff --git a/testing/web-platform/tests/resize-observer/devicepixel-ref.html b/testing/web-platform/tests/resize-observer/devicepixel-ref.html new file mode 100644 index 0000000000..a006732015 --- /dev/null +++ b/testing/web-platform/tests/resize-observer/devicepixel-ref.html @@ -0,0 +1,27 @@ +<!doctype html> +<style> + #greenrect { + width: 100%; + height: 100%; + border: 1px solid green; + box-sizing: border-box; + } + #outer { + padding-top: 1.7px; + width: 300.8px; + height: 250.1px; + } + #outer2 { + padding-top: 1.4px; + width: 300.8px; + height: 250.1px; + } +</style> +<body> + <div id="outer"> + <div id="greenrect"></div> + </div> + <div id="outer2"> + <div id="greenrect"></div> + </div> +</body> diff --git a/testing/web-platform/tests/resize-observer/devicepixel.html b/testing/web-platform/tests/resize-observer/devicepixel.html new file mode 100644 index 0000000000..e92079bea5 --- /dev/null +++ b/testing/web-platform/tests/resize-observer/devicepixel.html @@ -0,0 +1,85 @@ +<!doctype html> +<link rel="match" href="devicepixel-ref.html"> +<meta name="assert" content="Device pixel content box sizes and pixel snapping are correct in Resize Observer callback"> +<!-- + This test verifies that the device pixel content box sizes available + in a resize observer callback are correct. Resize observer notifications + are delivered as the element is loaded. A box is then drawn using the + available dimensions in device pixels. The result is compared to the reference + which uses div and border to draw a box. +--> + +<style> + #canvas2D { + width: 100%; + height: 100%; + } + #canvas2DPadding14 { + width: 100%; + height: 100%; + } + #outer { + padding-top: 1.7px; + width: 300.8px; + height: 250.1px; + } + #outer2 { + padding-top: 1.4px; + width: 300.8px; + height: 250.1px; + } + +</style> +<body> + <div id="outer"> + <canvas id="canvas2D"></canvas> + </div> + <div id="outer2"> + <canvas id="canvas2DPadding14"></canvas> + </div> +</body> + +<script> + // Create a box using device pixel content box dimensions + function paint2D(ctx, snappedSize) { + ctx.beginPath(); + // Use a linewidth of 2. Because the rectangle is drawn at 0,0 with + // its dimensions being the same as canvas dimensions, linewidth as it + // is drawn on the canvas will be 1. + ctx.lineWidth = window.devicePixelRatio * 2; + ctx.strokeStyle = "green"; + ctx.rect(0, 0, snappedSize.inlineSize, snappedSize.blockSize); + ctx.stroke(); + } + + function updateCanvasSize2D(canvas2D, ctx, snappedSize) { + canvas2D.width = snappedSize.inlineSize; + canvas2D.height = snappedSize.blockSize; + paint2D(ctx, snappedSize); + } + + (function() { + let canvas2D = document.querySelector("#canvas2D"); + let canvas2DPadding14 = document.querySelector("#canvas2DPadding14"); + + function observeSizes() { + let ro = new ResizeObserver( entries => { + for (entry of entries) { + let size = entry.devicePixelContentBoxSize[0]; + if (entry.target == canvas2D) { + let canvas2D = document.querySelector("#canvas2D"); + let ctx = canvas2D.getContext("2d"); + updateCanvasSize2D(canvas2D, ctx, size); + } else if (entry.target == canvas2DPadding14){ + let canvas2DPadding14 = document.querySelector("#canvas2DPadding14"); + let ctx = canvas2DPadding14.getContext("2d"); + updateCanvasSize2D(canvas2DPadding14, ctx, size); + } + } + }); + ro.observe(canvas2D, {box: "device-pixel-content-box"}); + ro.observe(canvas2DPadding14, {box: "device-pixel-content-box"}); + } + observeSizes(); + })(); +</script> diff --git a/testing/web-platform/tests/resize-observer/devicepixel2-ref.html b/testing/web-platform/tests/resize-observer/devicepixel2-ref.html new file mode 100644 index 0000000000..7674eb0ab8 --- /dev/null +++ b/testing/web-platform/tests/resize-observer/devicepixel2-ref.html @@ -0,0 +1,21 @@ +<!doctype html> +<body> +<script type="module"> +import createPatternDataURL from './create-pattern-data-url.js'; + +const { patternSize, dataURL } = createPatternDataURL(); +document.body.style.backgroundImage = `url("${dataURL}")`; + +function setBackgroundPatternTo1DevicePixel() { + const oneDevicePixel = 1 / devicePixelRatio; + const patternPixels = oneDevicePixel * patternSize; + document.body.style.backgroundSize = `${patternPixels}px ${patternPixels}px`; +} +setBackgroundPatternTo1DevicePixel(); + +// If we're viewed interactively and the user activates +// full-page-zoom, changes the page zoom level, or resizes +// the window, update the rendering to account for that: +window.addEventListener('resize', setBackgroundPatternTo1DevicePixel); +</script> +</body> diff --git a/testing/web-platform/tests/resize-observer/devicepixel2.html b/testing/web-platform/tests/resize-observer/devicepixel2.html new file mode 100644 index 0000000000..97b6255457 --- /dev/null +++ b/testing/web-platform/tests/resize-observer/devicepixel2.html @@ -0,0 +1,131 @@ +<!doctype html> +<link rel="match" href="devicepixel2-ref.html"> +<meta name="assert" content="Resize Observer's reported device pixel content box size should be consistent with the actual pixel-snapped painted box size"> +<style> + html, body { + margin: 0; + } + .line { + width: 100px; + display: flex; + } + .line>* { + flex: 1; + height: 20px; + } +</style> +<body> + <div></div> + <script type="module"> +import createPatternDataURL from './create-pattern-data-url.js'; + +const {patternSize, dataURL} = createPatternDataURL(); + +/** + * Set the pattern's size on this element so that it draws where + * 1 pixel in the pattern maps to 1 devicePixel and then set + * its position so it's aligned to the pattern of the body element. + */ +function setPattern(elem, x, y) { + const oneDevicePixel = 1 / devicePixelRatio; + const patternPixels = oneDevicePixel * patternSize; + elem.style.backgroundImage = `url("${dataURL}")`; + elem.style.backgroundSize = `${patternPixels}px ${patternPixels}px`; + elem.style.backgroundPosition = `${-x * oneDevicePixel}px ${-y * oneDevicePixel}px`; +} + +/* +This test creates elements like this + + <body> + <div> + <div class="line"><div></div><div></div></div> + <div class="line"><div></div><div></div><div></div></div> + <div class="line"><div></div><div></div><div></div><div></div></div> + ... + </div> + </body> + +It has the first `<div>` starting at the top left corner of the page, the +same as the body so they should both have a devicePixel position of 0,0 + +The devicePixelContentBoxSize of all the elements is queried with +a ResizeObserver. + +It then sets a repeating background-image, background-size and +background-position on the all of the innermost children elements so +they have a 4x4 checkerboard image at a size that should draw each pixel +of the 4x4 checkerboard pattern in single devicePixels and so they should +all align with the same pattern applied in a similar manner to the body. + +In other words + + <div class="line"> + <div></div><div></div><div></div> + </div> + +The first child will be displayed at left = 0 so its background-position is +set to 0px 0px. The second child should be displayed at + +secondChildLeft = entry(firstChild).devicePixelContentBoxSize[0].inlineSize. + +The 3rd child should be displayed at + +thirdChildLeft = entry(firstChild).devicePixelContentBoxSize[0].inlineSize + + entry(secondChild).devicePixelContentBoxSize[0].inlineSize + +Using the children's device-pixel positions (determined as described above), +we then set each child's background-position to the negated version of the +position that we computed for that child. This effectively makes its pattern's +origin match the document origin. + +If what devicePixelContentBox reports is correct the children's patterns +will align with the pattern in the body and the children will be invisible. +If what devicePixelContentBox reports is incorrect the patterns will not +align and it should be clearly visible. +*/ + +const wrapperElem = document.querySelector('div'); + +const elemToDevicePixelSize = new Map(); + +function setPatternsUsingSizeInfo(entries) { + setPattern(document.body, 0, 0); + + for (const entry of entries) { + elemToDevicePixelSize.set(entry.target, { + inlineSize: entry.devicePixelContentBoxSize[0].inlineSize, + blockSize: entry.devicePixelContentBoxSize[0].blockSize, + }); + } + + let devicePixelY = 0; + for (const lineElem of wrapperElem.children) { + let devicePixelX = 0; + for (const childElem of lineElem.children) { + setPattern(childElem, devicePixelX, devicePixelY); + const devicePixelSize = elemToDevicePixelSize.get(childElem); + devicePixelX += devicePixelSize.inlineSize; + } + + const lineEntry = elemToDevicePixelSize.get(lineElem); + const lineHeight = lineEntry.blockSize; + devicePixelY += lineHeight; + } +} + +const observer = new ResizeObserver(setPatternsUsingSizeInfo); +observer.observe(document.body); +for (let numFlexItems = 2; numFlexItems < 15; ++numFlexItems) { + const lineElem = document.createElement('div'); + lineElem.className = 'line'; + observer.observe(lineElem, {box:"device-pixel-content-box"}); + for (let j = 0; j < numFlexItems; ++j) { + const inner = document.createElement('div'); + lineElem.appendChild(inner); + observer.observe(inner, {box:"device-pixel-content-box"}); + } + wrapperElem.appendChild(lineElem); +} + </script> +</body> diff --git a/testing/web-platform/tests/resize-observer/eventloop.html b/testing/web-platform/tests/resize-observer/eventloop.html new file mode 100644 index 0000000000..3a9e453faf --- /dev/null +++ b/testing/web-platform/tests/resize-observer/eventloop.html @@ -0,0 +1,253 @@ +<!doctype html> +<title>ResizeObserver notification event loop tests</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="./resources/resizeTestHelper.js"></script> +<div id="log"></div> +<script> +'use strict'; + +// allow uncaught exception because ResizeObserver posts exceptions +// to window error handler when limit is exceeded. +// This codepath is tested in this file. + +setup({allow_uncaught_exception: true}); + +function template() { + let helper = new ResizeTestHelper( + "test0: title", + [ + { + setup: observer => { + }, + notify: (entries, observer) => { + return true; // Delay next step + } + } + ]); + return helper.start(); +} + +var onErrorCalled = false; + +window.onerror = err => { + onErrorCalled = true; +} + +function test0() { + let t1 = createAndAppendElement("div"); + let t2 = createAndAppendElement("div", t1); + let t3 = createAndAppendElement("div", t2); + + let divs = [t1, t2, t3]; + let rAF = 0; + let helper = new ResizeTestHelper( + "test0: multiple notifications inside same event loop", + [ + { + setup: observer => { + onErrorCalled = false; + observer.observe(t1); + observer.observe(t2); + observer.observe(t3); + }, + notify: (entries, observer) => { + assert_equals(entries.length, 3, "3 notifications"); + } + }, + { + setup: observer => { + helper.startCountingRaf(); + divs.forEach( el => { el.style.width = "101px";}); + }, + notify: (entries, observer) => { + // t1 is not delivered + assert_equals(entries.length, 2, "2 notifications"); + assert_equals(helper.rafCount, 0, "still in same loop"); + } + }, + { + setup: observer => { + divs.forEach( el => { el.style.width = "102px";}); + }, + notify: (entries, observer) => { + assert_equals(entries.length, 1, "1 notifications"); + assert_equals(helper.rafCount, 0, "same loop"); + } + }, + { // t1 and t2 get notified + setup: observer => { + }, + notify: (entries, observer) => { + assert_equals(entries.length, 2, "2 notifications"); + assert_equals(helper.rafCount, 1, "new loop"); + assert_equals(onErrorCalled, true, "error was fired"); + observer.disconnect(); + } + } + ]); + + return new Promise((resolve, reject) => { + // This test uses requestAnimationFrame() to check the count of event loop, + // but on some browsers, the FrameRequestCallback may be throttled (i.e. + // simply fired after some extra time) in cases where this test is running + // in an iframe that hasn't yet been painted (i.e. we're not visible). + // This may result in some intermittent failures if this test didn't get a + // first paint (and hence may not have started firing FrameRequestCallbacks) + // by the time the test starts expecting helper.rafCount to have changed. + // + // Therefore, we don't start the test logic until body.onload has fired. + // This increases the likelihood that this testcase will have gotten a + // chance to paint when we start invoking requestAnimationFrame, and that + // its rAF callbacks will fire when the test logic expects them to. + document.body.onload = () => resolve(); + }).then(() => { + return helper.start(() => t1.remove()); + }); +} + +function test1() { + let t1 = createAndAppendElement("div"); + t1.style.width = '100px'; + let t2 = createAndAppendElement("div", t1); + let t3 = createAndAppendElement("div", t2); + let shadow = t3.attachShadow({ mode: "open" }); + let t4 = createAndAppendElement("div", shadow); + let t5 = createAndAppendElement("div", t4); + + let resizers = [t1, t2, t3, t4, t5]; + + // Testing depths of shadow roots + // DOM: t1 <- t2 <- t3 <-shadow- t4 <- t5 + let helper = new ResizeTestHelper( + "test1: depths of shadow roots", + [ + { + setup: observer => { + onErrorCalled = false; + resizers.forEach( el => observer.observe(el) ); + }, + notify: (entries, observer) => { + assert_equals(entries.length, 5, "all entries resized"); + } + }, + { + setup: observer => { + resizers.forEach( el => el.style.width = "111px" ); + }, + notify: (entries, observer) => { + assert_equals(entries.length, 4, "depth limited"); + } + }, + { + setup: observer => { + resizers.forEach( el => el.style.width = "112px" ); + }, + notify: (entries, observer) => { + assert_equals(entries.length, 3, "depth limited"); + } + }, + { + setup: observer => { + resizers.forEach( el => el.style.width = "113px" ); + }, + notify: (entries, observer) => { + assert_equals(entries.length, 2, "depth limited"); + } + }, + { + setup: observer => { + resizers.forEach( el => el.style.width = "114px" ); + }, + notify: (entries, observer) => { + assert_equals(entries.length, 1, "depth limited"); + } + }, + { + setup: observer => { + }, + notify: (entries, observer) => { + assert_equals(entries.length, 4, "limit notifications"); + assert_equals(onErrorCalled, true, "breached limit"); + observer.disconnect(); + } + }, + ]); + return helper.start(() => t1.remove()); +} + +function test2() { + // <div id="container"> + // <div id="a1" style="width:100px;height:100px"> + // <div id="a2" style="width:100px;height:100px"></div> + // </div> + // <div id="b1" style="width:100px;height:100px"> + // <div id="b2" style="width:100px;height:100px"></div> + // </div> + // </div> + let container = createAndAppendElement("div"); + let a1 = createAndAppendElement("div", container); + let a2 = createAndAppendElement("div", a1); + let b1 = createAndAppendElement("div", container); + let b2 = createAndAppendElement("div", b1); + let targets = [a1, a2, b1, b2]; + + let helper = new ResizeTestHelper( + "test2: move target in dom while inside event loop", + [ + { + setup: observer => { + for (let t of targets) + observer.observe(t); + }, + notify: (entries, observer) => { + return true; // delay next observation + } + }, + { // resize them all + setup: observer => { + for (let t of targets) + t.style.width = "110px"; + }, + notify: (entries, observer) => { + assert_equals(entries.length, targets.length, "all targets observed"); + } + }, + { // resize all, move dom upwards + setup: observer => { + for (let t of targets) + t.style.width = "130px"; + container.appendChild(b2); + }, + notify: (entries, observer) => { + assert_equals(entries.length, 1, "b2 moved upwards"); + assert_equals(entries[0].target, a2); + } + }, + { // resize all, move dom downwards + setup: observer => { + for (let t of targets) + t.style.width = "130px"; + a2.appendChild(b2); + }, + notify: (entries, observer) => { + assert_equals(entries.length, 1, "b2 moved downwards"); + assert_equals(entries[0].target, b2); + } + }, + ]); + return helper.start(() => container.remove()); +} + +let guard; +test(_ => { + assert_own_property(window, "ResizeObserver"); + guard = async_test('guard'); +}, "ResizeObserver implemented") + +test0() + .then(() => test1()) + .then(() => test2()) + .then(() => guard.done()); + +</script> diff --git a/testing/web-platform/tests/resize-observer/fragments.html b/testing/web-platform/tests/resize-observer/fragments.html new file mode 100644 index 0000000000..bba94db88e --- /dev/null +++ b/testing/web-platform/tests/resize-observer/fragments.html @@ -0,0 +1,115 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>ResizeObserver with multiple fragments</title> +<link rel="author" title="Oriol Brufau" href="mailto:obrufau@igalia.com"> +<link rel="help" href="https://drafts.csswg.org/resize-observer-1/"> +<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/3673"> +<meta name="assert" content="Tests ResizeObserver supports multiple fragments." /> + +<style> +#wrapper { + column-width: 100px; + width: max-content; + height: 100px; + margin: 10px; +} +#target { + outline: solid; + background: orange; +} +.w50 { + width: 50px; +} +.w75 { + width: 75px; +} +.h100 { + height: 100px; +} +.h150 { + height: 150px; +} +.h175 { + height: 175px; +} +</style> + +<div id="log"></div> + +<div id="wrapper"> + <div id="target"></div> +</div> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +const target = document.getElementById("target"); + +const nextSizes = (() => { + let callback = null; + new ResizeObserver((entries) => { + if (callback) { + callback(entries[0].contentBoxSize); + callback = null; + } + }).observe(target); + return () => { + if (callback) { + throw "Already awaiting another notification"; + } + return new Promise((resolve, reject) => { + callback = resolve; + requestAnimationFrame(() => { + requestAnimationFrame(() => { + reject("Missing ResizeObserver notification"); + callback = null; + }); + }); + }); + }; +})(); + +function checkSizes(className, expectedSizes, msg) { + promise_test(async () => { + await new Promise(requestAnimationFrame); + target.className = className; + let sizes; + try { + sizes = await nextSizes(); + } catch (error) { + assert_unreached(error); + } + assert_equals(sizes.length, expectedSizes.length, "number of fragments"); + for (let i = 0; i < sizes.length; ++i) { + assert_equals(sizes[i].inlineSize, expectedSizes[i][0], `fragment #${i+1} inline size`); + assert_equals(sizes[i].blockSize, expectedSizes[i][1], `fragment #${i+1} block size`); + } + }, msg); +} + +checkSizes( + "w50 h100", + [[50, 100]], + "Single fragment" +); +checkSizes( + "w50 h150", + [[50, 100], [50, 50]], + "Adding 2nd fragment" +); +checkSizes( + "w50 h175", + [[50, 100], [50, 75]], + "Resizing 2nd fragment" +); +checkSizes( + "w75 h175", + [[75, 100], [75, 75]], + "Resizing all fragments" +); +checkSizes( + "w75 h100", + [[75, 100]], + "Removing 2nd fragment" +); +</script> diff --git a/testing/web-platform/tests/resize-observer/idlharness.window.js b/testing/web-platform/tests/resize-observer/idlharness.window.js new file mode 100644 index 0000000000..2d459b0b12 --- /dev/null +++ b/testing/web-platform/tests/resize-observer/idlharness.window.js @@ -0,0 +1,37 @@ +// META: script=/resources/WebIDLParser.js +// META: script=/resources/idlharness.js +// META: script=resources/resizeTestHelper.js + +'use strict'; + +// https://wicg.github.io/ResizeObserver/ + +idl_test( + ['resize-observer'], + ['dom', 'geometry'], + async idl_array => { + idl_array.add_objects({ + ResizeObserver: ['observer'], + ResizeObserverEntry: ['entry'], + }); + + const div = document.createElement('div'); + document.body.appendChild(div); + let helper = new ResizeTestHelper( + "ResizeObserverEntry creator", + [ + { + setup: observer => { + self.observer = observer; + observer.observe(div); + div.style.width = "5px"; + }, + notify: entries => { + self.entry = entries[0]; + assert_equals(entries[0].contentRect.width, 5, "target width"); + } + } + ]); + await helper.start(); + } +); diff --git a/testing/web-platform/tests/resize-observer/iframe-same-origin-ref.html b/testing/web-platform/tests/resize-observer/iframe-same-origin-ref.html new file mode 100644 index 0000000000..142db741ef --- /dev/null +++ b/testing/web-platform/tests/resize-observer/iframe-same-origin-ref.html @@ -0,0 +1,7 @@ +<!DOCTYPE html> +<title>Resize Observer: reference for the usage of iframes in the same origin</title> +<iframe style="width: 100px; height: 100px;" + srcdoc="<div style='background: green; height: 50px; width: 50px;'></div"> +</iframe> +<br> +Observer callbacks: 3 diff --git a/testing/web-platform/tests/resize-observer/iframe-same-origin.html b/testing/web-platform/tests/resize-observer/iframe-same-origin.html new file mode 100644 index 0000000000..22d8399b23 --- /dev/null +++ b/testing/web-platform/tests/resize-observer/iframe-same-origin.html @@ -0,0 +1,76 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<title>Resize Observer: observed elements and ResizeObserver object are in the + differnt documents</title> +<link rel="match" href="iframe-same-origin-ref.html"> +<meta name="assert" content="The resize observer callback should be notified + when the observed element inside an sub document while the resize observer + is registed in the outer document"> + +<script src="/common/reftest-wait.js"></script> + +<body> + <iframe id="container" style="width: 100px; height: 100px;" + srcdoc="<div style='background: green; height: 30px; width: 50px;'></div"> + </iframe> + <br> + Observer callbacks: <span id="callbackReport">0</span> +</body> + +<script> + function load() { + return new Promise(resolve => { + container.onload = resolve; + }); + } + + let target; + let resolvePromise; + load().then(() => { + // Get target after loaded. + target = container.contentWindow.document.body.firstElementChild; + + let observerCallbacks = 0; + const resizeObserver = new ResizeObserver(entries => { + callbackReport.innerText = ++observerCallbacks; + resolvePromise(); + }); + return new Promise(resolve => { + resolvePromise = resolve; + resizeObserver.observe(target); + // |observerCallbacks| will be increased by one here because we need to + // trigger notification in the event loop that contains ResizeObserver + // observe() call even when resize/reflow does not happen. + }); + }).then(() => { + return new Promise(resolve => { + // Use requestAnimationFrame() to make sure we handle the callback in + // the following event loop. (This makes sure we schedule the + // ResizeObserver event properly for the following event loop after + // handling the previous one.) + window.requestAnimationFrame(() => { + resolvePromise = resolve; + target.style.height = "40px"; + target.offsetHeight; // force to reflow the iframe document. + // |observerCallbacks| is 2 now. + }); + }); + }).then(() => { + return new Promise(resolve => { + window.requestAnimationFrame(() => { + resolvePromise = resolve; + target.style.height = "50px"; + target.offsetHeight; // force to reflow the iframe document. + // |observerCallbacks| is 3 now. + }); + }); + }).then(() => { + // This is needed to workaround a Chromium test infrastructure bug. + // See https://crbug.com/1270820#c8 for details. + return new Promise((resolve) => window.requestAnimationFrame(resolve)); + }).then(() => { + document.body.offsetHeight; // force to reflow the outer document. + takeScreenshot(); + }); +</script> +</html> diff --git a/testing/web-platform/tests/resize-observer/multiple-observers-with-mutation-crash.html b/testing/web-platform/tests/resize-observer/multiple-observers-with-mutation-crash.html new file mode 100644 index 0000000000..c844854e5c --- /dev/null +++ b/testing/web-platform/tests/resize-observer/multiple-observers-with-mutation-crash.html @@ -0,0 +1,49 @@ +<!doctype html> +<html class="test-wait"> +<!-- + This test is for crbug.com/1368458 which is a crash where we expected + up-to-date style&layout when delivering resize observations. We would crash + if a resize observer in one frame caused a modification in the presence of a + resize observer in another frame. +--> +<iframe id="iframe" style="border: none;" srcdoc=" + <!doctype html> + <style>body { margin: 0; }</style> + <div id='inner_element'>hello</div> + <script> + const resizeObserver = new ResizeObserver((entries) => { + const size = entries[0].borderBoxSize[0].inlineSize; + const event = new CustomEvent('onIframeResizeObserved', {detail: size}); + parent.document.dispatchEvent(event); + }); + resizeObserver.observe(inner_element); + </script> +"></iframe> +<div id="outer_element" style="width: 200px;">world</div> + +<script> + const onInnerElementInitialResize = (event) => { + // `inner_element` should result in an initial observation of width 300px. + window.document.removeEventListener( + 'onIframeResizeObserved', onInnerElementInitialResize, false); + + const resizeObserver = new ResizeObserver((entries) => { + // `outer_element` should result in an initial observation of width 200px. + + // Modify styles so that inner_element is resized. + iframe.contentDocument.body.style.width = "200px"; + }); + resizeObserver.observe(outer_element); + + const onInnerElementSecondResize = (event) => { + // `inner_element` should result in a second observation of width 100px. + + // Finish the test. + document.documentElement.classList.remove('test-wait'); + }; + window.document.addEventListener( + 'onIframeResizeObserved', onInnerElementSecondResize, false); + }; + window.document.addEventListener( + 'onIframeResizeObserved', onInnerElementInitialResize, false); +</script> diff --git a/testing/web-platform/tests/resize-observer/notify.html b/testing/web-platform/tests/resize-observer/notify.html new file mode 100644 index 0000000000..dc2e268b8a --- /dev/null +++ b/testing/web-platform/tests/resize-observer/notify.html @@ -0,0 +1,408 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="./resources/resizeTestHelper.js"></script> +<style> + div { + border: 1px dotted gray + } + .transform { + transform: scale(2,2) rotate(90deg) + } +</style> +<p>ResizeObserver tests</p> +<div id="target1" style="width:100px;height:100px;">t1 + <div id="target2" style="width:100px;height:100px;">t2 + <div id="target3" style="width:100px;height:100px;">t3 + <span id="inline">inline</span> + </div> + </div> +</div> +<div id="absolute" style="width:100.5px;height:100.5px;position:absolute;top:10.3px;left:10.3px"></div> +<script> +'use strict'; + +let t1 = document.querySelector('#target1'); +let t2 = document.querySelector('#target2'); +let t3 = document.querySelector('#target3'); +let abs = document.querySelector('#absolute'); +let inline = document.querySelector('#inline'); + +function test0() { + let helper = new ResizeTestHelper( + "test0: notification ordering", + [ + { + setup: observer => { + observer.observe(t3); + observer.observe(t2); + observer.observe(t1); + t1.style.width = "5px"; + t3.style.width = "5px"; + t2.style.width = "5px"; + }, + notify: (entries, observer) => { + assert_equals(entries.length, 3, "3 resizes"); + assert_equals(entries[0].target, t3, "ordering"); + assert_equals(entries[1].target, t2, "ordering"); + assert_equals(entries[2].target, t1, "ordering"); + observer.disconnect(); + t1.style.width = "100px"; + t2.style.width = "100px"; + t3.style.width = "100px"; + } + } + ]); + return helper.start(); +} + +function test1() { + let helper = new ResizeTestHelper( + "test1: display:none triggers notification", + [ + { + setup: observer => { + observer.observe(t1); + }, + notify: (entries, observer) => { + return true; // Delay next step + } + }, + { + setup: observer => { + t1.style.display = "none"; + }, + notify: (entries, observer) => { + t1.style.display = ""; + } + } + ]); + return helper.start(); +} + + +function test2() { + let helper = new ResizeTestHelper( + "test2: remove/appendChild trigger notification", + [ + { + setup: observer => { + observer.observe(t1); + }, + notify: (entries, observer) => { + return true; // Delay next step + } + }, + { // "removeChild triggers notification" + setup: observer => { + t1.parentNode.removeChild(t1); + }, + notify: (entries, observer) => { + assert_equals(entries[0].target, t1); + return true; // Delay next step + } + }, + { // "appendChild triggers notification", + setup: observer => { + document.body.appendChild(t1); + }, + notify: (entries, observer) => { + assert_equals(entries[0].target, t1) + } + } + ]); + return helper.start(); +} + + +function test3() { + let helper = new ResizeTestHelper( + "test3: dimensions match", + [ + { + setup: observer => { + observer.observe(t1); + t1.style.width = "200.5px"; + t1.style.height = "100px"; + t1.style.paddingLeft = "20px"; + t1.style.paddingTop = "10px"; + }, + notify: (entries, observer) => { + assert_equals(entries[0].contentRect.left,20); + assert_equals(entries[0].contentRect.top,10); + assert_between_inclusive(entries[0].contentRect.width, 200.4, 200.6, "width is not rounded"); + assert_equals(entries[0].contentRect.height, 100); + } + } + ]); + return helper.start(); +} + +function test4() { + let helper = new ResizeTestHelper( + "test4: transform do not cause notifications", + [ + { + setup: observer => { + observer.observe(t2); + }, + notify: (entries, observer) => { + return true; // Delay next step + } + }, + { + setup: observer => { + t2.classList.add("transform"); + }, + notify: (entries, observer) => { + assert_unreached("transform must not trigger notifications"); + }, + timeout: () => { + t2.classList.remove("transform"); + } + } + ]); + return helper.start(); +} + +function test5() { + let helper = new ResizeTestHelper( + "test5: moving an element does not trigger notifications", + [ + { + setup: observer => { + observer.observe(abs); + }, + notify: (entries, observer) => { + return true; // Delay next step + } + }, + { + setup: observer => { + abs.style.top = "20.33px"; + abs.style.left = "20.33px"; + }, + notify: (entries, observer) => { + assert_unreached("movement should not cause resize notifications"); + }, + timeout: () => { + } + } + ]); + return helper.start(); +} + +function test6() { + let helper = new ResizeTestHelper( + "test6: inline element notifies once with 0x0.", + [ + { + setup: observer => { + observer.observe(inline); + }, + notify: (entries, observer) => { + assert_equals(entries.length, 1, "observing inline element triggers notification"); + assert_equals(entries[0].target, inline, "observing inline element triggers notification"); + assert_equals(entries[0].contentRect.width, 0); + assert_equals(entries[0].contentRect.height, 0); + return true; // Delay next step + } + }, + { + setup: observer => { + inline.style.width = "66px"; + }, + notify: (entries, observer) => { + assert_unreached("resizing inline element should not cause resize notifications"); + }, + timeout: () => { + // expected + } + }, + { // "inline element that becomes block should notify", + setup: observer => { + inline.style.display = "block"; + }, + notify: (entries, observer) => { + assert_equals(entries.length, 1, "inline element becoming a non-zero sized block triggers a notification"); + assert_equals(entries[0].target, inline, "inline element becoming a non-zero sized block triggers a notification"); + } + } + ]); + return helper.start(); +} + +function test7() { + let helper = new ResizeTestHelper( + "test7: unobserve inside notify callback", + [ + { + setup: observer => { + observer.observe(t1); + observer.observe(t2); + }, + notify: (entries, observer) => { + t1.style.width = "777px"; + t2.style.width = "777px"; + observer.unobserve(t1); + return true; // Delay next step + } + }, + { + setup: observer => { + }, + notify: (entries, observer) => { + assert_equals(entries.length, 1, "only t2 is observed"); + assert_equals(entries[0].target, t2, "only t2 is observed"); + } + } + ]); + return helper.start(); +} + +function test8() { + let helper = new ResizeTestHelper( + "test8: observe inside notify callback", + [ + { + setup: observer => { + observer.observe(t1); + }, + notify: (entries, observer) => { + observer.observe(t2); + t2.style.width = "888px"; + return true; // Delay next step + } + }, + { + setup: observer => { + }, + notify: (entries, observer) => { + assert_equals(entries.length, 1, "only t2 is observed"); + assert_equals(entries[0].target, t2, "only t2 is observed"); + } + } + ]); + return helper.start(); +} + +function test9() { + let helper = new ResizeTestHelper( + "test9: disconnect inside notify callback", + [ + { + setup: observer => { + observer.observe(t1); + }, + notify: (entries, observer) => { + t1.style.width = "999px"; + observer.disconnect(); + return true; // Delay next step + } + }, + { + setup: observer => { + }, + notify: (entries, observer) => { + assert_unreached("there should be no notifications after disconnect"); + }, + timeout: () => { + } + } + ]); + return helper.start(); +} + +function test10() { + var parent = t1.parentNode; + let helper = new ResizeTestHelper( + "test10: element notifies when parent removed", + [ + { + setup: observer => { + observer.observe(t3); + }, + notify: (entries, observer) => { + return true; // Delay next step + } + }, + { + setup: observer => { + t1.parentNode.removeChild(t1); + }, + notify: (entries, observer) => { + assert_equals(entries.length, 1); + assert_equals(entries[0].target, t3); + parent.appendChild(t1); + } + } + ]); + return helper.start(); +} + +function test11() { + let t = createAndAppendElement("div"); + t.style.display = "none"; + + let helper = new ResizeTestHelper( + "test11: display:none element should be notified", + [ + { + setup: observer => { + observer.observe(t); + }, + notify: entries => { + assert_equals(entries.length, 1, "1 pending notification"); + assert_equals(entries[0].target, t, "target is t"); + assert_equals(entries[0].contentRect.width, 0, "target width"); + assert_equals(entries[0].contentRect.height, 0, "target height"); + } + } + ]); + return helper.start(() => t.remove()); +} + +function test12() { + let t = createAndAppendElement("div"); + t.style.width = "0px"; + + let helper = new ResizeTestHelper( + "test12: element sized 0x0 should be notified", + [ + { + setup: observer => { + observer.observe(t); + }, + notify: entries => { + assert_equals(entries.length, 1, "1 pending notification"); + assert_equals(entries[0].target, t, "target is t"); + assert_equals(entries[0].contentRect.width, 0, "target width"); + assert_equals(entries[0].contentRect.height, 0, "target height"); + } + } + ]); + return helper.start(() => t.remove()); +} + +let guard; +test(_ => { + assert_own_property(window, "ResizeObserver"); + guard = async_test('guard'); +}, "ResizeObserver implemented") + +test0() + .then(() => { return test1(); }) + .then(() => { return test2(); }) + .then(() => { return test3(); }) + .then(() => { return test4(); }) + .then(() => { return test5(); }) + .then(() => { return test6(); }) + .then(() => { return test7(); }) + .then(() => { return test8(); }) + .then(() => { return test9(); }) + .then(() => { return test10(); }) + .then(() => { return test11(); }) + .then(() => { return test12(); }) + .then(() => { guard.done(); }); + +</script> diff --git a/testing/web-platform/tests/resize-observer/observe.html b/testing/web-platform/tests/resize-observer/observe.html new file mode 100644 index 0000000000..f6015bef78 --- /dev/null +++ b/testing/web-platform/tests/resize-observer/observe.html @@ -0,0 +1,1007 @@ +<!doctype html> +<title>ResizeObserver tests</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="./resources/resizeTestHelper.js"></script> +<body> +<div id="log"></div> + +<script> +'use strict'; + +function test0() { + let t = createAndAppendElement("div"); + t.style.width = "100px"; + + let helper = new ResizeTestHelper( + "test0: simple observation", + [ + { + setup: observer => { + observer.observe(t); + t.style.width = "5px"; + }, + notify: entries => { + assert_equals(entries.length, 1, "1 pending notification"); + assert_equals(entries[0].target, t, "target is t"); + assert_equals(entries[0].contentRect.width, 5, "target width"); + } + } + ]); + return helper.start(() => t.remove()); +} + +function test1() { + let t = createAndAppendElement("div"); + t.style.width = "100px"; + + let helper = new ResizeTestHelper( + "test1: multiple observation on same element trigger only one", + [ + { + setup: observer => { + observer.observe(t); + observer.observe(t); + t.style.width = "10px"; + }, + notify: entries => { + assert_equals(entries.length, 1, "1 pending notification"); + } + } + ]); + return helper.start(() => t.remove()); +} + +function test2() { + test(() => { + assert_throws_js(TypeError, _=> { + let ro = new ResizeObserver(() => {}); + ro.observe({}); + }); + }, + "test2: throw exception when observing non-element" + ); + return Promise.resolve(); +} + +function test3() { + let t1 = createAndAppendElement("div"); + let t2 = createAndAppendElement("div"); + t1.style.width = "100px"; + t2.style.width = "100px"; + + let helper = new ResizeTestHelper( + "test3: disconnect stops all notifications", [ + { + setup: observer => { + observer.observe(t1); + observer.observe(t2); + observer.disconnect(); + t1.style.width = "30px"; + }, + notify: entries => { + assert_unreached("no entries should be observed"); + }, + timeout: () => { + // expected + } + } + ]); + return helper.start(() => { + t1.remove(); + t2.remove(); + }); +} + +function test4() { + let t1 = createAndAppendElement("div"); + let t2 = createAndAppendElement("div"); + t1.style.width = "100px"; + t2.style.width = "100px"; + + let helper = new ResizeTestHelper( + "test4: unobserve target stops notifications, unobserve non-observed does nothing", [ + { + setup: observer => { + observer.observe(t1); + observer.observe(t2); + observer.unobserve(t1); + observer.unobserve(document.body); + t1.style.width = "40px"; + t2.style.width = "40px"; + }, + notify: entries => { + assert_equals(entries.length, 1, "only t2"); + assert_equals(entries[0].target, t2, "t2 was observed"); + } + } + ]); + return helper.start(() => { + t1.remove(); + t2.remove(); + }); +} + +function test5() { + const img = new Image(); + img.style.width = "15px"; + img.style.height = "15px"; + img.src = "resources/image.png"; + + return img.decode().then(() => { + return new Promise(resolve => { + requestAnimationFrame(() => { + document.body.appendChild(img); + resolve(); + }); + }); + }).then(() => { + let helper = new ResizeTestHelper("test5: observe img",[ + { + setup: observer => { + observer.observe(img); + }, + notify: entries => { + return true; + } + }, + { + setup: observer => { + img.style.width = "15.5px"; + }, + notify: entries => { + assert_equals(entries.length, 1); + assert_equals(entries[0].contentRect.width, 15.5); + } + } + ]); + return helper.start(() => img.remove()); + }, () => { + // dummy test for dumping the error message. + test(_ => { + assert_unreached("decode image failed"); + }, "test5: observe img"); + }); +} + +function test6() { + let iframe = createAndAppendElement("iframe"); + iframe.width = "300px"; + iframe.height = "100px"; + iframe.style.display = "block"; + + let resolvePromise; + let promise = new Promise((resolve) => { + resolvePromise = resolve; + }); + let test = async_test('test6: iframe notifications'); + let testRequested = false; + test.add_cleanup(() => iframe.remove()); + + window.addEventListener('message', event => { + switch(event.data) { + case 'readyToTest': + if (!testRequested) { + iframe.contentWindow.postMessage('startTest', '*'); + testRequested = true; + } + break; + case 'success': + case 'fail': + window.requestAnimationFrame(() => { + resolvePromise(); + test.step(() => { + assert_equals(event.data, 'success'); + test.done(); + }); + }); + break; + } + }, false); + + iframe.src = "./resources/iframe.html"; + return new Promise(function(resolve, reject) { + iframe.onload = () => resolve(); + iframe.onerror = () => reject(); + }).then(() => { + return promise; + }).catch(error => { + test.step(() => { + assert_unreached("loading iframe is error"); + }); + }); +} + +function test7() { + let harnessTest = async_test("test7: callback.this"); + let resolvePromise; + let ro = new ResizeObserver( function(entries, obs) { + let callbackThis = this; + harnessTest.step(() => { + assert_equals(callbackThis, ro, "callback.this is ResizeObserver"); + assert_equals(obs, ro, "2nd argument is ResizeObserver"); + ro.disconnect(); + // every reference to RO must be null before test completes + // to avoid triggering test leak-detection + ro = null; + callbackThis = null; + obs = null; + harnessTest.step_timeout( _ => { + harnessTest.done(); + resolvePromise(); + }, 0); + }); + } + ); + + let t = createAndAppendElement("div"); + t.style.width = "100px"; + harnessTest.add_cleanup(() => t.remove()); + + ro.observe(t); + + return new Promise( (resolve, reject) => { + resolvePromise = resolve; + }); +} + +function test8() { + let t = createAndAppendElement("div"); + t.style.width = "100px"; + t.style.height = "100px"; + + let helper = new ResizeTestHelper( + "test8: simple content-box observation", + [ + { + setup: observer => { + + observer.observe(t, { box: "content-box" }); + }, + notify: entries => { + assert_equals(entries.length, 1, "1 pending notification"); + assert_equals(entries[0].target, t, "target is t"); + assert_equals(entries[0].contentRect.width, 100, "target width"); + assert_equals(entries[0].contentRect.height, 100, "target height"); + assert_equals(entries[0].contentRect.top, 0, "target top padding"); + assert_equals(entries[0].contentRect.left, 0, "target left padding"); + assert_equals(entries[0].contentBoxSize[0].inlineSize, 100, + "target content-box inline size"); + assert_equals(entries[0].contentBoxSize[0].blockSize, 100, + "target content-box block size"); + assert_equals(entries[0].borderBoxSize[0].inlineSize, 100, + "target border-box inline size"); + assert_equals(entries[0].borderBoxSize[0].blockSize, 100, + "target border-box block size"); + return true; + } + }, + { + setup: observer => { + t.style.width = "90px"; + t.style.height = "90px"; + }, + notify: entries => { + assert_equals(entries.length, 1, "1 pending notification"); + assert_equals(entries[0].target, t, "target is t"); + assert_equals(entries[0].contentRect.width, 90, "target width"); + assert_equals(entries[0].contentRect.height, 90, "target height"); + assert_equals(entries[0].contentRect.top, 0, "target top padding"); + assert_equals(entries[0].contentRect.left, 0, "target left padding"); + assert_equals(entries[0].contentBoxSize[0].inlineSize, 90, + "target content-box inline size"); + assert_equals(entries[0].contentBoxSize[0].blockSize, 90, + "target content-box block size"); + assert_equals(entries[0].borderBoxSize[0].inlineSize, 90, + "target border-box inline size"); + assert_equals(entries[0].borderBoxSize[0].blockSize, 90, + "target border-box block size"); + return true; + } + }, + { + setup: observer => { + t.style.padding = "5px"; + }, + notify: entries => { + assert_unreached("the 'content-box' ResizeObserver shouldn't fire " + + "for restyles that don't affect the content-box size"); + }, + timeout: () => { + // expected + // Note: the border-box size is 100px x 100px right now. + } + } + ]); + return helper.start(() => t.remove()); +} + +function test9() { + let t = createAndAppendElement("div"); + t.style.width = "100px"; + t.style.height = "100px"; + + let helper = new ResizeTestHelper( + "test9: simple content-box observation but keep border-box size unchanged", + [ + { + setup: observer => { + observer.observe(t, { box: "content-box" }); + }, + notify: entries => { + assert_equals(entries.length, 1, "1 pending notification"); + assert_equals(entries[0].target, t, "target is t"); + assert_equals(entries[0].contentRect.width, 100, "target width"); + assert_equals(entries[0].contentRect.height, 100, "target height"); + assert_equals(entries[0].contentRect.top, 0, "target top padding"); + assert_equals(entries[0].contentRect.left, 0, "target left padding"); + assert_equals(entries[0].contentBoxSize[0].inlineSize, 100, + "target content-box inline size"); + assert_equals(entries[0].contentBoxSize[0].blockSize, 100, + "target content-box block size"); + assert_equals(entries[0].borderBoxSize[0].inlineSize, 100, + "target border-box inline size"); + assert_equals(entries[0].borderBoxSize[0].blockSize, 100, + "target border-box block size"); + return true; + } + }, + { + setup: observer => { + // Keep the border-box size the same, and change the content-box size. + t.style.width = "92px"; + t.style.height = "92px"; + t.style.padding = "4px"; + }, + notify: entries => { + assert_equals(entries.length, 1, "1 pending notification"); + assert_equals(entries[0].target, t, "target is t"); + assert_equals(entries[0].contentRect.width, 92, "target width"); + assert_equals(entries[0].contentRect.height, 92, "target height"); + assert_equals(entries[0].contentRect.top, 4, "target top padding"); + assert_equals(entries[0].contentRect.left, 4, "target left padding"); + assert_equals(entries[0].contentBoxSize[0].inlineSize, 92, + "target content-box inline size"); + assert_equals(entries[0].contentBoxSize[0].blockSize, 92, + "target content-box block size"); + assert_equals(entries[0].borderBoxSize[0].inlineSize, 100, + "target border-box inline size"); + assert_equals(entries[0].borderBoxSize[0].blockSize, 100, + "target border-box block size"); + } + } + ]); + return helper.start(() => t.remove()); +} + +function test10() { + let t = createAndAppendElement("div"); + t.style.width = "100px"; + t.style.height = "100px"; + + let helper = new ResizeTestHelper( + "test10: simple border-box observation", + [ + { + setup: observer => { + observer.observe(t, { box: "border-box" }); + }, + notify: entries => { + assert_equals(entries.length, 1, "1 pending notification"); + assert_equals(entries[0].target, t, "target is t"); + assert_equals(entries[0].contentRect.width, 100, "target width"); + assert_equals(entries[0].contentRect.height, 100, "target height"); + assert_equals(entries[0].contentRect.top, 0, "target top padding"); + assert_equals(entries[0].contentRect.left, 0, "target left padding"); + assert_equals(entries[0].contentBoxSize[0].inlineSize, 100, + "target content-box inline size"); + assert_equals(entries[0].contentBoxSize[0].blockSize, 100, + "target content-box block size"); + assert_equals(entries[0].borderBoxSize[0].inlineSize, 100, + "target border-box inline size"); + assert_equals(entries[0].borderBoxSize[0].blockSize, 100, + "target border-box block size"); + return true; + } + }, + { + setup: observer => { + t.style.padding = "4px"; + }, + notify: entries => { + assert_equals(entries.length, 1, "1 pending notification"); + assert_equals(entries[0].target, t, "target is t"); + assert_equals(entries[0].contentRect.width, 100, "target width"); + assert_equals(entries[0].contentRect.height, 100, "target height"); + assert_equals(entries[0].contentRect.top, 4, "target top padding"); + assert_equals(entries[0].contentRect.left, 4, "target left padding"); + assert_equals(entries[0].contentBoxSize[0].inlineSize, 100, + "target content-box inline size"); + assert_equals(entries[0].contentBoxSize[0].blockSize, 100, + "target content-box block size"); + assert_equals(entries[0].borderBoxSize[0].inlineSize, 108, + "target border-box inline size"); + assert_equals(entries[0].borderBoxSize[0].blockSize, 108, + "target border-box block size"); + } + }, + { + setup: observer => { + t.style.width = "104px"; + t.style.height = "104px"; + t.style.padding = "2px"; + }, + notify: entries => { + assert_unreached("the 'border-box' ResizeObserver shouldn't fire " + + "for restyles that don't affect the border-box size"); + }, + timeout: () => { + // expected: 104 + 2 * 2 = 108. The border-box size is the same. + } + } + ]); + return helper.start(() => t.remove()); +} + +function test11() { + let wrapper = createAndAppendElement("div"); + wrapper.style.width = "100px"; + wrapper.style.height = "100px"; + wrapper.style.writingMode = "vertical-rl"; + let t = createAndAppendElement("div", wrapper); + t.style.inlineSize = "50px"; + t.style.blockSize = "50px"; + + let helper = new ResizeTestHelper( + "test11: simple observation with vertical writing mode", + [ + { + setup: observer => { + observer.observe(t); + }, + notify: entries => { + assert_equals(entries.length, 1, "1 pending notification"); + assert_equals(entries[0].target, t, "target is t"); + assert_equals(entries[0].contentRect.width, 50, "target width"); + assert_equals(entries[0].contentRect.height, 50, "target height"); + assert_equals(entries[0].contentBoxSize[0].inlineSize, 50, + "target content-box inline size"); + assert_equals(entries[0].contentBoxSize[0].blockSize, 50, + "target content-box block size"); + assert_equals(entries[0].borderBoxSize[0].inlineSize, 50, + "target border-box inline size"); + assert_equals(entries[0].borderBoxSize[0].blockSize, 50, + "target border-box block size"); + return true; + } + }, + { + setup: observer => { + t.style.blockSize = "75px"; + }, + notify: entries => { + assert_equals(entries.length, 1, "1 pending notification"); + assert_equals(entries[0].target, t, "target is t"); + assert_equals(entries[0].contentRect.width, 75, "target width"); + assert_equals(entries[0].contentRect.height, 50, "target height"); + assert_equals(entries[0].contentBoxSize[0].inlineSize, 50, + "target content-box inline size"); + assert_equals(entries[0].contentBoxSize[0].blockSize, 75, + "target content-box block size"); + assert_equals(entries[0].borderBoxSize[0].inlineSize, 50, + "target border-box inline size"); + assert_equals(entries[0].borderBoxSize[0].blockSize, 75, + "target border-box block size"); + } + } + ]); + + return helper.start(() => { + t.remove(); + wrapper.remove(); + }); +} + +function test12() { + let t = createAndAppendElement("div"); + t.style.writingMode = "vertical-lr"; + t.style.inlineSize = "100px"; + t.style.blockSize = "50px"; + + let helper = new ResizeTestHelper( + "test12: no observation is fired after the change of writing mode when " + + "box's specified size comes from logical size properties.", + [ + { + setup: observer => { + observer.observe(t); + }, + notify: entries => { + assert_equals(entries.length, 1, "1 pending notification"); + assert_equals(entries[0].target, t, "target is t"); + assert_equals(entries[0].contentRect.width, 50, "target width"); + assert_equals(entries[0].contentRect.height, 100, "target height"); + assert_equals(entries[0].contentBoxSize[0].inlineSize, 100, + "target content-box inline size"); + assert_equals(entries[0].contentBoxSize[0].blockSize, 50, + "target content-box block size"); + return true; + } + }, + { + setup: observer => { + t.style.writingMode = "horizontal-tb"; + }, + notify: entries => { + assert_unreached("the logical size of content-box doesn't change"); + }, + timeout: () => { + // expected: We don't change the logical size of content-box. + } + } + ]); + + return helper.start(() => t.remove()); +} + +function test13() { + let t = createAndAppendElement("div"); + t.style.writingMode = "vertical-lr"; + t.style.height = "100px"; + t.style.width = "50px"; + + let helper = new ResizeTestHelper( + "test13: an observation is fired after the change of writing mode when " + + "box's specified size comes from physical size properties.", + [ + { + setup: observer => { + observer.observe(t); + }, + notify: entries => { + assert_equals(entries.length, 1, "1 pending notification"); + assert_equals(entries[0].target, t, "target is t"); + assert_equals(entries[0].contentRect.width, 50, "target width"); + assert_equals(entries[0].contentRect.height, 100, "target height"); + assert_equals(entries[0].contentBoxSize[0].inlineSize, 100, + "target content-box inline size"); + assert_equals(entries[0].contentBoxSize[0].blockSize, 50, + "target content-box block size"); + return true; + } + }, + { + setup: observer => { + t.style.writingMode = "horizontal-tb"; + }, + notify: entries => { + assert_equals(entries.length, 1, "1 pending notification"); + assert_equals(entries[0].target, t, "target is t"); + assert_equals(entries[0].contentRect.width, 50, "target width"); + assert_equals(entries[0].contentRect.height, 100, "target height"); + assert_equals(entries[0].contentBoxSize[0].inlineSize, 50, + "target content-box inline size"); + assert_equals(entries[0].contentBoxSize[0].blockSize, 100, + "target content-box block size"); + }, + } + ]); + + return helper.start(() => t.remove()); +} + +function test14() { + let t = createAndAppendElement("div"); + t.style.width = "100px"; + t.style.height = "100px"; + + let helper = new ResizeTestHelper( + "test14: observe the same target but using a different box should " + + "override the previous one", + [ + { + setup: observer => { + observer.observe(t, { box: "content-box" }); + observer.observe(t, { box: "border-box" }); + }, + notify: entries => { + assert_equals(entries.length, 1, "1 pending notification"); + assert_equals(entries[0].target, t, "target is t"); + assert_equals(entries[0].contentRect.width, 100, "target width"); + assert_equals(entries[0].contentRect.height, 100, "target height"); + assert_equals(entries[0].contentRect.top, 0, "target top padding"); + assert_equals(entries[0].contentRect.left, 0, "target left padding"); + assert_equals(entries[0].contentBoxSize[0].inlineSize, 100, + "target content-box inline size"); + assert_equals(entries[0].contentBoxSize[0].blockSize, 100, + "target content-box block size"); + assert_equals(entries[0].borderBoxSize[0].inlineSize, 100, + "target border-box inline size"); + assert_equals(entries[0].borderBoxSize[0].blockSize, 100, + "target border-box block size"); + return true; + } + }, + { + setup: observer => { + // Change border-box size. + t.style.padding = "4px"; + }, + notify: entries => { + assert_equals(entries.length, 1, "1 pending notification"); + assert_equals(entries[0].target, t, "target is t"); + assert_equals(entries[0].contentRect.width, 100, "target width"); + assert_equals(entries[0].contentRect.height, 100, "target height"); + assert_equals(entries[0].contentRect.top, 4, "target top padding"); + assert_equals(entries[0].contentRect.left, 4, "target left padding"); + assert_equals(entries[0].contentBoxSize[0].inlineSize, 100, + "target content-box inline size"); + assert_equals(entries[0].contentBoxSize[0].blockSize, 100, + "target content-box block size"); + assert_equals(entries[0].borderBoxSize[0].inlineSize, 108, + "target border-box inline size"); + assert_equals(entries[0].borderBoxSize[0].blockSize, 108, + "target border-box block size"); + } + }, + { + setup: observer => { + // Change only content-box size. + t.style.width = "104px"; + t.style.height = "104px"; + t.style.padding = "2px"; + }, + notify: entries => { + assert_unreached("the 'border-box' ResizeObserver shouldn't fire " + + "for restyles that don't affect the border-box size"); + }, + timeout: () => { + // expected: 104 + 2 * 2 = 108. The border-box size is the same. + } + } + ]); + return helper.start(() => t.remove()); +} + +function test15() { + let t = createAndAppendElement("div"); + t.style.height = "100px"; + t.style.width = "50px"; + + let helper = new ResizeTestHelper( + "test15: an observation is fired with box dimensions 0 when element's " + + "display property is set to inline", + [ + { + setup: observer => { + observer.observe(t); + }, + notify: entries => { + assert_equals(entries.length, 1, "1 pending notification"); + assert_equals(entries[0].target, t, "target is t"); + assert_equals(entries[0].contentRect.width, 50, "target width"); + assert_equals(entries[0].contentRect.height, 100, "target height"); + assert_equals(entries[0].contentBoxSize[0].inlineSize, 50, + "target content-box inline size"); + assert_equals(entries[0].contentBoxSize[0].blockSize, 100, + "target content-box block size"); + assert_equals(entries[0].borderBoxSize[0].inlineSize, 50, + "target content-box inline size"); + assert_equals(entries[0].borderBoxSize[0].blockSize, 100, + "target content-box block size"); + return true; + } + }, + { + setup: observer => { + t.style.display = "inline"; + }, + notify: entries => { + assert_equals(entries[0].contentRect.width, 0, "target width"); + assert_equals(entries[0].contentRect.height, 0, "target height"); + assert_equals(entries[0].contentBoxSize[0].inlineSize, 0, + "target content-box inline size"); + assert_equals(entries[0].contentBoxSize[0].blockSize, 0, + "target content-box block size"); + assert_equals(entries[0].borderBoxSize[0].inlineSize, 0, + "target border-box inline size"); + assert_equals(entries[0].borderBoxSize[0].blockSize, 0, + "target border-box block size"); + } + } + ]); + + return helper.start(() => t.remove()); +} + +function test16() { + let t = createAndAppendElement("span"); + + let helper = new ResizeTestHelper( + // See: https://drafts.csswg.org/resize-observer/#intro. + "test16: observations fire once with 0x0 size for non-replaced inline elements", + [ + { + setup: observer => { + observer.observe(t); + }, + notify: entries => { + assert_equals(entries.length, 1, "1 pending notification"); + assert_equals(entries[0].target, t, "target is t"); + assert_equals(entries[0].contentRect.width, 0, "target width"); + assert_equals(entries[0].contentRect.height, 0, "target height"); + assert_equals(entries[0].contentBoxSize[0].inlineSize, 0, + "target content-box inline size"); + assert_equals(entries[0].contentBoxSize[0].blockSize, 0, + "target content-box block size"); + assert_equals(entries[0].borderBoxSize[0].inlineSize, 0, + "target border-box inline size"); + assert_equals(entries[0].borderBoxSize[0].blockSize, 0, + "target border-box block size"); + } + } + ]); + + return helper.start(() => t.remove()); + +} + +function test17() { + // <div id="outer"> + // <div id="nested"> + // </div> + // </div> + + let outer = document.createElement('div'); + outer.style.width = "100px"; + outer.style.height = "100px"; + outer.style.padding = "10px"; + outer.style.border = "1px solid blue" + let nested = document.createElement('div'); + nested.style.width = "60px"; + nested.style.height = "50px"; + nested.style.padding = "5%"; + nested.style.boxSizing = "border-box"; + nested.style.border = "5px solid black"; + outer.appendChild(nested); + document.body.appendChild(outer); + + let helper = new ResizeTestHelper( + "test17: Box sizing snd Resize Observer notifications", + [ + { + setup: observer => { + observer.observe(nested, { box: "content-box" }); + }, + notify: entries => { + assert_equals(entries.length, 1, "1 pending notification"); + assert_equals(entries[0].target, nested, "target is nested"); + assert_equals(entries[0].contentRect.width, 40, "target width"); + assert_equals(entries[0].contentRect.height, 30, "target height"); + assert_equals(entries[0].contentRect.top, 5, "target top padding"); + assert_equals(entries[0].contentRect.left, 5, "target left padding"); + assert_equals(entries[0].contentBoxSize[0].inlineSize, 40, + "target content-box inline size"); + assert_equals(entries[0].contentBoxSize[0].blockSize, 30, + "target content-box block size"); + assert_equals(entries[0].borderBoxSize[0].inlineSize, 60, + "target border-box inline size"); + assert_equals(entries[0].borderBoxSize[0].blockSize, 50, + "target border-box block size"); + return true; + } + }, + { + // Changes to a parent's dimensions with a child's padding set as a percentage + // should fire observation if content-box is being observed + setup: observer => { + outer.style.height = "200px"; + outer.style.width = "200px"; + }, + notify: entries => { + assert_equals(entries.length, 1, "1 pending notification"); + assert_equals(entries[0].target, nested, "target is nested"); + assert_equals(entries[0].contentRect.width, 30, "target width"); + assert_equals(entries[0].contentRect.height, 20, "target height"); + assert_equals(entries[0].contentRect.top, 10, "target top padding"); + assert_equals(entries[0].contentRect.left, 10, "target left padding"); + assert_equals(entries[0].contentBoxSize[0].inlineSize, 30, + "target content-box inline size"); + assert_equals(entries[0].contentBoxSize[0].blockSize, 20, + "target content-box block size"); + assert_equals(entries[0].borderBoxSize[0].inlineSize, 60, + "target border-box inline size"); + assert_equals(entries[0].borderBoxSize[0].blockSize, 50, + "target border-box block size"); + return true; + } + }, + { + // Changes to a parent's dimensions with a child's padding set as a percentage + // should fire observation if content-box is being observed + setup: observer => { + nested.style.border = "1px solid black"; + }, + notify: entries => { + assert_equals(entries.length, 1, "1 pending notification"); + assert_equals(entries[0].target, nested, "target is nested"); + assert_equals(entries[0].contentRect.width, 38, "target width"); + assert_equals(entries[0].contentRect.height, 28, "target height"); + assert_equals(entries[0].contentRect.top, 10, "target top padding"); + assert_equals(entries[0].contentRect.left, 10, "target left padding"); + assert_equals(entries[0].contentBoxSize[0].inlineSize, 38, + "target content-box inline size"); + assert_equals(entries[0].contentBoxSize[0].blockSize, 28, + "target content-box block size"); + assert_equals(entries[0].borderBoxSize[0].inlineSize, 60, + "target border-box inline size"); + assert_equals(entries[0].borderBoxSize[0].blockSize, 50, + "target border-box block size"); + return true; + } + }, + { + setup: observer => { + observer.observe(nested, { box: "border-box" }); + }, + notify: entries => { + assert_equals(entries.length, 1, "1 pending notification"); + assert_equals(entries[0].target, nested, "target is nested"); + assert_equals(entries[0].contentRect.width, 38, "target width"); + assert_equals(entries[0].contentRect.height, 28, "target height"); + assert_equals(entries[0].contentRect.top, 10, "target top padding"); + assert_equals(entries[0].contentRect.left, 10, "target left padding"); + assert_equals(entries[0].contentBoxSize[0].inlineSize, 38, + "target content-box inline size"); + assert_equals(entries[0].contentBoxSize[0].blockSize, 28, + "target content-box block size"); + assert_equals(entries[0].borderBoxSize[0].inlineSize, 60, + "target border-box inline size"); + assert_equals(entries[0].borderBoxSize[0].blockSize, 50, + "target border-box block size"); + return true; + } + }, + { + // Changes to a parent's dimensions with a child's padding set as a percentage + // should not fire observation if border-box is being observed + setup: observer => { + outer.style.height = "100px"; + }, + notify: entries => { + assert_unreached("No observation should be fired when nested border box remains constant"); + }, + timeout: () => { + // expected + } + }, + + ]); + return helper.start(() => nested.remove()); +} + +function test18() { + let t = createAndAppendElement("div"); + t.style.height = "100px"; + t.style.width = "50px"; + + let helper = new ResizeTestHelper( + "test18: an observation is fired when device-pixel-content-box is being " + + "observed", + [ + { + setup: observer => { + observer.observe(t, {box: "device-pixel-content-box"}); + }, + notify: entries => { + assert_equals(entries.length, 1, "1 pending notification"); + assert_equals(entries[0].target, t, "target is t"); + assert_equals(entries[0].contentRect.width, 50, "target width"); + assert_equals(entries[0].contentRect.height, 100, "target height"); + assert_equals(entries[0].contentBoxSize[0].inlineSize, 50, + "target content-box inline size"); + assert_equals(entries[0].contentBoxSize[0].blockSize, 100, + "target content-box block size"); + assert_equals(entries[0].borderBoxSize[0].inlineSize, 50, + "target border-box inline size"); + assert_equals(entries[0].borderBoxSize[0].blockSize, 100, + "target border-box block size"); + assert_equals(entries[0].devicePixelContentBoxSize[0].inlineSize, 50, + "target device-pixel-content-box inline size"); + assert_equals(entries[0].devicePixelContentBoxSize[0].blockSize, 100, + "target device-pixel-content-box block size"); + } + }, + ]); + + return helper.start(() => t.remove()); +} + +function test19() { + // zoom is not a standard css property, so we should check it first. If the + // browser doesn't support it, we skip this test. + if (!CSS.supports("zoom", "3")) { + return Promise.resolve(); + } + + let t = createAndAppendElement("div"); + t.style.height = "100px"; + t.style.width = "50px"; + + let helper = new ResizeTestHelper( + "test19: an observation is fired when device-pixel-content-box is being " + + "observed and zoom change", + [ + { + setup: observer => { + observer.observe(t, {box: "device-pixel-content-box"}); + }, + notify: entries => { + // No need to test again (see test18), so skip this event loop. + return true; + } + }, + { + setup: observer => { + document.body.style.zoom = 3; + }, + notify: entries => { + assert_equals(entries.length, 1, "1 pending notification"); + assert_equals(entries[0].target, t, "target is t"); + assert_equals(entries[0].contentRect.width, 50, "target width"); + assert_equals(entries[0].contentRect.height, 100, "target height"); + assert_equals(entries[0].contentBoxSize[0].inlineSize, 50, + "target content-box inline size"); + assert_equals(entries[0].contentBoxSize[0].blockSize, 100, + "target content-box block size"); + assert_equals(entries[0].borderBoxSize[0].inlineSize, 50, + "target border-box inline size"); + assert_equals(entries[0].borderBoxSize[0].blockSize, 100, + "target border-box block size"); + assert_equals(entries[0].devicePixelContentBoxSize[0].inlineSize, 150, + "target device-pixel-content-box inline size"); + assert_equals(entries[0].devicePixelContentBoxSize[0].blockSize, 300, + "target device-pixel-content-box block size"); + return true; + } + }, + { + setup: observer => { + document.body.style.zoom = ''; + }, + notify: entries => {} + } + ]); + + return helper.start(() => t.remove()); +} + +let guard; +test(_ => { + assert_own_property(window, "ResizeObserver"); + guard = async_test('guard'); +}, "ResizeObserver implemented") + +test0() + .then(() => test1()) + .then(() => test2()) + .then(() => test3()) + .then(() => test4()) + .then(() => test5()) + .then(() => test6()) + .then(() => test7()) + .then(() => test8()) + .then(() => test9()) + .then(() => test10()) + .then(() => test11()) + .then(() => test12()) + .then(() => test13()) + .then(() => test14()) + .then(() => test15()) + .then(() => test16()) + .then(() => test17()) + .then(() => test18()) + .then(() => test19()) + .then(() => guard.done()); + +</script> diff --git a/testing/web-platform/tests/resize-observer/ordering.html b/testing/web-platform/tests/resize-observer/ordering.html new file mode 100644 index 0000000000..1cd9950c53 --- /dev/null +++ b/testing/web-platform/tests/resize-observer/ordering.html @@ -0,0 +1,24 @@ +<!doctype html> +<title>ResizeObserver and IntersectionObserver ordering</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> + async_test(function(t) { + let sawResize = false; + let sawIo = false; + let resizeObserver = new ResizeObserver(t.step_func(function() { + assert_false(sawIo, "ResizeObserver notification should be delivered before IntersectionObserver notification"); + sawResize = true; + resizeObserver.disconnect(); + })); + + let io = new IntersectionObserver(t.step_func_done(function() { + assert_true(sawResize, "IntersectionObserver notification should be delivered after ResizeObserver notification"); + sawIo = true; + io.disconnect(); + })); + + resizeObserver.observe(document.documentElement); + io.observe(document.documentElement); + }); +</script> diff --git a/testing/web-platform/tests/resize-observer/resources/iframe.html b/testing/web-platform/tests/resize-observer/resources/iframe.html new file mode 100644 index 0000000000..5c801e6368 --- /dev/null +++ b/testing/web-platform/tests/resize-observer/resources/iframe.html @@ -0,0 +1,38 @@ +<!doctype html> +<head> + <script src="./resizeTestHelper.js"></script> +</head> +<p>iframe test</p> +<div id="itarget1" style="width:100px;height:100px;">t1</div> +<script> +'use strict'; +let t1 = document.querySelector('#itarget1'); +function test0() { + let timeoutId = window.setTimeout( () => { + window.parent.postMessage('fail', '*'); + }, ResizeTestHelper.TIMEOUT); + let ro = new ResizeObserver(function(entries) { + window.clearTimeout(timeoutId); + window.parent.postMessage('success', '*'); + }); + ro.observe(t1); +} +let testStarted = false; +window.addEventListener('message', function(ev) { + switch(ev.data) { + case 'startTest': + testStarted = true; + test0(); + break; + } +}); +// How does parent know we've loaded problem is solved by +// broadcasting readyToTest message repeatedly until test starts. +function broadcastReady() { + if (!testStarted) { + window.parent.postMessage('readyToTest', '*'); + window.requestAnimationFrame(broadcastReady); + } +} +window.onload = broadcastReady; +</script> diff --git a/testing/web-platform/tests/resize-observer/resources/image.png b/testing/web-platform/tests/resize-observer/resources/image.png Binary files differnew file mode 100644 index 0000000000..51741584a0 --- /dev/null +++ b/testing/web-platform/tests/resize-observer/resources/image.png diff --git a/testing/web-platform/tests/resize-observer/resources/resizeTestHelper.js b/testing/web-platform/tests/resize-observer/resources/resizeTestHelper.js new file mode 100644 index 0000000000..284a780c25 --- /dev/null +++ b/testing/web-platform/tests/resize-observer/resources/resizeTestHelper.js @@ -0,0 +1,195 @@ +'use strict'; + +/** + ResizeTestHelper is a framework to test ResizeObserver + notifications. Use it to make assertions about ResizeObserverEntries. + This framework is needed because ResizeObservations are + delivered asynchronously inside the event loop. + + Features: + - can queue multiple notification steps in a test + - handles timeouts + - returns Promise that is fulfilled when test completes. + Use to chain tests (since parallel async ResizeObserver tests + would conflict if reusing same DOM elements). + + Usage: + + create ResizeTestHelper for every test. + Make assertions inside notify, timeout callbacks. + Start tests with helper.start() + Chain tests with Promises. + Counts animation frames, see startCountingRaf +*/ + +/* + @param name: test name + @param steps: + { + setup: function(ResizeObserver) { + // called at the beginning of the test step + // your observe/resize code goes here + }, + notify: function(entries, observer) { + // ResizeObserver callback. + // Make assertions here. + // Return true if next step should start on the next event loop. + }, + timeout: function() { + // Define this if your test expects to time out, and the expected timeout + // value will be 100ms. + // If undefined, timeout is assert_unreached after 1000ms. + } + } +*/ +function ResizeTestHelper(name, steps) +{ + this._name = name; + this._steps = steps || []; + this._stepIdx = -1; + this._harnessTest = null; + this._observer = new ResizeObserver(this._handleNotification.bind(this)); + this._timeoutBind = this._handleTimeout.bind(this); + this._nextStepBind = this._nextStep.bind(this); +} + +// The default timeout value in ms. +// We expect TIMEOUT to be longer than we would ever have to wait for notify() +// to be fired. This is used for tests which are not expected to time out, so +// it can be large without slowing down the test. +ResizeTestHelper.TIMEOUT = 1000; +// A reasonable short timeout value in ms. +// We expect SHORT_TIMEOUT to be long enough that notify() would usually get a +// chance to fire before SHORT_TIMEOUT expires. This is used for tests which +// *are* expected to time out (with no callbacks fired), so we'd like to keep +// it relatively short to avoid slowing down the test. +ResizeTestHelper.SHORT_TIMEOUT = 100; + +ResizeTestHelper.prototype = { + get _currentStep() { + return this._steps[this._stepIdx]; + }, + + _nextStep: function() { + if (++this._stepIdx == this._steps.length) + return this._done(); + // Use SHORT_TIMEOUT if this step expects timeout. + let timeoutValue = this._steps[this._stepIdx].timeout ? + ResizeTestHelper.SHORT_TIMEOUT : + ResizeTestHelper.TIMEOUT; + this._timeoutId = this._harnessTest.step_timeout( + this._timeoutBind, timeoutValue); + try { + this._steps[this._stepIdx].setup(this._observer); + } + catch(err) { + this._harnessTest.step(() => { + assert_unreached("Caught a throw, possible syntax error"); + }); + } + }, + + _handleNotification: function(entries) { + if (this._timeoutId) { + window.clearTimeout(this._timeoutId); + delete this._timeoutId; + } + this._harnessTest.step(() => { + try { + let rafDelay = this._currentStep.notify(entries, this._observer); + if (rafDelay) + window.requestAnimationFrame(this._nextStepBind); + else + this._nextStep(); + } + catch(err) { + this._harnessTest.step(() => { + throw err; + }); + // Force to _done() the current test. + this._done(); + } + }); + }, + + _handleTimeout: function() { + delete this._timeoutId; + this._harnessTest.step(() => { + if (this._currentStep.timeout) { + this._currentStep.timeout(); + } + else { + this._harnessTest.step(() => { + assert_unreached("Timed out waiting for notification. (" + ResizeTestHelper.TIMEOUT + "ms)"); + }); + } + this._nextStep(); + }); + }, + + _done: function() { + this._observer.disconnect(); + delete this._observer; + this._harnessTest.done(); + if (this._rafCountRequest) { + window.cancelAnimationFrame(this._rafCountRequest); + delete this._rafCountRequest; + } + window.requestAnimationFrame(() => { this._resolvePromise(); }); + }, + + start: function(cleanup) { + this._harnessTest = async_test(this._name); + + if (cleanup) { + this._harnessTest.add_cleanup(cleanup); + } + + this._harnessTest.step(() => { + assert_equals(this._stepIdx, -1, "start can only be called once"); + this._nextStep(); + }); + return new Promise( (resolve, reject) => { + this._resolvePromise = resolve; + this._rejectPromise = reject; + }); + }, + + get rafCount() { + if (!this._rafCountRequest) + throw "rAF count is not active"; + return this._rafCount; + }, + + get test() { + if (!this._harnessTest) { + throw "_harnessTest is not initialized"; + } + return this._harnessTest; + }, + + _incrementRaf: function() { + if (this._rafCountRequest) { + this._rafCount++; + this._rafCountRequest = window.requestAnimationFrame(this._incrementRafBind); + } + }, + + startCountingRaf: function() { + if (this._rafCountRequest) + window.cancelAnimationFrame(this._rafCountRequest); + if (!this._incrementRafBind) + this._incrementRafBind = this._incrementRaf.bind(this); + this._rafCount = 0; + this._rafCountRequest = window.requestAnimationFrame(this._incrementRafBind); + } +} + +function createAndAppendElement(tagName, parent) { + if (!parent) { + parent = document.body; + } + const element = document.createElement(tagName); + parent.appendChild(element); + return element; +} diff --git a/testing/web-platform/tests/resize-observer/scrollbars-2.html b/testing/web-platform/tests/resize-observer/scrollbars-2.html new file mode 100644 index 0000000000..51b470c8a2 --- /dev/null +++ b/testing/web-platform/tests/resize-observer/scrollbars-2.html @@ -0,0 +1,36 @@ +<!DOCTYPE html> +<title>ResizeObserver content-box size and scrollbars</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1733042"> +<style> + #scrollContainer { + width: 100px; + height: 100px; + /* Should be bigger than any reasonable scrollbar */ + padding: 30px; + border: 10px solid blue; + overflow: scroll; + background: #818182; + } + +</style> +<div id="scrollContainer"></div> +<script> + promise_test(async function() { + let count = 0; + + const scrollContainer = document.getElementById('scrollContainer'); + // 20 to account for the border. + const scrollbarSize = scrollContainer.offsetWidth - scrollContainer.clientWidth - 20; + let size = await new Promise(resolve => { + const observer = new ResizeObserver(entries => { + resolve(entries[0].contentBoxSize[0]); + }); + observer.observe(scrollContainer); + }); + + assert_equals(size.inlineSize, 100 - scrollbarSize); + assert_equals(size.blockSize, 100 - scrollbarSize); + }); +</script> diff --git a/testing/web-platform/tests/resize-observer/scrollbars.html b/testing/web-platform/tests/resize-observer/scrollbars.html new file mode 100644 index 0000000000..129e74e5cd --- /dev/null +++ b/testing/web-platform/tests/resize-observer/scrollbars.html @@ -0,0 +1,56 @@ +<!DOCTYPE html> +<title>ResizeObserver content-box size and scrollbars</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1733042"> +<style> + #outer { + position: relative; + width: 100px; + height: 200px; + overflow: auto; + background: #818182; + } + + #inner { + position: absolute; + top: 0; + left: 0; + width: 10px; + height: 10px; + background: #0a6fc0; + } +</style> +<div id="outer"> + <div id="inner"></div> +</div> +<script> + async function animationFrame() { + return new Promise(r => requestAnimationFrame(r)); + } + + // This test is expected to fail with overlay scrollbars. + promise_test(async function() { + let count = 0; + + const outer = document.getElementById('outer'); + const inner = document.getElementById('inner'); + const observer = new ResizeObserver(entries => { + count++; + }); + + observer.observe(outer); + + inner.style.top = '1000px'; + + await animationFrame(); + await animationFrame(); + + inner.style.top = 0; + + await animationFrame(); + await animationFrame(); + + assert_equals(count, 2, "ResizeObserver should subtract scrollbar sizes from content-box rect"); + }); +</script> diff --git a/testing/web-platform/tests/resize-observer/svg-with-css-box-001.html b/testing/web-platform/tests/resize-observer/svg-with-css-box-001.html new file mode 100644 index 0000000000..4ec0a7de72 --- /dev/null +++ b/testing/web-platform/tests/resize-observer/svg-with-css-box-001.html @@ -0,0 +1,91 @@ +<!doctype html> +<title>ResizeObserver for SVG elements with CSS box.</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="./resources/resizeTestHelper.js"></script> +<div id="container" style="width: 500px; height: 500px;"> +<svg id="svg" width="100%" viewBox="0 0 100 100"> + <circle cx="50" cy="50" r="45" style="fill:orange;stroke:black;stroke-width:1" /> + <foreignObject id="foreign" x="0" y="0" width="100" height="100"> + <svg xmlns="http://www.w3.org/2000/svg" + width="100%" height="100%" + viewBox="0 0 100 100" + id="foreign-svg"> + <circle cx="50" cy="50" r="45" style="fill:orange;stroke:black;stroke-width:1" /> + </svg> + </foreignObject> +</svg> +<script> +'use strict'; + +function test0() { + let targetWidth = 150; + let target = document.getElementById('foreign-svg'); + let container = document.getElementById('foreign'); + let helper = new ResizeTestHelper( + "test0: observe `foreignObject` SVG in HTML document", + [ + { + setup: observer => { + observer.observe(target); + }, + notify: (entries, observer) => { + return true; // Delay next step + } + }, + { + setup: observer => { + target.setAttribute('width', targetWidth); + }, + notify: entries => { + assert_equals(entries.length, 1); + const entry = entries[0]; + assert_equals(entry.target, target); + assert_equals(entry.contentBoxSize[0].inlineSize, targetWidth); + }, + } + ]); + return helper.start(); +} + +function test1() { + let targetWidth = 400; + let target = document.getElementById('svg'); + let container = document.getElementById('container'); + let helper = new ResizeTestHelper( + "test1: observe inline SVG in HTML", + [ + { + setup: observer => { + observer.observe(target); + }, + notify: (entries, observer) => { + return true; // Delay next step + } + }, + { + setup: observer => { + target.style.width = targetWidth + 'px'; + }, + notify: (entries, observer) => { + assert_equals(entries.length, 1); + const entry = entries[0]; + assert_equals(entry.target, target); + assert_equals(entry.contentBoxSize[0].inlineSize, targetWidth); + } + } + ]); + return helper.start(); +} + +let guard; +test(_ => { + assert_implements(window.ResizeObserver); + guard = async_test('guard'); +}, "ResizeObserver implemented") + +test0() + .then(() => { test1(); }) + .then(() => { guard.done(); }); + +</script> diff --git a/testing/web-platform/tests/resize-observer/svg-with-css-box-002.svg b/testing/web-platform/tests/resize-observer/svg-with-css-box-002.svg new file mode 100644 index 0000000000..3c009641db --- /dev/null +++ b/testing/web-platform/tests/resize-observer/svg-with-css-box-002.svg @@ -0,0 +1,94 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg xmlns="http://www.w3.org/2000/svg" + xmlns:h="http://www.w3.org/1999/xhtml" + version="1.1" + width="100%" height="100%" + id="root"> +<title>ResizeObserver for SVG elements with CSS box.</title> +<h:script src="/resources/testharness.js"/> +<h:script src="/resources/testharnessreport.js"/> +<h:script src="./resources/resizeTestHelper.js"/> + +<foreignObject x="0" y="0" width="100" height="100"> + <svg xmlns="http://www.w3.org/2000/svg" + width="100" height="100" + viewBox="0 0 100 100" + id="foreign-svg"> + <circle cx="50" cy="50" r="45" style="fill:orange;stroke:black;stroke-width:1" /> + </svg> +</foreignObject> + +<script> +'use strict'; + +function test0() { + let targetWidth = 400; + let target = document.getElementById('root'); + let helper = new ResizeTestHelper( + "test0: Root SVG resize observed", + [ + { + setup: observer => { + observer.observe(target); + }, + notify: (entries, observer) => { + return true; // Delay next step + } + }, + { + setup: observer => { + target.setAttribute('width', targetWidth); + }, + notify: (entries, observer) => { + assert_equals(entries.length, 1); + const entry = entries[0]; + assert_equals(entry.target, target); + assert_equals(entry.contentBoxSize[0].inlineSize, targetWidth); + } + } + ]); + return helper.start(() => { + target.setAttribute('width', '100%'); + }); +} + +function test1() { + let targetWidth = 90; + let target = document.getElementById('foreign-svg'); + let helper = new ResizeTestHelper( + "test1: `foreignObject` SVG resize observed", + [ + { + setup: observer => { + observer.observe(target); + }, + notify: (entries, observer) => { + return true; // Delay next step + } + }, + { + setup: observer => { + target.setAttribute('width', targetWidth); + }, + notify: (entries, observer) => { + assert_equals(entries.length, 1); + const entry = entries[0]; + assert_equals(entry.target, target); + assert_equals(entry.contentBoxSize[0].inlineSize, targetWidth); + } + } + ]); + return helper.start(); +} + +let guard; +test(_ => { + assert_implements(window.ResizeObserver); + guard = async_test('guard'); +}, "ResizeObserver implemented") + +test0() + .then(() => { test1(); }) + .then(() => { guard.done(); }); +</script> +</svg> diff --git a/testing/web-platform/tests/resize-observer/svg.html b/testing/web-platform/tests/resize-observer/svg.html new file mode 100644 index 0000000000..6511afc8b0 --- /dev/null +++ b/testing/web-platform/tests/resize-observer/svg.html @@ -0,0 +1,619 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="./resources/resizeTestHelper.js"></script> +<p>ResizeObserver svg tests</p> +<svg height="430" width="500" > + <circle cx="10" cy="10" r="5" style="fill:orange;stroke:black;stroke-width:1" /> + <ellipse cx="10" cy="30" rx="5" ry="5" style="fill:orange;stroke:black;stroke-width:1"/> + <foreignObject cy="50" width="100" height="20"> + <body> + <p>Here is a paragraph that requires word wrap</p> + </body> + </foreignObject> + <image xlink:href="" x="0" y="100" height="30" width="100" /> + <line x1="0" y1="50" x2="20" y2="70" stroke="black" stroke-width="2"/> + <path d="M 0 100 L 100 100 L 50 150 z" + style="fill:orange;stroke:black;stroke-width:1" /> + <polygon points="0,200 100,200 50,250" style="fill:orange;stroke:black;stroke-width:1" /> + <polyline points="0,300 100,300 50,350" style="fill:orange;stroke:black;stroke-width:1"/> + <rect x="0" y="380" width="10" height="10" style="fill:orange; stroke:black; stroke-width:1" /> + <text x="0" y="400" font-size="20" font-family="Ahem">svg text tag</text> + <g fill="white" stroke="green" stroke-width="5"> + <rect x="0" y="380" width="50" height="20" id="g_rect" /> + </g> +</svg> +<script> +'use strict'; + +setup({allow_uncaught_exception: true}); + +function test0() { + let target = document.querySelector('circle'); + let helper = new ResizeTestHelper( + "test0: observe svg:circle", + [ + { + setup: observer => { + observer.observe(target); + }, + notify: (entries, observer) => { + return true; // Delay next step + } + }, + { + setup: observer => { + target.setAttribute('r', 10); + }, + notify: (entries, observer) => { + assert_equals(entries.length, 1); + } + } + ]); + return helper.start(); +} + +function test1() { + let target = document.querySelector('ellipse'); + let helper = new ResizeTestHelper( + "test1: observe svg:ellipse", + [ + { + setup: observer => { + observer.observe(target); + }, + notify: (entries, observer) => { + return true; // Delay next step + } + }, + { + setup: observer => { + target.setAttribute('rx', 10); + }, + notify: (entries, observer) => { + assert_equals(entries.length, 1); + assert_equals(entries[0].contentRect.width, 20); + assert_equals(entries[0].contentRect.height, 10); + } + } + ]); + return helper.start(); +} + +function test2() { + let target = document.querySelector('foreignObject'); + let helper = new ResizeTestHelper( + "test2: observe svg:foreignObject", + [ + { + setup: observer => { + observer.observe(target); + }, + notify: (entries, observer) => { + return true; // Delay next step + } + }, + { + setup: observer => { + target.setAttribute('width', 200); + }, + notify: (entries, observer) => { + assert_equals(entries.length, 1); + assert_equals(entries[0].contentRect.width, 200); + assert_equals(entries[0].contentRect.height, 20); + } + } + ]); + return helper.start(); +} + +function test3() { + let target = document.querySelector('image'); + let helper = new ResizeTestHelper( + "test3: observe svg:image", + [ + { + setup: observer => { + observer.observe(target); + }, + notify: (entries, observer) => { + return true; // Delay next step + } + }, + { + setup: observer => { + target.setAttribute('height', 40); + }, + notify: (entries, observer) => { + assert_equals(entries.length, 1); + assert_equals(entries[0].contentRect.width, 100); + assert_equals(entries[0].contentRect.height, 40); + } + } + ]); + return helper.start(); +} + +function test4() { + let target = document.querySelector('line'); + let helper = new ResizeTestHelper( + "test4: observe svg:line", + [ + { + setup: observer => { + observer.observe(target); + }, + notify: (entries, observer) => { + return true; // Delay next step + } + }, + { + setup: observer => { + target.setAttribute('y2', 80); + }, + notify: (entries, observer) => { + assert_equals(entries.length, 1); + assert_equals(entries[0].contentRect.width, 20); + assert_equals(entries[0].contentRect.height, 30); + } + } + ]); + return helper.start(); +} + +function test5() { + let target = document.querySelector('path'); + let helper = new ResizeTestHelper( + "test5: observe svg:path", + [ + { + setup: observer => { + observer.observe(target); + }, + notify: (entries, observer) => { + return true; // Delay next step + } + }, + { + setup: observer => { + target.setAttribute('d', "M 0 100 L 100 100 L 50 160 z"); + }, + notify: (entries, observer) => { + assert_equals(entries.length, 1); + assert_equals(entries[0].contentRect.width, 100); + assert_equals(entries[0].contentRect.height, 60); + } + } + ]); + return helper.start(); +} + +function test6() { + let target = document.querySelector('polygon'); + let helper = new ResizeTestHelper( + "test6: observe svg:polygon", + [ + { + setup: observer => { + observer.observe(target); + }, + notify: (entries, observer) => { + return true; // Delay next step + } + }, + { + setup: observer => { + target.setAttribute('points', "0,200 100,200 50,260"); + }, + notify: (entries, observer) => { + assert_equals(entries.length, 1); + assert_equals(entries[0].contentRect.width, 100); + assert_equals(entries[0].contentRect.height, 60); + } + } + ]); + return helper.start(); +} + +function test7() { + let target = document.querySelector('polyline'); + let helper = new ResizeTestHelper( + "test7: observe svg:polyline", + [ + { + setup: observer => { + observer.observe(target); + }, + notify: (entries, observer) => { + return true; // Delay next step + } + }, + { + setup: observer => { + target.setAttribute('points', "0,300 100,300 50,360"); + }, + notify: (entries, observer) => { + assert_equals(entries.length, 1); + assert_equals(entries[0].contentRect.width, 100); + assert_equals(entries[0].contentRect.height, 60); + } + } + ]); + return helper.start(); +} + +function test8() { + let target = document.querySelector('rect'); + let helper = new ResizeTestHelper( + "test8: observe svg:rect", + [ + { + setup: observer => { + observer.observe(target); + }, + notify: (entries, observer) => { + return true; // Delay next step + } + }, + { + setup: observer => { + target.setAttribute('width', "20"); + }, + notify: (entries, observer) => { + assert_equals(entries.length, 1); + assert_equals(entries[0].contentRect.width, 20); + assert_equals(entries[0].contentRect.height, 10); + } + } + ]); + return helper.start(); +} + +function test9() { + let target = document.querySelector('text'); + let helper = new ResizeTestHelper( + "test9: observe svg:text", + [ + { + setup: observer => { + observer.observe(target); + }, + notify: (entries, observer) => { + return true; // Delay next step + } + }, + { + setup: observer => { + target.setAttribute('font-size', "25"); + }, + notify: (entries, observer) => { + assert_equals(entries.length, 1); + } + } + ]); + return helper.start(); +} + + +function test10() { + let target = document.querySelector('svg'); + let helper = new ResizeTestHelper( + "test10: observe svg:svg, top/left is 0 even with padding", + [ + { + setup: observer => { + observer.observe(target); + }, + notify: (entries, observer) => { + assert_equals(entries.length, 1); + assert_equals(entries[0].contentRect.top, 0); + assert_equals(entries[0].contentRect.left, 0); + } + } + ]); + return helper.start(); +} + +function test11() { + // <svg> + // <view></view> + // <defs> + // <linearGradient> + // <stop></stop> + // </linearGradient> + // </defs> + // </svg> + const svgNS = "http://www.w3.org/2000/svg"; + let svg = document.createElementNS(svgNS, 'svg'); + document.body.appendChild(svg); + + let view = document.createElementNS(svgNS, 'view'); + svg.appendChild(view); + + let defs = document.createElementNS(svgNS, 'defs'); + let linearGradient = document.createElementNS(svgNS, 'linearGradient'); + let stop = document.createElementNS(svgNS, 'stop'); + linearGradient.appendChild(stop); + defs.appendChild(linearGradient); + svg.appendChild(defs); + + let helper = new ResizeTestHelper( + "test11: observe svg non-displayable element", + [ + { + setup: observer => { + observer.observe(view); + }, + notify: (entries, observer) => { + assert_equals(entries.length, 1); + assert_equals(entries[0].target, view); + assert_equals(entries[0].contentRect.width, 0); + assert_equals(entries[0].contentRect.height, 0); + } + }, + { + setup: observer => { + observer.observe(stop); + }, + notify: (entries, observer) => { + assert_equals(entries.length, 1); + assert_equals(entries[0].target, stop); + assert_equals(entries[0].contentRect.width, 0); + assert_equals(entries[0].contentRect.height, 0); + } + }, + ]); + return helper.start(() => svg.remove()); +} + +function test12() { + let target = document.querySelector('rect'); + let helper = new ResizeTestHelper( + "test12: observe svg:rect content box", + [ + { + setup: observer => { + observer.observe(target); + }, + notify: (entries, observer) => { + return true; // Delay next step + } + }, + { + setup: observer => { + target.setAttribute('width', 45); + }, + notify: (entries, observer) => { + assert_equals(entries.length, 1); + assert_equals(entries[0].contentRect.width, 45); + assert_equals(entries[0].contentRect.height, 10); + assert_equals(entries[0].contentBoxSize[0].inlineSize, 45); + assert_equals(entries[0].contentBoxSize[0].blockSize, 10); + } + } + ]); + return helper.start(); +} + +function test13() { + let target = document.querySelector('rect'); + let helper = new ResizeTestHelper( + "test13: observe svg:rect border box", + [ + { + setup: observer => { + observer.observe(target); + }, + notify: (entries, observer) => { + return true; // Delay next step + } + }, + { + setup: observer => { + target.setAttribute('width', 20); + target.setAttribute('height', 20); + }, + notify: (entries, observer) => { + assert_equals(entries.length, 1); + assert_equals(entries[0].contentRect.width, 20); + assert_equals(entries[0].contentRect.height, 20); + assert_equals(entries[0].contentBoxSize[0].inlineSize, 20); + assert_equals(entries[0].contentBoxSize[0].blockSize, 20); + assert_equals(entries[0].borderBoxSize[0].inlineSize, 20); + assert_equals(entries[0].borderBoxSize[0].blockSize, 20); + } + } + ]); + return helper.start(); +} + +function test14() { + let target = document.querySelector('#g_rect'); + let helper = new ResizeTestHelper( + "test14: observe g:rect content and border box", + [ + { + setup: observer => { + observer.observe(target); + }, + notify: (entries, observer) => { + assert_equals(entries.length, 1); + assert_equals(entries[0].contentRect.width, 50); + assert_equals(entries[0].contentRect.height, 20); + assert_equals(entries[0].contentBoxSize[0].inlineSize, 50); + assert_equals(entries[0].contentBoxSize[0].blockSize, 20); + assert_equals(entries[0].borderBoxSize[0].inlineSize, 50); + assert_equals(entries[0].borderBoxSize[0].blockSize, 20); + return true; // Delay next step + } + }, + { + setup: observer => { + target.setAttribute('width', 15); + }, + notify: (entries, observer) => { + assert_equals(entries.length, 1); + assert_equals(entries[0].contentRect.width, 15); + assert_equals(entries[0].contentRect.height, 20); + assert_equals(entries[0].contentBoxSize[0].inlineSize, 15); + assert_equals(entries[0].contentBoxSize[0].blockSize, 20); + assert_equals(entries[0].borderBoxSize[0].inlineSize, 15); + assert_equals(entries[0].borderBoxSize[0].blockSize, 20); + } + } + ]); + return helper.start(); +} + +function test15() { + let target = document.querySelector('text'); + let helper = new ResizeTestHelper( + "test15: observe svg:text content and border box", + [ + { + setup: observer => { + observer.observe(target); + }, + notify: (entries, observer) => { + return true; // Delay next step + } + }, + { + setup: observer => { + target.setAttribute('font-size', "30"); + }, + notify: (entries, observer) => { + assert_equals(entries.length, 1); + assert_equals(entries[0].contentRect.width, 360); + assert_equals(entries[0].contentRect.height, 30); + assert_equals(entries[0].contentBoxSize[0].inlineSize, 360); + assert_equals(entries[0].contentBoxSize[0].blockSize, 30); + assert_equals(entries[0].borderBoxSize[0].inlineSize, 360); + assert_equals(entries[0].borderBoxSize[0].blockSize, 30); + } + } + ]); + return helper.start(); +} + +function test16() { + let target = document.querySelector('#g_rect'); + let helper = new ResizeTestHelper( + "test16: observe g:rect content, border and device-pixel-content boxes", + [ + { + setup: observer => { + observer.observe(target, {box: "device-pixel-content-box"}); + target.setAttribute('width', 50); + }, + notify: (entries, observer) => { + assert_equals(entries.length, 1); + assert_equals(entries[0].contentRect.width, 50); + assert_equals(entries[0].contentRect.height, 20); + assert_equals(entries[0].contentBoxSize[0].inlineSize, 50); + assert_equals(entries[0].contentBoxSize[0].blockSize, 20); + assert_equals(entries[0].borderBoxSize[0].inlineSize, 50); + assert_equals(entries[0].borderBoxSize[0].blockSize, 20); + assert_equals(entries[0].devicePixelContentBoxSize[0].inlineSize, 50); + assert_equals(entries[0].devicePixelContentBoxSize[0].blockSize, 20); + return true; // Delay next step + } + }, + { + setup: observer => { + target.setAttribute('height', 30); + }, + notify: (entries, observer) => { + assert_equals(entries.length, 1); + assert_equals(entries[0].contentRect.width, 50); + assert_equals(entries[0].contentRect.height, 30); + assert_equals(entries[0].contentBoxSize[0].inlineSize, 50); + assert_equals(entries[0].contentBoxSize[0].blockSize, 30); + assert_equals(entries[0].borderBoxSize[0].inlineSize, 50); + assert_equals(entries[0].borderBoxSize[0].blockSize, 30); + assert_equals(entries[0].devicePixelContentBoxSize[0].inlineSize, 50); + assert_equals(entries[0].devicePixelContentBoxSize[0].blockSize, 30); + } + } + ]); + return helper.start(); +} + +function test17() { + // zoom is not a standard css property, so we should check it first. If the + // browser doesn't support it, we skip this test. + if (!CSS.supports("zoom", "0.1")) { + return Promise.resolve(); + } + + let target = document.querySelector('#g_rect'); + let helper = new ResizeTestHelper( + "test17: observe g:rect content, border and device-pixel-content boxes with zoom", + [ + { + setup: observer => { + observer.observe(target, {box: "device-pixel-content-box"}); + target.setAttribute('width', 50); + target.setAttribute('height', 30); + document.body.style.zoom = 0.1; + }, + notify: (entries, observer) => { + assert_equals(entries.length, 1); + assert_equals(entries[0].contentRect.width, 50); + assert_equals(entries[0].contentRect.height, 30); + assert_equals(entries[0].contentBoxSize[0].inlineSize, 50); + assert_equals(entries[0].contentBoxSize[0].blockSize, 30); + assert_equals(entries[0].borderBoxSize[0].inlineSize, 50); + assert_equals(entries[0].borderBoxSize[0].blockSize, 30); + assert_equals(entries[0].devicePixelContentBoxSize[0].inlineSize, 5); + assert_equals(entries[0].devicePixelContentBoxSize[0].blockSize, 3); + return true; // Delay next step + } + }, + { + setup: observer => { + document.body.style.zoom = 10; + }, + notify: (entries, observer) => { + assert_equals(entries.length, 1); + assert_equals(entries[0].contentRect.width, 50); + assert_equals(entries[0].contentRect.height, 30); + assert_equals(entries[0].contentBoxSize[0].inlineSize, 50); + assert_equals(entries[0].contentBoxSize[0].blockSize, 30); + assert_equals(entries[0].borderBoxSize[0].inlineSize, 50); + assert_equals(entries[0].borderBoxSize[0].blockSize, 30); + assert_equals(entries[0].devicePixelContentBoxSize[0].inlineSize, 500); + assert_equals(entries[0].devicePixelContentBoxSize[0].blockSize, 300); + }, + } + ]); + return helper.start(); +} + +let guard; +test(_ => { + assert_own_property(window, "ResizeObserver"); + guard = async_test('guard'); +}, "ResizeObserver implemented") + +test0() + .then(() => { return test1(); }) + .then(() => { return test2(); }) + .then(() => { return test3(); }) + .then(() => { return test4(); }) + .then(() => { return test5(); }) + .then(() => { return test6(); }) + .then(() => { return test7(); }) + .then(() => { return test8(); }) + .then(() => { return test9(); }) + .then(() => { return test10(); }) + .then(() => { return test11(); }) + .then(() => { return test12(); }) + .then(() => { return test13(); }) + .then(() => { return test14(); }) + .then(() => { return test15(); }) + .then(() => { return test16(); }) + .then(() => { return test17(); }) + .then(() => { guard.done(); }); + +</script> |