From ccf736eba0a177f9b3f3b45c0d538aa1f0989ff7 Mon Sep 17 00:00:00 2001 From: ldm0 Date: Sun, 27 Sep 2026 04:43:14 +0800 Subject: [PATCH] fix(html): complete obsolete element reflections --- moli-renderer-v8/src/native_bridge/element.rs | 60 +++++++-- .../element/global_attributes.rs | 115 +++++++++++++++++- .../src/native_bridge/element/reflection.rs | 30 +++++ .../form_controls_and_legacy_elements.rs | 82 +++++++++++++ 4 files changed, 276 insertions(+), 11 deletions(-) diff --git a/moli-renderer-v8/src/native_bridge/element.rs b/moli-renderer-v8/src/native_bridge/element.rs index f9ac0af2c9..3819fc393d 100644 --- a/moli-renderer-v8/src/native_bridge/element.rs +++ b/moli-renderer-v8/src/native_bridge/element.rs @@ -430,14 +430,15 @@ pub(super) use global_attributes::{ html_label_getter_function, html_long_desc_getter_function, html_lowsrc_getter_function, html_margin_height_getter_function, html_margin_width_getter_function, html_media_getter_function, html_name_getter_function, html_name_setter_function, - html_no_href_getter_function, html_no_shade_getter_function, html_no_shade_setter_function, - html_ping_getter_function, html_rel_getter_function, html_rel_setter_function, - html_scrolling_getter_function, html_shape_getter_function, html_size_getter_function, - html_sizes_getter_function, html_type_getter_function, html_use_map_getter_function, - html_value_getter_function, html_value_type_getter_function, html_version_getter_function, - html_vspace_getter_function, html_width_getter_function, image_decoding_setter_function, - image_long_desc_setter_function, image_lowsrc_setter_function, link_target_getter_function, - link_target_setter_function, node_access_key_getter_function, + html_no_href_getter_function, html_no_resize_getter_function, html_no_resize_setter_function, + html_no_shade_getter_function, html_no_shade_setter_function, html_ping_getter_function, + html_rel_getter_function, html_rel_setter_function, html_scrolling_getter_function, + html_shape_getter_function, html_size_getter_function, html_sizes_getter_function, + html_true_speed_getter_function, html_true_speed_setter_function, html_type_getter_function, + html_use_map_getter_function, html_value_getter_function, html_value_type_getter_function, + html_version_getter_function, html_vspace_getter_function, html_width_getter_function, + image_decoding_setter_function, image_long_desc_setter_function, image_lowsrc_setter_function, + link_target_getter_function, link_target_setter_function, node_access_key_getter_function, node_access_key_label_getter_function, node_access_key_setter_function, node_allow_fullscreen_getter_function, node_allow_fullscreen_setter_function, node_autocapitalize_getter_function, node_autocapitalize_setter_function, @@ -1808,6 +1809,25 @@ struct HtmlBodyElementLegacyPrototypeDeclaration { bg_color: (), } +#[derive(WebApiFunctionTemplate)] +#[webapi(interface = web_api_interfaces::HTMLFrameSetElement, enumerable)] +struct HtmlFrameSetElementLegacyPrototypeDeclaration { + #[webapi( + accessor_property, + getter = dom_string_reflection_getter_function, + setter = dom_string_reflection_setter_function, + data = DomStringReflection::FrameSetCols + )] + cols: (), + #[webapi( + accessor_property, + getter = dom_string_reflection_getter_function, + setter = dom_string_reflection_setter_function, + data = DomStringReflection::FrameSetRows + )] + rows: (), +} + #[derive(WebApiFunctionTemplate)] #[webapi(interface = web_api_interfaces::HTMLHRElement, enumerable)] struct HtmlHrElementLegacyPrototypeDeclaration { @@ -1843,6 +1863,13 @@ struct HtmlHrElementLegacyPrototypeDeclaration { #[derive(WebApiFunctionTemplate)] #[webapi(interface = web_api_interfaces::HTMLFontElement, enumerable)] struct HtmlFontElementLegacyPrototypeDeclaration { + #[webapi( + accessor_property, + getter = dom_string_reflection_getter_function, + setter = dom_string_reflection_setter_function, + data = DomStringReflection::FontFace + )] + face: (), #[webapi( accessor_property, getter = html_size_getter_function, @@ -1862,6 +1889,12 @@ struct HtmlFontElementLegacyPrototypeDeclaration { #[derive(WebApiFunctionTemplate)] #[webapi(interface = web_api_interfaces::HTMLMarqueeElement, enumerable)] struct HtmlMarqueeElementLegacyPrototypeDeclaration { + #[webapi( + accessor_property = "trueSpeed", + getter = html_true_speed_getter_function, + setter = html_true_speed_setter_function + )] + true_speed: (), #[webapi( accessor_property = "loop", getter = marquee_loop_getter_function, @@ -3924,6 +3957,12 @@ struct HtmlEmbedElementUrlPrototypeDeclaration { #[derive(WebApiFunctionTemplate)] #[webapi(interface = web_api_interfaces::HTMLFrameElement, enumerable)] struct HtmlFrameElementLegacyPrototypeDeclaration { + #[webapi( + accessor_property = "noResize", + getter = html_no_resize_getter_function, + setter = html_no_resize_setter_function + )] + no_resize: (), #[webapi( accessor_property, getter = generic_src_getter_function, @@ -7120,7 +7159,10 @@ pub(crate) fn install_element_template_bindings<'s>( HtmlBodyElementLegacyPrototypeDeclaration, ), "HTMLFrameSetElement" => { - install!(HtmlBodyOrFrameSetEventHandlersPrototypeDeclaration) + install!( + HtmlBodyOrFrameSetEventHandlersPrototypeDeclaration, + HtmlFrameSetElementLegacyPrototypeDeclaration, + ); } "HTMLHRElement" => install!(HtmlHrElementLegacyPrototypeDeclaration), "HTMLFontElement" => install!(HtmlFontElementLegacyPrototypeDeclaration), 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 022e762301..71de5f8737 100644 --- a/moli-renderer-v8/src/native_bridge/element/global_attributes.rs +++ b/moli-renderer-v8/src/native_bridge/element/global_attributes.rs @@ -20,8 +20,8 @@ use super::reflection::{ use super::{ attribute_property_getter_from_object_or_detached, boolean_attribute_property_getter_from_object_or_detached, element_attribute, - form_associated_form_owner, html_element_getter_receiver, html_element_setter_receiver, - property_dom_string_value, resolve_url_like_attribute, + element_has_attribute, form_associated_form_owner, html_element_getter_receiver, + html_element_setter_receiver, property_dom_string_value, resolve_url_like_attribute, set_attribute_property_on_object_or_detached, set_boolean_attribute_property_on_object_or_detached, set_dom_string_attribute_property_on_object, set_reflected_attribute, @@ -1219,6 +1219,117 @@ pub(in crate::native_bridge) fn html_no_shade_setter_function<'s>( rv.set_undefined(); } +fn html_boolean_attribute_getter<'s>( + scope: &mut v8::PinScope<'s, '_>, + receiver: v8::Local<'s, v8::Object>, + interface: &'static str, + member: &'static str, + local_name: &'static str, + attribute: &'static str, + mut rv: v8::ReturnValue<'s, v8::Value>, +) { + let Some((runtime_ptr, handle)) = + html_element_getter_receiver(scope, receiver, interface, member, local_name) + else { + rv.set_bool(false); + return; + }; + rv.set_bool(element_has_attribute( + unsafe { &*runtime_ptr }, + handle, + attribute, + )); +} + +fn html_boolean_attribute_setter<'s>( + scope: &mut v8::PinScope<'s, '_>, + receiver: v8::Local<'s, v8::Object>, + value: v8::Local<'s, v8::Value>, + interface: &'static str, + member: &'static str, + local_name: &'static str, + attribute: &'static str, +) { + let Some((runtime_ptr, handle)) = + html_element_setter_receiver(scope, receiver, interface, member, local_name) + else { + return; + }; + set_reflected_boolean_attribute( + scope, + runtime_ptr, + handle, + attribute, + value.boolean_value(scope), + ); +} + +pub(in crate::native_bridge) fn html_no_resize_getter_function<'s>( + scope: &mut v8::PinScope<'s, '_>, + args: v8::FunctionCallbackArguments<'s>, + rv: v8::ReturnValue<'s, v8::Value>, +) { + html_boolean_attribute_getter( + scope, + args.this(), + "HTMLFrameElement", + "noResize", + "frame", + "noresize", + rv, + ); +} + +pub(in crate::native_bridge) fn html_no_resize_setter_function<'s>( + scope: &mut v8::PinScope<'s, '_>, + args: v8::FunctionCallbackArguments<'s>, + mut rv: v8::ReturnValue<'_, v8::Value>, +) { + html_boolean_attribute_setter( + scope, + args.this(), + args.get(0), + "HTMLFrameElement", + "noResize", + "frame", + "noresize", + ); + rv.set_undefined(); +} + +pub(in crate::native_bridge) fn html_true_speed_getter_function<'s>( + scope: &mut v8::PinScope<'s, '_>, + args: v8::FunctionCallbackArguments<'s>, + rv: v8::ReturnValue<'s, v8::Value>, +) { + html_boolean_attribute_getter( + scope, + args.this(), + "HTMLMarqueeElement", + "trueSpeed", + "marquee", + "truespeed", + rv, + ); +} + +pub(in crate::native_bridge) fn html_true_speed_setter_function<'s>( + scope: &mut v8::PinScope<'s, '_>, + args: v8::FunctionCallbackArguments<'s>, + mut rv: v8::ReturnValue<'_, v8::Value>, +) { + html_boolean_attribute_setter( + scope, + args.this(), + args.get(0), + "HTMLMarqueeElement", + "trueSpeed", + "marquee", + "truespeed", + ); + rv.set_undefined(); +} + pub(in crate::native_bridge) fn node_allow_fullscreen_getter_function<'s>( scope: &mut v8::PinScope<'s, '_>, args: v8::FunctionCallbackArguments<'s>, diff --git a/moli-renderer-v8/src/native_bridge/element/reflection.rs b/moli-renderer-v8/src/native_bridge/element/reflection.rs index fde02a8bc3..150309bd37 100644 --- a/moli-renderer-v8/src/native_bridge/element/reflection.rs +++ b/moli-renderer-v8/src/native_bridge/element/reflection.rs @@ -180,9 +180,12 @@ pub(super) enum DomStringReflection { EmbedHeight, EmbedType, EmbedWidth, + FontFace, FontSize, FrameFrameBorder, FrameScrolling, + FrameSetCols, + FrameSetRows, HrColor, HrSize, HrWidth, @@ -382,6 +385,15 @@ const DOM_STRING_REFLECTION_DESCRIPTORS: &[(DomStringReflection, DomStringReflec DomStringReflection::EmbedWidth, DomStringReflectionDescriptor::new("HTMLEmbedElement", "width", "width"), ), + ( + DomStringReflection::FontFace, + DomStringReflectionDescriptor::new_html_element( + "HTMLFontElement", + "font", + "face", + "face", + ), + ), ( DomStringReflection::FontSize, DomStringReflectionDescriptor::new("HTMLFontElement", "size", "size"), @@ -394,6 +406,24 @@ const DOM_STRING_REFLECTION_DESCRIPTORS: &[(DomStringReflection, DomStringReflec DomStringReflection::FrameScrolling, DomStringReflectionDescriptor::new("HTMLFrameElement", "scrolling", "scrolling"), ), + ( + DomStringReflection::FrameSetCols, + DomStringReflectionDescriptor::new_html_element( + "HTMLFrameSetElement", + "frameset", + "cols", + "cols", + ), + ), + ( + DomStringReflection::FrameSetRows, + DomStringReflectionDescriptor::new_html_element( + "HTMLFrameSetElement", + "frameset", + "rows", + "rows", + ), + ), ( DomStringReflection::HrColor, DomStringReflectionDescriptor::new("HTMLHRElement", "color", "color"), diff --git a/moli-renderer-v8/src/script_vm/tests/dom_elements/detached/form_controls_and_legacy_elements.rs b/moli-renderer-v8/src/script_vm/tests/dom_elements/detached/form_controls_and_legacy_elements.rs index 896b49178b..098efd092c 100644 --- a/moli-renderer-v8/src/script_vm/tests/dom_elements/detached/form_controls_and_legacy_elements.rs +++ b/moli-renderer-v8/src/script_vm/tests/dom_elements/detached/form_controls_and_legacy_elements.rs @@ -1398,6 +1398,88 @@ fn detached_frame_legacy_accessors_use_owner_prototypes() { assert_eq!(result, "ok"); } +#[test] +fn detached_obsolete_element_reflectors_use_owner_prototypes() { + let mut vm = new_storage_test_vm("https://detached-obsolete-reflectors.test/"); + + let result = vm + .eval( + r#" +(() => { + const doc = document.implementation.createHTMLDocument(""); + const frameset = doc.createElement("frameset"); + const frame = doc.createElement("frame"); + const font = doc.createElement("font"); + const marquee = doc.createElement("marquee"); + const div = doc.createElement("div"); + + 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.name === "TypeError"; + } + }; + const cases = [ + [HTMLFrameSetElement.prototype, frameset, "cols", "40%,60%", "cols"], + [HTMLFrameSetElement.prototype, frameset, "rows", "100,*", "rows"], + [HTMLFontElement.prototype, font, "face", "serif, sans-serif", "face"], + ]; + + for (const [prototype, element, name, value, attribute] of cases) { + const descriptor = Object.getOwnPropertyDescriptor(prototype, name); + assert(!!descriptor, `${name} descriptor`); + assert(typeof descriptor.get === "function", `${name} getter`); + assert(typeof descriptor.set === "function", `${name} setter`); + assert(descriptor.enumerable && descriptor.configurable, `${name} descriptor flags`); + assert(!own(HTMLElement.prototype, name), `${name} owner`); + assert(!(name in div), `${name} should not be on div`); + assert(throwsTypeError(() => descriptor.get.call(div)), `${name} getter brand`); + assert(throwsTypeError(() => descriptor.set.call(div, value)), `${name} setter brand`); + assert(element[name] === "", `${name} default`); + element[name] = value; + assert(element[name] === value, `${name} value`); + assert(element.getAttribute(attribute) === value, `${name} attribute`); + assert(!own(element, name), `${name} should stay inherited`); + } + + for (const [prototype, element, name, attribute] of [ + [HTMLFrameElement.prototype, frame, "noResize", "noresize"], + [HTMLMarqueeElement.prototype, marquee, "trueSpeed", "truespeed"], + ]) { + const descriptor = Object.getOwnPropertyDescriptor(prototype, name); + assert(!!descriptor, `${name} descriptor`); + assert(typeof descriptor.get === "function", `${name} getter`); + assert(typeof descriptor.set === "function", `${name} setter`); + assert(descriptor.enumerable && descriptor.configurable, `${name} descriptor flags`); + assert(!own(HTMLElement.prototype, name), `${name} owner`); + assert(!(name in div), `${name} should not be on div`); + assert(throwsTypeError(() => descriptor.get.call(div)), `${name} getter brand`); + assert(throwsTypeError(() => descriptor.set.call(div, true)), `${name} setter brand`); + assert(element[name] === false, `${name} default`); + element.setAttribute(attribute, "false"); + assert(element[name] === true, `${name} attribute presence`); + element[name] = false; + assert(!element.hasAttribute(attribute), `${name} false removes attribute`); + element[name] = true; + assert(element.getAttribute(attribute) === "", `${name} true adds attribute`); + assert(!own(element, name), `${name} should stay inherited`); + } + + return "ok"; +})() +"#, + ) + .expect("detached obsolete element reflectors should evaluate"); + + assert_eq!(result, "ok"); +} + #[test] fn detached_resource_legacy_accessors_use_owner_prototypes() { let mut vm =