Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Test entry and incumbent settings object for promise jobs #21206

Merged
merged 8 commits into from Feb 12, 2020
Merged
@@ -0,0 +1,112 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>Entry settings object for promise jobs when the function realm is different from the test realm</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<!-- https://github.com/whatwg/html/pull/5212 -->
<!-- https://github.com/whatwg/html/issues/1426 -->

<!-- This is what would normally be considered the entry page. However, we use functions from the
resources/function/function.html realm. So window.open() should resolve relative to that realm
inside promise jobs. -->

<iframe src="resources/promise-job-entry-incumbent.html"></iframe>
<iframe src="resources/function/function.html" id="function-frame"></iframe>

<script>
setup({ explicit_done: true });

const relativeURL = "resources/window-to-open.html";
const expectedURL = (new URL(relativeURL, document.querySelector("#function-frame").src)).href;

const incumbentWindow = frames[0];
const functionWindow = frames[1];
const FunctionFromAnotherWindow = frames[1].Function;

window.onload = () => {
async_test(t => {
const func = FunctionFromAnotherWindow(`
const [incumbentWindow, relativeURL, t, assert_equals, expectedURL] = arguments[0];

const w = incumbentWindow.runWindowOpenVeryIndirectly(relativeURL);
w.onload = t.step_func_done(() => {
t.add_cleanup(() => w.close());
assert_equals(w.location.href, expectedURL);
});
`);

Promise.resolve([incumbentWindow, relativeURL, t, assert_equals, expectedURL]).then(func);
}, "Fulfillment handler on fulfilled promise");

async_test(t => {
const func = FunctionFromAnotherWindow(`
const [incumbentWindow, relativeURL, t, assert_equals, expectedURL] = arguments[0];

const w = incumbentWindow.runWindowOpenVeryIndirectly(relativeURL);
w.onload = t.step_func_done(() => {
t.add_cleanup(() => w.close());
assert_equals(w.location.href, expectedURL);
});
`);

Promise.reject([incumbentWindow, relativeURL, t, assert_equals, expectedURL]).catch(func);
}, "Rejection handler on rejected promise");

async_test(t => {
let resolve;
const p = new Promise(r => { resolve = r; });

const func = FunctionFromAnotherWindow(`
const [incumbentWindow, relativeURL, t, assert_equals, expectedURL] = arguments[0];

const w = incumbentWindow.runWindowOpenVeryIndirectly(relativeURL);
w.onload = t.step_func_done(() => {
t.add_cleanup(() => w.close());
assert_equals(w.location.href, expectedURL);
});
`);

p.then(func);
t.step_timeout(() => resolve([incumbentWindow, relativeURL, t, assert_equals, expectedURL]), 0);
}, "Fulfillment handler on pending-then-fulfilled promise");

async_test(t => {
let reject;
const p = new Promise((_, r) => { reject = r; });

const func = FunctionFromAnotherWindow(`
const [incumbentWindow, relativeURL, t, assert_equals, expectedURL] = arguments[0];

const w = incumbentWindow.runWindowOpenVeryIndirectly(relativeURL);
w.onload = t.step_func_done(() => {
t.add_cleanup(() => w.close());
assert_equals(w.location.href, expectedURL);
});
`);

p.catch(func);
t.step_timeout(() => reject([incumbentWindow, relativeURL, t, assert_equals, expectedURL]), 0);
}, "Rejection handler on pending-then-rejected promise");

async_test(t => {
t.add_cleanup(() => { delete frames[1].args; });
frames[1].args = [incumbentWindow, relativeURL, t, assert_equals, expectedURL];

const func = FunctionFromAnotherWindow(`
const [incumbentWindow, relativeURL, t, assert_equals, expectedURL] = window.args;

const w = incumbentWindow.runWindowOpenVeryIndirectly(relativeURL);
w.onload = t.step_func_done(() => {
t.add_cleanup(() => w.close());
assert_equals(w.location.href, expectedURL);
});
`);

const thenable = { then: func };

Promise.resolve(thenable);
}, "Thenable resolution");

done();
};
</script>
@@ -0,0 +1,101 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>Entry settings object for promise jobs</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<!-- https://github.com/whatwg/html/pull/5212 -->
<!-- https://github.com/whatwg/html/issues/1426 -->

<!-- This is the entry page, so window.open() should resolve relative to it, even inside promise jobs. -->

<iframe src="resources/promise-job-entry-incumbent.html"></iframe>

<script>
setup({ explicit_done: true });

const relativeURL = "resources/window-to-open.html";
const expectedURL = (new URL(relativeURL, location.href)).href;

const incumbentWindow = frames[0];

window.onload = () => {
async_test(t => {
const w = incumbentWindow.runWindowOpenVeryIndirectly(relativeURL);
w.onload = t.step_func_done(() => {
t.add_cleanup(() => w.close());
assert_equals(w.location.href, expectedURL);
});
}, "Sanity check: this all works as expected with no promises involved");

async_test(t => {
// No t.step_func because that could change the realms
Promise.resolve().then(() => {
const w = incumbentWindow.runWindowOpenVeryIndirectly(relativeURL);
w.onload = t.step_func_done(() => {
t.add_cleanup(() => w.close());
assert_equals(w.location.href, expectedURL);
});
});
}, "Fulfillment handler on fulfilled promise");

async_test(t => {
// No t.step_func because that could change the realms
Promise.reject().catch(() => {
const w = incumbentWindow.runWindowOpenVeryIndirectly(relativeURL);
w.onload = t.step_func_done(() => {
t.add_cleanup(() => w.close());
assert_equals(w.location.href, expectedURL);
});
});
}, "Rejection handler on rejected promise");

async_test(t => {
let resolve;
const p = new Promise(r => { resolve = r; });

// No t.step_func because that could change the realms
p.then(() => {
const w = incumbentWindow.runWindowOpenVeryIndirectly(relativeURL);
w.onload = t.step_func_done(() => {
t.add_cleanup(() => w.close());
assert_equals(w.location.href, expectedURL);
});
});

t.step_timeout(resolve, 0);
}, "Fulfillment handler on pending-then-fulfilled promise");

async_test(t => {
let reject;
const p = new Promise((_, r) => { reject = r; });

// No t.step_func because that could change the realms
p.catch(() => {
const w = incumbentWindow.runWindowOpenVeryIndirectly(relativeURL);
w.onload = t.step_func_done(() => {
t.add_cleanup(() => w.close());
assert_equals(w.location.href, expectedURL);
});
});

t.step_timeout(reject, 0);
}, "Rejection handler on pending-then-rejected promise");

async_test(t => {
const thenable = {
// No t.step_func because that could change the realms
then(f) {
const w = incumbentWindow.runWindowOpenVeryIndirectly(relativeURL);
w.onload = t.step_func_done(() => {
t.add_cleanup(() => w.close());
assert_equals(w.location.href, expectedURL);
});
}
};

Promise.resolve(thenable);
}, "Thenable resolution");

done();
};
</script>
domenic marked this conversation as resolved.
Show resolved Hide resolved
@@ -0,0 +1,164 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>Incumbent settings object for promise jobs</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>

<!-- This is the entry page. -->

<iframe src="resources/promise-job-incumbent-incumbent.html"></iframe>
<iframe src="resources/promise-job-incumbent-resolver.html"></iframe>

<script>
setup({ explicit_done: true });

// postMessage should pick the incumbent page as its .source value to set on the MessageEvent, even
// inside promise jobs.
const expectedURL = (new URL("resources/promise-job-incumbent-incumbent.html", location.href)).href;

let testId = 0;

window.onload = () => {
const relevantWindow = frames[0].document.querySelector("#r").contentWindow;
const runInResolver = frames[1].runWhatYouGiveMe;

function setupTest(t) {
++testId;
const thisTestId = testId;

relevantWindow.addEventListener("messagereceived", t.step_func(e => {
const [receivedTestId, receivedSourceURL] = e.detail;

if (receivedTestId !== thisTestId) {
return;
}

assert_equals(receivedSourceURL, expectedURL);
t.done();
}));

return thisTestId;
}

async_test(t => {
const thisTestId = setupTest(t);

frames[0].runWindowPostMessageVeryIndirectly(thisTestId, "*");
}, "Sanity check: this all works as expected with no promises involved");

async_test(t => {
const thisTestId = setupTest(t);

// No t.step_func because that could change the realms
Promise.resolve().then(() => {
frames[0].runWindowPostMessageVeryIndirectly(thisTestId, "*");
});
}, "Fulfillment handler on fulfilled promise");

async_test(t => {
const thisTestId = setupTest(t);

const p = Promise.resolve();
frames[0].runWindowPostMessageVeryIndirectlyWithNoUserCode(p, "then", thisTestId, "*");
}, "Fulfillment handler on fulfilled promise, using backup incumbent settings object stack");

async_test(t => {
const thisTestId = setupTest(t);

// No t.step_func because that could change the realms
Promise.reject().catch(() => {
frames[0].runWindowPostMessageVeryIndirectly(thisTestId, "*");
});
}, "Rejection handler on rejected promise");

async_test(t => {
const thisTestId = setupTest(t);

const p = Promise.reject();
frames[0].runWindowPostMessageVeryIndirectlyWithNoUserCode(p, "catch", thisTestId, "*");
}, "Rejection handler on rejected promise, using backup incumbent settings object stack");

// The following tests test that we derive the incumbent settings object at promise-job time from
// the incumbent realm at the time the handler was added, not at the time the resolve()/reject()
// was done. See https://github.com/whatwg/html/issues/5213 for the spec side of this issue.

async_test(t => {
const thisTestId = setupTest(t);

let resolve;
const p = new Promise(r => { resolve = r; });

// No t.step_func because that could change the realms
p.then(() => {
frames[0].runWindowPostMessageVeryIndirectly(thisTestId, "*");
});

t.step_timeout(() => {
runInResolver(resolve);
}, 0);
}, "Fulfillment handler on pending-then-fulfilled promise");

async_test(t => {
const thisTestId = setupTest(t);

let resolve;
const p = new Promise(r => { resolve = r; });

frames[0].runWindowPostMessageVeryIndirectlyWithNoUserCode(p, "then", thisTestId, "*");

t.step_timeout(() => {
runInResolver(resolve);
}, 0);
}, "Fulfillment handler on pending-then-fulfilled promise, using backup incumbent settings object stack");

async_test(t => {
const thisTestId = setupTest(t);

let reject;
const p = new Promise((_, r) => { reject = r; });

// No t.step_func because that could change the realms
p.catch(() => {
frames[0].runWindowPostMessageVeryIndirectly(thisTestId, "*");
});

t.step_timeout(() => {
runInResolver(reject);
}, 0);
}, "Rejection handler on pending-then-rejected promise");

async_test(t => {
const thisTestId = setupTest(t);

let reject;
const p = new Promise((_, r) => { reject = r; });

frames[0].runWindowPostMessageVeryIndirectlyWithNoUserCode(p, "catch", thisTestId, "*");

t.step_timeout(() => {
runInResolver(reject);
}, 0);
}, "Rejection handler on pending-then-rejected promise, using backup incumbent settings object stack");

async_test(t => {
const thisTestId = setupTest(t);

const thenable = {
// No t.step_func because that could change the realms
then(f) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Somewhere around here should we have a test where the then function of the thenable is effectively a scripted function from the incumbent global (perhaps runWindowPostMessageVeryIndirectly bound to some args)? Maybe it doesn't matter too much....

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I quite understand what this adds over the (2) suggested above (where the then function is from a different realm). Is using the incumbent global in particular important?

frames[0].runWindowPostMessageVeryIndirectly(thisTestId, "*");
}
};

Promise.resolve(thenable);
}, "Thenable resolution");

async_test(t => {
const thisTestId = setupTest(t);

frames[0].resolveThenableThatRunsWindowPostMessageVeryIndirectlyWithNoUserCode(testId, "*", []);
}, "Thenable resolution, using backup incumbent settings object stack");

done();
};
</script>
@@ -0,0 +1,5 @@
A couple notes about the files scattered in this `resources/` directory:

* The nested directory structure is necessary here so that relative URL resolution can be tested; we need different sub-paths for each document.

* The semi-duplicate `window-to-open.html`s scattered throughout are present because Firefox, at least, does not fire `Window` `load` events for 404s, so we want to ensure that no matter which global is used, `window`'s `load` event is hit and our tests can proceed.
@@ -0,0 +1,4 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>Current page used as a test helper</title>

@@ -0,0 +1,3 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>If the current settings object is used this page will be opened</title>
@@ -0,0 +1,3 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>Realm for a "then" function used as a test helper</title>