0
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2025-03-03 17:34:47 -05:00

Support window.onload (#2643)

This commit is contained in:
Yoshiya Hinosawa 2019-07-16 13:19:26 +09:00 committed by Ryan Dahl
parent bd6ebb32df
commit 9c45499864
14 changed files with 162 additions and 88 deletions

View file

@ -14,6 +14,7 @@
"@typescript-eslint/array-type": ["error", "array-simple"], "@typescript-eslint/array-type": ["error", "array-simple"],
"@typescript-eslint/explicit-member-accessibility": ["off"], "@typescript-eslint/explicit-member-accessibility": ["off"],
"@typescript-eslint/no-non-null-assertion": ["off"], "@typescript-eslint/no-non-null-assertion": ["off"],
"@typescript-eslint/no-use-before-define": ["off"],
"@typescript-eslint/no-parameter-properties": ["off"], "@typescript-eslint/no-parameter-properties": ["off"],
"@typescript-eslint/no-unused-vars": [ "@typescript-eslint/no-unused-vars": [
"error", "error",

View file

@ -314,6 +314,7 @@ fn run_script(flags: DenoFlags, argv: Vec<String>) {
worker worker
.execute_mod_async(&main_module, false) .execute_mod_async(&main_module, false)
.and_then(move |()| { .and_then(move |()| {
js_check(worker.execute("window.dispatchEvent(new Event('load'))"));
worker.then(|result| { worker.then(|result| {
js_check(result); js_check(result);
Ok(()) Ok(())

View file

@ -71,11 +71,16 @@ export enum NodeType {
DOCUMENT_FRAGMENT_NODE = 11 DOCUMENT_FRAGMENT_NODE = 11
} }
export const eventTargetHost: unique symbol = Symbol();
export const eventTargetListeners: unique symbol = Symbol();
export const eventTargetMode: unique symbol = Symbol();
export const eventTargetNodeType: unique symbol = Symbol();
export interface EventTarget { export interface EventTarget {
host: EventTarget | null; [eventTargetHost]: EventTarget | null;
listeners: { [type in string]: EventListener[] }; [eventTargetListeners]: { [type in string]: EventListener[] };
mode: string; [eventTargetMode]: string;
nodeType: NodeType; [eventTargetNodeType]: NodeType;
addEventListener( addEventListener(
type: string, type: string,
callback: (event: Event) => void | null, callback: (event: Event) => void | null,

View file

@ -9,16 +9,18 @@ export function isNode(nodeImpl: domTypes.EventTarget | null): boolean {
export function isShadowRoot(nodeImpl: domTypes.EventTarget | null): boolean { export function isShadowRoot(nodeImpl: domTypes.EventTarget | null): boolean {
return Boolean( return Boolean(
nodeImpl && nodeImpl &&
nodeImpl.nodeType === domTypes.NodeType.DOCUMENT_FRAGMENT_NODE && nodeImpl[domTypes.eventTargetNodeType] ===
"host" in nodeImpl domTypes.NodeType.DOCUMENT_FRAGMENT_NODE &&
nodeImpl[domTypes.eventTargetHost] != null
); );
} }
export function isSlotable(nodeImpl: domTypes.EventTarget | null): boolean { export function isSlotable(nodeImpl: domTypes.EventTarget | null): boolean {
return Boolean( return Boolean(
nodeImpl && nodeImpl &&
(nodeImpl.nodeType === domTypes.NodeType.ELEMENT_NODE || (nodeImpl[domTypes.eventTargetNodeType] ===
nodeImpl.nodeType === domTypes.NodeType.TEXT_NODE) domTypes.NodeType.ELEMENT_NODE ||
nodeImpl[domTypes.eventTargetNodeType] === domTypes.NodeType.TEXT_NODE)
); );
} }
@ -36,7 +38,7 @@ export function isShadowInclusiveAncestor(
} }
if (isShadowRoot(node)) { if (isShadowRoot(node)) {
node = node && node.host; node = node && node[domTypes.eventTargetHost];
} else { } else {
node = null; // domSymbolTree.parent(node); node = null; // domSymbolTree.parent(node);
} }
@ -77,7 +79,7 @@ export function retarget(
return a; return a;
} }
a = aRoot.host; a = aRoot[domTypes.eventTargetHost];
} }
} }
} }

View file

@ -98,13 +98,19 @@ export class EventListener implements domTypes.EventListener {
} }
} }
export const eventTargetAssignedSlot: unique symbol = Symbol();
export const eventTargetHasActivationBehavior: unique symbol = Symbol();
export class EventTarget implements domTypes.EventTarget { export class EventTarget implements domTypes.EventTarget {
public host: domTypes.EventTarget | null = null; public [domTypes.eventTargetHost]: domTypes.EventTarget | null = null;
public listeners: { [type in string]: domTypes.EventListener[] } = {}; public [domTypes.eventTargetListeners]: {
public mode = ""; [type in string]: domTypes.EventListener[]
public nodeType: domTypes.NodeType = domTypes.NodeType.DOCUMENT_FRAGMENT_NODE; } = {};
private _assignedSlot = false; public [domTypes.eventTargetMode] = "";
private _hasActivationBehavior = false; public [domTypes.eventTargetNodeType]: domTypes.NodeType =
domTypes.NodeType.DOCUMENT_FRAGMENT_NODE;
private [eventTargetAssignedSlot] = false;
private [eventTargetHasActivationBehavior] = false;
public addEventListener( public addEventListener(
type: string, type: string,
@ -112,7 +118,7 @@ export class EventTarget implements domTypes.EventTarget {
options?: domTypes.AddEventListenerOptions | boolean options?: domTypes.AddEventListenerOptions | boolean
): void { ): void {
requiredArguments("EventTarget.addEventListener", arguments.length, 2); requiredArguments("EventTarget.addEventListener", arguments.length, 2);
const normalizedOptions: domTypes.AddEventListenerOptions = this._normalizeAddEventHandlerOptions( const normalizedOptions: domTypes.AddEventListenerOptions = eventTargetHelpers.normalizeAddEventHandlerOptions(
options options
); );
@ -120,12 +126,14 @@ export class EventTarget implements domTypes.EventTarget {
return; return;
} }
if (!hasOwnProperty(this.listeners, type)) { const listeners = this[domTypes.eventTargetListeners];
this.listeners[type] = [];
if (!hasOwnProperty(listeners, type)) {
listeners[type] = [];
} }
for (let i = 0; i < this.listeners[type].length; ++i) { for (let i = 0; i < listeners[type].length; ++i) {
const listener = this.listeners[type][i]; const listener = listeners[type][i];
if ( if (
((typeof listener.options === "boolean" && ((typeof listener.options === "boolean" &&
listener.options === normalizedOptions.capture) || listener.options === normalizedOptions.capture) ||
@ -137,7 +145,7 @@ export class EventTarget implements domTypes.EventTarget {
} }
} }
this.listeners[type].push(new EventListener(callback, normalizedOptions)); listeners[type].push(new EventListener(callback, normalizedOptions));
} }
public removeEventListener( public removeEventListener(
@ -146,13 +154,14 @@ export class EventTarget implements domTypes.EventTarget {
options?: domTypes.EventListenerOptions | boolean options?: domTypes.EventListenerOptions | boolean
): void { ): void {
requiredArguments("EventTarget.removeEventListener", arguments.length, 2); requiredArguments("EventTarget.removeEventListener", arguments.length, 2);
if (hasOwnProperty(this.listeners, type) && callback !== null) { const listeners = this[domTypes.eventTargetListeners];
this.listeners[type] = this.listeners[type].filter( if (hasOwnProperty(listeners, type) && callback !== null) {
listeners[type] = listeners[type].filter(
(listener): boolean => listener.callback !== callback (listener): boolean => listener.callback !== callback
); );
} }
const normalizedOptions: domTypes.EventListenerOptions = this._normalizeEventHandlerOptions( const normalizedOptions: domTypes.EventListenerOptions = eventTargetHelpers.normalizeEventHandlerOptions(
options options
); );
@ -161,12 +170,12 @@ export class EventTarget implements domTypes.EventTarget {
return; return;
} }
if (!this.listeners[type]) { if (!listeners[type]) {
return; return;
} }
for (let i = 0; i < this.listeners[type].length; ++i) { for (let i = 0; i < listeners[type].length; ++i) {
const listener = this.listeners[type][i]; const listener = listeners[type][i];
if ( if (
((typeof listener.options === "boolean" && ((typeof listener.options === "boolean" &&
@ -175,7 +184,7 @@ export class EventTarget implements domTypes.EventTarget {
listener.options.capture === normalizedOptions.capture)) && listener.options.capture === normalizedOptions.capture)) &&
listener.callback === callback listener.callback === callback
) { ) {
this.listeners[type].splice(i, 1); listeners[type].splice(i, 1);
break; break;
} }
} }
@ -183,7 +192,8 @@ export class EventTarget implements domTypes.EventTarget {
public dispatchEvent(event: domTypes.Event): boolean { public dispatchEvent(event: domTypes.Event): boolean {
requiredArguments("EventTarget.dispatchEvent", arguments.length, 1); requiredArguments("EventTarget.dispatchEvent", arguments.length, 1);
if (!hasOwnProperty(this.listeners, event.type)) { const listeners = this[domTypes.eventTargetListeners];
if (!hasOwnProperty(listeners, event.type)) {
return true; return true;
} }
@ -201,15 +211,21 @@ export class EventTarget implements domTypes.EventTarget {
); );
} }
return this._dispatch(event); return eventTargetHelpers.dispatch(this, event);
} }
get [Symbol.toStringTag](): string {
return "EventTarget";
}
}
const eventTargetHelpers = {
// https://dom.spec.whatwg.org/#concept-event-dispatch // https://dom.spec.whatwg.org/#concept-event-dispatch
_dispatch( dispatch(
targetImpl: EventTarget,
eventImpl: domTypes.Event, eventImpl: domTypes.Event,
targetOverride?: domTypes.EventTarget targetOverride?: domTypes.EventTarget
): boolean { ): boolean {
let targetImpl = this;
let clearTargets = false; let clearTargets = false;
let activationTarget = null; let activationTarget = null;
@ -224,7 +240,7 @@ export class EventTarget implements domTypes.EventTarget {
) { ) {
const touchTargets: domTypes.EventTarget[] = []; const touchTargets: domTypes.EventTarget[] = [];
this._appendToEventPath( eventTargetHelpers.appendToEventPath(
eventImpl, eventImpl,
targetImpl, targetImpl,
targetOverride, targetOverride,
@ -235,13 +251,15 @@ export class EventTarget implements domTypes.EventTarget {
const isActivationEvent = eventImpl.type === "click"; const isActivationEvent = eventImpl.type === "click";
if (isActivationEvent && targetImpl._hasActivationBehavior) { if (isActivationEvent && targetImpl[eventTargetHasActivationBehavior]) {
activationTarget = targetImpl; activationTarget = targetImpl;
} }
let slotInClosedTree = false; let slotInClosedTree = false;
let slotable = let slotable =
isSlotable(targetImpl) && targetImpl._assignedSlot ? targetImpl : null; isSlotable(targetImpl) && targetImpl[eventTargetAssignedSlot]
? targetImpl
: null;
let parent = getEventTargetParent(targetImpl, eventImpl); let parent = getEventTargetParent(targetImpl, eventImpl);
// Populate event path // Populate event path
@ -254,7 +272,7 @@ export class EventTarget implements domTypes.EventTarget {
if ( if (
isShadowRoot(parentRoot) && isShadowRoot(parentRoot) &&
parentRoot && parentRoot &&
parentRoot.mode === "closed" parentRoot[domTypes.eventTargetMode] === "closed"
) { ) {
slotInClosedTree = true; slotInClosedTree = true;
} }
@ -266,7 +284,7 @@ export class EventTarget implements domTypes.EventTarget {
isNode(parent) && isNode(parent) &&
isShadowInclusiveAncestor(getRoot(targetImpl), parent) isShadowInclusiveAncestor(getRoot(targetImpl), parent)
) { ) {
this._appendToEventPath( eventTargetHelpers.appendToEventPath(
eventImpl, eventImpl,
parent, parent,
null, null,
@ -282,12 +300,12 @@ export class EventTarget implements domTypes.EventTarget {
if ( if (
isActivationEvent && isActivationEvent &&
activationTarget === null && activationTarget === null &&
targetImpl._hasActivationBehavior targetImpl[eventTargetHasActivationBehavior]
) { ) {
activationTarget = targetImpl; activationTarget = targetImpl;
} }
this._appendToEventPath( eventTargetHelpers.appendToEventPath(
eventImpl, eventImpl,
parent, parent,
targetImpl, targetImpl,
@ -328,7 +346,7 @@ export class EventTarget implements domTypes.EventTarget {
const tuple = eventImpl.path[i]; const tuple = eventImpl.path[i];
if (tuple.target === null) { if (tuple.target === null) {
this._invokeEventListeners(tuple, eventImpl); eventTargetHelpers.invokeEventListeners(targetImpl, tuple, eventImpl);
} }
} }
@ -346,7 +364,7 @@ export class EventTarget implements domTypes.EventTarget {
eventImpl.bubbles) || eventImpl.bubbles) ||
eventImpl.eventPhase === domTypes.EventPhase.AT_TARGET eventImpl.eventPhase === domTypes.EventPhase.AT_TARGET
) { ) {
this._invokeEventListeners(tuple, eventImpl); eventTargetHelpers.invokeEventListeners(targetImpl, tuple, eventImpl);
} }
} }
} }
@ -372,10 +390,11 @@ export class EventTarget implements domTypes.EventTarget {
// } // }
return !eventImpl.defaultPrevented; return !eventImpl.defaultPrevented;
} },
// https://dom.spec.whatwg.org/#concept-event-listener-invoke // https://dom.spec.whatwg.org/#concept-event-listener-invoke
_invokeEventListeners( invokeEventListeners(
targetImpl: EventTarget,
tuple: domTypes.EventPath, tuple: domTypes.EventPath,
eventImpl: domTypes.Event eventImpl: domTypes.Event
): void { ): void {
@ -396,11 +415,16 @@ export class EventTarget implements domTypes.EventTarget {
eventImpl.currentTarget = tuple.item; eventImpl.currentTarget = tuple.item;
this._innerInvokeEventListeners(eventImpl, tuple.item.listeners); eventTargetHelpers.innerInvokeEventListeners(
} targetImpl,
eventImpl,
tuple.item[domTypes.eventTargetListeners]
);
},
// https://dom.spec.whatwg.org/#concept-event-listener-inner-invoke // https://dom.spec.whatwg.org/#concept-event-listener-inner-invoke
_innerInvokeEventListeners( innerInvokeEventListeners(
targetImpl: EventTarget,
eventImpl: domTypes.Event, eventImpl: domTypes.Event,
targetListeners: { [type in string]: domTypes.EventListener[] } targetListeners: { [type in string]: domTypes.EventListener[] }
): boolean { ): boolean {
@ -471,9 +495,9 @@ export class EventTarget implements domTypes.EventTarget {
} }
return found; return found;
} },
_normalizeAddEventHandlerOptions( normalizeAddEventHandlerOptions(
options: boolean | domTypes.AddEventListenerOptions | undefined options: boolean | domTypes.AddEventListenerOptions | undefined
): domTypes.AddEventListenerOptions { ): domTypes.AddEventListenerOptions {
if (typeof options === "boolean" || typeof options === "undefined") { if (typeof options === "boolean" || typeof options === "undefined") {
@ -487,9 +511,9 @@ export class EventTarget implements domTypes.EventTarget {
} else { } else {
return options; return options;
} }
} },
_normalizeEventHandlerOptions( normalizeEventHandlerOptions(
options: boolean | domTypes.EventListenerOptions | undefined options: boolean | domTypes.EventListenerOptions | undefined
): domTypes.EventListenerOptions { ): domTypes.EventListenerOptions {
if (typeof options === "boolean" || typeof options === "undefined") { if (typeof options === "boolean" || typeof options === "undefined") {
@ -501,10 +525,10 @@ export class EventTarget implements domTypes.EventTarget {
} else { } else {
return options; return options;
} }
} },
// https://dom.spec.whatwg.org/#concept-event-path-append // https://dom.spec.whatwg.org/#concept-event-path-append
_appendToEventPath( appendToEventPath(
eventImpl: domTypes.Event, eventImpl: domTypes.Event,
target: domTypes.EventTarget, target: domTypes.EventTarget,
targetOverride: domTypes.EventTarget | null, targetOverride: domTypes.EventTarget | null,
@ -513,7 +537,8 @@ export class EventTarget implements domTypes.EventTarget {
slotInClosedTree: boolean slotInClosedTree: boolean
): void { ): void {
const itemInShadowTree = isNode(target) && isShadowRoot(getRoot(target)); const itemInShadowTree = isNode(target) && isShadowRoot(getRoot(target));
const rootOfClosedTree = isShadowRoot(target) && target.mode === "closed"; const rootOfClosedTree =
isShadowRoot(target) && target[domTypes.eventTargetMode] === "closed";
eventImpl.path.push({ eventImpl.path.push({
item: target, item: target,
@ -525,31 +550,11 @@ export class EventTarget implements domTypes.EventTarget {
slotInClosedTree slotInClosedTree
}); });
} }
};
get [Symbol.toStringTag](): string {
return "EventTarget";
}
}
/** Built-in objects providing `get` methods for our /** Built-in objects providing `get` methods for our
* interceptable JavaScript operations. * interceptable JavaScript operations.
*/ */
Reflect.defineProperty(EventTarget.prototype, "host", {
enumerable: true,
writable: true
});
Reflect.defineProperty(EventTarget.prototype, "listeners", {
enumerable: true,
writable: true
});
Reflect.defineProperty(EventTarget.prototype, "mode", {
enumerable: true,
writable: true
});
Reflect.defineProperty(EventTarget.prototype, "nodeType", {
enumerable: true,
writable: true
});
Reflect.defineProperty(EventTarget.prototype, "addEventListener", { Reflect.defineProperty(EventTarget.prototype, "addEventListener", {
enumerable: true enumerable: true
}); });

View file

@ -69,6 +69,7 @@ window.console = console;
window.setTimeout = timers.setTimeout; window.setTimeout = timers.setTimeout;
window.setInterval = timers.setInterval; window.setInterval = timers.setInterval;
window.location = (undefined as unknown) as domTypes.Location; window.location = (undefined as unknown) as domTypes.Location;
window.onload = undefined as undefined | Function;
// The following Crypto interface implementation is not up to par with the // The following Crypto interface implementation is not up to par with the
// standard https://www.w3.org/TR/WebCryptoAPI/#crypto-interface as it does not // standard https://www.w3.org/TR/WebCryptoAPI/#crypto-interface as it does not
// yet incorporate the SubtleCrypto interface as its "subtle" property. // yet incorporate the SubtleCrypto interface as its "subtle" property.
@ -135,6 +136,28 @@ window.postMessage = workers.postMessage;
window.Worker = workers.WorkerImpl; window.Worker = workers.WorkerImpl;
export type Worker = workers.Worker; export type Worker = workers.Worker;
window[domTypes.eventTargetHost] = null;
window[domTypes.eventTargetListeners] = {};
window[domTypes.eventTargetMode] = "";
window[domTypes.eventTargetNodeType] = 0;
window[eventTarget.eventTargetAssignedSlot] = false;
window[eventTarget.eventTargetHasActivationBehavior] = false;
window.addEventListener = eventTarget.EventTarget.prototype.addEventListener;
window.dispatchEvent = eventTarget.EventTarget.prototype.dispatchEvent;
window.removeEventListener =
eventTarget.EventTarget.prototype.removeEventListener;
// Registers the handler for window.onload function.
window.addEventListener(
"load",
(e: domTypes.Event): void => {
const onload = window.onload;
if (typeof onload === "function") {
onload(e);
}
}
);
// below are interfaces that are available in TypeScript but // below are interfaces that are available in TypeScript but
// have different signatures // have different signatures
export interface ImportMeta { export interface ImportMeta {

View file

@ -1,5 +1,4 @@
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
// (0, eval) is indirect eval. // (0, eval) is indirect eval.
// See the links below for details: // See the links below for details:
// - https://stackoverflow.com/a/14120023 // - https://stackoverflow.com/a/14120023

7
tests/034_onload.out Normal file
View file

@ -0,0 +1,7 @@
log from nest_imported script
log from imported script
log from main
got load event in onload function
got load event in event handler (nest_imported)
got load event in event handler (imported)
got load event in event handler (main)

2
tests/034_onload.test Normal file
View file

@ -0,0 +1,2 @@
args: run --reload tests/034_onload/main.ts
output: tests/034_onload.out

View file

@ -0,0 +1,8 @@
import "./nest_imported.ts";
window.addEventListener(
"load",
(e: Event): void => {
console.log(`got ${e.type} event in event handler (imported)`);
}
);
console.log("log from imported script");

14
tests/034_onload/main.ts Normal file
View file

@ -0,0 +1,14 @@
import "./imported.ts";
window.addEventListener(
"load",
(e: Event): void => {
console.log(`got ${e.type} event in event handler (main)`);
}
);
window.onload = (e: Event): void => {
console.log(`got ${e.type} event in onload function`);
};
console.log("log from main");

View file

@ -0,0 +1,7 @@
window.addEventListener(
"load",
(e: Event): void => {
console.log(`got ${e.type} event in event handler (nest_imported)`);
}
);
console.log("log from nest_imported script");

View file

@ -0,0 +1 @@
console.log("from imported script");

View file

@ -425,7 +425,7 @@ $ deno run --allow-net=deno.land allow-net-whitelist-example.ts
Example: Example:
```ts ```ts
async function main() { window.onload = async function() {
// create subprocess // create subprocess
const p = Deno.run({ const p = Deno.run({
args: ["echo", "hello"] args: ["echo", "hello"]
@ -433,9 +433,7 @@ async function main() {
// await its completion // await its completion
await p.status(); await p.status();
} };
main();
``` ```
Run it: Run it:
@ -445,12 +443,17 @@ $ deno run --allow-run ./subprocess_simple.ts
hello hello
``` ```
Here a function is assigned to `window.onload`. This function is called after
the main script is loaded. This is the same as
[onload](https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers/onload)
of the browsers, and it can be used as the main entrypoint.
By default when you use `Deno.run()` subprocess inherits `stdin`, `stdout` and By default when you use `Deno.run()` subprocess inherits `stdin`, `stdout` and
`stderr` of parent process. If you want to communicate with started subprocess `stderr` of parent process. If you want to communicate with started subprocess
you can use `"piped"` option. you can use `"piped"` option.
```ts ```ts
async function main() { window.onload = async function() {
const decoder = new TextDecoder(); const decoder = new TextDecoder();
const fileNames = Deno.args.slice(1); const fileNames = Deno.args.slice(1);
@ -479,9 +482,7 @@ async function main() {
} }
Deno.exit(code); Deno.exit(code);
} };
main();
``` ```
When you run it: When you run it:
@ -833,14 +834,12 @@ Example:
import { serve } from "http/server.ts"; import { serve } from "http/server.ts";
async function main() { window.onload = async function() {
const body = new TextEncoder().encode("Hello World\n"); const body = new TextEncoder().encode("Hello World\n");
for await (const req of serve(":8000")) { for await (const req of serve(":8000")) {
req.respond({ body }); req.respond({ body });
} }
} };
main();
``` ```
```shell ```shell