Bug 1272409 part 5: Add web-platform-tests for ResizeObserver. r?dholbert draft
authorFariskhi Vidyan <farislab@gmail.com>
Wed, 14 Sep 2016 16:53:26 -0700
changeset 413827 bc94498c3c232d45fcbbace6d8a02d11766a209b
parent 413826 4475b7bd5dbd72d9f905a955095060e73b02eecb
child 531312 fc13cab3fd395794f9e7802f4b1f21ea3271fe2f
push id29523
push userfarislab@gmail.com
push dateWed, 14 Sep 2016 23:58:19 +0000
reviewersdholbert
bugs1272409
milestone51.0a1
Bug 1272409 part 5: Add web-platform-tests for ResizeObserver. r?dholbert MozReview-Commit-ID: J2DA7LilCDB
testing/web-platform/meta/MANIFEST.json
testing/web-platform/tests/ResizeObserver/ResizeObserver-eventloop.html
testing/web-platform/tests/ResizeObserver/ResizeObserver-notify.html
testing/web-platform/tests/ResizeObserver/ResizeObserver-observe.html
testing/web-platform/tests/ResizeObserver/ResizeObserver-svg.html
testing/web-platform/tests/ResizeObserver/iframe.html
testing/web-platform/tests/ResizeObserver/imghelper.png
testing/web-platform/tests/ResizeObserver/resizeTestHelper.js
--- a/testing/web-platform/meta/MANIFEST.json
+++ b/testing/web-platform/meta/MANIFEST.json
@@ -12413,16 +12413,32 @@
         "path": "IndexedDB/value.htm",
         "url": "/IndexedDB/value.htm"
       },
       {
         "path": "IndexedDB/value_recursive.htm",
         "url": "/IndexedDB/value_recursive.htm"
       },
       {
+        "path": "ResizeObserver/ResizeObserver-eventloop.html",
+        "url": "/ResizeObserver/ResizeObserver-eventloop.html"
+      },
+      {
+        "path": "ResizeObserver/ResizeObserver-notify.html",
+        "url": "/ResizeObserver/ResizeObserver-notify.html"
+      },
+      {
+        "path": "ResizeObserver/ResizeObserver-observe.html",
+        "url": "/ResizeObserver/ResizeObserver-observe.html"
+      },
+      {
+        "path": "ResizeObserver/ResizeObserver-svg.html",
+        "url": "/ResizeObserver/ResizeObserver-svg.html"
+      },
+      {
         "path": "WebCryptoAPI/digest/digest.worker.js",
         "url": "/WebCryptoAPI/digest/digest.worker"
       },
       {
         "path": "WebCryptoAPI/encrypt_decrypt/aes_cbc.worker.js",
         "url": "/WebCryptoAPI/encrypt_decrypt/aes_cbc.worker"
       },
       {
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/ResizeObserver/ResizeObserver-eventloop.html
@@ -0,0 +1,256 @@
+<!doctype html>
+<!--
+Source:
+https://github.com/WICG/ResizeObserver/blob/master/test/eventloop.html
+-->
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resizeTestHelper.js"></script>
+<style>
+  div {
+    border: 1px dotted gray
+  }
+</style>
+<p>ResizeObserver notification event loop tests</p>
+<div id="target1" style="width:100px;height:100px;">t1
+</div>
+<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>
+<script>
+'use strict';
+
+let t1 = document.querySelector('#target1');
+
+// 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 divs = [t1];
+  let rAF = 0;
+  let helper = new ResizeTestHelper(
+    "test0: multiple notifications inside same event loop",
+  [
+    {
+      setup: observer => {
+        onErrorCalled = false;
+        let t2 = document.createElement('div');
+        let t3 = document.createElement('div');
+        t2.appendChild(t3);
+        t1.appendChild(t2);
+        divs.push(t2);
+        divs.push(t3);
+        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(onErrorCalled, true, "error was fired");
+        observer.disconnect();
+        while (t1.childNodes.length > 0)
+          t1.removeChild(t1.childNodes[0]);
+      }
+    }
+  ]);
+  return helper.start();
+}
+
+function test1() {
+
+  var resizers = [t1];
+  // 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;
+        let t2 = document.createElement('div');
+        t1.appendChild(t2);
+        resizers.push(t2);
+        let t3 = document.createElement('div');
+        resizers.push(t3);
+        t2.appendChild(t3);
+        let shadow = t3.createShadowRoot();
+        let t4 = document.createElement('div');
+        resizers.push(t4);
+        shadow.appendChild(t4);
+        let t5 = document.createElement('div');
+        resizers.push(t5);
+        t4.appendChild(t5);
+        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();
+        t1.removeChild(t1.firstChild);
+      }
+    },
+  ]);
+  return helper.start();
+}
+
+function test2() {
+  let container = document.querySelector('#container');
+  let a1 = document.querySelector('#a1');
+  let a2 = document.querySelector('#a2');
+  let b1 = document.querySelector('#b1');
+  let b2 = document.querySelector('#b2');
+  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);
+        a1.appendChild(a2);
+      }
+    },
+  ]);
+  return helper.start();
+}
+
+let guard = async_test('guard');
+test0()
+  .then(() => { return test1(); })
+  .then(() => { return test2(); })
+  .then(() => { guard.done(); });
+
+</script>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/ResizeObserver/ResizeObserver-notify.html
@@ -0,0 +1,362 @@
+<!doctype html>
+<!--
+Source:
+https://github.com/WICG/ResizeObserver/blob/master/test/notify.html
+-->
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="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);
+      },
+      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: observe non-replaced inline element",
+  [
+    {
+      setup: observer => {
+        observer.observe(inline);
+        observer.observe(t1);
+        t1.style.width = "66px";
+        inline.style.width = "66px";
+      },
+      notify: (entries, observer) => {
+        assert_equals(entries.length, 1,
+                      "inline elements must not trigger notifications");
+        assert_equals(entries[0].target, t1,
+                      "inline elements must not trigger notifications");
+        return true; // Delay next step
+      }
+    },
+    { // "inline element that becomes block should notify",
+      setup: observer => {
+        inline.style.display = "block";
+      },
+      notify: (entries, observer) => {
+        assert_equals(entries[0].target, inline);
+        return true; // Delay next step
+      }
+    },
+    { // "block element that becomes inline should notify",
+      setup: observer => {
+        inline.style.display = "inline";
+      },
+      notify: (entries, observer) => {
+        assert_equals(entries[0].target, inline);
+      }
+    }
+  ]);
+  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();
+}
+
+let guard = async_test('guard');
+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(() => { guard.done(); });
+
+</script>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/ResizeObserver/ResizeObserver-observe.html
@@ -0,0 +1,183 @@
+<!doctype html>
+<!--
+Source:
+https://github.com/WICG/ResizeObserver/blob/master/test/observe.html
+-->
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resizeTestHelper.js"></script>
+<p>ResizeObserver tests</p>
+<div id="target1" style="width:100px;height:100px;">t1</div>
+<div id="target2" style="width:100px;height:100px;">t2</div>
+<img id="target3" style="width:100px;height:100px;" src="imghelper.png">
+<iframe src="iframe.html" width="300px" height="100px" style="display:block">
+</iframe>
+<script>
+'use strict';
+
+let t1 = document.querySelector('#target1');
+let t2 = document.querySelector('#target2');
+
+// allow uncaught exception because ResizeObserver posts exceptions
+// to window error handler when limit is exceeded.
+setup({allow_uncaught_exception: true});
+
+function test0() {
+  let helper = new ResizeTestHelper(
+    "test0: simple observation",
+  [
+    {
+      setup: observer => {
+        observer.observe(t1);
+        t1.style.width = "5px";
+      },
+      notify: entries => {
+        assert_equals(entries.length, 1, "1 pending notification");
+        assert_equals(entries[0].target, t1, "target is t1");
+        assert_equals(entries[0].contentRect.width, 5, "target width");
+      }
+    }
+  ]);
+  return helper.start();
+}
+
+function test1() {
+  let helper = new ResizeTestHelper(
+    "test1: multiple observation on same element trigger only one",
+  [
+    {
+      setup: observer => {
+        observer.observe(t1);
+        observer.observe(t1);
+        t1.style.width = "10px";
+      },
+      notify: entries => {
+        assert_equals(entries.length, 1, "1 pending notification");
+      }
+    }
+  ]);
+  return helper.start();
+}
+
+function test2() {
+  test(() => {
+      assert_throws(null, _=> {
+        let ro = new ResizeObserver(() => {});
+        ro.observe({});
+      });
+    },
+    "test2: throw exception when observing non-element"
+  );
+  return Promise.resolve();
+}
+
+function test3() {
+  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();
+}
+
+function test4() {
+  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();
+}
+
+function test5() {
+  let t3 = document.querySelector('#target3');
+  var helper = new ResizeTestHelper("test5: observe img",[
+    {
+      setup: observer => {
+        observer.observe(t3);
+      },
+      notify: entries => {
+        return true; // Delay next step
+      }
+    },
+    {
+      setup: observer => {
+        t3.style.width = "100.5px";
+      },
+      notify: entries => {
+        assert_equals(entries.length, 1);
+        assert_equals(entries[0].contentRect.width, 100.5);
+      }
+    }
+  ]);
+  return helper.start();
+}
+
+function test6() {
+  let resolvePromise;
+  let promise = new Promise((resolve) => {
+    resolvePromise = resolve;
+  });
+  let test = async_test('test6: iframe notifications');
+  let testRequested = false;
+  let iframe = document.querySelector('iframe');
+  window.addEventListener('message', event => {
+    switch(event.data) {
+    case 'readyToTest':
+      if (!testRequested) {
+        //iframe.contentWindow.postMessage('startTest', '*');
+        testRequested = true;
+        test.step(()=>{test.done()});
+        resolvePromise();
+      }
+    break;
+    case 'success':
+    case 'fail':
+      window.requestAnimationFrame(() => {
+        test.step( () => {
+          assert_equals(event.data, 'success');
+          test.done();
+          resolvePromise();
+        });
+      });
+    break;
+    }
+  }, false);
+  return promise;
+}
+
+let guard = async_test('guard');
+test0()
+  .then(() => { return test1(); })
+  .then(() => { return test2(); })
+  .then(() => { return test3(); })
+  .then(() => { return test4(); })
+  .then(() => { return test5(); })
+  .then(() => { return test6(); })
+  .then(() => { guard.done(); });
+
+</script>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/ResizeObserver/ResizeObserver-svg.html
@@ -0,0 +1,314 @@
+<!doctype html>
+<!--
+Source:
+https://github.com/WICG/ResizeObserver/blob/master/test/svg.html
+-->
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="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">svg text tag</text>
+</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:path",
+  [
+    {
+      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();
+}
+let guard = async_test('guard');
+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(() => { guard.done(); });
+
+</script>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/ResizeObserver/iframe.html
@@ -0,0 +1,39 @@
+<!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.
+function broadcastReady() {
+  if (!testStarted) {
+    window.parent.postMessage('readyToTest', '*');
+    window.requestAnimationFrame(broadcastReady);
+  }
+}
+broadcastReady();
+</script>
+<!-- https://github.com/WICG/ResizeObserver/blob/master/test/resources/iframe.html -->
\ No newline at end of file
new file mode 100644
index 0000000000000000000000000000000000000000..87d86edc17b6ce37f0cdfddbd03e58d60e196d6e
GIT binary patch
literal 150
zc%17D@N?(olHy`uVBq!ia0vp^j3CUx1SBVv2j2ryoCO|{#S9GG!XV7ZFl&wkP>{XE
z)7O>#0VlhVisFlsTppm1Y-UJAiF1B#Zfaf$0|+=5r6!i7rYMwWmSiZnd-?{X=%um)
m#d$nk978y+C;#~W-=2Z>AJe}(XCj}2G<drDxvX<aXaWGzX(f#S
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/ResizeObserver/resizeTestHelper.js
@@ -0,0 +1,153 @@
+/**
+  Source:
+  https://github.com/WICG/ResizeObserver/blob/master/test/resources/resizeTestHelper.js
+*/
+
+'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 fullfilled 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.
+      // If undefined, timeout is assert_unreached.
+    }
+  }
+*/
+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);
+}
+
+ResizeTestHelper.TIMEOUT = 100;
+
+ResizeTestHelper.prototype = {
+  get _currentStep() {
+    return this._steps[this._stepIdx];
+  },
+
+  _nextStep: function() {
+    if (++this._stepIdx == this._steps.length)
+      return this._done();
+    this._timeoutId = this._harnessTest.step_timeout(
+      this._timeoutBind, ResizeTestHelper.TIMEOUT);
+    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(() => {
+      let rafDelay = this._currentStep.notify(entries, this._observer);
+      if (rafDelay)
+        window.requestAnimationFrame(this._nextStepBind);
+      else
+        this._nextStep();
+    });
+  },
+
+  _handleTimeout: function() {
+    delete this._timeoutId;
+    this._harnessTest.step(() => {
+      if (this._currentStep.timeout) {
+        this._currentStep.timeout();
+      }
+      else {
+        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() {
+    this._harnessTest = async_test(this._name);
+    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;
+  },
+
+  _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);
+  }
+}