mirror of
https://github.com/lexmount/moli.git
synced 2026-11-06 00:07:32 +00:00
feat(webapi): implement media and transport error values
This commit is contained in:
@@ -291,9 +291,12 @@ pub(crate) use self::canvas::{
|
||||
};
|
||||
use self::constructors::illegal_constructor_callback;
|
||||
pub(crate) use self::constructors::{
|
||||
dom_exception_clone_fields, ensure_dom_implementation_singleton, new_dom_error_value,
|
||||
new_dom_exception_value, new_most_derived_dom_exception_value, new_quota_exceeded_error_value,
|
||||
WebTransportErrorClonePayload, WebTransportErrorSource,
|
||||
build_web_transport_error_from_clone_payload, dom_exception_clone_fields,
|
||||
ensure_dom_implementation_singleton, new_dom_error_value, new_dom_exception_value,
|
||||
new_most_derived_dom_exception_value, new_quota_exceeded_error_value,
|
||||
quota_exceeded_error_clone_fields, throw_dom_exception_value,
|
||||
web_transport_error_clone_payload_from_object,
|
||||
};
|
||||
pub(crate) use self::css_stylesheet_runtime::{
|
||||
adopted_style_sheet_installations_from_value, bind_css_style_sheet_to_live_stylesheet,
|
||||
|
||||
@@ -311,6 +311,22 @@ pub(in crate::context_bootstrap) fn build_constructor_template_for_profile<'s>(
|
||||
.length(1)
|
||||
.build(scope)
|
||||
}
|
||||
ConstructorKind::OverconstrainedError => {
|
||||
v8::FunctionTemplate::builder(moli_webapi_declare::web_api_constructor!(
|
||||
web_api_interfaces::OverconstrainedError,
|
||||
overconstrained_error_constructor_callback
|
||||
))
|
||||
.length(1)
|
||||
.build(scope)
|
||||
}
|
||||
ConstructorKind::WebTransportError => {
|
||||
v8::FunctionTemplate::builder(moli_webapi_declare::web_api_constructor!(
|
||||
web_api_interfaces::WebTransportError,
|
||||
web_transport_error_constructor_callback
|
||||
))
|
||||
.length(0)
|
||||
.build(scope)
|
||||
}
|
||||
ConstructorKind::RtcErrorEvent => {
|
||||
v8::FunctionTemplate::builder(moli_webapi_declare::web_api_constructor!(
|
||||
web_api_interfaces::RTCErrorEvent,
|
||||
|
||||
@@ -33,12 +33,16 @@ pub(super) use document_nodes::{
|
||||
document_fragment_constructor_callback, text_constructor_callback,
|
||||
};
|
||||
pub(crate) use dom_exception::{
|
||||
dom_error_constructor_callback, dom_exception_clone_fields, dom_exception_constructor_callback,
|
||||
initialize_websocket_error, install_dom_exception_template_bindings, new_dom_error_value,
|
||||
new_dom_exception_value, new_most_derived_dom_exception_value, new_quota_exceeded_error_value,
|
||||
new_websocket_error_value, quota_exceeded_error_clone_fields,
|
||||
quota_exceeded_error_constructor_callback, rtc_error_constructor_callback,
|
||||
throw_dom_exception_value, websocket_error_close_info,
|
||||
WebTransportErrorClonePayload, WebTransportErrorSource,
|
||||
build_web_transport_error_from_clone_payload, dom_error_constructor_callback,
|
||||
dom_exception_clone_fields, dom_exception_constructor_callback, initialize_websocket_error,
|
||||
install_dom_exception_template_bindings, new_dom_error_value, new_dom_exception_value,
|
||||
new_most_derived_dom_exception_value, new_quota_exceeded_error_value,
|
||||
new_websocket_error_value, overconstrained_error_constructor_callback,
|
||||
quota_exceeded_error_clone_fields, quota_exceeded_error_constructor_callback,
|
||||
rtc_error_constructor_callback, throw_dom_exception_value,
|
||||
web_transport_error_clone_payload_from_object, web_transport_error_constructor_callback,
|
||||
websocket_error_close_info,
|
||||
};
|
||||
pub(crate) use dom_implementation::ensure_dom_implementation_singleton;
|
||||
pub(super) use dom_implementation::install_dom_implementation_template_bindings;
|
||||
|
||||
@@ -4,8 +4,16 @@ use crate::{util::get_private_value, webidl};
|
||||
use moli_web_errors::{DOM_EXCEPTION_CONSTANTS, dom_exception_legacy_code};
|
||||
use moli_webapi_declare::{WebApiFunctionTemplate, WebApiObject, WebApiTemplateValue};
|
||||
|
||||
mod overconstrained_error;
|
||||
mod rtc_error;
|
||||
mod web_transport_error;
|
||||
pub(crate) use overconstrained_error::overconstrained_error_constructor_callback;
|
||||
pub(crate) use rtc_error::rtc_error_constructor_callback;
|
||||
pub(crate) use web_transport_error::{
|
||||
WebTransportErrorClonePayload, WebTransportErrorSource,
|
||||
build_web_transport_error_from_clone_payload, web_transport_error_clone_payload_from_object,
|
||||
web_transport_error_constructor_callback,
|
||||
};
|
||||
|
||||
const DOM_EXCEPTION_MESSAGE_SLOT: &str = "__lmDomExceptionMessage";
|
||||
const DOM_EXCEPTION_NAME_SLOT: &str = "__lmDomExceptionName";
|
||||
@@ -191,6 +199,8 @@ pub(crate) fn install_dom_exception_template_bindings<'s>(
|
||||
);
|
||||
}
|
||||
"RTCError" => rtc_error::install(scope, prototype),
|
||||
"OverconstrainedError" => overconstrained_error::install(scope, prototype),
|
||||
"WebTransportError" => web_transport_error::install(scope, prototype),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
use super::*;
|
||||
use moli_webapi_declare::initialize_web_api_constructor_receiver;
|
||||
|
||||
const CONSTRAINT_SLOT: &str = "__moliOverconstrainedErrorConstraint";
|
||||
|
||||
#[derive(webidl::WebIdlArgs)]
|
||||
#[webidl(prefix = "OverconstrainedError")]
|
||||
struct ConstructorArgs {
|
||||
#[webidl(required, converter = "raw")]
|
||||
constraint: webidl::DomString16,
|
||||
#[webidl(default = webidl::DomString16(Vec::new()), converter = "raw")]
|
||||
message: webidl::DomString16,
|
||||
}
|
||||
|
||||
#[derive(WebApiObject)]
|
||||
#[webapi(interface = web_api_interfaces::OverconstrainedError)]
|
||||
struct ObjectDeclaration<'s> {
|
||||
#[webapi(slot = CONSTRAINT_SLOT)]
|
||||
constraint: v8::Local<'s, v8::String>,
|
||||
#[webapi(slot = DOM_EXCEPTION_MESSAGE_SLOT)]
|
||||
message: v8::Local<'s, v8::String>,
|
||||
#[webapi(slot = DOM_EXCEPTION_NAME_SLOT, init = string("OverconstrainedError"))]
|
||||
name: (),
|
||||
#[webapi(slot = DOM_EXCEPTION_CODE_SLOT, init = 0)]
|
||||
code: (),
|
||||
}
|
||||
|
||||
#[derive(WebApiFunctionTemplate)]
|
||||
#[webapi(interface = web_api_interfaces::OverconstrainedError, receiver, enumerable)]
|
||||
struct PrototypeDeclaration {
|
||||
#[webapi(accessor_property, getter = constraint_getter)]
|
||||
constraint: (),
|
||||
}
|
||||
|
||||
pub(super) fn install<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_, ()>,
|
||||
prototype: v8::Local<'s, v8::ObjectTemplate>,
|
||||
) {
|
||||
PrototypeDeclaration::initialize_prototype_template(scope, prototype);
|
||||
}
|
||||
|
||||
pub(crate) fn overconstrained_error_constructor_callback<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'s, v8::Value>,
|
||||
) {
|
||||
if !args.is_construct_call() {
|
||||
throw_type_error(scope, "OverconstrainedError constructor requires 'new'.");
|
||||
return;
|
||||
}
|
||||
let Some(parsed) = webidl::parse_args::<ConstructorArgs>(scope, &args) else {
|
||||
return;
|
||||
};
|
||||
if !initialize_web_api_constructor_receiver(scope, args.this(), "OverconstrainedError") {
|
||||
return;
|
||||
}
|
||||
let Some(constraint) =
|
||||
v8::String::new_from_two_byte(scope, &parsed.constraint.0, v8::NewStringType::Normal)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let Some(message) =
|
||||
v8::String::new_from_two_byte(scope, &parsed.message.0, v8::NewStringType::Normal)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
if ObjectDeclaration::new(constraint, message)
|
||||
.initialize(scope, args.this())
|
||||
.is_ok()
|
||||
{
|
||||
capture_dom_exception_stack(scope, args.this());
|
||||
rv.set(args.this().into());
|
||||
}
|
||||
}
|
||||
|
||||
fn constraint_getter<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'s, v8::Value>,
|
||||
) {
|
||||
let receiver = moli_webapi_declare::web_api_object_target(scope, args.this())
|
||||
.expect("OverconstrainedError receiver was validated");
|
||||
if let Some(value) = get_private_value(scope, receiver, CONSTRAINT_SLOT) {
|
||||
rv.set(value);
|
||||
}
|
||||
}
|
||||
+185
@@ -0,0 +1,185 @@
|
||||
use super::*;
|
||||
use moli_webapi_declare::initialize_web_api_constructor_receiver;
|
||||
use webidl::WebIdlEnum;
|
||||
|
||||
const SOURCE_SLOT: &str = "__moliWebTransportErrorSource";
|
||||
const STREAM_ERROR_CODE_SLOT: &str = "__moliWebTransportErrorStreamErrorCode";
|
||||
|
||||
#[derive(Clone, Copy, Debug, webidl::WebIdlEnum)]
|
||||
#[webidl(name = "WebTransportErrorSource")]
|
||||
pub(crate) enum WebTransportErrorSource {
|
||||
Stream,
|
||||
Session,
|
||||
}
|
||||
|
||||
impl WebTransportErrorSource {
|
||||
fn token(self) -> &'static str {
|
||||
match self {
|
||||
Self::Stream => "stream",
|
||||
Self::Session => "session",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(webidl::WebIdlDictionary)]
|
||||
#[webidl(prefix = "WebTransportErrorOptions")]
|
||||
struct Options {
|
||||
#[webidl(converter = "enum", default = WebTransportErrorSource::Stream)]
|
||||
source: WebTransportErrorSource,
|
||||
#[webidl(nullable, converter = "clamped_unsigned_long")]
|
||||
stream_error_code: Option<u32>,
|
||||
}
|
||||
|
||||
#[derive(webidl::WebIdlArgs)]
|
||||
#[webidl(prefix = "WebTransportError")]
|
||||
struct ConstructorArgs {
|
||||
#[webidl(default = webidl::DomString16(Vec::new()), converter = "raw")]
|
||||
message: webidl::DomString16,
|
||||
#[webidl(dictionary)]
|
||||
options: Options,
|
||||
}
|
||||
|
||||
#[derive(WebApiObject)]
|
||||
#[webapi(interface = web_api_interfaces::WebTransportError)]
|
||||
struct ObjectDeclaration<'s> {
|
||||
#[webapi(slot = DOM_EXCEPTION_MESSAGE_SLOT)]
|
||||
message: v8::Local<'s, v8::String>,
|
||||
#[webapi(slot = DOM_EXCEPTION_NAME_SLOT, init = string("WebTransportError"))]
|
||||
name: (),
|
||||
#[webapi(slot = DOM_EXCEPTION_CODE_SLOT, init = 0)]
|
||||
code: (),
|
||||
#[webapi(slot = SOURCE_SLOT)]
|
||||
source: v8::Local<'s, v8::String>,
|
||||
#[webapi(slot = STREAM_ERROR_CODE_SLOT)]
|
||||
stream_error_code: v8::Local<'s, v8::Value>,
|
||||
}
|
||||
|
||||
#[derive(WebApiFunctionTemplate)]
|
||||
#[webapi(interface = web_api_interfaces::WebTransportError, receiver, enumerable)]
|
||||
struct PrototypeDeclaration {
|
||||
#[webapi(accessor_property, getter = slot_getter, data = v8str(scope, SOURCE_SLOT))]
|
||||
source: (),
|
||||
#[webapi(accessor_property, getter = slot_getter, data = v8str(scope, STREAM_ERROR_CODE_SLOT))]
|
||||
stream_error_code: (),
|
||||
}
|
||||
|
||||
pub(super) fn install<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_, ()>,
|
||||
prototype: v8::Local<'s, v8::ObjectTemplate>,
|
||||
) {
|
||||
PrototypeDeclaration::initialize_prototype_template(scope, prototype);
|
||||
}
|
||||
|
||||
pub(crate) fn web_transport_error_constructor_callback<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'s, v8::Value>,
|
||||
) {
|
||||
if !args.is_construct_call() {
|
||||
throw_type_error(scope, "WebTransportError constructor requires 'new'.");
|
||||
return;
|
||||
}
|
||||
let Some(parsed) = webidl::parse_args::<ConstructorArgs>(scope, &args) else {
|
||||
return;
|
||||
};
|
||||
if !initialize_web_api_constructor_receiver(scope, args.this(), "WebTransportError") {
|
||||
return;
|
||||
}
|
||||
let payload = WebTransportErrorClonePayload {
|
||||
message: parsed.message.0,
|
||||
source: parsed.options.source,
|
||||
stream_error_code: parsed.options.stream_error_code,
|
||||
};
|
||||
let Some(declaration) = declaration(scope, &payload) else {
|
||||
return;
|
||||
};
|
||||
if declaration.initialize(scope, args.this()).is_ok() {
|
||||
capture_dom_exception_stack(scope, args.this());
|
||||
rv.set(args.this().into());
|
||||
}
|
||||
}
|
||||
|
||||
fn slot_getter<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'s, v8::Value>,
|
||||
) {
|
||||
let slot = args.data().to_rust_string_lossy(scope);
|
||||
let receiver = moli_webapi_declare::web_api_object_target(scope, args.this())
|
||||
.expect("WebTransportError receiver was validated");
|
||||
if let Some(value) = get_private_value(scope, receiver, &slot) {
|
||||
rv.set(value);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct WebTransportErrorClonePayload {
|
||||
pub(crate) message: Vec<u16>,
|
||||
pub(crate) source: WebTransportErrorSource,
|
||||
pub(crate) stream_error_code: Option<u32>,
|
||||
}
|
||||
|
||||
pub(crate) fn web_transport_error_clone_payload_from_object<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
object: v8::Local<'s, v8::Object>,
|
||||
) -> Option<WebTransportErrorClonePayload> {
|
||||
web_api_interfaces::WebTransportError::is_instance(scope, object).then_some(())?;
|
||||
let object = moli_webapi_declare::web_api_object_target(scope, object)?;
|
||||
let message = get_private_value(scope, object, DOM_EXCEPTION_MESSAGE_SLOT)?;
|
||||
let message = webidl::convert::<webidl::DomString16>(
|
||||
scope,
|
||||
message,
|
||||
webidl::Context::member("WebTransportError", "message"),
|
||||
)
|
||||
.ok()?
|
||||
.0;
|
||||
let source = get_private_value(scope, object, SOURCE_SLOT)?.to_rust_string_lossy(scope);
|
||||
let source = WebTransportErrorSource::parse_token(&source)?;
|
||||
let code = get_private_value(scope, object, STREAM_ERROR_CODE_SLOT)?;
|
||||
let stream_error_code = if code.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(v8::Local::<v8::Number>::try_from(code).ok()?.value() as u32)
|
||||
};
|
||||
Some(WebTransportErrorClonePayload {
|
||||
message,
|
||||
source,
|
||||
stream_error_code,
|
||||
})
|
||||
}
|
||||
|
||||
fn declaration<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
payload: &WebTransportErrorClonePayload,
|
||||
) -> Option<ObjectDeclaration<'s>> {
|
||||
let message =
|
||||
v8::String::new_from_two_byte(scope, &payload.message, v8::NewStringType::Normal)?;
|
||||
let source = v8str(scope, payload.source.token());
|
||||
let code = nullable_double_slot_value(scope, payload.stream_error_code.map(f64::from));
|
||||
Some(ObjectDeclaration::new(message, source, code))
|
||||
}
|
||||
|
||||
pub(crate) fn build_web_transport_error_from_clone_payload<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
payload: &WebTransportErrorClonePayload,
|
||||
) -> Option<v8::Local<'s, v8::Object>> {
|
||||
if !crate::context_bootstrap::exposed_interfaces::is_realm_interface_exposed(
|
||||
scope,
|
||||
"WebTransportError",
|
||||
) {
|
||||
throw_dom_exception_value(
|
||||
scope,
|
||||
"WebTransportError is not exposed in this realm.",
|
||||
"DataCloneError",
|
||||
);
|
||||
return None;
|
||||
}
|
||||
crate::context_bootstrap::exposed_interfaces::ensure_intrinsic_interface_prototype(
|
||||
scope,
|
||||
"WebTransportError",
|
||||
)
|
||||
.ok()?;
|
||||
let object = declaration(scope, payload)?.bind(scope).ok()?;
|
||||
capture_dom_exception_stack(scope, object);
|
||||
Some(object)
|
||||
}
|
||||
@@ -3,18 +3,20 @@ use crate::{
|
||||
context_bootstrap::{
|
||||
FileSystemHandleDurablePayload, build_file_list_object,
|
||||
build_file_system_handle_from_durable_payload, build_geometry_object_from_clone_payload,
|
||||
file_list_files_from_object, file_system_handle_clone_payload_from_object,
|
||||
build_web_transport_error_from_clone_payload, file_list_files_from_object,
|
||||
file_system_handle_clone_payload_from_object,
|
||||
file_system_handle_durable_payload_from_object, geometry_clone_payload_from_object,
|
||||
image_data_clone_payload_from_object,
|
||||
image_data_clone_payload_from_object, web_transport_error_clone_payload_from_object,
|
||||
},
|
||||
dom::native::SelectedFile,
|
||||
structured_clone::{
|
||||
BlobClonePayload, HOST_OBJECT_TAG_BLOB, HOST_OBJECT_TAG_CRYPTO_KEY,
|
||||
HOST_OBJECT_TAG_FILE_LIST, HOST_OBJECT_TAG_FILE_SYSTEM_HANDLE, HOST_OBJECT_TAG_GEOMETRY,
|
||||
HOST_OBJECT_TAG_IMAGE_DATA, blob_clone_payload_from_object,
|
||||
build_blob_object_from_clone_payload, read_crypto_key_payload, read_geometry_clone_payload,
|
||||
read_image_data_payload, write_crypto_key_payload, write_geometry_clone_payload,
|
||||
write_image_data_payload,
|
||||
HOST_OBJECT_TAG_IMAGE_DATA, HOST_OBJECT_TAG_WEB_TRANSPORT_ERROR,
|
||||
blob_clone_payload_from_object, build_blob_object_from_clone_payload,
|
||||
read_crypto_key_payload, read_geometry_clone_payload, read_image_data_payload,
|
||||
read_web_transport_error_payload, write_crypto_key_payload, write_geometry_clone_payload,
|
||||
write_image_data_payload, write_web_transport_error_payload,
|
||||
},
|
||||
};
|
||||
use moli_indexeddb::{IndexedDbFileSystemHandleBucket, IndexedDbFileSystemHandleKind};
|
||||
@@ -99,6 +101,13 @@ impl v8::ValueSerializerImpl for IndexedDbStructuredCloneSerializer {
|
||||
serializer: &dyn v8::ValueSerializerHelper,
|
||||
) -> Option<bool> {
|
||||
match moli_webapi_declare::web_api_object_type(scope, object).map(|kind| kind.name()) {
|
||||
Some("WebTransportError") => {
|
||||
if let Some(payload) = web_transport_error_clone_payload_from_object(scope, object)
|
||||
{
|
||||
write_web_transport_error_payload(serializer, &payload);
|
||||
return Some(true);
|
||||
}
|
||||
}
|
||||
Some("EncodedVideoChunk") => {
|
||||
let exception = dom_exception_value(
|
||||
scope,
|
||||
@@ -289,6 +298,10 @@ impl v8::ValueDeserializerImpl for IndexedDbStructuredCloneDeserializer {
|
||||
return None;
|
||||
}
|
||||
match tag {
|
||||
HOST_OBJECT_TAG_WEB_TRANSPORT_ERROR => {
|
||||
let payload = read_web_transport_error_payload(deserializer)?;
|
||||
build_web_transport_error_from_clone_payload(scope, &payload)
|
||||
}
|
||||
HOST_OBJECT_TAG_IMAGE_DATA => read_image_data_payload(scope, deserializer),
|
||||
HOST_OBJECT_TAG_CRYPTO_KEY => {
|
||||
read_crypto_key_payload(scope, deserializer).or_else(|| {
|
||||
|
||||
@@ -146,7 +146,7 @@ const CONSTRUCTOR_SPECS_BEFORE_STREAMS: &[ConstructorSpec] = &[
|
||||
},
|
||||
ConstructorSpec {
|
||||
interface: web_api_interfaces::OverconstrainedError::DESCRIPTOR,
|
||||
kind: ConstructorKind::Shell { length: 1 },
|
||||
kind: ConstructorKind::OverconstrainedError,
|
||||
},
|
||||
ConstructorSpec {
|
||||
interface: web_api_interfaces::PictureInPictureEvent::DESCRIPTOR,
|
||||
@@ -314,7 +314,7 @@ const CONSTRUCTOR_SPECS_BEFORE_STREAMS: &[ConstructorSpec] = &[
|
||||
},
|
||||
ConstructorSpec {
|
||||
interface: web_api_interfaces::WebTransportError::DESCRIPTOR,
|
||||
kind: ConstructorKind::Shell { length: 0 },
|
||||
kind: ConstructorKind::WebTransportError,
|
||||
},
|
||||
ConstructorSpec {
|
||||
interface: web_api_interfaces::External::DESCRIPTOR,
|
||||
|
||||
@@ -57,6 +57,8 @@ pub(in crate::context_bootstrap) enum ConstructorKind {
|
||||
HashChangeEvent,
|
||||
PageTransitionEvent,
|
||||
DomException,
|
||||
OverconstrainedError,
|
||||
WebTransportError,
|
||||
RtcError,
|
||||
RtcErrorEvent,
|
||||
DomError,
|
||||
|
||||
@@ -104,6 +104,7 @@ mod trusted_types;
|
||||
mod trusted_types_attributes;
|
||||
mod ui_event_init;
|
||||
mod user_activation;
|
||||
mod value_errors;
|
||||
mod value_events;
|
||||
mod web_audio;
|
||||
mod web_mcp;
|
||||
|
||||
@@ -0,0 +1,220 @@
|
||||
(() => {
|
||||
const checks = [];
|
||||
const check = (name, action) => {
|
||||
try { checks.push({name, passed: action() === true}); }
|
||||
catch (error) { checks.push({name, passed: false, error: String(error)}); }
|
||||
};
|
||||
const throws = (action, realm, expected) => {
|
||||
try { action(); } catch (error) {
|
||||
return expected === undefined ? Object.getPrototypeOf(error) === realm.TypeError.prototype : error === expected;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
const utf16 = 'a\u0000\ud800b\udfff\ud83d\ude00';
|
||||
function run(realm, label) {
|
||||
const test = (name, action) => check(label + ': ' + name, action);
|
||||
const WT = realm.WebTransportError;
|
||||
const OC = realm.OverconstrainedError;
|
||||
const createWT = (message, options = {}) => new WT(message, options);
|
||||
test('WebTransportError inheritance and defaults', () => {
|
||||
const e = new WT();
|
||||
return e instanceof WT && e instanceof realm.DOMException && e instanceof realm.Error &&
|
||||
Object.getPrototypeOf(WT.prototype) === realm.DOMException.prototype && e.name === 'WebTransportError' &&
|
||||
e.message === '' && e.code === 0 && e.source === 'stream' && e.streamErrorCode === null;
|
||||
});
|
||||
test('WebTransportError length and tag', () => WT.length === 0 &&
|
||||
Object.prototype.toString.call(new WT()) === '[object WebTransportError]');
|
||||
test('WebTransportError new before conversion', () => {
|
||||
let reads = 0;
|
||||
return throws(() => WT({toString() { reads++; return 'x'; }}), realm) && reads === 0;
|
||||
});
|
||||
for (const [index, [value, expected]] of [[undefined, ''], [null, 'null'], [true, 'true'],
|
||||
[0, '0'], [-0, '0'], [1n, '1'], [utf16, utf16]].entries()) {
|
||||
test('WebTransportError message ' + index + ': ' + String(value), () => createWT(value).message === expected);
|
||||
}
|
||||
test('WebTransportError Symbol message before dictionary', () => {
|
||||
let reads = 0;
|
||||
return throws(() => new WT(Symbol(), {get source() {reads++; return 'stream';}}), realm) && reads === 0;
|
||||
});
|
||||
for (const options of [undefined, null, {}, []]) {
|
||||
test('WebTransportError empty options ' + String(options), () => {
|
||||
const e = new WT(utf16, options);
|
||||
return e.message === utf16 && e.source === 'stream' && e.streamErrorCode === null;
|
||||
});
|
||||
}
|
||||
for (const options of [false, 0, '', 1n, Symbol('dictionary')]) {
|
||||
test('WebTransportError invalid dictionary ' + String(options), () => throws(() => new WT('', options), realm));
|
||||
}
|
||||
for (const source of ['stream', 'session']) {
|
||||
test('WebTransportError source ' + source, () => createWT(utf16, {source, streamErrorCode: 42}).source === source);
|
||||
}
|
||||
for (const source of [null, '', 'STREAM', 'other', 0, Symbol('source')]) {
|
||||
test('WebTransportError rejects source ' + String(source), () => {
|
||||
let reads = 0;
|
||||
return throws(() => createWT('', {source, get streamErrorCode() {reads++; return 1;}}), realm) && reads === 0;
|
||||
});
|
||||
}
|
||||
test('WebTransportError undefined source default', () => createWT('', {source: undefined}).source === 'stream');
|
||||
const codes = [[undefined, null], [null, null], [NaN, 0], [Infinity, 4294967295], [-Infinity, 0],
|
||||
[-4294967297, 0], [-1, 0], [-0, 0], [0, 0], [0.5, 0], [1.5, 2], [2.5, 2], [3.5, 4],
|
||||
[42.4, 42], [42.6, 43], [65536, 65536], [4294967294.5, 4294967294],
|
||||
[4294967295, 4294967295], [4294967295.5, 4294967295], [4294967296, 4294967295],
|
||||
[Number.MAX_VALUE, 4294967295], [false, 0], [true, 1], ['3.5', 4], ['', 0], ['x', 0]];
|
||||
for (const [index, [value, expected]] of codes.entries()) {
|
||||
test('WebTransportError clamp ' + index + ': ' + String(value), () => Object.is(createWT('', {streamErrorCode: value}).streamErrorCode, expected));
|
||||
}
|
||||
for (const value of [1n, Symbol('code')]) {
|
||||
test('WebTransportError rejects code ' + String(value), () => throws(() => createWT('', {streamErrorCode: value}), realm));
|
||||
}
|
||||
test('WebTransportError one numeric conversion', () => {
|
||||
let reads = 0;
|
||||
const e = createWT('', {streamErrorCode: {[Symbol.toPrimitive](hint) {reads++; if(hint !== 'number') throw 42; return 2.5;}}});
|
||||
return e.streamErrorCode === 2 && reads === 1;
|
||||
});
|
||||
const sentinel = {};
|
||||
for (const member of ['message', 'source', 'streamErrorCode']) {
|
||||
test('WebTransportError exception ' + member, () => {
|
||||
const order = [];
|
||||
const message = {toString() {order.push('message'); if(member === 'message') throw sentinel; return utf16;}};
|
||||
const options = new Proxy({}, {get(target, name) {order.push(name); if(name === member) throw sentinel; return undefined;}});
|
||||
const expected = ['message', 'source', 'streamErrorCode'].slice(0, ['message', 'source', 'streamErrorCode'].indexOf(member)+1);
|
||||
return throws(() => new WT(message, options), realm, sentinel) && JSON.stringify(order) === JSON.stringify(expected);
|
||||
});
|
||||
}
|
||||
test('WebTransportError conversion order', () => {
|
||||
const order = [];
|
||||
const e = new WT({toString() {order.push('message'); return utf16;}}, new Proxy({}, {get(target, name) {
|
||||
order.push(name); return name === 'source' ? {toString() {order.push('source string'); return 'session';}} : {valueOf() {order.push('code number'); return 4.5;}};
|
||||
}}));
|
||||
return e.message === utf16 && e.source === 'session' && e.streamErrorCode === 4 &&
|
||||
JSON.stringify(order) === JSON.stringify(['message', 'source', 'source string', 'streamErrorCode', 'code number']);
|
||||
});
|
||||
test('WebTransportError null options avoid prototype getters', () => {
|
||||
let reads = 0;
|
||||
Object.defineProperty(realm.Object.prototype, 'source', {configurable:true, get() {reads++; throw sentinel;}});
|
||||
try { return new WT('', null).source === 'stream' && new WT('', undefined).source === 'stream' && reads === 0; }
|
||||
finally { delete realm.Object.prototype.source; }
|
||||
});
|
||||
for (const name of ['source', 'streamErrorCode']) {
|
||||
test('WebTransportError descriptor ' + name, () => {
|
||||
const d = Object.getOwnPropertyDescriptor(WT.prototype, name);
|
||||
const e = createWT('', {streamErrorCode:42});
|
||||
return d.get.length === 0 && d.get.name === 'get ' + name && d.set === undefined && d.enumerable && d.configurable &&
|
||||
!Object.hasOwn(e, name) && !Reflect.set(e, name, 99);
|
||||
});
|
||||
for (const kind of ['plain', 'prototype', 'inherit', 'author proxy', 'revoked proxy', 'other error']) {
|
||||
test('WebTransportError receiver ' + name + ' ' + kind, () => {
|
||||
const e = createWT(); let traps = 0;
|
||||
const proxy = new Proxy(e, {get() {traps++; throw sentinel;}, getPrototypeOf() {traps++; throw sentinel;}});
|
||||
const revoked = Proxy.revocable(e, {}); revoked.revoke();
|
||||
const bad = {'plain':{}, 'prototype':WT.prototype, 'inherit':Object.create(e), 'author proxy':proxy,
|
||||
'revoked proxy':revoked.proxy, 'other error':new realm.DOMException()}[kind];
|
||||
return throws(() => Object.getOwnPropertyDescriptor(WT.prototype, name).get.call(bad), realm) && traps === 0;
|
||||
});
|
||||
}
|
||||
}
|
||||
test('WebTransportError brand survives prototype removal', () => {
|
||||
const e = createWT(utf16, {source:'session', streamErrorCode:3});
|
||||
Object.setPrototypeOf(e, null);
|
||||
return Object.getOwnPropertyDescriptor(WT.prototype, 'source').get.call(e) === 'session' &&
|
||||
Object.getOwnPropertyDescriptor(realm.DOMException.prototype, 'message').get.call(e) === utf16;
|
||||
});
|
||||
test('WebTransportError subclass and Reflect.construct', () => {
|
||||
class Derived extends WT {}
|
||||
const e = new Derived(utf16, {streamErrorCode:1.5});
|
||||
function NewTarget() {}
|
||||
const other = Reflect.construct(WT, [utf16, {source:'session'}], NewTarget);
|
||||
return e instanceof Derived && e.streamErrorCode === 2 && Object.getPrototypeOf(other) === NewTarget.prototype &&
|
||||
Object.getOwnPropertyDescriptor(WT.prototype, 'source').get.call(other) === 'session';
|
||||
});
|
||||
for (const source of ['stream', 'session']) for (const code of [null, 0, 42, 4294967295]) {
|
||||
test('WebTransportError structured clone ' + source + ' ' + code, () => {
|
||||
const e = createWT(utf16, {source, streamErrorCode:code});
|
||||
const sentinel = {};
|
||||
for (const name of ['name','message','source','streamErrorCode']) Object.defineProperty(e, name, {enumerable:true, get() {throw sentinel;}});
|
||||
const cloned = realm.structuredClone({a:e, b:e});
|
||||
return cloned.a === cloned.b && cloned.a !== e && Object.getPrototypeOf(cloned.a) === realm.WebTransportError.prototype &&
|
||||
cloned.a.name === 'WebTransportError' && cloned.a.message === utf16 && cloned.a.source === source &&
|
||||
cloned.a.streamErrorCode === code && cloned.a.code === 0 && !Object.hasOwn(cloned.a, 'name');
|
||||
});
|
||||
}
|
||||
test('WebTransportError author Proxy clone rejects without traps', () => {
|
||||
let traps = 0, error;
|
||||
const proxy = new Proxy(createWT(), {ownKeys() {traps++; throw sentinel;}, get() {traps++; throw sentinel;}});
|
||||
try { realm.structuredClone(proxy); } catch(caught) {error = caught;}
|
||||
return error instanceof realm.DOMException && error.name === 'DataCloneError' && traps === 0;
|
||||
});
|
||||
test('OverconstrainedError Window exposure', () => typeof OC === (typeof document === 'undefined' ? 'undefined' : 'function'));
|
||||
if (typeof document !== 'undefined') {
|
||||
test('OverconstrainedError inheritance and defaults', () => {
|
||||
const e = new OC('width');
|
||||
return e instanceof OC && e instanceof realm.DOMException && e instanceof realm.Error &&
|
||||
Object.getPrototypeOf(OC.prototype) === realm.DOMException.prototype && e.name === 'OverconstrainedError' &&
|
||||
e.constraint === 'width' && e.message === '' && e.code === 0;
|
||||
});
|
||||
test('OverconstrainedError length and tag', () => OC.length === 1 && Object.prototype.toString.call(new OC('')) === '[object OverconstrainedError]');
|
||||
test('OverconstrainedError missing arity', () => throws(() => new OC(), realm));
|
||||
test('OverconstrainedError new before conversion', () => {
|
||||
let reads = 0; return throws(() => OC({toString(){reads++; return '';}}), realm) && reads === 0;
|
||||
});
|
||||
for (const [value, expected] of [[undefined,'undefined'], [null,'null'], [true,'true'], [0,'0'], [1n,'1'], [utf16,utf16]]) {
|
||||
test('OverconstrainedError constraint ' + String(value), () => new OC(value).constraint === expected);
|
||||
}
|
||||
for (const [value, expected] of [[undefined,''], [null,'null'], [true,'true'], [0,'0'], [1n,'1'], [utf16,utf16]]) {
|
||||
test('OverconstrainedError message ' + String(value), () => new OC(utf16,value).message === expected);
|
||||
}
|
||||
test('OverconstrainedError conversion order', () => {
|
||||
const order=[]; const e=new OC({toString(){order.push('constraint');return utf16;}},{toString(){order.push('message');return utf16;}});
|
||||
return e.constraint === utf16 && e.message === utf16 && JSON.stringify(order) === '["constraint","message"]';
|
||||
});
|
||||
test('OverconstrainedError Symbol constraint before message', () => {
|
||||
let reads=0; return throws(() => new OC(Symbol(), {toString(){reads++;return '';}}),realm) && reads===0;
|
||||
});
|
||||
test('OverconstrainedError Symbol message', () => throws(() => new OC('',Symbol()),realm));
|
||||
for(const member of ['constraint','message']) test('OverconstrainedError exception '+member, () => {
|
||||
let reads=0;
|
||||
return throws(() => new OC({toString(){reads++;if(member==='constraint')throw sentinel;return utf16;}},
|
||||
{toString(){reads++;throw sentinel;}}),realm,sentinel) && reads===(member==='constraint'?1:2);
|
||||
});
|
||||
test('OverconstrainedError descriptor', () => {
|
||||
const d=Object.getOwnPropertyDescriptor(OC.prototype,'constraint');const e=new OC(utf16);
|
||||
return d.get.name === 'get constraint' && d.get.length === 0 && d.set === undefined && d.enumerable && d.configurable &&
|
||||
!Object.hasOwn(e,'constraint') && !Reflect.set(e,'constraint','changed');
|
||||
});
|
||||
for(const kind of ['plain','prototype','inherit','author proxy','revoked proxy','other error']) test('OverconstrainedError receiver '+kind,() => {
|
||||
const e=new OC(utf16);let traps=0;
|
||||
const proxy=new Proxy(e,{get(){traps++;throw sentinel;},getPrototypeOf(){traps++;throw sentinel;}});
|
||||
const revoked=Proxy.revocable(e,{});revoked.revoke();
|
||||
const bad={'plain':{},'prototype':OC.prototype,'inherit':Object.create(e),'author proxy':proxy,'revoked proxy':revoked.proxy,'other error':new realm.DOMException()}[kind];
|
||||
return throws(()=>Object.getOwnPropertyDescriptor(OC.prototype,'constraint').get.call(bad),realm)&&traps===0;
|
||||
});
|
||||
test('OverconstrainedError subclass and native brand', () => {
|
||||
class Derived extends OC {} const e=new Derived(utf16,utf16); Object.setPrototypeOf(e,null);
|
||||
return Object.getOwnPropertyDescriptor(OC.prototype,'constraint').get.call(e)===utf16 &&
|
||||
Object.getOwnPropertyDescriptor(realm.DOMException.prototype,'message').get.call(e)===utf16;
|
||||
});
|
||||
test('OverconstrainedError nonserializable subclass', () => {
|
||||
let error; try {realm.structuredClone(new OC('width'));}catch(caught){error=caught;}
|
||||
return error instanceof realm.DOMException && error.name==='DataCloneError';
|
||||
});
|
||||
}
|
||||
}
|
||||
if (typeof document === 'undefined') run(globalThis, 'worker');
|
||||
else {
|
||||
run(globalThis, 'main');
|
||||
run(document.querySelector('iframe').contentWindow, 'iframe');
|
||||
check('cross realm error getters and clone', () => {
|
||||
const child=document.querySelector('iframe').contentWindow;
|
||||
const wt=new child.WebTransportError(utf16,{source:'session',streamErrorCode:4.5});
|
||||
const oc=new child.OverconstrainedError(utf16,utf16);
|
||||
return Object.getOwnPropertyDescriptor(WebTransportError.prototype,'streamErrorCode').get.call(wt)===4 &&
|
||||
Object.getOwnPropertyDescriptor(OverconstrainedError.prototype,'constraint').get.call(oc)===utf16 &&
|
||||
Object.getOwnPropertyDescriptor(DOMException.prototype,'message').get.call(wt)===utf16 &&
|
||||
Object.getPrototypeOf(structuredClone(wt))===WebTransportError.prototype &&
|
||||
Object.getPrototypeOf(child.structuredClone(new WebTransportError()))===child.WebTransportError.prototype;
|
||||
});
|
||||
}
|
||||
globalThis.__uiEventResults={complete:true,total:checks.length,passed:checks.filter(row=>row.passed).length,checks};
|
||||
if(typeof document==='undefined') postMessage(globalThis.__uiEventResults);
|
||||
return true;
|
||||
})()
|
||||
@@ -0,0 +1,125 @@
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn media_and_transport_errors_preserve_native_values_and_conversion_order() {
|
||||
let mut vm = new_storage_page_task_executor_test_vm("https://value-errors.test/");
|
||||
vm.eval("document.body.innerHTML = '<iframe></iframe>'")
|
||||
.unwrap();
|
||||
vm.eval(include_str!("value_errors.js")).unwrap();
|
||||
assert_eq!(vm.eval("__uiEventResults.complete").unwrap(), "true");
|
||||
assert_eq!(vm.eval("__uiEventResults.total").unwrap(), "239");
|
||||
assert_eq!(
|
||||
vm.eval("JSON.stringify(__uiEventResults.checks.filter(row => !row.passed))")
|
||||
.unwrap(),
|
||||
"[]"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn media_and_transport_error_getters_accept_native_proxies_and_reject_author_wrappers() {
|
||||
let mut vm = new_storage_page_task_executor_test_vm("https://value-error-proxies.test/");
|
||||
vm.eval(r#"document.body.innerHTML='<iframe></iframe>';
|
||||
globalThis.childWindow=document.querySelector('iframe').contentWindow;
|
||||
globalThis.errors=[new childWindow.OverconstrainedError('w\ud800','m\udfff'),
|
||||
new childWindow.WebTransportError('m\udfff',{source:'session',streamErrorCode:4294967295})];"#).unwrap();
|
||||
let context_ptr = &vm.page_default_context as *const _;
|
||||
vm.with_context_scope_by_ptr_and_checkpoint_for_test(context_ptr, |scope, _| {
|
||||
let global = scope.get_current_context().global(scope);
|
||||
let key = crate::util::v8str(scope, "errors");
|
||||
let errors =
|
||||
v8::Local::<v8::Array>::try_from(global.get(scope, key.into()).unwrap()).unwrap();
|
||||
let proxies = v8::Array::new(scope, 2);
|
||||
for index in 0..2 {
|
||||
let object =
|
||||
v8::Local::<v8::Object>::try_from(errors.get_index(scope, index).unwrap()).unwrap();
|
||||
let handler = crate::util::new_null_prototype_object(scope);
|
||||
let proxy = v8::Proxy::new(scope, object, handler).unwrap();
|
||||
moli_webapi_declare::register_web_api_proxy(scope, proxy).unwrap();
|
||||
assert!(crate::web_api_interfaces::DOMException::is_instance(
|
||||
scope,
|
||||
proxy.into()
|
||||
));
|
||||
assert_eq!(proxies.set_index(scope, index, proxy.into()), Some(true));
|
||||
}
|
||||
let key = crate::util::v8str(scope, "nativeProxies");
|
||||
assert_eq!(
|
||||
global.create_data_property(scope, key.into(), proxies.into()),
|
||||
Some(true)
|
||||
);
|
||||
Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
assert_eq!(vm.eval(r#"(() => {
|
||||
for(const realm of [window,childWindow]) {
|
||||
for(let index=0;index<errors.length;index++) {
|
||||
const original=errors[index],proxy=nativeProxies[index];
|
||||
const members=index===0?['constraint']:['source','streamErrorCode'];
|
||||
const interfaceName=index===0?'OverconstrainedError':'WebTransportError';
|
||||
for(const name of [...members,'message','name','code']) {
|
||||
const prototype=members.includes(name)?realm[interfaceName].prototype:realm.DOMException.prototype;
|
||||
const getter=Object.getOwnPropertyDescriptor(prototype,name).get;
|
||||
if(getter.call(proxy)!==getter.call(original))throw Error('native value identity');
|
||||
let traps=0;
|
||||
const author=new Proxy(proxy,{get(){traps++;throw 42;},getPrototypeOf(){traps++;throw 43;}});
|
||||
const revoked=Proxy.revocable(proxy,{});revoked.revoke();
|
||||
for(const receiver of [author,revoked.proxy,Object.create(original)]) {
|
||||
let error;try{getter.call(receiver);}catch(caught){error=caught;}
|
||||
if(Object.getPrototypeOf(error)!==realm.TypeError.prototype||traps)throw Error('author wrapper and callee realm');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
})()"#).unwrap(), "true");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn web_transport_error_deserialization_respects_native_realm_exposure() {
|
||||
let mut source = new_storage_page_task_executor_test_vm("https://transport-error-source.test/");
|
||||
source.eval("globalThis.value=new WebTransportError('message',{source:'session',streamErrorCode:42})").unwrap();
|
||||
let context_ptr = &source.page_default_context as *const _;
|
||||
let mut payload = None;
|
||||
source
|
||||
.with_context_scope_by_ptr_and_checkpoint_for_test(context_ptr, |scope, _| {
|
||||
let global = scope.get_current_context().global(scope);
|
||||
let key = crate::util::v8str(scope, "value");
|
||||
let value = global.get(scope, key.into()).unwrap();
|
||||
payload = crate::structured_clone::serialize_for_wire_for_storage(scope, value);
|
||||
Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
let payload = payload.expect("WebTransportError should serialize in its exposed realm");
|
||||
drop(source);
|
||||
let mut target =
|
||||
new_storage_page_task_executor_test_vm("http://transport-error-insecure.test/");
|
||||
assert_eq!(
|
||||
target
|
||||
.eval("isSecureContext+'|'+typeof WebTransportError")
|
||||
.unwrap(),
|
||||
"false|undefined"
|
||||
);
|
||||
let context_ptr = &target.page_default_context as *const _;
|
||||
target
|
||||
.with_context_scope_by_ptr_and_checkpoint_for_test(context_ptr, |scope, _| {
|
||||
let scope = std::pin::pin!(v8::TryCatch::new(scope));
|
||||
let scope = &mut scope.init();
|
||||
assert!(crate::structured_clone::deserialize_from_wire(scope, &payload).is_none());
|
||||
let exception = scope
|
||||
.exception()
|
||||
.expect("unexposed interface should throw DataCloneError");
|
||||
let exception = v8::Local::<v8::Object>::try_from(exception).unwrap();
|
||||
assert!(crate::web_api_interfaces::DOMException::is_instance(
|
||||
scope, exception
|
||||
));
|
||||
let key = crate::util::v8str(scope, "name");
|
||||
assert_eq!(
|
||||
exception
|
||||
.get(scope, key.into())
|
||||
.unwrap()
|
||||
.to_rust_string_lossy(scope),
|
||||
"DataCloneError"
|
||||
);
|
||||
Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
@@ -49,6 +49,50 @@ fn indexed_db_roundtrips_image_data_with_graph_identity() {
|
||||
use super::*;
|
||||
use moli_url::origin_ascii_serialization;
|
||||
|
||||
#[test]
|
||||
fn indexed_db_roundtrips_web_transport_error_native_payloads_without_author_access() {
|
||||
let mut vm = new_storage_page_task_executor_test_vm("https://indexeddb-transport-error.test/");
|
||||
vm.eval(r#"(() => {
|
||||
globalThis.transportErrorStorageResult='pending';
|
||||
const WT=WebTransportError,message='m\u0000\ud800\udfff';
|
||||
const originals=[];
|
||||
for(const source of ['stream','session'])for(const streamErrorCode of [null,0,42,4294967295]) {
|
||||
const error=new WT(message,{source,streamErrorCode});
|
||||
Object.setPrototypeOf(error,null);
|
||||
for(const name of ['message','source','streamErrorCode','stack'])Object.defineProperty(error,name,{enumerable:true,get(){throw 42;}});
|
||||
originals.push({error,alias:error,source,streamErrorCode});
|
||||
}
|
||||
const value={rows:originals};value.self=value;
|
||||
const open=indexedDB.open('transport-errors-'+Math.random(),1);
|
||||
const fail=event=>{transportErrorStorageResult='error:'+event.target.error?.name;};
|
||||
open.onerror=fail;open.onupgradeneeded=()=>open.result.createObjectStore('values');
|
||||
open.onsuccess=()=>{
|
||||
const db=open.result,write=db.transaction('values','readwrite');
|
||||
write.onerror=fail;write.objectStore('values').put(value,'value');
|
||||
write.oncomplete=()=>{
|
||||
const descriptor=Object.getOwnPropertyDescriptor(window,'WebTransportError');
|
||||
Object.defineProperty(window,'WebTransportError',{configurable:true,get(){throw 43;}});
|
||||
const read=db.transaction('values').objectStore('values').get('value');read.onerror=fail;
|
||||
read.onsuccess=()=>{
|
||||
try {
|
||||
const clone=read.result;
|
||||
const valid=clone.self===clone&&clone.rows.length===8&&clone.rows.every((row,index)=>
|
||||
Object.getPrototypeOf(row.error)===WT.prototype&&row.error!==originals[index].error&&row.error===row.alias&&
|
||||
row.error.name==='WebTransportError'&&row.error.message===message&&row.error.source===row.source&&
|
||||
row.error.streamErrorCode===row.streamErrorCode&&row.error.code===0&&!Object.hasOwn(row.error,'message'));
|
||||
transportErrorStorageResult=String(valid);
|
||||
} finally {Object.defineProperty(window,'WebTransportError',descriptor);db.close();}
|
||||
};
|
||||
};
|
||||
};
|
||||
})()"#).expect("WebTransportError IndexedDB round trip should schedule");
|
||||
assert_eq!(
|
||||
vm.eval_after_selected_page_tasks("String(transportErrorStorageResult)")
|
||||
.unwrap(),
|
||||
"true"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn indexed_db_first_use_ignores_public_constructor_overrides() {
|
||||
let fixture = include_str!("../../../tests/fixtures/indexeddb-first-use.js");
|
||||
|
||||
@@ -11,17 +11,17 @@ use crate::{
|
||||
CryptoKeyAlgorithmClonePayload, CryptoKeyClonePayload, EncodedVideoChunkClonePayload,
|
||||
EncodedVideoChunkType, FileSystemFileSnapshotClonePayload, FileSystemHandleClonePayload,
|
||||
GeometryClonePayload, ImageDataClonePayload, ReadableStreamClonePayload,
|
||||
TransformStreamClonePayload, WritableStreamClonePayload,
|
||||
attach_file_system_file_snapshot_clone_payload,
|
||||
TransformStreamClonePayload, WebTransportErrorClonePayload, WebTransportErrorSource,
|
||||
WritableStreamClonePayload, attach_file_system_file_snapshot_clone_payload,
|
||||
build_encoded_video_chunk_from_clone_payload, build_file_list_object, build_file_object,
|
||||
build_file_system_handle_from_clone_payload, build_geometry_object_from_clone_payload,
|
||||
build_image_data_object_from_clone_payload, build_readable_stream_clone_shell,
|
||||
build_transform_stream_clone_shell, build_writable_stream_clone_shell,
|
||||
crypto_key_clone_payload_from_object, crypto_key_object_from_clone_payload,
|
||||
detach_message_port_owner_for_transfer, detach_transferred_message_port,
|
||||
dom_exception_clone_fields, encoded_video_chunk_clone_payload_from_object,
|
||||
ensure_message_port_wrapper_for_id, file_list_files_from_object,
|
||||
file_system_file_snapshot_clone_payload_from_object,
|
||||
build_transform_stream_clone_shell, build_web_transport_error_from_clone_payload,
|
||||
build_writable_stream_clone_shell, crypto_key_clone_payload_from_object,
|
||||
crypto_key_object_from_clone_payload, detach_message_port_owner_for_transfer,
|
||||
detach_transferred_message_port, dom_exception_clone_fields,
|
||||
encoded_video_chunk_clone_payload_from_object, ensure_message_port_wrapper_for_id,
|
||||
file_list_files_from_object, file_system_file_snapshot_clone_payload_from_object,
|
||||
file_system_handle_clone_payload_from_object, geometry_clone_payload_from_object,
|
||||
image_data_clone_payload_from_object, initialize_readable_stream_clone_shell,
|
||||
initialize_transform_stream_clone_shell, initialize_writable_stream_clone_shell,
|
||||
@@ -29,6 +29,7 @@ use crate::{
|
||||
prepare_readable_stream_transfer, prepare_transform_stream_transfer,
|
||||
prepare_writable_stream_transfer, quota_exceeded_error_clone_fields,
|
||||
require_internal_stream_value, selected_file_from_object,
|
||||
web_transport_error_clone_payload_from_object,
|
||||
},
|
||||
dom::native::SelectedFile,
|
||||
types::MessagePortId,
|
||||
@@ -51,6 +52,7 @@ const HOST_OBJECT_TAG_TRANSFORM_STREAM: u32 = 10;
|
||||
pub(crate) const HOST_OBJECT_TAG_GEOMETRY: u32 = 11;
|
||||
pub(crate) const HOST_OBJECT_TAG_FILE_LIST: u32 = 12;
|
||||
const HOST_OBJECT_TAG_ENCODED_VIDEO_CHUNK: u32 = 13;
|
||||
pub(crate) const HOST_OBJECT_TAG_WEB_TRANSPORT_ERROR: u32 = 14;
|
||||
|
||||
const GEOMETRY_KIND_DOM_POINT_READONLY: u32 = 0;
|
||||
const GEOMETRY_KIND_DOM_POINT: u32 = 1;
|
||||
@@ -576,6 +578,13 @@ impl v8::ValueSerializerImpl for WireSerializer {
|
||||
return Some(true);
|
||||
}
|
||||
}
|
||||
Some("WebTransportError") => {
|
||||
if let Some(payload) = web_transport_error_clone_payload_from_object(scope, object)
|
||||
{
|
||||
write_web_transport_error_payload(serializer, &payload);
|
||||
return Some(true);
|
||||
}
|
||||
}
|
||||
Some("QuotaExceededError") => {
|
||||
if let Some((message, quota, requested)) =
|
||||
quota_exceeded_error_clone_fields(scope, object)
|
||||
@@ -846,6 +855,10 @@ impl v8::ValueDeserializerImpl for WireDeserializer {
|
||||
None
|
||||
})
|
||||
}
|
||||
HOST_OBJECT_TAG_WEB_TRANSPORT_ERROR => {
|
||||
let payload = read_web_transport_error_payload(deserializer)?;
|
||||
build_web_transport_error_from_clone_payload(scope, &payload)
|
||||
}
|
||||
HOST_OBJECT_TAG_DOM_EXCEPTION => {
|
||||
let message = read_string(deserializer)?;
|
||||
let name = read_string(deserializer)?;
|
||||
@@ -1173,6 +1186,57 @@ pub(crate) fn read_crypto_key_payload<'s>(
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn write_web_transport_error_payload(
|
||||
serializer: &dyn v8::ValueSerializerHelper,
|
||||
payload: &WebTransportErrorClonePayload,
|
||||
) {
|
||||
serializer.write_uint32(HOST_OBJECT_TAG_WEB_TRANSPORT_ERROR);
|
||||
// A new wire tag keeps lossless DOMString code units separate from the
|
||||
// existing UTF-8 DOMException encoding retained by older stored values.
|
||||
let bytes: Vec<_> = payload
|
||||
.message
|
||||
.iter()
|
||||
.flat_map(|unit| unit.to_le_bytes())
|
||||
.collect();
|
||||
write_raw_vec(serializer, &bytes);
|
||||
serializer.write_uint32(match payload.source {
|
||||
WebTransportErrorSource::Stream => 0,
|
||||
WebTransportErrorSource::Session => 1,
|
||||
});
|
||||
serializer.write_uint32(u32::from(payload.stream_error_code.is_some()));
|
||||
if let Some(code) = payload.stream_error_code {
|
||||
serializer.write_uint32(code);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn read_web_transport_error_payload(
|
||||
deserializer: &dyn v8::ValueDeserializerHelper,
|
||||
) -> Option<WebTransportErrorClonePayload> {
|
||||
let bytes = read_raw_vec(deserializer)?;
|
||||
if !bytes.len().is_multiple_of(2) {
|
||||
return None;
|
||||
}
|
||||
let message = bytes
|
||||
.chunks_exact(2)
|
||||
.map(|units| u16::from_le_bytes([units[0], units[1]]))
|
||||
.collect();
|
||||
let source = match read_u32(deserializer)? {
|
||||
0 => WebTransportErrorSource::Stream,
|
||||
1 => WebTransportErrorSource::Session,
|
||||
_ => return None,
|
||||
};
|
||||
let stream_error_code = match read_u32(deserializer)? {
|
||||
0 => None,
|
||||
1 => Some(read_u32(deserializer)?),
|
||||
_ => return None,
|
||||
};
|
||||
Some(WebTransportErrorClonePayload {
|
||||
message,
|
||||
source,
|
||||
stream_error_code,
|
||||
})
|
||||
}
|
||||
|
||||
fn write_string(serializer: &dyn v8::ValueSerializerHelper, value: &str) {
|
||||
write_raw_vec(serializer, value.as_bytes());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user