/
githubmirror
/
deno
Обзор
Документация
Войти
/
githubmirror
/
deno
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
ext/web/02_event.js
1 731 строка
42 KB
Kenta Moriuchi
chore: make prefer-primordials lint an internal plugin (#35978)
23 июл 2026, 11:53
Не верифицирован
23 июл 2026, 11:53
5b55d08
Код
Авторство
О чём код?
// Copyright 2018-2026 the Deno authors. MIT license. // This module follows most of the WHATWG Living Standard for the DOM logic. // Many parts of the DOM are not implemented in Deno, but the logic for those // parts still exists. This means you will observe a lot of strange structures // and impossible logic branches based on what Deno currently supports. (function () { const { core, internals, primordials } = __bootstrap; const { ArrayPrototypeIncludes, ArrayPrototypeIndexOf, ArrayPrototypeMap, ArrayPrototypePush, ArrayPrototypeSlice, ArrayPrototypeSplice, ArrayPrototypeUnshift, Boolean, Error, FunctionPrototypeCall, JSONStringify, MapPrototypeGet, MapPrototypeSet, ObjectCreate, ObjectDefineProperty, ObjectFreeze, ObjectGetOwnPropertyDescriptor, ObjectGetPrototypeOf, ObjectPrototype, ObjectPrototypeIsPrototypeOf, ReflectDefineProperty, SafeArrayIterator, SafeMap, String, StringPrototypeStartsWith, Symbol, SymbolFor, SymbolIterator, SymbolToStringTag, TypeError, } = primordials; const webidl = core.loadExtScript("ext:deno_webidl/00_webidl.js"); const { DOMException } = core.loadExtScript("ext:deno_web/01_dom_exception.js"); // Lazy-load createFilteredInspectProxy from console to avoid // circular dependency at load time. Only needed for custom inspect. let _createFilteredInspectProxy; function getCreateFilteredInspectProxy() { if (!_createFilteredInspectProxy) { _createFilteredInspectProxy = core.loadExtScript( "ext:deno_web/01_console.js", ).createFilteredInspectProxy; } return _createFilteredInspectProxy; } // This should be set via setGlobalThis this is required so that if even // user deletes globalThis it is still usable let globalThis_; function saveGlobalThisReference(val) { globalThis_ = val; } // accessors for non runtime visible data function getDispatched(event) { return Boolean(event[_dispatched]); } function getPath(event) { return event[_path] ?? []; } function getStopImmediatePropagation(event) { return Boolean(event[_stopImmediatePropagationFlag]); } function setCurrentTarget( event, value, ) { event[_attributes].currentTarget = value; } function setIsTrusted(event, value) { event[_isTrusted] = value; } function setDispatched(event, value) { event[_dispatched] = value; } function setEventPhase(event, value) { event[_attributes].eventPhase = value; } function setInPassiveListener(event, value) { event[_inPassiveListener] = value; } function setPath(event, value) { event[_path] = value; } function setRelatedTarget( event, value, ) { event[_attributes].relatedTarget = value; } function setTarget(event, value) { event[_attributes].target = value; } function setStopImmediatePropagation( event, value, ) { event[_stopImmediatePropagationFlag] = value; } const isTrusted = ObjectGetOwnPropertyDescriptor({ get isTrusted() { return this[_isTrusted]; }, }, "isTrusted").get; const _attributes = Symbol("[[attributes]]"); const _canceledFlag = Symbol("[[canceledFlag]]"); const _stopPropagationFlag = Symbol("[[stopPropagationFlag]]"); const _stopImmediatePropagationFlag = Symbol( "[[stopImmediatePropagationFlag]]", ); const _inPassiveListener = Symbol("[[inPassiveListener]]"); const _dispatched = Symbol("[[dispatched]]"); const _isTrusted = Symbol("[[isTrusted]]"); const _path = Symbol("[[path]]"); class Event { constructor(type, eventInitDict = { __proto__: null }) { this[_canceledFlag] = false; this[_stopPropagationFlag] = false; this[_stopImmediatePropagationFlag] = false; this[_inPassiveListener] = false; this[_dispatched] = false; this[_isTrusted] = false; this[_path] = []; webidl.requiredArguments( arguments.length, 1, "Failed to construct 'Event'", ); type = webidl.converters.DOMString( type, "Failed to construct 'Event'", "Argument 1", ); this[_attributes] = { type, bubbles: !!eventInitDict.bubbles, cancelable: !!eventInitDict.cancelable, composed: !!eventInitDict.composed, currentTarget: null, eventPhase: Event.NONE, target: null, timeStamp: 0, }; } [SymbolFor("Deno.privateCustomInspect")](inspect, inspectOptions) { return inspect( getCreateFilteredInspectProxy()({ object: this, evaluate: ObjectPrototypeIsPrototypeOf(EventPrototype, this), keys: EVENT_PROPS, }), inspectOptions, ); } get type() { return this[_attributes].type; } get target() { return this[_attributes].target; } get srcElement() { return null; } set srcElement(_) { // this member is deprecated } get currentTarget() { return this[_attributes].currentTarget; } composedPath() { const path = this[_path]; if (path.length === 0) { return []; } if (!this.currentTarget) { throw new Error("assertion error"); } const composedPath = [ { item: this.currentTarget, itemInShadowTree: false, relatedTarget: null, rootOfClosedTree: false, slotInClosedTree: false, target: null, touchTargetList: [], }, ]; let currentTargetIndex = 0; let currentTargetHiddenSubtreeLevel = 0; for (let index = path.length - 1; index >= 0; index--) { const { item, rootOfClosedTree, slotInClosedTree } = path[index]; if (rootOfClosedTree) { currentTargetHiddenSubtreeLevel++; } if (item === this.currentTarget) { currentTargetIndex = index; break; } if (slotInClosedTree) { currentTargetHiddenSubtreeLevel--; } } let currentHiddenLevel = currentTargetHiddenSubtreeLevel; let maxHiddenLevel = currentTargetHiddenSubtreeLevel; for (let i = currentTargetIndex - 1; i >= 0; i--) { const { item, rootOfClosedTree, slotInClosedTree } = path[i]; if (rootOfClosedTree) { currentHiddenLevel++; } if (currentHiddenLevel <= maxHiddenLevel) { ArrayPrototypeUnshift(composedPath, { item, itemInShadowTree: false, relatedTarget: null, rootOfClosedTree: false, slotInClosedTree: false, target: null, touchTargetList: [], }); } if (slotInClosedTree) { currentHiddenLevel--; if (currentHiddenLevel < maxHiddenLevel) { maxHiddenLevel = currentHiddenLevel; } } } currentHiddenLevel = currentTargetHiddenSubtreeLevel; maxHiddenLevel = currentTargetHiddenSubtreeLevel; for (let index = currentTargetIndex + 1; index < path.length; index++) { const { item, rootOfClosedTree, slotInClosedTree } = path[index]; if (slotInClosedTree) { currentHiddenLevel++; } if (currentHiddenLevel <= maxHiddenLevel) { ArrayPrototypePush(composedPath, { item, itemInShadowTree: false, relatedTarget: null, rootOfClosedTree: false, slotInClosedTree: false, target: null, touchTargetList: [], }); } if (rootOfClosedTree) { currentHiddenLevel--; if (currentHiddenLevel < maxHiddenLevel) { maxHiddenLevel = currentHiddenLevel; } } } return ArrayPrototypeMap(composedPath, (p) => p.item); } get NONE() { return Event.NONE; } get CAPTURING_PHASE() { return Event.CAPTURING_PHASE; } get AT_TARGET() { return Event.AT_TARGET; } get BUBBLING_PHASE() { return Event.BUBBLING_PHASE; } get eventPhase() { return this[_attributes].eventPhase; } stopPropagation() { this[_stopPropagationFlag] = true; } get cancelBubble() { return this[_stopPropagationFlag]; } set cancelBubble(value) { this[_stopPropagationFlag] = webidl.converters.boolean(value); } stopImmediatePropagation() { this[_stopPropagationFlag] = true; this[_stopImmediatePropagationFlag] = true; } get bubbles() { return this[_attributes].bubbles; } get cancelable() { return this[_attributes].cancelable; } get returnValue() { return !this[_canceledFlag]; } set returnValue(value) { if (!webidl.converters.boolean(value)) { if (this[_attributes].cancelable && !this[_inPassiveListener]) { this[_canceledFlag] = true; } } } preventDefault() { if (this[_attributes].cancelable && !this[_inPassiveListener]) { this[_canceledFlag] = true; } } get defaultPrevented() { return this[_canceledFlag]; } get composed() { return this[_attributes].composed; } get initialized() { return true; } get timeStamp() { return this[_attributes].timeStamp; } } ObjectDefineProperty(Event, "NONE", { __proto__: null, value: 0, writable: false, enumerable: true, configurable: false, }); ObjectDefineProperty(Event, "CAPTURING_PHASE", { __proto__: null, value: 1, writable: false, enumerable: true, configurable: false, }); ObjectDefineProperty(Event, "AT_TARGET", { __proto__: null, value: 2, writable: false, enumerable: true, configurable: false, }); ObjectDefineProperty(Event, "BUBBLING_PHASE", { __proto__: null, value: 3, writable: false, enumerable: true, configurable: false, }); const EventPrototype = Event.prototype; ObjectDefineProperty(EventPrototype, SymbolToStringTag, { __proto__: null, value: "Event", writable: false, enumerable: false, configurable: true, }); // Not spec compliant. The spec defines it as [LegacyUnforgeable] // but doing so has a big performance hit ReflectDefineProperty(Event.prototype, "isTrusted", { __proto__: null, enumerable: true, get: isTrusted, }); function defineEnumerableProps( Ctor, props, ) { for (let i = 0; i < props.length; ++i) { const prop = props[i]; ReflectDefineProperty(Ctor.prototype, prop, { __proto__: null, enumerable: true, }); } } const EVENT_PROPS = [ "bubbles", "cancelable", "composed", "currentTarget", "defaultPrevented", "eventPhase", "srcElement", "target", "returnValue", "timeStamp", "type", ]; defineEnumerableProps(Event, EVENT_PROPS); // This is currently the only node type we are using, so instead of implementing // the whole of the Node interface at the moment, this just gives us the one // value to power the standards based logic const DOCUMENT_FRAGMENT_NODE = 11; // DOM Logic Helper functions and type guards /** Get the parent node, for event targets that have a parent. * * Ref: https://dom.spec.whatwg.org/#get-the-parent */ function getParent(eventTarget) { return isNode(eventTarget) ? eventTarget.parentNode : null; } function getRoot(eventTarget) { return isNode(eventTarget) ? eventTarget.getRootNode({ composed: true }) : null; } function isNode( eventTarget, ) { return eventTarget?.nodeType !== undefined; } // https://dom.spec.whatwg.org/#concept-shadow-including-inclusive-ancestor function isShadowInclusiveAncestor( ancestor, node, ) { while (isNode(node)) { if (node === ancestor) { return true; } if (isShadowRoot(node)) { node = node && getHost(node); } else { node = getParent(node); } } return false; } function isShadowRoot(nodeImpl) { return Boolean( nodeImpl && isNode(nodeImpl) && nodeImpl.nodeType === DOCUMENT_FRAGMENT_NODE && getHost(nodeImpl) != null, ); } function isSlottable( /* nodeImpl, */ ) { // TODO(marcosc90) currently there aren't any slottables nodes // https://dom.spec.whatwg.org/#concept-slotable // return isNode(nodeImpl) && ReflectHas(nodeImpl, "assignedSlot"); return false; } // DOM Logic functions /** Append a path item to an event's path. * * Ref: https://dom.spec.whatwg.org/#concept-event-path-append */ function appendToEventPath( eventImpl, target, targetOverride, relatedTarget, touchTargets, slotInClosedTree, ) { const itemInShadowTree = isNode(target) && isShadowRoot(getRoot(target)); const rootOfClosedTree = isShadowRoot(target) && getMode(target) === "closed"; ArrayPrototypePush(getPath(eventImpl), { item: target, itemInShadowTree, target: targetOverride, relatedTarget, touchTargetList: touchTargets, rootOfClosedTree, slotInClosedTree, }); } function dispatch( targetImpl, eventImpl, targetOverride, ) { let clearTargets = false; let activationTarget = null; setDispatched(eventImpl, true); targetOverride = targetOverride ?? targetImpl; const eventRelatedTarget = eventImpl.relatedTarget; let relatedTarget = retarget(eventRelatedTarget, targetImpl); if (targetImpl !== relatedTarget || targetImpl === eventRelatedTarget) { const touchTargets = []; appendToEventPath( eventImpl, targetImpl, targetOverride, relatedTarget, touchTargets, false, ); const isActivationEvent = eventImpl.type === "click"; if (isActivationEvent && getHasActivationBehavior(targetImpl)) { activationTarget = targetImpl; } let slotInClosedTree = false; let slottable = isSlottable(targetImpl) && getAssignedSlot(targetImpl) ? targetImpl : null; let parent = getParent(targetImpl); // Populate event path // https://dom.spec.whatwg.org/#event-path while (parent !== null) { if (slottable !== null) { slottable = null; const parentRoot = getRoot(parent); if ( isShadowRoot(parentRoot) && parentRoot && getMode(parentRoot) === "closed" ) { slotInClosedTree = true; } } relatedTarget = retarget(eventRelatedTarget, parent); if ( isNode(parent) && isShadowInclusiveAncestor(getRoot(targetImpl), parent) ) { appendToEventPath( eventImpl, parent, null, relatedTarget, touchTargets, slotInClosedTree, ); } else if (parent === relatedTarget) { parent = null; } else { targetImpl = parent; if ( isActivationEvent && activationTarget === null && getHasActivationBehavior(targetImpl) ) { activationTarget = targetImpl; } appendToEventPath( eventImpl, parent, targetImpl, relatedTarget, touchTargets, slotInClosedTree, ); } if (parent !== null) { parent = getParent(parent); } slotInClosedTree = false; } let clearTargetsTupleIndex = -1; const path = getPath(eventImpl); for ( let i = path.length - 1; i >= 0 && clearTargetsTupleIndex === -1; i-- ) { if (path[i].target !== null) { clearTargetsTupleIndex = i; } } const clearTargetsTuple = path[clearTargetsTupleIndex]; clearTargets = (isNode(clearTargetsTuple.target) && isShadowRoot(getRoot(clearTargetsTuple.target))) || (isNode(clearTargetsTuple.relatedTarget) && isShadowRoot(getRoot(clearTargetsTuple.relatedTarget))); setEventPhase(eventImpl, Event.CAPTURING_PHASE); for (let i = path.length - 1; i >= 0; --i) { const tuple = path[i]; if (tuple.target === null) { invokeEventListeners(tuple, eventImpl); } } for (let i = 0; i < path.length; i++) { const tuple = path[i]; if (tuple.target !== null) { setEventPhase(eventImpl, Event.AT_TARGET); } else { setEventPhase(eventImpl, Event.BUBBLING_PHASE); } if ( (eventImpl.eventPhase === Event.BUBBLING_PHASE && eventImpl.bubbles) || eventImpl.eventPhase === Event.AT_TARGET ) { invokeEventListeners(tuple, eventImpl); } } } setEventPhase(eventImpl, Event.NONE); setCurrentTarget(eventImpl, null); setPath(eventImpl, []); setDispatched(eventImpl, false); eventImpl.cancelBubble = false; setStopImmediatePropagation(eventImpl, false); if (clearTargets) { setTarget(eventImpl, null); setRelatedTarget(eventImpl, null); } // TODO(bartlomieju): invoke activation targets if HTML nodes will be implemented // if (activationTarget !== null) { // if (!eventImpl.defaultPrevented) { // activationTarget._activationBehavior(); // } // } return !eventImpl.defaultPrevented; } /** Inner invoking of the event listeners where the resolved listeners are * called. * * Ref: https://dom.spec.whatwg.org/#concept-event-listener-inner-invoke */ function innerInvokeEventListeners( eventImpl, targetListeners, ) { let found = false; const { type } = eventImpl; if (!targetListeners || !targetListeners[type]) { return found; } let handlers = targetListeners[type]; const handlersLength = handlers.length; // Copy event listeners before iterating since the list can be modified during the iteration. if (handlersLength > 1) { handlers = ArrayPrototypeSlice(targetListeners[type]); } for (let i = 0; i < handlersLength; i++) { const listener = handlers[i]; if ( getStopImmediatePropagation(eventImpl) && !listener.options[kResistStopImmediatePropagation] ) { continue; } let capture, once, passive; if (typeof listener.options === "boolean") { capture = listener.options; once = false; passive = false; } else { capture = listener.options.capture; once = listener.options.once; passive = listener.options.passive; } // Check if the event listener has been removed since the listeners has been cloned. if (!ArrayPrototypeIncludes(targetListeners[type], listener)) { continue; } found = true; if ( (eventImpl.eventPhase === Event.CAPTURING_PHASE && !capture) || (eventImpl.eventPhase === Event.BUBBLING_PHASE && capture) ) { continue; } if (once) { ArrayPrototypeSplice( targetListeners[type], ArrayPrototypeIndexOf(targetListeners[type], listener), 1, ); } if (passive) { setInPassiveListener(eventImpl, true); } try { if (typeof listener.callback === "object") { if (typeof listener.callback.handleEvent === "function") { listener.callback.handleEvent(eventImpl); } } else { FunctionPrototypeCall( listener.callback, eventImpl.currentTarget, eventImpl, ); } } catch (error) { reportException(error); } setInPassiveListener(eventImpl, false); } return found; } /** Invokes the listeners on a given event path with the supplied event. * * Ref: https://dom.spec.whatwg.org/#concept-event-listener-invoke */ function invokeEventListeners(tuple, eventImpl) { const path = getPath(eventImpl); if (path.length === 1) { const t = path[0]; if (t.target) { setTarget(eventImpl, t.target); } } else { const tupleIndex = ArrayPrototypeIndexOf(path, tuple); for (let i = tupleIndex; i >= 0; i--) { const t = path[i]; if (t.target) { setTarget(eventImpl, t.target); break; } } } setRelatedTarget(eventImpl, tuple.relatedTarget); if (eventImpl.cancelBubble) { return; } setCurrentTarget(eventImpl, tuple.item); try { innerInvokeEventListeners(eventImpl, getListeners(tuple.item)); } catch (error) { reportException(error); } } // https://dom.spec.whatwg.org/#dom-eventtarget-removeeventlistener function normalizeEventHandlerOptions( options, ) { if ( typeof options === "boolean" || typeof options === "undefined" || options === null ) { return { capture: Boolean(options), }; } else { return { capture: Boolean(options.capture), }; } } /** Retarget the target following the spec logic. * * Ref: https://dom.spec.whatwg.org/#retarget */ function retarget(a, b) { while (true) { if (!isNode(a)) { return a; } const aRoot = a.getRootNode(); if (aRoot) { if ( !isShadowRoot(aRoot) || (isNode(b) && isShadowInclusiveAncestor(aRoot, b)) ) { return a; } a = getHost(aRoot); } } } // Accessors for non-public data const eventTargetData = Symbol(); const kResistStopImmediatePropagation = Symbol( "kResistStopImmediatePropagation", ); function setEventTargetData(target) { target[eventTargetData] = getDefaultTargetData(); } function getAssignedSlot(target) { return Boolean(target?.[eventTargetData]?.assignedSlot); } function getHasActivationBehavior(target) { return Boolean(target?.[eventTargetData]?.hasActivationBehavior); } function getHost(target) { return target?.[eventTargetData]?.host ?? null; } function getListeners(target) { return target?.[eventTargetData]?.listeners ?? {}; } function getMode(target) { return target?.[eventTargetData]?.mode ?? null; } function listenerCount(target, type) { return getListeners(target)?.[type]?.length ?? 0; } function getDefaultTargetData() { return { assignedSlot: false, hasActivationBehavior: false, host: null, listeners: ObjectCreate(null), mode: "", }; } function addEventListenerOptionsConverter(V, prefix) { if (webidl.type(V) !== "Object") { return { capture: !!V, once: false, passive: false }; } const options = { capture: !!V.capture, once: !!V.once, passive: !!V.passive, // This field exists for simulating Node.js behavior, implemented in https://github.com/nodejs/node/commit/bcd35c334ec75402ee081f1c4da128c339f70c24 // Some internal event listeners in Node.js can ignore `e.stopImmediatePropagation()` calls // from the earlier event listeners. [kResistStopImmediatePropagation]: !!V[kResistStopImmediatePropagation], }; const signal = V.signal; if (signal !== undefined) { options.signal = webidl.converters.AbortSignal( signal, prefix, "'signal' of 'AddEventListenerOptions' (Argument 3)", ); } return options; } class EventTarget { constructor() { // eventTargetData is allocated lazily on first addEventListener (or via // setEventTargetData for objects built with webidl.createBranded). Most // short-lived EventTarget instances created on the request/response hot // path (AbortSignal, transient streams) never have a listener attached // and never need the listeners map / 5-field default object. this[webidl.brand] = webidl.brand; } addEventListener( type, callback, options, ) { const self = this ?? globalThis_; webidl.assertBranded(self, EventTargetPrototype); const prefix = "Failed to execute 'addEventListener' on 'EventTarget'"; webidl.requiredArguments(arguments.length, 2, prefix); options = addEventListenerOptionsConverter(options, prefix); if (callback === null) { return; } let data = self[eventTargetData]; if (data === undefined) { data = self[eventTargetData] = getDefaultTargetData(); } const { listeners } = data; if (!listeners[type]) { listeners[type] = []; } const listenerList = listeners[type]; for (let i = 0; i < listenerList.length; ++i) { const listener = listenerList[i]; if ( ((typeof listener.options === "boolean" && listener.options === options.capture) || (typeof listener.options === "object" && listener.options.capture === options.capture)) && listener.callback === callback ) { return; } } if (options?.signal) { const signal = options?.signal; if (signal.aborted) { // If signal is not null and its aborted flag is set, then return. return; } else { // If listener's signal is not null, then add the following abort // abort steps to it: Remove an event listener. signal.addEventListener("abort", () => { self.removeEventListener(type, callback, options); }); } } ArrayPrototypePush(listeners[type], { callback, options }); } removeEventListener( type, callback, options, ) { const self = this ?? globalThis_; webidl.assertBranded(self, EventTargetPrototype); webidl.requiredArguments( arguments.length, 2, "Failed to execute 'removeEventListener' on 'EventTarget'", ); const data = self[eventTargetData]; if (data === undefined || callback === null || !data.listeners[type]) { return; } const { listeners } = data; options = normalizeEventHandlerOptions(options); for (let i = 0; i < listeners[type].length; ++i) { const listener = listeners[type][i]; if ( ((typeof listener.options === "boolean" && listener.options === options.capture) || (typeof listener.options === "object" && listener.options.capture === options.capture)) && listener.callback === callback ) { ArrayPrototypeSplice(listeners[type], i, 1); break; } } } dispatchEvent(event) { // If `this` is not present, then fallback to global scope. We don't use // `globalThis` directly here, because it could be deleted by user. // Instead use saved reference to global scope when the script was // executed. const self = this ?? globalThis_; webidl.assertBranded(self, EventTargetPrototype); webidl.requiredArguments( arguments.length, 1, "Failed to execute 'dispatchEvent' on 'EventTarget'", ); // This is an optimization to avoid creating an event listener // on each startup. // Stores the flag for checking whether unload is dispatched or not. // This prevents the recursive dispatches of unload events. // See https://github.com/denoland/deno/issues/9201. if (event.type === "unload" && self === globalThis_) { globalThis_[SymbolFor("Deno.isUnloadDispatched")] = true; } const data = self[eventTargetData]; if (data === undefined || !data.listeners[event.type]) { setTarget(event, this); return true; } if (getDispatched(event)) { throw new DOMException("Invalid event state", "InvalidStateError"); } if (event.eventPhase !== Event.NONE) { throw new DOMException("Invalid event state", "InvalidStateError"); } return dispatch(self, event); } getParent(_event) { return null; } [SymbolFor("Deno.privateCustomInspect")](inspect, inspectOptions) { return `${this.constructor.name} ${inspect({}, inspectOptions)}`; } } webidl.configureInterface(EventTarget); const EventTargetPrototype = EventTarget.prototype; defineEnumerableProps(EventTarget, [ "addEventListener", "removeEventListener", "dispatchEvent", ]); class ErrorEvent extends Event { #message = ""; #filename = ""; #lineno = ""; #colno = ""; #error = ""; get message() { return this.#message; } get filename() { return this.#filename; } get lineno() { return this.#lineno; } get colno() { return this.#colno; } get error() { return this.#error; } constructor( type, { bubbles, cancelable, composed, message = "", filename = "", lineno = 0, colno = 0, error, } = { __proto__: null }, ) { super(type, { bubbles: bubbles, cancelable: cancelable, composed: composed, }); this.#message = message; this.#filename = filename; this.#lineno = lineno; this.#colno = colno; this.#error = error; } [SymbolFor("Deno.privateCustomInspect")](inspect, inspectOptions) { return inspect( getCreateFilteredInspectProxy()({ object: this, evaluate: ObjectPrototypeIsPrototypeOf(ErrorEventPrototype, this), keys: [ ...new SafeArrayIterator(EVENT_PROPS), "message", "filename", "lineno", "colno", "error", ], }), inspectOptions, ); } // TODO(lucacasonato): remove when this interface is spec aligned [SymbolToStringTag] = "ErrorEvent"; } const ErrorEventPrototype = ErrorEvent.prototype; defineEnumerableProps(ErrorEvent, [ "message", "filename", "lineno", "colno", "error", ]); class CloseEvent extends Event { #wasClean = ""; #code = ""; #reason = ""; get wasClean() { return this.#wasClean; } get code() { return this.#code; } get reason() { return this.#reason; } constructor(type, { bubbles, cancelable, composed, wasClean = false, code = 0, reason = "", } = { __proto__: null }) { super(type, { bubbles: bubbles, cancelable: cancelable, composed: composed, }); this.#wasClean = wasClean; this.#code = code; this.#reason = reason; } [SymbolFor("Deno.privateCustomInspect")](inspect, inspectOptions) { return inspect( getCreateFilteredInspectProxy()({ object: this, evaluate: ObjectPrototypeIsPrototypeOf(CloseEventPrototype, this), keys: [ ...new SafeArrayIterator(EVENT_PROPS), "wasClean", "code", "reason", ], }), inspectOptions, ); } } const CloseEventPrototype = CloseEvent.prototype; // Lazy reference to MessagePort.prototype - 13_message_port.js depends on // this module, so the script can't be loaded at definition time without // causing a load cycle. Cached on first access. let _MessagePortPrototype; function getMessagePortPrototype() { if (_MessagePortPrototype === undefined) { _MessagePortPrototype = core.loadExtScript("ext:deno_web/13_message_port.js") .MessagePortPrototype; } return _MessagePortPrototype; } // Format an arbitrary JS value for inclusion in a MessageEvent constructor // error message *for the "must be MessagePort" check*: strings and primitives // get JSON-quoted (`"null"`, `"1"`, `"{}"`), matching the regex assertions // Node's tests use for source/ports[i]. function formatForBranded(value) { if (value === null) return '"null"'; if (value === undefined) return '"undefined"'; const t = typeof value; if (t === "string") return JSONStringify(value); if (t === "object" || t === "function") return '"{}"'; return JSONStringify(String(value)); } // Format for the iterable-check error: the raw value's string representation, // unquoted. Node's test expects `eventInitDict.ports (0) is not iterable.` function formatForRaw(value) { if (value === null) return "null"; if (value === undefined) return "undefined"; const t = typeof value; if (t === "string") return JSONStringify(value); if (t === "object" || t === "function") return "{}"; return String(value); } class MessageEvent extends Event { #source = null; get source() { return this.#source; } constructor(type, eventInitDict) { super(type, { bubbles: eventInitDict?.bubbles ?? false, cancelable: eventInitDict?.cancelable ?? false, composed: eventInitDict?.composed ?? false, }); this.data = eventInitDict?.data ?? null; const ports = eventInitDict?.ports; if (ports == null) { // `ports` is a FrozenArray<MessagePort> per the HTML spec, so the // exposed array must be read-only. this.ports = ObjectFreeze([]); } else { // MessageEvent ports: iterable validation + per-element MessagePort // type check. Matches the messages Node asserts on so the // node_compat tests pass while still being WHATWG-shaped. if ( ports === null || typeof ports !== "object" || ports[SymbolIterator] === undefined ) { throw new TypeError( `MessageEvent constructor: eventInitDict.ports (${ formatForRaw(ports) }) is not iterable.`, ); } const MessagePortProto = getMessagePortPrototype(); const arr = []; let i = 0; // Iterate using the user's own iterator so values that aren't real // arrays (e.g. a `RegExp` with a custom `Symbol.iterator` -- covered // by WPT's no-regexp-special-casing test) still produce the // expected `ports` array. SafeArrayIterator can't be used here // because it walks the value as if it were an Array. // deno-lint-ignore deno-internal/prefer-primordials for (const p of ports) { if ( p === null || typeof p !== "object" || !ObjectPrototypeIsPrototypeOf(MessagePortProto, p) ) { throw new TypeError( `MessageEvent constructor: Expected eventInitDict.ports[${i}] (${ formatForBranded(p) }) to be an instance of MessagePort.`, ); } arr[i++] = p; } // `ports` is a FrozenArray<MessagePort> per the HTML spec. this.ports = ObjectFreeze(arr); } // origin and lastEventId are USVString per spec, so coerce to string // (Node's test passes numbers and expects string coercion). this.origin = eventInitDict?.origin === undefined ? "" : String(eventInitDict.origin); this.lastEventId = eventInitDict?.lastEventId === undefined ? "" : String(eventInitDict.lastEventId); const source = eventInitDict?.source; if (source != null) { // The Web spec types source as `MessageEventSource = MessagePort | // ServiceWorker | WindowProxy`. We don't have a JS-level Window or // ServiceWorker brand to gate against, so accept anything except // primitives (which the node_compat tests expect to throw on, // e.g. `source: 1`) and plain `Object` instances like `{}` (also // checked by Node). MessagePort, Window-like globals, and // user-defined classes all pass through unchanged -- matching both // the WPT `messageevent-constructor.https.html` test (which // assigns `window` to `source`) and node_compat // `test-worker-message-event`. const t = typeof source; const isPrimitive = t !== "object" && t !== "function"; // Treat values whose prototype is exactly Object.prototype (i.e. // plain object literals like `{}`) as invalid sources. Using the // prototype chain rather than `.constructor` avoids the // prefer-primordials lint and is more reliable since user code // can override `constructor`. const isPlainObject = !isPrimitive && ObjectGetPrototypeOf(source) === ObjectPrototype; if (isPrimitive || isPlainObject) { throw new TypeError( `MessageEvent constructor: Expected eventInitDict.source (${ formatForBranded(source) }) to be an instance of MessagePort.`, ); } this.#source = source; } else { this.#source = null; } } [SymbolFor("Deno.privateCustomInspect")](inspect, inspectOptions) { return inspect( getCreateFilteredInspectProxy()({ object: this, evaluate: ObjectPrototypeIsPrototypeOf(MessageEventPrototype, this), keys: [ ...new SafeArrayIterator(EVENT_PROPS), "data", "origin", "lastEventId", ], }), inspectOptions, ); } // TODO(lucacasonato): remove when this interface is spec aligned [SymbolToStringTag] = "MessageEvent"; } const MessageEventPrototype = MessageEvent.prototype; class CustomEvent extends Event { #detail = null; constructor(type, eventInitDict = { __proto__: null }) { super(type, eventInitDict); webidl.requiredArguments( arguments.length, 1, "Failed to construct 'CustomEvent'", ); const { detail } = eventInitDict; this.#detail = detail; } get detail() { return this.#detail; } [SymbolFor("Deno.privateCustomInspect")](inspect, inspectOptions) { return inspect( getCreateFilteredInspectProxy()({ object: this, evaluate: ObjectPrototypeIsPrototypeOf(CustomEventPrototype, this), keys: [ ...new SafeArrayIterator(EVENT_PROPS), "detail", ], }), inspectOptions, ); } // TODO(lucacasonato): remove when this interface is spec aligned [SymbolToStringTag] = "CustomEvent"; } const CustomEventPrototype = CustomEvent.prototype; ReflectDefineProperty(CustomEvent.prototype, "detail", { __proto__: null, enumerable: true, }); // ProgressEvent could also be used in other DOM progress event emits. // Current use is for FileReader. class ProgressEvent extends Event { constructor(type, eventInitDict = { __proto__: null }) { super(type, eventInitDict); this.lengthComputable = eventInitDict?.lengthComputable ?? false; this.loaded = eventInitDict?.loaded ?? 0; this.total = eventInitDict?.total ?? 0; } [SymbolFor("Deno.privateCustomInspect")](inspect, inspectOptions) { return inspect( getCreateFilteredInspectProxy()({ object: this, evaluate: ObjectPrototypeIsPrototypeOf(ProgressEventPrototype, this), keys: [ ...new SafeArrayIterator(EVENT_PROPS), "lengthComputable", "loaded", "total", ], }), inspectOptions, ); } // TODO(lucacasonato): remove when this interface is spec aligned [SymbolToStringTag] = "ProgressEvent"; } const ProgressEventPrototype = ProgressEvent.prototype; class PromiseRejectionEvent extends Event { #promise = null; #reason = null; get promise() { return this.#promise; } get reason() { return this.#reason; } constructor( type, { bubbles, cancelable, composed, promise, reason, } = { __proto__: null }, ) { super(type, { bubbles: bubbles, cancelable: cancelable, composed: composed, }); this.#promise = promise; this.#reason = reason; } [SymbolFor("Deno.privateCustomInspect")](inspect, inspectOptions) { return inspect( getCreateFilteredInspectProxy()({ object: this, evaluate: ObjectPrototypeIsPrototypeOf( PromiseRejectionEventPrototype, this, ), keys: [ ...new SafeArrayIterator(EVENT_PROPS), "promise", "reason", ], }), inspectOptions, ); } // TODO(lucacasonato): remove when this interface is spec aligned [SymbolToStringTag] = "PromiseRejectionEvent"; } const PromiseRejectionEventPrototype = PromiseRejectionEvent.prototype; defineEnumerableProps(PromiseRejectionEvent, [ "promise", "reason", ]); const _eventHandlers = Symbol("eventHandlers"); function makeWrappedHandler(handler, isSpecialErrorEventHandler) { function wrappedHandler(evt) { if (typeof wrappedHandler.handler !== "function") { return; } if ( isSpecialErrorEventHandler && ObjectPrototypeIsPrototypeOf(ErrorEventPrototype, evt) && evt.type === "error" ) { const ret = FunctionPrototypeCall( wrappedHandler.handler, this, evt.message, evt.filename, evt.lineno, evt.colno, evt.error, ); if (ret === true) { evt.preventDefault(); } return; } return FunctionPrototypeCall(wrappedHandler.handler, this, evt); } wrappedHandler.handler = handler; return wrappedHandler; } // `init` is an optional function that will be called the first time that the // event handler property is set. It will be called with the object on which // the property is set as its argument. // `isSpecialErrorEventHandler` can be set to true to opt into the special // behavior of event handlers for the "error" event in a global scope. function defineEventHandler( emitter, name, init = undefined, isSpecialErrorEventHandler = false, ) { // HTML specification section 8.1.7.1 ObjectDefineProperty(emitter, `on${name}`, { __proto__: null, get() { if (!this[_eventHandlers]) { return null; } return MapPrototypeGet(this[_eventHandlers], name)?.handler ?? null; }, set(value) { // All three Web IDL event handler types are nullable callback functions // with the [LegacyTreatNonObjectAsNull] extended attribute, meaning // anything other than an object is treated as null. if (typeof value !== "object" && typeof value !== "function") { value = null; } if (!this[_eventHandlers]) { this[_eventHandlers] = new SafeMap(); } let handlerWrapper = MapPrototypeGet(this[_eventHandlers], name); if (handlerWrapper) { handlerWrapper.handler = value; } else if (value !== null) { handlerWrapper = makeWrappedHandler( value, isSpecialErrorEventHandler, ); this.addEventListener(name, handlerWrapper); init?.(this); } MapPrototypeSet(this[_eventHandlers], name, handlerWrapper); }, configurable: true, enumerable: true, }); } let reportExceptionStackedCalls = 0; // https://html.spec.whatwg.org/#report-the-exception function reportException(error) { reportExceptionStackedCalls++; const jsError = core.destructureError(error); const message = jsError.exceptionMessage; let filename = ""; let lineno = 0; let colno = 0; if (jsError.frames.length > 0) { filename = jsError.frames[0].fileName; lineno = jsError.frames[0].lineNumber; colno = jsError.frames[0].columnNumber; } else { const jsError = core.destructureError(new Error()); const frames = jsError.frames; for (let i = 0; i < frames.length; ++i) { const frame = frames[i]; if ( typeof frame.fileName == "string" && !StringPrototypeStartsWith(frame.fileName, "ext:") ) { filename = frame.fileName; lineno = frame.lineNumber; colno = frame.columnNumber; break; } } } const event = new ErrorEvent("error", { cancelable: true, message, filename, lineno, colno, error, }); // Avoid recursing `reportException()` via error handlers more than once. if (reportExceptionStackedCalls > 1 || globalThis_.dispatchEvent(event)) { core.reportUnhandledException(error); } reportExceptionStackedCalls--; } function checkThis(thisArg) { if (thisArg !== null && thisArg !== undefined && thisArg !== globalThis_) { throw new TypeError("Illegal invocation"); } } // https://html.spec.whatwg.org/#dom-reporterror function reportError(error) { checkThis(this); const prefix = "Failed to execute 'reportError'"; webidl.requiredArguments(arguments.length, 1, prefix); reportException(error); } internals.defineEventHandler = defineEventHandler; internals.setEventTargetData = setEventTargetData; return { CloseEvent, CustomEvent, defineEventHandler, dispatch, ErrorEvent, Event, eventTargetData, EventTarget, EventTargetPrototype, kResistStopImmediatePropagation, listenerCount, MessageEvent, ProgressEvent, PromiseRejectionEvent, reportError, reportException, saveGlobalThisReference, setEventTargetData, setIsTrusted, setTarget, }; })();