mirror of
https://github.com/denoland/deno.git
synced 2024-12-29 02:29:06 -05:00
4f46dc999b
This commit fixes order of events for "onload" event. Previously handler attached using "window.onload" was always fired before handlers added using "addEventListener".
422 lines
15 KiB
JavaScript
422 lines
15 KiB
JavaScript
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
|
|
// Removes the `__proto__` for security reasons. This intentionally makes
|
|
// Deno non compliant with ECMA-262 Annex B.2.2.1
|
|
//
|
|
delete Object.prototype.__proto__;
|
|
|
|
((window) => {
|
|
const core = Deno.core;
|
|
const util = window.__bootstrap.util;
|
|
const eventTarget = window.__bootstrap.eventTarget;
|
|
const globalInterfaces = window.__bootstrap.globalInterfaces;
|
|
const dispatchMinimal = window.__bootstrap.dispatchMinimal;
|
|
const build = window.__bootstrap.build;
|
|
const version = window.__bootstrap.version;
|
|
const errorStack = window.__bootstrap.errorStack;
|
|
const os = window.__bootstrap.os;
|
|
const timers = window.__bootstrap.timers;
|
|
const Console = window.__bootstrap.console.Console;
|
|
const worker = window.__bootstrap.worker;
|
|
const signals = window.__bootstrap.signals;
|
|
const { internalSymbol, internalObject } = window.__bootstrap.internals;
|
|
const performance = window.__bootstrap.performance;
|
|
const crypto = window.__bootstrap.crypto;
|
|
const url = window.__bootstrap.url;
|
|
const headers = window.__bootstrap.headers;
|
|
const streams = window.__bootstrap.streams;
|
|
const fileReader = window.__bootstrap.fileReader;
|
|
const webSocket = window.__bootstrap.webSocket;
|
|
const fetch = window.__bootstrap.fetch;
|
|
const prompt = window.__bootstrap.prompt;
|
|
const denoNs = window.__bootstrap.denoNs;
|
|
const denoNsUnstable = window.__bootstrap.denoNsUnstable;
|
|
const errors = window.__bootstrap.errors.errors;
|
|
|
|
let windowIsClosing = false;
|
|
|
|
function windowClose() {
|
|
if (!windowIsClosing) {
|
|
windowIsClosing = true;
|
|
// Push a macrotask to exit after a promise resolve.
|
|
// This is not perfect, but should be fine for first pass.
|
|
Promise.resolve().then(() =>
|
|
timers.setTimeout.call(
|
|
null,
|
|
() => {
|
|
// This should be fine, since only Window/MainWorker has .close()
|
|
os.exit(0);
|
|
},
|
|
0,
|
|
)
|
|
);
|
|
}
|
|
}
|
|
|
|
const encoder = new TextEncoder();
|
|
|
|
function workerClose() {
|
|
if (isClosing) {
|
|
return;
|
|
}
|
|
|
|
isClosing = true;
|
|
opCloseWorker();
|
|
}
|
|
|
|
// TODO(bartlomieju): remove these functions
|
|
// Stuff for workers
|
|
const onmessage = () => {};
|
|
const onerror = () => {};
|
|
|
|
function postMessage(data) {
|
|
const dataJson = JSON.stringify(data);
|
|
const dataIntArray = encoder.encode(dataJson);
|
|
opPostMessage(dataIntArray);
|
|
}
|
|
|
|
let isClosing = false;
|
|
async function workerMessageRecvCallback(data) {
|
|
const msgEvent = new MessageEvent("message", {
|
|
cancelable: false,
|
|
data,
|
|
});
|
|
|
|
try {
|
|
if (globalThis["onmessage"]) {
|
|
const result = globalThis.onmessage(msgEvent);
|
|
if (result && "then" in result) {
|
|
await result;
|
|
}
|
|
}
|
|
globalThis.dispatchEvent(msgEvent);
|
|
} catch (e) {
|
|
let handled = false;
|
|
|
|
const errorEvent = new ErrorEvent("error", {
|
|
cancelable: true,
|
|
message: e.message,
|
|
lineno: e.lineNumber ? e.lineNumber + 1 : undefined,
|
|
colno: e.columnNumber ? e.columnNumber + 1 : undefined,
|
|
filename: e.fileName,
|
|
error: null,
|
|
});
|
|
|
|
if (globalThis["onerror"]) {
|
|
const ret = globalThis.onerror(
|
|
e.message,
|
|
e.fileName,
|
|
e.lineNumber,
|
|
e.columnNumber,
|
|
e,
|
|
);
|
|
handled = ret === true;
|
|
}
|
|
|
|
globalThis.dispatchEvent(errorEvent);
|
|
if (errorEvent.defaultPrevented) {
|
|
handled = true;
|
|
}
|
|
|
|
if (!handled) {
|
|
throw e;
|
|
}
|
|
}
|
|
}
|
|
|
|
function opPostMessage(data) {
|
|
core.jsonOpSync("op_worker_post_message", {}, data);
|
|
}
|
|
|
|
function opCloseWorker() {
|
|
core.jsonOpSync("op_worker_close");
|
|
}
|
|
|
|
function opStart() {
|
|
return core.jsonOpSync("op_start");
|
|
}
|
|
|
|
function opMainModule() {
|
|
return core.jsonOpSync("op_main_module");
|
|
}
|
|
|
|
// TODO(bartlomieju): temporary solution, must be fixed when moving
|
|
// dispatches to separate crates
|
|
function initOps() {
|
|
const opsMap = core.ops();
|
|
for (const [name, opId] of Object.entries(opsMap)) {
|
|
if (name === "op_write" || name === "op_read") {
|
|
core.setAsyncHandler(opId, dispatchMinimal.asyncMsgFromRust);
|
|
}
|
|
}
|
|
core.setMacrotaskCallback(timers.handleTimerMacrotask);
|
|
}
|
|
|
|
function runtimeStart(source) {
|
|
initOps();
|
|
// First we send an empty `Start` message to let the privileged side know we
|
|
// are ready. The response should be a `StartRes` message containing the CLI
|
|
// args and other info.
|
|
const s = opStart();
|
|
version.setVersions(s.denoVersion, s.v8Version, s.tsVersion);
|
|
build.setBuildInfo(s.target);
|
|
util.setLogDebug(s.debugFlag, source);
|
|
errorStack.setPrepareStackTrace(Error);
|
|
return s;
|
|
}
|
|
|
|
function registerErrors() {
|
|
core.registerErrorClass("NotFound", errors.NotFound);
|
|
core.registerErrorClass("PermissionDenied", errors.PermissionDenied);
|
|
core.registerErrorClass("ConnectionRefused", errors.ConnectionRefused);
|
|
core.registerErrorClass("ConnectionReset", errors.ConnectionReset);
|
|
core.registerErrorClass("ConnectionAborted", errors.ConnectionAborted);
|
|
core.registerErrorClass("NotConnected", errors.NotConnected);
|
|
core.registerErrorClass("AddrInUse", errors.AddrInUse);
|
|
core.registerErrorClass("AddrNotAvailable", errors.AddrNotAvailable);
|
|
core.registerErrorClass("BrokenPipe", errors.BrokenPipe);
|
|
core.registerErrorClass("AlreadyExists", errors.AlreadyExists);
|
|
core.registerErrorClass("InvalidData", errors.InvalidData);
|
|
core.registerErrorClass("TimedOut", errors.TimedOut);
|
|
core.registerErrorClass("Interrupted", errors.Interrupted);
|
|
core.registerErrorClass("WriteZero", errors.WriteZero);
|
|
core.registerErrorClass("UnexpectedEof", errors.UnexpectedEof);
|
|
core.registerErrorClass("BadResource", errors.BadResource);
|
|
core.registerErrorClass("Http", errors.Http);
|
|
core.registerErrorClass("Busy", errors.Busy);
|
|
core.registerErrorClass("NotSupported", errors.NotSupported);
|
|
core.registerErrorClass("Error", Error);
|
|
core.registerErrorClass("RangeError", RangeError);
|
|
core.registerErrorClass("ReferenceError", ReferenceError);
|
|
core.registerErrorClass("SyntaxError", SyntaxError);
|
|
core.registerErrorClass("TypeError", TypeError);
|
|
core.registerErrorClass("URIError", URIError);
|
|
}
|
|
|
|
// https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope
|
|
const windowOrWorkerGlobalScope = {
|
|
Blob: util.nonEnumerable(fetch.Blob),
|
|
ByteLengthQueuingStrategy: util.nonEnumerable(
|
|
streams.ByteLengthQueuingStrategy,
|
|
),
|
|
CloseEvent: util.nonEnumerable(CloseEvent),
|
|
CountQueuingStrategy: util.nonEnumerable(
|
|
streams.CountQueuingStrategy,
|
|
),
|
|
CustomEvent: util.nonEnumerable(CustomEvent),
|
|
DOMException: util.nonEnumerable(DOMException),
|
|
ErrorEvent: util.nonEnumerable(ErrorEvent),
|
|
Event: util.nonEnumerable(Event),
|
|
EventTarget: util.nonEnumerable(EventTarget),
|
|
File: util.nonEnumerable(fetch.DomFile),
|
|
FileReader: util.nonEnumerable(fileReader.FileReader),
|
|
FormData: util.nonEnumerable(fetch.FormData),
|
|
Headers: util.nonEnumerable(headers.Headers),
|
|
MessageEvent: util.nonEnumerable(MessageEvent),
|
|
Performance: util.nonEnumerable(performance.Performance),
|
|
PerformanceEntry: util.nonEnumerable(performance.PerformanceEntry),
|
|
PerformanceMark: util.nonEnumerable(performance.PerformanceMark),
|
|
PerformanceMeasure: util.nonEnumerable(performance.PerformanceMeasure),
|
|
ProgressEvent: util.nonEnumerable(ProgressEvent),
|
|
ReadableStream: util.nonEnumerable(streams.ReadableStream),
|
|
Request: util.nonEnumerable(fetch.Request),
|
|
Response: util.nonEnumerable(fetch.Response),
|
|
TextDecoder: util.nonEnumerable(TextDecoder),
|
|
TextEncoder: util.nonEnumerable(TextEncoder),
|
|
TransformStream: util.nonEnumerable(streams.TransformStream),
|
|
URL: util.nonEnumerable(url.URL),
|
|
URLSearchParams: util.nonEnumerable(url.URLSearchParams),
|
|
WebSocket: util.nonEnumerable(webSocket.WebSocket),
|
|
Worker: util.nonEnumerable(worker.Worker),
|
|
WritableStream: util.nonEnumerable(streams.WritableStream),
|
|
atob: util.writable(atob),
|
|
btoa: util.writable(btoa),
|
|
clearInterval: util.writable(timers.clearInterval),
|
|
clearTimeout: util.writable(timers.clearTimeout),
|
|
console: util.writable(new Console(core.print)),
|
|
crypto: util.readOnly(crypto),
|
|
fetch: util.writable(fetch.fetch),
|
|
performance: util.writable(performance.performance),
|
|
setInterval: util.writable(timers.setInterval),
|
|
setTimeout: util.writable(timers.setTimeout),
|
|
};
|
|
|
|
const mainRuntimeGlobalProperties = {
|
|
Window: globalInterfaces.windowConstructorDescriptor,
|
|
window: util.readOnly(globalThis),
|
|
self: util.readOnly(globalThis),
|
|
// TODO(bartlomieju): from MDN docs (https://developer.mozilla.org/en-US/docs/Web/API/WorkerGlobalScope)
|
|
// it seems those two properties should be available to workers as well
|
|
onload: util.writable(null),
|
|
onunload: util.writable(null),
|
|
close: util.writable(windowClose),
|
|
closed: util.getterOnly(() => windowIsClosing),
|
|
alert: util.writable(prompt.alert),
|
|
confirm: util.writable(prompt.confirm),
|
|
prompt: util.writable(prompt.prompt),
|
|
};
|
|
|
|
const workerRuntimeGlobalProperties = {
|
|
WorkerGlobalScope: globalInterfaces.workerGlobalScopeConstructorDescriptor,
|
|
DedicatedWorkerGlobalScope:
|
|
globalInterfaces.dedicatedWorkerGlobalScopeConstructorDescriptor,
|
|
self: util.readOnly(globalThis),
|
|
onmessage: util.writable(onmessage),
|
|
onerror: util.writable(onerror),
|
|
// TODO: should be readonly?
|
|
close: util.nonEnumerable(workerClose),
|
|
postMessage: util.writable(postMessage),
|
|
workerMessageRecvCallback: util.nonEnumerable(workerMessageRecvCallback),
|
|
};
|
|
|
|
let hasBootstrapped = false;
|
|
|
|
function bootstrapMainRuntime() {
|
|
if (hasBootstrapped) {
|
|
throw new Error("Worker runtime already bootstrapped");
|
|
}
|
|
// Remove bootstrapping data from the global scope
|
|
delete globalThis.__bootstrap;
|
|
delete globalThis.bootstrap;
|
|
util.log("bootstrapMainRuntime");
|
|
hasBootstrapped = true;
|
|
Object.defineProperties(globalThis, windowOrWorkerGlobalScope);
|
|
Object.defineProperties(globalThis, mainRuntimeGlobalProperties);
|
|
Object.setPrototypeOf(globalThis, Window.prototype);
|
|
eventTarget.setEventTargetData(globalThis);
|
|
|
|
const handlerSymbol = Symbol("eventHandlers");
|
|
|
|
function makeWrappedHandler(handler) {
|
|
function wrappedHandler(...args) {
|
|
if (typeof wrappedHandler.handler !== "function") {
|
|
return;
|
|
}
|
|
return wrappedHandler.handler.call(this, ...args);
|
|
}
|
|
wrappedHandler.handler = handler;
|
|
return wrappedHandler;
|
|
}
|
|
// TODO(benjamingr) reuse when we can reuse code between web crates
|
|
// This function is very similar to `defineEventHandler` in `01_web_util.js`
|
|
// but it returns `null` instead of `undefined` is handler is not defined.
|
|
function defineEventHandler(emitter, name) {
|
|
// HTML specification section 8.1.5.1
|
|
Object.defineProperty(emitter, `on${name}`, {
|
|
get() {
|
|
return this[handlerSymbol]?.get(name)?.handler ?? null;
|
|
},
|
|
set(value) {
|
|
if (!this[handlerSymbol]) {
|
|
this[handlerSymbol] = new Map();
|
|
}
|
|
let handlerWrapper = this[handlerSymbol]?.get(name);
|
|
if (handlerWrapper) {
|
|
handlerWrapper.handler = value;
|
|
} else {
|
|
handlerWrapper = makeWrappedHandler(value);
|
|
this.addEventListener(name, handlerWrapper);
|
|
}
|
|
this[handlerSymbol].set(name, handlerWrapper);
|
|
},
|
|
configurable: true,
|
|
enumerable: true,
|
|
});
|
|
}
|
|
defineEventHandler(window, "load");
|
|
defineEventHandler(window, "unload");
|
|
|
|
const { args, noColor, pid, ppid, unstableFlag } = runtimeStart();
|
|
|
|
registerErrors();
|
|
|
|
const finalDenoNs = {
|
|
core,
|
|
internal: internalSymbol,
|
|
[internalSymbol]: internalObject,
|
|
resources: core.resources,
|
|
close: core.close,
|
|
...denoNs,
|
|
};
|
|
Object.defineProperties(finalDenoNs, {
|
|
pid: util.readOnly(pid),
|
|
ppid: util.readOnly(ppid),
|
|
noColor: util.readOnly(noColor),
|
|
args: util.readOnly(Object.freeze(args)),
|
|
mainModule: util.getterOnly(opMainModule),
|
|
});
|
|
|
|
if (unstableFlag) {
|
|
Object.assign(finalDenoNs, denoNsUnstable);
|
|
}
|
|
|
|
// Setup `Deno` global - we're actually overriding already
|
|
// existing global `Deno` with `Deno` namespace from "./deno.ts".
|
|
util.immutableDefine(globalThis, "Deno", finalDenoNs);
|
|
Object.freeze(globalThis.Deno);
|
|
Object.freeze(globalThis.Deno.core);
|
|
Object.freeze(globalThis.Deno.core.sharedQueue);
|
|
signals.setSignals();
|
|
|
|
util.log("args", args);
|
|
}
|
|
|
|
function bootstrapWorkerRuntime(name, useDenoNamespace, internalName) {
|
|
if (hasBootstrapped) {
|
|
throw new Error("Worker runtime already bootstrapped");
|
|
}
|
|
// Remove bootstrapping data from the global scope
|
|
delete globalThis.__bootstrap;
|
|
delete globalThis.bootstrap;
|
|
util.log("bootstrapWorkerRuntime");
|
|
hasBootstrapped = true;
|
|
Object.defineProperties(globalThis, windowOrWorkerGlobalScope);
|
|
Object.defineProperties(globalThis, workerRuntimeGlobalProperties);
|
|
Object.defineProperties(globalThis, { name: util.readOnly(name) });
|
|
Object.setPrototypeOf(globalThis, DedicatedWorkerGlobalScope.prototype);
|
|
eventTarget.setEventTargetData(globalThis);
|
|
const { unstableFlag, pid, noColor, args } = runtimeStart(
|
|
internalName ?? name,
|
|
);
|
|
|
|
registerErrors();
|
|
|
|
const finalDenoNs = {
|
|
core,
|
|
internal: internalSymbol,
|
|
[internalSymbol]: internalObject,
|
|
resources: core.resources,
|
|
close: core.close,
|
|
...denoNs,
|
|
};
|
|
if (useDenoNamespace) {
|
|
if (unstableFlag) {
|
|
Object.assign(finalDenoNs, denoNsUnstable);
|
|
}
|
|
Object.defineProperties(finalDenoNs, {
|
|
pid: util.readOnly(pid),
|
|
noColor: util.readOnly(noColor),
|
|
args: util.readOnly(Object.freeze(args)),
|
|
});
|
|
// Setup `Deno` global - we're actually overriding already
|
|
// existing global `Deno` with `Deno` namespace from "./deno.ts".
|
|
util.immutableDefine(globalThis, "Deno", finalDenoNs);
|
|
Object.freeze(globalThis.Deno);
|
|
Object.freeze(globalThis.Deno.core);
|
|
Object.freeze(globalThis.Deno.core.sharedQueue);
|
|
signals.setSignals();
|
|
} else {
|
|
delete globalThis.Deno;
|
|
util.assert(globalThis.Deno === undefined);
|
|
}
|
|
}
|
|
|
|
Object.defineProperties(globalThis, {
|
|
bootstrap: {
|
|
value: {
|
|
mainRuntime: bootstrapMainRuntime,
|
|
workerRuntime: bootstrapWorkerRuntime,
|
|
},
|
|
configurable: true,
|
|
},
|
|
});
|
|
})(this);
|