deno/ext/web/02_event.js
Kenta Moriuchi 0c6deae6c5
tweak
2025-09-05 13:11:29 +09:00

547 lines
12 KiB
JavaScript

// Copyright 2018-2025 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.
import { primordials } from "ext:core/mod.js";
const {
ArrayPrototypeFlat,
FunctionPrototypeCall,
MapPrototypeGet,
MapPrototypeSet,
ObjectDefineProperty,
ObjectDefineProperties,
ObjectPrototypeIsPrototypeOf,
ObjectSetPrototypeOf,
SafeMap,
Symbol,
SymbolFor,
} = primordials;
import {
CloseEvent,
CustomEvent,
ErrorEvent,
Event,
EventTarget,
MessageEvent,
op_event_create_empty_event_target,
op_event_dispatch,
op_event_get_target_listener_count,
op_event_get_target_listeners,
op_event_report_error,
op_event_report_exception,
op_event_set_is_trusted,
op_event_set_target,
op_event_wrap_event_target,
ProgressEvent,
PromiseRejectionEvent,
} from "ext:core/ops";
import * as webidl from "ext:deno_webidl/00_webidl.js";
import { createFilteredInspectProxy } from "ext:deno_console/01_console.js";
function defineEnumerableProps(prototype, props) {
for (let i = 0; i < props.length; ++i) {
const prop = props[i];
ObjectDefineProperty(prototype, prop, {
__proto__: null,
enumerable: true,
});
}
}
// accessors for non runtime visible data
/**
* @param {Event} event
* @param {boolean} value
*/
function setIsTrusted(event, value) {
op_event_set_is_trusted(event, value);
}
/**
* @param {Event} event
* @param {object} value
*/
function setTarget(event, value) {
op_event_set_target(event, value);
}
ObjectDefineProperties(Event, {
NONE: {
__proto__: null,
value: 0,
writable: false,
enumerable: true,
configurable: false,
},
CAPTURING_PHASE: {
__proto__: null,
value: 1,
writable: false,
enumerable: true,
configurable: false,
},
AT_TARGET: {
__proto__: null,
value: 2,
writable: false,
enumerable: true,
configurable: false,
},
BUBBLING_PHASE: {
__proto__: null,
value: 3,
writable: false,
enumerable: true,
configurable: false,
},
});
webidl.configureInterface(Event);
const EventPrototype = Event.prototype;
ObjectDefineProperties(Event.prototype, {
NONE: {
__proto__: null,
value: 0,
writable: false,
enumerable: true,
configurable: false,
},
CAPTURING_PHASE: {
__proto__: null,
value: 1,
writable: false,
enumerable: true,
configurable: false,
},
AT_TARGET: {
__proto__: null,
value: 2,
writable: false,
enumerable: true,
configurable: false,
},
BUBBLING_PHASE: {
__proto__: null,
value: 3,
writable: false,
enumerable: true,
configurable: false,
},
[SymbolFor("Deno.privateCustomInspect")]: {
__proto__: null,
value(inspect, inspectOptions) {
return inspect(
createFilteredInspectProxy({
object: this,
evaluate: ObjectPrototypeIsPrototypeOf(EventPrototype, this),
keys: EVENT_PROPS,
}),
inspectOptions,
);
},
},
});
const EVENT_PROPS = [
"type",
"target",
"currentTarget",
"eventPhase",
"bubbles",
"cancelable",
"defaultPrevented",
"composed",
"timeStamp",
"srcElement",
"returnValue",
"cancelBubble",
// Not spec compliant. The spec defines it as [LegacyUnforgeable]
// but doing so has a big performance hit
"isTrusted",
];
defineEnumerableProps(Event.prototype, EVENT_PROPS);
// Accessors for non-public data
/**
* NOTE: It is necessary to call setEventTargetData at runtime, not at the snapshot timing.
* @param {object} prototype
* @returns {object}
*/
function createEventTargetBranded(prototype) {
const t = op_event_create_empty_event_target();
t[webidl.brand] = webidl.brand;
ObjectSetPrototypeOf(t, prototype);
return t;
}
/**
* @param {object} target
*/
function setEventTargetData(target) {
op_event_wrap_event_target(target);
}
/**
* @param {EventTarget} target
* @param {Event} event
* @param {object=} targetOverride
*/
function dispatch(target, event, targetOverride) {
op_event_dispatch(target, event, targetOverride);
}
/**
* @param {EventTarget} target
* @param {string} type
* @return {EventListenerOrEventListenerObject[]}
*/
function getListeners(target, type) {
return op_event_get_target_listeners(target, type);
}
/**
* @param {EventTarget} target
* @param {string} type
* @returns {number}
*/
function getListenerCount(target, type) {
return op_event_get_target_listener_count(target, type);
}
webidl.configureInterface(EventTarget);
const EventTargetPrototype = EventTarget.prototype;
ObjectDefineProperty(
EventTarget.prototype,
SymbolFor("Deno.privateCustomInspect"),
{
__proto__: null,
value(inspect, inspectOptions) {
return `${this.constructor.name} ${inspect({}, inspectOptions)}`;
},
},
);
defineEnumerableProps(EventTarget.prototype, [
"addEventListener",
"removeEventListener",
"dispatchEvent",
]);
webidl.configureInterface(CustomEvent);
const CustomEventPrototype = CustomEvent.prototype;
ObjectDefineProperty(
CustomEvent.prototype,
SymbolFor("Deno.privateCustomInspect"),
{
__proto__: null,
value(inspect, inspectOptions) {
return inspect(
createFilteredInspectProxy({
object: this,
evaluate: ObjectPrototypeIsPrototypeOf(CustomEventPrototype, this),
keys: ArrayPrototypeFlat([
EVENT_PROPS,
"detail",
]),
}),
inspectOptions,
);
},
},
);
ObjectDefineProperty(CustomEvent.prototype, "detail", {
__proto__: null,
enumerable: true,
});
webidl.configureInterface(ErrorEvent);
const ErrorEventPrototype = ErrorEvent.prototype;
ObjectDefineProperty(
ErrorEvent.prototype,
SymbolFor("Deno.privateCustomInspect"),
{
__proto__: null,
value(inspect, inspectOptions) {
return inspect(
createFilteredInspectProxy({
object: this,
evaluate: ObjectPrototypeIsPrototypeOf(ErrorEventPrototype, this),
keys: ArrayPrototypeFlat([
EVENT_PROPS,
ERROR_EVENT_PROPS,
]),
}),
inspectOptions,
);
},
},
);
const ERROR_EVENT_PROPS = [
"message",
"filename",
"lineno",
"colno",
"error",
];
defineEnumerableProps(ErrorEvent.prototype, ERROR_EVENT_PROPS);
webidl.configureInterface(PromiseRejectionEvent);
const PromiseRejectionEventPrototype = PromiseRejectionEvent.prototype;
ObjectDefineProperty(
PromiseRejectionEvent.prototype,
SymbolFor("Deno.privateCustomInspect"),
{
__proto__: null,
value(inspect, inspectOptions) {
return inspect(
createFilteredInspectProxy({
object: this,
evaluate: ObjectPrototypeIsPrototypeOf(
PromiseRejectionEventPrototype,
this,
),
keys: ArrayPrototypeFlat([
EVENT_PROPS,
PROMISE_REJECTION_EVENT_PROPS,
]),
}),
inspectOptions,
);
},
},
);
const PROMISE_REJECTION_EVENT_PROPS = [
"promise",
"reason",
];
defineEnumerableProps(
PromiseRejectionEvent.prototype,
PROMISE_REJECTION_EVENT_PROPS,
);
webidl.configureInterface(CloseEvent);
const CloseEventPrototype = CloseEvent.prototype;
ObjectDefineProperty(
CloseEvent.prototype,
SymbolFor("Deno.privateCustomInspect"),
{
__proto__: null,
value(inspect, inspectOptions) {
return inspect(
createFilteredInspectProxy({
object: this,
evaluate: ObjectPrototypeIsPrototypeOf(CloseEventPrototype, this),
keys: ArrayPrototypeFlat([
EVENT_PROPS,
CLOSE_EVENT_PROPS,
]),
}),
inspectOptions,
);
},
},
);
const CLOSE_EVENT_PROPS = [
"wasClean",
"code",
"reason",
];
defineEnumerableProps(CloseEvent.prototype, CLOSE_EVENT_PROPS);
webidl.configureInterface(MessageEvent);
const MessageEventPrototype = MessageEvent.prototype;
ObjectDefineProperty(
MessageEvent.prototype,
SymbolFor("Deno.privateCustomInspect"),
{
__proto__: null,
value(inspect, inspectOptions) {
return inspect(
createFilteredInspectProxy({
object: this,
evaluate: ObjectPrototypeIsPrototypeOf(MessageEventPrototype, this),
keys: ArrayPrototypeFlat([
EVENT_PROPS,
MESSAGE_EVENT_PROPS,
]),
}),
inspectOptions,
);
},
},
);
const MESSAGE_EVENT_PROPS = [
"data",
"origin",
"lastEventId",
"source",
"ports",
];
defineEnumerableProps(MessageEvent.prototype, MESSAGE_EVENT_PROPS);
webidl.configureInterface(ProgressEvent);
const ProgressEventPrototype = ProgressEvent.prototype;
ObjectDefineProperty(
ProgressEvent.prototype,
SymbolFor("Deno.privateCustomInspect"),
{
__proto__: null,
value(inspect, inspectOptions) {
return inspect(
createFilteredInspectProxy({
object: this,
evaluate: ObjectPrototypeIsPrototypeOf(ProgressEventPrototype, this),
keys: ArrayPrototypeFlat([
EVENT_PROPS,
PROGRESS_EVENT_PROPS,
]),
}),
inspectOptions,
);
},
},
);
const PROGRESS_EVENT_PROPS = [
"lengthComputable",
"loaded",
"total",
];
defineEnumerableProps(ProgressEvent.prototype, PROGRESS_EVENT_PROPS);
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,
});
}
// https://html.spec.whatwg.org/#report-the-exception
function reportException(error) {
op_event_report_exception(error);
}
// https://html.spec.whatwg.org/#dom-reporterror
function reportError(error) {
FunctionPrototypeCall(op_event_report_error, this, error);
}
export {
CloseEvent,
createEventTargetBranded,
CustomEvent,
defineEventHandler,
dispatch,
ErrorEvent,
Event,
EventTarget,
EventTargetPrototype,
getListenerCount,
getListeners,
MessageEvent,
ProgressEvent,
PromiseRejectionEvent,
reportError,
reportException,
setEventTargetData,
setIsTrusted,
setTarget,
};