From 330a1bc99cb1ccdfe4110cff45d132b98105ac53 Mon Sep 17 00:00:00 2001 From: ldm0 Date: Mon, 19 Oct 2026 22:35:50 +0800 Subject: [PATCH] fix(cssom): serialize computed background layers Serialize computed background from the shared retained style read, using the image list to coordinate layers, repeating or truncating other component lists, and emitting color only in the final layer. Include computed initial components instead of using specified-value shorthand elision. When Stylo cannot serialize a computed shorthand, reify the shared CSSOM result as one property-associated opaque CSSStyleValue. Preserve native realm allocation, snapshot immutability and longhand Typed OM types. Cover multiple layers, currentcolor, list coordination, private native writes and cross-realm reads. --- .../declaration/values/computed_properties.rs | 5 ++ .../values/computed_properties/background.rs | 67 +++++++++++++++++++ .../script_vm/tests/dom_xhr/computed_style.rs | 1 + .../computed_style/css_computed_background.js | 33 +++++++++ .../computed_style/css_computed_background.rs | 10 +++ 5 files changed, 116 insertions(+) create mode 100644 moli-renderer-v8/src/native_bridge/element/styles/declaration/values/computed_properties/background.rs create mode 100644 moli-renderer-v8/src/script_vm/tests/dom_xhr/computed_style/css_computed_background.js create mode 100644 moli-renderer-v8/src/script_vm/tests/dom_xhr/computed_style/css_computed_background.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 4a46349585..e5f4835b0f 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,5 +1,7 @@ use super::*; +mod background; + pub(in crate::native_bridge::element::styles) fn style_property_value_with_context( runtime: &JsContextHost, handle: DomHandle, @@ -105,6 +107,9 @@ pub(super) fn computed_style_property_value_after_style_update( if property == "font-variant" { return computed_font_variant_shorthand_value(runtime, handle, resolution); } + if property == "background" { + return background::computed_shorthand(runtime, handle, resolution); + } if property == "mask" { return computed_mask_shorthand_value(runtime, handle, resolution); } diff --git a/moli-renderer-v8/src/native_bridge/element/styles/declaration/values/computed_properties/background.rs b/moli-renderer-v8/src/native_bridge/element/styles/declaration/values/computed_properties/background.rs new file mode 100644 index 0000000000..927af2f2e7 --- /dev/null +++ b/moli-renderer-v8/src/native_bridge/element/styles/declaration/values/computed_properties/background.rs @@ -0,0 +1,67 @@ +use super::*; + +/// CSSOM serializes the coordinated background layers, including initial +/// components. Specified-value shorthand serialization would omit those parts. +pub(super) fn computed_shorthand( + runtime: &JsContextHost, + handle: DomHandle, + resolution: StyleResolutionContext<'_>, +) -> String { + let [ + Some(images), + Some(repeats), + Some(attachments), + Some(positions_x), + Some(positions_y), + Some(sizes), + Some(origins), + Some(clips), + ] = [ + "background-image", + "background-repeat", + "background-attachment", + "background-position-x", + "background-position-y", + "background-size", + "background-origin", + "background-clip", + ] + .map(|property| { + top_level_comma_separated_component_values( + &resolution.computed_property(runtime, handle, property), + ) + .filter(|values| !values.is_empty()) + }) + else { + return String::new(); + }; + let color = resolution.computed_property(runtime, handle, "background-color"); + images + .iter() + .enumerate() + .map(|(index, image)| { + // The image list controls the layer count. Other lists repeat or + // truncate to that count, and only the final layer carries color. + let color = if index + 1 == images.len() { + format!("{color} ") + } else { + String::new() + }; + format!( + "{color}{image} {} {} {} {} / {} {} {}", + layer_value(&repeats, index), + layer_value(&attachments, index), + layer_value(&positions_x, index), + layer_value(&positions_y, index), + layer_value(&sizes, index), + layer_value(&origins, index), + layer_value(&clips, index), + ) + }) + .collect::>() + .join(", ") +} + +fn layer_value(values: &[String], index: usize) -> &str { + &values[index % values.len()] +} 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 854b983bdc..4d936e5573 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 @@ -58,6 +58,7 @@ mod advanced_style_values; mod computed_style_access; mod content_and_invalidation; mod cross_document_and_animations; +mod css_computed_background; mod css_computed_map_enumeration; mod nested_document_invalidation; mod properties_and_selectors; diff --git a/moli-renderer-v8/src/script_vm/tests/dom_xhr/computed_style/css_computed_background.js b/moli-renderer-v8/src/script_vm/tests/dom_xhr/computed_style/css_computed_background.js new file mode 100644 index 0000000000..e1ad052488 --- /dev/null +++ b/moli-renderer-v8/src/script_vm/tests/dom_xhr/computed_style/css_computed_background.js @@ -0,0 +1,33 @@ +(() => { + const check = (ok, message) => {if (!ok) throw new Error(message);}; + const cases = [ + ['', 'rgba(0, 0, 0, 0) none repeat scroll 0% 0% / auto padding-box border-box'], + ['background:blue', 'rgb(0, 0, 255) none repeat scroll 0% 0% / auto padding-box border-box'], + ['color:red;background:currentcolor', 'rgb(255, 0, 0) none repeat scroll 0% 0% / auto padding-box border-box'], + ['background:blue content-box', 'rgb(0, 0, 255) none repeat scroll 0% 0% / auto content-box content-box'], + ['background:blue;background-repeat:no-repeat,repeat', 'rgb(0, 0, 255) none no-repeat scroll 0% 0% / auto padding-box border-box'], + ['background-image:none,none;background-color:blue', 'none repeat scroll 0% 0% / auto padding-box border-box, rgb(0, 0, 255) none repeat scroll 0% 0% / auto padding-box border-box'], + ['background-image:none,none;background-size:10px,20px,30px', 'none repeat scroll 0% 0% / 10px auto padding-box border-box, rgba(0, 0, 0, 0) none repeat scroll 0% 0% / 20px auto padding-box border-box'], + ['background:blue;background-position-x:10%;background-position-y:20%', 'rgb(0, 0, 255) none repeat scroll 10% 20% / auto padding-box border-box'], + ['background-image:linear-gradient(red,blue),linear-gradient(black,white);background-repeat:no-repeat', 'linear-gradient(rgb(255, 0, 0), rgb(0, 0, 255)) no-repeat scroll 0% 0% / auto padding-box border-box, rgba(0, 0, 0, 0) linear-gradient(rgb(0, 0, 0), rgb(255, 255, 255)) no-repeat scroll 0% 0% / auto padding-box border-box'], + ]; + for (const [text, expected] of cases) { + const element = document.createElement('div'); + element.style.cssText = text; + document.body.append(element); + const style = getComputedStyle(element); + check(style.background === expected, text + ': ' + style.background); + element.remove(); + } + const element = document.createElement('div'); + element.style.background = 'blue'; document.body.append(element); + const computed = getComputedStyle(element); + const previous = computed.background; + element.style.backgroundColor = 'red'; + check(String(previous).startsWith('rgb(0, 0, 255) '), 'retained value is immutable'); + check(computed.background.startsWith('rgb(255, 0, 0) '), 'retained declaration observes mutations'); + check(computed.backgroundImage === 'none', 'longhand value preserved'); + element.remove(); + check(computed.background === '', 'inactive owner is empty'); + return true; +})() diff --git a/moli-renderer-v8/src/script_vm/tests/dom_xhr/computed_style/css_computed_background.rs b/moli-renderer-v8/src/script_vm/tests/dom_xhr/computed_style/css_computed_background.rs new file mode 100644 index 0000000000..05b4c85aab --- /dev/null +++ b/moli-renderer-v8/src/script_vm/tests/dom_xhr/computed_style/css_computed_background.rs @@ -0,0 +1,10 @@ +use super::*; + +#[test] +fn css_computed_background_preserves_coordinated_layers() { + let mut vm = new_parsed_test_vm("https://computed-background.test/", ""); + assert_eq!( + vm.eval(include_str!("css_computed_background.js")).unwrap(), + "true" + ); +}