summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBartek Iwańczuk <biwanczuk@gmail.com>2020-08-07 16:55:02 +0200
committerGitHub <noreply@github.com>2020-08-07 16:55:02 +0200
commit41215eb29c50cdf7048f7431076ccc986b514f6d (patch)
tree519d44a39dbd01e6175abf9302f105d67ce2cc52
parentd7dcbab3efeeac5233c9cedb6edacc7202515449 (diff)
Op crate for Web APIs (#6906)
Co-authored-by: Ryan Dahl <ry@tinyclouds.org>
-rw-r--r--Cargo.lock9
-rw-r--r--Cargo.toml1
-rw-r--r--cli/Cargo.toml1
-rw-r--r--cli/build.rs75
-rw-r--r--cli/dts/lib.deno.shared_globals.d.ts184
-rw-r--r--cli/js.rs1
-rw-r--r--cli/main.rs3
-rw-r--r--cli/rt/07_base64.js157
-rw-r--r--cli/tests/performance_stats.out2
-rw-r--r--cli/tsc/99_main_compiler.js5
-rw-r--r--op_crates/web/00_dom_exception.js (renamed from cli/rt/00_dom_exception.js)0
-rw-r--r--op_crates/web/01_event.js (renamed from cli/rt/01_event.js)4
-rw-r--r--op_crates/web/08_text_encoding.js (renamed from cli/rt/08_text_encoding.js)154
-rw-r--r--op_crates/web/Cargo.toml20
-rw-r--r--op_crates/web/event_target_test.js244
-rw-r--r--op_crates/web/event_test.js111
-rw-r--r--op_crates/web/lib.deno_web.d.ts187
-rw-r--r--op_crates/web/lib.rs102
-rw-r--r--op_crates/web/text_encoding_test.js243
19 files changed, 1159 insertions, 344 deletions
diff --git a/Cargo.lock b/Cargo.lock
index a43eda71d..80f43a6eb 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -382,6 +382,7 @@ dependencies = [
"clap",
"deno_core",
"deno_lint",
+ "deno_web",
"dissimilar",
"dlopen",
"dprint-plugin-typescript",
@@ -460,6 +461,14 @@ dependencies = [
]
[[package]]
+name = "deno_web"
+version = "0.1.0"
+dependencies = [
+ "deno_core",
+ "futures",
+]
+
+[[package]]
name = "derive_deref"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
index b89523628..80e6ce4e6 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -4,6 +4,7 @@ members = [
"core",
"test_plugin",
"test_util",
+ "op_crates/web",
]
exclude = [
"std/hash/_wasm"
diff --git a/cli/Cargo.toml b/cli/Cargo.toml
index 8a8958b63..bedbdb657 100644
--- a/cli/Cargo.toml
+++ b/cli/Cargo.toml
@@ -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"
diff --git a/cli/build.rs b/cli/build.rs
index 777e74558..5a7f173e5 100644
--- a/cli/build.rs
+++ b/cli/build.rs
@@ -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()
+}
diff --git a/cli/dts/lib.deno.shared_globals.d.ts b/cli/dts/lib.deno.shared_globals.d.ts
index e331baee1..ec5367962 100644
--- a/cli/dts/lib.deno.shared_globals.d.ts
+++ b/cli/dts/lib.deno.shared_globals.d.ts
@@ -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>). */
diff --git a/cli/js.rs b/cli/js.rs
index f04f1ee91..763b62aed 100644
--- a/cli/js.rs
+++ b/cli/js.rs
@@ -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");
diff --git a/cli/main.rs b/cli/main.rs
index 97f3161df..728a23db0 100644
--- a/cli/main.rs
+++ b/cli/main.rs
@@ -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,
);
diff --git a/cli/rt/07_base64.js b/cli/rt/07_base64.js
deleted file mode 100644
index 86739fd52..000000000
--- a/cli/rt/07_base64.js
+++ /dev/null
@@ -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);
diff --git a/cli/tests/performance_stats.out b/cli/tests/performance_stats.out
index 9fea7715a..c623d81da 100644
--- a/cli/tests/performance_stats.out
+++ b/cli/tests/performance_stats.out
@@ -1,5 +1,5 @@
[WILDCARD]
-Files: 43
+Files: 44
Nodes: [WILDCARD]
Identifiers: [WILDCARD]
Symbols: [WILDCARD]
diff --git a/cli/tsc/99_main_compiler.js b/cli/tsc/99_main_compiler.js
index b8673b790..41051f2e0 100644
--- a/cli/tsc/99_main_compiler.js
+++ b/cli/tsc/99_main_compiler.js
@@ -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");
@@ -788,6 +789,10 @@ delete Object.prototype.__proto__;
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,
);
diff --git a/cli/rt/00_dom_exception.js b/op_crates/web/00_dom_exception.js
index 6d72779b0..6d72779b0 100644
--- a/cli/rt/00_dom_exception.js
+++ b/op_crates/web/00_dom_exception.js
diff --git a/cli/rt/01_event.js b/op_crates/web/01_event.js
index 35967e0a1..48899e6fd 100644
--- a/cli/rt/01_event.js
+++ b/op_crates/web/01_event.js
@@ -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,
};
diff --git a/cli/rt/08_text_encoding.js b/op_crates/web/08_text_encoding.js
index a7a9a49e4..b8959142f 100644
--- a/cli/rt/08_text_encoding.js
+++ b/op_crates/web/08_text_encoding.js
@@ -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;
diff --git a/op_crates/web/Cargo.toml b/op_crates/web/Cargo.toml
new file mode 100644
index 000000000..ae5677561
--- /dev/null
+++ b/op_crates/web/Cargo.toml
@@ -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"
diff --git a/op_crates/web/event_target_test.js b/op_crates/web/event_target_test.js
new file mode 100644
index 000000000..5e86c6efb
--- /dev/null
+++ b/op_crates/web/event_target_test.js
@@ -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();
diff --git a/op_crates/web/event_test.js b/op_crates/web/event_test.js
new file mode 100644
index 000000000..4f9f94fa9
--- /dev/null
+++ b/op_crates/web/event_test.js
@@ -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();
diff --git a/op_crates/web/lib.deno_web.d.ts b/op_crates/web/lib.deno_web.d.ts
new file mode 100644
index 000000000..b402529e2
--- /dev/null
+++ b/op_crates/web/lib.deno_web.d.ts
@@ -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;
+}
diff --git a/op_crates/web/lib.rs b/op_crates/web/lib.rs
new file mode 100644
index 000000000..4cfe8d090
--- /dev/null
+++ b/op_crates/web/lib.rs
@@ -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!();
+ }
+ });
+ }
+}
diff --git a/op_crates/web/text_encoding_test.js b/op_crates/web/text_encoding_test.js
new file mode 100644
index 000000000..f741fe409
--- /dev/null
+++ b/op_crates/web/text_encoding_test.js
@@ -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();