diff --git a/crates/obscura-js/js/bootstrap.js b/crates/obscura-js/js/bootstrap.js
index db39028..fb4b90c 100644
--- a/crates/obscura-js/js/bootstrap.js
+++ b/crates/obscura-js/js/bootstrap.js
@@ -663,6 +663,11 @@ class Node {
get previousSibling() { return _wrap(+_dom("prev_sibling", this._nid)); }
appendChild(c) {
if (!c) return c;
+ if (c instanceof DocumentFragment) {
+ const children = Array.from(c.childNodes);
+ for (const child of children) this.appendChild(child);
+ return c;
+ }
_dom("append_child", this._nid, c._nid);
if (globalThis.__mutationObservers?.length) globalThis.__notifyMutation('childList', this._nid, [c._nid], []);
if (c instanceof Element && c.tagName === 'SCRIPT') {
@@ -745,6 +750,12 @@ class Node {
}
replaceChild(newChild, oldChild) {
if (!oldChild || !newChild) return oldChild;
+ if (newChild instanceof DocumentFragment) {
+ const children = Array.from(newChild.childNodes);
+ for (const child of children) this.insertBefore(child, oldChild);
+ this.removeChild(oldChild);
+ return oldChild;
+ }
_dom("insert_before", newChild._nid, oldChild._nid);
_dom("remove_child", oldChild._nid);
return oldChild;
@@ -752,6 +763,11 @@ class Node {
insertBefore(n, ref) {
if (!n) return n;
if (!ref) { this.appendChild(n); return n; }
+ if (n instanceof DocumentFragment) {
+ const children = Array.from(n.childNodes);
+ for (const child of children) this.insertBefore(child, ref);
+ return n;
+ }
_dom("insert_before", n._nid, ref._nid);
return n;
}
diff --git a/crates/obscura-js/src/runtime.rs b/crates/obscura-js/src/runtime.rs
index eb9ecde..1f6e766 100644
--- a/crates/obscura-js/src/runtime.rs
+++ b/crates/obscura-js/src/runtime.rs
@@ -1377,6 +1377,112 @@ mod tests {
);
}
+ #[test]
+ fn append_child_flattens_document_fragment() {
+ let mut rt = setup_runtime(r#""#);
+ rt.run_page_init();
+ let result = rt
+ .evaluate(
+ r#"
+ const host = document.getElementById('host');
+ const fragment = document.createDocumentFragment();
+ const first = document.createElement('article');
+ const second = document.createElement('article');
+ first.id = 'first';
+ second.id = 'second';
+ first.className = second.className = 'quote';
+ fragment.appendChild(first);
+ fragment.appendChild(second);
+
+ const returned = host.appendChild(fragment);
+ return [
+ returned === fragment,
+ Array.from(host.children).map(node => node.id),
+ host.querySelectorAll('.quote').length,
+ fragment.childNodes.length,
+ first.parentNode === host,
+ first.parentElement === host,
+ ];
+ "#,
+ )
+ .unwrap();
+ assert_eq!(
+ result,
+ serde_json::json!([true, ["first", "second"], 2, 0, true, true])
+ );
+ }
+
+ #[test]
+ fn insert_before_flattens_document_fragment_in_order() {
+ let mut rt = setup_runtime(r#"
Hello