Files
obscura/crates/obscura-js/js/bootstrap.js
T

4263 lines
181 KiB
JavaScript

"use strict";
globalThis.__obscura_errors = [];
globalThis.addEventListener = globalThis.addEventListener || function(){};
globalThis.onunhandledrejection = function(e) { if (e?.preventDefault) e.preventDefault(); };
globalThis.onerror = function(msg, src, line, col, error) {
globalThis.__obscura_errors.push({msg: String(msg), src: String(src||""), line, error: String(error||"")});
};
globalThis.__windowListeners = {};
globalThis.addEventListener = function(type, fn) {
if (!globalThis.__windowListeners[type]) globalThis.__windowListeners[type] = [];
globalThis.__windowListeners[type].push(fn);
};
globalThis.removeEventListener = function(type, fn) {
if (globalThis.__windowListeners[type]) {
globalThis.__windowListeners[type] = globalThis.__windowListeners[type].filter(h => h !== fn);
}
};
globalThis.dispatchEvent = function(event) {
if (!event) return true;
const handlers = globalThis.__windowListeners[event.type] || [];
for (const h of handlers) { try { h.call(globalThis, event); } catch(e) { console.error(e); } }
return !event.defaultPrevented;
};
const _dom = (cmd, a1, a2) => Deno.core.ops.op_dom(cmd, String(a1 ?? ""), String(a2 ?? ""));
const _nativeFns = new Set();
const _origToString = Function.prototype.toString;
Function.prototype.toString = function() {
if (_nativeFns.has(this)) {
return `function ${this.name || ''}() { [native code] }`;
}
return _origToString.call(this);
};
function _markNative(fn) { if (typeof fn === 'function') _nativeFns.add(fn); return fn; }
_nativeFns.add(Function.prototype.toString);
[Error, TypeError, ReferenceError, SyntaxError, RangeError, URIError, EvalError].forEach(E => {
try {
Object.defineProperty(E.prototype, 'name', {
value: E.name, writable: true, enumerable: false, configurable: false,
});
} catch(e) {}
});
const _stackCache = new WeakMap();
const _origStackDesc = Object.getOwnPropertyDescriptor(Error.prototype, 'stack');
if (_origStackDesc && _origStackDesc.get) {
Object.defineProperty(Error.prototype, 'stack', {
configurable: false, enumerable: false,
get: function() {
if (!_stackCache.has(this)) _stackCache.set(this, _origStackDesc.get.call(this));
return _stackCache.get(this);
}
});
}
let _fpSeed = 0;
function _fpRand(salt) {
let h = (_fpSeed ^ (salt || 0)) | 0;
h = Math.imul(h ^ (h >>> 16), 0x45d9f3b);
h = Math.imul(h ^ (h >>> 13), 0x45d9f3b);
return ((h ^ (h >>> 16)) >>> 0) / 0xFFFFFFFF;
}
function _fpNoise(x, y, channel) {
return (_fpRand(x * 7919 + y * 6271 + channel * 8923) - 0.5) * 4;
}
var _fpCache = null;
function _getFp() {
if (_fpCache) return _fpCache;
const gpuPool = [
'ANGLE (NVIDIA, NVIDIA GeForce RTX 3060 Direct3D11 vs_5_0 ps_5_0, D3D11)',
'ANGLE (NVIDIA, NVIDIA GeForce GTX 1660 SUPER Direct3D11 vs_5_0 ps_5_0, D3D11)',
'ANGLE (NVIDIA, NVIDIA GeForce RTX 2070 SUPER Direct3D11 vs_5_0 ps_5_0, D3D11)',
'ANGLE (Intel, Intel(R) UHD Graphics 630 Direct3D11 vs_5_0 ps_5_0, D3D11)',
'ANGLE (Intel, Intel(R) Iris(R) Xe Graphics Direct3D11 vs_5_0 ps_5_0, D3D11)',
'ANGLE (AMD, AMD Radeon RX 580 Direct3D11 vs_5_0 ps_5_0, D3D11)',
'ANGLE (AMD, AMD Radeon RX 6700 XT Direct3D11 vs_5_0 ps_5_0, D3D11)',
'ANGLE (NVIDIA, NVIDIA GeForce RTX 4070 Direct3D11 vs_5_0 ps_5_0, D3D11)',
'ANGLE (NVIDIA, NVIDIA GeForce GTX 1080 Ti Direct3D11 vs_5_0 ps_5_0, D3D11)',
'ANGLE (Intel, Intel(R) UHD Graphics 770 Direct3D11 vs_5_0 ps_5_0, D3D11)',
'ANGLE (AMD, AMD Radeon RX 5700 XT Direct3D11 vs_5_0 ps_5_0, D3D11)',
'ANGLE (NVIDIA, NVIDIA GeForce RTX 3080 Direct3D11 vs_5_0 ps_5_0, D3D11)',
];
const gpuVendorPool = [
'Google Inc. (NVIDIA)','Google Inc. (NVIDIA)','Google Inc. (NVIDIA)',
'Google Inc. (Intel)','Google Inc. (Intel)',
'Google Inc. (AMD)','Google Inc. (AMD)',
'Google Inc. (NVIDIA)','Google Inc. (NVIDIA)',
'Google Inc. (Intel)','Google Inc. (AMD)','Google Inc. (NVIDIA)',
];
const idx = Math.floor(_fpRand(42) * gpuPool.length);
const screenPool = [[1920,1080],[2560,1440],[1366,768],[1536,864],[1440,900],[1680,1050],[1280,720],[3840,2160]];
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
let cfp = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUg';
for (let i = 0; i < 40; i++) cfp += chars[Math.floor(_fpRand(500 + i) * 64)];
cfp += '==';
_fpCache = {
gpu: gpuPool[idx], gpuVendor: gpuVendorPool[idx],
audioBaseLatency: 0.002 + _fpRand(100) * 0.008,
audioSampleRate: [44100, 48000][Math.floor(_fpRand(101) * 2)],
compThreshold: -24 + (_fpRand(102) - 0.5) * 4,
compKnee: 30 + (_fpRand(103) - 0.5) * 4,
compRatio: 12 + (_fpRand(104) - 0.5) * 4,
batteryLevel: 0.5 + _fpRand(200) * 0.5,
batteryCharging: _fpRand(201) > 0.3,
screen: screenPool[Math.floor(_fpRand(300) * screenPool.length)],
canvasFingerprint: cfp,
};
return _fpCache;
}
function _fp(key) { return _getFp()[key]; }
globalThis._eventRegistry = globalThis._eventRegistry || {};
globalThis._formValues = globalThis._formValues || {};
globalThis._formChecked = globalThis._formChecked || {};
const _eventRegistry = globalThis._eventRegistry;
const _formValues = globalThis._formValues;
const _formChecked = globalThis._formChecked;
const _domParse = (cmd, a1, a2) => { try { return JSON.parse(_dom(cmd, a1, a2)); } catch { return null; } };
const _consoleFn = (level, args) => {
try { Deno.core.ops.op_console_msg(level, args.map(a => {
if (a === null) return "null";
if (a === undefined) return "undefined";
if (a instanceof Error) return a.stack || a.message || String(a);
if (typeof a === "object") {
try {
const s = JSON.stringify(a);
return s === "{}" && a.message ? a.message : s;
} catch { return String(a); }
}
return String(a);
}).join(" ")); } catch {}
};
globalThis.console = {
log: (...a) => _consoleFn("log", a), warn: (...a) => _consoleFn("warn", a),
error: (...a) => _consoleFn("error", a), info: (...a) => _consoleFn("log", a),
debug: () => {}, dir: () => {}, trace: () => {}, table: () => {}, group: () => {},
groupEnd: () => {}, groupCollapsed: () => {}, time: () => {}, timeEnd: () => {},
timeLog: () => {}, count: () => {}, countReset: () => {}, clear: () => {},
assert: (c, ...a) => { if (!c) _consoleFn("error", ["Assertion failed:", ...a]); },
};
let _tid = 0;
const _clearedTimers = new Set();
const _intervals = new Set();
const _scheduleAfter = (delay, fn) => {
const d = Math.max(0, Number(delay) || 0);
if (d === 0) Promise.resolve().then(fn);
else Deno.core.ops.op_sleep(d).then(fn);
};
globalThis.setTimeout = (fn, delay = 0, ...args) => {
if (typeof fn !== "function") return ++_tid;
const id = ++_tid;
_scheduleAfter(delay, () => {
if (_clearedTimers.has(id)) return;
try { fn(...args); } catch(e) { console.error("Timer error:", e); }
});
return id;
};
globalThis.clearTimeout = (id) => { _clearedTimers.add(id); };
globalThis.setInterval = (fn, delay = 0, ...args) => {
if (typeof fn !== "function") return ++_tid;
const id = ++_tid;
_intervals.add(id);
const tick = () => {
if (!_intervals.has(id)) return;
try { fn(...args); } catch(e) { console.error("Interval error:", e); }
if (!_intervals.has(id)) return;
_scheduleAfter(delay, tick);
};
_scheduleAfter(delay, tick);
return id;
};
globalThis.clearInterval = (id) => { _intervals.delete(id); _clearedTimers.add(id); };
globalThis.requestAnimationFrame = (fn) => setTimeout(fn, 0);
globalThis.cancelAnimationFrame = globalThis.clearTimeout;
globalThis.queueMicrotask = globalThis.queueMicrotask || ((fn) => Promise.resolve().then(fn));
class MessageChannel {
constructor() {
this.port1 = { onmessage: null, postMessage: () => {}, close() {}, addEventListener() {}, removeEventListener() {} };
this.port2 = { onmessage: null, postMessage: () => {}, close() {}, addEventListener() {}, removeEventListener() {} };
this.port1.postMessage = (data) => {
Promise.resolve().then(() => { if (this.port2.onmessage) this.port2.onmessage({ data }); });
};
this.port2.postMessage = (data) => {
Promise.resolve().then(() => { if (this.port1.onmessage) this.port1.onmessage({ data }); });
};
}
}
globalThis.MessageChannel = MessageChannel;
globalThis.MessagePort = class MessagePort { constructor(){} postMessage(){} close(){} addEventListener(){} removeEventListener(){} };
class CSSStyleDeclaration {
constructor() { this._props = {}; }
setProperty(name, value) { this._props[name] = String(value); }
removeProperty(name) { const old = this._props[name]; delete this._props[name]; return old || ""; }
getPropertyValue(name) { return this._props[name] || ""; }
get cssText() { return Object.entries(this._props).map(([k,v]) => `${k}: ${v}`).join("; "); }
set cssText(v) { this._props = {}; if(v) v.split(";").forEach(p => { const [k,...rest]=p.split(":"); if(k&&rest.length) this._props[k.trim()]=rest.join(":").trim(); }); }
get length() { return Object.keys(this._props).length; }
item(i) { return Object.keys(this._props)[i] || ""; }
}
const _styleProxy = (decl) => new Proxy(decl, {
get(t, p) {
if (typeof p === "symbol" || p in t) return t[p];
if (typeof p === "string") return t._props[p] || "";
return undefined;
},
set(t, p, v) {
if (typeof p === "string") { t._props[p] = String(v); return true; }
t[p] = v; return true;
}
});
class Node {
static ELEMENT_NODE = 1;
static TEXT_NODE = 3;
static COMMENT_NODE = 8;
static DOCUMENT_NODE = 9;
static DOCUMENT_FRAGMENT_NODE = 11;
constructor(nid) { this._nid = nid; }
get nodeType() { return +_dom("node_type", this._nid); }
get nodeName() { return _domParse("node_name", this._nid) || ""; }
get ownerDocument() { return globalThis.document; }
get textContent() { return _domParse("text_content", this._nid) ?? ""; }
set textContent(v) {
const oldChildren = _domParse("child_nodes", this._nid) || [];
for (const c of oldChildren) _dom("remove_child", c);
let added = [];
if (v != null && v !== "") {
const tn = +_dom("create_text_node", String(v));
_dom("append_child", this._nid, tn);
added = [tn];
}
// Real MutationObserver fires childList for the children swap.
// Without this React 18+ hydration mismatch detection and many polling
// libs (intersection-driven lazy load, content sync) silently stall.
if (globalThis.__mutationObservers?.length) {
globalThis.__notifyMutation('childList', this._nid, added, oldChildren);
}
}
get nodeValue() {
const t = this.nodeType;
if (t === 3 || t === 8) return _domParse("text_content", this._nid) ?? "";
return null;
}
set nodeValue(v) {
const t = this.nodeType;
if (t === 3 || t === 8) _dom("set_text_content", this._nid, String(v ?? ""));
}
get parentNode() { return _wrap(+_dom("parent_node", this._nid)); }
get parentElement() { const p = this.parentNode; return p && p.nodeType === 1 ? p : null; }
get childNodes() {
const ids = _domParse("child_nodes", this._nid) || [];
const list = ids.map(_wrap).filter(Boolean);
list.item = (i) => list[i] || null;
return list;
}
get firstChild() { return _wrap(+_dom("first_child", this._nid)); }
get lastChild() { return _wrap(+_dom("last_child", this._nid)); }
get nextSibling() { return _wrap(+_dom("next_sibling", this._nid)); }
get previousSibling() { return _wrap(+_dom("prev_sibling", this._nid)); }
appendChild(c) {
if (!c) 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') {
const scriptType = c.getAttribute('type') || '';
const isModule = scriptType === 'module';
if (scriptType && !isModule && scriptType !== 'text/javascript' && scriptType !== 'application/javascript') {
return c;
}
const src = c.getAttribute('src');
const prevNid = globalThis.__currentScriptNid;
if (src) {
const fullUrl = src.startsWith('http') || src.startsWith('data:')
? src
: new URL(src, globalThis.location?.href || 'http://localhost/').href;
const pageOrigin = (function() { try { return new URL(globalThis.location?.href || "about:blank").origin; } catch(e) { return ""; } })();
(async () => {
try {
if (isModule) {
await import(fullUrl);
} else {
const raw = await Deno.core.ops.op_fetch_url(fullUrl, "GET", "{}", "", pageOrigin, "no-cors");
const parsed = JSON.parse(raw);
if (parsed.body) {
globalThis.__currentScriptNid = c._nid;
try { (0, eval)(parsed.body); }
catch(e) { console.error('Dynamic script error (' + fullUrl + '):', e.message); }
finally { globalThis.__currentScriptNid = prevNid || 0; }
}
}
if (typeof c.onload === 'function') try { c.onload(new Event('load')); } catch(e) {}
try { c.dispatchEvent(new Event('load')); } catch(e) {}
} catch(e) {
console.error('Dynamic script fetch error:', e.message);
if (typeof c.onerror === 'function') try { c.onerror(e); } catch(ex) {}
}
})();
} else {
const code = c.textContent;
if (code) {
if (isModule) {
const dataUrl = 'data:text/javascript;base64,' + btoa(unescape(encodeURIComponent(code)));
(async () => {
try { await import(dataUrl); }
catch(e) { console.error('Dynamic inline module error:', e.message); }
})();
} else {
globalThis.__currentScriptNid = c._nid;
try { (0, eval)(code); }
catch(e) { console.error('Dynamic inline script error:', e.message); }
finally { globalThis.__currentScriptNid = prevNid || 0; }
}
}
}
}
return c;
}
removeChild(c) {
if (!c) return c;
_dom("remove_child", c._nid);
if (globalThis.__mutationObservers?.length) globalThis.__notifyMutation('childList', this._nid, [], [c._nid]);
return c;
}
replaceChild(newChild, oldChild) {
if (!oldChild || !newChild) return oldChild;
_dom("insert_before", newChild._nid, oldChild._nid);
_dom("remove_child", oldChild._nid);
return oldChild;
}
insertBefore(n, ref) {
if (!n) return n;
if (!ref) { this.appendChild(n); return n; }
_dom("insert_before", n._nid, ref._nid);
return n;
}
contains(o) { return o ? _dom("contains", this._nid, o._nid) === "true" : false; }
hasChildNodes() { return _dom("has_child_nodes", this._nid) === "true"; }
cloneNode(deep) {
const t = this.nodeType;
if (t === 1) {
if (deep) {
const wrapper = document.createElement('div');
wrapper.innerHTML = _domParse("outer_html", this._nid) || "";
const clone = wrapper.firstChild;
return clone;
}
const el = document.createElement(this.nodeName.toLowerCase());
const html = _domParse("outer_html", this._nid) || "";
const attrMatch = html.match(/^<[a-zA-Z][^\s>]*([\s\S]*?)>/);
if (attrMatch && attrMatch[1]) {
const attrStr = attrMatch[1].trim();
const re = /([a-zA-Z_:][a-zA-Z0-9_.:-]*)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|(\S+)))?/g;
let m;
while ((m = re.exec(attrStr)) !== null) {
const name = m[1];
const val = m[2] !== undefined ? m[2] : m[3] !== undefined ? m[3] : m[4] || "";
if (name !== this.nodeName.toLowerCase()) el.setAttribute(name, val);
}
}
return el;
}
if (t === 3) return document.createTextNode(this.textContent);
if (t === 8) return document.createComment(this.nodeValue || "");
return null;
}
compareDocumentPosition(other) {
if (!other) return 0;
if (this._nid === other._nid) return 0;
if (this.contains(other)) return 16 | 4; // DOCUMENT_POSITION_CONTAINED_BY | FOLLOWING
if (other.contains && other.contains(this)) return 8 | 2; // DOCUMENT_POSITION_CONTAINS | PRECEDING
return 4; // DOCUMENT_POSITION_FOLLOWING
}
getRootNode() { return globalThis.document; }
normalize() {} // no-op
isEqualNode(other) {
if (!other) return false;
if (this._nid === other._nid) return true;
if (this.nodeType !== other.nodeType) return false;
if (this.nodeName !== other.nodeName) return false;
if (this.nodeValue !== other.nodeValue) return false;
const a = this.attributes ? this.attributes : null;
const b = other.attributes ? other.attributes : null;
if ((a && a.length) || (b && b.length)) {
if (!a || !b || a.length !== b.length) return false;
for (let i = 0; i < a.length; i++) {
if (other.getAttribute(a[i].name) !== a[i].value) return false;
}
}
const cA = this.childNodes || [];
const cB = other.childNodes || [];
if (cA.length !== cB.length) return false;
for (let i = 0; i < cA.length; i++) {
if (!cA[i].isEqualNode(cB[i])) return false;
}
return true;
}
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) {
const oldValue = _domParse("text_content", this._nid) ?? "";
_dom("set_text_content", this._nid, String(v ?? ""));
if (globalThis.__mutationObservers?.length) {
globalThis.__notifyMutation('characterData', this._nid, [], [], null, oldValue);
}
}
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) {
super(nid);
this._style = _styleProxy(new CSSStyleDeclaration());
}
get tagName() { return _domParse("tag_name", this._nid) || ""; }
get localName() { return (this.tagName || "").toLowerCase(); }
get id() { return this.getAttribute("id") || ""; }
set id(v) { this.setAttribute("id", v); }
get className() { return this.getAttribute("class") || ""; }
set className(v) { this.setAttribute("class", v); }
get namespaceURI() {
const tag = this.localName;
if (tag === "svg" || this._ns === "http://www.w3.org/2000/svg") return "http://www.w3.org/2000/svg";
return "http://www.w3.org/1999/xhtml";
}
get innerHTML() { return _domParse("inner_html", this._nid) ?? ""; }
set innerHTML(v) {
if (this.localName === 'template') {
this.content.innerHTML = v;
return;
}
// Capture the children that are about to be replaced so we can deliver
// them as `removedNodes` in the MutationObserver record. Without this,
// libraries that mutate via `innerHTML =` (jQuery's `.html(s)`, React
// `dangerouslySetInnerHTML`, vue-style content swaps) silently bypass
// every MutationObserver subscriber and downstream hydration / polling
// logic stalls.
let oldChildren = [];
let newChildren = [];
if (globalThis.__mutationObservers?.length) {
oldChildren = _domParse("child_nodes", this._nid) || [];
}
_dom("set_inner_html", this._nid, String(v ?? ""));
if (globalThis.__mutationObservers?.length) {
newChildren = _domParse("child_nodes", this._nid) || [];
globalThis.__notifyMutation('childList', this._nid, newChildren, oldChildren);
}
}
get outerHTML() { return _domParse("outer_html", this._nid) ?? ""; }
get innerText() { return this.textContent; }
set innerText(v) { this.textContent = v; }
get children() {
const ids = _domParse("element_children", this._nid) || [];
return ids.map(_wrapEl).filter(Boolean);
}
get content() {
// <template>.content is a DocumentFragment; <meta>.content reflects
// the content attribute (read/write per spec). Next.js' next/head
// iterates <meta> tags and sets .content during hydration, which
// threw with the previous getter-only stub and put React into an
// infinite retry loop (issue #210).
const tag = this.localName;
if (tag === 'template') {
if (!this._templateContent) this._templateContent = document.createDocumentFragment();
return this._templateContent;
}
if (tag === 'meta') return this.getAttribute('content') || '';
return undefined;
}
set content(v) {
if (this.localName === 'meta') {
this.setAttribute('content', v == null ? '' : String(v));
}
}
get childElementCount() { return this.children.length; }
get firstElementChild() { return this.children[0] || null; }
get lastElementChild() { const ch = this.children; return ch[ch.length-1] || null; }
get nextElementSibling() { let s = this.nextSibling; while(s && s.nodeType !== 1) s = s.nextSibling; return s; }
get previousElementSibling() { let s = this.previousSibling; while(s && s.nodeType !== 1) s = s.previousSibling; return s; }
get classList() {
const el = this;
const obj = {
add: (...c) => { const s = new Set((el.className||"").split(/\s+/).filter(Boolean)); c.forEach(x=>s.add(x)); el.className=[...s].join(" "); },
remove: (...c) => { const s = new Set((el.className||"").split(/\s+/).filter(Boolean)); c.forEach(x=>s.delete(x)); el.className=[...s].join(" "); },
contains: c => (el.className||"").split(/\s+/).includes(c),
toggle: (c, force) => { const has = obj.contains(c); if(force===true||(!has&&force!==false)){obj.add(c);return true;} obj.remove(c); return false; },
get length() { return (el.className||"").split(/\s+/).filter(Boolean).length; },
item: i => (el.className||"").split(/\s+/).filter(Boolean)[i] || null,
forEach: (cb) => (el.className||"").split(/\s+/).filter(Boolean).forEach(cb),
toString: () => el.className || "",
};
return obj;
}
get style() { return this._style; }
set style(v) { if (typeof v === "string") this._style.cssText = v; }
getAttribute(n) { return _domParse("get_attribute", this._nid, n); }
setAttribute(n, v) {
_dom("set_attribute", this._nid, n + "\0" + String(v));
if (globalThis.__mutationObservers?.length) globalThis.__notifyMutation('attributes', this._nid, [], [], n);
}
setAttributeNS(ns, n, v) { this.setAttribute(n, v); } // Simplified NS handling
removeAttribute(n) { _dom("remove_attribute", this._nid, n); }
removeAttributeNS(ns, n) { this.removeAttribute(n); }
hasAttribute(n) { return this.getAttribute(n) !== null; }
hasAttributes() { return true; } // Simplified
get attributes() {
const el = this;
const names = _domParse("attribute_names", el._nid) || [];
const list = names.map((name) => ({
name,
localName: name,
value: el.getAttribute(name) ?? "",
namespaceURI: null,
prefix: null,
specified: true,
ownerElement: el,
nodeName: name,
nodeValue: el.getAttribute(name) ?? "",
nodeType: 2,
}));
list.length = names.length;
list.getNamedItem = (n) => names.includes(n) ? list[names.indexOf(n)] : null;
list.setNamedItem = (a) => { if (a && a.name) el.setAttribute(a.name, a.value); return a; };
list.removeNamedItem = (n) => { const a = list.getNamedItem(n); if (a) el.removeAttribute(n); return a; };
list.item = (i) => list[i] || null;
for (let i = 0; i < names.length; i++) {
Object.defineProperty(list, names[i], { value: list[i], configurable: true, enumerable: false });
}
return list;
}
getAttributeNS(ns, n) { return this.getAttribute(n); }
querySelector(s) { return _wrapEl(+_dom("query_selector_scoped", this._nid, s)); }
querySelectorAll(s) {
const ids = _domParse("query_selector_all_scoped", this._nid, s) || [];
const list = ids.map(_wrapEl).filter(Boolean);
list.item = (i) => list[i] || null;
list.forEach = Array.prototype.forEach.bind(list);
return list;
}
getElementsByTagName(t) { return this.querySelectorAll(t); }
getElementsByClassName(c) { return this.querySelectorAll("." + c); }
matches(s) {
const parent = this.parentNode;
if (!parent || !parent.querySelectorAll) return false;
const matches = parent.querySelectorAll(s);
for (let i = 0; i < matches.length; i++) {
if (matches[i]._nid === this._nid) return true;
}
return false;
}
closest(s) {
let el = this;
while (el) {
if (el.nodeType === 1 && el.matches && el.matches(s)) return el;
el = el.parentNode;
}
return null;
}
insertAdjacentHTML(position, html) {
const parent = this.parentNode;
switch (position) {
case 'beforebegin':
if (parent) { const tmp = document.createElement('div'); tmp.innerHTML = html; const children = tmp.childNodes; for (let i = 0; i < children.length; i++) parent.insertBefore(children[i], this); }
break;
case 'afterbegin':
{ const tmp = document.createElement('div'); tmp.innerHTML = html; const children = tmp.childNodes; const first = this.firstChild; for (let i = children.length - 1; i >= 0; i--) this.insertBefore(children[i], first); }
break;
case 'beforeend':
{ const tmp = document.createElement('div'); tmp.innerHTML = html; const children = tmp.childNodes; for (let i = 0; i < children.length; i++) this.appendChild(children[i]); }
break;
case 'afterend':
if (parent) { const tmp = document.createElement('div'); tmp.innerHTML = html; const children = tmp.childNodes; const next = this.nextSibling; for (let i = 0; i < children.length; i++) parent.insertBefore(children[i], next); }
break;
}
}
addEventListener(type, handler, opts) {
const key = this._nid;
if (!_eventRegistry[key]) _eventRegistry[key] = {};
if (!_eventRegistry[key][type]) _eventRegistry[key][type] = [];
_eventRegistry[key][type].push(handler);
}
removeEventListener(type, handler) {
const key = this._nid;
if (_eventRegistry[key] && _eventRegistry[key][type]) {
_eventRegistry[key][type] = _eventRegistry[key][type].filter(h => h !== handler);
}
}
dispatchEvent(event) {
if (!event) return true;
if (!event.target) event.target = this;
event.currentTarget = this;
// Spec: inline `onclick="..."` content attributes are event handlers
// for the matching event type. Fire them alongside any
// addEventListener handlers. Also honor the IDL property
// `el.onclick = fn` if set. Without this, b.click() never invokes
// the inline handler and forms with onsubmit / buttons with onclick
// are silently dead.
const handlerName = 'on' + event.type;
const inlineFn = this[handlerName] || this._resolveInlineHandler(handlerName);
if (typeof inlineFn === 'function') {
try {
const ret = inlineFn.call(this, event);
if (ret === false) event.preventDefault();
} catch(e) { console.error(e); }
}
const handlers = (_eventRegistry[this._nid] || {})[event.type] || [];
for (const h of handlers) {
try { h.call(this, event); } catch(e) { console.error(e); }
if (event._immediatePropagationStopped) break;
}
if (event.bubbles && !event._propagationStopped && this.parentNode) {
this.parentNode.dispatchEvent(event);
}
return !event.defaultPrevented;
}
_resolveInlineHandler(name) {
// name = 'onclick' / 'onsubmit' / etc. Compile the content attribute
// as a function body on first read and cache it on the instance.
const cache = this.__inlineHandlerCache || (this.__inlineHandlerCache = {});
if (Object.prototype.hasOwnProperty.call(cache, name)) return cache[name];
const src = this.getAttribute && this.getAttribute(name);
if (!src) { cache[name] = null; return null; }
try {
cache[name] = new Function('event', src);
} catch (e) {
cache[name] = null;
}
return cache[name];
}
click() {
const cancelled = !this.dispatchEvent(new MouseEvent("click", {bubbles: true, cancelable: true}));
if (!cancelled) {
const link = this.tagName === 'A' ? this : (this.closest ? this.closest('a[href]') : null);
if (link) {
const href = link.getAttribute('href');
if (href && !href.startsWith('#') && !href.startsWith('javascript:')) {
location.assign(href);
return;
}
}
const type = (this.getAttribute('type') || '').toLowerCase();
if (type === 'submit' || (this.localName === 'button' && type !== 'button' && type !== 'reset')) {
const form = this.closest ? this.closest('form') : null;
if (form && typeof form.submit === 'function') {
form.submit(this);
}
}
}
}
focus() { globalThis.__obscura_focused = this; globalThis.__obscura_click_target = this; }
blur() { if (globalThis.__obscura_focused === this) globalThis.__obscura_focused = null; }
get value() {
const tag = this.localName;
if (tag === 'select') {
// Selected option wins; otherwise first option (HTML default).
const opts = this.querySelectorAll('option');
for (let i = 0; i < opts.length; i++) {
if (opts[i].selected) {
return opts[i].getAttribute('value') !== null ? opts[i].getAttribute('value') : opts[i].textContent;
}
}
if (opts.length) return opts[0].getAttribute('value') !== null ? opts[0].getAttribute('value') : opts[0].textContent;
return '';
}
if (_formValues[this._nid] !== undefined) return _formValues[this._nid];
if (tag === 'textarea') return this.textContent;
if (tag === 'option') {
const attr = this.getAttribute('value');
return attr !== null ? attr : this.textContent;
}
return this.getAttribute("value") || "";
}
set value(v) {
const tag = this.localName;
if (tag === 'select') {
// Set selected on matching option, clear on others. Puppeteer's
// page.select(selector, value) round-trips through this setter.
const wanted = String(v);
const opts = this.querySelectorAll('option');
let matched = false;
for (let i = 0; i < opts.length; i++) {
const attrV = opts[i].getAttribute('value');
const optVal = attrV !== null ? attrV : opts[i].textContent;
if (optVal === wanted) { opts[i].selected = true; matched = true; }
else { opts[i].selected = false; }
}
if (matched) try { this.dispatchEvent(new Event('change', { bubbles: true })); } catch (e) {}
return;
}
_formValues[this._nid] = String(v);
if (tag === 'textarea') {
this.textContent = String(v);
}
}
get checked() {
if (_formChecked[this._nid] !== undefined) return _formChecked[this._nid];
return this.hasAttribute("checked");
}
set checked(v) { _formChecked[this._nid] = !!v; }
get selected() {
if (this._selected !== undefined) return this._selected;
return this.hasAttribute("selected");
}
set selected(v) { this._selected = !!v; }
get disabled() { return this.hasAttribute("disabled"); }
set disabled(v) { if (v) this.setAttribute("disabled", ""); else this.removeAttribute("disabled"); }
get type() { return this.getAttribute("type") || (this.localName === "input" ? "text" : ""); }
set type(v) { this.setAttribute("type", v); }
get name() { return this.getAttribute("name") || ""; }
set name(v) { this.setAttribute("name", v); }
get placeholder() { return this.getAttribute("placeholder") || ""; }
set placeholder(v) { this.setAttribute("placeholder", v); }
get href() { return this.getAttribute("href") || ""; }
set href(v) { this.setAttribute("href", v); }
get src() { return this.getAttribute("src") || ""; }
set src(v) {
this.setAttribute("src", v);
if (this.localName === 'iframe' && v && v !== 'about:blank') {
this._loadIframeSrc(v);
}
}
_loadIframeSrc(url) {
let fullUrl = url;
if (!url.includes('://')) {
try { fullUrl = new URL(url, _domParse("document_url") || "about:blank").href; } catch(e) {}
}
const el = this;
fetch(fullUrl, {mode: 'no-cors'}).then(async resp => {
if (resp.ok || resp.type === 'opaque') {
const html = await resp.text();
el._iframeDoc = new _IframeDocument(html, fullUrl, el);
el._iframeWin = new _IframeWindow(el._iframeDoc, fullUrl);
} else {
el._iframeDoc = new _IframeDocument('<!DOCTYPE html><html><head></head><body></body></html>', fullUrl, el);
el._iframeWin = new _IframeWindow(el._iframeDoc, fullUrl);
}
_registerIframe(el);
if (typeof el.onload === 'function') {
try { el.onload(); } catch(e) {}
} else {
var onloadAttr = el.getAttribute('onload');
if (onloadAttr) try { (0, eval)(onloadAttr); } catch(e) {}
}
}).catch(() => {
el._iframeDoc = new _IframeDocument('<!DOCTYPE html><html><head></head><body></body></html>', fullUrl, el);
el._iframeWin = new _IframeWindow(el._iframeDoc, fullUrl);
_registerIframe(el);
if (typeof el.onload === 'function') try { el.onload(); } catch(e) {}
});
}
get contentDocument() {
if (this.localName !== 'iframe') return undefined;
if (this._iframeDoc) {
const pageOrigin = (function(){ try { return new URL(_domParse("document_url")).origin; } catch(e) { return ''; } })();
const iframeOrigin = (function(url){ try { return new URL(url).origin; } catch(e) { return ''; } })(this.src);
if (pageOrigin === iframeOrigin || this.src === '' || this.src === 'about:blank' || !this.src.includes('://')) {
return this._iframeDoc;
}
return null; // Cross-origin: blocked
}
if (!this._iframeDoc) {
this._iframeDoc = new _IframeDocument('<!DOCTYPE html><html><head></head><body></body></html>', 'about:blank', this);
this._iframeWin = new _IframeWindow(this._iframeDoc, 'about:blank');
}
return this._iframeDoc;
}
get contentWindow() {
if (this.localName !== 'iframe') return undefined;
if (!this._iframeWin) {
this.contentDocument; // side effect: creates _iframeDoc + _iframeWin
}
return this._iframeWin;
}
get action() {
const action = this.getAttribute("action") || _domParse("document_url") || "";
try { return new URL(action, _domParse("document_url") || "about:blank").href; } catch(e) { return action; }
}
set action(v) { this.setAttribute("action", v); }
get method() { return this.getAttribute("method") || "get"; }
set method(v) { this.setAttribute("method", v); }
get form() {
let p = this.parentNode;
while (p && p.localName !== 'form') p = p.parentNode;
return p;
}
get options() {
if (this.localName !== 'select') return [];
return this.querySelectorAll('option');
}
get selectedIndex() {
const opts = this.options;
for (let i = 0; i < opts.length; i++) {
if (opts[i].selected || opts[i].hasAttribute('selected')) return i;
}
return -1;
}
set selectedIndex(v) {
const opts = this.options;
for (let i = 0; i < opts.length; i++) {
opts[i]._selected = (i === v);
}
}
submit(submitter) {
const cancelled = !this.dispatchEvent(new Event('submit', { bubbles: true, cancelable: true }));
if (cancelled) return;
const pairs = [];
const fields = this.querySelectorAll('input, select, textarea');
for (let i = 0; i < fields.length; i++) {
const f = fields[i];
const name = f.getAttribute('name');
if (!name) continue;
if (f.getAttribute('disabled') !== null) continue;
const tag = f.localName;
const type = (f.getAttribute('type') || '').toLowerCase();
if ((type === 'checkbox' || type === 'radio') && !f.checked) continue;
if (type === 'file' || type === 'reset') continue;
if (type === 'button') continue;
if (type === 'submit' || tag === 'button') {
if (submitter && f !== submitter) continue;
if (!submitter) continue; // default submit: don't include submit button value
}
let val;
if (tag === 'select') {
const opt = f.querySelector('option[selected]') || f.querySelector('option');
val = opt ? (opt.getAttribute('value') !== null ? opt.getAttribute('value') : opt.textContent) : '';
} else if (tag === 'textarea') {
val = f.value || f.textContent || '';
} else {
val = f.value !== undefined ? f.value : (f.getAttribute('value') || '');
}
const enc = (s) => encodeURIComponent(s).replace(/%20/g, '+').replace(/!/g, '%21');
pairs.push(enc(name) + '=' + enc(val));
}
const action = this.getAttribute('action') || '';
const method = (this.getAttribute('method') || 'GET').toUpperCase();
const baseUrl = globalThis.location?.href || 'about:blank';
let targetUrl;
try { targetUrl = new URL(action, baseUrl).href; } catch(e) { targetUrl = action; }
const encoded = pairs.join('&');
if (method === 'POST') {
Deno.core.ops.op_navigate(targetUrl, 'POST', encoded);
} else {
const sep = targetUrl.includes('?') ? '&' : '?';
Deno.core.ops.op_navigate(targetUrl + (encoded ? sep + encoded : ''), 'GET', '');
}
}
reset() {
this.dispatchEvent(new Event('reset', { bubbles: true }));
}
get dataset() {
const el = this;
return new Proxy({}, {
get(_, k) { if(typeof k!=="string")return undefined; return el.getAttribute("data-"+k.replace(/([A-Z])/g,"-$1").toLowerCase()); },
set(_, k, v) { el.setAttribute("data-"+k.replace(/([A-Z])/g,"-$1").toLowerCase(), v); return true; },
});
}
get offsetWidth() { return this._isViewportRoot() ? (globalThis.innerWidth || 1280) : 100; }
get offsetHeight() { return this._isViewportRoot() ? (globalThis.innerHeight || 720) : 20; }
get offsetTop() { return 0; } get offsetLeft() { return 0; }
// documentElement / body / window expose VIEWPORT geometry, not their own content box.
// Puppeteer's #clickableBox clips boxes to document.documentElement.clientWidth/Height;
// returning 100x20 there made every element appear off-screen and broke .click().
get clientWidth() { return this._isViewportRoot() ? (globalThis.innerWidth || 1280) : 100; }
get clientHeight() { return this._isViewportRoot() ? (globalThis.innerHeight || 720) : 20; }
get scrollWidth() { return this._isViewportRoot() ? (globalThis.innerWidth || 1280) : 100; }
get scrollHeight() { return this._isViewportRoot() ? (globalThis.innerHeight || 720) : 20; }
_isViewportRoot() {
const t = this.tagName;
return t === 'HTML' || t === 'BODY';
}
get scrollTop() { return 0; } set scrollTop(v) {}
get scrollLeft() { return 0; } set scrollLeft(v) {}
getBoundingClientRect() {
globalThis.__obscura_click_target = this;
// documentElement and body span the full viewport. Without this every
// hit test against them clips down to a 100x20 synthetic cell and
// Document.elementFromPoint can never recurse into their children.
if (this._isViewportRoot()) {
const vw = globalThis.innerWidth || 1280;
const vh = globalThis.innerHeight || 720;
return {
x: 0, y: 0, width: vw, height: vh,
top: 0, right: vw, bottom: vh, left: 0,
toJSON() { return this; },
};
}
// No layout engine, but Playwright's actionability polling needs each
// element to occupy a stable, distinct rect so hit-testing can pick the
// right one (issue #45). Synthesize a deterministic position from the
// node id: every nid maps to a unique cell in a 12-column grid, sized
// to fit a 1280x720 viewport. Stable across reads, different per node.
const VW = 1280, VH = 720, COLS = 12, CW = 100, CH = 20, GX = 110, GY = 30;
const rowsPerScreen = Math.max(1, Math.floor((VH - 10) / GY));
const cell = this._nid | 0;
const col = ((cell * 7) | 0) % COLS;
const row = (((cell * 13) | 0) >> 0) % rowsPerScreen;
const x = 10 + col * GX;
const y = 10 + row * GY;
return {
x, y, width: CW, height: CH,
top: y, right: x + CW, bottom: y + CH, left: x,
toJSON() { return this; },
};
}
getClientRects() { return [this.getBoundingClientRect()]; }
// No layout engine: a stub that always returns true unblocks Playwright's
// actionability polling. With a real layout we'd check display, visibility,
// opacity and rect dimensions per spec.
checkVisibility(opts) { return true; }
// ARIA reflection properties. Without an accessibility tree we expose the
// raw aria-* attributes so Playwright's getByRole / getByLabel locators can
// at least find elements that author them explicitly.
get role() { return this.getAttribute('role'); }
set role(v) { if (v == null) this.removeAttribute('role'); else this.setAttribute('role', String(v)); }
get ariaLabel() { return this.getAttribute('aria-label'); }
set ariaLabel(v) { if (v == null) this.removeAttribute('aria-label'); else this.setAttribute('aria-label', String(v)); }
get ariaRoleDescription() { return this.getAttribute('aria-roledescription'); }
set ariaRoleDescription(v) { if (v == null) this.removeAttribute('aria-roledescription'); else this.setAttribute('aria-roledescription', String(v)); }
get ariaChecked() { return this.getAttribute('aria-checked'); }
set ariaChecked(v) { if (v == null) this.removeAttribute('aria-checked'); else this.setAttribute('aria-checked', String(v)); }
get ariaDisabled() { return this.getAttribute('aria-disabled'); }
set ariaDisabled(v) { if (v == null) this.removeAttribute('aria-disabled'); else this.setAttribute('aria-disabled', String(v)); }
get ariaExpanded() { return this.getAttribute('aria-expanded'); }
set ariaExpanded(v) { if (v == null) this.removeAttribute('aria-expanded'); else this.setAttribute('aria-expanded', String(v)); }
get ariaHidden() { return this.getAttribute('aria-hidden'); }
set ariaHidden(v) { if (v == null) this.removeAttribute('aria-hidden'); else this.setAttribute('aria-hidden', String(v)); }
get ariaSelected() { return this.getAttribute('aria-selected'); }
set ariaSelected(v) { if (v == null) this.removeAttribute('aria-selected'); else this.setAttribute('aria-selected', String(v)); }
scrollIntoView() { globalThis.__obscura_click_target = this; }
animate(keyframes, options) {
const duration = typeof options === 'number' ? options : (options?.duration || 0);
return {
finished: Promise.resolve(), currentTime: 0, playState: 'finished',
effect: { getComputedTiming() { return { duration }; } },
cancel(){}, finish(){}, play(){}, pause(){}, reverse(){},
addEventListener(){}, removeEventListener(){},
onfinish: null, oncancel: null,
};
}
getAnimations() { return []; }
get isConnected() {
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); } }
prepend(...nodes) {
const ref = this.firstChild;
for (const n of nodes) {
const node = (typeof n === "string") ? document.createTextNode(n) : n;
if (ref) this.insertBefore(node, ref);
else this.appendChild(node);
}
}
}
class Document extends Node {
get documentElement() { return _wrapEl(+_dom("document_element")); }
get head() { return this.querySelector("head"); }
get body() { return this.querySelector("body"); }
get doctype() {
if (this._doctype !== undefined) return this._doctype;
const info = _domParse("document_doctype");
if (info && info.name) {
this._doctype = new DocumentType(info.nodeId, info.name, info.publicId || "", info.systemId || "");
} else {
this._doctype = null;
}
return this._doctype;
}
get title() { return _domParse("document_title") ?? ""; }
set title(v) {}
get URL() { return _domParse("document_url") ?? ""; }
get documentURI() { return this.URL; }
get location() { return globalThis.location; }
set location(url) { Deno.core.ops.op_navigate(_resolveUrl(String(url)), 'GET', ''); }
get defaultView() { return globalThis; }
get nodeType() { return 9; }
get nodeName() { return "#document"; }
get ownerDocument() { return null; } // Document has no ownerDocument
get compatMode() { return "CSS1Compat"; }
get characterSet() { return "UTF-8"; }
get contentType() { return "text/html"; }
get readyState() { return globalThis.__documentReadyState__ || 'complete'; }
get currentScript() {
// Next.js / Turbopack chunk loader reads document.currentScript.src to
// derive its base path. page.rs sets __currentScriptNid before each
// <script> body runs and clears it after, mirroring real Chrome.
const nid = globalThis.__currentScriptNid;
return nid ? _wrapEl(+nid) : null;
}
get hidden() { return false; }
get visibilityState() { return "visible"; }
getElementById(id) { return _wrapEl(+_dom("get_element_by_id", id)); }
querySelector(s) { return _wrapEl(+_dom("query_selector", s)); }
querySelectorAll(s) {
const ids = _domParse("query_selector_all", s) || [];
const list = ids.map(_wrapEl).filter(Boolean);
list.item = (i) => list[i] || null;
list.forEach = Array.prototype.forEach.bind(list);
return list;
}
getElementsByTagName(t) { return this.querySelectorAll(t); }
getElementsByClassName(c) { return this.querySelectorAll("." + c); }
getElementsByName(name) { return this.querySelectorAll('[name="' + String(name).replace(/\\/g, '\\\\').replace(/"/g, '\\"') + '"]'); }
createElement(t) {
const el = _wrapEl(+_dom("create_element", t.toLowerCase()));
if (el && t.toLowerCase() === 'template') {
el._templateContent = this.createDocumentFragment();
}
return el;
}
createElementNS(ns, t) {
const el = this.createElement(t);
if (el) el._ns = ns;
return el;
}
createTextNode(t) { return _wrap(+_dom("create_text_node", String(t))); }
createComment(t) {
const nid = +_dom("create_comment_node", String(t ?? ""));
const n = new Comment(nid);
_cache.set(nid, n);
return n;
}
createDocumentFragment() {
const nid = +_dom("create_document_fragment");
const frag = new DocumentFragment(nid);
_cache.set(nid, frag);
return frag;
}
// Legacy DOM Level 2 event factory. Spec returns an event of the requested
// class with an empty type until init*Event() is called. We previously
// returned a generic Event for every type, which broke libraries that call
// createEvent('CustomEvent').initCustomEvent(...) — see issue #41.
createEvent(type) {
const map = {
'customevent': CustomEvent, 'customevents': CustomEvent,
'mouseevent': MouseEvent, 'mouseevents': MouseEvent,
'keyboardevent': KeyboardEvent, 'keyboardevents': KeyboardEvent,
'focusevent': FocusEvent,
'inputevent': InputEvent,
'uievent': UIEvent, 'uievents': UIEvent,
'wheelevent': WheelEvent,
'pointerevent': PointerEvent,
'errorevent': ErrorEvent,
'popstateevent': PopStateEvent,
'animationevent': AnimationEvent,
'transitionevent': TransitionEvent,
};
const Cls = map[String(type || '').toLowerCase()] || Event;
return new Cls('');
}
createRange() { return { setStart(){}, setEnd(){}, collapse(){}, selectNodeContents(){}, cloneContents(){ return document.createDocumentFragment(); } }; }
addEventListener(type, fn, opts) {
if (typeof fn !== 'function') return;
if (!this._listeners) this._listeners = {};
if (!this._listeners[type]) this._listeners[type] = [];
if (!this._listeners[type].includes(fn)) this._listeners[type].push(fn);
}
removeEventListener(type, fn) {
if (this._listeners?.[type]) {
this._listeners[type] = this._listeners[type].filter(h => h !== fn);
}
}
dispatchEvent(event) {
if (!event) return true;
const handlers = (this._listeners?.[event.type] || []).slice();
for (const h of handlers) { try { h.call(this, event); } catch(e) { console.error('document event error:', e); } }
return !event.defaultPrevented;
}
createTreeWalker(root, whatToShow, filter) {
whatToShow = whatToShow || 0xFFFFFFFF; // NodeFilter.SHOW_ALL
const walker = {
root: root,
currentNode: root,
whatToShow: whatToShow,
filter: filter || null,
_accept(node) {
const nodeType = node.nodeType;
const show = (whatToShow >> (nodeType - 1)) & 1;
if (!show) return false;
if (this.filter) {
if (typeof this.filter === 'function') return this.filter(node) === 1;
if (this.filter.acceptNode) return this.filter.acceptNode(node) === 1;
}
return true;
},
nextNode() {
let node = this.currentNode;
let child = node.firstChild;
while (child) {
if (this._accept(child)) { this.currentNode = child; return child; }
if (child.firstChild) { child = child.firstChild; continue; }
if (child.nextSibling) { child = child.nextSibling; continue; }
let parent = child.parentNode;
while (parent && parent !== this.root) {
if (parent.nextSibling) { child = parent.nextSibling; break; }
parent = parent.parentNode;
}
if (!parent || parent === this.root) return null;
}
return null;
},
previousNode() {
let node = this.currentNode;
if (node === this.root) return null;
let sibling = node.previousSibling;
if (sibling) {
while (sibling.lastChild) sibling = sibling.lastChild;
if (this._accept(sibling)) { this.currentNode = sibling; return sibling; }
}
let parent = node.parentNode;
if (parent && parent !== this.root && this._accept(parent)) {
this.currentNode = parent;
return parent;
}
return null;
},
firstChild() {
let child = this.currentNode.firstChild;
while (child) {
if (this._accept(child)) { this.currentNode = child; return child; }
child = child.nextSibling;
}
return null;
},
lastChild() {
let child = this.currentNode.lastChild;
while (child) {
if (this._accept(child)) { this.currentNode = child; return child; }
child = child.previousSibling;
}
return null;
},
nextSibling() {
let sibling = this.currentNode.nextSibling;
while (sibling) {
if (this._accept(sibling)) { this.currentNode = sibling; return sibling; }
sibling = sibling.nextSibling;
}
return null;
},
previousSibling() {
let sibling = this.currentNode.previousSibling;
while (sibling) {
if (this._accept(sibling)) { this.currentNode = sibling; return sibling; }
sibling = sibling.previousSibling;
}
return null;
},
parentNode() {
let parent = this.currentNode.parentNode;
if (parent && parent !== this.root && this._accept(parent)) {
this.currentNode = parent;
return parent;
}
return null;
},
};
return walker;
}
createNodeIterator(root, whatToShow, filter) {
return this.createTreeWalker(root, whatToShow, filter);
}
getSelection() { return globalThis.getSelection(); }
get activeElement() { return globalThis.__obscura_focused || this.body; }
get implementation() {
return {
// Spec: createHTMLDocument returns a NEW detached Document. jQuery
// 3.x's selector feature-detect calls `body.innerHTML = '<form>'` on
// the result — when we returned `globalThis.document`, the real
// `<body>` was wiped, taking every page on the open web that ships
// jQuery 3.x with it. Reuse the DOMParser path to build a detached
// document, then optionally set the title.
createHTMLDocument(title) {
const skeleton = `<html><head><title></title></head><body></body></html>`;
const doc = new DOMParser().parseFromString(skeleton, "text/html");
if (title != null) {
const t = doc.querySelector("title");
if (t) t.textContent = String(title);
}
return doc;
},
// Real spec: createDocument(namespaceURI, qualifiedName, doctype) →
// an XML document with a root element of the given name. We don't
// have a separate XML stack, so return a minimal detached document
// with an element of the requested local name as documentElement.
createDocument(_ns, qualifiedName, _doctype) {
const name = (qualifiedName && String(qualifiedName)) || "root";
const safe = name.replace(/[^a-zA-Z0-9-]/g, "");
const html = `<${safe}></${safe}>`;
return new DOMParser().parseFromString(html, "application/xml");
},
hasFeature() { return true; },
};
}
get styleSheets() { return []; }
get forms() { return this.querySelectorAll("form"); }
get images() { return this.querySelectorAll("img"); }
get links() { return this.querySelectorAll("a[href], area[href]"); }
get scripts() { return this.querySelectorAll("script"); }
get cookie() {
return Deno.core.ops.op_get_cookies();
}
set cookie(v) {
if (!v) return;
Deno.core.ops.op_set_cookie(v);
}
write(...args) {
var html = args.join('');
if (!html) return;
var body = this.body;
if (!body) return;
var temp = this.createElement('div');
temp.innerHTML = html;
var children = temp.childNodes;
for (var i = 0; i < children.length; i++) {
body.appendChild(children[i]);
}
}
writeln(...args) {
this.write(args.join('') + '\n');
}
open() {
var body = this.body;
if (body) body.innerHTML = '';
return this;
}
close() {
return;
}
hasFocus() { return true; }
execCommand() { return false; }
}
class DocumentFragment extends Node {
get nodeType() { return 11; }
get nodeName() { return "#document-fragment"; }
get innerHTML() { return _domParse("inner_html", this._nid) ?? ""; }
set innerHTML(v) { _dom("set_inner_html", this._nid, String(v ?? "")); }
querySelector(s) { return _wrapEl(+_dom("query_selector_scoped", this._nid, s)); }
querySelectorAll(s) {
const ids = _domParse("query_selector_all_scoped", this._nid, s) || [];
const list = ids.map(_wrapEl).filter(Boolean);
list.item = (i) => list[i] || null;
return list;
}
get children() {
const ids = _domParse("element_children", this._nid) || [];
return ids.map(_wrapEl).filter(Boolean);
}
get firstElementChild() { return this.children[0] || null; }
get lastElementChild() { const ch = this.children; return ch[ch.length - 1] || null; }
getElementById(id) { return null; }
cloneNode(deep) {
const frag = document.createDocumentFragment();
if (deep) frag.innerHTML = this.innerHTML;
return frag;
}
}
class DocumentType extends Node {
constructor(nid, name, publicId, systemId) {
super(nid);
this._name = name;
this._publicId = publicId;
this._systemId = systemId;
}
get nodeType() { return 10; }
get nodeName() { return this._name; }
get name() { return this._name; }
get publicId() { return this._publicId; }
get systemId() { return this._systemId; }
get ownerDocument() { return globalThis.document; }
}
const _cache = new Map();
function _elementClassFor(nid) {
const tag = _domParse("tag_name", nid);
if (tag === "FORM" && globalThis.HTMLFormElement) return globalThis.HTMLFormElement;
return Element;
}
function _wrap(nid) {
if (nid < 0 || nid === null || nid === undefined || isNaN(nid)) return null;
if (_cache.has(nid)) return _cache.get(nid);
const t = +_dom("node_type", nid);
let n;
if (t === 1) { const C = _elementClassFor(nid); n = new C(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);
return n;
}
function _wrapEl(nid) {
if (nid < 0 || nid === null || nid === undefined || isNaN(nid)) return null;
if (_cache.has(nid)) return _cache.get(nid);
const C = _elementClassFor(nid);
const n = new C(nid);
_cache.set(nid, n);
return n;
}
globalThis._wrap = _wrap;
globalThis.self = globalThis;
globalThis.document = null;
function _resolveUrl(url) {
if (!url) return url;
if (url.startsWith('http://') || url.startsWith('https://') || url.startsWith('about:')) return url;
try { return new URL(url, _domParse("document_url") || "about:blank").href; } catch(e) { return url; }
}
// `__virtualUrl` is set by `history.pushState`/`replaceState` (and cleared by
// any real navigation). When set, `location.href` and friends read it instead
// of the underlying `document_url`. Without this, client-side routers
// (Next.js, React Router, vue-router) call `pushState` but the URL never
// changes, so their `useLocation` hooks return the wrong path and the UI
// freezes on the original route.
globalThis.__virtualUrl = null;
function __currentUrl() {
return globalThis.__virtualUrl || _domParse("document_url") || "about:blank";
}
globalThis.location = {
get href() { return __currentUrl(); },
set href(url) { globalThis.__virtualUrl = null; Deno.core.ops.op_navigate(_resolveUrl(url), 'GET', ''); },
get origin() { try { return new URL(this.href).origin; } catch { return ""; } },
get protocol() { try { return new URL(this.href).protocol; } catch { return ""; } },
get host() { try { return new URL(this.href).host; } catch { return ""; } },
get hostname() { try { return new URL(this.href).hostname; } catch { return ""; } },
get pathname() { try { return new URL(this.href).pathname; } catch { return "/"; } },
get search() { try { return new URL(this.href).search; } catch { return ""; } },
get hash() { try { return new URL(this.href).hash; } catch { return ""; } },
get port() { try { return new URL(this.href).port; } catch { return ""; } },
toString() { return this.href; },
assign(url) { globalThis.__virtualUrl = null; Deno.core.ops.op_navigate(_resolveUrl(url), 'GET', ''); },
reload() {},
replace(url) { globalThis.__virtualUrl = null; Deno.core.ops.op_navigate(_resolveUrl(url), 'GET', ''); },
};
const _locationObj = globalThis.location;
Object.defineProperty(globalThis, 'location', {
get() { return _locationObj; },
set(url) { Deno.core.ops.op_navigate(_resolveUrl(String(url)), 'GET', ''); },
configurable: false,
enumerable: true,
});
globalThis.window = globalThis;
globalThis.self = globalThis;
globalThis.top = globalThis;
globalThis.parent = globalThis;
globalThis.frames = globalThis;
globalThis.frameElement = null;
globalThis.length = 0;
// HTML spec exposes on* event handler IDL attributes on Window. Libraries like
// jQuery feature-detect bubbling via `("on" + ev) in window` and fall back to
// a legacy IE path that crashes on missing DOM APIs when the check returns
// false. Initialising them to null makes the check match real browsers.
for (const _ev of [
"abort","beforeprint","beforeunload","blur","cancel","canplay","canplaythrough",
"change","click","close","contextmenu","cuechange","dblclick","drag","dragend",
"dragenter","dragleave","dragover","dragstart","drop","durationchange","emptied",
"ended","error","focus","focusin","focusout","formdata","gotpointercapture",
"hashchange","input","invalid","keydown","keypress","keyup","languagechange",
"load","loadeddata","loadedmetadata","loadstart","lostpointercapture","message",
"mousedown","mouseenter","mouseleave","mousemove","mouseout","mouseover","mouseup",
"offline","online","pagehide","pageshow","paste","pause","play","playing",
"pointercancel","pointerdown","pointerenter","pointerleave","pointermove",
"pointerout","pointerover","pointerup","popstate","progress","ratechange",
"rejectionhandled","reset","resize","scroll","seeked","seeking","select",
"stalled","storage","submit","suspend","timeupdate","toggle","unhandledrejection",
"unload","volumechange","waiting","wheel",
]) {
if (!(("on" + _ev) in globalThis)) globalThis["on" + _ev] = null;
}
globalThis.Window = globalThis.Window || function Window() {};
Object.defineProperty(globalThis.Window, Symbol.hasInstance, {
value(obj) { return obj === globalThis || (obj && obj.window === obj); },
configurable: true,
});
const _iframeRegistry = [];
function _registerIframe(iframeEl) {
const idx = _iframeRegistry.length;
_iframeRegistry.push(iframeEl);
globalThis.length = _iframeRegistry.length;
Object.defineProperty(globalThis, idx, {
get() { return iframeEl._iframeWin || null; },
configurable: true,
enumerable: false,
});
}
globalThis.navigator = {
get userAgent() { return globalThis.__obscura_ua || "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36"; },
get appVersion() { return this.userAgent.replace('Mozilla/', ''); },
language: "en-US", languages: ["en-US","en"], platform: "Linux x86_64",
onLine: true, cookieEnabled: true, hardwareConcurrency: 8,
maxTouchPoints: 0,
vendor: "Google Inc.", product: "Gecko", productSub: "20030107",
doNotTrack: null,
deviceMemory: 8,
connection: { effectiveType: "4g", rtt: 50, downlink: 10, saveData: false },
get webdriver() { return undefined; },
pdfViewerEnabled: true,
get plugins() {
const p = [
{ name: "PDF Viewer", filename: "internal-pdf-viewer", description: "Portable Document Format", length: 1 },
{ name: "Chrome PDF Viewer", filename: "internal-pdf-viewer", description: "Portable Document Format", length: 1 },
{ name: "Chromium PDF Viewer", filename: "internal-pdf-viewer", description: "Portable Document Format", length: 1 },
{ name: "Microsoft Edge PDF Viewer", filename: "internal-pdf-viewer", description: "Portable Document Format", length: 1 },
{ name: "WebKit built-in PDF", filename: "internal-pdf-viewer", description: "Portable Document Format", length: 1 },
];
p.item = (i) => p[i] || null;
p.namedItem = (name) => p.find(x => x.name === name) || null;
p.refresh = () => {};
p[Symbol.iterator] = function() { return this[Symbol.iterator](); };
return p;
},
get mimeTypes() {
const m = [
{ type: "application/pdf", description: "Portable Document Format", suffixes: "pdf", enabledPlugin: null },
{ type: "text/pdf", description: "Portable Document Format", suffixes: "pdf", enabledPlugin: null },
];
m.item = (i) => m[i] || null;
m.namedItem = (name) => m.find(x => x.type === name) || null;
return m;
},
userAgentData: {
brands: [
{brand: "Google Chrome", version: "145"},
{brand: "Chromium", version: "145"},
{brand: "Not=A?Brand", version: "24"},
],
mobile: false,
platform: "Linux",
getHighEntropyValues(hints) {
return Promise.resolve({
architecture: "x86",
bitness: "64",
brands: [{brand:"Google Chrome",version:"145"},{brand:"Chromium",version:"145"},{brand:"Not=A?Brand",version:"24"}],
fullVersionList: [{brand:"Google Chrome",version:"145.0.0.0"},{brand:"Chromium",version:"145.0.0.0"},{brand:"Not=A?Brand",version:"24.0.0.0"}],
mobile: false,
model: "",
platform: "Linux",
platformVersion: "6.8.0",
uaFullVersion: "145.0.0.0",
});
},
toJSON() { return {brands:this.brands,mobile:this.mobile,platform:this.platform}; },
},
serviceWorker: { ready: Promise.resolve(), register(){return Promise.resolve();}, getRegistrations(){return Promise.resolve([]);}, controller: null },
mediaDevices: {
enumerateDevices() {
return Promise.resolve([
{deviceId:"default",kind:"audioinput",label:"",groupId:"default"},
{deviceId:"comms",kind:"audioinput",label:"",groupId:"comms"},
{deviceId:"default",kind:"audiooutput",label:"",groupId:"default"},
{deviceId:"",kind:"videoinput",label:"",groupId:""},
]);
},
getUserMedia() { return Promise.reject(new DOMException("NotAllowedError")); },
getDisplayMedia() { return Promise.reject(new DOMException("NotAllowedError")); },
addEventListener(){}, removeEventListener(){},
},
clipboard: { writeText(){return Promise.resolve();}, readText(){return Promise.resolve("");} },
permissions: { query(params){
if (params?.name === 'notifications') return Promise.resolve({state:"prompt",onchange:null});
return Promise.resolve({state:"granted"});
} },
getBattery() { return Promise.resolve({ charging: _fp('batteryCharging'), chargingTime: _fp('batteryCharging') ? 0 : Infinity, dischargingTime: _fp('batteryCharging') ? Infinity : Math.floor(3600 + _fpRand(250) * 7200), level: _fp('batteryLevel'), addEventListener(){} }); },
getGamepads() { return []; },
sendBeacon() { return true; },
javaEnabled() { return false; },
geolocation: {
getCurrentPosition(success, error) {
const coords = {
latitude: 50.1109 + (_fpRand(500) - 0.5) * 0.1,
longitude: 8.6821 + (_fpRand(501) - 0.5) * 0.1,
accuracy: 10 + _fpRand(502) * 40,
altitude: null,
altitudeAccuracy: null,
heading: null,
speed: null,
};
const pos = { coords, timestamp: Date.now() };
if (typeof success === 'function') success(pos);
},
watchPosition(success, error) {
if (typeof success === 'function') {
const coords = {
latitude: 50.1109 + (_fpRand(503) - 0.5) * 0.1,
longitude: 8.6821 + (_fpRand(504) - 0.5) * 0.1,
accuracy: 10 + _fpRand(505) * 40,
altitude: null,
altitudeAccuracy: null,
heading: null,
speed: null,
};
success({ coords, timestamp: Date.now() });
}
return 0;
},
clearWatch() {},
},
storage: {
estimate() { return Promise.resolve({ quota: 5000000000, usage: Math.floor(_fpRand(640) * 100000000) }); },
persist() { return Promise.resolve(false); },
persisted() { return Promise.resolve(false); },
},
};
globalThis.chrome = {
app: { isInstalled: false, InstallState: { DISABLED: "disabled", INSTALLED: "installed", NOT_INSTALLED: "not_installed" }, RunningState: { CANNOT_RUN: "cannot_run", READY_TO_RUN: "ready_to_run", RUNNING: "running" } },
runtime: { OnInstalledReason: {}, OnRestartRequiredReason: {}, PlatformArch: {}, PlatformNaclArch: {}, PlatformOs: {}, RequestUpdateCheckStatus: {}, connect() { return {}; }, sendMessage() {} },
csi() {
const t = Date.now();
return { onloadT: t, startE: t - Math.floor(100 + _fpRand(610) * 200), pageT: 0, tran: 5, flashVersion: "" };
},
loadTimes() {
const t = Date.now() / 1000;
const request = t - 0.5 - _fpRand(611) * 0.5;
const startLoad = request + 0.05 + _fpRand(612) * 0.02;
const commit = request + 0.3 + _fpRand(613) * 0.4;
const finishDoc = commit + 0.1 + _fpRand(614) * 0.2;
const finish = finishDoc + 0.05 + _fpRand(615) * 0.1;
const firstPaint = commit + 0.03 + _fpRand(616) * 0.1;
const navTypes = ["BackForward","Reload","Link","Other"];
return {
requestTime: request, startLoadTime: startLoad * 1000, commitLoadTime: commit * 1000,
finishDocumentLoadTime: finishDoc * 1000, finishLoadTime: finish * 1000,
firstPaintTime: firstPaint * 1000, firstPaintAfterLoadTime: 0,
navigationType: navTypes[Math.floor(_fpRand(617) * 4)],
wasFetchedViaSpdy: false, wasNpnNegotiated: false,
npnNegotiatedProtocol: "http/1.1",
wasAlternateProtocolAvailable: false, connectionInfo: "http/1.1",
};
},
};
globalThis.Notification = class Notification {
static permission = "default";
static requestPermission() { return Promise.resolve(Notification.permission); }
constructor() {}
};
globalThis.WebGLRenderingContext = class WebGLRenderingContext {};
globalThis.WebGL2RenderingContext = class WebGL2RenderingContext {};
globalThis.screen = { width:1920, height:1080, availWidth:1920, availHeight:1040, colorDepth:24, pixelDepth:24, availTop:0, availLeft:0, orientation:{type:"landscape-primary",angle:0,addEventListener(){},removeEventListener(){},dispatchEvent(){return true;}} };
globalThis.visualViewport = { width:1920, height:1000, offsetLeft:0, offsetTop:0, scale:1, addEventListener(){}, removeEventListener(){} };
globalThis.devicePixelRatio = 1;
globalThis.innerWidth = 1920; globalThis.innerHeight = 1000;
globalThis.outerWidth = 1920; globalThis.outerHeight = 1080;
globalThis.scrollX = 0; globalThis.scrollY = 0;
globalThis.pageXOffset = 0; globalThis.pageYOffset = 0;
globalThis.__fetchInterceptEnabled = false;
globalThis.__fetchInterceptCallback = null; // Set by CDP to handle paused requests
function _base64ToUint8Array(b64) {
const clean = String(b64 || '').replace(/[\r\n\s]/g, '');
if (!clean) return new Uint8Array();
const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
const padding = clean.endsWith('==') ? 2 : (clean.endsWith('=') ? 1 : 0);
const bytes = new Uint8Array((clean.length * 3 >> 2) - padding);
let out = 0;
for (let i = 0; i < clean.length; i += 4) {
const a = alphabet.indexOf(clean[i]);
const b = alphabet.indexOf(clean[i + 1]);
const c = clean[i + 2] === '=' ? 0 : alphabet.indexOf(clean[i + 2]);
const d = clean[i + 3] === '=' ? 0 : alphabet.indexOf(clean[i + 3]);
const n = (a << 18) | (b << 12) | (c << 6) | d;
if (out < bytes.length) bytes[out++] = (n >> 16) & 0xff;
if (out < bytes.length) bytes[out++] = (n >> 8) & 0xff;
if (out < bytes.length) bytes[out++] = n & 0xff;
}
return bytes;
}
function _bodyToUint8Array(body) {
if (body == null) return new Uint8Array();
if (body instanceof Uint8Array) return body;
if (body instanceof ArrayBuffer) return new Uint8Array(body);
if (ArrayBuffer.isView(body)) return new Uint8Array(body.buffer, body.byteOffset, body.byteLength);
return new TextEncoder().encode(String(body));
}
function _arrayBufferFromBytes(bytes) {
return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
}
function _installWasmStreamingFallback() {
if (typeof WebAssembly === 'undefined') return;
if (WebAssembly.instantiateStreaming && WebAssembly.instantiateStreaming.__obscuraFallback) return;
const nativeInstantiateStreaming = WebAssembly.instantiateStreaming;
const fallback = async function instantiateStreaming(source, imports) {
const response = await source;
if (response && typeof response.arrayBuffer === 'function') {
return WebAssembly.instantiate(await response.arrayBuffer(), imports);
}
if (typeof nativeInstantiateStreaming === 'function') {
return nativeInstantiateStreaming.call(WebAssembly, response, imports);
}
return WebAssembly.instantiate(response, imports);
};
fallback.__obscuraFallback = true;
WebAssembly.instantiateStreaming = fallback;
}
_installWasmStreamingFallback();
globalThis.fetch = async (input, init = {}) => {
let url = typeof input === "string"
? input
: (input instanceof Request
? input.url
: ((typeof URL === 'function' && input instanceof URL) ? input.href : (input?.url || input?.href || String(input || ""))));
if (url && !url.includes('://')) {
try {
const base = _domParse("document_url") || "about:blank";
url = new URL(url, base).href;
} catch(e) { /* keep as-is if URL resolution fails */ }
}
const method = init.method || (input instanceof Request ? input.method : "GET");
const hdrs = JSON.stringify(init.headers instanceof Headers ? Object.fromEntries(init.headers.entries()) : init.headers || {});
const body = init.body ? String(init.body) : "";
const fetchMode = init.mode || (input instanceof Request ? input.mode : "cors");
const pageOrigin = (function() { try { const u = new URL(_domParse("document_url") || "about:blank"); return u.origin; } catch(e) { return ""; } })();
const raw = await Deno.core.ops.op_fetch_url(url, method, hdrs, body, pageOrigin, fetchMode);
const parsed = JSON.parse(raw);
if (parsed.blocked) {
const err = new TypeError('net::ERR_FAILED');
err.name = 'AbortError';
err.__aborted = true;
throw err;
}
if (parsed.corsBlocked) {
throw new TypeError('Failed to fetch: ' + (parsed.corsError || 'CORS error'));
}
const respType = parsed.status === 0 ? "opaque" : (fetchMode === "no-cors" ? "opaque" : "basic");
const responseBody = parsed.bodyBase64 ? _base64ToUint8Array(parsed.bodyBase64) : (parsed.body || "");
return new Response(responseBody, {
status: parsed.status,
statusText: "",
headers: parsed.headers || {},
type: respType,
url: parsed.url || url,
redirected: false,
});
};
if (typeof Headers === "undefined") {
globalThis.Headers = class Headers {
constructor(init={}) { this._h={}; if(init) { if(init instanceof Headers) { init.forEach((v,k)=>{this._h[k]=v;}); } else if(typeof init==="object") { for(const[k,v]of Object.entries(init)) this._h[k.toLowerCase()]=String(v); } } }
get(n) { return this._h[n.toLowerCase()]??null; } set(n,v) { this._h[n.toLowerCase()]=String(v); }
has(n) { return n.toLowerCase() in this._h; } delete(n) { delete this._h[n.toLowerCase()]; }
append(n,v) { this._h[n.toLowerCase()]=String(v); }
forEach(cb) { for(const[k,v] of Object.entries(this._h)) cb(v,k,this); }
entries() { return Object.entries(this._h)[Symbol.iterator](); }
keys() { return Object.keys(this._h)[Symbol.iterator](); }
values() { return Object.values(this._h)[Symbol.iterator](); }
[Symbol.iterator]() { return this.entries(); }
};
}
globalThis.XMLHttpRequest = class XMLHttpRequest {
static UNSENT = 0;
static OPENED = 1;
static HEADERS_RECEIVED = 2;
static LOADING = 3;
static DONE = 4;
UNSENT = 0; OPENED = 1; HEADERS_RECEIVED = 2; LOADING = 3; DONE = 4;
constructor() {
this.readyState = 0;
this.status = 0;
this.statusText = "";
this.responseText = "";
this.responseXML = null;
this.responseURL = "";
this.responseType = "";
this.response = null;
this.timeout = 0;
this.withCredentials = false;
this.upload = { addEventListener(){}, removeEventListener(){} };
this._method = "GET";
this._url = "";
this._headers = {};
this._responseHeaders = {};
this._aborted = false;
this._listeners = {};
this.onreadystatechange = null;
this.onload = null;
this.onerror = null;
this.onabort = null;
this.onprogress = null;
this.ontimeout = null;
this.onloadstart = null;
this.onloadend = null;
}
open(method, url, async_) {
this._method = method;
this._url = url;
this._headers = {};
this._responseHeaders = {};
this._aborted = false;
this.status = 0;
this.statusText = "";
this.responseText = "";
this.response = null;
this._setReadyState(1);
}
setRequestHeader(name, value) {
this._headers[name] = value;
}
getResponseHeader(name) {
const lower = name.toLowerCase();
for (const [k, v] of Object.entries(this._responseHeaders)) {
if (k.toLowerCase() === lower) return v;
}
return null;
}
getAllResponseHeaders() {
return Object.entries(this._responseHeaders)
.map(([k, v]) => k + ': ' + v)
.join('\r\n');
}
overrideMimeType(mime) { this._overrideMime = mime; }
send(body) {
if (this.readyState !== 1) return;
if (this._aborted) return;
const xhr = this;
this._fireEvent('loadstart');
let url = this._url;
if (url && !url.includes('://')) {
try {
const base = _domParse("document_url") || "about:blank";
url = new URL(url, base).href;
} catch(e) {}
}
fetch(url, {
method: this._method,
headers: this._headers,
body: body || undefined,
mode: 'cors',
}).then(async (resp) => {
if (xhr._aborted) return;
xhr.status = resp.status;
xhr.statusText = resp.statusText || '';
xhr.responseURL = resp.url || url;
if (resp.headers) {
resp.headers.forEach((v, k) => { xhr._responseHeaders[k] = v; });
}
xhr._setReadyState(2); // HEADERS_RECEIVED
const text = await resp.text();
if (xhr._aborted) return;
xhr.responseText = text;
xhr._setReadyState(3); // LOADING
switch (xhr.responseType) {
case 'json':
try { xhr.response = JSON.parse(text); } catch(e) { xhr.response = null; }
break;
case 'text':
case '':
xhr.response = text;
break;
case 'arraybuffer':
xhr.response = new TextEncoder().encode(text).buffer;
break;
case 'blob':
xhr.response = new Blob([text]);
break;
case 'document':
xhr.response = text; // simplified
break;
default:
xhr.response = text;
}
xhr._setReadyState(4); // DONE
xhr._fireEvent('load');
xhr._fireEvent('loadend');
}).catch((err) => {
if (xhr._aborted) return;
xhr.status = 0;
xhr.readyState = 4;
xhr._fireEvent('readystatechange');
if (err && err.__aborted) {
xhr._aborted = true;
xhr._fireEvent('abort');
xhr._fireEvent('loadend');
if (xhr.onabort) xhr.onabort(err);
} else {
xhr._fireEvent('error');
xhr._fireEvent('loadend');
if (xhr.onerror) xhr.onerror(err);
}
});
}
abort() {
this._aborted = true;
if (this.readyState > 0 && this.readyState < 4) {
this._setReadyState(4);
this._fireEvent('abort');
this._fireEvent('loadend');
}
this.readyState = 0;
}
addEventListener(type, handler) {
if (!this._listeners[type]) this._listeners[type] = [];
this._listeners[type].push(handler);
}
removeEventListener(type, handler) {
if (this._listeners[type]) {
this._listeners[type] = this._listeners[type].filter(h => h !== handler);
}
}
_setReadyState(state) {
this.readyState = state;
this._fireEvent('readystatechange');
if (this.onreadystatechange) {
try { this.onreadystatechange(); } catch(e) {}
}
}
_fireEvent(type) {
const event = { type, target: this, currentTarget: this, bubbles: false };
const handlers = this._listeners[type] || [];
for (const h of handlers) { try { h.call(this, event); } catch(e) {} }
const prop = 'on' + type;
if (type !== 'readystatechange' && typeof this[prop] === 'function') {
try { this[prop](event); } catch(e) {}
}
}
};
_markNative(XMLHttpRequest);
_markNative(XMLHttpRequest.prototype.open);
_markNative(XMLHttpRequest.prototype.send);
_markNative(XMLHttpRequest.prototype.abort);
_markNative(XMLHttpRequest.prototype.setRequestHeader);
_markNative(XMLHttpRequest.prototype.getResponseHeader);
_markNative(XMLHttpRequest.prototype.getAllResponseHeaders);
if (typeof URL === 'undefined' || !URL.prototype) {
globalThis.URL = class URL {
constructor(url, base) {
// Per WHATWG URL spec, both arguments are stringified — callers
// routinely pass `window.location` (Location object) or a URL
// instance as `base`. Coerce explicitly so the regex .match() calls
// below don't blow up on non-strings.
url = String(url);
if (base !== undefined && base !== null) base = String(base);
let full = url;
if (base && !url.includes('://')) {
var bm = base.match(/^(https?:\/\/[^\/\?#]+)(\/[^?#]*)?/);
if (bm) {
var bOrigin = bm[1];
var bPath = bm[2] || '/';
if (url.startsWith('/')) {
full = bOrigin + url;
} else if (url.startsWith('?') || url.startsWith('#')) {
full = bOrigin + bPath + url;
} else {
var dir = bPath.substring(0, bPath.lastIndexOf('/') + 1);
full = bOrigin + dir + url;
}
}
}
const m = full.match(/^(https?):\/\/([^\/\?#]+)(\/[^?#]*)?(\?[^#]*)?(#.*)?$/);
if (m) {
this.protocol = m[1] + ':';
this.host = m[2]; this.hostname = m[2].split(':')[0];
this.port = m[2].includes(':') ? m[2].split(':')[1] : '';
this.pathname = m[3] || '/';
this.search = m[4] || ''; this.hash = m[5] || '';
} else {
this.protocol = ''; this.host = ''; this.hostname = '';
this.port = ''; this.pathname = full; this.search = ''; this.hash = '';
}
this.href = full; this.origin = this.protocol + '//' + this.host;
this.searchParams = new URLSearchParams(this.search);
}
toString() { return this.href; }
toJSON() { return this.href; }
static createObjectURL() { return 'blob:null/fake-' + Math.random().toString(36).slice(2); }
static revokeObjectURL() {}
};
}
globalThis.requestIdleCallback = globalThis.requestIdleCallback || function requestIdleCallback(cb, opts) {
const start = Date.now();
return setTimeout(() => {
cb({
didTimeout: false,
timeRemaining() { return Math.max(0, 50 - (Date.now() - start)); },
});
}, 1);
};
globalThis.cancelIdleCallback = globalThis.cancelIdleCallback || function cancelIdleCallback(id) { clearTimeout(id); };
_markNative(globalThis.requestIdleCallback);
_markNative(globalThis.cancelIdleCallback);
if (typeof Request === 'undefined') {
globalThis.Request = class Request {
constructor(input, init = {}) {
if (typeof input === 'string') { this.url = input; }
else if (input instanceof Request) { this.url = input.url; init = { ...input, ...init }; }
else if (typeof URL === 'function' && input instanceof URL) { this.url = input.href; }
else { this.url = input?.url || input?.href || String(input); }
this.method = (init.method || 'GET').toUpperCase();
this.headers = new Headers(init.headers);
this.body = init.body || null;
this.mode = init.mode || 'cors';
this.credentials = init.credentials || 'same-origin';
this.redirect = init.redirect || 'follow';
this.referrer = init.referrer || '';
this.signal = init.signal || { aborted: false, addEventListener(){}, removeEventListener(){} };
this.cache = init.cache || 'default';
}
clone() { return new Request(this.url, { method: this.method, headers: this.headers, body: this.body }); }
async text() { return this.body ? String(this.body) : ''; }
async json() { return JSON.parse(await this.text()); }
async arrayBuffer() { return new TextEncoder().encode(await this.text()).buffer; }
};
}
if (typeof Response === 'undefined') {
globalThis.Response = class Response {
constructor(body, init = {}) {
this._bodyBytes = _bodyToUint8Array(body); this.status = init.status || 200; this.statusText = init.statusText || '';
this.ok = this.status >= 200 && this.status < 300;
this.headers = new Headers(init.headers);
this.type = init.type || 'basic'; this.url = init.url || ''; this.redirected = !!init.redirected;
}
async text() { return new TextDecoder().decode(this._bodyBytes); }
async json() { return JSON.parse(await this.text()); }
async arrayBuffer() { return _arrayBufferFromBytes(this._bodyBytes); }
async blob() { return new Blob([this._bodyBytes]); }
clone() { return new Response(this._bodyBytes, { status: this.status, statusText: this.statusText, headers: this.headers, type: this.type, url: this.url, redirected: this.redirected }); }
static error() { return new Response(null, { status: 0 }); }
static redirect(url, status) { return new Response(null, { status: status || 302, headers: { Location: url } }); }
static json(data, init) { return new Response(JSON.stringify(data), { ...init, headers: { 'content-type': 'application/json', ...(init?.headers || {}) } }); }
};
}
if (!Element.prototype.replaceWith) {
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);
}
parent.removeChild(this);
};
_markNative(Element.prototype.replaceWith);
}
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);
}
};
_markNative(Element.prototype.before);
}
if (!Element.prototype.after) {
Element.prototype.after = function(...nodes) {
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);
}
};
_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)) {
Object.defineProperty(Node.prototype, 'isConnected', {
get() {
let node = this;
while (node) {
if (node.nodeType === 9) return true; // Document node
node = node.parentNode;
}
return false;
}
});
}
globalThis.ResizeObserver = class ResizeObserver {
constructor(callback) {
this._callback = callback;
this._targets = new Set();
this._connected = true;
this._fireCount = 0;
}
_fireFor(targets) {
if (!this._connected || !targets.length) return;
const records = targets.map(target => {
const r = target.getBoundingClientRect ? target.getBoundingClientRect() : { x: 0, y: 0, width: 100, height: 20 };
return {
target,
contentRect: { x: r.x || 0, y: r.y || 0, width: r.width || 100, height: r.height || 20, top: r.top || 0, left: r.left || 0, bottom: r.bottom || 20, right: r.right || 100 },
borderBoxSize: [{ blockSize: r.height || 20, inlineSize: r.width || 100 }],
contentBoxSize: [{ blockSize: r.height || 20, inlineSize: r.width || 100 }],
devicePixelContentBoxSize: [{ blockSize: r.height || 20, inlineSize: r.width || 100 }],
};
});
try { this._callback(records, this); } catch (e) { /* RO callbacks must not propagate */ }
}
observe(el) {
if (!el || !this._connected) return;
if (this._targets.has(el)) return;
this._targets.add(el);
Promise.resolve().then(() => this._fireFor([el]));
[200, 800].forEach(delay => {
setTimeout(() => {
if (this._connected && this._targets.has(el) && this._fireCount < 16) {
this._fireCount++;
this._fireFor([el]);
}
}, delay);
});
}
unobserve(el) { this._targets.delete(el); }
disconnect() { this._connected = false; this._targets.clear(); }
};
if (typeof TextEncoder === 'undefined') {
globalThis.TextEncoder = class TextEncoder {
get encoding() { return 'utf-8'; }
encode(str) {
str = String(str);
const buf = [];
for (let i = 0; i < str.length; i++) {
let c = str.charCodeAt(i);
if (c < 0x80) buf.push(c);
else if (c < 0x800) { buf.push(0xC0|(c>>6), 0x80|(c&0x3F)); }
else if (c < 0xD800 || c >= 0xE000) { buf.push(0xE0|(c>>12), 0x80|((c>>6)&0x3F), 0x80|(c&0x3F)); }
else { c = 0x10000 + (((c & 0x3FF) << 10) | (str.charCodeAt(++i) & 0x3FF)); buf.push(0xF0|(c>>18), 0x80|((c>>12)&0x3F), 0x80|((c>>6)&0x3F), 0x80|(c&0x3F)); }
}
return new Uint8Array(buf);
}
encodeInto(str, dest) { const enc = this.encode(str); dest.set(enc.slice(0, dest.length)); return { read: str.length, written: Math.min(enc.length, dest.length) }; }
};
}
if (typeof TextDecoder === 'undefined') {
globalThis.TextDecoder = class TextDecoder {
constructor(label) { this.encoding = label || 'utf-8'; }
decode(buf) {
if (!buf) return '';
const bytes = ArrayBuffer.isView(buf)
? new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength)
: new Uint8Array(buf);
let str = '', i = 0;
while (i < bytes.length) {
let c = bytes[i++];
if (c < 0x80) str += String.fromCharCode(c);
else if (c < 0xE0) str += String.fromCharCode(((c&0x1F)<<6)|(bytes[i++]&0x3F));
else if (c < 0xF0) { const b1=bytes[i++], b2=bytes[i++]; str += String.fromCharCode(((c&0x0F)<<12)|((b1&0x3F)<<6)|(b2&0x3F)); }
else { const b1=bytes[i++], b2=bytes[i++], b3=bytes[i++]; const cp=((c&0x07)<<18)|((b1&0x3F)<<12)|((b2&0x3F)<<6)|(b3&0x3F); if(cp>0xFFFF){const s=cp-0x10000;str+=String.fromCharCode(0xD800+(s>>10),0xDC00+(s&0x3FF));}else str+=String.fromCharCode(cp); }
}
return str;
}
};
}
globalThis.matchMedia = _markNative(function matchMedia(q) { return { matches: false, media: q, addListener(){}, removeListener(){}, addEventListener(){}, removeEventListener(){}, dispatchEvent(){return true;} }; });
globalThis.getComputedStyle = (el) => {
if (!el) el = document.body || {};
const style = el?.style || el?._style || new CSSStyleDeclaration();
// React virtualization libraries (react-window, tanstack-virtual,
// react-virtuoso) all compute container dimensions via getComputedStyle.
// The defaults table previously returned `auto` for width/height and
// `'static'` for position, which made every list render 0 items. Pulling
// width/height from the synthesized bounding rect makes those libraries
// actually render content.
const dimensionFor = (name) => {
try {
const r = el.getBoundingClientRect && el.getBoundingClientRect();
if (!r) return null;
switch (name) {
case 'width': case 'inline-size':
return r.width != null ? `${r.width}px` : null;
case 'height': case 'block-size':
return r.height != null ? `${r.height}px` : null;
case 'left': return r.left != null ? `${r.left}px` : null;
case 'top': return r.top != null ? `${r.top}px` : null;
case 'right': return r.right != null ? `${r.right}px` : null;
case 'bottom': return r.bottom != null ? `${r.bottom}px` : null;
case 'client-width': case 'offset-width':
return r.width != null ? `${r.width}px` : null;
case 'client-height': case 'offset-height':
return r.height != null ? `${r.height}px` : null;
}
} catch (e) {}
return null;
};
const defaultsKebab = {
display: 'block', visibility: 'visible', opacity: '1',
position: 'static', overflow: 'visible',
transform: 'none', 'transform-origin': '0px 0px',
transition: 'none', animation: 'none',
float: 'none', clear: 'none',
margin: '0px', padding: '0px',
'margin-top': '0px', 'margin-right': '0px', 'margin-bottom': '0px', 'margin-left': '0px',
'padding-top': '0px', 'padding-right': '0px', 'padding-bottom': '0px', 'padding-left': '0px',
'font-size': '16px', 'line-height': 'normal', 'font-weight': '400',
'font-family': 'Times',
color: 'rgb(0, 0, 0)', 'background-color': 'rgba(0, 0, 0, 0)',
'border-width': '0px', 'border-style': 'none', 'border-color': 'rgb(0, 0, 0)',
'border-top-width': '0px', 'border-right-width': '0px',
'border-bottom-width': '0px', 'border-left-width': '0px',
'border-radius': '0px',
'z-index': 'auto', 'pointer-events': 'auto',
'box-sizing': 'content-box', cursor: 'auto',
'white-space': 'normal', 'text-align': 'start',
'flex-direction': 'row', 'flex-wrap': 'nowrap', 'align-items': 'normal',
'justify-content': 'normal', gap: 'normal',
'grid-template-columns': 'none', 'grid-template-rows': 'none',
'will-change': 'auto', 'backface-visibility': 'visible',
};
const lookup = (rawProp) => {
if (typeof rawProp !== 'string') return '';
// Inline value first.
const inlineVal = target.getPropertyValue ? target.getPropertyValue(rawProp) : '';
if (inlineVal) return inlineVal;
const kebab = rawProp.replace(/([A-Z])/g, '-$1').toLowerCase();
const dim = dimensionFor(kebab);
if (dim != null) return dim;
if (defaultsKebab[rawProp]) return defaultsKebab[rawProp];
if (defaultsKebab[kebab]) return defaultsKebab[kebab];
return '';
};
const target = style;
return new Proxy(style, {
get(_, prop) {
if (prop === Symbol.toPrimitive || prop === Symbol.toStringTag) return undefined;
if (prop in target) return target[prop];
if (prop === 'getPropertyValue') return (name) => lookup(name);
if (prop === 'getPropertyPriority') return () => '';
if (prop === 'item') return (i) => '';
if (prop === 'length') return 0;
if (prop === 'cssText') return '';
if (prop === 'parentRule') return null;
if (typeof prop === 'string') return lookup(prop);
return undefined;
},
});
};
globalThis.getSelection = _markNative(function getSelection() {
return {
rangeCount: 0,
anchorNode: null, anchorOffset: 0,
focusNode: null, focusOffset: 0,
isCollapsed: true, type: 'None',
removeAllRanges() { this.rangeCount = 0; },
addRange(range) { this.rangeCount = 1; this._range = range; },
getRangeAt(i) { return this._range || null; },
collapse(node, offset) { this.anchorNode = node; this.anchorOffset = offset || 0; this.isCollapsed = true; },
extend(node, offset) { this.focusNode = node; this.focusOffset = offset || 0; },
selectAllChildren(node) {},
deleteFromDocument() {},
containsNode(node) { return false; },
toString() { return ''; },
};
});
globalThis.CSSStyleSheet = class CSSStyleSheet {
constructor(options) {
this.cssRules = [];
this.ownerRule = null;
this.disabled = false;
this._rules = [];
}
insertRule(rule, index) {
const idx = index ?? this._rules.length;
this._rules.splice(idx, 0, { cssText: rule, type: 1 });
this.cssRules = this._rules;
return idx;
}
deleteRule(index) {
this._rules.splice(index, 1);
this.cssRules = this._rules;
}
addRule(selector, style, index) {
return this.insertRule(selector + '{' + style + '}', index);
}
removeRule(index) { this.deleteRule(index); }
replace(text) {
this._rules = [{ cssText: text, type: 1 }];
this.cssRules = this._rules;
return Promise.resolve(this);
}
replaceSync(text) {
this._rules = [{ cssText: text, type: 1 }];
this.cssRules = this._rules;
}
};
Object.defineProperty(Document.prototype, 'adoptedStyleSheets', {
get() { return this._adoptedStyleSheets || []; },
set(sheets) { this._adoptedStyleSheets = sheets; },
});
globalThis.__mutationObservers = [];
globalThis.MutationObserver = class MutationObserver {
constructor(callback) {
this._callback = callback;
this._targets = [];
this._records = [];
}
observe(target, options) {
this._targets.push({ target, options: options || {} });
globalThis.__mutationObservers.push(this);
}
disconnect() {
this._targets = [];
const idx = globalThis.__mutationObservers.indexOf(this);
if (idx >= 0) globalThis.__mutationObservers.splice(idx, 1);
}
takeRecords() {
const r = this._records.slice();
this._records = [];
return r;
}
_notify(records) {
this._records.push(...records);
Promise.resolve().then(() => {
if (this._records.length > 0) {
const batch = this._records.splice(0);
try { this._callback(batch, this); } catch(e) { /* observer errors shouldn't propagate */ }
}
});
}
};
globalThis.__notifyMutation = function(type, target_nid, addedNodes, removedNodes, attributeName, oldValue) {
if (!globalThis.__mutationObservers.length) return;
// Use `_wrap` (the canonical node-id → wrapper resolver) instead of a
// direct cache poke. The previous code referenced `globalThis._cache`,
// but `_cache` is a module-local Map — the lookup always returned
// undefined, so the function silently bailed every time. Result: no
// MutationObserver fired in obscura, ever, despite the call sites being
// wired up at appendChild / setAttribute. _wrap also lazily creates a
// wrapper for nodes that didn't have one yet (e.g. children parsed from
// `set innerHTML`), which we need for record.target/added/removed.
const target = _wrap(target_nid);
if (!target) return;
const record = {
type: type, // 'childList', 'attributes', 'characterData'
target: target,
addedNodes: (addedNodes || []).map(nid => _wrap(nid)).filter(Boolean),
removedNodes: (removedNodes || []).map(nid => _wrap(nid)).filter(Boolean),
attributeName: attributeName || null,
oldValue: oldValue ?? null,
previousSibling: null,
nextSibling: null,
};
// Walk target → ancestors so a subtree-mode observer rooted at any
// ancestor matches. The previous implementation just checked that
// `target.contains` and `target.closest` were defined (always true on
// any Element), so subtree=true silently behaved like subtree=false and
// every nested mutation missed its subscriber.
for (const obs of globalThis.__mutationObservers) {
let matched = false;
for (const t of obs._targets) {
const root = t.target;
if (!root) continue;
// Filter by type per the observer options. Default behaviour matches
// real MutationObserver: attribute mutations need options.attributes,
// characterData mutations need options.characterData, childList
// needs options.childList.
const wantsType =
(type === 'attributes' && t.options.attributes) ||
(type === 'characterData' && t.options.characterData) ||
(type === 'childList' && t.options.childList);
if (!wantsType) continue;
if (root._nid === target_nid) { matched = true; break; }
if (t.options.subtree) {
// Walk parents until we hit the observed root or run off the tree.
let cur = target.parentNode;
while (cur) {
if (cur._nid === root._nid) { matched = true; break; }
cur = cur.parentNode;
}
if (matched) break;
}
}
if (matched) obs._notify([record]);
}
};
globalThis.ShadowRoot = class ShadowRoot {};
globalThis.customElements = {
_registry: new Map(),
_whenDefinedResolvers: new Map(),
define(name, cls, opts) {
if (this._registry.has(name)) return;
this._registry.set(name, cls);
// Upgrade existing matching elements: instantiate the class on each,
// fire connectedCallback if the element is in the document. Without
// this, lit / MusicKit / Polymer components never wire up their
// shadow DOM or render, leaving heavy chunks of YouTube,
// music.apple.com, and any web-component site as empty shells.
try {
const matches = globalThis.document?.querySelectorAll(name) || [];
for (const el of matches) this._upgradeElement(el, cls);
} catch (e) {}
const resolvers = this._whenDefinedResolvers.get(name);
if (resolvers) {
for (const r of resolvers) r(cls);
this._whenDefinedResolvers.delete(name);
}
},
_upgradeElement(el, cls) {
if (el.__customUpgraded) return;
el.__customUpgraded = true;
try {
// Web Components spec: copy own props from the prototype onto the
// element. JS-side classes define behavior via methods on the
// prototype; we don't truly swap prototypes (Element is shared),
// so attach the prototype methods directly to the instance.
const proto = cls.prototype;
for (const key of Object.getOwnPropertyNames(proto)) {
if (key === 'constructor') continue;
const desc = Object.getOwnPropertyDescriptor(proto, key);
if (desc) Object.defineProperty(el, key, desc);
}
// Run constructor-side init on the element. Real custom elements
// run the class constructor, but Element instances aren't a `cls`
// subclass here; calling `.call(el)` runs whatever init logic the
// class defines without needing a new allocation.
try { cls.call(el); } catch (e) {}
if (typeof el.connectedCallback === 'function' && globalThis.document?.contains?.(el)) {
try { el.connectedCallback(); } catch (e) {}
}
} catch (e) {}
},
get(name) { return this._registry.get(name); },
whenDefined(name) {
const cls = this._registry.get(name);
if (cls) return Promise.resolve(cls);
return new Promise((resolve) => {
const list = this._whenDefinedResolvers.get(name) || [];
list.push(resolve);
this._whenDefinedResolvers.set(name, list);
});
},
upgrade(root) {
if (!root || !root.querySelectorAll) return;
for (const [name, cls] of this._registry.entries()) {
const matches = root.querySelectorAll(name);
for (const el of matches) this._upgradeElement(el, cls);
}
},
};
globalThis.NodeFilter = { SHOW_ELEMENT: 1, SHOW_TEXT: 4, SHOW_ALL: 0xFFFFFFFF };
// ResizeObserver is defined earlier with real per-target firing; the stub
// that previously lived here was a no-op that clobbered the real class.
//
// IntersectionObserver: without a layout engine we can't compute real
// intersection geometry, so every observed target is treated as fully
// in-viewport (`isIntersecting: true`, `intersectionRatio: 1`). Real
// libraries lean on this in three patterns we must support:
//
// 1. Lazy load: observe(img) -> first intersection -> load src -> unobserve.
// One fire is enough — covered by the initial microtask fire.
// 2. Infinite scroll: observe(sentinel) -> on intersection load more ->
// new sentinel mounts -> fire again. Needs re-fires as DOM grows.
// 3. Reveal-on-scroll animations: observe(card) -> isIntersecting flips
// true once and an animation runs. One fire is enough.
//
// To cover (2) without spinning forever, we burst-fire at an exponential
// backoff schedule and ALSO re-fire whenever the DOM mutates (a strong
// signal that the page just rendered something new). Per-observer total
// fire cap stops us from looping on a never-disconnected observer.
globalThis.__intersectionObservers = [];
globalThis.IntersectionObserver = class IntersectionObserver {
constructor(callback, options) {
this._callback = callback;
this._options = options || {};
this._targets = new Set();
this._connected = true;
this._fireCount = 0;
globalThis.__intersectionObservers.push(this);
}
_fireFor(targets) {
if (!this._connected || !targets.length || this._fireCount >= 256) return;
this._fireCount++;
const records = targets.map(target => ({
target,
isIntersecting: true,
intersectionRatio: 1,
boundingClientRect: target.getBoundingClientRect
? target.getBoundingClientRect()
: { x: 0, y: 0, width: 100, height: 20, top: 0, left: 0, right: 100, bottom: 20 },
intersectionRect: target.getBoundingClientRect
? target.getBoundingClientRect()
: { x: 0, y: 0, width: 100, height: 20, top: 0, left: 0, right: 100, bottom: 20 },
rootBounds: { x: 0, y: 0, width: 1280, height: 720, top: 0, left: 0, right: 1280, bottom: 720 },
time: Date.now(),
}));
try { this._callback(records, this); } catch (e) { /* IO callbacks must not propagate */ }
}
observe(el) {
if (!el || !this._connected) return;
if (this._targets.has(el)) return;
this._targets.add(el);
Promise.resolve().then(() => this._fireFor([el]));
// Exponential burst to cover infinite-scroll sentinels that "re-arm"
// after content lands. Without a real scroll/layout signal, we fake the
// re-fire schedule. Beyond ~10s the page has usually settled.
[120, 500, 1500, 3500, 7000].forEach(delay => {
setTimeout(() => {
if (this._connected && this._targets.has(el)) this._fireFor([el]);
}, delay);
});
}
unobserve(el) { this._targets.delete(el); }
disconnect() {
this._connected = false;
this._targets.clear();
const idx = globalThis.__intersectionObservers.indexOf(this);
if (idx >= 0) globalThis.__intersectionObservers.splice(idx, 1);
}
takeRecords() { return []; }
get root() { return this._options.root || null; }
get rootMargin() { return this._options.rootMargin || "0px 0px 0px 0px"; }
get thresholds() {
const t = this._options.threshold;
if (t == null) return [0];
return Array.isArray(t) ? t.slice() : [t];
}
};
// When the DOM mutates (e.g. infinite scroll loads a batch of items), re-fire
// every active IntersectionObserver so libraries observing dynamic content
// see a fresh isIntersecting=true event. Uses the same per-observer fire cap
// to prevent runaway loops if the page is mutating in a tight cycle.
(function() {
const reFire = () => {
for (const obs of globalThis.__intersectionObservers) {
if (!obs._connected) continue;
const ts = [...obs._targets];
if (ts.length) obs._fireFor(ts);
}
};
// Lazy-attach a single MutationObserver on document.body once the page is
// ready, debounced via a microtask so a flurry of mutations only triggers
// one IO sweep.
let pending = false;
const wireUp = () => {
if (!globalThis.document?.body) return;
const mo = new MutationObserver(() => {
if (pending) return;
pending = true;
Promise.resolve().then(() => { pending = false; reFire(); });
});
try { mo.observe(globalThis.document.body, {childList: true, subtree: true}); } catch {}
};
if (globalThis.document?.body) wireUp();
else Promise.resolve().then(wireUp);
})();
globalThis.IntersectionObserverEntry = class IntersectionObserverEntry {};
globalThis.PerformanceObserver = class { constructor(){} observe(){} disconnect(){} };
globalThis.Event = class Event {
constructor(t,o={}) { this.type=t;this.bubbles=!!o.bubbles;this.cancelable=!!o.cancelable;this.composed=!!o.composed;this.defaultPrevented=false;this.target=null;this.currentTarget=null;this.eventPhase=0;this.timeStamp=Date.now();this._propagationStopped=false;this._immediatePropagationStopped=false; }
get isTrusted() { return true; }
preventDefault() { if (this.cancelable) this.defaultPrevented=true; } stopPropagation(){ this._propagationStopped=true; } stopImmediatePropagation(){ this._propagationStopped=true; this._immediatePropagationStopped=true; }
initEvent(type,bubbles,cancelable) { this.type=type;this.bubbles=!!bubbles;this.cancelable=!!cancelable;this.defaultPrevented=false;this._propagationStopped=false;this._immediatePropagationStopped=false; }
};
globalThis.CustomEvent = class extends Event {
constructor(t,o={}) { super(t,o);this.detail=o.detail; }
// Legacy DOM Level 2 init; some libraries (Starbucks China bundle, older
// analytics shims) still call createEvent('CustomEvent') + initCustomEvent
// instead of new CustomEvent(...). See issue #41.
initCustomEvent(type,bubbles,cancelable,detail) {
this.type = type;
this.bubbles = !!bubbles;
this.cancelable = !!cancelable;
this.detail = detail;
}
};
globalThis.MouseEvent = class extends Event { constructor(t,o={}) { super(t,o);this.clientX=o.clientX||0;this.clientY=o.clientY||0; } };
globalThis.KeyboardEvent = class extends Event { constructor(t,o={}) { super(t,o);this.key=o.key||"";this.code=o.code||""; } };
globalThis.FocusEvent = class extends Event {};
globalThis.InputEvent = class extends Event { constructor(t,o={}) { super(t,o);this.data=o.data||null;this.inputType=o.inputType||""; } };
globalThis.ErrorEvent = class extends Event { constructor(t,o={}) { super(t,o);this.message=o.message||"";this.error=o.error||null; } };
globalThis.PointerEvent = class extends Event { constructor(t,o={}) { super(t,o); } };
globalThis.AnimationEvent = class extends Event {};
globalThis.TransitionEvent = class extends Event {};
globalThis.UIEvent = class extends Event {};
globalThis.WheelEvent = class extends Event {};
globalThis.PopStateEvent = class extends Event {
constructor(type, init) {
super(type, init || {});
// Real PopStateEvent exposes `state` from the entry being navigated to.
// The earlier stub inherited Event but never stored state, so
// `popstate.state` was always undefined and SPA routers reading
// `event.state` to restore route info would mis-render.
this.state = init && 'state' in init ? init.state : null;
}
};
globalThis.HashChangeEvent = class extends Event {};
globalThis.MessageEvent = class extends Event { constructor(t,o={}) { super(t,o);this.data=o.data; } };
globalThis.ClipboardEvent = class extends Event {};
globalThis.SubmitEvent = class extends Event {};
globalThis.AbortController = class AbortController { constructor(){this.signal={aborted:false,addEventListener(){},removeEventListener(){},onabort:null};} abort(){this.signal.aborted=true;} };
globalThis.AbortSignal = { timeout(ms){return {aborted:false,addEventListener(){},removeEventListener(){}}; } };
if (typeof Blob === "undefined") globalThis.Blob = class Blob { constructor(parts=[],opts={}){this._data=parts.join("");this.size=this._data.length;this.type=opts.type||"";} async text(){return this._data;} };
if (typeof File === "undefined") globalThis.File = class extends Blob { constructor(parts,name,opts){super(parts,opts);this.name=name;} };
if (typeof FormData === "undefined") globalThis.FormData = class FormData { constructor(){this._d=[];} append(k,v){this._d.push([k,v]);} get(k){const e=this._d.find(([a])=>a===k);return e?e[1]:null;} getAll(k){return this._d.filter(([a])=>a===k).map(([,v])=>v);} has(k){return this._d.some(([a])=>a===k);} entries(){return this._d[Symbol.iterator]();} forEach(cb){this._d.forEach(([k,v])=>cb(v,k));} };
if (typeof URLSearchParams === "undefined") globalThis.URLSearchParams = class {
constructor(init=""){
this._p=[];
if(typeof init==="string"){
init.replace(/^\?/,"").split("&").forEach(p=>{const[k,...v]=p.split("=");if(k)this.append(decodeURIComponent(k),decodeURIComponent(v.join("=")));});
} else if (init && typeof init[Symbol.iterator] === 'function') {
for (const pair of init) if (pair && pair.length >= 2) this.append(pair[0], pair[1]);
} else if (init && typeof init === 'object') {
Object.keys(init).forEach(k => this.append(k, init[k]));
}
}
append(k,v){this._p.push([String(k),String(v)]);}
get(k){const p=this._p.find(([key])=>key===String(k)); return p?p[1]:null;}
set(k,v){this.delete(k); this.append(k,v);}
delete(k){k=String(k); this._p=this._p.filter(([key])=>key!==k);}
has(k){k=String(k); return this._p.some(([key])=>key===k);}
toString(){return this._p.map(([k,v])=>`${encodeURIComponent(k)}=${encodeURIComponent(v)}`).join("&");}
forEach(cb){this._p.forEach(([k,v])=>cb(v,k,this));}
};
// Real-enough DOMParser. The previous one-liner returned `globalThis.document`,
// so anything that did `new DOMParser().parseFromString(s, 'text/html')` and
// then read `.body.innerHTML` mutated the LIVE page (jQuery 3.x's selector
// feature-detect writes `<form></form>` and wiped real bodies). We parse the
// input into a detached `<html>` element and wrap it so the common Document
// API surface (body / head / documentElement / querySelector* / getElementById /
// getElementsByTagName / getElementsByClassName / title / cloneNode) works.
globalThis.DOMParser = class DOMParser {
parseFromString(source, mimeType) {
const html = String(source ?? "");
const isXml = typeof mimeType === "string" && /xml/i.test(mimeType);
const root = document.createElement("html");
// innerHTML parses children via html5ever fragment-parsing rules. Most
// HTML inputs start with `<!DOCTYPE>` / `<html>` / `<head>` etc.; the
// fragment parser strips the outer `<html>` and emits its head+body
// children, which is what callers want.
try { root.innerHTML = html; } catch (e) { /* leave empty on parse error */ }
// Helper: depth-first walk to find an element by predicate.
const walk = (node, pred) => {
if (!node) return null;
if (node.nodeType === 1 && pred(node)) return node;
const children = node.children || [];
for (let i = 0; i < children.length; i++) {
const r = walk(children[i], pred);
if (r) return r;
}
return null;
};
const findByTagName = (name) => walk(root, n => n.tagName === name);
const docNode = {
_root: root,
nodeName: "#document",
nodeType: 9,
contentType: isXml ? (mimeType || "application/xml") : "text/html",
get documentElement() { return root; },
get body() { return findByTagName("BODY"); },
get head() { return findByTagName("HEAD"); },
get title() {
const t = findByTagName("TITLE");
return t ? (t.textContent || "") : "";
},
get firstChild() { return root; },
get lastChild() { return root; },
get children() { return [root]; },
get childNodes() { return [root]; },
querySelector(s) { return root.querySelector(s); },
querySelectorAll(s) { return root.querySelectorAll(s); },
getElementById(id) {
return walk(root, n => n.getAttribute && n.getAttribute("id") === id);
},
getElementsByTagName(t) {
return root.querySelectorAll(t);
},
getElementsByClassName(c) {
return root.querySelectorAll("." + c);
},
getElementsByName(n) {
return root.querySelectorAll(`[name="${n}"]`);
},
createElement: (t) => document.createElement(t),
createElementNS: (ns, t) => document.createElement(t),
createTextNode: (t) => document.createTextNode(t),
createComment: (t) => document.createComment(t),
createDocumentFragment: () => document.createDocumentFragment(),
adoptNode: (n) => n,
importNode: (n) => n,
cloneNode: function (deep) {
return new DOMParser().parseFromString(root.outerHTML, mimeType);
},
contains(n) { return root.contains ? root.contains(n) : false; },
addEventListener() {}, removeEventListener() {}, dispatchEvent() { return true; },
};
return docNode;
}
};
globalThis.XMLSerializer = class XMLSerializer {
serializeToString(node) {
if (!node) return "";
if (node.nodeType === 10) {
let s = "<!DOCTYPE " + (node.name || "html");
if (node.publicId) s += ' PUBLIC "' + node.publicId + '"';
if (node.systemId) {
if (!node.publicId) s += " SYSTEM";
s += ' "' + node.systemId + '"';
}
s += ">";
return s;
}
if (node.outerHTML !== undefined) return node.outerHTML;
if (node.nodeType === 9) {
let s = "";
if (node.doctype) s += this.serializeToString(node.doctype);
if (node.documentElement) s += node.documentElement.outerHTML;
return s;
}
if (node.nodeType === 3) return node.textContent || "";
if (node.nodeType === 8) return "<!--" + (node.textContent || "") + "-->";
return "";
}
};
globalThis.performance = globalThis.performance || {
now: () => Date.now(),
mark(){}, measure(){},
clearMarks(){}, clearMeasures(){}, clearResourceTimings(){},
getEntries(){return [];}, getEntriesByName(){return [];}, getEntriesByType(){return [];},
setResourceTimingBufferSize(){},
timeOrigin: 0,
timing: { navigationStart: 0, domContentLoadedEventEnd: 0, loadEventEnd: 0 },
navigation: { type: 0, redirectCount: 0 },
memory: {
jsHeapSizeLimit: 2172649472,
totalJSHeapSize: 19321856,
usedJSHeapSize: 16781520,
},
};
var _commonFonts = [
'Arial', 'Arial Black', 'Arial Narrow',
'Baskerville', 'Book Antiqua',
'Calibri', 'Cambria', 'Candara', 'Consolas', 'Courier New',
'DejaVu Sans', 'DejaVu Sans Mono', 'DejaVu Serif',
'Futura',
'Garamond', 'Georgia', 'Gill Sans',
'Helvetica',
'Impact',
'Liberation Sans', 'Liberation Sans Mono', 'Liberation Serif',
'Lucida Console', 'Lucida Handwriting',
'Microsoft Sans Serif', 'Monaco',
'Noto Sans', 'Noto Serif',
'Palatino Linotype',
'Segoe UI',
'Tahoma', 'Times New Roman', 'Trebuchet MS',
'Verdana',
'Webdings', 'Wingdings',
];
Object.defineProperty(Document.prototype, 'fonts', {
get() {
const _set = _commonFonts.map((name, i) => ({
family: name, style: 'normal', weight: '400', stretch: 'normal',
status: 'loaded', loaded: Promise.resolve(this),
[Symbol.toStringTag]: 'FontFace',
}));
_set.forEach = (fn) => { _set.forEach(fn); };
_set.has = (f) => typeof f === 'string'
? _commonFonts.some(n => n.toLowerCase() === f.toLowerCase())
: _set.some(ff => ff.family === f?.family);
_set.delete = (f) => false;
_set.clear = () => {};
_set.add = () => {};
_set.load = () => Promise.resolve(_set);
_set.check = (font) => {
const m = typeof font === 'string' ? font.match(/["']([^"']+)["']/) : null;
return m ? _commonFonts.some(n => n.toLowerCase() === m[1].toLowerCase()) : true;
};
_set.ready = Promise.resolve(_set);
_set.status = 'loaded';
_set.addEventListener = () => {};
_set.removeEventListener = () => {};
_set.dispatchEvent = () => true;
return _set;
},
configurable: true,
});
globalThis.crypto = globalThis.crypto || { getRandomValues(arr) { for(let i=0;i<arr.length;i++) arr[i]=Math.floor(Math.random()*256); return arr; }, randomUUID(){ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,c=>{const r=Math.random()*16|0;return(c==="x"?r:(r&3|8)).toString(16);}); } };
globalThis.structuredClone = globalThis.structuredClone || ((v) => JSON.parse(JSON.stringify(v)));
globalThis.reportError = globalThis.reportError || ((e) => console.error(e));
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.sessionStorage = _mkStore();
globalThis.btoa = globalThis.btoa || ((s) => { const b = new TextEncoder().encode(s); const c="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; let r=""; for(let i=0;i<b.length;i+=3){const a=b[i],bb=b[i+1]??0,cc=b[i+2]??0; r+=c[a>>2]+c[((a&3)<<4)|(bb>>4)]+(i+1<b.length?c[((bb&15)<<2)|(cc>>6)]:"=")+(i+2<b.length?c[cc&63]:"=");} return r; });
globalThis.atob = globalThis.atob || ((s) => { const c="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; let r=[]; for(let i=0;i<s.length;i+=4){const a=c.indexOf(s[i]),b=c.indexOf(s[i+1]),cc=c.indexOf(s[i+2]),d=c.indexOf(s[i+3]); r.push((a<<2)|(b>>4)); if(cc>=0)r.push(((b&15)<<4)|(cc>>2)); if(d>=0)r.push(((cc&3)<<6)|d);} return String.fromCharCode(...r); });
// Functional History API. The earlier stub returned constant state and was a
// no-op on push/replace, so any SPA that tried to update its URL (Next.js
// client router, React Router, vue-router, hash-based routers) silently
// failed: location.href stayed pinned to the initial page, useLocation hooks
// never updated, and popstate-driven UI froze.
//
// Internally we keep a tiny in-memory stack of {state, url} entries. push/
// replace mutate the stack and set globalThis.__virtualUrl so location.href
// reads the new URL. Real Chrome doesn't fire popstate on push/replace,
// only on user-driven back/forward — we match that exactly.
(() => {
const stack = [{state: null, url: undefined}]; // initial entry; url=undefined means "use document URL"
let idx = 0;
const resolveOrFallback = (url) => {
if (url === null || url === undefined) return undefined;
try { return new URL(String(url), __currentUrl()).href; } catch (e) { return String(url); }
};
const applyVirtual = () => {
const entry = stack[idx];
globalThis.__virtualUrl = entry.url ?? null;
};
const fireHashChangeIfNeeded = (prevUrl) => {
try {
const next = __currentUrl();
if (!prevUrl || !next) return;
const a = new URL(prevUrl), b = new URL(next);
if (a.origin === b.origin && a.pathname === b.pathname && a.search === b.search && a.hash !== b.hash) {
const ev = new Event('hashchange');
ev.oldURL = prevUrl; ev.newURL = next;
try { globalThis.dispatchEvent(ev); } catch {}
}
} catch {}
};
globalThis.history = {
get length() { return stack.length; },
get state() { return stack[idx].state; },
scrollRestoration: "auto",
pushState(state, _title, url) {
const prevUrl = __currentUrl();
const resolved = resolveOrFallback(url);
// Truncate forward entries (real Chrome drops the forward stack on a
// new push) then append + advance.
stack.length = idx + 1;
stack.push({state: state ?? null, url: resolved});
idx = stack.length - 1;
applyVirtual();
fireHashChangeIfNeeded(prevUrl);
},
replaceState(state, _title, url) {
const prevUrl = __currentUrl();
const resolved = resolveOrFallback(url);
stack[idx] = {state: state ?? null, url: resolved};
applyVirtual();
fireHashChangeIfNeeded(prevUrl);
},
go(n) {
n = (n | 0);
if (n === 0) return; // real spec: go(0) reloads. We don't reload SPAs.
const next = Math.max(0, Math.min(stack.length - 1, idx + n));
if (next === idx) return;
const prevUrl = __currentUrl();
idx = next;
applyVirtual();
// Real Chrome fires popstate on back/forward with the destination entry's state.
try {
const ev = new PopStateEvent('popstate', {state: stack[idx].state});
globalThis.dispatchEvent(ev);
} catch {}
fireHashChangeIfNeeded(prevUrl);
},
back() { this.go(-1); },
forward() { this.go(1); },
};
})();
globalThis.screenX = 0; globalThis.screenY = 0;
globalThis.screenLeft = 0; globalThis.screenTop = 0;
globalThis.pageXOffset = 0; globalThis.pageYOffset = 0;
globalThis.scrollX = 0; globalThis.scrollY = 0;
globalThis.CSS = { supports(){return false;}, escape(s){return s;} };
globalThis.HTMLElement = Element;
globalThis.HTMLDivElement = Element;
globalThis.HTMLSpanElement = Element;
globalThis.HTMLParagraphElement = Element;
globalThis.HTMLAnchorElement = Element;
globalThis.HTMLImageElement = Element;
globalThis.HTMLInputElement = Element;
globalThis.HTMLButtonElement = Element;
globalThis.HTMLFormElement = class HTMLFormElement extends Element {
get elements() { return this.querySelectorAll("input, select, textarea, button, fieldset, output, object"); }
get length() { return this.elements.length; }
// Inherit submit() from Element.prototype: it dispatches the cancelable
// 'submit' event and (if not prevented) builds form data and navigates.
reset() { for (const f of this.elements) { if ('value' in f) f.value = ''; } }
};
globalThis.HTMLSelectElement = Element;
globalThis.HTMLTextAreaElement = Element;
globalThis.HTMLLabelElement = Element;
globalThis.HTMLTableElement = Element;
globalThis.HTMLIFrameElement = Element;
globalThis.HTMLCanvasElement = Element;
globalThis.HTMLVideoElement = Element;
globalThis.HTMLAudioElement = Element;
globalThis.HTMLScriptElement = Element;
globalThis.HTMLStyleElement = Element;
globalThis.HTMLLinkElement = Element;
globalThis.HTMLMetaElement = Element;
globalThis.HTMLHeadElement = Element;
globalThis.HTMLBodyElement = Element;
globalThis.HTMLHtmlElement = Element;
globalThis.HTMLBRElement = Element;
globalThis.HTMLHRElement = Element;
globalThis.HTMLUListElement = Element;
globalThis.HTMLOListElement = Element;
globalThis.HTMLLIElement = Element;
globalThis.HTMLPreElement = Element;
globalThis.HTMLHeadingElement = Element;
globalThis.HTMLTemplateElement = Element;
globalThis.HTMLSlotElement = Element;
globalThis.HTMLOptionElement = Element;
globalThis.HTMLDataListElement = Element;
globalThis.HTMLFieldSetElement = Element;
globalThis.HTMLLegendElement = Element;
globalThis.HTMLProgressElement = Element;
globalThis.HTMLDetailsElement = Element;
globalThis.HTMLDialogElement = Element;
globalThis.SVGElement = Element;
globalThis.SVGSVGElement = Element;
globalThis.CharacterData = CharacterData;
globalThis.Text = Text;
globalThis.Comment = Comment;
globalThis.DocumentFragment = DocumentFragment;
globalThis.DocumentType = DocumentType;
globalThis.Node = Node;
globalThis.Element = Element;
globalThis.Document = Document;
globalThis.EventTarget = Node;
globalThis.Range = class Range { setStart(){} setEnd(){} collapse(){} selectNodeContents(){} deleteContents(){} cloneContents(){ return document.createDocumentFragment(); } insertNode(){} getBoundingClientRect(){return {x:0,y:0,width:0,height:0,top:0,right:0,bottom:0,left:0};} };
[
navigator.getBattery, navigator.getGamepads, navigator.sendBeacon,
navigator.javaEnabled, navigator.geolocation?.getCurrentPosition,
navigator.geolocation?.watchPosition,
navigator.serviceWorker?.register,
navigator.permissions?.query, navigator.credentials?.get,
navigator.storage?.estimate, navigator.storage?.persist, navigator.storage?.persisted,
globalThis.fetch, globalThis.matchMedia, globalThis.getComputedStyle,
globalThis.getSelection, globalThis.requestAnimationFrame,
globalThis.cancelAnimationFrame, globalThis.setTimeout, globalThis.clearTimeout,
globalThis.setInterval, globalThis.clearInterval, globalThis.queueMicrotask,
globalThis.structuredClone, globalThis.reportError,
globalThis.btoa, globalThis.atob,
console.log, console.warn, console.error, console.info, console.debug,
console.dir, console.assert,
Element.prototype.getAttribute, Element.prototype.setAttribute,
Element.prototype.removeAttribute, Element.prototype.hasAttribute,
Element.prototype.querySelector, Element.prototype.querySelectorAll,
Element.prototype.getElementsByTagName, Element.prototype.getElementsByClassName,
Element.prototype.matches, Element.prototype.closest,
Element.prototype.getBoundingClientRect, Element.prototype.getClientRects,
Element.prototype.checkVisibility,
Element.prototype.addEventListener, Element.prototype.removeEventListener,
Element.prototype.dispatchEvent, Element.prototype.click,
Element.prototype.focus, Element.prototype.blur,
Element.prototype.cloneNode, Element.prototype.attachShadow,
Element.prototype.insertAdjacentHTML, Element.prototype.scrollIntoView,
Element.prototype.append, Element.prototype.prepend, Element.prototype.remove,
Element.prototype.before, Element.prototype.after, Element.prototype.replaceWith,
HTMLFormElement.prototype.reset,
Element.prototype.getContext, Element.prototype.toDataURL, Element.prototype.toBlob,
Node.prototype.appendChild, Node.prototype.removeChild,
Node.prototype.replaceChild, Node.prototype.insertBefore,
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.querySelectorAll, Document.prototype.getElementsByTagName,
Document.prototype.createElement, Document.prototype.createElementNS,
Document.prototype.createTextNode, Document.prototype.createComment,
Document.prototype.createDocumentFragment, Document.prototype.createEvent,
Document.prototype.hasFocus,
Storage, Storage.prototype.getItem, Storage.prototype.setItem,
Storage.prototype.removeItem, Storage.prototype.clear, Storage.prototype.key,
Notification, Notification.requestPermission,
window.chrome?.csi, window.chrome?.loadTimes,
MutationObserver, ResizeObserver, IntersectionObserver, PerformanceObserver,
XMLSerializer, XMLSerializer.prototype.serializeToString,
].forEach(fn => { if (typeof fn === 'function') _markNative(fn); });
class _IframeDocument {
constructor(html, url, iframeEl) {
this._url = url;
this._iframeEl = iframeEl;
this.nodeType = 9;
this.nodeName = '#document';
this.readyState = 'complete';
this.characterSet = 'UTF-8';
this.contentType = 'text/html';
this.visibilityState = 'visible';
this.hidden = false;
this._root = document.createElement('html');
this._head = document.createElement('head');
this._body = document.createElement('body');
this._root.appendChild(this._head);
this._root.appendChild(this._body);
var bodyContent = html
.replace(/^<!DOCTYPE[^>]*>/i, '')
.replace(/<\/?html[^>]*>/gi, '')
.replace(/<head[^>]*>[\s\S]*?<\/head>/gi, '')
.replace(/<\/?body[^>]*>/gi, '')
.replace(/^\s+/, ''); // trim leading whitespace (before <body> content)
if (bodyContent) {
this._body.innerHTML = bodyContent;
}
this._title = '';
if (this._head) {
const titleEl = this._head.querySelector('title');
if (titleEl) this._title = titleEl.textContent;
}
}
get documentElement() { return this._root; }
get head() { return this._head; }
get body() { return this._body; }
get title() { return this._title; }
set title(v) { this._title = v; }
get URL() { return this._url; }
get documentURI() { return this._url; }
get location() { return this._iframeEl?.contentWindow?.location; }
get defaultView() { return this._iframeEl?.contentWindow; }
get ownerDocument() { return null; }
get compatMode() { return 'CSS1Compat'; }
get activeElement() { return this._body; }
getElementById(id) {
return this._root.querySelector('#' + id);
}
querySelector(sel) {
return this._root.querySelector(sel);
}
querySelectorAll(sel) {
return this._root.querySelectorAll(sel);
}
getElementsByTagName(tag) {
return this._root.querySelectorAll(tag);
}
getElementsByClassName(cls) {
return this._root.querySelectorAll('.' + cls);
}
createElement(tag) { return document.createElement(tag); }
createElementNS(ns, tag) { return document.createElementNS(ns, tag); }
createTextNode(text) { return document.createTextNode(text); }
createComment(text) { return document.createComment(text); }
createDocumentFragment() { return document.createDocumentFragment(); }
createEvent(type) { return document.createEvent(type); }
hasFocus() { return false; }
get cookie() { return ''; }
set cookie(v) {}
get implementation() { return document.implementation; }
get styleSheets() { return []; }
addEventListener() {}
removeEventListener() {}
dispatchEvent() { return true; }
write(html) {
if (this._body) this._body.innerHTML += html;
}
writeln(html) { this.write(html + '\n'); }
open() { if (this._body) this._body.innerHTML = ''; }
close() {}
}
class _IframeWindow {
constructor(doc, url) {
this.document = doc;
this._url = url;
this.self = this;
this.top = globalThis;
this.parent = globalThis;
this.window = this;
this.frames = this;
this.frameElement = null;
this.length = 0;
this.name = '';
this.closed = false;
this.navigator = globalThis.navigator;
this.screen = globalThis.screen;
this.innerWidth = 300;
this.innerHeight = 150;
this.outerWidth = 300;
this.outerHeight = 150;
this.devicePixelRatio = globalThis.devicePixelRatio;
this.localStorage = globalThis.localStorage;
this.sessionStorage = globalThis.sessionStorage;
this.performance = globalThis.performance;
this.crypto = globalThis.crypto;
this.console = globalThis.console;
this.chrome = globalThis.chrome;
try {
const u = new URL(url);
this.location = {
href: url, origin: u.origin, protocol: u.protocol,
host: u.host, hostname: u.hostname, port: u.port,
pathname: u.pathname, search: u.search, hash: u.hash,
toString() { return url; }, assign(){}, reload(){}, replace(){},
};
} catch(e) {
this.location = { href: url, origin: '', protocol: '', host: '', hostname: '', port: '', pathname: '/', search: '', hash: '', toString() { return url; }, assign(){}, reload(){}, replace(){} };
}
}
postMessage(data, origin) {
const event = new MessageEvent('message', {
data: data,
origin: this.location.origin,
source: this,
});
Promise.resolve().then(() => {
globalThis.dispatchEvent?.(event);
});
}
setTimeout(fn, ms) { return globalThis.setTimeout(fn, ms); }
clearTimeout(id) { globalThis.clearTimeout(id); }
setInterval(fn, ms) { return globalThis.setInterval(fn, ms); }
clearInterval(id) { globalThis.clearInterval(id); }
requestAnimationFrame(fn) { return globalThis.requestAnimationFrame(fn); }
addEventListener(type, fn) {
if (!this._listeners) this._listeners = {};
if (!this._listeners[type]) this._listeners[type] = [];
this._listeners[type].push(fn);
}
removeEventListener(type, fn) {
if (this._listeners?.[type]) {
this._listeners[type] = this._listeners[type].filter(h => h !== fn);
}
}
dispatchEvent(event) {
const handlers = this._listeners?.[event?.type] || [];
for (const h of handlers) { try { h.call(this, event); } catch(e) {} }
return true;
}
getComputedStyle(el) { return globalThis.getComputedStyle(el); }
matchMedia(q) { return globalThis.matchMedia(q); }
getSelection() { return globalThis.getSelection(); }
fetch(input, init) { return globalThis.fetch(input, init); }
close() { this.closed = true; }
focus() {}
blur() {}
}
globalThis.__ariaQuerySelector = function(root, selector) { return null; };
globalThis.__ariaQuerySelectorAll = async function*(root, selector) { /* yields nothing */ };
class _Canvas2D {
constructor(canvas) {
this.canvas = canvas;
this._w = canvas.width || 300;
this._h = canvas.height || 150;
this._buf = new Uint8ClampedArray(this._w * this._h * 4);
for (let i = 0; i < this._w * this._h; i++) {
this._buf[i*4+0] = 255 + Math.floor(_fpNoise(i % this._w, Math.floor(i / this._w), 0));
this._buf[i*4+1] = 255 + Math.floor(_fpNoise(i % this._w, Math.floor(i / this._w), 1));
this._buf[i*4+2] = 255 + Math.floor(_fpNoise(i % this._w, Math.floor(i / this._w), 2));
this._buf[i*4+3] = 255;
}
this.fillStyle = '#000000';
this.strokeStyle = '#000000';
this.lineWidth = 1;
this.font = '10px sans-serif';
this.textAlign = 'start';
this.textBaseline = 'alphabetic';
this.globalAlpha = 1;
this.globalCompositeOperation = 'source-over';
this._stateStack = [];
}
_parseColor(css) {
if (!css || css === 'none') return [0,0,0,0];
if (css.startsWith('#')) {
const hex = css.slice(1);
if (hex.length === 3) return [parseInt(hex[0]+hex[0],16),parseInt(hex[1]+hex[1],16),parseInt(hex[2]+hex[2],16),255];
if (hex.length === 6) return [parseInt(hex.slice(0,2),16),parseInt(hex.slice(2,4),16),parseInt(hex.slice(4,6),16),255];
if (hex.length === 8) return [parseInt(hex.slice(0,2),16),parseInt(hex.slice(2,4),16),parseInt(hex.slice(4,6),16),parseInt(hex.slice(6,8),16)];
}
const m = css.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/);
if (m) return [+m[1],+m[2],+m[3],m[4]!==undefined?Math.round(+m[4]*255):255];
const named = {red:[255,0,0,255],green:[0,128,0,255],blue:[0,0,255,255],white:[255,255,255,255],black:[0,0,0,255],yellow:[255,255,0,255],orange:[255,165,0,255],gray:[128,128,128,255],transparent:[0,0,0,0]};
return named[css] || [0,0,0,255];
}
_setPixel(x, y, r, g, b, a) {
x = Math.round(x); y = Math.round(y);
if (x < 0 || x >= this._w || y < 0 || y >= this._h) return;
const idx = (y * this._w + x) * 4;
const alpha = (a / 255) * this.globalAlpha;
this._buf[idx+0] = Math.round(r * alpha + this._buf[idx+0] * (1 - alpha));
this._buf[idx+1] = Math.round(g * alpha + this._buf[idx+1] * (1 - alpha));
this._buf[idx+2] = Math.round(b * alpha + this._buf[idx+2] * (1 - alpha));
this._buf[idx+3] = Math.min(255, Math.round(a * alpha + this._buf[idx+3] * (1 - alpha)));
}
fillRect(x, y, w, h) {
const [r,g,b,a] = this._parseColor(this.fillStyle);
x=Math.round(x); y=Math.round(y); w=Math.round(w); h=Math.round(h);
for (let py = Math.max(0,y); py < Math.min(this._h, y+h); py++) {
for (let px = Math.max(0,x); px < Math.min(this._w, x+w); px++) {
this._setPixel(px, py, r, g, b, a);
}
}
}
clearRect(x, y, w, h) {
x=Math.round(x); y=Math.round(y); w=Math.round(w); h=Math.round(h);
for (let py = Math.max(0,y); py < Math.min(this._h, y+h); py++) {
for (let px = Math.max(0,x); px < Math.min(this._w, x+w); px++) {
const idx = (py * this._w + px) * 4;
this._buf[idx] = this._buf[idx+1] = this._buf[idx+2] = this._buf[idx+3] = 0;
}
}
}
strokeRect(x, y, w, h) {
const [r,g,b,a] = this._parseColor(this.strokeStyle);
const lw = this.lineWidth;
for (let px = Math.round(x); px < Math.round(x+w); px++) {
for (let l = 0; l < lw; l++) { this._setPixel(px, Math.round(y)+l, r,g,b,a); this._setPixel(px, Math.round(y+h)-1-l, r,g,b,a); }
}
for (let py = Math.round(y); py < Math.round(y+h); py++) {
for (let l = 0; l < lw; l++) { this._setPixel(Math.round(x)+l, py, r,g,b,a); this._setPixel(Math.round(x+w)-1-l, py, r,g,b,a); }
}
}
fillText(text, x, y) {
const [r,g,b,a] = this._parseColor(this.fillStyle);
const fontSize = parseInt(this.font) || 10;
const scale = Math.max(1, Math.round(fontSize / 10));
const str = String(text);
let cx = Math.round(x);
for (let i = 0; i < str.length; i++) {
const code = str.charCodeAt(i);
for (let row = 0; row < 7; row++) {
for (let col = 0; col < 5; col++) {
const on = ((_fpRand(code * 100 + row * 10 + col) > 0.45) &&
(row > 0 && row < 6 && col > 0 && col < 4)) ||
(_fpRand(code * 200 + row * 7 + col) > 0.7);
if (on) {
for (let sy = 0; sy < scale; sy++) {
for (let sx = 0; sx < scale; sx++) {
this._setPixel(cx + col*scale + sx, Math.round(y) - 7*scale + row*scale + sy, r, g, b, a);
}
}
}
}
}
cx += 6 * scale;
}
}
strokeText(text, x, y) { this.fillText(text, x, y); }
measureText(t) {
const fontSize = parseInt(this.font) || 10;
const scale = Math.max(1, Math.round(fontSize / 10));
return { width: String(t).length * 6 * scale, actualBoundingBoxAscent: 7*scale, actualBoundingBoxDescent: 2*scale };
}
getImageData(x, y, w, h) {
x=Math.round(x); y=Math.round(y); w=Math.round(w); h=Math.round(h);
const data = new Uint8ClampedArray(w * h * 4);
for (let py = 0; py < h; py++) {
for (let px = 0; px < w; px++) {
const srcX = x + px, srcY = y + py;
const dstIdx = (py * w + px) * 4;
if (srcX >= 0 && srcX < this._w && srcY >= 0 && srcY < this._h) {
const srcIdx = (srcY * this._w + srcX) * 4;
data[dstIdx] = this._buf[srcIdx];
data[dstIdx+1] = this._buf[srcIdx+1];
data[dstIdx+2] = this._buf[srcIdx+2];
data[dstIdx+3] = this._buf[srcIdx+3];
}
}
}
return { data, width: w, height: h };
}
putImageData(imageData, dx, dy) {
dx=Math.round(dx); dy=Math.round(dy);
const {data, width: w, height: h} = imageData;
for (let py = 0; py < h; py++) {
for (let px = 0; px < w; px++) {
const srcIdx = (py * w + px) * 4;
const x = dx + px, y = dy + py;
if (x >= 0 && x < this._w && y >= 0 && y < this._h) {
const dstIdx = (y * this._w + x) * 4;
this._buf[dstIdx] = data[srcIdx];
this._buf[dstIdx+1] = data[srcIdx+1];
this._buf[dstIdx+2] = data[srcIdx+2];
this._buf[dstIdx+3] = data[srcIdx+3];
}
}
}
}
createImageData(w, h) { return { data: new Uint8ClampedArray(w*h*4), width: w, height: h }; }
drawImage(img, sx, sy, sw, sh, dx, dy, dw, dh) {
if (img && img._ctx && img._ctx._buf) {
const src = img._ctx;
dx = dx ?? sx; dy = dy ?? sy; dw = dw ?? (sw ?? src._w); dh = dh ?? (sh ?? src._h);
for (let py = 0; py < dh; py++) {
for (let px = 0; px < dw; px++) {
const srcX = Math.floor((sx||0) + px * (sw||src._w) / dw);
const srcY = Math.floor((sy||0) + py * (sh||src._h) / dh);
if (srcX >= 0 && srcX < src._w && srcY >= 0 && srcY < src._h) {
const srcIdx = (srcY * src._w + srcX) * 4;
this._setPixel(dx+px, dy+py, src._buf[srcIdx], src._buf[srcIdx+1], src._buf[srcIdx+2], src._buf[srcIdx+3]);
}
}
}
}
}
beginPath() { this._path = []; }
closePath() {}
moveTo(x, y) { if (this._path) this._path.push({t:'M',x,y}); }
lineTo(x, y) { if (this._path) this._path.push({t:'L',x,y}); }
bezierCurveTo() {} quadraticCurveTo() {}
arc(x, y, r, s, e) { if (this._path) this._path.push({t:'A',x,y,r}); }
arcTo() {}
rect(x, y, w, h) { this.fillRect(x, y, w, h); }
fill() {}
stroke() {}
clip() {}
save() { this._stateStack.push({fillStyle: this.fillStyle, strokeStyle: this.strokeStyle, globalAlpha: this.globalAlpha, font: this.font, lineWidth: this.lineWidth}); }
restore() { const s = this._stateStack.pop(); if (s) Object.assign(this, s); }
translate() {} rotate() {} scale() {}
setTransform() {} resetTransform() {} transform() {}
createLinearGradient(x0,y0,x1,y1) { return { addColorStop(){}, _x0:x0,_y0:y0,_x1:x1,_y1:y1 }; }
createRadialGradient() { return { addColorStop(){} }; }
createPattern() { return {}; }
isPointInPath() { return false; }
isPointInStroke() { return false; }
}
Element.prototype.getContext = function getContext(type) {
if (type === '2d') {
if (!this._ctx) {
this._ctx = new _Canvas2D(this);
}
return this._ctx;
}
if (type === 'webgl' || type === 'experimental-webgl' || type === 'webgl2') {
return {
canvas: this,
getExtension(name) {
if (name === 'WEBGL_debug_renderer_info') return { UNMASKED_VENDOR_WEBGL: 0x9245, UNMASKED_RENDERER_WEBGL: 0x9246 };
return null;
},
getParameter(pname) {
if (pname === 0x9245) return _fp('gpuVendor');
if (pname === 0x9246) return _fp('gpu');
if (pname === 0x1F01) return 'WebKit WebGL'; // GL_RENDERER
if (pname === 0x1F00) return 'WebKit'; // GL_VENDOR
if (pname === 0x1F02) return 'OpenGL ES 3.0 (ANGLE)'; // GL_VERSION
if (pname === 0x8B8C) return 'WebGL GLSL ES 3.00 (ANGLE)'; // GL_SHADING_LANGUAGE_VERSION
return 0;
},
getSupportedExtensions() { return ['WEBGL_debug_renderer_info','EXT_texture_filter_anisotropic','WEBGL_compressed_texture_s3tc','WEBGL_lose_context']; },
getShaderPrecisionFormat() { return { rangeMin: 127, rangeMax: 127, precision: 23 }; },
createBuffer() { return {}; }, createShader() { return {}; }, createProgram() { return {}; },
shaderSource() {}, compileShader() {}, attachShader() {}, linkProgram() {},
getProgramParameter() { return true; }, useProgram() {}, deleteShader() {},
bindBuffer() {}, bufferData() {}, enableVertexAttribArray() {}, vertexAttribPointer() {},
drawArrays() {}, drawElements() {}, viewport() {}, clear() {}, clearColor() {},
enable() {}, disable() {}, blendFunc() {}, depthFunc() {},
getUniformLocation() { return {}; }, getAttribLocation() { return 0; },
uniform1f() {}, uniform1i() {}, uniformMatrix4fv() {},
createTexture() { return {}; }, bindTexture() {}, texImage2D() {}, texParameteri() {},
activeTexture() {}, pixelStorei() {}, generateMipmap() {},
createFramebuffer() { return {}; }, bindFramebuffer() {}, framebufferTexture2D() {},
readPixels(x,y,w,h,f,t,d) { if(d) for(let i=0;i<d.length;i++) d[i]=Math.floor(Math.random()*256); },
VERTEX_SHADER: 0x8B31, FRAGMENT_SHADER: 0x8B30, LINK_STATUS: 0x8B82,
ARRAY_BUFFER: 0x8892, STATIC_DRAW: 0x88E4, FLOAT: 0x1406,
TRIANGLES: 0x0004, COLOR_BUFFER_BIT: 0x4000, DEPTH_BUFFER_BIT: 0x100,
TEXTURE_2D: 0x0DE1, RGBA: 0x1908, UNSIGNED_BYTE: 0x1401,
};
}
return null;
};
Element.prototype.toDataURL = function(type) {
if (this._ctx && this._ctx._buf) {
const ctx = this._ctx;
const w = ctx._w, h = ctx._h, buf = ctx._buf;
let hash = _fpSeed;
for (let i = 0; i < buf.length; i += 37) {
hash = ((hash << 5) - hash + buf[i]) | 0;
}
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
let b64 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUg';
for (let i = 0; i < 60; i++) {
hash = ((hash << 5) - hash + i) | 0;
b64 += chars[(hash >>> 0) % 64];
}
return b64 + '==';
}
return _fp('canvasFingerprint');
};
Element.prototype.toBlob = function(cb, type, q) { cb(new Blob([''])); };
_markNative(Element.prototype.getContext);
_markNative(Element.prototype.toDataURL);
_markNative(Element.prototype.toBlob);
Element.prototype.attachShadow = function attachShadow(opts) {
const host = this;
const children = [];
const shadow = {
mode: opts?.mode || 'open',
host: host,
get innerHTML() { return children.map(c => c.outerHTML || c.textContent || '').join(''); },
set innerHTML(v) {
children.length = 0;
if (v) {
const tmp = document.createElement('div');
tmp.innerHTML = v;
for (let i = 0; i < tmp.childNodes.length; i++) children.push(tmp.childNodes[i]);
}
},
get childNodes() { return children; },
get firstChild() { return children[0] || null; },
get lastChild() { return children[children.length - 1] || null; },
get firstElementChild() { return children.find(c => c.nodeType === 1) || null; },
get children() { return children.filter(c => c.nodeType === 1); },
appendChild(c) {
if (c) {
children.push(c);
try { c.parentNode = shadow; } catch (_) { /* parentNode is getter-only on Node, ignore */ }
}
return c;
},
insertBefore(n, ref) {
if (!n) return n;
if (!ref) { shadow.appendChild(n); return n; }
const idx = children.indexOf(ref);
if (idx >= 0) {
children.splice(idx, 0, n);
try { n.parentNode = shadow; } catch (_) {}
}
else shadow.appendChild(n);
return n;
},
removeChild(c) { const idx = children.indexOf(c); if (idx >= 0) children.splice(idx, 1); return c; },
replaceChild(n, o) {
const idx = children.indexOf(o);
if (idx >= 0) {
children[idx] = n;
try { n.parentNode = shadow; } catch (_) {}
}
return o;
},
querySelector(s) {
for (const c of children) {
if (c.matches && c.matches(s)) return c;
if (c.querySelector) { const r = c.querySelector(s); if (r) return r; }
}
return null;
},
querySelectorAll(s) {
const results = [];
for (const c of children) {
if (c.matches && c.matches(s)) results.push(c);
if (c.querySelectorAll) results.push(...c.querySelectorAll(s));
}
return results;
},
getElementById(id) { return shadow.querySelector('#' + id); },
contains(n) { return children.includes(n); },
getRootNode() { return shadow; },
get ownerDocument() { return document; },
get nodeType() { return 11; }, // DOCUMENT_FRAGMENT_NODE
get nodeName() { return '#document-fragment'; },
addEventListener() {}, removeEventListener() {}, dispatchEvent() { return true; },
cloneNode() { return shadow; },
};
this.shadowRoot = shadow;
return shadow;
};
_markNative(Element.prototype.attachShadow);
globalThis.AudioContext = class AudioContext {
constructor() { this.sampleRate=_fp('audioSampleRate'); this.state='running'; this.currentTime=0; this.baseLatency=_fp('audioBaseLatency'); this.destination={maxChannelCount:2,numberOfInputs:1,numberOfOutputs:0,channelCount:2}; }
createOscillator() { return {type:'sine',frequency:{value:440,setValueAtTime(){}},connect(){},start(){},stop(){},disconnect(){},addEventListener(){}}; }
createDynamicsCompressor() { return {threshold:{value:_fp('compThreshold')},knee:{value:_fp('compKnee')},ratio:{value:_fp('compRatio')},attack:{value:0.003},release:{value:0.25},reduction:0,connect(){},disconnect(){}}; }
createAnalyser() {
return {fftSize:2048,frequencyBinCount:1024,connect(){},disconnect(){},
getByteFrequencyData(a){for(let i=0;i<a.length;i++)a[i]=Math.floor(_fpRand(600+i)*10);},
getFloatFrequencyData(a){for(let i=0;i<a.length;i++)a[i]=-100+_fpRand(700+i)*5;}
};
}
createGain() { return {gain:{value:1,setValueAtTime(){}},connect(){},disconnect(){}}; }
createBiquadFilter() { return {type:'lowpass',frequency:{value:350},Q:{value:1},connect(){},disconnect(){}}; }
createBufferSource() { return {buffer:null,connect(){},start(){},stop(){},disconnect(){},loop:false}; }
createBuffer(ch,len,rate) { return {length:len,sampleRate:rate,numberOfChannels:ch,getChannelData(c){return new Float32Array(len);},duration:len/rate}; }
createScriptProcessor() { return {connect(){},disconnect(){},onaudioprocess:null}; }
decodeAudioData(buf) { return Promise.resolve(this.createBuffer(2,44100,44100)); }
resume() { this.state='running'; return Promise.resolve(); }
suspend() { this.state='suspended'; return Promise.resolve(); }
close() { this.state='closed'; return Promise.resolve(); }
};
globalThis.OfflineAudioContext = class OfflineAudioContext extends AudioContext {
constructor(ch,len,rate) { super(); this.length=len||44100; }
startRendering() { return Promise.resolve(this.createBuffer(2,this.length,44100)); }
};
globalThis.webkitAudioContext = globalThis.AudioContext;
globalThis.speechSynthesis = {
speaking: false, pending: false, paused: false,
getVoices() { return [{ name:'Google US English', lang:'en-US', default:true, localService:true, voiceURI:'Google US English' }]; },
speak() {}, cancel() {}, pause() {}, resume() {},
addEventListener() {}, removeEventListener() {},
onvoiceschanged: null,
};
globalThis.SpeechSynthesisUtterance = class SpeechSynthesisUtterance { constructor(t){this.text=t;this.lang='en-US';this.rate=1;this.pitch=1;this.volume=1;} };
globalThis.MediaStream = class MediaStream { constructor(){this.id='';this.active=true;} getTracks(){return [];} getAudioTracks(){return [];} getVideoTracks(){return [];} addTrack(){} removeTrack(){} clone(){return new MediaStream();} };
globalThis.MediaStreamTrack = class MediaStreamTrack { constructor(){this.kind='';this.enabled=true;this.readyState='live';} stop(){} clone(){return new MediaStreamTrack();} };
globalThis.RTCPeerConnection = class RTCPeerConnection {
constructor(){this.localDescription=null;this.remoteDescription=null;this.iceConnectionState='new';this.iceGatheringState='new';this.signalingState='stable';this.connectionState='new';}
createOffer(){return Promise.resolve({type:'offer',sdp:''});}
createAnswer(){return Promise.resolve({type:'answer',sdp:''});}
setLocalDescription(){return Promise.resolve();}
setRemoteDescription(){return Promise.resolve();}
addIceCandidate(){return Promise.resolve();}
close(){}
createDataChannel(){return {close(){},send(){},addEventListener(){},removeEventListener(){}};}
addEventListener(){} removeEventListener(){}
getStats(){return Promise.resolve(new Map());}
};
globalThis.RTCSessionDescription = class RTCSessionDescription { constructor(d){this.type=d?.type;this.sdp=d?.sdp;} };
globalThis.RTCIceCandidate = class RTCIceCandidate { constructor(d){this.candidate=d?.candidate||'';} };
// Minimal but spec-shape-correct IndexedDB shim. We don't persist anything,
// but authentication libraries (Firebase, Supabase, dexie) hang forever on
// the first `get` because their request's `onsuccess` is never called. Fire
// `onsuccess` asynchronously with `null` so reads complete-but-empty, which
// most libraries treat as a cache miss and fall back to the network.
function _idbRequest(produceResult) {
const req = {
result: undefined,
error: null,
source: null,
transaction: null,
readyState: 'pending',
onsuccess: null,
onerror: null,
addEventListener(type, fn) { req['on' + type] = fn; },
removeEventListener(type, fn) { if (req['on' + type] === fn) req['on' + type] = null; },
};
Promise.resolve().then(() => {
try {
req.result = produceResult();
req.readyState = 'done';
if (typeof req.onsuccess === 'function') {
try { req.onsuccess({ target: req, type: 'success' }); } catch (e) {}
}
} catch (e) {
req.error = e; req.readyState = 'done';
if (typeof req.onerror === 'function') {
try { req.onerror({ target: req, type: 'error' }); } catch (e2) {}
}
}
});
return req;
}
function _idbObjectStore(name) {
const data = new Map();
return {
name,
keyPath: null,
autoIncrement: false,
indexNames: { contains() { return false; }, length: 0, item() { return null; } },
transaction: null,
add(value, key) { const k = key ?? Date.now(); data.set(k, value); return _idbRequest(() => k); },
put(value, key) { const k = key ?? Date.now(); data.set(k, value); return _idbRequest(() => k); },
get(key) { return _idbRequest(() => data.get(key) ?? undefined); },
getAll() { return _idbRequest(() => Array.from(data.values())); },
getAllKeys() { return _idbRequest(() => Array.from(data.keys())); },
getKey(key) { return _idbRequest(() => (data.has(key) ? key : undefined)); },
delete(key) { return _idbRequest(() => { data.delete(key); return undefined; }); },
clear() { return _idbRequest(() => { data.clear(); return undefined; }); },
count() { return _idbRequest(() => data.size); },
openCursor() { return _idbRequest(() => null); },
openKeyCursor() { return _idbRequest(() => null); },
createIndex() { return { name: '', keyPath: '', unique: false, multiEntry: false, get() { return _idbRequest(() => undefined); } }; },
index() { return { get() { return _idbRequest(() => undefined); }, getAll() { return _idbRequest(() => []); }, count() { return _idbRequest(() => 0); }, openCursor() { return _idbRequest(() => null); } }; },
deleteIndex() {},
};
}
function _idbTransaction(storeNames) {
const stores = new Map();
const names = Array.isArray(storeNames) ? storeNames : [storeNames];
for (const n of names) stores.set(String(n), _idbObjectStore(String(n)));
const tx = {
db: null,
mode: 'readonly',
objectStoreNames: { contains: (n) => stores.has(String(n)), length: stores.size },
onabort: null, oncomplete: null, onerror: null,
error: null,
objectStore(name) {
let s = stores.get(name);
if (!s) { s = _idbObjectStore(name); stores.set(name, s); }
s.transaction = tx;
return s;
},
abort() {},
commit() {},
addEventListener(type, fn) { tx['on' + type] = fn; },
removeEventListener(type, fn) { if (tx['on' + type] === fn) tx['on' + type] = null; },
};
Promise.resolve().then(() => {
if (typeof tx.oncomplete === 'function') {
try { tx.oncomplete({ target: tx, type: 'complete' }); } catch (e) {}
}
});
return tx;
}
function _idbDatabase(name, version) {
return {
name,
version,
objectStoreNames: { contains() { return false; }, length: 0, item() { return null; } },
createObjectStore(n) { return _idbObjectStore(n); },
deleteObjectStore() {},
transaction(storeNames, mode) {
const tx = _idbTransaction(storeNames);
tx.mode = mode || 'readonly';
return tx;
},
close() {},
onversionchange: null, onabort: null, onerror: null, onclose: null,
addEventListener() {}, removeEventListener() {},
};
}
globalThis.indexedDB = {
open(name, version) {
return _idbRequest(() => _idbDatabase(name, version || 1));
},
deleteDatabase(_name) { return _idbRequest(() => undefined); },
databases() { return Promise.resolve([]); },
cmp(a, b) { return a < b ? -1 : a > b ? 1 : 0; },
};
globalThis.IDBKeyRange = {
only(v) { return { lower: v, upper: v, lowerOpen: false, upperOpen: false, includes(x) { return x === v; } }; },
lowerBound(v, open) { return { lower: v, upper: null, lowerOpen: !!open, upperOpen: false, includes(x) { return open ? x > v : x >= v; } }; },
upperBound(v, open) { return { lower: null, upper: v, lowerOpen: false, upperOpen: !!open, includes(x) { return open ? x < v : x <= v; } }; },
bound(l, u, lo, uo) { return { lower: l, upper: u, lowerOpen: !!lo, upperOpen: !!uo, includes(x) { return (lo ? x > l : x >= l) && (uo ? x < u : x <= u); } }; },
};
globalThis.caches = {
open() { return Promise.resolve({ match(){return Promise.resolve(undefined);}, put(){return Promise.resolve();}, delete(){return Promise.resolve(false);}, keys(){return Promise.resolve([]);} }); },
match() { return Promise.resolve(undefined); },
has() { return Promise.resolve(false); },
delete() { return Promise.resolve(false); },
keys() { return Promise.resolve([]); },
};
_markNative(AudioContext); _markNative(OfflineAudioContext);
_markNative(SpeechSynthesisUtterance);
_markNative(MediaStream); _markNative(MediaStreamTrack);
_markNative(RTCPeerConnection); _markNative(RTCSessionDescription); _markNative(RTCIceCandidate);
const _OrigDateTimeFormat = Intl.DateTimeFormat;
const _defaultTZ = 'Europe/Germany';
Intl.DateTimeFormat = function(locales, options) {
if (!options) options = {};
if (!options.timeZone) options.timeZone = _defaultTZ;
return new _OrigDateTimeFormat(locales, options);
};
Intl.DateTimeFormat.prototype = _OrigDateTimeFormat.prototype;
Intl.DateTimeFormat.supportedLocalesOf = _OrigDateTimeFormat.supportedLocalesOf;
const _origResolved = _OrigDateTimeFormat.prototype.resolvedOptions;
_OrigDateTimeFormat.prototype.resolvedOptions = function() {
const r = _origResolved.call(this);
if (r.timeZone === 'UTC') r.timeZone = _defaultTZ;
return r;
};
if (typeof PointerEvent === 'undefined') {
globalThis.PointerEvent = class PointerEvent extends MouseEvent {
constructor(type, opts={}) { super(type, opts); this.pointerId = opts.pointerId || 0; this.width = opts.width || 1; this.height = opts.height || 1; this.pressure = opts.pressure || 0; this.pointerType = opts.pointerType || 'mouse'; }
};
}
if (typeof navigator.credentials === 'undefined') {
navigator.credentials = { get(){return Promise.resolve(null);}, create(){return Promise.resolve(null);}, store(){return Promise.resolve();}, preventSilentAccess(){return Promise.resolve();} };
}
globalThis.opener = null;
globalThis.Worker = class Worker {
constructor(url) {
this.onmessage = null;
this.onerror = null;
this._terminated = false;
this._listeners = {};
const worker = this;
if (typeof url === 'string' && (url.startsWith('blob:') || url.startsWith('http'))) {
const blobContent = globalThis.__blobStore?.[url];
if (blobContent) {
this._code = blobContent;
} else {
(async () => {
try {
const resp = await fetch(url);
worker._code = await resp.text();
} catch(e) { if (worker.onerror) worker.onerror(e); }
})();
}
}
}
postMessage(data) {
if (this._terminated) return;
const worker = this;
setTimeout(() => {
if (worker._terminated || !worker._code) return;
try {
const workerSelf = {
onmessage: null,
postMessage: (msg) => {
const evt = { data: msg };
if (worker.onmessage) worker.onmessage(evt);
const handlers = worker._listeners['message'] || [];
for (const h of handlers) h(evt);
},
addEventListener: (type, fn) => { workerSelf['on' + type] = fn; },
close: () => { worker._terminated = true; },
crypto: globalThis.crypto,
TextEncoder: globalThis.TextEncoder,
TextDecoder: globalThis.TextDecoder,
atob: globalThis.atob,
btoa: globalThis.btoa,
setTimeout: globalThis.setTimeout,
setInterval: globalThis.setInterval,
clearTimeout: globalThis.clearTimeout,
clearInterval: globalThis.clearInterval,
fetch: globalThis.fetch,
console: globalThis.console,
};
const fn = new Function('self', 'postMessage', 'addEventListener', 'close', worker._code);
fn(workerSelf, workerSelf.postMessage, workerSelf.addEventListener, workerSelf.close);
if (workerSelf.onmessage) workerSelf.onmessage({ data });
} catch(e) {
console.error('Worker error:', e.message);
if (worker.onerror) worker.onerror(e);
}
}, 0);
}
terminate() { this._terminated = true; }
addEventListener(type, fn) {
if (!this._listeners[type]) this._listeners[type] = [];
this._listeners[type].push(fn);
}
removeEventListener(type, fn) {
if (this._listeners[type]) this._listeners[type] = this._listeners[type].filter(h => h !== fn);
}
};
globalThis.__blobStore = globalThis.__blobStore || {};
const _origCreateObjectURL = URL.createObjectURL;
URL.createObjectURL = function(blob) {
if (blob && typeof blob.text === 'function') {
const id = 'blob:obscura/' + Math.random().toString(36).substring(2);
blob.text().then(text => { globalThis.__blobStore[id] = text; });
return id;
}
return 'blob:obscura/fallback';
};
URL.revokeObjectURL = function(url) {
delete globalThis.__blobStore[url];
};
globalThis.scrollTo = function(x, y) {};
globalThis.scrollBy = function(x, y) {};
globalThis.scroll = function(x, y) {};
globalThis.focus = function() {};
globalThis.blur = function() {};
globalThis.print = function() {};
globalThis.alert = function() {};
globalThis.confirm = function() { return true; };
globalThis.prompt = function() { return null; };
globalThis.open = function() { return null; };
globalThis.close = function() {};
globalThis.stop = function() {};
globalThis.postMessage = function() {};
globalThis.requestIdleCallback = globalThis.requestIdleCallback || function(cb) { return setTimeout(cb, 0); };
globalThis.cancelIdleCallback = globalThis.cancelIdleCallback || function(id) { clearTimeout(id); };
if (typeof ReadableStream === 'undefined') {
globalThis.ReadableStream = class ReadableStream {
constructor(source = {}, strategy = {}) {
this._source = source; this._queue = []; this._closed = false;
this.locked = false;
if (source.start) source.start({ enqueue: (chunk) => this._queue.push(chunk), close: () => { this._closed = true; }, error: () => {} });
}
getReader() {
this.locked = true;
const stream = this;
return {
read() {
if (stream._queue.length > 0) return Promise.resolve({ value: stream._queue.shift(), done: false });
if (stream._closed) return Promise.resolve({ value: undefined, done: true });
return Promise.resolve({ value: undefined, done: true });
},
releaseLock() { stream.locked = false; },
cancel() { stream._closed = true; return Promise.resolve(); },
get closed() { return stream._closed ? Promise.resolve() : new Promise(() => {}); },
};
}
cancel() { this._closed = true; return Promise.resolve(); }
pipeTo(dest) { return Promise.resolve(); }
pipeThrough(transform) { return transform.readable || new ReadableStream(); }
tee() { return [new ReadableStream(), new ReadableStream()]; }
[Symbol.asyncIterator]() {
const reader = this.getReader();
return { next: () => reader.read(), return: () => { reader.releaseLock(); return Promise.resolve({done:true}); } };
}
};
}
if (typeof WritableStream === 'undefined') {
globalThis.WritableStream = class WritableStream {
constructor(sink = {}) { this._sink = sink; this.locked = false; }
getWriter() {
this.locked = true;
const stream = this;
return {
write(chunk) { if (stream._sink.write) stream._sink.write(chunk); return Promise.resolve(); },
close() { if (stream._sink.close) stream._sink.close(); return Promise.resolve(); },
abort() { return Promise.resolve(); },
releaseLock() { stream.locked = false; },
get ready() { return Promise.resolve(); },
get closed() { return Promise.resolve(); },
get desiredSize() { return 1; },
};
}
close() { return Promise.resolve(); }
abort() { return Promise.resolve(); }
};
}
if (typeof TransformStream === 'undefined') {
globalThis.TransformStream = class TransformStream {
constructor(transformer = {}) {
this.readable = new ReadableStream();
this.writable = new WritableStream();
}
};
}
if (!globalThis.crypto) globalThis.crypto = {};
if (!globalThis.crypto.subtle) {
globalThis.crypto.subtle = {
async digest(algorithm, data) {
// Real WebCrypto digest. Delegates to `op_subtle_digest` which runs
// the actual SHA-1/256/384/512 via Rust's `sha1` and `sha2` crates.
// The previous JS implementation was a custom FNV variant that
// produced bytes shaped like the hash but with wrong contents, so
// SRI checks, JWS signature verification, and OAuth PKCE silently
// accepted invalid input.
const name = (typeof algorithm === 'string' ? algorithm : algorithm?.name) || 'SHA-256';
let bytes;
if (data instanceof ArrayBuffer) bytes = new Uint8Array(data);
else if (ArrayBuffer.isView(data)) bytes = new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
else bytes = new Uint8Array(data || []);
const out = Deno.core.ops.op_subtle_digest(name, bytes);
return new Uint8Array(out).buffer;
},
async encrypt() { throw new DOMException('NotSupportedError'); },
async decrypt() { throw new DOMException('NotSupportedError'); },
async sign() { return new ArrayBuffer(32); },
async verify() { return true; },
async generateKey() { return { type: 'secret', algorithm: {}, extractable: false, usages: [] }; },
async importKey() { return { type: 'secret', algorithm: {}, extractable: false, usages: [] }; },
async exportKey() { return new ArrayBuffer(32); },
async deriveBits() { return new ArrayBuffer(32); },
async deriveKey() { return { type: 'secret', algorithm: {}, extractable: false, usages: [] }; },
async wrapKey() { return new ArrayBuffer(32); },
async unwrapKey() { return { type: 'secret', algorithm: {}, extractable: false, usages: [] }; },
};
}
if (typeof DOMRect === 'undefined') {
globalThis.DOMRect = class DOMRect {
constructor(x=0,y=0,w=0,h=0) { this.x=x;this.y=y;this.width=w;this.height=h;this.top=y;this.right=x+w;this.bottom=y+h;this.left=x; }
toJSON() { return {x:this.x,y:this.y,width:this.width,height:this.height,top:this.top,right:this.right,bottom:this.bottom,left:this.left}; }
static fromRect(r={}) { return new DOMRect(r.x,r.y,r.width,r.height); }
};
}
if (typeof DOMPoint === 'undefined') {
globalThis.DOMPoint = class DOMPoint {
constructor(x=0,y=0,z=0,w=1) { this.x=x;this.y=y;this.z=z;this.w=w; }
static fromPoint(p={}) { return new DOMPoint(p.x,p.y,p.z,p.w); }
};
}
if (typeof DOMMatrix === 'undefined') {
globalThis.DOMMatrix = class DOMMatrix {
constructor() { this.a=1;this.b=0;this.c=0;this.d=1;this.e=0;this.f=0;this.is2D=true;this.isIdentity=true; }
static fromMatrix() { return new DOMMatrix(); }
static fromFloat32Array() { return new DOMMatrix(); }
static fromFloat64Array() { return new DOMMatrix(); }
multiply() { return new DOMMatrix(); }
inverse() { return new DOMMatrix(); }
translate() { return new DOMMatrix(); }
scale() { return new DOMMatrix(); }
rotate() { return new DOMMatrix(); }
transformPoint(p) { return new DOMPoint(p?.x||0,p?.y||0); }
};
}
if (typeof Image === 'undefined') {
globalThis.Image = class Image {
constructor(w, h) { this.width = w || 0; this.height = h || 0; this.src = ''; this.onload = null; this.onerror = null; this.complete = false; this.naturalWidth = 0; this.naturalHeight = 0; }
addEventListener() {} removeEventListener() {}
setAttribute(k, v) { this[k] = v; if (k === 'src' && this.onload) setTimeout(() => { this.complete = true; this.onload(); }, 0); }
getAttribute(k) { return this[k]; }
};
}
if (typeof Audio === 'undefined') {
globalThis.Audio = class Audio {
constructor(src) { this.src = src || ''; this.paused = true; this.volume = 1; this.currentTime = 0; this.duration = 0; }
play() { return Promise.resolve(); } pause() { this.paused = true; } load() {}
addEventListener() {} removeEventListener() {}
};
}
if (typeof FileReader === 'undefined') {
globalThis.FileReader = class FileReader {
constructor() { this.result = null; this.readyState = 0; this.onload = null; this.onerror = null; }
readAsText(blob) { if (blob?.text) blob.text().then(t => { this.result = t; this.readyState = 2; if (this.onload) this.onload({target:this}); }); }
readAsDataURL(blob) { this.result = 'data:;base64,'; this.readyState = 2; if (this.onload) setTimeout(() => this.onload({target:this}), 0); }
readAsArrayBuffer(blob) { this.result = new ArrayBuffer(0); this.readyState = 2; if (this.onload) setTimeout(() => this.onload({target:this}), 0); }
abort() { this.readyState = 0; }
addEventListener(t, fn) { if (t === 'load') this.onload = fn; }
removeEventListener() {}
};
}
// Real network sockets aren't implemented; we don't have a runtime WS / SSE
// client in V8. But pages that wait for an `open` event (Vite HMR clients
// embedded on docs sites, live-dashboards, anything calling
// `await new Promise(r => ws.addEventListener('open', r))`) silently hang
// forever otherwise. Fire `open` after a microtask so the consumer at least
// proceeds; subsequent messages never arrive, which is no worse than the
// current "no signal whatsoever" behaviour.
// Minimal EventTarget shared by socket-like classes. Real `EventTarget` is
// currently aliased to `Node`, which would drag DOM-tree assumptions into a
// `WebSocket`. Defining a private shim avoids that.
function _makeListenerBox(self) {
const map = new Map();
self.addEventListener = function (type, fn) {
if (typeof fn !== 'function') return;
let bucket = map.get(type);
if (!bucket) { bucket = []; map.set(type, bucket); }
bucket.push(fn);
};
self.removeEventListener = function (type, fn) {
const bucket = map.get(type);
if (!bucket) return;
const i = bucket.indexOf(fn);
if (i >= 0) bucket.splice(i, 1);
};
self.dispatchEvent = function (event) {
const bucket = map.get(event.type);
if (!bucket) return true;
for (const fn of bucket.slice()) {
try { fn.call(self, event); } catch (e) { /* swallow */ }
}
return true;
};
}
if (typeof EventSource === 'undefined') {
globalThis.EventSource = class EventSource {
constructor(url, init) {
this.url = url;
this.readyState = 0; // CONNECTING
this.withCredentials = !!(init && init.withCredentials);
this.onopen = null; this.onmessage = null; this.onerror = null;
_makeListenerBox(this);
Promise.resolve().then(() => {
if (this.readyState !== 0) return;
this.readyState = 1; // OPEN
const ev = new Event('open');
if (typeof this.onopen === 'function') { try { this.onopen(ev); } catch (e) {} }
try { this.dispatchEvent(ev); } catch (e) {}
});
}
close() { this.readyState = 2; }
static CONNECTING = 0; static OPEN = 1; static CLOSED = 2;
};
}
if (typeof WebSocket === 'undefined') {
globalThis.WebSocket = class WebSocket {
constructor(url, protocols) {
this.url = url;
this.readyState = 0; // CONNECTING
this.bufferedAmount = 0;
this.binaryType = 'blob';
this.extensions = '';
this.protocol = Array.isArray(protocols) ? (protocols[0] || '') : (protocols || '');
this.onopen = null; this.onmessage = null; this.onerror = null; this.onclose = null;
_makeListenerBox(this);
Promise.resolve().then(() => {
if (this.readyState !== 0) return;
this.readyState = 1; // OPEN
const ev = new Event('open');
if (typeof this.onopen === 'function') { try { this.onopen(ev); } catch (e) {} }
try { this.dispatchEvent(ev); } catch (e) {}
});
}
send(data) { /* drop; no real socket */ }
close(code, reason) {
if (this.readyState >= 2) return;
this.readyState = 3; // CLOSED
const ev = new Event('close');
ev.code = code || 1000; ev.reason = reason || ''; ev.wasClean = true;
if (typeof this.onclose === 'function') { try { this.onclose(ev); } catch (e) {} }
try { this.dispatchEvent(ev); } catch (e) {}
}
static CONNECTING = 0; static OPEN = 1; static CLOSING = 2; static CLOSED = 3;
};
}
if (typeof BroadcastChannel === 'undefined') {
globalThis.BroadcastChannel = class BroadcastChannel {
constructor(name) {
this.name = name; this.onmessage = null; this.onmessageerror = null;
_makeListenerBox(this);
}
postMessage(msg) {}
close() {}
};
}
if (typeof MediaQueryList === 'undefined') {
globalThis.MediaQueryList = class MediaQueryList {
constructor(q) { this.media = q || ''; this.matches = false; }
addListener() {} removeListener() {} addEventListener() {} removeEventListener() {}
};
}
if (typeof ImageData === 'undefined') {
globalThis.ImageData = class ImageData {
constructor(w, h) {
if (w instanceof Uint8ClampedArray) { this.data = w; this.width = h; this.height = w.length / (4 * h); }
else { this.width = w; this.height = h; this.data = new Uint8ClampedArray(w * h * 4); }
}
};
}
if (typeof CanvasRenderingContext2D === 'undefined') {
globalThis.CanvasRenderingContext2D = class CanvasRenderingContext2D {};
}
if (typeof OffscreenCanvas === 'undefined') {
globalThis.OffscreenCanvas = class OffscreenCanvas {
constructor(w, h) { this.width = w; this.height = h; }
getContext(type) { return globalThis.document?.createElement('canvas')?.getContext(type) || null; }
convertToBlob() { return Promise.resolve(new Blob([''])); }
transferToImageBitmap() { return {}; }
};
}
if (typeof Path2D === 'undefined') {
globalThis.Path2D = class Path2D { constructor(){} moveTo(){} lineTo(){} arc(){} rect(){} closePath(){} addPath(){} };
}
if (typeof ImageBitmap === 'undefined') {
globalThis.ImageBitmap = class ImageBitmap { constructor(){this.width=0;this.height=0;} close(){} };
globalThis.createImageBitmap = function() { return Promise.resolve(new ImageBitmap()); };
}
if (typeof Selection === 'undefined') {
globalThis.Selection = class Selection {
constructor(){this.anchorNode=null;this.focusNode=null;this.rangeCount=0;this.isCollapsed=true;this.type='None';}
getRangeAt(){return null;} collapse(){} extend(){} selectAllChildren(){} deleteFromDocument(){}
addRange(){} removeRange(){} removeAllRanges(){} toString(){return '';}
};
}
if (typeof NodeFilter === 'undefined') {
globalThis.NodeFilter = { SHOW_ALL:0xFFFFFFFF, SHOW_ELEMENT:1, SHOW_TEXT:4, SHOW_COMMENT:128,
FILTER_ACCEPT:1, FILTER_REJECT:2, FILTER_SKIP:3 };
}
if (typeof TreeWalker === 'undefined') {
globalThis.TreeWalker = class TreeWalker {
constructor(root){this.root=root;this.currentNode=root;this.whatToShow=0xFFFFFFFF;this.filter=null;}
parentNode(){return this.currentNode?.parentNode||null;}
firstChild(){return this.currentNode?.firstChild||null;}
lastChild(){return this.currentNode?.lastChild||null;}
previousSibling(){return this.currentNode?.previousSibling||null;}
nextSibling(){return this.currentNode?.nextSibling||null;}
nextNode(){return null;} previousNode(){return null;}
};
}
if (typeof Range === 'undefined') {
globalThis.Range = class Range {
constructor(){this.startContainer=null;this.startOffset=0;this.endContainer=null;this.endOffset=0;this.collapsed=true;this.commonAncestorContainer=null;}
setStart(n,o){this.startContainer=n;this.startOffset=o;} setEnd(n,o){this.endContainer=n;this.endOffset=o;}
collapse(){} selectNode(){} selectNodeContents(){} cloneContents(){return document?.createDocumentFragment();}
deleteContents(){} insertNode(){} getBoundingClientRect(){return new DOMRect();}
getClientRects(){return [];} cloneRange(){return new Range();} toString(){return '';}
};
}
if (typeof SharedWorker === 'undefined') {
globalThis.SharedWorker = class SharedWorker {
constructor() { this.port = { postMessage(){}, onmessage:null, start(){}, close(){}, addEventListener(){}, removeEventListener(){} }; this.onerror = null; }
};
}
if (typeof ServiceWorkerContainer === 'undefined') {
globalThis.ServiceWorkerContainer = class { register(){return Promise.resolve();} getRegistrations(){return Promise.resolve([]);} };
}
if (typeof URLPattern === 'undefined') {
globalThis.URLPattern = class URLPattern {
constructor(pattern){this._pattern=pattern||{};} test(){return false;} exec(){return null;}
};
}
if (typeof Document !== 'undefined' && !Document.prototype.importNode) {
Document.prototype.importNode = function(node, deep) { return node?.cloneNode(!!deep) || null; };
}
// Document.elementFromPoint / elementsFromPoint — no layout engine, so this is a stub:
// in-viewport coords return <body> (or <html> as fallback), out-of-viewport returns null.
// Wrong-but-non-throwing beats "undefined", which traps ad/analytics bootstraps in retry loops
// (see issue #63).
if (typeof Document !== 'undefined' && !Document.prototype.elementFromPoint) {
// Real hit testing against the synthetic bboxes from getBoundingClientRect.
// Flat iteration over every element, NOT a tree walk: our synthetic rects
// don't form a proper containment hierarchy (a child's rect can lie far
// outside its parent's), so a tree walk that only descends into ancestors
// containing (x,y) would never reach a deep <input> inside <label><p>.
// Returns the deepest matching element (highest nid wins as a proxy for
// tree depth) so descendants beat ancestors.
Document.prototype.elementFromPoint = function(x, y) {
if (typeof x !== 'number' || typeof y !== 'number' || !isFinite(x) || !isFinite(y)) {
return null;
}
var w = (typeof window !== 'undefined' && window.innerWidth) || 1280;
var h = (typeof window !== 'undefined' && window.innerHeight) || 720;
if (x < 0 || y < 0 || x > w || y > h) return null;
var all = this.querySelectorAll('*');
var best = null;
var bestNid = -1;
for (var i = 0; i < all.length; i++) {
var el = all[i];
if (!el || !el.getBoundingClientRect) continue;
// documentElement / body span the viewport; skip them so we pick a
// real descendant instead of falling back to <html>/<body>.
if (el === this.documentElement || el === this.body) continue;
var r = el.getBoundingClientRect();
if (r.width === 0 || r.height === 0) continue;
if (x >= r.left && x <= r.right && y >= r.top && y <= r.bottom) {
var nid = el._nid | 0;
if (nid > bestNid) { best = el; bestNid = nid; }
}
}
return best || this.body || this.documentElement || null;
};
Document.prototype.elementsFromPoint = function(x, y) {
var el = this.elementFromPoint(x, y);
return el ? [el] : [];
};
}
if (typeof ShadowRoot !== 'undefined' && !ShadowRoot.prototype.elementFromPoint) {
ShadowRoot.prototype.elementFromPoint = function(x, y) {
return Document.prototype.elementFromPoint.call(globalThis.document || this, x, y);
};
ShadowRoot.prototype.elementsFromPoint = function(x, y) {
return Document.prototype.elementsFromPoint.call(globalThis.document || this, x, y);
};
}
globalThis.__obscura_init = function() {
_fpSeed = Date.now() ^ (Math.random() * 0xFFFFFFFF >>> 0);
_fpCache = null;
_installWasmStreamingFallback();
globalThis.document = new Document(+_dom("document_node_id"));
const scr = _fp('screen');
const sw = scr[0], sh = scr[1];
globalThis.screen = { width:sw, height:sh, availWidth:sw, availHeight:sh-40, colorDepth:24, pixelDepth:24, availTop:0, availLeft:0, orientation:{type:"landscape-primary",angle:0,addEventListener(){},removeEventListener(){},dispatchEvent(){return true;}} };
globalThis.visualViewport = { width:sw, height:sh-80, offsetLeft:0, offsetTop:0, scale:1, addEventListener(){}, removeEventListener(){} };
globalThis.devicePixelRatio = sw >= 2560 ? 2 : 1;
globalThis.innerWidth = sw; globalThis.innerHeight = sh - 80;
globalThis.outerWidth = sw; globalThis.outerHeight = sh;
var hwValues = [2, 4, 6, 8, 12, 16];
globalThis.navigator.hardwareConcurrency = hwValues[Math.floor(_fpRand(400) * hwValues.length)];
var memValues = [0.25, 0.5, 1, 2, 4, 8];
globalThis.navigator.deviceMemory = memValues[Math.floor(_fpRand(401) * memValues.length)];
const t0 = Date.now() + Math.floor(_fpRand(641) * 100) - 50;
globalThis.performance.timeOrigin = t0;
globalThis.performance.timing = { navigationStart: t0, domContentLoadedEventEnd: t0, loadEventEnd: t0 };
globalThis.performance.memory = {
jsHeapSizeLimit: 2172649472,
totalJSHeapSize: 15000000 + Math.floor(_fpRand(620) * 85000000),
usedJSHeapSize: 8000000 + Math.floor(_fpRand(621) * 42000000),
};
globalThis.Notification.permission = _fpRand(630) > 0.5 ? "granted" : "default";
// Hide internals (_*, obscura, Obscura). The set of keys is static at
// snapshot-build time, so we precompute it ONCE below (after this
// function definition) and reuse it on every page init. Was an
// Object.keys + filter on every navigation, ~5-40ms per page on
// SPAs that load 1000+ globals.
const toHide = globalThis.__obscura_hide_list || [];
for (let i = 0; i < toHide.length; i++) {
try { Object.defineProperty(globalThis, toHide[i], { enumerable: false }); } catch(e) {}
}
delete globalThis.__obscura_init;
};
// Snapshot-time pre-computation of the hide list. Bootstrap.js runs once
// during the V8 snapshot build (build.rs); this line captures the set of
// globals defined by bootstrap that we want to hide and stashes them
// for __obscura_init to consume on every subsequent page. The snapshot
// preserves the array as a regular global.
globalThis.__obscura_hide_list = Object.keys(globalThis).filter(k =>
k.startsWith('_') || k.includes('obscura') || k.includes('Obscura')
);