summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/service-workers/service-worker/resources/extendable-event-async-waituntil.js
blob: 8a975b0d2e9b0789ce7ab7b05757b6d93f2ec400 (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
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
// This worker calls waitUntil() and respondWith() asynchronously and
// reports back to the test whether they threw.
//
// These test cases are confusing. Bear in mind that the event is active
// (calling waitUntil() is allowed) if:
// * The pending promise count is not 0, or
// * The event dispatch flag is set.

// Controlled by 'init'/'done' messages.
var resolveLockPromise;
var port;

self.addEventListener('message', function(event) {
    var waitPromise;
    var resolveTestPromise;

    switch (event.data.step) {
      case 'init':
        event.waitUntil(new Promise((res) => { resolveLockPromise = res; }));
        port = event.data.port;
        break;
      case 'done':
        resolveLockPromise();
        break;

      // Throws because waitUntil() is called in a task after event dispatch
      // finishes.
      case 'no-current-extension-different-task':
        async_task_waituntil(event).then(reportResultExpecting('InvalidStateError'));
        break;

      // OK because waitUntil() is called in a microtask that runs after the
      // event handler runs, while the event dispatch flag is still set.
      case 'no-current-extension-different-microtask':
        async_microtask_waituntil(event).then(reportResultExpecting('OK'));
        break;

      // OK because the second waitUntil() is called while the first waitUntil()
      // promise is still pending.
      case 'current-extension-different-task':
        event.waitUntil(new Promise((res) => { resolveTestPromise = res; }));
        async_task_waituntil(event).then(reportResultExpecting('OK')).then(resolveTestPromise);
        break;

      // OK because all promises involved resolve "immediately", so the second
      // waitUntil() is called during the microtask checkpoint at the end of
      // event dispatching, when the event dispatch flag is still set.
      case 'during-event-dispatch-current-extension-expired-same-microtask-turn':
        waitPromise = Promise.resolve();
        event.waitUntil(waitPromise);
        waitPromise.then(() => { return sync_waituntil(event); })
          .then(reportResultExpecting('OK'))
        break;

      // OK for the same reason as above.
      case 'during-event-dispatch-current-extension-expired-same-microtask-turn-extra':
        waitPromise = Promise.resolve();
        event.waitUntil(waitPromise);
        waitPromise.then(() => { return async_microtask_waituntil(event); })
          .then(reportResultExpecting('OK'))
        break;


      // OK because the pending promise count is decremented in a microtask
      // queued upon fulfillment of the first waitUntil() promise, so the second
      // waitUntil() is called while the pending promise count is still
      // positive.
      case 'after-event-dispatch-current-extension-expired-same-microtask-turn':
        waitPromise = makeNewTaskPromise();
        event.waitUntil(waitPromise);
        waitPromise.then(() => { return sync_waituntil(event); })
          .then(reportResultExpecting('OK'))
        break;

      // Throws because the second waitUntil() is called after the pending
      // promise count was decremented to 0.
      case 'after-event-dispatch-current-extension-expired-same-microtask-turn-extra':
        waitPromise = makeNewTaskPromise();
        event.waitUntil(waitPromise);
        waitPromise.then(() => { return async_microtask_waituntil(event); })
          .then(reportResultExpecting('InvalidStateError'))
        break;

      // Throws because the second waitUntil() is called in a new task, after
      // first waitUntil() promise settled and the event dispatch flag is unset.
      case 'current-extension-expired-different-task':
        event.waitUntil(Promise.resolve());
        async_task_waituntil(event).then(reportResultExpecting('InvalidStateError'));
        break;

      case 'script-extendable-event':
        self.dispatchEvent(new ExtendableEvent('nontrustedevent'));
        break;
    }

    event.source.postMessage('ACK');
  });

self.addEventListener('fetch', function(event) {
  const path = new URL(event.request.url).pathname;
  const step = path.substring(path.lastIndexOf('/') + 1);
  let response;
  switch (step) {
    // OK because waitUntil() is called while the respondWith() promise is still
    // unsettled, so the pending promise count is positive.
    case 'pending-respondwith-async-waituntil':
      var resolveFetch;
      response = new Promise((res) => { resolveFetch = res; });
      event.respondWith(response);
      async_task_waituntil(event)
        .then(reportResultExpecting('OK'))
        .then(() => { resolveFetch(new Response('OK')); });
      break;

    // OK because all promises involved resolve "immediately", so waitUntil() is
    // called during the microtask checkpoint at the end of event dispatching,
    // when the event dispatch flag is still set.
    case 'during-event-dispatch-respondwith-microtask-sync-waituntil':
      response = Promise.resolve(new Response('RESP'));
      event.respondWith(response);
      response.then(() => { return sync_waituntil(event); })
        .then(reportResultExpecting('OK'));
      break;

    // OK because all promises involved resolve "immediately", so waitUntil() is
    // called during the microtask checkpoint at the end of event dispatching,
    // when the event dispatch flag is still set.
    case 'during-event-dispatch-respondwith-microtask-async-waituntil':
      response = Promise.resolve(new Response('RESP'));
      event.respondWith(response);
      response.then(() => { return async_microtask_waituntil(event); })
        .then(reportResultExpecting('OK'));
      break;

    // OK because the pending promise count is decremented in a microtask queued
    // upon fulfillment of the respondWith() promise, so waitUntil() is called
    // while the pending promise count is still positive.
    case 'after-event-dispatch-respondwith-microtask-sync-waituntil':
      response = makeNewTaskPromise().then(() => {return new Response('RESP');});
      event.respondWith(response);
      response.then(() => { return sync_waituntil(event); })
        .then(reportResultExpecting('OK'));
      break;


    // Throws because waitUntil() is called after the pending promise count was
    // decremented to 0.
    case 'after-event-dispatch-respondwith-microtask-async-waituntil':
      response = makeNewTaskPromise().then(() => {return new Response('RESP');});
      event.respondWith(response);
      response.then(() => { return async_microtask_waituntil(event); })
        .then(reportResultExpecting('InvalidStateError'))
      break;
  }
});

self.addEventListener('nontrustedevent', function(event) {
    sync_waituntil(event).then(reportResultExpecting('InvalidStateError'));
  });

function reportResultExpecting(expectedResult) {
  return function (result) {
    port.postMessage({result : result, expected: expectedResult});
    return result;
  };
}

function sync_waituntil(event) {
  return new Promise((res, rej) => {
    try {
      event.waitUntil(Promise.resolve());
      res('OK');
    } catch (error) {
      res(error.name);
    }
  });
}

function async_microtask_waituntil(event) {
  return new Promise((res, rej) => {
    Promise.resolve().then(() => {
      try {
        event.waitUntil(Promise.resolve());
        res('OK');
      } catch (error) {
        res(error.name);
      }
    });
  });
}

function async_task_waituntil(event) {
  return new Promise((res, rej) => {
    setTimeout(() => {
      try {
        event.waitUntil(Promise.resolve());
        res('OK');
      } catch (error) {
        res(error.name);
      }
    }, 0);
  });
}

// Returns a promise that settles in a separate task.
function makeNewTaskPromise() {
  return new Promise(resolve => {
    setTimeout(resolve, 0);
  });
}