From 499a5ef983d308d3cf9b95830b3013cdead1b496 Mon Sep 17 00:00:00 2001 From: Andrey Mnatsakanov Date: Sun, 26 Jul 2026 12:33:25 +0200 Subject: [PATCH] fix(js): coerce non-string primitives in before/after/replaceWith before(), after() and replaceWith() only converted string arguments to Text nodes; other primitives (numbers, booleans, null) were passed straight to insert_before with no _nid, corrupting the call. Route them through the shared _convertNodes helper (the same path append/prepend use), so every non-node argument becomes document.createTextNode(String(n)). Fixes #498 --- crates/obscura-js/js/bootstrap.js | 18 ++++++------------ crates/obscura-js/src/runtime.rs | 20 ++++++++++++++++++++ 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/crates/obscura-js/js/bootstrap.js b/crates/obscura-js/js/bootstrap.js index 3ef371a..96353c0 100644 --- a/crates/obscura-js/js/bootstrap.js +++ b/crates/obscura-js/js/bootstrap.js @@ -4122,13 +4122,13 @@ if (typeof Response === 'undefined') { } if (!Element.prototype.replaceWith) { + // _convertNodes turns any non-node argument (numbers, booleans, null, …) into + // a Text node via String(n), matching the spec and append()/prepend(); the + // old `typeof n === 'string'` check corrupted insert_before for other types. Element.prototype.replaceWith = function(...nodes) { const parent = this.parentNode; if (!parent) return; - for (const n of nodes) { - if (typeof n === 'string') parent.insertBefore(document.createTextNode(n), this); - else parent.insertBefore(n, this); - } + for (const n of _convertNodes(nodes)) parent.insertBefore(n, this); parent.removeChild(this); }; _markNative(Element.prototype.replaceWith); @@ -4137,10 +4137,7 @@ if (!Element.prototype.before) { Element.prototype.before = function(...nodes) { const parent = this.parentNode; if (!parent) return; - for (const n of nodes) { - if (typeof n === 'string') parent.insertBefore(document.createTextNode(n), this); - else parent.insertBefore(n, this); - } + for (const n of _convertNodes(nodes)) parent.insertBefore(n, this); }; _markNative(Element.prototype.before); } @@ -4149,10 +4146,7 @@ if (!Element.prototype.after) { const parent = this.parentNode; if (!parent) return; const ref = this.nextSibling; - for (const n of nodes) { - if (typeof n === 'string') parent.insertBefore(document.createTextNode(n), ref); - else parent.insertBefore(n, ref); - } + for (const n of _convertNodes(nodes)) parent.insertBefore(n, ref); }; _markNative(Element.prototype.after); } diff --git a/crates/obscura-js/src/runtime.rs b/crates/obscura-js/src/runtime.rs index fdfeb4d..a6bd24c 100644 --- a/crates/obscura-js/src/runtime.rs +++ b/crates/obscura-js/src/runtime.rs @@ -1317,6 +1317,26 @@ mod tests { rt } + #[test] + fn childnode_helpers_coerce_non_string_primitives_to_text() { + let mut rt = setup_runtime(r#"
x
"#); + let before = rt + .evaluate("(function(){var t=document.getElementById('t'); t.before(5); return t.previousSibling ? t.previousSibling.textContent : 'NULL';})()") + .unwrap(); + assert_eq!(before, serde_json::json!("5")); + let after = rt + .evaluate("(function(){var t=document.getElementById('t'); t.after(true); return t.nextSibling ? t.nextSibling.textContent : 'NULL';})()") + .unwrap(); + assert_eq!(after, serde_json::json!("true")); + let replaced = rt + .evaluate("(function(){var t=document.getElementById('t'); t.replaceWith(42); return document.getElementById('p').textContent;})()") + .unwrap(); + assert!( + replaced.as_str().unwrap().contains("42"), + "replaceWith(42) should leave text '42': {replaced}" + ); + } + #[test] fn test_document_title() { let mut rt = setup_runtime("Test");