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