fix(webrtc): preserve RTCError stacks and native proxy slots

This commit is contained in:
ldm0
2026-10-13 19:25:52 +08:00
committed by Donough Liu
parent 44a2e88d5a
commit 8f0ddb48e2
2 changed files with 80 additions and 1 deletions
@@ -142,6 +142,7 @@ pub(crate) fn rtc_error_constructor_callback<'s>(
nullable_double_slot_value(scope, parsed.init.sent_alert.map(f64::from)),
);
if declaration.initialize(scope, args.this()).is_ok() {
capture_dom_exception_stack(scope, args.this());
apply_webidl_constructor_prototype_fallback(
scope,
args.this(),
@@ -160,7 +161,9 @@ fn slot_getter<'s>(
let slot = v8::Local::<v8::String>::try_from(args.data())
.expect("RTCError attribute slot")
.to_rust_string_lossy(scope);
if let Some(value) = get_private_value(scope, args.this(), &slot) {
let receiver = moli_webapi_declare::web_api_object_target(scope, args.this())
.expect("RTCError receiver was validated");
if let Some(value) = get_private_value(scope, receiver, &slot) {
rv.set(value);
}
}
@@ -774,3 +774,79 @@ fn observer_and_webgl_factory_results_have_native_identity() {
"DataCloneError|DataCloneError|DataCloneError|DataCloneError"
);
}
#[test]
fn dom_exception_factories_capture_v8_stacks_without_author_lookup() {
let mut vm = new_storage_page_task_executor_test_vm("https://native-error-stack.test/");
assert_eq!(vm.eval(r#"(() => {
document.body.innerHTML = '<iframe></iframe>';
const other = document.querySelector('iframe').contentWindow;
for (const realm of [window, other]) {
const descriptor = Object.getOwnPropertyDescriptor(realm.Error, 'captureStackTrace');
Object.defineProperty(realm.Error, 'captureStackTrace', {configurable:true, get() {throw 42;}});
try {
for (const error of [new realm.DOMException('message', 'AbortError'),
new realm.QuotaExceededError('full'), new realm.RTCError({errorDetail:'dtls-failure'}, 'rtc')]) {
const stack = error.stack;
if (typeof stack !== 'string' || !stack.startsWith(error.name + ': ' + error.message) ||
!Object.hasOwn(error,'stack') || Object.getOwnPropertyDescriptor(error,'stack').enumerable) throw Error('native stack');
}
let syntax;
try { document.querySelector('['); } catch(error) { syntax = error; }
if (!(syntax instanceof DOMException) || typeof syntax.stack !== 'string') throw Error('native factory');
} finally {
if(descriptor)Object.defineProperty(realm.Error,'captureStackTrace',descriptor);
else delete realm.Error.captureStackTrace;
}
}
return true;
})()"#).unwrap(), "true");
}
#[test]
fn dom_exception_getters_read_registered_native_proxy_targets() {
let mut vm = new_storage_page_task_executor_test_vm("https://native-error-receiver.test/");
vm.eval("globalThis.errors = [new DOMException('message','AbortError'),new QuotaExceededError('full',{quota:12}),new RTCError({errorDetail:'dtls-failure'},'rtc')]").unwrap();
let context_ptr = &vm.page_default_runtime.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, 3);
for index in 0..3 {
let error =
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, error, 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(let index=0;index<errors.length;index++) {
const original=errors[index],proxy=nativeProxies[index];
for(const name of ['name','message','code']) {
const getter=Object.getOwnPropertyDescriptor(DOMException.prototype,name).get;
if(getter.call(proxy)!==getter.call(original))throw Error('native target '+name);
let traps=0,error;
const author=new Proxy(proxy,{get(){traps++;throw 42;},getPrototypeOf(){traps++;throw 43;}});
try{getter.call(author);}catch(caught){error=caught;}
if(!(error instanceof TypeError)||traps)throw Error('author proxy');
}
}
if(nativeProxies[1].quota!==12||nativeProxies[2].errorDetail!=='dtls-failure')throw Error('derived native target');
return true;
})()"#).unwrap(), "true");
}