1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-24 15:19:26 -05:00

refactor: Use ES modules for internal runtime code (#17648)

This PR refactors all internal js files (except core) to be written as
ES modules.
`__bootstrap`has been mostly replaced with static imports in form in
`internal:[path to file from repo root]`.
To specify if files are ESM, an `esm` method has been added to
`Extension`, similar to the `js` method.
A new ModuleLoader called `InternalModuleLoader` has been added to
enable the loading of internal specifiers, which is used in all
situations except when a snapshot is only loaded, and not a new one is
created from it.

---------

Co-authored-by: Bartek Iwańczuk <biwanczuk@gmail.com>
This commit is contained in:
Leo Kettmeir 2023-02-07 20:22:46 +01:00 committed by GitHub
parent 65500f36e8
commit b4aa153097
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
123 changed files with 41574 additions and 41713 deletions

View file

@ -10,6 +10,9 @@ use crate::profiling::is_profiling;
pub fn create_js_runtime(setup: impl FnOnce() -> Vec<Extension>) -> JsRuntime { pub fn create_js_runtime(setup: impl FnOnce() -> Vec<Extension>) -> JsRuntime {
JsRuntime::new(RuntimeOptions { JsRuntime::new(RuntimeOptions {
extensions_with_js: setup(), extensions_with_js: setup(),
module_loader: Some(std::rc::Rc::new(
deno_core::InternalModuleLoader::new(None),
)),
..Default::default() ..Default::default()
}) })
} }

View file

@ -275,6 +275,7 @@ mod ts {
.build()], .build()],
extensions_with_js: vec![], extensions_with_js: vec![],
additional_files: files, additional_files: files,
additional_esm_files: vec![],
compression_cb: Some(Box::new(|vec, snapshot_slice| { compression_cb: Some(Box::new(|vec, snapshot_slice| {
vec.extend_from_slice( vec.extend_from_slice(
&zstd::bulk::compress(snapshot_slice, 22) &zstd::bulk::compress(snapshot_slice, 22)
@ -306,7 +307,7 @@ mod ts {
} }
} }
fn create_cli_snapshot(snapshot_path: PathBuf, files: Vec<PathBuf>) { fn create_cli_snapshot(snapshot_path: PathBuf, esm_files: Vec<PathBuf>) {
let extensions: Vec<Extension> = vec![ let extensions: Vec<Extension> = vec![
deno_webidl::init(), deno_webidl::init(),
deno_console::init(), deno_console::init(),
@ -343,7 +344,8 @@ fn create_cli_snapshot(snapshot_path: PathBuf, files: Vec<PathBuf>) {
startup_snapshot: Some(deno_runtime::js::deno_isolate_init()), startup_snapshot: Some(deno_runtime::js::deno_isolate_init()),
extensions, extensions,
extensions_with_js: vec![], extensions_with_js: vec![],
additional_files: files, additional_files: vec![],
additional_esm_files: esm_files,
compression_cb: Some(Box::new(|vec, snapshot_slice| { compression_cb: Some(Box::new(|vec, snapshot_slice| {
lzzzz::lz4_hc::compress_to_vec( lzzzz::lz4_hc::compress_to_vec(
snapshot_slice, snapshot_slice,
@ -448,13 +450,13 @@ fn main() {
let o = PathBuf::from(env::var_os("OUT_DIR").unwrap()); let o = PathBuf::from(env::var_os("OUT_DIR").unwrap());
let compiler_snapshot_path = o.join("COMPILER_SNAPSHOT.bin"); let compiler_snapshot_path = o.join("COMPILER_SNAPSHOT.bin");
let js_files = get_js_files(env!("CARGO_MANIFEST_DIR"), "tsc"); let js_files = get_js_files(env!("CARGO_MANIFEST_DIR"), "tsc", None);
ts::create_compiler_snapshot(compiler_snapshot_path, js_files, &c); ts::create_compiler_snapshot(compiler_snapshot_path, js_files, &c);
let cli_snapshot_path = o.join("CLI_SNAPSHOT.bin"); let cli_snapshot_path = o.join("CLI_SNAPSHOT.bin");
let mut js_files = get_js_files(env!("CARGO_MANIFEST_DIR"), "js"); let mut esm_files = get_js_files(env!("CARGO_MANIFEST_DIR"), "js", None);
js_files.push(deno_runtime::js::get_99_main()); esm_files.push(deno_runtime::js::get_99_main());
create_cli_snapshot(cli_snapshot_path, js_files); create_cli_snapshot(cli_snapshot_path, esm_files);
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
{ {

File diff suppressed because it is too large Load diff

View file

@ -3844,3 +3844,15 @@ itest!(node_prefix_missing {
envs: env_vars_for_npm_tests_no_sync_download(), envs: env_vars_for_npm_tests_no_sync_download(),
exit_code: 1, exit_code: 1,
}); });
itest!(internal_import {
args: "run run/internal_import.ts",
output: "run/internal_import.ts.out",
exit_code: 1,
});
itest!(internal_dynamic_import {
args: "run run/internal_dynamic_import.ts",
output: "run/internal_dynamic_import.ts.out",
exit_code: 1,
});

View file

@ -1,5 +1,4 @@
1 1
queueMicrotask
error: Uncaught Error: bar error: Uncaught Error: bar
throw new Error("bar"); throw new Error("bar");
^ ^

View file

@ -0,0 +1 @@
await import("internal:runtime/js/01_build.js");

View file

@ -0,0 +1,4 @@
error: Uncaught TypeError: Cannot load internal module from external code
await import("internal:runtime/js/01_build.js");
^
at [WILDCARD]/internal_dynamic_import.ts:1:1

View file

@ -0,0 +1 @@
import "internal:runtime/js/01_build.js";

View file

@ -0,0 +1,8 @@
error: Unsupported scheme "internal" for module "internal:runtime/js/01_build.js". Supported schemes: [
"data",
"blob",
"file",
"http",
"https",
]
at [WILDCARD]

View file

@ -427,6 +427,8 @@
}); });
ObjectAssign(globalThis.__bootstrap, { core }); ObjectAssign(globalThis.__bootstrap, { core });
const internals = {};
ObjectAssign(globalThis.__bootstrap, { internals });
ObjectAssign(globalThis.Deno, { core }); ObjectAssign(globalThis.Deno, { core });
// Direct bindings on `globalThis` // Direct bindings on `globalThis`

View file

@ -267,45 +267,47 @@ pub fn host_import_module_dynamically_callback<'s>(
.unwrap() .unwrap()
.to_rust_string_lossy(scope); .to_rust_string_lossy(scope);
let is_internal_module = specifier_str.starts_with("internal:");
let resolver = v8::PromiseResolver::new(scope).unwrap(); let resolver = v8::PromiseResolver::new(scope).unwrap();
let promise = resolver.get_promise(scope); let promise = resolver.get_promise(scope);
let assertions = parse_import_assertions( if !is_internal_module {
scope, let assertions = parse_import_assertions(
import_assertions, scope,
ImportAssertionsKind::DynamicImport, import_assertions,
); ImportAssertionsKind::DynamicImport,
);
{ {
let tc_scope = &mut v8::TryCatch::new(scope); let tc_scope = &mut v8::TryCatch::new(scope);
validate_import_assertions(tc_scope, &assertions); validate_import_assertions(tc_scope, &assertions);
if tc_scope.has_caught() { if tc_scope.has_caught() {
let e = tc_scope.exception().unwrap(); let e = tc_scope.exception().unwrap();
resolver.reject(tc_scope, e); resolver.reject(tc_scope, e);
}
}
let asserted_module_type =
get_asserted_module_type_from_assertions(&assertions);
let resolver_handle = v8::Global::new(scope, resolver);
{
let state_rc = JsRuntime::state(scope);
let module_map_rc = JsRuntime::module_map(scope);
debug!(
"dyn_import specifier {} referrer {} ",
specifier_str, referrer_name_str
);
ModuleMap::load_dynamic_import(
module_map_rc,
&specifier_str,
&referrer_name_str,
asserted_module_type,
resolver_handle,
);
state_rc.borrow_mut().notify_new_dynamic_import();
} }
} }
let asserted_module_type =
get_asserted_module_type_from_assertions(&assertions);
let resolver_handle = v8::Global::new(scope, resolver);
{
let state_rc = JsRuntime::state(scope);
let module_map_rc = JsRuntime::module_map(scope);
debug!(
"dyn_import specifier {} referrer {} ",
specifier_str, referrer_name_str
);
ModuleMap::load_dynamic_import(
module_map_rc,
&specifier_str,
&referrer_name_str,
asserted_module_type,
resolver_handle,
);
state_rc.borrow_mut().notify_new_dynamic_import();
}
// Map errors from module resolution (not JS errors from module execution) to // Map errors from module resolution (not JS errors from module execution) to
// ones rethrown from this scope, so they include the call stack of the // ones rethrown from this scope, so they include the call stack of the
// dynamic import site. Error objects without any stack frames are assumed to // dynamic import site. Error objects without any stack frames are assumed to
@ -317,6 +319,14 @@ pub fn host_import_module_dynamically_callback<'s>(
let promise = promise.catch(scope, map_err).unwrap(); let promise = promise.catch(scope, map_err).unwrap();
if is_internal_module {
let message =
v8::String::new(scope, "Cannot load internal module from external code")
.unwrap();
let exception = v8::Exception::type_error(scope, message);
resolver.reject(scope, exception);
}
Some(promise) Some(promise)
} }

View file

@ -38,6 +38,7 @@ impl OpDecl {
#[derive(Default)] #[derive(Default)]
pub struct Extension { pub struct Extension {
js_files: Option<Vec<SourcePair>>, js_files: Option<Vec<SourcePair>>,
esm_files: Option<Vec<SourcePair>>,
ops: Option<Vec<OpDecl>>, ops: Option<Vec<OpDecl>>,
opstate_fn: Option<Box<OpStateFn>>, opstate_fn: Option<Box<OpStateFn>>,
middleware_fn: Option<Box<OpMiddlewareFn>>, middleware_fn: Option<Box<OpMiddlewareFn>>,
@ -81,13 +82,20 @@ impl Extension {
/// returns JS source code to be loaded into the isolate (either at snapshotting, /// returns JS source code to be loaded into the isolate (either at snapshotting,
/// or at startup). as a vector of a tuple of the file name, and the source code. /// or at startup). as a vector of a tuple of the file name, and the source code.
pub fn init_js(&self) -> &[SourcePair] { pub fn get_js_sources(&self) -> &[SourcePair] {
match &self.js_files { match &self.js_files {
Some(files) => files, Some(files) => files,
None => &[], None => &[],
} }
} }
pub fn get_esm_sources(&self) -> &[SourcePair] {
match &self.esm_files {
Some(files) => files,
None => &[],
}
}
/// Called at JsRuntime startup to initialize ops in the isolate. /// Called at JsRuntime startup to initialize ops in the isolate.
pub fn init_ops(&mut self) -> Option<Vec<OpDecl>> { pub fn init_ops(&mut self) -> Option<Vec<OpDecl>> {
// TODO(@AaronO): maybe make op registration idempotent // TODO(@AaronO): maybe make op registration idempotent
@ -145,6 +153,7 @@ impl Extension {
#[derive(Default)] #[derive(Default)]
pub struct ExtensionBuilder { pub struct ExtensionBuilder {
js: Vec<SourcePair>, js: Vec<SourcePair>,
esm: Vec<SourcePair>,
ops: Vec<OpDecl>, ops: Vec<OpDecl>,
state: Option<Box<OpStateFn>>, state: Option<Box<OpStateFn>>,
middleware: Option<Box<OpMiddlewareFn>>, middleware: Option<Box<OpMiddlewareFn>>,
@ -164,6 +173,11 @@ impl ExtensionBuilder {
self self
} }
pub fn esm(&mut self, js_files: Vec<SourcePair>) -> &mut Self {
self.esm.extend(js_files);
self
}
pub fn ops(&mut self, ops: Vec<OpDecl>) -> &mut Self { pub fn ops(&mut self, ops: Vec<OpDecl>) -> &mut Self {
self.ops.extend(ops); self.ops.extend(ops);
self self
@ -195,10 +209,12 @@ impl ExtensionBuilder {
pub fn build(&mut self) -> Extension { pub fn build(&mut self) -> Extension {
let js_files = Some(std::mem::take(&mut self.js)); let js_files = Some(std::mem::take(&mut self.js));
let esm_files = Some(std::mem::take(&mut self.esm));
let ops = Some(std::mem::take(&mut self.ops)); let ops = Some(std::mem::take(&mut self.ops));
let deps = Some(std::mem::take(&mut self.deps)); let deps = Some(std::mem::take(&mut self.deps));
Extension { Extension {
js_files, js_files,
esm_files,
ops, ops,
opstate_fn: self.state.take(), opstate_fn: self.state.take(),
middleware_fn: self.middleware.take(), middleware_fn: self.middleware.take(),

View file

@ -73,6 +73,7 @@ pub use crate::module_specifier::ModuleResolutionError;
pub use crate::module_specifier::ModuleSpecifier; pub use crate::module_specifier::ModuleSpecifier;
pub use crate::module_specifier::DUMMY_SPECIFIER; pub use crate::module_specifier::DUMMY_SPECIFIER;
pub use crate::modules::FsModuleLoader; pub use crate::modules::FsModuleLoader;
pub use crate::modules::InternalModuleLoader;
pub use crate::modules::ModuleId; pub use crate::modules::ModuleId;
pub use crate::modules::ModuleLoader; pub use crate::modules::ModuleLoader;
pub use crate::modules::ModuleSource; pub use crate::modules::ModuleSource;

View file

@ -292,6 +292,69 @@ impl ModuleLoader for NoopModuleLoader {
} }
} }
pub struct InternalModuleLoader(Rc<dyn ModuleLoader>);
impl InternalModuleLoader {
pub fn new(module_loader: Option<Rc<dyn ModuleLoader>>) -> Self {
InternalModuleLoader(
module_loader.unwrap_or_else(|| Rc::new(NoopModuleLoader)),
)
}
}
impl ModuleLoader for InternalModuleLoader {
fn resolve(
&self,
specifier: &str,
referrer: &str,
kind: ResolutionKind,
) -> Result<ModuleSpecifier, Error> {
if let Ok(url_specifier) = ModuleSpecifier::parse(specifier) {
if url_specifier.scheme() == "internal" {
let referrer_specifier = ModuleSpecifier::parse(referrer).ok();
if referrer == "." || referrer_specifier.unwrap().scheme() == "internal"
{
return Ok(url_specifier);
} else {
return Err(generic_error(
"Cannot load internal module from external code",
));
};
}
}
self.0.resolve(specifier, referrer, kind)
}
fn load(
&self,
module_specifier: &ModuleSpecifier,
maybe_referrer: Option<ModuleSpecifier>,
is_dyn_import: bool,
) -> Pin<Box<ModuleSourceFuture>> {
self.0.load(module_specifier, maybe_referrer, is_dyn_import)
}
fn prepare_load(
&self,
op_state: Rc<RefCell<OpState>>,
module_specifier: &ModuleSpecifier,
maybe_referrer: Option<String>,
is_dyn_import: bool,
) -> Pin<Box<dyn Future<Output = Result<(), Error>>>> {
if module_specifier.scheme() == "internal" {
return async { Ok(()) }.boxed_local();
}
self.0.prepare_load(
op_state,
module_specifier,
maybe_referrer,
is_dyn_import,
)
}
}
/// Basic file system module loader. /// Basic file system module loader.
/// ///
/// Note that this loader will **block** event loop /// Note that this loader will **block** event loop
@ -2508,4 +2571,33 @@ if (import.meta.url != 'file:///main_with_code.js') throw Error();
) )
.unwrap(); .unwrap();
} }
#[test]
fn internal_module_loader() {
let loader = InternalModuleLoader::new(None);
assert!(loader
.resolve("internal:foo", "internal:bar", ResolutionKind::Import)
.is_ok());
assert_eq!(
loader
.resolve("internal:foo", "file://bar", ResolutionKind::Import)
.err()
.map(|e| e.to_string()),
Some("Cannot load internal module from external code".to_string())
);
assert_eq!(
loader
.resolve("file://foo", "file://bar", ResolutionKind::Import)
.err()
.map(|e| e.to_string()),
Some("Module loading is not supported".to_string())
);
assert_eq!(
loader
.resolve("file://foo", "internal:bar", ResolutionKind::Import)
.err()
.map(|e| e.to_string()),
Some("Module loading is not supported".to_string())
);
}
} }

View file

@ -13,17 +13,18 @@ use crate::modules::ModuleId;
use crate::modules::ModuleLoadId; use crate::modules::ModuleLoadId;
use crate::modules::ModuleLoader; use crate::modules::ModuleLoader;
use crate::modules::ModuleMap; use crate::modules::ModuleMap;
use crate::modules::NoopModuleLoader;
use crate::op_void_async; use crate::op_void_async;
use crate::op_void_sync; use crate::op_void_sync;
use crate::ops::*; use crate::ops::*;
use crate::source_map::SourceMapCache; use crate::source_map::SourceMapCache;
use crate::source_map::SourceMapGetter; use crate::source_map::SourceMapGetter;
use crate::Extension; use crate::Extension;
use crate::NoopModuleLoader;
use crate::OpMiddlewareFn; use crate::OpMiddlewareFn;
use crate::OpResult; use crate::OpResult;
use crate::OpState; use crate::OpState;
use crate::PromiseId; use crate::PromiseId;
use anyhow::Context as AnyhowContext;
use anyhow::Error; use anyhow::Error;
use futures::channel::oneshot; use futures::channel::oneshot;
use futures::future::poll_fn; use futures::future::poll_fn;
@ -605,9 +606,16 @@ impl JsRuntime {
None None
}; };
let loader = options let loader = if snapshot_options != SnapshotOptions::Load {
.module_loader Rc::new(crate::modules::InternalModuleLoader::new(
.unwrap_or_else(|| Rc::new(NoopModuleLoader)); options.module_loader,
))
} else {
options
.module_loader
.unwrap_or_else(|| Rc::new(NoopModuleLoader))
};
{ {
let mut state = state_rc.borrow_mut(); let mut state = state_rc.borrow_mut();
state.global_realm = Some(JsRealm(global_context.clone())); state.global_realm = Some(JsRealm(global_context.clone()));
@ -805,10 +813,30 @@ impl JsRuntime {
// Take extensions to avoid double-borrow // Take extensions to avoid double-borrow
let extensions = std::mem::take(&mut self.extensions_with_js); let extensions = std::mem::take(&mut self.extensions_with_js);
for ext in &extensions { for ext in &extensions {
let js_files = ext.init_js(); {
for (filename, source) in js_files { let js_files = ext.get_esm_sources();
// TODO(@AaronO): use JsRuntime::execute_static() here to move src off heap for (filename, source) in js_files {
realm.execute_script(self.v8_isolate(), filename, source)?; futures::executor::block_on(async {
let id = self
.load_side_module(
&ModuleSpecifier::parse(filename)?,
Some(source.to_string()),
)
.await?;
let receiver = self.mod_evaluate(id);
self.run_event_loop(false).await?;
receiver.await?
})
.with_context(|| format!("Couldn't execute '{filename}'"))?;
}
}
{
let js_files = ext.get_js_sources();
for (filename, source) in js_files {
// TODO(@AaronO): use JsRuntime::execute_static() here to move src off heap
realm.execute_script(self.v8_isolate(), filename, source)?;
}
} }
} }
// Restore extensions // Restore extensions

View file

@ -1,10 +1,12 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
use anyhow::Context;
use std::path::Path; use std::path::Path;
use std::path::PathBuf; use std::path::PathBuf;
use crate::Extension; use crate::Extension;
use crate::JsRuntime; use crate::JsRuntime;
use crate::ModuleSpecifier;
use crate::RuntimeOptions; use crate::RuntimeOptions;
use crate::Snapshot; use crate::Snapshot;
@ -17,6 +19,7 @@ pub struct CreateSnapshotOptions {
pub extensions: Vec<Extension>, pub extensions: Vec<Extension>,
pub extensions_with_js: Vec<Extension>, pub extensions_with_js: Vec<Extension>,
pub additional_files: Vec<PathBuf>, pub additional_files: Vec<PathBuf>,
pub additional_esm_files: Vec<PathBuf>,
pub compression_cb: Option<Box<CompressionCb>>, pub compression_cb: Option<Box<CompressionCb>>,
} }
@ -44,6 +47,27 @@ pub fn create_snapshot(create_snapshot_options: CreateSnapshotOptions) {
) )
.unwrap(); .unwrap();
} }
for file in create_snapshot_options.additional_esm_files {
let display_path = file.strip_prefix(display_root).unwrap_or(&file);
let display_path_str = display_path.display().to_string();
let filename =
&("internal:".to_string() + &display_path_str.replace('\\', "/"));
futures::executor::block_on(async {
let id = js_runtime
.load_side_module(
&ModuleSpecifier::parse(filename)?,
Some(std::fs::read_to_string(&file)?),
)
.await?;
let receiver = js_runtime.mod_evaluate(id);
js_runtime.run_event_loop(false).await?;
receiver.await?
})
.with_context(|| format!("Couldn't execute '{}'", file.display()))
.unwrap();
}
let snapshot = js_runtime.snapshot(); let snapshot = js_runtime.snapshot();
let snapshot_slice: &[u8] = &snapshot; let snapshot_slice: &[u8] = &snapshot;
@ -79,9 +103,12 @@ pub fn create_snapshot(create_snapshot_options: CreateSnapshotOptions) {
); );
} }
pub type FilterFn = Box<dyn Fn(&PathBuf) -> bool>;
pub fn get_js_files( pub fn get_js_files(
cargo_manifest_dir: &'static str, cargo_manifest_dir: &'static str,
directory: &str, directory: &str,
filter: Option<FilterFn>,
) -> Vec<PathBuf> { ) -> Vec<PathBuf> {
let manifest_dir = Path::new(cargo_manifest_dir); let manifest_dir = Path::new(cargo_manifest_dir);
let mut js_files = std::fs::read_dir(directory) let mut js_files = std::fs::read_dir(directory)
@ -92,7 +119,7 @@ pub fn get_js_files(
}) })
.filter(|path| { .filter(|path| {
path.extension().unwrap_or_default() == "js" path.extension().unwrap_or_default() == "js"
&& !path.ends_with("99_main.js") && filter.as_ref().map(|filter| filter(path)).unwrap_or(true)
}) })
.collect::<Vec<PathBuf>>(); .collect::<Vec<PathBuf>>();
js_files.sort(); js_files.sort();

View file

@ -2,150 +2,149 @@
/// <reference path="../../core/internal.d.ts" /> /// <reference path="../../core/internal.d.ts" />
"use strict"; const core = globalThis.Deno.core;
const ops = core.ops;
import * as webidl from "internal:ext/webidl/00_webidl.js";
import {
defineEventHandler,
EventTarget,
setTarget,
} from "internal:ext/web/02_event.js";
import DOMException from "internal:ext/web/01_dom_exception.js";
const primordials = globalThis.__bootstrap.primordials;
const {
ArrayPrototypeIndexOf,
ArrayPrototypeSplice,
ArrayPrototypePush,
Symbol,
Uint8Array,
} = primordials;
((window) => { const _name = Symbol("[[name]]");
const core = window.Deno.core; const _closed = Symbol("[[closed]]");
const ops = core.ops;
const webidl = window.__bootstrap.webidl;
const { MessageEvent, defineEventHandler, setTarget } =
window.__bootstrap.event;
const { EventTarget } = window.__bootstrap.eventTarget;
const { DOMException } = window.__bootstrap.domException;
const {
ArrayPrototypeIndexOf,
ArrayPrototypeSplice,
ArrayPrototypePush,
Symbol,
Uint8Array,
} = window.__bootstrap.primordials;
const _name = Symbol("[[name]]"); const channels = [];
const _closed = Symbol("[[closed]]"); let rid = null;
const channels = []; async function recv() {
let rid = null; while (channels.length > 0) {
const message = await core.opAsync("op_broadcast_recv", rid);
async function recv() { if (message === null) {
while (channels.length > 0) { break;
const message = await core.opAsync("op_broadcast_recv", rid);
if (message === null) {
break;
}
const { 0: name, 1: data } = message;
dispatch(null, name, new Uint8Array(data));
} }
core.close(rid); const { 0: name, 1: data } = message;
rid = null; dispatch(null, name, new Uint8Array(data));
} }
function dispatch(source, name, data) { core.close(rid);
for (let i = 0; i < channels.length; ++i) { rid = null;
const channel = channels[i]; }
if (channel === source) continue; // Don't self-send. function dispatch(source, name, data) {
if (channel[_name] !== name) continue; for (let i = 0; i < channels.length; ++i) {
if (channel[_closed]) continue; const channel = channels[i];
const go = () => { if (channel === source) continue; // Don't self-send.
if (channel[_closed]) return; if (channel[_name] !== name) continue;
const event = new MessageEvent("message", { if (channel[_closed]) continue;
data: core.deserialize(data), // TODO(bnoordhuis) Cache immutables.
origin: "http://127.0.0.1",
});
setTarget(event, channel);
channel.dispatchEvent(event);
};
defer(go); const go = () => {
} if (channel[_closed]) return;
} const event = new MessageEvent("message", {
data: core.deserialize(data), // TODO(bnoordhuis) Cache immutables.
// Defer to avoid starving the event loop. Not using queueMicrotask() origin: "http://127.0.0.1",
// for that reason: it lets promises make forward progress but can
// still starve other parts of the event loop.
function defer(go) {
setTimeout(go, 1);
}
class BroadcastChannel extends EventTarget {
[_name];
[_closed] = false;
get name() {
return this[_name];
}
constructor(name) {
super();
const prefix = "Failed to construct 'BroadcastChannel'";
webidl.requiredArguments(arguments.length, 1, { prefix });
this[_name] = webidl.converters["DOMString"](name, {
prefix,
context: "Argument 1",
}); });
setTarget(event, channel);
channel.dispatchEvent(event);
};
this[webidl.brand] = webidl.brand; defer(go);
}
}
ArrayPrototypePush(channels, this); // Defer to avoid starving the event loop. Not using queueMicrotask()
// for that reason: it lets promises make forward progress but can
// still starve other parts of the event loop.
function defer(go) {
setTimeout(go, 1);
}
if (rid === null) { class BroadcastChannel extends EventTarget {
// Create the rid immediately, otherwise there is a time window (and a [_name];
// race condition) where messages can get lost, because recv() is async. [_closed] = false;
rid = ops.op_broadcast_subscribe();
recv();
}
}
postMessage(message) { get name() {
webidl.assertBranded(this, BroadcastChannelPrototype); return this[_name];
}
const prefix = "Failed to execute 'postMessage' on 'BroadcastChannel'"; constructor(name) {
webidl.requiredArguments(arguments.length, 1, { prefix }); super();
if (this[_closed]) { const prefix = "Failed to construct 'BroadcastChannel'";
throw new DOMException("Already closed", "InvalidStateError"); webidl.requiredArguments(arguments.length, 1, { prefix });
}
if (typeof message === "function" || typeof message === "symbol") { this[_name] = webidl.converters["DOMString"](name, {
throw new DOMException("Uncloneable value", "DataCloneError"); prefix,
} context: "Argument 1",
});
const data = core.serialize(message); this[webidl.brand] = webidl.brand;
// Send to other listeners in this VM. ArrayPrototypePush(channels, this);
dispatch(this, this[_name], new Uint8Array(data));
// Send to listeners in other VMs. if (rid === null) {
defer(() => { // Create the rid immediately, otherwise there is a time window (and a
if (!this[_closed]) { // race condition) where messages can get lost, because recv() is async.
core.opAsync("op_broadcast_send", rid, this[_name], data); rid = ops.op_broadcast_subscribe();
} recv();
});
}
close() {
webidl.assertBranded(this, BroadcastChannelPrototype);
this[_closed] = true;
const index = ArrayPrototypeIndexOf(channels, this);
if (index === -1) return;
ArrayPrototypeSplice(channels, index, 1);
if (channels.length === 0) {
ops.op_broadcast_unsubscribe(rid);
}
} }
} }
defineEventHandler(BroadcastChannel.prototype, "message"); postMessage(message) {
defineEventHandler(BroadcastChannel.prototype, "messageerror"); webidl.assertBranded(this, BroadcastChannelPrototype);
const BroadcastChannelPrototype = BroadcastChannel.prototype;
window.__bootstrap.broadcastChannel = { BroadcastChannel }; const prefix = "Failed to execute 'postMessage' on 'BroadcastChannel'";
})(this); webidl.requiredArguments(arguments.length, 1, { prefix });
if (this[_closed]) {
throw new DOMException("Already closed", "InvalidStateError");
}
if (typeof message === "function" || typeof message === "symbol") {
throw new DOMException("Uncloneable value", "DataCloneError");
}
const data = core.serialize(message);
// Send to other listeners in this VM.
dispatch(this, this[_name], new Uint8Array(data));
// Send to listeners in other VMs.
defer(() => {
if (!this[_closed]) {
core.opAsync("op_broadcast_send", rid, this[_name], data);
}
});
}
close() {
webidl.assertBranded(this, BroadcastChannelPrototype);
this[_closed] = true;
const index = ArrayPrototypeIndexOf(channels, this);
if (index === -1) return;
ArrayPrototypeSplice(channels, index, 1);
if (channels.length === 0) {
ops.op_broadcast_unsubscribe(rid);
}
}
}
defineEventHandler(BroadcastChannel.prototype, "message");
defineEventHandler(BroadcastChannel.prototype, "messageerror");
const BroadcastChannelPrototype = BroadcastChannel.prototype;
export { BroadcastChannel };

View file

@ -112,7 +112,7 @@ pub fn init<BC: BroadcastChannel + 'static>(
) -> Extension { ) -> Extension {
Extension::builder(env!("CARGO_PKG_NAME")) Extension::builder(env!("CARGO_PKG_NAME"))
.dependencies(vec!["deno_webidl", "deno_web"]) .dependencies(vec!["deno_webidl", "deno_web"])
.js(include_js_files!( .esm(include_js_files!(
prefix "internal:ext/broadcast_channel", prefix "internal:ext/broadcast_channel",
"01_broadcast_channel.js", "01_broadcast_channel.js",
)) ))

530
ext/cache/01_cache.js vendored
View file

@ -1,296 +1,292 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
"use strict";
((window) => { const core = globalThis.Deno.core;
const core = window.__bootstrap.core; import * as webidl from "internal:ext/webidl/00_webidl.js";
const webidl = window.__bootstrap.webidl; const primordials = globalThis.__bootstrap.primordials;
const { const {
Symbol, Symbol,
TypeError, TypeError,
ObjectPrototypeIsPrototypeOf, ObjectPrototypeIsPrototypeOf,
} = window.__bootstrap.primordials; } = primordials;
const { import {
Request, Request,
toInnerResponse, RequestPrototype,
toInnerRequest, toInnerRequest,
} = window.__bootstrap.fetch; } from "internal:ext/fetch/23_request.js";
const { URLPrototype } = window.__bootstrap.url; import { toInnerResponse } from "internal:ext/fetch/23_response.js";
const RequestPrototype = Request.prototype; import { URLPrototype } from "internal:ext/url/00_url.js";
const { getHeader } = window.__bootstrap.headers; import { getHeader } from "internal:ext/fetch/20_headers.js";
const { readableStreamForRid } = window.__bootstrap.streams; import { readableStreamForRid } from "internal:ext/web/06_streams.js";
class CacheStorage { class CacheStorage {
constructor() { constructor() {
webidl.illegalConstructor(); webidl.illegalConstructor();
}
async open(cacheName) {
webidl.assertBranded(this, CacheStoragePrototype);
const prefix = "Failed to execute 'open' on 'CacheStorage'";
webidl.requiredArguments(arguments.length, 1, { prefix });
cacheName = webidl.converters["DOMString"](cacheName, {
prefix,
context: "Argument 1",
});
const cacheId = await core.opAsync("op_cache_storage_open", cacheName);
const cache = webidl.createBranded(Cache);
cache[_id] = cacheId;
return cache;
}
async has(cacheName) {
webidl.assertBranded(this, CacheStoragePrototype);
const prefix = "Failed to execute 'has' on 'CacheStorage'";
webidl.requiredArguments(arguments.length, 1, { prefix });
cacheName = webidl.converters["DOMString"](cacheName, {
prefix,
context: "Argument 1",
});
return await core.opAsync("op_cache_storage_has", cacheName);
}
async delete(cacheName) {
webidl.assertBranded(this, CacheStoragePrototype);
const prefix = "Failed to execute 'delete' on 'CacheStorage'";
webidl.requiredArguments(arguments.length, 1, { prefix });
cacheName = webidl.converters["DOMString"](cacheName, {
prefix,
context: "Argument 1",
});
return await core.opAsync("op_cache_storage_delete", cacheName);
}
}
const _matchAll = Symbol("[[matchAll]]");
const _id = Symbol("id");
class Cache {
/** @type {number} */
[_id];
constructor() {
webidl.illegalConstructor();
}
/** See https://w3c.github.io/ServiceWorker/#dom-cache-put */
async put(request, response) {
webidl.assertBranded(this, CachePrototype);
const prefix = "Failed to execute 'put' on 'Cache'";
webidl.requiredArguments(arguments.length, 2, { prefix });
request = webidl.converters["RequestInfo_DOMString"](request, {
prefix,
context: "Argument 1",
});
response = webidl.converters["Response"](response, {
prefix,
context: "Argument 2",
});
// Step 1.
let innerRequest = null;
// Step 2.
if (ObjectPrototypeIsPrototypeOf(RequestPrototype, request)) {
innerRequest = toInnerRequest(request);
} else {
// Step 3.
innerRequest = toInnerRequest(new Request(request));
}
// Step 4.
const reqUrl = new URL(innerRequest.url());
if (reqUrl.protocol !== "http:" && reqUrl.protocol !== "https:") {
throw new TypeError(
"Request url protocol must be 'http:' or 'https:'",
);
}
if (innerRequest.method !== "GET") {
throw new TypeError("Request method must be GET");
}
// Step 5.
const innerResponse = toInnerResponse(response);
// Step 6.
if (innerResponse.status === 206) {
throw new TypeError("Response status must not be 206");
}
// Step 7.
const varyHeader = getHeader(innerResponse.headerList, "vary");
if (varyHeader) {
const fieldValues = varyHeader.split(",");
for (let i = 0; i < fieldValues.length; ++i) {
const field = fieldValues[i];
if (field.trim() === "*") {
throw new TypeError("Vary header must not contain '*'");
}
}
} }
async open(cacheName) { // Step 8.
webidl.assertBranded(this, CacheStoragePrototype); if (innerResponse.body !== null && innerResponse.body.unusable()) {
const prefix = "Failed to execute 'open' on 'CacheStorage'"; throw new TypeError("Response body is already used");
webidl.requiredArguments(arguments.length, 1, { prefix });
cacheName = webidl.converters["DOMString"](cacheName, {
prefix,
context: "Argument 1",
});
const cacheId = await core.opAsync("op_cache_storage_open", cacheName);
const cache = webidl.createBranded(Cache);
cache[_id] = cacheId;
return cache;
} }
// acquire lock before async op
const reader = innerResponse.body?.stream.getReader();
async has(cacheName) { // Remove fragment from request URL before put.
webidl.assertBranded(this, CacheStoragePrototype); reqUrl.hash = "";
const prefix = "Failed to execute 'has' on 'CacheStorage'";
webidl.requiredArguments(arguments.length, 1, { prefix }); // Step 9-11.
cacheName = webidl.converters["DOMString"](cacheName, { const rid = await core.opAsync(
prefix, "op_cache_put",
context: "Argument 1", {
}); cacheId: this[_id],
return await core.opAsync("op_cache_storage_has", cacheName); requestUrl: reqUrl.toString(),
responseHeaders: innerResponse.headerList,
requestHeaders: innerRequest.headerList,
responseHasBody: innerResponse.body !== null,
responseStatus: innerResponse.status,
responseStatusText: innerResponse.statusMessage,
},
);
if (reader) {
try {
while (true) {
const { value, done } = await reader.read();
if (done) {
await core.shutdown(rid);
break;
}
await core.writeAll(rid, value);
}
} finally {
core.close(rid);
}
} }
// Step 12-19: TODO(@satyarohith): do the insertion in background.
}
async delete(cacheName) { /** See https://w3c.github.io/ServiceWorker/#cache-match */
webidl.assertBranded(this, CacheStoragePrototype); async match(request, options) {
const prefix = "Failed to execute 'delete' on 'CacheStorage'"; webidl.assertBranded(this, CachePrototype);
webidl.requiredArguments(arguments.length, 1, { prefix }); const prefix = "Failed to execute 'match' on 'Cache'";
cacheName = webidl.converters["DOMString"](cacheName, { webidl.requiredArguments(arguments.length, 1, { prefix });
prefix, request = webidl.converters["RequestInfo_DOMString"](request, {
context: "Argument 1", prefix,
}); context: "Argument 1",
return await core.opAsync("op_cache_storage_delete", cacheName); });
const p = await this[_matchAll](request, options);
if (p.length > 0) {
return p[0];
} else {
return undefined;
} }
} }
const _matchAll = Symbol("[[matchAll]]"); /** See https://w3c.github.io/ServiceWorker/#cache-delete */
const _id = Symbol("id"); async delete(request, _options) {
webidl.assertBranded(this, CachePrototype);
const prefix = "Failed to execute 'delete' on 'Cache'";
webidl.requiredArguments(arguments.length, 1, { prefix });
request = webidl.converters["RequestInfo_DOMString"](request, {
prefix,
context: "Argument 1",
});
// Step 1.
let r = null;
// Step 2.
if (ObjectPrototypeIsPrototypeOf(RequestPrototype, request)) {
r = request;
if (request.method !== "GET") {
return false;
}
} else if (
typeof request === "string" ||
ObjectPrototypeIsPrototypeOf(URLPrototype, request)
) {
r = new Request(request);
}
return await core.opAsync("op_cache_delete", {
cacheId: this[_id],
requestUrl: r.url,
});
}
class Cache { /** See https://w3c.github.io/ServiceWorker/#cache-matchall
/** @type {number} */ *
[_id]; * Note: the function is private as we don't want to expose
* this API to the public yet.
constructor() { *
webidl.illegalConstructor(); * The function will return an array of responses.
*/
async [_matchAll](request, _options) {
// Step 1.
let r = null;
// Step 2.
if (ObjectPrototypeIsPrototypeOf(RequestPrototype, request)) {
r = request;
if (request.method !== "GET") {
return [];
}
} else if (
typeof request === "string" ||
ObjectPrototypeIsPrototypeOf(URLPrototype, request)
) {
r = new Request(request);
} }
/** See https://w3c.github.io/ServiceWorker/#dom-cache-put */ // Step 5.
async put(request, response) { const responses = [];
webidl.assertBranded(this, CachePrototype); // Step 5.2
const prefix = "Failed to execute 'put' on 'Cache'"; if (r === null) {
webidl.requiredArguments(arguments.length, 2, { prefix }); // Step 5.3
request = webidl.converters["RequestInfo_DOMString"](request, { // Note: we have to return all responses in the cache when
prefix, // the request is null.
context: "Argument 1", // We deviate from the spec here and return an empty array
}); // as we don't expose matchAll() API.
response = webidl.converters["Response"](response, { return responses;
prefix, } else {
context: "Argument 2", // Remove the fragment from the request URL.
}); const url = new URL(r.url);
// Step 1. url.hash = "";
let innerRequest = null; const innerRequest = toInnerRequest(r);
// Step 2. const matchResult = await core.opAsync(
if (ObjectPrototypeIsPrototypeOf(RequestPrototype, request)) { "op_cache_match",
innerRequest = toInnerRequest(request);
} else {
// Step 3.
innerRequest = toInnerRequest(new Request(request));
}
// Step 4.
const reqUrl = new URL(innerRequest.url());
if (reqUrl.protocol !== "http:" && reqUrl.protocol !== "https:") {
throw new TypeError(
"Request url protocol must be 'http:' or 'https:'",
);
}
if (innerRequest.method !== "GET") {
throw new TypeError("Request method must be GET");
}
// Step 5.
const innerResponse = toInnerResponse(response);
// Step 6.
if (innerResponse.status === 206) {
throw new TypeError("Response status must not be 206");
}
// Step 7.
const varyHeader = getHeader(innerResponse.headerList, "vary");
if (varyHeader) {
const fieldValues = varyHeader.split(",");
for (let i = 0; i < fieldValues.length; ++i) {
const field = fieldValues[i];
if (field.trim() === "*") {
throw new TypeError("Vary header must not contain '*'");
}
}
}
// Step 8.
if (innerResponse.body !== null && innerResponse.body.unusable()) {
throw new TypeError("Response body is already used");
}
// acquire lock before async op
const reader = innerResponse.body?.stream.getReader();
// Remove fragment from request URL before put.
reqUrl.hash = "";
// Step 9-11.
const rid = await core.opAsync(
"op_cache_put",
{ {
cacheId: this[_id], cacheId: this[_id],
requestUrl: reqUrl.toString(), requestUrl: url.toString(),
responseHeaders: innerResponse.headerList,
requestHeaders: innerRequest.headerList, requestHeaders: innerRequest.headerList,
responseHasBody: innerResponse.body !== null,
responseStatus: innerResponse.status,
responseStatusText: innerResponse.statusMessage,
}, },
); );
if (reader) { if (matchResult) {
try { const { 0: meta, 1: responseBodyRid } = matchResult;
while (true) { let body = null;
const { value, done } = await reader.read(); if (responseBodyRid !== null) {
if (done) { body = readableStreamForRid(responseBodyRid);
await core.shutdown(rid);
break;
}
await core.writeAll(rid, value);
}
} finally {
core.close(rid);
} }
} const response = new Response(
// Step 12-19: TODO(@satyarohith): do the insertion in background. body,
}
/** See https://w3c.github.io/ServiceWorker/#cache-match */
async match(request, options) {
webidl.assertBranded(this, CachePrototype);
const prefix = "Failed to execute 'match' on 'Cache'";
webidl.requiredArguments(arguments.length, 1, { prefix });
request = webidl.converters["RequestInfo_DOMString"](request, {
prefix,
context: "Argument 1",
});
const p = await this[_matchAll](request, options);
if (p.length > 0) {
return p[0];
} else {
return undefined;
}
}
/** See https://w3c.github.io/ServiceWorker/#cache-delete */
async delete(request, _options) {
webidl.assertBranded(this, CachePrototype);
const prefix = "Failed to execute 'delete' on 'Cache'";
webidl.requiredArguments(arguments.length, 1, { prefix });
request = webidl.converters["RequestInfo_DOMString"](request, {
prefix,
context: "Argument 1",
});
// Step 1.
let r = null;
// Step 2.
if (ObjectPrototypeIsPrototypeOf(RequestPrototype, request)) {
r = request;
if (request.method !== "GET") {
return false;
}
} else if (
typeof request === "string" ||
ObjectPrototypeIsPrototypeOf(URLPrototype, request)
) {
r = new Request(request);
}
return await core.opAsync("op_cache_delete", {
cacheId: this[_id],
requestUrl: r.url,
});
}
/** See https://w3c.github.io/ServiceWorker/#cache-matchall
*
* Note: the function is private as we don't want to expose
* this API to the public yet.
*
* The function will return an array of responses.
*/
async [_matchAll](request, _options) {
// Step 1.
let r = null;
// Step 2.
if (ObjectPrototypeIsPrototypeOf(RequestPrototype, request)) {
r = request;
if (request.method !== "GET") {
return [];
}
} else if (
typeof request === "string" ||
ObjectPrototypeIsPrototypeOf(URLPrototype, request)
) {
r = new Request(request);
}
// Step 5.
const responses = [];
// Step 5.2
if (r === null) {
// Step 5.3
// Note: we have to return all responses in the cache when
// the request is null.
// We deviate from the spec here and return an empty array
// as we don't expose matchAll() API.
return responses;
} else {
// Remove the fragment from the request URL.
const url = new URL(r.url);
url.hash = "";
const innerRequest = toInnerRequest(r);
const matchResult = await core.opAsync(
"op_cache_match",
{ {
cacheId: this[_id], headers: meta.responseHeaders,
requestUrl: url.toString(), status: meta.responseStatus,
requestHeaders: innerRequest.headerList, statusText: meta.responseStatusText,
}, },
); );
if (matchResult) { responses.push(response);
const { 0: meta, 1: responseBodyRid } = matchResult;
let body = null;
if (responseBodyRid !== null) {
body = readableStreamForRid(responseBodyRid);
}
const response = new Response(
body,
{
headers: meta.responseHeaders,
status: meta.responseStatus,
statusText: meta.responseStatusText,
},
);
responses.push(response);
}
} }
// Step 5.4-5.5: don't apply in this context.
return responses;
} }
// Step 5.4-5.5: don't apply in this context.
return responses;
} }
}
webidl.configurePrototype(CacheStorage); webidl.configurePrototype(CacheStorage);
webidl.configurePrototype(Cache); webidl.configurePrototype(Cache);
const CacheStoragePrototype = CacheStorage.prototype; const CacheStoragePrototype = CacheStorage.prototype;
const CachePrototype = Cache.prototype; const CachePrototype = Cache.prototype;
let cacheStorage; let cacheStorageStorage;
window.__bootstrap.caches = { function cacheStorage() {
CacheStorage, if (!cacheStorageStorage) {
Cache, cacheStorageStorage = webidl.createBranded(CacheStorage);
cacheStorage() { }
if (!cacheStorage) { return cacheStorageStorage;
cacheStorage = webidl.createBranded(CacheStorage); }
}
return cacheStorage; export { Cache, CacheStorage, cacheStorage };
},
};
})(this);

2
ext/cache/lib.rs vendored
View file

@ -27,7 +27,7 @@ pub fn init<CA: Cache + 'static>(
) -> Extension { ) -> Extension {
Extension::builder(env!("CARGO_PKG_NAME")) Extension::builder(env!("CARGO_PKG_NAME"))
.dependencies(vec!["deno_webidl", "deno_web", "deno_url", "deno_fetch"]) .dependencies(vec!["deno_webidl", "deno_web", "deno_url", "deno_fetch"])
.js(include_js_files!( .esm(include_js_files!(
prefix "internal:ext/cache", prefix "internal:ext/cache",
"01_cache.js", "01_cache.js",
)) ))

View file

@ -2,110 +2,107 @@
/// <reference path="../../core/internal.d.ts" /> /// <reference path="../../core/internal.d.ts" />
"use strict"; const primordials = globalThis.__bootstrap.primordials;
const {
RegExp,
StringPrototypeReplace,
ArrayPrototypeJoin,
} = primordials;
((window) => { let noColor = false;
const {
RegExp,
StringPrototypeReplace,
ArrayPrototypeJoin,
} = window.__bootstrap.primordials;
let noColor = false; function setNoColor(value) {
noColor = value;
}
function setNoColor(value) { function getNoColor() {
noColor = value; return noColor;
} }
function getNoColor() { function code(open, close) {
return noColor; return {
} open: `\x1b[${open}m`,
close: `\x1b[${close}m`,
function code(open, close) { regexp: new RegExp(`\\x1b\\[${close}m`, "g"),
return {
open: `\x1b[${open}m`,
close: `\x1b[${close}m`,
regexp: new RegExp(`\\x1b\\[${close}m`, "g"),
};
}
function run(str, code) {
return `${code.open}${
StringPrototypeReplace(str, code.regexp, code.open)
}${code.close}`;
}
function bold(str) {
return run(str, code(1, 22));
}
function italic(str) {
return run(str, code(3, 23));
}
function yellow(str) {
return run(str, code(33, 39));
}
function cyan(str) {
return run(str, code(36, 39));
}
function red(str) {
return run(str, code(31, 39));
}
function green(str) {
return run(str, code(32, 39));
}
function bgRed(str) {
return run(str, code(41, 49));
}
function white(str) {
return run(str, code(37, 39));
}
function gray(str) {
return run(str, code(90, 39));
}
function magenta(str) {
return run(str, code(35, 39));
}
// https://github.com/chalk/ansi-regex/blob/02fa893d619d3da85411acc8fd4e2eea0e95a9d9/index.js
const ANSI_PATTERN = new RegExp(
ArrayPrototypeJoin([
"[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]+)*|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)",
"(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-nq-uy=><~]))",
], "|"),
"g",
);
function stripColor(string) {
return StringPrototypeReplace(string, ANSI_PATTERN, "");
}
function maybeColor(fn) {
return !noColor ? fn : (s) => s;
}
window.__bootstrap.colors = {
bold,
italic,
yellow,
cyan,
red,
green,
bgRed,
white,
gray,
magenta,
stripColor,
maybeColor,
setNoColor,
getNoColor,
}; };
})(this); }
function run(str, code) {
return `${code.open}${
StringPrototypeReplace(str, code.regexp, code.open)
}${code.close}`;
}
function bold(str) {
return run(str, code(1, 22));
}
function italic(str) {
return run(str, code(3, 23));
}
function yellow(str) {
return run(str, code(33, 39));
}
function cyan(str) {
return run(str, code(36, 39));
}
function red(str) {
return run(str, code(31, 39));
}
function green(str) {
return run(str, code(32, 39));
}
function bgRed(str) {
return run(str, code(41, 49));
}
function white(str) {
return run(str, code(37, 39));
}
function gray(str) {
return run(str, code(90, 39));
}
function magenta(str) {
return run(str, code(35, 39));
}
// https://github.com/chalk/ansi-regex/blob/02fa893d619d3da85411acc8fd4e2eea0e95a9d9/index.js
const ANSI_PATTERN = new RegExp(
ArrayPrototypeJoin([
"[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]+)*|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)",
"(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-nq-uy=><~]))",
], "|"),
"g",
);
function stripColor(string) {
return StringPrototypeReplace(string, ANSI_PATTERN, "");
}
function maybeColor(fn) {
return !noColor ? fn : (s) => s;
}
export {
bgRed,
bold,
cyan,
getNoColor,
gray,
green,
italic,
magenta,
maybeColor,
red,
setNoColor,
stripColor,
white,
yellow,
};

File diff suppressed because it is too large Load diff

View file

@ -3,14 +3,10 @@
/// <reference no-default-lib="true" /> /// <reference no-default-lib="true" />
/// <reference lib="esnext" /> /// <reference lib="esnext" />
declare namespace globalThis { declare module "internal:ext/console/02_console.js" {
declare namespace __bootstrap { function createFilteredInspectProxy<TObject>(params: {
declare namespace console { object: TObject;
declare function createFilteredInspectProxy<TObject>(params: { keys: (keyof TObject)[];
object: TObject; evaluate: boolean;
keys: (keyof TObject)[]; }): Record<string, unknown>;
evaluate: boolean;
}): Record<string, unknown>;
}
}
} }

View file

@ -6,7 +6,7 @@ use std::path::PathBuf;
pub fn init() -> Extension { pub fn init() -> Extension {
Extension::builder(env!("CARGO_PKG_NAME")) Extension::builder(env!("CARGO_PKG_NAME"))
.js(include_js_files!( .esm(include_js_files!(
prefix "internal:ext/console", prefix "internal:ext/console",
"01_colors.js", "01_colors.js",
"02_console.js", "02_console.js",

File diff suppressed because it is too large Load diff

View file

@ -4,484 +4,481 @@
/// <reference path="../../core/lib.deno_core.d.ts" /> /// <reference path="../../core/lib.deno_core.d.ts" />
/// <reference path="../webidl/internal.d.ts" /> /// <reference path="../webidl/internal.d.ts" />
"use strict"; const primordials = globalThis.__bootstrap.primordials;
import * as webidl from "internal:ext/webidl/00_webidl.js";
import { CryptoKey } from "internal:ext/crypto/00_crypto.js";
const {
ArrayBufferIsView,
ArrayBufferPrototype,
ObjectPrototypeIsPrototypeOf,
SafeArrayIterator,
} = primordials;
((window) => { webidl.converters.AlgorithmIdentifier = (V, opts) => {
const webidl = window.__bootstrap.webidl; // Union for (object or DOMString)
const { CryptoKey } = window.__bootstrap.crypto; if (webidl.type(V) == "Object") {
const { return webidl.converters.object(V, opts);
ArrayBufferIsView, }
ArrayBufferPrototype, return webidl.converters.DOMString(V, opts);
ObjectPrototypeIsPrototypeOf, };
SafeArrayIterator,
} = window.__bootstrap.primordials;
webidl.converters.AlgorithmIdentifier = (V, opts) => { webidl.converters["BufferSource or JsonWebKey"] = (V, opts) => {
// Union for (object or DOMString) // Union for (BufferSource or JsonWebKey)
if (webidl.type(V) == "Object") { if (
return webidl.converters.object(V, opts); ArrayBufferIsView(V) ||
} ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, V)
return webidl.converters.DOMString(V, opts); ) {
}; return webidl.converters.BufferSource(V, opts);
}
return webidl.converters.JsonWebKey(V, opts);
};
webidl.converters["BufferSource or JsonWebKey"] = (V, opts) => { webidl.converters.KeyType = webidl.createEnumConverter("KeyType", [
// Union for (BufferSource or JsonWebKey) "public",
if ( "private",
ArrayBufferIsView(V) || "secret",
ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, V) ]);
) {
return webidl.converters.BufferSource(V, opts);
}
return webidl.converters.JsonWebKey(V, opts);
};
webidl.converters.KeyType = webidl.createEnumConverter("KeyType", [ webidl.converters.KeyFormat = webidl.createEnumConverter("KeyFormat", [
"public", "raw",
"private", "pkcs8",
"secret", "spki",
]); "jwk",
]);
webidl.converters.KeyFormat = webidl.createEnumConverter("KeyFormat", [ webidl.converters.KeyUsage = webidl.createEnumConverter("KeyUsage", [
"raw", "encrypt",
"pkcs8", "decrypt",
"spki", "sign",
"jwk", "verify",
]); "deriveKey",
"deriveBits",
"wrapKey",
"unwrapKey",
]);
webidl.converters.KeyUsage = webidl.createEnumConverter("KeyUsage", [ webidl.converters["sequence<KeyUsage>"] = webidl.createSequenceConverter(
"encrypt", webidl.converters.KeyUsage,
"decrypt", );
"sign",
"verify",
"deriveKey",
"deriveBits",
"wrapKey",
"unwrapKey",
]);
webidl.converters["sequence<KeyUsage>"] = webidl.createSequenceConverter( webidl.converters.HashAlgorithmIdentifier =
webidl.converters.KeyUsage, webidl.converters.AlgorithmIdentifier;
);
webidl.converters.HashAlgorithmIdentifier = /** @type {webidl.Dictionary} */
webidl.converters.AlgorithmIdentifier; const dictAlgorithm = [{
key: "name",
converter: webidl.converters.DOMString,
required: true,
}];
/** @type {__bootstrap.webidl.Dictionary} */ webidl.converters.Algorithm = webidl
const dictAlgorithm = [{ .createDictionaryConverter("Algorithm", dictAlgorithm);
key: "name",
converter: webidl.converters.DOMString, webidl.converters.BigInteger = webidl.converters.Uint8Array;
/** @type {webidl.Dictionary} */
const dictRsaKeyGenParams = [
...new SafeArrayIterator(dictAlgorithm),
{
key: "modulusLength",
converter: (V, opts) =>
webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }),
required: true, required: true,
}]; },
{
key: "publicExponent",
converter: webidl.converters.BigInteger,
required: true,
},
];
webidl.converters.Algorithm = webidl webidl.converters.RsaKeyGenParams = webidl
.createDictionaryConverter("Algorithm", dictAlgorithm); .createDictionaryConverter("RsaKeyGenParams", dictRsaKeyGenParams);
webidl.converters.BigInteger = webidl.converters.Uint8Array; const dictRsaHashedKeyGenParams = [
...new SafeArrayIterator(dictRsaKeyGenParams),
{
key: "hash",
converter: webidl.converters.HashAlgorithmIdentifier,
required: true,
},
];
/** @type {__bootstrap.webidl.Dictionary} */ webidl.converters.RsaHashedKeyGenParams = webidl.createDictionaryConverter(
const dictRsaKeyGenParams = [ "RsaHashedKeyGenParams",
...new SafeArrayIterator(dictAlgorithm), dictRsaHashedKeyGenParams,
{ );
key: "modulusLength",
converter: (V, opts) =>
webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }),
required: true,
},
{
key: "publicExponent",
converter: webidl.converters.BigInteger,
required: true,
},
];
webidl.converters.RsaKeyGenParams = webidl const dictRsaHashedImportParams = [
.createDictionaryConverter("RsaKeyGenParams", dictRsaKeyGenParams); ...new SafeArrayIterator(dictAlgorithm),
{
key: "hash",
converter: webidl.converters.HashAlgorithmIdentifier,
required: true,
},
];
const dictRsaHashedKeyGenParams = [ webidl.converters.RsaHashedImportParams = webidl.createDictionaryConverter(
...new SafeArrayIterator(dictRsaKeyGenParams), "RsaHashedImportParams",
{ dictRsaHashedImportParams,
key: "hash", );
converter: webidl.converters.HashAlgorithmIdentifier,
required: true,
},
];
webidl.converters.RsaHashedKeyGenParams = webidl.createDictionaryConverter( webidl.converters.NamedCurve = webidl.converters.DOMString;
"RsaHashedKeyGenParams",
dictRsaHashedKeyGenParams, const dictEcKeyImportParams = [
...new SafeArrayIterator(dictAlgorithm),
{
key: "namedCurve",
converter: webidl.converters.NamedCurve,
required: true,
},
];
webidl.converters.EcKeyImportParams = webidl.createDictionaryConverter(
"EcKeyImportParams",
dictEcKeyImportParams,
);
const dictEcKeyGenParams = [
...new SafeArrayIterator(dictAlgorithm),
{
key: "namedCurve",
converter: webidl.converters.NamedCurve,
required: true,
},
];
webidl.converters.EcKeyGenParams = webidl
.createDictionaryConverter("EcKeyGenParams", dictEcKeyGenParams);
const dictAesKeyGenParams = [
...new SafeArrayIterator(dictAlgorithm),
{
key: "length",
converter: (V, opts) =>
webidl.converters["unsigned short"](V, { ...opts, enforceRange: true }),
required: true,
},
];
webidl.converters.AesKeyGenParams = webidl
.createDictionaryConverter("AesKeyGenParams", dictAesKeyGenParams);
const dictHmacKeyGenParams = [
...new SafeArrayIterator(dictAlgorithm),
{
key: "hash",
converter: webidl.converters.HashAlgorithmIdentifier,
required: true,
},
{
key: "length",
converter: (V, opts) =>
webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }),
},
];
webidl.converters.HmacKeyGenParams = webidl
.createDictionaryConverter("HmacKeyGenParams", dictHmacKeyGenParams);
const dictRsaPssParams = [
...new SafeArrayIterator(dictAlgorithm),
{
key: "saltLength",
converter: (V, opts) =>
webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }),
required: true,
},
];
webidl.converters.RsaPssParams = webidl
.createDictionaryConverter("RsaPssParams", dictRsaPssParams);
const dictRsaOaepParams = [
...new SafeArrayIterator(dictAlgorithm),
{
key: "label",
converter: webidl.converters["BufferSource"],
},
];
webidl.converters.RsaOaepParams = webidl
.createDictionaryConverter("RsaOaepParams", dictRsaOaepParams);
const dictEcdsaParams = [
...new SafeArrayIterator(dictAlgorithm),
{
key: "hash",
converter: webidl.converters.HashAlgorithmIdentifier,
required: true,
},
];
webidl.converters["EcdsaParams"] = webidl
.createDictionaryConverter("EcdsaParams", dictEcdsaParams);
const dictHmacImportParams = [
...new SafeArrayIterator(dictAlgorithm),
{
key: "hash",
converter: webidl.converters.HashAlgorithmIdentifier,
required: true,
},
{
key: "length",
converter: (V, opts) =>
webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }),
},
];
webidl.converters.HmacImportParams = webidl
.createDictionaryConverter("HmacImportParams", dictHmacImportParams);
const dictRsaOtherPrimesInfo = [
{
key: "r",
converter: webidl.converters["DOMString"],
},
{
key: "d",
converter: webidl.converters["DOMString"],
},
{
key: "t",
converter: webidl.converters["DOMString"],
},
];
webidl.converters.RsaOtherPrimesInfo = webidl.createDictionaryConverter(
"RsaOtherPrimesInfo",
dictRsaOtherPrimesInfo,
);
webidl.converters["sequence<RsaOtherPrimesInfo>"] = webidl
.createSequenceConverter(
webidl.converters.RsaOtherPrimesInfo,
); );
const dictRsaHashedImportParams = [ const dictJsonWebKey = [
...new SafeArrayIterator(dictAlgorithm), // Sections 4.2 and 4.3 of RFC7517.
{ // https://datatracker.ietf.org/doc/html/rfc7517#section-4
key: "hash", {
converter: webidl.converters.HashAlgorithmIdentifier, key: "kty",
required: true, converter: webidl.converters["DOMString"],
}, },
]; {
key: "use",
converter: webidl.converters["DOMString"],
},
{
key: "key_ops",
converter: webidl.converters["sequence<DOMString>"],
},
{
key: "alg",
converter: webidl.converters["DOMString"],
},
// JSON Web Key Parameters Registration
{
key: "ext",
converter: webidl.converters["boolean"],
},
// Section 6 of RFC7518 JSON Web Algorithms
// https://datatracker.ietf.org/doc/html/rfc7518#section-6
{
key: "crv",
converter: webidl.converters["DOMString"],
},
{
key: "x",
converter: webidl.converters["DOMString"],
},
{
key: "y",
converter: webidl.converters["DOMString"],
},
{
key: "d",
converter: webidl.converters["DOMString"],
},
{
key: "n",
converter: webidl.converters["DOMString"],
},
{
key: "e",
converter: webidl.converters["DOMString"],
},
{
key: "p",
converter: webidl.converters["DOMString"],
},
{
key: "q",
converter: webidl.converters["DOMString"],
},
{
key: "dp",
converter: webidl.converters["DOMString"],
},
{
key: "dq",
converter: webidl.converters["DOMString"],
},
{
key: "qi",
converter: webidl.converters["DOMString"],
},
{
key: "oth",
converter: webidl.converters["sequence<RsaOtherPrimesInfo>"],
},
{
key: "k",
converter: webidl.converters["DOMString"],
},
];
webidl.converters.RsaHashedImportParams = webidl.createDictionaryConverter( webidl.converters.JsonWebKey = webidl.createDictionaryConverter(
"RsaHashedImportParams", "JsonWebKey",
dictRsaHashedImportParams, dictJsonWebKey,
); );
webidl.converters.NamedCurve = webidl.converters.DOMString; const dictHkdfParams = [
...new SafeArrayIterator(dictAlgorithm),
{
key: "hash",
converter: webidl.converters.HashAlgorithmIdentifier,
required: true,
},
{
key: "salt",
converter: webidl.converters["BufferSource"],
required: true,
},
{
key: "info",
converter: webidl.converters["BufferSource"],
required: true,
},
];
const dictEcKeyImportParams = [ webidl.converters.HkdfParams = webidl
...new SafeArrayIterator(dictAlgorithm), .createDictionaryConverter("HkdfParams", dictHkdfParams);
{
key: "namedCurve",
converter: webidl.converters.NamedCurve,
required: true,
},
];
webidl.converters.EcKeyImportParams = webidl.createDictionaryConverter( const dictPbkdf2Params = [
"EcKeyImportParams", ...new SafeArrayIterator(dictAlgorithm),
dictEcKeyImportParams, {
); key: "hash",
converter: webidl.converters.HashAlgorithmIdentifier,
required: true,
},
{
key: "iterations",
converter: (V, opts) =>
webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }),
required: true,
},
{
key: "salt",
converter: webidl.converters["BufferSource"],
required: true,
},
];
const dictEcKeyGenParams = [ webidl.converters.Pbkdf2Params = webidl
...new SafeArrayIterator(dictAlgorithm), .createDictionaryConverter("Pbkdf2Params", dictPbkdf2Params);
{
key: "namedCurve",
converter: webidl.converters.NamedCurve,
required: true,
},
];
webidl.converters.EcKeyGenParams = webidl const dictAesDerivedKeyParams = [
.createDictionaryConverter("EcKeyGenParams", dictEcKeyGenParams); ...new SafeArrayIterator(dictAlgorithm),
{
key: "length",
converter: (V, opts) =>
webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }),
required: true,
},
];
const dictAesKeyGenParams = [ const dictAesCbcParams = [
...new SafeArrayIterator(dictAlgorithm), ...new SafeArrayIterator(dictAlgorithm),
{ {
key: "length", key: "iv",
converter: (V, opts) => converter: webidl.converters["BufferSource"],
webidl.converters["unsigned short"](V, { ...opts, enforceRange: true }), required: true,
required: true, },
}, ];
];
webidl.converters.AesKeyGenParams = webidl const dictAesGcmParams = [
.createDictionaryConverter("AesKeyGenParams", dictAesKeyGenParams); ...new SafeArrayIterator(dictAlgorithm),
{
key: "iv",
converter: webidl.converters["BufferSource"],
required: true,
},
{
key: "tagLength",
converter: (V, opts) =>
webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }),
},
{
key: "additionalData",
converter: webidl.converters["BufferSource"],
},
];
const dictHmacKeyGenParams = [ const dictAesCtrParams = [
...new SafeArrayIterator(dictAlgorithm), ...new SafeArrayIterator(dictAlgorithm),
{ {
key: "hash", key: "counter",
converter: webidl.converters.HashAlgorithmIdentifier, converter: webidl.converters["BufferSource"],
required: true, required: true,
}, },
{ {
key: "length", key: "length",
converter: (V, opts) => converter: (V, opts) =>
webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }), webidl.converters["unsigned short"](V, { ...opts, enforceRange: true }),
}, required: true,
]; },
];
webidl.converters.HmacKeyGenParams = webidl webidl.converters.AesDerivedKeyParams = webidl
.createDictionaryConverter("HmacKeyGenParams", dictHmacKeyGenParams); .createDictionaryConverter("AesDerivedKeyParams", dictAesDerivedKeyParams);
const dictRsaPssParams = [ webidl.converters.AesCbcParams = webidl
...new SafeArrayIterator(dictAlgorithm), .createDictionaryConverter("AesCbcParams", dictAesCbcParams);
{
key: "saltLength",
converter: (V, opts) =>
webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }),
required: true,
},
];
webidl.converters.RsaPssParams = webidl webidl.converters.AesGcmParams = webidl
.createDictionaryConverter("RsaPssParams", dictRsaPssParams); .createDictionaryConverter("AesGcmParams", dictAesGcmParams);
const dictRsaOaepParams = [ webidl.converters.AesCtrParams = webidl
...new SafeArrayIterator(dictAlgorithm), .createDictionaryConverter("AesCtrParams", dictAesCtrParams);
{
key: "label",
converter: webidl.converters["BufferSource"],
},
];
webidl.converters.RsaOaepParams = webidl webidl.converters.CryptoKey = webidl.createInterfaceConverter(
.createDictionaryConverter("RsaOaepParams", dictRsaOaepParams); "CryptoKey",
CryptoKey.prototype,
);
const dictEcdsaParams = [ const dictCryptoKeyPair = [
...new SafeArrayIterator(dictAlgorithm), {
{ key: "publicKey",
key: "hash", converter: webidl.converters.CryptoKey,
converter: webidl.converters.HashAlgorithmIdentifier, },
required: true, {
}, key: "privateKey",
]; converter: webidl.converters.CryptoKey,
},
];
webidl.converters["EcdsaParams"] = webidl webidl.converters.CryptoKeyPair = webidl
.createDictionaryConverter("EcdsaParams", dictEcdsaParams); .createDictionaryConverter("CryptoKeyPair", dictCryptoKeyPair);
const dictHmacImportParams = [ const dictEcdhKeyDeriveParams = [
...new SafeArrayIterator(dictAlgorithm), ...new SafeArrayIterator(dictAlgorithm),
{ {
key: "hash", key: "public",
converter: webidl.converters.HashAlgorithmIdentifier, converter: webidl.converters.CryptoKey,
required: true, required: true,
}, },
{ ];
key: "length",
converter: (V, opts) =>
webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }),
},
];
webidl.converters.HmacImportParams = webidl webidl.converters.EcdhKeyDeriveParams = webidl
.createDictionaryConverter("HmacImportParams", dictHmacImportParams); .createDictionaryConverter("EcdhKeyDeriveParams", dictEcdhKeyDeriveParams);
const dictRsaOtherPrimesInfo = [
{
key: "r",
converter: webidl.converters["DOMString"],
},
{
key: "d",
converter: webidl.converters["DOMString"],
},
{
key: "t",
converter: webidl.converters["DOMString"],
},
];
webidl.converters.RsaOtherPrimesInfo = webidl.createDictionaryConverter(
"RsaOtherPrimesInfo",
dictRsaOtherPrimesInfo,
);
webidl.converters["sequence<RsaOtherPrimesInfo>"] = webidl
.createSequenceConverter(
webidl.converters.RsaOtherPrimesInfo,
);
const dictJsonWebKey = [
// Sections 4.2 and 4.3 of RFC7517.
// https://datatracker.ietf.org/doc/html/rfc7517#section-4
{
key: "kty",
converter: webidl.converters["DOMString"],
},
{
key: "use",
converter: webidl.converters["DOMString"],
},
{
key: "key_ops",
converter: webidl.converters["sequence<DOMString>"],
},
{
key: "alg",
converter: webidl.converters["DOMString"],
},
// JSON Web Key Parameters Registration
{
key: "ext",
converter: webidl.converters["boolean"],
},
// Section 6 of RFC7518 JSON Web Algorithms
// https://datatracker.ietf.org/doc/html/rfc7518#section-6
{
key: "crv",
converter: webidl.converters["DOMString"],
},
{
key: "x",
converter: webidl.converters["DOMString"],
},
{
key: "y",
converter: webidl.converters["DOMString"],
},
{
key: "d",
converter: webidl.converters["DOMString"],
},
{
key: "n",
converter: webidl.converters["DOMString"],
},
{
key: "e",
converter: webidl.converters["DOMString"],
},
{
key: "p",
converter: webidl.converters["DOMString"],
},
{
key: "q",
converter: webidl.converters["DOMString"],
},
{
key: "dp",
converter: webidl.converters["DOMString"],
},
{
key: "dq",
converter: webidl.converters["DOMString"],
},
{
key: "qi",
converter: webidl.converters["DOMString"],
},
{
key: "oth",
converter: webidl.converters["sequence<RsaOtherPrimesInfo>"],
},
{
key: "k",
converter: webidl.converters["DOMString"],
},
];
webidl.converters.JsonWebKey = webidl.createDictionaryConverter(
"JsonWebKey",
dictJsonWebKey,
);
const dictHkdfParams = [
...new SafeArrayIterator(dictAlgorithm),
{
key: "hash",
converter: webidl.converters.HashAlgorithmIdentifier,
required: true,
},
{
key: "salt",
converter: webidl.converters["BufferSource"],
required: true,
},
{
key: "info",
converter: webidl.converters["BufferSource"],
required: true,
},
];
webidl.converters.HkdfParams = webidl
.createDictionaryConverter("HkdfParams", dictHkdfParams);
const dictPbkdf2Params = [
...new SafeArrayIterator(dictAlgorithm),
{
key: "hash",
converter: webidl.converters.HashAlgorithmIdentifier,
required: true,
},
{
key: "iterations",
converter: (V, opts) =>
webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }),
required: true,
},
{
key: "salt",
converter: webidl.converters["BufferSource"],
required: true,
},
];
webidl.converters.Pbkdf2Params = webidl
.createDictionaryConverter("Pbkdf2Params", dictPbkdf2Params);
const dictAesDerivedKeyParams = [
...new SafeArrayIterator(dictAlgorithm),
{
key: "length",
converter: (V, opts) =>
webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }),
required: true,
},
];
const dictAesCbcParams = [
...new SafeArrayIterator(dictAlgorithm),
{
key: "iv",
converter: webidl.converters["BufferSource"],
required: true,
},
];
const dictAesGcmParams = [
...new SafeArrayIterator(dictAlgorithm),
{
key: "iv",
converter: webidl.converters["BufferSource"],
required: true,
},
{
key: "tagLength",
converter: (V, opts) =>
webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }),
},
{
key: "additionalData",
converter: webidl.converters["BufferSource"],
},
];
const dictAesCtrParams = [
...new SafeArrayIterator(dictAlgorithm),
{
key: "counter",
converter: webidl.converters["BufferSource"],
required: true,
},
{
key: "length",
converter: (V, opts) =>
webidl.converters["unsigned short"](V, { ...opts, enforceRange: true }),
required: true,
},
];
webidl.converters.AesDerivedKeyParams = webidl
.createDictionaryConverter("AesDerivedKeyParams", dictAesDerivedKeyParams);
webidl.converters.AesCbcParams = webidl
.createDictionaryConverter("AesCbcParams", dictAesCbcParams);
webidl.converters.AesGcmParams = webidl
.createDictionaryConverter("AesGcmParams", dictAesGcmParams);
webidl.converters.AesCtrParams = webidl
.createDictionaryConverter("AesCtrParams", dictAesCtrParams);
webidl.converters.CryptoKey = webidl.createInterfaceConverter(
"CryptoKey",
CryptoKey.prototype,
);
const dictCryptoKeyPair = [
{
key: "publicKey",
converter: webidl.converters.CryptoKey,
},
{
key: "privateKey",
converter: webidl.converters.CryptoKey,
},
];
webidl.converters.CryptoKeyPair = webidl
.createDictionaryConverter("CryptoKeyPair", dictCryptoKeyPair);
const dictEcdhKeyDeriveParams = [
...new SafeArrayIterator(dictAlgorithm),
{
key: "public",
converter: webidl.converters.CryptoKey,
required: true,
},
];
webidl.converters.EcdhKeyDeriveParams = webidl
.createDictionaryConverter("EcdhKeyDeriveParams", dictEcdhKeyDeriveParams);
})(this);

View file

@ -75,7 +75,7 @@ use crate::shared::RawKeyData;
pub fn init(maybe_seed: Option<u64>) -> Extension { pub fn init(maybe_seed: Option<u64>) -> Extension {
Extension::builder(env!("CARGO_PKG_NAME")) Extension::builder(env!("CARGO_PKG_NAME"))
.dependencies(vec!["deno_webidl", "deno_web"]) .dependencies(vec!["deno_webidl", "deno_web"])
.js(include_js_files!( .esm(include_js_files!(
prefix "internal:ext/crypto", prefix "internal:ext/crypto",
"00_crypto.js", "00_crypto.js",
"01_webidl.js", "01_webidl.js",

View file

@ -1,22 +0,0 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
"use strict";
((window) => {
const { TypeError } = window.__bootstrap.primordials;
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);
}
}
window.__bootstrap.fetchUtil = {
requiredArguments,
};
})(this);

View file

@ -8,97 +8,369 @@
/// <reference path="../web/06_streams_types.d.ts" /> /// <reference path="../web/06_streams_types.d.ts" />
/// <reference path="./lib.deno_fetch.d.ts" /> /// <reference path="./lib.deno_fetch.d.ts" />
/// <reference lib="esnext" /> /// <reference lib="esnext" />
"use strict";
((window) => { import * as webidl from "internal:ext/webidl/00_webidl.js";
const webidl = window.__bootstrap.webidl; import {
const { byteLowerCase,
HTTP_TAB_OR_SPACE_PREFIX_RE, collectHttpQuotedString,
HTTP_TAB_OR_SPACE_SUFFIX_RE, collectSequenceOfCodepoints,
HTTP_TOKEN_CODE_POINT_RE, HTTP_TAB_OR_SPACE_PREFIX_RE,
byteLowerCase, HTTP_TAB_OR_SPACE_SUFFIX_RE,
collectSequenceOfCodepoints, HTTP_TOKEN_CODE_POINT_RE,
collectHttpQuotedString, httpTrim,
httpTrim, } from "internal:ext/web/00_infra.js";
} = window.__bootstrap.infra; const primordials = globalThis.__bootstrap.primordials;
const { const {
ArrayIsArray, ArrayIsArray,
ArrayPrototypeMap, ArrayPrototypeMap,
ArrayPrototypePush, ArrayPrototypePush,
ArrayPrototypeSort, ArrayPrototypeSort,
ArrayPrototypeJoin, ArrayPrototypeJoin,
ArrayPrototypeSplice, ArrayPrototypeSplice,
ArrayPrototypeFilter, ArrayPrototypeFilter,
ObjectPrototypeHasOwnProperty, ObjectPrototypeHasOwnProperty,
ObjectEntries, ObjectEntries,
RegExpPrototypeTest, RegExpPrototypeTest,
SafeArrayIterator, SafeArrayIterator,
Symbol, Symbol,
SymbolFor, SymbolFor,
SymbolIterator, SymbolIterator,
StringPrototypeReplaceAll, StringPrototypeReplaceAll,
TypeError, TypeError,
} = window.__bootstrap.primordials; } = primordials;
const _headerList = Symbol("header list"); const _headerList = Symbol("header list");
const _iterableHeaders = Symbol("iterable headers"); const _iterableHeaders = Symbol("iterable headers");
const _guard = Symbol("guard"); const _guard = Symbol("guard");
/** /**
* @typedef Header * @typedef Header
* @type {[string, string]} * @type {[string, string]}
*/ */
/** /**
* @typedef HeaderList * @typedef HeaderList
* @type {Header[]} * @type {Header[]}
*/ */
/** /**
* @param {string} potentialValue * @param {string} potentialValue
* @returns {string} * @returns {string}
*/ */
function normalizeHeaderValue(potentialValue) { function normalizeHeaderValue(potentialValue) {
return httpTrim(potentialValue); return httpTrim(potentialValue);
}
/**
* @param {Headers} headers
* @param {HeadersInit} object
*/
function fillHeaders(headers, object) {
if (ArrayIsArray(object)) {
for (let i = 0; i < object.length; ++i) {
const header = object[i];
if (header.length !== 2) {
throw new TypeError(
`Invalid header. Length must be 2, but is ${header.length}`,
);
}
appendHeader(headers, header[0], header[1]);
}
} else {
for (const key in object) {
if (!ObjectPrototypeHasOwnProperty(object, key)) {
continue;
}
appendHeader(headers, key, object[key]);
}
}
}
// Regex matching illegal chars in a header value
// deno-lint-ignore no-control-regex
const ILLEGAL_VALUE_CHARS = /[\x00\x0A\x0D]/;
/**
* https://fetch.spec.whatwg.org/#concept-headers-append
* @param {Headers} headers
* @param {string} name
* @param {string} value
*/
function appendHeader(headers, name, value) {
// 1.
value = normalizeHeaderValue(value);
// 2.
if (!RegExpPrototypeTest(HTTP_TOKEN_CODE_POINT_RE, name)) {
throw new TypeError("Header name is not valid.");
}
if (RegExpPrototypeTest(ILLEGAL_VALUE_CHARS, value)) {
throw new TypeError("Header value is not valid.");
}
// 3.
if (headers[_guard] == "immutable") {
throw new TypeError("Headers are immutable.");
}
// 7.
const list = headers[_headerList];
const lowercaseName = byteLowerCase(name);
for (let i = 0; i < list.length; i++) {
if (byteLowerCase(list[i][0]) === lowercaseName) {
name = list[i][0];
break;
}
}
ArrayPrototypePush(list, [name, value]);
}
/**
* https://fetch.spec.whatwg.org/#concept-header-list-get
* @param {HeaderList} list
* @param {string} name
*/
function getHeader(list, name) {
const lowercaseName = byteLowerCase(name);
const entries = ArrayPrototypeMap(
ArrayPrototypeFilter(
list,
(entry) => byteLowerCase(entry[0]) === lowercaseName,
),
(entry) => entry[1],
);
if (entries.length === 0) {
return null;
} else {
return ArrayPrototypeJoin(entries, "\x2C\x20");
}
}
/**
* https://fetch.spec.whatwg.org/#concept-header-list-get-decode-split
* @param {HeaderList} list
* @param {string} name
* @returns {string[] | null}
*/
function getDecodeSplitHeader(list, name) {
const initialValue = getHeader(list, name);
if (initialValue === null) return null;
const input = initialValue;
let position = 0;
const values = [];
let value = "";
while (position < initialValue.length) {
// 7.1. collect up to " or ,
const res = collectSequenceOfCodepoints(
initialValue,
position,
(c) => c !== "\u0022" && c !== "\u002C",
);
value += res.result;
position = res.position;
if (position < initialValue.length) {
if (input[position] === "\u0022") {
const res = collectHttpQuotedString(input, position, false);
value += res.result;
position = res.position;
if (position < initialValue.length) {
continue;
}
} else {
if (input[position] !== "\u002C") throw new TypeError("Unreachable");
position += 1;
}
}
value = StringPrototypeReplaceAll(value, HTTP_TAB_OR_SPACE_PREFIX_RE, "");
value = StringPrototypeReplaceAll(value, HTTP_TAB_OR_SPACE_SUFFIX_RE, "");
ArrayPrototypePush(values, value);
value = "";
}
return values;
}
class Headers {
/** @type {HeaderList} */
[_headerList] = [];
/** @type {"immutable" | "request" | "request-no-cors" | "response" | "none"} */
[_guard];
get [_iterableHeaders]() {
const list = this[_headerList];
// The order of steps are not similar to the ones suggested by the
// spec but produce the same result.
const headers = {};
const cookies = [];
for (let i = 0; i < list.length; ++i) {
const entry = list[i];
const name = byteLowerCase(entry[0]);
const value = entry[1];
if (value === null) throw new TypeError("Unreachable");
// The following if statement is not spec compliant.
// `set-cookie` is the only header that can not be concatenated,
// so must be given to the user as multiple headers.
// The else block of the if statement is spec compliant again.
if (name === "set-cookie") {
ArrayPrototypePush(cookies, [name, value]);
} else {
// The following code has the same behaviour as getHeader()
// at the end of loop. But it avoids looping through the entire
// list to combine multiple values with same header name. It
// instead gradually combines them as they are found.
let header = headers[name];
if (header && header.length > 0) {
header += "\x2C\x20" + value;
} else {
header = value;
}
headers[name] = header;
}
}
return ArrayPrototypeSort(
[
...new SafeArrayIterator(ObjectEntries(headers)),
...new SafeArrayIterator(cookies),
],
(a, b) => {
const akey = a[0];
const bkey = b[0];
if (akey > bkey) return 1;
if (akey < bkey) return -1;
return 0;
},
);
}
/** @param {HeadersInit} [init] */
constructor(init = undefined) {
const prefix = "Failed to construct 'Headers'";
if (init !== undefined) {
init = webidl.converters["HeadersInit"](init, {
prefix,
context: "Argument 1",
});
}
this[webidl.brand] = webidl.brand;
this[_guard] = "none";
if (init !== undefined) {
fillHeaders(this, init);
}
} }
/** /**
* @param {Headers} headers * @param {string} name
* @param {HeadersInit} object * @param {string} value
*/ */
function fillHeaders(headers, object) { append(name, value) {
if (ArrayIsArray(object)) { webidl.assertBranded(this, HeadersPrototype);
for (let i = 0; i < object.length; ++i) { const prefix = "Failed to execute 'append' on 'Headers'";
const header = object[i]; webidl.requiredArguments(arguments.length, 2, { prefix });
if (header.length !== 2) { name = webidl.converters["ByteString"](name, {
throw new TypeError( prefix,
`Invalid header. Length must be 2, but is ${header.length}`, context: "Argument 1",
); });
} value = webidl.converters["ByteString"](value, {
appendHeader(headers, header[0], header[1]); prefix,
} context: "Argument 2",
} else { });
for (const key in object) { appendHeader(this, name, value);
if (!ObjectPrototypeHasOwnProperty(object, key)) { }
continue;
} /**
appendHeader(headers, key, object[key]); * @param {string} name
*/
delete(name) {
const prefix = "Failed to execute 'delete' on 'Headers'";
webidl.requiredArguments(arguments.length, 1, { prefix });
name = webidl.converters["ByteString"](name, {
prefix,
context: "Argument 1",
});
if (!RegExpPrototypeTest(HTTP_TOKEN_CODE_POINT_RE, name)) {
throw new TypeError("Header name is not valid.");
}
if (this[_guard] == "immutable") {
throw new TypeError("Headers are immutable.");
}
const list = this[_headerList];
const lowercaseName = byteLowerCase(name);
for (let i = 0; i < list.length; i++) {
if (byteLowerCase(list[i][0]) === lowercaseName) {
ArrayPrototypeSplice(list, i, 1);
i--;
} }
} }
} }
// Regex matching illegal chars in a header value /**
// deno-lint-ignore no-control-regex * @param {string} name
const ILLEGAL_VALUE_CHARS = /[\x00\x0A\x0D]/; */
get(name) {
const prefix = "Failed to execute 'get' on 'Headers'";
webidl.requiredArguments(arguments.length, 1, { prefix });
name = webidl.converters["ByteString"](name, {
prefix,
context: "Argument 1",
});
if (!RegExpPrototypeTest(HTTP_TOKEN_CODE_POINT_RE, name)) {
throw new TypeError("Header name is not valid.");
}
const list = this[_headerList];
return getHeader(list, name);
}
/**
* @param {string} name
*/
has(name) {
const prefix = "Failed to execute 'has' on 'Headers'";
webidl.requiredArguments(arguments.length, 1, { prefix });
name = webidl.converters["ByteString"](name, {
prefix,
context: "Argument 1",
});
if (!RegExpPrototypeTest(HTTP_TOKEN_CODE_POINT_RE, name)) {
throw new TypeError("Header name is not valid.");
}
const list = this[_headerList];
const lowercaseName = byteLowerCase(name);
for (let i = 0; i < list.length; i++) {
if (byteLowerCase(list[i][0]) === lowercaseName) {
return true;
}
}
return false;
}
/** /**
* https://fetch.spec.whatwg.org/#concept-headers-append
* @param {Headers} headers
* @param {string} name * @param {string} name
* @param {string} value * @param {string} value
*/ */
function appendHeader(headers, name, value) { set(name, value) {
// 1. webidl.assertBranded(this, HeadersPrototype);
const prefix = "Failed to execute 'set' on 'Headers'";
webidl.requiredArguments(arguments.length, 2, { prefix });
name = webidl.converters["ByteString"](name, {
prefix,
context: "Argument 1",
});
value = webidl.converters["ByteString"](value, {
prefix,
context: "Argument 2",
});
value = normalizeHeaderValue(value); value = normalizeHeaderValue(value);
// 2. // 2.
@ -109,371 +381,97 @@
throw new TypeError("Header value is not valid."); throw new TypeError("Header value is not valid.");
} }
// 3. if (this[_guard] == "immutable") {
if (headers[_guard] == "immutable") {
throw new TypeError("Headers are immutable."); throw new TypeError("Headers are immutable.");
} }
// 7. const list = this[_headerList];
const list = headers[_headerList];
const lowercaseName = byteLowerCase(name); const lowercaseName = byteLowerCase(name);
let added = false;
for (let i = 0; i < list.length; i++) { for (let i = 0; i < list.length; i++) {
if (byteLowerCase(list[i][0]) === lowercaseName) { if (byteLowerCase(list[i][0]) === lowercaseName) {
name = list[i][0]; if (!added) {
break; list[i][1] = value;
} added = true;
}
ArrayPrototypePush(list, [name, value]);
}
/**
* https://fetch.spec.whatwg.org/#concept-header-list-get
* @param {HeaderList} list
* @param {string} name
*/
function getHeader(list, name) {
const lowercaseName = byteLowerCase(name);
const entries = ArrayPrototypeMap(
ArrayPrototypeFilter(
list,
(entry) => byteLowerCase(entry[0]) === lowercaseName,
),
(entry) => entry[1],
);
if (entries.length === 0) {
return null;
} else {
return ArrayPrototypeJoin(entries, "\x2C\x20");
}
}
/**
* https://fetch.spec.whatwg.org/#concept-header-list-get-decode-split
* @param {HeaderList} list
* @param {string} name
* @returns {string[] | null}
*/
function getDecodeSplitHeader(list, name) {
const initialValue = getHeader(list, name);
if (initialValue === null) return null;
const input = initialValue;
let position = 0;
const values = [];
let value = "";
while (position < initialValue.length) {
// 7.1. collect up to " or ,
const res = collectSequenceOfCodepoints(
initialValue,
position,
(c) => c !== "\u0022" && c !== "\u002C",
);
value += res.result;
position = res.position;
if (position < initialValue.length) {
if (input[position] === "\u0022") {
const res = collectHttpQuotedString(input, position, false);
value += res.result;
position = res.position;
if (position < initialValue.length) {
continue;
}
} else { } else {
if (input[position] !== "\u002C") throw new TypeError("Unreachable");
position += 1;
}
}
value = StringPrototypeReplaceAll(value, HTTP_TAB_OR_SPACE_PREFIX_RE, "");
value = StringPrototypeReplaceAll(value, HTTP_TAB_OR_SPACE_SUFFIX_RE, "");
ArrayPrototypePush(values, value);
value = "";
}
return values;
}
class Headers {
/** @type {HeaderList} */
[_headerList] = [];
/** @type {"immutable" | "request" | "request-no-cors" | "response" | "none"} */
[_guard];
get [_iterableHeaders]() {
const list = this[_headerList];
// The order of steps are not similar to the ones suggested by the
// spec but produce the same result.
const headers = {};
const cookies = [];
for (let i = 0; i < list.length; ++i) {
const entry = list[i];
const name = byteLowerCase(entry[0]);
const value = entry[1];
if (value === null) throw new TypeError("Unreachable");
// The following if statement is not spec compliant.
// `set-cookie` is the only header that can not be concatenated,
// so must be given to the user as multiple headers.
// The else block of the if statement is spec compliant again.
if (name === "set-cookie") {
ArrayPrototypePush(cookies, [name, value]);
} else {
// The following code has the same behaviour as getHeader()
// at the end of loop. But it avoids looping through the entire
// list to combine multiple values with same header name. It
// instead gradually combines them as they are found.
let header = headers[name];
if (header && header.length > 0) {
header += "\x2C\x20" + value;
} else {
header = value;
}
headers[name] = header;
}
}
return ArrayPrototypeSort(
[
...new SafeArrayIterator(ObjectEntries(headers)),
...new SafeArrayIterator(cookies),
],
(a, b) => {
const akey = a[0];
const bkey = b[0];
if (akey > bkey) return 1;
if (akey < bkey) return -1;
return 0;
},
);
}
/** @param {HeadersInit} [init] */
constructor(init = undefined) {
const prefix = "Failed to construct 'Headers'";
if (init !== undefined) {
init = webidl.converters["HeadersInit"](init, {
prefix,
context: "Argument 1",
});
}
this[webidl.brand] = webidl.brand;
this[_guard] = "none";
if (init !== undefined) {
fillHeaders(this, init);
}
}
/**
* @param {string} name
* @param {string} value
*/
append(name, value) {
webidl.assertBranded(this, HeadersPrototype);
const prefix = "Failed to execute 'append' on 'Headers'";
webidl.requiredArguments(arguments.length, 2, { prefix });
name = webidl.converters["ByteString"](name, {
prefix,
context: "Argument 1",
});
value = webidl.converters["ByteString"](value, {
prefix,
context: "Argument 2",
});
appendHeader(this, name, value);
}
/**
* @param {string} name
*/
delete(name) {
const prefix = "Failed to execute 'delete' on 'Headers'";
webidl.requiredArguments(arguments.length, 1, { prefix });
name = webidl.converters["ByteString"](name, {
prefix,
context: "Argument 1",
});
if (!RegExpPrototypeTest(HTTP_TOKEN_CODE_POINT_RE, name)) {
throw new TypeError("Header name is not valid.");
}
if (this[_guard] == "immutable") {
throw new TypeError("Headers are immutable.");
}
const list = this[_headerList];
const lowercaseName = byteLowerCase(name);
for (let i = 0; i < list.length; i++) {
if (byteLowerCase(list[i][0]) === lowercaseName) {
ArrayPrototypeSplice(list, i, 1); ArrayPrototypeSplice(list, i, 1);
i--; i--;
} }
} }
} }
if (!added) {
/** ArrayPrototypePush(list, [name, value]);
* @param {string} name
*/
get(name) {
const prefix = "Failed to execute 'get' on 'Headers'";
webidl.requiredArguments(arguments.length, 1, { prefix });
name = webidl.converters["ByteString"](name, {
prefix,
context: "Argument 1",
});
if (!RegExpPrototypeTest(HTTP_TOKEN_CODE_POINT_RE, name)) {
throw new TypeError("Header name is not valid.");
}
const list = this[_headerList];
return getHeader(list, name);
}
/**
* @param {string} name
*/
has(name) {
const prefix = "Failed to execute 'has' on 'Headers'";
webidl.requiredArguments(arguments.length, 1, { prefix });
name = webidl.converters["ByteString"](name, {
prefix,
context: "Argument 1",
});
if (!RegExpPrototypeTest(HTTP_TOKEN_CODE_POINT_RE, name)) {
throw new TypeError("Header name is not valid.");
}
const list = this[_headerList];
const lowercaseName = byteLowerCase(name);
for (let i = 0; i < list.length; i++) {
if (byteLowerCase(list[i][0]) === lowercaseName) {
return true;
}
}
return false;
}
/**
* @param {string} name
* @param {string} value
*/
set(name, value) {
webidl.assertBranded(this, HeadersPrototype);
const prefix = "Failed to execute 'set' on 'Headers'";
webidl.requiredArguments(arguments.length, 2, { prefix });
name = webidl.converters["ByteString"](name, {
prefix,
context: "Argument 1",
});
value = webidl.converters["ByteString"](value, {
prefix,
context: "Argument 2",
});
value = normalizeHeaderValue(value);
// 2.
if (!RegExpPrototypeTest(HTTP_TOKEN_CODE_POINT_RE, name)) {
throw new TypeError("Header name is not valid.");
}
if (RegExpPrototypeTest(ILLEGAL_VALUE_CHARS, value)) {
throw new TypeError("Header value is not valid.");
}
if (this[_guard] == "immutable") {
throw new TypeError("Headers are immutable.");
}
const list = this[_headerList];
const lowercaseName = byteLowerCase(name);
let added = false;
for (let i = 0; i < list.length; i++) {
if (byteLowerCase(list[i][0]) === lowercaseName) {
if (!added) {
list[i][1] = value;
added = true;
} else {
ArrayPrototypeSplice(list, i, 1);
i--;
}
}
}
if (!added) {
ArrayPrototypePush(list, [name, value]);
}
}
[SymbolFor("Deno.privateCustomInspect")](inspect) {
const headers = {};
// deno-lint-ignore prefer-primordials
for (const header of this) {
headers[header[0]] = header[1];
}
return `Headers ${inspect(headers)}`;
} }
} }
webidl.mixinPairIterable("Headers", Headers, _iterableHeaders, 0, 1); [SymbolFor("Deno.privateCustomInspect")](inspect) {
const headers = {};
webidl.configurePrototype(Headers); // deno-lint-ignore prefer-primordials
const HeadersPrototype = Headers.prototype; for (const header of this) {
headers[header[0]] = header[1];
webidl.converters["HeadersInit"] = (V, opts) => {
// Union for (sequence<sequence<ByteString>> or record<ByteString, ByteString>)
if (webidl.type(V) === "Object" && V !== null) {
if (V[SymbolIterator] !== undefined) {
return webidl.converters["sequence<sequence<ByteString>>"](V, opts);
}
return webidl.converters["record<ByteString, ByteString>"](V, opts);
} }
throw webidl.makeException( return `Headers ${inspect(headers)}`;
TypeError, }
"The provided value is not of type '(sequence<sequence<ByteString>> or record<ByteString, ByteString>)'", }
opts,
); webidl.mixinPairIterable("Headers", Headers, _iterableHeaders, 0, 1);
};
webidl.converters["Headers"] = webidl.createInterfaceConverter( webidl.configurePrototype(Headers);
"Headers", const HeadersPrototype = Headers.prototype;
Headers.prototype,
webidl.converters["HeadersInit"] = (V, opts) => {
// Union for (sequence<sequence<ByteString>> or record<ByteString, ByteString>)
if (webidl.type(V) === "Object" && V !== null) {
if (V[SymbolIterator] !== undefined) {
return webidl.converters["sequence<sequence<ByteString>>"](V, opts);
}
return webidl.converters["record<ByteString, ByteString>"](V, opts);
}
throw webidl.makeException(
TypeError,
"The provided value is not of type '(sequence<sequence<ByteString>> or record<ByteString, ByteString>)'",
opts,
); );
};
webidl.converters["Headers"] = webidl.createInterfaceConverter(
"Headers",
Headers.prototype,
);
/** /**
* @param {HeaderList} list * @param {HeaderList} list
* @param {"immutable" | "request" | "request-no-cors" | "response" | "none"} guard * @param {"immutable" | "request" | "request-no-cors" | "response" | "none"} guard
* @returns {Headers} * @returns {Headers}
*/ */
function headersFromHeaderList(list, guard) { function headersFromHeaderList(list, guard) {
const headers = webidl.createBranded(Headers); const headers = webidl.createBranded(Headers);
headers[_headerList] = list; headers[_headerList] = list;
headers[_guard] = guard; headers[_guard] = guard;
return headers; return headers;
} }
/** /**
* @param {Headers} * @param {Headers} headers
* @returns {HeaderList} * @returns {HeaderList}
*/ */
function headerListFromHeaders(headers) { function headerListFromHeaders(headers) {
return headers[_headerList]; return headers[_headerList];
} }
/** /**
* @param {Headers} * @param {Headers} headers
* @returns {"immutable" | "request" | "request-no-cors" | "response" | "none"} * @returns {"immutable" | "request" | "request-no-cors" | "response" | "none"}
*/ */
function guardFromHeaders(headers) { function guardFromHeaders(headers) {
return headers[_guard]; return headers[_guard];
} }
window.__bootstrap.headers = { export {
headersFromHeaderList, fillHeaders,
headerListFromHeaders, getDecodeSplitHeader,
getDecodeSplitHeader, getHeader,
guardFromHeaders, guardFromHeaders,
fillHeaders, headerListFromHeaders,
getHeader, Headers,
Headers, headersFromHeaderList,
}; };
})(this);

View file

@ -8,516 +8,518 @@
/// <reference path="../web/06_streams_types.d.ts" /> /// <reference path="../web/06_streams_types.d.ts" />
/// <reference path="./lib.deno_fetch.d.ts" /> /// <reference path="./lib.deno_fetch.d.ts" />
/// <reference lib="esnext" /> /// <reference lib="esnext" />
"use strict";
((window) => { const core = globalThis.Deno.core;
const core = window.Deno.core; import * as webidl from "internal:ext/webidl/00_webidl.js";
const webidl = globalThis.__bootstrap.webidl; import {
const { Blob, BlobPrototype, File, FilePrototype } = Blob,
globalThis.__bootstrap.file; BlobPrototype,
const { File,
ArrayPrototypePush, FilePrototype,
ArrayPrototypeSlice, } from "internal:ext/web/09_file.js";
ArrayPrototypeSplice, const primordials = globalThis.__bootstrap.primordials;
Map, const {
MapPrototypeGet, ArrayPrototypePush,
MapPrototypeSet, ArrayPrototypeSlice,
MathRandom, ArrayPrototypeSplice,
ObjectPrototypeIsPrototypeOf, Map,
Symbol, MapPrototypeGet,
StringFromCharCode, MapPrototypeSet,
StringPrototypeTrim, MathRandom,
StringPrototypeSlice, ObjectPrototypeIsPrototypeOf,
StringPrototypeSplit, Symbol,
StringPrototypeReplace, StringFromCharCode,
StringPrototypeIndexOf, StringPrototypeTrim,
StringPrototypePadStart, StringPrototypeSlice,
StringPrototypeCodePointAt, StringPrototypeSplit,
StringPrototypeReplaceAll, StringPrototypeReplace,
TypeError, StringPrototypeIndexOf,
TypedArrayPrototypeSubarray, StringPrototypePadStart,
} = window.__bootstrap.primordials; StringPrototypeCodePointAt,
StringPrototypeReplaceAll,
TypeError,
TypedArrayPrototypeSubarray,
} = primordials;
const entryList = Symbol("entry list"); const entryList = Symbol("entry list");
/** /**
* @param {string} name * @param {string} name
* @param {string | Blob} value * @param {string | Blob} value
* @param {string | undefined} filename * @param {string | undefined} filename
* @returns {FormDataEntry} * @returns {FormDataEntry}
*/ */
function createEntry(name, value, filename) { function createEntry(name, value, filename) {
if ( if (
ObjectPrototypeIsPrototypeOf(BlobPrototype, value) && ObjectPrototypeIsPrototypeOf(BlobPrototype, value) &&
!ObjectPrototypeIsPrototypeOf(FilePrototype, value) !ObjectPrototypeIsPrototypeOf(FilePrototype, value)
) { ) {
value = new File([value], "blob", { type: value.type }); value = new File([value], "blob", { type: value.type });
}
if (
ObjectPrototypeIsPrototypeOf(FilePrototype, value) &&
filename !== undefined
) {
value = new File([value], filename, {
type: value.type,
lastModified: value.lastModified,
});
}
return {
name,
// @ts-expect-error because TS is not smart enough
value,
};
}
/**
* @typedef FormDataEntry
* @property {string} name
* @property {FormDataEntryValue} value
*/
class FormData {
/** @type {FormDataEntry[]} */
[entryList] = [];
/** @param {void} form */
constructor(form) {
if (form !== undefined) {
webidl.illegalConstructor();
} }
if ( this[webidl.brand] = webidl.brand;
ObjectPrototypeIsPrototypeOf(FilePrototype, value) &&
filename !== undefined
) {
value = new File([value], filename, {
type: value.type,
lastModified: value.lastModified,
});
}
return {
name,
// @ts-expect-error because TS is not smart enough
value,
};
} }
/** /**
* @typedef FormDataEntry * @param {string} name
* @property {string} name * @param {string | Blob} valueOrBlobValue
* @property {FormDataEntryValue} value * @param {string} [filename]
* @returns {void}
*/ */
append(name, valueOrBlobValue, filename) {
webidl.assertBranded(this, FormDataPrototype);
const prefix = "Failed to execute 'append' on 'FormData'";
webidl.requiredArguments(arguments.length, 2, { prefix });
class FormData { name = webidl.converters["USVString"](name, {
/** @type {FormDataEntry[]} */ prefix,
[entryList] = []; context: "Argument 1",
});
/** @param {void} form */ if (ObjectPrototypeIsPrototypeOf(BlobPrototype, valueOrBlobValue)) {
constructor(form) { valueOrBlobValue = webidl.converters["Blob"](valueOrBlobValue, {
if (form !== undefined) {
webidl.illegalConstructor();
}
this[webidl.brand] = webidl.brand;
}
/**
* @param {string} name
* @param {string | Blob} valueOrBlobValue
* @param {string} [filename]
* @returns {void}
*/
append(name, valueOrBlobValue, filename) {
webidl.assertBranded(this, FormDataPrototype);
const prefix = "Failed to execute 'append' on 'FormData'";
webidl.requiredArguments(arguments.length, 2, { prefix });
name = webidl.converters["USVString"](name, {
prefix, prefix,
context: "Argument 1", context: "Argument 2",
}); });
if (ObjectPrototypeIsPrototypeOf(BlobPrototype, valueOrBlobValue)) { if (filename !== undefined) {
valueOrBlobValue = webidl.converters["Blob"](valueOrBlobValue, { filename = webidl.converters["USVString"](filename, {
prefix, prefix,
context: "Argument 2", context: "Argument 3",
});
if (filename !== undefined) {
filename = webidl.converters["USVString"](filename, {
prefix,
context: "Argument 3",
});
}
} else {
valueOrBlobValue = webidl.converters["USVString"](valueOrBlobValue, {
prefix,
context: "Argument 2",
}); });
} }
} else {
const entry = createEntry(name, valueOrBlobValue, filename); valueOrBlobValue = webidl.converters["USVString"](valueOrBlobValue, {
prefix,
ArrayPrototypePush(this[entryList], entry); context: "Argument 2",
});
} }
/** const entry = createEntry(name, valueOrBlobValue, filename);
* @param {string} name
* @returns {void}
*/
delete(name) {
webidl.assertBranded(this, FormDataPrototype);
const prefix = "Failed to execute 'name' on 'FormData'";
webidl.requiredArguments(arguments.length, 1, { prefix });
name = webidl.converters["USVString"](name, { ArrayPrototypePush(this[entryList], entry);
}
/**
* @param {string} name
* @returns {void}
*/
delete(name) {
webidl.assertBranded(this, FormDataPrototype);
const prefix = "Failed to execute 'name' on 'FormData'";
webidl.requiredArguments(arguments.length, 1, { prefix });
name = webidl.converters["USVString"](name, {
prefix,
context: "Argument 1",
});
const list = this[entryList];
for (let i = 0; i < list.length; i++) {
if (list[i].name === name) {
ArrayPrototypeSplice(list, i, 1);
i--;
}
}
}
/**
* @param {string} name
* @returns {FormDataEntryValue | null}
*/
get(name) {
webidl.assertBranded(this, FormDataPrototype);
const prefix = "Failed to execute 'get' on 'FormData'";
webidl.requiredArguments(arguments.length, 1, { prefix });
name = webidl.converters["USVString"](name, {
prefix,
context: "Argument 1",
});
const entries = this[entryList];
for (let i = 0; i < entries.length; ++i) {
const entry = entries[i];
if (entry.name === name) return entry.value;
}
return null;
}
/**
* @param {string} name
* @returns {FormDataEntryValue[]}
*/
getAll(name) {
webidl.assertBranded(this, FormDataPrototype);
const prefix = "Failed to execute 'getAll' on 'FormData'";
webidl.requiredArguments(arguments.length, 1, { prefix });
name = webidl.converters["USVString"](name, {
prefix,
context: "Argument 1",
});
const returnList = [];
const entries = this[entryList];
for (let i = 0; i < entries.length; ++i) {
const entry = entries[i];
if (entry.name === name) ArrayPrototypePush(returnList, entry.value);
}
return returnList;
}
/**
* @param {string} name
* @returns {boolean}
*/
has(name) {
webidl.assertBranded(this, FormDataPrototype);
const prefix = "Failed to execute 'has' on 'FormData'";
webidl.requiredArguments(arguments.length, 1, { prefix });
name = webidl.converters["USVString"](name, {
prefix,
context: "Argument 1",
});
const entries = this[entryList];
for (let i = 0; i < entries.length; ++i) {
const entry = entries[i];
if (entry.name === name) return true;
}
return false;
}
/**
* @param {string} name
* @param {string | Blob} valueOrBlobValue
* @param {string} [filename]
* @returns {void}
*/
set(name, valueOrBlobValue, filename) {
webidl.assertBranded(this, FormDataPrototype);
const prefix = "Failed to execute 'set' on 'FormData'";
webidl.requiredArguments(arguments.length, 2, { prefix });
name = webidl.converters["USVString"](name, {
prefix,
context: "Argument 1",
});
if (ObjectPrototypeIsPrototypeOf(BlobPrototype, valueOrBlobValue)) {
valueOrBlobValue = webidl.converters["Blob"](valueOrBlobValue, {
prefix, prefix,
context: "Argument 1", context: "Argument 2",
}); });
if (filename !== undefined) {
filename = webidl.converters["USVString"](filename, {
prefix,
context: "Argument 3",
});
}
} else {
valueOrBlobValue = webidl.converters["USVString"](valueOrBlobValue, {
prefix,
context: "Argument 2",
});
}
const list = this[entryList]; const entry = createEntry(name, valueOrBlobValue, filename);
for (let i = 0; i < list.length; i++) {
if (list[i].name === name) { const list = this[entryList];
let added = false;
for (let i = 0; i < list.length; i++) {
if (list[i].name === name) {
if (!added) {
list[i] = entry;
added = true;
} else {
ArrayPrototypeSplice(list, i, 1); ArrayPrototypeSplice(list, i, 1);
i--; i--;
} }
} }
} }
if (!added) {
/** ArrayPrototypePush(list, entry);
* @param {string} name
* @returns {FormDataEntryValue | null}
*/
get(name) {
webidl.assertBranded(this, FormDataPrototype);
const prefix = "Failed to execute 'get' on 'FormData'";
webidl.requiredArguments(arguments.length, 1, { prefix });
name = webidl.converters["USVString"](name, {
prefix,
context: "Argument 1",
});
const entries = this[entryList];
for (let i = 0; i < entries.length; ++i) {
const entry = entries[i];
if (entry.name === name) return entry.value;
}
return null;
}
/**
* @param {string} name
* @returns {FormDataEntryValue[]}
*/
getAll(name) {
webidl.assertBranded(this, FormDataPrototype);
const prefix = "Failed to execute 'getAll' on 'FormData'";
webidl.requiredArguments(arguments.length, 1, { prefix });
name = webidl.converters["USVString"](name, {
prefix,
context: "Argument 1",
});
const returnList = [];
const entries = this[entryList];
for (let i = 0; i < entries.length; ++i) {
const entry = entries[i];
if (entry.name === name) ArrayPrototypePush(returnList, entry.value);
}
return returnList;
}
/**
* @param {string} name
* @returns {boolean}
*/
has(name) {
webidl.assertBranded(this, FormDataPrototype);
const prefix = "Failed to execute 'has' on 'FormData'";
webidl.requiredArguments(arguments.length, 1, { prefix });
name = webidl.converters["USVString"](name, {
prefix,
context: "Argument 1",
});
const entries = this[entryList];
for (let i = 0; i < entries.length; ++i) {
const entry = entries[i];
if (entry.name === name) return true;
}
return false;
}
/**
* @param {string} name
* @param {string | Blob} valueOrBlobValue
* @param {string} [filename]
* @returns {void}
*/
set(name, valueOrBlobValue, filename) {
webidl.assertBranded(this, FormDataPrototype);
const prefix = "Failed to execute 'set' on 'FormData'";
webidl.requiredArguments(arguments.length, 2, { prefix });
name = webidl.converters["USVString"](name, {
prefix,
context: "Argument 1",
});
if (ObjectPrototypeIsPrototypeOf(BlobPrototype, valueOrBlobValue)) {
valueOrBlobValue = webidl.converters["Blob"](valueOrBlobValue, {
prefix,
context: "Argument 2",
});
if (filename !== undefined) {
filename = webidl.converters["USVString"](filename, {
prefix,
context: "Argument 3",
});
}
} else {
valueOrBlobValue = webidl.converters["USVString"](valueOrBlobValue, {
prefix,
context: "Argument 2",
});
}
const entry = createEntry(name, valueOrBlobValue, filename);
const list = this[entryList];
let added = false;
for (let i = 0; i < list.length; i++) {
if (list[i].name === name) {
if (!added) {
list[i] = entry;
added = true;
} else {
ArrayPrototypeSplice(list, i, 1);
i--;
}
}
}
if (!added) {
ArrayPrototypePush(list, entry);
}
} }
} }
}
webidl.mixinPairIterable("FormData", FormData, entryList, "name", "value"); webidl.mixinPairIterable("FormData", FormData, entryList, "name", "value");
webidl.configurePrototype(FormData); webidl.configurePrototype(FormData);
const FormDataPrototype = FormData.prototype; const FormDataPrototype = FormData.prototype;
const escape = (str, isFilename) => { const escape = (str, isFilename) => {
const escapeMap = { const escapeMap = {
"\n": "%0A", "\n": "%0A",
"\r": "%0D", "\r": "%0D",
'"': "%22", '"': "%22",
};
return StringPrototypeReplace(
isFilename ? str : StringPrototypeReplace(str, /\r?\n|\r/g, "\r\n"),
/([\n\r"])/g,
(c) => escapeMap[c],
);
}; };
/** return StringPrototypeReplace(
* convert FormData to a Blob synchronous without reading all of the files isFilename ? str : StringPrototypeReplace(str, /\r?\n|\r/g, "\r\n"),
* @param {globalThis.FormData} formData /([\n\r"])/g,
*/ (c) => escapeMap[c],
function formDataToBlob(formData) { );
const boundary = StringPrototypePadStart( };
StringPrototypeSlice(
StringPrototypeReplaceAll(`${MathRandom()}${MathRandom()}`, ".", ""),
-28,
),
32,
"-",
);
const chunks = [];
const prefix = `--${boundary}\r\nContent-Disposition: form-data; name="`;
// deno-lint-ignore prefer-primordials /**
for (const { 0: name, 1: value } of formData) { * convert FormData to a Blob synchronous without reading all of the files
if (typeof value === "string") { * @param {globalThis.FormData} formData
ArrayPrototypePush( */
chunks, function formDataToBlob(formData) {
prefix + escape(name) + '"' + CRLF + CRLF + const boundary = StringPrototypePadStart(
StringPrototypeReplace(value, /\r(?!\n)|(?<!\r)\n/g, CRLF) + CRLF, StringPrototypeSlice(
); StringPrototypeReplaceAll(`${MathRandom()}${MathRandom()}`, ".", ""),
} else { -28,
ArrayPrototypePush( ),
chunks, 32,
prefix + escape(name) + `"; filename="${escape(value.name, true)}"` + "-",
CRLF + );
`Content-Type: ${value.type || "application/octet-stream"}\r\n\r\n`, const chunks = [];
value, const prefix = `--${boundary}\r\nContent-Disposition: form-data; name="`;
CRLF,
);
}
}
ArrayPrototypePush(chunks, `--${boundary}--`); // deno-lint-ignore prefer-primordials
for (const { 0: name, 1: value } of formData) {
return new Blob(chunks, { if (typeof value === "string") {
type: "multipart/form-data; boundary=" + boundary, ArrayPrototypePush(
}); chunks,
} prefix + escape(name) + '"' + CRLF + CRLF +
StringPrototypeReplace(value, /\r(?!\n)|(?<!\r)\n/g, CRLF) + CRLF,
/** );
* @param {string} value } else {
* @returns {Map<string, string>} ArrayPrototypePush(
*/ chunks,
function parseContentDisposition(value) { prefix + escape(name) + `"; filename="${escape(value.name, true)}"` +
/** @type {Map<string, string>} */ CRLF +
const params = new Map(); `Content-Type: ${value.type || "application/octet-stream"}\r\n\r\n`,
// Forced to do so for some Map constructor param mismatch value,
const values = ArrayPrototypeSlice(StringPrototypeSplit(value, ";"), 1); CRLF,
for (let i = 0; i < values.length; i++) {
const entries = StringPrototypeSplit(StringPrototypeTrim(values[i]), "=");
if (entries.length > 1) {
MapPrototypeSet(
params,
entries[0],
StringPrototypeReplace(entries[1], /^"([^"]*)"$/, "$1"),
);
}
}
return params;
}
const CRLF = "\r\n";
const LF = StringPrototypeCodePointAt(CRLF, 1);
const CR = StringPrototypeCodePointAt(CRLF, 0);
class MultipartParser {
/**
* @param {Uint8Array} body
* @param {string | undefined} boundary
*/
constructor(body, boundary) {
if (!boundary) {
throw new TypeError("multipart/form-data must provide a boundary");
}
this.boundary = `--${boundary}`;
this.body = body;
this.boundaryChars = core.encode(this.boundary);
}
/**
* @param {string} headersText
* @returns {{ headers: Headers, disposition: Map<string, string> }}
*/
#parseHeaders(headersText) {
const headers = new Headers();
const rawHeaders = StringPrototypeSplit(headersText, "\r\n");
for (let i = 0; i < rawHeaders.length; ++i) {
const rawHeader = rawHeaders[i];
const sepIndex = StringPrototypeIndexOf(rawHeader, ":");
if (sepIndex < 0) {
continue; // Skip this header
}
const key = StringPrototypeSlice(rawHeader, 0, sepIndex);
const value = StringPrototypeSlice(rawHeader, sepIndex + 1);
headers.set(key, value);
}
const disposition = parseContentDisposition(
headers.get("Content-Disposition") ?? "",
); );
return { headers, disposition };
}
/**
* @returns {FormData}
*/
parse() {
// To have fields body must be at least 2 boundaries + \r\n + --
// on the last boundary.
if (this.body.length < (this.boundary.length * 2) + 4) {
const decodedBody = core.decode(this.body);
const lastBoundary = this.boundary + "--";
// check if it's an empty valid form data
if (
decodedBody === lastBoundary ||
decodedBody === lastBoundary + "\r\n"
) {
return new FormData();
}
throw new TypeError("Unable to parse body as form data.");
}
const formData = new FormData();
let headerText = "";
let boundaryIndex = 0;
let state = 0;
let fileStart = 0;
for (let i = 0; i < this.body.length; i++) {
const byte = this.body[i];
const prevByte = this.body[i - 1];
const isNewLine = byte === LF && prevByte === CR;
if (state === 1 || state === 2 || state == 3) {
headerText += StringFromCharCode(byte);
}
if (state === 0 && isNewLine) {
state = 1;
} else if (state === 1 && isNewLine) {
state = 2;
const headersDone = this.body[i + 1] === CR &&
this.body[i + 2] === LF;
if (headersDone) {
state = 3;
}
} else if (state === 2 && isNewLine) {
state = 3;
} else if (state === 3 && isNewLine) {
state = 4;
fileStart = i + 1;
} else if (state === 4) {
if (this.boundaryChars[boundaryIndex] !== byte) {
boundaryIndex = 0;
} else {
boundaryIndex++;
}
if (boundaryIndex >= this.boundary.length) {
const { headers, disposition } = this.#parseHeaders(headerText);
const content = TypedArrayPrototypeSubarray(
this.body,
fileStart,
i - boundaryIndex - 1,
);
// https://fetch.spec.whatwg.org/#ref-for-dom-body-formdata
const filename = MapPrototypeGet(disposition, "filename");
const name = MapPrototypeGet(disposition, "name");
state = 5;
// Reset
boundaryIndex = 0;
headerText = "";
if (!name) {
continue; // Skip, unknown name
}
if (filename) {
const blob = new Blob([content], {
type: headers.get("Content-Type") || "application/octet-stream",
});
formData.append(name, blob, filename);
} else {
formData.append(name, core.decode(content));
}
}
} else if (state === 5 && isNewLine) {
state = 1;
}
}
return formData;
} }
} }
ArrayPrototypePush(chunks, `--${boundary}--`);
return new Blob(chunks, {
type: "multipart/form-data; boundary=" + boundary,
});
}
/**
* @param {string} value
* @returns {Map<string, string>}
*/
function parseContentDisposition(value) {
/** @type {Map<string, string>} */
const params = new Map();
// Forced to do so for some Map constructor param mismatch
const values = ArrayPrototypeSlice(StringPrototypeSplit(value, ";"), 1);
for (let i = 0; i < values.length; i++) {
const entries = StringPrototypeSplit(StringPrototypeTrim(values[i]), "=");
if (entries.length > 1) {
MapPrototypeSet(
params,
entries[0],
StringPrototypeReplace(entries[1], /^"([^"]*)"$/, "$1"),
);
}
}
return params;
}
const CRLF = "\r\n";
const LF = StringPrototypeCodePointAt(CRLF, 1);
const CR = StringPrototypeCodePointAt(CRLF, 0);
class MultipartParser {
/** /**
* @param {Uint8Array} body * @param {Uint8Array} body
* @param {string | undefined} boundary * @param {string | undefined} boundary
* @returns {FormData}
*/ */
function parseFormData(body, boundary) { constructor(body, boundary) {
const parser = new MultipartParser(body, boundary); if (!boundary) {
return parser.parse(); throw new TypeError("multipart/form-data must provide a boundary");
}
this.boundary = `--${boundary}`;
this.body = body;
this.boundaryChars = core.encode(this.boundary);
} }
/** /**
* @param {FormDataEntry[]} entries * @param {string} headersText
* @returns {FormData} * @returns {{ headers: Headers, disposition: Map<string, string> }}
*/ */
function formDataFromEntries(entries) { #parseHeaders(headersText) {
const fd = new FormData(); const headers = new Headers();
fd[entryList] = entries; const rawHeaders = StringPrototypeSplit(headersText, "\r\n");
return fd; for (let i = 0; i < rawHeaders.length; ++i) {
const rawHeader = rawHeaders[i];
const sepIndex = StringPrototypeIndexOf(rawHeader, ":");
if (sepIndex < 0) {
continue; // Skip this header
}
const key = StringPrototypeSlice(rawHeader, 0, sepIndex);
const value = StringPrototypeSlice(rawHeader, sepIndex + 1);
headers.set(key, value);
}
const disposition = parseContentDisposition(
headers.get("Content-Disposition") ?? "",
);
return { headers, disposition };
} }
webidl.converters["FormData"] = webidl /**
.createInterfaceConverter("FormData", FormDataPrototype); * @returns {FormData}
*/
parse() {
// To have fields body must be at least 2 boundaries + \r\n + --
// on the last boundary.
if (this.body.length < (this.boundary.length * 2) + 4) {
const decodedBody = core.decode(this.body);
const lastBoundary = this.boundary + "--";
// check if it's an empty valid form data
if (
decodedBody === lastBoundary ||
decodedBody === lastBoundary + "\r\n"
) {
return new FormData();
}
throw new TypeError("Unable to parse body as form data.");
}
globalThis.__bootstrap.formData = { const formData = new FormData();
FormData, let headerText = "";
FormDataPrototype, let boundaryIndex = 0;
formDataToBlob, let state = 0;
parseFormData, let fileStart = 0;
formDataFromEntries,
}; for (let i = 0; i < this.body.length; i++) {
})(globalThis); const byte = this.body[i];
const prevByte = this.body[i - 1];
const isNewLine = byte === LF && prevByte === CR;
if (state === 1 || state === 2 || state == 3) {
headerText += StringFromCharCode(byte);
}
if (state === 0 && isNewLine) {
state = 1;
} else if (state === 1 && isNewLine) {
state = 2;
const headersDone = this.body[i + 1] === CR &&
this.body[i + 2] === LF;
if (headersDone) {
state = 3;
}
} else if (state === 2 && isNewLine) {
state = 3;
} else if (state === 3 && isNewLine) {
state = 4;
fileStart = i + 1;
} else if (state === 4) {
if (this.boundaryChars[boundaryIndex] !== byte) {
boundaryIndex = 0;
} else {
boundaryIndex++;
}
if (boundaryIndex >= this.boundary.length) {
const { headers, disposition } = this.#parseHeaders(headerText);
const content = TypedArrayPrototypeSubarray(
this.body,
fileStart,
i - boundaryIndex - 1,
);
// https://fetch.spec.whatwg.org/#ref-for-dom-body-formdata
const filename = MapPrototypeGet(disposition, "filename");
const name = MapPrototypeGet(disposition, "name");
state = 5;
// Reset
boundaryIndex = 0;
headerText = "";
if (!name) {
continue; // Skip, unknown name
}
if (filename) {
const blob = new Blob([content], {
type: headers.get("Content-Type") || "application/octet-stream",
});
formData.append(name, blob, filename);
} else {
formData.append(name, core.decode(content));
}
}
} else if (state === 5 && isNewLine) {
state = 1;
}
}
return formData;
}
}
/**
* @param {Uint8Array} body
* @param {string | undefined} boundary
* @returns {FormData}
*/
function parseFormData(body, boundary) {
const parser = new MultipartParser(body, boundary);
return parser.parse();
}
/**
* @param {FormDataEntry[]} entries
* @returns {FormData}
*/
function formDataFromEntries(entries) {
const fd = new FormData();
fd[entryList] = entries;
return fd;
}
webidl.converters["FormData"] = webidl
.createInterfaceConverter("FormData", FormDataPrototype);
export {
FormData,
formDataFromEntries,
FormDataPrototype,
formDataToBlob,
parseFormData,
};

View file

@ -10,462 +10,462 @@
/// <reference path="../web/06_streams_types.d.ts" /> /// <reference path="../web/06_streams_types.d.ts" />
/// <reference path="./lib.deno_fetch.d.ts" /> /// <reference path="./lib.deno_fetch.d.ts" />
/// <reference lib="esnext" /> /// <reference lib="esnext" />
"use strict";
((window) => { const core = globalThis.Deno.core;
const core = window.Deno.core; import * as webidl from "internal:ext/webidl/00_webidl.js";
const webidl = globalThis.__bootstrap.webidl; import {
const { parseUrlEncoded } = globalThis.__bootstrap.url; parseUrlEncoded,
const { URLSearchParamsPrototype } = globalThis.__bootstrap.url; URLSearchParamsPrototype,
const { } from "internal:ext/url/00_url.js";
parseFormData, import {
formDataFromEntries, formDataFromEntries,
formDataToBlob, FormDataPrototype,
FormDataPrototype, formDataToBlob,
} = globalThis.__bootstrap.formData; parseFormData,
const mimesniff = globalThis.__bootstrap.mimesniff; } from "internal:ext/fetch/21_formdata.js";
const { BlobPrototype } = globalThis.__bootstrap.file; import * as mimesniff from "internal:ext/web/01_mimesniff.js";
const { import { BlobPrototype } from "internal:ext/web/09_file.js";
isReadableStreamDisturbed, import {
errorReadableStream, createProxy,
readableStreamClose, errorReadableStream,
readableStreamDisturb, isReadableStreamDisturbed,
readableStreamCollectIntoUint8Array, readableStreamClose,
readableStreamThrowIfErrored, readableStreamCollectIntoUint8Array,
createProxy, readableStreamDisturb,
ReadableStreamPrototype, ReadableStreamPrototype,
} = globalThis.__bootstrap.streams; readableStreamThrowIfErrored,
const { } from "internal:ext/web/06_streams.js";
ArrayBufferPrototype, const primordials = globalThis.__bootstrap.primordials;
ArrayBufferIsView, const {
ArrayPrototypeMap, ArrayBufferPrototype,
JSONParse, ArrayBufferIsView,
ObjectDefineProperties, ArrayPrototypeMap,
ObjectPrototypeIsPrototypeOf, JSONParse,
// TODO(lucacasonato): add SharedArrayBuffer to primordials ObjectDefineProperties,
// SharedArrayBufferPrototype ObjectPrototypeIsPrototypeOf,
TypedArrayPrototypeSlice, // TODO(lucacasonato): add SharedArrayBuffer to primordials
TypeError, // SharedArrayBufferPrototype
Uint8Array, TypedArrayPrototypeSlice,
Uint8ArrayPrototype, TypeError,
} = window.__bootstrap.primordials; Uint8Array,
Uint8ArrayPrototype,
} = primordials;
/**
* @param {Uint8Array | string} chunk
* @returns {Uint8Array}
*/
function chunkToU8(chunk) {
return typeof chunk === "string" ? core.encode(chunk) : chunk;
}
/**
* @param {Uint8Array | string} chunk
* @returns {string}
*/
function chunkToString(chunk) {
return typeof chunk === "string" ? chunk : core.decode(chunk);
}
class InnerBody {
/** /**
* @param {Uint8Array | string} chunk * @param {ReadableStream<Uint8Array> | { body: Uint8Array | string, consumed: boolean }} stream
* @returns {Uint8Array}
*/ */
function chunkToU8(chunk) { constructor(stream) {
return typeof chunk === "string" ? core.encode(chunk) : chunk; /** @type {ReadableStream<Uint8Array> | { body: Uint8Array | string, consumed: boolean }} */
this.streamOrStatic = stream ??
{ body: new Uint8Array(), consumed: false };
/** @type {null | Uint8Array | string | Blob | FormData} */
this.source = null;
/** @type {null | number} */
this.length = null;
} }
/** get stream() {
* @param {Uint8Array | string} chunk if (
* @returns {string} !ObjectPrototypeIsPrototypeOf(
*/ ReadableStreamPrototype,
function chunkToString(chunk) { this.streamOrStatic,
return typeof chunk === "string" ? chunk : core.decode(chunk); )
} ) {
const { body, consumed } = this.streamOrStatic;
class InnerBody { if (consumed) {
/** this.streamOrStatic = new ReadableStream();
* @param {ReadableStream<Uint8Array> | { body: Uint8Array | string, consumed: boolean }} stream this.streamOrStatic.getReader();
*/ readableStreamDisturb(this.streamOrStatic);
constructor(stream) { readableStreamClose(this.streamOrStatic);
/** @type {ReadableStream<Uint8Array> | { body: Uint8Array | string, consumed: boolean }} */
this.streamOrStatic = stream ??
{ body: new Uint8Array(), consumed: false };
/** @type {null | Uint8Array | string | Blob | FormData} */
this.source = null;
/** @type {null | number} */
this.length = null;
}
get stream() {
if (
!ObjectPrototypeIsPrototypeOf(
ReadableStreamPrototype,
this.streamOrStatic,
)
) {
const { body, consumed } = this.streamOrStatic;
if (consumed) {
this.streamOrStatic = new ReadableStream();
this.streamOrStatic.getReader();
readableStreamDisturb(this.streamOrStatic);
readableStreamClose(this.streamOrStatic);
} else {
this.streamOrStatic = new ReadableStream({
start(controller) {
controller.enqueue(chunkToU8(body));
controller.close();
},
});
}
}
return this.streamOrStatic;
}
/**
* https://fetch.spec.whatwg.org/#body-unusable
* @returns {boolean}
*/
unusable() {
if (
ObjectPrototypeIsPrototypeOf(
ReadableStreamPrototype,
this.streamOrStatic,
)
) {
return this.streamOrStatic.locked ||
isReadableStreamDisturbed(this.streamOrStatic);
}
return this.streamOrStatic.consumed;
}
/**
* @returns {boolean}
*/
consumed() {
if (
ObjectPrototypeIsPrototypeOf(
ReadableStreamPrototype,
this.streamOrStatic,
)
) {
return isReadableStreamDisturbed(this.streamOrStatic);
}
return this.streamOrStatic.consumed;
}
/**
* https://fetch.spec.whatwg.org/#concept-body-consume-body
* @returns {Promise<Uint8Array>}
*/
consume() {
if (this.unusable()) throw new TypeError("Body already consumed.");
if (
ObjectPrototypeIsPrototypeOf(
ReadableStreamPrototype,
this.streamOrStatic,
)
) {
readableStreamThrowIfErrored(this.stream);
return readableStreamCollectIntoUint8Array(this.stream);
} else { } else {
this.streamOrStatic.consumed = true; this.streamOrStatic = new ReadableStream({
return this.streamOrStatic.body; start(controller) {
} controller.enqueue(chunkToU8(body));
} controller.close();
},
cancel(error) {
if (
ObjectPrototypeIsPrototypeOf(
ReadableStreamPrototype,
this.streamOrStatic,
)
) {
this.streamOrStatic.cancel(error);
} else {
this.streamOrStatic.consumed = true;
}
}
error(error) {
if (
ObjectPrototypeIsPrototypeOf(
ReadableStreamPrototype,
this.streamOrStatic,
)
) {
errorReadableStream(this.streamOrStatic, error);
} else {
this.streamOrStatic.consumed = true;
}
}
/**
* @returns {InnerBody}
*/
clone() {
const { 0: out1, 1: out2 } = this.stream.tee();
this.streamOrStatic = out1;
const second = new InnerBody(out2);
second.source = core.deserialize(core.serialize(this.source));
second.length = this.length;
return second;
}
/**
* @returns {InnerBody}
*/
createProxy() {
let proxyStreamOrStatic;
if (
ObjectPrototypeIsPrototypeOf(
ReadableStreamPrototype,
this.streamOrStatic,
)
) {
proxyStreamOrStatic = createProxy(this.streamOrStatic);
} else {
proxyStreamOrStatic = { ...this.streamOrStatic };
this.streamOrStatic.consumed = true;
}
const proxy = new InnerBody(proxyStreamOrStatic);
proxy.source = this.source;
proxy.length = this.length;
return proxy;
}
}
/**
* @param {any} prototype
* @param {symbol} bodySymbol
* @param {symbol} mimeTypeSymbol
* @returns {void}
*/
function mixinBody(prototype, bodySymbol, mimeTypeSymbol) {
async function consumeBody(object, type) {
webidl.assertBranded(object, prototype);
const body = object[bodySymbol] !== null
? await object[bodySymbol].consume()
: new Uint8Array();
const mimeType = type === "Blob" || type === "FormData"
? object[mimeTypeSymbol]
: null;
return packageData(body, type, mimeType);
}
/** @type {PropertyDescriptorMap} */
const mixin = {
body: {
/**
* @returns {ReadableStream<Uint8Array> | null}
*/
get() {
webidl.assertBranded(this, prototype);
if (this[bodySymbol] === null) {
return null;
} else {
return this[bodySymbol].stream;
}
},
configurable: true,
enumerable: true,
},
bodyUsed: {
/**
* @returns {boolean}
*/
get() {
webidl.assertBranded(this, prototype);
if (this[bodySymbol] !== null) {
return this[bodySymbol].consumed();
}
return false;
},
configurable: true,
enumerable: true,
},
arrayBuffer: {
/** @returns {Promise<ArrayBuffer>} */
value: function arrayBuffer() {
return consumeBody(this, "ArrayBuffer");
},
writable: true,
configurable: true,
enumerable: true,
},
blob: {
/** @returns {Promise<Blob>} */
value: function blob() {
return consumeBody(this, "Blob");
},
writable: true,
configurable: true,
enumerable: true,
},
formData: {
/** @returns {Promise<FormData>} */
value: function formData() {
return consumeBody(this, "FormData");
},
writable: true,
configurable: true,
enumerable: true,
},
json: {
/** @returns {Promise<any>} */
value: function json() {
return consumeBody(this, "JSON");
},
writable: true,
configurable: true,
enumerable: true,
},
text: {
/** @returns {Promise<string>} */
value: function text() {
return consumeBody(this, "text");
},
writable: true,
configurable: true,
enumerable: true,
},
};
return ObjectDefineProperties(prototype, mixin);
}
/**
* https://fetch.spec.whatwg.org/#concept-body-package-data
* @param {Uint8Array | string} bytes
* @param {"ArrayBuffer" | "Blob" | "FormData" | "JSON" | "text"} type
* @param {MimeType | null} [mimeType]
*/
function packageData(bytes, type, mimeType) {
switch (type) {
case "ArrayBuffer":
return chunkToU8(bytes).buffer;
case "Blob":
return new Blob([bytes], {
type: mimeType !== null ? mimesniff.serializeMimeType(mimeType) : "",
}); });
case "FormData": { }
if (mimeType !== null) { }
const essence = mimesniff.essence(mimeType); return this.streamOrStatic;
if (essence === "multipart/form-data") { }
const boundary = mimeType.parameters.get("boundary");
if (boundary === null) { /**
throw new TypeError( * https://fetch.spec.whatwg.org/#body-unusable
"Missing boundary parameter in mime type of multipart formdata.", * @returns {boolean}
); */
} unusable() {
return parseFormData(chunkToU8(bytes), boundary); if (
} else if (essence === "application/x-www-form-urlencoded") { ObjectPrototypeIsPrototypeOf(
// TODO(@AaronO): pass as-is with StringOrBuffer in op-layer ReadableStreamPrototype,
const entries = parseUrlEncoded(chunkToU8(bytes)); this.streamOrStatic,
return formDataFromEntries( )
ArrayPrototypeMap( ) {
entries, return this.streamOrStatic.locked ||
(x) => ({ name: x[0], value: x[1] }), isReadableStreamDisturbed(this.streamOrStatic);
), }
return this.streamOrStatic.consumed;
}
/**
* @returns {boolean}
*/
consumed() {
if (
ObjectPrototypeIsPrototypeOf(
ReadableStreamPrototype,
this.streamOrStatic,
)
) {
return isReadableStreamDisturbed(this.streamOrStatic);
}
return this.streamOrStatic.consumed;
}
/**
* https://fetch.spec.whatwg.org/#concept-body-consume-body
* @returns {Promise<Uint8Array>}
*/
consume() {
if (this.unusable()) throw new TypeError("Body already consumed.");
if (
ObjectPrototypeIsPrototypeOf(
ReadableStreamPrototype,
this.streamOrStatic,
)
) {
readableStreamThrowIfErrored(this.stream);
return readableStreamCollectIntoUint8Array(this.stream);
} else {
this.streamOrStatic.consumed = true;
return this.streamOrStatic.body;
}
}
cancel(error) {
if (
ObjectPrototypeIsPrototypeOf(
ReadableStreamPrototype,
this.streamOrStatic,
)
) {
this.streamOrStatic.cancel(error);
} else {
this.streamOrStatic.consumed = true;
}
}
error(error) {
if (
ObjectPrototypeIsPrototypeOf(
ReadableStreamPrototype,
this.streamOrStatic,
)
) {
errorReadableStream(this.streamOrStatic, error);
} else {
this.streamOrStatic.consumed = true;
}
}
/**
* @returns {InnerBody}
*/
clone() {
const { 0: out1, 1: out2 } = this.stream.tee();
this.streamOrStatic = out1;
const second = new InnerBody(out2);
second.source = core.deserialize(core.serialize(this.source));
second.length = this.length;
return second;
}
/**
* @returns {InnerBody}
*/
createProxy() {
let proxyStreamOrStatic;
if (
ObjectPrototypeIsPrototypeOf(
ReadableStreamPrototype,
this.streamOrStatic,
)
) {
proxyStreamOrStatic = createProxy(this.streamOrStatic);
} else {
proxyStreamOrStatic = { ...this.streamOrStatic };
this.streamOrStatic.consumed = true;
}
const proxy = new InnerBody(proxyStreamOrStatic);
proxy.source = this.source;
proxy.length = this.length;
return proxy;
}
}
/**
* @param {any} prototype
* @param {symbol} bodySymbol
* @param {symbol} mimeTypeSymbol
* @returns {void}
*/
function mixinBody(prototype, bodySymbol, mimeTypeSymbol) {
async function consumeBody(object, type) {
webidl.assertBranded(object, prototype);
const body = object[bodySymbol] !== null
? await object[bodySymbol].consume()
: new Uint8Array();
const mimeType = type === "Blob" || type === "FormData"
? object[mimeTypeSymbol]
: null;
return packageData(body, type, mimeType);
}
/** @type {PropertyDescriptorMap} */
const mixin = {
body: {
/**
* @returns {ReadableStream<Uint8Array> | null}
*/
get() {
webidl.assertBranded(this, prototype);
if (this[bodySymbol] === null) {
return null;
} else {
return this[bodySymbol].stream;
}
},
configurable: true,
enumerable: true,
},
bodyUsed: {
/**
* @returns {boolean}
*/
get() {
webidl.assertBranded(this, prototype);
if (this[bodySymbol] !== null) {
return this[bodySymbol].consumed();
}
return false;
},
configurable: true,
enumerable: true,
},
arrayBuffer: {
/** @returns {Promise<ArrayBuffer>} */
value: function arrayBuffer() {
return consumeBody(this, "ArrayBuffer");
},
writable: true,
configurable: true,
enumerable: true,
},
blob: {
/** @returns {Promise<Blob>} */
value: function blob() {
return consumeBody(this, "Blob");
},
writable: true,
configurable: true,
enumerable: true,
},
formData: {
/** @returns {Promise<FormData>} */
value: function formData() {
return consumeBody(this, "FormData");
},
writable: true,
configurable: true,
enumerable: true,
},
json: {
/** @returns {Promise<any>} */
value: function json() {
return consumeBody(this, "JSON");
},
writable: true,
configurable: true,
enumerable: true,
},
text: {
/** @returns {Promise<string>} */
value: function text() {
return consumeBody(this, "text");
},
writable: true,
configurable: true,
enumerable: true,
},
};
return ObjectDefineProperties(prototype, mixin);
}
/**
* https://fetch.spec.whatwg.org/#concept-body-package-data
* @param {Uint8Array | string} bytes
* @param {"ArrayBuffer" | "Blob" | "FormData" | "JSON" | "text"} type
* @param {MimeType | null} [mimeType]
*/
function packageData(bytes, type, mimeType) {
switch (type) {
case "ArrayBuffer":
return chunkToU8(bytes).buffer;
case "Blob":
return new Blob([bytes], {
type: mimeType !== null ? mimesniff.serializeMimeType(mimeType) : "",
});
case "FormData": {
if (mimeType !== null) {
const essence = mimesniff.essence(mimeType);
if (essence === "multipart/form-data") {
const boundary = mimeType.parameters.get("boundary");
if (boundary === null) {
throw new TypeError(
"Missing boundary parameter in mime type of multipart formdata.",
); );
} }
throw new TypeError("Body can not be decoded as form data"); return parseFormData(chunkToU8(bytes), boundary);
} else if (essence === "application/x-www-form-urlencoded") {
// TODO(@AaronO): pass as-is with StringOrBuffer in op-layer
const entries = parseUrlEncoded(chunkToU8(bytes));
return formDataFromEntries(
ArrayPrototypeMap(
entries,
(x) => ({ name: x[0], value: x[1] }),
),
);
} }
throw new TypeError("Missing content type"); throw new TypeError("Body can not be decoded as form data");
} }
case "JSON": throw new TypeError("Missing content type");
return JSONParse(chunkToString(bytes)); }
case "text": case "JSON":
return chunkToString(bytes); return JSONParse(chunkToString(bytes));
case "text":
return chunkToString(bytes);
}
}
/**
* @param {BodyInit} object
* @returns {{body: InnerBody, contentType: string | null}}
*/
function extractBody(object) {
/** @type {ReadableStream<Uint8Array> | { body: Uint8Array | string, consumed: boolean }} */
let stream;
let source = null;
let length = null;
let contentType = null;
if (typeof object === "string") {
source = object;
contentType = "text/plain;charset=UTF-8";
} else if (ObjectPrototypeIsPrototypeOf(BlobPrototype, object)) {
stream = object.stream();
source = object;
length = object.size;
if (object.type.length !== 0) {
contentType = object.type;
}
} else if (ObjectPrototypeIsPrototypeOf(Uint8ArrayPrototype, object)) {
// Fast(er) path for common case of Uint8Array
const copy = TypedArrayPrototypeSlice(object, 0, object.byteLength);
source = copy;
} else if (
ArrayBufferIsView(object) ||
ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, object)
) {
const u8 = ArrayBufferIsView(object)
? new Uint8Array(
object.buffer,
object.byteOffset,
object.byteLength,
)
: new Uint8Array(object);
const copy = TypedArrayPrototypeSlice(u8, 0, u8.byteLength);
source = copy;
} else if (ObjectPrototypeIsPrototypeOf(FormDataPrototype, object)) {
const res = formDataToBlob(object);
stream = res.stream();
source = res;
length = res.size;
contentType = res.type;
} else if (
ObjectPrototypeIsPrototypeOf(URLSearchParamsPrototype, object)
) {
// TODO(@satyarohith): not sure what primordial here.
source = object.toString();
contentType = "application/x-www-form-urlencoded;charset=UTF-8";
} else if (ObjectPrototypeIsPrototypeOf(ReadableStreamPrototype, object)) {
stream = object;
if (object.locked || isReadableStreamDisturbed(object)) {
throw new TypeError("ReadableStream is locked or disturbed");
} }
} }
if (typeof source === "string") {
/** // WARNING: this deviates from spec (expects length to be set)
* @param {BodyInit} object // https://fetch.spec.whatwg.org/#bodyinit > 7.
* @returns {{body: InnerBody, contentType: string | null}} // no observable side-effect for users so far, but could change
*/ stream = { body: source, consumed: false };
function extractBody(object) { length = null; // NOTE: string length != byte length
/** @type {ReadableStream<Uint8Array> | { body: Uint8Array | string, consumed: boolean }} */ } else if (ObjectPrototypeIsPrototypeOf(Uint8ArrayPrototype, source)) {
let stream; stream = { body: source, consumed: false };
let source = null; length = source.byteLength;
let length = null;
let contentType = null;
if (typeof object === "string") {
source = object;
contentType = "text/plain;charset=UTF-8";
} else if (ObjectPrototypeIsPrototypeOf(BlobPrototype, object)) {
stream = object.stream();
source = object;
length = object.size;
if (object.type.length !== 0) {
contentType = object.type;
}
} else if (ObjectPrototypeIsPrototypeOf(Uint8ArrayPrototype, object)) {
// Fast(er) path for common case of Uint8Array
const copy = TypedArrayPrototypeSlice(object, 0, object.byteLength);
source = copy;
} else if (
ArrayBufferIsView(object) ||
ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, object)
) {
const u8 = ArrayBufferIsView(object)
? new Uint8Array(
object.buffer,
object.byteOffset,
object.byteLength,
)
: new Uint8Array(object);
const copy = TypedArrayPrototypeSlice(u8, 0, u8.byteLength);
source = copy;
} else if (ObjectPrototypeIsPrototypeOf(FormDataPrototype, object)) {
const res = formDataToBlob(object);
stream = res.stream();
source = res;
length = res.size;
contentType = res.type;
} else if (
ObjectPrototypeIsPrototypeOf(URLSearchParamsPrototype, object)
) {
// TODO(@satyarohith): not sure what primordial here.
source = object.toString();
contentType = "application/x-www-form-urlencoded;charset=UTF-8";
} else if (ObjectPrototypeIsPrototypeOf(ReadableStreamPrototype, object)) {
stream = object;
if (object.locked || isReadableStreamDisturbed(object)) {
throw new TypeError("ReadableStream is locked or disturbed");
}
}
if (typeof source === "string") {
// WARNING: this deviates from spec (expects length to be set)
// https://fetch.spec.whatwg.org/#bodyinit > 7.
// no observable side-effect for users so far, but could change
stream = { body: source, consumed: false };
length = null; // NOTE: string length != byte length
} else if (ObjectPrototypeIsPrototypeOf(Uint8ArrayPrototype, source)) {
stream = { body: source, consumed: false };
length = source.byteLength;
}
const body = new InnerBody(stream);
body.source = source;
body.length = length;
return { body, contentType };
} }
const body = new InnerBody(stream);
body.source = source;
body.length = length;
return { body, contentType };
}
webidl.converters["BodyInit_DOMString"] = (V, opts) => { webidl.converters["BodyInit_DOMString"] = (V, opts) => {
// Union for (ReadableStream or Blob or ArrayBufferView or ArrayBuffer or FormData or URLSearchParams or USVString) // Union for (ReadableStream or Blob or ArrayBufferView or ArrayBuffer or FormData or URLSearchParams or USVString)
if (ObjectPrototypeIsPrototypeOf(ReadableStreamPrototype, V)) { if (ObjectPrototypeIsPrototypeOf(ReadableStreamPrototype, V)) {
return webidl.converters["ReadableStream"](V, opts); return webidl.converters["ReadableStream"](V, opts);
} else if (ObjectPrototypeIsPrototypeOf(BlobPrototype, V)) { } else if (ObjectPrototypeIsPrototypeOf(BlobPrototype, V)) {
return webidl.converters["Blob"](V, opts); return webidl.converters["Blob"](V, opts);
} else if (ObjectPrototypeIsPrototypeOf(FormDataPrototype, V)) { } else if (ObjectPrototypeIsPrototypeOf(FormDataPrototype, V)) {
return webidl.converters["FormData"](V, opts); return webidl.converters["FormData"](V, opts);
} else if (ObjectPrototypeIsPrototypeOf(URLSearchParamsPrototype, V)) { } else if (ObjectPrototypeIsPrototypeOf(URLSearchParamsPrototype, V)) {
return webidl.converters["URLSearchParams"](V, opts); return webidl.converters["URLSearchParams"](V, opts);
}
if (typeof V === "object") {
if (
ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, V) ||
// deno-lint-ignore prefer-primordials
ObjectPrototypeIsPrototypeOf(SharedArrayBuffer.prototype, V)
) {
return webidl.converters["ArrayBuffer"](V, opts);
} }
if (typeof V === "object") { if (ArrayBufferIsView(V)) {
if ( return webidl.converters["ArrayBufferView"](V, opts);
ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, V) ||
// deno-lint-ignore prefer-primordials
ObjectPrototypeIsPrototypeOf(SharedArrayBuffer.prototype, V)
) {
return webidl.converters["ArrayBuffer"](V, opts);
}
if (ArrayBufferIsView(V)) {
return webidl.converters["ArrayBufferView"](V, opts);
}
} }
// BodyInit conversion is passed to extractBody(), which calls core.encode(). }
// core.encode() will UTF-8 encode strings with replacement, being equivalent to the USV normalization. // BodyInit conversion is passed to extractBody(), which calls core.encode().
// Therefore we can convert to DOMString instead of USVString and avoid a costly redundant conversion. // core.encode() will UTF-8 encode strings with replacement, being equivalent to the USV normalization.
return webidl.converters["DOMString"](V, opts); // Therefore we can convert to DOMString instead of USVString and avoid a costly redundant conversion.
}; return webidl.converters["DOMString"](V, opts);
webidl.converters["BodyInit_DOMString?"] = webidl.createNullableConverter( };
webidl.converters["BodyInit_DOMString"], webidl.converters["BodyInit_DOMString?"] = webidl.createNullableConverter(
); webidl.converters["BodyInit_DOMString"],
);
window.__bootstrap.fetchBody = { mixinBody, InnerBody, extractBody }; export { extractBody, InnerBody, mixinBody };
})(globalThis);

View file

@ -9,40 +9,34 @@
/// <reference path="../web/06_streams_types.d.ts" /> /// <reference path="../web/06_streams_types.d.ts" />
/// <reference path="./lib.deno_fetch.d.ts" /> /// <reference path="./lib.deno_fetch.d.ts" />
/// <reference lib="esnext" /> /// <reference lib="esnext" />
"use strict";
((window) => { const core = globalThis.Deno.core;
const core = window.Deno.core; const ops = core.ops;
const ops = core.ops;
/**
* @param {Deno.CreateHttpClientOptions} options
* @returns {HttpClient}
*/
function createHttpClient(options) {
options.caCerts ??= [];
return new HttpClient(
ops.op_fetch_custom_client(
options,
),
);
}
class HttpClient {
/** /**
* @param {Deno.CreateHttpClientOptions} options * @param {number} rid
* @returns {HttpClient}
*/ */
function createHttpClient(options) { constructor(rid) {
options.caCerts ??= []; this.rid = rid;
return new HttpClient(
ops.op_fetch_custom_client(
options,
),
);
} }
close() {
class HttpClient { core.close(this.rid);
/**
* @param {number} rid
*/
constructor(rid) {
this.rid = rid;
}
close() {
core.close(this.rid);
}
} }
const HttpClientPrototype = HttpClient.prototype; }
const HttpClientPrototype = HttpClient.prototype;
window.__bootstrap.fetch ??= {}; export { createHttpClient, HttpClient, HttpClientPrototype };
window.__bootstrap.fetch.createHttpClient = createHttpClient;
window.__bootstrap.fetch.HttpClient = HttpClient;
window.__bootstrap.fetch.HttpClientPrototype = HttpClientPrototype;
})(globalThis);

File diff suppressed because it is too large Load diff

View file

@ -9,510 +9,510 @@
/// <reference path="../web/06_streams_types.d.ts" /> /// <reference path="../web/06_streams_types.d.ts" />
/// <reference path="./lib.deno_fetch.d.ts" /> /// <reference path="./lib.deno_fetch.d.ts" />
/// <reference lib="esnext" /> /// <reference lib="esnext" />
"use strict";
((window) => { const core = globalThis.Deno.core;
const { isProxy } = Deno.core; import * as webidl from "internal:ext/webidl/00_webidl.js";
const webidl = window.__bootstrap.webidl; import { createFilteredInspectProxy } from "internal:ext/console/02_console.js";
const consoleInternal = window.__bootstrap.console; import {
const { byteLowerCase,
byteLowerCase, HTTP_TAB_OR_SPACE,
} = window.__bootstrap.infra; regexMatcher,
const { HTTP_TAB_OR_SPACE, regexMatcher, serializeJSValueToJSONString } = serializeJSValueToJSONString,
window.__bootstrap.infra; } from "internal:ext/web/00_infra.js";
const { extractBody, mixinBody } = window.__bootstrap.fetchBody; import { extractBody, mixinBody } from "internal:ext/fetch/22_body.js";
const { getLocationHref } = window.__bootstrap.location; import { getLocationHref } from "internal:ext/web/12_location.js";
const { extractMimeType } = window.__bootstrap.mimesniff; import { extractMimeType } from "internal:ext/web/01_mimesniff.js";
const { URL } = window.__bootstrap.url; import { URL } from "internal:ext/url/00_url.js";
const { import {
getDecodeSplitHeader, fillHeaders,
headerListFromHeaders, getDecodeSplitHeader,
headersFromHeaderList, guardFromHeaders,
guardFromHeaders, headerListFromHeaders,
fillHeaders, headersFromHeaderList,
} = window.__bootstrap.headers; } from "internal:ext/fetch/20_headers.js";
const { const primordials = globalThis.__bootstrap.primordials;
ArrayPrototypeMap, const {
ArrayPrototypePush, ArrayPrototypeMap,
ObjectDefineProperties, ArrayPrototypePush,
ObjectPrototypeIsPrototypeOf, ObjectDefineProperties,
RangeError, ObjectPrototypeIsPrototypeOf,
RegExp, RangeError,
RegExpPrototypeTest, RegExp,
SafeArrayIterator, RegExpPrototypeTest,
Symbol, SafeArrayIterator,
SymbolFor, Symbol,
TypeError, SymbolFor,
} = window.__bootstrap.primordials; TypeError,
} = primordials;
const VCHAR = ["\x21-\x7E"]; const VCHAR = ["\x21-\x7E"];
const OBS_TEXT = ["\x80-\xFF"]; const OBS_TEXT = ["\x80-\xFF"];
const REASON_PHRASE = [ const REASON_PHRASE = [
...new SafeArrayIterator(HTTP_TAB_OR_SPACE), ...new SafeArrayIterator(HTTP_TAB_OR_SPACE),
...new SafeArrayIterator(VCHAR), ...new SafeArrayIterator(VCHAR),
...new SafeArrayIterator(OBS_TEXT), ...new SafeArrayIterator(OBS_TEXT),
]; ];
const REASON_PHRASE_MATCHER = regexMatcher(REASON_PHRASE); const REASON_PHRASE_MATCHER = regexMatcher(REASON_PHRASE);
const REASON_PHRASE_RE = new RegExp(`^[${REASON_PHRASE_MATCHER}]*$`); const REASON_PHRASE_RE = new RegExp(`^[${REASON_PHRASE_MATCHER}]*$`);
const _response = Symbol("response"); const _response = Symbol("response");
const _headers = Symbol("headers"); const _headers = Symbol("headers");
const _mimeType = Symbol("mime type"); const _mimeType = Symbol("mime type");
const _body = Symbol("body"); const _body = Symbol("body");
/** /**
* @typedef InnerResponse * @typedef InnerResponse
* @property {"basic" | "cors" | "default" | "error" | "opaque" | "opaqueredirect"} type * @property {"basic" | "cors" | "default" | "error" | "opaque" | "opaqueredirect"} type
* @property {() => string | null} url * @property {() => string | null} url
* @property {string[]} urlList * @property {string[]} urlList
* @property {number} status * @property {number} status
* @property {string} statusMessage * @property {string} statusMessage
* @property {[string, string][]} headerList * @property {[string, string][]} headerList
* @property {null | typeof __window.bootstrap.fetchBody.InnerBody} body * @property {null | typeof __window.bootstrap.fetchBody.InnerBody} body
* @property {boolean} aborted * @property {boolean} aborted
* @property {string} [error] * @property {string} [error]
*/ */
/** /**
* @param {number} status * @param {number} status
* @returns {boolean} * @returns {boolean}
*/ */
function nullBodyStatus(status) { function nullBodyStatus(status) {
return status === 101 || status === 204 || status === 205 || status === 304; return status === 101 || status === 204 || status === 205 || status === 304;
} }
/** /**
* @param {number} status * @param {number} status
* @returns {boolean} * @returns {boolean}
*/ */
function redirectStatus(status) { function redirectStatus(status) {
return status === 301 || status === 302 || status === 303 || return status === 301 || status === 302 || status === 303 ||
status === 307 || status === 308; status === 307 || status === 308;
} }
/** /**
* https://fetch.spec.whatwg.org/#concept-response-clone * https://fetch.spec.whatwg.org/#concept-response-clone
* @param {InnerResponse} response * @param {InnerResponse} response
* @returns {InnerResponse} * @returns {InnerResponse}
*/ */
function cloneInnerResponse(response) { function cloneInnerResponse(response) {
const urlList = [...new SafeArrayIterator(response.urlList)]; const urlList = [...new SafeArrayIterator(response.urlList)];
const headerList = ArrayPrototypeMap( const headerList = ArrayPrototypeMap(
response.headerList, response.headerList,
(x) => [x[0], x[1]], (x) => [x[0], x[1]],
);
let body = null;
if (response.body !== null) {
body = response.body.clone();
}
return {
type: response.type,
body,
headerList,
urlList,
status: response.status,
statusMessage: response.statusMessage,
aborted: response.aborted,
url() {
if (this.urlList.length == 0) return null;
return this.urlList[this.urlList.length - 1];
},
};
}
/**
* @returns {InnerResponse}
*/
function newInnerResponse(status = 200, statusMessage = "") {
return {
type: "default",
body: null,
headerList: [],
urlList: [],
status,
statusMessage,
aborted: false,
url() {
if (this.urlList.length == 0) return null;
return this.urlList[this.urlList.length - 1];
},
};
}
/**
* @param {string} error
* @returns {InnerResponse}
*/
function networkError(error) {
const resp = newInnerResponse(0);
resp.type = "error";
resp.error = error;
return resp;
}
/**
* @returns {InnerResponse}
*/
function abortedNetworkError() {
const resp = networkError("aborted");
resp.aborted = true;
return resp;
}
/**
* https://fetch.spec.whatwg.org#initialize-a-response
* @param {Response} response
* @param {ResponseInit} init
* @param {{ body: __bootstrap.fetchBody.InnerBody, contentType: string | null } | null} bodyWithType
*/
function initializeAResponse(response, init, bodyWithType) {
// 1.
if ((init.status < 200 || init.status > 599) && init.status != 101) {
throw new RangeError(
`The status provided (${init.status}) is not equal to 101 and outside the range [200, 599].`,
);
}
// 2.
if (
init.statusText &&
!RegExpPrototypeTest(REASON_PHRASE_RE, init.statusText)
) {
throw new TypeError("Status text is not valid.");
}
// 3.
response[_response].status = init.status;
// 4.
response[_response].statusMessage = init.statusText;
// 5.
/** @type {__bootstrap.headers.Headers} */
const headers = response[_headers];
if (init.headers) {
fillHeaders(headers, init.headers);
}
// 6.
if (bodyWithType !== null) {
if (nullBodyStatus(response[_response].status)) {
throw new TypeError(
"Response with null body status cannot have body",
);
}
const { body, contentType } = bodyWithType;
response[_response].body = body;
if (contentType !== null) {
let hasContentType = false;
const list = headerListFromHeaders(headers);
for (let i = 0; i < list.length; i++) {
if (byteLowerCase(list[i][0]) === "content-type") {
hasContentType = true;
break;
}
}
if (!hasContentType) {
ArrayPrototypePush(list, ["Content-Type", contentType]);
}
}
}
}
class Response {
get [_mimeType]() {
const values = getDecodeSplitHeader(
headerListFromHeaders(this[_headers]),
"Content-Type",
);
return extractMimeType(values);
}
get [_body]() {
return this[_response].body;
}
/**
* @returns {Response}
*/
static error() {
const inner = newInnerResponse(0);
inner.type = "error";
const response = webidl.createBranded(Response);
response[_response] = inner;
response[_headers] = headersFromHeaderList(
response[_response].headerList,
"immutable",
);
return response;
}
/**
* @param {string} url
* @param {number} status
* @returns {Response}
*/
static redirect(url, status = 302) {
const prefix = "Failed to call 'Response.redirect'";
url = webidl.converters["USVString"](url, {
prefix,
context: "Argument 1",
});
status = webidl.converters["unsigned short"](status, {
prefix,
context: "Argument 2",
});
const baseURL = getLocationHref();
const parsedURL = new URL(url, baseURL);
if (!redirectStatus(status)) {
throw new RangeError("Invalid redirect status code.");
}
const inner = newInnerResponse(status);
inner.type = "default";
ArrayPrototypePush(inner.headerList, ["Location", parsedURL.href]);
const response = webidl.createBranded(Response);
response[_response] = inner;
response[_headers] = headersFromHeaderList(
response[_response].headerList,
"immutable",
);
return response;
}
/**
* @param {any} data
* @param {ResponseInit} init
* @returns {Response}
*/
static json(data = undefined, init = {}) {
const prefix = "Failed to call 'Response.json'";
data = webidl.converters.any(data);
init = webidl.converters["ResponseInit_fast"](init, {
prefix,
context: "Argument 2",
});
const str = serializeJSValueToJSONString(data);
const res = extractBody(str);
res.contentType = "application/json";
const response = webidl.createBranded(Response);
response[_response] = newInnerResponse();
response[_headers] = headersFromHeaderList(
response[_response].headerList,
"response",
);
initializeAResponse(response, init, res);
return response;
}
/**
* @param {BodyInit | null} body
* @param {ResponseInit} init
*/
constructor(body = null, init = undefined) {
const prefix = "Failed to construct 'Response'";
body = webidl.converters["BodyInit_DOMString?"](body, {
prefix,
context: "Argument 1",
});
init = webidl.converters["ResponseInit_fast"](init, {
prefix,
context: "Argument 2",
});
this[_response] = newInnerResponse();
this[_headers] = headersFromHeaderList(
this[_response].headerList,
"response",
);
let bodyWithType = null;
if (body !== null) {
bodyWithType = extractBody(body);
}
initializeAResponse(this, init, bodyWithType);
this[webidl.brand] = webidl.brand;
}
/**
* @returns {"basic" | "cors" | "default" | "error" | "opaque" | "opaqueredirect"}
*/
get type() {
webidl.assertBranded(this, ResponsePrototype);
return this[_response].type;
}
/**
* @returns {string}
*/
get url() {
webidl.assertBranded(this, ResponsePrototype);
const url = this[_response].url();
if (url === null) return "";
const newUrl = new URL(url);
newUrl.hash = "";
return newUrl.href;
}
/**
* @returns {boolean}
*/
get redirected() {
webidl.assertBranded(this, ResponsePrototype);
return this[_response].urlList.length > 1;
}
/**
* @returns {number}
*/
get status() {
webidl.assertBranded(this, ResponsePrototype);
return this[_response].status;
}
/**
* @returns {boolean}
*/
get ok() {
webidl.assertBranded(this, ResponsePrototype);
const status = this[_response].status;
return status >= 200 && status <= 299;
}
/**
* @returns {string}
*/
get statusText() {
webidl.assertBranded(this, ResponsePrototype);
return this[_response].statusMessage;
}
/**
* @returns {Headers}
*/
get headers() {
webidl.assertBranded(this, ResponsePrototype);
return this[_headers];
}
/**
* @returns {Response}
*/
clone() {
webidl.assertBranded(this, ResponsePrototype);
if (this[_body] && this[_body].unusable()) {
throw new TypeError("Body is unusable.");
}
const second = webidl.createBranded(Response);
const newRes = cloneInnerResponse(this[_response]);
second[_response] = newRes;
second[_headers] = headersFromHeaderList(
newRes.headerList,
guardFromHeaders(this[_headers]),
);
return second;
}
[SymbolFor("Deno.customInspect")](inspect) {
return inspect(consoleInternal.createFilteredInspectProxy({
object: this,
evaluate: ObjectPrototypeIsPrototypeOf(ResponsePrototype, this),
keys: [
"body",
"bodyUsed",
"headers",
"ok",
"redirected",
"status",
"statusText",
"url",
],
}));
}
}
webidl.configurePrototype(Response);
ObjectDefineProperties(Response, {
json: { enumerable: true },
redirect: { enumerable: true },
error: { enumerable: true },
});
const ResponsePrototype = Response.prototype;
mixinBody(ResponsePrototype, _body, _mimeType);
webidl.converters["Response"] = webidl.createInterfaceConverter(
"Response",
ResponsePrototype,
); );
webidl.converters["ResponseInit"] = webidl.createDictionaryConverter(
"ResponseInit", let body = null;
[{ if (response.body !== null) {
key: "status", body = response.body.clone();
defaultValue: 200, }
converter: webidl.converters["unsigned short"],
}, { return {
key: "statusText", type: response.type,
defaultValue: "", body,
converter: webidl.converters["ByteString"], headerList,
}, { urlList,
key: "headers", status: response.status,
converter: webidl.converters["HeadersInit"], statusMessage: response.statusMessage,
}], aborted: response.aborted,
); url() {
webidl.converters["ResponseInit_fast"] = function (init, opts) { if (this.urlList.length == 0) return null;
if (init === undefined || init === null) { return this.urlList[this.urlList.length - 1];
return { status: 200, statusText: "", headers: undefined }; },
}
// Fast path, if not a proxy
if (typeof init === "object" && !isProxy(init)) {
// Not a proxy fast path
const status = init.status !== undefined
? webidl.converters["unsigned short"](init.status)
: 200;
const statusText = init.statusText !== undefined
? webidl.converters["ByteString"](init.statusText)
: "";
const headers = init.headers !== undefined
? webidl.converters["HeadersInit"](init.headers)
: undefined;
return { status, statusText, headers };
}
// Slow default path
return webidl.converters["ResponseInit"](init, opts);
}; };
}
/** /**
* @param {Response} response * @returns {InnerResponse}
* @returns {InnerResponse} */
*/ function newInnerResponse(status = 200, statusMessage = "") {
function toInnerResponse(response) { return {
return response[_response]; type: "default",
body: null,
headerList: [],
urlList: [],
status,
statusMessage,
aborted: false,
url() {
if (this.urlList.length == 0) return null;
return this.urlList[this.urlList.length - 1];
},
};
}
/**
* @param {string} error
* @returns {InnerResponse}
*/
function networkError(error) {
const resp = newInnerResponse(0);
resp.type = "error";
resp.error = error;
return resp;
}
/**
* @returns {InnerResponse}
*/
function abortedNetworkError() {
const resp = networkError("aborted");
resp.aborted = true;
return resp;
}
/**
* https://fetch.spec.whatwg.org#initialize-a-response
* @param {Response} response
* @param {ResponseInit} init
* @param {{ body: fetchBody.InnerBody, contentType: string | null } | null} bodyWithType
*/
function initializeAResponse(response, init, bodyWithType) {
// 1.
if ((init.status < 200 || init.status > 599) && init.status != 101) {
throw new RangeError(
`The status provided (${init.status}) is not equal to 101 and outside the range [200, 599].`,
);
}
// 2.
if (
init.statusText &&
!RegExpPrototypeTest(REASON_PHRASE_RE, init.statusText)
) {
throw new TypeError("Status text is not valid.");
}
// 3.
response[_response].status = init.status;
// 4.
response[_response].statusMessage = init.statusText;
// 5.
/** @type {headers.Headers} */
const headers = response[_headers];
if (init.headers) {
fillHeaders(headers, init.headers);
}
// 6.
if (bodyWithType !== null) {
if (nullBodyStatus(response[_response].status)) {
throw new TypeError(
"Response with null body status cannot have body",
);
}
const { body, contentType } = bodyWithType;
response[_response].body = body;
if (contentType !== null) {
let hasContentType = false;
const list = headerListFromHeaders(headers);
for (let i = 0; i < list.length; i++) {
if (byteLowerCase(list[i][0]) === "content-type") {
hasContentType = true;
break;
}
}
if (!hasContentType) {
ArrayPrototypePush(list, ["Content-Type", contentType]);
}
}
}
}
class Response {
get [_mimeType]() {
const values = getDecodeSplitHeader(
headerListFromHeaders(this[_headers]),
"Content-Type",
);
return extractMimeType(values);
}
get [_body]() {
return this[_response].body;
} }
/** /**
* @param {InnerResponse} inner
* @param {"request" | "immutable" | "request-no-cors" | "response" | "none"} guard
* @returns {Response} * @returns {Response}
*/ */
function fromInnerResponse(inner, guard) { static error() {
const inner = newInnerResponse(0);
inner.type = "error";
const response = webidl.createBranded(Response); const response = webidl.createBranded(Response);
response[_response] = inner; response[_response] = inner;
response[_headers] = headersFromHeaderList(inner.headerList, guard); response[_headers] = headersFromHeaderList(
response[_response].headerList,
"immutable",
);
return response; return response;
} }
window.__bootstrap.fetch ??= {}; /**
window.__bootstrap.fetch.Response = Response; * @param {string} url
window.__bootstrap.fetch.ResponsePrototype = ResponsePrototype; * @param {number} status
window.__bootstrap.fetch.newInnerResponse = newInnerResponse; * @returns {Response}
window.__bootstrap.fetch.toInnerResponse = toInnerResponse; */
window.__bootstrap.fetch.fromInnerResponse = fromInnerResponse; static redirect(url, status = 302) {
window.__bootstrap.fetch.redirectStatus = redirectStatus; const prefix = "Failed to call 'Response.redirect'";
window.__bootstrap.fetch.nullBodyStatus = nullBodyStatus; url = webidl.converters["USVString"](url, {
window.__bootstrap.fetch.networkError = networkError; prefix,
window.__bootstrap.fetch.abortedNetworkError = abortedNetworkError; context: "Argument 1",
})(globalThis); });
status = webidl.converters["unsigned short"](status, {
prefix,
context: "Argument 2",
});
const baseURL = getLocationHref();
const parsedURL = new URL(url, baseURL);
if (!redirectStatus(status)) {
throw new RangeError("Invalid redirect status code.");
}
const inner = newInnerResponse(status);
inner.type = "default";
ArrayPrototypePush(inner.headerList, ["Location", parsedURL.href]);
const response = webidl.createBranded(Response);
response[_response] = inner;
response[_headers] = headersFromHeaderList(
response[_response].headerList,
"immutable",
);
return response;
}
/**
* @param {any} data
* @param {ResponseInit} init
* @returns {Response}
*/
static json(data = undefined, init = {}) {
const prefix = "Failed to call 'Response.json'";
data = webidl.converters.any(data);
init = webidl.converters["ResponseInit_fast"](init, {
prefix,
context: "Argument 2",
});
const str = serializeJSValueToJSONString(data);
const res = extractBody(str);
res.contentType = "application/json";
const response = webidl.createBranded(Response);
response[_response] = newInnerResponse();
response[_headers] = headersFromHeaderList(
response[_response].headerList,
"response",
);
initializeAResponse(response, init, res);
return response;
}
/**
* @param {BodyInit | null} body
* @param {ResponseInit} init
*/
constructor(body = null, init = undefined) {
const prefix = "Failed to construct 'Response'";
body = webidl.converters["BodyInit_DOMString?"](body, {
prefix,
context: "Argument 1",
});
init = webidl.converters["ResponseInit_fast"](init, {
prefix,
context: "Argument 2",
});
this[_response] = newInnerResponse();
this[_headers] = headersFromHeaderList(
this[_response].headerList,
"response",
);
let bodyWithType = null;
if (body !== null) {
bodyWithType = extractBody(body);
}
initializeAResponse(this, init, bodyWithType);
this[webidl.brand] = webidl.brand;
}
/**
* @returns {"basic" | "cors" | "default" | "error" | "opaque" | "opaqueredirect"}
*/
get type() {
webidl.assertBranded(this, ResponsePrototype);
return this[_response].type;
}
/**
* @returns {string}
*/
get url() {
webidl.assertBranded(this, ResponsePrototype);
const url = this[_response].url();
if (url === null) return "";
const newUrl = new URL(url);
newUrl.hash = "";
return newUrl.href;
}
/**
* @returns {boolean}
*/
get redirected() {
webidl.assertBranded(this, ResponsePrototype);
return this[_response].urlList.length > 1;
}
/**
* @returns {number}
*/
get status() {
webidl.assertBranded(this, ResponsePrototype);
return this[_response].status;
}
/**
* @returns {boolean}
*/
get ok() {
webidl.assertBranded(this, ResponsePrototype);
const status = this[_response].status;
return status >= 200 && status <= 299;
}
/**
* @returns {string}
*/
get statusText() {
webidl.assertBranded(this, ResponsePrototype);
return this[_response].statusMessage;
}
/**
* @returns {Headers}
*/
get headers() {
webidl.assertBranded(this, ResponsePrototype);
return this[_headers];
}
/**
* @returns {Response}
*/
clone() {
webidl.assertBranded(this, ResponsePrototype);
if (this[_body] && this[_body].unusable()) {
throw new TypeError("Body is unusable.");
}
const second = webidl.createBranded(Response);
const newRes = cloneInnerResponse(this[_response]);
second[_response] = newRes;
second[_headers] = headersFromHeaderList(
newRes.headerList,
guardFromHeaders(this[_headers]),
);
return second;
}
[SymbolFor("Deno.customInspect")](inspect) {
return inspect(createFilteredInspectProxy({
object: this,
evaluate: ObjectPrototypeIsPrototypeOf(ResponsePrototype, this),
keys: [
"body",
"bodyUsed",
"headers",
"ok",
"redirected",
"status",
"statusText",
"url",
],
}));
}
}
webidl.configurePrototype(Response);
ObjectDefineProperties(Response, {
json: { enumerable: true },
redirect: { enumerable: true },
error: { enumerable: true },
});
const ResponsePrototype = Response.prototype;
mixinBody(ResponsePrototype, _body, _mimeType);
webidl.converters["Response"] = webidl.createInterfaceConverter(
"Response",
ResponsePrototype,
);
webidl.converters["ResponseInit"] = webidl.createDictionaryConverter(
"ResponseInit",
[{
key: "status",
defaultValue: 200,
converter: webidl.converters["unsigned short"],
}, {
key: "statusText",
defaultValue: "",
converter: webidl.converters["ByteString"],
}, {
key: "headers",
converter: webidl.converters["HeadersInit"],
}],
);
webidl.converters["ResponseInit_fast"] = function (init, opts) {
if (init === undefined || init === null) {
return { status: 200, statusText: "", headers: undefined };
}
// Fast path, if not a proxy
if (typeof init === "object" && !core.isProxy(init)) {
// Not a proxy fast path
const status = init.status !== undefined
? webidl.converters["unsigned short"](init.status)
: 200;
const statusText = init.statusText !== undefined
? webidl.converters["ByteString"](init.statusText)
: "";
const headers = init.headers !== undefined
? webidl.converters["HeadersInit"](init.headers)
: undefined;
return { status, statusText, headers };
}
// Slow default path
return webidl.converters["ResponseInit"](init, opts);
};
/**
* @param {Response} response
* @returns {InnerResponse}
*/
function toInnerResponse(response) {
return response[_response];
}
/**
* @param {InnerResponse} inner
* @param {"request" | "immutable" | "request-no-cors" | "response" | "none"} guard
* @returns {Response}
*/
function fromInnerResponse(inner, guard) {
const response = webidl.createBranded(Response);
response[_response] = inner;
response[_headers] = headersFromHeaderList(inner.headerList, guard);
return response;
}
export {
abortedNetworkError,
fromInnerResponse,
networkError,
newInnerResponse,
nullBodyStatus,
redirectStatus,
Response,
ResponsePrototype,
toInnerResponse,
};

File diff suppressed because it is too large Load diff

View file

@ -5,106 +5,98 @@
/// <reference no-default-lib="true" /> /// <reference no-default-lib="true" />
/// <reference lib="esnext" /> /// <reference lib="esnext" />
declare namespace globalThis { declare var domIterable: {
declare namespace __bootstrap { DomIterableMixin(base: any, dataSymbol: symbol): any;
declare var fetchUtil: { };
requiredArguments(name: string, length: number, required: number): void;
};
declare var domIterable: { declare module "internal:ext/fetch/20_headers.js" {
DomIterableMixin(base: any, dataSymbol: symbol): any; class Headers {
};
declare namespace headers {
class Headers {
}
type HeaderList = [string, string][];
function headersFromHeaderList(
list: HeaderList,
guard:
| "immutable"
| "request"
| "request-no-cors"
| "response"
| "none",
): Headers;
function headerListFromHeaders(headers: Headers): HeaderList;
function fillHeaders(headers: Headers, object: HeadersInit): void;
function getDecodeSplitHeader(
list: HeaderList,
name: string,
): string[] | null;
function guardFromHeaders(
headers: Headers,
): "immutable" | "request" | "request-no-cors" | "response" | "none";
}
declare namespace formData {
declare type FormData = typeof FormData;
declare function formDataToBlob(
formData: globalThis.FormData,
): Blob;
declare function parseFormData(
body: Uint8Array,
boundary: string | undefined,
): FormData;
declare function formDataFromEntries(entries: FormDataEntry[]): FormData;
}
declare namespace fetchBody {
function mixinBody(
prototype: any,
bodySymbol: symbol,
mimeTypeSymbol: symbol,
): void;
class InnerBody {
constructor(stream?: ReadableStream<Uint8Array>);
stream: ReadableStream<Uint8Array>;
source: null | Uint8Array | Blob | FormData;
length: null | number;
unusable(): boolean;
consume(): Promise<Uint8Array>;
clone(): InnerBody;
}
function extractBody(object: BodyInit): {
body: InnerBody;
contentType: string | null;
};
}
declare namespace fetch {
function toInnerRequest(request: Request): InnerRequest;
function fromInnerRequest(
inner: InnerRequest,
signal: AbortSignal | null,
guard:
| "request"
| "immutable"
| "request-no-cors"
| "response"
| "none",
skipBody: boolean,
flash: boolean,
): Request;
function redirectStatus(status: number): boolean;
function nullBodyStatus(status: number): boolean;
function newInnerRequest(
method: string,
url: any,
headerList?: [string, string][],
body?: globalThis.__bootstrap.fetchBody.InnerBody,
): InnerResponse;
function toInnerResponse(response: Response): InnerResponse;
function fromInnerResponse(
inner: InnerResponse,
guard:
| "request"
| "immutable"
| "request-no-cors"
| "response"
| "none",
): Response;
function networkError(error: string): InnerResponse;
}
} }
type HeaderList = [string, string][];
function headersFromHeaderList(
list: HeaderList,
guard:
| "immutable"
| "request"
| "request-no-cors"
| "response"
| "none",
): Headers;
function headerListFromHeaders(headers: Headers): HeaderList;
function fillHeaders(headers: Headers, object: HeadersInit): void;
function getDecodeSplitHeader(
list: HeaderList,
name: string,
): string[] | null;
function guardFromHeaders(
headers: Headers,
): "immutable" | "request" | "request-no-cors" | "response" | "none";
}
declare module "internal:ext/fetch/21_formdata.js" {
type FormData = typeof FormData;
function formDataToBlob(
formData: FormData,
): Blob;
function parseFormData(
body: Uint8Array,
boundary: string | undefined,
): FormData;
function formDataFromEntries(entries: FormDataEntry[]): FormData;
}
declare module "internal:ext/fetch/22_body.js" {
function mixinBody(
prototype: any,
bodySymbol: symbol,
mimeTypeSymbol: symbol,
): void;
class InnerBody {
constructor(stream?: ReadableStream<Uint8Array>);
stream: ReadableStream<Uint8Array>;
source: null | Uint8Array | Blob | FormData;
length: null | number;
unusable(): boolean;
consume(): Promise<Uint8Array>;
clone(): InnerBody;
}
function extractBody(object: BodyInit): {
body: InnerBody;
contentType: string | null;
};
}
declare module "internal:ext/fetch/26_fetch.js" {
function toInnerRequest(request: Request): InnerRequest;
function fromInnerRequest(
inner: InnerRequest,
signal: AbortSignal | null,
guard:
| "request"
| "immutable"
| "request-no-cors"
| "response"
| "none",
skipBody: boolean,
flash: boolean,
): Request;
function redirectStatus(status: number): boolean;
function nullBodyStatus(status: number): boolean;
function newInnerRequest(
method: string,
url: any,
headerList?: [string, string][],
body?: fetchBody.InnerBody,
): InnerResponse;
function toInnerResponse(response: Response): InnerResponse;
function fromInnerResponse(
inner: InnerResponse,
guard:
| "request"
| "immutable"
| "request-no-cors"
| "response"
| "none",
): Response;
function networkError(error: string): InnerResponse;
} }

View file

@ -97,9 +97,8 @@ where
{ {
Extension::builder(env!("CARGO_PKG_NAME")) Extension::builder(env!("CARGO_PKG_NAME"))
.dependencies(vec!["deno_webidl", "deno_web", "deno_url", "deno_console"]) .dependencies(vec!["deno_webidl", "deno_web", "deno_url", "deno_console"])
.js(include_js_files!( .esm(include_js_files!(
prefix "internal:ext/fetch", prefix "internal:ext/fetch",
"01_fetch_util.js",
"20_headers.js", "20_headers.js",
"21_formdata.js", "21_formdata.js",
"22_body.js", "22_body.js",

View file

@ -1,510 +1,509 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
"use strict";
((window) => { const core = globalThis.Deno.core;
const core = window.Deno.core; const ops = core.ops;
const ops = core.ops; const internals = globalThis.__bootstrap.internals;
const __bootstrap = window.__bootstrap; const primordials = globalThis.__bootstrap.primordials;
const { const {
ArrayPrototypeMap, ArrayPrototypeMap,
ArrayPrototypeJoin, ArrayPrototypeJoin,
ObjectDefineProperty, ObjectDefineProperty,
ObjectPrototypeHasOwnProperty, ObjectPrototypeHasOwnProperty,
ObjectPrototypeIsPrototypeOf, ObjectPrototypeIsPrototypeOf,
Number, Number,
NumberIsSafeInteger, NumberIsSafeInteger,
TypeError, TypeError,
Uint8Array, Uint8Array,
Int32Array, Int32Array,
Uint32Array, Uint32Array,
BigInt64Array, BigInt64Array,
BigUint64Array, BigUint64Array,
Function, Function,
ReflectHas, ReflectHas,
PromisePrototypeThen, PromisePrototypeThen,
MathMax, MathMax,
MathCeil, MathCeil,
SafeMap, SafeMap,
SafeArrayIterator, SafeArrayIterator,
} = window.__bootstrap.primordials; } = primordials;
const U32_BUFFER = new Uint32Array(2); const U32_BUFFER = new Uint32Array(2);
const U64_BUFFER = new BigUint64Array(U32_BUFFER.buffer); const U64_BUFFER = new BigUint64Array(U32_BUFFER.buffer);
const I64_BUFFER = new BigInt64Array(U32_BUFFER.buffer); const I64_BUFFER = new BigInt64Array(U32_BUFFER.buffer);
class UnsafePointerView { class UnsafePointerView {
pointer; pointer;
constructor(pointer) { constructor(pointer) {
this.pointer = pointer; this.pointer = pointer;
}
getBool(offset = 0) {
return ops.op_ffi_read_bool(
this.pointer,
offset,
);
}
getUint8(offset = 0) {
return ops.op_ffi_read_u8(
this.pointer,
offset,
);
}
getInt8(offset = 0) {
return ops.op_ffi_read_i8(
this.pointer,
offset,
);
}
getUint16(offset = 0) {
return ops.op_ffi_read_u16(
this.pointer,
offset,
);
}
getInt16(offset = 0) {
return ops.op_ffi_read_i16(
this.pointer,
offset,
);
}
getUint32(offset = 0) {
return ops.op_ffi_read_u32(
this.pointer,
offset,
);
}
getInt32(offset = 0) {
return ops.op_ffi_read_i32(
this.pointer,
offset,
);
}
getBigUint64(offset = 0) {
ops.op_ffi_read_u64(
this.pointer,
offset,
U32_BUFFER,
);
return U64_BUFFER[0];
}
getBigInt64(offset = 0) {
ops.op_ffi_read_i64(
this.pointer,
offset,
U32_BUFFER,
);
return I64_BUFFER[0];
}
getFloat32(offset = 0) {
return ops.op_ffi_read_f32(
this.pointer,
offset,
);
}
getFloat64(offset = 0) {
return ops.op_ffi_read_f64(
this.pointer,
offset,
);
}
getCString(offset = 0) {
return ops.op_ffi_cstr_read(
this.pointer,
offset,
);
}
static getCString(pointer, offset = 0) {
return ops.op_ffi_cstr_read(
pointer,
offset,
);
}
getArrayBuffer(byteLength, offset = 0) {
return ops.op_ffi_get_buf(
this.pointer,
offset,
byteLength,
);
}
static getArrayBuffer(pointer, byteLength, offset = 0) {
return ops.op_ffi_get_buf(
pointer,
offset,
byteLength,
);
}
copyInto(destination, offset = 0) {
ops.op_ffi_buf_copy_into(
this.pointer,
offset,
destination,
destination.byteLength,
);
}
static copyInto(pointer, destination, offset = 0) {
ops.op_ffi_buf_copy_into(
pointer,
offset,
destination,
destination.byteLength,
);
}
} }
const OUT_BUFFER = new Uint32Array(2); getBool(offset = 0) {
const OUT_BUFFER_64 = new BigInt64Array(OUT_BUFFER.buffer); return ops.op_ffi_read_bool(
class UnsafePointer { this.pointer,
static of(value) { offset,
if (ObjectPrototypeIsPrototypeOf(UnsafeCallbackPrototype, value)) { );
return value.pointer;
}
ops.op_ffi_ptr_of(value, OUT_BUFFER);
const result = OUT_BUFFER[0] + 2 ** 32 * OUT_BUFFER[1];
if (NumberIsSafeInteger(result)) {
return result;
}
return OUT_BUFFER_64[0];
}
} }
class UnsafeFnPointer { getUint8(offset = 0) {
pointer; return ops.op_ffi_read_u8(
definition; this.pointer,
#structSize; offset,
);
}
constructor(pointer, definition) { getInt8(offset = 0) {
this.pointer = pointer; return ops.op_ffi_read_i8(
this.definition = definition; this.pointer,
this.#structSize = isStruct(definition.result) offset,
? getTypeSizeAndAlignment(definition.result)[0] );
: null; }
getUint16(offset = 0) {
return ops.op_ffi_read_u16(
this.pointer,
offset,
);
}
getInt16(offset = 0) {
return ops.op_ffi_read_i16(
this.pointer,
offset,
);
}
getUint32(offset = 0) {
return ops.op_ffi_read_u32(
this.pointer,
offset,
);
}
getInt32(offset = 0) {
return ops.op_ffi_read_i32(
this.pointer,
offset,
);
}
getBigUint64(offset = 0) {
ops.op_ffi_read_u64(
this.pointer,
offset,
U32_BUFFER,
);
return U64_BUFFER[0];
}
getBigInt64(offset = 0) {
ops.op_ffi_read_i64(
this.pointer,
offset,
U32_BUFFER,
);
return I64_BUFFER[0];
}
getFloat32(offset = 0) {
return ops.op_ffi_read_f32(
this.pointer,
offset,
);
}
getFloat64(offset = 0) {
return ops.op_ffi_read_f64(
this.pointer,
offset,
);
}
getCString(offset = 0) {
return ops.op_ffi_cstr_read(
this.pointer,
offset,
);
}
static getCString(pointer, offset = 0) {
return ops.op_ffi_cstr_read(
pointer,
offset,
);
}
getArrayBuffer(byteLength, offset = 0) {
return ops.op_ffi_get_buf(
this.pointer,
offset,
byteLength,
);
}
static getArrayBuffer(pointer, byteLength, offset = 0) {
return ops.op_ffi_get_buf(
pointer,
offset,
byteLength,
);
}
copyInto(destination, offset = 0) {
ops.op_ffi_buf_copy_into(
this.pointer,
offset,
destination,
destination.byteLength,
);
}
static copyInto(pointer, destination, offset = 0) {
ops.op_ffi_buf_copy_into(
pointer,
offset,
destination,
destination.byteLength,
);
}
}
const OUT_BUFFER = new Uint32Array(2);
const OUT_BUFFER_64 = new BigInt64Array(OUT_BUFFER.buffer);
class UnsafePointer {
static of(value) {
if (ObjectPrototypeIsPrototypeOf(UnsafeCallbackPrototype, value)) {
return value.pointer;
} }
ops.op_ffi_ptr_of(value, OUT_BUFFER);
const result = OUT_BUFFER[0] + 2 ** 32 * OUT_BUFFER[1];
if (NumberIsSafeInteger(result)) {
return result;
}
return OUT_BUFFER_64[0];
}
}
call(...parameters) { class UnsafeFnPointer {
if (this.definition.nonblocking) { pointer;
if (this.#structSize === null) { definition;
return core.opAsync( #structSize;
constructor(pointer, definition) {
this.pointer = pointer;
this.definition = definition;
this.#structSize = isStruct(definition.result)
? getTypeSizeAndAlignment(definition.result)[0]
: null;
}
call(...parameters) {
if (this.definition.nonblocking) {
if (this.#structSize === null) {
return core.opAsync(
"op_ffi_call_ptr_nonblocking",
this.pointer,
this.definition,
parameters,
);
} else {
const buffer = new Uint8Array(this.#structSize);
return PromisePrototypeThen(
core.opAsync(
"op_ffi_call_ptr_nonblocking", "op_ffi_call_ptr_nonblocking",
this.pointer, this.pointer,
this.definition, this.definition,
parameters, parameters,
);
} else {
const buffer = new Uint8Array(this.#structSize);
return PromisePrototypeThen(
core.opAsync(
"op_ffi_call_ptr_nonblocking",
this.pointer,
this.definition,
parameters,
buffer,
),
() => buffer,
);
}
} else {
if (this.#structSize === null) {
return ops.op_ffi_call_ptr(
this.pointer,
this.definition,
parameters,
);
} else {
const buffer = new Uint8Array(this.#structSize);
ops.op_ffi_call_ptr(
this.pointer,
this.definition,
parameters,
buffer, buffer,
); ),
return buffer; () => buffer,
}
}
}
}
function isReturnedAsBigInt(type) {
return type === "buffer" || type === "pointer" || type === "function" ||
type === "u64" || type === "i64" ||
type === "usize" || type === "isize";
}
function isI64(type) {
return type === "i64" || type === "isize";
}
function isStruct(type) {
return typeof type === "object" && type !== null &&
typeof type.struct === "object";
}
function getTypeSizeAndAlignment(type, cache = new SafeMap()) {
if (isStruct(type)) {
const cached = cache.get(type);
if (cached !== undefined) {
if (cached === null) {
throw new TypeError("Recursive struct definition");
}
return cached;
}
cache.set(type, null);
let size = 0;
let alignment = 1;
for (const field of new SafeArrayIterator(type.struct)) {
const { 0: fieldSize, 1: fieldAlign } = getTypeSizeAndAlignment(
field,
cache,
);
alignment = MathMax(alignment, fieldAlign);
size = MathCeil(size / fieldAlign) * fieldAlign;
size += fieldSize;
}
size = MathCeil(size / alignment) * alignment;
cache.set(type, size);
return [size, alignment];
}
switch (type) {
case "bool":
case "u8":
case "i8":
return [1, 1];
case "u16":
case "i16":
return [2, 2];
case "u32":
case "i32":
case "f32":
return [4, 4];
case "u64":
case "i64":
case "f64":
case "pointer":
case "buffer":
case "function":
case "usize":
case "isize":
return [8, 8];
default:
throw new TypeError(`Unsupported type: ${type}`);
}
}
class UnsafeCallback {
#refcount;
// Internal promise only meant to keep Deno from exiting
#refpromise;
#rid;
definition;
callback;
pointer;
constructor(definition, callback) {
if (definition.nonblocking) {
throw new TypeError(
"Invalid UnsafeCallback, cannot be nonblocking",
); );
} }
const { 0: rid, 1: pointer } = ops.op_ffi_unsafe_callback_create( } else {
definition, if (this.#structSize === null) {
callback, return ops.op_ffi_call_ptr(
this.pointer,
this.definition,
parameters,
);
} else {
const buffer = new Uint8Array(this.#structSize);
ops.op_ffi_call_ptr(
this.pointer,
this.definition,
parameters,
buffer,
);
return buffer;
}
}
}
}
function isReturnedAsBigInt(type) {
return type === "buffer" || type === "pointer" || type === "function" ||
type === "u64" || type === "i64" ||
type === "usize" || type === "isize";
}
function isI64(type) {
return type === "i64" || type === "isize";
}
function isStruct(type) {
return typeof type === "object" && type !== null &&
typeof type.struct === "object";
}
function getTypeSizeAndAlignment(type, cache = new SafeMap()) {
if (isStruct(type)) {
const cached = cache.get(type);
if (cached !== undefined) {
if (cached === null) {
throw new TypeError("Recursive struct definition");
}
return cached;
}
cache.set(type, null);
let size = 0;
let alignment = 1;
for (const field of new SafeArrayIterator(type.struct)) {
const { 0: fieldSize, 1: fieldAlign } = getTypeSizeAndAlignment(
field,
cache,
); );
this.#refcount = 0; alignment = MathMax(alignment, fieldAlign);
this.#rid = rid; size = MathCeil(size / fieldAlign) * fieldAlign;
this.pointer = pointer; size += fieldSize;
this.definition = definition;
this.callback = callback;
}
ref() {
if (this.#refcount++ === 0) {
this.#refpromise = core.opAsync(
"op_ffi_unsafe_callback_ref",
this.#rid,
);
}
return this.#refcount;
}
unref() {
// Only decrement refcount if it is positive, and only
// unref the callback if refcount reaches zero.
if (this.#refcount > 0 && --this.#refcount === 0) {
ops.op_ffi_unsafe_callback_unref(this.#rid);
}
return this.#refcount;
}
close() {
this.#refcount = 0;
core.close(this.#rid);
} }
size = MathCeil(size / alignment) * alignment;
cache.set(type, size);
return [size, alignment];
} }
const UnsafeCallbackPrototype = UnsafeCallback.prototype; switch (type) {
case "bool":
case "u8":
case "i8":
return [1, 1];
case "u16":
case "i16":
return [2, 2];
case "u32":
case "i32":
case "f32":
return [4, 4];
case "u64":
case "i64":
case "f64":
case "pointer":
case "buffer":
case "function":
case "usize":
case "isize":
return [8, 8];
default:
throw new TypeError(`Unsupported type: ${type}`);
}
}
class DynamicLibrary { class UnsafeCallback {
#rid; #refcount;
symbols = {}; // Internal promise only meant to keep Deno from exiting
#refpromise;
#rid;
definition;
callback;
pointer;
constructor(path, symbols) { constructor(definition, callback) {
({ 0: this.#rid, 1: this.symbols } = ops.op_ffi_load({ path, symbols })); if (definition.nonblocking) {
for (const symbol in symbols) { throw new TypeError(
if (!ObjectPrototypeHasOwnProperty(symbols, symbol)) { "Invalid UnsafeCallback, cannot be nonblocking",
continue; );
}
const { 0: rid, 1: pointer } = ops.op_ffi_unsafe_callback_create(
definition,
callback,
);
this.#refcount = 0;
this.#rid = rid;
this.pointer = pointer;
this.definition = definition;
this.callback = callback;
}
ref() {
if (this.#refcount++ === 0) {
this.#refpromise = core.opAsync(
"op_ffi_unsafe_callback_ref",
this.#rid,
);
}
return this.#refcount;
}
unref() {
// Only decrement refcount if it is positive, and only
// unref the callback if refcount reaches zero.
if (this.#refcount > 0 && --this.#refcount === 0) {
ops.op_ffi_unsafe_callback_unref(this.#rid);
}
return this.#refcount;
}
close() {
this.#refcount = 0;
core.close(this.#rid);
}
}
const UnsafeCallbackPrototype = UnsafeCallback.prototype;
class DynamicLibrary {
#rid;
symbols = {};
constructor(path, symbols) {
({ 0: this.#rid, 1: this.symbols } = ops.op_ffi_load({ path, symbols }));
for (const symbol in symbols) {
if (!ObjectPrototypeHasOwnProperty(symbols, symbol)) {
continue;
}
if (ReflectHas(symbols[symbol], "type")) {
const type = symbols[symbol].type;
if (type === "void") {
throw new TypeError(
"Foreign symbol of type 'void' is not supported.",
);
} }
if (ReflectHas(symbols[symbol], "type")) { const name = symbols[symbol].name || symbol;
const type = symbols[symbol].type; const value = ops.op_ffi_get_static(
if (type === "void") { this.#rid,
throw new TypeError( name,
"Foreign symbol of type 'void' is not supported.", type,
); );
} ObjectDefineProperty(
this.symbols,
symbol,
{
configurable: false,
enumerable: true,
value,
writable: false,
},
);
continue;
}
const resultType = symbols[symbol].result;
const isStructResult = isStruct(resultType);
const structSize = isStructResult
? getTypeSizeAndAlignment(resultType)[0]
: 0;
const needsUnpacking = isReturnedAsBigInt(resultType);
const name = symbols[symbol].name || symbol; const isNonBlocking = symbols[symbol].nonblocking;
const value = ops.op_ffi_get_static( if (isNonBlocking) {
this.#rid, ObjectDefineProperty(
name, this.symbols,
type, symbol,
); {
ObjectDefineProperty( configurable: false,
this.symbols, enumerable: true,
symbol, value: (...parameters) => {
{ if (isStructResult) {
configurable: false, const buffer = new Uint8Array(structSize);
enumerable: true, const ret = core.opAsync(
value, "op_ffi_call_nonblocking",
writable: false, this.#rid,
symbol,
parameters,
buffer,
);
return PromisePrototypeThen(
ret,
() => buffer,
);
} else {
return core.opAsync(
"op_ffi_call_nonblocking",
this.#rid,
symbol,
parameters,
);
}
}, },
); writable: false,
continue; },
} );
const resultType = symbols[symbol].result; }
const isStructResult = isStruct(resultType);
const structSize = isStructResult
? getTypeSizeAndAlignment(resultType)[0]
: 0;
const needsUnpacking = isReturnedAsBigInt(resultType);
const isNonBlocking = symbols[symbol].nonblocking; if (needsUnpacking && !isNonBlocking) {
if (isNonBlocking) { const call = this.symbols[symbol];
ObjectDefineProperty( const parameters = symbols[symbol].parameters;
this.symbols, const vi = new Int32Array(2);
symbol, const vui = new Uint32Array(vi.buffer);
{ const b = new BigInt64Array(vi.buffer);
configurable: false,
enumerable: true,
value: (...parameters) => {
if (isStructResult) {
const buffer = new Uint8Array(structSize);
const ret = core.opAsync(
"op_ffi_call_nonblocking",
this.#rid,
symbol,
parameters,
buffer,
);
return PromisePrototypeThen(
ret,
() => buffer,
);
} else {
return core.opAsync(
"op_ffi_call_nonblocking",
this.#rid,
symbol,
parameters,
);
}
},
writable: false,
},
);
}
if (needsUnpacking && !isNonBlocking) { const params = ArrayPrototypeJoin(
const call = this.symbols[symbol]; ArrayPrototypeMap(parameters, (_, index) => `p${index}`),
const parameters = symbols[symbol].parameters; ", ",
const vi = new Int32Array(2); );
const vui = new Uint32Array(vi.buffer); // Make sure V8 has no excuse to not optimize this function.
const b = new BigInt64Array(vi.buffer); this.symbols[symbol] = new Function(
"vi",
const params = ArrayPrototypeJoin( "vui",
ArrayPrototypeMap(parameters, (_, index) => `p${index}`), "b",
", ", "call",
); "NumberIsSafeInteger",
// Make sure V8 has no excuse to not optimize this function. "Number",
this.symbols[symbol] = new Function( `return function (${params}) {
"vi",
"vui",
"b",
"call",
"NumberIsSafeInteger",
"Number",
`return function (${params}) {
call(${params}${parameters.length > 0 ? ", " : ""}vi); call(${params}${parameters.length > 0 ? ", " : ""}vi);
${ ${
isI64(resultType) isI64(resultType)
? `const n1 = Number(b[0])` ? `const n1 = Number(b[0])`
: `const n1 = vui[0] + 2 ** 32 * vui[1]` // Faster path for u64 : `const n1 = vui[0] + 2 ** 32 * vui[1]` // Faster path for u64
}; };
if (NumberIsSafeInteger(n1)) return n1; if (NumberIsSafeInteger(n1)) return n1;
return b[0]; return b[0];
}`, }`,
)(vi, vui, b, call, NumberIsSafeInteger, Number); )(vi, vui, b, call, NumberIsSafeInteger, Number);
} else if (isStructResult && !isNonBlocking) { } else if (isStructResult && !isNonBlocking) {
const call = this.symbols[symbol]; const call = this.symbols[symbol];
const parameters = symbols[symbol].parameters; const parameters = symbols[symbol].parameters;
const params = ArrayPrototypeJoin( const params = ArrayPrototypeJoin(
ArrayPrototypeMap(parameters, (_, index) => `p${index}`), ArrayPrototypeMap(parameters, (_, index) => `p${index}`),
", ", ", ",
); );
this.symbols[symbol] = new Function( this.symbols[symbol] = new Function(
"call", "call",
`return function (${params}) { `return function (${params}) {
const buffer = new Uint8Array(${structSize}); const buffer = new Uint8Array(${structSize});
call(${params}${parameters.length > 0 ? ", " : ""}buffer); call(${params}${parameters.length > 0 ? ", " : ""}buffer);
return buffer; return buffer;
}`, }`,
)(call); )(call);
}
} }
} }
close() {
core.close(this.#rid);
}
} }
function dlopen(path, symbols) { close() {
// URL support is progressively enhanced by util in `runtime/js`. core.close(this.#rid);
const pathFromURL = __bootstrap.util.pathFromURL ?? ((p) => p);
return new DynamicLibrary(pathFromURL(path), symbols);
} }
}
window.__bootstrap.ffi = { function dlopen(path, symbols) {
dlopen, // TODO(@crowlKats): remove me
UnsafeCallback, // URL support is progressively enhanced by util in `runtime/js`.
UnsafePointer, const pathFromURL = internals.pathFromURL ?? ((p) => p);
UnsafePointerView, return new DynamicLibrary(pathFromURL(path), symbols);
UnsafeFnPointer, }
};
})(this); export {
dlopen,
UnsafeCallback,
UnsafeFnPointer,
UnsafePointer,
UnsafePointerView,
};

View file

@ -84,7 +84,7 @@ pub(crate) struct FfiState {
pub fn init<P: FfiPermissions + 'static>(unstable: bool) -> Extension { pub fn init<P: FfiPermissions + 'static>(unstable: bool) -> Extension {
Extension::builder(env!("CARGO_PKG_NAME")) Extension::builder(env!("CARGO_PKG_NAME"))
.js(include_js_files!( .esm(include_js_files!(
prefix "internal:ext/ffi", prefix "internal:ext/ffi",
"00_ffi.js", "00_ffi.js",
)) ))

File diff suppressed because it is too large Load diff

View file

@ -1514,7 +1514,7 @@ pub fn init<P: FlashPermissions + 'static>(unstable: bool) -> Extension {
"deno_websocket", "deno_websocket",
"deno_http", "deno_http",
]) ])
.js(deno_core::include_js_files!( .esm(deno_core::include_js_files!(
prefix "internal:ext/flash", prefix "internal:ext/flash",
"01_http.js", "01_http.js",
)) ))

View file

@ -1,296 +1,318 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
"use strict"; const core = globalThis.Deno.core;
const primordials = globalThis.__bootstrap.primordials;
const { BadResourcePrototype, InterruptedPrototype, ops } = core;
import * as webidl from "internal:ext/webidl/00_webidl.js";
import { InnerBody } from "internal:ext/fetch/22_body.js";
import { Event, setEventTargetData } from "internal:ext/web/02_event.js";
import { BlobPrototype } from "internal:ext/web/09_file.js";
import {
fromInnerResponse,
newInnerResponse,
ResponsePrototype,
toInnerResponse,
} from "internal:ext/fetch/23_response.js";
import {
_flash,
fromInnerRequest,
newInnerRequest,
} from "internal:ext/fetch/23_request.js";
import * as abortSignal from "internal:ext/web/03_abort_signal.js";
import {
_eventLoop,
_idleTimeoutDuration,
_idleTimeoutTimeout,
_protocol,
_readyState,
_rid,
_server,
_serverHandleIdleTimeout,
WebSocket,
} from "internal:ext/websocket/01_websocket.js";
import { TcpConn, UnixConn } from "internal:ext/net/01_net.js";
import { TlsConn } from "internal:ext/net/02_tls.js";
import {
Deferred,
getReadableStreamResourceBacking,
readableStreamClose,
readableStreamForRid,
ReadableStreamPrototype,
} from "internal:ext/web/06_streams.js";
const {
ArrayPrototypeIncludes,
ArrayPrototypePush,
ArrayPrototypeSome,
Error,
ObjectPrototypeIsPrototypeOf,
SafeSetIterator,
Set,
SetPrototypeAdd,
SetPrototypeDelete,
StringPrototypeIncludes,
StringPrototypeToLowerCase,
StringPrototypeSplit,
Symbol,
SymbolAsyncIterator,
TypeError,
Uint8Array,
Uint8ArrayPrototype,
} = primordials;
((window) => { const connErrorSymbol = Symbol("connError");
const webidl = window.__bootstrap.webidl; const _deferred = Symbol("upgradeHttpDeferred");
const { InnerBody } = window.__bootstrap.fetchBody;
const { Event } = window.__bootstrap.event;
const { setEventTargetData } = window.__bootstrap.eventTarget;
const { BlobPrototype } = window.__bootstrap.file;
const {
ResponsePrototype,
fromInnerRequest,
toInnerResponse,
newInnerRequest,
newInnerResponse,
fromInnerResponse,
_flash,
} = window.__bootstrap.fetch;
const core = window.Deno.core;
const { BadResourcePrototype, InterruptedPrototype, ops } = core;
const { ReadableStreamPrototype } = window.__bootstrap.streams;
const abortSignal = window.__bootstrap.abortSignal;
const {
WebSocket,
_rid,
_readyState,
_eventLoop,
_protocol,
_server,
_idleTimeoutDuration,
_idleTimeoutTimeout,
_serverHandleIdleTimeout,
} = window.__bootstrap.webSocket;
const { TcpConn, UnixConn } = window.__bootstrap.net;
const { TlsConn } = window.__bootstrap.tls;
const {
Deferred,
getReadableStreamResourceBacking,
readableStreamForRid,
readableStreamClose,
} = window.__bootstrap.streams;
const {
ArrayPrototypeIncludes,
ArrayPrototypePush,
ArrayPrototypeSome,
Error,
ObjectPrototypeIsPrototypeOf,
SafeSetIterator,
Set,
SetPrototypeAdd,
SetPrototypeDelete,
StringPrototypeIncludes,
StringPrototypeToLowerCase,
StringPrototypeSplit,
Symbol,
SymbolAsyncIterator,
TypeError,
Uint8Array,
Uint8ArrayPrototype,
} = window.__bootstrap.primordials;
const connErrorSymbol = Symbol("connError"); class HttpConn {
const _deferred = Symbol("upgradeHttpDeferred"); #rid = 0;
#closed = false;
#remoteAddr;
#localAddr;
class HttpConn { // This set holds resource ids of resources
#rid = 0; // that were created during lifecycle of this request.
#closed = false; // When the connection is closed these resources should be closed
#remoteAddr; // as well.
#localAddr; managedResources = new Set();
// This set holds resource ids of resources constructor(rid, remoteAddr, localAddr) {
// that were created during lifecycle of this request. this.#rid = rid;
// When the connection is closed these resources should be closed this.#remoteAddr = remoteAddr;
// as well. this.#localAddr = localAddr;
managedResources = new Set(); }
constructor(rid, remoteAddr, localAddr) { /** @returns {number} */
this.#rid = rid; get rid() {
this.#remoteAddr = remoteAddr; return this.#rid;
this.#localAddr = localAddr; }
}
/** @returns {number} */ /** @returns {Promise<RequestEvent | null>} */
get rid() { async nextRequest() {
return this.#rid; let nextRequest;
} try {
nextRequest = await core.opAsync("op_http_accept", this.#rid);
/** @returns {Promise<RequestEvent | null>} */ } catch (error) {
async nextRequest() { this.close();
let nextRequest; // A connection error seen here would cause disrupted responses to throw
try { // a generic `BadResource` error. Instead store this error and replace
nextRequest = await core.opAsync("op_http_accept", this.#rid); // those with it.
} catch (error) { this[connErrorSymbol] = error;
this.close(); if (
// A connection error seen here would cause disrupted responses to throw ObjectPrototypeIsPrototypeOf(BadResourcePrototype, error) ||
// a generic `BadResource` error. Instead store this error and replace ObjectPrototypeIsPrototypeOf(InterruptedPrototype, error) ||
// those with it. StringPrototypeIncludes(error.message, "connection closed")
this[connErrorSymbol] = error; ) {
if (
ObjectPrototypeIsPrototypeOf(BadResourcePrototype, error) ||
ObjectPrototypeIsPrototypeOf(InterruptedPrototype, error) ||
StringPrototypeIncludes(error.message, "connection closed")
) {
return null;
}
throw error;
}
if (nextRequest == null) {
// Work-around for servers (deno_std/http in particular) that call
// `nextRequest()` before upgrading a previous request which has a
// `connection: upgrade` header.
await null;
this.close();
return null; return null;
} }
throw error;
}
if (nextRequest == null) {
// Work-around for servers (deno_std/http in particular) that call
// `nextRequest()` before upgrading a previous request which has a
// `connection: upgrade` header.
await null;
const { 0: streamRid, 1: method, 2: url } = nextRequest; this.close();
SetPrototypeAdd(this.managedResources, streamRid); return null;
/** @type {ReadableStream<Uint8Array> | undefined} */
let body = null;
// There might be a body, but we don't expose it for GET/HEAD requests.
// It will be closed automatically once the request has been handled and
// the response has been sent.
if (method !== "GET" && method !== "HEAD") {
body = readableStreamForRid(streamRid, false);
}
const innerRequest = newInnerRequest(
() => method,
url,
() => ops.op_http_headers(streamRid),
body !== null ? new InnerBody(body) : null,
false,
);
const signal = abortSignal.newSignal();
const request = fromInnerRequest(
innerRequest,
signal,
"immutable",
false,
);
const respondWith = createRespondWith(
this,
streamRid,
request,
this.#remoteAddr,
this.#localAddr,
);
return { request, respondWith };
} }
/** @returns {void} */ const { 0: streamRid, 1: method, 2: url } = nextRequest;
close() { SetPrototypeAdd(this.managedResources, streamRid);
if (!this.#closed) {
this.#closed = true; /** @type {ReadableStream<Uint8Array> | undefined} */
core.close(this.#rid); let body = null;
for (const rid of new SafeSetIterator(this.managedResources)) { // There might be a body, but we don't expose it for GET/HEAD requests.
SetPrototypeDelete(this.managedResources, rid); // It will be closed automatically once the request has been handled and
core.close(rid); // the response has been sent.
} if (method !== "GET" && method !== "HEAD") {
} body = readableStreamForRid(streamRid, false);
} }
[SymbolAsyncIterator]() { const innerRequest = newInnerRequest(
// deno-lint-ignore no-this-alias () => method,
const httpConn = this; url,
return { () => ops.op_http_headers(streamRid),
async next() { body !== null ? new InnerBody(body) : null,
const reqEvt = await httpConn.nextRequest(); false,
// Change with caution, current form avoids a v8 deopt );
return { value: reqEvt ?? undefined, done: reqEvt === null }; const signal = abortSignal.newSignal();
}, const request = fromInnerRequest(
}; innerRequest,
signal,
"immutable",
false,
);
const respondWith = createRespondWith(
this,
streamRid,
request,
this.#remoteAddr,
this.#localAddr,
);
return { request, respondWith };
}
/** @returns {void} */
close() {
if (!this.#closed) {
this.#closed = true;
core.close(this.#rid);
for (const rid of new SafeSetIterator(this.managedResources)) {
SetPrototypeDelete(this.managedResources, rid);
core.close(rid);
}
} }
} }
function createRespondWith( [SymbolAsyncIterator]() {
httpConn, // deno-lint-ignore no-this-alias
streamRid, const httpConn = this;
request, return {
remoteAddr, async next() {
localAddr, const reqEvt = await httpConn.nextRequest();
) { // Change with caution, current form avoids a v8 deopt
return async function respondWith(resp) { return { value: reqEvt ?? undefined, done: reqEvt === null };
try { },
resp = await resp; };
if (!(ObjectPrototypeIsPrototypeOf(ResponsePrototype, resp))) { }
throw new TypeError( }
"First argument to respondWith must be a Response or a promise resolving to a Response.",
); function createRespondWith(
httpConn,
streamRid,
request,
remoteAddr,
localAddr,
) {
return async function respondWith(resp) {
try {
resp = await resp;
if (!(ObjectPrototypeIsPrototypeOf(ResponsePrototype, resp))) {
throw new TypeError(
"First argument to respondWith must be a Response or a promise resolving to a Response.",
);
}
const innerResp = toInnerResponse(resp);
// If response body length is known, it will be sent synchronously in a
// single op, in other case a "response body" resource will be created and
// we'll be streaming it.
/** @type {ReadableStream<Uint8Array> | Uint8Array | null} */
let respBody = null;
if (innerResp.body !== null) {
if (innerResp.body.unusable()) {
throw new TypeError("Body is unusable.");
} }
if (
const innerResp = toInnerResponse(resp); ObjectPrototypeIsPrototypeOf(
ReadableStreamPrototype,
// If response body length is known, it will be sent synchronously in a innerResp.body.streamOrStatic,
// single op, in other case a "response body" resource will be created and )
// we'll be streaming it. ) {
/** @type {ReadableStream<Uint8Array> | Uint8Array | null} */
let respBody = null;
if (innerResp.body !== null) {
if (innerResp.body.unusable()) {
throw new TypeError("Body is unusable.");
}
if ( if (
innerResp.body.length === null ||
ObjectPrototypeIsPrototypeOf( ObjectPrototypeIsPrototypeOf(
ReadableStreamPrototype, BlobPrototype,
innerResp.body.streamOrStatic, innerResp.body.source,
) )
) { ) {
if ( respBody = innerResp.body.stream;
innerResp.body.length === null ||
ObjectPrototypeIsPrototypeOf(
BlobPrototype,
innerResp.body.source,
)
) {
respBody = innerResp.body.stream;
} else {
const reader = innerResp.body.stream.getReader();
const r1 = await reader.read();
if (r1.done) {
respBody = new Uint8Array(0);
} else {
respBody = r1.value;
const r2 = await reader.read();
if (!r2.done) throw new TypeError("Unreachable");
}
}
} else { } else {
innerResp.body.streamOrStatic.consumed = true; const reader = innerResp.body.stream.getReader();
respBody = innerResp.body.streamOrStatic.body; const r1 = await reader.read();
if (r1.done) {
respBody = new Uint8Array(0);
} else {
respBody = r1.value;
const r2 = await reader.read();
if (!r2.done) throw new TypeError("Unreachable");
}
} }
} else { } else {
respBody = new Uint8Array(0); innerResp.body.streamOrStatic.consumed = true;
respBody = innerResp.body.streamOrStatic.body;
} }
const isStreamingResponseBody = !( } else {
typeof respBody === "string" || respBody = new Uint8Array(0);
ObjectPrototypeIsPrototypeOf(Uint8ArrayPrototype, respBody) }
const isStreamingResponseBody = !(
typeof respBody === "string" ||
ObjectPrototypeIsPrototypeOf(Uint8ArrayPrototype, respBody)
);
try {
await core.opAsync(
"op_http_write_headers",
streamRid,
innerResp.status ?? 200,
innerResp.headerList,
isStreamingResponseBody ? null : respBody,
); );
try { } catch (error) {
await core.opAsync( const connError = httpConn[connErrorSymbol];
"op_http_write_headers", if (
streamRid, ObjectPrototypeIsPrototypeOf(BadResourcePrototype, error) &&
innerResp.status ?? 200, connError != null
innerResp.headerList, ) {
isStreamingResponseBody ? null : respBody, // deno-lint-ignore no-ex-assign
); error = new connError.constructor(connError.message);
} catch (error) {
const connError = httpConn[connErrorSymbol];
if (
ObjectPrototypeIsPrototypeOf(BadResourcePrototype, error) &&
connError != null
) {
// deno-lint-ignore no-ex-assign
error = new connError.constructor(connError.message);
}
if (
respBody !== null &&
ObjectPrototypeIsPrototypeOf(ReadableStreamPrototype, respBody)
) {
await respBody.cancel(error);
}
throw error;
} }
if (
respBody !== null &&
ObjectPrototypeIsPrototypeOf(ReadableStreamPrototype, respBody)
) {
await respBody.cancel(error);
}
throw error;
}
if (isStreamingResponseBody) { if (isStreamingResponseBody) {
let success = false; let success = false;
if ( if (
respBody === null || respBody === null ||
!ObjectPrototypeIsPrototypeOf(ReadableStreamPrototype, respBody) !ObjectPrototypeIsPrototypeOf(ReadableStreamPrototype, respBody)
) { ) {
throw new TypeError("Unreachable"); throw new TypeError("Unreachable");
}
const resourceBacking = getReadableStreamResourceBacking(respBody);
let reader;
if (resourceBacking) {
if (respBody.locked) {
throw new TypeError("ReadableStream is locked.");
} }
const resourceBacking = getReadableStreamResourceBacking(respBody); reader = respBody.getReader(); // Aquire JS lock.
let reader; try {
if (resourceBacking) { await core.opAsync(
if (respBody.locked) { "op_http_write_resource",
throw new TypeError("ReadableStream is locked."); streamRid,
resourceBacking.rid,
);
if (resourceBacking.autoClose) core.tryClose(resourceBacking.rid);
readableStreamClose(respBody); // Release JS lock.
success = true;
} catch (error) {
const connError = httpConn[connErrorSymbol];
if (
ObjectPrototypeIsPrototypeOf(BadResourcePrototype, error) &&
connError != null
) {
// deno-lint-ignore no-ex-assign
error = new connError.constructor(connError.message);
}
await reader.cancel(error);
throw error;
}
} else {
reader = respBody.getReader();
while (true) {
const { value, done } = await reader.read();
if (done) break;
if (!ObjectPrototypeIsPrototypeOf(Uint8ArrayPrototype, value)) {
await reader.cancel(new TypeError("Value not a Uint8Array"));
break;
} }
reader = respBody.getReader(); // Aquire JS lock.
try { try {
await core.opAsync( await core.opAsync("op_http_write", streamRid, value);
"op_http_write_resource",
streamRid,
resourceBacking.rid,
);
if (resourceBacking.autoClose) core.tryClose(resourceBacking.rid);
readableStreamClose(respBody); // Release JS lock.
success = true;
} catch (error) { } catch (error) {
const connError = httpConn[connErrorSymbol]; const connError = httpConn[connErrorSymbol];
if ( if (
@ -303,176 +325,147 @@
await reader.cancel(error); await reader.cancel(error);
throw error; throw error;
} }
} else {
reader = respBody.getReader();
while (true) {
const { value, done } = await reader.read();
if (done) break;
if (!ObjectPrototypeIsPrototypeOf(Uint8ArrayPrototype, value)) {
await reader.cancel(new TypeError("Value not a Uint8Array"));
break;
}
try {
await core.opAsync("op_http_write", streamRid, value);
} catch (error) {
const connError = httpConn[connErrorSymbol];
if (
ObjectPrototypeIsPrototypeOf(BadResourcePrototype, error) &&
connError != null
) {
// deno-lint-ignore no-ex-assign
error = new connError.constructor(connError.message);
}
await reader.cancel(error);
throw error;
}
}
success = true;
}
if (success) {
try {
await core.opAsync("op_http_shutdown", streamRid);
} catch (error) {
await reader.cancel(error);
throw error;
}
} }
success = true;
} }
const deferred = request[_deferred]; if (success) {
if (deferred) { try {
const res = await core.opAsync("op_http_upgrade", streamRid); await core.opAsync("op_http_shutdown", streamRid);
let conn; } catch (error) {
if (res.connType === "tcp") { await reader.cancel(error);
conn = new TcpConn(res.connRid, remoteAddr, localAddr); throw error;
} else if (res.connType === "tls") {
conn = new TlsConn(res.connRid, remoteAddr, localAddr);
} else if (res.connType === "unix") {
conn = new UnixConn(res.connRid, remoteAddr, localAddr);
} else {
throw new Error("unreachable");
} }
deferred.resolve([conn, res.readBuf]);
}
const ws = resp[_ws];
if (ws) {
const wsRid = await core.opAsync(
"op_http_upgrade_websocket",
streamRid,
);
ws[_rid] = wsRid;
ws[_protocol] = resp.headers.get("sec-websocket-protocol");
httpConn.close();
ws[_readyState] = WebSocket.OPEN;
const event = new Event("open");
ws.dispatchEvent(event);
ws[_eventLoop]();
if (ws[_idleTimeoutDuration]) {
ws.addEventListener(
"close",
() => clearTimeout(ws[_idleTimeoutTimeout]),
);
}
ws[_serverHandleIdleTimeout]();
}
} finally {
if (SetPrototypeDelete(httpConn.managedResources, streamRid)) {
core.close(streamRid);
} }
} }
};
}
const _ws = Symbol("[[associated_ws]]"); const deferred = request[_deferred];
if (deferred) {
const res = await core.opAsync("op_http_upgrade", streamRid);
let conn;
if (res.connType === "tcp") {
conn = new TcpConn(res.connRid, remoteAddr, localAddr);
} else if (res.connType === "tls") {
conn = new TlsConn(res.connRid, remoteAddr, localAddr);
} else if (res.connType === "unix") {
conn = new UnixConn(res.connRid, remoteAddr, localAddr);
} else {
throw new Error("unreachable");
}
function upgradeWebSocket(request, options = {}) { deferred.resolve([conn, res.readBuf]);
const upgrade = request.headers.get("upgrade"); }
const upgradeHasWebSocketOption = upgrade !== null && const ws = resp[_ws];
ArrayPrototypeSome( if (ws) {
StringPrototypeSplit(upgrade, /\s*,\s*/), const wsRid = await core.opAsync(
(option) => StringPrototypeToLowerCase(option) === "websocket", "op_http_upgrade_websocket",
); streamRid,
if (!upgradeHasWebSocketOption) {
throw new TypeError(
"Invalid Header: 'upgrade' header must contain 'websocket'",
);
}
const connection = request.headers.get("connection");
const connectionHasUpgradeOption = connection !== null &&
ArrayPrototypeSome(
StringPrototypeSplit(connection, /\s*,\s*/),
(option) => StringPrototypeToLowerCase(option) === "upgrade",
);
if (!connectionHasUpgradeOption) {
throw new TypeError(
"Invalid Header: 'connection' header must contain 'Upgrade'",
);
}
const websocketKey = request.headers.get("sec-websocket-key");
if (websocketKey === null) {
throw new TypeError(
"Invalid Header: 'sec-websocket-key' header must be set",
);
}
const accept = ops.op_http_websocket_accept_header(websocketKey);
const r = newInnerResponse(101);
r.headerList = [
["upgrade", "websocket"],
["connection", "Upgrade"],
["sec-websocket-accept", accept],
];
const protocolsStr = request.headers.get("sec-websocket-protocol") || "";
const protocols = StringPrototypeSplit(protocolsStr, ", ");
if (protocols && options.protocol) {
if (ArrayPrototypeIncludes(protocols, options.protocol)) {
ArrayPrototypePush(r.headerList, [
"sec-websocket-protocol",
options.protocol,
]);
} else {
throw new TypeError(
`Protocol '${options.protocol}' not in the request's protocol list (non negotiable)`,
); );
ws[_rid] = wsRid;
ws[_protocol] = resp.headers.get("sec-websocket-protocol");
httpConn.close();
ws[_readyState] = WebSocket.OPEN;
const event = new Event("open");
ws.dispatchEvent(event);
ws[_eventLoop]();
if (ws[_idleTimeoutDuration]) {
ws.addEventListener(
"close",
() => clearTimeout(ws[_idleTimeoutTimeout]),
);
}
ws[_serverHandleIdleTimeout]();
}
} finally {
if (SetPrototypeDelete(httpConn.managedResources, streamRid)) {
core.close(streamRid);
} }
} }
};
}
const response = fromInnerResponse(r, "immutable"); const _ws = Symbol("[[associated_ws]]");
const socket = webidl.createBranded(WebSocket); function upgradeWebSocket(request, options = {}) {
setEventTargetData(socket); const upgrade = request.headers.get("upgrade");
socket[_server] = true; const upgradeHasWebSocketOption = upgrade !== null &&
response[_ws] = socket; ArrayPrototypeSome(
socket[_idleTimeoutDuration] = options.idleTimeout ?? 120; StringPrototypeSplit(upgrade, /\s*,\s*/),
socket[_idleTimeoutTimeout] = null; (option) => StringPrototypeToLowerCase(option) === "websocket",
);
return { response, socket }; if (!upgradeHasWebSocketOption) {
throw new TypeError(
"Invalid Header: 'upgrade' header must contain 'websocket'",
);
} }
function upgradeHttp(req) { const connection = request.headers.get("connection");
if (req[_flash]) { const connectionHasUpgradeOption = connection !== null &&
ArrayPrototypeSome(
StringPrototypeSplit(connection, /\s*,\s*/),
(option) => StringPrototypeToLowerCase(option) === "upgrade",
);
if (!connectionHasUpgradeOption) {
throw new TypeError(
"Invalid Header: 'connection' header must contain 'Upgrade'",
);
}
const websocketKey = request.headers.get("sec-websocket-key");
if (websocketKey === null) {
throw new TypeError(
"Invalid Header: 'sec-websocket-key' header must be set",
);
}
const accept = ops.op_http_websocket_accept_header(websocketKey);
const r = newInnerResponse(101);
r.headerList = [
["upgrade", "websocket"],
["connection", "Upgrade"],
["sec-websocket-accept", accept],
];
const protocolsStr = request.headers.get("sec-websocket-protocol") || "";
const protocols = StringPrototypeSplit(protocolsStr, ", ");
if (protocols && options.protocol) {
if (ArrayPrototypeIncludes(protocols, options.protocol)) {
ArrayPrototypePush(r.headerList, [
"sec-websocket-protocol",
options.protocol,
]);
} else {
throw new TypeError( throw new TypeError(
"Flash requests can not be upgraded with `upgradeHttp`. Use `upgradeHttpRaw` instead.", `Protocol '${options.protocol}' not in the request's protocol list (non negotiable)`,
); );
} }
req[_deferred] = new Deferred();
return req[_deferred].promise;
} }
window.__bootstrap.http = { const response = fromInnerResponse(r, "immutable");
HttpConn,
upgradeWebSocket, const socket = webidl.createBranded(WebSocket);
upgradeHttp, setEventTargetData(socket);
_ws, socket[_server] = true;
}; response[_ws] = socket;
})(this); socket[_idleTimeoutDuration] = options.idleTimeout ?? 120;
socket[_idleTimeoutTimeout] = null;
return { response, socket };
}
function upgradeHttp(req) {
if (req[_flash]) {
throw new TypeError(
"Flash requests can not be upgraded with `upgradeHttp`. Use `upgradeHttpRaw` instead.",
);
}
req[_deferred] = new Deferred();
return req[_deferred].promise;
}
export { _ws, HttpConn, upgradeHttp, upgradeWebSocket };

View file

@ -80,7 +80,7 @@ mod reader_stream;
pub fn init() -> Extension { pub fn init() -> Extension {
Extension::builder(env!("CARGO_PKG_NAME")) Extension::builder(env!("CARGO_PKG_NAME"))
.dependencies(vec!["deno_web", "deno_net", "deno_fetch", "deno_websocket"]) .dependencies(vec!["deno_web", "deno_net", "deno_fetch", "deno_websocket"])
.js(include_js_files!( .esm(include_js_files!(
prefix "internal:ext/http", prefix "internal:ext/http",
"01_http.js", "01_http.js",
)) ))

View file

@ -1,423 +1,421 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
"use strict";
((window) => { const core = globalThis.Deno.core;
const core = window.Deno.core; const { BadResourcePrototype, InterruptedPrototype, ops } = core;
const { BadResourcePrototype, InterruptedPrototype, ops } = core; import {
const { readableStreamForRidUnrefable,
readableStreamForRidUnrefable, readableStreamForRidUnrefableRef,
readableStreamForRidUnrefableRef, readableStreamForRidUnrefableUnref,
readableStreamForRidUnrefableUnref, writableStreamForRid,
writableStreamForRid, } from "internal:ext/web/06_streams.js";
} = window.__bootstrap.streams; const primordials = globalThis.__bootstrap.primordials;
const { const {
Error, Error,
ObjectPrototypeIsPrototypeOf, ObjectPrototypeIsPrototypeOf,
PromiseResolve, PromiseResolve,
SymbolAsyncIterator, SymbolAsyncIterator,
SymbolFor, SymbolFor,
TypedArrayPrototypeSubarray, TypedArrayPrototypeSubarray,
TypeError, TypeError,
Uint8Array, Uint8Array,
} = window.__bootstrap.primordials; } = primordials;
const promiseIdSymbol = SymbolFor("Deno.core.internalPromiseId"); const promiseIdSymbol = SymbolFor("Deno.core.internalPromiseId");
async function write(rid, data) { async function write(rid, data) {
return await core.write(rid, data); return await core.write(rid, data);
}
function shutdown(rid) {
return core.shutdown(rid);
}
function resolveDns(query, recordType, options) {
return core.opAsync("op_dns_resolve", { query, recordType, options });
}
class Conn {
#rid = 0;
#remoteAddr = null;
#localAddr = null;
#unref = false;
#pendingReadPromiseIds = [];
#readable;
#writable;
constructor(rid, remoteAddr, localAddr) {
this.#rid = rid;
this.#remoteAddr = remoteAddr;
this.#localAddr = localAddr;
} }
function shutdown(rid) { get rid() {
return core.shutdown(rid); return this.#rid;
} }
function resolveDns(query, recordType, options) { get remoteAddr() {
return core.opAsync("op_dns_resolve", { query, recordType, options }); return this.#remoteAddr;
} }
class Conn { get localAddr() {
#rid = 0; return this.#localAddr;
#remoteAddr = null; }
#localAddr = null;
#unref = false;
#pendingReadPromiseIds = [];
#readable; write(p) {
#writable; return write(this.rid, p);
}
constructor(rid, remoteAddr, localAddr) { async read(buffer) {
this.#rid = rid; if (buffer.length === 0) {
this.#remoteAddr = remoteAddr; return 0;
this.#localAddr = localAddr;
} }
const promise = core.read(this.rid, buffer);
get rid() { const promiseId = promise[promiseIdSymbol];
return this.#rid; if (this.#unref) core.unrefOp(promiseId);
this.#pendingReadPromiseIds.push(promiseId);
let nread;
try {
nread = await promise;
} catch (e) {
throw e;
} finally {
this.#pendingReadPromiseIds = this.#pendingReadPromiseIds.filter((id) =>
id !== promiseId
);
} }
return nread === 0 ? null : nread;
}
get remoteAddr() { close() {
return this.#remoteAddr; core.close(this.rid);
} }
get localAddr() { closeWrite() {
return this.#localAddr; return shutdown(this.rid);
} }
write(p) { get readable() {
return write(this.rid, p); if (this.#readable === undefined) {
} this.#readable = readableStreamForRidUnrefable(this.rid);
if (this.#unref) {
async read(buffer) {
if (buffer.length === 0) {
return 0;
}
const promise = core.read(this.rid, buffer);
const promiseId = promise[promiseIdSymbol];
if (this.#unref) core.unrefOp(promiseId);
this.#pendingReadPromiseIds.push(promiseId);
let nread;
try {
nread = await promise;
} catch (e) {
throw e;
} finally {
this.#pendingReadPromiseIds = this.#pendingReadPromiseIds.filter((id) =>
id !== promiseId
);
}
return nread === 0 ? null : nread;
}
close() {
core.close(this.rid);
}
closeWrite() {
return shutdown(this.rid);
}
get readable() {
if (this.#readable === undefined) {
this.#readable = readableStreamForRidUnrefable(this.rid);
if (this.#unref) {
readableStreamForRidUnrefableUnref(this.#readable);
}
}
return this.#readable;
}
get writable() {
if (this.#writable === undefined) {
this.#writable = writableStreamForRid(this.rid);
}
return this.#writable;
}
ref() {
this.#unref = false;
if (this.#readable) {
readableStreamForRidUnrefableRef(this.#readable);
}
this.#pendingReadPromiseIds.forEach((id) => core.refOp(id));
}
unref() {
this.#unref = true;
if (this.#readable) {
readableStreamForRidUnrefableUnref(this.#readable); readableStreamForRidUnrefableUnref(this.#readable);
} }
this.#pendingReadPromiseIds.forEach((id) => core.unrefOp(id)); }
return this.#readable;
}
get writable() {
if (this.#writable === undefined) {
this.#writable = writableStreamForRid(this.rid);
}
return this.#writable;
}
ref() {
this.#unref = false;
if (this.#readable) {
readableStreamForRidUnrefableRef(this.#readable);
}
this.#pendingReadPromiseIds.forEach((id) => core.refOp(id));
}
unref() {
this.#unref = true;
if (this.#readable) {
readableStreamForRidUnrefableUnref(this.#readable);
}
this.#pendingReadPromiseIds.forEach((id) => core.unrefOp(id));
}
}
class TcpConn extends Conn {
setNoDelay(noDelay = true) {
return ops.op_set_nodelay(this.rid, noDelay);
}
setKeepAlive(keepAlive = true) {
return ops.op_set_keepalive(this.rid, keepAlive);
}
}
class UnixConn extends Conn {}
class Listener {
#rid = 0;
#addr = null;
#unref = false;
#promiseId = null;
constructor(rid, addr) {
this.#rid = rid;
this.#addr = addr;
}
get rid() {
return this.#rid;
}
get addr() {
return this.#addr;
}
async accept() {
let promise;
switch (this.addr.transport) {
case "tcp":
promise = core.opAsync("op_net_accept_tcp", this.rid);
break;
case "unix":
promise = core.opAsync("op_net_accept_unix", this.rid);
break;
default:
throw new Error(`Unsupported transport: ${this.addr.transport}`);
}
this.#promiseId = promise[promiseIdSymbol];
if (this.#unref) core.unrefOp(this.#promiseId);
const { 0: rid, 1: localAddr, 2: remoteAddr } = await promise;
this.#promiseId = null;
if (this.addr.transport == "tcp") {
localAddr.transport = "tcp";
remoteAddr.transport = "tcp";
return new TcpConn(rid, remoteAddr, localAddr);
} else if (this.addr.transport == "unix") {
return new UnixConn(
rid,
{ transport: "unix", path: remoteAddr },
{ transport: "unix", path: localAddr },
);
} else {
throw new Error("unreachable");
} }
} }
class TcpConn extends Conn { async next() {
setNoDelay(noDelay = true) { let conn;
return ops.op_set_nodelay(this.rid, noDelay); try {
conn = await this.accept();
} catch (error) {
if (
ObjectPrototypeIsPrototypeOf(BadResourcePrototype, error) ||
ObjectPrototypeIsPrototypeOf(InterruptedPrototype, error)
) {
return { value: undefined, done: true };
}
throw error;
} }
return { value: conn, done: false };
}
setKeepAlive(keepAlive = true) { return(value) {
return ops.op_set_keepalive(this.rid, keepAlive); this.close();
return PromiseResolve({ value, done: true });
}
close() {
core.close(this.rid);
}
[SymbolAsyncIterator]() {
return this;
}
ref() {
this.#unref = false;
if (typeof this.#promiseId === "number") {
core.refOp(this.#promiseId);
} }
} }
class UnixConn extends Conn {} unref() {
this.#unref = true;
class Listener { if (typeof this.#promiseId === "number") {
#rid = 0; core.unrefOp(this.#promiseId);
#addr = null;
#unref = false;
#promiseId = null;
constructor(rid, addr) {
this.#rid = rid;
this.#addr = addr;
}
get rid() {
return this.#rid;
}
get addr() {
return this.#addr;
}
async accept() {
let promise;
switch (this.addr.transport) {
case "tcp":
promise = core.opAsync("op_net_accept_tcp", this.rid);
break;
case "unix":
promise = core.opAsync("op_net_accept_unix", this.rid);
break;
default:
throw new Error(`Unsupported transport: ${this.addr.transport}`);
}
this.#promiseId = promise[promiseIdSymbol];
if (this.#unref) core.unrefOp(this.#promiseId);
const { 0: rid, 1: localAddr, 2: remoteAddr } = await promise;
this.#promiseId = null;
if (this.addr.transport == "tcp") {
localAddr.transport = "tcp";
remoteAddr.transport = "tcp";
return new TcpConn(rid, remoteAddr, localAddr);
} else if (this.addr.transport == "unix") {
return new UnixConn(
rid,
{ transport: "unix", path: remoteAddr },
{ transport: "unix", path: localAddr },
);
} else {
throw new Error("unreachable");
}
}
async next() {
let conn;
try {
conn = await this.accept();
} catch (error) {
if (
ObjectPrototypeIsPrototypeOf(BadResourcePrototype, error) ||
ObjectPrototypeIsPrototypeOf(InterruptedPrototype, error)
) {
return { value: undefined, done: true };
}
throw error;
}
return { value: conn, done: false };
}
return(value) {
this.close();
return PromiseResolve({ value, done: true });
}
close() {
core.close(this.rid);
}
[SymbolAsyncIterator]() {
return this;
}
ref() {
this.#unref = false;
if (typeof this.#promiseId === "number") {
core.refOp(this.#promiseId);
}
}
unref() {
this.#unref = true;
if (typeof this.#promiseId === "number") {
core.unrefOp(this.#promiseId);
}
} }
} }
}
class Datagram { class Datagram {
#rid = 0; #rid = 0;
#addr = null; #addr = null;
constructor(rid, addr, bufSize = 1024) { constructor(rid, addr, bufSize = 1024) {
this.#rid = rid; this.#rid = rid;
this.#addr = addr; this.#addr = addr;
this.bufSize = bufSize; this.bufSize = bufSize;
}
get rid() {
return this.#rid;
}
get addr() {
return this.#addr;
}
async receive(p) {
const buf = p || new Uint8Array(this.bufSize);
let nread;
let remoteAddr;
switch (this.addr.transport) {
case "udp": {
({ 0: nread, 1: remoteAddr } = await core.opAsync(
"op_net_recv_udp",
this.rid,
buf,
));
remoteAddr.transport = "udp";
break;
}
case "unixpacket": {
let path;
({ 0: nread, 1: path } = await core.opAsync(
"op_net_recv_unixpacket",
this.rid,
buf,
));
remoteAddr = { transport: "unixpacket", path };
break;
}
default:
throw new Error(`Unsupported transport: ${this.addr.transport}`);
}
const sub = TypedArrayPrototypeSubarray(buf, 0, nread);
return [sub, remoteAddr];
}
async send(p, opts) {
switch (this.addr.transport) {
case "udp":
return await core.opAsync(
"op_net_send_udp",
this.rid,
{ hostname: opts.hostname ?? "127.0.0.1", port: opts.port },
p,
);
case "unixpacket":
return await core.opAsync(
"op_net_send_unixpacket",
this.rid,
opts.path,
p,
);
default:
throw new Error(`Unsupported transport: ${this.addr.transport}`);
}
}
close() {
core.close(this.rid);
}
async *[SymbolAsyncIterator]() {
while (true) {
try {
yield await this.receive();
} catch (err) {
if (
ObjectPrototypeIsPrototypeOf(BadResourcePrototype, err) ||
ObjectPrototypeIsPrototypeOf(InterruptedPrototype, err)
) {
break;
}
throw err;
}
}
}
} }
function listen(args) { get rid() {
switch (args.transport ?? "tcp") { return this.#rid;
case "tcp": { }
const { 0: rid, 1: addr } = ops.op_net_listen_tcp({
hostname: args.hostname ?? "0.0.0.0", get addr() {
port: args.port, return this.#addr;
}, args.reusePort); }
addr.transport = "tcp";
return new Listener(rid, addr); async receive(p) {
const buf = p || new Uint8Array(this.bufSize);
let nread;
let remoteAddr;
switch (this.addr.transport) {
case "udp": {
({ 0: nread, 1: remoteAddr } = await core.opAsync(
"op_net_recv_udp",
this.rid,
buf,
));
remoteAddr.transport = "udp";
break;
} }
case "unix": { case "unixpacket": {
const { 0: rid, 1: path } = ops.op_net_listen_unix(args.path); let path;
const addr = { ({ 0: nread, 1: path } = await core.opAsync(
transport: "unix", "op_net_recv_unixpacket",
path, this.rid,
}; buf,
return new Listener(rid, addr); ));
remoteAddr = { transport: "unixpacket", path };
break;
} }
default: default:
throw new TypeError(`Unsupported transport: '${transport}'`); throw new Error(`Unsupported transport: ${this.addr.transport}`);
}
const sub = TypedArrayPrototypeSubarray(buf, 0, nread);
return [sub, remoteAddr];
}
async send(p, opts) {
switch (this.addr.transport) {
case "udp":
return await core.opAsync(
"op_net_send_udp",
this.rid,
{ hostname: opts.hostname ?? "127.0.0.1", port: opts.port },
p,
);
case "unixpacket":
return await core.opAsync(
"op_net_send_unixpacket",
this.rid,
opts.path,
p,
);
default:
throw new Error(`Unsupported transport: ${this.addr.transport}`);
} }
} }
function createListenDatagram(udpOpFn, unixOpFn) { close() {
return function listenDatagram(args) { core.close(this.rid);
switch (args.transport) {
case "udp": {
const { 0: rid, 1: addr } = udpOpFn(
{
hostname: args.hostname ?? "127.0.0.1",
port: args.port,
},
args.reuseAddress ?? false,
);
addr.transport = "udp";
return new Datagram(rid, addr);
}
case "unixpacket": {
const { 0: rid, 1: path } = unixOpFn(args.path);
const addr = {
transport: "unixpacket",
path,
};
return new Datagram(rid, addr);
}
default:
throw new TypeError(`Unsupported transport: '${transport}'`);
}
};
} }
async function connect(args) { async *[SymbolAsyncIterator]() {
switch (args.transport ?? "tcp") { while (true) {
case "tcp": { try {
const { 0: rid, 1: localAddr, 2: remoteAddr } = await core.opAsync( yield await this.receive();
"op_net_connect_tcp", } catch (err) {
if (
ObjectPrototypeIsPrototypeOf(BadResourcePrototype, err) ||
ObjectPrototypeIsPrototypeOf(InterruptedPrototype, err)
) {
break;
}
throw err;
}
}
}
}
function listen(args) {
switch (args.transport ?? "tcp") {
case "tcp": {
const { 0: rid, 1: addr } = ops.op_net_listen_tcp({
hostname: args.hostname ?? "0.0.0.0",
port: args.port,
}, args.reusePort);
addr.transport = "tcp";
return new Listener(rid, addr);
}
case "unix": {
const { 0: rid, 1: path } = ops.op_net_listen_unix(args.path);
const addr = {
transport: "unix",
path,
};
return new Listener(rid, addr);
}
default:
throw new TypeError(`Unsupported transport: '${transport}'`);
}
}
function createListenDatagram(udpOpFn, unixOpFn) {
return function listenDatagram(args) {
switch (args.transport) {
case "udp": {
const { 0: rid, 1: addr } = udpOpFn(
{ {
hostname: args.hostname ?? "127.0.0.1", hostname: args.hostname ?? "127.0.0.1",
port: args.port, port: args.port,
}, },
args.reuseAddress ?? false,
); );
localAddr.transport = "tcp"; addr.transport = "udp";
remoteAddr.transport = "tcp"; return new Datagram(rid, addr);
return new TcpConn(rid, remoteAddr, localAddr);
} }
case "unix": { case "unixpacket": {
const { 0: rid, 1: localAddr, 2: remoteAddr } = await core.opAsync( const { 0: rid, 1: path } = unixOpFn(args.path);
"op_net_connect_unix", const addr = {
args.path, transport: "unixpacket",
); path,
return new UnixConn( };
rid, return new Datagram(rid, addr);
{ transport: "unix", path: remoteAddr },
{ transport: "unix", path: localAddr },
);
} }
default: default:
throw new TypeError(`Unsupported transport: '${transport}'`); throw new TypeError(`Unsupported transport: '${transport}'`);
} }
}
window.__bootstrap.net = {
connect,
Conn,
TcpConn,
UnixConn,
listen,
createListenDatagram,
Listener,
shutdown,
Datagram,
resolveDns,
}; };
})(this); }
async function connect(args) {
switch (args.transport ?? "tcp") {
case "tcp": {
const { 0: rid, 1: localAddr, 2: remoteAddr } = await core.opAsync(
"op_net_connect_tcp",
{
hostname: args.hostname ?? "127.0.0.1",
port: args.port,
},
);
localAddr.transport = "tcp";
remoteAddr.transport = "tcp";
return new TcpConn(rid, remoteAddr, localAddr);
}
case "unix": {
const { 0: rid, 1: localAddr, 2: remoteAddr } = await core.opAsync(
"op_net_connect_unix",
args.path,
);
return new UnixConn(
rid,
{ transport: "unix", path: remoteAddr },
{ transport: "unix", path: localAddr },
);
}
default:
throw new TypeError(`Unsupported transport: '${transport}'`);
}
}
export {
Conn,
connect,
createListenDatagram,
Datagram,
listen,
Listener,
resolveDns,
shutdown,
TcpConn,
UnixConn,
};

View file

@ -1,106 +1,98 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
"use strict";
((window) => { const core = globalThis.Deno.core;
const core = window.Deno.core; const ops = core.ops;
const ops = core.ops; import { Conn, Listener } from "internal:ext/net/01_net.js";
const { Listener, Conn } = window.__bootstrap.net; const primordials = globalThis.__bootstrap.primordials;
const { TypeError } = window.__bootstrap.primordials; const { TypeError } = primordials;
function opStartTls(args) { function opStartTls(args) {
return core.opAsync("op_tls_start", args); return core.opAsync("op_tls_start", args);
}
function opTlsHandshake(rid) {
return core.opAsync("op_tls_handshake", rid);
}
class TlsConn extends Conn {
handshake() {
return opTlsHandshake(this.rid);
} }
}
function opTlsHandshake(rid) { async function connectTls({
return core.opAsync("op_tls_handshake", rid); port,
hostname = "127.0.0.1",
transport = "tcp",
certFile = undefined,
caCerts = [],
certChain = undefined,
privateKey = undefined,
alpnProtocols = undefined,
}) {
if (transport !== "tcp") {
throw new TypeError(`Unsupported transport: '${transport}'`);
} }
const { 0: rid, 1: localAddr, 2: remoteAddr } = await core.opAsync(
"op_net_connect_tls",
{ hostname, port },
{ certFile, caCerts, certChain, privateKey, alpnProtocols },
);
localAddr.transport = "tcp";
remoteAddr.transport = "tcp";
return new TlsConn(rid, remoteAddr, localAddr);
}
class TlsConn extends Conn { class TlsListener extends Listener {
handshake() { async accept() {
return opTlsHandshake(this.rid);
}
}
async function connectTls({
port,
hostname = "127.0.0.1",
transport = "tcp",
certFile = undefined,
caCerts = [],
certChain = undefined,
privateKey = undefined,
alpnProtocols = undefined,
}) {
if (transport !== "tcp") {
throw new TypeError(`Unsupported transport: '${transport}'`);
}
const { 0: rid, 1: localAddr, 2: remoteAddr } = await core.opAsync( const { 0: rid, 1: localAddr, 2: remoteAddr } = await core.opAsync(
"op_net_connect_tls", "op_net_accept_tls",
{ hostname, port }, this.rid,
{ certFile, caCerts, certChain, privateKey, alpnProtocols },
); );
localAddr.transport = "tcp"; localAddr.transport = "tcp";
remoteAddr.transport = "tcp"; remoteAddr.transport = "tcp";
return new TlsConn(rid, remoteAddr, localAddr); return new TlsConn(rid, remoteAddr, localAddr);
} }
}
class TlsListener extends Listener { function listenTls({
async accept() { port,
const { 0: rid, 1: localAddr, 2: remoteAddr } = await core.opAsync( cert,
"op_net_accept_tls", certFile,
this.rid, key,
); keyFile,
localAddr.transport = "tcp"; hostname = "0.0.0.0",
remoteAddr.transport = "tcp"; transport = "tcp",
return new TlsConn(rid, remoteAddr, localAddr); alpnProtocols = undefined,
} reusePort = false,
}) {
if (transport !== "tcp") {
throw new TypeError(`Unsupported transport: '${transport}'`);
} }
const { 0: rid, 1: localAddr } = ops.op_net_listen_tls(
{ hostname, port },
{ cert, certFile, key, keyFile, alpnProtocols, reusePort },
);
return new TlsListener(rid, localAddr);
}
function listenTls({ async function startTls(
port, conn,
cert, {
certFile, hostname = "127.0.0.1",
key, certFile = undefined,
keyFile, caCerts = [],
hostname = "0.0.0.0",
transport = "tcp",
alpnProtocols = undefined, alpnProtocols = undefined,
reusePort = false, } = {},
}) { ) {
if (transport !== "tcp") { const { 0: rid, 1: localAddr, 2: remoteAddr } = await opStartTls({
throw new TypeError(`Unsupported transport: '${transport}'`); rid: conn.rid,
} hostname,
const { 0: rid, 1: localAddr } = ops.op_net_listen_tls( certFile,
{ hostname, port }, caCerts,
{ cert, certFile, key, keyFile, alpnProtocols, reusePort }, alpnProtocols,
); });
return new TlsListener(rid, localAddr); return new TlsConn(rid, remoteAddr, localAddr);
} }
async function startTls( export { connectTls, listenTls, startTls, TlsConn, TlsListener };
conn,
{
hostname = "127.0.0.1",
certFile = undefined,
caCerts = [],
alpnProtocols = undefined,
} = {},
) {
const { 0: rid, 1: localAddr, 2: remoteAddr } = await opStartTls({
rid: conn.rid,
hostname,
certFile,
caCerts,
alpnProtocols,
});
return new TlsConn(rid, remoteAddr, localAddr);
}
window.__bootstrap.tls = {
startTls,
listenTls,
connectTls,
TlsConn,
TlsListener,
};
})(this);

View file

@ -86,7 +86,7 @@ pub fn init<P: NetPermissions + 'static>(
ops.extend(ops_tls::init::<P>()); ops.extend(ops_tls::init::<P>());
Extension::builder(env!("CARGO_PKG_NAME")) Extension::builder(env!("CARGO_PKG_NAME"))
.dependencies(vec!["deno_web"]) .dependencies(vec!["deno_web"])
.js(include_js_files!( .esm(include_js_files!(
prefix "internal:ext/net", prefix "internal:ext/net",
"01_net.js", "01_net.js",
"02_tls.js", "02_tls.js",

View file

@ -2,127 +2,122 @@
// deno-lint-ignore-file // deno-lint-ignore-file
"use strict"; const internals = globalThis.__bootstrap.internals;
const primordials = globalThis.__bootstrap.primordials;
const {
ArrayPrototypePush,
ArrayPrototypeFilter,
ObjectEntries,
ObjectCreate,
ObjectDefineProperty,
Proxy,
ReflectDefineProperty,
ReflectGetOwnPropertyDescriptor,
ReflectOwnKeys,
Set,
SetPrototypeHas,
} = primordials;
((window) => { function assert(cond) {
const { if (!cond) {
ArrayPrototypePush, throw Error("assert");
ArrayPrototypeFilter,
ObjectEntries,
ObjectCreate,
ObjectDefineProperty,
Proxy,
ReflectDefineProperty,
ReflectGetOwnPropertyDescriptor,
ReflectOwnKeys,
Set,
SetPrototypeHas,
} = window.__bootstrap.primordials;
function assert(cond) {
if (!cond) {
throw Error("assert");
}
} }
}
let initialized = false; let initialized = false;
const nodeGlobals = {}; const nodeGlobals = {};
const nodeGlobalThis = new Proxy(globalThis, { const nodeGlobalThis = new Proxy(globalThis, {
get(_target, prop, _receiver) { get(_target, prop, _receiver) {
if (prop in nodeGlobals) { if (prop in nodeGlobals) {
return nodeGlobals[prop]; return nodeGlobals[prop];
} else { } else {
return globalThis[prop]; return globalThis[prop];
} }
}, },
set(_target, prop, value) { set(_target, prop, value) {
if (prop in nodeGlobals) { if (prop in nodeGlobals) {
nodeGlobals[prop] = value; nodeGlobals[prop] = value;
} else { } else {
globalThis[prop] = value; globalThis[prop] = value;
} }
return true; return true;
}, },
deleteProperty(_target, prop) { deleteProperty(_target, prop) {
let success = false; let success = false;
if (prop in nodeGlobals) { if (prop in nodeGlobals) {
delete nodeGlobals[prop]; delete nodeGlobals[prop];
success = true; success = true;
} }
if (prop in globalThis) { if (prop in globalThis) {
delete globalThis[prop]; delete globalThis[prop];
success = true; success = true;
} }
return success; return success;
}, },
ownKeys(_target) { ownKeys(_target) {
const globalThisKeys = ReflectOwnKeys(globalThis); const globalThisKeys = ReflectOwnKeys(globalThis);
const nodeGlobalsKeys = ReflectOwnKeys(nodeGlobals); const nodeGlobalsKeys = ReflectOwnKeys(nodeGlobals);
const nodeGlobalsKeySet = new Set(nodeGlobalsKeys); const nodeGlobalsKeySet = new Set(nodeGlobalsKeys);
return [ return [
...ArrayPrototypeFilter( ...ArrayPrototypeFilter(
globalThisKeys, globalThisKeys,
(k) => !SetPrototypeHas(nodeGlobalsKeySet, k), (k) => !SetPrototypeHas(nodeGlobalsKeySet, k),
), ),
...nodeGlobalsKeys, ...nodeGlobalsKeys,
]; ];
}, },
defineProperty(_target, prop, desc) { defineProperty(_target, prop, desc) {
if (prop in nodeGlobals) { if (prop in nodeGlobals) {
return ReflectDefineProperty(nodeGlobals, prop, desc); return ReflectDefineProperty(nodeGlobals, prop, desc);
} else { } else {
return ReflectDefineProperty(globalThis, prop, desc); return ReflectDefineProperty(globalThis, prop, desc);
} }
}, },
getOwnPropertyDescriptor(_target, prop) { getOwnPropertyDescriptor(_target, prop) {
if (prop in nodeGlobals) { if (prop in nodeGlobals) {
return ReflectGetOwnPropertyDescriptor(nodeGlobals, prop); return ReflectGetOwnPropertyDescriptor(nodeGlobals, prop);
} else { } else {
return ReflectGetOwnPropertyDescriptor(globalThis, prop); return ReflectGetOwnPropertyDescriptor(globalThis, prop);
} }
}, },
has(_target, prop) { has(_target, prop) {
return prop in nodeGlobals || prop in globalThis; return prop in nodeGlobals || prop in globalThis;
}, },
});
const nativeModuleExports = ObjectCreate(null);
const builtinModules = [];
function initialize(nodeModules, nodeGlobalThisName) {
assert(!initialized);
initialized = true;
for (const [name, exports] of ObjectEntries(nodeModules)) {
nativeModuleExports[name] = exports;
ArrayPrototypePush(builtinModules, name);
}
nodeGlobals.Buffer = nativeModuleExports["buffer"].Buffer;
nodeGlobals.clearImmediate = nativeModuleExports["timers"].clearImmediate;
nodeGlobals.clearInterval = nativeModuleExports["timers"].clearInterval;
nodeGlobals.clearTimeout = nativeModuleExports["timers"].clearTimeout;
nodeGlobals.console = nativeModuleExports["console"];
nodeGlobals.global = nodeGlobalThis;
nodeGlobals.process = nativeModuleExports["process"];
nodeGlobals.setImmediate = nativeModuleExports["timers"].setImmediate;
nodeGlobals.setInterval = nativeModuleExports["timers"].setInterval;
nodeGlobals.setTimeout = nativeModuleExports["timers"].setTimeout;
// add a hidden global for the esm code to use in order to reliably
// get node's globalThis
ObjectDefineProperty(globalThis, nodeGlobalThisName, {
enumerable: false,
writable: false,
value: nodeGlobalThis,
}); });
}
const nativeModuleExports = ObjectCreate(null); internals.node = {
const builtinModules = []; globalThis: nodeGlobalThis,
initialize,
function initialize(nodeModules, nodeGlobalThisName) { nativeModuleExports,
assert(!initialized); builtinModules,
initialized = true; };
for (const [name, exports] of ObjectEntries(nodeModules)) {
nativeModuleExports[name] = exports;
ArrayPrototypePush(builtinModules, name);
}
nodeGlobals.Buffer = nativeModuleExports["buffer"].Buffer;
nodeGlobals.clearImmediate = nativeModuleExports["timers"].clearImmediate;
nodeGlobals.clearInterval = nativeModuleExports["timers"].clearInterval;
nodeGlobals.clearTimeout = nativeModuleExports["timers"].clearTimeout;
nodeGlobals.console = nativeModuleExports["console"];
nodeGlobals.global = nodeGlobalThis;
nodeGlobals.process = nativeModuleExports["process"];
nodeGlobals.setImmediate = nativeModuleExports["timers"].setImmediate;
nodeGlobals.setInterval = nativeModuleExports["timers"].setInterval;
nodeGlobals.setTimeout = nativeModuleExports["timers"].setTimeout;
// add a hidden global for the esm code to use in order to reliably
// get node's globalThis
ObjectDefineProperty(globalThis, nodeGlobalThisName, {
enumerable: false,
writable: false,
value: nodeGlobalThis,
});
}
window.__bootstrap.internals = {
...window.__bootstrap.internals ?? {},
node: {
globalThis: nodeGlobalThis,
initialize,
nativeModuleExports,
builtinModules,
},
};
})(globalThis);

File diff suppressed because it is too large Load diff

View file

@ -85,7 +85,7 @@ pub fn init<P: NodePermissions + 'static>(
maybe_npm_resolver: Option<Rc<dyn RequireNpmResolver>>, maybe_npm_resolver: Option<Rc<dyn RequireNpmResolver>>,
) -> Extension { ) -> Extension {
Extension::builder(env!("CARGO_PKG_NAME")) Extension::builder(env!("CARGO_PKG_NAME"))
.js(include_js_files!( .esm(include_js_files!(
prefix "internal:ext/node", prefix "internal:ext/node",
"01_node.js", "01_node.js",
"02_require.js", "02_require.js",

File diff suppressed because it is too large Load diff

View file

@ -7,268 +7,263 @@
/// <reference path="./internal.d.ts" /> /// <reference path="./internal.d.ts" />
/// <reference path="./lib.deno_url.d.ts" /> /// <reference path="./lib.deno_url.d.ts" />
"use strict"; const core = globalThis.Deno.core;
const ops = core.ops;
import * as webidl from "internal:ext/webidl/00_webidl.js";
const primordials = globalThis.__bootstrap.primordials;
const {
ArrayPrototypeMap,
ObjectKeys,
ObjectFromEntries,
RegExp,
RegExpPrototypeExec,
RegExpPrototypeTest,
Symbol,
SymbolFor,
TypeError,
} = primordials;
((window) => { const _components = Symbol("components");
const core = window.Deno.core;
const ops = core.ops;
const webidl = window.__bootstrap.webidl;
const {
ArrayPrototypeMap,
ObjectKeys,
ObjectFromEntries,
RegExp,
RegExpPrototypeExec,
RegExpPrototypeTest,
Symbol,
SymbolFor,
TypeError,
} = window.__bootstrap.primordials;
const _components = Symbol("components"); /**
* @typedef Components
* @property {Component} protocol
* @property {Component} username
* @property {Component} password
* @property {Component} hostname
* @property {Component} port
* @property {Component} pathname
* @property {Component} search
* @property {Component} hash
*/
/**
* @typedef Component
* @property {string} patternString
* @property {RegExp} regexp
* @property {string[]} groupNameList
*/
class URLPattern {
/** @type {Components} */
[_components];
/** /**
* @typedef Components * @param {URLPatternInput} input
* @property {Component} protocol * @param {string} [baseURL]
* @property {Component} username
* @property {Component} password
* @property {Component} hostname
* @property {Component} port
* @property {Component} pathname
* @property {Component} search
* @property {Component} hash
*/ */
constructor(input, baseURL = undefined) {
/** this[webidl.brand] = webidl.brand;
* @typedef Component const prefix = "Failed to construct 'URLPattern'";
* @property {string} patternString webidl.requiredArguments(arguments.length, 1, { prefix });
* @property {RegExp} regexp input = webidl.converters.URLPatternInput(input, {
* @property {string[]} groupNameList prefix,
*/ context: "Argument 1",
});
class URLPattern { if (baseURL !== undefined) {
/** @type {Components} */ baseURL = webidl.converters.USVString(baseURL, {
[_components];
/**
* @param {URLPatternInput} input
* @param {string} [baseURL]
*/
constructor(input, baseURL = undefined) {
this[webidl.brand] = webidl.brand;
const prefix = "Failed to construct 'URLPattern'";
webidl.requiredArguments(arguments.length, 1, { prefix });
input = webidl.converters.URLPatternInput(input, {
prefix, prefix,
context: "Argument 1", context: "Argument 2",
}); });
if (baseURL !== undefined) {
baseURL = webidl.converters.USVString(baseURL, {
prefix,
context: "Argument 2",
});
}
const components = ops.op_urlpattern_parse(input, baseURL);
const keys = ObjectKeys(components);
for (let i = 0; i < keys.length; ++i) {
const key = keys[i];
try {
components[key].regexp = new RegExp(
components[key].regexpString,
"u",
);
} catch (e) {
throw new TypeError(`${prefix}: ${key} is invalid; ${e.message}`);
}
}
this[_components] = components;
} }
get protocol() { const components = ops.op_urlpattern_parse(input, baseURL);
webidl.assertBranded(this, URLPatternPrototype);
return this[_components].protocol.patternString;
}
get username() { const keys = ObjectKeys(components);
webidl.assertBranded(this, URLPatternPrototype); for (let i = 0; i < keys.length; ++i) {
return this[_components].username.patternString; const key = keys[i];
} try {
components[key].regexp = new RegExp(
get password() { components[key].regexpString,
webidl.assertBranded(this, URLPatternPrototype); "u",
return this[_components].password.patternString;
}
get hostname() {
webidl.assertBranded(this, URLPatternPrototype);
return this[_components].hostname.patternString;
}
get port() {
webidl.assertBranded(this, URLPatternPrototype);
return this[_components].port.patternString;
}
get pathname() {
webidl.assertBranded(this, URLPatternPrototype);
return this[_components].pathname.patternString;
}
get search() {
webidl.assertBranded(this, URLPatternPrototype);
return this[_components].search.patternString;
}
get hash() {
webidl.assertBranded(this, URLPatternPrototype);
return this[_components].hash.patternString;
}
/**
* @param {URLPatternInput} input
* @param {string} [baseURL]
* @returns {boolean}
*/
test(input, baseURL = undefined) {
webidl.assertBranded(this, URLPatternPrototype);
const prefix = "Failed to execute 'test' on 'URLPattern'";
webidl.requiredArguments(arguments.length, 1, { prefix });
input = webidl.converters.URLPatternInput(input, {
prefix,
context: "Argument 1",
});
if (baseURL !== undefined) {
baseURL = webidl.converters.USVString(baseURL, {
prefix,
context: "Argument 2",
});
}
const res = ops.op_urlpattern_process_match_input(
input,
baseURL,
);
if (res === null) {
return false;
}
const values = res[0];
const keys = ObjectKeys(values);
for (let i = 0; i < keys.length; ++i) {
const key = keys[i];
if (!RegExpPrototypeTest(this[_components][key].regexp, values[key])) {
return false;
}
}
return true;
}
/**
* @param {URLPatternInput} input
* @param {string} [baseURL]
* @returns {URLPatternResult | null}
*/
exec(input, baseURL = undefined) {
webidl.assertBranded(this, URLPatternPrototype);
const prefix = "Failed to execute 'exec' on 'URLPattern'";
webidl.requiredArguments(arguments.length, 1, { prefix });
input = webidl.converters.URLPatternInput(input, {
prefix,
context: "Argument 1",
});
if (baseURL !== undefined) {
baseURL = webidl.converters.USVString(baseURL, {
prefix,
context: "Argument 2",
});
}
const res = ops.op_urlpattern_process_match_input(
input,
baseURL,
);
if (res === null) {
return null;
}
const { 0: values, 1: inputs } = res;
if (inputs[1] === null) {
inputs.pop();
}
/** @type {URLPatternResult} */
const result = { inputs };
const keys = ObjectKeys(values);
for (let i = 0; i < keys.length; ++i) {
const key = keys[i];
/** @type {Component} */
const component = this[_components][key];
const input = values[key];
const match = RegExpPrototypeExec(component.regexp, input);
if (match === null) {
return null;
}
const groupEntries = ArrayPrototypeMap(
component.groupNameList,
(name, i) => [name, match[i + 1] ?? ""],
); );
const groups = ObjectFromEntries(groupEntries); } catch (e) {
result[key] = { throw new TypeError(`${prefix}: ${key} is invalid; ${e.message}`);
input,
groups,
};
} }
return result;
} }
[SymbolFor("Deno.customInspect")](inspect) { this[_components] = components;
return `URLPattern ${
inspect({
protocol: this.protocol,
username: this.username,
password: this.password,
hostname: this.hostname,
port: this.port,
pathname: this.pathname,
search: this.search,
hash: this.hash,
})
}`;
}
} }
webidl.configurePrototype(URLPattern); get protocol() {
const URLPatternPrototype = URLPattern.prototype; webidl.assertBranded(this, URLPatternPrototype);
return this[_components].protocol.patternString;
}
webidl.converters.URLPatternInit = webidl get username() {
.createDictionaryConverter("URLPatternInit", [ webidl.assertBranded(this, URLPatternPrototype);
{ key: "protocol", converter: webidl.converters.USVString }, return this[_components].username.patternString;
{ key: "username", converter: webidl.converters.USVString }, }
{ key: "password", converter: webidl.converters.USVString },
{ key: "hostname", converter: webidl.converters.USVString },
{ key: "port", converter: webidl.converters.USVString },
{ key: "pathname", converter: webidl.converters.USVString },
{ key: "search", converter: webidl.converters.USVString },
{ key: "hash", converter: webidl.converters.USVString },
{ key: "baseURL", converter: webidl.converters.USVString },
]);
webidl.converters["URLPatternInput"] = (V, opts) => { get password() {
// Union for (URLPatternInit or USVString) webidl.assertBranded(this, URLPatternPrototype);
if (typeof V == "object") { return this[_components].password.patternString;
return webidl.converters.URLPatternInit(V, opts); }
get hostname() {
webidl.assertBranded(this, URLPatternPrototype);
return this[_components].hostname.patternString;
}
get port() {
webidl.assertBranded(this, URLPatternPrototype);
return this[_components].port.patternString;
}
get pathname() {
webidl.assertBranded(this, URLPatternPrototype);
return this[_components].pathname.patternString;
}
get search() {
webidl.assertBranded(this, URLPatternPrototype);
return this[_components].search.patternString;
}
get hash() {
webidl.assertBranded(this, URLPatternPrototype);
return this[_components].hash.patternString;
}
/**
* @param {URLPatternInput} input
* @param {string} [baseURL]
* @returns {boolean}
*/
test(input, baseURL = undefined) {
webidl.assertBranded(this, URLPatternPrototype);
const prefix = "Failed to execute 'test' on 'URLPattern'";
webidl.requiredArguments(arguments.length, 1, { prefix });
input = webidl.converters.URLPatternInput(input, {
prefix,
context: "Argument 1",
});
if (baseURL !== undefined) {
baseURL = webidl.converters.USVString(baseURL, {
prefix,
context: "Argument 2",
});
} }
return webidl.converters.USVString(V, opts);
};
window.__bootstrap.urlPattern = { const res = ops.op_urlpattern_process_match_input(
URLPattern, input,
}; baseURL,
})(globalThis); );
if (res === null) {
return false;
}
const values = res[0];
const keys = ObjectKeys(values);
for (let i = 0; i < keys.length; ++i) {
const key = keys[i];
if (!RegExpPrototypeTest(this[_components][key].regexp, values[key])) {
return false;
}
}
return true;
}
/**
* @param {URLPatternInput} input
* @param {string} [baseURL]
* @returns {URLPatternResult | null}
*/
exec(input, baseURL = undefined) {
webidl.assertBranded(this, URLPatternPrototype);
const prefix = "Failed to execute 'exec' on 'URLPattern'";
webidl.requiredArguments(arguments.length, 1, { prefix });
input = webidl.converters.URLPatternInput(input, {
prefix,
context: "Argument 1",
});
if (baseURL !== undefined) {
baseURL = webidl.converters.USVString(baseURL, {
prefix,
context: "Argument 2",
});
}
const res = ops.op_urlpattern_process_match_input(
input,
baseURL,
);
if (res === null) {
return null;
}
const { 0: values, 1: inputs } = res;
if (inputs[1] === null) {
inputs.pop();
}
/** @type {URLPatternResult} */
const result = { inputs };
const keys = ObjectKeys(values);
for (let i = 0; i < keys.length; ++i) {
const key = keys[i];
/** @type {Component} */
const component = this[_components][key];
const input = values[key];
const match = RegExpPrototypeExec(component.regexp, input);
if (match === null) {
return null;
}
const groupEntries = ArrayPrototypeMap(
component.groupNameList,
(name, i) => [name, match[i + 1] ?? ""],
);
const groups = ObjectFromEntries(groupEntries);
result[key] = {
input,
groups,
};
}
return result;
}
[SymbolFor("Deno.customInspect")](inspect) {
return `URLPattern ${
inspect({
protocol: this.protocol,
username: this.username,
password: this.password,
hostname: this.hostname,
port: this.port,
pathname: this.pathname,
search: this.search,
hash: this.hash,
})
}`;
}
}
webidl.configurePrototype(URLPattern);
const URLPatternPrototype = URLPattern.prototype;
webidl.converters.URLPatternInit = webidl
.createDictionaryConverter("URLPatternInit", [
{ key: "protocol", converter: webidl.converters.USVString },
{ key: "username", converter: webidl.converters.USVString },
{ key: "password", converter: webidl.converters.USVString },
{ key: "hostname", converter: webidl.converters.USVString },
{ key: "port", converter: webidl.converters.USVString },
{ key: "pathname", converter: webidl.converters.USVString },
{ key: "search", converter: webidl.converters.USVString },
{ key: "hash", converter: webidl.converters.USVString },
{ key: "baseURL", converter: webidl.converters.USVString },
]);
webidl.converters["URLPatternInput"] = (V, opts) => {
// Union for (URLPatternInit or USVString)
if (typeof V == "object") {
return webidl.converters.URLPatternInit(V, opts);
}
return webidl.converters.USVString(V, opts);
};
export { URLPattern };

View file

@ -12,9 +12,11 @@ fn setup() -> Vec<Extension> {
deno_webidl::init(), deno_webidl::init(),
deno_url::init(), deno_url::init(),
Extension::builder("bench_setup") Extension::builder("bench_setup")
.js(vec![( .esm(vec![(
"setup", "internal:setup",
"const { URL } = globalThis.__bootstrap.url;", r#"import { URL } from "internal:ext/url/00_url.js";
globalThis.URL = URL;
"#,
)]) )])
.build(), .build(),
] ]

22
ext/url/internal.d.ts vendored
View file

@ -1,20 +1,14 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
// deno-lint-ignore-file no-var
/// <reference no-default-lib="true" /> /// <reference no-default-lib="true" />
/// <reference lib="esnext" /> /// <reference lib="esnext" />
declare namespace globalThis { declare module "internal:ext/url/00_url.js" {
declare namespace __bootstrap { const URL: typeof URL;
declare var url: { const URLSearchParams: typeof URLSearchParams;
URL: typeof URL; function parseUrlEncoded(bytes: Uint8Array): [string, string][];
URLSearchParams: typeof URLSearchParams; }
parseUrlEncoded(bytes: Uint8Array): [string, string][];
}; declare module "internal:ext/url/01_urlpattern.js" {
const URLPattern: typeof URLPattern;
declare var urlPattern: {
URLPattern: typeof URLPattern;
};
}
} }

View file

@ -20,7 +20,7 @@ use crate::urlpattern::op_urlpattern_process_match_input;
pub fn init() -> Extension { pub fn init() -> Extension {
Extension::builder(env!("CARGO_PKG_NAME")) Extension::builder(env!("CARGO_PKG_NAME"))
.dependencies(vec!["deno_webidl"]) .dependencies(vec!["deno_webidl"])
.js(include_js_files!( .esm(include_js_files!(
prefix "internal:ext/url", prefix "internal:ext/url",
"00_url.js", "00_url.js",
"01_urlpattern.js", "01_urlpattern.js",

View file

@ -6,334 +6,331 @@
/// <reference path="../web/internal.d.ts" /> /// <reference path="../web/internal.d.ts" />
/// <reference path="../web/lib.deno_web.d.ts" /> /// <reference path="../web/lib.deno_web.d.ts" />
"use strict"; const core = globalThis.Deno.core;
const ops = core.ops;
const primordials = globalThis.__bootstrap.primordials;
const {
ArrayPrototypeJoin,
ArrayPrototypeMap,
Error,
JSONStringify,
NumberPrototypeToString,
RegExp,
SafeArrayIterator,
String,
StringPrototypeCharAt,
StringPrototypeCharCodeAt,
StringPrototypeMatch,
StringPrototypePadStart,
StringPrototypeReplace,
StringPrototypeSlice,
StringPrototypeSubstring,
StringPrototypeToLowerCase,
StringPrototypeToUpperCase,
TypeError,
} = primordials;
((window) => { const ASCII_DIGIT = ["\u0030-\u0039"];
const core = Deno.core; const ASCII_UPPER_ALPHA = ["\u0041-\u005A"];
const ops = core.ops; const ASCII_LOWER_ALPHA = ["\u0061-\u007A"];
const { const ASCII_ALPHA = [
ArrayPrototypeJoin, ...new SafeArrayIterator(ASCII_UPPER_ALPHA),
ArrayPrototypeMap, ...new SafeArrayIterator(ASCII_LOWER_ALPHA),
Error, ];
JSONStringify, const ASCII_ALPHANUMERIC = [
NumberPrototypeToString, ...new SafeArrayIterator(ASCII_DIGIT),
RegExp, ...new SafeArrayIterator(ASCII_ALPHA),
SafeArrayIterator, ];
String,
StringPrototypeCharAt,
StringPrototypeCharCodeAt,
StringPrototypeMatch,
StringPrototypePadStart,
StringPrototypeReplace,
StringPrototypeSlice,
StringPrototypeSubstring,
StringPrototypeToLowerCase,
StringPrototypeToUpperCase,
TypeError,
} = window.__bootstrap.primordials;
const ASCII_DIGIT = ["\u0030-\u0039"]; const HTTP_TAB_OR_SPACE = ["\u0009", "\u0020"];
const ASCII_UPPER_ALPHA = ["\u0041-\u005A"]; const HTTP_WHITESPACE = [
const ASCII_LOWER_ALPHA = ["\u0061-\u007A"]; "\u000A",
const ASCII_ALPHA = [ "\u000D",
...new SafeArrayIterator(ASCII_UPPER_ALPHA), ...new SafeArrayIterator(HTTP_TAB_OR_SPACE),
...new SafeArrayIterator(ASCII_LOWER_ALPHA), ];
];
const ASCII_ALPHANUMERIC = [
...new SafeArrayIterator(ASCII_DIGIT),
...new SafeArrayIterator(ASCII_ALPHA),
];
const HTTP_TAB_OR_SPACE = ["\u0009", "\u0020"]; const HTTP_TOKEN_CODE_POINT = [
const HTTP_WHITESPACE = [ "\u0021",
"\u000A", "\u0023",
"\u000D", "\u0024",
...new SafeArrayIterator(HTTP_TAB_OR_SPACE), "\u0025",
]; "\u0026",
"\u0027",
"\u002A",
"\u002B",
"\u002D",
"\u002E",
"\u005E",
"\u005F",
"\u0060",
"\u007C",
"\u007E",
...new SafeArrayIterator(ASCII_ALPHANUMERIC),
];
const HTTP_TOKEN_CODE_POINT_RE = new RegExp(
`^[${regexMatcher(HTTP_TOKEN_CODE_POINT)}]+$`,
);
const HTTP_QUOTED_STRING_TOKEN_POINT = [
"\u0009",
"\u0020-\u007E",
"\u0080-\u00FF",
];
const HTTP_QUOTED_STRING_TOKEN_POINT_RE = new RegExp(
`^[${regexMatcher(HTTP_QUOTED_STRING_TOKEN_POINT)}]+$`,
);
const HTTP_TAB_OR_SPACE_MATCHER = regexMatcher(HTTP_TAB_OR_SPACE);
const HTTP_TAB_OR_SPACE_PREFIX_RE = new RegExp(
`^[${HTTP_TAB_OR_SPACE_MATCHER}]+`,
"g",
);
const HTTP_TAB_OR_SPACE_SUFFIX_RE = new RegExp(
`[${HTTP_TAB_OR_SPACE_MATCHER}]+$`,
"g",
);
const HTTP_WHITESPACE_MATCHER = regexMatcher(HTTP_WHITESPACE);
const HTTP_BETWEEN_WHITESPACE = new RegExp(
`^[${HTTP_WHITESPACE_MATCHER}]*(.*?)[${HTTP_WHITESPACE_MATCHER}]*$`,
);
const HTTP_WHITESPACE_PREFIX_RE = new RegExp(
`^[${HTTP_WHITESPACE_MATCHER}]+`,
"g",
);
const HTTP_WHITESPACE_SUFFIX_RE = new RegExp(
`[${HTTP_WHITESPACE_MATCHER}]+$`,
"g",
);
const HTTP_TOKEN_CODE_POINT = [ /**
"\u0021", * Turn a string of chars into a regex safe matcher.
"\u0023", * @param {string[]} chars
"\u0024", * @returns {string}
"\u0025", */
"\u0026", function regexMatcher(chars) {
"\u0027", const matchers = ArrayPrototypeMap(chars, (char) => {
"\u002A", if (char.length === 1) {
"\u002B", const a = StringPrototypePadStart(
"\u002D", NumberPrototypeToString(StringPrototypeCharCodeAt(char, 0), 16),
"\u002E", 4,
"\u005E", "0",
"\u005F",
"\u0060",
"\u007C",
"\u007E",
...new SafeArrayIterator(ASCII_ALPHANUMERIC),
];
const HTTP_TOKEN_CODE_POINT_RE = new RegExp(
`^[${regexMatcher(HTTP_TOKEN_CODE_POINT)}]+$`,
);
const HTTP_QUOTED_STRING_TOKEN_POINT = [
"\u0009",
"\u0020-\u007E",
"\u0080-\u00FF",
];
const HTTP_QUOTED_STRING_TOKEN_POINT_RE = new RegExp(
`^[${regexMatcher(HTTP_QUOTED_STRING_TOKEN_POINT)}]+$`,
);
const HTTP_TAB_OR_SPACE_MATCHER = regexMatcher(HTTP_TAB_OR_SPACE);
const HTTP_TAB_OR_SPACE_PREFIX_RE = new RegExp(
`^[${HTTP_TAB_OR_SPACE_MATCHER}]+`,
"g",
);
const HTTP_TAB_OR_SPACE_SUFFIX_RE = new RegExp(
`[${HTTP_TAB_OR_SPACE_MATCHER}]+$`,
"g",
);
const HTTP_WHITESPACE_MATCHER = regexMatcher(HTTP_WHITESPACE);
const HTTP_BETWEEN_WHITESPACE = new RegExp(
`^[${HTTP_WHITESPACE_MATCHER}]*(.*?)[${HTTP_WHITESPACE_MATCHER}]*$`,
);
const HTTP_WHITESPACE_PREFIX_RE = new RegExp(
`^[${HTTP_WHITESPACE_MATCHER}]+`,
"g",
);
const HTTP_WHITESPACE_SUFFIX_RE = new RegExp(
`[${HTTP_WHITESPACE_MATCHER}]+$`,
"g",
);
/**
* Turn a string of chars into a regex safe matcher.
* @param {string[]} chars
* @returns {string}
*/
function regexMatcher(chars) {
const matchers = ArrayPrototypeMap(chars, (char) => {
if (char.length === 1) {
const a = StringPrototypePadStart(
NumberPrototypeToString(StringPrototypeCharCodeAt(char, 0), 16),
4,
"0",
);
return `\\u${a}`;
} else if (char.length === 3 && char[1] === "-") {
const a = StringPrototypePadStart(
NumberPrototypeToString(StringPrototypeCharCodeAt(char, 0), 16),
4,
"0",
);
const b = StringPrototypePadStart(
NumberPrototypeToString(StringPrototypeCharCodeAt(char, 2), 16),
4,
"0",
);
return `\\u${a}-\\u${b}`;
} else {
throw TypeError("unreachable");
}
});
return ArrayPrototypeJoin(matchers, "");
}
/**
* https://infra.spec.whatwg.org/#collect-a-sequence-of-code-points
* @param {string} input
* @param {number} position
* @param {(char: string) => boolean} condition
* @returns {{result: string, position: number}}
*/
function collectSequenceOfCodepoints(input, position, condition) {
const start = position;
for (
let c = StringPrototypeCharAt(input, position);
position < input.length && condition(c);
c = StringPrototypeCharAt(input, ++position)
);
return { result: StringPrototypeSlice(input, start, position), position };
}
/**
* @param {string} s
* @returns {string}
*/
function byteUpperCase(s) {
return StringPrototypeReplace(
String(s),
/[a-z]/g,
function byteUpperCaseReplace(c) {
return StringPrototypeToUpperCase(c);
},
);
}
/**
* @param {string} s
* @returns {string}
*/
function byteLowerCase(s) {
// NOTE: correct since all callers convert to ByteString first
// TODO(@AaronO): maybe prefer a ByteString_Lower webidl converter
return StringPrototypeToLowerCase(s);
}
/**
* https://fetch.spec.whatwg.org/#collect-an-http-quoted-string
* @param {string} input
* @param {number} position
* @param {boolean} extractValue
* @returns {{result: string, position: number}}
*/
function collectHttpQuotedString(input, position, extractValue) {
// 1.
const positionStart = position;
// 2.
let value = "";
// 3.
if (input[position] !== "\u0022") throw new TypeError('must be "');
// 4.
position++;
// 5.
while (true) {
// 5.1.
const res = collectSequenceOfCodepoints(
input,
position,
(c) => c !== "\u0022" && c !== "\u005C",
); );
value += res.result; return `\\u${a}`;
position = res.position; } else if (char.length === 3 && char[1] === "-") {
// 5.2. const a = StringPrototypePadStart(
if (position >= input.length) break; NumberPrototypeToString(StringPrototypeCharCodeAt(char, 0), 16),
// 5.3. 4,
const quoteOrBackslash = input[position]; "0",
// 5.4. );
position++; const b = StringPrototypePadStart(
// 5.5. NumberPrototypeToString(StringPrototypeCharCodeAt(char, 2), 16),
if (quoteOrBackslash === "\u005C") { 4,
// 5.5.1. "0",
if (position >= input.length) { );
value += "\u005C"; return `\\u${a}-\\u${b}`;
break; } else {
} throw TypeError("unreachable");
// 5.5.2. }
value += input[position]; });
// 5.5.3. return ArrayPrototypeJoin(matchers, "");
position++; }
} else { // 5.6.
// 5.6.1 /**
if (quoteOrBackslash !== "\u0022") throw new TypeError('must be "'); * https://infra.spec.whatwg.org/#collect-a-sequence-of-code-points
// 5.6.2 * @param {string} input
* @param {number} position
* @param {(char: string) => boolean} condition
* @returns {{result: string, position: number}}
*/
function collectSequenceOfCodepoints(input, position, condition) {
const start = position;
for (
let c = StringPrototypeCharAt(input, position);
position < input.length && condition(c);
c = StringPrototypeCharAt(input, ++position)
);
return { result: StringPrototypeSlice(input, start, position), position };
}
/**
* @param {string} s
* @returns {string}
*/
function byteUpperCase(s) {
return StringPrototypeReplace(
String(s),
/[a-z]/g,
function byteUpperCaseReplace(c) {
return StringPrototypeToUpperCase(c);
},
);
}
/**
* @param {string} s
* @returns {string}
*/
function byteLowerCase(s) {
// NOTE: correct since all callers convert to ByteString first
// TODO(@AaronO): maybe prefer a ByteString_Lower webidl converter
return StringPrototypeToLowerCase(s);
}
/**
* https://fetch.spec.whatwg.org/#collect-an-http-quoted-string
* @param {string} input
* @param {number} position
* @param {boolean} extractValue
* @returns {{result: string, position: number}}
*/
function collectHttpQuotedString(input, position, extractValue) {
// 1.
const positionStart = position;
// 2.
let value = "";
// 3.
if (input[position] !== "\u0022") throw new TypeError('must be "');
// 4.
position++;
// 5.
while (true) {
// 5.1.
const res = collectSequenceOfCodepoints(
input,
position,
(c) => c !== "\u0022" && c !== "\u005C",
);
value += res.result;
position = res.position;
// 5.2.
if (position >= input.length) break;
// 5.3.
const quoteOrBackslash = input[position];
// 5.4.
position++;
// 5.5.
if (quoteOrBackslash === "\u005C") {
// 5.5.1.
if (position >= input.length) {
value += "\u005C";
break; break;
} }
} // 5.5.2.
// 6. value += input[position];
if (extractValue) return { result: value, position }; // 5.5.3.
// 7. position++;
return { } else { // 5.6.
result: StringPrototypeSubstring(input, positionStart, position + 1), // 5.6.1
position, if (quoteOrBackslash !== "\u0022") throw new TypeError('must be "');
}; // 5.6.2
} break;
/**
* @param {Uint8Array} data
* @returns {string}
*/
function forgivingBase64Encode(data) {
return ops.op_base64_encode(data);
}
/**
* @param {string} data
* @returns {Uint8Array}
*/
function forgivingBase64Decode(data) {
return ops.op_base64_decode(data);
}
/**
* @param {string} char
* @returns {boolean}
*/
function isHttpWhitespace(char) {
switch (char) {
case "\u0009":
case "\u000A":
case "\u000D":
case "\u0020":
return true;
default:
return false;
} }
} }
// 6.
/** if (extractValue) return { result: value, position };
* @param {string} s // 7.
* @returns {string} return {
*/ result: StringPrototypeSubstring(input, positionStart, position + 1),
function httpTrim(s) { position,
if (!isHttpWhitespace(s[0]) && !isHttpWhitespace(s[s.length - 1])) {
return s;
}
return StringPrototypeMatch(s, HTTP_BETWEEN_WHITESPACE)?.[1] ?? "";
}
class AssertionError extends Error {
constructor(msg) {
super(msg);
this.name = "AssertionError";
}
}
/**
* @param {unknown} cond
* @param {string=} msg
* @returns {asserts cond}
*/
function assert(cond, msg = "Assertion failed.") {
if (!cond) {
throw new AssertionError(msg);
}
}
/**
* @param {unknown} value
* @returns {string}
*/
function serializeJSValueToJSONString(value) {
const result = JSONStringify(value);
if (result === undefined) {
throw new TypeError("Value is not JSON serializable.");
}
return result;
}
window.__bootstrap.infra = {
collectSequenceOfCodepoints,
ASCII_DIGIT,
ASCII_UPPER_ALPHA,
ASCII_LOWER_ALPHA,
ASCII_ALPHA,
ASCII_ALPHANUMERIC,
HTTP_TAB_OR_SPACE,
HTTP_WHITESPACE,
HTTP_TOKEN_CODE_POINT,
HTTP_TOKEN_CODE_POINT_RE,
HTTP_QUOTED_STRING_TOKEN_POINT,
HTTP_QUOTED_STRING_TOKEN_POINT_RE,
HTTP_TAB_OR_SPACE_PREFIX_RE,
HTTP_TAB_OR_SPACE_SUFFIX_RE,
HTTP_WHITESPACE_PREFIX_RE,
HTTP_WHITESPACE_SUFFIX_RE,
httpTrim,
regexMatcher,
byteUpperCase,
byteLowerCase,
collectHttpQuotedString,
forgivingBase64Encode,
forgivingBase64Decode,
AssertionError,
assert,
serializeJSValueToJSONString,
}; };
})(globalThis); }
/**
* @param {Uint8Array} data
* @returns {string}
*/
function forgivingBase64Encode(data) {
return ops.op_base64_encode(data);
}
/**
* @param {string} data
* @returns {Uint8Array}
*/
function forgivingBase64Decode(data) {
return ops.op_base64_decode(data);
}
/**
* @param {string} char
* @returns {boolean}
*/
function isHttpWhitespace(char) {
switch (char) {
case "\u0009":
case "\u000A":
case "\u000D":
case "\u0020":
return true;
default:
return false;
}
}
/**
* @param {string} s
* @returns {string}
*/
function httpTrim(s) {
if (!isHttpWhitespace(s[0]) && !isHttpWhitespace(s[s.length - 1])) {
return s;
}
return StringPrototypeMatch(s, HTTP_BETWEEN_WHITESPACE)?.[1] ?? "";
}
class AssertionError extends Error {
constructor(msg) {
super(msg);
this.name = "AssertionError";
}
}
/**
* @param {unknown} cond
* @param {string=} msg
* @returns {asserts cond}
*/
function assert(cond, msg = "Assertion failed.") {
if (!cond) {
throw new AssertionError(msg);
}
}
/**
* @param {unknown} value
* @returns {string}
*/
function serializeJSValueToJSONString(value) {
const result = JSONStringify(value);
if (result === undefined) {
throw new TypeError("Value is not JSON serializable.");
}
return result;
}
export {
ASCII_ALPHA,
ASCII_ALPHANUMERIC,
ASCII_DIGIT,
ASCII_LOWER_ALPHA,
ASCII_UPPER_ALPHA,
assert,
AssertionError,
byteLowerCase,
byteUpperCase,
collectHttpQuotedString,
collectSequenceOfCodepoints,
forgivingBase64Decode,
forgivingBase64Encode,
HTTP_QUOTED_STRING_TOKEN_POINT,
HTTP_QUOTED_STRING_TOKEN_POINT_RE,
HTTP_TAB_OR_SPACE,
HTTP_TAB_OR_SPACE_PREFIX_RE,
HTTP_TAB_OR_SPACE_SUFFIX_RE,
HTTP_TOKEN_CODE_POINT,
HTTP_TOKEN_CODE_POINT_RE,
HTTP_WHITESPACE,
HTTP_WHITESPACE_PREFIX_RE,
HTTP_WHITESPACE_SUFFIX_RE,
httpTrim,
regexMatcher,
serializeJSValueToJSONString,
};

View file

@ -7,197 +7,194 @@
/// <reference path="../web/internal.d.ts" /> /// <reference path="../web/internal.d.ts" />
/// <reference path="../web/lib.deno_web.d.ts" /> /// <reference path="../web/lib.deno_web.d.ts" />
"use strict"; const primordials = globalThis.__bootstrap.primordials;
const {
ArrayPrototypeSlice,
Error,
ErrorPrototype,
ObjectDefineProperty,
ObjectCreate,
ObjectEntries,
ObjectPrototypeIsPrototypeOf,
ObjectSetPrototypeOf,
Symbol,
SymbolFor,
} = primordials;
import * as webidl from "internal:ext/webidl/00_webidl.js";
import { createFilteredInspectProxy } from "internal:ext/console/02_console.js";
((window) => { const _name = Symbol("name");
const { const _message = Symbol("message");
ArrayPrototypeSlice, const _code = Symbol("code");
Error,
ErrorPrototype,
ObjectDefineProperty,
ObjectCreate,
ObjectEntries,
ObjectPrototypeIsPrototypeOf,
ObjectSetPrototypeOf,
Symbol,
SymbolFor,
} = window.__bootstrap.primordials;
const webidl = window.__bootstrap.webidl;
const consoleInternal = window.__bootstrap.console;
const _name = Symbol("name"); // Defined in WebIDL 4.3.
const _message = Symbol("message"); // https://webidl.spec.whatwg.org/#idl-DOMException
const _code = Symbol("code"); const INDEX_SIZE_ERR = 1;
const DOMSTRING_SIZE_ERR = 2;
const HIERARCHY_REQUEST_ERR = 3;
const WRONG_DOCUMENT_ERR = 4;
const INVALID_CHARACTER_ERR = 5;
const NO_DATA_ALLOWED_ERR = 6;
const NO_MODIFICATION_ALLOWED_ERR = 7;
const NOT_FOUND_ERR = 8;
const NOT_SUPPORTED_ERR = 9;
const INUSE_ATTRIBUTE_ERR = 10;
const INVALID_STATE_ERR = 11;
const SYNTAX_ERR = 12;
const INVALID_MODIFICATION_ERR = 13;
const NAMESPACE_ERR = 14;
const INVALID_ACCESS_ERR = 15;
const VALIDATION_ERR = 16;
const TYPE_MISMATCH_ERR = 17;
const SECURITY_ERR = 18;
const NETWORK_ERR = 19;
const ABORT_ERR = 20;
const URL_MISMATCH_ERR = 21;
const QUOTA_EXCEEDED_ERR = 22;
const TIMEOUT_ERR = 23;
const INVALID_NODE_TYPE_ERR = 24;
const DATA_CLONE_ERR = 25;
// Defined in WebIDL 4.3. // Defined in WebIDL 2.8.1.
// https://webidl.spec.whatwg.org/#idl-DOMException // https://webidl.spec.whatwg.org/#dfn-error-names-table
const INDEX_SIZE_ERR = 1; /** @type {Record<string, number>} */
const DOMSTRING_SIZE_ERR = 2; // the prototype should be null, to prevent user code from looking
const HIERARCHY_REQUEST_ERR = 3; // up Object.prototype properties, such as "toString"
const WRONG_DOCUMENT_ERR = 4; const nameToCodeMapping = ObjectCreate(null, {
const INVALID_CHARACTER_ERR = 5; IndexSizeError: { value: INDEX_SIZE_ERR },
const NO_DATA_ALLOWED_ERR = 6; HierarchyRequestError: { value: HIERARCHY_REQUEST_ERR },
const NO_MODIFICATION_ALLOWED_ERR = 7; WrongDocumentError: { value: WRONG_DOCUMENT_ERR },
const NOT_FOUND_ERR = 8; InvalidCharacterError: { value: INVALID_CHARACTER_ERR },
const NOT_SUPPORTED_ERR = 9; NoModificationAllowedError: { value: NO_MODIFICATION_ALLOWED_ERR },
const INUSE_ATTRIBUTE_ERR = 10; NotFoundError: { value: NOT_FOUND_ERR },
const INVALID_STATE_ERR = 11; NotSupportedError: { value: NOT_SUPPORTED_ERR },
const SYNTAX_ERR = 12; InUseAttributeError: { value: INUSE_ATTRIBUTE_ERR },
const INVALID_MODIFICATION_ERR = 13; InvalidStateError: { value: INVALID_STATE_ERR },
const NAMESPACE_ERR = 14; SyntaxError: { value: SYNTAX_ERR },
const INVALID_ACCESS_ERR = 15; InvalidModificationError: { value: INVALID_MODIFICATION_ERR },
const VALIDATION_ERR = 16; NamespaceError: { value: NAMESPACE_ERR },
const TYPE_MISMATCH_ERR = 17; InvalidAccessError: { value: INVALID_ACCESS_ERR },
const SECURITY_ERR = 18; TypeMismatchError: { value: TYPE_MISMATCH_ERR },
const NETWORK_ERR = 19; SecurityError: { value: SECURITY_ERR },
const ABORT_ERR = 20; NetworkError: { value: NETWORK_ERR },
const URL_MISMATCH_ERR = 21; AbortError: { value: ABORT_ERR },
const QUOTA_EXCEEDED_ERR = 22; URLMismatchError: { value: URL_MISMATCH_ERR },
const TIMEOUT_ERR = 23; QuotaExceededError: { value: QUOTA_EXCEEDED_ERR },
const INVALID_NODE_TYPE_ERR = 24; TimeoutError: { value: TIMEOUT_ERR },
const DATA_CLONE_ERR = 25; InvalidNodeTypeError: { value: INVALID_NODE_TYPE_ERR },
DataCloneError: { value: DATA_CLONE_ERR },
});
// Defined in WebIDL 2.8.1. // Defined in WebIDL 4.3.
// https://webidl.spec.whatwg.org/#dfn-error-names-table // https://webidl.spec.whatwg.org/#idl-DOMException
/** @type {Record<string, number>} */ class DOMException {
// the prototype should be null, to prevent user code from looking [_message];
// up Object.prototype properties, such as "toString" [_name];
const nameToCodeMapping = ObjectCreate(null, { [_code];
IndexSizeError: { value: INDEX_SIZE_ERR },
HierarchyRequestError: { value: HIERARCHY_REQUEST_ERR },
WrongDocumentError: { value: WRONG_DOCUMENT_ERR },
InvalidCharacterError: { value: INVALID_CHARACTER_ERR },
NoModificationAllowedError: { value: NO_MODIFICATION_ALLOWED_ERR },
NotFoundError: { value: NOT_FOUND_ERR },
NotSupportedError: { value: NOT_SUPPORTED_ERR },
InUseAttributeError: { value: INUSE_ATTRIBUTE_ERR },
InvalidStateError: { value: INVALID_STATE_ERR },
SyntaxError: { value: SYNTAX_ERR },
InvalidModificationError: { value: INVALID_MODIFICATION_ERR },
NamespaceError: { value: NAMESPACE_ERR },
InvalidAccessError: { value: INVALID_ACCESS_ERR },
TypeMismatchError: { value: TYPE_MISMATCH_ERR },
SecurityError: { value: SECURITY_ERR },
NetworkError: { value: NETWORK_ERR },
AbortError: { value: ABORT_ERR },
URLMismatchError: { value: URL_MISMATCH_ERR },
QuotaExceededError: { value: QUOTA_EXCEEDED_ERR },
TimeoutError: { value: TIMEOUT_ERR },
InvalidNodeTypeError: { value: INVALID_NODE_TYPE_ERR },
DataCloneError: { value: DATA_CLONE_ERR },
});
// Defined in WebIDL 4.3. // https://webidl.spec.whatwg.org/#dom-domexception-domexception
// https://webidl.spec.whatwg.org/#idl-DOMException constructor(message = "", name = "Error") {
class DOMException { message = webidl.converters.DOMString(message, {
[_message]; prefix: "Failed to construct 'DOMException'",
[_name]; context: "Argument 1",
[_code]; });
name = webidl.converters.DOMString(name, {
prefix: "Failed to construct 'DOMException'",
context: "Argument 2",
});
const code = nameToCodeMapping[name] ?? 0;
// https://webidl.spec.whatwg.org/#dom-domexception-domexception this[_message] = message;
constructor(message = "", name = "Error") { this[_name] = name;
message = webidl.converters.DOMString(message, { this[_code] = code;
prefix: "Failed to construct 'DOMException'", this[webidl.brand] = webidl.brand;
context: "Argument 1",
});
name = webidl.converters.DOMString(name, {
prefix: "Failed to construct 'DOMException'",
context: "Argument 2",
});
const code = nameToCodeMapping[name] ?? 0;
this[_message] = message; const error = new Error(message);
this[_name] = name; error.name = "DOMException";
this[_code] = code; ObjectDefineProperty(this, "stack", {
this[webidl.brand] = webidl.brand; value: error.stack,
writable: true,
configurable: true,
});
const error = new Error(message); // `DOMException` isn't a native error, so `Error.prepareStackTrace()` is
error.name = "DOMException"; // not called when accessing `.stack`, meaning our structured stack trace
ObjectDefineProperty(this, "stack", { // hack doesn't apply. This patches it in.
value: error.stack, ObjectDefineProperty(this, "__callSiteEvals", {
writable: true, value: ArrayPrototypeSlice(error.__callSiteEvals, 1),
configurable: true, configurable: true,
}); });
// `DOMException` isn't a native error, so `Error.prepareStackTrace()` is
// not called when accessing `.stack`, meaning our structured stack trace
// hack doesn't apply. This patches it in.
ObjectDefineProperty(this, "__callSiteEvals", {
value: ArrayPrototypeSlice(error.__callSiteEvals, 1),
configurable: true,
});
}
get message() {
webidl.assertBranded(this, DOMExceptionPrototype);
return this[_message];
}
get name() {
webidl.assertBranded(this, DOMExceptionPrototype);
return this[_name];
}
get code() {
webidl.assertBranded(this, DOMExceptionPrototype);
return this[_code];
}
[SymbolFor("Deno.customInspect")](inspect) {
if (ObjectPrototypeIsPrototypeOf(DOMExceptionPrototype, this)) {
return `DOMException: ${this[_message]}`;
} else {
return inspect(consoleInternal.createFilteredInspectProxy({
object: this,
evaluate: false,
keys: [
"message",
"name",
"code",
],
}));
}
}
} }
ObjectSetPrototypeOf(DOMException.prototype, ErrorPrototype); get message() {
webidl.assertBranded(this, DOMExceptionPrototype);
webidl.configurePrototype(DOMException); return this[_message];
const DOMExceptionPrototype = DOMException.prototype;
const entries = ObjectEntries({
INDEX_SIZE_ERR,
DOMSTRING_SIZE_ERR,
HIERARCHY_REQUEST_ERR,
WRONG_DOCUMENT_ERR,
INVALID_CHARACTER_ERR,
NO_DATA_ALLOWED_ERR,
NO_MODIFICATION_ALLOWED_ERR,
NOT_FOUND_ERR,
NOT_SUPPORTED_ERR,
INUSE_ATTRIBUTE_ERR,
INVALID_STATE_ERR,
SYNTAX_ERR,
INVALID_MODIFICATION_ERR,
NAMESPACE_ERR,
INVALID_ACCESS_ERR,
VALIDATION_ERR,
TYPE_MISMATCH_ERR,
SECURITY_ERR,
NETWORK_ERR,
ABORT_ERR,
URL_MISMATCH_ERR,
QUOTA_EXCEEDED_ERR,
TIMEOUT_ERR,
INVALID_NODE_TYPE_ERR,
DATA_CLONE_ERR,
});
for (let i = 0; i < entries.length; ++i) {
const { 0: key, 1: value } = entries[i];
const desc = { value, enumerable: true };
ObjectDefineProperty(DOMException, key, desc);
ObjectDefineProperty(DOMException.prototype, key, desc);
} }
window.__bootstrap.domException = { DOMException }; get name() {
})(this); webidl.assertBranded(this, DOMExceptionPrototype);
return this[_name];
}
get code() {
webidl.assertBranded(this, DOMExceptionPrototype);
return this[_code];
}
[SymbolFor("Deno.customInspect")](inspect) {
if (ObjectPrototypeIsPrototypeOf(DOMExceptionPrototype, this)) {
return `DOMException: ${this[_message]}`;
} else {
return inspect(createFilteredInspectProxy({
object: this,
evaluate: false,
keys: [
"message",
"name",
"code",
],
}));
}
}
}
ObjectSetPrototypeOf(DOMException.prototype, ErrorPrototype);
webidl.configurePrototype(DOMException);
const DOMExceptionPrototype = DOMException.prototype;
const entries = ObjectEntries({
INDEX_SIZE_ERR,
DOMSTRING_SIZE_ERR,
HIERARCHY_REQUEST_ERR,
WRONG_DOCUMENT_ERR,
INVALID_CHARACTER_ERR,
NO_DATA_ALLOWED_ERR,
NO_MODIFICATION_ALLOWED_ERR,
NOT_FOUND_ERR,
NOT_SUPPORTED_ERR,
INUSE_ATTRIBUTE_ERR,
INVALID_STATE_ERR,
SYNTAX_ERR,
INVALID_MODIFICATION_ERR,
NAMESPACE_ERR,
INVALID_ACCESS_ERR,
VALIDATION_ERR,
TYPE_MISMATCH_ERR,
SECURITY_ERR,
NETWORK_ERR,
ABORT_ERR,
URL_MISMATCH_ERR,
QUOTA_EXCEEDED_ERR,
TIMEOUT_ERR,
INVALID_NODE_TYPE_ERR,
DATA_CLONE_ERR,
});
for (let i = 0; i < entries.length; ++i) {
const { 0: key, 1: value } = entries[i];
const desc = { value, enumerable: true };
ObjectDefineProperty(DOMException, key, desc);
ObjectDefineProperty(DOMException.prototype, key, desc);
}
export default DOMException;

View file

@ -6,255 +6,247 @@
/// <reference path="../web/internal.d.ts" /> /// <reference path="../web/internal.d.ts" />
/// <reference path="../web/lib.deno_web.d.ts" /> /// <reference path="../web/lib.deno_web.d.ts" />
"use strict"; const primordials = globalThis.__bootstrap.primordials;
const {
ArrayPrototypeIncludes,
Map,
MapPrototypeGet,
MapPrototypeHas,
MapPrototypeSet,
RegExpPrototypeTest,
SafeMapIterator,
StringPrototypeReplaceAll,
StringPrototypeToLowerCase,
} = primordials;
import {
collectHttpQuotedString,
collectSequenceOfCodepoints,
HTTP_QUOTED_STRING_TOKEN_POINT_RE,
HTTP_TOKEN_CODE_POINT_RE,
HTTP_WHITESPACE,
HTTP_WHITESPACE_PREFIX_RE,
HTTP_WHITESPACE_SUFFIX_RE,
} from "internal:ext/web/00_infra.js";
((window) => { /**
const { * @typedef MimeType
ArrayPrototypeIncludes, * @property {string} type
Map, * @property {string} subtype
MapPrototypeGet, * @property {Map<string,string>} parameters
MapPrototypeHas, */
MapPrototypeSet,
RegExpPrototypeTest,
SafeMapIterator,
StringPrototypeReplaceAll,
StringPrototypeToLowerCase,
} = window.__bootstrap.primordials;
const {
collectSequenceOfCodepoints,
HTTP_WHITESPACE,
HTTP_WHITESPACE_PREFIX_RE,
HTTP_WHITESPACE_SUFFIX_RE,
HTTP_QUOTED_STRING_TOKEN_POINT_RE,
HTTP_TOKEN_CODE_POINT_RE,
collectHttpQuotedString,
} = window.__bootstrap.infra;
/** /**
* @typedef MimeType * @param {string} input
* @property {string} type * @returns {MimeType | null}
* @property {string} subtype */
* @property {Map<string,string>} parameters function parseMimeType(input) {
*/ // 1.
input = StringPrototypeReplaceAll(input, HTTP_WHITESPACE_PREFIX_RE, "");
input = StringPrototypeReplaceAll(input, HTTP_WHITESPACE_SUFFIX_RE, "");
/** // 2.
* @param {string} input let position = 0;
* @returns {MimeType | null} const endOfInput = input.length;
*/
function parseMimeType(input) {
// 1.
input = StringPrototypeReplaceAll(input, HTTP_WHITESPACE_PREFIX_RE, "");
input = StringPrototypeReplaceAll(input, HTTP_WHITESPACE_SUFFIX_RE, "");
// 2. // 3.
let position = 0; const res1 = collectSequenceOfCodepoints(
const endOfInput = input.length; input,
position,
(c) => c != "\u002F",
);
const type = res1.result;
position = res1.position;
// 3. // 4.
if (type === "" || !RegExpPrototypeTest(HTTP_TOKEN_CODE_POINT_RE, type)) {
return null;
}
// 5.
if (position >= endOfInput) return null;
// 6.
position++;
// 7.
const res2 = collectSequenceOfCodepoints(
input,
position,
(c) => c != "\u003B",
);
let subtype = res2.result;
position = res2.position;
// 8.
subtype = StringPrototypeReplaceAll(subtype, HTTP_WHITESPACE_SUFFIX_RE, "");
// 9.
if (
subtype === "" || !RegExpPrototypeTest(HTTP_TOKEN_CODE_POINT_RE, subtype)
) {
return null;
}
// 10.
const mimeType = {
type: StringPrototypeToLowerCase(type),
subtype: StringPrototypeToLowerCase(subtype),
/** @type {Map<string, string>} */
parameters: new Map(),
};
// 11.
while (position < endOfInput) {
// 11.1.
position++;
// 11.2.
const res1 = collectSequenceOfCodepoints( const res1 = collectSequenceOfCodepoints(
input, input,
position, position,
(c) => c != "\u002F", (c) => ArrayPrototypeIncludes(HTTP_WHITESPACE, c),
); );
const type = res1.result;
position = res1.position; position = res1.position;
// 4. // 11.3.
if (type === "" || !RegExpPrototypeTest(HTTP_TOKEN_CODE_POINT_RE, type)) {
return null;
}
// 5.
if (position >= endOfInput) return null;
// 6.
position++;
// 7.
const res2 = collectSequenceOfCodepoints( const res2 = collectSequenceOfCodepoints(
input, input,
position, position,
(c) => c != "\u003B", (c) => c !== "\u003B" && c !== "\u003D",
); );
let subtype = res2.result; let parameterName = res2.result;
position = res2.position; position = res2.position;
// 8. // 11.4.
subtype = StringPrototypeReplaceAll(subtype, HTTP_WHITESPACE_SUFFIX_RE, ""); parameterName = StringPrototypeToLowerCase(parameterName);
// 9. // 11.5.
if ( if (position < endOfInput) {
subtype === "" || !RegExpPrototypeTest(HTTP_TOKEN_CODE_POINT_RE, subtype) if (input[position] == "\u003B") continue;
) {
return null;
}
// 10.
const mimeType = {
type: StringPrototypeToLowerCase(type),
subtype: StringPrototypeToLowerCase(subtype),
/** @type {Map<string, string>} */
parameters: new Map(),
};
// 11.
while (position < endOfInput) {
// 11.1.
position++; position++;
}
// 11.2. // 11.6.
const res1 = collectSequenceOfCodepoints( if (position >= endOfInput) break;
// 11.7.
let parameterValue = null;
// 11.8.
if (input[position] === "\u0022") {
// 11.8.1.
const res = collectHttpQuotedString(input, position, true);
parameterValue = res.result;
position = res.position;
// 11.8.2.
position++;
} else { // 11.9.
// 11.9.1.
const res = collectSequenceOfCodepoints(
input, input,
position, position,
(c) => ArrayPrototypeIncludes(HTTP_WHITESPACE, c), (c) => c !== "\u003B",
); );
position = res1.position; parameterValue = res.result;
position = res.position;
// 11.3. // 11.9.2.
const res2 = collectSequenceOfCodepoints( parameterValue = StringPrototypeReplaceAll(
input, parameterValue,
position, HTTP_WHITESPACE_SUFFIX_RE,
(c) => c !== "\u003B" && c !== "\u003D", "",
); );
let parameterName = res2.result;
position = res2.position;
// 11.4. // 11.9.3.
parameterName = StringPrototypeToLowerCase(parameterName); if (parameterValue === "") continue;
}
// 11.5. // 11.10.
if (position < endOfInput) { if (
if (input[position] == "\u003B") continue; parameterName !== "" &&
position++; RegExpPrototypeTest(HTTP_TOKEN_CODE_POINT_RE, parameterName) &&
RegExpPrototypeTest(
HTTP_QUOTED_STRING_TOKEN_POINT_RE,
parameterValue,
) &&
!MapPrototypeHas(mimeType.parameters, parameterName)
) {
MapPrototypeSet(mimeType.parameters, parameterName, parameterValue);
}
}
// 12.
return mimeType;
}
/**
* @param {MimeType} mimeType
* @returns {string}
*/
function essence(mimeType) {
return `${mimeType.type}/${mimeType.subtype}`;
}
/**
* @param {MimeType} mimeType
* @returns {string}
*/
function serializeMimeType(mimeType) {
let serialization = essence(mimeType);
for (const param of new SafeMapIterator(mimeType.parameters)) {
serialization += `;${param[0]}=`;
let value = param[1];
if (!RegExpPrototypeTest(HTTP_TOKEN_CODE_POINT_RE, value)) {
value = StringPrototypeReplaceAll(value, "\\", "\\\\");
value = StringPrototypeReplaceAll(value, '"', '\\"');
value = `"${value}"`;
}
serialization += value;
}
return serialization;
}
/**
* Part of the Fetch spec's "extract a MIME type" algorithm
* (https://fetch.spec.whatwg.org/#concept-header-extract-mime-type).
* @param {string[] | null} headerValues The result of getting, decoding and
* splitting the "Content-Type" header.
* @returns {MimeType | null}
*/
function extractMimeType(headerValues) {
if (headerValues === null) return null;
let charset = null;
let essence_ = null;
let mimeType = null;
for (let i = 0; i < headerValues.length; ++i) {
const value = headerValues[i];
const temporaryMimeType = parseMimeType(value);
if (
temporaryMimeType === null ||
essence(temporaryMimeType) == "*/*"
) {
continue;
}
mimeType = temporaryMimeType;
if (essence(mimeType) !== essence_) {
charset = null;
const newCharset = MapPrototypeGet(mimeType.parameters, "charset");
if (newCharset !== undefined) {
charset = newCharset;
} }
essence_ = essence(mimeType);
// 11.6. } else {
if (position >= endOfInput) break;
// 11.7.
let parameterValue = null;
// 11.8.
if (input[position] === "\u0022") {
// 11.8.1.
const res = collectHttpQuotedString(input, position, true);
parameterValue = res.result;
position = res.position;
// 11.8.2.
position++;
} else { // 11.9.
// 11.9.1.
const res = collectSequenceOfCodepoints(
input,
position,
(c) => c !== "\u003B",
);
parameterValue = res.result;
position = res.position;
// 11.9.2.
parameterValue = StringPrototypeReplaceAll(
parameterValue,
HTTP_WHITESPACE_SUFFIX_RE,
"",
);
// 11.9.3.
if (parameterValue === "") continue;
}
// 11.10.
if ( if (
parameterName !== "" && !MapPrototypeHas(mimeType.parameters, "charset") &&
RegExpPrototypeTest(HTTP_TOKEN_CODE_POINT_RE, parameterName) && charset !== null
RegExpPrototypeTest(
HTTP_QUOTED_STRING_TOKEN_POINT_RE,
parameterValue,
) &&
!MapPrototypeHas(mimeType.parameters, parameterName)
) { ) {
MapPrototypeSet(mimeType.parameters, parameterName, parameterValue); MapPrototypeSet(mimeType.parameters, "charset", charset);
} }
} }
// 12.
return mimeType;
} }
return mimeType;
}
/** export { essence, extractMimeType, parseMimeType, serializeMimeType };
* @param {MimeType} mimeType
* @returns {string}
*/
function essence(mimeType) {
return `${mimeType.type}/${mimeType.subtype}`;
}
/**
* @param {MimeType} mimeType
* @returns {string}
*/
function serializeMimeType(mimeType) {
let serialization = essence(mimeType);
for (const param of new SafeMapIterator(mimeType.parameters)) {
serialization += `;${param[0]}=`;
let value = param[1];
if (!RegExpPrototypeTest(HTTP_TOKEN_CODE_POINT_RE, value)) {
value = StringPrototypeReplaceAll(value, "\\", "\\\\");
value = StringPrototypeReplaceAll(value, '"', '\\"');
value = `"${value}"`;
}
serialization += value;
}
return serialization;
}
/**
* Part of the Fetch spec's "extract a MIME type" algorithm
* (https://fetch.spec.whatwg.org/#concept-header-extract-mime-type).
* @param {string[] | null} headerValues The result of getting, decoding and
* splitting the "Content-Type" header.
* @returns {MimeType | null}
*/
function extractMimeType(headerValues) {
if (headerValues === null) return null;
let charset = null;
let essence_ = null;
let mimeType = null;
for (let i = 0; i < headerValues.length; ++i) {
const value = headerValues[i];
const temporaryMimeType = parseMimeType(value);
if (
temporaryMimeType === null ||
essence(temporaryMimeType) == "*/*"
) {
continue;
}
mimeType = temporaryMimeType;
if (essence(mimeType) !== essence_) {
charset = null;
const newCharset = MapPrototypeGet(mimeType.parameters, "charset");
if (newCharset !== undefined) {
charset = newCharset;
}
essence_ = essence(mimeType);
} else {
if (
!MapPrototypeHas(mimeType.parameters, "charset") &&
charset !== null
) {
MapPrototypeSet(mimeType.parameters, "charset", charset);
}
}
}
return mimeType;
}
window.__bootstrap.mimesniff = {
parseMimeType,
essence,
serializeMimeType,
extractMimeType,
};
})(this);

File diff suppressed because it is too large Load diff

View file

@ -6,138 +6,135 @@
/// <reference path="../web/internal.d.ts" /> /// <reference path="../web/internal.d.ts" />
/// <reference path="../web/lib.deno_web.d.ts" /> /// <reference path="../web/lib.deno_web.d.ts" />
"use strict"; const core = globalThis.Deno.core;
import DOMException from "internal:ext/web/01_dom_exception.js";
const primordials = globalThis.__bootstrap.primordials;
const {
ArrayBuffer,
ArrayBufferPrototype,
ArrayBufferPrototypeGetByteLength,
ArrayBufferPrototypeSlice,
ArrayBufferIsView,
DataView,
DataViewPrototypeGetBuffer,
DataViewPrototypeGetByteLength,
DataViewPrototypeGetByteOffset,
ObjectPrototypeIsPrototypeOf,
TypedArrayPrototypeGetBuffer,
TypedArrayPrototypeGetByteOffset,
TypedArrayPrototypeGetLength,
TypedArrayPrototypeGetSymbolToStringTag,
TypeErrorPrototype,
WeakMap,
WeakMapPrototypeSet,
Int8Array,
Int16Array,
Int32Array,
BigInt64Array,
Uint8Array,
Uint8ClampedArray,
Uint16Array,
Uint32Array,
BigUint64Array,
Float32Array,
Float64Array,
} = primordials;
((window) => { const objectCloneMemo = new WeakMap();
const core = window.Deno.core;
const { DOMException } = window.__bootstrap.domException;
const {
ArrayBuffer,
ArrayBufferPrototype,
ArrayBufferPrototypeGetByteLength,
ArrayBufferPrototypeSlice,
ArrayBufferIsView,
DataView,
DataViewPrototypeGetBuffer,
DataViewPrototypeGetByteLength,
DataViewPrototypeGetByteOffset,
ObjectPrototypeIsPrototypeOf,
TypedArrayPrototypeGetBuffer,
TypedArrayPrototypeGetByteOffset,
TypedArrayPrototypeGetLength,
TypedArrayPrototypeGetSymbolToStringTag,
TypeErrorPrototype,
WeakMap,
WeakMapPrototypeSet,
Int8Array,
Int16Array,
Int32Array,
BigInt64Array,
Uint8Array,
Uint8ClampedArray,
Uint16Array,
Uint32Array,
BigUint64Array,
Float32Array,
Float64Array,
} = window.__bootstrap.primordials;
const objectCloneMemo = new WeakMap(); function cloneArrayBuffer(
srcBuffer,
function cloneArrayBuffer( srcByteOffset,
srcLength,
_cloneConstructor,
) {
// this function fudges the return type but SharedArrayBuffer is disabled for a while anyway
return ArrayBufferPrototypeSlice(
srcBuffer, srcBuffer,
srcByteOffset, srcByteOffset,
srcLength, srcByteOffset + srcLength,
_cloneConstructor, );
) { }
// this function fudges the return type but SharedArrayBuffer is disabled for a while anyway
return ArrayBufferPrototypeSlice( // TODO(petamoriken): Resizable ArrayBuffer support in the future
srcBuffer, /** Clone a value in a similar way to structured cloning. It is similar to a
srcByteOffset, * StructureDeserialize(StructuredSerialize(...)). */
srcByteOffset + srcLength, function structuredClone(value) {
// Performance optimization for buffers, otherwise
// `serialize/deserialize` will allocate new buffer.
if (ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, value)) {
const cloned = cloneArrayBuffer(
value,
0,
ArrayBufferPrototypeGetByteLength(value),
ArrayBuffer,
);
WeakMapPrototypeSet(objectCloneMemo, value, cloned);
return cloned;
}
if (ArrayBufferIsView(value)) {
const tag = TypedArrayPrototypeGetSymbolToStringTag(value);
// DataView
if (tag === undefined) {
return new DataView(
structuredClone(DataViewPrototypeGetBuffer(value)),
DataViewPrototypeGetByteOffset(value),
DataViewPrototypeGetByteLength(value),
);
}
// TypedArray
let Constructor;
switch (tag) {
case "Int8Array":
Constructor = Int8Array;
break;
case "Int16Array":
Constructor = Int16Array;
break;
case "Int32Array":
Constructor = Int32Array;
break;
case "BigInt64Array":
Constructor = BigInt64Array;
break;
case "Uint8Array":
Constructor = Uint8Array;
break;
case "Uint8ClampedArray":
Constructor = Uint8ClampedArray;
break;
case "Uint16Array":
Constructor = Uint16Array;
break;
case "Uint32Array":
Constructor = Uint32Array;
break;
case "BigUint64Array":
Constructor = BigUint64Array;
break;
case "Float32Array":
Constructor = Float32Array;
break;
case "Float64Array":
Constructor = Float64Array;
break;
}
return new Constructor(
structuredClone(TypedArrayPrototypeGetBuffer(value)),
TypedArrayPrototypeGetByteOffset(value),
TypedArrayPrototypeGetLength(value),
); );
} }
// TODO(petamoriken): Resizable ArrayBuffer support in the future try {
/** Clone a value in a similar way to structured cloning. It is similar to a return core.deserialize(core.serialize(value));
* StructureDeserialize(StructuredSerialize(...)). */ } catch (e) {
function structuredClone(value) { if (ObjectPrototypeIsPrototypeOf(TypeErrorPrototype, e)) {
// Performance optimization for buffers, otherwise throw new DOMException(e.message, "DataCloneError");
// `serialize/deserialize` will allocate new buffer.
if (ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, value)) {
const cloned = cloneArrayBuffer(
value,
0,
ArrayBufferPrototypeGetByteLength(value),
ArrayBuffer,
);
WeakMapPrototypeSet(objectCloneMemo, value, cloned);
return cloned;
}
if (ArrayBufferIsView(value)) {
const tag = TypedArrayPrototypeGetSymbolToStringTag(value);
// DataView
if (tag === undefined) {
return new DataView(
structuredClone(DataViewPrototypeGetBuffer(value)),
DataViewPrototypeGetByteOffset(value),
DataViewPrototypeGetByteLength(value),
);
}
// TypedArray
let Constructor;
switch (tag) {
case "Int8Array":
Constructor = Int8Array;
break;
case "Int16Array":
Constructor = Int16Array;
break;
case "Int32Array":
Constructor = Int32Array;
break;
case "BigInt64Array":
Constructor = BigInt64Array;
break;
case "Uint8Array":
Constructor = Uint8Array;
break;
case "Uint8ClampedArray":
Constructor = Uint8ClampedArray;
break;
case "Uint16Array":
Constructor = Uint16Array;
break;
case "Uint32Array":
Constructor = Uint32Array;
break;
case "BigUint64Array":
Constructor = BigUint64Array;
break;
case "Float32Array":
Constructor = Float32Array;
break;
case "Float64Array":
Constructor = Float64Array;
break;
}
return new Constructor(
structuredClone(TypedArrayPrototypeGetBuffer(value)),
TypedArrayPrototypeGetByteOffset(value),
TypedArrayPrototypeGetLength(value),
);
}
try {
return core.deserialize(core.serialize(value));
} catch (e) {
if (ObjectPrototypeIsPrototypeOf(TypeErrorPrototype, e)) {
throw new DOMException(e.message, "DataCloneError");
}
throw e;
} }
throw e;
} }
}
window.__bootstrap.structuredClone = structuredClone; export { structuredClone };
})(globalThis);

View file

@ -1,375 +1,372 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
"use strict";
((window) => { const core = globalThis.Deno.core;
const core = window.Deno.core; const ops = core.ops;
const ops = core.ops; const primordials = globalThis.__bootstrap.primordials;
const { const {
ArrayPrototypePush, ArrayPrototypePush,
ArrayPrototypeShift, ArrayPrototypeShift,
FunctionPrototypeCall, FunctionPrototypeCall,
Map, Map,
MapPrototypeDelete, MapPrototypeDelete,
MapPrototypeGet, MapPrototypeGet,
MapPrototypeHas, MapPrototypeHas,
MapPrototypeSet, MapPrototypeSet,
Uint8Array, Uint8Array,
Uint32Array, Uint32Array,
// deno-lint-ignore camelcase // deno-lint-ignore camelcase
NumberPOSITIVE_INFINITY, NumberPOSITIVE_INFINITY,
PromisePrototypeThen, PromisePrototypeThen,
SafeArrayIterator, SafeArrayIterator,
SymbolFor, SymbolFor,
TypeError, TypeError,
indirectEval, indirectEval,
} = window.__bootstrap.primordials; } = primordials;
const { webidl } = window.__bootstrap; import * as webidl from "internal:ext/webidl/00_webidl.js";
const { reportException } = window.__bootstrap.event; import { reportException } from "internal:ext/web/02_event.js";
const { assert } = window.__bootstrap.infra; import { assert } from "internal:ext/web/00_infra.js";
const hrU8 = new Uint8Array(8); const hrU8 = new Uint8Array(8);
const hr = new Uint32Array(hrU8.buffer); const hr = new Uint32Array(hrU8.buffer);
function opNow() { function opNow() {
ops.op_now(hrU8); ops.op_now(hrU8);
return (hr[0] * 1000 + hr[1] / 1e6); return (hr[0] * 1000 + hr[1] / 1e6);
}
// ---------------------------------------------------------------------------
/**
* The task queue corresponding to the timer task source.
*
* @type { {action: () => void, nestingLevel: number}[] }
*/
const timerTasks = [];
/**
* The current task's timer nesting level, or zero if we're not currently
* running a timer task (since the minimum nesting level is 1).
*
* @type {number}
*/
let timerNestingLevel = 0;
function handleTimerMacrotask() {
if (timerTasks.length === 0) {
return true;
} }
// --------------------------------------------------------------------------- const task = ArrayPrototypeShift(timerTasks);
/** timerNestingLevel = task.nestingLevel;
* The task queue corresponding to the timer task source.
*
* @type { {action: () => void, nestingLevel: number}[] }
*/
const timerTasks = [];
/** try {
* The current task's timer nesting level, or zero if we're not currently task.action();
* running a timer task (since the minimum nesting level is 1). } finally {
* timerNestingLevel = 0;
* @type {number} }
*/ return timerTasks.length === 0;
let timerNestingLevel = 0; }
function handleTimerMacrotask() { // ---------------------------------------------------------------------------
if (timerTasks.length === 0) {
return true;
}
const task = ArrayPrototypeShift(timerTasks); /**
* The keys in this map correspond to the key ID's in the spec's map of active
* timers. The values are the timeout's cancel rid.
*
* @type {Map<number, { cancelRid: number, isRef: boolean, promiseId: number }>}
*/
const activeTimers = new Map();
timerNestingLevel = task.nestingLevel; let nextId = 1;
try { /**
task.action(); * @param {Function | string} callback
} finally { * @param {number} timeout
timerNestingLevel = 0; * @param {Array<any>} args
} * @param {boolean} repeat
return timerTasks.length === 0; * @param {number | undefined} prevId
* @returns {number} The timer ID
*/
function initializeTimer(
callback,
timeout,
args,
repeat,
prevId,
) {
// 2. If previousId was given, let id be previousId; otherwise, let
// previousId be an implementation-defined integer than is greater than zero
// and does not already exist in global's map of active timers.
let id;
let timerInfo;
if (prevId !== undefined) {
// `prevId` is only passed for follow-up calls on intervals
assert(repeat);
id = prevId;
timerInfo = MapPrototypeGet(activeTimers, id);
} else {
// TODO(@andreubotella): Deal with overflow.
// https://github.com/whatwg/html/issues/7358
id = nextId++;
const cancelRid = ops.op_timer_handle();
timerInfo = { cancelRid, isRef: true, promiseId: -1 };
// Step 4 in "run steps after a timeout".
MapPrototypeSet(activeTimers, id, timerInfo);
} }
// --------------------------------------------------------------------------- // 3. If the surrounding agent's event loop's currently running task is a
// task that was created by this algorithm, then let nesting level be the
// task's timer nesting level. Otherwise, let nesting level be zero.
// 4. If timeout is less than 0, then set timeout to 0.
// 5. If nesting level is greater than 5, and timeout is less than 4, then
// set timeout to 4.
//
// The nesting level of 5 and minimum of 4 ms are spec-mandated magic
// constants.
if (timeout < 0) timeout = 0;
if (timerNestingLevel > 5 && timeout < 4) timeout = 4;
/** // 9. Let task be a task that runs the following steps:
* The keys in this map correspond to the key ID's in the spec's map of active const task = {
* timers. The values are the timeout's cancel rid. action: () => {
* // 1. If id does not exist in global's map of active timers, then abort
* @type {Map<number, { cancelRid: number, isRef: boolean, promiseId: number }>} // these steps.
*/ //
const activeTimers = new Map(); // This is relevant if the timer has been canceled after the sleep op
// resolves but before this task runs.
if (!MapPrototypeHas(activeTimers, id)) {
return;
}
let nextId = 1; // 2.
// 3.
if (typeof callback === "function") {
try {
FunctionPrototypeCall(
callback,
globalThis,
...new SafeArrayIterator(args),
);
} catch (error) {
reportException(error);
}
} else {
indirectEval(callback);
}
/** if (repeat) {
* @param {Function | string} callback if (MapPrototypeHas(activeTimers, id)) {
* @param {number} timeout // 4. If id does not exist in global's map of active timers, then
* @param {Array<any>} args // abort these steps.
* @param {boolean} repeat // NOTE: If might have been removed via the author code in handler
* @param {number | undefined} prevId // calling clearTimeout() or clearInterval().
* @returns {number} The timer ID // 5. If repeat is true, then perform the timer initialization steps
*/ // again, given global, handler, timeout, arguments, true, and id.
function initializeTimer( initializeTimer(callback, timeout, args, true, id);
callback, }
} else {
// 6. Otherwise, remove global's map of active timers[id].
core.tryClose(timerInfo.cancelRid);
MapPrototypeDelete(activeTimers, id);
}
},
// 10. Increment nesting level by one.
// 11. Set task's timer nesting level to nesting level.
nestingLevel: timerNestingLevel + 1,
};
// 12. Let completionStep be an algorithm step which queues a global task on
// the timer task source given global to run task.
// 13. Run steps after a timeout given global, "setTimeout/setInterval",
// timeout, completionStep, and id.
runAfterTimeout(
() => ArrayPrototypePush(timerTasks, task),
timeout, timeout,
args, timerInfo,
repeat, );
prevId,
) {
// 2. If previousId was given, let id be previousId; otherwise, let
// previousId be an implementation-defined integer than is greater than zero
// and does not already exist in global's map of active timers.
let id;
let timerInfo;
if (prevId !== undefined) {
// `prevId` is only passed for follow-up calls on intervals
assert(repeat);
id = prevId;
timerInfo = MapPrototypeGet(activeTimers, id);
} else {
// TODO(@andreubotella): Deal with overflow.
// https://github.com/whatwg/html/issues/7358
id = nextId++;
const cancelRid = ops.op_timer_handle();
timerInfo = { cancelRid, isRef: true, promiseId: -1 };
// Step 4 in "run steps after a timeout". return id;
MapPrototypeSet(activeTimers, id, timerInfo); }
}
// 3. If the surrounding agent's event loop's currently running task is a // ---------------------------------------------------------------------------
// task that was created by this algorithm, then let nesting level be the
// task's timer nesting level. Otherwise, let nesting level be zero.
// 4. If timeout is less than 0, then set timeout to 0.
// 5. If nesting level is greater than 5, and timeout is less than 4, then
// set timeout to 4.
//
// The nesting level of 5 and minimum of 4 ms are spec-mandated magic
// constants.
if (timeout < 0) timeout = 0;
if (timerNestingLevel > 5 && timeout < 4) timeout = 4;
// 9. Let task be a task that runs the following steps: /**
const task = { * @typedef ScheduledTimer
action: () => { * @property {number} millis
// 1. If id does not exist in global's map of active timers, then abort * @property {() => void} cb
// these steps. * @property {boolean} resolved
// * @property {ScheduledTimer | null} prev
// This is relevant if the timer has been canceled after the sleep op * @property {ScheduledTimer | null} next
// resolves but before this task runs. */
if (!MapPrototypeHas(activeTimers, id)) {
return;
}
// 2. /**
// 3. * A doubly linked list of timers.
if (typeof callback === "function") { * @type { { head: ScheduledTimer | null, tail: ScheduledTimer | null } }
try { */
FunctionPrototypeCall( const scheduledTimers = { head: null, tail: null };
callback,
globalThis,
...new SafeArrayIterator(args),
);
} catch (error) {
reportException(error);
}
} else {
indirectEval(callback);
}
if (repeat) { /**
if (MapPrototypeHas(activeTimers, id)) { * @param {() => void} cb Will be run after the timeout, if it hasn't been
// 4. If id does not exist in global's map of active timers, then * cancelled.
// abort these steps. * @param {number} millis
// NOTE: If might have been removed via the author code in handler * @param {{ cancelRid: number, isRef: boolean, promiseId: number }} timerInfo
// calling clearTimeout() or clearInterval(). */
// 5. If repeat is true, then perform the timer initialization steps function runAfterTimeout(cb, millis, timerInfo) {
// again, given global, handler, timeout, arguments, true, and id. const cancelRid = timerInfo.cancelRid;
initializeTimer(callback, timeout, args, true, id); const sleepPromise = core.opAsync("op_sleep", millis, cancelRid);
} timerInfo.promiseId = sleepPromise[SymbolFor("Deno.core.internalPromiseId")];
} else { if (!timerInfo.isRef) {
// 6. Otherwise, remove global's map of active timers[id].
core.tryClose(timerInfo.cancelRid);
MapPrototypeDelete(activeTimers, id);
}
},
// 10. Increment nesting level by one.
// 11. Set task's timer nesting level to nesting level.
nestingLevel: timerNestingLevel + 1,
};
// 12. Let completionStep be an algorithm step which queues a global task on
// the timer task source given global to run task.
// 13. Run steps after a timeout given global, "setTimeout/setInterval",
// timeout, completionStep, and id.
runAfterTimeout(
() => ArrayPrototypePush(timerTasks, task),
timeout,
timerInfo,
);
return id;
}
// ---------------------------------------------------------------------------
/**
* @typedef ScheduledTimer
* @property {number} millis
* @property {() => void} cb
* @property {boolean} resolved
* @property {ScheduledTimer | null} prev
* @property {ScheduledTimer | null} next
*/
/**
* A doubly linked list of timers.
* @type { { head: ScheduledTimer | null, tail: ScheduledTimer | null } }
*/
const scheduledTimers = { head: null, tail: null };
/**
* @param {() => void} cb Will be run after the timeout, if it hasn't been
* cancelled.
* @param {number} millis
* @param {{ cancelRid: number, isRef: boolean, promiseId: number }} timerInfo
*/
function runAfterTimeout(cb, millis, timerInfo) {
const cancelRid = timerInfo.cancelRid;
const sleepPromise = core.opAsync("op_sleep", millis, cancelRid);
timerInfo.promiseId =
sleepPromise[SymbolFor("Deno.core.internalPromiseId")];
if (!timerInfo.isRef) {
core.unrefOp(timerInfo.promiseId);
}
/** @type {ScheduledTimer} */
const timerObject = {
millis,
cb,
resolved: false,
prev: scheduledTimers.tail,
next: null,
};
// Add timerObject to the end of the list.
if (scheduledTimers.tail === null) {
assert(scheduledTimers.head === null);
scheduledTimers.head = scheduledTimers.tail = timerObject;
} else {
scheduledTimers.tail.next = timerObject;
scheduledTimers.tail = timerObject;
}
// 1.
PromisePrototypeThen(
sleepPromise,
(cancelled) => {
if (!cancelled) {
// The timer was cancelled.
removeFromScheduledTimers(timerObject);
return;
}
// 2. Wait until any invocations of this algorithm that had the same
// global and orderingIdentifier, that started before this one, and
// whose milliseconds is equal to or less than this one's, have
// completed.
// 4. Perform completionSteps.
// IMPORTANT: Since the sleep ops aren't guaranteed to resolve in the
// right order, whenever one resolves, we run through the scheduled
// timers list (which is in the order in which they were scheduled), and
// we call the callback for every timer which both:
// a) has resolved, and
// b) its timeout is lower than the lowest unresolved timeout found so
// far in the list.
timerObject.resolved = true;
let lowestUnresolvedTimeout = NumberPOSITIVE_INFINITY;
let currentEntry = scheduledTimers.head;
while (currentEntry !== null) {
if (currentEntry.millis < lowestUnresolvedTimeout) {
if (currentEntry.resolved) {
currentEntry.cb();
removeFromScheduledTimers(currentEntry);
} else {
lowestUnresolvedTimeout = currentEntry.millis;
}
}
currentEntry = currentEntry.next;
}
},
);
}
/** @param {ScheduledTimer} timerObj */
function removeFromScheduledTimers(timerObj) {
if (timerObj.prev !== null) {
timerObj.prev.next = timerObj.next;
} else {
assert(scheduledTimers.head === timerObj);
scheduledTimers.head = timerObj.next;
}
if (timerObj.next !== null) {
timerObj.next.prev = timerObj.prev;
} else {
assert(scheduledTimers.tail === timerObj);
scheduledTimers.tail = timerObj.prev;
}
}
// ---------------------------------------------------------------------------
function checkThis(thisArg) {
if (thisArg !== null && thisArg !== undefined && thisArg !== globalThis) {
throw new TypeError("Illegal invocation");
}
}
function setTimeout(callback, timeout = 0, ...args) {
checkThis(this);
if (typeof callback !== "function") {
callback = webidl.converters.DOMString(callback);
}
timeout = webidl.converters.long(timeout);
return initializeTimer(callback, timeout, args, false);
}
function setInterval(callback, timeout = 0, ...args) {
checkThis(this);
if (typeof callback !== "function") {
callback = webidl.converters.DOMString(callback);
}
timeout = webidl.converters.long(timeout);
return initializeTimer(callback, timeout, args, true);
}
function clearTimeout(id = 0) {
checkThis(this);
id = webidl.converters.long(id);
const timerInfo = MapPrototypeGet(activeTimers, id);
if (timerInfo !== undefined) {
core.tryClose(timerInfo.cancelRid);
MapPrototypeDelete(activeTimers, id);
}
}
function clearInterval(id = 0) {
checkThis(this);
clearTimeout(id);
}
function refTimer(id) {
const timerInfo = MapPrototypeGet(activeTimers, id);
if (timerInfo === undefined || timerInfo.isRef) {
return;
}
timerInfo.isRef = true;
core.refOp(timerInfo.promiseId);
}
function unrefTimer(id) {
const timerInfo = MapPrototypeGet(activeTimers, id);
if (timerInfo === undefined || !timerInfo.isRef) {
return;
}
timerInfo.isRef = false;
core.unrefOp(timerInfo.promiseId); core.unrefOp(timerInfo.promiseId);
} }
window.__bootstrap.timers = { /** @type {ScheduledTimer} */
setTimeout, const timerObject = {
setInterval, millis,
clearTimeout, cb,
clearInterval, resolved: false,
handleTimerMacrotask, prev: scheduledTimers.tail,
opNow, next: null,
refTimer,
unrefTimer,
}; };
})(this);
// Add timerObject to the end of the list.
if (scheduledTimers.tail === null) {
assert(scheduledTimers.head === null);
scheduledTimers.head = scheduledTimers.tail = timerObject;
} else {
scheduledTimers.tail.next = timerObject;
scheduledTimers.tail = timerObject;
}
// 1.
PromisePrototypeThen(
sleepPromise,
(cancelled) => {
if (!cancelled) {
// The timer was cancelled.
removeFromScheduledTimers(timerObject);
return;
}
// 2. Wait until any invocations of this algorithm that had the same
// global and orderingIdentifier, that started before this one, and
// whose milliseconds is equal to or less than this one's, have
// completed.
// 4. Perform completionSteps.
// IMPORTANT: Since the sleep ops aren't guaranteed to resolve in the
// right order, whenever one resolves, we run through the scheduled
// timers list (which is in the order in which they were scheduled), and
// we call the callback for every timer which both:
// a) has resolved, and
// b) its timeout is lower than the lowest unresolved timeout found so
// far in the list.
timerObject.resolved = true;
let lowestUnresolvedTimeout = NumberPOSITIVE_INFINITY;
let currentEntry = scheduledTimers.head;
while (currentEntry !== null) {
if (currentEntry.millis < lowestUnresolvedTimeout) {
if (currentEntry.resolved) {
currentEntry.cb();
removeFromScheduledTimers(currentEntry);
} else {
lowestUnresolvedTimeout = currentEntry.millis;
}
}
currentEntry = currentEntry.next;
}
},
);
}
/** @param {ScheduledTimer} timerObj */
function removeFromScheduledTimers(timerObj) {
if (timerObj.prev !== null) {
timerObj.prev.next = timerObj.next;
} else {
assert(scheduledTimers.head === timerObj);
scheduledTimers.head = timerObj.next;
}
if (timerObj.next !== null) {
timerObj.next.prev = timerObj.prev;
} else {
assert(scheduledTimers.tail === timerObj);
scheduledTimers.tail = timerObj.prev;
}
}
// ---------------------------------------------------------------------------
function checkThis(thisArg) {
if (thisArg !== null && thisArg !== undefined && thisArg !== globalThis) {
throw new TypeError("Illegal invocation");
}
}
function setTimeout(callback, timeout = 0, ...args) {
checkThis(this);
if (typeof callback !== "function") {
callback = webidl.converters.DOMString(callback);
}
timeout = webidl.converters.long(timeout);
return initializeTimer(callback, timeout, args, false);
}
function setInterval(callback, timeout = 0, ...args) {
checkThis(this);
if (typeof callback !== "function") {
callback = webidl.converters.DOMString(callback);
}
timeout = webidl.converters.long(timeout);
return initializeTimer(callback, timeout, args, true);
}
function clearTimeout(id = 0) {
checkThis(this);
id = webidl.converters.long(id);
const timerInfo = MapPrototypeGet(activeTimers, id);
if (timerInfo !== undefined) {
core.tryClose(timerInfo.cancelRid);
MapPrototypeDelete(activeTimers, id);
}
}
function clearInterval(id = 0) {
checkThis(this);
clearTimeout(id);
}
function refTimer(id) {
const timerInfo = MapPrototypeGet(activeTimers, id);
if (timerInfo === undefined || timerInfo.isRef) {
return;
}
timerInfo.isRef = true;
core.refOp(timerInfo.promiseId);
}
function unrefTimer(id) {
const timerInfo = MapPrototypeGet(activeTimers, id);
if (timerInfo === undefined || !timerInfo.isRef) {
return;
}
timerInfo.isRef = false;
core.unrefOp(timerInfo.promiseId);
}
export {
clearInterval,
clearTimeout,
handleTimerMacrotask,
opNow,
refTimer,
setInterval,
setTimeout,
unrefTimer,
};

View file

@ -1,200 +1,205 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
"use strict";
// @ts-check // @ts-check
/// <reference path="../../core/internal.d.ts" /> /// <reference path="../../core/internal.d.ts" />
((window) => { import * as webidl from "internal:ext/webidl/00_webidl.js";
const webidl = window.__bootstrap.webidl; import {
const { Event, setIsTrusted, defineEventHandler } = window.__bootstrap.event; defineEventHandler,
const { EventTarget, listenerCount } = window.__bootstrap.eventTarget; Event,
const { EventTarget,
SafeArrayIterator, listenerCount,
SafeSetIterator, setIsTrusted,
Set, } from "internal:ext/web/02_event.js";
SetPrototypeAdd, const primordials = globalThis.__bootstrap.primordials;
SetPrototypeDelete, const {
Symbol, SafeArrayIterator,
TypeError, SafeSetIterator,
} = window.__bootstrap.primordials; Set,
const { setTimeout, refTimer, unrefTimer } = window.__bootstrap.timers; SetPrototypeAdd,
SetPrototypeDelete,
Symbol,
TypeError,
} = primordials;
import {
refTimer,
setTimeout,
unrefTimer,
} from "internal:ext/web/02_timers.js";
const add = Symbol("[[add]]"); const add = Symbol("[[add]]");
const signalAbort = Symbol("[[signalAbort]]"); const signalAbort = Symbol("[[signalAbort]]");
const remove = Symbol("[[remove]]"); const remove = Symbol("[[remove]]");
const abortReason = Symbol("[[abortReason]]"); const abortReason = Symbol("[[abortReason]]");
const abortAlgos = Symbol("[[abortAlgos]]"); const abortAlgos = Symbol("[[abortAlgos]]");
const signal = Symbol("[[signal]]"); const signal = Symbol("[[signal]]");
const timerId = Symbol("[[timerId]]"); const timerId = Symbol("[[timerId]]");
const illegalConstructorKey = Symbol("illegalConstructorKey"); const illegalConstructorKey = Symbol("illegalConstructorKey");
class AbortSignal extends EventTarget { class AbortSignal extends EventTarget {
static abort(reason = undefined) { static abort(reason = undefined) {
if (reason !== undefined) { if (reason !== undefined) {
reason = webidl.converters.any(reason); reason = webidl.converters.any(reason);
}
const signal = new AbortSignal(illegalConstructorKey);
signal[signalAbort](reason);
return signal;
}
static timeout(millis) {
const prefix = "Failed to call 'AbortSignal.timeout'";
webidl.requiredArguments(arguments.length, 1, { prefix });
millis = webidl.converters["unsigned long long"](millis, {
enforceRange: true,
});
const signal = new AbortSignal(illegalConstructorKey);
signal[timerId] = setTimeout(
() => {
signal[timerId] = null;
signal[signalAbort](
new DOMException("Signal timed out.", "TimeoutError"),
);
},
millis,
);
unrefTimer(signal[timerId]);
return signal;
}
[add](algorithm) {
if (this.aborted) {
return;
}
if (this[abortAlgos] === null) {
this[abortAlgos] = new Set();
}
SetPrototypeAdd(this[abortAlgos], algorithm);
}
[signalAbort](
reason = new DOMException("The signal has been aborted", "AbortError"),
) {
if (this.aborted) {
return;
}
this[abortReason] = reason;
if (this[abortAlgos] !== null) {
for (const algorithm of new SafeSetIterator(this[abortAlgos])) {
algorithm();
}
this[abortAlgos] = null;
}
const event = new Event("abort");
setIsTrusted(event, true);
this.dispatchEvent(event);
}
[remove](algorithm) {
this[abortAlgos] && SetPrototypeDelete(this[abortAlgos], algorithm);
}
constructor(key = null) {
if (key != illegalConstructorKey) {
throw new TypeError("Illegal constructor.");
}
super();
this[abortReason] = undefined;
this[abortAlgos] = null;
this[timerId] = null;
this[webidl.brand] = webidl.brand;
}
get aborted() {
webidl.assertBranded(this, AbortSignalPrototype);
return this[abortReason] !== undefined;
}
get reason() {
webidl.assertBranded(this, AbortSignalPrototype);
return this[abortReason];
}
throwIfAborted() {
webidl.assertBranded(this, AbortSignalPrototype);
if (this[abortReason] !== undefined) {
throw this[abortReason];
}
}
// `addEventListener` and `removeEventListener` have to be overriden in
// order to have the timer block the event loop while there are listeners.
// `[add]` and `[remove]` don't ref and unref the timer because they can
// only be used by Deno internals, which use it to essentially cancel async
// ops which would block the event loop.
addEventListener(...args) {
super.addEventListener(...new SafeArrayIterator(args));
if (this[timerId] !== null && listenerCount(this, "abort") > 0) {
refTimer(this[timerId]);
}
}
removeEventListener(...args) {
super.removeEventListener(...new SafeArrayIterator(args));
if (this[timerId] !== null && listenerCount(this, "abort") === 0) {
unrefTimer(this[timerId]);
}
}
}
defineEventHandler(AbortSignal.prototype, "abort");
webidl.configurePrototype(AbortSignal);
const AbortSignalPrototype = AbortSignal.prototype;
class AbortController {
[signal] = new AbortSignal(illegalConstructorKey);
constructor() {
this[webidl.brand] = webidl.brand;
}
get signal() {
webidl.assertBranded(this, AbortControllerPrototype);
return this[signal];
}
abort(reason) {
webidl.assertBranded(this, AbortControllerPrototype);
this[signal][signalAbort](reason);
} }
const signal = new AbortSignal(illegalConstructorKey);
signal[signalAbort](reason);
return signal;
} }
webidl.configurePrototype(AbortController); static timeout(millis) {
const AbortControllerPrototype = AbortController.prototype; const prefix = "Failed to call 'AbortSignal.timeout'";
webidl.requiredArguments(arguments.length, 1, { prefix });
millis = webidl.converters["unsigned long long"](millis, {
enforceRange: true,
});
webidl.converters["AbortSignal"] = webidl.createInterfaceConverter( const signal = new AbortSignal(illegalConstructorKey);
"AbortSignal", signal[timerId] = setTimeout(
AbortSignal.prototype, () => {
); signal[timerId] = null;
signal[signalAbort](
function newSignal() { new DOMException("Signal timed out.", "TimeoutError"),
return new AbortSignal(illegalConstructorKey); );
},
millis,
);
unrefTimer(signal[timerId]);
return signal;
} }
function follow(followingSignal, parentSignal) { [add](algorithm) {
if (followingSignal.aborted) { if (this.aborted) {
return; return;
} }
if (parentSignal.aborted) { if (this[abortAlgos] === null) {
followingSignal[signalAbort](parentSignal.reason); this[abortAlgos] = new Set();
} else { }
parentSignal[add](() => SetPrototypeAdd(this[abortAlgos], algorithm);
followingSignal[signalAbort](parentSignal.reason) }
);
[signalAbort](
reason = new DOMException("The signal has been aborted", "AbortError"),
) {
if (this.aborted) {
return;
}
this[abortReason] = reason;
if (this[abortAlgos] !== null) {
for (const algorithm of new SafeSetIterator(this[abortAlgos])) {
algorithm();
}
this[abortAlgos] = null;
}
const event = new Event("abort");
setIsTrusted(event, true);
this.dispatchEvent(event);
}
[remove](algorithm) {
this[abortAlgos] && SetPrototypeDelete(this[abortAlgos], algorithm);
}
constructor(key = null) {
if (key != illegalConstructorKey) {
throw new TypeError("Illegal constructor.");
}
super();
this[abortReason] = undefined;
this[abortAlgos] = null;
this[timerId] = null;
this[webidl.brand] = webidl.brand;
}
get aborted() {
webidl.assertBranded(this, AbortSignalPrototype);
return this[abortReason] !== undefined;
}
get reason() {
webidl.assertBranded(this, AbortSignalPrototype);
return this[abortReason];
}
throwIfAborted() {
webidl.assertBranded(this, AbortSignalPrototype);
if (this[abortReason] !== undefined) {
throw this[abortReason];
} }
} }
window.__bootstrap.abortSignal = { // `addEventListener` and `removeEventListener` have to be overriden in
AbortSignal, // order to have the timer block the event loop while there are listeners.
AbortController, // `[add]` and `[remove]` don't ref and unref the timer because they can
AbortSignalPrototype, // only be used by Deno internals, which use it to essentially cancel async
add, // ops which would block the event loop.
signalAbort, addEventListener(...args) {
remove, super.addEventListener(...new SafeArrayIterator(args));
follow, if (this[timerId] !== null && listenerCount(this, "abort") > 0) {
newSignal, refTimer(this[timerId]);
}; }
})(this); }
removeEventListener(...args) {
super.removeEventListener(...new SafeArrayIterator(args));
if (this[timerId] !== null && listenerCount(this, "abort") === 0) {
unrefTimer(this[timerId]);
}
}
}
defineEventHandler(AbortSignal.prototype, "abort");
webidl.configurePrototype(AbortSignal);
const AbortSignalPrototype = AbortSignal.prototype;
class AbortController {
[signal] = new AbortSignal(illegalConstructorKey);
constructor() {
this[webidl.brand] = webidl.brand;
}
get signal() {
webidl.assertBranded(this, AbortControllerPrototype);
return this[signal];
}
abort(reason) {
webidl.assertBranded(this, AbortControllerPrototype);
this[signal][signalAbort](reason);
}
}
webidl.configurePrototype(AbortController);
const AbortControllerPrototype = AbortController.prototype;
webidl.converters["AbortSignal"] = webidl.createInterfaceConverter(
"AbortSignal",
AbortSignal.prototype,
);
function newSignal() {
return new AbortSignal(illegalConstructorKey);
}
function follow(followingSignal, parentSignal) {
if (followingSignal.aborted) {
return;
}
if (parentSignal.aborted) {
followingSignal[signalAbort](parentSignal.reason);
} else {
parentSignal[add](() => followingSignal[signalAbort](parentSignal.reason));
}
}
export {
AbortController,
AbortSignal,
AbortSignalPrototype,
add,
follow,
newSignal,
remove,
signalAbort,
};

View file

@ -1,79 +1,83 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
"use strict";
// @ts-check // @ts-check
/// <reference path="../../core/internal.d.ts" /> /// <reference path="../../core/internal.d.ts" />
((window) => { import { EventTarget } from "internal:ext/web/02_event.js";
const { EventTarget } = window.__bootstrap.eventTarget; const primordials = globalThis.__bootstrap.primordials;
const { const {
Symbol, Symbol,
SymbolToStringTag, SymbolToStringTag,
TypeError, TypeError,
} = window.__bootstrap.primordials; } = primordials;
const illegalConstructorKey = Symbol("illegalConstructorKey"); const illegalConstructorKey = Symbol("illegalConstructorKey");
class Window extends EventTarget { class Window extends EventTarget {
constructor(key = null) { constructor(key = null) {
if (key !== illegalConstructorKey) { if (key !== illegalConstructorKey) {
throw new TypeError("Illegal constructor."); throw new TypeError("Illegal constructor.");
}
super();
}
get [SymbolToStringTag]() {
return "Window";
} }
super();
} }
class WorkerGlobalScope extends EventTarget { get [SymbolToStringTag]() {
constructor(key = null) { return "Window";
if (key != illegalConstructorKey) { }
throw new TypeError("Illegal constructor."); }
}
super();
}
get [SymbolToStringTag]() { class WorkerGlobalScope extends EventTarget {
return "WorkerGlobalScope"; constructor(key = null) {
if (key != illegalConstructorKey) {
throw new TypeError("Illegal constructor.");
} }
super();
} }
class DedicatedWorkerGlobalScope extends WorkerGlobalScope { get [SymbolToStringTag]() {
constructor(key = null) { return "WorkerGlobalScope";
if (key != illegalConstructorKey) { }
throw new TypeError("Illegal constructor."); }
}
super();
}
get [SymbolToStringTag]() { class DedicatedWorkerGlobalScope extends WorkerGlobalScope {
return "DedicatedWorkerGlobalScope"; constructor(key = null) {
if (key != illegalConstructorKey) {
throw new TypeError("Illegal constructor.");
} }
super();
} }
window.__bootstrap.globalInterfaces = { get [SymbolToStringTag]() {
DedicatedWorkerGlobalScope, return "DedicatedWorkerGlobalScope";
Window, }
WorkerGlobalScope, }
dedicatedWorkerGlobalScopeConstructorDescriptor: {
configurable: true, const dedicatedWorkerGlobalScopeConstructorDescriptor = {
enumerable: false, configurable: true,
value: DedicatedWorkerGlobalScope, enumerable: false,
writable: true, value: DedicatedWorkerGlobalScope,
}, writable: true,
windowConstructorDescriptor: { };
configurable: true,
enumerable: false, const windowConstructorDescriptor = {
value: Window, configurable: true,
writable: true, enumerable: false,
}, value: Window,
workerGlobalScopeConstructorDescriptor: { writable: true,
configurable: true, };
enumerable: false,
value: WorkerGlobalScope, const workerGlobalScopeConstructorDescriptor = {
writable: true, configurable: true,
}, enumerable: false,
}; value: WorkerGlobalScope,
})(this); writable: true,
};
export {
DedicatedWorkerGlobalScope,
dedicatedWorkerGlobalScopeConstructorDescriptor,
Window,
windowConstructorDescriptor,
WorkerGlobalScope,
workerGlobalScopeConstructorDescriptor,
};

View file

@ -6,68 +6,62 @@
/// <reference path="../web/internal.d.ts" /> /// <reference path="../web/internal.d.ts" />
/// <reference lib="esnext" /> /// <reference lib="esnext" />
"use strict"; const core = globalThis.Deno.core;
const ops = core.ops;
import * as webidl from "internal:ext/webidl/00_webidl.js";
import DOMException from "internal:ext/web/01_dom_exception.js";
const primordials = globalThis.__bootstrap.primordials;
const {
ObjectPrototypeIsPrototypeOf,
TypeErrorPrototype,
} = primordials;
((window) => { /**
const core = Deno.core; * @param {string} data
const ops = core.ops; * @returns {string}
const webidl = window.__bootstrap.webidl; */
const { DOMException } = window.__bootstrap.domException; function atob(data) {
const { const prefix = "Failed to execute 'atob'";
ObjectPrototypeIsPrototypeOf, webidl.requiredArguments(arguments.length, 1, { prefix });
TypeErrorPrototype, data = webidl.converters.DOMString(data, {
} = window.__bootstrap.primordials; prefix,
context: "Argument 1",
/** });
* @param {string} data try {
* @returns {string} return ops.op_base64_atob(data);
*/ } catch (e) {
function atob(data) { if (ObjectPrototypeIsPrototypeOf(TypeErrorPrototype, e)) {
const prefix = "Failed to execute 'atob'"; throw new DOMException(
webidl.requiredArguments(arguments.length, 1, { prefix }); "Failed to decode base64: invalid character",
data = webidl.converters.DOMString(data, { "InvalidCharacterError",
prefix, );
context: "Argument 1",
});
try {
return ops.op_base64_atob(data);
} catch (e) {
if (ObjectPrototypeIsPrototypeOf(TypeErrorPrototype, e)) {
throw new DOMException(
"Failed to decode base64: invalid character",
"InvalidCharacterError",
);
}
throw e;
} }
throw e;
} }
}
/** /**
* @param {string} data * @param {string} data
* @returns {string} * @returns {string}
*/ */
function btoa(data) { function btoa(data) {
const prefix = "Failed to execute 'btoa'"; const prefix = "Failed to execute 'btoa'";
webidl.requiredArguments(arguments.length, 1, { prefix }); webidl.requiredArguments(arguments.length, 1, { prefix });
data = webidl.converters.DOMString(data, { data = webidl.converters.DOMString(data, {
prefix, prefix,
context: "Argument 1", context: "Argument 1",
}); });
try { try {
return ops.op_base64_btoa(data); return ops.op_base64_btoa(data);
} catch (e) { } catch (e) {
if (ObjectPrototypeIsPrototypeOf(TypeErrorPrototype, e)) { if (ObjectPrototypeIsPrototypeOf(TypeErrorPrototype, e)) {
throw new DOMException( throw new DOMException(
"The string to be encoded contains characters outside of the Latin1 range.", "The string to be encoded contains characters outside of the Latin1 range.",
"InvalidCharacterError", "InvalidCharacterError",
); );
}
throw e;
} }
throw e;
} }
}
window.__bootstrap.base64 = { export { atob, btoa };
atob,
btoa,
};
})(globalThis);

File diff suppressed because it is too large Load diff

View file

@ -9,437 +9,434 @@
/// <reference path="../web/lib.deno_web.d.ts" /> /// <reference path="../web/lib.deno_web.d.ts" />
/// <reference lib="esnext" /> /// <reference lib="esnext" />
"use strict"; const core = globalThis.Deno.core;
const ops = core.ops;
import * as webidl from "internal:ext/webidl/00_webidl.js";
const primordials = globalThis.__bootstrap.primordials;
const {
PromiseReject,
PromiseResolve,
// TODO(lucacasonato): add SharedArrayBuffer to primordials
// SharedArrayBufferPrototype
StringPrototypeCharCodeAt,
StringPrototypeSlice,
TypedArrayPrototypeSubarray,
Uint8Array,
ObjectPrototypeIsPrototypeOf,
ArrayBufferIsView,
Uint32Array,
} = primordials;
((window) => { class TextDecoder {
const core = Deno.core; /** @type {string} */
const ops = core.ops; #encoding;
const webidl = window.__bootstrap.webidl; /** @type {boolean} */
const { #fatal;
PromiseReject, /** @type {boolean} */
PromiseResolve, #ignoreBOM;
// TODO(lucacasonato): add SharedArrayBuffer to primordials /** @type {boolean} */
// SharedArrayBufferPrototype #utf8SinglePass;
StringPrototypeCharCodeAt,
StringPrototypeSlice,
TypedArrayPrototypeSubarray,
Uint8Array,
ObjectPrototypeIsPrototypeOf,
ArrayBufferIsView,
Uint32Array,
} = window.__bootstrap.primordials;
class TextDecoder { /** @type {number | null} */
/** @type {string} */ #rid = null;
#encoding;
/** @type {boolean} */
#fatal;
/** @type {boolean} */
#ignoreBOM;
/** @type {boolean} */
#utf8SinglePass;
/** @type {number | null} */ /**
#rid = null; * @param {string} label
* @param {TextDecoderOptions} options
/** */
* @param {string} label constructor(label = "utf-8", options = {}) {
* @param {TextDecoderOptions} options const prefix = "Failed to construct 'TextDecoder'";
*/ label = webidl.converters.DOMString(label, {
constructor(label = "utf-8", options = {}) { prefix,
const prefix = "Failed to construct 'TextDecoder'"; context: "Argument 1",
label = webidl.converters.DOMString(label, { });
prefix, options = webidl.converters.TextDecoderOptions(options, {
context: "Argument 1", prefix,
}); context: "Argument 2",
options = webidl.converters.TextDecoderOptions(options, { });
prefix, const encoding = ops.op_encoding_normalize_label(label);
context: "Argument 2", this.#encoding = encoding;
}); this.#fatal = options.fatal;
const encoding = ops.op_encoding_normalize_label(label); this.#ignoreBOM = options.ignoreBOM;
this.#encoding = encoding; this.#utf8SinglePass = encoding === "utf-8" && !options.fatal;
this.#fatal = options.fatal; this[webidl.brand] = webidl.brand;
this.#ignoreBOM = options.ignoreBOM;
this.#utf8SinglePass = encoding === "utf-8" && !options.fatal;
this[webidl.brand] = webidl.brand;
}
/** @returns {string} */
get encoding() {
webidl.assertBranded(this, TextDecoderPrototype);
return this.#encoding;
}
/** @returns {boolean} */
get fatal() {
webidl.assertBranded(this, TextDecoderPrototype);
return this.#fatal;
}
/** @returns {boolean} */
get ignoreBOM() {
webidl.assertBranded(this, TextDecoderPrototype);
return this.#ignoreBOM;
}
/**
* @param {BufferSource} [input]
* @param {TextDecodeOptions} options
*/
decode(input = new Uint8Array(), options = undefined) {
webidl.assertBranded(this, TextDecoderPrototype);
const prefix = "Failed to execute 'decode' on 'TextDecoder'";
if (input !== undefined) {
input = webidl.converters.BufferSource(input, {
prefix,
context: "Argument 1",
allowShared: true,
});
}
let stream = false;
if (options !== undefined) {
options = webidl.converters.TextDecodeOptions(options, {
prefix,
context: "Argument 2",
});
stream = options.stream;
}
try {
// Note from spec: implementations are strongly encouraged to use an implementation strategy that avoids this copy.
// When doing so they will have to make sure that changes to input do not affect future calls to decode().
if (
ObjectPrototypeIsPrototypeOf(
// deno-lint-ignore prefer-primordials
SharedArrayBuffer.prototype,
input || input.buffer,
)
) {
// We clone the data into a non-shared ArrayBuffer so we can pass it
// to Rust.
// `input` is now a Uint8Array, and calling the TypedArray constructor
// with a TypedArray argument copies the data.
if (ArrayBufferIsView(input)) {
input = new Uint8Array(
input.buffer,
input.byteOffset,
input.byteLength,
);
} else {
input = new Uint8Array(input);
}
}
// Fast path for single pass encoding.
if (!stream && this.#rid === null) {
// Fast path for utf8 single pass encoding.
if (this.#utf8SinglePass) {
return ops.op_encoding_decode_utf8(input, this.#ignoreBOM);
}
return ops.op_encoding_decode_single(
input,
this.#encoding,
this.#fatal,
this.#ignoreBOM,
);
}
if (this.#rid === null) {
this.#rid = ops.op_encoding_new_decoder(
this.#encoding,
this.#fatal,
this.#ignoreBOM,
);
}
return ops.op_encoding_decode(input, this.#rid, stream);
} finally {
if (!stream && this.#rid !== null) {
core.close(this.#rid);
this.#rid = null;
}
}
}
} }
webidl.configurePrototype(TextDecoder); /** @returns {string} */
const TextDecoderPrototype = TextDecoder.prototype; get encoding() {
webidl.assertBranded(this, TextDecoderPrototype);
return this.#encoding;
}
class TextEncoder { /** @returns {boolean} */
constructor() { get fatal() {
this[webidl.brand] = webidl.brand; webidl.assertBranded(this, TextDecoderPrototype);
} return this.#fatal;
}
/** @returns {string} */ /** @returns {boolean} */
get encoding() { get ignoreBOM() {
webidl.assertBranded(this, TextEncoderPrototype); webidl.assertBranded(this, TextDecoderPrototype);
return "utf-8"; return this.#ignoreBOM;
} }
/** /**
* @param {string} input * @param {BufferSource} [input]
* @returns {Uint8Array} * @param {TextDecodeOptions} options
*/ */
encode(input = "") { decode(input = new Uint8Array(), options = undefined) {
webidl.assertBranded(this, TextEncoderPrototype); webidl.assertBranded(this, TextDecoderPrototype);
const prefix = "Failed to execute 'encode' on 'TextEncoder'"; const prefix = "Failed to execute 'decode' on 'TextDecoder'";
// The WebIDL type of `input` is `USVString`, but `core.encode` already if (input !== undefined) {
// converts lone surrogates to the replacement character. input = webidl.converters.BufferSource(input, {
input = webidl.converters.DOMString(input, {
prefix, prefix,
context: "Argument 1", context: "Argument 1",
});
return core.encode(input);
}
/**
* @param {string} source
* @param {Uint8Array} destination
* @returns {TextEncoderEncodeIntoResult}
*/
encodeInto(source, destination) {
webidl.assertBranded(this, TextEncoderPrototype);
const prefix = "Failed to execute 'encodeInto' on 'TextEncoder'";
// The WebIDL type of `source` is `USVString`, but the ops bindings
// already convert lone surrogates to the replacement character.
source = webidl.converters.DOMString(source, {
prefix,
context: "Argument 1",
});
destination = webidl.converters.Uint8Array(destination, {
prefix,
context: "Argument 2",
allowShared: true, allowShared: true,
}); });
ops.op_encoding_encode_into(source, destination, encodeIntoBuf);
return {
read: encodeIntoBuf[0],
written: encodeIntoBuf[1],
};
} }
} let stream = false;
if (options !== undefined) {
const encodeIntoBuf = new Uint32Array(2); options = webidl.converters.TextDecodeOptions(options, {
webidl.configurePrototype(TextEncoder);
const TextEncoderPrototype = TextEncoder.prototype;
class TextDecoderStream {
/** @type {TextDecoder} */
#decoder;
/** @type {TransformStream<BufferSource, string>} */
#transform;
/**
* @param {string} label
* @param {TextDecoderOptions} options
*/
constructor(label = "utf-8", options = {}) {
const prefix = "Failed to construct 'TextDecoderStream'";
label = webidl.converters.DOMString(label, {
prefix,
context: "Argument 1",
});
options = webidl.converters.TextDecoderOptions(options, {
prefix, prefix,
context: "Argument 2", context: "Argument 2",
}); });
this.#decoder = new TextDecoder(label, options); stream = options.stream;
this.#transform = new TransformStream({
// The transform and flush functions need access to TextDecoderStream's
// `this`, so they are defined as functions rather than methods.
transform: (chunk, controller) => {
try {
chunk = webidl.converters.BufferSource(chunk, {
allowShared: true,
});
const decoded = this.#decoder.decode(chunk, { stream: true });
if (decoded) {
controller.enqueue(decoded);
}
return PromiseResolve();
} catch (err) {
return PromiseReject(err);
}
},
flush: (controller) => {
try {
const final = this.#decoder.decode();
if (final) {
controller.enqueue(final);
}
return PromiseResolve();
} catch (err) {
return PromiseReject(err);
}
},
});
this[webidl.brand] = webidl.brand;
} }
/** @returns {string} */ try {
get encoding() { // Note from spec: implementations are strongly encouraged to use an implementation strategy that avoids this copy.
webidl.assertBranded(this, TextDecoderStreamPrototype); // When doing so they will have to make sure that changes to input do not affect future calls to decode().
return this.#decoder.encoding; if (
} ObjectPrototypeIsPrototypeOf(
// deno-lint-ignore prefer-primordials
SharedArrayBuffer.prototype,
input || input.buffer,
)
) {
// We clone the data into a non-shared ArrayBuffer so we can pass it
// to Rust.
// `input` is now a Uint8Array, and calling the TypedArray constructor
// with a TypedArray argument copies the data.
if (ArrayBufferIsView(input)) {
input = new Uint8Array(
input.buffer,
input.byteOffset,
input.byteLength,
);
} else {
input = new Uint8Array(input);
}
}
/** @returns {boolean} */ // Fast path for single pass encoding.
get fatal() { if (!stream && this.#rid === null) {
webidl.assertBranded(this, TextDecoderStreamPrototype); // Fast path for utf8 single pass encoding.
return this.#decoder.fatal; if (this.#utf8SinglePass) {
} return ops.op_encoding_decode_utf8(input, this.#ignoreBOM);
}
/** @returns {boolean} */ return ops.op_encoding_decode_single(
get ignoreBOM() { input,
webidl.assertBranded(this, TextDecoderStreamPrototype); this.#encoding,
return this.#decoder.ignoreBOM; this.#fatal,
} this.#ignoreBOM,
);
}
/** @returns {ReadableStream<string>} */ if (this.#rid === null) {
get readable() { this.#rid = ops.op_encoding_new_decoder(
webidl.assertBranded(this, TextDecoderStreamPrototype); this.#encoding,
return this.#transform.readable; this.#fatal,
} this.#ignoreBOM,
);
/** @returns {WritableStream<BufferSource>} */ }
get writable() { return ops.op_encoding_decode(input, this.#rid, stream);
webidl.assertBranded(this, TextDecoderStreamPrototype); } finally {
return this.#transform.writable; if (!stream && this.#rid !== null) {
core.close(this.#rid);
this.#rid = null;
}
} }
} }
}
webidl.configurePrototype(TextDecoderStream); webidl.configurePrototype(TextDecoder);
const TextDecoderStreamPrototype = TextDecoderStream.prototype; const TextDecoderPrototype = TextDecoder.prototype;
class TextEncoderStream { class TextEncoder {
/** @type {string | null} */ constructor() {
#pendingHighSurrogate = null; this[webidl.brand] = webidl.brand;
/** @type {TransformStream<string, Uint8Array>} */
#transform;
constructor() {
this.#transform = new TransformStream({
// The transform and flush functions need access to TextEncoderStream's
// `this`, so they are defined as functions rather than methods.
transform: (chunk, controller) => {
try {
chunk = webidl.converters.DOMString(chunk);
if (chunk === "") {
return PromiseResolve();
}
if (this.#pendingHighSurrogate !== null) {
chunk = this.#pendingHighSurrogate + chunk;
}
const lastCodeUnit = StringPrototypeCharCodeAt(
chunk,
chunk.length - 1,
);
if (0xD800 <= lastCodeUnit && lastCodeUnit <= 0xDBFF) {
this.#pendingHighSurrogate = StringPrototypeSlice(chunk, -1);
chunk = StringPrototypeSlice(chunk, 0, -1);
} else {
this.#pendingHighSurrogate = null;
}
if (chunk) {
controller.enqueue(core.encode(chunk));
}
return PromiseResolve();
} catch (err) {
return PromiseReject(err);
}
},
flush: (controller) => {
try {
if (this.#pendingHighSurrogate !== null) {
controller.enqueue(new Uint8Array([0xEF, 0xBF, 0xBD]));
}
return PromiseResolve();
} catch (err) {
return PromiseReject(err);
}
},
});
this[webidl.brand] = webidl.brand;
}
/** @returns {string} */
get encoding() {
webidl.assertBranded(this, TextEncoderStreamPrototype);
return "utf-8";
}
/** @returns {ReadableStream<Uint8Array>} */
get readable() {
webidl.assertBranded(this, TextEncoderStreamPrototype);
return this.#transform.readable;
}
/** @returns {WritableStream<string>} */
get writable() {
webidl.assertBranded(this, TextEncoderStreamPrototype);
return this.#transform.writable;
}
} }
webidl.configurePrototype(TextEncoderStream); /** @returns {string} */
const TextEncoderStreamPrototype = TextEncoderStream.prototype; get encoding() {
webidl.assertBranded(this, TextEncoderPrototype);
webidl.converters.TextDecoderOptions = webidl.createDictionaryConverter( return "utf-8";
"TextDecoderOptions",
[
{
key: "fatal",
converter: webidl.converters.boolean,
defaultValue: false,
},
{
key: "ignoreBOM",
converter: webidl.converters.boolean,
defaultValue: false,
},
],
);
webidl.converters.TextDecodeOptions = webidl.createDictionaryConverter(
"TextDecodeOptions",
[
{
key: "stream",
converter: webidl.converters.boolean,
defaultValue: false,
},
],
);
/**
* @param {Uint8Array} bytes
*/
function decode(bytes, encoding) {
const BOMEncoding = BOMSniff(bytes);
if (BOMEncoding !== null) {
encoding = BOMEncoding;
const start = BOMEncoding === "UTF-8" ? 3 : 2;
bytes = TypedArrayPrototypeSubarray(bytes, start);
}
return new TextDecoder(encoding).decode(bytes);
} }
/** /**
* @param {Uint8Array} bytes * @param {string} input
* @returns {Uint8Array}
*/ */
function BOMSniff(bytes) { encode(input = "") {
if (bytes[0] === 0xEF && bytes[1] === 0xBB && bytes[2] === 0xBF) { webidl.assertBranded(this, TextEncoderPrototype);
return "UTF-8"; const prefix = "Failed to execute 'encode' on 'TextEncoder'";
} // The WebIDL type of `input` is `USVString`, but `core.encode` already
if (bytes[0] === 0xFE && bytes[1] === 0xFF) return "UTF-16BE"; // converts lone surrogates to the replacement character.
if (bytes[0] === 0xFF && bytes[1] === 0xFE) return "UTF-16LE"; input = webidl.converters.DOMString(input, {
return null; prefix,
context: "Argument 1",
});
return core.encode(input);
} }
window.__bootstrap.encoding = { /**
TextEncoder, * @param {string} source
TextDecoder, * @param {Uint8Array} destination
TextEncoderStream, * @returns {TextEncoderEncodeIntoResult}
TextDecoderStream, */
decode, encodeInto(source, destination) {
}; webidl.assertBranded(this, TextEncoderPrototype);
})(this); const prefix = "Failed to execute 'encodeInto' on 'TextEncoder'";
// The WebIDL type of `source` is `USVString`, but the ops bindings
// already convert lone surrogates to the replacement character.
source = webidl.converters.DOMString(source, {
prefix,
context: "Argument 1",
});
destination = webidl.converters.Uint8Array(destination, {
prefix,
context: "Argument 2",
allowShared: true,
});
ops.op_encoding_encode_into(source, destination, encodeIntoBuf);
return {
read: encodeIntoBuf[0],
written: encodeIntoBuf[1],
};
}
}
const encodeIntoBuf = new Uint32Array(2);
webidl.configurePrototype(TextEncoder);
const TextEncoderPrototype = TextEncoder.prototype;
class TextDecoderStream {
/** @type {TextDecoder} */
#decoder;
/** @type {TransformStream<BufferSource, string>} */
#transform;
/**
* @param {string} label
* @param {TextDecoderOptions} options
*/
constructor(label = "utf-8", options = {}) {
const prefix = "Failed to construct 'TextDecoderStream'";
label = webidl.converters.DOMString(label, {
prefix,
context: "Argument 1",
});
options = webidl.converters.TextDecoderOptions(options, {
prefix,
context: "Argument 2",
});
this.#decoder = new TextDecoder(label, options);
this.#transform = new TransformStream({
// The transform and flush functions need access to TextDecoderStream's
// `this`, so they are defined as functions rather than methods.
transform: (chunk, controller) => {
try {
chunk = webidl.converters.BufferSource(chunk, {
allowShared: true,
});
const decoded = this.#decoder.decode(chunk, { stream: true });
if (decoded) {
controller.enqueue(decoded);
}
return PromiseResolve();
} catch (err) {
return PromiseReject(err);
}
},
flush: (controller) => {
try {
const final = this.#decoder.decode();
if (final) {
controller.enqueue(final);
}
return PromiseResolve();
} catch (err) {
return PromiseReject(err);
}
},
});
this[webidl.brand] = webidl.brand;
}
/** @returns {string} */
get encoding() {
webidl.assertBranded(this, TextDecoderStreamPrototype);
return this.#decoder.encoding;
}
/** @returns {boolean} */
get fatal() {
webidl.assertBranded(this, TextDecoderStreamPrototype);
return this.#decoder.fatal;
}
/** @returns {boolean} */
get ignoreBOM() {
webidl.assertBranded(this, TextDecoderStreamPrototype);
return this.#decoder.ignoreBOM;
}
/** @returns {ReadableStream<string>} */
get readable() {
webidl.assertBranded(this, TextDecoderStreamPrototype);
return this.#transform.readable;
}
/** @returns {WritableStream<BufferSource>} */
get writable() {
webidl.assertBranded(this, TextDecoderStreamPrototype);
return this.#transform.writable;
}
}
webidl.configurePrototype(TextDecoderStream);
const TextDecoderStreamPrototype = TextDecoderStream.prototype;
class TextEncoderStream {
/** @type {string | null} */
#pendingHighSurrogate = null;
/** @type {TransformStream<string, Uint8Array>} */
#transform;
constructor() {
this.#transform = new TransformStream({
// The transform and flush functions need access to TextEncoderStream's
// `this`, so they are defined as functions rather than methods.
transform: (chunk, controller) => {
try {
chunk = webidl.converters.DOMString(chunk);
if (chunk === "") {
return PromiseResolve();
}
if (this.#pendingHighSurrogate !== null) {
chunk = this.#pendingHighSurrogate + chunk;
}
const lastCodeUnit = StringPrototypeCharCodeAt(
chunk,
chunk.length - 1,
);
if (0xD800 <= lastCodeUnit && lastCodeUnit <= 0xDBFF) {
this.#pendingHighSurrogate = StringPrototypeSlice(chunk, -1);
chunk = StringPrototypeSlice(chunk, 0, -1);
} else {
this.#pendingHighSurrogate = null;
}
if (chunk) {
controller.enqueue(core.encode(chunk));
}
return PromiseResolve();
} catch (err) {
return PromiseReject(err);
}
},
flush: (controller) => {
try {
if (this.#pendingHighSurrogate !== null) {
controller.enqueue(new Uint8Array([0xEF, 0xBF, 0xBD]));
}
return PromiseResolve();
} catch (err) {
return PromiseReject(err);
}
},
});
this[webidl.brand] = webidl.brand;
}
/** @returns {string} */
get encoding() {
webidl.assertBranded(this, TextEncoderStreamPrototype);
return "utf-8";
}
/** @returns {ReadableStream<Uint8Array>} */
get readable() {
webidl.assertBranded(this, TextEncoderStreamPrototype);
return this.#transform.readable;
}
/** @returns {WritableStream<string>} */
get writable() {
webidl.assertBranded(this, TextEncoderStreamPrototype);
return this.#transform.writable;
}
}
webidl.configurePrototype(TextEncoderStream);
const TextEncoderStreamPrototype = TextEncoderStream.prototype;
webidl.converters.TextDecoderOptions = webidl.createDictionaryConverter(
"TextDecoderOptions",
[
{
key: "fatal",
converter: webidl.converters.boolean,
defaultValue: false,
},
{
key: "ignoreBOM",
converter: webidl.converters.boolean,
defaultValue: false,
},
],
);
webidl.converters.TextDecodeOptions = webidl.createDictionaryConverter(
"TextDecodeOptions",
[
{
key: "stream",
converter: webidl.converters.boolean,
defaultValue: false,
},
],
);
/**
* @param {Uint8Array} bytes
*/
function decode(bytes, encoding) {
const BOMEncoding = BOMSniff(bytes);
if (BOMEncoding !== null) {
encoding = BOMEncoding;
const start = BOMEncoding === "UTF-8" ? 3 : 2;
bytes = TypedArrayPrototypeSubarray(bytes, start);
}
return new TextDecoder(encoding).decode(bytes);
}
/**
* @param {Uint8Array} bytes
*/
function BOMSniff(bytes) {
if (bytes[0] === 0xEF && bytes[1] === 0xBB && bytes[2] === 0xBF) {
return "UTF-8";
}
if (bytes[0] === 0xFE && bytes[1] === 0xFF) return "UTF-16BE";
if (bytes[0] === 0xFF && bytes[1] === 0xFE) return "UTF-16LE";
return null;
}
export {
decode,
TextDecoder,
TextDecoderStream,
TextEncoder,
TextEncoderStream,
};

File diff suppressed because it is too large Load diff

View file

@ -10,487 +10,482 @@
/// <reference path="./internal.d.ts" /> /// <reference path="./internal.d.ts" />
/// <reference lib="esnext" /> /// <reference lib="esnext" />
"use strict"; const core = globalThis.Deno.core;
const ops = core.ops;
import * as webidl from "internal:ext/webidl/00_webidl.js";
const primordials = globalThis.__bootstrap.primordials;
import { forgivingBase64Encode } from "internal:ext/web/00_infra.js";
import { EventTarget, ProgressEvent } from "internal:ext/web/02_event.js";
import { decode, TextDecoder } from "internal:ext/web/08_text_encoding.js";
import { parseMimeType } from "internal:ext/web/01_mimesniff.js";
import DOMException from "internal:ext/web/01_dom_exception.js";
const {
ArrayPrototypePush,
ArrayPrototypeReduce,
FunctionPrototypeCall,
Map,
MapPrototypeGet,
MapPrototypeSet,
ObjectDefineProperty,
ObjectPrototypeIsPrototypeOf,
queueMicrotask,
SafeArrayIterator,
Symbol,
TypedArrayPrototypeSet,
TypeError,
Uint8Array,
Uint8ArrayPrototype,
} = primordials;
((window) => { const state = Symbol("[[state]]");
const core = window.Deno.core; const result = Symbol("[[result]]");
const webidl = window.__bootstrap.webidl; const error = Symbol("[[error]]");
const { forgivingBase64Encode } = window.__bootstrap.infra; const aborted = Symbol("[[aborted]]");
const { ProgressEvent } = window.__bootstrap.event; const handlerSymbol = Symbol("eventHandlers");
const { EventTarget } = window.__bootstrap.eventTarget;
const { decode, TextDecoder } = window.__bootstrap.encoding;
const { parseMimeType } = window.__bootstrap.mimesniff;
const { DOMException } = window.__bootstrap.domException;
const {
ArrayPrototypePush,
ArrayPrototypeReduce,
FunctionPrototypeCall,
Map,
MapPrototypeGet,
MapPrototypeSet,
ObjectDefineProperty,
ObjectPrototypeIsPrototypeOf,
queueMicrotask,
SafeArrayIterator,
Symbol,
TypedArrayPrototypeSet,
TypeError,
Uint8Array,
Uint8ArrayPrototype,
} = window.__bootstrap.primordials;
const state = Symbol("[[state]]"); class FileReader extends EventTarget {
const result = Symbol("[[result]]"); /** @type {"empty" | "loading" | "done"} */
const error = Symbol("[[error]]"); [state] = "empty";
const aborted = Symbol("[[aborted]]"); /** @type {null | string | ArrayBuffer} */
const handlerSymbol = Symbol("eventHandlers"); [result] = null;
/** @type {null | DOMException} */
[error] = null;
/** @type {null | {aborted: boolean}} */
[aborted] = null;
class FileReader extends EventTarget { /**
/** @type {"empty" | "loading" | "done"} */ * @param {Blob} blob
[state] = "empty"; * @param {{kind: "ArrayBuffer" | "Text" | "DataUrl" | "BinaryString", encoding?: string}} readtype
/** @type {null | string | ArrayBuffer} */ */
[result] = null; #readOperation(blob, readtype) {
/** @type {null | DOMException} */ // 1. If frs state is "loading", throw an InvalidStateError DOMException.
[error] = null; if (this[state] === "loading") {
/** @type {null | {aborted: boolean}} */ throw new DOMException(
[aborted] = null; "Invalid FileReader state.",
"InvalidStateError",
);
}
// 2. Set frs state to "loading".
this[state] = "loading";
// 3. Set frs result to null.
this[result] = null;
// 4. Set frs error to null.
this[error] = null;
/** // We set this[aborted] to a new object, and keep track of it in a
* @param {Blob} blob // separate variable, so if a new read operation starts while there are
* @param {{kind: "ArrayBuffer" | "Text" | "DataUrl" | "BinaryString", encoding?: string}} readtype // remaining tasks from a previous aborted operation, the new operation
*/ // will run while the tasks from the previous one are still aborted.
#readOperation(blob, readtype) { const abortedState = this[aborted] = { aborted: false };
// 1. If frs state is "loading", throw an InvalidStateError DOMException.
if (this[state] === "loading") {
throw new DOMException(
"Invalid FileReader state.",
"InvalidStateError",
);
}
// 2. Set frs state to "loading".
this[state] = "loading";
// 3. Set frs result to null.
this[result] = null;
// 4. Set frs error to null.
this[error] = null;
// We set this[aborted] to a new object, and keep track of it in a // 5. Let stream be the result of calling get stream on blob.
// separate variable, so if a new read operation starts while there are const stream /*: ReadableStream<ArrayBufferView>*/ = blob.stream();
// remaining tasks from a previous aborted operation, the new operation
// will run while the tasks from the previous one are still aborted.
const abortedState = this[aborted] = { aborted: false };
// 5. Let stream be the result of calling get stream on blob. // 6. Let reader be the result of getting a reader from stream.
const stream /*: ReadableStream<ArrayBufferView>*/ = blob.stream(); const reader = stream.getReader();
// 6. Let reader be the result of getting a reader from stream. // 7. Let bytes be an empty byte sequence.
const reader = stream.getReader(); /** @type {Uint8Array[]} */
const chunks = [];
// 7. Let bytes be an empty byte sequence. // 8. Let chunkPromise be the result of reading a chunk from stream with reader.
/** @type {Uint8Array[]} */ let chunkPromise = reader.read();
const chunks = [];
// 8. Let chunkPromise be the result of reading a chunk from stream with reader. // 9. Let isFirstChunk be true.
let chunkPromise = reader.read(); let isFirstChunk = true;
// 9. Let isFirstChunk be true. // 10 in parallel while true
let isFirstChunk = true; (async () => {
while (!abortedState.aborted) {
// 1. Wait for chunkPromise to be fulfilled or rejected.
try {
const chunk = await chunkPromise;
if (abortedState.aborted) return;
// 10 in parallel while true // 2. If chunkPromise is fulfilled, and isFirstChunk is true, queue a task to fire a progress event called loadstart at fr.
(async () => { if (isFirstChunk) {
while (!abortedState.aborted) {
// 1. Wait for chunkPromise to be fulfilled or rejected.
try {
const chunk = await chunkPromise;
if (abortedState.aborted) return;
// 2. If chunkPromise is fulfilled, and isFirstChunk is true, queue a task to fire a progress event called loadstart at fr.
if (isFirstChunk) {
// TODO(lucacasonato): this is wrong, should be HTML "queue a task"
queueMicrotask(() => {
if (abortedState.aborted) return;
// fire a progress event for loadstart
const ev = new ProgressEvent("loadstart", {});
this.dispatchEvent(ev);
});
}
// 3. Set isFirstChunk to false.
isFirstChunk = false;
// 4. If chunkPromise is fulfilled with an object whose done property is false
// and whose value property is a Uint8Array object, run these steps:
if (
!chunk.done &&
ObjectPrototypeIsPrototypeOf(Uint8ArrayPrototype, chunk.value)
) {
ArrayPrototypePush(chunks, chunk.value);
// TODO(bartlomieju): (only) If roughly 50ms have passed since last progress
{
const size = ArrayPrototypeReduce(
chunks,
(p, i) => p + i.byteLength,
0,
);
const ev = new ProgressEvent("progress", {
loaded: size,
});
// TODO(lucacasonato): this is wrong, should be HTML "queue a task"
queueMicrotask(() => {
if (abortedState.aborted) return;
this.dispatchEvent(ev);
});
}
chunkPromise = reader.read();
} // 5 Otherwise, if chunkPromise is fulfilled with an object whose done property is true, queue a task to run the following steps and abort this algorithm:
else if (chunk.done === true) {
// TODO(lucacasonato): this is wrong, should be HTML "queue a task"
queueMicrotask(() => {
if (abortedState.aborted) return;
// 1. Set frs state to "done".
this[state] = "done";
// 2. Let result be the result of package data given bytes, type, blobs type, and encodingName.
const size = ArrayPrototypeReduce(
chunks,
(p, i) => p + i.byteLength,
0,
);
const bytes = new Uint8Array(size);
let offs = 0;
for (let i = 0; i < chunks.length; ++i) {
const chunk = chunks[i];
TypedArrayPrototypeSet(bytes, chunk, offs);
offs += chunk.byteLength;
}
switch (readtype.kind) {
case "ArrayBuffer": {
this[result] = bytes.buffer;
break;
}
case "BinaryString":
this[result] = core.ops.op_encode_binary_string(bytes);
break;
case "Text": {
let decoder = undefined;
if (readtype.encoding) {
try {
decoder = new TextDecoder(readtype.encoding);
} catch {
// don't care about the error
}
}
if (decoder === undefined) {
const mimeType = parseMimeType(blob.type);
if (mimeType) {
const charset = MapPrototypeGet(
mimeType.parameters,
"charset",
);
if (charset) {
try {
decoder = new TextDecoder(charset);
} catch {
// don't care about the error
}
}
}
}
if (decoder === undefined) {
decoder = new TextDecoder();
}
this[result] = decode(bytes, decoder.encoding);
break;
}
case "DataUrl": {
const mediaType = blob.type || "application/octet-stream";
this[result] = `data:${mediaType};base64,${
forgivingBase64Encode(bytes)
}`;
break;
}
}
// 4.2 Fire a progress event called load at the fr.
{
const ev = new ProgressEvent("load", {
lengthComputable: true,
loaded: size,
total: size,
});
this.dispatchEvent(ev);
}
// 5. If frs state is not "loading", fire a progress event called loadend at the fr.
//Note: Event handler for the load or error events could have started another load, if that happens the loadend event for this load is not fired.
if (this[state] !== "loading") {
const ev = new ProgressEvent("loadend", {
lengthComputable: true,
loaded: size,
total: size,
});
this.dispatchEvent(ev);
}
});
break;
}
} catch (err) {
// TODO(lucacasonato): this is wrong, should be HTML "queue a task" // TODO(lucacasonato): this is wrong, should be HTML "queue a task"
queueMicrotask(() => { queueMicrotask(() => {
if (abortedState.aborted) return; if (abortedState.aborted) return;
// fire a progress event for loadstart
const ev = new ProgressEvent("loadstart", {});
this.dispatchEvent(ev);
});
}
// 3. Set isFirstChunk to false.
isFirstChunk = false;
// chunkPromise rejected // 4. If chunkPromise is fulfilled with an object whose done property is false
// and whose value property is a Uint8Array object, run these steps:
if (
!chunk.done &&
ObjectPrototypeIsPrototypeOf(Uint8ArrayPrototype, chunk.value)
) {
ArrayPrototypePush(chunks, chunk.value);
// TODO(bartlomieju): (only) If roughly 50ms have passed since last progress
{
const size = ArrayPrototypeReduce(
chunks,
(p, i) => p + i.byteLength,
0,
);
const ev = new ProgressEvent("progress", {
loaded: size,
});
// TODO(lucacasonato): this is wrong, should be HTML "queue a task"
queueMicrotask(() => {
if (abortedState.aborted) return;
this.dispatchEvent(ev);
});
}
chunkPromise = reader.read();
} // 5 Otherwise, if chunkPromise is fulfilled with an object whose done property is true, queue a task to run the following steps and abort this algorithm:
else if (chunk.done === true) {
// TODO(lucacasonato): this is wrong, should be HTML "queue a task"
queueMicrotask(() => {
if (abortedState.aborted) return;
// 1. Set frs state to "done".
this[state] = "done"; this[state] = "done";
this[error] = err; // 2. Let result be the result of package data given bytes, type, blobs type, and encodingName.
const size = ArrayPrototypeReduce(
chunks,
(p, i) => p + i.byteLength,
0,
);
const bytes = new Uint8Array(size);
let offs = 0;
for (let i = 0; i < chunks.length; ++i) {
const chunk = chunks[i];
TypedArrayPrototypeSet(bytes, chunk, offs);
offs += chunk.byteLength;
}
switch (readtype.kind) {
case "ArrayBuffer": {
this[result] = bytes.buffer;
break;
}
case "BinaryString":
this[result] = ops.op_encode_binary_string(bytes);
break;
case "Text": {
let decoder = undefined;
if (readtype.encoding) {
try {
decoder = new TextDecoder(readtype.encoding);
} catch {
// don't care about the error
}
}
if (decoder === undefined) {
const mimeType = parseMimeType(blob.type);
if (mimeType) {
const charset = MapPrototypeGet(
mimeType.parameters,
"charset",
);
if (charset) {
try {
decoder = new TextDecoder(charset);
} catch {
// don't care about the error
}
}
}
}
if (decoder === undefined) {
decoder = new TextDecoder();
}
this[result] = decode(bytes, decoder.encoding);
break;
}
case "DataUrl": {
const mediaType = blob.type || "application/octet-stream";
this[result] = `data:${mediaType};base64,${
forgivingBase64Encode(bytes)
}`;
break;
}
}
// 4.2 Fire a progress event called load at the fr.
{ {
const ev = new ProgressEvent("error", {}); const ev = new ProgressEvent("load", {
lengthComputable: true,
loaded: size,
total: size,
});
this.dispatchEvent(ev); this.dispatchEvent(ev);
} }
//If frs state is not "loading", fire a progress event called loadend at fr. // 5. If frs state is not "loading", fire a progress event called loadend at the fr.
//Note: Event handler for the error event could have started another load, if that happens the loadend event for this load is not fired. //Note: Event handler for the load or error events could have started another load, if that happens the loadend event for this load is not fired.
if (this[state] !== "loading") { if (this[state] !== "loading") {
const ev = new ProgressEvent("loadend", {}); const ev = new ProgressEvent("loadend", {
lengthComputable: true,
loaded: size,
total: size,
});
this.dispatchEvent(ev); this.dispatchEvent(ev);
} }
}); });
break; break;
} }
} catch (err) {
// TODO(lucacasonato): this is wrong, should be HTML "queue a task"
queueMicrotask(() => {
if (abortedState.aborted) return;
// chunkPromise rejected
this[state] = "done";
this[error] = err;
{
const ev = new ProgressEvent("error", {});
this.dispatchEvent(ev);
}
//If frs state is not "loading", fire a progress event called loadend at fr.
//Note: Event handler for the error event could have started another load, if that happens the loadend event for this load is not fired.
if (this[state] !== "loading") {
const ev = new ProgressEvent("loadend", {});
this.dispatchEvent(ev);
}
});
break;
} }
})();
}
#getEventHandlerFor(name) {
webidl.assertBranded(this, FileReaderPrototype);
const maybeMap = this[handlerSymbol];
if (!maybeMap) return null;
return MapPrototypeGet(maybeMap, name)?.handler ?? null;
}
#setEventHandlerFor(name, value) {
webidl.assertBranded(this, FileReaderPrototype);
if (!this[handlerSymbol]) {
this[handlerSymbol] = new Map();
}
let handlerWrapper = MapPrototypeGet(this[handlerSymbol], name);
if (handlerWrapper) {
handlerWrapper.handler = value;
} else {
handlerWrapper = makeWrappedHandler(value);
this.addEventListener(name, handlerWrapper);
} }
})();
}
MapPrototypeSet(this[handlerSymbol], name, handlerWrapper); #getEventHandlerFor(name) {
webidl.assertBranded(this, FileReaderPrototype);
const maybeMap = this[handlerSymbol];
if (!maybeMap) return null;
return MapPrototypeGet(maybeMap, name)?.handler ?? null;
}
#setEventHandlerFor(name, value) {
webidl.assertBranded(this, FileReaderPrototype);
if (!this[handlerSymbol]) {
this[handlerSymbol] = new Map();
}
let handlerWrapper = MapPrototypeGet(this[handlerSymbol], name);
if (handlerWrapper) {
handlerWrapper.handler = value;
} else {
handlerWrapper = makeWrappedHandler(value);
this.addEventListener(name, handlerWrapper);
} }
constructor() { MapPrototypeSet(this[handlerSymbol], name, handlerWrapper);
super(); }
this[webidl.brand] = webidl.brand;
constructor() {
super();
this[webidl.brand] = webidl.brand;
}
/** @returns {number} */
get readyState() {
webidl.assertBranded(this, FileReaderPrototype);
switch (this[state]) {
case "empty":
return FileReader.EMPTY;
case "loading":
return FileReader.LOADING;
case "done":
return FileReader.DONE;
default:
throw new TypeError("Invalid state");
}
}
get result() {
webidl.assertBranded(this, FileReaderPrototype);
return this[result];
}
get error() {
webidl.assertBranded(this, FileReaderPrototype);
return this[error];
}
abort() {
webidl.assertBranded(this, FileReaderPrototype);
// If context object's state is "empty" or if context object's state is "done" set context object's result to null and terminate this algorithm.
if (
this[state] === "empty" ||
this[state] === "done"
) {
this[result] = null;
return;
}
// If context object's state is "loading" set context object's state to "done" and set context object's result to null.
if (this[state] === "loading") {
this[state] = "done";
this[result] = null;
}
// If there are any tasks from the context object on the file reading task source in an affiliated task queue, then remove those tasks from that task queue.
// Terminate the algorithm for the read method being processed.
if (this[aborted] !== null) {
this[aborted].aborted = true;
} }
/** @returns {number} */ // Fire a progress event called abort at the context object.
get readyState() { const ev = new ProgressEvent("abort", {});
webidl.assertBranded(this, FileReaderPrototype); this.dispatchEvent(ev);
switch (this[state]) {
case "empty":
return FileReader.EMPTY;
case "loading":
return FileReader.LOADING;
case "done":
return FileReader.DONE;
default:
throw new TypeError("Invalid state");
}
}
get result() { // If context object's state is not "loading", fire a progress event called loadend at the context object.
webidl.assertBranded(this, FileReaderPrototype); if (this[state] !== "loading") {
return this[result]; const ev = new ProgressEvent("loadend", {});
}
get error() {
webidl.assertBranded(this, FileReaderPrototype);
return this[error];
}
abort() {
webidl.assertBranded(this, FileReaderPrototype);
// If context object's state is "empty" or if context object's state is "done" set context object's result to null and terminate this algorithm.
if (
this[state] === "empty" ||
this[state] === "done"
) {
this[result] = null;
return;
}
// If context object's state is "loading" set context object's state to "done" and set context object's result to null.
if (this[state] === "loading") {
this[state] = "done";
this[result] = null;
}
// If there are any tasks from the context object on the file reading task source in an affiliated task queue, then remove those tasks from that task queue.
// Terminate the algorithm for the read method being processed.
if (this[aborted] !== null) {
this[aborted].aborted = true;
}
// Fire a progress event called abort at the context object.
const ev = new ProgressEvent("abort", {});
this.dispatchEvent(ev); this.dispatchEvent(ev);
// If context object's state is not "loading", fire a progress event called loadend at the context object.
if (this[state] !== "loading") {
const ev = new ProgressEvent("loadend", {});
this.dispatchEvent(ev);
}
}
/** @param {Blob} blob */
readAsArrayBuffer(blob) {
webidl.assertBranded(this, FileReaderPrototype);
const prefix = "Failed to execute 'readAsArrayBuffer' on 'FileReader'";
webidl.requiredArguments(arguments.length, 1, { prefix });
this.#readOperation(blob, { kind: "ArrayBuffer" });
}
/** @param {Blob} blob */
readAsBinaryString(blob) {
webidl.assertBranded(this, FileReaderPrototype);
const prefix = "Failed to execute 'readAsBinaryString' on 'FileReader'";
webidl.requiredArguments(arguments.length, 1, { prefix });
// alias for readAsArrayBuffer
this.#readOperation(blob, { kind: "BinaryString" });
}
/** @param {Blob} blob */
readAsDataURL(blob) {
webidl.assertBranded(this, FileReaderPrototype);
const prefix = "Failed to execute 'readAsDataURL' on 'FileReader'";
webidl.requiredArguments(arguments.length, 1, { prefix });
// alias for readAsArrayBuffer
this.#readOperation(blob, { kind: "DataUrl" });
}
/**
* @param {Blob} blob
* @param {string} [encoding]
*/
readAsText(blob, encoding = undefined) {
webidl.assertBranded(this, FileReaderPrototype);
const prefix = "Failed to execute 'readAsText' on 'FileReader'";
webidl.requiredArguments(arguments.length, 1, { prefix });
if (encoding !== undefined) {
encoding = webidl.converters["DOMString"](encoding, {
prefix,
context: "Argument 2",
});
}
// alias for readAsArrayBuffer
this.#readOperation(blob, { kind: "Text", encoding });
}
get onerror() {
return this.#getEventHandlerFor("error");
}
set onerror(value) {
this.#setEventHandlerFor("error", value);
}
get onloadstart() {
return this.#getEventHandlerFor("loadstart");
}
set onloadstart(value) {
this.#setEventHandlerFor("loadstart", value);
}
get onload() {
return this.#getEventHandlerFor("load");
}
set onload(value) {
this.#setEventHandlerFor("load", value);
}
get onloadend() {
return this.#getEventHandlerFor("loadend");
}
set onloadend(value) {
this.#setEventHandlerFor("loadend", value);
}
get onprogress() {
return this.#getEventHandlerFor("progress");
}
set onprogress(value) {
this.#setEventHandlerFor("progress", value);
}
get onabort() {
return this.#getEventHandlerFor("abort");
}
set onabort(value) {
this.#setEventHandlerFor("abort", value);
} }
} }
webidl.configurePrototype(FileReader); /** @param {Blob} blob */
const FileReaderPrototype = FileReader.prototype; readAsArrayBuffer(blob) {
webidl.assertBranded(this, FileReaderPrototype);
ObjectDefineProperty(FileReader, "EMPTY", { const prefix = "Failed to execute 'readAsArrayBuffer' on 'FileReader'";
writable: false, webidl.requiredArguments(arguments.length, 1, { prefix });
enumerable: true, this.#readOperation(blob, { kind: "ArrayBuffer" });
configurable: false,
value: 0,
});
ObjectDefineProperty(FileReader, "LOADING", {
writable: false,
enumerable: true,
configurable: false,
value: 1,
});
ObjectDefineProperty(FileReader, "DONE", {
writable: false,
enumerable: true,
configurable: false,
value: 2,
});
ObjectDefineProperty(FileReader.prototype, "EMPTY", {
writable: false,
enumerable: true,
configurable: false,
value: 0,
});
ObjectDefineProperty(FileReader.prototype, "LOADING", {
writable: false,
enumerable: true,
configurable: false,
value: 1,
});
ObjectDefineProperty(FileReader.prototype, "DONE", {
writable: false,
enumerable: true,
configurable: false,
value: 2,
});
function makeWrappedHandler(handler) {
function wrappedHandler(...args) {
if (typeof wrappedHandler.handler !== "function") {
return;
}
return FunctionPrototypeCall(
wrappedHandler.handler,
this,
...new SafeArrayIterator(args),
);
}
wrappedHandler.handler = handler;
return wrappedHandler;
} }
window.__bootstrap.fileReader = { /** @param {Blob} blob */
FileReader, readAsBinaryString(blob) {
}; webidl.assertBranded(this, FileReaderPrototype);
})(this); const prefix = "Failed to execute 'readAsBinaryString' on 'FileReader'";
webidl.requiredArguments(arguments.length, 1, { prefix });
// alias for readAsArrayBuffer
this.#readOperation(blob, { kind: "BinaryString" });
}
/** @param {Blob} blob */
readAsDataURL(blob) {
webidl.assertBranded(this, FileReaderPrototype);
const prefix = "Failed to execute 'readAsDataURL' on 'FileReader'";
webidl.requiredArguments(arguments.length, 1, { prefix });
// alias for readAsArrayBuffer
this.#readOperation(blob, { kind: "DataUrl" });
}
/**
* @param {Blob} blob
* @param {string} [encoding]
*/
readAsText(blob, encoding = undefined) {
webidl.assertBranded(this, FileReaderPrototype);
const prefix = "Failed to execute 'readAsText' on 'FileReader'";
webidl.requiredArguments(arguments.length, 1, { prefix });
if (encoding !== undefined) {
encoding = webidl.converters["DOMString"](encoding, {
prefix,
context: "Argument 2",
});
}
// alias for readAsArrayBuffer
this.#readOperation(blob, { kind: "Text", encoding });
}
get onerror() {
return this.#getEventHandlerFor("error");
}
set onerror(value) {
this.#setEventHandlerFor("error", value);
}
get onloadstart() {
return this.#getEventHandlerFor("loadstart");
}
set onloadstart(value) {
this.#setEventHandlerFor("loadstart", value);
}
get onload() {
return this.#getEventHandlerFor("load");
}
set onload(value) {
this.#setEventHandlerFor("load", value);
}
get onloadend() {
return this.#getEventHandlerFor("loadend");
}
set onloadend(value) {
this.#setEventHandlerFor("loadend", value);
}
get onprogress() {
return this.#getEventHandlerFor("progress");
}
set onprogress(value) {
this.#setEventHandlerFor("progress", value);
}
get onabort() {
return this.#getEventHandlerFor("abort");
}
set onabort(value) {
this.#setEventHandlerFor("abort", value);
}
}
webidl.configurePrototype(FileReader);
const FileReaderPrototype = FileReader.prototype;
ObjectDefineProperty(FileReader, "EMPTY", {
writable: false,
enumerable: true,
configurable: false,
value: 0,
});
ObjectDefineProperty(FileReader, "LOADING", {
writable: false,
enumerable: true,
configurable: false,
value: 1,
});
ObjectDefineProperty(FileReader, "DONE", {
writable: false,
enumerable: true,
configurable: false,
value: 2,
});
ObjectDefineProperty(FileReader.prototype, "EMPTY", {
writable: false,
enumerable: true,
configurable: false,
value: 0,
});
ObjectDefineProperty(FileReader.prototype, "LOADING", {
writable: false,
enumerable: true,
configurable: false,
value: 1,
});
ObjectDefineProperty(FileReader.prototype, "DONE", {
writable: false,
enumerable: true,
configurable: false,
value: 2,
});
function makeWrappedHandler(handler) {
function wrappedHandler(...args) {
if (typeof wrappedHandler.handler !== "function") {
return;
}
return FunctionPrototypeCall(
wrappedHandler.handler,
this,
...new SafeArrayIterator(args),
);
}
wrappedHandler.handler = handler;
return wrappedHandler;
}
export { FileReader };

View file

@ -10,50 +10,42 @@
/// <reference path="../url/lib.deno_url.d.ts" /> /// <reference path="../url/lib.deno_url.d.ts" />
/// <reference path="./internal.d.ts" /> /// <reference path="./internal.d.ts" />
/// <reference lib="esnext" /> /// <reference lib="esnext" />
"use strict";
((window) => { const core = globalThis.Deno.core;
const core = Deno.core; const ops = core.ops;
const ops = core.ops; import * as webidl from "internal:ext/webidl/00_webidl.js";
const webidl = window.__bootstrap.webidl; import { getParts } from "internal:ext/web/09_file.js";
const { getParts } = window.__bootstrap.file; import { URL } from "internal:ext/url/00_url.js";
const { URL } = window.__bootstrap.url;
/** /**
* @param {Blob} blob * @param {Blob} blob
* @returns {string} * @returns {string}
*/ */
function createObjectURL(blob) { function createObjectURL(blob) {
const prefix = "Failed to execute 'createObjectURL' on 'URL'"; const prefix = "Failed to execute 'createObjectURL' on 'URL'";
webidl.requiredArguments(arguments.length, 1, { prefix }); webidl.requiredArguments(arguments.length, 1, { prefix });
blob = webidl.converters["Blob"](blob, { blob = webidl.converters["Blob"](blob, {
context: "Argument 1", context: "Argument 1",
prefix, prefix,
}); });
const url = ops.op_blob_create_object_url( return ops.op_blob_create_object_url(blob.type, getParts(blob));
blob.type, }
getParts(blob),
);
return url; /**
} * @param {string} url
* @returns {void}
*/
function revokeObjectURL(url) {
const prefix = "Failed to execute 'revokeObjectURL' on 'URL'";
webidl.requiredArguments(arguments.length, 1, { prefix });
url = webidl.converters["DOMString"](url, {
context: "Argument 1",
prefix,
});
/** ops.op_blob_revoke_object_url(url);
* @param {string} url }
* @returns {void}
*/
function revokeObjectURL(url) {
const prefix = "Failed to execute 'revokeObjectURL' on 'URL'";
webidl.requiredArguments(arguments.length, 1, { prefix });
url = webidl.converters["DOMString"](url, {
context: "Argument 1",
prefix,
});
ops.op_blob_revoke_object_url(url); URL.createObjectURL = createObjectURL;
} URL.revokeObjectURL = revokeObjectURL;
URL.createObjectURL = createObjectURL;
URL.revokeObjectURL = revokeObjectURL;
})(globalThis);

View file

@ -1,403 +1,410 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
"use strict";
/// <reference path="../../core/internal.d.ts" /> /// <reference path="../../core/internal.d.ts" />
((window) => { import { URL } from "internal:ext/url/00_url.js";
const { URL } = window.__bootstrap.url; import DOMException from "internal:ext/web/01_dom_exception.js";
const { DOMException } = window.__bootstrap.domException; const primordials = globalThis.__bootstrap.primordials;
const { const {
Error, Error,
ObjectDefineProperties, ObjectDefineProperties,
Symbol, Symbol,
SymbolFor, SymbolFor,
SymbolToStringTag, SymbolToStringTag,
TypeError, TypeError,
WeakMap, WeakMap,
WeakMapPrototypeGet, WeakMapPrototypeGet,
WeakMapPrototypeSet, WeakMapPrototypeSet,
} = window.__bootstrap.primordials; } = primordials;
const locationConstructorKey = Symbol("locationConstuctorKey"); const locationConstructorKey = Symbol("locationConstuctorKey");
// The differences between the definitions of `Location` and `WorkerLocation` // The differences between the definitions of `Location` and `WorkerLocation`
// are because of the `LegacyUnforgeable` attribute only specified upon // are because of the `LegacyUnforgeable` attribute only specified upon
// `Location`'s properties. See: // `Location`'s properties. See:
// - https://html.spec.whatwg.org/multipage/history.html#the-location-interface // - https://html.spec.whatwg.org/multipage/history.html#the-location-interface
// - https://heycam.github.io/webidl/#LegacyUnforgeable // - https://heycam.github.io/webidl/#LegacyUnforgeable
class Location { class Location {
constructor(href = null, key = null) { constructor(href = null, key = null) {
if (key != locationConstructorKey) { if (key != locationConstructorKey) {
throw new TypeError("Illegal constructor."); throw new TypeError("Illegal constructor.");
}
const url = new URL(href);
url.username = "";
url.password = "";
ObjectDefineProperties(this, {
hash: {
get() {
return url.hash;
},
set() {
throw new DOMException(
`Cannot set "location.hash".`,
"NotSupportedError",
);
},
enumerable: true,
},
host: {
get() {
return url.host;
},
set() {
throw new DOMException(
`Cannot set "location.host".`,
"NotSupportedError",
);
},
enumerable: true,
},
hostname: {
get() {
return url.hostname;
},
set() {
throw new DOMException(
`Cannot set "location.hostname".`,
"NotSupportedError",
);
},
enumerable: true,
},
href: {
get() {
return url.href;
},
set() {
throw new DOMException(
`Cannot set "location.href".`,
"NotSupportedError",
);
},
enumerable: true,
},
origin: {
get() {
return url.origin;
},
enumerable: true,
},
pathname: {
get() {
return url.pathname;
},
set() {
throw new DOMException(
`Cannot set "location.pathname".`,
"NotSupportedError",
);
},
enumerable: true,
},
port: {
get() {
return url.port;
},
set() {
throw new DOMException(
`Cannot set "location.port".`,
"NotSupportedError",
);
},
enumerable: true,
},
protocol: {
get() {
return url.protocol;
},
set() {
throw new DOMException(
`Cannot set "location.protocol".`,
"NotSupportedError",
);
},
enumerable: true,
},
search: {
get() {
return url.search;
},
set() {
throw new DOMException(
`Cannot set "location.search".`,
"NotSupportedError",
);
},
enumerable: true,
},
ancestorOrigins: {
get() {
// TODO(nayeemrmn): Replace with a `DOMStringList` instance.
return {
length: 0,
item: () => null,
contains: () => false,
};
},
enumerable: true,
},
assign: {
value: function assign() {
throw new DOMException(
`Cannot call "location.assign()".`,
"NotSupportedError",
);
},
enumerable: true,
},
reload: {
value: function reload() {
throw new DOMException(
`Cannot call "location.reload()".`,
"NotSupportedError",
);
},
enumerable: true,
},
replace: {
value: function replace() {
throw new DOMException(
`Cannot call "location.replace()".`,
"NotSupportedError",
);
},
enumerable: true,
},
toString: {
value: function toString() {
return url.href;
},
enumerable: true,
},
[SymbolFor("Deno.privateCustomInspect")]: {
value: function (inspect) {
const object = {
hash: this.hash,
host: this.host,
hostname: this.hostname,
href: this.href,
origin: this.origin,
pathname: this.pathname,
port: this.port,
protocol: this.protocol,
search: this.search,
};
return `${this.constructor.name} ${inspect(object)}`;
},
},
});
} }
} const url = new URL(href);
url.username = "";
ObjectDefineProperties(Location.prototype, { url.password = "";
[SymbolToStringTag]: { ObjectDefineProperties(this, {
value: "Location", hash: {
configurable: true, get() {
}, return url.hash;
}); },
set() {
const workerLocationUrls = new WeakMap(); throw new DOMException(
`Cannot set "location.hash".`,
class WorkerLocation { "NotSupportedError",
constructor(href = null, key = null) {
if (key != locationConstructorKey) {
throw new TypeError("Illegal constructor.");
}
const url = new URL(href);
url.username = "";
url.password = "";
WeakMapPrototypeSet(workerLocationUrls, this, url);
}
}
ObjectDefineProperties(WorkerLocation.prototype, {
hash: {
get() {
const url = WeakMapPrototypeGet(workerLocationUrls, this);
if (url == null) {
throw new TypeError("Illegal invocation.");
}
return url.hash;
},
configurable: true,
enumerable: true,
},
host: {
get() {
const url = WeakMapPrototypeGet(workerLocationUrls, this);
if (url == null) {
throw new TypeError("Illegal invocation.");
}
return url.host;
},
configurable: true,
enumerable: true,
},
hostname: {
get() {
const url = WeakMapPrototypeGet(workerLocationUrls, this);
if (url == null) {
throw new TypeError("Illegal invocation.");
}
return url.hostname;
},
configurable: true,
enumerable: true,
},
href: {
get() {
const url = WeakMapPrototypeGet(workerLocationUrls, this);
if (url == null) {
throw new TypeError("Illegal invocation.");
}
return url.href;
},
configurable: true,
enumerable: true,
},
origin: {
get() {
const url = WeakMapPrototypeGet(workerLocationUrls, this);
if (url == null) {
throw new TypeError("Illegal invocation.");
}
return url.origin;
},
configurable: true,
enumerable: true,
},
pathname: {
get() {
const url = WeakMapPrototypeGet(workerLocationUrls, this);
if (url == null) {
throw new TypeError("Illegal invocation.");
}
return url.pathname;
},
configurable: true,
enumerable: true,
},
port: {
get() {
const url = WeakMapPrototypeGet(workerLocationUrls, this);
if (url == null) {
throw new TypeError("Illegal invocation.");
}
return url.port;
},
configurable: true,
enumerable: true,
},
protocol: {
get() {
const url = WeakMapPrototypeGet(workerLocationUrls, this);
if (url == null) {
throw new TypeError("Illegal invocation.");
}
return url.protocol;
},
configurable: true,
enumerable: true,
},
search: {
get() {
const url = WeakMapPrototypeGet(workerLocationUrls, this);
if (url == null) {
throw new TypeError("Illegal invocation.");
}
return url.search;
},
configurable: true,
enumerable: true,
},
toString: {
value: function toString() {
const url = WeakMapPrototypeGet(workerLocationUrls, this);
if (url == null) {
throw new TypeError("Illegal invocation.");
}
return url.href;
},
configurable: true,
enumerable: true,
writable: true,
},
[SymbolToStringTag]: {
value: "WorkerLocation",
configurable: true,
},
[SymbolFor("Deno.privateCustomInspect")]: {
value: function (inspect) {
const object = {
hash: this.hash,
host: this.host,
hostname: this.hostname,
href: this.href,
origin: this.origin,
pathname: this.pathname,
port: this.port,
protocol: this.protocol,
search: this.search,
};
return `${this.constructor.name} ${inspect(object)}`;
},
},
});
let location = undefined;
let workerLocation = undefined;
function setLocationHref(href) {
location = new Location(href, locationConstructorKey);
workerLocation = new WorkerLocation(href, locationConstructorKey);
}
window.__bootstrap.location = {
locationConstructorDescriptor: {
value: Location,
configurable: true,
writable: true,
},
workerLocationConstructorDescriptor: {
value: WorkerLocation,
configurable: true,
writable: true,
},
locationDescriptor: {
get() {
return location;
},
set() {
throw new DOMException(`Cannot set "location".`, "NotSupportedError");
},
enumerable: true,
},
workerLocationDescriptor: {
get() {
if (workerLocation == null) {
throw new Error(
`Assertion: "globalThis.location" must be defined in a worker.`,
); );
} },
return workerLocation; enumerable: true,
}, },
configurable: true, host: {
enumerable: true, get() {
return url.host;
},
set() {
throw new DOMException(
`Cannot set "location.host".`,
"NotSupportedError",
);
},
enumerable: true,
},
hostname: {
get() {
return url.hostname;
},
set() {
throw new DOMException(
`Cannot set "location.hostname".`,
"NotSupportedError",
);
},
enumerable: true,
},
href: {
get() {
return url.href;
},
set() {
throw new DOMException(
`Cannot set "location.href".`,
"NotSupportedError",
);
},
enumerable: true,
},
origin: {
get() {
return url.origin;
},
enumerable: true,
},
pathname: {
get() {
return url.pathname;
},
set() {
throw new DOMException(
`Cannot set "location.pathname".`,
"NotSupportedError",
);
},
enumerable: true,
},
port: {
get() {
return url.port;
},
set() {
throw new DOMException(
`Cannot set "location.port".`,
"NotSupportedError",
);
},
enumerable: true,
},
protocol: {
get() {
return url.protocol;
},
set() {
throw new DOMException(
`Cannot set "location.protocol".`,
"NotSupportedError",
);
},
enumerable: true,
},
search: {
get() {
return url.search;
},
set() {
throw new DOMException(
`Cannot set "location.search".`,
"NotSupportedError",
);
},
enumerable: true,
},
ancestorOrigins: {
get() {
// TODO(nayeemrmn): Replace with a `DOMStringList` instance.
return {
length: 0,
item: () => null,
contains: () => false,
};
},
enumerable: true,
},
assign: {
value: function assign() {
throw new DOMException(
`Cannot call "location.assign()".`,
"NotSupportedError",
);
},
enumerable: true,
},
reload: {
value: function reload() {
throw new DOMException(
`Cannot call "location.reload()".`,
"NotSupportedError",
);
},
enumerable: true,
},
replace: {
value: function replace() {
throw new DOMException(
`Cannot call "location.replace()".`,
"NotSupportedError",
);
},
enumerable: true,
},
toString: {
value: function toString() {
return url.href;
},
enumerable: true,
},
[SymbolFor("Deno.privateCustomInspect")]: {
value: function (inspect) {
const object = {
hash: this.hash,
host: this.host,
hostname: this.hostname,
href: this.href,
origin: this.origin,
pathname: this.pathname,
port: this.port,
protocol: this.protocol,
search: this.search,
};
return `${this.constructor.name} ${inspect(object)}`;
},
},
});
}
}
ObjectDefineProperties(Location.prototype, {
[SymbolToStringTag]: {
value: "Location",
configurable: true,
},
});
const workerLocationUrls = new WeakMap();
class WorkerLocation {
constructor(href = null, key = null) {
if (key != locationConstructorKey) {
throw new TypeError("Illegal constructor.");
}
const url = new URL(href);
url.username = "";
url.password = "";
WeakMapPrototypeSet(workerLocationUrls, this, url);
}
}
ObjectDefineProperties(WorkerLocation.prototype, {
hash: {
get() {
const url = WeakMapPrototypeGet(workerLocationUrls, this);
if (url == null) {
throw new TypeError("Illegal invocation.");
}
return url.hash;
}, },
setLocationHref, configurable: true,
getLocationHref() { enumerable: true,
return location?.href; },
host: {
get() {
const url = WeakMapPrototypeGet(workerLocationUrls, this);
if (url == null) {
throw new TypeError("Illegal invocation.");
}
return url.host;
}, },
}; configurable: true,
})(this); enumerable: true,
},
hostname: {
get() {
const url = WeakMapPrototypeGet(workerLocationUrls, this);
if (url == null) {
throw new TypeError("Illegal invocation.");
}
return url.hostname;
},
configurable: true,
enumerable: true,
},
href: {
get() {
const url = WeakMapPrototypeGet(workerLocationUrls, this);
if (url == null) {
throw new TypeError("Illegal invocation.");
}
return url.href;
},
configurable: true,
enumerable: true,
},
origin: {
get() {
const url = WeakMapPrototypeGet(workerLocationUrls, this);
if (url == null) {
throw new TypeError("Illegal invocation.");
}
return url.origin;
},
configurable: true,
enumerable: true,
},
pathname: {
get() {
const url = WeakMapPrototypeGet(workerLocationUrls, this);
if (url == null) {
throw new TypeError("Illegal invocation.");
}
return url.pathname;
},
configurable: true,
enumerable: true,
},
port: {
get() {
const url = WeakMapPrototypeGet(workerLocationUrls, this);
if (url == null) {
throw new TypeError("Illegal invocation.");
}
return url.port;
},
configurable: true,
enumerable: true,
},
protocol: {
get() {
const url = WeakMapPrototypeGet(workerLocationUrls, this);
if (url == null) {
throw new TypeError("Illegal invocation.");
}
return url.protocol;
},
configurable: true,
enumerable: true,
},
search: {
get() {
const url = WeakMapPrototypeGet(workerLocationUrls, this);
if (url == null) {
throw new TypeError("Illegal invocation.");
}
return url.search;
},
configurable: true,
enumerable: true,
},
toString: {
value: function toString() {
const url = WeakMapPrototypeGet(workerLocationUrls, this);
if (url == null) {
throw new TypeError("Illegal invocation.");
}
return url.href;
},
configurable: true,
enumerable: true,
writable: true,
},
[SymbolToStringTag]: {
value: "WorkerLocation",
configurable: true,
},
[SymbolFor("Deno.privateCustomInspect")]: {
value: function (inspect) {
const object = {
hash: this.hash,
host: this.host,
hostname: this.hostname,
href: this.href,
origin: this.origin,
pathname: this.pathname,
port: this.port,
protocol: this.protocol,
search: this.search,
};
return `${this.constructor.name} ${inspect(object)}`;
},
},
});
let location = undefined;
let workerLocation = undefined;
function setLocationHref(href) {
location = new Location(href, locationConstructorKey);
workerLocation = new WorkerLocation(href, locationConstructorKey);
}
function getLocationHref() {
return location?.href;
}
const locationConstructorDescriptor = {
value: Location,
configurable: true,
writable: true,
};
const workerLocationConstructorDescriptor = {
value: WorkerLocation,
configurable: true,
writable: true,
};
const locationDescriptor = {
get() {
return location;
},
set() {
throw new DOMException(`Cannot set "location".`, "NotSupportedError");
},
enumerable: true,
};
const workerLocationDescriptor = {
get() {
if (workerLocation == null) {
throw new Error(
`Assertion: "globalThis.location" must be defined in a worker.`,
);
}
return workerLocation;
},
configurable: true,
enumerable: true,
};
export {
getLocationHref,
locationConstructorDescriptor,
locationDescriptor,
setLocationHref,
workerLocationConstructorDescriptor,
workerLocationDescriptor,
};

View file

@ -6,338 +6,339 @@
/// <reference path="./internal.d.ts" /> /// <reference path="./internal.d.ts" />
/// <reference path="./lib.deno_web.d.ts" /> /// <reference path="./lib.deno_web.d.ts" />
"use strict"; const core = globalThis.Deno.core;
const { InterruptedPrototype, ops } = core;
import * as webidl from "internal:ext/webidl/00_webidl.js";
import {
defineEventHandler,
EventTarget,
MessageEvent,
setEventTargetData,
} from "internal:ext/web/02_event.js";
import DOMException from "internal:ext/web/01_dom_exception.js";
const primordials = globalThis.__bootstrap.primordials;
const {
ArrayBufferPrototype,
ArrayPrototypeFilter,
ArrayPrototypeIncludes,
ArrayPrototypePush,
ObjectPrototypeIsPrototypeOf,
ObjectSetPrototypeOf,
Symbol,
SymbolFor,
SymbolIterator,
TypeError,
} = primordials;
((window) => { class MessageChannel {
const core = window.Deno.core; /** @type {MessagePort} */
const { InterruptedPrototype, ops } = core; #port1;
const webidl = window.__bootstrap.webidl; /** @type {MessagePort} */
const { EventTarget, setEventTargetData } = window.__bootstrap.eventTarget; #port2;
const { MessageEvent, defineEventHandler } = window.__bootstrap.event;
const { DOMException } = window.__bootstrap.domException;
const {
ArrayBufferPrototype,
ArrayPrototypeFilter,
ArrayPrototypeIncludes,
ArrayPrototypePush,
ObjectPrototypeIsPrototypeOf,
ObjectSetPrototypeOf,
Symbol,
SymbolFor,
SymbolIterator,
TypeError,
} = window.__bootstrap.primordials;
class MessageChannel { constructor() {
/** @type {MessagePort} */ this[webidl.brand] = webidl.brand;
#port1; const { 0: port1Id, 1: port2Id } = opCreateEntangledMessagePort();
/** @type {MessagePort} */ const port1 = createMessagePort(port1Id);
#port2; const port2 = createMessagePort(port2Id);
this.#port1 = port1;
constructor() { this.#port2 = port2;
this[webidl.brand] = webidl.brand;
const { 0: port1Id, 1: port2Id } = opCreateEntangledMessagePort();
const port1 = createMessagePort(port1Id);
const port2 = createMessagePort(port2Id);
this.#port1 = port1;
this.#port2 = port2;
}
get port1() {
webidl.assertBranded(this, MessageChannelPrototype);
return this.#port1;
}
get port2() {
webidl.assertBranded(this, MessageChannelPrototype);
return this.#port2;
}
[SymbolFor("Deno.inspect")](inspect) {
return `MessageChannel ${
inspect({ port1: this.port1, port2: this.port2 })
}`;
}
} }
webidl.configurePrototype(MessageChannel); get port1() {
const MessageChannelPrototype = MessageChannel.prototype; webidl.assertBranded(this, MessageChannelPrototype);
return this.#port1;
const _id = Symbol("id");
const _enabled = Symbol("enabled");
/**
* @param {number} id
* @returns {MessagePort}
*/
function createMessagePort(id) {
const port = core.createHostObject();
ObjectSetPrototypeOf(port, MessagePortPrototype);
port[webidl.brand] = webidl.brand;
setEventTargetData(port);
port[_id] = id;
return port;
} }
class MessagePort extends EventTarget { get port2() {
/** @type {number | null} */ webidl.assertBranded(this, MessageChannelPrototype);
[_id] = null; return this.#port2;
/** @type {boolean} */
[_enabled] = false;
constructor() {
super();
webidl.illegalConstructor();
}
/**
* @param {any} message
* @param {object[] | StructuredSerializeOptions} transferOrOptions
*/
postMessage(message, transferOrOptions = {}) {
webidl.assertBranded(this, MessagePortPrototype);
const prefix = "Failed to execute 'postMessage' on 'MessagePort'";
webidl.requiredArguments(arguments.length, 1, { prefix });
message = webidl.converters.any(message);
let options;
if (
webidl.type(transferOrOptions) === "Object" &&
transferOrOptions !== undefined &&
transferOrOptions[SymbolIterator] !== undefined
) {
const transfer = webidl.converters["sequence<object>"](
transferOrOptions,
{ prefix, context: "Argument 2" },
);
options = { transfer };
} else {
options = webidl.converters.StructuredSerializeOptions(
transferOrOptions,
{
prefix,
context: "Argument 2",
},
);
}
const { transfer } = options;
if (ArrayPrototypeIncludes(transfer, this)) {
throw new DOMException("Can not tranfer self", "DataCloneError");
}
const data = serializeJsMessageData(message, transfer);
if (this[_id] === null) return;
ops.op_message_port_post_message(this[_id], data);
}
start() {
webidl.assertBranded(this, MessagePortPrototype);
if (this[_enabled]) return;
(async () => {
this[_enabled] = true;
while (true) {
if (this[_id] === null) break;
let data;
try {
data = await core.opAsync(
"op_message_port_recv_message",
this[_id],
);
} catch (err) {
if (ObjectPrototypeIsPrototypeOf(InterruptedPrototype, err)) break;
throw err;
}
if (data === null) break;
let message, transferables;
try {
const v = deserializeJsMessageData(data);
message = v[0];
transferables = v[1];
} catch (err) {
const event = new MessageEvent("messageerror", { data: err });
this.dispatchEvent(event);
return;
}
const event = new MessageEvent("message", {
data: message,
ports: ArrayPrototypeFilter(
transferables,
(t) => ObjectPrototypeIsPrototypeOf(MessagePortPrototype, t),
),
});
this.dispatchEvent(event);
}
this[_enabled] = false;
})();
}
close() {
webidl.assertBranded(this, MessagePortPrototype);
if (this[_id] !== null) {
core.close(this[_id]);
this[_id] = null;
}
}
} }
defineEventHandler(MessagePort.prototype, "message", function (self) { [SymbolFor("Deno.inspect")](inspect) {
self.start(); return `MessageChannel ${
}); inspect({ port1: this.port1, port2: this.port2 })
defineEventHandler(MessagePort.prototype, "messageerror"); }`;
}
}
webidl.configurePrototype(MessagePort); webidl.configurePrototype(MessageChannel);
const MessagePortPrototype = MessagePort.prototype; const MessageChannelPrototype = MessageChannel.prototype;
/** const _id = Symbol("id");
* @returns {[number, number]} const _enabled = Symbol("enabled");
*/
function opCreateEntangledMessagePort() { /**
return ops.op_message_port_create_entangled(); * @param {number} id
* @returns {MessagePort}
*/
function createMessagePort(id) {
const port = core.createHostObject();
ObjectSetPrototypeOf(port, MessagePortPrototype);
port[webidl.brand] = webidl.brand;
setEventTargetData(port);
port[_id] = id;
return port;
}
class MessagePort extends EventTarget {
/** @type {number | null} */
[_id] = null;
/** @type {boolean} */
[_enabled] = false;
constructor() {
super();
webidl.illegalConstructor();
} }
/** /**
* @param {globalThis.__bootstrap.messagePort.MessageData} messageData * @param {any} message
* @returns {[any, object[]]} * @param {object[] | StructuredSerializeOptions} transferOrOptions
*/ */
function deserializeJsMessageData(messageData) { postMessage(message, transferOrOptions = {}) {
/** @type {object[]} */ webidl.assertBranded(this, MessagePortPrototype);
const transferables = []; const prefix = "Failed to execute 'postMessage' on 'MessagePort'";
const hostObjects = [];
const arrayBufferIdsInTransferables = [];
const transferredArrayBuffers = [];
for (let i = 0; i < messageData.transferables.length; ++i) {
const transferable = messageData.transferables[i];
switch (transferable.kind) {
case "messagePort": {
const port = createMessagePort(transferable.data);
ArrayPrototypePush(transferables, port);
ArrayPrototypePush(hostObjects, port);
break;
}
case "arrayBuffer": {
ArrayPrototypePush(transferredArrayBuffers, transferable.data);
const index = ArrayPrototypePush(transferables, null);
ArrayPrototypePush(arrayBufferIdsInTransferables, index);
break;
}
default:
throw new TypeError("Unreachable");
}
}
const data = core.deserialize(messageData.data, {
hostObjects,
transferredArrayBuffers,
});
for (let i = 0; i < arrayBufferIdsInTransferables.length; ++i) {
const id = arrayBufferIdsInTransferables[i];
transferables[id] = transferredArrayBuffers[i];
}
return [data, transferables];
}
/**
* @param {any} data
* @param {object[]} transferables
* @returns {globalThis.__bootstrap.messagePort.MessageData}
*/
function serializeJsMessageData(data, transferables) {
const transferredArrayBuffers = [];
for (let i = 0, j = 0; i < transferables.length; i++) {
const ab = transferables[i];
if (ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, ab)) {
if (ab.byteLength === 0 && core.ops.op_arraybuffer_was_detached(ab)) {
throw new DOMException(
`ArrayBuffer at index ${j} is already detached`,
"DataCloneError",
);
}
j++;
transferredArrayBuffers.push(ab);
}
}
const serializedData = core.serialize(data, {
hostObjects: ArrayPrototypeFilter(
transferables,
(a) => ObjectPrototypeIsPrototypeOf(MessagePortPrototype, a),
),
transferredArrayBuffers,
}, (err) => {
throw new DOMException(err, "DataCloneError");
});
/** @type {globalThis.__bootstrap.messagePort.Transferable[]} */
const serializedTransferables = [];
let arrayBufferI = 0;
for (let i = 0; i < transferables.length; ++i) {
const transferable = transferables[i];
if (ObjectPrototypeIsPrototypeOf(MessagePortPrototype, transferable)) {
webidl.assertBranded(transferable, MessagePortPrototype);
const id = transferable[_id];
if (id === null) {
throw new DOMException(
"Can not transfer disentangled message port",
"DataCloneError",
);
}
transferable[_id] = null;
ArrayPrototypePush(serializedTransferables, {
kind: "messagePort",
data: id,
});
} else if (
ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, transferable)
) {
ArrayPrototypePush(serializedTransferables, {
kind: "arrayBuffer",
data: transferredArrayBuffers[arrayBufferI],
});
arrayBufferI++;
} else {
throw new DOMException("Value not transferable", "DataCloneError");
}
}
return {
data: serializedData,
transferables: serializedTransferables,
};
}
webidl.converters.StructuredSerializeOptions = webidl
.createDictionaryConverter(
"StructuredSerializeOptions",
[
{
key: "transfer",
converter: webidl.converters["sequence<object>"],
get defaultValue() {
return [];
},
},
],
);
function structuredClone(value, options) {
const prefix = "Failed to execute 'structuredClone'";
webidl.requiredArguments(arguments.length, 1, { prefix }); webidl.requiredArguments(arguments.length, 1, { prefix });
options = webidl.converters.StructuredSerializeOptions(options, { message = webidl.converters.any(message);
prefix, let options;
context: "Argument 2", if (
}); webidl.type(transferOrOptions) === "Object" &&
const messageData = serializeJsMessageData(value, options.transfer); transferOrOptions !== undefined &&
return deserializeJsMessageData(messageData)[0]; transferOrOptions[SymbolIterator] !== undefined
) {
const transfer = webidl.converters["sequence<object>"](
transferOrOptions,
{ prefix, context: "Argument 2" },
);
options = { transfer };
} else {
options = webidl.converters.StructuredSerializeOptions(
transferOrOptions,
{
prefix,
context: "Argument 2",
},
);
}
const { transfer } = options;
if (ArrayPrototypeIncludes(transfer, this)) {
throw new DOMException("Can not tranfer self", "DataCloneError");
}
const data = serializeJsMessageData(message, transfer);
if (this[_id] === null) return;
ops.op_message_port_post_message(this[_id], data);
} }
window.__bootstrap.messagePort = { start() {
MessageChannel, webidl.assertBranded(this, MessagePortPrototype);
MessagePort, if (this[_enabled]) return;
MessagePortPrototype, (async () => {
deserializeJsMessageData, this[_enabled] = true;
serializeJsMessageData, while (true) {
structuredClone, if (this[_id] === null) break;
let data;
try {
data = await core.opAsync(
"op_message_port_recv_message",
this[_id],
);
} catch (err) {
if (ObjectPrototypeIsPrototypeOf(InterruptedPrototype, err)) break;
throw err;
}
if (data === null) break;
let message, transferables;
try {
const v = deserializeJsMessageData(data);
message = v[0];
transferables = v[1];
} catch (err) {
const event = new MessageEvent("messageerror", { data: err });
this.dispatchEvent(event);
return;
}
const event = new MessageEvent("message", {
data: message,
ports: ArrayPrototypeFilter(
transferables,
(t) => ObjectPrototypeIsPrototypeOf(MessagePortPrototype, t),
),
});
this.dispatchEvent(event);
}
this[_enabled] = false;
})();
}
close() {
webidl.assertBranded(this, MessagePortPrototype);
if (this[_id] !== null) {
core.close(this[_id]);
this[_id] = null;
}
}
}
defineEventHandler(MessagePort.prototype, "message", function (self) {
self.start();
});
defineEventHandler(MessagePort.prototype, "messageerror");
webidl.configurePrototype(MessagePort);
const MessagePortPrototype = MessagePort.prototype;
/**
* @returns {[number, number]}
*/
function opCreateEntangledMessagePort() {
return ops.op_message_port_create_entangled();
}
/**
* @param {messagePort.MessageData} messageData
* @returns {[any, object[]]}
*/
function deserializeJsMessageData(messageData) {
/** @type {object[]} */
const transferables = [];
const hostObjects = [];
const arrayBufferIdsInTransferables = [];
const transferredArrayBuffers = [];
for (let i = 0; i < messageData.transferables.length; ++i) {
const transferable = messageData.transferables[i];
switch (transferable.kind) {
case "messagePort": {
const port = createMessagePort(transferable.data);
ArrayPrototypePush(transferables, port);
ArrayPrototypePush(hostObjects, port);
break;
}
case "arrayBuffer": {
ArrayPrototypePush(transferredArrayBuffers, transferable.data);
const index = ArrayPrototypePush(transferables, null);
ArrayPrototypePush(arrayBufferIdsInTransferables, index);
break;
}
default:
throw new TypeError("Unreachable");
}
}
const data = core.deserialize(messageData.data, {
hostObjects,
transferredArrayBuffers,
});
for (let i = 0; i < arrayBufferIdsInTransferables.length; ++i) {
const id = arrayBufferIdsInTransferables[i];
transferables[id] = transferredArrayBuffers[i];
}
return [data, transferables];
}
/**
* @param {any} data
* @param {object[]} transferables
* @returns {messagePort.MessageData}
*/
function serializeJsMessageData(data, transferables) {
const transferredArrayBuffers = [];
for (let i = 0, j = 0; i < transferables.length; i++) {
const ab = transferables[i];
if (ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, ab)) {
if (ab.byteLength === 0 && ops.op_arraybuffer_was_detached(ab)) {
throw new DOMException(
`ArrayBuffer at index ${j} is already detached`,
"DataCloneError",
);
}
j++;
transferredArrayBuffers.push(ab);
}
}
const serializedData = core.serialize(data, {
hostObjects: ArrayPrototypeFilter(
transferables,
(a) => ObjectPrototypeIsPrototypeOf(MessagePortPrototype, a),
),
transferredArrayBuffers,
}, (err) => {
throw new DOMException(err, "DataCloneError");
});
/** @type {messagePort.Transferable[]} */
const serializedTransferables = [];
let arrayBufferI = 0;
for (let i = 0; i < transferables.length; ++i) {
const transferable = transferables[i];
if (ObjectPrototypeIsPrototypeOf(MessagePortPrototype, transferable)) {
webidl.assertBranded(transferable, MessagePortPrototype);
const id = transferable[_id];
if (id === null) {
throw new DOMException(
"Can not transfer disentangled message port",
"DataCloneError",
);
}
transferable[_id] = null;
ArrayPrototypePush(serializedTransferables, {
kind: "messagePort",
data: id,
});
} else if (
ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, transferable)
) {
ArrayPrototypePush(serializedTransferables, {
kind: "arrayBuffer",
data: transferredArrayBuffers[arrayBufferI],
});
arrayBufferI++;
} else {
throw new DOMException("Value not transferable", "DataCloneError");
}
}
return {
data: serializedData,
transferables: serializedTransferables,
}; };
})(globalThis); }
webidl.converters.StructuredSerializeOptions = webidl
.createDictionaryConverter(
"StructuredSerializeOptions",
[
{
key: "transfer",
converter: webidl.converters["sequence<object>"],
get defaultValue() {
return [];
},
},
],
);
function structuredClone(value, options) {
const prefix = "Failed to execute 'structuredClone'";
webidl.requiredArguments(arguments.length, 1, { prefix });
options = webidl.converters.StructuredSerializeOptions(options, {
prefix,
context: "Argument 2",
});
const messageData = serializeJsMessageData(value, options.transfer);
return deserializeJsMessageData(messageData)[0];
}
export {
deserializeJsMessageData,
MessageChannel,
MessagePort,
MessagePortPrototype,
serializeJsMessageData,
structuredClone,
};

View file

@ -5,127 +5,120 @@
/// <reference path="./internal.d.ts" /> /// <reference path="./internal.d.ts" />
/// <reference path="./lib.deno_web.d.ts" /> /// <reference path="./lib.deno_web.d.ts" />
"use strict"; const core = globalThis.Deno.core;
const ops = core.ops;
import * as webidl from "internal:ext/webidl/00_webidl.js";
import { TransformStream } from "internal:ext/web/06_streams.js";
((window) => { webidl.converters.CompressionFormat = webidl.createEnumConverter(
const core = window.Deno.core; "CompressionFormat",
const ops = core.ops; [
const webidl = window.__bootstrap.webidl; "deflate",
const { TransformStream } = window.__bootstrap.streams; "deflate-raw",
"gzip",
],
);
webidl.converters.CompressionFormat = webidl.createEnumConverter( class CompressionStream {
"CompressionFormat", #transform;
[
"deflate",
"deflate-raw",
"gzip",
],
);
class CompressionStream { constructor(format) {
#transform; const prefix = "Failed to construct 'CompressionStream'";
webidl.requiredArguments(arguments.length, 1, { prefix });
format = webidl.converters.CompressionFormat(format, {
prefix,
context: "Argument 1",
});
constructor(format) { const rid = ops.op_compression_new(format, false);
const prefix = "Failed to construct 'CompressionStream'";
webidl.requiredArguments(arguments.length, 1, { prefix });
format = webidl.converters.CompressionFormat(format, {
prefix,
context: "Argument 1",
});
const rid = ops.op_compression_new(format, false); this.#transform = new TransformStream({
transform(chunk, controller) {
chunk = webidl.converters.BufferSource(chunk, {
prefix,
context: "chunk",
});
const output = ops.op_compression_write(
rid,
chunk,
);
maybeEnqueue(controller, output);
},
flush(controller) {
const output = ops.op_compression_finish(rid);
maybeEnqueue(controller, output);
},
});
this.#transform = new TransformStream({ this[webidl.brand] = webidl.brand;
transform(chunk, controller) {
chunk = webidl.converters.BufferSource(chunk, {
prefix,
context: "chunk",
});
const output = ops.op_compression_write(
rid,
chunk,
);
maybeEnqueue(controller, output);
},
flush(controller) {
const output = ops.op_compression_finish(rid);
maybeEnqueue(controller, output);
},
});
this[webidl.brand] = webidl.brand;
}
get readable() {
webidl.assertBranded(this, CompressionStreamPrototype);
return this.#transform.readable;
}
get writable() {
webidl.assertBranded(this, CompressionStreamPrototype);
return this.#transform.writable;
}
} }
webidl.configurePrototype(CompressionStream); get readable() {
const CompressionStreamPrototype = CompressionStream.prototype; webidl.assertBranded(this, CompressionStreamPrototype);
return this.#transform.readable;
class DecompressionStream {
#transform;
constructor(format) {
const prefix = "Failed to construct 'DecompressionStream'";
webidl.requiredArguments(arguments.length, 1, { prefix });
format = webidl.converters.CompressionFormat(format, {
prefix,
context: "Argument 1",
});
const rid = ops.op_compression_new(format, true);
this.#transform = new TransformStream({
transform(chunk, controller) {
chunk = webidl.converters.BufferSource(chunk, {
prefix,
context: "chunk",
});
const output = ops.op_compression_write(
rid,
chunk,
);
maybeEnqueue(controller, output);
},
flush(controller) {
const output = ops.op_compression_finish(rid);
maybeEnqueue(controller, output);
},
});
this[webidl.brand] = webidl.brand;
}
get readable() {
webidl.assertBranded(this, DecompressionStreamPrototype);
return this.#transform.readable;
}
get writable() {
webidl.assertBranded(this, DecompressionStreamPrototype);
return this.#transform.writable;
}
} }
function maybeEnqueue(controller, output) { get writable() {
if (output && output.byteLength > 0) { webidl.assertBranded(this, CompressionStreamPrototype);
controller.enqueue(output); return this.#transform.writable;
} }
}
webidl.configurePrototype(CompressionStream);
const CompressionStreamPrototype = CompressionStream.prototype;
class DecompressionStream {
#transform;
constructor(format) {
const prefix = "Failed to construct 'DecompressionStream'";
webidl.requiredArguments(arguments.length, 1, { prefix });
format = webidl.converters.CompressionFormat(format, {
prefix,
context: "Argument 1",
});
const rid = ops.op_compression_new(format, true);
this.#transform = new TransformStream({
transform(chunk, controller) {
chunk = webidl.converters.BufferSource(chunk, {
prefix,
context: "chunk",
});
const output = ops.op_compression_write(
rid,
chunk,
);
maybeEnqueue(controller, output);
},
flush(controller) {
const output = ops.op_compression_finish(rid);
maybeEnqueue(controller, output);
},
});
this[webidl.brand] = webidl.brand;
} }
webidl.configurePrototype(DecompressionStream); get readable() {
const DecompressionStreamPrototype = DecompressionStream.prototype; webidl.assertBranded(this, DecompressionStreamPrototype);
return this.#transform.readable;
}
window.__bootstrap.compression = { get writable() {
CompressionStream, webidl.assertBranded(this, DecompressionStreamPrototype);
DecompressionStream, return this.#transform.writable;
}; }
})(globalThis); }
function maybeEnqueue(controller, output) {
if (output && output.byteLength > 0) {
controller.enqueue(output);
}
}
webidl.configurePrototype(DecompressionStream);
const DecompressionStreamPrototype = DecompressionStream.prototype;
export { CompressionStream, DecompressionStream };

File diff suppressed because it is too large Load diff

View file

@ -29,11 +29,12 @@ fn setup() -> Vec<Extension> {
deno_console::init(), deno_console::init(),
deno_web::init::<Permissions>(BlobStore::default(), None), deno_web::init::<Permissions>(BlobStore::default(), None),
Extension::builder("bench_setup") Extension::builder("bench_setup")
.js(vec![( .esm(vec![(
"setup", "internal:setup",
r#" r#"
const { TextDecoder } = globalThis.__bootstrap.encoding; import { TextDecoder } from "internal:ext/web/08_text_encoding.js";
const hello12k = Deno.core.encode("hello world\n".repeat(1e3)); globalThis.TextDecoder = TextDecoder;
globalThis.hello12k = Deno.core.encode("hello world\n".repeat(1e3));
"#, "#,
)]) )])
.state(|state| { .state(|state| {

View file

@ -28,9 +28,10 @@ fn setup() -> Vec<Extension> {
deno_console::init(), deno_console::init(),
deno_web::init::<Permissions>(BlobStore::default(), None), deno_web::init::<Permissions>(BlobStore::default(), None),
Extension::builder("bench_setup") Extension::builder("bench_setup")
.js(vec![ .esm(vec![
("setup", r#" ("internal:setup", r#"
const { setTimeout, handleTimerMacrotask } = globalThis.__bootstrap.timers; import { setTimeout, handleTimerMacrotask } from "internal:ext/web/02_timers.js";
globalThis.setTimeout = setTimeout;
Deno.core.setMacrotaskCallback(handleTimerMacrotask); Deno.core.setMacrotaskCallback(handleTimerMacrotask);
"#), "#),
]) ])

201
ext/web/internal.d.ts vendored
View file

@ -1,120 +1,111 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
// deno-lint-ignore-file no-var
/// <reference no-default-lib="true" /> /// <reference no-default-lib="true" />
/// <reference lib="esnext" /> /// <reference lib="esnext" />
declare namespace globalThis { declare module "internal:ext/web/00_infra.js" {
declare namespace __bootstrap { function collectSequenceOfCodepoints(
declare var infra: { input: string,
collectSequenceOfCodepoints( position: number,
input: string, condition: (char: string) => boolean,
position: number, ): {
condition: (char: string) => boolean, result: string;
): { position: number;
result: string; };
position: number; const ASCII_DIGIT: string[];
}; const ASCII_UPPER_ALPHA: string[];
ASCII_DIGIT: string[]; const ASCII_LOWER_ALPHA: string[];
ASCII_UPPER_ALPHA: string[]; const ASCII_ALPHA: string[];
ASCII_LOWER_ALPHA: string[]; const ASCII_ALPHANUMERIC: string[];
ASCII_ALPHA: string[]; const HTTP_TAB_OR_SPACE: string[];
ASCII_ALPHANUMERIC: string[]; const HTTP_WHITESPACE: string[];
HTTP_TAB_OR_SPACE: string[]; const HTTP_TOKEN_CODE_POINT: string[];
HTTP_WHITESPACE: string[]; const HTTP_TOKEN_CODE_POINT_RE: RegExp;
HTTP_TOKEN_CODE_POINT: string[]; const HTTP_QUOTED_STRING_TOKEN_POINT: string[];
HTTP_TOKEN_CODE_POINT_RE: RegExp; const HTTP_QUOTED_STRING_TOKEN_POINT_RE: RegExp;
HTTP_QUOTED_STRING_TOKEN_POINT: string[]; const HTTP_TAB_OR_SPACE_PREFIX_RE: RegExp;
HTTP_QUOTED_STRING_TOKEN_POINT_RE: RegExp; const HTTP_TAB_OR_SPACE_SUFFIX_RE: RegExp;
HTTP_TAB_OR_SPACE_PREFIX_RE: RegExp; const HTTP_WHITESPACE_PREFIX_RE: RegExp;
HTTP_TAB_OR_SPACE_SUFFIX_RE: RegExp; const HTTP_WHITESPACE_SUFFIX_RE: RegExp;
HTTP_WHITESPACE_PREFIX_RE: RegExp; function httpTrim(s: string): string;
HTTP_WHITESPACE_SUFFIX_RE: RegExp; function regexMatcher(chars: string[]): string;
httpTrim(s: string): string; function byteUpperCase(s: string): string;
regexMatcher(chars: string[]): string; function byteLowerCase(s: string): string;
byteUpperCase(s: string): string; function collectHttpQuotedString(
byteLowerCase(s: string): string; input: string,
collectHttpQuotedString( position: number,
input: string, extractValue: boolean,
position: number, ): {
extractValue: boolean, result: string;
): { position: number;
result: string; };
position: number; function forgivingBase64Encode(data: Uint8Array): string;
}; function forgivingBase64Decode(data: string): Uint8Array;
forgivingBase64Encode(data: Uint8Array): string; function serializeJSValueToJSONString(value: unknown): string;
forgivingBase64Decode(data: string): Uint8Array; }
serializeJSValueToJSONString(value: unknown): string;
};
declare var domException: { declare module "internal:ext/web/01_dom_exception.js" {
DOMException: typeof DOMException; export = DOMException;
}; }
declare namespace mimesniff { declare module "internal:ext/web/01_mimesniff.js" {
declare interface MimeType { interface MimeType {
type: string; type: string;
subtype: string; subtype: string;
parameters: Map<string, string>; parameters: Map<string, string>;
} }
declare function parseMimeType(input: string): MimeType | null; function parseMimeType(input: string): MimeType | null;
declare function essence(mimeType: MimeType): string; function essence(mimeType: MimeType): string;
declare function serializeMimeType(mimeType: MimeType): string; function serializeMimeType(mimeType: MimeType): string;
declare function extractMimeType( function extractMimeType(
headerValues: string[] | null, headerValues: string[] | null,
): MimeType | null; ): MimeType | null;
} }
declare var eventTarget: { declare module "internal:ext/web/02_event.js" {
EventTarget: typeof EventTarget; const EventTarget: typeof EventTarget;
}; const Event: typeof event;
const ErrorEvent: typeof ErrorEvent;
const CloseEvent: typeof CloseEvent;
const MessageEvent: typeof MessageEvent;
const CustomEvent: typeof CustomEvent;
const ProgressEvent: typeof ProgressEvent;
const PromiseRejectionEvent: typeof PromiseRejectionEvent;
const reportError: typeof reportError;
}
declare var event: { declare module "internal:ext/web/12_location.js" {
Event: typeof event; function getLocationHref(): string | undefined;
ErrorEvent: typeof ErrorEvent; }
CloseEvent: typeof CloseEvent;
MessageEvent: typeof MessageEvent;
CustomEvent: typeof CustomEvent;
ProgressEvent: typeof ProgressEvent;
PromiseRejectionEvent: typeof PromiseRejectionEvent;
reportError: typeof reportError;
};
declare var location: { declare module "internal:ext/web/05_base64.js" {
getLocationHref(): string | undefined; function atob(data: string): string;
}; function btoa(data: string): string;
}
declare var base64: { declare module "internal:ext/web/09_file.js" {
atob(data: string): string; function blobFromObjectUrl(url: string): Blob | null;
btoa(data: string): string; function getParts(blob: Blob): string[];
}; const Blob: typeof Blob;
const File: typeof File;
}
declare var file: { declare module "internal:ext/web/06_streams.js" {
blobFromObjectUrl(url: string): Blob | null; const ReadableStream: typeof ReadableStream;
getParts(blob: Blob): string[]; function isReadableStreamDisturbed(stream: ReadableStream): boolean;
Blob: typeof Blob; function createProxy<T>(stream: ReadableStream<T>): ReadableStream<T>;
File: typeof File; }
};
declare var streams: { declare module "internal:ext/web/13_message_port.js" {
ReadableStream: typeof ReadableStream; type Transferable = {
isReadableStreamDisturbed(stream: ReadableStream): boolean; kind: "messagePort";
createProxy<T>(stream: ReadableStream<T>): ReadableStream<T>; data: number;
}; } | {
kind: "arrayBuffer";
declare namespace messagePort { data: number;
declare type Transferable = { };
kind: "messagePort"; interface MessageData {
data: number; data: Uint8Array;
} | { transferables: Transferable[];
kind: "arrayBuffer";
data: number;
};
declare interface MessageData {
data: Uint8Array;
transferables: Transferable[];
}
}
} }
} }

View file

@ -64,7 +64,7 @@ pub fn init<P: TimersPermission + 'static>(
) -> Extension { ) -> Extension {
Extension::builder(env!("CARGO_PKG_NAME")) Extension::builder(env!("CARGO_PKG_NAME"))
.dependencies(vec!["deno_webidl", "deno_console", "deno_url"]) .dependencies(vec!["deno_webidl", "deno_console", "deno_url"])
.js(include_js_files!( .esm(include_js_files!(
prefix "internal:ext/web", prefix "internal:ext/web",
"00_infra.js", "00_infra.js",
"01_dom_exception.js", "01_dom_exception.js",

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -6,144 +6,141 @@
/// <reference path="../web/lib.deno_web.d.ts" /> /// <reference path="../web/lib.deno_web.d.ts" />
/// <reference path="./lib.deno_webgpu.d.ts" /> /// <reference path="./lib.deno_webgpu.d.ts" />
"use strict"; const core = globalThis.Deno.core;
const ops = core.ops;
import * as webidl from "internal:ext/webidl/00_webidl.js";
const primordials = globalThis.__bootstrap.primordials;
const { Symbol } = primordials;
import {
_device,
assertDevice,
createGPUTexture,
} from "internal:ext/webgpu/01_webgpu.js";
((window) => { const _surfaceRid = Symbol("[[surfaceRid]]");
const core = window.Deno.core; const _configuration = Symbol("[[configuration]]");
const ops = core.ops; const _canvas = Symbol("[[canvas]]");
const webidl = window.__bootstrap.webidl; const _currentTexture = Symbol("[[currentTexture]]");
const { Symbol } = window.__bootstrap.primordials; class GPUCanvasContext {
const { _device, assertDevice, createGPUTexture } = window.__bootstrap.webgpu; /** @type {number} */
[_surfaceRid];
/** @type {InnerGPUDevice} */
[_device];
[_configuration];
[_canvas];
/** @type {GPUTexture | undefined} */
[_currentTexture];
const _surfaceRid = Symbol("[[surfaceRid]]"); get canvas() {
const _configuration = Symbol("[[configuration]]"); webidl.assertBranded(this, GPUCanvasContextPrototype);
const _canvas = Symbol("[[canvas]]"); return this[_canvas];
const _currentTexture = Symbol("[[currentTexture]]"); }
class GPUCanvasContext {
/** @type {number} */
[_surfaceRid];
/** @type {InnerGPUDevice} */
[_device];
[_configuration];
[_canvas];
/** @type {GPUTexture | undefined} */
[_currentTexture];
get canvas() { constructor() {
webidl.assertBranded(this, GPUCanvasContextPrototype); webidl.illegalConstructor();
return this[_canvas]; }
}
constructor() { configure(configuration) {
webidl.illegalConstructor(); webidl.assertBranded(this, GPUCanvasContextPrototype);
} const prefix = "Failed to execute 'configure' on 'GPUCanvasContext'";
webidl.requiredArguments(arguments.length, 1, { prefix });
configuration = webidl.converters.GPUCanvasConfiguration(configuration, {
prefix,
context: "Argument 1",
});
configure(configuration) { this[_device] = configuration.device[_device];
webidl.assertBranded(this, GPUCanvasContextPrototype); this[_configuration] = configuration;
const prefix = "Failed to execute 'configure' on 'GPUCanvasContext'"; const device = assertDevice(this, {
webidl.requiredArguments(arguments.length, 1, { prefix }); prefix,
configuration = webidl.converters.GPUCanvasConfiguration(configuration, { context: "configuration.device",
prefix, });
context: "Argument 1",
});
this[_device] = configuration.device[_device]; const { err } = ops.op_webgpu_surface_configure({
this[_configuration] = configuration; surfaceRid: this[_surfaceRid],
const device = assertDevice(this, { deviceRid: device.rid,
prefix, format: configuration.format,
context: "configuration.device", viewFormats: configuration.viewFormats,
}); usage: configuration.usage,
width: configuration.width,
height: configuration.height,
alphaMode: configuration.alphaMode,
});
const { err } = ops.op_webgpu_surface_configure({ device.pushError(err);
surfaceRid: this[_surfaceRid], }
deviceRid: device.rid,
format: configuration.format,
viewFormats: configuration.viewFormats,
usage: configuration.usage,
width: configuration.width,
height: configuration.height,
alphaMode: configuration.alphaMode,
});
device.pushError(err); unconfigure() {
} webidl.assertBranded(this, GPUCanvasContextPrototype);
unconfigure() { this[_configuration] = null;
webidl.assertBranded(this, GPUCanvasContextPrototype); this[_device] = null;
}
this[_configuration] = null; getCurrentTexture() {
this[_device] = null; webidl.assertBranded(this, GPUCanvasContextPrototype);
} const prefix =
"Failed to execute 'getCurrentTexture' on 'GPUCanvasContext'";
getCurrentTexture() { if (this[_configuration] === null) {
webidl.assertBranded(this, GPUCanvasContextPrototype); throw new DOMException(
const prefix = "context is not configured.",
"Failed to execute 'getCurrentTexture' on 'GPUCanvasContext'"; "InvalidStateError",
if (this[_configuration] === null) {
throw new DOMException(
"context is not configured.",
"InvalidStateError",
);
}
const device = assertDevice(this, { prefix, context: "this" });
if (this[_currentTexture]) {
return this[_currentTexture];
}
const { rid } = ops.op_webgpu_surface_get_current_texture(
device.rid,
this[_surfaceRid],
); );
}
const texture = createGPUTexture( const device = assertDevice(this, { prefix, context: "this" });
{
size: { if (this[_currentTexture]) {
width: this[_configuration].width, return this[_currentTexture];
height: this[_configuration].height, }
depthOrArrayLayers: 1,
}, const { rid } = ops.op_webgpu_surface_get_current_texture(
mipLevelCount: 1, device.rid,
sampleCount: 1, this[_surfaceRid],
dimension: "2d", );
format: this[_configuration].format,
usage: this[_configuration].usage, const texture = createGPUTexture(
{
size: {
width: this[_configuration].width,
height: this[_configuration].height,
depthOrArrayLayers: 1,
}, },
device, mipLevelCount: 1,
rid, sampleCount: 1,
); dimension: "2d",
device.trackResource(texture); format: this[_configuration].format,
this[_currentTexture] = texture; usage: this[_configuration].usage,
return texture; },
} device,
rid,
// Extended from spec. Required to present the texture; browser don't need this. );
present() { device.trackResource(texture);
webidl.assertBranded(this, GPUCanvasContextPrototype); this[_currentTexture] = texture;
const prefix = "Failed to execute 'present' on 'GPUCanvasContext'"; return texture;
const device = assertDevice(this[_currentTexture], {
prefix,
context: "this",
});
ops.op_webgpu_surface_present(device.rid, this[_surfaceRid]);
this[_currentTexture].destroy();
this[_currentTexture] = undefined;
}
}
const GPUCanvasContextPrototype = GPUCanvasContext.prototype;
function createCanvasContext(options) {
const canvasContext = webidl.createBranded(GPUCanvasContext);
canvasContext[_surfaceRid] = options.surfaceRid;
canvasContext[_canvas] = options.canvas;
return canvasContext;
} }
window.__bootstrap.webgpu = { // Extended from spec. Required to present the texture; browser don't need this.
...window.__bootstrap.webgpu, present() {
GPUCanvasContext, webidl.assertBranded(this, GPUCanvasContextPrototype);
createCanvasContext, const prefix = "Failed to execute 'present' on 'GPUCanvasContext'";
}; const device = assertDevice(this[_currentTexture], {
})(this); prefix,
context: "this",
});
ops.op_webgpu_surface_present(device.rid, this[_surfaceRid]);
this[_currentTexture].destroy();
this[_currentTexture] = undefined;
}
}
const GPUCanvasContextPrototype = GPUCanvasContext.prototype;
function createCanvasContext(options) {
const canvasContext = webidl.createBranded(GPUCanvasContext);
canvasContext[_surfaceRid] = options.surfaceRid;
canvasContext[_canvas] = options.canvas;
return canvasContext;
}
export { createCanvasContext, GPUCanvasContext };

View file

@ -6,81 +6,77 @@
/// <reference path="../web/lib.deno_web.d.ts" /> /// <reference path="../web/lib.deno_web.d.ts" />
/// <reference path="./lib.deno_webgpu.d.ts" /> /// <reference path="./lib.deno_webgpu.d.ts" />
"use strict"; import * as webidl from "internal:ext/webidl/00_webidl.js";
import { GPUTextureUsage } from "internal:ext/webgpu/01_webgpu.js";
((window) => { // ENUM: GPUCanvasAlphaMode
const webidl = window.__bootstrap.webidl; webidl.converters["GPUCanvasAlphaMode"] = webidl.createEnumConverter(
const { GPUTextureUsage } = window.__bootstrap.webgpu; "GPUCanvasAlphaMode",
[
"opaque",
"premultiplied",
],
);
// ENUM: GPUCanvasAlphaMode // NON-SPEC: ENUM: GPUPresentMode
webidl.converters["GPUCanvasAlphaMode"] = webidl.createEnumConverter( webidl.converters["GPUPresentMode"] = webidl.createEnumConverter(
"GPUCanvasAlphaMode", "GPUPresentMode",
[ [
"opaque", "autoVsync",
"premultiplied", "autoNoVsync",
], "fifo",
"fifoRelaxed",
"immediate",
"mailbox",
],
);
// DICT: GPUCanvasConfiguration
const dictMembersGPUCanvasConfiguration = [
{ key: "device", converter: webidl.converters.GPUDevice, required: true },
{
key: "format",
converter: webidl.converters.GPUTextureFormat,
required: true,
},
{
key: "usage",
converter: webidl.converters["GPUTextureUsageFlags"],
defaultValue: GPUTextureUsage.RENDER_ATTACHMENT,
},
{
key: "alphaMode",
converter: webidl.converters["GPUCanvasAlphaMode"],
defaultValue: "opaque",
},
// Extended from spec
{
key: "presentMode",
converter: webidl.converters["GPUPresentMode"],
},
{
key: "width",
converter: webidl.converters["long"],
required: true,
},
{
key: "height",
converter: webidl.converters["long"],
required: true,
},
{
key: "viewFormats",
converter: webidl.createSequenceConverter(
webidl.converters["GPUTextureFormat"],
),
get defaultValue() {
return [];
},
},
];
webidl.converters["GPUCanvasConfiguration"] = webidl
.createDictionaryConverter(
"GPUCanvasConfiguration",
dictMembersGPUCanvasConfiguration,
); );
// NON-SPEC: ENUM: GPUPresentMode
webidl.converters["GPUPresentMode"] = webidl.createEnumConverter(
"GPUPresentMode",
[
"autoVsync",
"autoNoVsync",
"fifo",
"fifoRelaxed",
"immediate",
"mailbox",
],
);
// DICT: GPUCanvasConfiguration
const dictMembersGPUCanvasConfiguration = [
{ key: "device", converter: webidl.converters.GPUDevice, required: true },
{
key: "format",
converter: webidl.converters.GPUTextureFormat,
required: true,
},
{
key: "usage",
converter: webidl.converters["GPUTextureUsageFlags"],
defaultValue: GPUTextureUsage.RENDER_ATTACHMENT,
},
{
key: "alphaMode",
converter: webidl.converters["GPUCanvasAlphaMode"],
defaultValue: "opaque",
},
// Extended from spec
{
key: "presentMode",
converter: webidl.converters["GPUPresentMode"],
},
{
key: "width",
converter: webidl.converters["long"],
required: true,
},
{
key: "height",
converter: webidl.converters["long"],
required: true,
},
{
key: "viewFormats",
converter: webidl.createSequenceConverter(
webidl.converters["GPUTextureFormat"],
),
get defaultValue() {
return [];
},
},
];
webidl.converters["GPUCanvasConfiguration"] = webidl
.createDictionaryConverter(
"GPUCanvasConfiguration",
dictMembersGPUCanvasConfiguration,
);
})(this);

View file

@ -119,7 +119,7 @@ impl Resource for WebGpuQuerySet {
pub fn init(unstable: bool) -> Extension { pub fn init(unstable: bool) -> Extension {
Extension::builder(env!("CARGO_PKG_NAME")) Extension::builder(env!("CARGO_PKG_NAME"))
.dependencies(vec!["deno_webidl", "deno_web"]) .dependencies(vec!["deno_webidl", "deno_web"])
.js(include_js_files!( .esm(include_js_files!(
prefix "internal:ext/webgpu", prefix "internal:ext/webgpu",
"01_webgpu.js", "01_webgpu.js",
"02_idl_types.js", "02_idl_types.js",

View file

@ -15,8 +15,8 @@ use wgpu_types::SurfaceStatus;
pub fn init_surface(unstable: bool) -> Extension { pub fn init_surface(unstable: bool) -> Extension {
Extension::builder("deno_webgpu_surface") Extension::builder("deno_webgpu_surface")
.dependencies(vec!["deno_webidl", "deno_web", "deno_webgpu"]) .dependencies(vec!["deno_webidl", "deno_web", "deno_webgpu"])
.js(include_js_files!( .esm(include_js_files!(
prefix "internal:deno_webgpu", prefix "internal:ext/webgpu",
"03_surface.js", "03_surface.js",
"04_surface_idl_types.js", "04_surface_idl_types.js",
)) ))

File diff suppressed because it is too large Load diff

View file

@ -2,7 +2,10 @@
// deno-lint-ignore-file // deno-lint-ignore-file
const { createDictionaryConverter, converters } = globalThis.__bootstrap.webidl; import {
converters,
createDictionaryConverter,
} from "internal:ext/webidl/00_webidl.js";
const TextDecodeOptions = createDictionaryConverter( const TextDecodeOptions = createDictionaryConverter(
"TextDecodeOptions", "TextDecodeOptions",
@ -14,6 +17,7 @@ const TextDecodeOptions = createDictionaryConverter(
}, },
], ],
); );
globalThis.TextDecodeOptions = TextDecodeOptions;
// Sanity check // Sanity check
{ {
@ -33,3 +37,4 @@ function handwrittenConverter(V) {
} }
return defaultValue; return defaultValue;
} }
globalThis.handwrittenConverter = handwrittenConverter;

View file

@ -11,7 +11,7 @@ fn setup() -> Vec<Extension> {
vec![ vec![
deno_webidl::init(), deno_webidl::init(),
Extension::builder("deno_webidl_bench") Extension::builder("deno_webidl_bench")
.js(vec![("setup", include_str!("dict.js"))]) .esm(vec![("internal:setup", include_str!("dict.js"))])
.build(), .build(),
] ]
} }

View file

@ -4,338 +4,334 @@
/// <reference no-default-lib="true" /> /// <reference no-default-lib="true" />
/// <reference lib="esnext" /> /// <reference lib="esnext" />
declare namespace globalThis { declare module "internal:ext/webidl/00_webidl.js" {
declare namespace __bootstrap { interface ConverterOpts {
declare namespace webidl { /**
declare interface ConverterOpts { * The prefix for error messages created by this converter.
/** * Examples:
* The prefix for error messages created by this converter. * - `Failed to construct 'Event'`
* Examples: * - `Failed to execute 'removeEventListener' on 'EventTarget'`
* - `Failed to construct 'Event'` */
* - `Failed to execute 'removeEventListener' on 'EventTarget'` prefix: string;
*/
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<double>"](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<T>(
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<T>(
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<T>(
converter: (v: any, opts: ValueConverterOpts) => T,
): (v: any, opts: ValueConverterOpts) => T[];
/**
* Create a converter that converts a Promise of the inner type.
*/
declare function createPromiseConverter<T>(
converter: (v: any, opts: ValueConverterOpts) => T,
): (v: any, opts: ValueConverterOpts) => Promise<T>;
/**
* Invoke a callback function.
*/
declare function invokeCallbackFunction<T>(
callable: (...args: any) => any,
args: any[],
thisArg: any,
returnValueConverter: (v: any, opts: ValueConverterOpts) => T,
opts: ConverterOpts & { returnsPromise?: boolean },
): 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<K, V>,
opts: ValueConverterOpts,
) => any;
/**
* Mix in the iterable declarations defined in WebIDL.
* https://heycam.github.io/webidl/#es-iterable
*/
declare function mixinPairIterable(
name: string,
prototype: any,
dataSymbol: symbol,
keyKey: string | number | symbol,
valueKey: string | number | symbol,
): void;
/**
* Configure prototype properties enumerability / writability / configurability.
*/
declare function configurePrototype(prototype: any);
/**
* Get the WebIDL / ES type of a value.
*/
declare function type(
v: any,
):
| "Null"
| "Undefined"
| "Boolean"
| "Number"
| "String"
| "Symbol"
| "BigInt"
| "Object";
}
} }
interface ValueConverterOpts extends ConverterOpts {
/**
* The context of this value error messages created by this converter.
* Examples:
* - `Argument 1`
* - `Argument 3`
*/
context: string;
}
function makeException(
ErrorType: any,
message: string,
opts: ValueConverterOpts,
): any;
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;
}
interface StringConverterOpts extends ValueConverterOpts {
/**
* Wether to treat `null` value as an empty string.
*/
treatNullAsEmptyString?: boolean;
}
interface BufferConverterOpts extends ValueConverterOpts {
/**
* Wether to allow `SharedArrayBuffer` (not just `ArrayBuffer`).
*/
allowShared?: boolean;
}
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<double>"](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.
*/
function requiredArguments(
length: number,
required: number,
opts: ConverterOpts,
): void;
type Dictionary = DictionaryMember[];
interface DictionaryMember {
key: string;
converter: (v: any, opts: ValueConverterOpts) => any;
defaultValue?: any;
required?: boolean;
}
/**
* Create a converter for dictionaries.
*/
function createDictionaryConverter<T>(
name: string,
...dictionaries: Dictionary[]
): (v: any, opts: ValueConverterOpts) => T;
/**
* Create a converter for enums.
*/
function createEnumConverter(
name: string,
values: string[],
): (v: any, opts: ValueConverterOpts) => string;
/**
* Create a converter that makes the contained type nullable.
*/
function createNullableConverter<T>(
converter: (v: any, opts: ValueConverterOpts) => T,
): (v: any, opts: ValueConverterOpts) => T | null;
/**
* Create a converter that converts a sequence of the inner type.
*/
function createSequenceConverter<T>(
converter: (v: any, opts: ValueConverterOpts) => T,
): (v: any, opts: ValueConverterOpts) => T[];
/**
* Create a converter that converts a Promise of the inner type.
*/
function createPromiseConverter<T>(
converter: (v: any, opts: ValueConverterOpts) => T,
): (v: any, opts: ValueConverterOpts) => Promise<T>;
/**
* Invoke a callback function.
*/
function invokeCallbackFunction<T>(
callable: (...args: any) => any,
args: any[],
thisArg: any,
returnValueConverter: (v: any, opts: ValueConverterOpts) => T,
opts: ConverterOpts & { returnsPromise?: boolean },
): T;
/**
* Throw an illegal constructor error.
*/
function illegalConstructor(): never;
/**
* The branding symbol.
*/
const brand: unique symbol;
/**
* Create a branded instance of an interface.
*/
function createBranded(self: any): any;
/**
* Assert that self is branded.
*/
function assertBranded(self: any, type: any): void;
/**
* Create a converter for interfaces.
*/
function createInterfaceConverter(
name: string,
prototype: any,
): (v: any, opts: ValueConverterOpts) => any;
function createRecordConverter<
K extends string | number | symbol,
V,
>(
keyConverter: (v: any, opts: ValueConverterOpts) => K,
valueConverter: (v: any, opts: ValueConverterOpts) => V,
): (
v: Record<K, V>,
opts: ValueConverterOpts,
) => any;
/**
* Mix in the iterable declarations defined in WebIDL.
* https://heycam.github.io/webidl/#es-iterable
*/
function mixinPairIterable(
name: string,
prototype: any,
dataSymbol: symbol,
keyKey: string | number | symbol,
valueKey: string | number | symbol,
): void;
/**
* Configure prototype properties enumerability / writability / configurability.
*/
function configurePrototype(prototype: any);
/**
* Get the WebIDL / ES type of a value.
*/
function type(
v: any,
):
| "Null"
| "Undefined"
| "Boolean"
| "Number"
| "String"
| "Symbol"
| "BigInt"
| "Object";
} }

View file

@ -6,7 +6,7 @@ use deno_core::Extension;
/// Load and execute the javascript code. /// Load and execute the javascript code.
pub fn init() -> Extension { pub fn init() -> Extension {
Extension::builder(env!("CARGO_PKG_NAME")) Extension::builder(env!("CARGO_PKG_NAME"))
.js(include_js_files!( .esm(include_js_files!(
prefix "internal:ext/webidl", prefix "internal:ext/webidl",
"00_webidl.js", "00_webidl.js",
)) ))

File diff suppressed because it is too large Load diff

View file

@ -1,426 +1,424 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
"use strict";
/// <reference path="../../core/internal.d.ts" /> /// <reference path="../../core/internal.d.ts" />
((window) => { const core = globalThis.Deno.core;
const core = window.Deno.core; const ops = core.ops;
const ops = core.ops; import * as webidl from "internal:ext/webidl/00_webidl.js";
const webidl = window.__bootstrap.webidl; import { Deferred, writableStreamClose } from "internal:ext/web/06_streams.js";
const { writableStreamClose, Deferred } = window.__bootstrap.streams; import DOMException from "internal:ext/web/01_dom_exception.js";
const { DOMException } = window.__bootstrap.domException; import { add, remove } from "internal:ext/web/03_abort_signal.js";
const { add, remove } = window.__bootstrap.abortSignal; import {
const { headersFromHeaderList, headerListFromHeaders, fillHeaders } = fillHeaders,
window.__bootstrap.headers; headerListFromHeaders,
headersFromHeaderList,
} from "internal:ext/fetch/20_headers.js";
const primordials = globalThis.__bootstrap.primordials;
const {
ArrayPrototypeJoin,
ArrayPrototypeMap,
Error,
ObjectPrototypeIsPrototypeOf,
PromisePrototypeCatch,
PromisePrototypeThen,
Set,
StringPrototypeEndsWith,
StringPrototypeToLowerCase,
Symbol,
SymbolFor,
TypeError,
Uint8ArrayPrototype,
} = primordials;
const { webidl.converters.WebSocketStreamOptions = webidl.createDictionaryConverter(
ArrayPrototypeJoin, "WebSocketStreamOptions",
ArrayPrototypeMap, [
Error, {
ObjectPrototypeIsPrototypeOf, key: "protocols",
PromisePrototypeCatch, converter: webidl.converters["sequence<USVString>"],
PromisePrototypeThen, get defaultValue() {
Set, return [];
StringPrototypeEndsWith, },
StringPrototypeToLowerCase, },
Symbol, {
SymbolFor, key: "signal",
TypeError, converter: webidl.converters.AbortSignal,
Uint8ArrayPrototype, },
} = window.__bootstrap.primordials; {
key: "headers",
converter: webidl.converters.HeadersInit,
},
],
);
webidl.converters.WebSocketCloseInfo = webidl.createDictionaryConverter(
"WebSocketCloseInfo",
[
{
key: "code",
converter: webidl.converters["unsigned short"],
},
{
key: "reason",
converter: webidl.converters.USVString,
defaultValue: "",
},
],
);
webidl.converters.WebSocketStreamOptions = webidl.createDictionaryConverter( const CLOSE_RESPONSE_TIMEOUT = 5000;
"WebSocketStreamOptions",
[
{
key: "protocols",
converter: webidl.converters["sequence<USVString>"],
get defaultValue() {
return [];
},
},
{
key: "signal",
converter: webidl.converters.AbortSignal,
},
{
key: "headers",
converter: webidl.converters.HeadersInit,
},
],
);
webidl.converters.WebSocketCloseInfo = webidl.createDictionaryConverter(
"WebSocketCloseInfo",
[
{
key: "code",
converter: webidl.converters["unsigned short"],
},
{
key: "reason",
converter: webidl.converters.USVString,
defaultValue: "",
},
],
);
const CLOSE_RESPONSE_TIMEOUT = 5000; const _rid = Symbol("[[rid]]");
const _url = Symbol("[[url]]");
const _connection = Symbol("[[connection]]");
const _closed = Symbol("[[closed]]");
const _earlyClose = Symbol("[[earlyClose]]");
const _closeSent = Symbol("[[closeSent]]");
class WebSocketStream {
[_rid];
const _rid = Symbol("[[rid]]"); [_url];
const _url = Symbol("[[url]]"); get url() {
const _connection = Symbol("[[connection]]"); webidl.assertBranded(this, WebSocketStreamPrototype);
const _closed = Symbol("[[closed]]"); return this[_url];
const _earlyClose = Symbol("[[earlyClose]]"); }
const _closeSent = Symbol("[[closeSent]]");
class WebSocketStream {
[_rid];
[_url]; constructor(url, options) {
get url() { this[webidl.brand] = webidl.brand;
webidl.assertBranded(this, WebSocketStreamPrototype); const prefix = "Failed to construct 'WebSocketStream'";
return this[_url]; webidl.requiredArguments(arguments.length, 1, { prefix });
url = webidl.converters.USVString(url, {
prefix,
context: "Argument 1",
});
options = webidl.converters.WebSocketStreamOptions(options, {
prefix,
context: "Argument 2",
});
const wsURL = new URL(url);
if (wsURL.protocol !== "ws:" && wsURL.protocol !== "wss:") {
throw new DOMException(
"Only ws & wss schemes are allowed in a WebSocket URL.",
"SyntaxError",
);
} }
constructor(url, options) { if (wsURL.hash !== "" || StringPrototypeEndsWith(wsURL.href, "#")) {
this[webidl.brand] = webidl.brand; throw new DOMException(
const prefix = "Failed to construct 'WebSocketStream'"; "Fragments are not allowed in a WebSocket URL.",
webidl.requiredArguments(arguments.length, 1, { prefix }); "SyntaxError",
url = webidl.converters.USVString(url, {
prefix,
context: "Argument 1",
});
options = webidl.converters.WebSocketStreamOptions(options, {
prefix,
context: "Argument 2",
});
const wsURL = new URL(url);
if (wsURL.protocol !== "ws:" && wsURL.protocol !== "wss:") {
throw new DOMException(
"Only ws & wss schemes are allowed in a WebSocket URL.",
"SyntaxError",
);
}
if (wsURL.hash !== "" || StringPrototypeEndsWith(wsURL.href, "#")) {
throw new DOMException(
"Fragments are not allowed in a WebSocket URL.",
"SyntaxError",
);
}
this[_url] = wsURL.href;
if (
options.protocols.length !==
new Set(
ArrayPrototypeMap(
options.protocols,
(p) => StringPrototypeToLowerCase(p),
),
).size
) {
throw new DOMException(
"Can't supply multiple times the same protocol.",
"SyntaxError",
);
}
const headers = headersFromHeaderList([], "request");
if (options.headers !== undefined) {
fillHeaders(headers, options.headers);
}
const cancelRid = ops.op_ws_check_permission_and_cancel_handle(
"WebSocketStream.abort()",
this[_url],
true,
); );
}
if (options.signal?.aborted) { this[_url] = wsURL.href;
core.close(cancelRid);
const err = options.signal.reason; if (
this[_connection].reject(err); options.protocols.length !==
this[_closed].reject(err); new Set(
} else { ArrayPrototypeMap(
const abort = () => { options.protocols,
core.close(cancelRid); (p) => StringPrototypeToLowerCase(p),
};
options.signal?.[add](abort);
PromisePrototypeThen(
core.opAsync(
"op_ws_create",
"new WebSocketStream()",
this[_url],
options.protocols
? ArrayPrototypeJoin(options.protocols, ", ")
: "",
cancelRid,
headerListFromHeaders(headers),
), ),
(create) => { ).size
options.signal?.[remove](abort); ) {
if (this[_earlyClose]) { throw new DOMException(
PromisePrototypeThen( "Can't supply multiple times the same protocol.",
core.opAsync("op_ws_close", create.rid), "SyntaxError",
() => { );
PromisePrototypeThen( }
(async () => {
while (true) {
const { kind } = await core.opAsync(
"op_ws_next_event",
create.rid,
);
if (kind === "close") { const headers = headersFromHeaderList([], "request");
break; if (options.headers !== undefined) {
} fillHeaders(headers, options.headers);
} }
})(),
() => { const cancelRid = ops.op_ws_check_permission_and_cancel_handle(
const err = new DOMException( "WebSocketStream.abort()",
"Closed while connecting", this[_url],
"NetworkError", true,
);
if (options.signal?.aborted) {
core.close(cancelRid);
const err = options.signal.reason;
this[_connection].reject(err);
this[_closed].reject(err);
} else {
const abort = () => {
core.close(cancelRid);
};
options.signal?.[add](abort);
PromisePrototypeThen(
core.opAsync(
"op_ws_create",
"new WebSocketStream()",
this[_url],
options.protocols ? ArrayPrototypeJoin(options.protocols, ", ") : "",
cancelRid,
headerListFromHeaders(headers),
),
(create) => {
options.signal?.[remove](abort);
if (this[_earlyClose]) {
PromisePrototypeThen(
core.opAsync("op_ws_close", create.rid),
() => {
PromisePrototypeThen(
(async () => {
while (true) {
const { kind } = await core.opAsync(
"op_ws_next_event",
create.rid,
); );
this[_connection].reject(err);
this[_closed].reject(err);
},
);
},
() => {
const err = new DOMException(
"Closed while connecting",
"NetworkError",
);
this[_connection].reject(err);
this[_closed].reject(err);
},
);
} else {
this[_rid] = create.rid;
const writable = new WritableStream({ if (kind === "close") {
write: async (chunk) => { break;
if (typeof chunk === "string") { }
await core.opAsync("op_ws_send", this[_rid], { }
kind: "text", })(),
value: chunk, () => {
}); const err = new DOMException(
} else if ( "Closed while connecting",
ObjectPrototypeIsPrototypeOf(Uint8ArrayPrototype, chunk) "NetworkError",
) {
await core.opAsync("op_ws_send", this[_rid], {
kind: "binary",
value: chunk,
}, chunk);
} else {
throw new TypeError(
"A chunk may only be either a string or an Uint8Array",
); );
} this[_connection].reject(err);
},
close: async (reason) => {
try {
this.close(reason?.code !== undefined ? reason : {});
} catch (_) {
this.close();
}
await this.closed;
},
abort: async (reason) => {
try {
this.close(reason?.code !== undefined ? reason : {});
} catch (_) {
this.close();
}
await this.closed;
},
});
const pull = async (controller) => {
const { kind, value } = await core.opAsync(
"op_ws_next_event",
this[_rid],
);
switch (kind) {
case "string": {
controller.enqueue(value);
break;
}
case "binary": {
controller.enqueue(value);
break;
}
case "ping": {
await core.opAsync("op_ws_send", this[_rid], {
kind: "pong",
});
await pull(controller);
break;
}
case "closed":
case "close": {
this[_closed].resolve(value);
core.tryClose(this[_rid]);
break;
}
case "error": {
const err = new Error(value);
this[_closed].reject(err); this[_closed].reject(err);
controller.error(err); },
core.tryClose(this[_rid]); );
break; },
} () => {
} const err = new DOMException(
"Closed while connecting",
"NetworkError",
);
this[_connection].reject(err);
this[_closed].reject(err);
},
);
} else {
this[_rid] = create.rid;
if ( const writable = new WritableStream({
this[_closeSent].state === "fulfilled" && write: async (chunk) => {
this[_closed].state === "pending" if (typeof chunk === "string") {
await core.opAsync("op_ws_send", this[_rid], {
kind: "text",
value: chunk,
});
} else if (
ObjectPrototypeIsPrototypeOf(Uint8ArrayPrototype, chunk)
) { ) {
if ( await core.opAsync("op_ws_send", this[_rid], {
new Date().getTime() - await this[_closeSent].promise <= kind: "binary",
CLOSE_RESPONSE_TIMEOUT value: chunk,
) { }, chunk);
return pull(controller); } else {
} throw new TypeError(
"A chunk may only be either a string or an Uint8Array",
);
}
},
close: async (reason) => {
try {
this.close(reason?.code !== undefined ? reason : {});
} catch (_) {
this.close();
}
await this.closed;
},
abort: async (reason) => {
try {
this.close(reason?.code !== undefined ? reason : {});
} catch (_) {
this.close();
}
await this.closed;
},
});
const pull = async (controller) => {
const { kind, value } = await core.opAsync(
"op_ws_next_event",
this[_rid],
);
switch (kind) {
case "string": {
controller.enqueue(value);
break;
}
case "binary": {
controller.enqueue(value);
break;
}
case "ping": {
await core.opAsync("op_ws_send", this[_rid], {
kind: "pong",
});
await pull(controller);
break;
}
case "closed":
case "close": {
this[_closed].resolve(value); this[_closed].resolve(value);
core.tryClose(this[_rid]); core.tryClose(this[_rid]);
break;
} }
}; case "error": {
const readable = new ReadableStream({ const err = new Error(value);
start: (controller) => { this[_closed].reject(err);
PromisePrototypeThen(this.closed, () => { controller.error(err);
try { core.tryClose(this[_rid]);
controller.close(); break;
} catch (_) { }
// needed to ignore warnings & assertions }
}
try {
PromisePrototypeCatch(
writableStreamClose(writable),
() => {},
);
} catch (_) {
// needed to ignore warnings & assertions
}
});
PromisePrototypeThen(this[_closeSent].promise, () => { if (
if (this[_closed].state === "pending") { this[_closeSent].state === "fulfilled" &&
return pull(controller); this[_closed].state === "pending"
} ) {
}); if (
}, new Date().getTime() - await this[_closeSent].promise <=
pull, CLOSE_RESPONSE_TIMEOUT
cancel: async (reason) => { ) {
return pull(controller);
}
this[_closed].resolve(value);
core.tryClose(this[_rid]);
}
};
const readable = new ReadableStream({
start: (controller) => {
PromisePrototypeThen(this.closed, () => {
try { try {
this.close(reason?.code !== undefined ? reason : {}); controller.close();
} catch (_) { } catch (_) {
this.close(); // needed to ignore warnings & assertions
} }
await this.closed; try {
}, PromisePrototypeCatch(
}); writableStreamClose(writable),
() => {},
);
} catch (_) {
// needed to ignore warnings & assertions
}
});
this[_connection].resolve({ PromisePrototypeThen(this[_closeSent].promise, () => {
readable, if (this[_closed].state === "pending") {
writable, return pull(controller);
extensions: create.extensions ?? "", }
protocol: create.protocol ?? "", });
}); },
} pull,
}, cancel: async (reason) => {
(err) => { try {
if (ObjectPrototypeIsPrototypeOf(core.InterruptedPrototype, err)) { this.close(reason?.code !== undefined ? reason : {});
// The signal was aborted. } catch (_) {
err = options.signal.reason; this.close();
} else { }
core.tryClose(cancelRid); await this.closed;
} },
this[_connection].reject(err); });
this[_closed].reject(err);
},
);
}
}
[_connection] = new Deferred(); this[_connection].resolve({
get connection() { readable,
webidl.assertBranded(this, WebSocketStreamPrototype); writable,
return this[_connection].promise; extensions: create.extensions ?? "",
} protocol: create.protocol ?? "",
});
[_earlyClose] = false; }
[_closed] = new Deferred(); },
[_closeSent] = new Deferred(); (err) => {
get closed() { if (ObjectPrototypeIsPrototypeOf(core.InterruptedPrototype, err)) {
webidl.assertBranded(this, WebSocketStreamPrototype); // The signal was aborted.
return this[_closed].promise; err = options.signal.reason;
} } else {
core.tryClose(cancelRid);
close(closeInfo) { }
webidl.assertBranded(this, WebSocketStreamPrototype); this[_connection].reject(err);
closeInfo = webidl.converters.WebSocketCloseInfo(closeInfo, { this[_closed].reject(err);
prefix: "Failed to execute 'close' on 'WebSocketStream'", },
context: "Argument 1", );
});
if (
closeInfo.code &&
!(closeInfo.code === 1000 ||
(3000 <= closeInfo.code && closeInfo.code < 5000))
) {
throw new DOMException(
"The close code must be either 1000 or in the range of 3000 to 4999.",
"InvalidAccessError",
);
}
const encoder = new TextEncoder();
if (
closeInfo.reason && encoder.encode(closeInfo.reason).byteLength > 123
) {
throw new DOMException(
"The close reason may not be longer than 123 bytes.",
"SyntaxError",
);
}
let code = closeInfo.code;
if (closeInfo.reason && code === undefined) {
code = 1000;
}
if (this[_connection].state === "pending") {
this[_earlyClose] = true;
} else if (this[_closed].state === "pending") {
PromisePrototypeThen(
core.opAsync("op_ws_close", this[_rid], code, closeInfo.reason),
() => {
setTimeout(() => {
this[_closeSent].resolve(new Date().getTime());
}, 0);
},
(err) => {
this[_rid] && core.tryClose(this[_rid]);
this[_closed].reject(err);
},
);
}
}
[SymbolFor("Deno.customInspect")](inspect) {
return `${this.constructor.name} ${
inspect({
url: this.url,
})
}`;
} }
} }
const WebSocketStreamPrototype = WebSocketStream.prototype; [_connection] = new Deferred();
get connection() {
webidl.assertBranded(this, WebSocketStreamPrototype);
return this[_connection].promise;
}
window.__bootstrap.webSocket.WebSocketStream = WebSocketStream; [_earlyClose] = false;
})(this); [_closed] = new Deferred();
[_closeSent] = new Deferred();
get closed() {
webidl.assertBranded(this, WebSocketStreamPrototype);
return this[_closed].promise;
}
close(closeInfo) {
webidl.assertBranded(this, WebSocketStreamPrototype);
closeInfo = webidl.converters.WebSocketCloseInfo(closeInfo, {
prefix: "Failed to execute 'close' on 'WebSocketStream'",
context: "Argument 1",
});
if (
closeInfo.code &&
!(closeInfo.code === 1000 ||
(3000 <= closeInfo.code && closeInfo.code < 5000))
) {
throw new DOMException(
"The close code must be either 1000 or in the range of 3000 to 4999.",
"InvalidAccessError",
);
}
const encoder = new TextEncoder();
if (
closeInfo.reason && encoder.encode(closeInfo.reason).byteLength > 123
) {
throw new DOMException(
"The close reason may not be longer than 123 bytes.",
"SyntaxError",
);
}
let code = closeInfo.code;
if (closeInfo.reason && code === undefined) {
code = 1000;
}
if (this[_connection].state === "pending") {
this[_earlyClose] = true;
} else if (this[_closed].state === "pending") {
PromisePrototypeThen(
core.opAsync("op_ws_close", this[_rid], code, closeInfo.reason),
() => {
setTimeout(() => {
this[_closeSent].resolve(new Date().getTime());
}, 0);
},
(err) => {
this[_rid] && core.tryClose(this[_rid]);
this[_closed].reject(err);
},
);
}
}
[SymbolFor("Deno.customInspect")](inspect) {
return `${this.constructor.name} ${
inspect({
url: this.url,
})
}`;
}
}
const WebSocketStreamPrototype = WebSocketStream.prototype;
export { WebSocketStream };

View file

@ -504,7 +504,7 @@ pub fn init<P: WebSocketPermissions + 'static>(
) -> Extension { ) -> Extension {
Extension::builder(env!("CARGO_PKG_NAME")) Extension::builder(env!("CARGO_PKG_NAME"))
.dependencies(vec!["deno_url", "deno_webidl"]) .dependencies(vec!["deno_url", "deno_webidl"])
.js(include_js_files!( .esm(include_js_files!(
prefix "internal:ext/websocket", prefix "internal:ext/websocket",
"01_websocket.js", "01_websocket.js",
"02_websocketstream.js", "02_websocketstream.js",

View file

@ -2,191 +2,189 @@
/// <reference path="../../core/internal.d.ts" /> /// <reference path="../../core/internal.d.ts" />
((window) => { const core = globalThis.Deno.core;
const core = window.Deno.core; const ops = core.ops;
const ops = core.ops; import * as webidl from "internal:ext/webidl/00_webidl.js";
const webidl = window.__bootstrap.webidl; const primordials = globalThis.__bootstrap.primordials;
const { const {
SafeArrayIterator, SafeArrayIterator,
Symbol, Symbol,
SymbolFor, SymbolFor,
ObjectDefineProperty, ObjectDefineProperty,
ObjectFromEntries, ObjectFromEntries,
ObjectEntries, ObjectEntries,
ReflectGet, ReflectGet,
ReflectHas, ReflectHas,
Proxy, Proxy,
} = window.__bootstrap.primordials; } = primordials;
const _persistent = Symbol("[[persistent]]"); const _persistent = Symbol("[[persistent]]");
class Storage { class Storage {
[_persistent]; [_persistent];
constructor() { constructor() {
webidl.illegalConstructor(); webidl.illegalConstructor();
}
get length() {
webidl.assertBranded(this, StoragePrototype);
return ops.op_webstorage_length(this[_persistent]);
}
key(index) {
webidl.assertBranded(this, StoragePrototype);
const prefix = "Failed to execute 'key' on 'Storage'";
webidl.requiredArguments(arguments.length, 1, { prefix });
index = webidl.converters["unsigned long"](index, {
prefix,
context: "Argument 1",
});
return ops.op_webstorage_key(index, this[_persistent]);
}
setItem(key, value) {
webidl.assertBranded(this, StoragePrototype);
const prefix = "Failed to execute 'setItem' on 'Storage'";
webidl.requiredArguments(arguments.length, 2, { prefix });
key = webidl.converters.DOMString(key, {
prefix,
context: "Argument 1",
});
value = webidl.converters.DOMString(value, {
prefix,
context: "Argument 2",
});
ops.op_webstorage_set(key, value, this[_persistent]);
}
getItem(key) {
webidl.assertBranded(this, StoragePrototype);
const prefix = "Failed to execute 'getItem' on 'Storage'";
webidl.requiredArguments(arguments.length, 1, { prefix });
key = webidl.converters.DOMString(key, {
prefix,
context: "Argument 1",
});
return ops.op_webstorage_get(key, this[_persistent]);
}
removeItem(key) {
webidl.assertBranded(this, StoragePrototype);
const prefix = "Failed to execute 'removeItem' on 'Storage'";
webidl.requiredArguments(arguments.length, 1, { prefix });
key = webidl.converters.DOMString(key, {
prefix,
context: "Argument 1",
});
ops.op_webstorage_remove(key, this[_persistent]);
}
clear() {
webidl.assertBranded(this, StoragePrototype);
ops.op_webstorage_clear(this[_persistent]);
}
} }
const StoragePrototype = Storage.prototype; get length() {
webidl.assertBranded(this, StoragePrototype);
return ops.op_webstorage_length(this[_persistent]);
}
function createStorage(persistent) { key(index) {
const storage = webidl.createBranded(Storage); webidl.assertBranded(this, StoragePrototype);
storage[_persistent] = persistent; const prefix = "Failed to execute 'key' on 'Storage'";
webidl.requiredArguments(arguments.length, 1, { prefix });
const proxy = new Proxy(storage, { index = webidl.converters["unsigned long"](index, {
deleteProperty(target, key) { prefix,
if (typeof key == "symbol") { context: "Argument 1",
delete target[key];
} else {
target.removeItem(key);
}
return true;
},
defineProperty(target, key, descriptor) {
if (typeof key == "symbol") {
ObjectDefineProperty(target, key, descriptor);
} else {
target.setItem(key, descriptor.value);
}
return true;
},
get(target, key) {
if (typeof key == "symbol") return target[key];
if (ReflectHas(target, key)) {
return ReflectGet(...new SafeArrayIterator(arguments));
} else {
return target.getItem(key) ?? undefined;
}
},
set(target, key, value) {
if (typeof key == "symbol") {
ObjectDefineProperty(target, key, {
value,
configurable: true,
});
} else {
target.setItem(key, value);
}
return true;
},
has(target, p) {
return p === SymbolFor("Deno.customInspect") ||
(typeof target.getItem(p)) === "string";
},
ownKeys() {
return ops.op_webstorage_iterate_keys(persistent);
},
getOwnPropertyDescriptor(target, key) {
if (arguments.length === 1) {
return undefined;
}
if (ReflectHas(target, key)) {
return undefined;
}
const value = target.getItem(key);
if (value === null) {
return undefined;
}
return {
value,
enumerable: true,
configurable: true,
writable: true,
};
},
}); });
proxy[SymbolFor("Deno.customInspect")] = function (inspect) { return ops.op_webstorage_key(index, this[_persistent]);
return `${this.constructor.name} ${
inspect({
length: this.length,
...ObjectFromEntries(ObjectEntries(proxy)),
})
}`;
};
return proxy;
} }
let localStorage; setItem(key, value) {
let sessionStorage; webidl.assertBranded(this, StoragePrototype);
const prefix = "Failed to execute 'setItem' on 'Storage'";
webidl.requiredArguments(arguments.length, 2, { prefix });
key = webidl.converters.DOMString(key, {
prefix,
context: "Argument 1",
});
value = webidl.converters.DOMString(value, {
prefix,
context: "Argument 2",
});
window.__bootstrap.webStorage = { ops.op_webstorage_set(key, value, this[_persistent]);
localStorage() { }
if (!localStorage) {
localStorage = createStorage(true); getItem(key) {
webidl.assertBranded(this, StoragePrototype);
const prefix = "Failed to execute 'getItem' on 'Storage'";
webidl.requiredArguments(arguments.length, 1, { prefix });
key = webidl.converters.DOMString(key, {
prefix,
context: "Argument 1",
});
return ops.op_webstorage_get(key, this[_persistent]);
}
removeItem(key) {
webidl.assertBranded(this, StoragePrototype);
const prefix = "Failed to execute 'removeItem' on 'Storage'";
webidl.requiredArguments(arguments.length, 1, { prefix });
key = webidl.converters.DOMString(key, {
prefix,
context: "Argument 1",
});
ops.op_webstorage_remove(key, this[_persistent]);
}
clear() {
webidl.assertBranded(this, StoragePrototype);
ops.op_webstorage_clear(this[_persistent]);
}
}
const StoragePrototype = Storage.prototype;
function createStorage(persistent) {
const storage = webidl.createBranded(Storage);
storage[_persistent] = persistent;
const proxy = new Proxy(storage, {
deleteProperty(target, key) {
if (typeof key == "symbol") {
delete target[key];
} else {
target.removeItem(key);
} }
return localStorage; return true;
}, },
sessionStorage() { defineProperty(target, key, descriptor) {
if (!sessionStorage) { if (typeof key == "symbol") {
sessionStorage = createStorage(false); ObjectDefineProperty(target, key, descriptor);
} else {
target.setItem(key, descriptor.value);
} }
return sessionStorage; return true;
}, },
Storage, get(target, key) {
if (typeof key == "symbol") return target[key];
if (ReflectHas(target, key)) {
return ReflectGet(...new SafeArrayIterator(arguments));
} else {
return target.getItem(key) ?? undefined;
}
},
set(target, key, value) {
if (typeof key == "symbol") {
ObjectDefineProperty(target, key, {
value,
configurable: true,
});
} else {
target.setItem(key, value);
}
return true;
},
has(target, p) {
return p === SymbolFor("Deno.customInspect") ||
(typeof target.getItem(p)) === "string";
},
ownKeys() {
return ops.op_webstorage_iterate_keys(persistent);
},
getOwnPropertyDescriptor(target, key) {
if (arguments.length === 1) {
return undefined;
}
if (ReflectHas(target, key)) {
return undefined;
}
const value = target.getItem(key);
if (value === null) {
return undefined;
}
return {
value,
enumerable: true,
configurable: true,
writable: true,
};
},
});
proxy[SymbolFor("Deno.customInspect")] = function (inspect) {
return `${this.constructor.name} ${
inspect({
length: this.length,
...ObjectFromEntries(ObjectEntries(proxy)),
})
}`;
}; };
})(this);
return proxy;
}
let localStorageStorage;
function localStorage() {
if (!localStorageStorage) {
localStorageStorage = createStorage(true);
}
return localStorageStorage;
}
let sessionStorageStorage;
function sessionStorage() {
if (!sessionStorageStorage) {
sessionStorageStorage = createStorage(false);
}
return sessionStorageStorage;
}
export { localStorage, sessionStorage, Storage };

View file

@ -24,7 +24,7 @@ const MAX_STORAGE_BYTES: u32 = 10 * 1024 * 1024;
pub fn init(origin_storage_dir: Option<PathBuf>) -> Extension { pub fn init(origin_storage_dir: Option<PathBuf>) -> Extension {
Extension::builder(env!("CARGO_PKG_NAME")) Extension::builder(env!("CARGO_PKG_NAME"))
.dependencies(vec!["deno_webidl"]) .dependencies(vec!["deno_webidl"])
.js(include_js_files!( .esm(include_js_files!(
prefix "internal:ext/webstorage", prefix "internal:ext/webstorage",
"01_webstorage.js", "01_webstorage.js",
)) ))

View file

@ -5,7 +5,6 @@ use std::env;
use std::path::PathBuf; use std::path::PathBuf;
// This is a shim that allows to generate documentation on docs.rs // This is a shim that allows to generate documentation on docs.rs
#[cfg(not(feature = "docsrs"))]
mod not_docs { mod not_docs {
use std::path::Path; use std::path::Path;
@ -121,7 +120,7 @@ mod not_docs {
} }
} }
fn create_runtime_snapshot(snapshot_path: PathBuf, files: Vec<PathBuf>) { fn create_runtime_snapshot(snapshot_path: PathBuf, esm_files: Vec<PathBuf>) {
let extensions_with_js: Vec<Extension> = vec![ let extensions_with_js: Vec<Extension> = vec![
deno_webidl::init(), deno_webidl::init(),
deno_console::init(), deno_console::init(),
@ -158,7 +157,8 @@ mod not_docs {
startup_snapshot: None, startup_snapshot: None,
extensions: vec![], extensions: vec![],
extensions_with_js, extensions_with_js,
additional_files: files, additional_files: vec![],
additional_esm_files: esm_files,
compression_cb: Some(Box::new(|vec, snapshot_slice| { compression_cb: Some(Box::new(|vec, snapshot_slice| {
lzzzz::lz4_hc::compress_to_vec( lzzzz::lz4_hc::compress_to_vec(
snapshot_slice, snapshot_slice,
@ -172,14 +172,19 @@ mod not_docs {
pub fn build_snapshot(runtime_snapshot_path: PathBuf) { pub fn build_snapshot(runtime_snapshot_path: PathBuf) {
#[allow(unused_mut)] #[allow(unused_mut)]
let mut js_files = get_js_files(env!("CARGO_MANIFEST_DIR"), "js"); let mut esm_files = get_js_files(
env!("CARGO_MANIFEST_DIR"),
"js",
Some(Box::new(|path| !path.ends_with("99_main.js"))),
);
#[cfg(not(feature = "snapshot_from_snapshot"))] #[cfg(not(feature = "snapshot_from_snapshot"))]
{ {
let manifest = env!("CARGO_MANIFEST_DIR"); let manifest = env!("CARGO_MANIFEST_DIR");
let path = PathBuf::from(manifest); let path = PathBuf::from(manifest);
js_files.push(path.join("js").join("99_main.js")); esm_files.push(path.join("js").join("99_main.js"));
} }
create_runtime_snapshot(runtime_snapshot_path, js_files); create_runtime_snapshot(runtime_snapshot_path, esm_files);
} }
} }

View file

@ -1,33 +1,28 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
"use strict";
((window) => { const primordials = globalThis.__bootstrap.primordials;
const { ObjectFreeze, StringPrototypeSplit } = window.__bootstrap.primordials; const { ObjectFreeze, StringPrototypeSplit } = primordials;
const build = { const build = {
target: "unknown", target: "unknown",
arch: "unknown", arch: "unknown",
os: "unknown", os: "unknown",
vendor: "unknown", vendor: "unknown",
env: undefined, env: undefined,
}; };
function setBuildInfo(target) { function setBuildInfo(target) {
const { 0: arch, 1: vendor, 2: os, 3: env } = StringPrototypeSplit( const { 0: arch, 1: vendor, 2: os, 3: env } = StringPrototypeSplit(
target, target,
"-", "-",
4, 4,
); );
build.target = target; build.target = target;
build.arch = arch; build.arch = arch;
build.vendor = vendor; build.vendor = vendor;
build.os = os; build.os = os;
build.env = env; build.env = env;
ObjectFreeze(build); ObjectFreeze(build);
} }
window.__bootstrap.build = { export { build, setBuildInfo };
build,
setBuildInfo,
};
})(this);

View file

@ -1,153 +1,149 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
"use strict";
((window) => { const core = globalThis.Deno.core;
const core = window.Deno.core; const { BadResource, Interrupted } = core;
const { Error } = window.__bootstrap.primordials; const primordials = globalThis.__bootstrap.primordials;
const { BadResource, Interrupted } = core; const { Error } = primordials;
class NotFound extends Error { class NotFound extends Error {
constructor(msg) { constructor(msg) {
super(msg); super(msg);
this.name = "NotFound"; this.name = "NotFound";
}
} }
}
class PermissionDenied extends Error { class PermissionDenied extends Error {
constructor(msg) { constructor(msg) {
super(msg); super(msg);
this.name = "PermissionDenied"; this.name = "PermissionDenied";
}
} }
}
class ConnectionRefused extends Error { class ConnectionRefused extends Error {
constructor(msg) { constructor(msg) {
super(msg); super(msg);
this.name = "ConnectionRefused"; this.name = "ConnectionRefused";
}
} }
}
class ConnectionReset extends Error { class ConnectionReset extends Error {
constructor(msg) { constructor(msg) {
super(msg); super(msg);
this.name = "ConnectionReset"; this.name = "ConnectionReset";
}
} }
}
class ConnectionAborted extends Error { class ConnectionAborted extends Error {
constructor(msg) { constructor(msg) {
super(msg); super(msg);
this.name = "ConnectionAborted"; this.name = "ConnectionAborted";
}
} }
}
class NotConnected extends Error { class NotConnected extends Error {
constructor(msg) { constructor(msg) {
super(msg); super(msg);
this.name = "NotConnected"; this.name = "NotConnected";
}
} }
}
class AddrInUse extends Error { class AddrInUse extends Error {
constructor(msg) { constructor(msg) {
super(msg); super(msg);
this.name = "AddrInUse"; this.name = "AddrInUse";
}
} }
}
class AddrNotAvailable extends Error { class AddrNotAvailable extends Error {
constructor(msg) { constructor(msg) {
super(msg); super(msg);
this.name = "AddrNotAvailable"; this.name = "AddrNotAvailable";
}
} }
}
class BrokenPipe extends Error { class BrokenPipe extends Error {
constructor(msg) { constructor(msg) {
super(msg); super(msg);
this.name = "BrokenPipe"; this.name = "BrokenPipe";
}
} }
}
class AlreadyExists extends Error { class AlreadyExists extends Error {
constructor(msg) { constructor(msg) {
super(msg); super(msg);
this.name = "AlreadyExists"; this.name = "AlreadyExists";
}
} }
}
class InvalidData extends Error { class InvalidData extends Error {
constructor(msg) { constructor(msg) {
super(msg); super(msg);
this.name = "InvalidData"; this.name = "InvalidData";
}
} }
}
class TimedOut extends Error { class TimedOut extends Error {
constructor(msg) { constructor(msg) {
super(msg); super(msg);
this.name = "TimedOut"; this.name = "TimedOut";
}
} }
}
class WriteZero extends Error { class WriteZero extends Error {
constructor(msg) { constructor(msg) {
super(msg); super(msg);
this.name = "WriteZero"; this.name = "WriteZero";
}
} }
}
class UnexpectedEof extends Error { class UnexpectedEof extends Error {
constructor(msg) { constructor(msg) {
super(msg); super(msg);
this.name = "UnexpectedEof"; this.name = "UnexpectedEof";
}
} }
}
class Http extends Error { class Http extends Error {
constructor(msg) { constructor(msg) {
super(msg); super(msg);
this.name = "Http"; this.name = "Http";
}
} }
}
class Busy extends Error { class Busy extends Error {
constructor(msg) { constructor(msg) {
super(msg); super(msg);
this.name = "Busy"; this.name = "Busy";
}
} }
}
class NotSupported extends Error { class NotSupported extends Error {
constructor(msg) { constructor(msg) {
super(msg); super(msg);
this.name = "NotSupported"; this.name = "NotSupported";
}
} }
}
const errors = { const errors = {
NotFound, NotFound,
PermissionDenied, PermissionDenied,
ConnectionRefused, ConnectionRefused,
ConnectionReset, ConnectionReset,
ConnectionAborted, ConnectionAborted,
NotConnected, NotConnected,
AddrInUse, AddrInUse,
AddrNotAvailable, AddrNotAvailable,
BrokenPipe, BrokenPipe,
AlreadyExists, AlreadyExists,
InvalidData, InvalidData,
TimedOut, TimedOut,
Interrupted, Interrupted,
WriteZero, WriteZero,
UnexpectedEof, UnexpectedEof,
BadResource, BadResource,
Http, Http,
Busy, Busy,
NotSupported, NotSupported,
}; };
window.__bootstrap.errors = { export { errors };
errors,
};
})(this);

View file

@ -1,29 +1,24 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
"use strict";
((window) => { const primordials = globalThis.__bootstrap.primordials;
const { ObjectFreeze } = window.__bootstrap.primordials; const { ObjectFreeze } = primordials;
const version = { const version = {
deno: "", deno: "",
v8: "", v8: "",
typescript: "", typescript: "",
}; };
function setVersions( function setVersions(
denoVersion, denoVersion,
v8Version, v8Version,
tsVersion, tsVersion,
) { ) {
version.deno = denoVersion; version.deno = denoVersion;
version.v8 = v8Version; version.v8 = v8Version;
version.typescript = tsVersion; version.typescript = tsVersion;
ObjectFreeze(version); ObjectFreeze(version);
} }
window.__bootstrap.version = { export { setVersions, version };
version,
setVersions,
};
})(this);

View file

@ -1,25 +0,0 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
"use strict";
((window) => {
const { TypeError, Symbol } = window.__bootstrap.primordials;
const illegalConstructorKey = Symbol("illegalConstructorKey");
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);
}
}
window.__bootstrap.webUtil = {
illegalConstructorKey,
requiredArguments,
};
})(this);

View file

@ -1,150 +1,147 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
"use strict";
((window) => { const internals = globalThis.__bootstrap.internals;
const { const primordials = globalThis.__bootstrap.primordials;
decodeURIComponent, const {
ObjectPrototypeIsPrototypeOf, decodeURIComponent,
Promise, ObjectPrototypeIsPrototypeOf,
SafeArrayIterator, Promise,
StringPrototypeReplace, SafeArrayIterator,
TypeError, StringPrototypeReplace,
} = window.__bootstrap.primordials; TypeError,
const { build } = window.__bootstrap.build; } = primordials;
const { URLPrototype } = window.__bootstrap.url; import { build } from "internal:runtime/js/01_build.js";
let logDebug = false; import { URLPrototype } from "internal:ext/url/00_url.js";
let logSource = "JS"; let logDebug = false;
let logSource = "JS";
function setLogDebug(debug, source) { function setLogDebug(debug, source) {
logDebug = debug; logDebug = debug;
if (source) { if (source) {
logSource = source; logSource = source;
}
} }
}
function log(...args) { function log(...args) {
if (logDebug) { if (logDebug) {
// if we destructure `console` off `globalThis` too early, we don't bind to // if we destructure `console` off `globalThis` too early, we don't bind to
// the right console, therefore we don't log anything out. // the right console, therefore we don't log anything out.
globalThis.console.log( globalThis.console.log(
`DEBUG ${logSource} -`, `DEBUG ${logSource} -`,
...new SafeArrayIterator(args), ...new SafeArrayIterator(args),
);
}
}
function createResolvable() {
let resolve;
let reject;
const promise = new Promise((res, rej) => {
resolve = res;
reject = rej;
});
promise.resolve = resolve;
promise.reject = reject;
return promise;
}
// Keep in sync with `fromFileUrl()` in `std/path/win32.ts`.
function pathFromURLWin32(url) {
let p = StringPrototypeReplace(
url.pathname,
/^\/*([A-Za-z]:)(\/|$)/,
"$1/",
);
p = StringPrototypeReplace(
p,
/\//g,
"\\",
);
p = StringPrototypeReplace(
p,
/%(?![0-9A-Fa-f]{2})/g,
"%25",
);
let path = decodeURIComponent(p);
if (url.hostname != "") {
// Note: The `URL` implementation guarantees that the drive letter and
// hostname are mutually exclusive. Otherwise it would not have been valid
// to append the hostname and path like this.
path = `\\\\${url.hostname}${path}`;
}
return path;
}
// Keep in sync with `fromFileUrl()` in `std/path/posix.ts`.
function pathFromURLPosix(url) {
if (url.hostname !== "") {
throw new TypeError(`Host must be empty.`);
}
return decodeURIComponent(
StringPrototypeReplace(url.pathname, /%(?![0-9A-Fa-f]{2})/g, "%25"),
); );
} }
}
function pathFromURL(pathOrUrl) { function createResolvable() {
if (ObjectPrototypeIsPrototypeOf(URLPrototype, pathOrUrl)) { let resolve;
if (pathOrUrl.protocol != "file:") { let reject;
throw new TypeError("Must be a file URL."); const promise = new Promise((res, rej) => {
} resolve = res;
reject = rej;
});
promise.resolve = resolve;
promise.reject = reject;
return promise;
}
return build.os == "windows" // Keep in sync with `fromFileUrl()` in `std/path/win32.ts`.
? pathFromURLWin32(pathOrUrl) function pathFromURLWin32(url) {
: pathFromURLPosix(pathOrUrl); let p = StringPrototypeReplace(
} url.pathname,
return pathOrUrl; /^\/*([A-Za-z]:)(\/|$)/,
"$1/",
);
p = StringPrototypeReplace(
p,
/\//g,
"\\",
);
p = StringPrototypeReplace(
p,
/%(?![0-9A-Fa-f]{2})/g,
"%25",
);
let path = decodeURIComponent(p);
if (url.hostname != "") {
// Note: The `URL` implementation guarantees that the drive letter and
// hostname are mutually exclusive. Otherwise it would not have been valid
// to append the hostname and path like this.
path = `\\\\${url.hostname}${path}`;
}
return path;
}
// Keep in sync with `fromFileUrl()` in `std/path/posix.ts`.
function pathFromURLPosix(url) {
if (url.hostname !== "") {
throw new TypeError(`Host must be empty.`);
} }
window.__bootstrap.internals = { return decodeURIComponent(
...window.__bootstrap.internals ?? {}, StringPrototypeReplace(url.pathname, /%(?![0-9A-Fa-f]{2})/g, "%25"),
pathFromURL, );
}
function pathFromURL(pathOrUrl) {
if (ObjectPrototypeIsPrototypeOf(URLPrototype, pathOrUrl)) {
if (pathOrUrl.protocol != "file:") {
throw new TypeError("Must be a file URL.");
}
return build.os == "windows"
? pathFromURLWin32(pathOrUrl)
: pathFromURLPosix(pathOrUrl);
}
return pathOrUrl;
}
// TODO(bartlomieju): remove
internals.pathFromURL = pathFromURL;
function writable(value) {
return {
value,
writable: true,
enumerable: true,
configurable: true,
}; };
}
function writable(value) { function nonEnumerable(value) {
return { return {
value, value,
writable: true, writable: true,
enumerable: true, enumerable: false,
configurable: true, configurable: true,
};
}
function nonEnumerable(value) {
return {
value,
writable: true,
enumerable: false,
configurable: true,
};
}
function readOnly(value) {
return {
value,
enumerable: true,
writable: false,
configurable: true,
};
}
function getterOnly(getter) {
return {
get: getter,
set() {},
enumerable: true,
configurable: true,
};
}
window.__bootstrap.util = {
log,
setLogDebug,
createResolvable,
pathFromURL,
writable,
nonEnumerable,
readOnly,
getterOnly,
}; };
})(this); }
function readOnly(value) {
return {
value,
enumerable: true,
writable: false,
configurable: true,
};
}
function getterOnly(getter) {
return {
get: getter,
set() {},
enumerable: true,
configurable: true,
};
}
export {
createResolvable,
getterOnly,
log,
nonEnumerable,
pathFromURL,
readOnly,
setLogDebug,
writable,
};

View file

@ -1,287 +1,282 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
"use strict";
((window) => { const core = globalThis.Deno.core;
const { ops } = Deno.core; const ops = core.ops;
const { Event } = window.__bootstrap.event; import { Event, EventTarget } from "internal:ext/web/02_event.js";
const { EventTarget } = window.__bootstrap.eventTarget; import { pathFromURL } from "internal:runtime/js/06_util.js";
const { pathFromURL } = window.__bootstrap.util; const primordials = globalThis.__bootstrap.primordials;
const { illegalConstructorKey } = window.__bootstrap.webUtil; const {
const { ArrayIsArray,
ArrayIsArray, ArrayPrototypeIncludes,
ArrayPrototypeIncludes, ArrayPrototypeMap,
ArrayPrototypeMap, ArrayPrototypeSlice,
ArrayPrototypeSlice, Map,
Map, MapPrototypeGet,
MapPrototypeGet, MapPrototypeHas,
MapPrototypeHas, MapPrototypeSet,
MapPrototypeSet, FunctionPrototypeCall,
FunctionPrototypeCall, PromiseResolve,
PromiseResolve, PromiseReject,
PromiseReject, ReflectHas,
ReflectHas, SafeArrayIterator,
SafeArrayIterator, Symbol,
SymbolFor, SymbolFor,
TypeError, TypeError,
} = window.__bootstrap.primordials; } = primordials;
/** const illegalConstructorKey = Symbol("illegalConstructorKey");
* @typedef StatusCacheValue
* @property {PermissionState} state
* @property {PermissionStatus} status
*/
/** @type {ReadonlyArray<"read" | "write" | "net" | "env" | "sys" | "run" | "ffi" | "hrtime">} */ /**
const permissionNames = [ * @typedef StatusCacheValue
"read", * @property {PermissionState} state
"write", * @property {PermissionStatus} status
"net", */
"env",
"sys",
"run",
"ffi",
"hrtime",
];
/** /** @type {ReadonlyArray<"read" | "write" | "net" | "env" | "sys" | "run" | "ffi" | "hrtime">} */
* @param {Deno.PermissionDescriptor} desc const permissionNames = [
* @returns {Deno.PermissionState} "read",
*/ "write",
function opQuery(desc) { "net",
return ops.op_query_permission(desc); "env",
"sys",
"run",
"ffi",
"hrtime",
];
/**
* @param {Deno.PermissionDescriptor} desc
* @returns {Deno.PermissionState}
*/
function opQuery(desc) {
return ops.op_query_permission(desc);
}
/**
* @param {Deno.PermissionDescriptor} desc
* @returns {Deno.PermissionState}
*/
function opRevoke(desc) {
return ops.op_revoke_permission(desc);
}
/**
* @param {Deno.PermissionDescriptor} desc
* @returns {Deno.PermissionState}
*/
function opRequest(desc) {
return ops.op_request_permission(desc);
}
class PermissionStatus extends EventTarget {
/** @type {{ state: Deno.PermissionState }} */
#state;
/** @type {((this: PermissionStatus, event: Event) => any) | null} */
onchange = null;
/** @returns {Deno.PermissionState} */
get state() {
return this.#state.state;
} }
/** /**
* @param {Deno.PermissionDescriptor} desc * @param {{ state: Deno.PermissionState }} state
* @returns {Deno.PermissionState} * @param {unknown} key
*/ */
function opRevoke(desc) { constructor(state = null, key = null) {
return ops.op_revoke_permission(desc); if (key != illegalConstructorKey) {
throw new TypeError("Illegal constructor.");
}
super();
this.#state = state;
} }
/** /**
* @param {Deno.PermissionDescriptor} desc * @param {Event} event
* @returns {Deno.PermissionState} * @returns {boolean}
*/ */
function opRequest(desc) { dispatchEvent(event) {
return ops.op_request_permission(desc); let dispatched = super.dispatchEvent(event);
if (dispatched && this.onchange) {
FunctionPrototypeCall(this.onchange, this, event);
dispatched = !event.defaultPrevented;
}
return dispatched;
} }
class PermissionStatus extends EventTarget { [SymbolFor("Deno.privateCustomInspect")](inspect) {
/** @type {{ state: Deno.PermissionState }} */ return `${this.constructor.name} ${
#state; inspect({ state: this.state, onchange: this.onchange })
}`;
/** @type {((this: PermissionStatus, event: Event) => any) | null} */
onchange = null;
/** @returns {Deno.PermissionState} */
get state() {
return this.#state.state;
}
/**
* @param {{ state: Deno.PermissionState }} state
* @param {unknown} key
*/
constructor(state = null, key = null) {
if (key != illegalConstructorKey) {
throw new TypeError("Illegal constructor.");
}
super();
this.#state = state;
}
/**
* @param {Event} event
* @returns {boolean}
*/
dispatchEvent(event) {
let dispatched = super.dispatchEvent(event);
if (dispatched && this.onchange) {
FunctionPrototypeCall(this.onchange, this, event);
dispatched = !event.defaultPrevented;
}
return dispatched;
}
[SymbolFor("Deno.privateCustomInspect")](inspect) {
return `${this.constructor.name} ${
inspect({ state: this.state, onchange: this.onchange })
}`;
}
} }
}
/** @type {Map<string, StatusCacheValue>} */ /** @type {Map<string, StatusCacheValue>} */
const statusCache = new Map(); const statusCache = new Map();
/** /**
* @param {Deno.PermissionDescriptor} desc * @param {Deno.PermissionDescriptor} desc
* @param {Deno.PermissionState} state * @param {Deno.PermissionState} state
* @returns {PermissionStatus} * @returns {PermissionStatus}
*/ */
function cache(desc, state) { function cache(desc, state) {
let { name: key } = desc; let { name: key } = desc;
if ( if (
(desc.name === "read" || desc.name === "write" || desc.name === "ffi") && (desc.name === "read" || desc.name === "write" || desc.name === "ffi") &&
ReflectHas(desc, "path") ReflectHas(desc, "path")
) { ) {
key += `-${desc.path}&`; key += `-${desc.path}&`;
} else if (desc.name === "net" && desc.host) { } else if (desc.name === "net" && desc.host) {
key += `-${desc.host}&`; key += `-${desc.host}&`;
} else if (desc.name === "run" && desc.command) { } else if (desc.name === "run" && desc.command) {
key += `-${desc.command}&`; key += `-${desc.command}&`;
} else if (desc.name === "env" && desc.variable) { } else if (desc.name === "env" && desc.variable) {
key += `-${desc.variable}&`; key += `-${desc.variable}&`;
} else if (desc.name === "sys" && desc.kind) { } else if (desc.name === "sys" && desc.kind) {
key += `-${desc.kind}&`; key += `-${desc.kind}&`;
} else { } else {
key += "$"; key += "$";
}
if (MapPrototypeHas(statusCache, key)) {
const status = MapPrototypeGet(statusCache, key);
if (status.state !== state) {
status.state = state;
status.status.dispatchEvent(new Event("change", { cancelable: false }));
} }
if (MapPrototypeHas(statusCache, key)) {
const status = MapPrototypeGet(statusCache, key);
if (status.state !== state) {
status.state = state;
status.status.dispatchEvent(new Event("change", { cancelable: false }));
}
return status.status;
}
/** @type {{ state: Deno.PermissionState; status?: PermissionStatus }} */
const status = { state };
status.status = new PermissionStatus(status, illegalConstructorKey);
MapPrototypeSet(statusCache, key, status);
return status.status; return status.status;
} }
/** @type {{ state: Deno.PermissionState; status?: PermissionStatus }} */
const status = { state };
status.status = new PermissionStatus(status, illegalConstructorKey);
MapPrototypeSet(statusCache, key, status);
return status.status;
}
/** /**
* @param {unknown} desc * @param {unknown} desc
* @returns {desc is Deno.PermissionDescriptor} * @returns {desc is Deno.PermissionDescriptor}
*/ */
function isValidDescriptor(desc) { function isValidDescriptor(desc) {
return typeof desc === "object" && desc !== null && return typeof desc === "object" && desc !== null &&
ArrayPrototypeIncludes(permissionNames, desc.name); ArrayPrototypeIncludes(permissionNames, desc.name);
}
/**
* @param {Deno.PermissionDescriptor} desc
* @returns {desc is Deno.PermissionDescriptor}
*/
function formDescriptor(desc) {
if (
desc.name === "read" || desc.name === "write" || desc.name === "ffi"
) {
desc.path = pathFromURL(desc.path);
} else if (desc.name === "run") {
desc.command = pathFromURL(desc.command);
}
}
class Permissions {
constructor(key = null) {
if (key != illegalConstructorKey) {
throw new TypeError("Illegal constructor.");
}
} }
/** query(desc) {
* @param {Deno.PermissionDescriptor} desc try {
* @returns {desc is Deno.PermissionDescriptor} return PromiseResolve(this.querySync(desc));
*/ } catch (error) {
function formDescriptor(desc) { return PromiseReject(error);
if ( }
desc.name === "read" || desc.name === "write" || desc.name === "ffi" }
querySync(desc) {
if (!isValidDescriptor(desc)) {
throw new TypeError(
`The provided value "${desc?.name}" is not a valid permission name.`,
);
}
formDescriptor(desc);
const state = opQuery(desc);
return cache(desc, state);
}
revoke(desc) {
try {
return PromiseResolve(this.revokeSync(desc));
} catch (error) {
return PromiseReject(error);
}
}
revokeSync(desc) {
if (!isValidDescriptor(desc)) {
throw new TypeError(
`The provided value "${desc?.name}" is not a valid permission name.`,
);
}
formDescriptor(desc);
const state = opRevoke(desc);
return cache(desc, state);
}
request(desc) {
try {
return PromiseResolve(this.requestSync(desc));
} catch (error) {
return PromiseReject(error);
}
}
requestSync(desc) {
if (!isValidDescriptor(desc)) {
throw new TypeError(
`The provided value "${desc?.name}" is not a valid permission name.`,
);
}
formDescriptor(desc);
const state = opRequest(desc);
return cache(desc, state);
}
}
const permissions = new Permissions(illegalConstructorKey);
/** Converts all file URLs in FS allowlists to paths. */
function serializePermissions(permissions) {
if (typeof permissions == "object" && permissions != null) {
const serializedPermissions = {};
for (
const key of new SafeArrayIterator(["read", "write", "run", "ffi"])
) { ) {
desc.path = pathFromURL(desc.path); if (ArrayIsArray(permissions[key])) {
} else if (desc.name === "run") { serializedPermissions[key] = ArrayPrototypeMap(
desc.command = pathFromURL(desc.command); permissions[key],
} (path) => pathFromURL(path),
}
class Permissions {
constructor(key = null) {
if (key != illegalConstructorKey) {
throw new TypeError("Illegal constructor.");
}
}
query(desc) {
try {
return PromiseResolve(this.querySync(desc));
} catch (error) {
return PromiseReject(error);
}
}
querySync(desc) {
if (!isValidDescriptor(desc)) {
throw new TypeError(
`The provided value "${desc?.name}" is not a valid permission name.`,
); );
} } else {
serializedPermissions[key] = permissions[key];
formDescriptor(desc);
const state = opQuery(desc);
return cache(desc, state);
}
revoke(desc) {
try {
return PromiseResolve(this.revokeSync(desc));
} catch (error) {
return PromiseReject(error);
} }
} }
for (
revokeSync(desc) { const key of new SafeArrayIterator(["env", "hrtime", "net", "sys"])
if (!isValidDescriptor(desc)) { ) {
throw new TypeError( if (ArrayIsArray(permissions[key])) {
`The provided value "${desc?.name}" is not a valid permission name.`, serializedPermissions[key] = ArrayPrototypeSlice(permissions[key]);
); } else {
} serializedPermissions[key] = permissions[key];
formDescriptor(desc);
const state = opRevoke(desc);
return cache(desc, state);
}
request(desc) {
try {
return PromiseResolve(this.requestSync(desc));
} catch (error) {
return PromiseReject(error);
} }
} }
return serializedPermissions;
requestSync(desc) {
if (!isValidDescriptor(desc)) {
throw new TypeError(
`The provided value "${desc?.name}" is not a valid permission name.`,
);
}
formDescriptor(desc);
const state = opRequest(desc);
return cache(desc, state);
}
} }
return permissions;
}
const permissions = new Permissions(illegalConstructorKey); export { Permissions, permissions, PermissionStatus, serializePermissions };
/** Converts all file URLs in FS allowlists to paths. */
function serializePermissions(permissions) {
if (typeof permissions == "object" && permissions != null) {
const serializedPermissions = {};
for (
const key of new SafeArrayIterator(["read", "write", "run", "ffi"])
) {
if (ArrayIsArray(permissions[key])) {
serializedPermissions[key] = ArrayPrototypeMap(
permissions[key],
(path) => pathFromURL(path),
);
} else {
serializedPermissions[key] = permissions[key];
}
}
for (
const key of new SafeArrayIterator(["env", "hrtime", "net", "sys"])
) {
if (ArrayIsArray(permissions[key])) {
serializedPermissions[key] = ArrayPrototypeSlice(permissions[key]);
} else {
serializedPermissions[key] = permissions[key];
}
}
return serializedPermissions;
}
return permissions;
}
window.__bootstrap.permissions = {
serializePermissions,
permissions,
Permissions,
PermissionStatus,
};
})(this);

View file

@ -1,254 +1,253 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
"use strict";
((window) => { const core = globalThis.Deno.core;
const core = window.Deno.core; const ops = core.ops;
const ops = core.ops; const primordials = globalThis.__bootstrap.primordials;
const { const {
Error, Error,
ObjectPrototypeIsPrototypeOf, ObjectPrototypeIsPrototypeOf,
StringPrototypeStartsWith, StringPrototypeStartsWith,
String, String,
SymbolIterator, SymbolIterator,
SymbolToStringTag, SymbolToStringTag,
} = window.__bootstrap.primordials; } = primordials;
const webidl = window.__bootstrap.webidl; import * as webidl from "internal:ext/webidl/00_webidl.js";
const { URL } = window.__bootstrap.url; import { URL } from "internal:ext/url/00_url.js";
const { getLocationHref } = window.__bootstrap.location; import { getLocationHref } from "internal:ext/web/12_location.js";
const { serializePermissions } = window.__bootstrap.permissions; import { serializePermissions } from "internal:runtime/js/10_permissions.js";
const { log } = window.__bootstrap.util; import { log } from "internal:runtime/js/06_util.js";
const { ErrorEvent, MessageEvent, defineEventHandler } = import {
window.__bootstrap.event; defineEventHandler,
const { EventTarget } = window.__bootstrap.eventTarget; ErrorEvent,
const { EventTarget,
deserializeJsMessageData, MessageEvent,
serializeJsMessageData, } from "internal:ext/web/02_event.js";
MessagePortPrototype, import {
} = window.__bootstrap.messagePort; deserializeJsMessageData,
MessagePortPrototype,
serializeJsMessageData,
} from "internal:ext/web/13_message_port.js";
function createWorker( function createWorker(
specifier, specifier,
hasSourceCode,
sourceCode,
permissions,
name,
workerType,
) {
return ops.op_create_worker({
hasSourceCode, hasSourceCode,
sourceCode,
permissions,
name, name,
permissions: serializePermissions(permissions),
sourceCode,
specifier,
workerType, workerType,
) { });
return ops.op_create_worker({ }
hasSourceCode,
function hostTerminateWorker(id) {
ops.op_host_terminate_worker(id);
}
function hostPostMessage(id, data) {
ops.op_host_post_message(id, data);
}
function hostRecvCtrl(id) {
return core.opAsync("op_host_recv_ctrl", id);
}
function hostRecvMessage(id) {
return core.opAsync("op_host_recv_message", id);
}
class Worker extends EventTarget {
#id = 0;
#name = "";
// "RUNNING" | "CLOSED" | "TERMINATED"
// "TERMINATED" means that any controls or messages received will be
// discarded. "CLOSED" means that we have received a control
// indicating that the worker is no longer running, but there might
// still be messages left to receive.
#status = "RUNNING";
constructor(specifier, options = {}) {
super();
specifier = String(specifier);
const {
deno,
name, name,
permissions: serializePermissions(permissions), type = "classic",
sourceCode, } = options;
const workerType = webidl.converters["WorkerType"](type);
if (
StringPrototypeStartsWith(specifier, "./") ||
StringPrototypeStartsWith(specifier, "../") ||
StringPrototypeStartsWith(specifier, "/") || workerType === "classic"
) {
const baseUrl = getLocationHref();
if (baseUrl != null) {
specifier = new URL(specifier, baseUrl).href;
}
}
this.#name = name;
let hasSourceCode, sourceCode;
if (workerType === "classic") {
hasSourceCode = true;
sourceCode = `importScripts("#");`;
} else {
hasSourceCode = false;
sourceCode = "";
}
const id = createWorker(
specifier, specifier,
hasSourceCode,
sourceCode,
deno?.permissions,
name,
workerType, workerType,
);
this.#id = id;
this.#pollControl();
this.#pollMessages();
}
#handleError(e) {
const event = new ErrorEvent("error", {
cancelable: true,
message: e.message,
lineno: e.lineNumber ? e.lineNumber : undefined,
colno: e.columnNumber ? e.columnNumber : undefined,
filename: e.fileName,
error: null,
}); });
}
function hostTerminateWorker(id) { this.dispatchEvent(event);
ops.op_host_terminate_worker(id); // Don't bubble error event to window for loader errors (`!e.fileName`).
} // TODO(nayeemrmn): It's not correct to use `e.fileName` to detect user
// errors. It won't be there for non-awaited async ops for example.
function hostPostMessage(id, data) { if (e.fileName && !event.defaultPrevented) {
ops.op_host_post_message(id, data); globalThis.dispatchEvent(event);
}
function hostRecvCtrl(id) {
return core.opAsync("op_host_recv_ctrl", id);
}
function hostRecvMessage(id) {
return core.opAsync("op_host_recv_message", id);
}
class Worker extends EventTarget {
#id = 0;
#name = "";
// "RUNNING" | "CLOSED" | "TERMINATED"
// "TERMINATED" means that any controls or messages received will be
// discarded. "CLOSED" means that we have received a control
// indicating that the worker is no longer running, but there might
// still be messages left to receive.
#status = "RUNNING";
constructor(specifier, options = {}) {
super();
specifier = String(specifier);
const {
deno,
name,
type = "classic",
} = options;
const workerType = webidl.converters["WorkerType"](type);
if (
StringPrototypeStartsWith(specifier, "./") ||
StringPrototypeStartsWith(specifier, "../") ||
StringPrototypeStartsWith(specifier, "/") || workerType === "classic"
) {
const baseUrl = getLocationHref();
if (baseUrl != null) {
specifier = new URL(specifier, baseUrl).href;
}
}
this.#name = name;
let hasSourceCode, sourceCode;
if (workerType === "classic") {
hasSourceCode = true;
sourceCode = `importScripts("#");`;
} else {
hasSourceCode = false;
sourceCode = "";
}
const id = createWorker(
specifier,
hasSourceCode,
sourceCode,
deno?.permissions,
name,
workerType,
);
this.#id = id;
this.#pollControl();
this.#pollMessages();
} }
#handleError(e) { return event.defaultPrevented;
const event = new ErrorEvent("error", { }
cancelable: true,
message: e.message,
lineno: e.lineNumber ? e.lineNumber : undefined,
colno: e.columnNumber ? e.columnNumber : undefined,
filename: e.fileName,
error: null,
});
this.dispatchEvent(event); #pollControl = async () => {
// Don't bubble error event to window for loader errors (`!e.fileName`). while (this.#status === "RUNNING") {
// TODO(nayeemrmn): It's not correct to use `e.fileName` to detect user const { 0: type, 1: data } = await hostRecvCtrl(this.#id);
// errors. It won't be there for non-awaited async ops for example.
if (e.fileName && !event.defaultPrevented) { // If terminate was called then we ignore all messages
window.dispatchEvent(event); if (this.#status === "TERMINATED") {
return;
} }
return event.defaultPrevented; switch (type) {
case 1: { // TerminalError
this.#status = "CLOSED";
} /* falls through */
case 2: { // Error
if (!this.#handleError(data)) {
throw new Error("Unhandled error in child worker.");
}
break;
}
case 3: { // Close
log(`Host got "close" message from worker: ${this.#name}`);
this.#status = "CLOSED";
return;
}
default: {
throw new Error(`Unknown worker event: "${type}"`);
}
}
} }
};
#pollControl = async () => { #pollMessages = async () => {
while (this.#status === "RUNNING") { while (this.#status !== "TERMINATED") {
const { 0: type, 1: data } = await hostRecvCtrl(this.#id); const data = await hostRecvMessage(this.#id);
if (this.#status === "TERMINATED" || data === null) {
// If terminate was called then we ignore all messages return;
if (this.#status === "TERMINATED") {
return;
}
switch (type) {
case 1: { // TerminalError
this.#status = "CLOSED";
} /* falls through */
case 2: { // Error
if (!this.#handleError(data)) {
throw new Error("Unhandled error in child worker.");
}
break;
}
case 3: { // Close
log(`Host got "close" message from worker: ${this.#name}`);
this.#status = "CLOSED";
return;
}
default: {
throw new Error(`Unknown worker event: "${type}"`);
}
}
} }
}; let message, transferables;
try {
#pollMessages = async () => { const v = deserializeJsMessageData(data);
while (this.#status !== "TERMINATED") { message = v[0];
const data = await hostRecvMessage(this.#id); transferables = v[1];
if (this.#status === "TERMINATED" || data === null) { } catch (err) {
return; const event = new MessageEvent("messageerror", {
}
let message, transferables;
try {
const v = deserializeJsMessageData(data);
message = v[0];
transferables = v[1];
} catch (err) {
const event = new MessageEvent("messageerror", {
cancelable: false,
data: err,
});
this.dispatchEvent(event);
return;
}
const event = new MessageEvent("message", {
cancelable: false, cancelable: false,
data: message, data: err,
ports: transferables.filter((t) =>
ObjectPrototypeIsPrototypeOf(MessagePortPrototype, t)
),
}); });
this.dispatchEvent(event); this.dispatchEvent(event);
return;
} }
}; const event = new MessageEvent("message", {
cancelable: false,
postMessage(message, transferOrOptions = {}) { data: message,
const prefix = "Failed to execute 'postMessage' on 'MessagePort'"; ports: transferables.filter((t) =>
webidl.requiredArguments(arguments.length, 1, { prefix }); ObjectPrototypeIsPrototypeOf(MessagePortPrototype, t)
message = webidl.converters.any(message); ),
let options; });
if ( this.dispatchEvent(event);
webidl.type(transferOrOptions) === "Object" &&
transferOrOptions !== undefined &&
transferOrOptions[SymbolIterator] !== undefined
) {
const transfer = webidl.converters["sequence<object>"](
transferOrOptions,
{ prefix, context: "Argument 2" },
);
options = { transfer };
} else {
options = webidl.converters.StructuredSerializeOptions(
transferOrOptions,
{
prefix,
context: "Argument 2",
},
);
}
const { transfer } = options;
const data = serializeJsMessageData(message, transfer);
if (this.#status === "RUNNING") {
hostPostMessage(this.#id, data);
}
} }
};
terminate() { postMessage(message, transferOrOptions = {}) {
if (this.#status !== "TERMINATED") { const prefix = "Failed to execute 'postMessage' on 'MessagePort'";
this.#status = "TERMINATED"; webidl.requiredArguments(arguments.length, 1, { prefix });
hostTerminateWorker(this.#id); message = webidl.converters.any(message);
} let options;
if (
webidl.type(transferOrOptions) === "Object" &&
transferOrOptions !== undefined &&
transferOrOptions[SymbolIterator] !== undefined
) {
const transfer = webidl.converters["sequence<object>"](
transferOrOptions,
{ prefix, context: "Argument 2" },
);
options = { transfer };
} else {
options = webidl.converters.StructuredSerializeOptions(
transferOrOptions,
{
prefix,
context: "Argument 2",
},
);
}
const { transfer } = options;
const data = serializeJsMessageData(message, transfer);
if (this.#status === "RUNNING") {
hostPostMessage(this.#id, data);
} }
[SymbolToStringTag] = "Worker";
} }
defineEventHandler(Worker.prototype, "error"); terminate() {
defineEventHandler(Worker.prototype, "message"); if (this.#status !== "TERMINATED") {
defineEventHandler(Worker.prototype, "messageerror"); this.#status = "TERMINATED";
hostTerminateWorker(this.#id);
}
}
webidl.converters["WorkerType"] = webidl.createEnumConverter("WorkerType", [ [SymbolToStringTag] = "Worker";
"classic", }
"module",
]);
window.__bootstrap.worker = { defineEventHandler(Worker.prototype, "error");
Worker, defineEventHandler(Worker.prototype, "message");
}; defineEventHandler(Worker.prototype, "messageerror");
})(this);
webidl.converters["WorkerType"] = webidl.createEnumConverter("WorkerType", [
"classic",
"module",
]);
export { Worker };

Some files were not shown because too many files have changed in this diff Show more