Skip to content

Commit 6ccc91b

Browse files
shaseleychromium-wpt-export-bot
authored andcommitted
Scheduling APIs: associate inherited signals with a SecutityOrigin
In rare cases, e.g. changing focus in scheduler.postTask() task causing blur in a cross origin frame, the scheduling state can be propagated to cross origin microtasks and used if scheduler.yield() is called. To avoid this, propagate the SecurityOrigin along with the scheduling state and ignore the inherited state if calling yield() would inherit cross origin state. Alternatively, we might consider scoping the signals to a specific ExecutionContext, but that is a bigger change, and it's more complicated because developers can cross that boundary when posting tasks/running script. Bug: 399478940 Change-Id: Ia6c2139138d3893d7e2930e5ca886775d3214384 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6306840 Reviewed-by: Kent Tamura <[email protected]> Reviewed-by: Nate Chapin <[email protected]> Commit-Queue: Scott Haseley <[email protected]> Cr-Commit-Position: refs/heads/main@{#1426380}
1 parent e205870 commit 6ccc91b

File tree

5 files changed

+138
-0
lines changed

5 files changed

+138
-0
lines changed
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<!doctype html>
2+
3+
<script>
4+
function handleFocus() {
5+
window.parent.postMessage({status: "focus"}, '*');
6+
}
7+
8+
async function handleBlur() {
9+
let didRun = false;
10+
scheduler.postTask(() => { didRun = true; });
11+
await scheduler.yield();
12+
window.parent.postMessage({status: "done", didRun}, '*');
13+
}
14+
</script>
15+
16+
<input placeholder='focus me' id=input onfocus="handleFocus()" onblur="handleBlur()">
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// Tests scheduler context propagation when a blur is caused by a task in one
2+
// context and observed in another, within the same task (depending on origin
3+
// and site isolation).
4+
function runFocusChangeTest(t, crossOrigin) {
5+
window.onload = () => {
6+
const iframe = document.createElement('iframe');
7+
let src = location.href.slice(0, location.href.lastIndexOf('/'))
8+
+ '/resources/focus-change-test-subframe.html';
9+
if (crossOrigin) {
10+
src = src.replace('://', '://www1.')
11+
}
12+
iframe.src = src;
13+
iframe.onload = () => {
14+
// TAB to focus the first input.
15+
test_driver.send_keys(document.body, "\ue004");
16+
// TAB again to focus the iframe's input.
17+
test_driver.send_keys(document.body, "\ue004");
18+
}
19+
document.body.appendChild(iframe);
20+
}
21+
22+
let count = 0;
23+
24+
window.onmessage = t.step_func((e) => {
25+
if (e.data.status === 'focus') {
26+
++count;
27+
// The scheduling state is set when running the scheduler.postTask() and
28+
// propagated to continuations descending from the callback.
29+
if (count == 1) {
30+
scheduler.postTask(() => { input.focus(); }, {priority: 'background'});
31+
} else {
32+
assert_equals(count, 2);
33+
scheduler.postTask(async () => {
34+
await Promise.resolve();
35+
input.focus();
36+
}, {priority: 'background'});
37+
}
38+
} else {
39+
assert_equals(e.data.status, 'done');
40+
// If the default priority task runs before the background priority
41+
// continuation, then the scheduling state was used for the continuation.
42+
const expectedToRun = !crossOrigin;
43+
assert_equals(expectedToRun, e.data.didRun);
44+
if (count == 1) {
45+
test_driver.send_keys(document.body, "\ue004");
46+
} else {
47+
assert_equals(count, 2);
48+
t.done();
49+
}
50+
}
51+
});
52+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<!doctype html>
2+
<script src="/resources/testharness.js"></script>
3+
<script src="/resources/testharnessreport.js"></script>
4+
<script src="/resources/testdriver.js"></script>
5+
<script src="/resources/testdriver-vendor.js"></script>
6+
<script src="resources/test-helper.js"></script>
7+
8+
<script>
9+
async_test(t => {
10+
runFocusChangeTest(t, /*crossOrigin=*/true);
11+
}, 'Test scheduler.yield() does not use propagated state in cross origin frames');
12+
</script>
13+
14+
<input id=input><br>
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<!doctype html>
2+
<script src="/resources/testharness.js"></script>
3+
<script src="/resources/testharnessreport.js"></script>
4+
<script src="/resources/testdriver.js"></script>
5+
<script src="/resources/testdriver-vendor.js"></script>
6+
<script src="resources/test-helper.js"></script>
7+
8+
<script>
9+
async_test(t => {
10+
runFocusChangeTest(t, /*crossOrigin=*/false);
11+
}, 'Test scheduler.yield() uses propagated state in same origin frames');
12+
</script>
13+
14+
<input id=input><br>
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<!doctype html>
2+
<script src="/resources/testharness.js"></script>
3+
<script src="/resources/testharnessreport.js"></script>
4+
5+
<script>
6+
promise_test(async t => {
7+
await new Promise(resolve => window.onload = resolve);
8+
const iframe = document.createElement('iframe');
9+
iframe.srcdoc = `
10+
<script>
11+
window.task = async function() {
12+
await scheduler.yield();
13+
return "inner";
14+
}
15+
</scr`+`ipt>
16+
`;
17+
const p = new Promise(resolve => iframe.onload = resolve);
18+
document.body.appendChild(iframe);
19+
await p;
20+
21+
let task1 = scheduler.postTask(
22+
iframe.contentWindow.task, {priority: 'user-blocking'});
23+
let task2 = scheduler.postTask(() => "outer", {priority: 'user-blocking'});
24+
let result = await Promise.all([task1, task2]);
25+
assert_equals(result.toString(), "inner,outer",
26+
"Expected inner before outer for iframe task with main frame scheduler");
27+
28+
task1 = iframe.contentWindow.scheduler.postTask(
29+
iframe.contentWindow.task, {priority: 'user-blocking'});
30+
task2 = scheduler.postTask(() => "outer", {priority: 'user-blocking'});
31+
result = await Promise.all([task1, task2]);
32+
assert_equals(result.toString(), "inner,outer",
33+
"Expected inner before outer for iframe task with iframe scheduler");
34+
35+
task1 = scheduler.postTask(
36+
async () => await iframe.contentWindow.task(), {priority: 'user-blocking'});
37+
task2 = scheduler.postTask(() => "outer", {priority: 'user-blocking'});
38+
result = await Promise.all([task1, task2]);
39+
assert_equals(result.toString(), "inner,outer",
40+
"Expected inner before outer for iframe task called from main frame task");
41+
}, 'Test scheduler.yield() uses propagated state in same origin frames');
42+
</script>

0 commit comments

Comments
 (0)