From 86a868bfd64d58e90a608dab21a95989b6af8408 Mon Sep 17 00:00:00 2001 From: KuaaMU <138859253+KuaaMU@users.noreply.github.com> Date: Sun, 3 May 2026 23:16:26 +0800 Subject: [PATCH] fix: implement CharacterData DOM API for jQuery DataTables compatibility (#73) jQuery DataTables references the standard CharacterData interface which was undefined in obscura's DOM environment, causing ReferenceError and empty page renders. - Add CharacterData class extending Node with data/length/substringData/ appendData/insertData/deleteData/replaceData - Make Text and Comment extend CharacterData instead of Node - Update _wrap() to create proper Text/Comment instances by nodeType - Add create_comment_node op for Document.createComment() - Fix set_text_content to handle Comment node mutations Fixes #72 Co-authored-by: KuaaMU --- crates/obscura-js/js/bootstrap.js | 64 +++++++++++++++++++++++++++---- crates/obscura-js/src/ops.rs | 9 ++++- 2 files changed, 64 insertions(+), 9 deletions(-) diff --git a/crates/obscura-js/js/bootstrap.js b/crates/obscura-js/js/bootstrap.js index 9122dc0..c94bf2b 100644 --- a/crates/obscura-js/js/bootstrap.js +++ b/crates/obscura-js/js/bootstrap.js @@ -345,6 +345,56 @@ class Node { isSameNode(other) { return other && this._nid === other._nid; } addEventListener() {} removeEventListener() {} dispatchEvent() { return true; } } +class CharacterData extends Node { + get data() { + return _domParse("text_content", this._nid) ?? ""; + } + set data(v) { + _dom("set_text_content", this._nid, String(v ?? "")); + } + get length() { return this.data.length; } + substringData(offset, count) { + return this.data.substring(offset, offset + count); + } + appendData(s) { this.data += s; } + insertData(offset, s) { + const d = this.data; + this.data = d.slice(0, offset) + s + d.slice(offset); + } + deleteData(offset, count) { + const d = this.data; + this.data = d.slice(0, offset) + d.slice(offset + count); + } + replaceData(offset, count, s) { + const d = this.data; + this.data = d.slice(0, offset) + s + d.slice(offset + count); + } +} + +class Text extends CharacterData { + get nodeName() { return "#text"; } + get nodeType() { return 3; } + get wholeText() { return this.data; } + splitText(offset) { + const d = this.data; + const tail = d.substring(offset); + this.data = d.substring(0, offset); + const newNid = +_dom("create_text_node", tail); + const parent = this.parentNode; + if (parent) { + const ref = this.nextSibling; + parent.insertBefore(_wrap(newNid), ref); + } + return _wrap(newNid); + } + cloneNode() { return document.createTextNode(this.data); } +} + +class Comment extends CharacterData { + get nodeName() { return "#comment"; } + get nodeType() { return 8; } + cloneNode() { return document.createComment(this.data); } +} class Element extends Node { constructor(nid) { @@ -764,11 +814,8 @@ class Document extends Node { } createTextNode(t) { return _wrap(+_dom("create_text_node", String(t))); } createComment(t) { - const nid = +_dom("create_text_node", ""); - const n = new Node(nid); - n._isComment = true; - Object.defineProperty(n, "nodeType", { value: 8, writable: false, configurable: true }); - Object.defineProperty(n, "nodeName", { value: "#comment", writable: false, configurable: true }); + const nid = +_dom("create_comment_node", String(t ?? "")); + const n = new Comment(nid); _cache.set(nid, n); return n; } @@ -971,6 +1018,8 @@ function _wrap(nid) { const t = +_dom("node_type", nid); let n; if (t === 1) n = new Element(nid); + else if (t === 3) n = new Text(nid); + else if (t === 8) n = new Comment(nid); else if (t === 9) n = new Document(nid); else n = new Node(nid); _cache.set(nid, n); @@ -1987,8 +2036,9 @@ globalThis.HTMLDetailsElement = Element; globalThis.HTMLDialogElement = Element; globalThis.SVGElement = Element; globalThis.SVGSVGElement = Element; -globalThis.Text = Node; -globalThis.Comment = Node; +globalThis.CharacterData = CharacterData; +globalThis.Text = Text; +globalThis.Comment = Comment; globalThis.DocumentFragment = DocumentFragment; globalThis.DocumentType = DocumentType; globalThis.Node = Node; diff --git a/crates/obscura-js/src/ops.rs b/crates/obscura-js/src/ops.rs index 999399a..729930e 100644 --- a/crates/obscura-js/src/ops.rs +++ b/crates/obscura-js/src/ops.rs @@ -228,8 +228,10 @@ fn op_dom(state: &OpState, #[string] cmd: String, #[string] arg1: String, #[stri "set_text_content" => { let nid = arg1.parse::().unwrap_or(0); dom.with_node_mut(NodeId::new(nid), |n| { - if let NodeData::Text { contents } = &mut n.data { - *contents = arg2.clone(); + match &mut n.data { + NodeData::Text { contents } => { *contents = arg2.clone(); } + NodeData::Comment { contents } => { *contents = arg2.clone(); } + _ => {} } }); "true".into() @@ -246,6 +248,9 @@ fn op_dom(state: &OpState, #[string] cmd: String, #[string] arg1: String, #[stri "create_text_node" => { dom.new_node(NodeData::Text { contents: arg1.clone() }).index().to_string() } + "create_comment_node" => { + dom.new_node(NodeData::Comment { contents: arg1.clone() }).index().to_string() + } "element_children" => { let nid = arg1.parse::().unwrap_or(0); let ids: Vec = dom.children(NodeId::new(nid)).iter()