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

async function third_party_test(t) {
  let target_url = HTTPS_ORIGIN + '/web-locks/resources/iframe.html';
  target_url = new URL(
    `./resources/partitioned-parent.html?target=${encodeURIComponent(target_url)}`,
    HTTPS_NOTSAMESITE_ORIGIN + self.location.pathname);

  navigator.locks.request('testLock', {mode: 'exclusive', ifAvailable: true},
        lock => {
          if (lock === null) {
            assert_true(false)
            return;
          }
          let lock_id = next_lock_id++;
          let release;
          const promise = new Promise(r => { release = r; });
          held.set(lock_id, release);
          return promise;
        });

  const w = window.open(target_url);
  const result = await new Promise(resolve => window.onmessage = resolve);

  // When 3rd party storage partitioning is enabled, the iframe should be able
  // to aquire 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');

  t.add_cleanup(() => {
    w.close()
    for(let i = 1; i < next_lock_id; i++){
      held.get(i)();
    }
  });
}

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.

// 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(
    `./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(
    `./resources/partitioned-parent.html?target=${encodeURIComponent(middle_url)}`,
    HTTPS_ORIGIN + self.location.pathname);

  // Request the weblock for the top-level site.
  navigator.locks.request('testLock', {mode: 'exclusive', ifAvailable: true},
        lock => {
          if (lock === null) {
            assert_true(false)
            return;
          }
          // Obtain and store the release functions for clean-up.
          let lock_id = next_lock_id_2++;
          let release;
          const promise = new Promise(r => { release = r; });
          held_2.set(lock_id, release);
          return promise;
        }).catch(error => alert(error.message));

  // 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);

  // 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');

  t.add_cleanup(() => {
    w.close()
    for(let i = 1; i < next_lock_id_2; i++){
      held_2.get(i)();
    }
  });
}

promise_test(t => {
  return nested_iframe_test(t);
}, 'WebLocks of a nested iframe with a cross-site ancestor are partitioned');
</script>
</body>
