summaryrefslogtreecommitdiffstats
path: root/dom/serviceworkers/test/test_abrupt_completion.html
blob: bbf9e965f0702436749d67ba7e4d4d86d191a061 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
<!doctype html>
<meta charset=utf-8>
<title></title>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<script>

// Tests a _registered_ ServiceWorker whose script evaluation results in an
// "abrupt completion", e.g. threw an uncaught exception. Such a ServiceWorker's
// first script evaluation must result in a "normal completion", however, for
// the Update algorithm to not abort in its step 18 when registering:
//
// 18. If runResult is failure or an abrupt completion, then: [...]

const script = "./abrupt_completion_worker.js";
const scope = "./empty.html";
const expectedMessage = "handler-before-throw";
let registration = null;

// Should only be called once registration.active is non-null. Uses
// implementation details by zero-ing the "idle timeout"s and then sending  an
// event to the ServiceWorker, which should immediately cause its termination.
// The idle timeouts are restored after the ServiceWorker is terminated.
async function startAndStopServiceWorker() {
  SpecialPowers.registerObservers("service-worker-shutdown");

  const spTopic = "specialpowers-service-worker-shutdown";

  const origIdleTimeout =
    SpecialPowers.getIntPref("dom.serviceWorkers.idle_timeout");

  const origIdleExtendedTimeout =
    SpecialPowers.getIntPref("dom.serviceWorkers.idle_extended_timeout");

  await new Promise(resolve => {
    const observer = {
      async observe(subject, topic, data) {
        if (topic !== spTopic) {
          return;
        }

        SpecialPowers.removeObserver(observer, spTopic);

        await SpecialPowers.pushPrefEnv({
          set: [
              ["dom.serviceWorkers.idle_timeout", origIdleTimeout],
              ["dom.serviceWorkers.idle_extended_timeout", origIdleExtendedTimeout]
            ]
        });

        resolve();
      },
    };

    // Speed things up.
    SpecialPowers.pushPrefEnv({
      set: [
          ["dom.serviceWorkers.idle_timeout", 0],
          ["dom.serviceWorkers.idle_extended_timeout", 0]
        ]
    }).then(() => {
      SpecialPowers.addObserver(observer, spTopic);

      registration.active.postMessage("");
    });
  });
}

// eslint-disable-next-line mozilla/no-addtask-setup
add_task(async function setup() {
  await SpecialPowers.pushPrefEnv({
    set: [
        ["dom.serviceWorkers.enabled", true],
        ["dom.serviceWorkers.testing.enabled", true]
      ]
  });

  registration = await navigator.serviceWorker.register(script, { scope });
  SimpleTest.registerCleanupFunction(async function unregisterRegistration() {
    await registration.unregister();
  });

  await new Promise(resolve => {
    const serviceWorker = registration.installing;

    serviceWorker.onstatechange = () => {
      if (serviceWorker.state === "activated") {
        resolve();
      }
    };
  });

  ok(registration.active instanceof ServiceWorker, "ServiceWorker is activated");
});

// We expect that the restarted SW that experiences an abrupt completion at
// startup after adding its message handler 1) will be active in order to
// respond to our postMessage and 2) will respond with the global value set
// prior to the importScripts call that throws (and not the global value that
// would have been assigned after the importScripts call if it didn't throw).
add_task(async function testMessageHandler() {
  await startAndStopServiceWorker();

  await new Promise(resolve => {
    navigator.serviceWorker.onmessage = e => {
      is(e.data, expectedMessage, "Correct message handler");
      resolve();
    };
    registration.active.postMessage("");
  });
});

// We expect that the restarted SW that experiences an abrupt completion at
// startup before adding its "fetch" listener will 1) successfully dispatch the
// event and 2) it will not be handled (respondWith() will not be called) so
// interception will be reset and the response will contain the contents of
// empty.html. Before the fix in bug 1603484 the SW would fail to properly start
// up and the fetch event would result in a NetworkError, breaking the
// controlled page.
add_task(async function testFetchHandler() {
  await startAndStopServiceWorker();

  const iframe = document.createElement("iframe");
  SimpleTest.registerCleanupFunction(function removeIframe() {
    iframe.remove();
  });

  await new Promise(resolve => {
    iframe.src = scope;
    iframe.onload = resolve;
    document.body.appendChild(iframe);
  });

  const response = await iframe.contentWindow.fetch(scope);

  // NetworkError will have a status of 0, which is not "ok", and this is
  // a stronger guarantee that should be true instead of just checking if there
  // isn't a NetworkError.
  ok(response.ok, "Fetch succeeded and didn't result in a NetworkError");

  const text = await response.text();
  is(text, "", "Correct response text");
});

</script>