<!DOCTYPE html>
<meta charset="utf-8">
<title>Service WorkerRegistration from a removed iframe</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="resources/test-helpers.sub.js"></script>
<body>
</body>
<script>
// NOTE: This file tests corner case behavior that might not be defined in the
// spec. See https://github.com/w3c/ServiceWorker/issues/1221

promise_test(t => {
    const url = 'resources/blank.html';
    const scope_for_iframe = 'removed-registration'
    const scope_for_main = 'resources/' + scope_for_iframe;
    const script = 'resources/empty-worker.js';
    let frame;
    let resolvedCount = 0;

    return service_worker_unregister(t, scope_for_main)
      .then(() => {
          return with_iframe(url);
        })
      .then(f => {
          frame = f;
          return navigator.serviceWorker.register(script,
                                                  {scope: scope_for_main});
        })
      .then(r => {
          add_completion_callback(() => { r.unregister(); });
          return wait_for_state(t, r.installing, 'activated');
        })
      .then(() => {
          return frame.contentWindow.navigator.serviceWorker.getRegistration(
            scope_for_iframe);
        })
      .then(r => {
          frame.remove();
          assert_equals(r.installing, null);
          assert_equals(r.waiting, null);
          assert_equals(r.active.state, 'activated');
          assert_equals(r.scope, normalizeURL(scope_for_main));
          r.onupdatefound = () => { /* empty */ };

          // We want to verify that unregister() and update() do not
          // resolve on a detached registration.  We can't check for
          // an explicit rejection, though, because not all browsers
          // fire rejection callbacks on detached promises.  Instead
          // we wait for a sample scope to install, activate, and
          // unregister before declaring that the promises did not
          // resolve.
          r.unregister().then(() => resolvedCount += 1,
                              () => {});
          r.update().then(() => resolvedCount += 1,
                          () => {});
          return wait_for_activation_on_sample_scope(t, window);
        })
      .then(() => {
          assert_equals(resolvedCount, 0,
                        'methods called on a detached registration should not resolve');
          frame.remove();
        })
  }, 'accessing a ServiceWorkerRegistration from a removed iframe');

promise_test(t => {
    const script = 'resources/empty-worker.js';
    const scope = 'resources/scope/serviceworker-from-detached';

    return service_worker_unregister_and_register(t, script, scope)
      .then(registration => {
          add_completion_callback(() => { registration.unregister(); });
          return wait_for_state(t, registration.installing, 'activated');
        })
      .then(() => { return with_iframe(scope); })
      .then(frame => {
          const worker = frame.contentWindow.navigator.serviceWorker.controller;
          const ctor = frame.contentWindow.DOMException;
          frame.remove();
          assert_equals(worker.scriptURL, normalizeURL(script));
          assert_equals(worker.state, 'activated');
          worker.onstatechange = () => { /* empty */ };
          assert_throws_dom(
              'InvalidStateError',
               ctor,
              () => { worker.postMessage(''); },
              'postMessage on a detached client should throw an exception.');
        });
  }, 'accessing a ServiceWorker object from a removed iframe');

promise_test(t => {
    const iframe = document.createElement('iframe');
    iframe.src = 'resources/blank.html';
    document.body.appendChild(iframe);
    const f = iframe.contentWindow.Function;
    function get_navigator() {
      return f('return navigator')();
    }
    return new Promise(resolve => {
        assert_equals(iframe.contentWindow.navigator, get_navigator());
        iframe.src = 'resources/blank.html?navigate-to-new-url';
        iframe.onload = resolve;
      }).then(function() {
        assert_not_equals(get_navigator().serviceWorker, null);
        assert_equals(
            get_navigator().serviceWorker,
            iframe.contentWindow.navigator.serviceWorker);
        iframe.remove();
      });
  }, 'accessing navigator.serviceWorker on a detached iframe');

test(t => {
    const iframe = document.createElement('iframe');
    iframe.src = 'resources/blank.html';
    document.body.appendChild(iframe);
    const f = iframe.contentWindow.Function;
    function get_navigator() {
      return f('return navigator')();
    }
    assert_not_equals(get_navigator().serviceWorker, null);
    iframe.remove();
    assert_throws_js(TypeError, () => get_navigator());
  }, 'accessing navigator on a removed frame');

// It seems weird that about:blank and blank.html (the test above) have
// different behavior. These expectations are based on Chromium behavior, which
// might not be right.
test(t => {
    const iframe = document.createElement('iframe');
    iframe.src = 'about:blank';
    document.body.appendChild(iframe);
    const f = iframe.contentWindow.Function;
    function get_navigator() {
      return f('return navigator')();
    }
    assert_not_equals(get_navigator().serviceWorker, null);
    iframe.remove();
    assert_equals(get_navigator().serviceWorker, null);
  }, 'accessing navigator.serviceWorker on a removed about:blank frame');

</script>
