refactor(geometry): validate DOMRect receivers through native bindings

This commit is contained in:
ldm0
2026-10-07 15:16:22 +08:00
committed by Donough Liu
parent f6ba3a43b0
commit 846f1f6c1b
3 changed files with 53 additions and 37 deletions
@@ -36,7 +36,7 @@ struct DomRectReadOnlyObjectDeclaration {
}
#[derive(WebApiFunctionTemplate)]
#[webapi(interface = web_api_interfaces::DOMRectReadOnly)]
#[webapi(interface = web_api_interfaces::DOMRectReadOnly, receiver)]
struct DomRectReadOnlyPrototypeDeclaration {
#[webapi(
accessor_property,
@@ -99,7 +99,7 @@ struct DomRectReadOnlyPrototypeDeclaration {
}
#[derive(WebApiFunctionTemplate)]
#[webapi(interface = web_api_interfaces::DOMRect)]
#[webapi(interface = web_api_interfaces::DOMRect, receiver)]
struct DomRectPrototypeDeclaration {
#[webapi(
accessor_property,
@@ -404,13 +404,9 @@ fn dom_rect_writable_getter_callback<'s>(
rv.set_undefined();
return;
};
if !dom_rect_receiver_branded(scope, args.this()) {
throw_type_error(scope, "Illegal invocation");
return;
}
rv.set(
get_private_value(scope, args.this(), slot).unwrap_or_else(|| v8::undefined(scope).into()),
);
let receiver = moli_webapi_declare::web_api_object_target(scope, args.this())
.expect("validated DOMRectReadOnly receiver");
rv.set(get_private_value(scope, receiver, slot).unwrap_or_else(|| v8::undefined(scope).into()));
}
fn dom_rect_readonly_getter_callback<'s>(
@@ -427,11 +423,9 @@ fn dom_rect_readonly_getter_callback<'s>(
rv.set_undefined();
return;
};
if !dom_rect_receiver_branded(scope, args.this()) {
throw_type_error(scope, "Illegal invocation");
return;
}
let value = dom_rect_readonly_attribute_value(scope, args.this(), attribute);
let receiver = moli_webapi_declare::web_api_object_target(scope, args.this())
.expect("validated DOMRectReadOnly receiver");
let value = dom_rect_readonly_attribute_value(scope, receiver, attribute);
rv.set(v8::Number::new(scope, value).into());
}
@@ -449,10 +443,8 @@ fn dom_rect_setter_callback<'s>(
rv.set_undefined();
return;
};
if !dom_rect_receiver_branded(scope, args.this()) {
throw_type_error(scope, "Illegal invocation");
return;
}
let receiver = moli_webapi_declare::web_api_object_target(scope, args.this())
.expect("validated DOMRect receiver");
let value = match webidl::convert::<webidl::UnrestrictedDouble>(
scope,
args.get(0),
@@ -464,12 +456,7 @@ fn dom_rect_setter_callback<'s>(
return;
}
};
set_private_value(
scope,
args.this(),
slot,
v8::Number::new(scope, value).into(),
);
set_private_value(scope, receiver, slot, v8::Number::new(scope, value).into());
rv.set_undefined();
}
@@ -478,11 +465,8 @@ fn dom_rect_to_json_callback<'s>(
args: v8::FunctionCallbackArguments<'s>,
mut rv: v8::ReturnValue<'_, v8::Value>,
) {
let this = args.this();
if !dom_rect_receiver_branded(scope, this) {
throw_type_error(scope, "Illegal invocation");
return;
}
let this = moli_webapi_declare::web_api_object_target(scope, args.this())
.expect("validated DOMRectReadOnly receiver");
let declaration = DomRectJsonDeclaration {
x: dom_rect_slot(this, scope, DOM_RECT_X_SLOT),
y: dom_rect_slot(this, scope, DOM_RECT_Y_SLOT),
@@ -554,20 +538,14 @@ fn dom_rect_slot<'s>(
.unwrap_or(0.0)
}
fn dom_rect_receiver_branded<'s>(
scope: &mut v8::PinScope<'s, '_>,
receiver: v8::Local<'s, v8::Object>,
) -> bool {
web_api_interfaces::DOMRectReadOnly::is_instance(scope, receiver)
}
pub(crate) fn dom_rect_clone_data<'s>(
scope: &mut v8::PinScope<'s, '_>,
object: v8::Local<'s, v8::Object>,
) -> Option<(bool, [f64; 4])> {
if !dom_rect_receiver_branded(scope, object) {
if !web_api_interfaces::DOMRectReadOnly::is_instance(scope, object) {
return None;
}
let object = moli_webapi_declare::web_api_object_target(scope, object)?;
let mutable = web_api_interfaces::DOMRect::is_instance(scope, object);
Some((
mutable,
@@ -0,0 +1,37 @@
use super::*;
#[test]
fn domrect_native_receiver_checks_preserve_interface_and_callee_realm() {
let mut vm = new_parsed_test_vm(
"https://domrect_receiver_consolidation.test/",
"<!doctype html><body></body>",
);
assert_eq!(vm.eval(r#"(() => {
const child=document.body.appendChild(document.createElement('iframe')).contentWindow;
let conversions=0,traps=0;
const value={valueOf(){conversions++;return 7;}};
for(const realm of [window,child]){
const mutable=new realm.DOMRect(1,2,3,4),readonly=new realm.DOMRectReadOnly(1,2,3,4);
const revoked=Proxy.revocable(mutable,{});revoked.revoke();
const invalid=[{},Object.create(mutable),Object.create(realm.DOMRect.prototype),new Proxy(mutable,{get(){traps++;throw Error('trap');}}),revoked.proxy];
for(const name of ['x','y','width','height']){
const getter=Object.getOwnPropertyDescriptor(realm.DOMRectReadOnly.prototype,name).get;
const setter=Object.getOwnPropertyDescriptor(realm.DOMRect.prototype,name).set;
for(const target of invalid){
for(const invoke of [()=>getter.call(target),()=>setter.call(target,value)]){
let caught;try{invoke();}catch(error){caught=error;}
if(!(caught instanceof realm.TypeError))throw Error(name+' receiver realm');
}
}
let caught;try{setter.call(readonly,value);}catch(error){caught=error;}
if(!(caught instanceof realm.TypeError))throw Error('readonly accepted mutable setter');
}
const foreign=realm===window?new child.DOMRect(5,6,7,8):new DOMRect(5,6,7,8);
const setter=Object.getOwnPropertyDescriptor(realm.DOMRect.prototype,'x').set;
setter.call(foreign,9);if(foreign.x!==9)throw Error('genuine foreign DOMRect');
for(const target of invalid){let caught;try{realm.DOMRectReadOnly.prototype.toJSON.call(target);}catch(error){caught=error;}if(!(caught instanceof realm.TypeError))throw Error('toJSON receiver');}
}
if(conversions||traps)throw Error('receiver validation executed author code');
return 'ok';
})()"#).expect("native binding regression"), "ok");
}
@@ -2204,3 +2204,4 @@ mod media_device_interfaces;
mod dom_rect_factory_descriptors;
mod dom_rect_structured_clone;
mod domrect_receiver_consolidation;