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