mirror of
https://github.com/denoland/deno.git
synced 2025-09-26 20:29:11 +00:00
547 lines
12 KiB
JavaScript
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,
|
|
};
|