From f26501e0ba5033613ca55e2ff949bd16659f4256 Mon Sep 17 00:00:00 2001 From: ldm0 Date: Mon, 19 Oct 2026 23:10:20 +0800 Subject: [PATCH] fix(cssom): serialize computed font from native longhands Serialize computed font from the retained style read and Stylo font longhand metadata. Reuse the native font serializer for CSS2 variant/stretch constraints, reset-only longhands and omission of initial components. Resolve font size, weight and line height through the shared CSSOM path. Keep computed Typed OM font values opaque, property-associated and immutable while retained maps observe new styles. Cover relative and inherited sizes, numeric line height, snapshots and supported unrepresentable longhands. --- .../declaration/values/computed_properties.rs | 4 ++ .../values/computed_properties/font.rs | 27 ++++++++++++ .../script_vm/tests/dom_xhr/computed_style.rs | 1 + .../computed_style/css_computed_font.js | 42 +++++++++++++++++++ .../computed_style/css_computed_font.rs | 10 +++++ 5 files changed, 84 insertions(+) create mode 100644 moli-renderer-v8/src/native_bridge/element/styles/declaration/values/computed_properties/font.rs create mode 100644 moli-renderer-v8/src/script_vm/tests/dom_xhr/computed_style/css_computed_font.js create mode 100644 moli-renderer-v8/src/script_vm/tests/dom_xhr/computed_style/css_computed_font.rs diff --git a/moli-renderer-v8/src/native_bridge/element/styles/declaration/values/computed_properties.rs b/moli-renderer-v8/src/native_bridge/element/styles/declaration/values/computed_properties.rs index 1cd9827619..55b84d16de 100644 --- a/moli-renderer-v8/src/native_bridge/element/styles/declaration/values/computed_properties.rs +++ b/moli-renderer-v8/src/native_bridge/element/styles/declaration/values/computed_properties.rs @@ -1,6 +1,7 @@ use super::*; mod background; +mod font; pub(in crate::native_bridge::element::styles) fn style_property_value_with_context( runtime: &JsContextHost, @@ -104,6 +105,9 @@ pub(super) fn computed_style_property_value_after_style_update( if property == "text-emphasis" { return computed_text_emphasis_shorthand_value(runtime, handle, context); } + if property == "font" { + return font::computed_shorthand(runtime, handle, resolution); + } if property == "font-variant" { return computed_font_variant_shorthand_value(runtime, handle, resolution); } diff --git a/moli-renderer-v8/src/native_bridge/element/styles/declaration/values/computed_properties/font.rs b/moli-renderer-v8/src/native_bridge/element/styles/declaration/values/computed_properties/font.rs new file mode 100644 index 0000000000..d6859d855a --- /dev/null +++ b/moli-renderer-v8/src/native_bridge/element/styles/declaration/values/computed_properties/font.rs @@ -0,0 +1,27 @@ +use super::*; + +pub(super) fn computed_shorthand( + runtime: &JsContextHost, + handle: DomHandle, + resolution: StyleResolutionContext<'_>, +) -> String { + // Servo's font schema excludes this compatibility longhand. It is reset + // by font, but a non-initial value cannot be expressed by the shorthand. + if resolution.computed_property(runtime, handle, "font-size-adjust") != "none" { + return String::new(); + } + let mut block = moli_css_parse::CssDeclarationBlock::default(); + for longhand in style::properties::ShorthandId::Font.longhands() { + let property = longhand.name(); + let value = resolution.computed_property(runtime, handle, property); + if value.is_empty() + || block.set_property(property, &value, false) + == moli_css_parse::CssSetResult::ParseError + { + return String::new(); + } + } + // The native serializer handles CSS2 variant/stretch restrictions, + // reset-only longhands and omission of initial components. + block.property_value("font").unwrap_or_default() +} diff --git a/moli-renderer-v8/src/script_vm/tests/dom_xhr/computed_style.rs b/moli-renderer-v8/src/script_vm/tests/dom_xhr/computed_style.rs index 264dcd161e..f00526d114 100644 --- a/moli-renderer-v8/src/script_vm/tests/dom_xhr/computed_style.rs +++ b/moli-renderer-v8/src/script_vm/tests/dom_xhr/computed_style.rs @@ -59,6 +59,7 @@ mod computed_style_access; mod content_and_invalidation; mod cross_document_and_animations; mod css_computed_background; +mod css_computed_font; mod css_computed_map_enumeration; mod css_variable_fonts; mod nested_document_invalidation; diff --git a/moli-renderer-v8/src/script_vm/tests/dom_xhr/computed_style/css_computed_font.js b/moli-renderer-v8/src/script_vm/tests/dom_xhr/computed_style/css_computed_font.js new file mode 100644 index 0000000000..eb7e87c3e0 --- /dev/null +++ b/moli-renderer-v8/src/script_vm/tests/dom_xhr/computed_style/css_computed_font.js @@ -0,0 +1,42 @@ +(() => { + const check = (ok, message) => {if (!ok) throw new Error(message);}; + const cases = [ + ['font:16px serif', '16px serif'], + ['font:italic bold 20px / 1.5 monospace', 'italic 700 20px / 30px monospace'], + ['font:small-caps bold condensed 24px / 2 serif', 'small-caps 700 condensed 24px / 48px serif'], + ['font:oblique 20deg 20px serif', 'oblique 20deg 20px serif'], + ['font-size:24px;font-family:serif;font-weight:700;line-height:1.25', '700 24px / 30px serif'], + ]; + for (const [text, expected] of cases) { + const element = document.createElement('div'); + element.style.cssText = text; + document.body.append(element); + const computed = getComputedStyle(element); + const value = computed.font; + check(getComputedStyle(element).font === expected, text + ': ' + getComputedStyle(element).font); + check(value === expected, text + ': serialized value'); + element.style.font = '12px sans-serif'; + check(value === expected, 'retained font string'); + check(computed.font === '12px sans-serif', 'live computed declaration'); + element.remove(); + } + const element = document.createElement('div'); + document.body.append(element); + for (const [property, value] of [ + ['font-variant-ligatures', 'none'], + ['font-variant-east-asian', 'full-width'], + ['font-kerning', 'normal'], + ]) { + element.style.cssText = 'font:16px serif'; + element.style.setProperty(property, value); + check(element.style.font === '', property + ': specified font cannot represent value'); + check(getComputedStyle(element).font === '', property + ': computed font cannot represent value'); + } + const parent = document.createElement('div'); + parent.style.font = 'bold 20px / 1.5 monospace'; + parent.append(element); document.body.append(parent); + element.style.cssText = 'font-size:2em'; + check(getComputedStyle(element).font === '700 40px / 60px monospace', 'inherited computed longhands'); + parent.remove(); + return true; +})() diff --git a/moli-renderer-v8/src/script_vm/tests/dom_xhr/computed_style/css_computed_font.rs b/moli-renderer-v8/src/script_vm/tests/dom_xhr/computed_style/css_computed_font.rs new file mode 100644 index 0000000000..0d5395af20 --- /dev/null +++ b/moli-renderer-v8/src/script_vm/tests/dom_xhr/computed_style/css_computed_font.rs @@ -0,0 +1,10 @@ +use super::*; + +#[test] +fn css_computed_font_serializes_resolved_longhands_and_retains_values() { + let mut vm = new_parsed_test_vm("https://computed-font.test/", ""); + assert_eq!( + vm.eval(include_str!("css_computed_font.js")).unwrap(), + "true" + ); +}