Bug 1646108 [wpt PR 24168] - Fix handling of remote frames and URLs in performance.measureMemory, a=testonly
authorUlan Degenbaev <ulan@chromium.org>
Mon, 22 Jun 2020 10:44:55 +0000
changeset 600938 88fa5bd17cde18067f9ef26b10636a333e119e51
parent 600937 406df6d65cc57594968a45a1fe5ec067979ae53f
child 600939 d66ae07454fffe60e879b1b66fd7afdf03f27eca
push id13310
push userffxbld-merge
push dateMon, 29 Jun 2020 14:50:06 +0000
treeherdermozilla-beta@15a59a0afa5c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstestonly
bugs1646108, 24168, 1093880, 1084999, 2246175, 780319
milestone79.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1646108 [wpt PR 24168] - Fix handling of remote frames and URLs in performance.measureMemory, a=testonly Automatic update from web-platform-tests Fix handling of remote frames and URLs in performance.measureMemory The existing implementation measures memory usage of the main JS agent and reports URLs of the JS realms in the agent. The algorithm for mapping a JS realm to its reported URL walks the frame tree upwards to find the top-most cross-origin frame. The algorithm incorrectly assumed that all frames the path are local frames since the realms are local. This does not hold in the ABA case, where the main origin A embeds an iframe from origin B that in turn embeds an iframe from origin A. In such a case, the main JS realm and the grandchild realm are in the same JS agent and their frames are local. However, the child frame B is a remote frame. This CL fixes the algorithm to work both with local and remote frames. The URL of a remote frame can no longer be fetched from its document. Instead, it is fetched from the src attribute of the owner iframe element. This aligns with the upcoming spec and fixes the leak of post-server-redirect URLs. The CL also removes LocalFrame::FirstUrlCrossOriginToParent that is no longer needed. Bug: 1093880,1084999 Change-Id: I7a57a17701448d0fe210a66c7bdb8c0229fa5149 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2246175 Commit-Queue: Ulan Degenbaev <ulan@chromium.org> Reviewed-by: Daniel Cheng <dcheng@chromium.org> Reviewed-by: Kentaro Hara <haraken@chromium.org> Cr-Commit-Position: refs/heads/master@{#780319} -- wpt-commits: 6221819cb0d18cee78ea5d011a8170b20a4fb5df wpt-pr: 24168
testing/web-platform/tests/lint.ignore
testing/web-platform/tests/measure-memory/detached.tentative.window.js
testing/web-platform/tests/measure-memory/iframe.cross-origin.tentative.window.js
testing/web-platform/tests/measure-memory/iframe.cross-site.tentative.window.js
testing/web-platform/tests/measure-memory/iframe.same-origin.tentative.window.js
testing/web-platform/tests/measure-memory/main-frame.tentative.window.js
testing/web-platform/tests/measure-memory/measure-memory-cross-origin-iframe.tentative.window.js
testing/web-platform/tests/measure-memory/measure-memory-cross-origin-redirecting-iframe.tentative.window.js
testing/web-platform/tests/measure-memory/measure-memory-same-origin-iframe.tentative.window.js
testing/web-platform/tests/measure-memory/measure-memory.tentative.window.js
testing/web-platform/tests/measure-memory/redirect.client.tentative.window.js
testing/web-platform/tests/measure-memory/redirect.server.tentative.window.js
testing/web-platform/tests/measure-memory/resources/child.sub.html
testing/web-platform/tests/measure-memory/resources/common.js
testing/web-platform/tests/measure-memory/resources/grandchild.sub.html
testing/web-platform/tests/measure-memory/resources/iframe.redirect.sub.html
testing/web-platform/tests/measure-memory/resources/iframe.secret.sub.html
testing/web-platform/tests/measure-memory/resources/iframe.sub.html
testing/web-platform/tests/measure-memory/resources/redirecting-child.sub.html
testing/web-platform/tests/measure-memory/resources/window.redirect.sub.html
testing/web-platform/tests/measure-memory/resources/window.secret.sub.html
testing/web-platform/tests/measure-memory/resources/window.sub.html
testing/web-platform/tests/measure-memory/window-open.cross-origin.tentative.window.js
testing/web-platform/tests/measure-memory/window-open.cross-site.tentative.window.js
testing/web-platform/tests/measure-memory/window-open.mix.tentative.window.js
testing/web-platform/tests/measure-memory/window-open.same-origin.tentative.window.js
--- a/testing/web-platform/tests/lint.ignore
+++ b/testing/web-platform/tests/lint.ignore
@@ -167,16 +167,17 @@ SET TIMEOUT: html/webappapis/dynamic-mar
 SET TIMEOUT: html/webappapis/dynamic-markup-insertion/opening-the-input-stream/tasks.window.js
 SET TIMEOUT: html/webappapis/scripting/event-loops/*
 SET TIMEOUT: html/webappapis/scripting/events/event-handler-processing-algorithm-error/*
 SET TIMEOUT: html/webappapis/scripting/processing-model-2/*
 SET TIMEOUT: IndexedDB/*
 SET TIMEOUT: infrastructure/*
 SET TIMEOUT: intersection-observer/resources/*
 SET TIMEOUT: intersection-observer/target-in-different-window.html
+SET TIMEOUT: measure-memory/*
 SET TIMEOUT: media-source/mediasource-util.js
 SET TIMEOUT: media-source/URL-createObjectURL-revoke.html
 SET TIMEOUT: mixed-content/generic/sanity-checker.js
 SET TIMEOUT: navigation-timing/*
 SET TIMEOUT: html/canvas/offscreen/the-offscreen-canvas/*
 SET TIMEOUT: html/canvas/offscreen/manual/the-offscreen-canvas/*
 SET TIMEOUT: old-tests/submission/Microsoft/history/history_000.htm
 SET TIMEOUT: paint-timing/resources/subframe-painting.html
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/measure-memory/detached.tentative.window.js
@@ -0,0 +1,108 @@
+// META: script=/common/get-host-info.sub.js
+// META: script=./resources/common.js
+// META: timeout=long
+'use strict';
+
+promise_test(async testCase => {
+  const {iframes, windows} = await build([
+    {
+      id: 'cross-site-1',
+      children: [
+        {
+          id: 'same-origin-2',
+        },
+        {
+          id: 'same-origin-11',
+          window_open: true,
+        },
+      ],
+    },
+    {
+      id: 'same-origin-3',
+      children: [
+        {
+          id: 'same-origin-4',
+        },
+        {
+          id: 'same-origin-12',
+          window_open: true,
+        },
+      ],
+    },
+    {
+      id: 'cross-origin-5',
+      children: [
+        {
+          id: 'same-origin-6',
+        },
+        {
+          id: 'same-origin-13',
+          window_open: true,
+        },
+      ],
+    },
+    {
+      id: 'same-origin-7',
+      window_open: true,
+      children: [
+        {
+          id: 'same-origin-8',
+        }
+      ],
+    },
+    {
+      id: 'cross-origin-9',
+      window_open: true,
+      children: [
+        {
+          id: 'same-origin-10',
+        }
+      ],
+    },
+  ]);
+  const allowed = [
+    window.location.href,
+    iframes['cross-site-1'].src,
+    iframes['same-origin-3'].src,
+    iframes['same-origin-4'].src,
+    iframes['cross-origin-5'].src,
+    windows['same-origin-7'].location.href,
+    windows['same-origin-12'].location.href,
+  ];
+  const keep = sameOriginContexts(frames).concat(sameOriginContexts(windows));
+  // Detach iframes:
+  // 1) By setting src attribute:
+  iframes['cross-site-1'].src =
+      iframes['cross-site-1'].src.replace('iframe.sub', 'iframe.secret.sub');
+  // 2) By setting location attribute:
+  let url = iframes['same-origin-3'].contentWindow.location.href;
+  url = url.replace('iframe.sub', 'iframe.secret.sub');
+  iframes['same-origin-3'].contentWindow.location.href = url;
+  // 3) By removing from the DOM tree:
+  iframes['cross-origin-5'].parentNode.removeChild(iframes['cross-origin-5']);
+
+  // Detach windows:
+  // 1) By setting document.location attribute:
+  url = windows['same-origin-7'].location.href;
+  url = url.replace('window.sub', 'window.secret.sub');
+  windows['same-origin-7'].location.href = url;
+  // 2) By closing the window:
+  windows['cross-origin-9'].close();
+
+  try {
+    const result = await performance.measureMemory();
+    checkMeasureMemory(result, {
+      allowed: allowed.concat([
+        iframes['cross-site-1'].src,
+      ]),
+      required: [
+        window.location.href,
+      ],
+    });
+  } catch (error) {
+    if (!(error instanceof DOMException)) {
+      throw error;
+    }
+    assert_equals(error.name, 'SecurityError');
+  }
+}, 'performance.measureMemory URLs within a cross-site iframe.');
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/measure-memory/iframe.cross-origin.tentative.window.js
@@ -0,0 +1,40 @@
+// META: script=/common/get-host-info.sub.js
+// META: script=./resources/common.js
+// META: timeout=long
+'use strict';
+
+promise_test(async testCase => {
+  const {iframes} = await build([
+    {
+      id: 'cross-origin-1',
+      children: [
+        {
+          id: 'same-origin-2',
+        },
+        {
+          id: 'cross-origin-3',
+        },
+        {
+          id: 'cross-site-4',
+        }
+      ],
+    },
+  ]);
+  try {
+    const result = await performance.measureMemory();
+    checkMeasureMemory(result, {
+      allowed: [
+        window.location.href,
+        iframes['cross-origin-1'].src,
+      ],
+      required: [
+        window.location.href,
+      ],
+    });
+  } catch (error) {
+    if (!(error instanceof DOMException)) {
+      throw error;
+    }
+    assert_equals(error.name, 'SecurityError');
+  }
+}, 'performance.measureMemory URLs within a cross-origin iframe.');
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/measure-memory/iframe.cross-site.tentative.window.js
@@ -0,0 +1,40 @@
+// META: script=/common/get-host-info.sub.js
+// META: script=./resources/common.js
+// META: timeout=long
+'use strict';
+
+promise_test(async testCase => {
+  const {iframes} = await build([
+    {
+      id: 'cross-site-1',
+      children: [
+        {
+          id: 'same-origin-2',
+        },
+        {
+          id: 'cross-origin-3',
+        },
+        {
+          id: 'cross-site-4',
+        }
+      ],
+    },
+  ]);
+  try {
+    const result = await performance.measureMemory();
+    checkMeasureMemory(result, {
+      allowed: [
+        window.location.href,
+        iframes['cross-site-1'].src,
+      ],
+      required: [
+        window.location.href,
+      ],
+    });
+  } catch (error) {
+    if (!(error instanceof DOMException)) {
+      throw error;
+    }
+    assert_equals(error.name, 'SecurityError');
+  }
+}, 'performance.measureMemory URLs within a cross-site iframe.');
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/measure-memory/iframe.same-origin.tentative.window.js
@@ -0,0 +1,37 @@
+// META: script=/common/get-host-info.sub.js
+// META: script=./resources/common.js
+// META: timeout=long
+'use strict';
+
+promise_test(async testCase => {
+  const {iframes} = await build([
+    {
+      id: 'same-origin-1',
+      children: [
+        {
+          id: 'same-origin-2',
+        }
+      ],
+    },
+  ]);
+  try {
+    const result = await performance.measureMemory();
+    checkMeasureMemory(result, {
+      allowed: [
+        window.location.href,
+        iframes['same-origin-1'].src,
+        iframes['same-origin-2'].src,
+      ],
+      required: [
+        window.location.href,
+        iframes['same-origin-1'].src,
+        iframes['same-origin-2'].src,
+      ]
+    });
+  } catch (error) {
+    if (!(error instanceof DOMException)) {
+      throw error;
+    }
+    assert_equals(error.name, 'SecurityError');
+  }
+}, 'Well-formed result of performance.measureMemory with same-origin iframes.');
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/measure-memory/main-frame.tentative.window.js
@@ -0,0 +1,20 @@
+// META: script=/common/get-host-info.sub.js
+// META: script=./resources/common.js
+// META: timeout=long
+'use strict';
+
+promise_test(async testCase => {
+ try {
+    const result = await performance.measureMemory();
+    checkMeasureMemory(result, {
+      allowed: [window.location.href],
+      required: [window.location.href],
+    });
+  } catch (error) {
+    if (!(error instanceof DOMException)) {
+      throw error;
+    }
+    assert_equals(error.name, 'SecurityError');
+  }
+}, 'Well-formed result of performance.measureMemory.');
+
deleted file mode 100644
--- a/testing/web-platform/tests/measure-memory/measure-memory-cross-origin-iframe.tentative.window.js
+++ /dev/null
@@ -1,30 +0,0 @@
-// META: script=/common/get-host-info.sub.js
-// META: script=./resources/common.js
-// META: timeout=long
-'use strict';
-
-promise_test(async testCase => {
-  const grandchildLoaded = new Promise(resolve => {
-    window.onmessage = function(message) {
-      if (message.data === 'grandchild-loaded') {
-        resolve(message);
-      }
-    }
-  });
-  const frame = document.createElement('iframe');
-  const child = getUrl(CROSS_ORIGIN, 'resources/child.sub.html');
-  frame.src = child;
-  document.body.append(frame);
-  await grandchildLoaded;
-  try {
-    let result = await performance.measureMemory();
-    checkMeasureMemory(result, {
-      allowed: [window.location.href, child]
-    });
-  } catch (error) {
-    if (!(error instanceof DOMException)) {
-      throw error;
-    }
-    assert_equals(error.name, 'SecurityError');
-  }
-}, 'Well-formed result of performance.measureMemory with cross-origin iframe.');
deleted file mode 100644
--- a/testing/web-platform/tests/measure-memory/measure-memory-cross-origin-redirecting-iframe.tentative.window.js
+++ /dev/null
@@ -1,30 +0,0 @@
-// META: script=/common/get-host-info.sub.js
-// META: script=./resources/common.js
-// META: timeout=long
-'use strict';
-
-promise_test(async testCase => {
-  const grandchildLoaded = new Promise(resolve => {
-    window.onmessage = function(message) {
-      if (message.data === 'grandchild-loaded') {
-        resolve(message);
-      }
-    }
-  });
-  const frame = document.createElement('iframe');
-  const redirecting_child = getUrl(CROSS_ORIGIN, 'resources/redirecting-child.sub.html');
-  frame.src = redirecting_child;
-  document.body.append(frame);
-  await grandchildLoaded;
-  try {
-    let result = await performance.measureMemory();
-    checkMeasureMemory(result, {
-      allowed: [window.location.href, redirecting_child]
-    });
-  } catch (error) {
-    if (!(error instanceof DOMException)) {
-      throw error;
-    }
-    assert_equals(error.name, 'SecurityError');
-  }
-}, 'Well-formed result of performance.measureMemory with cross-origin iframe.');
deleted file mode 100644
--- a/testing/web-platform/tests/measure-memory/measure-memory-same-origin-iframe.tentative.window.js
+++ /dev/null
@@ -1,31 +0,0 @@
-// META: script=/common/get-host-info.sub.js
-// META: script=./resources/common.js
-// META: timeout=long
-'use strict';
-
-promise_test(async testCase => {
-  const grandchildLoaded = new Promise(resolve => {
-    window.onmessage = function(message) {
-      if (message.data === 'grandchild-loaded') {
-        resolve(message);
-      }
-    }
-  });
-  const frame = document.createElement('iframe');
-  const child = getUrl(SAME_ORIGIN, 'resources/child.sub.html');
-  const grandchild = getUrl(SAME_ORIGIN, 'resources/grandchild.sub.html');
-  frame.src = child;
-  document.body.append(frame);
-  await grandchildLoaded;
-  try {
-    let result = await performance.measureMemory();
-    checkMeasureMemory(result, {
-      allowed: [window.location.href, child, grandchild],
-    });
-  } catch (error) {
-    if (!(error instanceof DOMException)) {
-      throw error;
-    }
-    assert_equals(error.name, 'SecurityError');
-  }
-}, 'Well-formed result of performance.measureMemory with same-origin iframe.');
deleted file mode 100644
--- a/testing/web-platform/tests/measure-memory/measure-memory.tentative.window.js
+++ /dev/null
@@ -1,17 +0,0 @@
-// META: script=/common/get-host-info.sub.js
-// META: script=./resources/common.js
-// META: timeout=long
-'use strict';
-
-promise_test(async testCase => {
-  try {
-    let result = await performance.measureMemory();
-    checkMeasureMemory(result, {allowed: [window.location.href]});
-  } catch (error) {
-    if (!(error instanceof DOMException)) {
-      throw error;
-    }
-    assert_equals(error.name, 'SecurityError');
-  }
-}, 'Well-formed result of performance.measureMemory.');
-
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/measure-memory/redirect.client.tentative.window.js
@@ -0,0 +1,58 @@
+// META: script=/common/get-host-info.sub.js
+// META: script=./resources/common.js
+// META: timeout=long
+'use strict';
+
+promise_test(async testCase => {
+  const {iframes, windows} = await build([
+    {
+      id: 'cross-origin-1',
+      redirect: 'client',
+      children: [
+        {
+          id: 'same-origin-2',
+        },
+        {
+          id: 'cross-origin-3',
+        },
+        {
+          id: 'cross-site-4',
+        }
+      ],
+    },
+    {
+      id: 'cross-origin-5',
+      redirect: 'client',
+      window_open: true,
+      children: [
+        {
+          id: 'same-origin-6',
+        },
+        {
+          id: 'cross-origin-7',
+        },
+        {
+          id: 'cross-site-8',
+        }
+      ],
+    },
+  ]);
+  const keep = sameOriginContexts(frames).concat(sameOriginContexts(windows));
+  try {
+    const result = await performance.measureMemory();
+    checkMeasureMemory(result, {
+      allowed: [
+        window.location.href,
+        iframes['cross-origin-1'].src,
+      ],
+      required: [
+        window.location.href,
+      ],
+    });
+  } catch (error) {
+    if (!(error instanceof DOMException)) {
+      throw error;
+    }
+    assert_equals(error.name, 'SecurityError');
+  }
+}, 'performance.measureMemory does not leak client redirected URL.');
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/measure-memory/redirect.server.tentative.window.js
@@ -0,0 +1,58 @@
+// META: script=/common/get-host-info.sub.js
+// META: script=./resources/common.js
+// META: timeout=long
+'use strict';
+
+promise_test(async testCase => {
+  const {iframes, windows} = await build([
+    {
+      id: 'cross-origin-1',
+      redirect: 'server',
+      children: [
+        {
+          id: 'same-origin-2',
+        },
+        {
+          id: 'cross-origin-3',
+        },
+        {
+          id: 'cross-site-4',
+        }
+      ],
+    },
+    {
+      id: 'cross-origin-5',
+      redirect: 'server',
+      window_open: true,
+      children: [
+        {
+          id: 'same-origin-6',
+        },
+        {
+          id: 'cross-origin-7',
+        },
+        {
+          id: 'cross-site-8',
+        }
+      ],
+    },
+  ]);
+  const keep = sameOriginContexts(frames).concat(sameOriginContexts(windows));
+  try {
+    const result = await performance.measureMemory();
+    checkMeasureMemory(result, {
+      allowed: [
+        window.location.href,
+        iframes['cross-origin-1'].src,
+      ],
+      required: [
+        window.location.href,
+      ],
+    });
+  } catch (error) {
+    if (!(error instanceof DOMException)) {
+      throw error;
+    }
+    assert_equals(error.name, 'SecurityError');
+  }
+}, 'performance.measureMemory does not leak server redirected URL.');
deleted file mode 100644
--- a/testing/web-platform/tests/measure-memory/resources/child.sub.html
+++ /dev/null
@@ -1,17 +0,0 @@
-<!doctype html>
-<meta charset=utf-8>
-<html>
-<script>
-window.onmessage = function (message) {
-  // Forward the message to the parent.
-  window.parent.postMessage(message.data, '*');
-}
-window.onload = function () {
-  window.parent.postMessage('grandchild-loaded', '*');
-}
-</script>
-<body>
-  Hello from child iframe.
-  <iframe src="grandchild.sub.html"></iframe>
-</body>
-</html>
--- a/testing/web-platform/tests/measure-memory/resources/common.js
+++ b/testing/web-platform/tests/measure-memory/resources/common.js
@@ -1,35 +1,182 @@
-const SAME_ORIGIN = {origin: get_host_info().HTTP_ORIGIN, name: "SAME_ORIGIN"};
-const CROSS_ORIGIN = {origin: get_host_info().HTTP_REMOTE_ORIGIN, name: "CROSS_ORIGIN"}
+const ORIGINS = {
+  'same-origin': get_host_info().HTTP_ORIGIN,
+  'cross-origin': get_host_info().HTTP_REMOTE_ORIGIN,
+  'cross-site': get_host_info().HTTP_NOTSAMESITE_ORIGIN,
+}
 
-function checkMeasureMemoryBreakdown(breakdown, options) {
-  let allowed = new Set(options.allowed);
+function checkMeasureMemoryBreakdown(breakdown, options, required) {
+  const allowed = new Set(options.allowed);
   assert_own_property(breakdown, 'bytes');
   assert_greater_than_equal(breakdown.bytes, 0);
   assert_own_property(breakdown, 'userAgentSpecificTypes');
-  for (let userAgentSpecificType of breakdown.userAgentSpecificTypes) {
+  for (const userAgentSpecificType of breakdown.userAgentSpecificTypes) {
     assert_equals(typeof userAgentSpecificType, 'string');
   }
   assert_own_property(breakdown, 'attribution');
-  for (let attribution of breakdown.attribution) {
+  for (const attribution of breakdown.attribution) {
     assert_equals(typeof attribution, 'string');
     assert_true(
         allowed.has(attribution),
         `${attribution} must be in ${JSON.stringify(options.allowed)}`);
+    if (required.has(attribution)) {
+      required.delete(attribution);
+    }
   }
 }
 
 function checkMeasureMemory(result, options) {
     assert_own_property(result, 'bytes');
     assert_own_property(result, 'breakdown');
+    const required = new Set(options.required);
     let bytes = 0;
     for (let breakdown of result.breakdown) {
-      checkMeasureMemoryBreakdown(breakdown, options);
+      checkMeasureMemoryBreakdown(breakdown, options, required);
       bytes += breakdown.bytes;
     }
     assert_equals(bytes, result.bytes);
+    assert_equals(required.size, 0, JSON.stringify(result.breakdown) +
+        ' does not include ' + JSON.stringify(required.values()));
+}
+
+function url(params) {
+  let origin = null;
+  for (const key of Object.keys(ORIGINS)) {
+    if (params.id.startsWith(key)) {
+      origin = ORIGINS[key];
+    }
+  }
+  const child = params.window_open ? 'window' : 'iframe';
+  let file = `measure-memory/resources/${child}.sub.html`;
+  if (params.redirect) {
+    file = `measure-memory/resources/${child}.redirect.sub.html`;
+  }
+  let url = `${origin}/${file}?id=${params.id}`;
+  if (params.redirect === 'server') {
+    url = `${origin}/common/redirect.py?location=${encodeURIComponent(url)}`;
+  }
+  return url;
+}
+
+// A simple multiplexor of messages based on iframe ids.
+let waitForMessage = (function () {
+  class Inbox {
+    constructor() {
+      this.queue = [];
+      this.resolve = null;
+    }
+    push(value) {
+      if (this.resolve) {
+        this.resolve(value);
+        this.resolve = null;
+      } else {
+        this.queue.push(value);
+      }
+    }
+    pop() {
+      let promise = new Promise(resolve => this.resolve = resolve);
+      if (this.queue.length > 0) {
+        this.resolve(this.queue.shift());
+        this.resolve = null;
+      }
+      return promise;
+    }
+  }
+  const inbox = {};
+
+  window.onmessage = function (message) {
+    const id = message.data.id;
+    const payload = message.data.payload;
+    inbox[id] = inbox[id] || new Inbox();
+    inbox[id].push(payload);
+  }
+  return function (id) {
+    inbox[id] = inbox[id] || new Inbox();
+    return inbox[id].pop();
+  }
+})();
+
+// Constructs iframes based on their descriptoin.
+async function build(children) {
+  window.accessible_children = {iframes: {}, windows: {}};
+  await Promise.all(children.map(buildChild));
+  const result = window.accessible_children;
+  delete window.accessible_children;
+  return result;
 }
 
-function getUrl(host, relativePath) {
-  const path = new URL(relativePath, window.location).pathname;
-  return `${host.origin}${path}`;
+async function buildChild(params) {
+  let child = null;
+  function target() {
+    return params.window_open ? child : child.contentWindow;
+  }
+  if (params.window_open) {
+    child = window.open(url(params));
+  } else {
+    child = document.createElement('iframe');
+    child.src = url(params);
+    child.id = params.id;
+    document.body.appendChild(child);
+  }
+  const ready = await waitForMessage(params.id);
+  target().postMessage({id: 'parent', payload: params.children}, '*');
+  const done = await waitForMessage(params.id);
+  let main = window;
+  while (true) {
+    if (main === main.parent) {
+      if (!main.opener) {
+        break;
+      } else {
+        main = main.opener;
+      }
+    } else {
+      main = main.parent;
+    }
+  }
+  try {
+    main.accessible_children;
+  } catch (e) {
+    // Cross-origin iframe that cannot access the main frame.
+    return;
+  }
+  if (params.window_open) {
+    main.accessible_children.windows[params.id] = child;
+  } else  {
+    main.accessible_children.iframes[params.id] = child;
+  }
+}
+
+function getId() {
+  const params = new URLSearchParams(document.location.search);
+  return params.get('id');
+}
+
+function getParent() {
+  if (window.parent == window && window.opener) {
+    return window.opener;
+  }
+  return window.parent;
+}
+
+// This function runs within an iframe.
+// It gets the children descriptions from the parent and constructs them.
+async function setupChild() {
+  const id = getId();
+  document.getElementById('title').textContent = id;
+  getParent().postMessage({id : id, payload: 'ready'}, '*');
+  const children = await waitForMessage('parent');
+  if (children) {
+    await build(children);
+  }
+  getParent().postMessage({id: id, payload: 'done'}, '*');
+}
+
+function sameOriginContexts(children) {
+  const result = [];
+  for (const [id, child] of Object.entries(children)) {
+    if (id.includes('same-origin')) {
+      result.push(child.contentWindow
+          ? child.contentWindow.performance : child.performance);
+    }
+  }
+  return result;
 }
\ No newline at end of file
deleted file mode 100644
--- a/testing/web-platform/tests/measure-memory/resources/grandchild.sub.html
+++ /dev/null
@@ -1,12 +0,0 @@
-<!doctype html>
-<meta charset=utf-8>
-<html>
-<script>
-window.onload = function () {
-  window.parent.postMessage('grandchild-loaded', '*');
-}
-</script>
-<body>
-  Hello from grandchild iframe.
-</body>
-</html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/measure-memory/resources/iframe.redirect.sub.html
@@ -0,0 +1,12 @@
+<!doctype html>
+<meta charset="utf-8">
+<html>
+<script>
+window.onload = function () {
+  document.location.href = document.location.href.replace('redirect', 'secret');
+}
+</script>
+<body>
+  Hello from the redirecting iframe: <span id="title"></span>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/measure-memory/resources/iframe.secret.sub.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<meta charset="utf-8">
+<html>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="./common.js"></script>
+<script>
+window.onload = function () {
+  setTimeout(setupChild, 0);
+}
+</script>
+<body>
+  Hello from the secrect iframe: <span id="title"></span>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/measure-memory/resources/iframe.sub.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<meta charset="utf-8">
+<html>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="./common.js"></script>
+<script>
+window.onload = function() {
+  setTimeout(setupChild, 0);
+}
+</script>
+<body>
+  Hello from the iframe: <span id="title"></span>
+</body>
+</html>
deleted file mode 100644
--- a/testing/web-platform/tests/measure-memory/resources/redirecting-child.sub.html
+++ /dev/null
@@ -1,12 +0,0 @@
-<!doctype html>
-<meta charset=utf-8>
-<html>
-<script>
-window.onload = function () {
-  document.location.href = document.location.href.replace('redirecting-child', 'child');
-}
-</script>
-<body>
-  Hello from child iframe.
-</body>
-</html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/measure-memory/resources/window.redirect.sub.html
@@ -0,0 +1,12 @@
+<!doctype html>
+<meta charset="utf-8">
+<html>
+<script>
+window.onload = function () {
+  document.location.href = document.location.href.replace('redirect', 'secret');
+}
+</script>
+<body>
+  Hello from the redirecting widnow: <span id="title"></span>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/measure-memory/resources/window.secret.sub.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<meta charset="utf-8">
+<html>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="./common.js"></script>
+<script>
+window.onload = function () {
+  setTimeout(setupChild, 0);
+}
+</script>
+<body>
+  Hello from the secrect window: <span id="title"></span>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/measure-memory/resources/window.sub.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<meta charset="utf-8">
+<html>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="./common.js"></script>
+<script>
+window.onload = function() {
+  setTimeout(setupChild, 0);
+}
+</script>
+<body>
+  Hello from the window: <span id="title"></span>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/measure-memory/window-open.cross-origin.tentative.window.js
@@ -0,0 +1,41 @@
+// META: script=/common/get-host-info.sub.js
+// META: script=./resources/common.js
+// META: timeout=long
+'use strict';
+
+promise_test(async testCase => {
+  const {windows, iframes} = await build([
+    {
+      id: 'cross-origin-1',
+      window_open: true,
+      children: [
+        {
+          id: 'same-origin-2',
+          window_open: true,
+        },
+        {
+          id: 'same-origin-3',
+        },
+        {
+          id: 'cross-origin-4',
+        },
+      ]
+    },
+  ]);
+  try {
+    const result = await performance.measureMemory();
+    checkMeasureMemory(result, {
+      allowed: [
+        window.location.href,
+      ],
+      required: [
+        window.location.href,
+      ],
+    });
+  } catch (error) {
+    if (!(error instanceof DOMException)) {
+      throw error;
+    }
+    assert_equals(error.name, 'SecurityError');
+  }
+}, 'performance.measureMemory does not leak URL of cross-origin window.open.');
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/measure-memory/window-open.cross-site.tentative.window.js
@@ -0,0 +1,41 @@
+// META: script=/common/get-host-info.sub.js
+// META: script=./resources/common.js
+// META: timeout=long
+'use strict';
+
+promise_test(async testCase => {
+  const {windows, iframes} = await build([
+    {
+      id: 'cross-site-1',
+      window_open: true,
+      children: [
+        {
+          id: 'same-origin-2',
+          window_open: true,
+        },
+        {
+          id: 'same-origin-3',
+        },
+        {
+          id: 'cross-origin-4',
+        },
+      ]
+    },
+  ]);
+  try {
+    const result = await performance.measureMemory();
+    checkMeasureMemory(result, {
+      allowed: [
+        window.location.href,
+      ],
+      required: [
+        window.location.href,
+      ],
+    });
+  } catch (error) {
+    if (!(error instanceof DOMException)) {
+      throw error;
+    }
+    assert_equals(error.name, 'SecurityError');
+  }
+}, 'performance.measureMemory does not leak URL of cross-site window.open.');
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/measure-memory/window-open.mix.tentative.window.js
@@ -0,0 +1,99 @@
+// META: script=/common/get-host-info.sub.js
+// META: script=./resources/common.js
+// META: timeout=long
+'use strict';
+
+promise_test(async testCase => {
+  const {windows, iframes} = await build([
+    {
+      id: 'same-origin-1',
+      children: [
+        {
+          id: 'same-origin-2',
+          window_open: true,
+          children: [
+            {
+              id: 'same-origin-3',
+              window_open: true,
+            },
+          ],
+        },
+        {
+          id: 'cross-origin-4',
+          children: [
+            {
+              id: 'same-origin-5',
+              window_open: true,
+            },
+          ],
+        },
+        {
+          id: 'cross-site-6',
+          children: [
+            {
+              id: 'same-origin-7',
+              window_open: true,
+            },
+          ],
+        },
+        {
+          id: 'same-origin-8',
+          children: [
+            {
+              id: 'cross-origin-9',
+              window_open: true,
+              children: [
+                {
+                  id: 'same-origin-10',
+                },
+                {
+                  id: 'same-origin-11',
+                  window_open: true,
+                },
+              ],
+            },
+            {
+              id: 'cross-site-12',
+              window_open: true,
+              children: [
+                {
+                  id: 'same-origin-13',
+                },
+                {
+                  id: 'same-origin-14',
+                  window_open: true,
+                },
+              ],
+            },
+          ],
+        },
+      ]
+    },
+  ]);
+  try {
+    const result = await performance.measureMemory();
+    checkMeasureMemory(result, {
+      allowed: [
+        window.location.href,
+        iframes['same-origin-1'].src,
+        windows['same-origin-2'].location.href,
+        windows['same-origin-3'].location.href,
+        iframes['cross-origin-4'].src,
+        iframes['cross-site-6'].src,
+        iframes['same-origin-8'].src,
+      ],
+      required: [
+        window.location.href,
+        iframes['same-origin-1'].src,
+        windows['same-origin-2'].location.href,
+        windows['same-origin-3'].location.href,
+        iframes['same-origin-8'].src,
+      ],
+    });
+  } catch (error) {
+    if (!(error instanceof DOMException)) {
+      throw error;
+    }
+    assert_equals(error.name, 'SecurityError');
+  }
+}, 'performance.measureMemory does not leak URLs in cross-origin iframes and windows.');
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/measure-memory/window-open.same-origin.tentative.window.js
@@ -0,0 +1,44 @@
+// META: script=/common/get-host-info.sub.js
+// META: script=./resources/common.js
+// META: timeout=long
+'use strict';
+
+promise_test(async testCase => {
+  const {windows, iframes} = await build([
+    {
+      id: 'same-origin-1',
+      window_open: true,
+      children: [
+        {
+          id: 'same-origin-2',
+          window_open: true,
+        },
+        {
+          id: 'same-origin-3',
+        },
+      ]
+    },
+  ]);
+  try {
+    const result = await performance.measureMemory();
+    checkMeasureMemory(result, {
+      allowed: [
+        window.location.href,
+        windows['same-origin-1'].location.href,
+        windows['same-origin-2'].location.href,
+        iframes['same-origin-3'].src,
+      ],
+      required: [
+        window.location.href,
+        windows['same-origin-1'].location.href,
+        windows['same-origin-2'].location.href,
+        iframes['same-origin-3'].src,
+      ],
+    });
+  } catch (error) {
+    if (!(error instanceof DOMException)) {
+      throw error;
+    }
+    assert_equals(error.name, 'SecurityError');
+  }
+}, 'Well-formed result of performance.measureMemory with same-origin window.open.');