@@ -859,15 +859,25 @@ class EventTarget {
859859 return ;
860860 }
861861 }
862-
863862 if ( processedOptions ?. signal ) {
864863 const signal = processedOptions ?. signal ;
865864 if ( signal . aborted ) {
866865 return ;
867866 } else {
868- signal . addEventListener ( "abort" , ( ) => {
869- this . removeEventListener ( type , callback , options ) ;
870- } ) ;
867+ const removeListener = ( ) => {
868+ // Remove the specific listener entry that was added
869+ const listenerList = this [ eventTargetData ] . listeners [ type ] ;
870+ if ( listenerList ) {
871+ for ( let i = 0 ; i < listenerList . length ; i ++ ) {
872+ const listener = listenerList [ i ] ;
873+ if ( listener . callback === callback && listener . options === processedOptions ) {
874+ listenerList . splice ( i , 1 ) ;
875+ break ;
876+ }
877+ }
878+ }
879+ } ;
880+ signal . addEventListener ( "abort" , removeListener ) ;
871881 }
872882 }
873883
@@ -924,15 +934,15 @@ class EventTarget {
924934
925935 // Per spec: Check if event is currently being dispatched
926936 if ( getDispatched ( event ) ) {
927- throw new DOMException (
937+ throw createDOMException (
928938 "Failed to execute 'dispatchEvent' on 'EventTarget': The event is already being dispatched." ,
929939 "InvalidStateError" ,
930940 ) ;
931941 }
932942
933943 // Per spec: Check if event's initialized flag is not set
934944 if ( event . eventPhase !== Event . NONE ) {
935- throw new DOMException (
945+ throw createDOMException (
936946 "Failed to execute 'dispatchEvent' on 'EventTarget': The event's phase is not NONE." ,
937947 "InvalidStateError" ,
938948 ) ;
@@ -960,37 +970,6 @@ class EventTarget {
960970 }
961971}
962972
963- // Simple DOMException implementation for spec compliance
964- class DOMException extends Error {
965- override readonly name : string ;
966- readonly code : number ;
967-
968- constructor ( message ?: string , name = "Error" ) {
969- super ( message ) ;
970- this . name = name ;
971-
972- // Common DOMException codes
973- const codes : { [ key : string ] : number ; } = {
974- "InvalidStateError" : 11 ,
975- "NotSupportedError" : 9 ,
976- "InvalidCharacterError" : 5 ,
977- "NoModificationAllowedError" : 7 ,
978- "NotFoundError" : 8 ,
979- "QuotaExceededError" : 22 ,
980- "TypeMismatchError" : 17 ,
981- "SecurityError" : 18 ,
982- "NetworkError" : 19 ,
983- "AbortError" : 20 ,
984- "URLMismatchError" : 21 ,
985- "InvalidAccessError" : 15 ,
986- "ValidationError" : 0 ,
987- "TimeoutError" : 23 ,
988- } ;
989-
990- this . code = codes [ name ] || 0 ;
991- }
992- }
993-
994973class ErrorEvent extends Event {
995974 readonly message : string ;
996975 readonly filename : string ;
@@ -1224,3 +1203,171 @@ function reportError(error: any): void {
12241203function listenerCount ( target : any , type : string ) : number {
12251204 return getListeners ( target ) ?. [ type ] ?. length ?? 0 ;
12261205}
1206+
1207+ // AbortSignal and AbortController implementation
1208+ // Compliant with WHATWG DOM Standard
1209+ // https://dom.spec.whatwg.org/#interface-abortsignal
1210+
1211+ // Private symbols for AbortSignal internal state
1212+ const _aborted = Symbol ( "[[aborted]]" ) ;
1213+ const _abortReason = Symbol ( "[[abortReason]]" ) ;
1214+ const _abortAlgorithms = Symbol ( "[[abortAlgorithms]]" ) ;
1215+
1216+ class AbortSignal extends EventTarget {
1217+ constructor ( ) {
1218+ super ( ) ;
1219+
1220+ ( this as any ) [ _aborted ] = false ;
1221+ ( this as any ) [ _abortReason ] = undefined ;
1222+ ( this as any ) [ _abortAlgorithms ] = new Set ( ) ;
1223+ }
1224+ get aborted ( ) : boolean {
1225+ return ( this as any ) [ _aborted ] ;
1226+ }
1227+
1228+ get reason ( ) : any {
1229+ return ( this as any ) [ _abortReason ] ;
1230+ }
1231+
1232+ throwIfAborted ( ) : void {
1233+ if ( ( this as any ) [ _aborted ] ) {
1234+ throw ( this as any ) [ _abortReason ] ;
1235+ }
1236+ }
1237+ // Static factory methods
1238+ static abort ( reason ?: any ) : AbortSignal {
1239+ const signal = new AbortSignal ( ) ;
1240+ ( signal as any ) [ _aborted ] = true ;
1241+ ( signal as any ) [ _abortReason ] = reason !== undefined ?
1242+ reason :
1243+ createDOMException ( "signal is aborted without reason" , "AbortError" ) ;
1244+ return signal ;
1245+ }
1246+ static timeout ( milliseconds : number ) : AbortSignal {
1247+ if ( milliseconds < 0 ) {
1248+ throw new RangeError ( "milliseconds must be non-negative" ) ;
1249+ }
1250+
1251+ const signal = new AbortSignal ( ) ;
1252+ if ( milliseconds === 0 ) {
1253+ ( signal as any ) [ _aborted ] = true ;
1254+ ( signal as any ) [ _abortReason ] = createDOMException ( "signal timed out" , "TimeoutError" ) ;
1255+ } else {
1256+ const timeoutCallback = function ( ) {
1257+ if ( ! ( signal as any ) [ _aborted ] ) {
1258+ signalAbort (
1259+ signal ,
1260+ createDOMException ( "signal timed out" , "TimeoutError" ) ,
1261+ ) ;
1262+ }
1263+ } ;
1264+ setTimeout ( timeoutCallback , milliseconds ) ;
1265+ }
1266+
1267+ return signal ;
1268+ }
1269+ static any ( signals : AbortSignal [ ] ) : AbortSignal {
1270+ const resultSignal = new AbortSignal ( ) ;
1271+
1272+ // If any signal is already aborted, return an aborted signal
1273+ for ( const signal of signals ) {
1274+ if ( signal . aborted ) {
1275+ ( resultSignal as any ) [ _aborted ] = true ;
1276+ ( resultSignal as any ) [ _abortReason ] = signal . reason ;
1277+ return resultSignal ;
1278+ }
1279+ }
1280+
1281+ // Listen for abort on any of the signals
1282+ for ( const signal of signals ) {
1283+ signal . addEventListener ( "abort" , ( ) => {
1284+ if ( ! resultSignal . aborted ) {
1285+ signalAbort ( resultSignal , signal . reason ) ;
1286+ }
1287+ } ) ;
1288+ }
1289+
1290+ return resultSignal ;
1291+ }
1292+ }
1293+
1294+ // AbortController implementation
1295+ class AbortController {
1296+ #signal: AbortSignal ;
1297+
1298+ constructor ( ) {
1299+ this . #signal = new AbortSignal ( ) ;
1300+ }
1301+
1302+ get signal ( ) : AbortSignal {
1303+ return this . #signal;
1304+ }
1305+
1306+ abort ( reason ?: any ) : void {
1307+ signalAbort (
1308+ this . #signal,
1309+ reason !== undefined ?
1310+ reason :
1311+ createDOMException ( "signal is aborted without reason" , "AbortError" ) ,
1312+ ) ;
1313+ }
1314+ }
1315+
1316+ // Internal function to signal abort
1317+ function signalAbort ( signal : AbortSignal , reason : any ) : void {
1318+ if ( ( signal as any ) [ _aborted ] ) {
1319+ return ;
1320+ }
1321+
1322+ ( signal as any ) [ _aborted ] = true ;
1323+ ( signal as any ) [ _abortReason ] = reason ;
1324+
1325+ // Execute abort algorithms
1326+ const algorithms = ( signal as any ) [ _abortAlgorithms ] ;
1327+ for ( const algorithm of algorithms ) {
1328+ try {
1329+ algorithm ( ) ;
1330+ } catch ( error ) {
1331+ // Report the exception but continue with other algorithms
1332+ reportError ( error ) ;
1333+ }
1334+ }
1335+ algorithms . clear ( ) ;
1336+
1337+ // Fire abort event
1338+ const event = new Event ( "abort" ) ;
1339+ signal . dispatchEvent ( event ) ;
1340+ }
1341+
1342+ // DOMException implementation for abort-related errors
1343+ function createDOMException ( message ?: string , name : string = "Error" ) : Error {
1344+ const error = new Error ( message ) ;
1345+ error . name = name ;
1346+
1347+ // Add code property for DOMException compatibility
1348+ const codes : Record < string , number > = {
1349+ "IndexSizeError" : 1 ,
1350+ "HierarchyRequestError" : 3 ,
1351+ "WrongDocumentError" : 4 ,
1352+ "InvalidCharacterError" : 5 ,
1353+ "NoModificationAllowedError" : 7 ,
1354+ "NotFoundError" : 8 ,
1355+ "NotSupportedError" : 9 ,
1356+ "InvalidStateError" : 11 ,
1357+ "SyntaxError" : 12 ,
1358+ "InvalidModificationError" : 13 ,
1359+ "NamespaceError" : 14 ,
1360+ "InvalidAccessError" : 15 ,
1361+ "SecurityError" : 18 ,
1362+ "NetworkError" : 19 ,
1363+ "AbortError" : 20 ,
1364+ "URLMismatchError" : 21 ,
1365+ "QuotaExceededError" : 22 ,
1366+ "TimeoutError" : 23 ,
1367+ "InvalidNodeTypeError" : 24 ,
1368+ "DataCloneError" : 25 ,
1369+ } ;
1370+
1371+ ( error as any ) . code = codes [ name ] || 0 ;
1372+ return error ;
1373+ }
0 commit comments