| <!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> |