diff --git a/moli-renderer-v8/src/native_bridge/document.rs b/moli-renderer-v8/src/native_bridge/document.rs index bfdbe42764..56992c0b04 100644 --- a/moli-renderer-v8/src/native_bridge/document.rs +++ b/moli-renderer-v8/src/native_bridge/document.rs @@ -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> { + 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> { + 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 { + 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::( + 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); diff --git a/moli-renderer-v8/src/native_bridge/element.rs b/moli-renderer-v8/src/native_bridge/element.rs index 1caf90474a..a97c8e790f 100644 --- a/moli-renderer-v8/src/native_bridge/element.rs +++ b/moli-renderer-v8/src/native_bridge/element.rs @@ -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( diff --git a/moli-renderer-v8/src/native_bridge/element/global_attributes.rs b/moli-renderer-v8/src/native_bridge/element/global_attributes.rs index e8f9dd6efa..415982b85a 100644 --- a/moli-renderer-v8/src/native_bridge/element/global_attributes.rs +++ b/moli-renderer-v8/src/native_bridge/element/global_attributes.rs @@ -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(), diff --git a/moli-renderer-v8/src/native_bridge/element/html_elements/misc.rs b/moli-renderer-v8/src/native_bridge/element/html_elements/misc.rs index 17b9950e6c..c246cd48c9 100644 --- a/moli-renderer-v8/src/native_bridge/element/html_elements/misc.rs +++ b/moli-renderer-v8/src/native_bridge/element/html_elements/misc.rs @@ -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, diff --git a/moli-renderer-v8/src/native_bridge/element/reflection.rs b/moli-renderer-v8/src/native_bridge/element/reflection.rs index f01da993e7..75199fb6a5 100644 --- a/moli-renderer-v8/src/native_bridge/element/reflection.rs +++ b/moli-renderer-v8/src/native_bridge/element/reflection.rs @@ -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, diff --git a/moli-renderer-v8/src/native_bridge/element/shared.rs b/moli-renderer-v8/src/native_bridge/element/shared.rs index fc65342abd..c8bcd1aa8e 100644 --- a/moli-renderer-v8/src/native_bridge/element/shared.rs +++ b/moli-renderer-v8/src/native_bridge/element/shared.rs @@ -15,7 +15,7 @@ fn with_element( .map(map) } -pub(super) fn element_attribute( +pub(in crate::native_bridge) fn element_attribute( runtime: &JsContextHost, handle: DomHandle, name: &str, diff --git a/moli-renderer-v8/src/script_vm/tests/dom_elements/detached.rs b/moli-renderer-v8/src/script_vm/tests/dom_elements/detached.rs index bf9083e2ad..0dc100269d 100644 --- a/moli-renderer-v8/src/script_vm/tests/dom_elements/detached.rs +++ b/moli-renderer-v8/src/script_vm/tests/dom_elements/detached.rs @@ -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";