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 <[email protected]>
This commit is contained in:
KuaaMUandKuaaMU authored and GitHub committed 2026-05-03 17:16:26 +02:00
1 parent 34205e50d1
commit 86a868bfd6
2 files changed
+64 -9

No files matched your search

+57 -7
View File
@@ -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;
+7 -2
View File
@@ -228,8 +228,10 @@ fn op_dom(state: &OpState, #[string] cmd: String, #[string] arg1: String, #[stri
"set_text_content" => {
let nid = arg1.parse::<u32>().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::<u32>().unwrap_or(0);
let ids: Vec<i32> = dom.children(NodeId::new(nid)).iter()