fix(renderer): complete section element reflection

This commit is contained in:
ldm0
2026-09-09 06:31:04 +08:00
parent 242901764c
commit 441c445654
7 changed files with 496 additions and 67 deletions
@@ -2,6 +2,7 @@ use crate::{
custom_elements,
dom::native::{DocumentTitleSetterTarget, Node},
native_bridge::abort::dom_exception_value,
webidl,
};
use super::super::{
@@ -11,6 +12,7 @@ use super::super::{
global_bridge_object, set_private_value, throw_type_error, v8_string, v8str,
},
};
use super::element::{canonical_dir_value, element_attribute, set_reflected_attribute};
use super::node::{
node_is_document, node_runtime_and_handle_from_object,
node_runtime_and_handle_from_object_or_detached,
@@ -300,6 +302,182 @@ pub(in crate::native_bridge::document) use structure::set_document_body_for_nati
pub(crate) const XHTML_NS: &str = "http://www.w3.org/1999/xhtml";
pub(crate) const SVG_NS: &str = "http://www.w3.org/2000/svg";
#[derive(Clone, Copy)]
#[repr(u32)]
enum DocumentForwardedReflection {
Dir,
FgColor,
LinkColor,
VlinkColor,
AlinkColor,
BgColor,
Count,
}
#[derive(Clone, Copy)]
enum DocumentForwardedTarget {
DocumentElement,
Body,
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum DocumentForwardedValueKind {
Dir,
LegacyNullToEmptyDomString,
}
struct DocumentForwardedReflectionDescriptor {
target: DocumentForwardedTarget,
attribute: &'static str,
member: &'static str,
value_kind: DocumentForwardedValueKind,
}
const DOCUMENT_FORWARDED_REFLECTION_DESCRIPTORS: &[(
DocumentForwardedReflection,
DocumentForwardedReflectionDescriptor,
)] = &[
(
DocumentForwardedReflection::Dir,
DocumentForwardedReflectionDescriptor {
target: DocumentForwardedTarget::DocumentElement,
attribute: "dir",
member: "dir",
value_kind: DocumentForwardedValueKind::Dir,
},
),
(
DocumentForwardedReflection::FgColor,
DocumentForwardedReflectionDescriptor {
target: DocumentForwardedTarget::Body,
attribute: "text",
member: "fgColor",
value_kind: DocumentForwardedValueKind::LegacyNullToEmptyDomString,
},
),
(
DocumentForwardedReflection::LinkColor,
DocumentForwardedReflectionDescriptor {
target: DocumentForwardedTarget::Body,
attribute: "link",
member: "linkColor",
value_kind: DocumentForwardedValueKind::LegacyNullToEmptyDomString,
},
),
(
DocumentForwardedReflection::VlinkColor,
DocumentForwardedReflectionDescriptor {
target: DocumentForwardedTarget::Body,
attribute: "vlink",
member: "vlinkColor",
value_kind: DocumentForwardedValueKind::LegacyNullToEmptyDomString,
},
),
(
DocumentForwardedReflection::AlinkColor,
DocumentForwardedReflectionDescriptor {
target: DocumentForwardedTarget::Body,
attribute: "alink",
member: "alinkColor",
value_kind: DocumentForwardedValueKind::LegacyNullToEmptyDomString,
},
),
(
DocumentForwardedReflection::BgColor,
DocumentForwardedReflectionDescriptor {
target: DocumentForwardedTarget::Body,
attribute: "bgcolor",
member: "bgColor",
value_kind: DocumentForwardedValueKind::LegacyNullToEmptyDomString,
},
),
];
const _: () = {
assert!(
DOCUMENT_FORWARDED_REFLECTION_DESCRIPTORS.len()
== DocumentForwardedReflection::Count as usize
);
let mut index = 0;
while index < DOCUMENT_FORWARDED_REFLECTION_DESCRIPTORS.len() {
assert!(DOCUMENT_FORWARDED_REFLECTION_DESCRIPTORS[index].0 as usize == index);
index += 1;
}
};
impl DocumentForwardedReflection {
fn descriptor_from_callback_data(
scope: &mut v8::PinScope<'_, '_>,
data: v8::Local<'_, v8::Value>,
) -> Option<&'static DocumentForwardedReflectionDescriptor> {
DOCUMENT_FORWARDED_REFLECTION_DESCRIPTORS
.get(data.uint32_value(scope)? as usize)
.map(|(_, descriptor)| descriptor)
}
}
impl<'s> moli_webapi_declare::WebApiValue<'s> for DocumentForwardedReflection {
fn to_v8_value(&self, scope: &mut v8::PinScope<'s, '_>) -> Option<v8::Local<'s, v8::Value>> {
Some(v8::Integer::new_from_unsigned(scope, *self as u32).into())
}
}
impl<'s> moli_webapi_declare::WebApiTemplateValue<'s> for DocumentForwardedReflection {
fn to_v8_template_value(
&self,
scope: &mut v8::PinScope<'s, '_, ()>,
) -> Option<v8::Local<'s, v8::Value>> {
Some(v8::Integer::new_from_unsigned(scope, *self as u32).into())
}
}
#[derive(WebApiFunctionTemplate)]
#[webapi(name = "Document", enumerable)]
struct DocumentForwardedReflectionPrototypeDeclaration {
#[webapi(
accessor_property,
getter = document_forwarded_reflection_getter_function,
setter = document_forwarded_reflection_setter_function,
data = DocumentForwardedReflection::Dir
)]
dir: (),
#[webapi(
accessor_property = "fgColor",
getter = document_forwarded_reflection_getter_function,
setter = document_forwarded_reflection_setter_function,
data = DocumentForwardedReflection::FgColor
)]
fg_color: (),
#[webapi(
accessor_property = "linkColor",
getter = document_forwarded_reflection_getter_function,
setter = document_forwarded_reflection_setter_function,
data = DocumentForwardedReflection::LinkColor
)]
link_color: (),
#[webapi(
accessor_property = "vlinkColor",
getter = document_forwarded_reflection_getter_function,
setter = document_forwarded_reflection_setter_function,
data = DocumentForwardedReflection::VlinkColor
)]
vlink_color: (),
#[webapi(
accessor_property = "alinkColor",
getter = document_forwarded_reflection_getter_function,
setter = document_forwarded_reflection_setter_function,
data = DocumentForwardedReflection::AlinkColor
)]
alink_color: (),
#[webapi(
accessor_property = "bgColor",
getter = document_forwarded_reflection_getter_function,
setter = document_forwarded_reflection_setter_function,
data = DocumentForwardedReflection::BgColor
)]
bg_color: (),
}
#[derive(WebApiFunctionTemplate)]
#[webapi(name = "Document", enumerable)]
struct DocumentMetadataPrototypeDeclaration {
@@ -518,6 +696,125 @@ fn document_receiver_runtime_and_handle<'s>(
Some((runtime_ptr, handle))
}
fn document_forwarded_target_handle(
runtime: &JsContextHost,
document_handle: DomHandle,
target: DocumentForwardedTarget,
for_setter: bool,
) -> Option<DomHandle> {
let dom = runtime.dom_host().dom();
let document = dom.node(document_handle).and_then(Node::as_document)?;
let handle = match target {
DocumentForwardedTarget::DocumentElement => {
document.document_element_handle(dom, document_handle)?
}
DocumentForwardedTarget::Body => match document.body_handle(dom, document_handle) {
Some(body) => body,
None if !for_setter => {
let document_element = document.document_element_handle(dom, document_handle)?;
dom.find_child(document_element, |handle| {
dom.node(handle)
.is_some_and(|node| node.is_html_element_named("frameset"))
})?
}
None => return None,
},
};
let node = runtime.dom_host().node(handle)?;
let matches = match target {
DocumentForwardedTarget::DocumentElement => node.is_html_element_named("html"),
DocumentForwardedTarget::Body => {
node.is_html_element_named("body")
|| (!for_setter && node.is_html_element_named("frameset"))
}
};
matches.then_some(handle)
}
fn document_forwarded_reflection_getter_function<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
mut rv: v8::ReturnValue<'s, v8::Value>,
) {
let Some(descriptor) =
DocumentForwardedReflection::descriptor_from_callback_data(scope, args.data())
else {
return;
};
let Some((runtime_ptr, document_handle)) =
document_receiver_runtime_and_handle(scope, args.this())
else {
throw_type_error(
scope,
"Failed to get a reflected property on 'Document': Illegal invocation.",
);
return;
};
let runtime = unsafe { &*runtime_ptr };
let Some(target_handle) =
document_forwarded_target_handle(runtime, document_handle, descriptor.target, false)
else {
rv.set_empty_string();
return;
};
let raw = element_attribute(runtime, target_handle, descriptor.attribute).unwrap_or_default();
let value = match descriptor.value_kind {
DocumentForwardedValueKind::Dir => canonical_dir_value(&raw),
DocumentForwardedValueKind::LegacyNullToEmptyDomString => raw.as_str(),
};
set_document_string_return_value(scope, &mut rv, value);
}
fn document_forwarded_reflection_setter_function<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
mut rv: v8::ReturnValue<'s, v8::Value>,
) {
let Some(descriptor) =
DocumentForwardedReflection::descriptor_from_callback_data(scope, args.data())
else {
return;
};
let Some((runtime_ptr, document_handle)) =
document_receiver_runtime_and_handle(scope, args.this())
else {
throw_type_error(
scope,
"Failed to set a reflected property on 'Document': Illegal invocation.",
);
return;
};
let options = webidl::StringOptions {
treat_null_as_empty_string: descriptor.value_kind
== DocumentForwardedValueKind::LegacyNullToEmptyDomString,
};
let value = match webidl::convert_with_options::<webidl::DomString>(
scope,
args.get(0),
webidl::Context::member("Document", descriptor.member),
&options,
) {
Ok(value) => value.0,
Err(error) => {
webidl::throw_error(scope, &error);
return;
}
};
let runtime = unsafe { &*runtime_ptr };
if let Some(target_handle) =
document_forwarded_target_handle(runtime, document_handle, descriptor.target, true)
{
set_reflected_attribute(
scope,
runtime_ptr,
target_handle,
descriptor.attribute,
&value,
);
}
rv.set_undefined();
}
fn document_obsolete_noop_callback<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
@@ -1356,6 +1653,9 @@ pub(crate) fn install_document_template_bindings<'s>(
if interface_name == "Document" {
DocumentMetadataPrototypeDeclaration::initialize_prototype_template(scope, prototype);
DocumentStructurePrototypeDeclaration::initialize_prototype_template(scope, prototype);
DocumentForwardedReflectionPrototypeDeclaration::initialize_prototype_template(
scope, prototype,
);
DocumentFocusPrototypeDeclaration::initialize_prototype_template(scope, prototype);
DocumentViewPrototypeDeclaration::initialize_prototype_template(scope, prototype);
DocumentStatePrototypeDeclaration::initialize_prototype_template(scope, prototype);
+41 -37
View File
@@ -411,8 +411,8 @@ pub(super) use geometry::{
pub(super) use global_attributes::{
anchor_target_getter_function, anchor_target_setter_function, area_no_href_setter_function,
area_target_getter_function, area_target_setter_function, base_target_getter_function,
base_target_setter_function, canonical_cross_origin_value, canonical_loading_value,
canonical_preload_value, canonical_referrer_policy_value,
base_target_setter_function, canonical_cross_origin_value, canonical_dir_value,
canonical_loading_value, canonical_preload_value, canonical_referrer_policy_value,
dom_string_reflection_getter_function, dom_string_reflection_setter_function,
html_align_getter_function, html_align_setter_function, html_alt_getter_function,
html_as_getter_function, html_bg_color_getter_function, html_border_getter_function,
@@ -475,30 +475,28 @@ pub(in crate::native_bridge) const BODY_LEGACY_PROTOTYPE_ACCESSORS: &[&str] = &[
];
use html_elements::{
body_a_link_getter_function, body_a_link_setter_function, body_background_getter_function,
body_background_setter_function, body_link_getter_function, body_link_setter_function,
body_text_getter_function, body_text_setter_function, body_v_link_getter_function,
body_v_link_setter_function, li_value_getter_function, li_value_setter_function,
meta_content_getter_function, meta_content_setter_function, meta_http_equiv_getter_function,
meta_http_equiv_setter_function, ol_reversed_getter_function, ol_reversed_setter_function,
ol_start_getter_function, ol_start_setter_function, ol_type_getter_function,
ol_type_setter_function, optgroup_disabled_getter_function, optgroup_disabled_setter_function,
table_caption_getter_function, table_caption_setter_function,
table_cell_col_span_getter_function, table_cell_col_span_setter_function,
table_cell_index_getter_function, table_cell_row_span_getter_function,
table_cell_row_span_setter_function, table_col_span_getter_function,
table_col_span_setter_function, table_create_caption_callback, table_create_t_body_callback,
table_create_t_foot_callback, table_create_t_head_callback, table_delete_caption_callback,
table_delete_row_callback, table_delete_t_foot_callback, table_delete_t_head_callback,
table_insert_row_callback, table_row_cells_getter_function, table_row_delete_cell_callback,
table_row_index_getter_function, table_row_insert_cell_callback, table_rows_getter_function,
table_section_delete_row_callback, table_section_insert_row_callback,
table_section_row_index_getter_function, table_section_rows_getter_function,
table_t_bodies_getter_function, table_t_foot_getter_function, table_t_foot_setter_function,
table_t_head_getter_function, table_t_head_setter_function, track_default_getter_function,
track_default_setter_function, track_kind_getter_function, track_kind_setter_function,
track_ready_state_getter_function, track_src_getter_function, track_src_setter_function,
track_srclang_getter_function, track_srclang_setter_function,
body_background_getter_function, body_background_setter_function, li_value_getter_function,
li_value_setter_function, meta_content_getter_function, meta_content_setter_function,
meta_http_equiv_getter_function, meta_http_equiv_setter_function, ol_reversed_getter_function,
ol_reversed_setter_function, ol_start_getter_function, ol_start_setter_function,
ol_type_getter_function, ol_type_setter_function, optgroup_disabled_getter_function,
optgroup_disabled_setter_function, table_caption_getter_function,
table_caption_setter_function, table_cell_col_span_getter_function,
table_cell_col_span_setter_function, table_cell_index_getter_function,
table_cell_row_span_getter_function, table_cell_row_span_setter_function,
table_col_span_getter_function, table_col_span_setter_function, table_create_caption_callback,
table_create_t_body_callback, table_create_t_foot_callback, table_create_t_head_callback,
table_delete_caption_callback, table_delete_row_callback, table_delete_t_foot_callback,
table_delete_t_head_callback, table_insert_row_callback, table_row_cells_getter_function,
table_row_delete_cell_callback, table_row_index_getter_function,
table_row_insert_cell_callback, table_rows_getter_function, table_section_delete_row_callback,
table_section_insert_row_callback, table_section_row_index_getter_function,
table_section_rows_getter_function, table_t_bodies_getter_function,
table_t_foot_getter_function, table_t_foot_setter_function, table_t_head_getter_function,
table_t_head_setter_function, track_default_getter_function, track_default_setter_function,
track_kind_getter_function, track_kind_setter_function, track_ready_state_getter_function,
track_src_getter_function, track_src_setter_function, track_srclang_getter_function,
track_srclang_setter_function,
};
use html_elements::{
marquee_loop_getter_function, marquee_loop_setter_function,
@@ -568,6 +566,7 @@ pub(super) use query::{
node_get_elements_by_tag_name_ns_callback, node_matches_callback,
node_query_selector_all_callback, node_query_selector_callback,
};
pub(super) use reflection::set_reflected_attribute;
use reflection::{
CrossOriginReflection, DomStringReflection, ElementReflectionInterface,
NullToEmptyDomStringReflection, UnsignedLongReflection, UsvStringReflection,
@@ -578,8 +577,8 @@ use reflection::{
remove_reflected_attribute, set_attribute_property_on_object_or_detached,
set_boolean_attribute_property_on_object_or_detached,
set_dom_string_attribute_property_on_object,
set_nullable_dom_string_attribute_property_on_object, set_reflected_attribute,
set_reflected_boolean_attribute, set_reflected_style_attribute_with_inline_base_url,
set_nullable_dom_string_attribute_property_on_object, set_reflected_boolean_attribute,
set_reflected_style_attribute_with_inline_base_url,
set_usv_string_attribute_property_on_object,
};
pub(crate) use shadow_dom::install_element_internals_template_bindings;
@@ -610,7 +609,8 @@ use shadow_dom::{
template_shadow_root_slot_assignment_getter_function,
template_shadow_root_slot_assignment_setter_function,
};
use shared::{element_attribute, element_attribute_names, element_has_attribute, style_string};
pub(super) use shared::element_attribute;
use shared::{element_attribute_names, element_has_attribute, style_string};
pub(super) use state_callbacks::{
bridge_set_checked_state_callback, bridge_set_indeterminate_state_callback,
bridge_set_input_value_callback, bridge_set_selected_state_callback,
@@ -1759,26 +1759,30 @@ struct HtmlBodyOrFrameSetEventHandlersPrototypeDeclaration {
struct HtmlBodyElementLegacyPrototypeDeclaration {
#[webapi(
accessor_property,
getter = body_text_getter_function,
setter = body_text_setter_function
getter = null_to_empty_dom_string_reflection_getter_function,
setter = null_to_empty_dom_string_reflection_setter_function,
data = NullToEmptyDomStringReflection::BodyText
)]
text: (),
#[webapi(
accessor_property,
getter = body_link_getter_function,
setter = body_link_setter_function
getter = null_to_empty_dom_string_reflection_getter_function,
setter = null_to_empty_dom_string_reflection_setter_function,
data = NullToEmptyDomStringReflection::BodyLink
)]
link: (),
#[webapi(
accessor_property = "vLink",
getter = body_v_link_getter_function,
setter = body_v_link_setter_function
getter = null_to_empty_dom_string_reflection_getter_function,
setter = null_to_empty_dom_string_reflection_setter_function,
data = NullToEmptyDomStringReflection::BodyVLink
)]
v_link: (),
#[webapi(
accessor_property = "aLink",
getter = body_a_link_getter_function,
setter = body_a_link_setter_function
getter = null_to_empty_dom_string_reflection_getter_function,
setter = null_to_empty_dom_string_reflection_setter_function,
data = NullToEmptyDomStringReflection::BodyALink
)]
a_link: (),
#[webapi(
@@ -718,7 +718,7 @@ pub(in crate::native_bridge) fn node_dir_getter_function<'s>(
canonical_attribute_getter_from_object(scope, args.this(), "dir", canonical_dir_value, rv);
}
fn canonical_dir_value(raw: &str) -> &'static str {
pub(in crate::native_bridge) fn canonical_dir_value(raw: &str) -> &'static str {
if raw.eq_ignore_ascii_case("ltr") {
"ltr"
} else if raw.eq_ignore_ascii_case("rtl") {
@@ -1093,7 +1093,7 @@ pub(in crate::native_bridge) fn null_to_empty_dom_string_reflection_getter_funct
else {
return;
};
if let Some(local_name) = descriptor.local_name
if let Some(local_name) = descriptor.html_local_name
&& html_element_getter_receiver(
scope,
args.this(),
@@ -1116,7 +1116,7 @@ pub(in crate::native_bridge) fn null_to_empty_dom_string_reflection_setter_funct
if let Some(descriptor) =
NullToEmptyDomStringReflection::descriptor_from_callback_data(scope, args.data())
{
if let Some(local_name) = descriptor.local_name
if let Some(local_name) = descriptor.html_local_name
&& html_element_setter_receiver(
scope,
args.this(),
@@ -110,30 +110,6 @@ macro_rules! body_attr_reflection {
};
}
body_attr_reflection!(
body_text_getter_function,
body_text_setter_function,
"text",
"text"
);
body_attr_reflection!(
body_link_getter_function,
body_link_setter_function,
"link",
"link"
);
body_attr_reflection!(
body_v_link_getter_function,
body_v_link_setter_function,
"vlink",
"vLink"
);
body_attr_reflection!(
body_a_link_getter_function,
body_a_link_setter_function,
"alink",
"aLink"
);
body_attr_reflection!(
body_background_getter_function,
body_background_setter_function,
@@ -908,7 +908,11 @@ impl_reflection_callback_data!(UsvStringReflection);
#[derive(Clone, Copy)]
#[repr(u32)]
pub(super) enum NullToEmptyDomStringReflection {
BodyALink,
BodyBgColor,
BodyLink,
BodyText,
BodyVLink,
FontColor,
FrameMarginHeight,
FrameMarginWidth,
@@ -928,10 +932,26 @@ const NULL_TO_EMPTY_DOM_STRING_REFLECTION_DESCRIPTORS: &[(
NullToEmptyDomStringReflection,
ReflectedAttributeDescriptor,
)] = &[
(
NullToEmptyDomStringReflection::BodyALink,
ReflectedAttributeDescriptor::new_html_element("HTMLBodyElement", "body", "alink", "aLink"),
),
(
NullToEmptyDomStringReflection::BodyBgColor,
ReflectedAttributeDescriptor::new("HTMLBodyElement", "bgcolor", "bgColor"),
),
(
NullToEmptyDomStringReflection::BodyLink,
ReflectedAttributeDescriptor::new_html_element("HTMLBodyElement", "body", "link", "link"),
),
(
NullToEmptyDomStringReflection::BodyText,
ReflectedAttributeDescriptor::new_html_element("HTMLBodyElement", "body", "text", "text"),
),
(
NullToEmptyDomStringReflection::BodyVLink,
ReflectedAttributeDescriptor::new_html_element("HTMLBodyElement", "body", "vlink", "vLink"),
),
(
NullToEmptyDomStringReflection::FontColor,
ReflectedAttributeDescriptor::new("HTMLFontElement", "color", "color"),
@@ -1140,7 +1160,7 @@ impl CrossOriginReflection {
impl_reflection_callback_data!(CrossOriginReflection);
pub(super) fn set_reflected_attribute(
pub(in crate::native_bridge) fn set_reflected_attribute(
scope: &mut v8::PinScope<'_, '_>,
runtime_ptr: *mut super::super::JsContextHost,
handle: DomHandle,
@@ -15,7 +15,7 @@ fn with_element<T>(
.map(map)
}
pub(super) fn element_attribute(
pub(in crate::native_bridge) fn element_attribute(
runtime: &JsContextHost,
handle: DomHandle,
name: &str,
@@ -3704,6 +3704,114 @@ fn document_structure_uses_document_prototype_accessors() {
assert_eq!(result, "ok");
}
#[test]
fn document_forwarded_reflections_use_html_targets() {
let mut vm = new_storage_test_vm("https://document-forwarded-reflections.test/");
let result = vm
.eval(
r#"
(() => {
const assert = (condition, message) => {
if (!condition) throw new Error(message);
};
const own = (object, name) => Object.prototype.hasOwnProperty.call(object, name);
const throwsTypeError = callback => {
try {
callback();
return false;
} catch (error) {
return error instanceof TypeError;
}
};
const names = ["dir", "fgColor", "linkColor", "vlinkColor", "alinkColor", "bgColor"];
const colorAttributes = {
fgColor: "text",
linkColor: "link",
vlinkColor: "vlink",
alinkColor: "alink",
bgColor: "bgcolor"
};
const descriptors = new Map();
for (const name of names) {
const descriptor = Object.getOwnPropertyDescriptor(Document.prototype, name);
assert(!!descriptor, `${name} descriptor missing`);
assert(typeof descriptor.get === "function", `${name} getter`);
assert(typeof descriptor.set === "function", `${name} setter`);
assert(descriptor.enumerable === true, `${name} enumerable`);
assert(descriptor.configurable === true, `${name} configurable`);
descriptors.set(name, descriptor);
}
if (!document.documentElement) {
const html = document.createElement("html");
html.append(document.createElement("head"), document.createElement("body"));
document.append(html);
}
const detachedDocument = document.implementation.createHTMLDocument("");
for (const [doc, label] of [[document, "live"], [detachedDocument, "detached"]]) {
for (const name of names) {
assert(!own(doc, name), `${label}.${name} should not be own before set`);
assert(doc[name] === "", `${label}.${name} missing-value default`);
}
doc.documentElement.setAttribute("dir", "RTL");
assert(doc.dir === "rtl", `${label}.dir canonical getter`);
doc.dir = { toString: () => "AUTO" };
assert(doc.documentElement.getAttribute("dir") === "AUTO", `${label}.dir target attribute`);
assert(doc.dir === "auto", `${label}.dir setter canonical getter`);
doc.dir = null;
assert(doc.documentElement.getAttribute("dir") === "null", `${label}.dir null attribute`);
assert(doc.dir === "", `${label}.dir null canonical getter`);
for (const [name, attribute] of Object.entries(colorAttributes)) {
doc[name] = { toString: () => `${label}-${name}` };
assert(doc[name] === `${label}-${name}`, `${label}.${name} getter`);
assert(doc.body.getAttribute(attribute) === `${label}-${name}`, `${label}.${name} target attribute`);
doc[name] = null;
assert(doc[name] === "", `${label}.${name} null getter`);
assert(doc.body.getAttribute(attribute) === "", `${label}.${name} null attribute`);
}
for (const name of names) {
assert(!own(doc, name), `${label}.${name} should stay inherited after set`);
assert(delete doc[name], `${label}.${name} delete`);
assert(!own(doc, name), `${label}.${name} should stay inherited after delete`);
}
}
const xmlDocument = document.implementation.createDocument("urn:test", "root", null);
let converted = false;
xmlDocument.dir = { toString() { converted = true; return "rtl"; } };
assert(converted, "XML document setter should still convert the value");
assert(xmlDocument.dir === "", "XML document dir getter");
assert(!xmlDocument.documentElement.hasAttribute("dir"), "XML document should not forward dir");
xmlDocument.fgColor = "red";
assert(xmlDocument.fgColor === "", "XML document color getter");
const framesetDocument = document.implementation.createHTMLDocument("");
const frameset = framesetDocument.createElement("frameset");
frameset.setAttribute("text", "seed");
framesetDocument.body = frameset;
assert(framesetDocument.fgColor === "seed", "frameset color getter target");
framesetDocument.fgColor = "changed";
assert(frameset.getAttribute("text") === "seed", "frameset color setter should be a no-op");
for (const [name, descriptor] of descriptors) {
for (const receiver of [document.documentElement, {}]) {
assert(throwsTypeError(() => descriptor.get.call(receiver)), `${name} getter receiver`);
assert(throwsTypeError(() => descriptor.set.call(receiver, "wrong")), `${name} setter receiver`);
}
}
return "ok";
})()
"#,
)
.expect("document forwarded reflections should evaluate");
assert_eq!(result, "ok");
}
#[test]
fn detached_document_type_metadata_uses_prototype_accessors() {
let mut vm = new_storage_test_vm("https://detached-doctype-prototype.test/");
@@ -6654,6 +6762,14 @@ fn detached_body_legacy_accessors_use_owner_prototype() {
if (!condition) throw new Error(message);
};
const own = (object, name) => Object.prototype.hasOwnProperty.call(object, name);
const throwsTypeError = callback => {
try {
callback();
return false;
} catch (error) {
return error instanceof TypeError;
}
};
const accessor = (prototype, name) => {
const descriptor = Object.getOwnPropertyDescriptor(prototype, name);
assert(!!descriptor, `${prototype.constructor.name}.${name} descriptor missing`);
@@ -6692,13 +6808,26 @@ fn detached_body_legacy_accessors_use_owner_prototype() {
assert(body.vLink === `${label}-vlink` && body.getAttribute("vlink") === `${label}-vlink`, `${label}.vLink`);
assert(body.aLink === `${label}-alink` && body.getAttribute("alink") === `${label}-alink`, `${label}.aLink`);
assert(body.background === `${label}-background` && body.getAttribute("background") === `${label}-background`, `${label}.background`);
for (const name of ["text", "link", "vLink", "aLink"]) {
body[name] = null;
const attribute = name.toLowerCase();
assert(body[name] === "", `${label}.${name} null getter`);
assert(body.getAttribute(attribute) === "", `${label}.${name} null attribute`);
}
for (const name of names) {
assert(!own(body, name), `${label}.${name} should not be own after set`);
assert(delete body[name], `${label}.${name} delete`);
assert(!own(body, name), `${label}.${name} should stay inherited`);
}
assert(body.onload === handler, `${label}.onload after delete`);
assert(body.text === `${label}-text`, `${label}.text after delete`);
assert(body.text === "", `${label}.text after delete`);
}
for (const name of ["text", "link", "vLink", "aLink"]) {
const descriptor = Object.getOwnPropertyDescriptor(HTMLBodyElement.prototype, name);
for (const receiver of [document.createElement("div"), {}]) {
assert(throwsTypeError(() => descriptor.get.call(receiver)), `${name} getter receiver`);
assert(throwsTypeError(() => descriptor.set.call(receiver, "wrong")), `${name} setter receiver`);
}
}
window.onload = null;
return "ok";