Op crate for Web APIs (#6906)

Co-authored-by: Ryan Dahl <ry@tinyclouds.org>
This commit is contained in:
Bartek Iwańczuk 2020-08-07 16:55:02 +02:00 committed by GitHub
parent d7dcbab3ef
commit 41215eb29c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 1159 additions and 344 deletions

9
Cargo.lock generated
View file

@ -382,6 +382,7 @@ dependencies = [
"clap",
"deno_core",
"deno_lint",
"deno_web",
"dissimilar",
"dlopen",
"dprint-plugin-typescript",
@ -459,6 +460,14 @@ dependencies = [
"swc_ecmascript",
]
[[package]]
name = "deno_web"
version = "0.1.0"
dependencies = [
"deno_core",
"futures",
]
[[package]]
name = "derive_deref"
version = "1.1.1"

View file

@ -4,6 +4,7 @@ members = [
"core",
"test_plugin",
"test_util",
"op_crates/web",
]
exclude = [
"std/hash/_wasm"

View file

@ -16,6 +16,7 @@ path = "main.rs"
[build-dependencies]
deno_core = { path = "../core", version = "0.51.0" }
deno_web = { path = "../op_crates/web", version = "0.1.0" }
[target.'cfg(windows)'.build-dependencies]
winres = "0.1"

View file

@ -38,6 +38,11 @@ fn create_compiler_snapshot(
) {
let mut runtime_isolate = CoreIsolate::new(StartupData::None, true);
let mut custom_libs: HashMap<String, PathBuf> = HashMap::new();
let web_scripts = deno_web::get_scripts();
custom_libs.insert(
"lib.deno.web.d.ts".to_string(),
PathBuf::from(web_scripts.declaration),
);
custom_libs.insert(
"lib.deno.window.d.ts".to_string(),
cwd.join("dts/lib.deno.window.d.ts"),
@ -80,6 +85,10 @@ fn main() {
// op_fetch_asset::trace_serializer();
println!("cargo:rustc-env=TS_VERSION={}", ts_version());
println!(
"cargo:rustc-env=DENO_WEB_LIB_PATH={}",
deno_web::get_scripts().declaration
);
println!(
"cargo:rustc-env=TARGET={}",
@ -93,7 +102,7 @@ fn main() {
let runtime_snapshot_path = o.join("CLI_SNAPSHOT.bin");
let compiler_snapshot_path = o.join("COMPILER_SNAPSHOT.bin");
let js_files = get_js_files("rt");
let js_files = get_js_files_for_rt();
create_runtime_snapshot(&runtime_snapshot_path, js_files);
let js_files = get_js_files("tsc");
@ -123,3 +132,67 @@ fn get_js_files(d: &str) -> Vec<String> {
js_files.sort();
js_files
}
fn get_js_files_for_rt() -> Vec<String> {
let web_scripts = deno_web::get_scripts();
let f = vec![
"rt/00_bootstrap_namespace.js",
&web_scripts.dom_exception,
"rt/01_build.js",
"rt/01_colors.js",
"rt/01_errors.js",
&web_scripts.event,
"rt/01_internals.js",
"rt/01_version.js",
"rt/01_web_util.js",
"rt/02_abort_signal.js",
"rt/02_console.js",
"rt/03_dom_iterable.js",
"rt/06_util.js",
&web_scripts.text_encoding,
"rt/10_dispatch_json.js",
"rt/10_dispatch_minimal.js",
"rt/11_crypto.js",
"rt/11_resources.js",
"rt/11_streams.js",
"rt/11_timers.js",
"rt/11_url.js",
"rt/11_workers.js",
"rt/12_io.js",
"rt/13_buffer.js",
"rt/20_blob.js",
"rt/20_headers.js",
"rt/20_streams_queuing_strategy.js",
"rt/21_dom_file.js",
"rt/22_form_data.js",
"rt/23_multipart.js",
"rt/24_body.js",
"rt/25_request.js",
"rt/26_fetch.js",
"rt/30_files.js",
"rt/30_fs.js",
"rt/30_metrics.js",
"rt/30_net.js",
"rt/30_os.js",
"rt/40_compiler_api.js",
"rt/40_diagnostics.js",
"rt/40_error_stack.js",
"rt/40_fs_events.js",
"rt/40_net_unstable.js",
"rt/40_performance.js",
"rt/40_permissions.js",
"rt/40_plugins.js",
"rt/40_process.js",
"rt/40_read_file.js",
"rt/40_repl.js",
"rt/40_signals.js",
"rt/40_testing.js",
"rt/40_tls.js",
"rt/40_tty.js",
"rt/40_write_file.js",
"rt/90_deno_ns.js",
"rt/99_main.js",
];
f.iter().map(|p| p.to_string()).collect()
}

View file

@ -4,6 +4,7 @@
/// <reference no-default-lib="true" />
/// <reference lib="esnext" />
/// <reference lib="deno.web" />
// This follows the WebIDL at: https://webassembly.github.io/spec/js-api/
// and: https://webassembly.github.io/spec/web-api/
@ -529,12 +530,6 @@ interface DOMStringList {
[index: number]: string;
}
declare class DOMException extends Error {
constructor(message?: string, name?: string);
readonly name: string;
readonly message: string;
}
type BufferSource = ArrayBufferView | ArrayBuffer;
type BlobPart = BufferSource | Blob | string;
@ -1030,46 +1025,6 @@ declare function fetch(
init?: RequestInit,
): Promise<Response>;
/** Decodes a string of data which has been encoded using base-64 encoding.
*
* console.log(atob("aGVsbG8gd29ybGQ=")); // outputs 'hello world'
*/
declare function atob(s: string): string;
/** Creates a base-64 ASCII encoded string from the input string.
*
* console.log(btoa("hello world")); // outputs "aGVsbG8gd29ybGQ="
*/
declare function btoa(s: string): string;
declare class TextDecoder {
/** Returns encoding's name, lowercased. */
readonly encoding: string;
/** Returns `true` if error mode is "fatal", and `false` otherwise. */
readonly fatal: boolean;
/** Returns `true` if ignore BOM flag is set, and `false` otherwise. */
readonly ignoreBOM = false;
constructor(
label?: string,
options?: { fatal?: boolean; ignoreBOM?: boolean },
);
/** Returns the result of running encoding's decoder. */
decode(input?: BufferSource, options?: { stream?: false }): string;
readonly [Symbol.toStringTag]: string;
}
declare class TextEncoder {
/** Returns "utf-8". */
readonly encoding = "utf-8";
/** Returns the result of running UTF-8's encoder. */
encode(input?: string): Uint8Array;
encodeInto(
input: string,
dest: Uint8Array,
): { read: number; written: number };
readonly [Symbol.toStringTag]: string;
}
interface URLSearchParams {
/** Appends a specified key/value pair as a new search parameter.
*
@ -1444,143 +1399,6 @@ declare class PerformanceMeasure extends PerformanceEntry {
readonly entryType: "measure";
}
interface EventInit {
bubbles?: boolean;
cancelable?: boolean;
composed?: boolean;
}
/** An event which takes place in the DOM. */
declare class Event {
constructor(type: string, eventInitDict?: EventInit);
/** Returns true or false depending on how event was initialized. True if
* event goes through its target's ancestors in reverse tree order, and
* false otherwise. */
readonly bubbles: boolean;
cancelBubble: boolean;
/** Returns true or false depending on how event was initialized. Its return
* value does not always carry meaning, but true can indicate that part of the
* operation during which event was dispatched, can be canceled by invoking
* the preventDefault() method. */
readonly cancelable: boolean;
/** Returns true or false depending on how event was initialized. True if
* event invokes listeners past a ShadowRoot node that is the root of its
* target, and false otherwise. */
readonly composed: boolean;
/** Returns the object whose event listener's callback is currently being
* invoked. */
readonly currentTarget: EventTarget | null;
/** Returns true if preventDefault() was invoked successfully to indicate
* cancellation, and false otherwise. */
readonly defaultPrevented: boolean;
/** Returns the event's phase, which is one of NONE, CAPTURING_PHASE,
* AT_TARGET, and BUBBLING_PHASE. */
readonly eventPhase: number;
/** Returns true if event was dispatched by the user agent, and false
* otherwise. */
readonly isTrusted: boolean;
/** Returns the object to which event is dispatched (its target). */
readonly target: EventTarget | null;
/** Returns the event's timestamp as the number of milliseconds measured
* relative to the time origin. */
readonly timeStamp: number;
/** Returns the type of event, e.g. "click", "hashchange", or "submit". */
readonly type: string;
/** Returns the invocation target objects of event's path (objects on which
* listeners will be invoked), except for any nodes in shadow trees of which
* the shadow root's mode is "closed" that are not reachable from event's
* currentTarget. */
composedPath(): EventTarget[];
/** If invoked when the cancelable attribute value is true, and while
* executing a listener for the event with passive set to false, signals to
* the operation that caused event to be dispatched that it needs to be
* canceled. */
preventDefault(): void;
/** Invoking this method prevents event from reaching any registered event
* listeners after the current one finishes running and, when dispatched in a
* tree, also prevents event from reaching any other objects. */
stopImmediatePropagation(): void;
/** When dispatched in a tree, invoking this method prevents event from
* reaching any objects other than the current object. */
stopPropagation(): void;
readonly AT_TARGET: number;
readonly BUBBLING_PHASE: number;
readonly CAPTURING_PHASE: number;
readonly NONE: number;
static readonly AT_TARGET: number;
static readonly BUBBLING_PHASE: number;
static readonly CAPTURING_PHASE: number;
static readonly NONE: number;
}
/**
* EventTarget is a DOM interface implemented by objects that can receive events
* and may have listeners for them.
*/
declare class EventTarget {
/** Appends an event listener for events whose type attribute value is type.
* The callback argument sets the callback that will be invoked when the event
* is dispatched.
*
* The options argument sets listener-specific options. For compatibility this
* can be a boolean, in which case the method behaves exactly as if the value
* was specified as options's capture.
*
* When set to true, options's capture prevents callback from being invoked
* when the event's eventPhase attribute value is BUBBLING_PHASE. When false
* (or not present), callback will not be invoked when event's eventPhase
* attribute value is CAPTURING_PHASE. Either way, callback will be invoked if
* event's eventPhase attribute value is AT_TARGET.
*
* When set to true, options's passive indicates that the callback will not
* cancel the event by invoking preventDefault(). This is used to enable
* performance optimizations described in § 2.8 Observing event listeners.
*
* When set to true, options's once indicates that the callback will only be
* invoked once after which the event listener will be removed.
*
* The event listener is appended to target's event listener list and is not
* appended if it has the same type, callback, and capture. */
addEventListener(
type: string,
listener: EventListenerOrEventListenerObject | null,
options?: boolean | AddEventListenerOptions,
): void;
/** Dispatches a synthetic event event to target and returns true if either
* event's cancelable attribute value is false or its preventDefault() method
* was not invoked, and false otherwise. */
dispatchEvent(event: Event): boolean;
/** Removes the event listener in target's event listener list with the same
* type, callback, and options. */
removeEventListener(
type: string,
callback: EventListenerOrEventListenerObject | null,
options?: EventListenerOptions | boolean,
): void;
[Symbol.toStringTag]: string;
}
interface EventListener {
(evt: Event): void | Promise<void>;
}
interface EventListenerObject {
handleEvent(evt: Event): void | Promise<void>;
}
declare type EventListenerOrEventListenerObject =
| EventListener
| EventListenerObject;
interface AddEventListenerOptions extends EventListenerOptions {
once?: boolean;
passive?: boolean;
}
interface EventListenerOptions {
capture?: boolean;
}
/** Events measuring progress of an underlying process, like an HTTP request
* (for an XMLHttpRequest, or the loading of the underlying resource of an
* <img>, <audio>, <video>, <style> or <link>). */

View file

@ -5,6 +5,7 @@ pub static CLI_SNAPSHOT: &[u8] =
pub static COMPILER_SNAPSHOT: &[u8] =
include_bytes!(concat!(env!("OUT_DIR"), "/COMPILER_SNAPSHOT.bin"));
pub static DENO_NS_LIB: &str = include_str!("dts/lib.deno.ns.d.ts");
pub static DENO_WEB_LIB: &str = include_str!(env!("DENO_WEB_LIB_PATH"));
pub static SHARED_GLOBALS_LIB: &str =
include_str!("dts/lib.deno.shared_globals.d.ts");
pub static WINDOW_LIB: &str = include_str!("dts/lib.deno.window.d.ts");

View file

@ -287,8 +287,9 @@ async fn print_file_info(
fn get_types(unstable: bool) -> String {
let mut types = format!(
"{}\n{}\n{}",
"{}\n{}\n{}\n{}",
crate::js::DENO_NS_LIB,
crate::js::DENO_WEB_LIB,
crate::js::SHARED_GLOBALS_LIB,
crate::js::WINDOW_LIB,
);

View file

@ -1,157 +0,0 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
// Forked from https://github.com/beatgammit/base64-js
// Copyright (c) 2014 Jameson Little. MIT License.
((window) => {
const lookup = [];
const revLookup = [];
const code =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
for (let i = 0, len = code.length; i < len; ++i) {
lookup[i] = code[i];
revLookup[code.charCodeAt(i)] = i;
}
// Support decoding URL-safe base64 strings, as Node.js does.
// See: https://en.wikipedia.org/wiki/Base64#URL_applications
revLookup["-".charCodeAt(0)] = 62;
revLookup["_".charCodeAt(0)] = 63;
function getLens(b64) {
const len = b64.length;
if (len % 4 > 0) {
throw new Error("Invalid string. Length must be a multiple of 4");
}
// Trim off extra bytes after placeholder bytes are found
// See: https://github.com/beatgammit/base64-js/issues/42
let validLen = b64.indexOf("=");
if (validLen === -1) validLen = len;
const placeHoldersLen = validLen === len ? 0 : 4 - (validLen % 4);
return [validLen, placeHoldersLen];
}
// base64 is 4/3 + up to two characters of the original data
function byteLength(b64) {
const lens = getLens(b64);
const validLen = lens[0];
const placeHoldersLen = lens[1];
return ((validLen + placeHoldersLen) * 3) / 4 - placeHoldersLen;
}
function _byteLength(
b64,
validLen,
placeHoldersLen,
) {
return ((validLen + placeHoldersLen) * 3) / 4 - placeHoldersLen;
}
function toByteArray(b64) {
let tmp;
const lens = getLens(b64);
const validLen = lens[0];
const placeHoldersLen = lens[1];
const arr = new Uint8Array(_byteLength(b64, validLen, placeHoldersLen));
let curByte = 0;
// if there are placeholders, only get up to the last complete 4 chars
const len = placeHoldersLen > 0 ? validLen - 4 : validLen;
let i;
for (i = 0; i < len; i += 4) {
tmp = (revLookup[b64.charCodeAt(i)] << 18) |
(revLookup[b64.charCodeAt(i + 1)] << 12) |
(revLookup[b64.charCodeAt(i + 2)] << 6) |
revLookup[b64.charCodeAt(i + 3)];
arr[curByte++] = (tmp >> 16) & 0xff;
arr[curByte++] = (tmp >> 8) & 0xff;
arr[curByte++] = tmp & 0xff;
}
if (placeHoldersLen === 2) {
tmp = (revLookup[b64.charCodeAt(i)] << 2) |
(revLookup[b64.charCodeAt(i + 1)] >> 4);
arr[curByte++] = tmp & 0xff;
}
if (placeHoldersLen === 1) {
tmp = (revLookup[b64.charCodeAt(i)] << 10) |
(revLookup[b64.charCodeAt(i + 1)] << 4) |
(revLookup[b64.charCodeAt(i + 2)] >> 2);
arr[curByte++] = (tmp >> 8) & 0xff;
arr[curByte++] = tmp & 0xff;
}
return arr;
}
function tripletToBase64(num) {
return (
lookup[(num >> 18) & 0x3f] +
lookup[(num >> 12) & 0x3f] +
lookup[(num >> 6) & 0x3f] +
lookup[num & 0x3f]
);
}
function encodeChunk(uint8, start, end) {
let tmp;
const output = [];
for (let i = start; i < end; i += 3) {
tmp = ((uint8[i] << 16) & 0xff0000) +
((uint8[i + 1] << 8) & 0xff00) +
(uint8[i + 2] & 0xff);
output.push(tripletToBase64(tmp));
}
return output.join("");
}
function fromByteArray(uint8) {
let tmp;
const len = uint8.length;
const extraBytes = len % 3; // if we have 1 byte left, pad 2 bytes
const parts = [];
const maxChunkLength = 16383; // must be multiple of 3
// go through the array every three bytes, we'll deal with trailing stuff later
for (let i = 0, len2 = len - extraBytes; i < len2; i += maxChunkLength) {
parts.push(
encodeChunk(
uint8,
i,
i + maxChunkLength > len2 ? len2 : i + maxChunkLength,
),
);
}
// pad the end with zeros, but make sure to not forget the extra bytes
if (extraBytes === 1) {
tmp = uint8[len - 1];
parts.push(lookup[tmp >> 2] + lookup[(tmp << 4) & 0x3f] + "==");
} else if (extraBytes === 2) {
tmp = (uint8[len - 2] << 8) + uint8[len - 1];
parts.push(
lookup[tmp >> 10] +
lookup[(tmp >> 4) & 0x3f] +
lookup[(tmp << 2) & 0x3f] +
"=",
);
}
return parts.join("");
}
window.__bootstrap.base64 = {
byteLength,
toByteArray,
fromByteArray,
};
})(this);

View file

@ -1,5 +1,5 @@
[WILDCARD]
Files: 43
Files: 44
Nodes: [WILDCARD]
Identifiers: [WILDCARD]
Symbols: [WILDCARD]

View file

@ -776,6 +776,7 @@ delete Object.prototype.__proto__;
// as these are internal APIs of TypeScript which maintain valid libs
ts.libs.push("deno.ns", "deno.window", "deno.worker", "deno.shared_globals");
ts.libMap.set("deno.ns", "lib.deno.ns.d.ts");
ts.libMap.set("deno.web", "lib.deno.web.d.ts");
ts.libMap.set("deno.window", "lib.deno.window.d.ts");
ts.libMap.set("deno.worker", "lib.deno.worker.d.ts");
ts.libMap.set("deno.shared_globals", "lib.deno.shared_globals.d.ts");
@ -787,6 +788,10 @@ delete Object.prototype.__proto__;
`${ASSETS}/lib.deno.ns.d.ts`,
ts.ScriptTarget.ESNext,
);
SNAPSHOT_HOST.getSourceFile(
`${ASSETS}/lib.deno.web.d.ts`,
ts.ScriptTarget.ESNext,
);
SNAPSHOT_HOST.getSourceFile(
`${ASSETS}/lib.deno.window.d.ts`,
ts.ScriptTarget.ESNext,

View file

@ -1038,6 +1038,10 @@
window.EventTarget = EventTarget;
window.ErrorEvent = ErrorEvent;
window.CustomEvent = CustomEvent;
window.dispatchEvent = EventTarget.prototype.dispatchEvent;
window.addEventListener = EventTarget.prototype.addEventListener;
window.removeEventListener = EventTarget.prototype.removeEventListener;
window.__bootstrap = (window.__bootstrap || {});
window.__bootstrap.eventTarget = {
setEventTargetData,
};

View file

@ -26,7 +26,6 @@
((window) => {
const core = Deno.core;
const base64 = window.__bootstrap.base64;
const CONTINUE = null;
const END_OF_STREAM = -1;
@ -679,6 +678,159 @@
return outString;
}
// Following code is forked from https://github.com/beatgammit/base64-js
// Copyright (c) 2014 Jameson Little. MIT License.
const lookup = [];
const revLookup = [];
const code =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
for (let i = 0, len = code.length; i < len; ++i) {
lookup[i] = code[i];
revLookup[code.charCodeAt(i)] = i;
}
// Support decoding URL-safe base64 strings, as Node.js does.
// See: https://en.wikipedia.org/wiki/Base64#URL_applications
revLookup["-".charCodeAt(0)] = 62;
revLookup["_".charCodeAt(0)] = 63;
function getLens(b64) {
const len = b64.length;
if (len % 4 > 0) {
throw new Error("Invalid string. Length must be a multiple of 4");
}
// Trim off extra bytes after placeholder bytes are found
// See: https://github.com/beatgammit/base64-js/issues/42
let validLen = b64.indexOf("=");
if (validLen === -1) validLen = len;
const placeHoldersLen = validLen === len ? 0 : 4 - (validLen % 4);
return [validLen, placeHoldersLen];
}
// base64 is 4/3 + up to two characters of the original data
function byteLength(b64) {
const lens = getLens(b64);
const validLen = lens[0];
const placeHoldersLen = lens[1];
return ((validLen + placeHoldersLen) * 3) / 4 - placeHoldersLen;
}
function _byteLength(
b64,
validLen,
placeHoldersLen,
) {
return ((validLen + placeHoldersLen) * 3) / 4 - placeHoldersLen;
}
function toByteArray(b64) {
let tmp;
const lens = getLens(b64);
const validLen = lens[0];
const placeHoldersLen = lens[1];
const arr = new Uint8Array(_byteLength(b64, validLen, placeHoldersLen));
let curByte = 0;
// if there are placeholders, only get up to the last complete 4 chars
const len = placeHoldersLen > 0 ? validLen - 4 : validLen;
let i;
for (i = 0; i < len; i += 4) {
tmp = (revLookup[b64.charCodeAt(i)] << 18) |
(revLookup[b64.charCodeAt(i + 1)] << 12) |
(revLookup[b64.charCodeAt(i + 2)] << 6) |
revLookup[b64.charCodeAt(i + 3)];
arr[curByte++] = (tmp >> 16) & 0xff;
arr[curByte++] = (tmp >> 8) & 0xff;
arr[curByte++] = tmp & 0xff;
}
if (placeHoldersLen === 2) {
tmp = (revLookup[b64.charCodeAt(i)] << 2) |
(revLookup[b64.charCodeAt(i + 1)] >> 4);
arr[curByte++] = tmp & 0xff;
}
if (placeHoldersLen === 1) {
tmp = (revLookup[b64.charCodeAt(i)] << 10) |
(revLookup[b64.charCodeAt(i + 1)] << 4) |
(revLookup[b64.charCodeAt(i + 2)] >> 2);
arr[curByte++] = (tmp >> 8) & 0xff;
arr[curByte++] = tmp & 0xff;
}
return arr;
}
function tripletToBase64(num) {
return (
lookup[(num >> 18) & 0x3f] +
lookup[(num >> 12) & 0x3f] +
lookup[(num >> 6) & 0x3f] +
lookup[num & 0x3f]
);
}
function encodeChunk(uint8, start, end) {
let tmp;
const output = [];
for (let i = start; i < end; i += 3) {
tmp = ((uint8[i] << 16) & 0xff0000) +
((uint8[i + 1] << 8) & 0xff00) +
(uint8[i + 2] & 0xff);
output.push(tripletToBase64(tmp));
}
return output.join("");
}
function fromByteArray(uint8) {
let tmp;
const len = uint8.length;
const extraBytes = len % 3; // if we have 1 byte left, pad 2 bytes
const parts = [];
const maxChunkLength = 16383; // must be multiple of 3
// go through the array every three bytes, we'll deal with trailing stuff later
for (let i = 0, len2 = len - extraBytes; i < len2; i += maxChunkLength) {
parts.push(
encodeChunk(
uint8,
i,
i + maxChunkLength > len2 ? len2 : i + maxChunkLength,
),
);
}
// pad the end with zeros, but make sure to not forget the extra bytes
if (extraBytes === 1) {
tmp = uint8[len - 1];
parts.push(lookup[tmp >> 2] + lookup[(tmp << 4) & 0x3f] + "==");
} else if (extraBytes === 2) {
tmp = (uint8[len - 2] << 8) + uint8[len - 1];
parts.push(
lookup[tmp >> 10] +
lookup[(tmp >> 4) & 0x3f] +
lookup[(tmp << 2) & 0x3f] +
"=",
);
}
return parts.join("");
}
const base64 = {
byteLength,
toByteArray,
fromByteArray,
};
window.TextEncoder = TextEncoder;
window.TextDecoder = TextDecoder;
window.atob = atob;

20
op_crates/web/Cargo.toml Normal file
View file

@ -0,0 +1,20 @@
# Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
[package]
name = "deno_web"
version = "0.1.0"
edition = "2018"
description = "Collection of Web APIs"
authors = ["the Deno authors"]
license = "MIT"
readme = "README.md"
repository = "https://github.com/denoland/deno"
[lib]
path = "lib.rs"
[dependencies]
deno_core = { version = "0.51.0", path = "../../core" }
[dev-dependencies]
futures = "0.3.5"

View file

@ -0,0 +1,244 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
function assert(cond) {
if (!cond) {
throw Error("assert");
}
}
function addEventListenerTest() {
const document = new EventTarget();
assert(document.addEventListener("x", null, false) === undefined);
assert(document.addEventListener("x", null, true) === undefined);
assert(document.addEventListener("x", null) === undefined);
}
function constructedEventTargetCanBeUsedAsExpected() {
const target = new EventTarget();
const event = new Event("foo", { bubbles: true, cancelable: false });
let callCount = 0;
const listener = (e) => {
assert(e === event);
++callCount;
};
target.addEventListener("foo", listener);
target.dispatchEvent(event);
assert(callCount === 1);
target.dispatchEvent(event);
assert(callCount === 2);
target.removeEventListener("foo", listener);
target.dispatchEvent(event);
assert(callCount === 2);
}
function anEventTargetCanBeSubclassed() {
class NicerEventTarget extends EventTarget {
on(
type,
callback,
options,
) {
this.addEventListener(type, callback, options);
}
off(
type,
callback,
options,
) {
this.removeEventListener(type, callback, options);
}
}
const target = new NicerEventTarget();
new Event("foo", { bubbles: true, cancelable: false });
let callCount = 0;
const listener = () => {
++callCount;
};
target.on("foo", listener);
assert(callCount === 0);
target.off("foo", listener);
assert(callCount === 0);
}
function removingNullEventListenerShouldSucceed() {
const document = new EventTarget();
assert(document.removeEventListener("x", null, false) === undefined);
assert(document.removeEventListener("x", null, true) === undefined);
assert(document.removeEventListener("x", null) === undefined);
}
function constructedEventTargetUseObjectPrototype() {
const target = new EventTarget();
const event = new Event("toString", { bubbles: true, cancelable: false });
let callCount = 0;
const listener = (e) => {
assert(e === event);
++callCount;
};
target.addEventListener("toString", listener);
target.dispatchEvent(event);
assert(callCount === 1);
target.dispatchEvent(event);
assert(callCount === 2);
target.removeEventListener("toString", listener);
target.dispatchEvent(event);
assert(callCount === 2);
}
function toStringShouldBeWebCompatible() {
const target = new EventTarget();
assert(target.toString() === "[object EventTarget]");
}
function dispatchEventShouldNotThrowError() {
let hasThrown = false;
try {
const target = new EventTarget();
const event = new Event("hasOwnProperty", {
bubbles: true,
cancelable: false,
});
const listener = () => {};
target.addEventListener("hasOwnProperty", listener);
target.dispatchEvent(event);
} catch {
hasThrown = true;
}
assert(hasThrown === false);
}
function eventTargetThisShouldDefaultToWindow() {
const {
addEventListener,
dispatchEvent,
removeEventListener,
} = EventTarget.prototype;
let n = 1;
const event = new Event("hello");
const listener = () => {
n = 2;
};
addEventListener("hello", listener);
globalThis.dispatchEvent(event);
assert(n === 2);
n = 1;
removeEventListener("hello", listener);
globalThis.dispatchEvent(event);
assert(n === 1);
globalThis.addEventListener("hello", listener);
dispatchEvent(event);
assert(n === 2);
n = 1;
globalThis.removeEventListener("hello", listener);
dispatchEvent(event);
assert(n === 1);
}
function eventTargetShouldAcceptEventListenerObject() {
const target = new EventTarget();
const event = new Event("foo", { bubbles: true, cancelable: false });
let callCount = 0;
const listener = {
handleEvent(e) {
assert(e === event);
++callCount;
},
};
target.addEventListener("foo", listener);
target.dispatchEvent(event);
assert(callCount === 1);
target.dispatchEvent(event);
assert(callCount === 2);
target.removeEventListener("foo", listener);
target.dispatchEvent(event);
assert(callCount === 2);
}
function eventTargetShouldAcceptAsyncFunction() {
const target = new EventTarget();
const event = new Event("foo", { bubbles: true, cancelable: false });
let callCount = 0;
const listener = (e) => {
assert(e === event);
++callCount;
};
target.addEventListener("foo", listener);
target.dispatchEvent(event);
assert(callCount === 1);
target.dispatchEvent(event);
assert(callCount === 2);
target.removeEventListener("foo", listener);
target.dispatchEvent(event);
assert(callCount === 2);
}
function eventTargetShouldAcceptAsyncFunctionForEventListenerObject() {
const target = new EventTarget();
const event = new Event("foo", { bubbles: true, cancelable: false });
let callCount = 0;
const listener = {
handleEvent(e) {
assert(e === event);
++callCount;
},
};
target.addEventListener("foo", listener);
target.dispatchEvent(event);
assert(callCount === 1);
target.dispatchEvent(event);
assert(callCount === 2);
target.removeEventListener("foo", listener);
target.dispatchEvent(event);
assert(callCount === 2);
}
function main() {
globalThis.__bootstrap.eventTarget.setEventTargetData(globalThis);
addEventListenerTest();
constructedEventTargetCanBeUsedAsExpected();
anEventTargetCanBeSubclassed();
removingNullEventListenerShouldSucceed();
constructedEventTargetUseObjectPrototype();
toStringShouldBeWebCompatible();
dispatchEventShouldNotThrowError();
eventTargetThisShouldDefaultToWindow();
eventTargetShouldAcceptEventListenerObject();
eventTargetShouldAcceptAsyncFunction();
eventTargetShouldAcceptAsyncFunctionForEventListenerObject();
}
main();

111
op_crates/web/event_test.js Normal file
View file

@ -0,0 +1,111 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
function assert(cond) {
if (!cond) {
throw Error("assert");
}
}
function eventInitializedWithType() {
const type = "click";
const event = new Event(type);
assert(event.isTrusted === false);
assert(event.target === null);
assert(event.currentTarget === null);
assert(event.type === "click");
assert(event.bubbles === false);
assert(event.cancelable === false);
}
function eventInitializedWithTypeAndDict() {
const init = "submit";
const eventInit = { bubbles: true, cancelable: true };
const event = new Event(init, eventInit);
assert(event.isTrusted === false);
assert(event.target === null);
assert(event.currentTarget === null);
assert(event.type === "submit");
assert(event.bubbles === true);
assert(event.cancelable === true);
}
function eventComposedPathSuccess() {
const type = "click";
const event = new Event(type);
const composedPath = event.composedPath();
assert(composedPath.length === 0);
}
function eventStopPropagationSuccess() {
const type = "click";
const event = new Event(type);
assert(event.cancelBubble === false);
event.stopPropagation();
assert(event.cancelBubble === true);
}
function eventStopImmediatePropagationSuccess() {
const type = "click";
const event = new Event(type);
assert(event.cancelBubble === false);
event.stopImmediatePropagation();
assert(event.cancelBubble === true);
}
function eventPreventDefaultSuccess() {
const type = "click";
const event = new Event(type);
assert(event.defaultPrevented === false);
event.preventDefault();
assert(event.defaultPrevented === false);
const eventInit = { bubbles: true, cancelable: true };
const cancelableEvent = new Event(type, eventInit);
assert(cancelableEvent.defaultPrevented === false);
cancelableEvent.preventDefault();
assert(cancelableEvent.defaultPrevented === true);
}
function eventInitializedWithNonStringType() {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const type = undefined;
const event = new Event(type);
assert(event.isTrusted === false);
assert(event.target === null);
assert(event.currentTarget === null);
assert(event.type === "undefined");
assert(event.bubbles === false);
assert(event.cancelable === false);
}
// ref https://github.com/web-platform-tests/wpt/blob/master/dom/events/Event-isTrusted.any.js
function eventIsTrusted() {
const desc1 = Object.getOwnPropertyDescriptor(new Event("x"), "isTrusted");
assert(desc1);
assert(typeof desc1.get === "function");
const desc2 = Object.getOwnPropertyDescriptor(new Event("x"), "isTrusted");
assert(desc2);
assert(typeof desc2.get === "function");
assert(desc1.get === desc2.get);
}
function main() {
eventInitializedWithType();
eventInitializedWithTypeAndDict();
eventComposedPathSuccess();
eventStopPropagationSuccess();
eventStopImmediatePropagationSuccess();
eventPreventDefaultSuccess();
eventInitializedWithNonStringType();
eventIsTrusted();
}
main();

187
op_crates/web/lib.deno_web.d.ts vendored Normal file
View file

@ -0,0 +1,187 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
/// <reference no-default-lib="true" />
/// <reference lib="esnext" />
declare class DOMException extends Error {
constructor(message?: string, name?: string);
readonly name: string;
readonly message: string;
}
interface EventInit {
bubbles?: boolean;
cancelable?: boolean;
composed?: boolean;
}
/** An event which takes place in the DOM. */
declare class Event {
constructor(type: string, eventInitDict?: EventInit);
/** Returns true or false depending on how event was initialized. True if
* event goes through its target's ancestors in reverse tree order, and
* false otherwise. */
readonly bubbles: boolean;
cancelBubble: boolean;
/** Returns true or false depending on how event was initialized. Its return
* value does not always carry meaning, but true can indicate that part of the
* operation during which event was dispatched, can be canceled by invoking
* the preventDefault() method. */
readonly cancelable: boolean;
/** Returns true or false depending on how event was initialized. True if
* event invokes listeners past a ShadowRoot node that is the root of its
* target, and false otherwise. */
readonly composed: boolean;
/** Returns the object whose event listener's callback is currently being
* invoked. */
readonly currentTarget: EventTarget | null;
/** Returns true if preventDefault() was invoked successfully to indicate
* cancellation, and false otherwise. */
readonly defaultPrevented: boolean;
/** Returns the event's phase, which is one of NONE, CAPTURING_PHASE,
* AT_TARGET, and BUBBLING_PHASE. */
readonly eventPhase: number;
/** Returns true if event was dispatched by the user agent, and false
* otherwise. */
readonly isTrusted: boolean;
/** Returns the object to which event is dispatched (its target). */
readonly target: EventTarget | null;
/** Returns the event's timestamp as the number of milliseconds measured
* relative to the time origin. */
readonly timeStamp: number;
/** Returns the type of event, e.g. "click", "hashchange", or "submit". */
readonly type: string;
/** Returns the invocation target objects of event's path (objects on which
* listeners will be invoked), except for any nodes in shadow trees of which
* the shadow root's mode is "closed" that are not reachable from event's
* currentTarget. */
composedPath(): EventTarget[];
/** If invoked when the cancelable attribute value is true, and while
* executing a listener for the event with passive set to false, signals to
* the operation that caused event to be dispatched that it needs to be
* canceled. */
preventDefault(): void;
/** Invoking this method prevents event from reaching any registered event
* listeners after the current one finishes running and, when dispatched in a
* tree, also prevents event from reaching any other objects. */
stopImmediatePropagation(): void;
/** When dispatched in a tree, invoking this method prevents event from
* reaching any objects other than the current object. */
stopPropagation(): void;
readonly AT_TARGET: number;
readonly BUBBLING_PHASE: number;
readonly CAPTURING_PHASE: number;
readonly NONE: number;
static readonly AT_TARGET: number;
static readonly BUBBLING_PHASE: number;
static readonly CAPTURING_PHASE: number;
static readonly NONE: number;
}
/**
* EventTarget is a DOM interface implemented by objects that can receive events
* and may have listeners for them.
*/
declare class EventTarget {
/** Appends an event listener for events whose type attribute value is type.
* The callback argument sets the callback that will be invoked when the event
* is dispatched.
*
* The options argument sets listener-specific options. For compatibility this
* can be a boolean, in which case the method behaves exactly as if the value
* was specified as options's capture.
*
* When set to true, options's capture prevents callback from being invoked
* when the event's eventPhase attribute value is BUBBLING_PHASE. When false
* (or not present), callback will not be invoked when event's eventPhase
* attribute value is CAPTURING_PHASE. Either way, callback will be invoked if
* event's eventPhase attribute value is AT_TARGET.
*
* When set to true, options's passive indicates that the callback will not
* cancel the event by invoking preventDefault(). This is used to enable
* performance optimizations described in § 2.8 Observing event listeners.
*
* When set to true, options's once indicates that the callback will only be
* invoked once after which the event listener will be removed.
*
* The event listener is appended to target's event listener list and is not
* appended if it has the same type, callback, and capture. */
addEventListener(
type: string,
listener: EventListenerOrEventListenerObject | null,
options?: boolean | AddEventListenerOptions,
): void;
/** Dispatches a synthetic event event to target and returns true if either
* event's cancelable attribute value is false or its preventDefault() method
* was not invoked, and false otherwise. */
dispatchEvent(event: Event): boolean;
/** Removes the event listener in target's event listener list with the same
* type, callback, and options. */
removeEventListener(
type: string,
callback: EventListenerOrEventListenerObject | null,
options?: EventListenerOptions | boolean,
): void;
[Symbol.toStringTag]: string;
}
interface EventListener {
(evt: Event): void | Promise<void>;
}
interface EventListenerObject {
handleEvent(evt: Event): void | Promise<void>;
}
declare type EventListenerOrEventListenerObject =
| EventListener
| EventListenerObject;
interface AddEventListenerOptions extends EventListenerOptions {
once?: boolean;
passive?: boolean;
}
interface EventListenerOptions {
capture?: boolean;
}
/** Decodes a string of data which has been encoded using base-64 encoding.
*
* console.log(atob("aGVsbG8gd29ybGQ=")); // outputs 'hello world'
*/
declare function atob(s: string): string;
/** Creates a base-64 ASCII encoded string from the input string.
*
* console.log(btoa("hello world")); // outputs "aGVsbG8gd29ybGQ="
*/
declare function btoa(s: string): string;
declare class TextDecoder {
/** Returns encoding's name, lowercased. */
readonly encoding: string;
/** Returns `true` if error mode is "fatal", and `false` otherwise. */
readonly fatal: boolean;
/** Returns `true` if ignore BOM flag is set, and `false` otherwise. */
readonly ignoreBOM = false;
constructor(
label?: string,
options?: { fatal?: boolean; ignoreBOM?: boolean },
);
/** Returns the result of running encoding's decoder. */
decode(input?: BufferSource, options?: { stream?: false }): string;
readonly [Symbol.toStringTag]: string;
}
declare class TextEncoder {
/** Returns "utf-8". */
readonly encoding = "utf-8";
/** Returns the result of running UTF-8's encoder. */
encode(input?: string): Uint8Array;
encodeInto(
input: string,
dest: Uint8Array,
): { read: number; written: number };
readonly [Symbol.toStringTag]: string;
}

102
op_crates/web/lib.rs Normal file
View file

@ -0,0 +1,102 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
use deno_core::crate_modules;
use std::path::PathBuf;
crate_modules!();
pub struct WebScripts {
pub declaration: String,
pub dom_exception: String,
pub event: String,
pub text_encoding: String,
}
fn get_str_path(file_name: &str) -> String {
PathBuf::from(DENO_CRATE_PATH)
.join(file_name)
.to_string_lossy()
.to_string()
}
pub fn get_scripts() -> WebScripts {
WebScripts {
declaration: get_str_path("lib.deno_web.d.ts"),
dom_exception: get_str_path("00_dom_exception.js"),
event: get_str_path("01_event.js"),
text_encoding: get_str_path("08_text_encoding.js"),
}
}
#[cfg(test)]
mod tests {
use deno_core::js_check;
use deno_core::CoreIsolate;
use deno_core::StartupData;
use futures::future::lazy;
use futures::future::FutureExt;
use futures::task::Context;
use futures::task::Poll;
fn run_in_task<F>(f: F)
where
F: FnOnce(&mut Context) + Send + 'static,
{
futures::executor::block_on(lazy(move |cx| f(cx)));
}
fn setup() -> CoreIsolate {
let mut isolate = CoreIsolate::new(StartupData::None, false);
js_check(
isolate
.execute("00_dom_exception.js", include_str!("00_dom_exception.js")),
);
js_check(isolate.execute("01_event.js", include_str!("01_event.js")));
js_check(
isolate
.execute("08_text_encoding.js", include_str!("08_text_encoding.js")),
);
isolate
}
#[test]
fn test_event() {
run_in_task(|mut cx| {
let mut isolate = setup();
js_check(isolate.execute("event_test.js", include_str!("event_test.js")));
if let Poll::Ready(Err(_)) = isolate.poll_unpin(&mut cx) {
unreachable!();
}
});
}
#[test]
fn test_event_target() {
run_in_task(|mut cx| {
let mut isolate = setup();
js_check(
isolate.execute(
"event_target_test.js",
include_str!("event_target_test.js"),
),
);
if let Poll::Ready(Err(_)) = isolate.poll_unpin(&mut cx) {
unreachable!();
}
});
}
#[test]
fn test_text_encoding() {
run_in_task(|mut cx| {
let mut isolate = setup();
js_check(isolate.execute(
"text_encoding_test.js",
include_str!("text_encoding_test.js"),
));
if let Poll::Ready(Err(_)) = isolate.poll_unpin(&mut cx) {
unreachable!();
}
});
}
}

View file

@ -0,0 +1,243 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
function assert(cond) {
if (!cond) {
throw Error("assert");
}
}
function assertArrayEquals(a1, a2) {
if (a1.length !== a2.length) throw Error("assert");
for (const index in a1) {
if (a1[index] !== a2[index]) {
throw Error("assert");
}
}
}
function btoaSuccess() {
const text = "hello world";
const encoded = btoa(text);
assert(encoded === "aGVsbG8gd29ybGQ=");
}
function atobSuccess() {
const encoded = "aGVsbG8gd29ybGQ=";
const decoded = atob(encoded);
assert(decoded === "hello world");
}
function atobWithAsciiWhitespace() {
const encodedList = [
" aGVsbG8gd29ybGQ=",
" aGVsbG8gd29ybGQ=",
"aGVsbG8gd29ybGQ= ",
"aGVsbG8gd29ybGQ=\n",
"aGVsbG\t8gd29ybGQ=",
`aGVsbG\t8g
d29ybGQ=`,
];
for (const encoded of encodedList) {
const decoded = atob(encoded);
assert(decoded === "hello world");
}
}
function atobThrows() {
let threw = false;
try {
atob("aGVsbG8gd29ybGQ==");
} catch (e) {
threw = true;
}
assert(threw);
}
function atobThrows2() {
let threw = false;
try {
atob("aGVsbG8gd29ybGQ===");
} catch (e) {
threw = true;
}
assert(threw);
}
function btoaFailed() {
let threw = false;
const text = "你好";
try {
btoa(text);
} catch (e) {
assert(e instanceof TypeError);
threw = true;
}
assert(threw);
}
function textDecoder2() {
// deno-fmt-ignore
const fixture = new Uint8Array([
0xf0, 0x9d, 0x93, 0xbd,
0xf0, 0x9d, 0x93, 0xae,
0xf0, 0x9d, 0x94, 0x81,
0xf0, 0x9d, 0x93, 0xbd
]);
const decoder = new TextDecoder();
assert(decoder.decode(fixture) === "𝓽𝓮𝔁𝓽");
}
function textDecoderIgnoreBOM() {
// deno-fmt-ignore
const fixture = new Uint8Array([
0xef, 0xbb, 0xbf,
0xf0, 0x9d, 0x93, 0xbd,
0xf0, 0x9d, 0x93, 0xae,
0xf0, 0x9d, 0x94, 0x81,
0xf0, 0x9d, 0x93, 0xbd
]);
const decoder = new TextDecoder("utf-8", { ignoreBOM: true });
assert(decoder.decode(fixture) === "𝓽𝓮𝔁𝓽");
}
function textDecoderNotBOM() {
// deno-fmt-ignore
const fixture = new Uint8Array([
0xef, 0xbb, 0x89,
0xf0, 0x9d, 0x93, 0xbd,
0xf0, 0x9d, 0x93, 0xae,
0xf0, 0x9d, 0x94, 0x81,
0xf0, 0x9d, 0x93, 0xbd
]);
const decoder = new TextDecoder("utf-8", { ignoreBOM: true });
assert(decoder.decode(fixture) === "ﻉ𝓽𝓮𝔁𝓽");
}
function textDecoderASCII() {
const fixture = new Uint8Array([0x89, 0x95, 0x9f, 0xbf]);
const decoder = new TextDecoder("ascii");
assert(decoder.decode(fixture) === "‰•Ÿ¿");
}
function textDecoderErrorEncoding() {
let didThrow = false;
try {
new TextDecoder("foo");
} catch (e) {
didThrow = true;
assert(e.message === "The encoding label provided ('foo') is invalid.");
}
assert(didThrow);
}
function textEncoder() {
const fixture = "𝓽𝓮𝔁𝓽";
const encoder = new TextEncoder();
// deno-fmt-ignore
assertArrayEquals(Array.from(encoder.encode(fixture)), [
0xf0, 0x9d, 0x93, 0xbd,
0xf0, 0x9d, 0x93, 0xae,
0xf0, 0x9d, 0x94, 0x81,
0xf0, 0x9d, 0x93, 0xbd
]);
}
function textEncodeInto() {
const fixture = "text";
const encoder = new TextEncoder();
const bytes = new Uint8Array(5);
const result = encoder.encodeInto(fixture, bytes);
assert(result.read === 4);
assert(result.written === 4);
// deno-fmt-ignore
assertArrayEquals(Array.from(bytes), [
0x74, 0x65, 0x78, 0x74, 0x00,
]);
}
function textEncodeInto2() {
const fixture = "𝓽𝓮𝔁𝓽";
const encoder = new TextEncoder();
const bytes = new Uint8Array(17);
const result = encoder.encodeInto(fixture, bytes);
assert(result.read === 8);
assert(result.written === 16);
// deno-fmt-ignore
assertArrayEquals(Array.from(bytes), [
0xf0, 0x9d, 0x93, 0xbd,
0xf0, 0x9d, 0x93, 0xae,
0xf0, 0x9d, 0x94, 0x81,
0xf0, 0x9d, 0x93, 0xbd, 0x00,
]);
}
function textEncodeInto3() {
const fixture = "𝓽𝓮𝔁𝓽";
const encoder = new TextEncoder();
const bytes = new Uint8Array(5);
const result = encoder.encodeInto(fixture, bytes);
assert(result.read === 2);
assert(result.written === 4);
// deno-fmt-ignore
assertArrayEquals(Array.from(bytes), [
0xf0, 0x9d, 0x93, 0xbd, 0x00,
]);
}
function textDecoderSharedUint8Array() {
const ab = new SharedArrayBuffer(6);
const dataView = new DataView(ab);
const charCodeA = "A".charCodeAt(0);
for (let i = 0; i < ab.byteLength; i++) {
dataView.setUint8(i, charCodeA + i);
}
const ui8 = new Uint8Array(ab);
const decoder = new TextDecoder();
const actual = decoder.decode(ui8);
assert(actual === "ABCDEF");
}
function textDecoderSharedInt32Array() {
const ab = new SharedArrayBuffer(8);
const dataView = new DataView(ab);
const charCodeA = "A".charCodeAt(0);
for (let i = 0; i < ab.byteLength; i++) {
dataView.setUint8(i, charCodeA + i);
}
const i32 = new Int32Array(ab);
const decoder = new TextDecoder();
const actual = decoder.decode(i32);
assert(actual === "ABCDEFGH");
}
function toStringShouldBeWebCompatibility() {
const encoder = new TextEncoder();
assert(encoder.toString() === "[object TextEncoder]");
const decoder = new TextDecoder();
assert(decoder.toString() === "[object TextDecoder]");
}
function main() {
btoaSuccess();
atobSuccess();
atobWithAsciiWhitespace();
atobThrows();
atobThrows2();
btoaFailed();
textDecoder2();
textDecoderIgnoreBOM();
textDecoderNotBOM();
textDecoderASCII();
textDecoderErrorEncoding();
textEncoder();
textEncodeInto();
textEncodeInto2();
textEncodeInto3();
textDecoderSharedUint8Array();
textDecoderSharedInt32Array();
toStringShouldBeWebCompatibility();
}
main();