mod blob;
mod broadcast_channel;
mod compression;
mod console;
mod message_port;
mod stream_resource;
mod timers;
mod url;
mod urlpattern;
use std::borrow::Cow;
use std::cell::RefCell;
use std::sync::Arc;
pub use blob::BlobError;
pub use compression::CompressionError;
use deno_core::U16String;
use deno_core::convert::ByteString;
use deno_core::convert::Uint8Array;
use deno_core::op2;
use deno_core::url::Url;
use deno_core::v8;
use encoding_rs::CoderResult;
use encoding_rs::Decoder;
use encoding_rs::DecoderResult;
use encoding_rs::Encoding;
pub use message_port::MessagePortError;
pub use stream_resource::StreamResourceError;
pub use crate::blob::Blob;
pub use crate::blob::BlobPart;
pub use crate::blob::BlobStore;
pub use crate::blob::InMemoryBlobPart;
use crate::blob::op_blob_create_object_url;
use crate::blob::op_blob_create_part;
use crate::blob::op_blob_from_object_url;
use crate::blob::op_blob_read_part;
use crate::blob::op_blob_remove_part;
use crate::blob::op_blob_revoke_object_url;
use crate::blob::op_blob_slice_part;
pub use crate::broadcast_channel::InMemoryBroadcastChannel;
pub use crate::message_port::JsMessageData;
pub use crate::message_port::MessagePort;
pub use crate::message_port::Transferable;
pub use crate::message_port::create_entangled_message_port;
pub use crate::message_port::deserialize_js_transferables;
use crate::message_port::op_message_port_create_entangled;
use crate::message_port::op_message_port_post_message;
use crate::message_port::op_message_port_post_message_raw;
use crate::message_port::op_message_port_recv_message;
use crate::message_port::op_message_port_recv_message_sync;
pub use crate::message_port::serialize_transferables;
pub use crate::timers::StartTime;
use crate::timers::op_defer;
use crate::timers::op_now;
use crate::timers::op_time_origin;
deno_core::extension!(deno_web,
deps = [ deno_webidl ],
ops = [
op_base64_decode,
op_base64_decode_into,
op_base64_encode,
op_base64_encode_from_buffer,
op_base64_atob,
op_base64_btoa,
op_encoding_normalize_label,
op_encoding_decode_single,
op_encoding_decode_utf8,
op_encoding_new_decoder,
op_encoding_decode,
op_encoding_encode_into,
op_blob_create_part,
op_blob_slice_part,
op_blob_read_part,
op_blob_remove_part,
op_blob_create_object_url,
op_blob_revoke_object_url,
op_blob_from_object_url,
op_message_port_create_entangled,
op_message_port_post_message,
op_message_port_post_message_raw,
op_message_port_recv_message,
op_message_port_recv_message_sync,
compression::op_compression_new,
compression::op_compression_write,
compression::op_compression_finish,
op_now,
op_time_origin,
op_defer,
stream_resource::op_readable_stream_resource_allocate,
stream_resource::op_readable_stream_resource_allocate_sized,
stream_resource::op_readable_stream_resource_get_sink,
stream_resource::op_readable_stream_resource_write_error,
stream_resource::op_readable_stream_resource_write_buf,
stream_resource::op_readable_stream_resource_write_sync,
stream_resource::op_readable_stream_resource_close,
stream_resource::op_readable_stream_resource_await_close,
url::op_url_reparse,
url::op_url_parse,
url::op_url_get_serialization,
url::op_url_parse_with_base,
url::op_url_parse_search_params,
url::op_url_stringify_search_params,
urlpattern::op_urlpattern_parse,
urlpattern::op_urlpattern_process_match_input,
console::op_preview_entries,
broadcast_channel::op_broadcast_subscribe,
broadcast_channel::op_broadcast_unsubscribe,
broadcast_channel::op_broadcast_send,
broadcast_channel::op_broadcast_recv,
],
esm = [
"00_infra.js",
"01_dom_exception.js",
"01_mimesniff.js",
"02_event.js",
"02_structured_clone.js",
"02_timers.js",
"03_abort_signal.js",
"04_global_interfaces.js",
"05_base64.js",
"06_streams.js",
"08_text_encoding.js",
"09_file.js",
"10_filereader.js",
"12_location.js",
"13_message_port.js",
"14_compression.js",
"15_performance.js",
"16_image_data.js",
"00_url.js",
"01_urlpattern.js",
"01_console.js",
"01_broadcast_channel.js"
],
lazy_loaded_esm = [ "webtransport.js" ],
options = {
blob_store: Arc<BlobStore>,
maybe_location: Option<Url>,
bc: InMemoryBroadcastChannel,
},
state = |state, options| {
state.put(options.blob_store);
if let Some(location) = options.maybe_location {
state.put(Location(location));
}
state.put(StartTime::default());
state.put(options.bc);
}
);
#[derive(Debug, thiserror::Error, deno_error::JsError)]
pub enum WebError {
#[class("DOMExceptionInvalidCharacterError")]
#[error("Failed to decode base64")]
Base64Decode,
#[class(range)]
#[error("The encoding label provided ('{0}') is invalid.")]
InvalidEncodingLabel(String),
#[class(type)]
#[error("buffer exceeds maximum length")]
BufferTooLong,
#[class(range)]
#[error("Value too large to decode")]
ValueTooLarge,
#[class(range)]
#[error("Provided buffer too small")]
BufferTooSmall,
#[class(type)]
#[error("The encoded data is not valid")]
DataInvalid,
#[class(generic)]
#[error(transparent)]
DataError(#[from] v8::DataError),
}
#[inline]
fn simdutf_base64_decode_to_vec(input: &[u8]) -> Result<Vec<u8>, WebError> {
use v8::simdutf;
let max_len = simdutf::maximal_binary_length_from_base64(input);
let mut output = Vec::with_capacity(max_len);
let result = unsafe {
ffi_base64_to_binary(
input.as_ptr(),
input.len(),
output.as_mut_ptr(),
simdutf::Base64Options::Default as u64,
simdutf::LastChunkHandling::Loose as u64,
)
};
if result.error != 0 {
return Err(WebError::Base64Decode);
}
unsafe { output.set_len(result.count) };
Ok(output)
}
#[inline]
fn simdutf_base64_decode_into(
input: &[u8],
output: &mut [u8],
) -> Result<usize, WebError> {
use v8::simdutf;
let result = unsafe {
simdutf::base64_to_binary(
input,
output,
simdutf::Base64Options::Default,
simdutf::LastChunkHandling::Loose,
)
};
if !result.is_ok() {
return Err(WebError::Base64Decode);
}
Ok(result.count)
}
#[inline]
fn simdutf_base64_decode_strict(
input: &[u8],
output: &mut [u8],
) -> Option<usize> {
use v8::simdutf;
let result = unsafe {
simdutf::base64_to_binary(
input,
output,
simdutf::Base64Options::Default,
simdutf::LastChunkHandling::Strict,
)
};
if result.is_ok() {
Some(result.count)
} else {
None
}
}
#[repr(C)]
struct SimdutfFfiResult {
error: i32,
count: usize,
}
unsafe extern "C" {
#[link_name = "simdutf__binary_to_base64"]
fn ffi_binary_to_base64(
input: *const u8,
length: usize,
output: *mut u8,
options: u64,
) -> usize;
#[link_name = "simdutf__base64_to_binary"]
fn ffi_base64_to_binary(
input: *const u8,
length: usize,
output: *mut u8,
options: u64,
last_chunk_options: u64,
) -> SimdutfFfiResult;
}
#[inline]
unsafe fn simdutf_base64_encode(
input: &[u8],
output: *mut u8,
output_len: usize,
) -> usize {
debug_assert!(
output_len
>= v8::simdutf::base64_length_from_binary(
input.len(),
v8::simdutf::Base64Options::Default
)
);
unsafe {
ffi_binary_to_base64(
input.as_ptr(),
input.len(),
output,
v8::simdutf::Base64Options::Default as u64,
)
}
}
#[op2]
fn op_base64_decode(
#[string(onebyte)] input: Cow<[u8]>,
) -> Result<Uint8Array, WebError> {
let v = simdutf_base64_decode_to_vec(&input)?;
Ok(v.into())
}
#[op2(fast)]
fn op_base64_decode_into(
#[string(onebyte)] input: Cow<[u8]>,
#[buffer] target: &mut [u8],
#[smi] offset: u32,
) -> Result<u32, WebError> {
let offset = offset as usize;
let target = &mut target[offset..];
let max_len = v8::simdutf::maximal_binary_length_from_base64(&input);
if target.len() >= max_len
&& let Some(len) = simdutf_base64_decode_strict(&input, target)
{
return Ok(len as u32);
}
const STACK_BUF_SIZE: usize = 8192;
if max_len <= STACK_BUF_SIZE {
let mut buf = std::mem::MaybeUninit::<[u8; STACK_BUF_SIZE]>::uninit();
let decoded_len = simdutf_base64_decode_into(&input, unsafe {
std::slice::from_raw_parts_mut(
buf.as_mut_ptr() as *mut u8,
STACK_BUF_SIZE,
)
})?;
let bytes_to_write = decoded_len.min(target.len());
target[..bytes_to_write].copy_from_slice(unsafe {
std::slice::from_raw_parts(buf.as_ptr() as *const u8, bytes_to_write)
});
Ok(bytes_to_write as u32)
} else {
let decoded = simdutf_base64_decode_to_vec(&input)?;
let bytes_to_write = decoded.len().min(target.len());
target[..bytes_to_write].copy_from_slice(&decoded[..bytes_to_write]);
Ok(bytes_to_write as u32)
}
}
#[op2]
fn op_base64_atob(#[scoped] mut s: ByteString) -> Result<ByteString, WebError> {
let max_len = v8::simdutf::maximal_binary_length_from_base64(&s);
const STACK_BUF_SIZE: usize = 8192;
if max_len <= STACK_BUF_SIZE {
let mut buf = std::mem::MaybeUninit::<[u8; STACK_BUF_SIZE]>::uninit();
let decoded_len = simdutf_base64_decode_into(&s, unsafe {
std::slice::from_raw_parts_mut(
buf.as_mut_ptr() as *mut u8,
STACK_BUF_SIZE,
)
})?;
s[..decoded_len].copy_from_slice(unsafe {
std::slice::from_raw_parts(buf.as_ptr() as *const u8, decoded_len)
});
s.truncate(decoded_len);
Ok(s)
} else {
let decoded = simdutf_base64_decode_to_vec(&s)?;
let decoded_len = decoded.len();
s[..decoded_len].copy_from_slice(&decoded[..decoded_len]);
s.truncate(decoded_len);
Ok(s)
}
}
#[op2]
#[string]
fn op_base64_encode(#[buffer] s: &[u8]) -> String {
forgiving_base64_encode(s)
}
#[op2]
fn op_base64_encode_from_buffer<'a>(
scope: &mut v8::PinScope<'a, '_>,
#[buffer] s: &[u8],
#[smi] offset: u32,
#[smi] length: u32,
) -> Result<v8::Local<'a, v8::String>, WebError> {
let offset = offset as usize;
let length = length as usize;
let end = (offset + length).min(s.len());
base64_encode_to_v8_string(scope, &s[offset..end])
}
#[inline]
fn base64_encode_to_v8_string<'a>(
scope: &mut v8::PinScope<'a, '_>,
src: &[u8],
) -> Result<v8::Local<'a, v8::String>, WebError> {
let b64_len = v8::simdutf::base64_length_from_binary(
src.len(),
v8::simdutf::Base64Options::Default,
);
const STACK_BUF_SIZE: usize = 8192;
if b64_len <= STACK_BUF_SIZE {
let mut buf = std::mem::MaybeUninit::<[u8; STACK_BUF_SIZE]>::uninit();
let written = unsafe {
simdutf_base64_encode(src, buf.as_mut_ptr() as *mut u8, b64_len)
};
v8::String::new_from_one_byte(
scope,
unsafe { std::slice::from_raw_parts(buf.as_ptr() as *const u8, written) },
v8::NewStringType::Normal,
)
.ok_or(WebError::BufferTooLong)
} else {
let mut buf = Vec::with_capacity(b64_len);
let written =
unsafe { simdutf_base64_encode(src, buf.as_mut_ptr(), b64_len) };
unsafe { buf.set_len(written) };
let buf = buf.into_boxed_slice();
debug_assert_eq!(written, b64_len);
v8::String::new_external_onebyte(scope, buf).ok_or(WebError::BufferTooLong)
}
}
#[op2]
#[string]
fn op_base64_btoa(#[scoped] s: ByteString) -> String {
forgiving_base64_encode(s.as_ref())
}
#[inline]
pub fn forgiving_base64_encode(s: &[u8]) -> String {
let b64_len = v8::simdutf::base64_length_from_binary(
s.len(),
v8::simdutf::Base64Options::Default,
);
let mut buf = Vec::with_capacity(b64_len);
unsafe {
let written = simdutf_base64_encode(s, buf.as_mut_ptr(), b64_len);
buf.set_len(written);
String::from_utf8_unchecked(buf)
}
}
#[op2]
#[string]
fn op_encoding_normalize_label(
#[string] label: String,
) -> Result<String, WebError> {
let encoding = Encoding::for_label_no_replacement(label.as_bytes())
.ok_or(WebError::InvalidEncodingLabel(label))?;
Ok(encoding.name().to_lowercase())
}
#[op2]
fn op_encoding_decode_utf8<'a>(
scope: &mut v8::PinScope<'a, '_>,
#[anybuffer] zero_copy: &[u8],
ignore_bom: bool,
) -> Result<v8::Local<'a, v8::String>, WebError> {
let buf = &zero_copy;
let buf = if !ignore_bom
&& buf.len() >= 3
&& buf[0] == 0xef
&& buf[1] == 0xbb
&& buf[2] == 0xbf
{
&buf[3..]
} else {
buf
};
match v8::String::new_from_utf8(scope, buf, v8::NewStringType::Normal) {
Some(text) => Ok(text),
None => Err(WebError::BufferTooLong),
}
}
#[op2]
#[serde]
fn op_encoding_decode_single(
#[anybuffer] data: &[u8],
#[string] label: String,
fatal: bool,
ignore_bom: bool,
) -> Result<U16String, WebError> {
let encoding = Encoding::for_label(label.as_bytes())
.ok_or(WebError::InvalidEncodingLabel(label))?;
let mut decoder = if ignore_bom {
encoding.new_decoder_without_bom_handling()
} else {
encoding.new_decoder_with_bom_removal()
};
let max_buffer_length = decoder
.max_utf16_buffer_length(data.len())
.ok_or(WebError::ValueTooLarge)?;
let mut output = vec![0; max_buffer_length];
if fatal {
let (result, _, written) =
decoder.decode_to_utf16_without_replacement(data, &mut output, true);
match result {
DecoderResult::InputEmpty => {
output.truncate(written);
Ok(output.into())
}
DecoderResult::OutputFull => Err(WebError::BufferTooSmall),
DecoderResult::Malformed(_, _) => Err(WebError::DataInvalid),
}
} else {
let (result, _, written, _) =
decoder.decode_to_utf16(data, &mut output, true);
match result {
CoderResult::InputEmpty => {
output.truncate(written);
Ok(output.into())
}
CoderResult::OutputFull => Err(WebError::BufferTooSmall),
}
}
}
#[op2]
#[cppgc]
fn op_encoding_new_decoder(
#[string] label: &str,
fatal: bool,
ignore_bom: bool,
) -> Result<TextDecoderResource, WebError> {
let encoding = Encoding::for_label(label.as_bytes())
.ok_or_else(|| WebError::InvalidEncodingLabel(label.to_string()))?;
let decoder = if ignore_bom {
encoding.new_decoder_without_bom_handling()
} else {
encoding.new_decoder_with_bom_removal()
};
Ok(TextDecoderResource {
decoder: RefCell::new(decoder),
fatal,
})
}
#[op2]
#[serde]
fn op_encoding_decode(
#[anybuffer] data: &[u8],
#[cppgc] resource: &TextDecoderResource,
stream: bool,
) -> Result<U16String, WebError> {
let mut decoder = resource.decoder.borrow_mut();
let fatal = resource.fatal;
let max_buffer_length = decoder
.max_utf16_buffer_length(data.len())
.ok_or(WebError::ValueTooLarge)?;
let mut output = vec![0; max_buffer_length];
if fatal {
let (result, _, written) =
decoder.decode_to_utf16_without_replacement(data, &mut output, !stream);
match result {
DecoderResult::InputEmpty => {
output.truncate(written);
Ok(output.into())
}
DecoderResult::OutputFull => Err(WebError::BufferTooSmall),
DecoderResult::Malformed(_, _) => Err(WebError::DataInvalid),
}
} else {
let (result, _, written, _) =
decoder.decode_to_utf16(data, &mut output, !stream);
match result {
CoderResult::InputEmpty => {
output.truncate(written);
Ok(output.into())
}
CoderResult::OutputFull => Err(WebError::BufferTooSmall),
}
}
}
struct TextDecoderResource {
decoder: RefCell<Decoder>,
fatal: bool,
}
unsafe impl deno_core::GarbageCollected for TextDecoderResource {
fn trace(&self, _visitor: &mut deno_core::v8::cppgc::Visitor) {}
fn get_name(&self) -> &'static std::ffi::CStr {
c"TextDecoderResource"
}
}
#[op2(fast(op_encoding_encode_into_fast))]
fn op_encoding_encode_into(
scope: &mut v8::PinScope<'_, '_>,
input: v8::Local<v8::Value>,
#[buffer] buffer: &mut [u8],
#[buffer] out_buf: &mut [u32],
) -> Result<(), WebError> {
let s = v8::Local::<v8::String>::try_from(input)?;
let mut nchars = 0;
let len = s.write_utf8_v2(
scope,
buffer,
v8::WriteFlags::kReplaceInvalidUtf8,
Some(&mut nchars),
);
out_buf[1] = len as u32;
out_buf[0] = nchars as u32;
Ok(())
}
#[op2(fast)]
fn op_encoding_encode_into_fast(
#[string] input: Cow<'_, str>,
#[buffer] buffer: &mut [u8],
#[buffer] out_buf: &mut [u32],
) {
let boundary = if buffer.len() >= input.len() {
input.len()
} else {
let mut boundary = buffer.len();
for _ in 0..4 {
if input.is_char_boundary(boundary) {
break;
}
debug_assert!(boundary > 0);
boundary -= 1;
}
debug_assert!(input.is_char_boundary(boundary));
boundary
};
buffer[..boundary].copy_from_slice(input[..boundary].as_bytes());
out_buf[0] = match input {
Cow::Borrowed(v) => v[..boundary].len() as u32,
Cow::Owned(v) => v[..boundary].encode_utf16().count() as u32,
};
out_buf[1] = boundary as u32;
}
pub struct Location(pub Url);