summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYoshiya Hinosawa <stibium121@gmail.com>2019-07-16 13:19:26 +0900
committerRyan Dahl <ry@tinyclouds.org>2019-07-16 00:19:26 -0400
commit9c454998646ef49f652bc919f53503ed07a1c55c (patch)
treea6275bdefa3da50cae08c6c0f753d816215d9602
parentbd6ebb32df11641e148fd0adca41e7188f16afce (diff)
Support window.onload (#2643)
-rw-r--r--.eslintrc.json1
-rw-r--r--cli/main.rs1
-rw-r--r--js/dom_types.ts13
-rw-r--r--js/dom_util.ts14
-rw-r--r--js/event_target.ts135
-rw-r--r--js/globals.ts23
-rw-r--r--js/window.ts1
-rw-r--r--tests/034_onload.out7
-rw-r--r--tests/034_onload.test2
-rw-r--r--tests/034_onload/imported.ts8
-rw-r--r--tests/034_onload/main.ts14
-rw-r--r--tests/034_onload/nest_imported.ts7
-rw-r--r--tests/034_onload_imported.ts1
-rw-r--r--website/manual.md23
14 files changed, 162 insertions, 88 deletions
diff --git a/.eslintrc.json b/.eslintrc.json
index 43405e21e..c6b145d78 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -14,6 +14,7 @@
"@typescript-eslint/array-type": ["error", "array-simple"],
"@typescript-eslint/explicit-member-accessibility": ["off"],
"@typescript-eslint/no-non-null-assertion": ["off"],
+ "@typescript-eslint/no-use-before-define": ["off"],
"@typescript-eslint/no-parameter-properties": ["off"],
"@typescript-eslint/no-unused-vars": [
"error",
diff --git a/cli/main.rs b/cli/main.rs
index 50d65c06d..ec6c46616 100644
--- a/cli/main.rs
+++ b/cli/main.rs
@@ -314,6 +314,7 @@ fn run_script(flags: DenoFlags, argv: Vec<String>) {
worker
.execute_mod_async(&main_module, false)
.and_then(move |()| {
+ js_check(worker.execute("window.dispatchEvent(new Event('load'))"));
worker.then(|result| {
js_check(result);
Ok(())
diff --git a/js/dom_types.ts b/js/dom_types.ts
index 07f7d7898..a7c36df32 100644
--- a/js/dom_types.ts
+++ b/js/dom_types.ts
@@ -71,11 +71,16 @@ export enum NodeType {
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 {
- host: EventTarget | null;
- listeners: { [type in string]: EventListener[] };
- mode: string;
- nodeType: NodeType;
+ [eventTargetHost]: EventTarget | null;
+ [eventTargetListeners]: { [type in string]: EventListener[] };
+ [eventTargetMode]: string;
+ [eventTargetNodeType]: NodeType;
addEventListener(
type: string,
callback: (event: Event) => void | null,
diff --git a/js/dom_util.ts b/js/dom_util.ts
index 2f22a4b51..7a5dc80b9 100644
--- a/js/dom_util.ts
+++ b/js/dom_util.ts
@@ -9,16 +9,18 @@ export function isNode(nodeImpl: domTypes.EventTarget | null): boolean {
export function isShadowRoot(nodeImpl: domTypes.EventTarget | null): boolean {
return Boolean(
nodeImpl &&
- nodeImpl.nodeType === domTypes.NodeType.DOCUMENT_FRAGMENT_NODE &&
- "host" in nodeImpl
+ nodeImpl[domTypes.eventTargetNodeType] ===
+ domTypes.NodeType.DOCUMENT_FRAGMENT_NODE &&
+ nodeImpl[domTypes.eventTargetHost] != null
);
}
export function isSlotable(nodeImpl: domTypes.EventTarget | null): boolean {
return Boolean(
nodeImpl &&
- (nodeImpl.nodeType === domTypes.NodeType.ELEMENT_NODE ||
- nodeImpl.nodeType === domTypes.NodeType.TEXT_NODE)
+ (nodeImpl[domTypes.eventTargetNodeType] ===
+ domTypes.NodeType.ELEMENT_NODE ||
+ nodeImpl[domTypes.eventTargetNodeType] === domTypes.NodeType.TEXT_NODE)
);
}
@@ -36,7 +38,7 @@ export function isShadowInclusiveAncestor(
}
if (isShadowRoot(node)) {
- node = node && node.host;
+ node = node && node[domTypes.eventTargetHost];
} else {
node = null; // domSymbolTree.parent(node);
}
@@ -77,7 +79,7 @@ export function retarget(
return a;
}
- a = aRoot.host;
+ a = aRoot[domTypes.eventTargetHost];
}
}
}
diff --git a/js/event_target.ts b/js/event_target.ts
index 0e06dc34f..e376e78a1 100644
--- a/js/event_target.ts
+++ b/js/event_target.ts
@@ -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 {
- public host: domTypes.EventTarget | null = null;
- public listeners: { [type in string]: domTypes.EventListener[] } = {};
- public mode = "";
- public nodeType: domTypes.NodeType = domTypes.NodeType.DOCUMENT_FRAGMENT_NODE;
- private _assignedSlot = false;
- private _hasActivationBehavior = false;
+ public [domTypes.eventTargetHost]: domTypes.EventTarget | null = null;
+ public [domTypes.eventTargetListeners]: {
+ [type in string]: domTypes.EventListener[]
+ } = {};
+ public [domTypes.eventTargetMode] = "";
+ public [domTypes.eventTargetNodeType]: domTypes.NodeType =
+ domTypes.NodeType.DOCUMENT_FRAGMENT_NODE;
+ private [eventTargetAssignedSlot] = false;
+ private [eventTargetHasActivationBehavior] = false;
public addEventListener(
type: string,
@@ -112,7 +118,7 @@ export class EventTarget implements domTypes.EventTarget {
options?: domTypes.AddEventListenerOptions | boolean
): void {
requiredArguments("EventTarget.addEventListener", arguments.length, 2);
- const normalizedOptions: domTypes.AddEventListenerOptions = this._normalizeAddEventHandlerOptions(
+ const normalizedOptions: domTypes.AddEventListenerOptions = eventTargetHelpers.normalizeAddEventHandlerOptions(
options
);
@@ -120,12 +126,14 @@ export class EventTarget implements domTypes.EventTarget {
return;
}
- if (!hasOwnProperty(this.listeners, type)) {
- this.listeners[type] = [];
+ const listeners = this[domTypes.eventTargetListeners];
+
+ if (!hasOwnProperty(listeners, type)) {
+ listeners[type] = [];
}
- for (let i = 0; i < this.listeners[type].length; ++i) {
- const listener = this.listeners[type][i];
+ for (let i = 0; i < listeners[type].length; ++i) {
+ const listener = listeners[type][i];
if (
((typeof listener.options === "boolean" &&
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(
@@ -146,13 +154,14 @@ export class EventTarget implements domTypes.EventTarget {
options?: domTypes.EventListenerOptions | boolean
): void {
requiredArguments("EventTarget.removeEventListener", arguments.length, 2);
- if (hasOwnProperty(this.listeners, type) && callback !== null) {
- this.listeners[type] = this.listeners[type].filter(
+ const listeners = this[domTypes.eventTargetListeners];
+ if (hasOwnProperty(listeners, type) && callback !== null) {
+ listeners[type] = listeners[type].filter(
(listener): boolean => listener.callback !== callback
);
}
- const normalizedOptions: domTypes.EventListenerOptions = this._normalizeEventHandlerOptions(
+ const normalizedOptions: domTypes.EventListenerOptions = eventTargetHelpers.normalizeEventHandlerOptions(
options
);
@@ -161,12 +170,12 @@ export class EventTarget implements domTypes.EventTarget {
return;
}
- if (!this.listeners[type]) {
+ if (!listeners[type]) {
return;
}
- for (let i = 0; i < this.listeners[type].length; ++i) {
- const listener = this.listeners[type][i];
+ for (let i = 0; i < listeners[type].length; ++i) {
+ const listener = listeners[type][i];
if (
((typeof listener.options === "boolean" &&
@@ -175,7 +184,7 @@ export class EventTarget implements domTypes.EventTarget {
listener.options.capture === normalizedOptions.capture)) &&
listener.callback === callback
) {
- this.listeners[type].splice(i, 1);
+ listeners[type].splice(i, 1);
break;
}
}
@@ -183,7 +192,8 @@ export class EventTarget implements domTypes.EventTarget {
public dispatchEvent(event: domTypes.Event): boolean {
requiredArguments("EventTarget.dispatchEvent", arguments.length, 1);
- if (!hasOwnProperty(this.listeners, event.type)) {
+ const listeners = this[domTypes.eventTargetListeners];
+ if (!hasOwnProperty(listeners, event.type)) {
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
- _dispatch(
+ dispatch(
+ targetImpl: EventTarget,
eventImpl: domTypes.Event,
targetOverride?: domTypes.EventTarget
): boolean {
- let targetImpl = this;
let clearTargets = false;
let activationTarget = null;
@@ -224,7 +240,7 @@ export class EventTarget implements domTypes.EventTarget {
) {
const touchTargets: domTypes.EventTarget[] = [];
- this._appendToEventPath(
+ eventTargetHelpers.appendToEventPath(
eventImpl,
targetImpl,
targetOverride,
@@ -235,13 +251,15 @@ export class EventTarget implements domTypes.EventTarget {
const isActivationEvent = eventImpl.type === "click";
- if (isActivationEvent && targetImpl._hasActivationBehavior) {
+ if (isActivationEvent && targetImpl[eventTargetHasActivationBehavior]) {
activationTarget = targetImpl;
}
let slotInClosedTree = false;
let slotable =
- isSlotable(targetImpl) && targetImpl._assignedSlot ? targetImpl : null;
+ isSlotable(targetImpl) && targetImpl[eventTargetAssignedSlot]
+ ? targetImpl
+ : null;
let parent = getEventTargetParent(targetImpl, eventImpl);
// Populate event path
@@ -254,7 +272,7 @@ export class EventTarget implements domTypes.EventTarget {
if (
isShadowRoot(parentRoot) &&
parentRoot &&
- parentRoot.mode === "closed"
+ parentRoot[domTypes.eventTargetMode] === "closed"
) {
slotInClosedTree = true;
}
@@ -266,7 +284,7 @@ export class EventTarget implements domTypes.EventTarget {
isNode(parent) &&
isShadowInclusiveAncestor(getRoot(targetImpl), parent)
) {
- this._appendToEventPath(
+ eventTargetHelpers.appendToEventPath(
eventImpl,
parent,
null,
@@ -282,12 +300,12 @@ export class EventTarget implements domTypes.EventTarget {
if (
isActivationEvent &&
activationTarget === null &&
- targetImpl._hasActivationBehavior
+ targetImpl[eventTargetHasActivationBehavior]
) {
activationTarget = targetImpl;
}
- this._appendToEventPath(
+ eventTargetHelpers.appendToEventPath(
eventImpl,
parent,
targetImpl,
@@ -328,7 +346,7 @@ export class EventTarget implements domTypes.EventTarget {
const tuple = eventImpl.path[i];
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.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;
- }
+ },
// https://dom.spec.whatwg.org/#concept-event-listener-invoke
- _invokeEventListeners(
+ invokeEventListeners(
+ targetImpl: EventTarget,
tuple: domTypes.EventPath,
eventImpl: domTypes.Event
): void {
@@ -396,11 +415,16 @@ export class EventTarget implements domTypes.EventTarget {
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
- _innerInvokeEventListeners(
+ innerInvokeEventListeners(
+ targetImpl: EventTarget,
eventImpl: domTypes.Event,
targetListeners: { [type in string]: domTypes.EventListener[] }
): boolean {
@@ -471,9 +495,9 @@ export class EventTarget implements domTypes.EventTarget {
}
return found;
- }
+ },
- _normalizeAddEventHandlerOptions(
+ normalizeAddEventHandlerOptions(
options: boolean | domTypes.AddEventListenerOptions | undefined
): domTypes.AddEventListenerOptions {
if (typeof options === "boolean" || typeof options === "undefined") {
@@ -487,9 +511,9 @@ export class EventTarget implements domTypes.EventTarget {
} else {
return options;
}
- }
+ },
- _normalizeEventHandlerOptions(
+ normalizeEventHandlerOptions(
options: boolean | domTypes.EventListenerOptions | undefined
): domTypes.EventListenerOptions {
if (typeof options === "boolean" || typeof options === "undefined") {
@@ -501,10 +525,10 @@ export class EventTarget implements domTypes.EventTarget {
} else {
return options;
}
- }
+ },
// https://dom.spec.whatwg.org/#concept-event-path-append
- _appendToEventPath(
+ appendToEventPath(
eventImpl: domTypes.Event,
target: domTypes.EventTarget,
targetOverride: domTypes.EventTarget | null,
@@ -513,7 +537,8 @@ export class EventTarget implements domTypes.EventTarget {
slotInClosedTree: boolean
): void {
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({
item: target,
@@ -525,31 +550,11 @@ export class EventTarget implements domTypes.EventTarget {
slotInClosedTree
});
}
-
- get [Symbol.toStringTag](): string {
- return "EventTarget";
- }
-}
+};
/** Built-in objects providing `get` methods for our
* 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", {
enumerable: true
});
diff --git a/js/globals.ts b/js/globals.ts
index 1d33f6523..3be55e979 100644
--- a/js/globals.ts
+++ b/js/globals.ts
@@ -69,6 +69,7 @@ window.console = console;
window.setTimeout = timers.setTimeout;
window.setInterval = timers.setInterval;
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
// standard https://www.w3.org/TR/WebCryptoAPI/#crypto-interface as it does not
// yet incorporate the SubtleCrypto interface as its "subtle" property.
@@ -135,6 +136,28 @@ window.postMessage = workers.postMessage;
window.Worker = workers.WorkerImpl;
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
// have different signatures
export interface ImportMeta {
diff --git a/js/window.ts b/js/window.ts
index 6773ea5b2..3d3d6601f 100644
--- a/js/window.ts
+++ b/js/window.ts
@@ -1,5 +1,4 @@
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
-
// (0, eval) is indirect eval.
// See the links below for details:
// - https://stackoverflow.com/a/14120023
diff --git a/tests/034_onload.out b/tests/034_onload.out
new file mode 100644
index 000000000..0939be8cd
--- /dev/null
+++ b/tests/034_onload.out
@@ -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)
diff --git a/tests/034_onload.test b/tests/034_onload.test
new file mode 100644
index 000000000..d3105656f
--- /dev/null
+++ b/tests/034_onload.test
@@ -0,0 +1,2 @@
+args: run --reload tests/034_onload/main.ts
+output: tests/034_onload.out
diff --git a/tests/034_onload/imported.ts b/tests/034_onload/imported.ts
new file mode 100644
index 000000000..5cf2d7b4c
--- /dev/null
+++ b/tests/034_onload/imported.ts
@@ -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");
diff --git a/tests/034_onload/main.ts b/tests/034_onload/main.ts
new file mode 100644
index 000000000..68851950a
--- /dev/null
+++ b/tests/034_onload/main.ts
@@ -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");
diff --git a/tests/034_onload/nest_imported.ts b/tests/034_onload/nest_imported.ts
new file mode 100644
index 000000000..2e2bee1d5
--- /dev/null
+++ b/tests/034_onload/nest_imported.ts
@@ -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");
diff --git a/tests/034_onload_imported.ts b/tests/034_onload_imported.ts
new file mode 100644
index 000000000..d97aabeca
--- /dev/null
+++ b/tests/034_onload_imported.ts
@@ -0,0 +1 @@
+console.log("from imported script");
diff --git a/website/manual.md b/website/manual.md
index 4025e5acc..0424a2d25 100644
--- a/website/manual.md
+++ b/website/manual.md
@@ -425,7 +425,7 @@ $ deno run --allow-net=deno.land allow-net-whitelist-example.ts
Example:
```ts
-async function main() {
+window.onload = async function() {
// create subprocess
const p = Deno.run({
args: ["echo", "hello"]
@@ -433,9 +433,7 @@ async function main() {
// await its completion
await p.status();
-}
-
-main();
+};
```
Run it:
@@ -445,12 +443,17 @@ $ deno run --allow-run ./subprocess_simple.ts
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
`stderr` of parent process. If you want to communicate with started subprocess
you can use `"piped"` option.
```ts
-async function main() {
+window.onload = async function() {
const decoder = new TextDecoder();
const fileNames = Deno.args.slice(1);
@@ -479,9 +482,7 @@ async function main() {
}
Deno.exit(code);
-}
-
-main();
+};
```
When you run it:
@@ -833,14 +834,12 @@ Example:
import { serve } from "http/server.ts";
-async function main() {
+window.onload = async function() {
const body = new TextEncoder().encode("Hello World\n");
for await (const req of serve(":8000")) {
req.respond({ body });
}
-}
-
-main();
+};
```
```shell