summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/resize-observer
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /testing/web-platform/tests/resize-observer
parentInitial commit. (diff)
downloadfirefox-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')
-rw-r--r--testing/web-platform/tests/resize-observer/META.yml4
-rw-r--r--testing/web-platform/tests/resize-observer/calculate-depth-for-node.html26
-rw-r--r--testing/web-platform/tests/resize-observer/callback-cross-realm-report-exception.html30
-rw-r--r--testing/web-platform/tests/resize-observer/change-layout-in-error.html33
-rw-r--r--testing/web-platform/tests/resize-observer/create-pattern-data-url.js22
-rw-r--r--testing/web-platform/tests/resize-observer/devicepixel-ref.html27
-rw-r--r--testing/web-platform/tests/resize-observer/devicepixel.html85
-rw-r--r--testing/web-platform/tests/resize-observer/devicepixel2-ref.html21
-rw-r--r--testing/web-platform/tests/resize-observer/devicepixel2.html131
-rw-r--r--testing/web-platform/tests/resize-observer/eventloop.html253
-rw-r--r--testing/web-platform/tests/resize-observer/fragments.html115
-rw-r--r--testing/web-platform/tests/resize-observer/idlharness.window.js37
-rw-r--r--testing/web-platform/tests/resize-observer/iframe-same-origin-ref.html7
-rw-r--r--testing/web-platform/tests/resize-observer/iframe-same-origin.html76
-rw-r--r--testing/web-platform/tests/resize-observer/multiple-observers-with-mutation-crash.html49
-rw-r--r--testing/web-platform/tests/resize-observer/notify.html408
-rw-r--r--testing/web-platform/tests/resize-observer/observe.html1007
-rw-r--r--testing/web-platform/tests/resize-observer/ordering.html24
-rw-r--r--testing/web-platform/tests/resize-observer/resources/iframe.html38
-rw-r--r--testing/web-platform/tests/resize-observer/resources/image.pngbin0 -> 170 bytes
-rw-r--r--testing/web-platform/tests/resize-observer/resources/resizeTestHelper.js195
-rw-r--r--testing/web-platform/tests/resize-observer/scrollbars-2.html36
-rw-r--r--testing/web-platform/tests/resize-observer/scrollbars.html56
-rw-r--r--testing/web-platform/tests/resize-observer/svg-with-css-box-001.html91
-rw-r--r--testing/web-platform/tests/resize-observer/svg-with-css-box-002.svg94
-rw-r--r--testing/web-platform/tests/resize-observer/svg.html619
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
new file mode 100644
index 0000000000..51741584a0
--- /dev/null
+++ b/testing/web-platform/tests/resize-observer/resources/image.png
Binary files differ
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>