fix(js,dom): add Storage global, CharacterData ChildNode methods, fix remove/isConnected (#176)
fix(dom): add missing DOM/JS shims for modern frameworks Closes #163. Adds missing DOM and JS shims required by modern frameworks (Svelte 5, Vue, Lit) and consent-management SDKs to execute without errors: - Define the `Storage` global constructor to resolve `ReferenceError`s and fix `Storage.prototype` access. - Apply standard ChildNode methods (`before`, `after`, `replaceWith`, `remove`) to `CharacterData.prototype`. This prevents `TypeError`s when frameworks call these on Text or Comment nodes during hydration. - Update `Element.prototype.isConnected` to perform a genuine parent-walk rather than returning a hardcoded `true`. - Fix `Element.prototype.remove` to properly clean up the internal ID index so removed elements are no longer queryable. - Remove outdated no-op stubs on the `Element` class that were shadowing the proper polyfill implementations.
This commit is contained in:
1 parent
10810e250c
commit
183c444a56
3 files changed
+67
-4
No files matched your search
@@ -322,6 +322,40 @@ impl DomTree {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Detach a node from its parent AND remove it (and all descendants)
|
||||||
|
/// from the id-index so that `getElementById` no longer returns them.
|
||||||
|
/// Unlike `remove()`, this does NOT free the nodes — the JS side may
|
||||||
|
/// still hold references to the wrappers.
|
||||||
|
pub fn remove_child(&self, node_id: NodeId) {
|
||||||
|
// Collect all id attribute values in the subtree. We snapshot them
|
||||||
|
// before detaching so `get_attribute` can still see the tree.
|
||||||
|
let ids_to_remove: Vec<String> = {
|
||||||
|
let descendants = self.descendants(node_id);
|
||||||
|
let inner = self.inner.borrow();
|
||||||
|
let mut ids: Vec<String> = Vec::new();
|
||||||
|
if let Some(Some(node)) = inner.nodes.get(node_id.index()) {
|
||||||
|
if let Some(id_val) = node.get_attribute("id") {
|
||||||
|
ids.push(id_val.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for desc_id in &descendants {
|
||||||
|
if let Some(Some(node)) = inner.nodes.get(desc_id.index()) {
|
||||||
|
if let Some(id_val) = node.get_attribute("id") {
|
||||||
|
ids.push(id_val.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ids
|
||||||
|
};
|
||||||
|
|
||||||
|
self.detach(node_id);
|
||||||
|
|
||||||
|
let mut inner = self.inner.borrow_mut();
|
||||||
|
for id_str in &ids_to_remove {
|
||||||
|
inner.id_index.remove(id_str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn remove(&self, node_id: NodeId) {
|
pub fn remove(&self, node_id: NodeId) {
|
||||||
self.detach(node_id);
|
self.detach(node_id);
|
||||||
let descendants = self.descendants(node_id);
|
let descendants = self.descendants(node_id);
|
||||||
|
|||||||
Vendored
+32
-3
@@ -758,8 +758,15 @@ class Element extends Node {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
getAnimations() { return []; }
|
getAnimations() { return []; }
|
||||||
get isConnected() { return true; }
|
get isConnected() {
|
||||||
after() {} before() {} remove() { if (this.parentNode) this.parentNode.removeChild(this); }
|
var node = this;
|
||||||
|
while (node) {
|
||||||
|
if (node.nodeType === 9) return true;
|
||||||
|
node = node.parentNode;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
remove() { if (this.parentNode) this.parentNode.removeChild(this); }
|
||||||
append(...nodes) { for (const n of nodes) { if (typeof n === "string") this.appendChild(document.createTextNode(n)); else this.appendChild(n); } }
|
append(...nodes) { for (const n of nodes) { if (typeof n === "string") this.appendChild(document.createTextNode(n)); else this.appendChild(n); } }
|
||||||
prepend() {}
|
prepend() {}
|
||||||
}
|
}
|
||||||
@@ -1678,6 +1685,15 @@ if (!Element.prototype.after) {
|
|||||||
_markNative(Element.prototype.after);
|
_markNative(Element.prototype.after);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ChildNode mixin: also mix before/after/replaceWith/remove into
|
||||||
|
// CharacterData.prototype (covers Text, Comment, ProcessingInstruction).
|
||||||
|
// These are the same implementations as Element.prototype — frameworks
|
||||||
|
// (Svelte 5, Vue, Lit) anchor on Comment/Text nodes and call these methods.
|
||||||
|
if (!CharacterData.prototype.before) CharacterData.prototype.before = Element.prototype.before;
|
||||||
|
if (!CharacterData.prototype.after) CharacterData.prototype.after = Element.prototype.after;
|
||||||
|
if (!CharacterData.prototype.replaceWith) CharacterData.prototype.replaceWith = Element.prototype.replaceWith;
|
||||||
|
if (!CharacterData.prototype.remove) CharacterData.prototype.remove = Element.prototype.remove;
|
||||||
|
|
||||||
if (!('isConnected' in Node.prototype)) {
|
if (!('isConnected' in Node.prototype)) {
|
||||||
Object.defineProperty(Node.prototype, 'isConnected', {
|
Object.defineProperty(Node.prototype, 'isConnected', {
|
||||||
get() {
|
get() {
|
||||||
@@ -2054,7 +2070,15 @@ globalThis.crypto = globalThis.crypto || { getRandomValues(arr) { for(let i=0;i<
|
|||||||
globalThis.structuredClone = globalThis.structuredClone || ((v) => JSON.parse(JSON.stringify(v)));
|
globalThis.structuredClone = globalThis.structuredClone || ((v) => JSON.parse(JSON.stringify(v)));
|
||||||
globalThis.reportError = globalThis.reportError || ((e) => console.error(e));
|
globalThis.reportError = globalThis.reportError || ((e) => console.error(e));
|
||||||
|
|
||||||
const _mkStore = () => { const s={}; return { getItem:k=>s[k]??null, setItem:(k,v)=>{s[k]=String(v);}, removeItem:k=>{delete s[k];}, clear:()=>{for(const k in s)delete s[k];}, get length(){return Object.keys(s).length;}, key:i=>Object.keys(s)[i]??null }; };
|
globalThis.Storage = function Storage() {};
|
||||||
|
Storage.prototype.getItem = function(k) { return (this._data && this._data[k]) ?? null; };
|
||||||
|
Storage.prototype.setItem = function(k, v) { if (this._data) this._data[k] = String(v); };
|
||||||
|
Storage.prototype.removeItem = function(k) { if (this._data) delete this._data[k]; };
|
||||||
|
Storage.prototype.clear = function() { if (this._data) for (var k in this._data) delete this._data[k]; };
|
||||||
|
Object.defineProperty(Storage.prototype, 'length', { get: function() { return this._data ? Object.keys(this._data).length : 0; } });
|
||||||
|
Storage.prototype.key = function(i) { return this._data ? Object.keys(this._data)[i] ?? null : null; };
|
||||||
|
|
||||||
|
const _mkStore = () => { var s = Object.create(Storage.prototype); s._data = {}; return s; };
|
||||||
globalThis.localStorage = _mkStore();
|
globalThis.localStorage = _mkStore();
|
||||||
globalThis.sessionStorage = _mkStore();
|
globalThis.sessionStorage = _mkStore();
|
||||||
|
|
||||||
@@ -2146,16 +2170,21 @@ globalThis.Range = class Range { setStart(){} setEnd(){} collapse(){} selectNode
|
|||||||
Element.prototype.cloneNode, Element.prototype.attachShadow,
|
Element.prototype.cloneNode, Element.prototype.attachShadow,
|
||||||
Element.prototype.insertAdjacentHTML, Element.prototype.scrollIntoView,
|
Element.prototype.insertAdjacentHTML, Element.prototype.scrollIntoView,
|
||||||
Element.prototype.append, Element.prototype.remove,
|
Element.prototype.append, Element.prototype.remove,
|
||||||
|
Element.prototype.before, Element.prototype.after, Element.prototype.replaceWith,
|
||||||
Element.prototype.getContext, Element.prototype.toDataURL, Element.prototype.toBlob,
|
Element.prototype.getContext, Element.prototype.toDataURL, Element.prototype.toBlob,
|
||||||
Node.prototype.appendChild, Node.prototype.removeChild,
|
Node.prototype.appendChild, Node.prototype.removeChild,
|
||||||
Node.prototype.replaceChild, Node.prototype.insertBefore,
|
Node.prototype.replaceChild, Node.prototype.insertBefore,
|
||||||
Node.prototype.contains, Node.prototype.hasChildNodes, Node.prototype.cloneNode,
|
Node.prototype.contains, Node.prototype.hasChildNodes, Node.prototype.cloneNode,
|
||||||
|
CharacterData.prototype.before, CharacterData.prototype.after,
|
||||||
|
CharacterData.prototype.replaceWith, CharacterData.prototype.remove,
|
||||||
Document.prototype.getElementById, Document.prototype.querySelector,
|
Document.prototype.getElementById, Document.prototype.querySelector,
|
||||||
Document.prototype.querySelectorAll, Document.prototype.getElementsByTagName,
|
Document.prototype.querySelectorAll, Document.prototype.getElementsByTagName,
|
||||||
Document.prototype.createElement, Document.prototype.createElementNS,
|
Document.prototype.createElement, Document.prototype.createElementNS,
|
||||||
Document.prototype.createTextNode, Document.prototype.createComment,
|
Document.prototype.createTextNode, Document.prototype.createComment,
|
||||||
Document.prototype.createDocumentFragment, Document.prototype.createEvent,
|
Document.prototype.createDocumentFragment, Document.prototype.createEvent,
|
||||||
Document.prototype.hasFocus,
|
Document.prototype.hasFocus,
|
||||||
|
Storage, Storage.prototype.getItem, Storage.prototype.setItem,
|
||||||
|
Storage.prototype.removeItem, Storage.prototype.clear, Storage.prototype.key,
|
||||||
Notification, Notification.requestPermission,
|
Notification, Notification.requestPermission,
|
||||||
window.chrome?.csi, window.chrome?.loadTimes,
|
window.chrome?.csi, window.chrome?.loadTimes,
|
||||||
MutationObserver, ResizeObserver, IntersectionObserver, PerformanceObserver,
|
MutationObserver, ResizeObserver, IntersectionObserver, PerformanceObserver,
|
||||||
|
|||||||
@@ -193,7 +193,7 @@ fn op_dom(state: &OpState, #[string] cmd: String, #[string] arg1: String, #[stri
|
|||||||
}
|
}
|
||||||
"remove_child" => {
|
"remove_child" => {
|
||||||
let child = arg1.parse::<u32>().unwrap_or(0);
|
let child = arg1.parse::<u32>().unwrap_or(0);
|
||||||
dom.detach(NodeId::new(child));
|
dom.remove_child(NodeId::new(child));
|
||||||
"true".into()
|
"true".into()
|
||||||
}
|
}
|
||||||
"insert_before" => {
|
"insert_before" => {
|
||||||
|
|||||||
Reference in new issue
Block a user