Add EventTarget, Event and CustomEvent

This commit is contained in:
Chris Tate
2026-10-14 02:58:57 -05:00
parent 6738693f3d
commit a4af77ee8d
51 changed files with 2721 additions and 1338 deletions
@@ -2,6 +2,6 @@
"schemaVersion": 3,
"nodeVersion": "24.15.0",
"nodeCommit": "848430679556aed0bd073f2bc263331ad84fa119",
"artifactVersion": "d94b8161bbee649b9890",
"artifactVersion": "ad22ef7f9c0a3e2d2f5a",
"rowCount": 3662
}
+93 -93
View File
@@ -298,10 +298,10 @@
"entries": 78,
"apiEntries": 78,
"static": {
"supported": 0,
"partial": 22,
"supported": 18,
"partial": 34,
"refused": 0,
"not-implemented": 56,
"not-implemented": 26,
"by-design": 0,
"unreviewed": 0,
"not-applicable": 0
@@ -26433,9 +26433,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
}
},
{
@@ -26456,9 +26456,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
}
},
{
@@ -26502,9 +26502,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
}
},
{
@@ -26525,9 +26525,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
}
},
{
@@ -26548,9 +26548,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "supported",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
}
},
{
@@ -26571,9 +26571,9 @@
"inherited": false
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "supported",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
}
},
{
@@ -26594,9 +26594,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "supported",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
}
},
{
@@ -26617,9 +26617,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "supported",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
}
},
{
@@ -26640,9 +26640,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "supported",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
}
},
{
@@ -26663,9 +26663,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "supported",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
}
},
{
@@ -26686,9 +26686,9 @@
"inherited": false
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "supported",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
}
},
{
@@ -26709,9 +26709,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "supported",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
}
},
{
@@ -26732,9 +26732,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "supported",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
}
},
{
@@ -26755,9 +26755,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "supported",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
}
},
{
@@ -26778,9 +26778,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "supported",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
}
},
{
@@ -26801,9 +26801,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "supported",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
}
},
{
@@ -26824,9 +26824,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "supported",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
}
},
{
@@ -26847,9 +26847,9 @@
"inherited": false
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "supported",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
}
},
{
@@ -26870,9 +26870,9 @@
"inherited": false
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "supported",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
}
},
{
@@ -26893,9 +26893,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "supported",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
}
},
{
@@ -26916,9 +26916,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "supported",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
}
},
{
@@ -26939,9 +26939,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "supported",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
}
},
{
@@ -26962,9 +26962,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
}
},
{
@@ -26985,9 +26985,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
}
},
{
@@ -27008,9 +27008,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
}
},
{
@@ -27031,9 +27031,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
}
},
{
@@ -27054,9 +27054,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
}
},
{
@@ -27077,9 +27077,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
}
},
{
@@ -28066,9 +28066,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
}
},
{
@@ -28112,9 +28112,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
}
},
{
@@ -3,21 +3,21 @@
"nodeVersion": "24.15.0",
"nodeCommit": "848430679556aed0bd073f2bc263331ad84fa119",
"summary": {
"taskCount": 3288,
"tierItemCount": 3288,
"taskCount": 3270,
"tierItemCount": 3270,
"tiers": {
"static": {
"replace-refusal": 40,
"verify-gap": 2384,
"audit-partial": 782,
"verify-gap": 2354,
"audit-partial": 794,
"classify": 2,
"implement": 80
}
},
"priorities": {
"high": 2239,
"high": 2225,
"normal": 673,
"low": 376
"low": 372
}
},
"tasks": [
@@ -22999,12 +22999,18 @@
"priority": "high",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:events",
"tests": []
"status": "partial",
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "compiler-feature:events.EventTarget",
"tests": [
"tests/corpus/5130-event-target-dispatch.js",
"tests/corpus/5131-event-target-listener-errors.js",
"tests/corpus/5132-event-target-typed.ts",
"tests/corpus/5133-abort-signal-event-target.ts",
"tests/corpus/5135-event-target-warnings.js"
]
}
}
},
@@ -23024,12 +23030,18 @@
"priority": "high",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:events",
"tests": []
"status": "partial",
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "compiler-feature:events.EventTarget",
"tests": [
"tests/corpus/5130-event-target-dispatch.js",
"tests/corpus/5131-event-target-listener-errors.js",
"tests/corpus/5132-event-target-typed.ts",
"tests/corpus/5133-abort-signal-event-target.ts",
"tests/corpus/5135-event-target-warnings.js"
]
}
}
},
@@ -23074,12 +23086,18 @@
"priority": "high",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:events",
"tests": []
"status": "partial",
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "compiler-feature:events.EventTarget",
"tests": [
"tests/corpus/5130-event-target-dispatch.js",
"tests/corpus/5131-event-target-listener-errors.js",
"tests/corpus/5132-event-target-typed.ts",
"tests/corpus/5133-abort-signal-event-target.ts",
"tests/corpus/5135-event-target-warnings.js"
]
}
}
},
@@ -23099,462 +23117,15 @@
"priority": "high",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:events",
"tests": []
}
}
},
{
"id": "events:e5c069095e5c",
"chapter": "events",
"label": "event.composedPath()",
"signature": "`event.composedPath()`",
"apiSymbol": "event.composedPath",
"docsUrl": "https://nodejs.org/docs/v24.15.0/api/events.html#eventcomposedpath",
"nodeStability": {
"index": "2",
"level": 2,
"text": "Stable",
"inherited": true
},
"priority": "high",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:events",
"tests": []
}
}
},
{
"id": "events:85436cb5ea2b",
"chapter": "events",
"label": "event.initEvent(type[, bubbles[, cancelable]])",
"signature": "`event.initEvent(type[, bubbles[, cancelable]])`",
"apiSymbol": "event.initEvent",
"docsUrl": "https://nodejs.org/docs/v24.15.0/api/events.html#eventiniteventtype-bubbles-cancelable",
"nodeStability": {
"index": "3",
"level": 3,
"text": "Legacy: The WHATWG spec considers it deprecated and users shouldn't use it at all.",
"inherited": false
},
"priority": "low",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:events",
"tests": []
}
}
},
{
"id": "events:5848ab30b2e5",
"chapter": "events",
"label": "event.preventDefault()",
"signature": "`event.preventDefault()`",
"apiSymbol": "event.preventDefault",
"docsUrl": "https://nodejs.org/docs/v24.15.0/api/events.html#eventpreventdefault",
"nodeStability": {
"index": "2",
"level": 2,
"text": "Stable",
"inherited": true
},
"priority": "high",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:events",
"tests": []
}
}
},
{
"id": "events:c16743d778c4",
"chapter": "events",
"label": "event.stopImmediatePropagation()",
"signature": "`event.stopImmediatePropagation()`",
"apiSymbol": "event.stopImmediatePropagation",
"docsUrl": "https://nodejs.org/docs/v24.15.0/api/events.html#eventstopimmediatepropagation",
"nodeStability": {
"index": "2",
"level": 2,
"text": "Stable",
"inherited": true
},
"priority": "high",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:events",
"tests": []
}
}
},
{
"id": "events:9bab844aa1c2",
"chapter": "events",
"label": "event.stopPropagation()",
"signature": "`event.stopPropagation()`",
"apiSymbol": "event.stopPropagation",
"docsUrl": "https://nodejs.org/docs/v24.15.0/api/events.html#eventstoppropagation",
"nodeStability": {
"index": "2",
"level": 2,
"text": "Stable",
"inherited": true
},
"priority": "high",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:events",
"tests": []
}
}
},
{
"id": "events:abe0ea02a52c",
"chapter": "events",
"label": "bubbles",
"signature": "`bubbles` Type: {boolean} Always returns `false`.",
"apiSymbol": "bubbles",
"docsUrl": "https://nodejs.org/docs/v24.15.0/api/events.html#class-event",
"nodeStability": {
"index": "2",
"level": 2,
"text": "Stable",
"inherited": true
},
"priority": "high",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:events",
"tests": []
}
}
},
{
"id": "events:44e2dc361428",
"chapter": "events",
"label": "cancelBubble",
"signature": "`cancelBubble` Type: {boolean}",
"apiSymbol": "cancelBubble",
"docsUrl": "https://nodejs.org/docs/v24.15.0/api/events.html#class-event",
"nodeStability": {
"index": "3",
"level": 3,
"text": "Legacy: Use [`event.stopPropagation()`][] instead.",
"inherited": false
},
"priority": "low",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:events",
"tests": []
}
}
},
{
"id": "events:51ee094d86d3",
"chapter": "events",
"label": "cancelable",
"signature": "`cancelable` Type: {boolean} True if the event was created with the `cancelable` option.",
"apiSymbol": "cancelable",
"docsUrl": "https://nodejs.org/docs/v24.15.0/api/events.html#class-event",
"nodeStability": {
"index": "2",
"level": 2,
"text": "Stable",
"inherited": true
},
"priority": "high",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:events",
"tests": []
}
}
},
{
"id": "events:369be877d737",
"chapter": "events",
"label": "composed",
"signature": "`composed` Type: {boolean} Always returns `false`.",
"apiSymbol": "composed",
"docsUrl": "https://nodejs.org/docs/v24.15.0/api/events.html#class-event",
"nodeStability": {
"index": "2",
"level": 2,
"text": "Stable",
"inherited": true
},
"priority": "high",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:events",
"tests": []
}
}
},
{
"id": "events:4e0c88e53f49",
"chapter": "events",
"label": "currentTarget",
"signature": "`currentTarget` Type: {EventTarget} The `EventTarget` dispatching the event.",
"apiSymbol": "currentTarget",
"docsUrl": "https://nodejs.org/docs/v24.15.0/api/events.html#class-event",
"nodeStability": {
"index": "2",
"level": 2,
"text": "Stable",
"inherited": true
},
"priority": "high",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:events",
"tests": []
}
}
},
{
"id": "events:1e8421d3cc04",
"chapter": "events",
"label": "defaultPrevented",
"signature": "`defaultPrevented` Type: {boolean}",
"apiSymbol": "defaultPrevented",
"docsUrl": "https://nodejs.org/docs/v24.15.0/api/events.html#class-event",
"nodeStability": {
"index": "2",
"level": 2,
"text": "Stable",
"inherited": true
},
"priority": "high",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:events",
"tests": []
}
}
},
{
"id": "events:602700d21dcf",
"chapter": "events",
"label": "eventPhase",
"signature": "`eventPhase` Type: {number} Returns `0` while an event is not being dispatched, `2` while it is being dispatched.",
"apiSymbol": "eventPhase",
"docsUrl": "https://nodejs.org/docs/v24.15.0/api/events.html#class-event",
"nodeStability": {
"index": "2",
"level": 2,
"text": "Stable",
"inherited": true
},
"priority": "high",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:events",
"tests": []
}
}
},
{
"id": "events:4e83ca3c5e4c",
"chapter": "events",
"label": "isTrusted",
"signature": "`isTrusted` Type: {boolean}",
"apiSymbol": "isTrusted",
"docsUrl": "https://nodejs.org/docs/v24.15.0/api/events.html#class-event",
"nodeStability": {
"index": "2",
"level": 2,
"text": "Stable",
"inherited": true
},
"priority": "high",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:events",
"tests": []
}
}
},
{
"id": "events:100e8d286de2",
"chapter": "events",
"label": "returnValue",
"signature": "`returnValue` Type: {boolean} True if the event has not been canceled.",
"apiSymbol": "returnValue",
"docsUrl": "https://nodejs.org/docs/v24.15.0/api/events.html#class-event",
"nodeStability": {
"index": "3",
"level": 3,
"text": "Legacy: Use [`event.defaultPrevented`][] instead.",
"inherited": false
},
"priority": "low",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:events",
"tests": []
}
}
},
{
"id": "events:3d3da3858847",
"chapter": "events",
"label": "srcElement",
"signature": "`srcElement` Type: {EventTarget} The `EventTarget` dispatching the event.",
"apiSymbol": "srcElement",
"docsUrl": "https://nodejs.org/docs/v24.15.0/api/events.html#class-event",
"nodeStability": {
"index": "3",
"level": 3,
"text": "Legacy: Use [`event.target`][] instead.",
"inherited": false
},
"priority": "low",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:events",
"tests": []
}
}
},
{
"id": "events:fdfee26f8c22",
"chapter": "events",
"label": "target",
"signature": "`target` Type: {EventTarget} The `EventTarget` dispatching the event.",
"apiSymbol": "target",
"docsUrl": "https://nodejs.org/docs/v24.15.0/api/events.html#class-event",
"nodeStability": {
"index": "2",
"level": 2,
"text": "Stable",
"inherited": true
},
"priority": "high",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:events",
"tests": []
}
}
},
{
"id": "events:051e03ef86de",
"chapter": "events",
"label": "timeStamp",
"signature": "`timeStamp` Type: {number}",
"apiSymbol": "timeStamp",
"docsUrl": "https://nodejs.org/docs/v24.15.0/api/events.html#class-event",
"nodeStability": {
"index": "2",
"level": 2,
"text": "Stable",
"inherited": true
},
"priority": "high",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:events",
"tests": []
}
}
},
{
"id": "events:88b512802b39",
"chapter": "events",
"label": "type",
"signature": "`type` Type: {string}",
"apiSymbol": "type",
"docsUrl": "https://nodejs.org/docs/v24.15.0/api/events.html#class-event",
"nodeStability": {
"index": "2",
"level": 2,
"text": "Stable",
"inherited": true
},
"priority": "high",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:events",
"tests": []
"status": "partial",
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "compiler-feature:events.Event",
"tests": [
"tests/corpus/5130-event-target-dispatch.js",
"tests/corpus/5132-event-target-typed.ts"
]
}
}
},
@@ -23574,12 +23145,18 @@
"priority": "high",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:events",
"tests": []
"status": "partial",
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "compiler-feature:events.EventTarget",
"tests": [
"tests/corpus/5130-event-target-dispatch.js",
"tests/corpus/5131-event-target-listener-errors.js",
"tests/corpus/5132-event-target-typed.ts",
"tests/corpus/5133-abort-signal-event-target.ts",
"tests/corpus/5135-event-target-warnings.js"
]
}
}
},
@@ -23599,12 +23176,18 @@
"priority": "high",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:events",
"tests": []
"status": "partial",
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "compiler-feature:events.eventTarget.addEventListener",
"tests": [
"tests/corpus/5130-event-target-dispatch.js",
"tests/corpus/5131-event-target-listener-errors.js",
"tests/corpus/5132-event-target-typed.ts",
"tests/corpus/5133-abort-signal-event-target.ts",
"tests/corpus/5135-event-target-warnings.js"
]
}
}
},
@@ -23624,12 +23207,18 @@
"priority": "high",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:events",
"tests": []
"status": "partial",
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "compiler-feature:events.eventTarget.dispatchEvent",
"tests": [
"tests/corpus/5130-event-target-dispatch.js",
"tests/corpus/5131-event-target-listener-errors.js",
"tests/corpus/5132-event-target-typed.ts",
"tests/corpus/5133-abort-signal-event-target.ts",
"tests/corpus/5135-event-target-warnings.js"
]
}
}
},
@@ -23649,12 +23238,18 @@
"priority": "high",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:events",
"tests": []
"status": "partial",
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "compiler-feature:events.eventTarget.removeEventListener",
"tests": [
"tests/corpus/5130-event-target-dispatch.js",
"tests/corpus/5131-event-target-listener-errors.js",
"tests/corpus/5132-event-target-typed.ts",
"tests/corpus/5133-abort-signal-event-target.ts",
"tests/corpus/5135-event-target-warnings.js"
]
}
}
},
@@ -23674,12 +23269,15 @@
"priority": "high",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:events",
"tests": []
"status": "partial",
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "compiler-feature:events.CustomEvent",
"tests": [
"tests/corpus/5130-event-target-dispatch.js",
"tests/corpus/5132-event-target-typed.ts"
]
}
}
},
@@ -23699,12 +23297,15 @@
"priority": "high",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:events",
"tests": []
"status": "partial",
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "compiler-feature:events.detail",
"tests": [
"tests/corpus/5130-event-target-dispatch.js",
"tests/corpus/5132-event-target-typed.ts"
]
}
}
},
@@ -24837,12 +24438,14 @@
"priority": "high",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:events",
"tests": []
"status": "partial",
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "compiler-feature:events.events.addAbortListener",
"tests": [
"tests/corpus/5133-abort-signal-event-target.ts"
]
}
}
},
@@ -24887,12 +24490,14 @@
"priority": "high",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:events",
"tests": []
"status": "partial",
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "compiler-feature:events.events.errorMonitor",
"tests": [
"tests/corpus/5134-events-error-monitor.ts"
]
}
}
},
@@ -310,10 +310,10 @@
"entries": 84,
"apiEntries": 78,
"static": {
"supported": 0,
"partial": 22,
"supported": 18,
"partial": 34,
"refused": 0,
"not-implemented": 56,
"not-implemented": 26,
"by-design": 0,
"unreviewed": 0,
"not-applicable": 0
@@ -40005,7 +40005,13 @@
"evidence": "derived:descendants",
"tests": [
"tests/corpus/1677-emitter-listeners.ts",
"tests/corpus/2620-ee-override-once-order.ts"
"tests/corpus/2620-ee-override-once-order.ts",
"tests/corpus/5130-event-target-dispatch.js",
"tests/corpus/5131-event-target-listener-errors.js",
"tests/corpus/5132-event-target-typed.ts",
"tests/corpus/5133-abort-signal-event-target.ts",
"tests/corpus/5134-events-error-monitor.ts",
"tests/corpus/5135-event-target-warnings.js"
]
},
"anchorSource": "chapter"
@@ -40143,8 +40149,15 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:events"
"status": "partial",
"evidence": "compiler-feature:events.EventTarget",
"tests": [
"tests/corpus/5130-event-target-dispatch.js",
"tests/corpus/5131-event-target-listener-errors.js",
"tests/corpus/5132-event-target-typed.ts",
"tests/corpus/5133-abort-signal-event-target.ts",
"tests/corpus/5135-event-target-warnings.js"
]
},
"anchorSource": "exact"
},
@@ -40166,8 +40179,15 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:events"
"status": "partial",
"evidence": "compiler-feature:events.EventTarget",
"tests": [
"tests/corpus/5130-event-target-dispatch.js",
"tests/corpus/5131-event-target-listener-errors.js",
"tests/corpus/5132-event-target-typed.ts",
"tests/corpus/5133-abort-signal-event-target.ts",
"tests/corpus/5135-event-target-warnings.js"
]
},
"anchorSource": "exact"
},
@@ -40235,8 +40255,15 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:events"
"status": "partial",
"evidence": "compiler-feature:events.EventTarget",
"tests": [
"tests/corpus/5130-event-target-dispatch.js",
"tests/corpus/5131-event-target-listener-errors.js",
"tests/corpus/5132-event-target-typed.ts",
"tests/corpus/5133-abort-signal-event-target.ts",
"tests/corpus/5135-event-target-warnings.js"
]
},
"anchorSource": "exact"
},
@@ -40258,8 +40285,12 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:events"
"status": "partial",
"evidence": "compiler-feature:events.Event",
"tests": [
"tests/corpus/5130-event-target-dispatch.js",
"tests/corpus/5132-event-target-typed.ts"
]
},
"anchorSource": "exact"
},
@@ -40281,8 +40312,13 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:events"
"status": "supported",
"evidence": "compiler-feature:events.event.composedPath",
"tests": [
"tests/corpus/5130-event-target-dispatch.js",
"tests/corpus/5132-event-target-typed.ts",
"tests/corpus/5133-abort-signal-event-target.ts"
]
},
"anchorSource": "exact"
},
@@ -40304,8 +40340,13 @@
"inherited": false
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:events"
"status": "supported",
"evidence": "compiler-feature:events.event.initEvent",
"tests": [
"tests/corpus/5130-event-target-dispatch.js",
"tests/corpus/5132-event-target-typed.ts",
"tests/corpus/5133-abort-signal-event-target.ts"
]
},
"anchorSource": "exact"
},
@@ -40327,8 +40368,13 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:events"
"status": "supported",
"evidence": "compiler-feature:events.event.preventDefault",
"tests": [
"tests/corpus/5130-event-target-dispatch.js",
"tests/corpus/5132-event-target-typed.ts",
"tests/corpus/5133-abort-signal-event-target.ts"
]
},
"anchorSource": "exact"
},
@@ -40350,8 +40396,13 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:events"
"status": "supported",
"evidence": "compiler-feature:events.event.stopImmediatePropagation",
"tests": [
"tests/corpus/5130-event-target-dispatch.js",
"tests/corpus/5132-event-target-typed.ts",
"tests/corpus/5133-abort-signal-event-target.ts"
]
},
"anchorSource": "exact"
},
@@ -40373,8 +40424,13 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:events"
"status": "supported",
"evidence": "compiler-feature:events.event.stopPropagation",
"tests": [
"tests/corpus/5130-event-target-dispatch.js",
"tests/corpus/5132-event-target-typed.ts",
"tests/corpus/5133-abort-signal-event-target.ts"
]
},
"anchorSource": "exact"
},
@@ -40396,8 +40452,13 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:events"
"status": "supported",
"evidence": "compiler-feature:events.bubbles",
"tests": [
"tests/corpus/5130-event-target-dispatch.js",
"tests/corpus/5132-event-target-typed.ts",
"tests/corpus/5133-abort-signal-event-target.ts"
]
},
"anchorSource": "ancestor"
},
@@ -40419,8 +40480,13 @@
"inherited": false
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:events"
"status": "supported",
"evidence": "compiler-feature:events.cancelBubble",
"tests": [
"tests/corpus/5130-event-target-dispatch.js",
"tests/corpus/5132-event-target-typed.ts",
"tests/corpus/5133-abort-signal-event-target.ts"
]
},
"anchorSource": "ancestor"
},
@@ -40442,8 +40508,13 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:events"
"status": "supported",
"evidence": "compiler-feature:events.cancelable",
"tests": [
"tests/corpus/5130-event-target-dispatch.js",
"tests/corpus/5132-event-target-typed.ts",
"tests/corpus/5133-abort-signal-event-target.ts"
]
},
"anchorSource": "ancestor"
},
@@ -40465,8 +40536,13 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:events"
"status": "supported",
"evidence": "compiler-feature:events.composed",
"tests": [
"tests/corpus/5130-event-target-dispatch.js",
"tests/corpus/5132-event-target-typed.ts",
"tests/corpus/5133-abort-signal-event-target.ts"
]
},
"anchorSource": "ancestor"
},
@@ -40488,8 +40564,13 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:events"
"status": "supported",
"evidence": "compiler-feature:events.currentTarget",
"tests": [
"tests/corpus/5130-event-target-dispatch.js",
"tests/corpus/5132-event-target-typed.ts",
"tests/corpus/5133-abort-signal-event-target.ts"
]
},
"anchorSource": "ancestor"
},
@@ -40511,8 +40592,13 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:events"
"status": "supported",
"evidence": "compiler-feature:events.defaultPrevented",
"tests": [
"tests/corpus/5130-event-target-dispatch.js",
"tests/corpus/5132-event-target-typed.ts",
"tests/corpus/5133-abort-signal-event-target.ts"
]
},
"anchorSource": "ancestor"
},
@@ -40534,8 +40620,13 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:events"
"status": "supported",
"evidence": "compiler-feature:events.eventPhase",
"tests": [
"tests/corpus/5130-event-target-dispatch.js",
"tests/corpus/5132-event-target-typed.ts",
"tests/corpus/5133-abort-signal-event-target.ts"
]
},
"anchorSource": "ancestor"
},
@@ -40557,8 +40648,13 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:events"
"status": "supported",
"evidence": "compiler-feature:events.isTrusted",
"tests": [
"tests/corpus/5130-event-target-dispatch.js",
"tests/corpus/5132-event-target-typed.ts",
"tests/corpus/5133-abort-signal-event-target.ts"
]
},
"anchorSource": "ancestor"
},
@@ -40580,8 +40676,13 @@
"inherited": false
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:events"
"status": "supported",
"evidence": "compiler-feature:events.returnValue",
"tests": [
"tests/corpus/5130-event-target-dispatch.js",
"tests/corpus/5132-event-target-typed.ts",
"tests/corpus/5133-abort-signal-event-target.ts"
]
},
"anchorSource": "ancestor"
},
@@ -40603,8 +40704,13 @@
"inherited": false
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:events"
"status": "supported",
"evidence": "compiler-feature:events.srcElement",
"tests": [
"tests/corpus/5130-event-target-dispatch.js",
"tests/corpus/5132-event-target-typed.ts",
"tests/corpus/5133-abort-signal-event-target.ts"
]
},
"anchorSource": "ancestor"
},
@@ -40626,8 +40732,13 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:events"
"status": "supported",
"evidence": "compiler-feature:events.target",
"tests": [
"tests/corpus/5130-event-target-dispatch.js",
"tests/corpus/5132-event-target-typed.ts",
"tests/corpus/5133-abort-signal-event-target.ts"
]
},
"anchorSource": "ancestor"
},
@@ -40649,8 +40760,13 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:events"
"status": "supported",
"evidence": "compiler-feature:events.timeStamp",
"tests": [
"tests/corpus/5130-event-target-dispatch.js",
"tests/corpus/5132-event-target-typed.ts",
"tests/corpus/5133-abort-signal-event-target.ts"
]
},
"anchorSource": "ancestor"
},
@@ -40672,8 +40788,13 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:events"
"status": "supported",
"evidence": "compiler-feature:events.type",
"tests": [
"tests/corpus/5130-event-target-dispatch.js",
"tests/corpus/5132-event-target-typed.ts",
"tests/corpus/5133-abort-signal-event-target.ts"
]
},
"anchorSource": "ancestor"
},
@@ -40695,8 +40816,15 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:events"
"status": "partial",
"evidence": "compiler-feature:events.EventTarget",
"tests": [
"tests/corpus/5130-event-target-dispatch.js",
"tests/corpus/5131-event-target-listener-errors.js",
"tests/corpus/5132-event-target-typed.ts",
"tests/corpus/5133-abort-signal-event-target.ts",
"tests/corpus/5135-event-target-warnings.js"
]
},
"anchorSource": "exact"
},
@@ -40718,8 +40846,15 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:events"
"status": "partial",
"evidence": "compiler-feature:events.eventTarget.addEventListener",
"tests": [
"tests/corpus/5130-event-target-dispatch.js",
"tests/corpus/5131-event-target-listener-errors.js",
"tests/corpus/5132-event-target-typed.ts",
"tests/corpus/5133-abort-signal-event-target.ts",
"tests/corpus/5135-event-target-warnings.js"
]
},
"anchorSource": "exact"
},
@@ -40741,8 +40876,15 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:events"
"status": "partial",
"evidence": "compiler-feature:events.eventTarget.dispatchEvent",
"tests": [
"tests/corpus/5130-event-target-dispatch.js",
"tests/corpus/5131-event-target-listener-errors.js",
"tests/corpus/5132-event-target-typed.ts",
"tests/corpus/5133-abort-signal-event-target.ts",
"tests/corpus/5135-event-target-warnings.js"
]
},
"anchorSource": "exact"
},
@@ -40764,8 +40906,15 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:events"
"status": "partial",
"evidence": "compiler-feature:events.eventTarget.removeEventListener",
"tests": [
"tests/corpus/5130-event-target-dispatch.js",
"tests/corpus/5131-event-target-listener-errors.js",
"tests/corpus/5132-event-target-typed.ts",
"tests/corpus/5133-abort-signal-event-target.ts",
"tests/corpus/5135-event-target-warnings.js"
]
},
"anchorSource": "exact"
},
@@ -40787,8 +40936,12 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:events"
"status": "partial",
"evidence": "compiler-feature:events.CustomEvent",
"tests": [
"tests/corpus/5130-event-target-dispatch.js",
"tests/corpus/5132-event-target-typed.ts"
]
},
"anchorSource": "exact"
},
@@ -40810,8 +40963,12 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:events"
"status": "partial",
"evidence": "compiler-feature:events.detail",
"tests": [
"tests/corpus/5130-event-target-dispatch.js",
"tests/corpus/5132-event-target-typed.ts"
]
},
"anchorSource": "ancestor"
},
@@ -41906,8 +42063,11 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:events"
"status": "partial",
"evidence": "compiler-feature:events.events.addAbortListener",
"tests": [
"tests/corpus/5133-abort-signal-event-target.ts"
]
},
"anchorSource": "exact"
},
@@ -41952,8 +42112,11 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:events"
"status": "partial",
"evidence": "compiler-feature:events.events.errorMonitor",
"tests": [
"tests/corpus/5134-events-error-monitor.ts"
]
},
"anchorSource": "exact"
},
@@ -138,6 +138,12 @@
},
"features": [
{ "chapter": "errors", "parent": "Class: `SystemError`", "symbols": ["code", "errno", "syscall", "path", "dest"], "status": "partial", "evidence": ["tests/corpus/caught-system-error-fields.ts", "tests/fixtures/node-types/terminal-error-fields.ts", "tests/harness/project-config.test.ts"] },
{"chapter": "events", "symbols": ["EventTarget", "eventTarget.addEventListener", "eventTarget.removeEventListener", "eventTarget.dispatchEvent"], "status": "partial", "evidence": ["tests/corpus/5130-event-target-dispatch.js", "tests/corpus/5131-event-target-listener-errors.js", "tests/corpus/5132-event-target-typed.ts", "tests/corpus/5133-abort-signal-event-target.ts", "tests/corpus/5135-event-target-warnings.js"]},
{"chapter": "events", "symbols": ["Event"], "status": "partial", "evidence": ["tests/corpus/5130-event-target-dispatch.js", "tests/corpus/5132-event-target-typed.ts"]},
{"chapter": "events", "symbols": ["event.composedPath", "event.preventDefault", "event.stopImmediatePropagation", "event.stopPropagation", "event.initEvent", "bubbles", "cancelable", "cancelBubble", "composed", "currentTarget", "defaultPrevented", "eventPhase", "isTrusted", "returnValue", "srcElement", "target", "timeStamp", "type"], "status": "supported", "evidence": ["tests/corpus/5130-event-target-dispatch.js", "tests/corpus/5132-event-target-typed.ts", "tests/corpus/5133-abort-signal-event-target.ts"]},
{"chapter": "events", "symbols": ["CustomEvent", "detail"], "status": "partial", "evidence": ["tests/corpus/5130-event-target-dispatch.js", "tests/corpus/5132-event-target-typed.ts"]},
{"chapter": "events", "symbols": ["events.errorMonitor"], "status": "partial", "evidence": ["tests/corpus/5134-events-error-monitor.ts"]},
{"chapter": "events", "symbols": ["events.addAbortListener"], "status": "partial", "evidence": ["tests/corpus/5133-abort-signal-event-target.ts"]},
{"chapter": "worker_threads", "symbols": ["Worker"], "status": "partial", "evidence": ["tests/corpus/native-worker-messages.ts", "tests/corpus/native-worker-commonjs.cjs", "tests/corpus/native-worker-environment.ts", "tests/corpus/native-worker-roots/main.ts"]},
{"chapter": "worker_threads", "symbols": ["MessagePort", "parentPort", "worker_threads.workerData", "isMainThread", "threadId"], "status": "partial", "evidence": ["tests/corpus/native-worker-commonjs.cjs", "tests/corpus/native-worker-port-lifetime.ts", "tests/corpus/native-worker-references.ts"]},
{"chapter": "worker_threads", "symbols": ["worker.postMessage", "port.postMessage"], "status": "partial", "evidence": ["tests/corpus/native-worker-message-graphs.ts", "tests/corpus/native-worker-shared-views.ts", "tests/corpus/native-worker-clone-errors.ts", "tests/corpus/native-worker-delayed-messages.ts"]},
+14 -10
View File
@@ -4469,16 +4469,20 @@ declare module "events" {
class EventEmitter {
constructor();
static defaultMaxListeners: number;
on(eventName: string, listener: (...args: any[]) => void): this;
addListener(eventName: string, listener: (...args: any[]) => void): this;
once(eventName: string, listener: (...args: any[]) => void): this;
prependListener(eventName: string, listener: (...args: any[]) => void): this;
prependOnceListener(eventName: string, listener: (...args: any[]) => void): this;
off(eventName: string, listener: (...args: any[]) => void): this;
removeListener(eventName: string, listener: (...args: any[]) => void): this;
removeAllListeners(eventName?: string): this;
emit(eventName: string, ...args: any[]): boolean;
listenerCount(eventName: string, listener?: (...args: any[]) => void): number;
/* Listeners registered under this symbol observe 'error' emits first
* (an emit with no 'error' listener still throws). */
static readonly errorMonitor: unique symbol;
static addAbortListener(signal: AbortSignal, listener: (event: Event) => void): Disposable;
on(eventName: string | symbol, listener: (...args: any[]) => void): this;
addListener(eventName: string | symbol, listener: (...args: any[]) => void): this;
once(eventName: string | symbol, listener: (...args: any[]) => void): this;
prependListener(eventName: string | symbol, listener: (...args: any[]) => void): this;
prependOnceListener(eventName: string | symbol, listener: (...args: any[]) => void): this;
off(eventName: string | symbol, listener: (...args: any[]) => void): this;
removeListener(eventName: string | symbol, listener: (...args: any[]) => void): this;
removeAllListeners(eventName?: string | symbol): this;
emit(eventName: string | symbol, ...args: any[]): boolean;
listenerCount(eventName: string | symbol, listener?: (...args: any[]) => void): number;
/* Declared as zero-parameter closures so the RESULT is statically
* consumable (.length, indexing, identity): the lowering only admits
* these calls for events whose argument tuple is empty — a listener
@@ -13,6 +13,7 @@ export function executableLinkFeatures(mod: IrModule): NativeLinkFeatures {
textDecoderLegacy: features.legacyTextDecoder,
fileHandle: features.fileHandle,
fetch: features.fetch,
...(features.eventTarget ? { eventTarget: true } : {}),
zlib: features.zlib,
assert: features.assert,
inspect: features.inspect,
@@ -397,6 +397,8 @@ export function emitLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlValue {
case "buffer":
case "bytes":
case "tlsca":
case "event":
case "events":
return host.emitGenericLibCall(e);
case "test":
return emitTestLibCall(host, e);
@@ -665,6 +665,11 @@ export const LIB_FN_SYMS: Record<string, string> = {
"fetch.responseNew": "scr_fetch_response_new",
"fetch.input": "scr_fetch_input",
"abort.controllerNew": "scr_fetch_abort_controller_new",
"event.targetNew": "scr_event_target_new",
"event.new": "scr_event_new",
"event.customNew": "scr_custom_event_new",
"event.is": "scr_event_is",
"events.addAbortListener": "scr_events_add_abort_listener",
"abort.timeout": "scr_fetch_abort_timeout",
"abort.now": "scr_fetch_abort_now",
"abort.any": "scr_fetch_abort_any",
@@ -21,6 +21,7 @@ export interface NativeLinkFeatures {
textDecoderLegacy: boolean;
fileHandle: boolean;
fetch: boolean;
eventTarget?: boolean;
zlib: boolean;
assert: boolean;
inspect: boolean;
@@ -107,6 +107,11 @@ export interface CcOptions {
* reactions, AsyncLocalStorage, the unhandledRejection/warning
* process events. */
dynAsync?: boolean;
/** The program constructs EventTarget/Event/CustomEvent values or
* calls events.addAbortListener (the event.* libCalls), or uses the
* native fetch AbortSignal built on the same engine: compiles
* scr_event_target.c. */
eventTarget?: boolean;
/** The program uses the process-events surface (signal/exit listeners,
* stdin events, for-await over stdin — moduleUsesProcessEvents on the
* IR): compiles scr_events.c into the binary. Event-free binaries keep
@@ -486,6 +486,7 @@ export async function compileCInternal(
: []),
...(opts.dynInvoke || opts.workers || nativeFetch ? [rt(join(rtDir, "scr_dyn_invoke.c"))] : []),
...(opts.dc ? [rt(join(rtDir, "scr_dc.c"))] : []),
...(opts.eventTarget || nativeFetch ? [rt(join(rtDir, "scr_event_target.c"))] : []),
...(opts.dynAsync || opts.dynInvoke || opts.workers || opts.dc || opts.fileHandle || nativeFetch
? [rt(join(rtDir, "scr_async_dyn.c"))]
: []),
@@ -80,6 +80,7 @@ export interface RuntimePackManifest {
export interface RuntimeFeatureSet extends NativeLinkFeatures {
workers: boolean;
eventTarget: boolean;
nativeFetch: boolean;
netEffective: boolean;
httpEffective: boolean;
@@ -264,6 +265,7 @@ export function effectiveRuntimeFeatures(
return {
...features,
workers: features.workers === true,
eventTarget: features.eventTarget === true,
nativeFetch,
netEffective,
httpEffective,
@@ -54,6 +54,7 @@ export interface EarlyExecutableNativeFeatures {
textDecoderLegacy: boolean;
fileHandle: boolean;
fetch: boolean;
eventTarget?: boolean;
zlib: boolean;
assert: boolean;
inspect: boolean;
@@ -1,11 +1,50 @@
import * as ts from "../ts7/adapter.js";
import type { IrExpr, SrcLoc } from "../../ir/ir.js";
import { dynUndefinedExpr } from "../../ir/build.js";
import { DYN, type IrExpr, type SrcLoc } from "../../ir/ir.js";
import type { Lowerer } from "./lowerer.js";
import { lowerTimersMemberCall } from "./lower-timers.js";
import { lowerStreamModuleCall } from "./lower-stream.js";
import { lowerNodeModuleCall } from "./builtins/module-resolution.js";
import { builtinModuleFnOf, builtinFenceHintOf } from "./surfaces.js";
/** `events.addAbortListener(signal, listener)`: the runtime registers the
* once/resist-stop-propagation abort listener (or queues the call for an
* aborted signal) and answers the disposer; the Disposable is a fresh
* object carrying it under Symbol.dispose. */
export function lowerAddAbortListener(
lowerer: Lowerer,
expr: ts.CallExpression,
loc: SrcLoc,
): IrExpr {
const args = expr.arguments;
if (args.length > 2 || args.some(ts.isSpreadElement)) {
lowerer.noLowering(`addAbortListener with this argument list`, expr);
}
const values = [0, 1].map((i) =>
args[i] ? lowerer.lowerExprExpecting(args[i]!, DYN) : dynUndefinedExpr(loc),
);
const key: IrExpr = {
kind: "dynFrom",
value: {
kind: "libCall",
fn: "sym.wellKnown",
args: [{ kind: "strLit", value: "dispose", type: { kind: "string" }, loc }],
type: { kind: "symbol" },
loc,
},
type: DYN,
loc,
};
const dispose: IrExpr = {
kind: "libCall",
fn: "events.addAbortListener",
args: values,
type: DYN,
loc,
};
return { kind: "dynObjLit", fields: [{ key, value: dispose }], type: DYN, loc };
}
/** Named imports, namespace members and callable aliases share validation
* and dispatch. Resolve diagnostic provenance only if no handler accepts. */
export function lowerBuiltinCall(
@@ -18,6 +57,9 @@ export function lowerBuiltinCall(
if (bi.module === "child_process" && bi.member === "execFile") {
return lowerer.lowerExecFileCall(expr, loc);
}
if (bi.module === "events" && bi.member === "addAbortListener") {
return lowerAddAbortListener(lowerer, expr, loc);
}
if (bi.module === "timers") {
const timersServed = lowerTimersMemberCall(lowerer, expr, bi.member, loc);
if (timersServed) return timersServed;
@@ -124,3 +124,24 @@ export function lowerNumberStaticProperty(
if (value === undefined) return null;
return { kind: "numLit", value, type: F64, loc: locOf(expr) };
}
/** The Event phase constants (Event.NONE ... Event.BUBBLING_PHASE),
* which CustomEvent inherits through its constructor. */
const EVENT_PHASES: Record<string, number | undefined> = {
NONE: 0,
CAPTURING_PHASE: 1,
AT_TARGET: 2,
BUBBLING_PHASE: 3,
};
export function lowerEventStaticProperty(
lowerer: Lowerer,
expr: ts.PropertyAccessExpression,
): IrExpr | null {
const member =
lowerer.stdlibGlobalMember(expr, "Event") ?? lowerer.stdlibGlobalMember(expr, "CustomEvent");
if (member === null) return null;
const value = own(EVENT_PHASES, member);
if (value === undefined) return null;
return { kind: "numLit", value, type: F64, loc: locOf(expr) };
}
@@ -70,6 +70,7 @@ import {
import { STREAM_FORCED_EVENT_NAMES, streamForcedTuple, streamSidesOf } from "./lower-stream.js";
import { boolLit, strLit, varRef } from "../../ir/build.js";
import { tryLowerExpression } from "./expressions/try-lower-expression.js";
import { lowerAddAbortListener } from "./builtin-calls.js";
/** True when the class descends from (or is) the runtime emitter. */
export function emitterRooted(lowerer: Lowerer, info: ClassInfo | undefined | null): boolean {
@@ -185,14 +186,17 @@ function emitterEvents(lowerer: Lowerer): Map<string, EventSig> {
);
return sig;
};
// The two forced tuples (see the header comment).
table.set("error", {
tuple: [{ kind: "object", className: "%Error" }],
fromEmit: true,
conflict: null,
dynListener: false,
hasListener: false,
});
// The forced tuples (see the header comment); errorMonitor listeners
// receive the 'error' payload.
for (const name of ["error", ERROR_MONITOR_EVENT]) {
table.set(name, {
tuple: [{ kind: "object", className: "%Error" }],
fromEmit: true,
conflict: null,
dynListener: false,
hasListener: false,
});
}
table.set("newListener", {
tuple: [STRING],
fromEmit: true,
@@ -295,7 +299,12 @@ function emitterEvents(lowerer: Lowerer): Map<string, EventSig> {
}
const info = scannedEmitterInfo(lowerer, recvT);
if (!info) return;
if (!nameT.isStringLiteralType()) {
const literal = isErrorMonitorRef(lowerer, arg0)
? ERROR_MONITOR_EVENT
: nameT.isStringLiteralType()
? nameT.value
: null;
if (literal === null) {
const pattern = computedEventPatternOf(lowerer, arg0);
if (
pattern &&
@@ -306,8 +315,8 @@ function emitterEvents(lowerer: Lowerer): Map<string, EventSig> {
}
return;
}
const name = nameT.value;
if (META_EVENTS.has(name) || name === "error") return; // forced tuples
const name = literal;
if (META_EVENTS.has(name) || name === "error" || name === ERROR_MONITOR_EVENT) return; // forced tuples
// Stream receivers' runtime-emitted events carry PER-BASE forced
// tuples (lower-stream.ts) — their sites never join the program-
// global table, so a stream's 'data' cannot collide with a user
@@ -368,12 +377,69 @@ function computedPatternsOverlap(left: ComputedEventPattern, right: ComputedEven
);
}
/** The compile-time name for a literal-only operation, or a pointed fence
* (string-literal TYPE, so const bindings and literal unions of one
* member count — the staticHeaderKeyOf stance). */
function eventNameOf(lowerer: Lowerer, member: string, arg: ts.Expression): string {
/** The registry name of `events.errorMonitor`: a symbol in Node, so no
* string event can collide with it (the NUL never appears in a literal
* name the lowering admits as the same key — see literalEventName). */
export const ERROR_MONITOR_EVENT = "\u0000events.errorMonitor";
/** `errorMonitor` / `EventEmitter.errorMonitor` / `events.errorMonitor`:
* the events module's unique symbol, by declaration provenance. */
function isErrorMonitorRef(lowerer: Lowerer, arg: ts.Expression): boolean {
const isMonitorType = (t: ts.Type): boolean => {
if (!(t.flags & ts.TypeFlags.UniqueESSymbol)) return false;
const symbol = t.getSymbol();
return symbol?.name === "errorMonitor" && lowerer.isStdlibSymbol(symbol);
};
// The unique symbol TYPE names its declaration through any alias.
if (isMonitorType(lowerer.typeOf(arg))) return true;
// A const destructuring (`const { errorMonitor } = events`) widens the
// binding's type in JavaScript; its source property still names it.
if (!ts.isIdentifier(arg)) return false;
const symbol = lowerer.resolveValueSymbol(arg);
const declaration = symbol ? lowerer.checker.valueDeclarationOf(symbol) : undefined;
if (!declaration || !ts.isBindingElement(declaration)) return false;
const pattern = declaration.parent;
const variable = pattern?.parent;
if (
!pattern ||
!variable ||
!ts.isObjectBindingPattern(pattern) ||
!ts.isVariableDeclaration(variable) ||
!variable.initializer ||
!(ts.getCombinedNodeFlags(variable) & ts.NodeFlags.Const)
)
return false;
const key = declaration.propertyName ?? declaration.name;
if (!ts.isIdentifier(key)) return false;
const property = lowerer.checker.getPropertyOfType(
lowerer.typeOf(variable.initializer),
key.text,
);
return property !== undefined && isMonitorType(lowerer.checker.getTypeOfSymbol(property));
}
/** A compile-time event name: a string-literal TYPE (const bindings and
* literal unions of one member count — the staticHeaderKeyOf stance) or
* the errorMonitor symbol. */
function literalEventName(lowerer: Lowerer, arg: ts.Expression): string | null {
if (isErrorMonitorRef(lowerer, arg)) return ERROR_MONITOR_EVENT;
const t = lowerer.typeOf(arg);
if (t.isStringLiteralType()) return t.value;
if (!t.isStringLiteralType()) return null;
// A string spelling of the reserved key stays a separate event in Node.
if (t.value === ERROR_MONITOR_EVENT) {
lowerer.noLowering(
"an event name spelling the errorMonitor key",
arg,
"the runtime reserves this name for events.errorMonitor",
);
}
return t.value;
}
/** The compile-time name for a literal-only operation, or a pointed fence. */
function eventNameOf(lowerer: Lowerer, member: string, arg: ts.Expression): string {
const name = literalEventName(lowerer, arg);
if (name !== null) return name;
lowerer.noLowering(
`${member} with a non-literal event name`,
arg,
@@ -987,6 +1053,7 @@ export function lowerEmitterMethodCall(
superRecv === undefined &&
lowerer.checker.getConstructSignatures(lowerer.typeOf(access.expression)).length > 0
) {
if (member === "addAbortListener") return lowerAddAbortListener(lowerer, call, loc);
if (member === "setMaxListeners" && args.length === 1) {
if (lowerer.mapTypeOf(lowerer.typeOf(args[0]!))?.kind !== "f64") {
// Node's runtime ladder over the dyn value ("setMaxListeners" is
@@ -1080,7 +1147,7 @@ export function lowerEmitterMethodCall(
);
}
const registering = REGISTER_MEMBERS.has(member);
if (!lowerer.typeOf(args[0]!).isStringLiteralType()) {
if (literalEventName(lowerer, args[0]!) === null) {
computedEventPattern(lowerer, info, member, args[0]!);
const receiver = lowerReceiver();
const name = lowerer.lowerExprExpecting(args[0]!, STRING);
@@ -1230,8 +1297,7 @@ export function lowerEmitterMethodCall(
if (args.length === 0) {
lowerer.noLowering("emit without an event name", call);
}
const nameType = lowerer.typeOf(args[0]!);
if (!nameType.isStringLiteralType()) {
if (literalEventName(lowerer, args[0]!) === null) {
const pattern = computedEventPatternOf(lowerer, args[0]!);
const mayBeObserved =
pattern === null ||
@@ -1363,7 +1429,11 @@ export function lowerEmitterMethodCall(
}
const all = args.length === 0;
const receiver = lowerReceiver();
const name = all ? strLit("", loc) : lowerer.lowerExprExpecting(args[0]!, STRING);
const name = all
? strLit("", loc)
: isErrorMonitorRef(lowerer, args[0]!)
? strLit(ERROR_MONITOR_EVENT, loc)
: lowerer.lowerExprExpecting(args[0]!, STRING);
if (name.type.kind !== "string") {
lowerer.noLowering(
`removeAllListeners with a '${lowerer.fmt(name.type)}' event name`,
@@ -1384,7 +1454,9 @@ export function lowerEmitterMethodCall(
lowerer.noLowering(`listenerCount with ${args.length} arguments`, call);
}
const receiver = lowerReceiver();
const name = lowerer.lowerExprExpecting(args[0]!, STRING);
const name = isErrorMonitorRef(lowerer, args[0]!)
? strLit(ERROR_MONITOR_EVENT, loc)
: lowerer.lowerExprExpecting(args[0]!, STRING);
if (name.type.kind !== "string") {
lowerer.noLowering(`listenerCount with a '${lowerer.fmt(name.type)}' event name`, args[0]!);
}
@@ -1,4 +1,5 @@
import { generatorDrain } from "./iterator-adapters.js";
import { lowerEventStaticProperty } from "./builtins/numbers.js";
import { lowerUnionEquality, tagEqualityMayMissAlias } from "./strict-equality.js";
import { isOptionalProcessStreamProperty } from "./builtins/process.js";
import { lowerWorkerMetadata } from "./builtins/workers.js";
@@ -2147,6 +2148,7 @@ function lowerExprInner(lowerer: Lowerer, expr: ts.Expression): IrExpr {
lowerer.lowerJsonProperty(expr) ??
lowerer.lowerErrorCodeProperty(expr) ??
lowerer.lowerNumberStaticProperty(expr) ??
lowerEventStaticProperty(lowerer, expr) ??
lowerer.lowerMathProperty(expr) ??
lowerer.lowerIntrinsicProperty(expr) ??
// The _readableState/_writableState scalar READS (the suite's
@@ -11666,6 +11668,16 @@ export function lowerInstanceOf(lowerer: Lowerer, expr: ts.BinaryExpression, loc
const value = lowerer.coerceInto(expr.left, lowerer.lowerExpr(expr.left), DYN);
return { kind: "libCall", fn: "dyn.dataViewIs", args: [value], type: BOOL, loc };
}
{
const eventKind = ["Event", "CustomEvent", "EventTarget"].findIndex((name) =>
lowerer.isStdlibGlobal(expr.right, name),
);
if (eventKind >= 0) {
const value = lowerer.coerceInto(expr.left, lowerer.lowerExpr(expr.left), DYN);
const kind: IrExpr = { kind: "numLit", value: eventKind, type: F64, loc };
return { kind: "libCall", fn: "event.is", args: [value, kind], type: BOOL, loc };
}
}
if (lowerer.isStdlibGlobal(expr.right, "SharedArrayBuffer")) {
const value = lowerer.coerceInto(expr.left, lowerer.lowerExpr(expr.left), DYN);
return { kind: "libCall", fn: "sharedArrayBuffer.is", args: [value], type: BOOL, loc };
@@ -1771,7 +1771,7 @@ export function lowerStaticAbortSignalListenerCall(
const receiverTs = lowerer.checker.getBaseTypeOfLiteralType(lowerer.typeOf(access.expression));
const symbol = receiverTs.getAliasSymbol() ?? receiverTs.getSymbol();
if (
symbol?.name !== "AbortSignal" ||
(symbol?.name !== "AbortSignal" && symbol?.name !== "EventTarget") ||
!lowerer.checker
.declarationsOf(symbol)
.some((d) => ts.isInterfaceDeclaration(d) && lowerer.isStdlibFile(d.getSourceFile()))
@@ -1803,6 +1803,40 @@ export function lowerStaticAbortSignalListenerCall(
);
}
/** `new EventTarget()`, `new Event(type, options?)` and `new
* CustomEvent(type, options?)`: native handles built from the checked
* argument vector (Node validates it in the constructor). */
export function lowerEventTargetNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr | null {
const name = stdlibGlobalNameOf(lowerer, expr.expression);
if (name !== "EventTarget" && name !== "Event" && name !== "CustomEvent") return null;
const args = expr.arguments ?? [];
if (args.some(ts.isSpreadElement)) return null;
const loc = locOf(expr);
const values = args.map((arg) =>
ts.isObjectLiteralExpression(arg)
? lowerDynObjectLiteral(lowerer, arg)
: lowerer.lowerExprExpecting(arg, DYN),
);
if (name === "EventTarget") {
const result: IrExpr = { kind: "libCall", fn: "event.targetNew", args: [], type: DYN, loc };
if (values.length === 0) return result;
return {
kind: "seqExpr",
stmts: values.map((value): IrStmt => ({ kind: "exprStmt", expr: value, loc: value.loc })),
result,
type: DYN,
loc,
};
}
return {
kind: "libCall",
fn: name === "Event" ? "event.new" : "event.customNew",
args: [{ kind: "dynArrLit", elems: values, type: DYN, loc }],
type: DYN,
loc,
};
}
export function lowerAbortControllerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr | null {
if (!ts.isIdentifier(expr.expression) || expr.expression.text !== "AbortController") {
return null;
@@ -443,6 +443,7 @@ import {
refuseUnsupportedApi,
stdlibGlobalFnOf,
lowerAbortControllerNew,
lowerEventTargetNew,
lowerDynamicImportCall,
lowerFetchCall,
lowerFetchElementMethodCall,
@@ -10670,6 +10671,7 @@ export class Lowerer {
const builtin =
lowerLifetimeRegionNew(this, expr) ??
lowerAbortControllerNew(this, expr) ??
lowerEventTargetNew(this, expr) ??
lowerFetchWebNew(this, expr) ??
lowerResponseNew(this, expr) ??
lowerStaticReadableStreamNew(this, expr) ??
@@ -127,7 +127,12 @@ export const FETCH_AMBIENT_TYPES = [
"ResponseInit",
"RequestInit",
"Event",
"CustomEvent",
"EventTarget",
"AddEventListenerOptions",
"EventListenerOptions",
"EventInit",
"CustomEventInit",
"AbortController",
"AbortSignal",
"Headers",
@@ -2107,9 +2112,11 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null {
}
// The stdlib Console interface (the global console and node:console
// Console instances) is a native handle carried by the checked-dynamic
// representation and consumed by the console method lowerings.
// representation and consumed by the console method lowerings. The
// stdlib Disposable (events.addAbortListener's result) is a checked
// object carrying its Symbol.dispose method.
if (
psym?.name === "Console" &&
(psym?.name === "Console" || psym?.name === "Disposable") &&
checker
.declarationsOf(psym)
.some((d) => ts.isInterfaceDeclaration(d) && ctx.isStdlibFile(d.getSourceFile()))
+1
View File
@@ -507,6 +507,7 @@ async function compileExecutableNative(
textDecoderLegacy: features.textDecoderLegacy,
fileHandle: features.fileHandle,
fetch: features.fetch,
...(features.eventTarget ? { eventTarget: true } : {}),
zlib: features.zlib,
assert: features.assert,
inspect: features.inspect,
@@ -88,6 +88,9 @@ export const MAY_THROW_LIB_FNS: ReadonlySet<IrLibFn> = new Set([
"fetch.abortAny",
"abort.timeout",
"abort.any",
"event.new",
"event.customNew",
"events.addAbortListener",
"fetch.streamNew",
"fetch.streamFrom",
"num.toFixed",
@@ -64,6 +64,16 @@ export const LIB_FN_SIGS = defineLibFnSignatures({
"fetch.responseNew": { argTypes: [DYN, DYN], result: DYN },
"fetch.input": { argTypes: [DYN, DYN], result: { kind: "promise", inner: DYN } },
"abort.controllerNew": { argTypes: [], result: DYN },
/** EventTarget / Event / CustomEvent construction (scr_event_target.c):
* the Event constructors take the argument vector as one dyn array and
* throw Node's validation errors; `event.is` is `v instanceof Event`
* (kind 0), `CustomEvent` (1), `EventTarget` (2). addAbortListener
* answers the disposer (the Disposable's [Symbol.dispose]). */
"event.targetNew": { argTypes: [], result: DYN },
"event.new": { argTypes: [DYN], result: DYN },
"event.customNew": { argTypes: [DYN], result: DYN },
"event.is": { argTypes: [DYN, F64], result: BOOL },
"events.addAbortListener": { argTypes: [DYN, DYN], result: DYN },
"abort.timeout": { argTypes: [DYN], result: DYN },
"abort.now": { argTypes: [DYN], result: DYN },
"abort.any": { argTypes: [DYN], result: DYN },
@@ -9,6 +9,7 @@ export interface RuntimeFeatures {
legacyTextDecoder: boolean;
fileHandle: boolean;
fetch: boolean;
eventTarget: boolean;
processEvents: boolean;
emitter: boolean;
stream: boolean;
@@ -78,6 +79,7 @@ function scanRuntimeFeatures(mod: IrModule, stopAt?: keyof RuntimeFeatures): Run
legacyTextDecoder: false,
fileHandle: false,
fetch: false,
eventTarget: false,
processEvents: false,
emitter:
mod.workers === true || (mod.classes ?? []).some((c) => c.name === RUNTIME_EMITTER_CLASS),
@@ -124,6 +126,7 @@ function scanRuntimeFeatures(mod: IrModule, stopAt?: keyof RuntimeFeatures): Run
)
features.legacyTextDecoder = true;
if (fn.startsWith("fetch.")) features.fetch = true;
if (fn.startsWith("event.") || fn === "events.addAbortListener") features.eventTarget = true;
if (PROCESS_EVENT_LIB_FNS.has(fn)) features.processEvents = true;
if (fn.startsWith("emitter.")) features.emitter = true;
if (
@@ -142,6 +142,7 @@ export function moduleLibAsyncSurface(mod: IrModule): { surface: string; loc: Sr
[features.stream, "the node:stream surface"],
[features.tls, "the node:tls surface"],
[features.fetch, "fetch"],
[features.eventTarget, "the EventTarget surface"],
[features.nodeTest, "the node:test surface"],
[features.dynAsync, "the checked-dynamic async surface"],
[features.dc, "the diagnostics_channel surface"],
@@ -73,6 +73,7 @@ test.skipIf(process.platform !== "linux" && process.platform !== "darwin")(
systemLibraries: [],
features: {
workers: false,
eventTarget: false,
regex: true,
copying: false,
textDecoderLegacy: false,
@@ -0,0 +1,6 @@
{
"order": [
"<repo>/tests/corpus/5130-event-target-dispatch.js"
],
"diags": []
}
@@ -0,0 +1,6 @@
{
"order": [
"<repo>/tests/corpus/5131-event-target-listener-errors.js"
],
"diags": []
}
@@ -0,0 +1,6 @@
{
"order": [
"<repo>/tests/corpus/5132-event-target-typed.ts"
],
"diags": []
}
@@ -0,0 +1,6 @@
{
"order": [
"<repo>/tests/corpus/5133-abort-signal-event-target.ts"
],
"diags": []
}
@@ -0,0 +1,6 @@
{
"order": [
"<repo>/tests/corpus/5134-events-error-monitor.ts"
],
"diags": []
}
@@ -0,0 +1,6 @@
{
"order": [
"<repo>/tests/corpus/5135-event-target-warnings.js"
],
"diags": []
}
@@ -52,6 +52,7 @@ const OPTIONAL = [
["scr_console_native.c", "inspect"],
["scr_dyn_invoke.c", any("dynInvoke", "nativeFetch")],
["scr_dc.c", "dc"],
["scr_event_target.c", any("eventTarget", "nativeFetch")],
["scr_async_dyn.c", any("dynAsync", "dynInvoke", "dc", "fileHandle", "nativeFetch")],
["scr_zlib.c", "zlib"],
["scr_events.c", "events"],
@@ -3,10 +3,12 @@ import { fileURLToPath } from "node:url";
process.env.SCRIPTC_RUNTIME_PACK_ROOT = dirname(dirname(fileURLToPath(import.meta.url)));
process.env.SCRIPTC_RUNTIME_PACK_CONFIG = JSON.stringify({
platform: "wasi",
extraRuntimeSources: ["scr_abort.c"],
// AbortSignal is portable here and dispatches through the EventTarget unit.
extraRuntimeSources: ["scr_abort.c", "scr_event_target.c"],
runtimeDefines: ["_GNU_SOURCE", "_WASI_EMULATED_SIGNAL", "_WASI_EMULATED_PROCESS_CLOCKS"],
omitRuntimeSources: ["scr_child.c"],
omitOptionalSources: [
"scr_event_target.c",
"scr_loop_kqueue.c",
"scr_loop_epoll.c",
"scr_loop_wsapoll.c",
+1
View File
@@ -621,6 +621,7 @@ void scr_process_on_warning(ScrDyn *fn) {
scr_throw_error_msg_code(SCR_ERR_TYPE, msg, strlen(msg), "ERR_INVALID_ARG_TYPE");
return;
}
scr_warning_emit_hook = scr_emit_warning;
if (scr_nwarn == scr_warn_cap) {
scr_warn_cap = scr_warn_cap ? scr_warn_cap * 2 : 4;
scr_warn_listeners = realloc(scr_warn_listeners, scr_warn_cap * sizeof *scr_warn_listeners);
File diff suppressed because it is too large Load Diff
+59 -1
View File
@@ -322,8 +322,30 @@ static bool scr_ee_emit_va(ScrEmitter *em, ScrStr *name, ...);
/* Fires a meta event with the affected event's NAME as the argument
* (listeners are fenced to arity <= 1, so the listener-function argument
* Node passes second is never read). */
/* events.errorMonitor's registry key (a symbol in Node; the compiler
* spells it with a leading NUL no admitted literal name carries). */
static const char scr_ee_error_monitor[] = "\0events.errorMonitor";
#define SCR_EE_ERROR_MONITOR_LEN (sizeof scr_ee_error_monitor - 1)
static bool scr_ee_is_error_monitor(const ScrStr *name) {
return name->len == SCR_EE_ERROR_MONITOR_LEN &&
memcmp(name->data, scr_ee_error_monitor, SCR_EE_ERROR_MONITOR_LEN) == 0;
}
static void scr_ee_symbol_name_fence(const char *what) {
char message[160];
snprintf(message, sizeof message, "%s with an events.errorMonitor listener is not supported yet",
what);
scr_throw_error(SCR_ERR_ERROR, scr_str_new(message, strlen(message)));
}
static void scr_ee_emit_meta(ScrEmitter *em, const char *meta, ScrStr *name) {
if (!scr_ee_has(em->reg, meta)) return;
/* Node passes the symbol itself, which the string tuple cannot carry. */
if (scr_ee_is_error_monitor(name)) {
scr_ee_symbol_name_fence(meta);
return;
}
ScrStr *m = scr_str_new(meta, strlen(meta));
scr_ee_emit_va(em, m, name);
scr_str_release(m);
@@ -631,8 +653,26 @@ ScrEmitter *scr_emitter_remove_all(ScrEmitter *em, ScrStr *name, bool all) {
* still holds it (the shared fired flag — Node's wrapper.fired). A
* listener throw stops the pass (the exception stays pending for the
* caller). Returns whether the bucket had listeners. */
static bool scr_ee_emit_core(ScrEmitter *em, ScrStr *name, va_list *ap_template,
ScrDyn *const *flex_args, size_t flex_argc);
/* emit('error', ...) first emits the same arguments to errorMonitor. */
static void scr_ee_emit_error_monitor(ScrEmitter *em, va_list *ap_template,
ScrDyn *const *flex_args, size_t flex_argc) {
ScrEeBucket *b =
em->reg ? scr_ee_bucket_find(em->reg, scr_ee_error_monitor, SCR_EE_ERROR_MONITOR_LEN) : NULL;
if (!b || b->n == 0) return;
ScrStr *monitor = scr_str_new(scr_ee_error_monitor, SCR_EE_ERROR_MONITOR_LEN);
scr_ee_emit_core(em, monitor, ap_template, flex_args, flex_argc);
scr_str_release(monitor);
}
static bool scr_ee_emit_core(ScrEmitter *em, ScrStr *name, va_list *ap_template,
ScrDyn *const *flex_args, size_t flex_argc) {
if (name->len == 5 && memcmp(name->data, "error", 5) == 0) {
scr_ee_emit_error_monitor(em, ap_template, flex_args, flex_argc);
if (scr_exc_pending()) return true;
}
ScrEeBucket *b = em->reg ? scr_ee_bucket_find(em->reg, name->data, name->len) : NULL;
if (!b || b->n == 0) return false;
size_t n = b->n;
@@ -719,6 +759,8 @@ bool scr_emitter_emit_flex(ScrEmitter *em, ScrStr *name, ScrDyn *const *args, si
* errors. Keep this separate from the statically typed Error ABI above. */
bool scr_emitter_emit_error_flex(ScrEmitter *em, ScrDyn *error) {
if (!scr_ee_has(em->reg, "error")) {
scr_ee_emit_error_monitor(em, NULL, &error, 1);
if (scr_exc_pending()) return false;
scr_dyn_throw(scr_dyn_retain(error));
return false;
}
@@ -731,8 +773,17 @@ bool scr_emitter_emit_error_flex(ScrEmitter *em, ScrDyn *error) {
/* emit('error', err) — Node's special event: with no 'error' listener the
* payload THROWS (catchable; uncaught exits 1 with the error report).
* err is borrowed; the throw takes its own +1. */
static void scr_ee_emit_error_monitor_va(ScrEmitter *em, ...) {
va_list ap;
va_start(ap, em);
scr_ee_emit_error_monitor(em, &ap, NULL, 0);
va_end(ap);
}
bool scr_emitter_emit_error(ScrEmitter *em, ScrStr *name, ScrError *err) {
if (!scr_ee_has(em->reg, "error")) {
scr_ee_emit_error_monitor_va(em, err);
if (scr_exc_pending()) return false;
scr_throw_obj(scr_error_retain(err), &scr_error_retain_v, &scr_error_release_v,
scr_error_trace_arg());
return false;
@@ -781,7 +832,14 @@ ScrArr *scr_emitter_event_names(ScrEmitter *em) {
ScrArr *out = scr_arr_new(SCR_ELEM_STR, 0);
if (em->reg) {
for (ScrEeBucket *b = em->reg->head; b; b = b->next) {
if (b->n > 0) scr_arr_push_ref(out, scr_str_retain(b->name));
if (b->n == 0) continue;
/* Node lists the errorMonitor symbol, which a string[] cannot hold. */
if (scr_ee_is_error_monitor(b->name)) {
scr_arr_release(out);
scr_ee_symbol_name_fence("eventNames()");
return scr_arr_new(SCR_ELEM_STR, 0);
}
scr_arr_push_ref(out, scr_str_retain(b->name));
}
}
return out;
+28 -603
View File
@@ -26,7 +26,6 @@ static void sf_signal_install(void);
typedef struct SfTransfer SfTransfer;
typedef struct SfSignal SfSignal;
typedef struct SfSignalWatch SfSignalWatch;
typedef struct SfEvent SfEvent;
typedef struct SfStream SfStream;
typedef struct SfReader SfReader;
typedef struct SfResponse SfResponse;
@@ -53,17 +52,6 @@ struct SfTypedRef {
SfTypedRef *next;
};
typedef struct SfAbortListener {
ScrDyn *listener;
bool capture;
bool once;
size_t id;
SfSignal *target; /* weak; the target owns this listener */
SfSignal *option_signal; /* owned unless it is the target */
SfSignalWatch *signal_watch;
struct SfAbortListener *next;
} SfAbortListener;
struct SfSignalWatch {
SfSignal *source; /* weak; NULL after the source fires */
void *owner; /* owner keeps source alive */
@@ -80,26 +68,12 @@ struct SfSignal {
ScrDynHandleTag reason_self_tag;
bool reason_tracked;
SfSignal *reason_next;
ScrDyn *onabort;
size_t onabort_order;
SfAbortListener *listeners;
size_t next_listener_id;
ScrEventTargetCore core; /* listeners and the onabort attribute */
SfSignalWatch *watchers;
SfSignal **sources;
SfSignalWatch **source_watches;
size_t source_count;
double timer_id;
bool callbacks_tracked;
SfSignal *callbacks_next;
};
struct SfEvent {
size_t rc;
SfSignal *target;
double time_stamp;
bool dispatching;
bool stop_propagation;
bool stop_immediate;
};
struct SfReader {
@@ -248,7 +222,6 @@ static SfTransfer *sf_live;
/* Weak registries: owners unlink themselves on destruction. They let the
* fetch teardown sever opaque native→dyn edges before final cycle collection,
* including otherwise-untraceable callback/reason→handle backedges. */
static SfSignal *sf_callback_signals;
static SfSignal *sf_reason_signals;
#ifndef SCR_FETCH_SIGNAL_ONLY
static SfStream *sf_callback_streams;
@@ -340,26 +313,6 @@ static ScrDyn *sf_type_reason(const char *message, ScrError **error_out) {
/* ── AbortSignal ─────────────────────────────────────────────────── */
static void sf_signal_track_callbacks(SfSignal *s) {
if (s->callbacks_tracked) return;
s->callbacks_tracked = true;
s->callbacks_next = sf_callback_signals;
sf_callback_signals = s;
}
static void sf_signal_untrack_callbacks(SfSignal *s) {
if (!s->callbacks_tracked) return;
for (SfSignal **at = &sf_callback_signals; *at;
at = &(*at)->callbacks_next) {
if (*at == s) {
*at = s->callbacks_next;
break;
}
}
s->callbacks_tracked = false;
s->callbacks_next = NULL;
}
static void sf_signal_track_reason(SfSignal *s) {
if (s->reason_tracked) return;
s->reason_tracked = true;
@@ -414,6 +367,8 @@ static SfSignal *sf_signal_new(void) {
SfSignal *s = calloc(1, sizeof *s);
if (!s) sf_oom();
s->rc = 1;
/* AbortSignal sets kMaxEventTargetListeners to 0: no leak warning. */
scr_et_core_init(&s->core, 0, "AbortSignal");
return s;
}
@@ -437,32 +392,6 @@ static void sf_watch_free(SfSignalWatch *w) {
free(w);
}
static void sf_abort_listener_free(SfAbortListener *l) {
if (!l) return;
sf_watch_free(l->signal_watch);
if (l->option_signal && l->option_signal != l->target) {
sf_signal_release(l->option_signal);
}
scr_dyn_release(l->listener);
free(l);
}
static void sf_signal_drop_callbacks(SfSignal *s) {
SfAbortListener *listeners = s->listeners;
ScrDyn *onabort = s->onabort;
s->listeners = NULL; /* unlink before callback releases can re-enter */
s->onabort = NULL;
s->onabort_order = 0;
sf_signal_untrack_callbacks(s);
while (listeners) {
SfAbortListener *next = listeners->next;
listeners->next = NULL;
sf_abort_listener_free(listeners);
listeners = next;
}
scr_dyn_release(onabort);
}
static void sf_signal_release(SfSignal *s) {
if (!s || --s->rc > 0) return;
for (size_t i = 0; i < s->source_count; i++) {
@@ -471,7 +400,7 @@ static void sf_signal_release(SfSignal *s) {
}
free(s->source_watches);
free(s->sources);
sf_signal_drop_callbacks(s);
scr_et_core_drop(&s->core);
sf_signal_drop_reason(s);
free(s);
}
@@ -483,148 +412,6 @@ static void sf_signal_release_v(void *p) {
sf_signal_release((SfSignal *)p);
}
static SfEvent *sf_event_new(SfSignal *target) {
SfEvent *event = calloc(1, sizeof *event);
if (!event) sf_oom();
event->rc = 1;
event->target = sf_signal_retain(target);
event->time_stamp = scr_perf_now();
event->dispatching = true;
return event;
}
static SfEvent *sf_event_retain(SfEvent *event) {
event->rc++;
return event;
}
static void sf_event_release(SfEvent *event) {
if (!event || --event->rc > 0) return;
sf_signal_release(event->target);
free(event);
}
static void *sf_event_retain_v(void *ptr) {
return sf_event_retain((SfEvent *)ptr);
}
static void sf_event_release_v(void *ptr) {
sf_event_release((SfEvent *)ptr);
}
static void *sf_listener_error_retain_v(void *ptr) {
return scr_caught_retain((ScrCaught *)ptr);
}
static void sf_listener_error_release_v(void *ptr) {
scr_caught_release((ScrCaught *)ptr);
}
static void sf_listener_error_tick(ScrClosure *cb) {
ScrCaught *caught = scr_box_get_ref(cb->caps[0]);
scr_rethrow(caught);
scr_caught_release(caught);
}
/*
* EventTarget reports listener exceptions as uncaught next-tick failures;
* dispatchEvent itself keeps running listeners and returns its boolean.
* Queue every failure separately so an uncaughtException handler that
* recovers from one still observes later listener failures in order.
*/
static void sf_defer_listener_error(ScrCaught *caught) {
ScrClosure *cb =
scr_closure_new((void *)&sf_listener_error_tick, 1);
cb->caps[0] = scr_box_new_obj(
&sf_listener_error_retain_v, &sf_listener_error_release_v, NULL);
scr_box_set_ref(cb->caps[0], caught); /* moves */
scr_next_tick(cb);
}
static ScrDyn *sf_event_invoke(void *ptr, ScrDyn *self,
const char *method, ScrDyn *const *args,
size_t argc, const char *what) {
SfEvent *event = ptr;
(void)self;
(void)args;
(void)argc;
if (strcmp(method, "preventDefault") == 0) {
/* Abort events are not cancelable. */
return scr_dyn_retain(scr_dyn_undefined());
}
if (strcmp(method, "stopPropagation") == 0) {
event->stop_propagation = true;
return scr_dyn_retain(scr_dyn_undefined());
}
if (strcmp(method, "stopImmediatePropagation") == 0) {
event->stop_propagation = true;
event->stop_immediate = true;
return scr_dyn_retain(scr_dyn_undefined());
}
if (strcmp(method, "composedPath") == 0) {
ScrDyn *path = scr_dyn_new_arr();
if (event->dispatching) {
scr_dyn_arr_push(
path,
scr_dyn_new_handle(event->target, SCR_DYNH_ABORT_SIGNAL));
}
return path;
}
sf_not_function(what);
return NULL;
}
static ScrDyn *sf_event_get(void *ptr, const char *key, size_t len) {
SfEvent *event = ptr;
if (sf_name(key, len, "type")) {
ScrStr *type = scr_str_new("abort", 5);
ScrDyn *out = scr_dyn_new_str(type);
scr_str_release(type);
return out;
}
if (sf_name(key, len, "bubbles") ||
sf_name(key, len, "cancelable") ||
sf_name(key, len, "composed") ||
sf_name(key, len, "defaultPrevented")) {
return scr_dyn_new_bool(false);
}
if (sf_name(key, len, "eventPhase")) {
return scr_dyn_new_num(event->dispatching ? 2 : 0);
}
if (sf_name(key, len, "target") ||
sf_name(key, len, "srcElement")) {
return scr_dyn_new_handle(event->target, SCR_DYNH_ABORT_SIGNAL);
}
if (sf_name(key, len, "currentTarget")) {
return event->dispatching
? scr_dyn_new_handle(event->target, SCR_DYNH_ABORT_SIGNAL)
: scr_dyn_new_null();
}
if (sf_name(key, len, "isTrusted")) return scr_dyn_new_bool(true);
if (sf_name(key, len, "timeStamp")) {
return scr_dyn_new_num(event->time_stamp);
}
if (sf_name(key, len, "cancelBubble")) {
return scr_dyn_new_bool(event->stop_propagation);
}
if (sf_name(key, len, "returnValue")) return scr_dyn_new_bool(true);
return NULL;
}
static bool sf_event_set(void *ptr, const char *key, size_t len,
const ScrDyn *value) {
SfEvent *event = ptr;
if (sf_name(key, len, "cancelBubble")) {
if (scr_dyn_truthy(value)) event->stop_propagation = true;
return true;
}
if (sf_name(key, len, "returnValue")) {
/* Setting false only prevents a cancelable event's default action. */
return true;
}
return false;
}
static SfSignalWatch *sf_signal_watch(SfSignal *source, void *owner,
void (*fire)(SfSignalWatch *,
SfSignal *)) {
@@ -638,199 +425,12 @@ static SfSignalWatch *sf_signal_watch(SfSignal *source, void *owner,
return w;
}
static void sf_listener_signal_abort(SfSignalWatch *w,
SfSignal *source) {
(void)source;
SfAbortListener *listener = w->owner;
SfSignal *target = listener->target;
for (SfAbortListener **at = &target->listeners; *at;
at = &(*at)->next) {
if (*at == listener) {
*at = listener->next;
sf_abort_listener_free(listener);
return;
}
}
sf_watch_free(w);
}
static void sf_signal_dispatch_listeners(SfSignal *s, ScrDyn *provided_event) {
SfEvent *event_state;
ScrDyn *event;
if (provided_event) {
event = scr_dyn_retain(provided_event);
event_state = (SfEvent *)provided_event->v.handle.ptr;
SfSignal *old_target = event_state->target;
event_state->target = sf_signal_retain(s);
sf_signal_release(old_target);
event_state->dispatching = true;
} else {
event_state = sf_event_new(s);
event = scr_dyn_new_handle(event_state, SCR_DYNH_EVENT);
sf_event_release(event_state);
}
/*
* EventTarget dispatch snapshots the handlers present at the start:
* additions wait for the next event, removals before a listener's turn
* suppress it, and a listener may remove itself safely. `onabort` occupies
* the position where it was first assigned, just like an event-handler
* attribute in the DOM. Holding raw list nodes across the user callback is
* not safe because removeEventListener frees those nodes.
*/
size_t count = s->onabort && s->onabort->kind == SCR_DYN_FUNC ? 1 : 0;
for (SfAbortListener *l = s->listeners; l; l = l->next) count++;
typedef struct {
ScrDyn *listener;
bool once;
bool attribute;
size_t id;
} SfAbortDispatch;
SfAbortDispatch *dispatch =
count ? calloc(count, sizeof *dispatch) : NULL;
if (count && !dispatch) sf_oom();
size_t i = 0;
if (s->onabort && s->onabort->kind == SCR_DYN_FUNC) {
dispatch[i].listener = scr_dyn_retain(s->onabort);
dispatch[i].attribute = true;
dispatch[i].id = s->onabort_order;
i++;
}
for (SfAbortListener *l = s->listeners; l; l = l->next) {
dispatch[i].listener = scr_dyn_retain(l->listener);
dispatch[i].once = l->once;
dispatch[i].id = l->id;
i++;
}
for (i = 1; i < count; i++) {
SfAbortDispatch entry = dispatch[i];
size_t j = i;
while (j > 0 && dispatch[j - 1].id > entry.id) {
dispatch[j] = dispatch[j - 1];
j--;
}
dispatch[j] = entry;
}
for (i = 0; i < count && !event_state->stop_immediate; i++) {
if (dispatch[i].attribute) {
if (!s->onabort || s->onabort_order != dispatch[i].id) {
scr_dyn_release(dispatch[i].listener);
continue;
}
/*
* Assignment replaces the handler without moving its registration
* position. Invoke the current value if an earlier callback replaced
* it during this dispatch.
*/
ScrDyn *handler = scr_dyn_retain(s->onabort);
ScrDyn *args[1] = {event};
scr_dyn_this_push(s, SCR_DYNH_ABORT_SIGNAL);
ScrDyn *r = scr_dyn_call(handler, args, 1, "signal.onabort");
scr_dyn_this_pop();
scr_dyn_release(r);
scr_dyn_release(handler);
scr_dyn_release(dispatch[i].listener);
if (scr_exc_pending()) {
sf_defer_listener_error(scr_exc_take());
}
if (event_state->stop_immediate) {
i++;
break;
}
continue;
}
SfAbortListener **at = &s->listeners;
while (*at && (*at)->id != dispatch[i].id) {
at = &(*at)->next;
}
if (!*at) {
scr_dyn_release(dispatch[i].listener);
continue;
}
if (dispatch[i].once) {
SfAbortListener *l = *at;
*at = l->next;
sf_abort_listener_free(l);
}
ScrDyn *callable = dispatch[i].listener;
ScrDyn *current_listener = NULL;
ScrDyn *owned_handle = NULL;
ScrDyn *this_listener = NULL;
if (callable->kind == SCR_DYN_OBJ) {
/*
* EventListener is a Web IDL callback interface: handleEvent is read
* when the callback runs, not when it is registered. Typed records
* normally cross into dyn as value snapshots, so explicitly refresh
* an identity-carrying listener from its retained source here.
*/
current_listener =
callable->v.obj.source_identity && callable->v.obj.source_access
? callable->v.obj.source_access(
callable->v.obj.source_identity, true)
: scr_dyn_retain(callable);
this_listener = current_listener;
const ScrDyn *handle =
scr_dyn_obj_get(current_listener, "handleEvent", 11);
if (!handle) {
scr_dyn_release(current_listener);
scr_dyn_release(dispatch[i].listener);
continue;
}
callable = (ScrDyn *)handle;
} else if (callable->kind == SCR_DYN_TYPED_REF) {
/* A live static record must remain the EventListener receiver so
* handleEvent mutations commit to the original source. Arrays and
* byte views are valid Web IDL objects but have no handleEvent in
* the statically modeled surface, so dispatching them is a no-op. */
current_listener = scr_dyn_typed_ref_materialize(callable);
if (current_listener->kind != SCR_DYN_OBJ) {
scr_dyn_release(current_listener);
scr_dyn_release(dispatch[i].listener);
continue;
}
const ScrDyn *handle =
scr_dyn_obj_get(current_listener, "handleEvent", 11);
if (!handle) {
scr_dyn_release(current_listener);
scr_dyn_release(dispatch[i].listener);
continue;
}
this_listener = callable;
callable = (ScrDyn *)handle;
} else if (callable->kind != SCR_DYN_FUNC) {
/* Web IDL accepts every object as an EventListener. Native arrays,
* byte views, handles, and promises cannot carry an expando
* handleEvent in the static runtime, so a missing method is a no-op. */
scr_dyn_release(dispatch[i].listener);
continue;
}
ScrDyn *args[1] = {event};
if (this_listener) {
scr_dyn_this_push_dyn(this_listener);
} else {
scr_dyn_this_push(s, SCR_DYNH_ABORT_SIGNAL);
}
ScrDyn *r =
scr_dyn_call(callable, args, 1, "abort listener");
scr_dyn_this_pop();
scr_dyn_release(r);
scr_dyn_release(owned_handle);
scr_dyn_release(current_listener);
scr_dyn_release(dispatch[i].listener);
if (scr_exc_pending()) {
sf_defer_listener_error(scr_exc_take());
}
if (event_state->stop_immediate) {
i++;
break;
}
}
while (i < count) {
scr_dyn_release(dispatch[i].listener);
i++;
}
event_state->dispatching = false;
free(dispatch);
/* runAbort: a trusted 'abort' Event through the signal's EventTarget. */
static void sf_signal_dispatch_abort(SfSignal *s) {
ScrDyn *event = scr_event_new_trusted("abort");
ScrDyn *self = scr_dyn_new_handle(s, SCR_DYNH_ABORT_SIGNAL);
scr_et_dispatch(&s->core, self, event);
scr_dyn_release(self);
scr_dyn_release(event);
}
@@ -855,7 +455,7 @@ static void sf_signal_abort_full(SfSignal *s, ScrDyn *reason,
w->source = NULL;
w->fire(w, s); /* the owner may free w */
}
sf_signal_dispatch_listeners(s, NULL);
sf_signal_dispatch_abort(s);
}
static void sf_signal_abort_default(SfSignal *s, bool timeout) {
@@ -879,6 +479,14 @@ static SfSignal *sf_signal_of(const ScrDyn *d, const char *where) {
return (SfSignal *)d->v.handle.ptr;
}
/* options.signal for the EventTarget engine. */
static ScrEventTargetCore *sf_signal_core_of(const ScrDyn *d, bool *aborted) {
if (!d || d->kind != SCR_DYN_HANDLE || d->v.handle.tag != SCR_DYNH_ABORT_SIGNAL) return NULL;
SfSignal *s = (SfSignal *)d->v.handle.ptr;
*aborted = s->aborted;
return &s->core;
}
static void sf_signal_timer_fire(ScrClosure *cb) {
SfSignal *s = (SfSignal *)scr_box_get_ref(cb->caps[0]);
if (!s) return;
@@ -1031,63 +639,6 @@ static ScrDyn *sf_abort_controller_get(
return NULL;
}
static bool sf_signal_listener_options(
ScrDyn *const *args, size_t argc, bool *capture, bool *once,
SfSignal **option_signal) {
*capture = false;
*once = false;
if (option_signal) *option_signal = NULL;
if (argc < 3) return true;
if (args[2]->kind == SCR_DYN_BOOL) {
/* EventTarget's boolean overload is `useCapture`, never `once`. */
*capture = args[2]->v.b;
return true;
}
if (args[2]->kind != SCR_DYN_OBJ) return true;
const ScrDyn *capture_value =
scr_dyn_obj_get(args[2], "capture", 7);
const ScrDyn *once_value = scr_dyn_obj_get(args[2], "once", 4);
*capture = capture_value && scr_dyn_truthy(capture_value);
*once = once_value && scr_dyn_truthy(once_value);
if (option_signal) {
const ScrDyn *signal_value =
scr_dyn_obj_get(args[2], "signal", 6);
if (signal_value && signal_value->kind != SCR_DYN_UNDEF) {
*option_signal = sf_signal_of(
signal_value,
"AddEventListenerOptions.signal must be an AbortSignal");
if (!*option_signal) return false;
}
}
return true;
}
static bool sf_abort_listener_value(const ScrDyn *listener) {
/* Web IDL callback interfaces accept any object. handleEvent is looked up
* when the event is dispatched, where a non-callable value reports an
* asynchronous listener error and a missing value is a no-op. */
switch (listener->kind) {
case SCR_DYN_FUNC:
case SCR_DYN_OBJ:
case SCR_DYN_ARR:
case SCR_DYN_BYTES:
case SCR_DYN_HANDLE:
case SCR_DYN_PROMISE:
return true;
case SCR_DYN_TYPED_REF:
return scr_dyn_ref_typeof_is(listener, "function") ||
scr_dyn_ref_typeof_is(listener, "object");
default:
return false;
}
}
static bool sf_abort_listener_equal(
const ScrDyn *a, const ScrDyn *b) {
return scr_dyn_strict_eq(a, b) || scr_dyn_obj_same_source(a, b);
}
static ScrDyn *sf_signal_invoke(void *ptr, ScrDyn *self, const char *method,
ScrDyn *const *args, size_t argc,
const char *what) {
@@ -1101,113 +652,11 @@ static ScrDyn *sf_signal_invoke(void *ptr, ScrDyn *self, const char *method,
}
return scr_dyn_retain(scr_dyn_undefined());
}
if (strcmp(method, "addEventListener") == 0) {
if (argc < 2) {
sf_type_error("AbortSignal.addEventListener requires an event name");
return NULL;
}
ScrStr *event_name = scr_dyn_string_coerce_js(args[0]);
if (!event_name) return NULL;
bool is_abort_event =
sf_name(event_name->data, event_name->len, "abort");
scr_str_release(event_name);
/* Web IDL's nullable EventListener argument: null is a no-op. */
if (args[1]->kind == SCR_DYN_NULL ||
args[1]->kind == SCR_DYN_UNDEF) {
return scr_dyn_retain(scr_dyn_undefined());
}
if (!sf_abort_listener_value(args[1])) {
sf_type_error(
"AbortSignal.addEventListener listener must be a function, "
"an object with handleEvent, or null");
return NULL;
}
bool capture = false;
bool once = false;
SfSignal *option_signal = NULL;
if (!sf_signal_listener_options(
args, argc, &capture, &once, &option_signal)) {
return NULL;
}
/* AbortSignal is an EventTarget: listeners for other event names are
* valid registrations even though this target only dispatches abort. */
if (!is_abort_event) {
return scr_dyn_retain(scr_dyn_undefined());
}
if (option_signal && option_signal->aborted) {
return scr_dyn_retain(scr_dyn_undefined());
}
SfAbortListener **tail = &s->listeners;
while (*tail) {
if ((*tail)->capture == capture &&
sf_abort_listener_equal((*tail)->listener, args[1])) {
return scr_dyn_retain(scr_dyn_undefined());
}
tail = &(*tail)->next;
}
SfAbortListener *l = calloc(1, sizeof *l);
if (!l) sf_oom();
l->listener = scr_dyn_retain(args[1]);
l->capture = capture;
l->once = once;
l->id = ++s->next_listener_id;
l->target = s;
l->option_signal =
option_signal && option_signal != s
? sf_signal_retain(option_signal)
: option_signal;
*tail = l;
if (option_signal) {
l->signal_watch =
sf_signal_watch(option_signal, l, &sf_listener_signal_abort);
}
sf_signal_track_callbacks(s);
return scr_dyn_retain(scr_dyn_undefined());
}
if (strcmp(method, "removeEventListener") == 0) {
if (argc < 2) {
sf_type_error(
"AbortSignal.removeEventListener requires an event name and listener");
return NULL;
}
ScrStr *event_name = scr_dyn_string_coerce_js(args[0]);
if (!event_name) return NULL;
bool is_abort_event =
sf_name(event_name->data, event_name->len, "abort");
scr_str_release(event_name);
if (is_abort_event) {
bool capture = false;
bool once = false;
(void)sf_signal_listener_options(
args, argc, &capture, &once, NULL);
(void)once;
for (SfAbortListener **at = &s->listeners; *at; at = &(*at)->next) {
if ((*at)->capture == capture &&
sf_abort_listener_equal((*at)->listener, args[1])) {
SfAbortListener *l = *at;
*at = l->next;
sf_abort_listener_free(l);
break;
}
}
}
return scr_dyn_retain(scr_dyn_undefined());
}
if (strcmp(method, "dispatchEvent") == 0) {
if (argc != 1 || args[0]->kind != SCR_DYN_HANDLE ||
args[0]->v.handle.tag != SCR_DYNH_EVENT) {
sf_type_error("AbortSignal.dispatchEvent requires an Event");
return NULL;
}
SfEvent *event = (SfEvent *)args[0]->v.handle.ptr;
if (event->dispatching) {
sf_type_error("The event is already being dispatched");
return NULL;
}
sf_signal_dispatch_listeners(s, args[0]);
if (scr_exc_pending()) return NULL;
return scr_dyn_new_bool(true);
}
if (strcmp(method, "addEventListener") == 0) return scr_et_add_listener(&s->core, args, argc);
if (strcmp(method, "removeEventListener") == 0)
return scr_et_remove_listener(&s->core, args, argc);
if (strcmp(method, "dispatchEvent") == 0)
return scr_et_dispatch_event(&s->core, self, args, argc);
(void)self;
sf_not_function(what);
return NULL;
@@ -1220,9 +669,7 @@ static ScrDyn *sf_signal_get(void *ptr, const char *key, size_t len) {
return s->aborted ? sf_signal_reason_ref(s)
: scr_dyn_retain(scr_dyn_undefined());
}
if (sf_name(key, len, "onabort")) {
return s->onabort ? scr_dyn_retain(s->onabort) : scr_dyn_new_null();
}
if (sf_name(key, len, "onabort")) return scr_et_handler_get(&s->core, "abort");
return NULL;
}
@@ -1230,19 +677,7 @@ static bool sf_signal_set(void *ptr, const char *key, size_t len,
const ScrDyn *value) {
SfSignal *s = ptr;
if (!sf_name(key, len, "onabort")) return false;
bool had_handler = s->onabort && s->onabort->kind == SCR_DYN_FUNC;
scr_dyn_release(s->onabort);
if (value->kind != SCR_DYN_NULL && value->kind != SCR_DYN_UNDEF) {
s->onabort = scr_dyn_retain((ScrDyn *)value);
} else {
s->onabort = NULL;
}
if (value->kind == SCR_DYN_FUNC) {
if (!had_handler) s->onabort_order = ++s->next_listener_id;
sf_signal_track_callbacks(s);
} else {
s->onabort_order = 0;
}
scr_et_handler_set(&s->core, "abort", value);
return true;
}
@@ -5627,9 +5062,6 @@ static const ScrDynHandleOps sf_response_ops = {
static const ScrDynHandleOps sf_headers_ops = {
"Headers", &sf_headers_retain_v, &sf_headers_release_v,
&sf_headers_invoke, &sf_headers_get, &sf_no_set, NULL};
static const ScrDynHandleOps sf_event_ops = {
"Event", &sf_event_retain_v, &sf_event_release_v,
&sf_event_invoke, &sf_event_get, &sf_event_set, NULL};
static void sf_teardown(void) {
while (sf_live) {
@@ -5647,9 +5079,6 @@ static void sf_teardown(void) {
while (sf_callback_streams) {
sf_stream_drop_source_callbacks(sf_callback_streams, true);
}
while (sf_callback_signals) {
sf_signal_drop_callbacks(sf_callback_signals);
}
while (sf_reason_signals) {
sf_signal_drop_reason(sf_reason_signals);
}
@@ -5673,7 +5102,7 @@ void scr_fetch_install(void) {
scr_dyn_handle_install(SCR_DYNH_FETCH_REQUEST, &sf_request_ops);
scr_dyn_handle_install(SCR_DYNH_FETCH_RESPONSE, &sf_response_ops);
scr_dyn_handle_install(SCR_DYNH_FETCH_HEADERS, &sf_headers_ops);
scr_dyn_handle_install(SCR_DYNH_EVENT, &sf_event_ops);
scr_et_signal_core_fn = sf_signal_core_of;
scr_atexit(sf_teardown);
}
@@ -5689,11 +5118,7 @@ static const ScrDynHandleOps sf_signal_ops = {
static const ScrDynHandleOps sf_abort_controller_ops = {
"AbortController", &sf_signal_retain_v, &sf_signal_release_v,
&sf_abort_controller_invoke, &sf_abort_controller_get, &sf_no_set, NULL };
static const ScrDynHandleOps sf_event_ops = {
"Event", &sf_event_retain_v, &sf_event_release_v,
&sf_event_invoke, &sf_event_get, &sf_event_set, NULL };
static void sf_signal_teardown(void) {
while (sf_callback_signals) sf_signal_drop_callbacks(sf_callback_signals);
while (sf_reason_signals) sf_signal_drop_reason(sf_reason_signals);
}
static void sf_signal_install(void) {
@@ -5702,7 +5127,7 @@ static void sf_signal_install(void) {
installed = true;
scr_dyn_handle_install(SCR_DYNH_ABORT_SIGNAL, &sf_signal_ops);
scr_dyn_handle_install(SCR_DYNH_ABORT_CONTROLLER, &sf_abort_controller_ops);
scr_dyn_handle_install(SCR_DYNH_EVENT, &sf_event_ops);
scr_et_signal_core_fn = sf_signal_core_of;
scr_atexit(sf_signal_teardown);
}
+15
View File
@@ -967,6 +967,21 @@ ScrStr *scr_insp_dyn(ScrDyn *d, double recurse, double depth) {
d->v.handle.tag == SCR_DYNH_SHARED_ARRAY_BUFFER) {
return scr_insp_array_buffer(d->v.handle.ptr, scr_dyn_handle_cls(d), recurse, depth);
}
const ScrDynHandleOps *ops = scr_dyn_handle_ops_of(d);
if (ops->inspect_view) {
/* Node's custom inspectors answer the bare class name beyond the
* depth limit (`depth < 0`). */
if (recurse > depth) return scr_str_new(ops->cls, strlen(ops->cls));
ScrDyn *view = ops->inspect_view(d->v.handle.ptr);
ScrStr *body = scr_insp_dyn(view, recurse, depth);
scr_dyn_release(view);
InspBuf out = {0};
ib_cstr(&out, ops->cls);
ib_char(&out, ' ');
ib_bytes(&out, body->data, body->len);
scr_str_release(body);
return ib_take(&out);
}
/* Node prints the class's full property dump (ServerResponse {
* ... }) — internals we do not model. Fence loudly instead of a
* silent-wrong shape (SEMANTICS.md). */
+4 -2
View File
@@ -4556,7 +4556,8 @@ ScrStr *scr_dyn_object_tag(const ScrDyn *d) {
case SCR_DYN_OBJ: tag = "[object Object]"; break;
case SCR_DYN_HANDLE:
if ((d->v.handle.tag >= SCR_DYNH_ABORT_SIGNAL &&
d->v.handle.tag <= SCR_DYNH_ABORT_CONTROLLER) || d->v.handle.tag == SCR_DYNH_ARRAY_BUFFER || d->v.handle.tag == SCR_DYNH_SHARED_ARRAY_BUFFER ||
d->v.handle.tag <= SCR_DYNH_ABORT_CONTROLLER) || d->v.handle.tag == SCR_DYNH_EVENT_TARGET ||
d->v.handle.tag == SCR_DYNH_CUSTOM_EVENT || d->v.handle.tag == SCR_DYNH_ARRAY_BUFFER || d->v.handle.tag == SCR_DYNH_SHARED_ARRAY_BUFFER ||
d->v.handle.tag == SCR_DYNH_WEAK_MAP || d->v.handle.tag == SCR_DYNH_WEAK_SET ||
d->v.handle.tag == SCR_DYNH_SET || d->v.handle.tag == SCR_DYNH_MAP || d->v.handle.tag == SCR_DYNH_REGEXP ||
d->v.handle.tag == SCR_DYNH_DATE) {
@@ -5093,7 +5094,8 @@ ScrStr *scr_dyn_to_string(const ScrDyn *d, const ScrStr *enc) {
return out;
}
if ((d->v.handle.tag >= SCR_DYNH_ABORT_SIGNAL &&
d->v.handle.tag <= SCR_DYNH_ABORT_CONTROLLER) || d->v.handle.tag == SCR_DYNH_ARRAY_BUFFER || d->v.handle.tag == SCR_DYNH_SHARED_ARRAY_BUFFER || d->v.handle.tag == SCR_DYNH_SET || d->v.handle.tag == SCR_DYNH_MAP) {
d->v.handle.tag <= SCR_DYNH_ABORT_CONTROLLER) || d->v.handle.tag == SCR_DYNH_EVENT_TARGET ||
d->v.handle.tag == SCR_DYNH_CUSTOM_EVENT || d->v.handle.tag == SCR_DYNH_ARRAY_BUFFER || d->v.handle.tag == SCR_DYNH_SHARED_ARRAY_BUFFER || d->v.handle.tag == SCR_DYNH_SET || d->v.handle.tag == SCR_DYNH_MAP) {
ScrJsonBuf b;
scr_jb_init(&b);
scr_jb_puts(&b, "[object ");
+1
View File
@@ -1892,6 +1892,7 @@ void scr_os_ifaddrs_free(ScrIfaddrs *s) {
/* The events-unit hooks (scr_events.c fills them at install; NULL in
* event-free binaries and in the standalone runtime C tests). */
SCR_TL void (*scr_process_exit_hook)(double code) = NULL;
SCR_TL void (*scr_warning_emit_hook)(const char *name, const char *code, ScrStr *message) = NULL;
SCR_TL void (*scr_stdin_destroy_hook)(void) = NULL;
void scr_process_set_exit_code(double code) {
+79 -1
View File
@@ -4633,7 +4633,7 @@ typedef enum {
SCR_DYNH_WEB_CONTROLLER, /* native ReadableStreamDefaultController */
SCR_DYNH_FETCH_RESPONSE, /* native static-fetch Response */
SCR_DYNH_FETCH_HEADERS, /* native static-fetch response Headers */
SCR_DYNH_EVENT, /* native static-fetch abort Event */
SCR_DYNH_EVENT, /* ScrEvent — new Event() and AbortSignal's abort events */
SCR_DYNH_ABORT_CONTROLLER, /* native static-fetch AbortController */
SCR_DYNH_CHILD, /* child_process.ChildProcess */
SCR_DYNH_FILE_HANDLE, /* fs/promises.FileHandle */
@@ -4658,6 +4658,8 @@ typedef enum {
SCR_DYNH_MESSAGE_PORT,
SCR_DYNH_SHARED_ARRAY_BUFFER,
SCR_DYNH_CONSOLE_INSTANCE, /* node:console Console instance */
SCR_DYNH_EVENT_TARGET, /* ScrEventTarget — new EventTarget() */
SCR_DYNH_CUSTOM_EVENT, /* ScrEvent with detail — new CustomEvent() */
SCR_DYNH_COUNT,
} ScrDynHandleTag;
@@ -5406,9 +5408,81 @@ typedef struct ScrDynHandleOps {
ScrDyn *(*iter_pack)(void *h);
/* A live native iterator step; index advances only when a value is emitted. */
ScrDyn *(*iter_step)(void *h, double *index, unsigned selection);
/* util.inspect renders `<cls> <view>` for the dyn object this answers
* (+1); NULL = the class has no inspection (the loud fence). */
ScrDyn *(*inspect_view)(void *h);
} ScrDynHandleOps;
ScrDyn *scr_dyn_native_handle_iterator(ScrDyn *source, unsigned selection);
/* ── EventTarget / Event / CustomEvent (scr_event_target.c) ───────────
* Node's internal/event_target over checked-dynamic values. A target's
* listeners live in a ScrEventTargetCore: per event type, a linked list
* in registration order whose nodes keep their `next` link after removal,
* so a dispatch that cached a removed node continues where Node's does.
* Plain `new EventTarget()` values are SCR_DYNH_EVENT_TARGET handles;
* AbortSignal embeds a core of its own (MaxListeners 0, the `onabort`
* event-handler attribute). Events are ScrEvent handles (SCR_DYNH_EVENT,
* SCR_DYNH_CUSTOM_EVENT for CustomEvent). Listener exceptions, and
* rejections of a listener's returned promise or of a handleEvent call,
* are reported as uncaught exceptions from a nextTick, like Node. */
typedef struct ScrEtRoot ScrEtRoot;
typedef struct ScrEtHandler ScrEtHandler;
typedef struct ScrEtHook ScrEtHook;
typedef struct ScrEventTargetCore {
ScrEtRoot *roots; /* per event type, in first-add order */
ScrEtHandler *handlers; /* event-handler attributes (`onabort`) */
ScrEtHook *hooks; /* options.signal removal hooks aimed here (weak) */
double max_listeners; /* 0 = no leak warning (AbortSignal) */
bool warned;
const char *cls; /* the warning's target name ("EventTarget") */
bool tracked; /* in the exit teardown registry */
struct ScrEventTargetCore *tracked_prev, *tracked_next;
} ScrEventTargetCore;
typedef struct ScrEvent {
size_t rc;
ScrStr *type;
ScrDyn *target; /* +1, NULL until dispatched */
ScrDyn *detail; /* CustomEvent's detail (+1); NULL for Event */
double time_stamp;
bool custom, bubbles, cancelable, composed, trusted;
bool default_prevented, propagation_stopped, stop, dispatching, in_passive;
bool tracked;
struct ScrEvent *tracked_prev, *tracked_next;
} ScrEvent;
void scr_et_core_init(ScrEventTargetCore *core, double max_listeners, const char *cls);
/* Drops every listener and handler (teardown and target destruction). */
void scr_et_core_drop(ScrEventTargetCore *core);
/* The EventTarget methods over a core: `self` is the target's own dyn
* value (borrowed — `this` for listeners and the events' target), args
* borrowed. +1 result, or NULL with the exception pending. */
ScrDyn *scr_et_add_listener(ScrEventTargetCore *core, ScrDyn *const *args, size_t argc);
ScrDyn *scr_et_remove_listener(ScrEventTargetCore *core, ScrDyn *const *args, size_t argc);
ScrDyn *scr_et_dispatch_event(ScrEventTargetCore *core, ScrDyn *self, ScrDyn *const *args, size_t argc);
/* Dispatches an event handle (borrowed) without the argument checks. */
void scr_et_dispatch(ScrEventTargetCore *core, ScrDyn *self, ScrDyn *event);
/* `on<type>` attributes (Node's defineEventHandler): get answers +1 (null
* when unset); set registers the wrapper listener on first assignment. */
ScrDyn *scr_et_handler_get(ScrEventTargetCore *core, const char *type);
void scr_et_handler_set(ScrEventTargetCore *core, const char *type, const ScrDyn *value);
/* A trusted (isTrusted) Event handle of the given type, +1. */
ScrDyn *scr_event_new_trusted(const char *type);
/* new EventTarget() / new Event(type, options) / new CustomEvent(type,
* options): `args` is the argument vector as one dyn array (borrowed).
* +1 handle, or NULL with the exception pending. */
ScrDyn *scr_event_target_new(void);
ScrDyn *scr_event_new(ScrDyn *args);
ScrDyn *scr_custom_event_new(ScrDyn *args);
/* `v instanceof Event` (kind 0), `CustomEvent` (1), `EventTarget` (2). */
bool scr_event_is(const ScrDyn *v, double kind);
/* The AbortSignal unit's view of its handles for options.signal: the
* signal's core and aborted flag, or NULL for a non-signal value.
* Installed by the AbortSignal unit; NULL when no signal can exist. */
extern SCR_TL ScrEventTargetCore *(*scr_et_signal_core_fn)(const ScrDyn *d, bool *aborted);
/* events.addAbortListener(signal, listener): +1 disposable, or NULL. */
ScrDyn *scr_events_add_abort_listener(ScrDyn *signal, ScrDyn *listener);
ScrDyn *scr_intl_segmenter_new(void);
ScrStr *scr_process_builtin_id(ScrDyn *id, ScrArr *known);
ScrDyn *scr_process_builtin_module(ScrStr *id, ScrDyn *getter);
@@ -5973,6 +6047,10 @@ void scr_process_on_warning(ScrDyn *fn);
void scr_process_off_warning(ScrDyn *fn);
void scr_process_emit_warning(ScrDyn *args);
void scr_emit_warning(const char *name, const char *code, ScrStr *message);
/* scr_emit_warning once a 'warning' listener exists (scr_lib.c; set by the
* listener registration): units that cannot link the dyn-async unit route
* their warnings through it, and print the default report otherwise. */
extern SCR_TL void (*scr_warning_emit_hook)(const char *name, const char *code, ScrStr *message);
/* The promise-or-absent await's unit arm: one microtask hop, like JS's
* await of any non-thenable (and like awaiting a settled promise). */
void scr_await_hop(void);
+153
View File
@@ -0,0 +1,153 @@
// EventTarget dispatch follows Node's linked-list walk: listeners run in
// registration order with the target as `this`, a listener appended while
// a later one is still pending runs in the same dispatch, a removed
// listener is skipped, `once` listeners leave before they run, and the
// (listener, capture) pair is the identity. After the first callback the
// event no longer reports itself as dispatching (eventPhase 0, null
// currentTarget), exactly Node's kIsBeingDispatched reset.
const t = new EventTarget();
function section(name) {
console.log("--", name);
}
section("order and this");
function first(e) {
console.log("first", e.type, this === t, e.eventPhase, e.currentTarget === t, e.target === t);
t.addEventListener("x", () => console.log("appended during dispatch"));
t.removeEventListener("x", third);
}
function second(e) {
console.log("second", e.eventPhase, e.currentTarget === null, e.composedPath().length);
}
function third() {
console.log("third (removed before its turn)");
}
t.addEventListener("x", first);
t.addEventListener("x", second);
t.addEventListener("x", third);
t.addEventListener("x", first); // duplicate (listener, capture) is ignored
const ev = new Event("x");
console.log("result", t.dispatchEvent(ev));
console.log("after", ev.eventPhase, ev.currentTarget, ev.target === t, ev.srcElement === t);
console.log("again", t.dispatchEvent(ev));
section("tail append");
const tail = new EventTarget();
tail.addEventListener("y", () => {
console.log("only listener appends");
tail.addEventListener("y", () => console.log("appended after the last node"));
});
tail.dispatchEvent(new Event("y"));
tail.dispatchEvent(new Event("y"));
section("once and capture");
const o = new EventTarget();
const shared = () => console.log("shared");
o.addEventListener("o", () => console.log("once"), { once: true });
o.addEventListener("o", shared, true);
o.addEventListener("o", shared, { capture: false });
o.dispatchEvent(new Event("o"));
o.removeEventListener("o", shared, true); // a boolean options value reads no capture
o.dispatchEvent(new Event("o"));
o.removeEventListener("o", shared, { capture: true });
o.dispatchEvent(new Event("o"));
section("cancelable and passive");
const c = new EventTarget();
c.addEventListener("p", (e) => {
e.preventDefault();
console.log("in passive", e.defaultPrevented);
}, { passive: true });
c.addEventListener("c", (e) => {
e.preventDefault();
console.log("prevented", e.defaultPrevented, e.returnValue);
});
c.addEventListener("r", (e) => {
e.returnValue = false;
});
console.log("passive result", c.dispatchEvent(new Event("p", { cancelable: true })));
const cancel = new Event("c", { cancelable: true });
console.log("cancel result", c.dispatchEvent(cancel), cancel.defaultPrevented);
console.log("not cancelable", c.dispatchEvent(new Event("c")));
const rv = new Event("r", { cancelable: true });
console.log("returnValue result", c.dispatchEvent(rv), rv.defaultPrevented);
section("stop propagation");
const s = new EventTarget();
s.addEventListener("s", (e) => {
e.stopPropagation();
console.log("stopPropagation keeps going", e.cancelBubble);
});
s.addEventListener("s", (e) => {
e.stopImmediatePropagation();
console.log("stopImmediatePropagation");
});
s.addEventListener("s", () => console.log("never"));
s.dispatchEvent(new Event("s"));
const cb = new Event("cb");
cb.cancelBubble = true;
console.log("cancelBubble", cb.cancelBubble);
section("handleEvent");
const h = new EventTarget();
const handler = {
tag: "obj",
handleEvent(e) {
console.log("handleEvent", this.tag, e.type, this === handler);
},
};
h.addEventListener("h", handler);
h.dispatchEvent(new Event("h"));
handler.handleEvent = (e) => console.log("replaced handleEvent", e.type);
h.dispatchEvent(new Event("h"));
h.addEventListener("missing", {});
h.dispatchEvent(new Event("missing"));
h.removeEventListener("h", handler);
h.dispatchEvent(new Event("h"));
section("validation");
// Values the checker would reject at these call sites.
function loose(value) {
return value;
}
function attempt(label, fn) {
try {
fn();
console.log(label, "ok");
} catch (e) {
console.log(label, e.name, e.code, e.message);
}
}
// @ts-expect-error -- the missing argument is the point
attempt("new Event()", () => new Event());
attempt("bad options", () => new Event("x", loose(5)));
attempt("dispatch non-event", () => t.dispatchEvent(loose({})));
// @ts-expect-error -- the missing argument is the point
attempt("dispatch nothing", () => t.dispatchEvent());
attempt("listener number", () => t.addEventListener("x", loose(5)));
// @ts-expect-error -- the missing argument is the point
attempt("missing listener", () => t.addEventListener("x"));
attempt("options number", () => t.addEventListener("x", () => {}, loose(5)));
attempt("signal object", () => t.addEventListener("x", () => {}, { signal: loose(1) }));
const rec = new EventTarget();
rec.addEventListener("rec", (e) => {
attempt("recursion", () => rec.dispatchEvent(e));
});
rec.dispatchEvent(new Event("rec"));
section("event fields");
const fresh = new Event("f", { bubbles: true, cancelable: true, composed: true });
console.log(fresh.type, fresh.bubbles, fresh.cancelable, fresh.composed, fresh.isTrusted);
console.log(fresh.target, fresh.currentTarget, fresh.eventPhase, fresh.timeStamp >= 0);
console.log(Event.NONE, Event.CAPTURING_PHASE, Event.AT_TARGET, Event.BUBBLING_PHASE);
console.log(String(fresh), String(t), Object.prototype.toString.call(new CustomEvent("c")));
fresh.initEvent("renamed", false, false);
console.log(fresh.type, fresh.bubbles, fresh.cancelable);
const custom = new CustomEvent("d", { detail: { n: 1 } });
console.log(custom.detail.n, new CustomEvent("e").detail, custom instanceof Event, custom instanceof CustomEvent);
console.log(fresh instanceof CustomEvent, t instanceof EventTarget, loose({}) instanceof Event);
console.log(new Event(loose(42)).type, new Event(loose(null)).type);
// util.inspect: EventTarget prints its (empty) custom view; beyond the
// depth limit both print the bare class name.
console.log([t], { deep: { deeper: { target: t, event: fresh } } });
@@ -0,0 +1,51 @@
// A throwing EventTarget listener does not stop the dispatch: Node reports
// the exception as uncaught from a nextTick. A listener's returned promise
// is caught with then(), so its rejection is reported after the reaction
// runs; a handleEvent object goes through an async wrapper, so even its
// synchronous throw (or a non-callable handleEvent) arrives one microtask
// later. Each phase raises one failure and shows where its report lands
// among a microtask and a tick queued right after the dispatch.
process.on("uncaughtException", (err) => console.log("uncaught:", err.message));
async function phase(name, listener) {
console.log("--", name);
const t = new EventTarget();
t.addEventListener("x", listener);
t.addEventListener("x", () => console.log("next listener still runs"));
console.log("dispatch result", t.dispatchEvent(new Event("x")));
queueMicrotask(() => console.log("microtask"));
process.nextTick(() => console.log("tick"));
await new Promise((resolve) => setTimeout(resolve, 5));
}
async function main() {
await phase("throwing function", () => {
throw new Error("listener threw");
});
await phase("async function", async () => {
throw new Error("async listener rejected");
});
await phase("async function after an await", async () => {
await null;
throw new Error("late rejection");
});
await phase("throwing handleEvent", {
handleEvent() {
throw new Error("handleEvent threw");
},
});
await phase("non-callable handleEvent", { handleEvent: 5 });
await phase("async handleEvent", {
async handleEvent() {
await null;
throw new Error("async handleEvent rejected");
},
});
await phase("thenable result", () => ({
then(resolve, reject) {
console.log("then called", typeof resolve, typeof reject);
reject(new Error("thenable rejected"));
},
}));
}
main();
+79
View File
@@ -0,0 +1,79 @@
// EventTarget, Event and CustomEvent from typed code: listeners typed
// `(event: Event) => void` or `{ handleEvent }` records, the dispatch
// result, the event's target and phase during and after dispatch, the
// instanceof tests, the phase constants, and CustomEvent's detail.
const bus = new EventTarget();
const seen: string[] = [];
function onPing(event: Event): void {
seen.push(`${event.type}:${event.eventPhase}:${event.target === bus}:${event.currentTarget === bus}`);
}
bus.addEventListener("ping", onPing);
bus.addEventListener("ping", onPing, { once: true });
const ping = new Event("ping", { cancelable: true });
console.log(bus.dispatchEvent(ping), ping.eventPhase, ping.currentTarget === null, ping.target === bus);
console.log(seen.join(" "));
// Options dictionaries held in typed bindings.
const onceOptions: AddEventListenerOptions = { once: true };
for (let i = 0; i < 3; i++) {
bus.addEventListener("round", () => seen.push(`round listener ${i}`), onceOptions);
bus.dispatchEvent(new Event("round"));
}
const captureOptions = { capture: true };
const captured = (): void => {
seen.push("captured");
};
bus.addEventListener("cap", captured, captureOptions);
bus.removeEventListener("cap", captured);
bus.dispatchEvent(new Event("cap"));
bus.removeEventListener("cap", captured, captureOptions);
bus.dispatchEvent(new Event("cap"));
console.log(seen.splice(0).join(" "));
const canceller = (event: Event): void => {
event.preventDefault();
};
bus.addEventListener("save", canceller);
const save = new Event("save", { cancelable: true });
console.log("save", bus.dispatchEvent(save), save.defaultPrevented, save.returnValue);
bus.removeEventListener("save", canceller);
console.log("save again", bus.dispatchEvent(new Event("save", { cancelable: true })));
interface Payload {
id: number;
tags: string[];
}
const data = new CustomEvent<Payload>("data", { detail: { id: 7, tags: ["a", "b"] } });
bus.addEventListener("data", (event: Event) => {
if (event instanceof CustomEvent) {
const payload = event.detail as Payload;
console.log("detail", payload.id, payload.tags.join("+"), event.detail === data.detail);
}
});
bus.dispatchEvent(data);
const counter = {
hits: 0,
handleEvent(event: Event): void {
this.hits = this.hits + 1;
console.log("handleEvent", event.type, this.hits);
},
};
bus.addEventListener("count", counter);
bus.dispatchEvent(new Event("count"));
bus.dispatchEvent(new Event("count"));
console.log("hits", counter.hits);
console.log(ping instanceof Event, data instanceof CustomEvent, ping instanceof CustomEvent);
console.log(bus instanceof EventTarget, Event.NONE, Event.AT_TARGET, Event.BUBBLING_PHASE);
console.log(ping.bubbles, ping.composed, ping.isTrusted, typeof ping.timeStamp);
function relay(source: EventTarget, type: string): Promise<string> {
return new Promise((resolve) => {
source.addEventListener(type, (event: Event) => resolve(event.type), { once: true });
});
}
relay(bus, "later").then((type) => console.log("relayed", type));
setTimeout(() => bus.dispatchEvent(new Event("later")), 1);
@@ -0,0 +1,88 @@
// AbortSignal is an EventTarget on the same engine: `onabort` takes the
// position of its first assignment, abort listeners and the removal hooks
// that options.signal registers run in registration order (the hooks run
// even after stopImmediatePropagation), any event type can be dispatched
// on a signal, and events.addAbortListener registers a once listener or,
// for an aborted signal, queues the call.
import { addAbortListener } from "node:events";
const log: string[] = [];
// onabort is registered where it was first assigned, even as null.
const first = new AbortController();
first.signal.onabort = null;
first.signal.addEventListener("abort", () => log.push("listener"));
first.signal.onabort = (event: Event) => log.push(`onabort ${event.type} ${event.isTrusted}`);
first.abort();
console.log(log.splice(0).join(", "));
console.log(first.signal.onabort !== null);
// options.signal: the removal hook is an abort listener of its own.
const controller = new AbortController();
const target = new EventTarget();
controller.signal.addEventListener("abort", () => {
log.push("early abort listener dispatches");
target.dispatchEvent(new Event("work"));
});
target.addEventListener("work", () => log.push("work listener"), { signal: controller.signal });
controller.signal.addEventListener("abort", (event: Event) => {
log.push("stops the rest");
event.stopImmediatePropagation();
});
controller.signal.addEventListener("abort", () => log.push("never runs"));
target.dispatchEvent(new Event("work"));
controller.abort();
target.dispatchEvent(new Event("work"));
console.log(log.splice(0).join(", "));
// An aborted signal registers nothing.
const done = new AbortController();
done.abort();
target.addEventListener("work", () => log.push("not registered"), { signal: done.signal });
target.dispatchEvent(new Event("work"));
console.log("after aborted signal:", log.splice(0).join(", ") || "none");
// Signals dispatch any event type, with their own `this` and target.
const custom = new AbortController();
custom.signal.addEventListener("custom", (event: Event) => {
log.push(`custom ${event.type} ${event.target === custom.signal} ${event.isTrusted}`);
});
console.log(custom.signal.dispatchEvent(new Event("custom")), log.splice(0).join(", "));
custom.signal.addEventListener("abort", (event: Event) => log.push(`manual ${event.isTrusted}`));
custom.signal.dispatchEvent(new Event("abort"));
console.log(custom.signal.aborted, log.splice(0).join(", "));
// events.addAbortListener
const viaHelper = new AbortController();
const disposable = addAbortListener(viaHelper.signal, (event: Event) => {
log.push(`helper ${event.type}`);
});
viaHelper.signal.addEventListener("abort", (event: Event) => event.stopImmediatePropagation());
viaHelper.signal.addEventListener("abort", () => log.push("stopped"));
viaHelper.abort();
console.log(log.splice(0).join(", "));
disposable[Symbol.dispose]();
const disposed = new AbortController();
const handle = addAbortListener(disposed.signal, () => log.push("disposed listener"));
handle[Symbol.dispose]();
disposed.abort();
console.log("after dispose:", log.splice(0).join(", ") || "none");
const already = new AbortController();
already.abort();
addAbortListener(already.signal, () => console.log("aborted-signal listener runs later"));
queueMicrotask(() => console.log("microtask queued after it"));
console.log("sync end");
function attempt(label: string, fn: () => void): void {
try {
fn();
console.log(label, "ok");
} catch (e) {
const err = e as Error & { code?: string };
console.log(label, err.name, err.code, err.message);
}
}
attempt("not a signal", () => addAbortListener(target as unknown as AbortSignal, () => {}));
attempt("not a function", () => addAbortListener(already.signal, 5 as unknown as () => void));
+38
View File
@@ -0,0 +1,38 @@
// events.errorMonitor: listeners registered under the symbol see every
// 'error' emit first, with the same payload; they do not count as 'error'
// listeners, so an emit with none still throws after the monitors ran.
// Emitting the symbol itself reaches only the monitors.
import { EventEmitter, errorMonitor } from "node:events";
class Job extends EventEmitter {
readonly name: string;
constructor(name: string) {
super();
this.name = name;
}
}
const job = new Job("build");
job.on(errorMonitor, (err: Error) => console.log("monitor A", err.message));
job.once(EventEmitter.errorMonitor, (err: Error) => console.log("monitor once", err.message));
job.on("error", (err: Error) => console.log("error listener", err.message));
job.on(errorMonitor, (err: Error) => console.log("monitor B", err.message));
console.log(job.emit("error", new Error("first")));
console.log(job.emit("error", new TypeError("second")));
console.log(job.listenerCount(errorMonitor), job.listenerCount("error"));
const bare = new Job("bare");
bare.on(errorMonitor, (err: Error) => console.log("bare monitor", err.message));
try {
bare.emit("error", new RangeError("unhandled"));
} catch (err) {
console.log("thrown", (err as Error).name, (err as Error).message);
}
console.log(bare.emit(errorMonitor, new Error("direct")));
bare.removeAllListeners(errorMonitor);
console.log(bare.listenerCount(errorMonitor));
try {
bare.emit("error", new Error("no monitors left"));
} catch (err) {
console.log("thrown", (err as Error).message);
}
@@ -0,0 +1,23 @@
// @exit: 1
// The process warnings EventTarget emits: the one-time leak warning past
// ten listeners of one type (none for AbortSignal, whose limit is 0) and
// the no-op warning for a null or undefined listener. Both arrive through
// process.emitWarning on a later tick; the 'warning' listener prints them
// (the stderr report carries the pid, so the @exit directive keeps it out
// of the comparison).
process.on("warning", (w) => console.log("warning:", w.name, "|", w.message));
const t = new EventTarget();
for (let i = 0; i < 11; i++) t.addEventListener("x", () => {});
for (let i = 0; i < 12; i++) t.addEventListener("x", () => {});
for (let i = 0; i < 11; i++) t.addEventListener("y", () => {});
t.addEventListener("z", null);
t.addEventListener("z", undefined);
const ac = new AbortController();
for (let i = 0; i < 12; i++) ac.signal.addEventListener("abort", () => {});
for (let i = 0; i < 12; i++) new EventTarget().addEventListener("w", () => {}, { signal: ac.signal });
console.log("sync end");
process.nextTick(() => console.log("tick queued after the warnings"));
setTimeout(() => {
process.exitCode = 1;
}, 1);
+7 -9
View File
@@ -74,14 +74,10 @@ async function runBinary(
env?: NodeJS.ProcessEnv,
timeout = 30_000,
): Promise<RunResult> {
// These fixtures intentionally exercise missing listeners, and some JS
// entries rely on Node's module detection. Neither warning is program output.
// Some JS entries rely on Node's module detection; that warning is not
// program output.
if (cmd === "node") {
args = [
"--disable-warning=MODULE_TYPELESS_PACKAGE_JSON",
"--disable-warning=AddEventListenerArgumentTypeWarning",
...args,
];
args = ["--disable-warning=MODULE_TYPELESS_PACKAGE_JSON", ...args];
}
try {
const { stdout, stderr } = await execFileAsync(cmd, args, {
@@ -98,13 +94,15 @@ async function runBinary(
}
}
// Keep sanitizer infrastructure noise separate from program stderr.
// Keep sanitizer infrastructure noise separate from program stderr; the
// process warnings carry each process's own pid.
function expectCleanStderr(native: RunResult, node: RunResult): void {
if (node.exitCode !== 0) return;
const pid = (text: string): string => text.replace(/^\(node:\d+\) /gm, "(node:PID) ");
const stderr = native.stderr
.toString("utf8")
.replace(/^==\d+==WARNING: ASan doesn't fully support makecontext\/swapcontext.*\n/gm, "");
expect(stderr).toBe(node.stderr.toString("utf8"));
expect(pid(stderr)).toBe(pid(node.stderr.toString("utf8")));
}
async function build(entry: string, flavor = ""): Promise<string> {
+6
View File
@@ -139,6 +139,12 @@ describe.skipIf(!zigOnPath())("wasm32-wasi differential", () => {
"js-promise-all-values.js",
"js-promise-catch-handler.js",
"js-abort-portable.js",
"5130-event-target-dispatch.js",
"5131-event-target-listener-errors.js",
"5132-event-target-typed.ts",
"5133-abort-signal-event-target.ts",
"5134-events-error-monitor.ts",
"5135-event-target-warnings.js",
"4031-event-emitter-long-tuples.ts",
"2700-wasi-core.ts",
"local-union-storage.ts",