const ORIGINS = {
'same-origin': get_host_info().HTTPS_ORIGIN,
'cross-origin': get_host_info().HTTPS_REMOTE_ORIGIN,
'cross-site': get_host_info().HTTPS_NOTSAMESITE_ORIGIN,
}
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}/measure-memory/resources/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();
}
})();
function getMainWindow() {
let main = window;
while (true) {
if (main === main.parent) {
if (!main.opener) {
break;
} else {
main = main.opener;
}
} else {
main = main.parent;
}
}
return main;
}
function isSameOrigin(other) {
try {
other.descendants;
} catch (e) {
// Cross-origin iframe that cannot access the main frame.
return false;
}
return !!other.descendants;
}
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;
}
// Constructs iframes based on their descriptoin.
async function build(children) {
window.descendants = {iframes: {}, windows: {}};
await Promise.all(children.map(buildChild));
const result = window.descendants;
return result;
}
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));
if (!params.id.startsWith('same-origin')) {
// Cross-origin windows gets their own browsing context groups with COOP.
// The postMessage calls before would not work for them, so we do not
// wait for them to load.
return;
}
} 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);
if (!params.window_open) {
const main = getMainWindow();
if (isSameOrigin(main)) {
main.descendants.iframes[params.id] = child;
}
}
}
// This function runs within an iframe.
// It gets the children descriptions from the parent and constructs them.
async function setupChild() {
const id = getId();
const main = getMainWindow();
if (isSameOrigin(main)) {
main.descendants.windows[id] = window;
}
document.getElementById('title').textContent = id;
getParent().postMessage({id : id, payload: 'ready'}, '*');
const children = await waitForMessage('parent');
if (children) {
await Promise.all(children.map(buildChild));
}
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;
}
async function createWorker(bytes) {
const worker = new Worker('resources/worker.js');
let resolve_promise;
const promise = new Promise(resolve => resolve_promise = resolve);
worker.onmessage = function (message) {
resolve_promise(message.data);
}
worker.postMessage({bytes});
return promise;
}