From e83ff62ccbe33ad9c19cb9cab9154b6767d6d74b Mon Sep 17 00:00:00 2001 From: Luca Casonato Date: Fri, 12 Mar 2021 16:17:18 +0100 Subject: chore: split web op crate (#9635) This commit starts splitting out the deno_web op crate into multiple smaller crates. This commit splits out WebIDL and URL API, but in the future I want to split out each spec into its own crate. That means we will have (in rough order of loading): `webidl`, `dom`, `streams`, `console`, `encoding`, `url`, `file`, `fetch`, `websocket`, and `webgpu` crates. --- op_crates/crypto/Cargo.toml | 2 +- op_crates/crypto/README.md | 6 +- op_crates/fetch/Cargo.toml | 2 +- op_crates/fetch/README.md | 6 +- op_crates/url/00_url.js | 406 +++++++++++++++++ op_crates/url/Cargo.toml | 19 + op_crates/url/README.md | 5 + op_crates/url/internal.d.ts | 13 + op_crates/url/lib.deno_url.d.ts | 175 +++++++ op_crates/url/lib.rs | 155 +++++++ op_crates/web/00_webidl.js | 810 --------------------------------- op_crates/web/01_dom_exception.js | 1 + op_crates/web/11_url.js | 406 ----------------- op_crates/web/Cargo.toml | 1 - op_crates/web/abort_controller_test.js | 140 ------ op_crates/web/event_target_test.js | 245 ---------- op_crates/web/event_test.js | 142 ------ op_crates/web/internal.d.ts | 287 ------------ op_crates/web/lib.deno_web.d.ts | 169 ------- op_crates/web/lib.rs | 226 --------- op_crates/webgpu/Cargo.toml | 2 +- op_crates/webidl/00_webidl.js | 810 +++++++++++++++++++++++++++++++++ op_crates/webidl/Cargo.toml | 17 + op_crates/webidl/README.md | 6 + op_crates/webidl/internal.d.ts | 291 ++++++++++++ op_crates/webidl/lib.rs | 14 + 26 files changed, 1924 insertions(+), 2432 deletions(-) create mode 100644 op_crates/url/00_url.js create mode 100644 op_crates/url/Cargo.toml create mode 100644 op_crates/url/README.md create mode 100644 op_crates/url/internal.d.ts create mode 100644 op_crates/url/lib.deno_url.d.ts create mode 100644 op_crates/url/lib.rs delete mode 100644 op_crates/web/00_webidl.js delete mode 100644 op_crates/web/11_url.js delete mode 100644 op_crates/web/abort_controller_test.js delete mode 100644 op_crates/web/event_target_test.js delete mode 100644 op_crates/web/event_test.js create mode 100644 op_crates/webidl/00_webidl.js create mode 100644 op_crates/webidl/Cargo.toml create mode 100644 op_crates/webidl/README.md create mode 100644 op_crates/webidl/internal.d.ts create mode 100644 op_crates/webidl/lib.rs (limited to 'op_crates') diff --git a/op_crates/crypto/Cargo.toml b/op_crates/crypto/Cargo.toml index 3d5af740e..8b0d2d3fa 100644 --- a/op_crates/crypto/Cargo.toml +++ b/op_crates/crypto/Cargo.toml @@ -4,7 +4,7 @@ name = "deno_crypto" version = "0.14.1" edition = "2018" -description = "Collection of WebCrypto APIs" +description = "Web Cryptography API implementation for Deno" authors = ["the Deno authors"] license = "MIT" readme = "README.md" diff --git a/op_crates/crypto/README.md b/op_crates/crypto/README.md index 0e1c248e6..be0724458 100644 --- a/op_crates/crypto/README.md +++ b/op_crates/crypto/README.md @@ -1,3 +1,5 @@ -# deno crypto +# deno_crypto -Op crate that implements crypto functions. +This crate implements the Web Cryptography API. + +Spec: https://www.w3.org/TR/WebCryptoAPI/ diff --git a/op_crates/fetch/Cargo.toml b/op_crates/fetch/Cargo.toml index 9491b87a3..57a56d740 100644 --- a/op_crates/fetch/Cargo.toml +++ b/op_crates/fetch/Cargo.toml @@ -4,7 +4,7 @@ name = "deno_fetch" version = "0.22.3" edition = "2018" -description = "provides fetch Web API to deno_core" +description = "Fetch API implementation for Deno" authors = ["the Deno authors"] license = "MIT" readme = "README.md" diff --git a/op_crates/fetch/README.md b/op_crates/fetch/README.md index 1a6dcab17..2c946197e 100644 --- a/op_crates/fetch/README.md +++ b/op_crates/fetch/README.md @@ -1 +1,5 @@ -This crate provides the web standard fetch API to `deno_core`. +# deno_fetch + +This crate implements the Fetch API. + +Spec: https://fetch.spec.whatwg.org/ diff --git a/op_crates/url/00_url.js b/op_crates/url/00_url.js new file mode 100644 index 000000000..9dd2b7800 --- /dev/null +++ b/op_crates/url/00_url.js @@ -0,0 +1,406 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. +"use strict"; + +((window) => { + const core = window.Deno.core; + + function requiredArguments(name, length, required) { + if (length < required) { + const errMsg = `${name} requires at least ${required} argument${ + required === 1 ? "" : "s" + }, but only ${length} present`; + throw new TypeError(errMsg); + } + } + + const paramLists = new WeakMap(); + const urls = new WeakMap(); + + class URLSearchParams { + #params = []; + + constructor(init = "") { + if (typeof init === "string") { + // Overload: USVString + // If init is a string and starts with U+003F (?), + // remove the first code point from init. + if (init[0] == "?") { + init = init.slice(1); + } + + this.#params = core.jsonOpSync("op_url_parse_search_params", init); + } else if ( + Array.isArray(init) || + typeof init?.[Symbol.iterator] == "function" + ) { + // Overload: sequence> + for (const pair of init) { + // If pair does not contain exactly two items, then throw a TypeError. + if (pair.length !== 2) { + throw new TypeError( + "URLSearchParams.constructor sequence argument must only contain pair elements", + ); + } + this.#params.push([String(pair[0]), String(pair[1])]); + } + } else if (Object(init) !== init) { + // pass + } else if (init instanceof URLSearchParams) { + this.#params = [...init.#params]; + } else { + // Overload: record + for (const key of Object.keys(init)) { + this.#params.push([key, String(init[key])]); + } + } + + paramLists.set(this, this.#params); + urls.set(this, null); + } + + #updateUrlSearch = () => { + const url = urls.get(this); + if (url == null) { + return; + } + const parseArgs = { href: url.href, setSearch: this.toString() }; + parts.set(url, core.jsonOpSync("op_url_parse", parseArgs)); + }; + + append(name, value) { + requiredArguments("URLSearchParams.append", arguments.length, 2); + this.#params.push([String(name), String(value)]); + this.#updateUrlSearch(); + } + + delete(name) { + requiredArguments("URLSearchParams.delete", arguments.length, 1); + name = String(name); + let i = 0; + while (i < this.#params.length) { + if (this.#params[i][0] === name) { + this.#params.splice(i, 1); + } else { + i++; + } + } + this.#updateUrlSearch(); + } + + getAll(name) { + requiredArguments("URLSearchParams.getAll", arguments.length, 1); + name = String(name); + const values = []; + for (const entry of this.#params) { + if (entry[0] === name) { + values.push(entry[1]); + } + } + + return values; + } + + get(name) { + requiredArguments("URLSearchParams.get", arguments.length, 1); + name = String(name); + for (const entry of this.#params) { + if (entry[0] === name) { + return entry[1]; + } + } + + return null; + } + + has(name) { + requiredArguments("URLSearchParams.has", arguments.length, 1); + name = String(name); + return this.#params.some((entry) => entry[0] === name); + } + + set(name, value) { + requiredArguments("URLSearchParams.set", arguments.length, 2); + + // If there are any name-value pairs whose name is name, in list, + // set the value of the first such name-value pair to value + // and remove the others. + name = String(name); + value = String(value); + let found = false; + let i = 0; + while (i < this.#params.length) { + if (this.#params[i][0] === name) { + if (!found) { + this.#params[i][1] = value; + found = true; + i++; + } else { + this.#params.splice(i, 1); + } + } else { + i++; + } + } + + // Otherwise, append a new name-value pair whose name is name + // and value is value, to list. + if (!found) { + this.#params.push([String(name), String(value)]); + } + + this.#updateUrlSearch(); + } + + sort() { + this.#params.sort((a, b) => (a[0] === b[0] ? 0 : a[0] > b[0] ? 1 : -1)); + this.#updateUrlSearch(); + } + + forEach(callbackfn, thisArg) { + requiredArguments("URLSearchParams.forEach", arguments.length, 1); + + if (typeof thisArg !== "undefined") { + callbackfn = callbackfn.bind(thisArg); + } + + for (const [key, value] of this.#params) { + callbackfn(value, key, this); + } + } + + *keys() { + for (const [key] of this.#params) { + yield key; + } + } + + *values() { + for (const [, value] of this.#params) { + yield value; + } + } + + *entries() { + yield* this.#params; + } + + *[Symbol.iterator]() { + yield* this.#params; + } + + toString() { + return core.jsonOpSync("op_url_stringify_search_params", this.#params); + } + } + + const parts = new WeakMap(); + + class URL { + #searchParams = null; + + constructor(url, base) { + new.target; + + if (url instanceof URL && base === undefined) { + parts.set(this, parts.get(url)); + } else { + base = base !== undefined ? String(base) : base; + const parseArgs = { href: String(url), baseHref: base }; + parts.set(this, core.jsonOpSync("op_url_parse", parseArgs)); + } + } + + [Symbol.for("Deno.customInspect")](inspect) { + const object = { + href: this.href, + origin: this.origin, + protocol: this.protocol, + username: this.username, + password: this.password, + host: this.host, + hostname: this.hostname, + port: this.port, + pathname: this.pathname, + hash: this.hash, + search: this.search, + }; + return `${this.constructor.name} ${inspect(object)}`; + } + + #updateSearchParams = () => { + if (this.#searchParams != null) { + const params = paramLists.get(this.#searchParams); + const newParams = core.jsonOpSync( + "op_url_parse_search_params", + this.search.slice(1), + ); + params.splice(0, params.length, ...newParams); + } + }; + + get hash() { + return parts.get(this).hash; + } + + set hash(value) { + try { + const parseArgs = { href: this.href, setHash: String(value) }; + parts.set(this, core.jsonOpSync("op_url_parse", parseArgs)); + } catch { + /* pass */ + } + } + + get host() { + return parts.get(this).host; + } + + set host(value) { + try { + const parseArgs = { href: this.href, setHost: String(value) }; + parts.set(this, core.jsonOpSync("op_url_parse", parseArgs)); + } catch { + /* pass */ + } + } + + get hostname() { + return parts.get(this).hostname; + } + + set hostname(value) { + try { + const parseArgs = { href: this.href, setHostname: String(value) }; + parts.set(this, core.jsonOpSync("op_url_parse", parseArgs)); + } catch { + /* pass */ + } + } + + get href() { + return parts.get(this).href; + } + + set href(value) { + try { + const parseArgs = { href: String(value) }; + parts.set(this, core.jsonOpSync("op_url_parse", parseArgs)); + } catch { + throw new TypeError("Invalid URL"); + } + this.#updateSearchParams(); + } + + get origin() { + return parts.get(this).origin; + } + + get password() { + return parts.get(this).password; + } + + set password(value) { + try { + const parseArgs = { href: this.href, setPassword: String(value) }; + parts.set(this, core.jsonOpSync("op_url_parse", parseArgs)); + } catch { + /* pass */ + } + } + + get pathname() { + return parts.get(this).pathname; + } + + set pathname(value) { + try { + const parseArgs = { href: this.href, setPathname: String(value) }; + parts.set(this, core.jsonOpSync("op_url_parse", parseArgs)); + } catch { + /* pass */ + } + } + + get port() { + return parts.get(this).port; + } + + set port(value) { + try { + const parseArgs = { href: this.href, setPort: String(value) }; + parts.set(this, core.jsonOpSync("op_url_parse", parseArgs)); + } catch { + /* pass */ + } + } + + get protocol() { + return parts.get(this).protocol; + } + + set protocol(value) { + try { + const parseArgs = { href: this.href, setProtocol: String(value) }; + parts.set(this, core.jsonOpSync("op_url_parse", parseArgs)); + } catch { + /* pass */ + } + } + + get search() { + return parts.get(this).search; + } + + set search(value) { + try { + const parseArgs = { href: this.href, setSearch: String(value) }; + parts.set(this, core.jsonOpSync("op_url_parse", parseArgs)); + this.#updateSearchParams(); + } catch { + /* pass */ + } + } + + get username() { + return parts.get(this).username; + } + + set username(value) { + try { + const parseArgs = { href: this.href, setUsername: String(value) }; + parts.set(this, core.jsonOpSync("op_url_parse", parseArgs)); + } catch { + /* pass */ + } + } + + get searchParams() { + if (this.#searchParams == null) { + this.#searchParams = new URLSearchParams(this.search); + urls.set(this.#searchParams, this); + } + return this.#searchParams; + } + + toString() { + return this.href; + } + + toJSON() { + return this.href; + } + + static createObjectURL() { + throw new Error("Not implemented"); + } + + static revokeObjectURL() { + throw new Error("Not implemented"); + } + } + + window.__bootstrap.url = { + URL, + URLSearchParams, + }; +})(this); diff --git a/op_crates/url/Cargo.toml b/op_crates/url/Cargo.toml new file mode 100644 index 000000000..a07bfc7d3 --- /dev/null +++ b/op_crates/url/Cargo.toml @@ -0,0 +1,19 @@ +# Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. + +[package] +name = "deno_url" +version = "0.1.0" +edition = "2018" +description = "URL API implementation for Deno" +authors = ["the Deno authors"] +license = "MIT" +readme = "README.md" +repository = "https://github.com/denoland/deno" + +[lib] +path = "lib.rs" + +[dependencies] +deno_core = { version = "0.80.2", path = "../../core" } +idna = "0.2.1" +serde = { version = "1.0.123", features = ["derive"] } diff --git a/op_crates/url/README.md b/op_crates/url/README.md new file mode 100644 index 000000000..991dd8b20 --- /dev/null +++ b/op_crates/url/README.md @@ -0,0 +1,5 @@ +# deno_url + +This crate implements the URL API for Deno. + +Spec: https://url.spec.whatwg.org/ diff --git a/op_crates/url/internal.d.ts b/op_crates/url/internal.d.ts new file mode 100644 index 000000000..f852928d3 --- /dev/null +++ b/op_crates/url/internal.d.ts @@ -0,0 +1,13 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. + +/// +/// + +declare namespace globalThis { + declare namespace __bootstrap { + declare var url: { + URL: typeof URL; + URLSearchParams: typeof URLSearchParams; + }; + } +} diff --git a/op_crates/url/lib.deno_url.d.ts b/op_crates/url/lib.deno_url.d.ts new file mode 100644 index 000000000..2a27fe693 --- /dev/null +++ b/op_crates/url/lib.deno_url.d.ts @@ -0,0 +1,175 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. + +// deno-lint-ignore-file no-explicit-any + +/// +/// + +declare class URLSearchParams { + constructor( + init?: string[][] | Record | string | URLSearchParams, + ); + static toString(): string; + + /** Appends a specified key/value pair as a new search parameter. + * + * ```ts + * let searchParams = new URLSearchParams(); + * searchParams.append('name', 'first'); + * searchParams.append('name', 'second'); + * ``` + */ + append(name: string, value: string): void; + + /** Deletes the given search parameter and its associated value, + * from the list of all search parameters. + * + * ```ts + * let searchParams = new URLSearchParams([['name', 'value']]); + * searchParams.delete('name'); + * ``` + */ + delete(name: string): void; + + /** Returns all the values associated with a given search parameter + * as an array. + * + * ```ts + * searchParams.getAll('name'); + * ``` + */ + getAll(name: string): string[]; + + /** Returns the first value associated to the given search parameter. + * + * ```ts + * searchParams.get('name'); + * ``` + */ + get(name: string): string | null; + + /** Returns a Boolean that indicates whether a parameter with the + * specified name exists. + * + * ```ts + * searchParams.has('name'); + * ``` + */ + has(name: string): boolean; + + /** Sets the value associated with a given search parameter to the + * given value. If there were several matching values, this method + * deletes the others. If the search parameter doesn't exist, this + * method creates it. + * + * ```ts + * searchParams.set('name', 'value'); + * ``` + */ + set(name: string, value: string): void; + + /** Sort all key/value pairs contained in this object in place and + * return undefined. The sort order is according to Unicode code + * points of the keys. + * + * ```ts + * searchParams.sort(); + * ``` + */ + sort(): void; + + /** Calls a function for each element contained in this object in + * place and return undefined. Optionally accepts an object to use + * as this when executing callback as second argument. + * + * ```ts + * const params = new URLSearchParams([["a", "b"], ["c", "d"]]); + * params.forEach((value, key, parent) => { + * console.log(value, key, parent); + * }); + * ``` + * + */ + forEach( + callbackfn: (value: string, key: string, parent: this) => void, + thisArg?: any, + ): void; + + /** Returns an iterator allowing to go through all keys contained + * in this object. + * + * ```ts + * const params = new URLSearchParams([["a", "b"], ["c", "d"]]); + * for (const key of params.keys()) { + * console.log(key); + * } + * ``` + */ + keys(): IterableIterator; + + /** Returns an iterator allowing to go through all values contained + * in this object. + * + * ```ts + * const params = new URLSearchParams([["a", "b"], ["c", "d"]]); + * for (const value of params.values()) { + * console.log(value); + * } + * ``` + */ + values(): IterableIterator; + + /** Returns an iterator allowing to go through all key/value + * pairs contained in this object. + * + * ```ts + * const params = new URLSearchParams([["a", "b"], ["c", "d"]]); + * for (const [key, value] of params.entries()) { + * console.log(key, value); + * } + * ``` + */ + entries(): IterableIterator<[string, string]>; + + /** Returns an iterator allowing to go through all key/value + * pairs contained in this object. + * + * ```ts + * const params = new URLSearchParams([["a", "b"], ["c", "d"]]); + * for (const [key, value] of params) { + * console.log(key, value); + * } + * ``` + */ + [Symbol.iterator](): IterableIterator<[string, string]>; + + /** Returns a query string suitable for use in a URL. + * + * ```ts + * searchParams.toString(); + * ``` + */ + toString(): string; +} + +/** The URL interface represents an object providing static methods used for creating object URLs. */ +declare class URL { + constructor(url: string, base?: string | URL); + createObjectURL(object: any): string; + revokeObjectURL(url: string): void; + + hash: string; + host: string; + hostname: string; + href: string; + toString(): string; + readonly origin: string; + password: string; + pathname: string; + port: string; + protocol: string; + search: string; + readonly searchParams: URLSearchParams; + username: string; + toJSON(): string; +} diff --git a/op_crates/url/lib.rs b/op_crates/url/lib.rs new file mode 100644 index 000000000..a655d8c34 --- /dev/null +++ b/op_crates/url/lib.rs @@ -0,0 +1,155 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. + +use deno_core::error::generic_error; +use deno_core::error::type_error; +use deno_core::error::uri_error; +use deno_core::error::AnyError; +use deno_core::serde_json; +use deno_core::serde_json::json; +use deno_core::serde_json::Value; +use deno_core::url::form_urlencoded; +use deno_core::url::quirks; +use deno_core::url::Url; +use deno_core::JsRuntime; +use deno_core::ZeroCopyBuf; +use serde::Deserialize; +use serde::Serialize; +use std::panic::catch_unwind; +use std::path::PathBuf; + +/// Parse `UrlParseArgs::href` with an optional `UrlParseArgs::base_href`, or an +/// optional part to "set" after parsing. Return `UrlParts`. +pub fn op_url_parse( + _state: &mut deno_core::OpState, + args: Value, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result { + #[derive(Deserialize)] + #[serde(rename_all = "camelCase")] + struct UrlParseArgs { + href: String, + base_href: Option, + // If one of the following are present, this is a setter call. Apply the + // proper `Url::set_*()` method after (re)parsing `href`. + set_hash: Option, + set_host: Option, + set_hostname: Option, + set_password: Option, + set_pathname: Option, + set_port: Option, + set_protocol: Option, + set_search: Option, + set_username: Option, + } + let args: UrlParseArgs = serde_json::from_value(args)?; + let base_url = args + .base_href + .as_ref() + .map(|b| Url::parse(b).map_err(|_| type_error("Invalid base URL"))) + .transpose()?; + let mut url = Url::options() + .base_url(base_url.as_ref()) + .parse(&args.href) + .map_err(|_| type_error("Invalid URL"))?; + + if let Some(hash) = args.set_hash.as_ref() { + quirks::set_hash(&mut url, hash); + } else if let Some(host) = args.set_host.as_ref() { + quirks::set_host(&mut url, host).map_err(|_| uri_error("Invalid host"))?; + } else if let Some(hostname) = args.set_hostname.as_ref() { + quirks::set_hostname(&mut url, hostname) + .map_err(|_| uri_error("Invalid hostname"))?; + } else if let Some(password) = args.set_password.as_ref() { + quirks::set_password(&mut url, password) + .map_err(|_| uri_error("Invalid password"))?; + } else if let Some(pathname) = args.set_pathname.as_ref() { + quirks::set_pathname(&mut url, pathname); + } else if let Some(port) = args.set_port.as_ref() { + quirks::set_port(&mut url, port).map_err(|_| uri_error("Invalid port"))?; + } else if let Some(protocol) = args.set_protocol.as_ref() { + quirks::set_protocol(&mut url, protocol) + .map_err(|_| uri_error("Invalid protocol"))?; + } else if let Some(search) = args.set_search.as_ref() { + quirks::set_search(&mut url, search); + } else if let Some(username) = args.set_username.as_ref() { + quirks::set_username(&mut url, username) + .map_err(|_| uri_error("Invalid username"))?; + } + + #[derive(Serialize)] + struct UrlParts<'a> { + href: &'a str, + hash: &'a str, + host: &'a str, + hostname: &'a str, + origin: &'a str, + password: &'a str, + pathname: &'a str, + port: &'a str, + protocol: &'a str, + search: &'a str, + username: &'a str, + } + // TODO(nayeemrmn): Panic that occurs in rust-url for the `non-spec:` + // url-constructor wpt tests: https://github.com/servo/rust-url/issues/670. + let username = catch_unwind(|| quirks::username(&url)).map_err(|_| { + generic_error(format!( + "Internal error while parsing \"{}\"{}, \ + see https://github.com/servo/rust-url/issues/670", + args.href, + args + .base_href + .map(|b| format!(" against \"{}\"", b)) + .unwrap_or_default() + )) + })?; + Ok(json!(UrlParts { + href: quirks::href(&url), + hash: quirks::hash(&url), + host: quirks::host(&url), + hostname: quirks::hostname(&url), + origin: &quirks::origin(&url), + password: quirks::password(&url), + pathname: quirks::pathname(&url), + port: quirks::port(&url), + protocol: quirks::protocol(&url), + search: quirks::search(&url), + username, + })) +} + +pub fn op_url_parse_search_params( + _state: &mut deno_core::OpState, + args: Value, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result { + let search: String = serde_json::from_value(args)?; + let search_params: Vec<_> = form_urlencoded::parse(search.as_bytes()) + .into_iter() + .collect(); + Ok(json!(search_params)) +} + +pub fn op_url_stringify_search_params( + _state: &mut deno_core::OpState, + args: Value, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result { + let search_params: Vec<(String, String)> = serde_json::from_value(args)?; + let search = form_urlencoded::Serializer::new(String::new()) + .extend_pairs(search_params) + .finish(); + Ok(json!(search)) +} + +/// Load and execute the javascript code. +pub fn init(isolate: &mut JsRuntime) { + let files = vec![("deno:op_crates/url/00_url.js", include_str!("00_url.js"))]; + for (url, source_code) in files { + isolate.execute(url, source_code).unwrap(); + } +} + +pub fn get_declaration() -> PathBuf { + PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("lib.deno_url.d.ts") +} diff --git a/op_crates/web/00_webidl.js b/op_crates/web/00_webidl.js deleted file mode 100644 index c00c605e8..000000000 --- a/op_crates/web/00_webidl.js +++ /dev/null @@ -1,810 +0,0 @@ -// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. - -// Adapted from https://github.com/jsdom/webidl-conversions. -// Copyright Domenic Denicola. Licensed under BSD-2-Clause License. -// Original license at https://github.com/jsdom/webidl-conversions/blob/master/LICENSE.md. - -"use strict"; - -((window) => { - function makeException(ErrorType, message, opts = {}) { - if (opts.globals) { - ErrorType = opts.globals[ErrorType.name]; - } - return new ErrorType( - `${opts.prefix ? opts.prefix + ": " : ""}${ - opts.context ? opts.context : "Value" - } ${message}`, - ); - } - - function toNumber(value, opts = {}) { - if (!opts.globals) { - return +value; - } - if (typeof value === "bigint") { - throw opts.globals.TypeError("Cannot convert a BigInt value to a number"); - } - return opts.globals.Number(value); - } - - function type(V) { - if (V === null) { - return "Null"; - } - switch (typeof V) { - case "undefined": - return "Undefined"; - case "boolean": - return "Boolean"; - case "number": - return "Number"; - case "string": - return "String"; - case "symbol": - return "Symbol"; - case "bigint": - return "BigInt"; - case "object": - // Falls through - case "function": - // Falls through - default: - // Per ES spec, typeof returns an implemention-defined value that is not any of the existing ones for - // uncallable non-standard exotic objects. Yet Type() which the Web IDL spec depends on returns Object for - // such cases. So treat the default case as an object. - return "Object"; - } - } - - // Round x to the nearest integer, choosing the even integer if it lies halfway between two. - function evenRound(x) { - // There are four cases for numbers with fractional part being .5: - // - // case | x | floor(x) | round(x) | expected | x <> 0 | x % 1 | x & 1 | example - // 1 | 2n + 0.5 | 2n | 2n + 1 | 2n | > | 0.5 | 0 | 0.5 -> 0 - // 2 | 2n + 1.5 | 2n + 1 | 2n + 2 | 2n + 2 | > | 0.5 | 1 | 1.5 -> 2 - // 3 | -2n - 0.5 | -2n - 1 | -2n | -2n | < | -0.5 | 0 | -0.5 -> 0 - // 4 | -2n - 1.5 | -2n - 2 | -2n - 1 | -2n - 2 | < | -0.5 | 1 | -1.5 -> -2 - // (where n is a non-negative integer) - // - // Branch here for cases 1 and 4 - if ( - (x > 0 && x % 1 === +0.5 && (x & 1) === 0) || - (x < 0 && x % 1 === -0.5 && (x & 1) === 1) - ) { - return censorNegativeZero(Math.floor(x)); - } - - return censorNegativeZero(Math.round(x)); - } - - function integerPart(n) { - return censorNegativeZero(Math.trunc(n)); - } - - function sign(x) { - return x < 0 ? -1 : 1; - } - - function modulo(x, y) { - // https://tc39.github.io/ecma262/#eqn-modulo - // Note that http://stackoverflow.com/a/4467559/3191 does NOT work for large modulos - const signMightNotMatch = x % y; - if (sign(y) !== sign(signMightNotMatch)) { - return signMightNotMatch + y; - } - return signMightNotMatch; - } - - function censorNegativeZero(x) { - return x === 0 ? 0 : x; - } - - function createIntegerConversion(bitLength, typeOpts) { - const isSigned = !typeOpts.unsigned; - - let lowerBound; - let upperBound; - if (bitLength === 64) { - upperBound = Number.MAX_SAFE_INTEGER; - lowerBound = !isSigned ? 0 : Number.MIN_SAFE_INTEGER; - } else if (!isSigned) { - lowerBound = 0; - upperBound = Math.pow(2, bitLength) - 1; - } else { - lowerBound = -Math.pow(2, bitLength - 1); - upperBound = Math.pow(2, bitLength - 1) - 1; - } - - const twoToTheBitLength = Math.pow(2, bitLength); - const twoToOneLessThanTheBitLength = Math.pow(2, bitLength - 1); - - return (V, opts = {}) => { - let x = toNumber(V, opts); - x = censorNegativeZero(x); - - if (opts.enforceRange) { - if (!Number.isFinite(x)) { - throw makeException(TypeError, "is not a finite number", opts); - } - - x = integerPart(x); - - if (x < lowerBound || x > upperBound) { - throw makeException( - TypeError, - `is outside the accepted range of ${lowerBound} to ${upperBound}, inclusive`, - opts, - ); - } - - return x; - } - - if (!Number.isNaN(x) && opts.clamp) { - x = Math.min(Math.max(x, lowerBound), upperBound); - x = evenRound(x); - return x; - } - - if (!Number.isFinite(x) || x === 0) { - return 0; - } - x = integerPart(x); - - // Math.pow(2, 64) is not accurately representable in JavaScript, so try to avoid these per-spec operations if - // possible. Hopefully it's an optimization for the non-64-bitLength cases too. - if (x >= lowerBound && x <= upperBound) { - return x; - } - - // These will not work great for bitLength of 64, but oh well. See the README for more details. - x = modulo(x, twoToTheBitLength); - if (isSigned && x >= twoToOneLessThanTheBitLength) { - return x - twoToTheBitLength; - } - return x; - }; - } - - function createLongLongConversion(bitLength, { unsigned }) { - const upperBound = Number.MAX_SAFE_INTEGER; - const lowerBound = unsigned ? 0 : Number.MIN_SAFE_INTEGER; - const asBigIntN = unsigned ? BigInt.asUintN : BigInt.asIntN; - - return (V, opts = {}) => { - let x = toNumber(V, opts); - x = censorNegativeZero(x); - - if (opts.enforceRange) { - if (!Number.isFinite(x)) { - throw makeException(TypeError, "is not a finite number", opts); - } - - x = integerPart(x); - - if (x < lowerBound || x > upperBound) { - throw makeException( - TypeError, - `is outside the accepted range of ${lowerBound} to ${upperBound}, inclusive`, - opts, - ); - } - - return x; - } - - if (!Number.isNaN(x) && opts.clamp) { - x = Math.min(Math.max(x, lowerBound), upperBound); - x = evenRound(x); - return x; - } - - if (!Number.isFinite(x) || x === 0) { - return 0; - } - - let xBigInt = BigInt(integerPart(x)); - xBigInt = asBigIntN(bitLength, xBigInt); - return Number(xBigInt); - }; - } - - const converters = []; - - converters.any = (V) => { - return V; - }; - - converters.boolean = function (val) { - return !!val; - }; - - converters.byte = createIntegerConversion(8, { unsigned: false }); - converters.octet = createIntegerConversion(8, { unsigned: true }); - - converters.short = createIntegerConversion(16, { unsigned: false }); - converters["unsigned short"] = createIntegerConversion(16, { - unsigned: true, - }); - - converters.long = createIntegerConversion(32, { unsigned: false }); - converters["unsigned long"] = createIntegerConversion(32, { unsigned: true }); - - converters["long long"] = createLongLongConversion(64, { unsigned: false }); - converters["unsigned long long"] = createLongLongConversion(64, { - unsigned: true, - }); - - converters.float = (V, opts) => { - const x = toNumber(V, opts); - - if (!Number.isFinite(x)) { - throw makeException( - TypeError, - "is not a finite floating-point value", - opts, - ); - } - - if (Object.is(x, -0)) { - return x; - } - - const y = Math.fround(x); - - if (!Number.isFinite(y)) { - throw makeException( - TypeError, - "is outside the range of a single-precision floating-point value", - opts, - ); - } - - return y; - }; - - converters["unrestricted float"] = (V, opts) => { - const x = toNumber(V, opts); - - if (isNaN(x)) { - return x; - } - - if (Object.is(x, -0)) { - return x; - } - - return Math.fround(x); - }; - - converters.double = (V, opts) => { - const x = toNumber(V, opts); - - if (!Number.isFinite(x)) { - throw makeException( - TypeError, - "is not a finite floating-point value", - opts, - ); - } - - return x; - }; - - converters["unrestricted double"] = (V, opts) => { - const x = toNumber(V, opts); - - return x; - }; - - converters.DOMString = function (V, opts = {}) { - if (opts.treatNullAsEmptyString && V === null) { - return ""; - } - - if (typeof V === "symbol") { - throw makeException( - TypeError, - "is a symbol, which cannot be converted to a string", - opts, - ); - } - - const StringCtor = opts.globals ? opts.globals.String : String; - return StringCtor(V); - }; - - converters.ByteString = (V, opts) => { - const x = converters.DOMString(V, opts); - let c; - for (let i = 0; (c = x.codePointAt(i)) !== undefined; ++i) { - if (c > 255) { - throw makeException(TypeError, "is not a valid ByteString", opts); - } - } - - return x; - }; - - converters.USVString = (V, opts) => { - const S = converters.DOMString(V, opts); - const n = S.length; - const U = []; - for (let i = 0; i < n; ++i) { - const c = S.charCodeAt(i); - if (c < 0xd800 || c > 0xdfff) { - U.push(String.fromCodePoint(c)); - } else if (0xdc00 <= c && c <= 0xdfff) { - U.push(String.fromCodePoint(0xfffd)); - } else if (i === n - 1) { - U.push(String.fromCodePoint(0xfffd)); - } else { - const d = S.charCodeAt(i + 1); - if (0xdc00 <= d && d <= 0xdfff) { - const a = c & 0x3ff; - const b = d & 0x3ff; - U.push(String.fromCodePoint((2 << 15) + (2 << 9) * a + b)); - ++i; - } else { - U.push(String.fromCodePoint(0xfffd)); - } - } - } - - return U.join(""); - }; - - converters.object = (V, opts) => { - if (type(V) !== "Object") { - throw makeException(TypeError, "is not an object", opts); - } - - return V; - }; - - // Not exported, but used in Function and VoidFunction. - - // Neither Function nor VoidFunction is defined with [TreatNonObjectAsNull], so - // handling for that is omitted. - function convertCallbackFunction(V, opts) { - if (typeof V !== "function") { - throw makeException(TypeError, "is not a function", opts); - } - return V; - } - - const abByteLengthGetter = Object.getOwnPropertyDescriptor( - ArrayBuffer.prototype, - "byteLength", - ).get; - - function isNonSharedArrayBuffer(V) { - try { - // This will throw on SharedArrayBuffers, but not detached ArrayBuffers. - // (The spec says it should throw, but the spec conflicts with implementations: https://github.com/tc39/ecma262/issues/678) - abByteLengthGetter.call(V); - - return true; - } catch { - return false; - } - } - - let sabByteLengthGetter; - - function isSharedArrayBuffer(V) { - // TODO(lucacasonato): vulnerable to prototype pollution. Needs to happen - // here because SharedArrayBuffer is not available during snapshotting. - if (!sabByteLengthGetter) { - sabByteLengthGetter = Object.getOwnPropertyDescriptor( - SharedArrayBuffer.prototype, - "byteLength", - ).get; - } - try { - sabByteLengthGetter.call(V); - return true; - } catch { - return false; - } - } - - function isArrayBufferDetached(V) { - try { - // eslint-disable-next-line no-new - new Uint8Array(V); - return false; - } catch { - return true; - } - } - - converters.ArrayBuffer = (V, opts = {}) => { - if (!isNonSharedArrayBuffer(V)) { - if (opts.allowShared && !isSharedArrayBuffer(V)) { - throw makeException( - TypeError, - "is not an ArrayBuffer or SharedArrayBuffer", - opts, - ); - } - throw makeException(TypeError, "is not an ArrayBuffer", opts); - } - if (isArrayBufferDetached(V)) { - throw makeException(TypeError, "is a detached ArrayBuffer", opts); - } - - return V; - }; - - const dvByteLengthGetter = Object.getOwnPropertyDescriptor( - DataView.prototype, - "byteLength", - ).get; - converters.DataView = (V, opts = {}) => { - try { - dvByteLengthGetter.call(V); - } catch (e) { - throw makeException(TypeError, "is not a DataView", opts); - } - - if (!opts.allowShared && isSharedArrayBuffer(V.buffer)) { - throw makeException( - TypeError, - "is backed by a SharedArrayBuffer, which is not allowed", - opts, - ); - } - if (isArrayBufferDetached(V.buffer)) { - throw makeException( - TypeError, - "is backed by a detached ArrayBuffer", - opts, - ); - } - - return V; - }; - - // Returns the unforgeable `TypedArray` constructor name or `undefined`, - // if the `this` value isn't a valid `TypedArray` object. - // - // https://tc39.es/ecma262/#sec-get-%typedarray%.prototype-@@tostringtag - const typedArrayNameGetter = Object.getOwnPropertyDescriptor( - Object.getPrototypeOf(Uint8Array).prototype, - Symbol.toStringTag, - ).get; - [ - Int8Array, - Int16Array, - Int32Array, - Uint8Array, - Uint16Array, - Uint32Array, - Uint8ClampedArray, - Float32Array, - Float64Array, - ].forEach((func) => { - const name = func.name; - const article = /^[AEIOU]/.test(name) ? "an" : "a"; - converters[name] = (V, opts = {}) => { - if (!ArrayBuffer.isView(V) || typedArrayNameGetter.call(V) !== name) { - throw makeException( - TypeError, - `is not ${article} ${name} object`, - opts, - ); - } - if (!opts.allowShared && isSharedArrayBuffer(V.buffer)) { - throw makeException( - TypeError, - "is a view on a SharedArrayBuffer, which is not allowed", - opts, - ); - } - if (isArrayBufferDetached(V.buffer)) { - throw makeException( - TypeError, - "is a view on a detached ArrayBuffer", - opts, - ); - } - - return V; - }; - }); - - // Common definitions - - converters.ArrayBufferView = (V, opts = {}) => { - if (!ArrayBuffer.isView(V)) { - throw makeException( - TypeError, - "is not a view on an ArrayBuffer or SharedArrayBuffer", - opts, - ); - } - - if (!opts.allowShared && isSharedArrayBuffer(V.buffer)) { - throw makeException( - TypeError, - "is a view on a SharedArrayBuffer, which is not allowed", - opts, - ); - } - - if (isArrayBufferDetached(V.buffer)) { - throw makeException( - TypeError, - "is a view on a detached ArrayBuffer", - opts, - ); - } - return V; - }; - - converters.BufferSource = (V, opts = {}) => { - if (ArrayBuffer.isView(V)) { - if (!opts.allowShared && isSharedArrayBuffer(V.buffer)) { - throw makeException( - TypeError, - "is a view on a SharedArrayBuffer, which is not allowed", - opts, - ); - } - - if (isArrayBufferDetached(V.buffer)) { - throw makeException( - TypeError, - "is a view on a detached ArrayBuffer", - opts, - ); - } - return V; - } - - if (!opts.allowShared && !isNonSharedArrayBuffer(V)) { - throw makeException( - TypeError, - "is not an ArrayBuffer or a view on one", - opts, - ); - } - if ( - opts.allowShared && - !isSharedArrayBuffer(V) && - !isNonSharedArrayBuffer(V) - ) { - throw makeException( - TypeError, - "is not an ArrayBuffer, SharedArrayBuffer, or a view on one", - opts, - ); - } - if (isArrayBufferDetached(V)) { - throw makeException(TypeError, "is a detached ArrayBuffer", opts); - } - - return V; - }; - - converters.DOMTimeStamp = converters["unsigned long long"]; - - converters.Function = convertCallbackFunction; - - converters.VoidFunction = convertCallbackFunction; - - converters["UVString?"] = createNullableConverter( - converters.USVString, - ); - converters["sequence"] = createSequenceConverter( - converters["double"], - ); - - function requiredArguments(length, required, opts = {}) { - if (length < required) { - const errMsg = `${ - opts.prefix ? opts.prefix + ": " : "" - }${required} argument${ - required === 1 ? "" : "s" - } required, but only ${length} present.`; - throw new TypeError(errMsg); - } - } - - function createDictionaryConverter(name, ...dictionaries) { - return function (V, opts = {}) { - const typeV = type(V); - switch (typeV) { - case "Undefined": - case "Null": - case "Object": - break; - default: - throw makeException( - TypeError, - "can not be converted to a dictionary", - opts, - ); - } - const esDict = V; - - const idlDict = {}; - - for (const members of dictionaries) { - for (const member of members) { - const key = member.key; - - let esMemberValue; - if (typeV === "Undefined" || typeV === "Null") { - esMemberValue = undefined; - } else { - esMemberValue = esDict[key]; - } - - const context = `'${key}' of '${name}'${ - opts.context ? ` (${opts.context})` : "" - }`; - - if (esMemberValue !== undefined) { - const converter = member.converter; - const idlMemberValue = converter(esMemberValue, { - ...opts, - context, - }); - idlDict[key] = idlMemberValue; - } else if ("defaultValue" in member) { - const defaultValue = member.defaultValue; - const idlMemberValue = defaultValue; - idlDict[key] = idlMemberValue; - } else if (member.required) { - throw makeException( - TypeError, - `can not be converted to '${name}' because '${key}' is required in '${name}'.`, - { ...opts }, - ); - } - } - } - - return idlDict; - }; - } - - // https://heycam.github.io/webidl/#es-enumeration - function createEnumConverter(name, values) { - const E = new Set(values); - - return function (V, opts = {}) { - const S = String(V); - - if (!E.has(S)) { - throw new TypeError( - `${ - opts.prefix ? opts.prefix + ": " : "" - }The provided value '${S}' is not a valid enum value of type ${name}.`, - ); - } - - return S; - }; - } - - function createNullableConverter(converter) { - return (V, opts = {}) => { - // FIXME: If Type(V) is not Object, and the conversion to an IDL value is - // being performed due to V being assigned to an attribute whose type is a - // nullable callback function that is annotated with - // [LegacyTreatNonObjectAsNull], then return the IDL nullable type T? - // value null. - - if (V === null || V === undefined) return null; - return converter(V, opts); - }; - } - - // https://heycam.github.io/webidl/#es-sequence - function createSequenceConverter(converter) { - return function (V, opts = {}) { - if (typeof V !== "object") { - throw makeException( - TypeError, - "can not be converted to sequence.", - opts, - ); - } - const iter = V?.[Symbol.iterator]?.(); - if (iter === undefined) { - throw makeException( - TypeError, - "can not be converted to sequence.", - opts, - ); - } - const array = []; - while (true) { - const res = iter?.next?.(); - if (res === undefined) { - throw makeException( - TypeError, - "can not be converted to sequence.", - opts, - ); - } - if (res.done === true) break; - const val = converter(res.value, { - ...opts, - context: `${opts.context}, index ${array.length}`, - }); - array.push(val); - } - return array; - }; - } - - function createRecordConverter(keyConverter, valueConverter) { - return (V, opts) => { - if (typeof V !== "object") { - throw makeException( - TypeError, - "can not be converted to dictionary.", - opts, - ); - } - const result = {}; - for (const key of V) { - const typedKey = keyConverter(key, opts); - const value = V[key]; - const typedValue = valueConverter(value, opts); - result[typedKey] = typedValue; - } - return result; - }; - } - - const brand = Symbol("[[webidl.brand]]"); - - function createInterfaceConverter(name, prototype) { - return (V, opts) => { - if (!(V instanceof prototype) || V[brand] !== brand) { - throw makeException(TypeError, `is not of type ${name}.`, opts); - } - return V; - }; - } - - function createBranded(Type) { - const t = Object.create(Type.prototype); - t[brand] = brand; - return t; - } - - function assertBranded(self, prototype) { - if (!(self instanceof prototype) || self[brand] !== brand) { - throw new TypeError("Illegal invocation"); - } - } - - function illegalConstructor() { - throw new TypeError("Illegal constructor"); - } - - window.__bootstrap ??= {}; - window.__bootstrap.webidl = { - makeException, - converters, - requiredArguments, - createDictionaryConverter, - createEnumConverter, - createNullableConverter, - createSequenceConverter, - createRecordConverter, - createInterfaceConverter, - brand, - createBranded, - assertBranded, - illegalConstructor, - }; -})(this); diff --git a/op_crates/web/01_dom_exception.js b/op_crates/web/01_dom_exception.js index 14f4ca8e9..f5bd3289b 100644 --- a/op_crates/web/01_dom_exception.js +++ b/op_crates/web/01_dom_exception.js @@ -2,6 +2,7 @@ // @ts-check /// +/// /// /// diff --git a/op_crates/web/11_url.js b/op_crates/web/11_url.js deleted file mode 100644 index d8f5bd5f7..000000000 --- a/op_crates/web/11_url.js +++ /dev/null @@ -1,406 +0,0 @@ -// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. -"use strict"; - -((window) => { - const core = window.Deno.core; - - function requiredArguments(name, length, required) { - if (length < required) { - const errMsg = `${name} requires at least ${required} argument${ - required === 1 ? "" : "s" - }, but only ${length} present`; - throw new TypeError(errMsg); - } - } - - const paramLists = new WeakMap(); - const urls = new WeakMap(); - - class URLSearchParams { - #params = []; - - constructor(init = "") { - if (typeof init === "string") { - // Overload: USVString - // If init is a string and starts with U+003F (?), - // remove the first code point from init. - if (init[0] == "?") { - init = init.slice(1); - } - - this.#params = core.jsonOpSync("op_parse_url_search_params", init); - } else if ( - Array.isArray(init) || - typeof init?.[Symbol.iterator] == "function" - ) { - // Overload: sequence> - for (const pair of init) { - // If pair does not contain exactly two items, then throw a TypeError. - if (pair.length !== 2) { - throw new TypeError( - "URLSearchParams.constructor sequence argument must only contain pair elements", - ); - } - this.#params.push([String(pair[0]), String(pair[1])]); - } - } else if (Object(init) !== init) { - // pass - } else if (init instanceof URLSearchParams) { - this.#params = [...init.#params]; - } else { - // Overload: record - for (const key of Object.keys(init)) { - this.#params.push([key, String(init[key])]); - } - } - - paramLists.set(this, this.#params); - urls.set(this, null); - } - - #updateUrlSearch = () => { - const url = urls.get(this); - if (url == null) { - return; - } - const parseArgs = { href: url.href, setSearch: this.toString() }; - parts.set(url, core.jsonOpSync("op_parse_url", parseArgs)); - }; - - append(name, value) { - requiredArguments("URLSearchParams.append", arguments.length, 2); - this.#params.push([String(name), String(value)]); - this.#updateUrlSearch(); - } - - delete(name) { - requiredArguments("URLSearchParams.delete", arguments.length, 1); - name = String(name); - let i = 0; - while (i < this.#params.length) { - if (this.#params[i][0] === name) { - this.#params.splice(i, 1); - } else { - i++; - } - } - this.#updateUrlSearch(); - } - - getAll(name) { - requiredArguments("URLSearchParams.getAll", arguments.length, 1); - name = String(name); - const values = []; - for (const entry of this.#params) { - if (entry[0] === name) { - values.push(entry[1]); - } - } - - return values; - } - - get(name) { - requiredArguments("URLSearchParams.get", arguments.length, 1); - name = String(name); - for (const entry of this.#params) { - if (entry[0] === name) { - return entry[1]; - } - } - - return null; - } - - has(name) { - requiredArguments("URLSearchParams.has", arguments.length, 1); - name = String(name); - return this.#params.some((entry) => entry[0] === name); - } - - set(name, value) { - requiredArguments("URLSearchParams.set", arguments.length, 2); - - // If there are any name-value pairs whose name is name, in list, - // set the value of the first such name-value pair to value - // and remove the others. - name = String(name); - value = String(value); - let found = false; - let i = 0; - while (i < this.#params.length) { - if (this.#params[i][0] === name) { - if (!found) { - this.#params[i][1] = value; - found = true; - i++; - } else { - this.#params.splice(i, 1); - } - } else { - i++; - } - } - - // Otherwise, append a new name-value pair whose name is name - // and value is value, to list. - if (!found) { - this.#params.push([String(name), String(value)]); - } - - this.#updateUrlSearch(); - } - - sort() { - this.#params.sort((a, b) => (a[0] === b[0] ? 0 : a[0] > b[0] ? 1 : -1)); - this.#updateUrlSearch(); - } - - forEach(callbackfn, thisArg) { - requiredArguments("URLSearchParams.forEach", arguments.length, 1); - - if (typeof thisArg !== "undefined") { - callbackfn = callbackfn.bind(thisArg); - } - - for (const [key, value] of this.#params) { - callbackfn(value, key, this); - } - } - - *keys() { - for (const [key] of this.#params) { - yield key; - } - } - - *values() { - for (const [, value] of this.#params) { - yield value; - } - } - - *entries() { - yield* this.#params; - } - - *[Symbol.iterator]() { - yield* this.#params; - } - - toString() { - return core.jsonOpSync("op_stringify_url_search_params", this.#params); - } - } - - const parts = new WeakMap(); - - class URL { - #searchParams = null; - - constructor(url, base) { - new.target; - - if (url instanceof URL && base === undefined) { - parts.set(this, parts.get(url)); - } else { - base = base !== undefined ? String(base) : base; - const parseArgs = { href: String(url), baseHref: base }; - parts.set(this, core.jsonOpSync("op_parse_url", parseArgs)); - } - } - - [Symbol.for("Deno.customInspect")](inspect) { - const object = { - href: this.href, - origin: this.origin, - protocol: this.protocol, - username: this.username, - password: this.password, - host: this.host, - hostname: this.hostname, - port: this.port, - pathname: this.pathname, - hash: this.hash, - search: this.search, - }; - return `${this.constructor.name} ${inspect(object)}`; - } - - #updateSearchParams = () => { - if (this.#searchParams != null) { - const params = paramLists.get(this.#searchParams); - const newParams = core.jsonOpSync( - "op_parse_url_search_params", - this.search.slice(1), - ); - params.splice(0, params.length, ...newParams); - } - }; - - get hash() { - return parts.get(this).hash; - } - - set hash(value) { - try { - const parseArgs = { href: this.href, setHash: String(value) }; - parts.set(this, core.jsonOpSync("op_parse_url", parseArgs)); - } catch { - /* pass */ - } - } - - get host() { - return parts.get(this).host; - } - - set host(value) { - try { - const parseArgs = { href: this.href, setHost: String(value) }; - parts.set(this, core.jsonOpSync("op_parse_url", parseArgs)); - } catch { - /* pass */ - } - } - - get hostname() { - return parts.get(this).hostname; - } - - set hostname(value) { - try { - const parseArgs = { href: this.href, setHostname: String(value) }; - parts.set(this, core.jsonOpSync("op_parse_url", parseArgs)); - } catch { - /* pass */ - } - } - - get href() { - return parts.get(this).href; - } - - set href(value) { - try { - const parseArgs = { href: String(value) }; - parts.set(this, core.jsonOpSync("op_parse_url", parseArgs)); - } catch { - throw new TypeError("Invalid URL"); - } - this.#updateSearchParams(); - } - - get origin() { - return parts.get(this).origin; - } - - get password() { - return parts.get(this).password; - } - - set password(value) { - try { - const parseArgs = { href: this.href, setPassword: String(value) }; - parts.set(this, core.jsonOpSync("op_parse_url", parseArgs)); - } catch { - /* pass */ - } - } - - get pathname() { - return parts.get(this).pathname; - } - - set pathname(value) { - try { - const parseArgs = { href: this.href, setPathname: String(value) }; - parts.set(this, core.jsonOpSync("op_parse_url", parseArgs)); - } catch { - /* pass */ - } - } - - get port() { - return parts.get(this).port; - } - - set port(value) { - try { - const parseArgs = { href: this.href, setPort: String(value) }; - parts.set(this, core.jsonOpSync("op_parse_url", parseArgs)); - } catch { - /* pass */ - } - } - - get protocol() { - return parts.get(this).protocol; - } - - set protocol(value) { - try { - const parseArgs = { href: this.href, setProtocol: String(value) }; - parts.set(this, core.jsonOpSync("op_parse_url", parseArgs)); - } catch { - /* pass */ - } - } - - get search() { - return parts.get(this).search; - } - - set search(value) { - try { - const parseArgs = { href: this.href, setSearch: String(value) }; - parts.set(this, core.jsonOpSync("op_parse_url", parseArgs)); - this.#updateSearchParams(); - } catch { - /* pass */ - } - } - - get username() { - return parts.get(this).username; - } - - set username(value) { - try { - const parseArgs = { href: this.href, setUsername: String(value) }; - parts.set(this, core.jsonOpSync("op_parse_url", parseArgs)); - } catch { - /* pass */ - } - } - - get searchParams() { - if (this.#searchParams == null) { - this.#searchParams = new URLSearchParams(this.search); - urls.set(this.#searchParams, this); - } - return this.#searchParams; - } - - toString() { - return this.href; - } - - toJSON() { - return this.href; - } - - static createObjectURL() { - throw new Error("Not implemented"); - } - - static revokeObjectURL() { - throw new Error("Not implemented"); - } - } - - window.__bootstrap.url = { - URL, - URLSearchParams, - }; -})(this); diff --git a/op_crates/web/Cargo.toml b/op_crates/web/Cargo.toml index d1d37c216..e86207881 100644 --- a/op_crates/web/Cargo.toml +++ b/op_crates/web/Cargo.toml @@ -15,7 +15,6 @@ path = "lib.rs" [dependencies] deno_core = { version = "0.80.2", path = "../../core" } -serde = { version = "1.0.123", features = ["derive"] } [dev-dependencies] futures = "0.3.12" diff --git a/op_crates/web/abort_controller_test.js b/op_crates/web/abort_controller_test.js deleted file mode 100644 index 26f58dccb..000000000 --- a/op_crates/web/abort_controller_test.js +++ /dev/null @@ -1,140 +0,0 @@ -// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. -"use strict"; -function assert(cond) { - if (!cond) { - throw Error("assert"); - } -} - -function assertEquals(left, right) { - assert(left === right); -} - -function assertThrows(fn) { - let error = null; - try { - fn(); - } catch (error_) { - error = error_; - } - if (error == null) { - throw new Error("Didn't throw."); - } - return error; -} - -function basicAbortController() { - const controller = new AbortController(); - assert(controller); - const { signal } = controller; - assert(signal); - assertEquals(signal.aborted, false); - controller.abort(); - assertEquals(signal.aborted, true); -} - -function signalCallsOnabort() { - const controller = new AbortController(); - const { signal } = controller; - let called = false; - signal.onabort = (evt) => { - assert(evt); - assertEquals(evt.type, "abort"); - called = true; - }; - controller.abort(); - assert(called); -} - -function signalEventListener() { - const controller = new AbortController(); - const { signal } = controller; - let called = false; - signal.addEventListener("abort", function (ev) { - assert(this === signal); - assertEquals(ev.type, "abort"); - called = true; - }); - controller.abort(); - assert(called); -} - -function onlyAbortsOnce() { - const controller = new AbortController(); - const { signal } = controller; - let called = 0; - signal.addEventListener("abort", () => called++); - signal.onabort = () => { - called++; - }; - controller.abort(); - assertEquals(called, 2); - controller.abort(); - assertEquals(called, 2); -} - -function controllerHasProperToString() { - const actual = Object.prototype.toString.call(new AbortController()); - assertEquals(actual, "[object AbortController]"); -} - -function abortSignalIllegalConstructor() { - const error = assertThrows(() => new AbortSignal()); - assert(error instanceof TypeError); - assertEquals(error.message, "Illegal constructor."); -} - -function abortSignalEventOrder() { - const arr = []; - const controller = new AbortController(); - const { signal } = controller; - signal.addEventListener("abort", () => arr.push(1)); - signal.onabort = () => arr.push(2); - signal.addEventListener("abort", () => arr.push(3)); - controller.abort(); - assertEquals(arr[0], 1); - assertEquals(arr[1], 2); - assertEquals(arr[2], 3); -} - -function abortSignalEventOrderComplex() { - const arr = []; - const controller = new AbortController(); - const { signal } = controller; - signal.addEventListener("abort", () => arr.push(1)); - signal.onabort = () => { - throw new Error(); - }; - signal.addEventListener("abort", () => arr.push(3)); - signal.onabort = () => arr.push(2); - controller.abort(); - assertEquals(arr[0], 1); - assertEquals(arr[1], 2); - assertEquals(arr[2], 3); -} - -function abortSignalHandlerLocation() { - const controller = new AbortController(); - const { signal } = controller; - const abortHandler = Object.getOwnPropertyDescriptor(signal, "onabort"); - assertEquals(abortHandler, undefined); -} -function abortSignalLength() { - const controller = new AbortController(); - const { signal } = controller; - assertEquals(signal.constructor.length, 0); -} -function main() { - basicAbortController(); - signalCallsOnabort(); - signalEventListener(); - onlyAbortsOnce(); - controllerHasProperToString(); - abortSignalIllegalConstructor(); - abortSignalEventOrder(); - abortSignalEventOrderComplex(); - abortSignalHandlerLocation(); - abortSignalLength(); -} - -main(); diff --git a/op_crates/web/event_target_test.js b/op_crates/web/event_target_test.js deleted file mode 100644 index acb75cc19..000000000 --- a/op_crates/web/event_target_test.js +++ /dev/null @@ -1,245 +0,0 @@ -// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. -"use strict"; -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 deleted file mode 100644 index fa92a3e07..000000000 --- a/op_crates/web/event_test.js +++ /dev/null @@ -1,142 +0,0 @@ -// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. -"use strict"; -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() { - 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 eventIsTrustedGetterName() { - const { get } = Object.getOwnPropertyDescriptor(new Event("x"), "isTrusted"); - assert(get.name === "get isTrusted"); - try { - Reflect.construct(get); - throw new Error("Should not have reached here"); - } catch (e) { - assert(e.message.includes("not a constructor")); - } -} -function eventAbortSignal() { - let count = 0; - function handler() { - count++; - } - const et = new EventTarget(); - const controller = new AbortController(); - et.addEventListener("test", handler, { signal: controller.signal }); - et.dispatchEvent(new Event("test")); - assert(count === 1); - et.dispatchEvent(new Event("test")); - assert(count === 2); - controller.abort(); - et.dispatchEvent(new Event("test")); - assert(count === 2); - et.addEventListener("test", handler, { signal: controller.signal }); - et.dispatchEvent(new Event("test")); - assert(count === 2); -} -function main() { - eventInitializedWithType(); - eventInitializedWithTypeAndDict(); - eventComposedPathSuccess(); - eventStopPropagationSuccess(); - eventStopImmediatePropagationSuccess(); - eventPreventDefaultSuccess(); - eventInitializedWithNonStringType(); - eventIsTrusted(); - eventIsTrustedGetterName(); - eventAbortSignal(); -} - -main(); diff --git a/op_crates/web/internal.d.ts b/op_crates/web/internal.d.ts index efafee26c..458f4a173 100644 --- a/op_crates/web/internal.d.ts +++ b/op_crates/web/internal.d.ts @@ -1,301 +1,14 @@ // Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. -// deno-lint-ignore-file no-explicit-any ban-types /// /// declare namespace globalThis { declare namespace __bootstrap { - declare namespace webidl { - declare interface ConverterOpts { - /** - * The prefix for error messages created by this converter. - * Examples: - * - `Failed to construct 'Event'` - * - `Failed to execute 'removeEventListener' on 'EventTarget'` - */ - prefix: string; - } - declare interface ValueConverterOpts extends ConverterOpts { - /** - * The context of this value error messages created by this converter. - * Examples: - * - `Argument 1` - * - `Argument 3` - */ - context: string; - } - declare function makeException( - ErrorType: any, - message: string, - opts: ValueConverterOpts, - ): any; - declare interface IntConverterOpts extends ValueConverterOpts { - /** - * Wether to throw if the number is outside of the acceptable values for - * this type. - */ - enforceRange?: boolean; - /** - * Wether to clamp this number to the acceptable values for this type. - */ - clamp?: boolean; - } - declare interface StringConverterOpts extends ValueConverterOpts { - /** - * Wether to treat `null` value as an empty string. - */ - treatNullAsEmptyString?: boolean; - } - declare interface BufferConverterOpts extends ValueConverterOpts { - /** - * Wether to allow `SharedArrayBuffer` (not just `ArrayBuffer`). - */ - allowShared?: boolean; - } - declare const converters: { - any(v: any): any; - /** - * Convert a value into a `boolean` (bool). - */ - boolean(v: any, opts?: IntConverterOpts): boolean; - /** - * Convert a value into a `byte` (int8). - */ - byte(v: any, opts?: IntConverterOpts): number; - /** - * Convert a value into a `octet` (uint8). - */ - octet(v: any, opts?: IntConverterOpts): number; - /** - * Convert a value into a `short` (int16). - */ - short(v: any, opts?: IntConverterOpts): number; - /** - * Convert a value into a `unsigned short` (uint16). - */ - ["unsigned short"](v: any, opts?: IntConverterOpts): number; - /** - * Convert a value into a `long` (int32). - */ - long(v: any, opts?: IntConverterOpts): number; - /** - * Convert a value into a `unsigned long` (uint32). - */ - ["unsigned long"](v: any, opts?: IntConverterOpts): number; - /** - * Convert a value into a `long long` (int64). - * **Note this is truncated to a JS number (53 bit precision).** - */ - ["long long"](v: any, opts?: IntConverterOpts): number; - /** - * Convert a value into a `unsigned long long` (uint64). - * **Note this is truncated to a JS number (53 bit precision).** - */ - ["unsigned long long"](v: any, opts?: IntConverterOpts): number; - /** - * Convert a value into a `float` (f32). - */ - float(v: any, opts?: ValueConverterOpts): number; - /** - * Convert a value into a `unrestricted float` (f32, infinity, or NaN). - */ - ["unrestricted float"](v: any, opts?: ValueConverterOpts): number; - /** - * Convert a value into a `double` (f64). - */ - double(v: any, opts?: ValueConverterOpts): number; - /** - * Convert a value into a `unrestricted double` (f64, infinity, or NaN). - */ - ["unrestricted double"](v: any, opts?: ValueConverterOpts): number; - /** - * Convert a value into a `DOMString` (string). - */ - DOMString(v: any, opts?: StringConverterOpts): string; - /** - * Convert a value into a `ByteString` (string with only u8 codepoints). - */ - ByteString(v: any, opts?: StringConverterOpts): string; - /** - * Convert a value into a `USVString` (string with only valid non - * surrogate Unicode code points). - */ - USVString(v: any, opts?: StringConverterOpts): string; - /** - * Convert a value into an `object` (object). - */ - object(v: any, opts?: ValueConverterOpts): object; - /** - * Convert a value into an `ArrayBuffer` (ArrayBuffer). - */ - ArrayBuffer(v: any, opts?: BufferConverterOpts): ArrayBuffer; - /** - * Convert a value into a `DataView` (ArrayBuffer). - */ - DataView(v: any, opts?: BufferConverterOpts): DataView; - /** - * Convert a value into a `Int8Array` (Int8Array). - */ - Int8Array(v: any, opts?: BufferConverterOpts): Int8Array; - /** - * Convert a value into a `Int16Array` (Int16Array). - */ - Int16Array(v: any, opts?: BufferConverterOpts): Int16Array; - /** - * Convert a value into a `Int32Array` (Int32Array). - */ - Int32Array(v: any, opts?: BufferConverterOpts): Int32Array; - /** - * Convert a value into a `Uint8Array` (Uint8Array). - */ - Uint8Array(v: any, opts?: BufferConverterOpts): Uint8Array; - /** - * Convert a value into a `Uint16Array` (Uint16Array). - */ - Uint16Array(v: any, opts?: BufferConverterOpts): Uint16Array; - /** - * Convert a value into a `Uint32Array` (Uint32Array). - */ - Uint32Array(v: any, opts?: BufferConverterOpts): Uint32Array; - /** - * Convert a value into a `Uint8ClampedArray` (Uint8ClampedArray). - */ - Uint8ClampedArray( - v: any, - opts?: BufferConverterOpts, - ): Uint8ClampedArray; - /** - * Convert a value into a `Float32Array` (Float32Array). - */ - Float32Array(v: any, opts?: BufferConverterOpts): Float32Array; - /** - * Convert a value into a `Float64Array` (Float64Array). - */ - Float64Array(v: any, opts?: BufferConverterOpts): Float64Array; - /** - * Convert a value into an `ArrayBufferView` (ArrayBufferView). - */ - ArrayBufferView(v: any, opts?: BufferConverterOpts): ArrayBufferView; - /** - * Convert a value into a `BufferSource` (ArrayBuffer or ArrayBufferView). - */ - BufferSource( - v: any, - opts?: BufferConverterOpts, - ): ArrayBuffer | ArrayBufferView; - /** - * Convert a value into a `DOMTimeStamp` (u64). Alias for unsigned long long - */ - DOMTimeStamp(v: any, opts?: IntConverterOpts): number; - /** - * Convert a value into a `Function` ((...args: any[]) => any). - */ - Function(v: any, opts?: ValueConverterOpts): (...args: any) => any; - /** - * Convert a value into a `VoidFunction` (() => void). - */ - VoidFunction(v: any, opts?: ValueConverterOpts): () => void; - ["UVString?"](v: any, opts?: ValueConverterOpts): string | null; - ["sequence"](v: any, opts?: ValueConverterOpts): number[]; - - [type: string]: (v: any, opts: ValueConverterOpts) => any; - }; - - /** - * Assert that the a function has at least a required amount of arguments. - */ - declare function requiredArguments( - length: number, - required: number, - opts: ConverterOpts, - ): void; - declare type Dictionary = DictionaryMember[]; - declare interface DictionaryMember { - key: string; - converter: (v: any, opts: ValueConverterOpts) => any; - defaultValue?: any; - required?: boolean; - } - - /** - * Create a converter for dictionaries. - */ - declare function createDictionaryConverter( - name: string, - ...dictionaries: Dictionary[] - ): (v: any, opts: ValueConverterOpts) => T; - - /** - * Create a converter for enums. - */ - declare function createEnumConverter( - name: string, - values: string[], - ): (v: any, opts: ValueConverterOpts) => string; - - /** - * Create a converter that makes the contained type nullable. - */ - declare function createNullableConverter( - converter: (v: any, opts: ValueConverterOpts) => T, - ): (v: any, opts: ValueConverterOpts) => T | null; - - /** - * Create a converter that converts a sequence of the inner type. - */ - declare function createSequenceConverter( - converter: (v: any, opts: ValueConverterOpts) => T, - ): (v: any, opts: ValueConverterOpts) => T[]; - - /** - * Throw an illegal constructor error. - */ - declare function illegalConstructor(): never; - - /** - * The branding symbol. - */ - declare const brand: unique symbol; - - /** - * Create a branded instance of an interface. - */ - declare function createBranded(self: any): any; - - /** - * Assert that self is branded. - */ - declare function assertBranded(self: any, type: any): void; - - /** - * Create a converter for interfaces. - */ - declare function createInterfaceConverter( - name: string, - prototype: any, - ): (v: any, opts: ValueConverterOpts) => any; - - declare function createRecordConverter< - K extends string | number | symbol, - V, - >( - keyConverter: (v: any, opts: ValueConverterOpts) => K, - valueConverter: (v: any, opts: ValueConverterOpts) => V, - ): ( - v: Record, - opts: ValueConverterOpts, - ) => any; - } - declare var eventTarget: { EventTarget: typeof EventTarget; }; - declare var url: { - URLSearchParams: typeof URLSearchParams; - }; - declare var location: { getLocationHref(): string | undefined; }; diff --git a/op_crates/web/lib.deno_web.d.ts b/op_crates/web/lib.deno_web.d.ts index 24a8f929d..79b56f68e 100644 --- a/op_crates/web/lib.deno_web.d.ts +++ b/op_crates/web/lib.deno_web.d.ts @@ -313,172 +313,3 @@ declare var FileReader: { readonly EMPTY: number; readonly LOADING: number; }; - -declare class URLSearchParams { - constructor( - init?: string[][] | Record | string | URLSearchParams, - ); - static toString(): string; - - /** Appends a specified key/value pair as a new search parameter. - * - * ```ts - * let searchParams = new URLSearchParams(); - * searchParams.append('name', 'first'); - * searchParams.append('name', 'second'); - * ``` - */ - append(name: string, value: string): void; - - /** Deletes the given search parameter and its associated value, - * from the list of all search parameters. - * - * ```ts - * let searchParams = new URLSearchParams([['name', 'value']]); - * searchParams.delete('name'); - * ``` - */ - delete(name: string): void; - - /** Returns all the values associated with a given search parameter - * as an array. - * - * ```ts - * searchParams.getAll('name'); - * ``` - */ - getAll(name: string): string[]; - - /** Returns the first value associated to the given search parameter. - * - * ```ts - * searchParams.get('name'); - * ``` - */ - get(name: string): string | null; - - /** Returns a Boolean that indicates whether a parameter with the - * specified name exists. - * - * ```ts - * searchParams.has('name'); - * ``` - */ - has(name: string): boolean; - - /** Sets the value associated with a given search parameter to the - * given value. If there were several matching values, this method - * deletes the others. If the search parameter doesn't exist, this - * method creates it. - * - * ```ts - * searchParams.set('name', 'value'); - * ``` - */ - set(name: string, value: string): void; - - /** Sort all key/value pairs contained in this object in place and - * return undefined. The sort order is according to Unicode code - * points of the keys. - * - * ```ts - * searchParams.sort(); - * ``` - */ - sort(): void; - - /** Calls a function for each element contained in this object in - * place and return undefined. Optionally accepts an object to use - * as this when executing callback as second argument. - * - * ```ts - * const params = new URLSearchParams([["a", "b"], ["c", "d"]]); - * params.forEach((value, key, parent) => { - * console.log(value, key, parent); - * }); - * ``` - * - */ - forEach( - callbackfn: (value: string, key: string, parent: this) => void, - thisArg?: any, - ): void; - - /** Returns an iterator allowing to go through all keys contained - * in this object. - * - * ```ts - * const params = new URLSearchParams([["a", "b"], ["c", "d"]]); - * for (const key of params.keys()) { - * console.log(key); - * } - * ``` - */ - keys(): IterableIterator; - - /** Returns an iterator allowing to go through all values contained - * in this object. - * - * ```ts - * const params = new URLSearchParams([["a", "b"], ["c", "d"]]); - * for (const value of params.values()) { - * console.log(value); - * } - * ``` - */ - values(): IterableIterator; - - /** Returns an iterator allowing to go through all key/value - * pairs contained in this object. - * - * ```ts - * const params = new URLSearchParams([["a", "b"], ["c", "d"]]); - * for (const [key, value] of params.entries()) { - * console.log(key, value); - * } - * ``` - */ - entries(): IterableIterator<[string, string]>; - - /** Returns an iterator allowing to go through all key/value - * pairs contained in this object. - * - * ```ts - * const params = new URLSearchParams([["a", "b"], ["c", "d"]]); - * for (const [key, value] of params) { - * console.log(key, value); - * } - * ``` - */ - [Symbol.iterator](): IterableIterator<[string, string]>; - - /** Returns a query string suitable for use in a URL. - * - * ```ts - * searchParams.toString(); - * ``` - */ - toString(): string; -} - -/** The URL interface represents an object providing static methods used for creating object URLs. */ -declare class URL { - constructor(url: string, base?: string | URL); - createObjectURL(object: any): string; - revokeObjectURL(url: string): void; - - hash: string; - host: string; - hostname: string; - href: string; - toString(): string; - readonly origin: string; - password: string; - pathname: string; - port: string; - protocol: string; - search: string; - readonly searchParams: URLSearchParams; - username: string; - toJSON(): string; -} diff --git a/op_crates/web/lib.rs b/op_crates/web/lib.rs index f67fd81a1..af7a7cebc 100644 --- a/op_crates/web/lib.rs +++ b/op_crates/web/lib.rs @@ -1,29 +1,11 @@ // Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. -use deno_core::error::generic_error; -use deno_core::error::type_error; -use deno_core::error::uri_error; -use deno_core::error::AnyError; -use deno_core::serde_json; -use deno_core::serde_json::json; -use deno_core::serde_json::Value; -use deno_core::url::form_urlencoded; -use deno_core::url::quirks; -use deno_core::url::Url; use deno_core::JsRuntime; -use deno_core::ZeroCopyBuf; -use serde::Deserialize; -use serde::Serialize; -use std::panic::catch_unwind; use std::path::PathBuf; /// Load and execute the javascript code. pub fn init(isolate: &mut JsRuntime) { let files = vec![ - ( - "deno:op_crates/web/00_webidl.js", - include_str!("00_webidl.js"), - ), ( "deno:op_crates/web/01_dom_exception.js", include_str!("01_dom_exception.js"), @@ -44,7 +26,6 @@ pub fn init(isolate: &mut JsRuntime) { "deno:op_crates/web/08_text_encoding.js", include_str!("08_text_encoding.js"), ), - ("deno:op_crates/web/11_url.js", include_str!("11_url.js")), ( "deno:op_crates/web/12_location.js", include_str!("12_location.js"), @@ -59,213 +40,6 @@ pub fn init(isolate: &mut JsRuntime) { } } -/// Parse `UrlParseArgs::href` with an optional `UrlParseArgs::base_href`, or an -/// optional part to "set" after parsing. Return `UrlParts`. -pub fn op_parse_url( - _state: &mut deno_core::OpState, - args: Value, - _zero_copy: &mut [ZeroCopyBuf], -) -> Result { - #[derive(Deserialize)] - #[serde(rename_all = "camelCase")] - struct UrlParseArgs { - href: String, - base_href: Option, - // If one of the following are present, this is a setter call. Apply the - // proper `Url::set_*()` method after (re)parsing `href`. - set_hash: Option, - set_host: Option, - set_hostname: Option, - set_password: Option, - set_pathname: Option, - set_port: Option, - set_protocol: Option, - set_search: Option, - set_username: Option, - } - let args: UrlParseArgs = serde_json::from_value(args)?; - let base_url = args - .base_href - .as_ref() - .map(|b| Url::parse(b).map_err(|_| type_error("Invalid base URL"))) - .transpose()?; - let mut url = Url::options() - .base_url(base_url.as_ref()) - .parse(&args.href) - .map_err(|_| type_error("Invalid URL"))?; - - if let Some(hash) = args.set_hash.as_ref() { - quirks::set_hash(&mut url, hash); - } else if let Some(host) = args.set_host.as_ref() { - quirks::set_host(&mut url, host).map_err(|_| uri_error("Invalid host"))?; - } else if let Some(hostname) = args.set_hostname.as_ref() { - quirks::set_hostname(&mut url, hostname) - .map_err(|_| uri_error("Invalid hostname"))?; - } else if let Some(password) = args.set_password.as_ref() { - quirks::set_password(&mut url, password) - .map_err(|_| uri_error("Invalid password"))?; - } else if let Some(pathname) = args.set_pathname.as_ref() { - quirks::set_pathname(&mut url, pathname); - } else if let Some(port) = args.set_port.as_ref() { - quirks::set_port(&mut url, port).map_err(|_| uri_error("Invalid port"))?; - } else if let Some(protocol) = args.set_protocol.as_ref() { - quirks::set_protocol(&mut url, protocol) - .map_err(|_| uri_error("Invalid protocol"))?; - } else if let Some(search) = args.set_search.as_ref() { - quirks::set_search(&mut url, search); - } else if let Some(username) = args.set_username.as_ref() { - quirks::set_username(&mut url, username) - .map_err(|_| uri_error("Invalid username"))?; - } - - #[derive(Serialize)] - struct UrlParts<'a> { - href: &'a str, - hash: &'a str, - host: &'a str, - hostname: &'a str, - origin: &'a str, - password: &'a str, - pathname: &'a str, - port: &'a str, - protocol: &'a str, - search: &'a str, - username: &'a str, - } - // TODO(nayeemrmn): Panic that occurs in rust-url for the `non-spec:` - // url-constructor wpt tests: https://github.com/servo/rust-url/issues/670. - let username = catch_unwind(|| quirks::username(&url)).map_err(|_| { - generic_error(format!( - "Internal error while parsing \"{}\"{}, \ - see https://github.com/servo/rust-url/issues/670", - args.href, - args - .base_href - .map(|b| format!(" against \"{}\"", b)) - .unwrap_or_default() - )) - })?; - Ok(json!(UrlParts { - href: quirks::href(&url), - hash: quirks::hash(&url), - host: quirks::host(&url), - hostname: quirks::hostname(&url), - origin: &quirks::origin(&url), - password: quirks::password(&url), - pathname: quirks::pathname(&url), - port: quirks::port(&url), - protocol: quirks::protocol(&url), - search: quirks::search(&url), - username, - })) -} - -pub fn op_parse_url_search_params( - _state: &mut deno_core::OpState, - args: Value, - _zero_copy: &mut [ZeroCopyBuf], -) -> Result { - let search: String = serde_json::from_value(args)?; - let search_params: Vec<_> = form_urlencoded::parse(search.as_bytes()) - .into_iter() - .collect(); - Ok(json!(search_params)) -} - -pub fn op_stringify_url_search_params( - _state: &mut deno_core::OpState, - args: Value, - _zero_copy: &mut [ZeroCopyBuf], -) -> Result { - let search_params: Vec<(String, String)> = serde_json::from_value(args)?; - let search = form_urlencoded::Serializer::new(String::new()) - .extend_pairs(search_params) - .finish(); - Ok(json!(search)) -} - pub fn get_declaration() -> PathBuf { PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("lib.deno_web.d.ts") } - -#[cfg(test)] -mod tests { - use deno_core::JsRuntime; - use futures::future::lazy; - use futures::task::Context; - use futures::task::Poll; - - fn run_in_task(f: F) - where - F: FnOnce(&mut Context) + Send + 'static, - { - futures::executor::block_on(lazy(move |cx| f(cx))); - } - - fn setup() -> JsRuntime { - let mut isolate = JsRuntime::new(Default::default()); - crate::init(&mut isolate); - isolate - } - - #[test] - fn test_abort_controller() { - run_in_task(|mut cx| { - let mut isolate = setup(); - isolate - .execute( - "abort_controller_test.js", - include_str!("abort_controller_test.js"), - ) - .unwrap(); - if let Poll::Ready(Err(_)) = isolate.poll_event_loop(&mut cx) { - unreachable!(); - } - }); - } - - #[test] - fn test_event() { - run_in_task(|mut cx| { - let mut isolate = setup(); - isolate - .execute("event_test.js", include_str!("event_test.js")) - .unwrap(); - if let Poll::Ready(Err(_)) = isolate.poll_event_loop(&mut cx) { - unreachable!(); - } - }); - } - - #[test] - fn test_event_error() { - run_in_task(|mut cx| { - let mut isolate = setup(); - let result = isolate.execute("foo.js", "new Event()"); - if let Err(error) = result { - let error_string = error.to_string(); - // Test that the script specifier is a URL: `deno:`. - assert!(error_string.contains("deno:op_crates/web/02_event.js")); - assert!(error_string.contains("TypeError")); - } else { - unreachable!(); - } - if let Poll::Ready(Err(_)) = isolate.poll_event_loop(&mut cx) { - unreachable!(); - } - }); - } - - #[test] - fn test_event_target() { - run_in_task(|mut cx| { - let mut isolate = setup(); - isolate - .execute("event_target_test.js", include_str!("event_target_test.js")) - .unwrap(); - if let Poll::Ready(Err(_)) = isolate.poll_event_loop(&mut cx) { - unreachable!(); - } - }); - } -} diff --git a/op_crates/webgpu/Cargo.toml b/op_crates/webgpu/Cargo.toml index f420106d3..603d0ad1b 100644 --- a/op_crates/webgpu/Cargo.toml +++ b/op_crates/webgpu/Cargo.toml @@ -4,7 +4,7 @@ name = "deno_webgpu" version = "0.1.1" edition = "2018" -description = "provides webgpu Web API to deno_core" +description = "WebGPU implementation for Deno" authors = ["the Deno authors"] license = "MIT" readme = "README.md" diff --git a/op_crates/webidl/00_webidl.js b/op_crates/webidl/00_webidl.js new file mode 100644 index 000000000..c00c605e8 --- /dev/null +++ b/op_crates/webidl/00_webidl.js @@ -0,0 +1,810 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. + +// Adapted from https://github.com/jsdom/webidl-conversions. +// Copyright Domenic Denicola. Licensed under BSD-2-Clause License. +// Original license at https://github.com/jsdom/webidl-conversions/blob/master/LICENSE.md. + +"use strict"; + +((window) => { + function makeException(ErrorType, message, opts = {}) { + if (opts.globals) { + ErrorType = opts.globals[ErrorType.name]; + } + return new ErrorType( + `${opts.prefix ? opts.prefix + ": " : ""}${ + opts.context ? opts.context : "Value" + } ${message}`, + ); + } + + function toNumber(value, opts = {}) { + if (!opts.globals) { + return +value; + } + if (typeof value === "bigint") { + throw opts.globals.TypeError("Cannot convert a BigInt value to a number"); + } + return opts.globals.Number(value); + } + + function type(V) { + if (V === null) { + return "Null"; + } + switch (typeof V) { + case "undefined": + return "Undefined"; + case "boolean": + return "Boolean"; + case "number": + return "Number"; + case "string": + return "String"; + case "symbol": + return "Symbol"; + case "bigint": + return "BigInt"; + case "object": + // Falls through + case "function": + // Falls through + default: + // Per ES spec, typeof returns an implemention-defined value that is not any of the existing ones for + // uncallable non-standard exotic objects. Yet Type() which the Web IDL spec depends on returns Object for + // such cases. So treat the default case as an object. + return "Object"; + } + } + + // Round x to the nearest integer, choosing the even integer if it lies halfway between two. + function evenRound(x) { + // There are four cases for numbers with fractional part being .5: + // + // case | x | floor(x) | round(x) | expected | x <> 0 | x % 1 | x & 1 | example + // 1 | 2n + 0.5 | 2n | 2n + 1 | 2n | > | 0.5 | 0 | 0.5 -> 0 + // 2 | 2n + 1.5 | 2n + 1 | 2n + 2 | 2n + 2 | > | 0.5 | 1 | 1.5 -> 2 + // 3 | -2n - 0.5 | -2n - 1 | -2n | -2n | < | -0.5 | 0 | -0.5 -> 0 + // 4 | -2n - 1.5 | -2n - 2 | -2n - 1 | -2n - 2 | < | -0.5 | 1 | -1.5 -> -2 + // (where n is a non-negative integer) + // + // Branch here for cases 1 and 4 + if ( + (x > 0 && x % 1 === +0.5 && (x & 1) === 0) || + (x < 0 && x % 1 === -0.5 && (x & 1) === 1) + ) { + return censorNegativeZero(Math.floor(x)); + } + + return censorNegativeZero(Math.round(x)); + } + + function integerPart(n) { + return censorNegativeZero(Math.trunc(n)); + } + + function sign(x) { + return x < 0 ? -1 : 1; + } + + function modulo(x, y) { + // https://tc39.github.io/ecma262/#eqn-modulo + // Note that http://stackoverflow.com/a/4467559/3191 does NOT work for large modulos + const signMightNotMatch = x % y; + if (sign(y) !== sign(signMightNotMatch)) { + return signMightNotMatch + y; + } + return signMightNotMatch; + } + + function censorNegativeZero(x) { + return x === 0 ? 0 : x; + } + + function createIntegerConversion(bitLength, typeOpts) { + const isSigned = !typeOpts.unsigned; + + let lowerBound; + let upperBound; + if (bitLength === 64) { + upperBound = Number.MAX_SAFE_INTEGER; + lowerBound = !isSigned ? 0 : Number.MIN_SAFE_INTEGER; + } else if (!isSigned) { + lowerBound = 0; + upperBound = Math.pow(2, bitLength) - 1; + } else { + lowerBound = -Math.pow(2, bitLength - 1); + upperBound = Math.pow(2, bitLength - 1) - 1; + } + + const twoToTheBitLength = Math.pow(2, bitLength); + const twoToOneLessThanTheBitLength = Math.pow(2, bitLength - 1); + + return (V, opts = {}) => { + let x = toNumber(V, opts); + x = censorNegativeZero(x); + + if (opts.enforceRange) { + if (!Number.isFinite(x)) { + throw makeException(TypeError, "is not a finite number", opts); + } + + x = integerPart(x); + + if (x < lowerBound || x > upperBound) { + throw makeException( + TypeError, + `is outside the accepted range of ${lowerBound} to ${upperBound}, inclusive`, + opts, + ); + } + + return x; + } + + if (!Number.isNaN(x) && opts.clamp) { + x = Math.min(Math.max(x, lowerBound), upperBound); + x = evenRound(x); + return x; + } + + if (!Number.isFinite(x) || x === 0) { + return 0; + } + x = integerPart(x); + + // Math.pow(2, 64) is not accurately representable in JavaScript, so try to avoid these per-spec operations if + // possible. Hopefully it's an optimization for the non-64-bitLength cases too. + if (x >= lowerBound && x <= upperBound) { + return x; + } + + // These will not work great for bitLength of 64, but oh well. See the README for more details. + x = modulo(x, twoToTheBitLength); + if (isSigned && x >= twoToOneLessThanTheBitLength) { + return x - twoToTheBitLength; + } + return x; + }; + } + + function createLongLongConversion(bitLength, { unsigned }) { + const upperBound = Number.MAX_SAFE_INTEGER; + const lowerBound = unsigned ? 0 : Number.MIN_SAFE_INTEGER; + const asBigIntN = unsigned ? BigInt.asUintN : BigInt.asIntN; + + return (V, opts = {}) => { + let x = toNumber(V, opts); + x = censorNegativeZero(x); + + if (opts.enforceRange) { + if (!Number.isFinite(x)) { + throw makeException(TypeError, "is not a finite number", opts); + } + + x = integerPart(x); + + if (x < lowerBound || x > upperBound) { + throw makeException( + TypeError, + `is outside the accepted range of ${lowerBound} to ${upperBound}, inclusive`, + opts, + ); + } + + return x; + } + + if (!Number.isNaN(x) && opts.clamp) { + x = Math.min(Math.max(x, lowerBound), upperBound); + x = evenRound(x); + return x; + } + + if (!Number.isFinite(x) || x === 0) { + return 0; + } + + let xBigInt = BigInt(integerPart(x)); + xBigInt = asBigIntN(bitLength, xBigInt); + return Number(xBigInt); + }; + } + + const converters = []; + + converters.any = (V) => { + return V; + }; + + converters.boolean = function (val) { + return !!val; + }; + + converters.byte = createIntegerConversion(8, { unsigned: false }); + converters.octet = createIntegerConversion(8, { unsigned: true }); + + converters.short = createIntegerConversion(16, { unsigned: false }); + converters["unsigned short"] = createIntegerConversion(16, { + unsigned: true, + }); + + converters.long = createIntegerConversion(32, { unsigned: false }); + converters["unsigned long"] = createIntegerConversion(32, { unsigned: true }); + + converters["long long"] = createLongLongConversion(64, { unsigned: false }); + converters["unsigned long long"] = createLongLongConversion(64, { + unsigned: true, + }); + + converters.float = (V, opts) => { + const x = toNumber(V, opts); + + if (!Number.isFinite(x)) { + throw makeException( + TypeError, + "is not a finite floating-point value", + opts, + ); + } + + if (Object.is(x, -0)) { + return x; + } + + const y = Math.fround(x); + + if (!Number.isFinite(y)) { + throw makeException( + TypeError, + "is outside the range of a single-precision floating-point value", + opts, + ); + } + + return y; + }; + + converters["unrestricted float"] = (V, opts) => { + const x = toNumber(V, opts); + + if (isNaN(x)) { + return x; + } + + if (Object.is(x, -0)) { + return x; + } + + return Math.fround(x); + }; + + converters.double = (V, opts) => { + const x = toNumber(V, opts); + + if (!Number.isFinite(x)) { + throw makeException( + TypeError, + "is not a finite floating-point value", + opts, + ); + } + + return x; + }; + + converters["unrestricted double"] = (V, opts) => { + const x = toNumber(V, opts); + + return x; + }; + + converters.DOMString = function (V, opts = {}) { + if (opts.treatNullAsEmptyString && V === null) { + return ""; + } + + if (typeof V === "symbol") { + throw makeException( + TypeError, + "is a symbol, which cannot be converted to a string", + opts, + ); + } + + const StringCtor = opts.globals ? opts.globals.String : String; + return StringCtor(V); + }; + + converters.ByteString = (V, opts) => { + const x = converters.DOMString(V, opts); + let c; + for (let i = 0; (c = x.codePointAt(i)) !== undefined; ++i) { + if (c > 255) { + throw makeException(TypeError, "is not a valid ByteString", opts); + } + } + + return x; + }; + + converters.USVString = (V, opts) => { + const S = converters.DOMString(V, opts); + const n = S.length; + const U = []; + for (let i = 0; i < n; ++i) { + const c = S.charCodeAt(i); + if (c < 0xd800 || c > 0xdfff) { + U.push(String.fromCodePoint(c)); + } else if (0xdc00 <= c && c <= 0xdfff) { + U.push(String.fromCodePoint(0xfffd)); + } else if (i === n - 1) { + U.push(String.fromCodePoint(0xfffd)); + } else { + const d = S.charCodeAt(i + 1); + if (0xdc00 <= d && d <= 0xdfff) { + const a = c & 0x3ff; + const b = d & 0x3ff; + U.push(String.fromCodePoint((2 << 15) + (2 << 9) * a + b)); + ++i; + } else { + U.push(String.fromCodePoint(0xfffd)); + } + } + } + + return U.join(""); + }; + + converters.object = (V, opts) => { + if (type(V) !== "Object") { + throw makeException(TypeError, "is not an object", opts); + } + + return V; + }; + + // Not exported, but used in Function and VoidFunction. + + // Neither Function nor VoidFunction is defined with [TreatNonObjectAsNull], so + // handling for that is omitted. + function convertCallbackFunction(V, opts) { + if (typeof V !== "function") { + throw makeException(TypeError, "is not a function", opts); + } + return V; + } + + const abByteLengthGetter = Object.getOwnPropertyDescriptor( + ArrayBuffer.prototype, + "byteLength", + ).get; + + function isNonSharedArrayBuffer(V) { + try { + // This will throw on SharedArrayBuffers, but not detached ArrayBuffers. + // (The spec says it should throw, but the spec conflicts with implementations: https://github.com/tc39/ecma262/issues/678) + abByteLengthGetter.call(V); + + return true; + } catch { + return false; + } + } + + let sabByteLengthGetter; + + function isSharedArrayBuffer(V) { + // TODO(lucacasonato): vulnerable to prototype pollution. Needs to happen + // here because SharedArrayBuffer is not available during snapshotting. + if (!sabByteLengthGetter) { + sabByteLengthGetter = Object.getOwnPropertyDescriptor( + SharedArrayBuffer.prototype, + "byteLength", + ).get; + } + try { + sabByteLengthGetter.call(V); + return true; + } catch { + return false; + } + } + + function isArrayBufferDetached(V) { + try { + // eslint-disable-next-line no-new + new Uint8Array(V); + return false; + } catch { + return true; + } + } + + converters.ArrayBuffer = (V, opts = {}) => { + if (!isNonSharedArrayBuffer(V)) { + if (opts.allowShared && !isSharedArrayBuffer(V)) { + throw makeException( + TypeError, + "is not an ArrayBuffer or SharedArrayBuffer", + opts, + ); + } + throw makeException(TypeError, "is not an ArrayBuffer", opts); + } + if (isArrayBufferDetached(V)) { + throw makeException(TypeError, "is a detached ArrayBuffer", opts); + } + + return V; + }; + + const dvByteLengthGetter = Object.getOwnPropertyDescriptor( + DataView.prototype, + "byteLength", + ).get; + converters.DataView = (V, opts = {}) => { + try { + dvByteLengthGetter.call(V); + } catch (e) { + throw makeException(TypeError, "is not a DataView", opts); + } + + if (!opts.allowShared && isSharedArrayBuffer(V.buffer)) { + throw makeException( + TypeError, + "is backed by a SharedArrayBuffer, which is not allowed", + opts, + ); + } + if (isArrayBufferDetached(V.buffer)) { + throw makeException( + TypeError, + "is backed by a detached ArrayBuffer", + opts, + ); + } + + return V; + }; + + // Returns the unforgeable `TypedArray` constructor name or `undefined`, + // if the `this` value isn't a valid `TypedArray` object. + // + // https://tc39.es/ecma262/#sec-get-%typedarray%.prototype-@@tostringtag + const typedArrayNameGetter = Object.getOwnPropertyDescriptor( + Object.getPrototypeOf(Uint8Array).prototype, + Symbol.toStringTag, + ).get; + [ + Int8Array, + Int16Array, + Int32Array, + Uint8Array, + Uint16Array, + Uint32Array, + Uint8ClampedArray, + Float32Array, + Float64Array, + ].forEach((func) => { + const name = func.name; + const article = /^[AEIOU]/.test(name) ? "an" : "a"; + converters[name] = (V, opts = {}) => { + if (!ArrayBuffer.isView(V) || typedArrayNameGetter.call(V) !== name) { + throw makeException( + TypeError, + `is not ${article} ${name} object`, + opts, + ); + } + if (!opts.allowShared && isSharedArrayBuffer(V.buffer)) { + throw makeException( + TypeError, + "is a view on a SharedArrayBuffer, which is not allowed", + opts, + ); + } + if (isArrayBufferDetached(V.buffer)) { + throw makeException( + TypeError, + "is a view on a detached ArrayBuffer", + opts, + ); + } + + return V; + }; + }); + + // Common definitions + + converters.ArrayBufferView = (V, opts = {}) => { + if (!ArrayBuffer.isView(V)) { + throw makeException( + TypeError, + "is not a view on an ArrayBuffer or SharedArrayBuffer", + opts, + ); + } + + if (!opts.allowShared && isSharedArrayBuffer(V.buffer)) { + throw makeException( + TypeError, + "is a view on a SharedArrayBuffer, which is not allowed", + opts, + ); + } + + if (isArrayBufferDetached(V.buffer)) { + throw makeException( + TypeError, + "is a view on a detached ArrayBuffer", + opts, + ); + } + return V; + }; + + converters.BufferSource = (V, opts = {}) => { + if (ArrayBuffer.isView(V)) { + if (!opts.allowShared && isSharedArrayBuffer(V.buffer)) { + throw makeException( + TypeError, + "is a view on a SharedArrayBuffer, which is not allowed", + opts, + ); + } + + if (isArrayBufferDetached(V.buffer)) { + throw makeException( + TypeError, + "is a view on a detached ArrayBuffer", + opts, + ); + } + return V; + } + + if (!opts.allowShared && !isNonSharedArrayBuffer(V)) { + throw makeException( + TypeError, + "is not an ArrayBuffer or a view on one", + opts, + ); + } + if ( + opts.allowShared && + !isSharedArrayBuffer(V) && + !isNonSharedArrayBuffer(V) + ) { + throw makeException( + TypeError, + "is not an ArrayBuffer, SharedArrayBuffer, or a view on one", + opts, + ); + } + if (isArrayBufferDetached(V)) { + throw makeException(TypeError, "is a detached ArrayBuffer", opts); + } + + return V; + }; + + converters.DOMTimeStamp = converters["unsigned long long"]; + + converters.Function = convertCallbackFunction; + + converters.VoidFunction = convertCallbackFunction; + + converters["UVString?"] = createNullableConverter( + converters.USVString, + ); + converters["sequence"] = createSequenceConverter( + converters["double"], + ); + + function requiredArguments(length, required, opts = {}) { + if (length < required) { + const errMsg = `${ + opts.prefix ? opts.prefix + ": " : "" + }${required} argument${ + required === 1 ? "" : "s" + } required, but only ${length} present.`; + throw new TypeError(errMsg); + } + } + + function createDictionaryConverter(name, ...dictionaries) { + return function (V, opts = {}) { + const typeV = type(V); + switch (typeV) { + case "Undefined": + case "Null": + case "Object": + break; + default: + throw makeException( + TypeError, + "can not be converted to a dictionary", + opts, + ); + } + const esDict = V; + + const idlDict = {}; + + for (const members of dictionaries) { + for (const member of members) { + const key = member.key; + + let esMemberValue; + if (typeV === "Undefined" || typeV === "Null") { + esMemberValue = undefined; + } else { + esMemberValue = esDict[key]; + } + + const context = `'${key}' of '${name}'${ + opts.context ? ` (${opts.context})` : "" + }`; + + if (esMemberValue !== undefined) { + const converter = member.converter; + const idlMemberValue = converter(esMemberValue, { + ...opts, + context, + }); + idlDict[key] = idlMemberValue; + } else if ("defaultValue" in member) { + const defaultValue = member.defaultValue; + const idlMemberValue = defaultValue; + idlDict[key] = idlMemberValue; + } else if (member.required) { + throw makeException( + TypeError, + `can not be converted to '${name}' because '${key}' is required in '${name}'.`, + { ...opts }, + ); + } + } + } + + return idlDict; + }; + } + + // https://heycam.github.io/webidl/#es-enumeration + function createEnumConverter(name, values) { + const E = new Set(values); + + return function (V, opts = {}) { + const S = String(V); + + if (!E.has(S)) { + throw new TypeError( + `${ + opts.prefix ? opts.prefix + ": " : "" + }The provided value '${S}' is not a valid enum value of type ${name}.`, + ); + } + + return S; + }; + } + + function createNullableConverter(converter) { + return (V, opts = {}) => { + // FIXME: If Type(V) is not Object, and the conversion to an IDL value is + // being performed due to V being assigned to an attribute whose type is a + // nullable callback function that is annotated with + // [LegacyTreatNonObjectAsNull], then return the IDL nullable type T? + // value null. + + if (V === null || V === undefined) return null; + return converter(V, opts); + }; + } + + // https://heycam.github.io/webidl/#es-sequence + function createSequenceConverter(converter) { + return function (V, opts = {}) { + if (typeof V !== "object") { + throw makeException( + TypeError, + "can not be converted to sequence.", + opts, + ); + } + const iter = V?.[Symbol.iterator]?.(); + if (iter === undefined) { + throw makeException( + TypeError, + "can not be converted to sequence.", + opts, + ); + } + const array = []; + while (true) { + const res = iter?.next?.(); + if (res === undefined) { + throw makeException( + TypeError, + "can not be converted to sequence.", + opts, + ); + } + if (res.done === true) break; + const val = converter(res.value, { + ...opts, + context: `${opts.context}, index ${array.length}`, + }); + array.push(val); + } + return array; + }; + } + + function createRecordConverter(keyConverter, valueConverter) { + return (V, opts) => { + if (typeof V !== "object") { + throw makeException( + TypeError, + "can not be converted to dictionary.", + opts, + ); + } + const result = {}; + for (const key of V) { + const typedKey = keyConverter(key, opts); + const value = V[key]; + const typedValue = valueConverter(value, opts); + result[typedKey] = typedValue; + } + return result; + }; + } + + const brand = Symbol("[[webidl.brand]]"); + + function createInterfaceConverter(name, prototype) { + return (V, opts) => { + if (!(V instanceof prototype) || V[brand] !== brand) { + throw makeException(TypeError, `is not of type ${name}.`, opts); + } + return V; + }; + } + + function createBranded(Type) { + const t = Object.create(Type.prototype); + t[brand] = brand; + return t; + } + + function assertBranded(self, prototype) { + if (!(self instanceof prototype) || self[brand] !== brand) { + throw new TypeError("Illegal invocation"); + } + } + + function illegalConstructor() { + throw new TypeError("Illegal constructor"); + } + + window.__bootstrap ??= {}; + window.__bootstrap.webidl = { + makeException, + converters, + requiredArguments, + createDictionaryConverter, + createEnumConverter, + createNullableConverter, + createSequenceConverter, + createRecordConverter, + createInterfaceConverter, + brand, + createBranded, + assertBranded, + illegalConstructor, + }; +})(this); diff --git a/op_crates/webidl/Cargo.toml b/op_crates/webidl/Cargo.toml new file mode 100644 index 000000000..5d2b17999 --- /dev/null +++ b/op_crates/webidl/Cargo.toml @@ -0,0 +1,17 @@ +# Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. + +[package] +name = "deno_webidl" +version = "0.1.0" +edition = "2018" +description = "WebIDL implementation for Deno" +authors = ["the Deno authors"] +license = "MIT" +readme = "README.md" +repository = "https://github.com/denoland/deno" + +[lib] +path = "lib.rs" + +[dependencies] +deno_core = { version = "0.80.2", path = "../../core" } diff --git a/op_crates/webidl/README.md b/op_crates/webidl/README.md new file mode 100644 index 000000000..ce2d661e3 --- /dev/null +++ b/op_crates/webidl/README.md @@ -0,0 +1,6 @@ +# deno_webidl + +This crate implements WebIDL for Deno. It consists of infrastructure to do ECMA +-> WebIDL conversions. + +Spec: https://heycam.github.io/webidl/ diff --git a/op_crates/webidl/internal.d.ts b/op_crates/webidl/internal.d.ts new file mode 100644 index 000000000..425ee674e --- /dev/null +++ b/op_crates/webidl/internal.d.ts @@ -0,0 +1,291 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. +// deno-lint-ignore-file no-explicit-any ban-types + +/// +/// + +declare namespace globalThis { + declare namespace __bootstrap { + declare namespace webidl { + declare interface ConverterOpts { + /** + * The prefix for error messages created by this converter. + * Examples: + * - `Failed to construct 'Event'` + * - `Failed to execute 'removeEventListener' on 'EventTarget'` + */ + prefix: string; + } + declare interface ValueConverterOpts extends ConverterOpts { + /** + * The context of this value error messages created by this converter. + * Examples: + * - `Argument 1` + * - `Argument 3` + */ + context: string; + } + declare function makeException( + ErrorType: any, + message: string, + opts: ValueConverterOpts, + ): any; + declare interface IntConverterOpts extends ValueConverterOpts { + /** + * Wether to throw if the number is outside of the acceptable values for + * this type. + */ + enforceRange?: boolean; + /** + * Wether to clamp this number to the acceptable values for this type. + */ + clamp?: boolean; + } + declare interface StringConverterOpts extends ValueConverterOpts { + /** + * Wether to treat `null` value as an empty string. + */ + treatNullAsEmptyString?: boolean; + } + declare interface BufferConverterOpts extends ValueConverterOpts { + /** + * Wether to allow `SharedArrayBuffer` (not just `ArrayBuffer`). + */ + allowShared?: boolean; + } + declare const converters: { + any(v: any): any; + /** + * Convert a value into a `boolean` (bool). + */ + boolean(v: any, opts?: IntConverterOpts): boolean; + /** + * Convert a value into a `byte` (int8). + */ + byte(v: any, opts?: IntConverterOpts): number; + /** + * Convert a value into a `octet` (uint8). + */ + octet(v: any, opts?: IntConverterOpts): number; + /** + * Convert a value into a `short` (int16). + */ + short(v: any, opts?: IntConverterOpts): number; + /** + * Convert a value into a `unsigned short` (uint16). + */ + ["unsigned short"](v: any, opts?: IntConverterOpts): number; + /** + * Convert a value into a `long` (int32). + */ + long(v: any, opts?: IntConverterOpts): number; + /** + * Convert a value into a `unsigned long` (uint32). + */ + ["unsigned long"](v: any, opts?: IntConverterOpts): number; + /** + * Convert a value into a `long long` (int64). + * **Note this is truncated to a JS number (53 bit precision).** + */ + ["long long"](v: any, opts?: IntConverterOpts): number; + /** + * Convert a value into a `unsigned long long` (uint64). + * **Note this is truncated to a JS number (53 bit precision).** + */ + ["unsigned long long"](v: any, opts?: IntConverterOpts): number; + /** + * Convert a value into a `float` (f32). + */ + float(v: any, opts?: ValueConverterOpts): number; + /** + * Convert a value into a `unrestricted float` (f32, infinity, or NaN). + */ + ["unrestricted float"](v: any, opts?: ValueConverterOpts): number; + /** + * Convert a value into a `double` (f64). + */ + double(v: any, opts?: ValueConverterOpts): number; + /** + * Convert a value into a `unrestricted double` (f64, infinity, or NaN). + */ + ["unrestricted double"](v: any, opts?: ValueConverterOpts): number; + /** + * Convert a value into a `DOMString` (string). + */ + DOMString(v: any, opts?: StringConverterOpts): string; + /** + * Convert a value into a `ByteString` (string with only u8 codepoints). + */ + ByteString(v: any, opts?: StringConverterOpts): string; + /** + * Convert a value into a `USVString` (string with only valid non + * surrogate Unicode code points). + */ + USVString(v: any, opts?: StringConverterOpts): string; + /** + * Convert a value into an `object` (object). + */ + object(v: any, opts?: ValueConverterOpts): object; + /** + * Convert a value into an `ArrayBuffer` (ArrayBuffer). + */ + ArrayBuffer(v: any, opts?: BufferConverterOpts): ArrayBuffer; + /** + * Convert a value into a `DataView` (ArrayBuffer). + */ + DataView(v: any, opts?: BufferConverterOpts): DataView; + /** + * Convert a value into a `Int8Array` (Int8Array). + */ + Int8Array(v: any, opts?: BufferConverterOpts): Int8Array; + /** + * Convert a value into a `Int16Array` (Int16Array). + */ + Int16Array(v: any, opts?: BufferConverterOpts): Int16Array; + /** + * Convert a value into a `Int32Array` (Int32Array). + */ + Int32Array(v: any, opts?: BufferConverterOpts): Int32Array; + /** + * Convert a value into a `Uint8Array` (Uint8Array). + */ + Uint8Array(v: any, opts?: BufferConverterOpts): Uint8Array; + /** + * Convert a value into a `Uint16Array` (Uint16Array). + */ + Uint16Array(v: any, opts?: BufferConverterOpts): Uint16Array; + /** + * Convert a value into a `Uint32Array` (Uint32Array). + */ + Uint32Array(v: any, opts?: BufferConverterOpts): Uint32Array; + /** + * Convert a value into a `Uint8ClampedArray` (Uint8ClampedArray). + */ + Uint8ClampedArray( + v: any, + opts?: BufferConverterOpts, + ): Uint8ClampedArray; + /** + * Convert a value into a `Float32Array` (Float32Array). + */ + Float32Array(v: any, opts?: BufferConverterOpts): Float32Array; + /** + * Convert a value into a `Float64Array` (Float64Array). + */ + Float64Array(v: any, opts?: BufferConverterOpts): Float64Array; + /** + * Convert a value into an `ArrayBufferView` (ArrayBufferView). + */ + ArrayBufferView(v: any, opts?: BufferConverterOpts): ArrayBufferView; + /** + * Convert a value into a `BufferSource` (ArrayBuffer or ArrayBufferView). + */ + BufferSource( + v: any, + opts?: BufferConverterOpts, + ): ArrayBuffer | ArrayBufferView; + /** + * Convert a value into a `DOMTimeStamp` (u64). Alias for unsigned long long + */ + DOMTimeStamp(v: any, opts?: IntConverterOpts): number; + /** + * Convert a value into a `Function` ((...args: any[]) => any). + */ + Function(v: any, opts?: ValueConverterOpts): (...args: any) => any; + /** + * Convert a value into a `VoidFunction` (() => void). + */ + VoidFunction(v: any, opts?: ValueConverterOpts): () => void; + ["UVString?"](v: any, opts?: ValueConverterOpts): string | null; + ["sequence"](v: any, opts?: ValueConverterOpts): number[]; + + [type: string]: (v: any, opts: ValueConverterOpts) => any; + }; + + /** + * Assert that the a function has at least a required amount of arguments. + */ + declare function requiredArguments( + length: number, + required: number, + opts: ConverterOpts, + ): void; + declare type Dictionary = DictionaryMember[]; + declare interface DictionaryMember { + key: string; + converter: (v: any, opts: ValueConverterOpts) => any; + defaultValue?: any; + required?: boolean; + } + + /** + * Create a converter for dictionaries. + */ + declare function createDictionaryConverter( + name: string, + ...dictionaries: Dictionary[] + ): (v: any, opts: ValueConverterOpts) => T; + + /** + * Create a converter for enums. + */ + declare function createEnumConverter( + name: string, + values: string[], + ): (v: any, opts: ValueConverterOpts) => string; + + /** + * Create a converter that makes the contained type nullable. + */ + declare function createNullableConverter( + converter: (v: any, opts: ValueConverterOpts) => T, + ): (v: any, opts: ValueConverterOpts) => T | null; + + /** + * Create a converter that converts a sequence of the inner type. + */ + declare function createSequenceConverter( + converter: (v: any, opts: ValueConverterOpts) => T, + ): (v: any, opts: ValueConverterOpts) => T[]; + + /** + * Throw an illegal constructor error. + */ + declare function illegalConstructor(): never; + + /** + * The branding symbol. + */ + declare const brand: unique symbol; + + /** + * Create a branded instance of an interface. + */ + declare function createBranded(self: any): any; + + /** + * Assert that self is branded. + */ + declare function assertBranded(self: any, type: any): void; + + /** + * Create a converter for interfaces. + */ + declare function createInterfaceConverter( + name: string, + prototype: any, + ): (v: any, opts: ValueConverterOpts) => any; + + declare function createRecordConverter< + K extends string | number | symbol, + V, + >( + keyConverter: (v: any, opts: ValueConverterOpts) => K, + valueConverter: (v: any, opts: ValueConverterOpts) => V, + ): ( + v: Record, + opts: ValueConverterOpts, + ) => any; + } + } +} diff --git a/op_crates/webidl/lib.rs b/op_crates/webidl/lib.rs new file mode 100644 index 000000000..7e617c7a7 --- /dev/null +++ b/op_crates/webidl/lib.rs @@ -0,0 +1,14 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. + +use deno_core::JsRuntime; + +/// Load and execute the javascript code. +pub fn init(isolate: &mut JsRuntime) { + let files = vec![( + "deno:op_crates/webidl/00_webidl.js", + include_str!("00_webidl.js"), + )]; + for (url, source_code) in files { + isolate.execute(url, source_code).unwrap(); + } +} -- cgit v1.2.3