From 5d7a02041a26e794be02c18521507001c560281d Mon Sep 17 00:00:00 2001 From: SGavrl <277843151+SGavrl@users.noreply.github.com> Date: Sat, 1 Aug 2026 15:41:45 +0000 Subject: [PATCH] fix(js): preserve inline styles when cloning --- crates/obscura-js/js/bootstrap.js | 4 ++++ crates/obscura-js/src/runtime.rs | 22 ++++++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/crates/obscura-js/js/bootstrap.js b/crates/obscura-js/js/bootstrap.js index a63d335..eb2b223 100644 --- a/crates/obscura-js/js/bootstrap.js +++ b/crates/obscura-js/js/bootstrap.js @@ -654,6 +654,10 @@ function _shallowCloneNode(node) { const v = node.getAttribute(name); if (v !== null) el.setAttribute(name, v); } + // CSS declarations currently live on the JS wrapper independently of the + // DOM attribute. Copy that state as well so styles assigned through + // `node.style` survive cloning even before attribute reflection runs. + if (node.style && node.style.cssText) el.style.cssText = node.style.cssText; return el; } diff --git a/crates/obscura-js/src/runtime.rs b/crates/obscura-js/src/runtime.rs index 0f2b629..714543b 100644 --- a/crates/obscura-js/src/runtime.rs +++ b/crates/obscura-js/src/runtime.rs @@ -1369,6 +1369,28 @@ mod tests { assert_eq!(out, serde_json::json!("7|0")); } + #[test] + fn clone_node_copies_js_assigned_inline_styles() { + let mut rt = setup_runtime("
"); + let out = rt + .evaluate( + "(function(){var d=document.getElementById('d');d.style.color='red';d.style.fontSize='12px';var c=d.cloneNode(false);return c.style.color+'|'+c.style.fontSize+'|'+c.style.cssText;})()", + ) + .unwrap(); + assert_eq!(out, serde_json::json!("red|12px|color: red; font-size: 12px;")); + } + + #[test] + fn clone_node_deep_copies_template_content() { + let mut rt = setup_runtime(""); + let out = rt + .evaluate( + "(function(){var t=document.createElement('template');t.content.appendChild(document.createElement('option')).textContent='choice';var c=t.cloneNode(true);return c.content.childNodes.length+'|'+c.content.firstChild.tagName+'|'+c.content.firstChild.textContent;})()", + ) + .unwrap(); + assert_eq!(out, serde_json::json!("1|OPTION|choice")); + } + #[test] fn test_document_title() { let mut rt = setup_runtime("