mirror of
https://github.com/lexmount/moli.git
synced 2026-10-08 00:01:00 +00:00
fix(webidl): honor NewTarget realm prototype fallback
This commit is contained in:
@@ -4,12 +4,15 @@ use super::element_state::{
|
||||
set_dom_custom_element_state,
|
||||
};
|
||||
use super::html_constructor_prototype::{
|
||||
receiver_prototype_chain_contains_constructor_prototype,
|
||||
receiver_uses_new_target_realm_object_fallback, set_wrapper_html_constructor_prototype,
|
||||
receiver_prototype_chain_contains_constructor_prototype, set_wrapper_html_constructor_prototype,
|
||||
};
|
||||
use crate::dom::{native::CustomElementState, native::html_element_interface_name};
|
||||
|
||||
use super::super::{document_runtime::DomHandle, native_bridge::JsContextHost, util::v8_string};
|
||||
use super::super::{
|
||||
document_runtime::DomHandle,
|
||||
native_bridge::JsContextHost,
|
||||
util::{receiver_uses_new_target_realm_object_fallback, v8_string},
|
||||
};
|
||||
|
||||
pub(crate) fn create_element_from_registered_constructor<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
@@ -30,7 +33,7 @@ pub(crate) fn create_element_from_registered_constructor<'s>(
|
||||
return None;
|
||||
}
|
||||
let receiver_uses_object_fallback =
|
||||
receiver_uses_new_target_realm_object_fallback(scope, receiver, constructor);
|
||||
receiver_uses_new_target_realm_object_fallback(scope, receiver, constructor.into());
|
||||
let receiver_inherits_active_interface =
|
||||
receiver_prototype_chain_contains_constructor_prototype(
|
||||
scope,
|
||||
|
||||
@@ -2,8 +2,8 @@ use super::super::{
|
||||
dom_parser::DOM_PARSER_FOREIGN_NODE_SLOT,
|
||||
native_bridge::JsContextHost,
|
||||
util::{
|
||||
callable_relevant_context, constructor_prototype, get_private_object,
|
||||
global_constructor_prototype,
|
||||
constructor_prototype, get_private_object, global_constructor_prototype,
|
||||
new_target_realm_constructor_prototype, receiver_uses_new_target_realm_object_fallback,
|
||||
},
|
||||
};
|
||||
use super::CustomElementRegistryKey;
|
||||
@@ -51,22 +51,6 @@ fn registry_constructor_prototype<'s>(
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn receiver_uses_new_target_realm_object_fallback<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
receiver: v8::Local<'s, v8::Object>,
|
||||
new_target: v8::Local<'s, v8::Function>,
|
||||
) -> bool {
|
||||
let Some(receiver_prototype) = receiver.get_prototype(scope) else {
|
||||
return false;
|
||||
};
|
||||
let Some(object_prototype) =
|
||||
new_target_realm_constructor_prototype(scope, new_target, "Object")
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
receiver_prototype.strict_equals(object_prototype.into())
|
||||
}
|
||||
|
||||
pub(super) fn set_wrapper_html_constructor_prototype<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
wrapper: v8::Local<'s, v8::Object>,
|
||||
@@ -82,29 +66,20 @@ pub(super) fn set_wrapper_html_constructor_prototype<'s>(
|
||||
// getters twice. A non-object value is represented by the Object
|
||||
// prototype from NewTarget's relevant Realm; replace only that fallback
|
||||
// with the active HTML interface prototype from the same Realm.
|
||||
let prototype = if receiver_uses_new_target_realm_object_fallback(scope, receiver, new_target) {
|
||||
new_target_realm_constructor_prototype(scope, new_target, active_constructor_name)
|
||||
let prototype =
|
||||
if receiver_uses_new_target_realm_object_fallback(scope, receiver, new_target.into()) {
|
||||
new_target_realm_constructor_prototype(
|
||||
scope,
|
||||
new_target.into(),
|
||||
active_constructor_name,
|
||||
)
|
||||
.map(Into::into)
|
||||
.unwrap_or(prototype)
|
||||
} else {
|
||||
prototype
|
||||
};
|
||||
} else {
|
||||
prototype
|
||||
};
|
||||
let _ = wrapper.set_prototype(scope, prototype);
|
||||
if let Some(foreign) = get_private_object(scope, wrapper, DOM_PARSER_FOREIGN_NODE_SLOT) {
|
||||
let _ = foreign.set_prototype(scope, prototype);
|
||||
}
|
||||
}
|
||||
|
||||
fn new_target_realm_constructor_prototype<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
new_target: v8::Local<'s, v8::Function>,
|
||||
constructor_name: &str,
|
||||
) -> Option<v8::Local<'s, v8::Object>> {
|
||||
let context = callable_relevant_context(scope, new_target.into())?;
|
||||
let prototype = {
|
||||
let context_scope = &mut v8::ContextScope::new(scope, context);
|
||||
let prototype = global_constructor_prototype(context_scope, constructor_name)?;
|
||||
v8::Global::new(context_scope, prototype)
|
||||
};
|
||||
Some(v8::Local::new(scope, &prototype))
|
||||
}
|
||||
|
||||
@@ -20,8 +20,8 @@ use super::{
|
||||
},
|
||||
},
|
||||
util::{
|
||||
context_host_ptr_from_global_bridge, get_private_object, get_private_value,
|
||||
set_private_value, throw_type_error,
|
||||
apply_webidl_constructor_prototype_fallback, context_host_ptr_from_global_bridge,
|
||||
get_private_object, get_private_value, set_private_value, throw_type_error,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -118,9 +118,9 @@ struct DomParserPrototypeMethodsDeclaration {
|
||||
parse_from_string: (),
|
||||
}
|
||||
|
||||
pub(super) fn dom_parser_constructor_callback(
|
||||
scope: &mut v8::PinScope<'_, '_>,
|
||||
args: v8::FunctionCallbackArguments<'_>,
|
||||
pub(super) fn dom_parser_constructor_callback<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) {
|
||||
if !args.is_construct_call() {
|
||||
@@ -128,6 +128,7 @@ pub(super) fn dom_parser_constructor_callback(
|
||||
return;
|
||||
}
|
||||
let parser = args.this();
|
||||
apply_webidl_constructor_prototype_fallback(scope, parser, args.new_target(), "DOMParser");
|
||||
let Some(host_ptr) = context_host_ptr_from_global_bridge(scope) else {
|
||||
throw_type_error(scope, "DOMParser constructor has no associated Document");
|
||||
return;
|
||||
|
||||
@@ -9490,6 +9490,107 @@ fn dom_parser_uses_its_constructor_realm_associated_document() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dom_parser_non_object_new_target_prototype_uses_new_target_realm_default() {
|
||||
let mut vm = new_storage_test_vm("https://dom-parser-new-target.test/top.html");
|
||||
|
||||
let result = vm
|
||||
.eval(
|
||||
r#"
|
||||
(() => {
|
||||
const frame = document.createElement('iframe');
|
||||
(document.body || document.documentElement || document).appendChild(frame);
|
||||
const child = frame.contentWindow;
|
||||
child.history.replaceState(null, '', '/child.html');
|
||||
|
||||
const TopBad = new Function();
|
||||
TopBad.prototype = 7;
|
||||
const ChildBad = new child.Function();
|
||||
ChildBad.prototype = 7;
|
||||
|
||||
const BoundChild = Function.prototype.bind.call(new child.Function());
|
||||
BoundChild.prototype = 7;
|
||||
const BoundTop = child.Function.prototype.bind.call(new Function());
|
||||
BoundTop.prototype = 7;
|
||||
|
||||
const ProxyChild = new Proxy(new child.Function(), {});
|
||||
ProxyChild.prototype = 7;
|
||||
const ProxyTop = new child.Proxy(new Function(), {});
|
||||
ProxyTop.prototype = 7;
|
||||
|
||||
let getterCount = 0;
|
||||
const GetterProxyChild = new Proxy(new child.Function(), {
|
||||
get(target, property, receiver) {
|
||||
if (property === 'prototype') {
|
||||
getterCount += 1;
|
||||
return 7;
|
||||
}
|
||||
return Reflect.get(target, property, receiver);
|
||||
}
|
||||
});
|
||||
|
||||
const parseUrl = parser =>
|
||||
DOMParser.prototype.parseFromString.call(
|
||||
parser,
|
||||
'<html></html>',
|
||||
'text/html'
|
||||
).URL;
|
||||
const check = (parser, expectedPrototype, expectedUrl) => [
|
||||
Object.getPrototypeOf(parser) === expectedPrototype,
|
||||
parseUrl(parser) === expectedUrl
|
||||
];
|
||||
const topUrl = location.href;
|
||||
const childUrl = child.location.href;
|
||||
|
||||
return JSON.stringify({
|
||||
directTop: check(
|
||||
Reflect.construct(child.DOMParser, [], TopBad),
|
||||
DOMParser.prototype,
|
||||
childUrl
|
||||
),
|
||||
directChild: check(
|
||||
Reflect.construct(DOMParser, [], ChildBad),
|
||||
child.DOMParser.prototype,
|
||||
topUrl
|
||||
),
|
||||
boundChild: check(
|
||||
Reflect.construct(DOMParser, [], BoundChild),
|
||||
child.DOMParser.prototype,
|
||||
topUrl
|
||||
),
|
||||
boundTop: check(
|
||||
Reflect.construct(child.DOMParser, [], BoundTop),
|
||||
DOMParser.prototype,
|
||||
childUrl
|
||||
),
|
||||
proxyChild: check(
|
||||
Reflect.construct(DOMParser, [], ProxyChild),
|
||||
child.DOMParser.prototype,
|
||||
topUrl
|
||||
),
|
||||
proxyTop: check(
|
||||
Reflect.construct(child.DOMParser, [], ProxyTop),
|
||||
DOMParser.prototype,
|
||||
childUrl
|
||||
),
|
||||
getterProxyChild: check(
|
||||
Reflect.construct(DOMParser, [], GetterProxyChild),
|
||||
child.DOMParser.prototype,
|
||||
topUrl
|
||||
),
|
||||
getterCount
|
||||
});
|
||||
})()
|
||||
"#,
|
||||
)
|
||||
.expect("DOMParser NewTarget realm prototype fallback probe should evaluate");
|
||||
|
||||
assert_eq!(
|
||||
result,
|
||||
r#"{"directTop":[true,true],"directChild":[true,true],"boundChild":[true,true],"boundTop":[true,true],"proxyChild":[true,true],"proxyTop":[true,true],"getterProxyChild":[true,true],"getterCount":1}"#
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn dom_parser_retained_across_child_navigation_uses_original_document() {
|
||||
const HOST: &str = "dom-parser-retained.test";
|
||||
|
||||
Reference in New Issue
Block a user