<!DOCTYPE html>
<meta charset="utf-8"/>
<title>Service Worker: Partitioned Service Workers</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="resources/test-helpers.sub.js"></script>
<script src="/common/get-host-info.sub.js"></script>
<script src="resources/partitioned-utils.js"></script>

<body>
  <!-- Debugging text for both test cases -->
  The 3p iframe's postMessage:
  <p id="iframe_response">No message received</p>

  The nested iframe's postMessage:
  <p id="nested_iframe_response">No message received</p>

<script>
promise_test(async t => {
  const script = './resources/partitioned-storage-sw.js'
  const scope = './resources/partitioned-'

  // Add service worker to this 1P context. wait_for_state() and
  // service_worker_unregister_and_register() are helper functions
  // for creating test ServiceWorkers defined in:
  // service-workers/service-worker/resources/test-helpers.sub.js
  const reg = await service_worker_unregister_and_register(t, script, scope);
  t.add_cleanup(() => reg.unregister());
  await wait_for_state(t, reg.installing, 'activated');

  // Registers the message listener with messageEventHandler(), defined in:
  // service-workers/service-worker/resources/partitioned-utils.js
  self.addEventListener('message', messageEventHandler);

  // Open an iframe that will create a promise within the SW.
  // Defined in service-workers/service-worker/resources/partitioned-storage-sw.js:
  // `waitUntilResolved.fakehtml`: URL scope that creates the promise.
  // `?From1pFrame`: query param that tracks which request the service worker is
  // handling.
  const wait_frame_url = new URL(
    './resources/partitioned-waitUntilResolved.fakehtml?From1pFrame',
    self.location);

  // Loads a child iframe with wait_frame_url as the content and returns
  // a promise for the data messaged from the loaded iframe.
  // loadAndReturnSwData() defined in:
  // service-workers/service-worker/resources/partitioned-utils.js:
  const wait_frame_1p_data = await loadAndReturnSwData(t, wait_frame_url,
                                                       'iframe');
  assert_equals(wait_frame_1p_data.source, 'From1pFrame',
    'The data for the 1p frame came from the wrong source');

  // Now create a 3p iframe that will try to resolve the SW in a 3p context.
  const third_party_iframe_url = new URL(
    './resources/partitioned-service-worker-third-party-iframe.html',
    get_host_info().HTTPS_ORIGIN + self.location.pathname);

  // loadAndReturnSwData() creates a HTTPS_NOTSAMESITE_ORIGIN or 3p `window`
  // element which embeds an iframe with the ServiceWorker and returns
  // a promise of the data messaged from that frame.
  const frame_3p_data = await loadAndReturnSwData(t, third_party_iframe_url, 'window');
  assert_equals(frame_3p_data.source, 'From3pFrame',
    'The data for the 3p frame came from the wrong source');

  // Print some debug info to the main frame.
  document.getElementById("iframe_response").innerHTML =
      "3p iframe's has_pending: " + frame_3p_data.has_pending + " source: " +
      frame_3p_data.source + ". ";

  // Now do the same for the 1p iframe.
  // Defined in service-workers/service-worker/resources/partitioned-storage-sw.js:
  // `resolve.fakehtml`: URL scope that resolves the promise.
  const resolve_frame_url = new URL(
    './resources/partitioned-resolve.fakehtml?From1pFrame', self.location);

  const frame_1p_data = await loadAndReturnSwData(t, resolve_frame_url,
                                                  'iframe');
  assert_equals(frame_1p_data.source, 'From1pFrame',
    'The data for the 1p frame came from the wrong source');
  // Both the 1p frames should have been serviced by the same service worker ID.
  // If this isn't the case then that means the SW could have been deactivated
  // which invalidates the test.
  assert_equals(frame_1p_data.ID, wait_frame_1p_data.ID,
    'The 1p frames were serviced by different service workers.');

  document.getElementById("iframe_response").innerHTML +=
    "1p iframe's has_pending: " + frame_1p_data.has_pending + " source: " +
    frame_1p_data.source;

  // If partitioning is working correctly then only the 1p iframe should see
  // (and resolve) its SW's promise. Additionally the two frames should see
  // different IDs.
  assert_true(frame_1p_data.has_pending,
      'The 1p iframe saw a pending promise in the service worker.');
  assert_false(frame_3p_data.has_pending,
    'The 3p iframe saw a pending promise in the service worker.');
  assert_not_equals(frame_1p_data.ID, frame_3p_data.ID,
    'The frames were serviced by the same service worker thread.');
}, 'Services workers under different top-level sites are partitioned.');

// Optional Test: Checking for partitioned ServiceWorkers in an A->B->A
// (nested-iframes with cross-site ancestor) scenario.
promise_test(async t => {
  const script = './resources/partitioned-storage-sw.js'
  const scope = './resources/partitioned-'

  // Add service worker to this 1P context. wait_for_state() and
  // service_worker_unregister_and_register() are helper functions
  // for creating test ServiceWorkers defined in:
  // service-workers/service-worker/resources/test-helpers.sub.js
  const reg = await service_worker_unregister_and_register(t, script, scope);
  t.add_cleanup(() => reg.unregister());
  await wait_for_state(t, reg.installing, 'activated');

  // Registers the message listener with messageEventHandler(), defined in:
  // service-workers/service-worker/resources/partitioned-utils.js
  self.addEventListener('message', messageEventHandler);

  // Open an iframe that will create a promise within the SW.
  // Defined in service-workers/service-worker/resources/partitioned-storage-sw.js:
  // `waitUntilResolved.fakehtml`: URL scope that creates the promise.
  // `?From1pFrame`: query param that tracks which request the service worker is
  // handling.
  const wait_frame_url = new URL(
    './resources/partitioned-waitUntilResolved.fakehtml?From1pFrame',
    self.location);

  // Load a child iframe with wait_frame_url as the content.
  // loadAndReturnSwData() defined in:
  // service-workers/service-worker/resources/partitioned-utils.js:
  const wait_frame_1p_data = await loadAndReturnSwData(t, wait_frame_url,
                                                       'iframe');
  assert_equals(wait_frame_1p_data.source, 'From1pFrame',
    'The data for the 1p frame came from the wrong source');

  // Now create a set of nested iframes in the configuration A1->B->A2
  // where B is cross-site and A2 is same-site to this top-level
  // site (A1). The innermost iframe of the nested iframes (A2) will
  // create an additional iframe to finally resolve the ServiceWorker.
  const nested_iframe_url = new URL(
    './resources/partitioned-service-worker-nested-iframe-parent.html',
    get_host_info().HTTPS_NOTSAMESITE_ORIGIN + self.location.pathname);

  // Create the nested iframes (which will in turn create the iframe
  // with the ServiceWorker) and await on receiving its data.
  const nested_iframe_data = await loadAndReturnSwData(t, nested_iframe_url, 'iframe');
  assert_equals(nested_iframe_data.source, 'FromNestedFrame',
    'The data for the nested iframe frame came from the wrong source');

  // Print some debug info to the main frame.
  document.getElementById("nested_iframe_response").innerHTML =
      "Nested iframe's has_pending: " + nested_iframe_data.has_pending + " source: " +
      nested_iframe_data.source + ". ";

  // Now do the same for the 1p iframe.
  // Defined in service-workers/service-worker/resources/partitioned-storage-sw.js:
  // `resolve.fakehtml`: URL scope that resolves the promise.
  const resolve_frame_url = new URL(
    './resources/partitioned-resolve.fakehtml?From1pFrame', self.location);

  const frame_1p_data = await loadAndReturnSwData(t, resolve_frame_url,
                                                  'iframe');
  assert_equals(frame_1p_data.source, 'From1pFrame',
    'The data for the 1p frame came from the wrong source');
  // Both the 1p frames should have been serviced by the same service worker ID.
  // If this isn't the case then that means the SW could have been deactivated
  // which invalidates the test.
  assert_equals(frame_1p_data.ID, wait_frame_1p_data.ID,
    'The 1p frames were serviced by different service workers.');

  document.getElementById("nested_iframe_response").innerHTML +=
    "1p iframe's has_pending: " + frame_1p_data.has_pending + " source: " +
    frame_1p_data.source;

  // If partitioning is working correctly then only the 1p iframe should see
  // (and resolve) its SW's promise. Additionally, the innermost iframe of
  // the nested iframes (A2 in the configuration A1->B->A2) should have a
  // different service worker ID than the 1p (A1) frame.
  assert_true(frame_1p_data.has_pending,
      'The 1p iframe saw a pending promise in the service worker.');
  assert_false(nested_iframe_data.has_pending,
    'The 3p iframe saw a pending promise in the service worker.');
  assert_not_equals(frame_1p_data.ID, nested_iframe_data.ID,
    'The frames were serviced by the same service worker thread.');
}, 'Services workers with cross-site ancestors are partitioned.');

</script>
</body>