From 926860a30c02cc7459de049bd4beb4cf4d85bf44 Mon Sep 17 00:00:00 2001 From: ldm0 Date: Wed, 7 Oct 2026 05:09:30 +0800 Subject: [PATCH] refactor(svg): synchronize transform list members through one path --- .../context_bootstrap/svg_runtime/builders.rs | 35 ++++++++----------- moli-renderer-v8/src/script_vm/tests/mod.rs | 1 + .../tests/svg_transform_sync_consolidation.rs | 28 +++++++++++++++ 3 files changed, 44 insertions(+), 20 deletions(-) create mode 100644 moli-renderer-v8/src/script_vm/tests/svg_transform_sync_consolidation.rs diff --git a/moli-renderer-v8/src/context_bootstrap/svg_runtime/builders.rs b/moli-renderer-v8/src/context_bootstrap/svg_runtime/builders.rs index db5c3c5e45..cc688a3f76 100644 --- a/moli-renderer-v8/src/context_bootstrap/svg_runtime/builders.rs +++ b/moli-renderer-v8/src/context_bootstrap/svg_runtime/builders.rs @@ -1436,25 +1436,20 @@ pub(super) fn sync_svg_animated_transform_list_from_owner_attribute<'s>( attribute: &str, ) { let raw = svg_owner_attribute_value(scope, owner, attribute); - let raw_value = raw.clone().unwrap_or_default(); - if let Some(base_val) = svg_animated_transform_list_member(scope, animated, "baseVal") - && svg_transform_list_synced_attribute_value(scope, base_val) - .as_deref() - .is_some_and(|synced| synced == raw_value) - { - set_svg_transform_list_owner_attribute(scope, base_val, owner, attribute); - return; - } - let base_items = build_svg_transform_list_items_from_attribute(scope, raw.as_deref()); - let anim_items = build_svg_transform_list_items_from_attribute(scope, raw.as_deref()); - if let Some(base_val) = svg_animated_transform_list_member(scope, animated, "baseVal") { - set_svg_transform_list_items(scope, base_val, base_items); - set_svg_transform_list_owner_attribute(scope, base_val, owner, attribute); - set_svg_transform_list_synced_attribute_value(scope, base_val, &raw_value); - } - if let Some(anim_val) = svg_animated_transform_list_member(scope, animated, "animVal") { - set_svg_transform_list_items(scope, anim_val, anim_items); - set_svg_transform_list_synced_attribute_value(scope, anim_val, &raw_value); + let raw_value = raw.as_deref().unwrap_or_default(); + for member in ["baseVal", "animVal"] { + let Some(list) = svg_animated_transform_list_member(scope, animated, member) else { + continue; + }; + if member == "baseVal" { + set_svg_transform_list_owner_attribute(scope, list, owner, attribute); + } + if svg_transform_list_synced_attribute_value(scope, list).as_deref() == Some(raw_value) { + continue; + } + let items = build_svg_transform_list_items_from_attribute(scope, raw.as_deref()); + set_svg_transform_list_items(scope, list, items); + set_svg_transform_list_synced_attribute_value(scope, list, raw_value); } } @@ -1510,7 +1505,7 @@ pub(super) fn reflect_svg_transform_list_to_owner_attribute<'s>( }; let value = serialize_svg_transform_list_items(scope, list); let Ok((runtime_ptr, handle)) = - crate::native_bridge::node_runtime_and_handle_from_object(scope, owner) + crate::native_bridge::node_runtime_and_handle_from_object_or_detached(scope, owner) else { return; }; diff --git a/moli-renderer-v8/src/script_vm/tests/mod.rs b/moli-renderer-v8/src/script_vm/tests/mod.rs index a7f30d5305..1ecca2f9ad 100644 --- a/moli-renderer-v8/src/script_vm/tests/mod.rs +++ b/moli-renderer-v8/src/script_vm/tests/mod.rs @@ -2149,6 +2149,7 @@ mod http_fixture; mod media_owner_playback_interfaces; mod media_recorder_shell; mod svg_animation_interfaces; +mod svg_transform_sync_consolidation; mod audio_event_interfaces; mod hyperlink_null_url_protocol; diff --git a/moli-renderer-v8/src/script_vm/tests/svg_transform_sync_consolidation.rs b/moli-renderer-v8/src/script_vm/tests/svg_transform_sync_consolidation.rs new file mode 100644 index 0000000000..5079e39e95 --- /dev/null +++ b/moli-renderer-v8/src/script_vm/tests/svg_transform_sync_consolidation.rs @@ -0,0 +1,28 @@ +use super::*; + +#[test] +fn svg_transform_sync_retains_members_and_reflects_windowless_mutations() { + let mut vm = new_parsed_test_vm( + "https://svg_transform_sync_consolidation.test/", + "", + ); + assert_eq!(vm.eval(r#"(() => { + const docs=[document,document.implementation.createHTMLDocument('detached')]; + for(const doc of docs){ + const group=doc.createElementNS('http://www.w3.org/2000/svg','g');group.setAttribute('transform','translate(1 2)'); + const animated=group.transform,base=animated.baseVal,anim=animated.animVal; + const baseItem=base.getItem(0),animItem=anim.getItem(0); + if(group.transform!==animated||group.transform.baseVal!==base||group.transform.animVal!==anim)throw Error('SameObject'); + if(base.getItem(0)!==baseItem||anim.getItem(0)!==animItem)throw Error('unchanged tear-off identity'); + group.setAttribute('transform','translate(3 4)'); + const refreshed=group.transform; + if(refreshed.baseVal!==base||refreshed.animVal!==anim||base.getItem(0).matrix.e!==3||anim.getItem(0).matrix.f!==4)throw Error('attribute synchronization'); + const item=base.getItem(0);item.setTranslate(5,6); + if(!group.getAttribute('transform').includes('5'))throw Error('windowless reflection'); + group.transform; + if(anim.getItem(0).matrix.e!==5||anim.getItem(0).matrix.f!==6)throw Error('animated list synchronization'); + const retained=anim.getItem(0);group.transform;if(anim.getItem(0)!==retained)throw Error('animated tear-off identity'); + } + return 'ok'; +})()"#).expect("native binding regression"), "ok"); +}