1// Flags: --no-warnings --expose-gc --expose-internals
2'use strict';
3
4const common = require('../common');
5const { inspect } = require('util');
6
7const {
8  ok,
9  notStrictEqual,
10  strictEqual,
11  throws,
12} = require('assert');
13
14const {
15  kWeakHandler,
16} = require('internal/event_target');
17
18const { setTimeout: sleep } = require('timers/promises');
19
20{
21  // Tests that abort is fired with the correct event type on AbortControllers
22  const ac = new AbortController();
23  ok(ac.signal);
24  ac.signal.onabort = common.mustCall((event) => {
25    ok(event);
26    strictEqual(event.type, 'abort');
27  });
28  ac.signal.addEventListener('abort', common.mustCall((event) => {
29    ok(event);
30    strictEqual(event.type, 'abort');
31  }), { once: true });
32  ac.abort();
33  ac.abort();
34  ok(ac.signal.aborted);
35}
36
37{
38  // Tests that abort events are trusted
39  const ac = new AbortController();
40  ac.signal.addEventListener('abort', common.mustCall((event) => {
41    ok(event.isTrusted);
42  }));
43  ac.abort();
44}
45
46{
47  // Tests that abort events have the same `isTrusted` reference
48  const first = new AbortController();
49  const second = new AbortController();
50  let ev1, ev2;
51  const ev3 = new Event('abort');
52  first.signal.addEventListener('abort', common.mustCall((event) => {
53    ev1 = event;
54  }));
55  second.signal.addEventListener('abort', common.mustCall((event) => {
56    ev2 = event;
57  }));
58  first.abort();
59  second.abort();
60  const firstTrusted = Reflect.getOwnPropertyDescriptor(Object.getPrototypeOf(ev1), 'isTrusted').get;
61  const secondTrusted = Reflect.getOwnPropertyDescriptor(Object.getPrototypeOf(ev2), 'isTrusted').get;
62  const untrusted = Reflect.getOwnPropertyDescriptor(Object.getPrototypeOf(ev3), 'isTrusted').get;
63  strictEqual(firstTrusted, secondTrusted);
64  strictEqual(untrusted, firstTrusted);
65}
66
67{
68  // Tests that AbortSignal is impossible to construct manually
69  const ac = new AbortController();
70  throws(() => new ac.signal.constructor(), {
71    code: 'ERR_ILLEGAL_CONSTRUCTOR',
72  });
73}
74{
75  // Symbol.toStringTag
76  const toString = (o) => Object.prototype.toString.call(o);
77  const ac = new AbortController();
78  strictEqual(toString(ac), '[object AbortController]');
79  strictEqual(toString(ac.signal), '[object AbortSignal]');
80}
81
82{
83  const signal = AbortSignal.abort();
84  ok(signal.aborted);
85}
86
87{
88  // Test that AbortController properties and methods validate the receiver
89  const acSignalGet = Object.getOwnPropertyDescriptor(
90    AbortController.prototype,
91    'signal'
92  ).get;
93  const acAbort = AbortController.prototype.abort;
94
95  const goodController = new AbortController();
96  ok(acSignalGet.call(goodController));
97  acAbort.call(goodController);
98
99  const badAbortControllers = [
100    null,
101    undefined,
102    0,
103    NaN,
104    true,
105    'AbortController',
106    Object.create(AbortController.prototype),
107  ];
108  for (const badController of badAbortControllers) {
109    throws(
110      () => acSignalGet.call(badController),
111      { code: 'ERR_INVALID_THIS', name: 'TypeError' }
112    );
113    throws(
114      () => acAbort.call(badController),
115      { code: 'ERR_INVALID_THIS', name: 'TypeError' }
116    );
117  }
118}
119
120{
121  // Test that AbortSignal properties validate the receiver
122  const signalAbortedGet = Object.getOwnPropertyDescriptor(
123    AbortSignal.prototype,
124    'aborted'
125  ).get;
126
127  const goodSignal = new AbortController().signal;
128  strictEqual(signalAbortedGet.call(goodSignal), false);
129
130  const badAbortSignals = [
131    null,
132    undefined,
133    0,
134    NaN,
135    true,
136    'AbortSignal',
137    Object.create(AbortSignal.prototype),
138  ];
139  for (const badSignal of badAbortSignals) {
140    throws(
141      () => signalAbortedGet.call(badSignal),
142      { code: 'ERR_INVALID_THIS', name: 'TypeError' }
143    );
144  }
145}
146
147{
148  const ac = new AbortController();
149  strictEqual(inspect(ac, { depth: 1 }),
150              'AbortController { signal: [AbortSignal] }');
151  strictEqual(inspect(ac, { depth: null }),
152              'AbortController { signal: AbortSignal { aborted: false } }');
153}
154
155{
156  // Test AbortSignal.reason
157  const ac = new AbortController();
158  ac.abort('reason');
159  strictEqual(ac.signal.reason, 'reason');
160}
161
162{
163  // Test AbortSignal.reason
164  const signal = AbortSignal.abort('reason');
165  strictEqual(signal.reason, 'reason');
166}
167
168{
169  // Test AbortSignal timeout
170  const signal = AbortSignal.timeout(10);
171  ok(!signal.aborted);
172  setTimeout(common.mustCall(() => {
173    ok(signal.aborted);
174    strictEqual(signal.reason.name, 'TimeoutError');
175    strictEqual(signal.reason.code, 23);
176  }), 20);
177}
178
179{
180  (async () => {
181    // Test AbortSignal timeout doesn't prevent the signal
182    // from being garbage collected.
183    let ref;
184    {
185      ref = new globalThis.WeakRef(AbortSignal.timeout(1_200_000));
186    }
187
188    await sleep(10);
189    globalThis.gc();
190    strictEqual(ref.deref(), undefined);
191  })().then(common.mustCall());
192
193  (async () => {
194    // Test that an AbortSignal with a timeout is not gc'd while
195    // there is an active listener on it.
196    let ref;
197    function handler() {}
198    {
199      ref = new globalThis.WeakRef(AbortSignal.timeout(1_200_000));
200      ref.deref().addEventListener('abort', handler);
201    }
202
203    await sleep(10);
204    globalThis.gc();
205    notStrictEqual(ref.deref(), undefined);
206    ok(ref.deref() instanceof AbortSignal);
207
208    ref.deref().removeEventListener('abort', handler);
209
210    await sleep(10);
211    globalThis.gc();
212    strictEqual(ref.deref(), undefined);
213  })().then(common.mustCall());
214
215  (async () => {
216    // If the event listener is weak, however, it should not prevent gc
217    let ref;
218    function handler() {}
219    {
220      ref = new globalThis.WeakRef(AbortSignal.timeout(1_200_000));
221      ref.deref().addEventListener('abort', handler, { [kWeakHandler]: {} });
222    }
223
224    await sleep(10);
225    globalThis.gc();
226    strictEqual(ref.deref(), undefined);
227  })().then(common.mustCall());
228
229  // Setting a long timeout (20 minutes here) should not
230  // keep the Node.js process open (the timer is unref'd)
231  AbortSignal.timeout(1_200_000);
232}
233
234{
235  // Test AbortSignal.reason default
236  const signal = AbortSignal.abort();
237  ok(signal.reason instanceof DOMException);
238  strictEqual(signal.reason.code, 20);
239
240  const ac = new AbortController();
241  ac.abort();
242  ok(ac.signal.reason instanceof DOMException);
243  strictEqual(ac.signal.reason.code, 20);
244}
245
246{
247  // Test abortSignal.throwIfAborted()
248  throws(() => AbortSignal.abort().throwIfAborted(), {
249    code: 20,
250    name: 'AbortError',
251  });
252
253  // Does not throw because it's not aborted.
254  const ac = new AbortController();
255  ac.signal.throwIfAborted();
256}
257
258{
259  const originalDesc = Reflect.getOwnPropertyDescriptor(AbortSignal.prototype, 'aborted');
260  const actualReason = new Error();
261  Reflect.defineProperty(AbortSignal.prototype, 'aborted', { value: false });
262  throws(() => AbortSignal.abort(actualReason).throwIfAborted(), actualReason);
263  Reflect.defineProperty(AbortSignal.prototype, 'aborted', originalDesc);
264}
265
266{
267  const originalDesc = Reflect.getOwnPropertyDescriptor(AbortSignal.prototype, 'reason');
268  const actualReason = new Error();
269  const fakeExcuse = new Error();
270  Reflect.defineProperty(AbortSignal.prototype, 'reason', { value: fakeExcuse });
271  throws(() => AbortSignal.abort(actualReason).throwIfAborted(), actualReason);
272  Reflect.defineProperty(AbortSignal.prototype, 'reason', originalDesc);
273}
274