mirror of
https://github.com/lexmount/moli.git
synced 2026-10-07 08:01:00 +00:00
fix(webidl): allocate factory results in relevant realms
This commit is contained in:
@@ -1897,10 +1897,13 @@ pub(crate) fn canvas_context_create_image_data_callback<'s>(
|
||||
rv.set(v8::undefined(scope).into());
|
||||
return;
|
||||
};
|
||||
if let Some(image_data) = build_image_data_object(scope, width, height) {
|
||||
let relevant_context = canvas_context_relevant_context(scope, args.this())
|
||||
.unwrap_or_else(|| scope.get_current_context());
|
||||
let target_scope = &mut v8::ContextScope::new(scope, relevant_context);
|
||||
if let Some(image_data) = build_image_data_object(target_scope, width, height) {
|
||||
rv.set(image_data.into());
|
||||
} else {
|
||||
rv.set(v8::undefined(scope).into());
|
||||
rv.set(v8::undefined(target_scope).into());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2010,10 +2013,13 @@ pub(crate) fn canvas_context_get_image_data_callback<'s>(
|
||||
} else {
|
||||
blank_image_data(width, height)
|
||||
};
|
||||
let Some(image_data) = build_image_data_object_with_bytes(scope, width, height, bytes)
|
||||
.or_else(|| build_image_data_object(scope, width, height))
|
||||
else {
|
||||
rv.set(v8::undefined(scope).into());
|
||||
let relevant_context = canvas_context_relevant_context(scope, args.this())
|
||||
.unwrap_or_else(|| scope.get_current_context());
|
||||
let target_scope = &mut v8::ContextScope::new(scope, relevant_context);
|
||||
let image_data = build_image_data_object_with_bytes(target_scope, width, height, bytes)
|
||||
.or_else(|| build_image_data_object(target_scope, width, height));
|
||||
let Some(image_data) = image_data else {
|
||||
rv.set(v8::undefined(target_scope).into());
|
||||
return;
|
||||
};
|
||||
rv.set(image_data.into());
|
||||
|
||||
@@ -106,6 +106,34 @@ fn current_document_object<'s>(
|
||||
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
||||
}
|
||||
|
||||
fn dom_implementation_owner_document<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
implementation: v8::Local<'s, v8::Object>,
|
||||
) -> Option<v8::Local<'s, v8::Object>> {
|
||||
get_private_object(
|
||||
scope,
|
||||
implementation,
|
||||
DOM_IMPLEMENTATION_OWNER_DOCUMENT_SLOT,
|
||||
)
|
||||
.or_else(|| current_document_object(scope))
|
||||
}
|
||||
|
||||
fn create_document_type_value<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
parsed: &DomImplementationCreateDocumentTypeArgs,
|
||||
owner_document: Option<v8::Local<'s, v8::Object>>,
|
||||
) -> Option<v8::Local<'s, v8::Value>> {
|
||||
let qualified_name = v8_string(scope, &parsed.qualified_name)?;
|
||||
let public_id = v8_string(scope, &parsed.public_id)?;
|
||||
let system_id = v8_string(scope, &parsed.system_id)?;
|
||||
let mut argv = vec![qualified_name.into(), public_id.into(), system_id.into()];
|
||||
if let Some(owner_document) = owner_document {
|
||||
argv.push(owner_document.into());
|
||||
}
|
||||
let (bridge, method) = global_bridge_method(scope, "__createDetachedDocumentType")?;
|
||||
method.call(scope, bridge.into(), &argv)
|
||||
}
|
||||
|
||||
fn dom_implementation_create_document_type_callback<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
@@ -124,30 +152,13 @@ fn dom_implementation_create_document_type_callback<'s>(
|
||||
);
|
||||
return;
|
||||
}
|
||||
let Some(qualified_name) = v8_string(scope, &parsed.qualified_name) else {
|
||||
rv.set_undefined();
|
||||
return;
|
||||
};
|
||||
let Some(public_id) = v8_string(scope, &parsed.public_id) else {
|
||||
rv.set_undefined();
|
||||
return;
|
||||
};
|
||||
let Some(system_id) = v8_string(scope, &parsed.system_id) else {
|
||||
rv.set_undefined();
|
||||
return;
|
||||
};
|
||||
let mut argv = vec![qualified_name.into(), public_id.into(), system_id.into()];
|
||||
if let Some(owner_document) =
|
||||
get_private_object(scope, args.this(), DOM_IMPLEMENTATION_OWNER_DOCUMENT_SLOT)
|
||||
.or_else(|| current_document_object(scope))
|
||||
{
|
||||
argv.push(owner_document.into());
|
||||
}
|
||||
let Some((bridge, method)) = global_bridge_method(scope, "__createDetachedDocumentType") else {
|
||||
rv.set_undefined();
|
||||
return;
|
||||
};
|
||||
match method.call(scope, bridge.into(), &argv) {
|
||||
let owner_document = dom_implementation_owner_document(scope, args.this());
|
||||
let relevant_context = owner_document
|
||||
.and_then(|document| crate::native_bridge::node_relevant_context(scope, document))
|
||||
.or_else(|| args.this().get_creation_context(scope))
|
||||
.unwrap_or_else(|| scope.get_current_context());
|
||||
let target_scope = &mut v8::ContextScope::new(scope, relevant_context);
|
||||
match create_document_type_value(target_scope, &parsed, owner_document) {
|
||||
Some(value) => rv.set(value),
|
||||
None => rv.set_undefined(),
|
||||
}
|
||||
@@ -168,39 +179,48 @@ fn dom_implementation_create_html_document_callback<'s>(
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let Some((bridge, method)) = global_bridge_method(scope, "__createDetachedHTMLDocument") else {
|
||||
rv.set_undefined();
|
||||
return;
|
||||
};
|
||||
let call_args = match parsed.title {
|
||||
Some(title) => {
|
||||
let Some(title) = v8_string(scope, &title) else {
|
||||
rv.set_undefined();
|
||||
return;
|
||||
};
|
||||
vec![title.into()]
|
||||
}
|
||||
None => Vec::new(),
|
||||
};
|
||||
match method.call(scope, bridge.into(), &call_args) {
|
||||
let owner_document = dom_implementation_owner_document(scope, args.this());
|
||||
let relevant_context = owner_document
|
||||
.and_then(|document| crate::native_bridge::node_relevant_context(scope, document))
|
||||
.or_else(|| args.this().get_creation_context(scope))
|
||||
.unwrap_or_else(|| scope.get_current_context());
|
||||
let target_scope = &mut v8::ContextScope::new(scope, relevant_context);
|
||||
match create_html_document_value(target_scope, parsed.title.as_deref()) {
|
||||
Some(value) => rv.set(value),
|
||||
None => rv.set_undefined(),
|
||||
}
|
||||
}
|
||||
|
||||
fn create_html_document_value<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
title: Option<&str>,
|
||||
) -> Option<v8::Local<'s, v8::Value>> {
|
||||
let (bridge, method) = global_bridge_method(scope, "__createDetachedHTMLDocument")?;
|
||||
let call_args = match title {
|
||||
Some(title) => vec![v8_string(scope, title)?.into()],
|
||||
None => Vec::new(),
|
||||
};
|
||||
method.call(scope, bridge.into(), &call_args)
|
||||
}
|
||||
|
||||
enum DomImplementationQualifiedName {
|
||||
Null,
|
||||
String(String),
|
||||
}
|
||||
|
||||
fn dom_implementation_create_document_qualified_name<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
value: v8::Local<'s, v8::Value>,
|
||||
) -> Option<v8::Local<'s, v8::Value>> {
|
||||
) -> Option<DomImplementationQualifiedName> {
|
||||
if value.is_null() {
|
||||
return Some(v8::null(scope).into());
|
||||
return Some(DomImplementationQualifiedName::Null);
|
||||
}
|
||||
match webidl::convert::<webidl::DomString>(
|
||||
scope,
|
||||
value,
|
||||
webidl::Context::argument("DOMImplementation.createDocument", 2),
|
||||
) {
|
||||
Ok(value) => v8_string(scope, &value.0).map(v8::Local::<v8::Value>::from),
|
||||
Ok(value) => Some(DomImplementationQualifiedName::String(value.0)),
|
||||
Err(error) => {
|
||||
webidl::throw_error(scope, &error);
|
||||
None
|
||||
@@ -208,6 +228,28 @@ fn dom_implementation_create_document_qualified_name<'s>(
|
||||
}
|
||||
}
|
||||
|
||||
fn create_document_value<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
namespace_uri: Option<&str>,
|
||||
qualified_name: &DomImplementationQualifiedName,
|
||||
doctype: Option<v8::Local<'s, v8::Object>>,
|
||||
) -> Option<v8::Local<'s, v8::Value>> {
|
||||
let namespace_uri = match namespace_uri {
|
||||
Some(namespace_uri) => v8_string(scope, namespace_uri)?.into(),
|
||||
None => v8::null(scope).into(),
|
||||
};
|
||||
let qualified_name = match qualified_name {
|
||||
DomImplementationQualifiedName::Null => v8::null(scope).into(),
|
||||
DomImplementationQualifiedName::String(value) => v8_string(scope, value)?.into(),
|
||||
};
|
||||
let doctype = doctype
|
||||
.map(v8::Local::<v8::Value>::from)
|
||||
.unwrap_or_else(|| v8::null(scope).into());
|
||||
let argv = [namespace_uri, qualified_name, doctype];
|
||||
let (bridge, method) = global_bridge_method(scope, "__createDetachedXmlDocument")?;
|
||||
method.call(scope, bridge.into(), &argv)
|
||||
}
|
||||
|
||||
fn dom_implementation_create_document_callback<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
@@ -217,32 +259,24 @@ fn dom_implementation_create_document_callback<'s>(
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let namespace_uri = match parsed.namespace_uri {
|
||||
Some(namespace_uri) => {
|
||||
let Some(namespace_uri) = v8_string(scope, &namespace_uri) else {
|
||||
rv.set_undefined();
|
||||
return;
|
||||
};
|
||||
namespace_uri.into()
|
||||
}
|
||||
None => v8::null(scope).into(),
|
||||
};
|
||||
let Some(qualified_name) =
|
||||
dom_implementation_create_document_qualified_name(scope, parsed.qualified_name)
|
||||
else {
|
||||
rv.set_undefined();
|
||||
return;
|
||||
};
|
||||
let doctype = parsed
|
||||
.doctype
|
||||
.map(v8::Local::<v8::Value>::from)
|
||||
.unwrap_or_else(|| v8::null(scope).into());
|
||||
let argv = [namespace_uri, qualified_name, doctype];
|
||||
let Some((bridge, method)) = global_bridge_method(scope, "__createDetachedXmlDocument") else {
|
||||
rv.set_undefined();
|
||||
return;
|
||||
};
|
||||
match method.call(scope, bridge.into(), &argv) {
|
||||
let owner_document = dom_implementation_owner_document(scope, args.this());
|
||||
let relevant_context = owner_document
|
||||
.and_then(|document| crate::native_bridge::node_relevant_context(scope, document))
|
||||
.or_else(|| args.this().get_creation_context(scope))
|
||||
.unwrap_or_else(|| scope.get_current_context());
|
||||
let target_scope = &mut v8::ContextScope::new(scope, relevant_context);
|
||||
match create_document_value(
|
||||
target_scope,
|
||||
parsed.namespace_uri.as_deref(),
|
||||
&qualified_name,
|
||||
parsed.doctype,
|
||||
) {
|
||||
Some(value) => rv.set(value),
|
||||
None => rv.set_undefined(),
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ struct DocumentCreateEventArgs {
|
||||
interface: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, strum::EnumString)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, strum::EnumString)]
|
||||
#[strum(serialize_all = "lowercase", ascii_case_insensitive)]
|
||||
enum DocumentCreateEventKind {
|
||||
BeforeUnloadEvent,
|
||||
@@ -68,6 +68,31 @@ fn throw_not_supported_dom_exception(scope: &mut v8::PinScope<'_, '_>, message:
|
||||
crate::context_bootstrap::throw_dom_exception_value(scope, message, "NotSupportedError");
|
||||
}
|
||||
|
||||
fn new_uninitialized_document_event<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
kind: DocumentCreateEventKind,
|
||||
) -> Option<v8::Local<'s, v8::Object>> {
|
||||
if matches!(
|
||||
kind,
|
||||
DocumentCreateEventKind::BeforeUnloadEvent | DocumentCreateEventKind::TextEvent
|
||||
) {
|
||||
return match kind {
|
||||
DocumentCreateEventKind::BeforeUnloadEvent => {
|
||||
new_uninitialized_before_unload_event(scope)
|
||||
}
|
||||
DocumentCreateEventKind::TextEvent => new_uninitialized_text_event(scope),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
}
|
||||
let global = scope.get_current_context().global(scope);
|
||||
let constructor_value = global.get(scope, v8str(scope, kind.constructor_name()).into())?;
|
||||
let constructor = v8::Local::<v8::Function>::try_from(constructor_value).ok()?;
|
||||
let empty_type = v8str(scope, "");
|
||||
let event = constructor.new_instance(scope, &[empty_type.into()])?;
|
||||
set_event_initialized(scope, event, false);
|
||||
Some(event)
|
||||
}
|
||||
|
||||
pub(super) fn document_create_event_callback<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
@@ -80,40 +105,11 @@ pub(super) fn document_create_event_callback<'s>(
|
||||
throw_not_supported_dom_exception(scope, "The provided event type is not supported.");
|
||||
return;
|
||||
};
|
||||
if matches!(
|
||||
kind,
|
||||
DocumentCreateEventKind::BeforeUnloadEvent | DocumentCreateEventKind::TextEvent
|
||||
) {
|
||||
let event = match kind {
|
||||
DocumentCreateEventKind::BeforeUnloadEvent => {
|
||||
new_uninitialized_before_unload_event(scope)
|
||||
}
|
||||
DocumentCreateEventKind::TextEvent => new_uninitialized_text_event(scope),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
match event {
|
||||
Some(event) => rv.set(event.into()),
|
||||
None => rv.set_undefined(),
|
||||
}
|
||||
return;
|
||||
}
|
||||
let ctor_name = kind.constructor_name();
|
||||
|
||||
let global = scope.get_current_context().global(scope);
|
||||
let Some(constructor_value) = global.get(scope, v8str(scope, ctor_name).into()) else {
|
||||
rv.set_undefined();
|
||||
return;
|
||||
};
|
||||
let Ok(constructor) = v8::Local::<v8::Function>::try_from(constructor_value) else {
|
||||
rv.set_undefined();
|
||||
return;
|
||||
};
|
||||
let empty_type = v8str(scope, "");
|
||||
match constructor.new_instance(scope, &[empty_type.into()]) {
|
||||
Some(event) => {
|
||||
set_event_initialized(scope, event, false);
|
||||
rv.set(event.into());
|
||||
}
|
||||
let relevant_context = crate::native_bridge::node_relevant_context(scope, args.this())
|
||||
.unwrap_or_else(|| scope.get_current_context());
|
||||
let target_scope = &mut v8::ContextScope::new(scope, relevant_context);
|
||||
match new_uninitialized_document_event(target_scope, kind) {
|
||||
Some(event) => rv.set(event.into()),
|
||||
None => rv.set_undefined(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,24 @@ pub(in crate::context_bootstrap) fn current_document_object<'s>(
|
||||
pub(in crate::context_bootstrap) fn new_range_for_document<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
document: v8::Local<'s, v8::Object>,
|
||||
) -> Option<v8::Local<'s, v8::Object>> {
|
||||
let relevant_context = crate::native_bridge::node_relevant_context(scope, document)?;
|
||||
if relevant_context != scope.get_current_context() {
|
||||
let document = v8::Global::new(scope, document);
|
||||
let range = {
|
||||
let target_scope = &mut v8::ContextScope::new(scope, relevant_context);
|
||||
let document = v8::Local::new(target_scope, &document);
|
||||
let range = new_range_for_document_in_current_context(target_scope, document)?;
|
||||
v8::Global::new(target_scope, range)
|
||||
};
|
||||
return Some(v8::Local::new(scope, &range));
|
||||
}
|
||||
new_range_for_document_in_current_context(scope, document)
|
||||
}
|
||||
|
||||
fn new_range_for_document_in_current_context<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
document: v8::Local<'s, v8::Object>,
|
||||
) -> Option<v8::Local<'s, v8::Object>> {
|
||||
let global = scope.get_current_context().global(scope);
|
||||
let ctor = global.get(scope, v8str(scope, "Range").into())?;
|
||||
|
||||
@@ -35,7 +35,20 @@ pub(in crate::native_bridge) fn node_create_attribute_callback<'s>(
|
||||
} else {
|
||||
parsed.name
|
||||
};
|
||||
match new_attr_object(scope, &name, "", None, Some(args.this()), None, None, &name) {
|
||||
let document = args.this();
|
||||
let relevant_context = crate::native_bridge::node_relevant_context(scope, document)
|
||||
.unwrap_or_else(|| scope.get_current_context());
|
||||
let target_scope = &mut v8::ContextScope::new(scope, relevant_context);
|
||||
match new_attr_object(
|
||||
target_scope,
|
||||
&name,
|
||||
"",
|
||||
None,
|
||||
Some(document),
|
||||
None,
|
||||
None,
|
||||
&name,
|
||||
) {
|
||||
Some(attr) => rv.set(attr.into()),
|
||||
None => rv.set_null(),
|
||||
}
|
||||
@@ -70,12 +83,16 @@ pub(in crate::native_bridge) fn node_create_attribute_ns_callback<'s>(
|
||||
return;
|
||||
}
|
||||
};
|
||||
let document = args.this();
|
||||
let relevant_context = crate::native_bridge::node_relevant_context(scope, document)
|
||||
.unwrap_or_else(|| scope.get_current_context());
|
||||
let target_scope = &mut v8::ContextScope::new(scope, relevant_context);
|
||||
match new_attr_object(
|
||||
scope,
|
||||
target_scope,
|
||||
&parsed.qualified_name,
|
||||
"",
|
||||
None,
|
||||
Some(args.this()),
|
||||
Some(document),
|
||||
namespace.as_deref(),
|
||||
prefix.as_deref(),
|
||||
&local_name,
|
||||
|
||||
@@ -1016,9 +1016,12 @@ pub(in crate::native_bridge::document) fn detached_document_implementation_gette
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) {
|
||||
let document = args.this();
|
||||
let global = scope.get_current_context().global(scope);
|
||||
let _ = crate::context_bootstrap::ensure_dom_implementation_singleton(scope, global);
|
||||
match ensure_detached_document_implementation(scope, document) {
|
||||
let relevant_context = crate::native_bridge::node_relevant_context(scope, document)
|
||||
.unwrap_or_else(|| scope.get_current_context());
|
||||
let target_scope = &mut v8::ContextScope::new(scope, relevant_context);
|
||||
let global = relevant_context.global(target_scope);
|
||||
let _ = crate::context_bootstrap::ensure_dom_implementation_singleton(target_scope, global);
|
||||
match ensure_detached_document_implementation(target_scope, document) {
|
||||
Some(implementation) => rv.set(implementation.into()),
|
||||
None => rv.set_null(),
|
||||
}
|
||||
|
||||
@@ -53,9 +53,12 @@ pub(in crate::native_bridge) fn node_create_node_iterator_callback<'s>(
|
||||
rv.set_null();
|
||||
return;
|
||||
};
|
||||
let relevant_context = crate::native_bridge::node_relevant_context(scope, args.this())
|
||||
.unwrap_or_else(|| scope.get_current_context());
|
||||
let target_scope = &mut v8::ContextScope::new(scope, relevant_context);
|
||||
let runtime = unsafe { &mut *runtime_ptr };
|
||||
let iterator = traversal::build_node_iterator_wrapper(
|
||||
scope,
|
||||
target_scope,
|
||||
runtime_ptr,
|
||||
runtime.native_bridge_mut(),
|
||||
root,
|
||||
@@ -93,9 +96,12 @@ pub(in crate::native_bridge) fn node_create_tree_walker_callback<'s>(
|
||||
rv.set_null();
|
||||
return;
|
||||
};
|
||||
let relevant_context = crate::native_bridge::node_relevant_context(scope, args.this())
|
||||
.unwrap_or_else(|| scope.get_current_context());
|
||||
let target_scope = &mut v8::ContextScope::new(scope, relevant_context);
|
||||
let runtime = unsafe { &mut *runtime_ptr };
|
||||
let walker = traversal::build_tree_walker_wrapper(
|
||||
scope,
|
||||
target_scope,
|
||||
runtime_ptr,
|
||||
runtime.native_bridge_mut(),
|
||||
root,
|
||||
|
||||
@@ -8,7 +8,9 @@ use crate::{
|
||||
webidl,
|
||||
};
|
||||
|
||||
use super::super::node::node_runtime_and_handle_from_object_or_detached;
|
||||
use super::super::node::{
|
||||
node_owner_document_relevant_context, node_runtime_and_handle_from_object_or_detached,
|
||||
};
|
||||
use super::{element_attribute, set_reflected_attribute};
|
||||
|
||||
const CANVAS_CONTEXT_KIND_SLOT: &str = "__moliCanvasContextKind";
|
||||
@@ -31,6 +33,17 @@ fn canvas_context_slot(kind: CanvasContextKind) -> &'static str {
|
||||
}
|
||||
}
|
||||
|
||||
fn build_canvas_context_for_kind<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
kind: CanvasContextKind,
|
||||
) -> Option<v8::Local<'s, v8::Value>> {
|
||||
match kind {
|
||||
CanvasContextKind::TwoD => build_canvas_rendering_context_2d_object(scope).map(Into::into),
|
||||
CanvasContextKind::WebGl => build_webgl_context_object(scope).map(Into::into),
|
||||
CanvasContextKind::WebGl2 => build_webgl2_context_object(scope).map(Into::into),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn html_canvas_width_getter_callback<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
@@ -179,28 +192,32 @@ pub(crate) fn canvas_get_context_callback<'s>(
|
||||
}
|
||||
}
|
||||
|
||||
let Some(value) = (match kind {
|
||||
CanvasContextKind::TwoD => build_canvas_rendering_context_2d_object(scope).map(Into::into),
|
||||
CanvasContextKind::WebGl => build_webgl_context_object(scope).map(Into::into),
|
||||
CanvasContextKind::WebGl2 => build_webgl2_context_object(scope).map(Into::into),
|
||||
}) else {
|
||||
let relevant_context = node_runtime_and_handle_from_object_or_detached(scope, canvas)
|
||||
.ok()
|
||||
.and_then(|(runtime_ptr, handle)| {
|
||||
node_owner_document_relevant_context(scope, runtime_ptr, handle)
|
||||
})
|
||||
.or_else(|| canvas.get_creation_context(scope))
|
||||
.unwrap_or_else(|| scope.get_current_context());
|
||||
let target_scope = &mut v8::ContextScope::new(scope, relevant_context);
|
||||
let Some(value) = build_canvas_context_for_kind(target_scope, kind) else {
|
||||
rv.set_null();
|
||||
return;
|
||||
};
|
||||
let Some(kind_value) = v8_string(scope, kind.label()) else {
|
||||
let Some(kind_value) = v8_string(target_scope, kind.label()) else {
|
||||
rv.set_null();
|
||||
return;
|
||||
};
|
||||
let _ = canvas.define_own_property(
|
||||
scope,
|
||||
v8str(scope, CANVAS_CONTEXT_KIND_SLOT).into(),
|
||||
target_scope,
|
||||
v8str(target_scope, CANVAS_CONTEXT_KIND_SLOT).into(),
|
||||
kind_value.into(),
|
||||
v8::PropertyAttribute::DONT_ENUM,
|
||||
);
|
||||
let slot = canvas_context_slot(kind);
|
||||
let _ = canvas.define_own_property(
|
||||
scope,
|
||||
v8str(scope, slot).into(),
|
||||
target_scope,
|
||||
v8str(target_scope, slot).into(),
|
||||
value,
|
||||
v8::PropertyAttribute::DONT_ENUM,
|
||||
);
|
||||
@@ -209,7 +226,7 @@ pub(crate) fn canvas_get_context_callback<'s>(
|
||||
CanvasContextKind::TwoD | CanvasContextKind::WebGl | CanvasContextKind::WebGl2
|
||||
) && let Ok(context) = v8::Local::<v8::Object>::try_from(value)
|
||||
{
|
||||
attach_canvas_like_context_object(scope, canvas, context);
|
||||
attach_canvas_like_context_object(target_scope, canvas, context);
|
||||
}
|
||||
rv.set(value);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use super::super::{
|
||||
CollectionKind, LiveCollectionQueryKind, collections, encode_tag_name_ns_query,
|
||||
node::{
|
||||
node_is_document, node_runtime_and_handle_from_args,
|
||||
node_is_document, node_owner_document_relevant_context, node_runtime_and_handle_from_args,
|
||||
node_runtime_and_handle_from_args_or_detached,
|
||||
node_runtime_and_handle_from_object_or_detached, receiver_has_detached_state,
|
||||
require_element_method_receiver, require_parent_node_receiver, set_wrapped_node_or_null,
|
||||
@@ -170,7 +170,24 @@ pub(in crate::native_bridge) fn node_query_selector_all_callback<'s>(
|
||||
};
|
||||
match unsafe { &*runtime_ptr }.query_selector_all(Some(handle), &parsed.selectors) {
|
||||
Ok(handles) => {
|
||||
let list = collections::build_node_list_from_handles(scope, runtime_ptr, &handles);
|
||||
// Default-world wrappers are shared across same-origin documents,
|
||||
// so their creation context need not be their document's realm.
|
||||
// Isolated-world wrappers instead retain their own world identity.
|
||||
let receiver_context = args.this().get_creation_context(scope);
|
||||
let isolated_context = receiver_context.filter(|context| {
|
||||
let host = unsafe { &*runtime_ptr };
|
||||
host.window_execution_context_identity_for_v8_context(scope, *context)
|
||||
.is_some_and(|identity| {
|
||||
!host.window_execution_context_identity_is_default_world(identity)
|
||||
})
|
||||
});
|
||||
let relevant_context = isolated_context
|
||||
.or_else(|| node_owner_document_relevant_context(scope, runtime_ptr, handle))
|
||||
.or(receiver_context)
|
||||
.unwrap_or_else(|| scope.get_current_context());
|
||||
let target_scope = &mut v8::ContextScope::new(scope, relevant_context);
|
||||
let list =
|
||||
collections::build_node_list_from_handles(target_scope, runtime_ptr, &handles);
|
||||
rv.set(list.into());
|
||||
}
|
||||
Err(error) => throw_native_selector_error_for_selector(scope, &parsed.selectors, &error),
|
||||
|
||||
@@ -51,6 +51,57 @@ fn child_document_create_text_and_comment_nodes_work() {
|
||||
"function|3|#text|hello|true|function|8|#comment|note|2"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn child_document_factories_allocate_results_in_document_realm() {
|
||||
let mut vm = new_storage_test_vm("https://child-document-factory-realm.test/");
|
||||
|
||||
let result = vm
|
||||
.eval(
|
||||
r#"
|
||||
(() => {
|
||||
const frame = document.createElement('iframe');
|
||||
(document.body || document.documentElement || document).appendChild(frame);
|
||||
const child = frame.contentWindow;
|
||||
const doc = child.document;
|
||||
const ChildObject = child.Object;
|
||||
child.Object = Object;
|
||||
const range = doc.createRange();
|
||||
const canvas = doc.createElement('canvas');
|
||||
const context = canvas.getContext('2d');
|
||||
const createdImageData = context.createImageData(1, 1);
|
||||
const readImageData = context.getImageData(0, 0, 1, 1);
|
||||
const values = {
|
||||
nodeList: doc.querySelectorAll('*'),
|
||||
attribute: doc.createAttribute('data-value'),
|
||||
namespacedAttribute: doc.createAttributeNS(null, 'data-value'),
|
||||
event: doc.createEvent('Event'),
|
||||
range,
|
||||
clonedRange: range.cloneRange(),
|
||||
nodeIterator: doc.createNodeIterator(doc),
|
||||
treeWalker: doc.createTreeWalker(doc),
|
||||
implementation: doc.implementation,
|
||||
documentType: doc.implementation.createDocumentType('html', '', ''),
|
||||
xmlDocument: doc.implementation.createDocument(null, 'root'),
|
||||
htmlDocument: doc.implementation.createHTMLDocument('title'),
|
||||
canvasContext: context,
|
||||
createdImageData,
|
||||
createdImageDataData: createdImageData.data,
|
||||
readImageData,
|
||||
readImageDataData: readImageData.data
|
||||
};
|
||||
return Object.entries(values)
|
||||
.filter(([, value]) => !(value instanceof ChildObject) || value instanceof Object)
|
||||
.map(([name]) => name)
|
||||
.join(',');
|
||||
})()
|
||||
"#,
|
||||
)
|
||||
.expect("child document factory realm probe should evaluate");
|
||||
|
||||
assert_eq!(result, "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn detached_character_data_accessors_parse_webidl_arguments() {
|
||||
let mut vm = new_storage_test_vm("https://detached-character-data-webidl.test/");
|
||||
|
||||
@@ -5,6 +5,7 @@ mod cssom;
|
||||
mod dom;
|
||||
mod forms;
|
||||
mod misc;
|
||||
mod query_realms;
|
||||
mod shadow_dom;
|
||||
mod style_invalidation;
|
||||
mod xhr;
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn query_selector_all_preserves_isolated_world_node_identity() {
|
||||
for universal_access in [false, true] {
|
||||
let mut vm = new_parsed_test_vm(
|
||||
"https://query-selector-realms.test/",
|
||||
"<!doctype html><html><body></body></html>",
|
||||
);
|
||||
let context_id = vm
|
||||
.create_isolated_world("query-selector-identity", universal_access)
|
||||
.expect("isolated query realm should be created");
|
||||
let result = vm
|
||||
.eval_in_isolated_context(
|
||||
context_id,
|
||||
r#"
|
||||
(() => {
|
||||
const assert = (condition, message) => { if (!condition) throw new Error(message); };
|
||||
const fragment = document.createDocumentFragment();
|
||||
const detached = document.createElement('section');
|
||||
const shadow = document.body.appendChild(document.createElement('div')).attachShadow({mode:'open'});
|
||||
for (const root of [document.body, fragment, detached, shadow]) {
|
||||
for (let index = 0; index < 1100; ++index) {
|
||||
const node = document.createElement('span');
|
||||
node.className = index === 0 ? 'hit first' : 'hit';
|
||||
root.appendChild(node);
|
||||
}
|
||||
}
|
||||
for (const root of [document, document.body, fragment, detached, shadow]) {
|
||||
for (const selector of ['.first', '.hit']) {
|
||||
const first = root.querySelector(selector);
|
||||
const list = root.querySelectorAll(selector);
|
||||
assert(list[0] === first, 'querySelector and querySelectorAll must return the same wrapper');
|
||||
assert(Object.getPrototypeOf(list) === NodeList.prototype, 'NodeList belongs to the isolated receiver realm');
|
||||
assert(list.item(0) === first && [...list][0] === first, 'item and iteration preserve node identity');
|
||||
assert(list !== root.querySelectorAll(selector), 'each query creates a fresh static NodeList');
|
||||
const length = list.length;
|
||||
first.remove();
|
||||
assert(list.length === length && list[0] === first, 'removed nodes remain in the static result');
|
||||
root === document ? document.body.prepend(first) : root.prepend(first);
|
||||
}
|
||||
}
|
||||
return 'ok';
|
||||
})()
|
||||
"#,
|
||||
)
|
||||
.expect("isolated queries must not fall back to the main world");
|
||||
assert_eq!(result, "ok", "universal_access={universal_access}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn query_selector_all_borrowed_methods_keep_the_receiver_realm() {
|
||||
let mut vm = new_parsed_test_vm(
|
||||
"https://query-selector-borrowed.test/",
|
||||
"<!doctype html><html><body><span class=hit></span></body></html>",
|
||||
);
|
||||
let result = vm
|
||||
.eval(
|
||||
r#"
|
||||
(() => {
|
||||
const assert = (condition, message) => { if (!condition) throw new Error(message); };
|
||||
const frame = document.body.appendChild(document.createElement('iframe'));
|
||||
const child = frame.contentWindow;
|
||||
child.document.body.appendChild(child.document.createElement('span')).className = 'hit';
|
||||
for (const [owner, caller] of [[window, child], [child, window]]) {
|
||||
const doc = owner.document;
|
||||
const fragment = doc.createDocumentFragment();
|
||||
fragment.appendChild(doc.createElement('span')).className = 'hit';
|
||||
const shadow = doc.body.appendChild(doc.createElement('div')).attachShadow({mode:'open'});
|
||||
shadow.appendChild(doc.createElement('span')).className = 'hit';
|
||||
for (const [root, method] of [
|
||||
[doc, caller.Document.prototype.querySelectorAll],
|
||||
[doc.body, caller.Element.prototype.querySelectorAll],
|
||||
[fragment, caller.DocumentFragment.prototype.querySelectorAll],
|
||||
[shadow, caller.DocumentFragment.prototype.querySelectorAll],
|
||||
]) {
|
||||
const list = method.call(root, '.hit');
|
||||
assert(Object.getPrototypeOf(list) === owner.NodeList.prototype, 'result uses receiver realm, not borrowed method realm');
|
||||
assert(list[0] === root.querySelector('.hit'), 'borrowed query preserves native node identity');
|
||||
assert(list.item(0) === list[0] && [...list][0] === list[0], 'all collection access paths agree');
|
||||
}
|
||||
}
|
||||
return 'ok';
|
||||
})()
|
||||
"#,
|
||||
)
|
||||
.expect("querySelectorAll must retain the receiver realm across borrowed methods");
|
||||
assert_eq!(result, "ok");
|
||||
}
|
||||
Reference in New Issue
Block a user