1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172
|
<!DOCTYPE html>
<meta charset="utf-8"/>
<title>Web Locks API: Partitioned WebLocks</title>
<!-- Pull in get_host_info() -->
<script src="/common/get-host-info.sub.js"></script>
<script src="/common/utils.js"></script>
<script src="/common/dispatcher/dispatcher.js"></script>
<script src="resources/helpers.js"></script>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<body>
<script>
const { HTTPS_ORIGIN, HTTPS_NOTSAMESITE_ORIGIN } = get_host_info();
// Map of lock_id => function that releases a lock.
const held = new Map();
let next_lock_id = 1;
// How this test works:
// Step 1 (top-frame): request an exclusive web-lock and store its id
// and release for clean-up.
// Step 2 (top-frame): open a pop-up window and load a not-same-site
// ./web-locks/resources/partitioned-parent.html
// Step 3 (pop-up): load a same-site iframe inside the pop-up.
// Step 4 (pop-up): send a web-lock request to the same-site iframe.
// Step 5 (iframe): process the web-lock request and message the result
// back to the pop-up.
// Step 6 (pop-up): intercept the result message from the iframe and
// send it to the top-frame.
// Step 7 (top-frame): add cleanup hook.
// Step 8 (top-frame): ensure that the same-site iframe's web-lock
// request succeeds since it and the top-level site are successfully
// partitioned and each can hold an exclusive lock.
async function third_party_test(t) {
let target_url = HTTPS_ORIGIN + '/web-locks/resources/iframe.html';
target_url = new URL(
`/web-locks/resources/partitioned-parent.html?target=${encodeURIComponent(target_url)}`,
HTTPS_NOTSAMESITE_ORIGIN + self.location.pathname);
// Step 1.
let lock_id = next_lock_id++;
let [ promise, release ] = makePromiseAndResolveFunc();
let released = navigator.locks.request('testLock', {mode: 'exclusive', ifAvailable: true},
lock => {
if (lock === null) {
assert_true(false)
return;
}
return promise;
});
held.set(lock_id, { release, released });
// Step 2.
const w = window.open(target_url);
const result = await new Promise(resolve => window.onmessage = resolve);
// Step 7.
t.add_cleanup(() => {
w.close();
let released = [];
for(let i = 1; i < next_lock_id; i++){
let h = held.get(i);
h.release();
released.push(h.released);
}
return Promise.allSettled(released);
});
// Step 8.
// When 3rd party storage partitioning is enabled, the iframe should be able
// to acquire a lock with the same name as one exclusively held by the opener
// of its top window, even when that opener has the same origin.
assert_equals(result.data.failed, undefined,
'The 1p iframe failed to acquire the lock');
}
promise_test(t => {
return third_party_test(t);
}, 'WebLocks of an iframe under a 3rd-party site are partitioned');
// Optional Test: Checking for partitioned web locks in an A->B->A
// (nested-iframe with cross-site ancestor chain) scenario.
//
// How this test works:
// Nested Step 1 (top frame): request an exclusive web-lock and
// store its id and release for clean-up.
// Nested Step 2 (top frame): open a pop-up window and load a
// same-site /web-locks/resources/partitioned-parent.html.
// Nested Step 3 (pop-up): load a not-same-site "parent" iframe (A->B)
// (/web-locks/resources/iframe-parent.html) inside the pop-up.
// Nested Step 4 (pop-up): send a web-lock request to the parent iframe.
// Nested Step 5 (parent iframe): load a "child" iframe (A->B->A)
// (/web-locks/resources/iframe.html) that is same-site with the
// pop-up inside the "parent" iframe.
// Nested Step 6 (parent iframe): pass on the web-lock request message to
// the "child" iframe.
// Nested Step 7 (child iframe): process the web-lock request and message
// the result to the parent iframe.
// Nested Step 8 (parent iframe): intercept the result message from the
// child iframe and send it to the pop-up.
// Nested Step 9 (pop-up): intercept the result message from the parent
// iframe and send it to the top frame.
// Nested Step 10 (top frame): add cleanup hook
// Nested Step 11 (top frame): ensure that the same-site iframe's web-lock
// request succeeds since it and the top-level are successfully
// partitioned and each can hold an exclusive lock.
// Map of lock_id => function that releases a lock.
const held_2 = new Map();
let next_lock_id_2 = 1;
async function nested_iframe_test(t) {
// Create innermost child iframe (leaf).
let leaf_url = HTTPS_ORIGIN + '/web-locks/resources/iframe.html';
// Wrap the child iframe in its cross-origin parent (middle).
let middle_url = new URL(
`/web-locks/resources/iframe-parent.html?target=${encodeURIComponent(leaf_url)}`,
HTTPS_NOTSAMESITE_ORIGIN + self.location.pathname);
// Embed the parent iframe in the top-level site (top).
let top_url = new URL(
`/web-locks/resources/partitioned-parent.html?target=${encodeURIComponent(middle_url)}`,
HTTPS_ORIGIN + self.location.pathname);
// Nested Step 1.
// Request the weblock for the top-level site.
let lock_id = next_lock_id_2++;
let [ promise, release ] = makePromiseAndResolveFunc();
let released = navigator.locks.request('testLock', {mode: 'exclusive', ifAvailable: true},
lock => {
if (lock === null) {
assert_true(false)
return;
}
return promise;
}).catch(error => alert(error.message));
held_2.set(lock_id, { release, released });
// Nested Step 2.
// Open the nested iframes. The script in the innermost child iframe
// will attempt to obtain the same weblock as above.
const w = window.open(top_url);
const result = await new Promise(resolve => window.onmessage = resolve);
// Nested Step 10.
t.add_cleanup(() => {
w.close();
let released = [];
for(let i = 1; i < next_lock_id; i++){
let h = held_2.get(i);
h.release();
released.push(h.released);
}
return Promise.allSettled(released);
});
// Nested Step 11.
// With third-party storage partitioning enabled, the same-site iframe
// should be able to acquire the lock as it has a cross-site ancestor
// and is partitioned separately from the top-level site.
assert_equals(result.data.failed, undefined,
'The 1p iframe failed to acquire the lock');
}
promise_test(t => {
return nested_iframe_test(t);
}, 'WebLocks of a nested iframe with a cross-site ancestor are partitioned');
</script>
</body>
|