"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; } }; // HTML "ASCII whitespace": U+0009 TAB, U+000A LF, U+000C FF, U+000D CR, U+0020 SPACE. // Class token splitting (classList, getElementsByClassName) uses exactly this set. // JS \s is wider (U+000B, U+00A0, U+2028, etc.), so it must not be used here. const _ASCII_WS = /[ \t\n\f\r]+/; function _splitAsciiWhitespace(s) { // WebIDL DOMString coercion: null -> "null", undefined -> "undefined". return String(s).split(_ASCII_WS).filter(Boolean); } // Shared getElementsByClassName: split the argument into an ordered set of // tokens on ASCII whitespace, then return descendants (in tree order) whose // class attribute contains every token, as an HTMLCollection (so namedItem and // named access work on the result). `root` must expose querySelectorAll. function _getElementsByClassName(root, classNames) { const tokens = _splitAsciiWhitespace(classNames); if (tokens.length === 0) return HTMLCollection._from([]); // Fast path: a single CSS-identifier token goes straight to the native // selector engine (the common case). Only multi-token sets or exotic class // names (NBSP, leading digits, etc.) fall back to the O(n) JS scan below. if (tokens.length === 1 && /^[A-Za-z_-][\w-]*$/.test(tokens[0])) { return HTMLCollection._from(root.querySelectorAll("." + tokens[0])); } const all = root.querySelectorAll("*"); const matched = []; for (let i = 0; i < all.length; i++) { const el = all[i]; const elTokens = _splitAsciiWhitespace(el.getAttribute ? (el.getAttribute("class") || "") : ""); let ok = true; for (let t = 0; t < tokens.length; t++) { if (elTokens.indexOf(tokens[t]) < 0) { ok = false; break; } } if (ok) matched.push(el); } return HTMLCollection._from(matched); } 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); } } // DOMTokenList backs class/rel/sandbox/etc. attribute reflection. It parses the // associated content attribute as an ordered set of tokens and writes changes // straight back, so reads and writes stay live with the element. A Proxy is // layered on top so numeric indexing (list[0]) hits item(). class DOMTokenList { constructor(el, attr, supportedTokens) { // Non-enumerable so the element <-> token-list cycle is not visible to // enumeration/serialization (JSON.stringify(classList) would otherwise // throw "circular structure"). Object.defineProperty(this, "_el", { value: el, writable: true, enumerable: false }); Object.defineProperty(this, "_attr", { value: attr, writable: true, enumerable: false }); Object.defineProperty(this, "_supported", { value: supportedTokens || null, writable: true, enumerable: false }); return new Proxy(this, { get(t, k, r) { if (typeof k === "string" && /^\d+$/.test(k)) return t.item(+k); return Reflect.get(t, k, r); }, has(t, k) { if (typeof k === "string" && /^\d+$/.test(k)) return +k < t.length; return Reflect.has(t, k); }, }); } get [Symbol.toStringTag]() { return "DOMTokenList"; } _tokens() { const v = this._el.getAttribute(this._attr); if (!v) return []; const seen = new Set(); const out = []; for (const tok of v.split(/[ \t\n\f\r]+/)) { if (tok && !seen.has(tok)) { seen.add(tok); out.push(tok); } } return out; } _write(tokens) { this._el.setAttribute(this._attr, tokens.join(" ")); } get length() { return this._tokens().length; } get value() { return this._el.getAttribute(this._attr) || ""; } set value(v) { this._el.setAttribute(this._attr, String(v)); } item(i) { const t = this._tokens(); return (i >= 0 && i < t.length) ? t[i] : null; } contains(token) { return this._tokens().includes(String(token)); } add(...tokens) { const t = this._tokens(); for (const raw of tokens) { const tok = String(raw); if (tok === "") throw new DOMException("The token provided must not be empty.", "SyntaxError"); if (/[ \t\n\f\r]/.test(tok)) throw new DOMException("The token provided contains HTML space characters, which are not valid in tokens.", "InvalidCharacterError"); if (!t.includes(tok)) t.push(tok); } this._write(t); } remove(...tokens) { let t = this._tokens(); for (const raw of tokens) { const tok = String(raw); if (tok === "") throw new DOMException("The token provided must not be empty.", "SyntaxError"); if (/[ \t\n\f\r]/.test(tok)) throw new DOMException("The token provided contains HTML space characters, which are not valid in tokens.", "InvalidCharacterError"); t = t.filter((x) => x !== tok); } this._write(t); } toggle(token, force) { const tok = String(token); if (tok === "") throw new DOMException("The token provided must not be empty.", "SyntaxError"); if (/[ \t\n\f\r]/.test(tok)) throw new DOMException("The token provided contains HTML space characters, which are not valid in tokens.", "InvalidCharacterError"); const t = this._tokens(); const has = t.includes(tok); if (has) { if (force === true) return true; this._write(t.filter((x) => x !== tok)); return false; } if (force === false) return false; t.push(tok); this._write(t); return true; } replace(token, newToken) { const a = String(token), b = String(newToken); if (a === "" || b === "") throw new DOMException("The token provided must not be empty.", "SyntaxError"); if (/[ \t\n\f\r]/.test(a) || /[ \t\n\f\r]/.test(b)) throw new DOMException("The token provided contains HTML space characters, which are not valid in tokens.", "InvalidCharacterError"); const t = this._tokens(); const i = t.indexOf(a); if (i === -1) return false; if (t.includes(b) && b !== a) { t.splice(i, 1); } else { t[i] = b; } this._write(t); return true; } supports(token) { if (!this._supported) throw new TypeError("DOMTokenList has no supported tokens."); return this._supported.includes(String(token).toLowerCase()); } forEach(cb, thisArg) { const t = this._tokens(); for (let i = 0; i < t.length; i++) cb.call(thisArg, t[i], i, this); } *values() { yield* this._tokens(); } *keys() { const t = this._tokens(); for (let i = 0; i < t.length; i++) yield i; } *entries() { const t = this._tokens(); for (let i = 0; i < t.length; i++) yield [i, t[i]]; } [Symbol.iterator]() { return this._tokens()[Symbol.iterator](); } toString() { return this.value; } } // CDATASection: a Text-derived node (nodeType 4) used only in XML documents. // Extends Text so data/length/textContent/childNodes reuse the working text // node machinery; only the type-identifying getters differ. class CDATASection extends Text { get nodeName() { return "#cdata-section"; } get nodeType() { return 4; } get nodeValue() { return this.data; } set nodeValue(v) { this.data = v; } cloneNode() { return new CDATASection(+_dom("create_text_node", this.data)); } } // ProcessingInstruction: nodeType 7, nodeName === target. Extends CharacterData // and carries a separate target. Backed by a text node so data/nodeValue/ // textContent/length work without native PI support. class ProcessingInstruction extends CharacterData { constructor(nid, target) { super(nid); this._target = target; } get target() { return this._target; } get nodeName() { return this._target; } get nodeType() { return 7; } get nodeValue() { return this.data; } set nodeValue(v) { this.data = v; } cloneNode() { return new ProcessingInstruction(+_dom("create_text_node", this.data), this._target); } } // HTMLHyperlinkElementUtils helpers (the / URL-decomposition members). // The element's href attribute is parsed against the document base URL via the // WHATWG url op; component getters read it, setters rewrite the href attribute. function _anchorBase() { return _domParse("document_url") || "about:blank"; } function _elemHrefURL(el) { const raw = el.getAttribute('href'); if (raw === null || raw === undefined) return null; return _urlParseOp(raw, _anchorBase()); } function _setElemHrefPart(el, part, value) { const u = _elemHrefURL(el); if (!u) return; const c = _urlSetOp(u.href, part, value); if (c) el.setAttribute('href', c.href); } class Element extends Node { constructor(nid) { super(nid); this._style = _styleProxy(new CSSStyleDeclaration()); } get tagName() { return _domParse("tag_name", this._nid) || ""; } get localName() { // tagName is an op call and the tag never changes, so cache the lowercased // localName. This keeps the new / href getters (which read // localName) and every other localName consumer off the op path. if (this._lname !== undefined) return this._lname; const ln = (this.tagName || "").toLowerCase(); if (ln) this._lname = ln; return ln; } 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() { // createElementNS records the requested namespace on _ns; an empty string // maps to the null namespace per spec. Elements made via createElement (or // parsed) have no _ns: default to XHTML, except which is SVG. if (this._ns !== undefined) return this._ns === "" ? null : this._ns; if (this.localName === "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 HTMLCollection._from(ids.map(_wrapEl).filter(Boolean)); } get content() { // .content reflects // the content attribute (read/write per spec). Next.js' next/head // iterates 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() { if (!this._classList) this._classList = new DOMTokenList(this, "class"); return this._classList; } get relList() { const ns = this.namespaceURI, ln = this.localName; const ok = (ns === "http://www.w3.org/2000/svg" && ln === "a") || (ns === "http://www.w3.org/1999/xhtml" && (ln === "a" || ln === "area" || ln === "link")); if (!ok) return undefined; if (!this._relList) this._relList = new DOMTokenList(this, "rel"); return this._relList; } get sandbox() { if (this.namespaceURI !== "http://www.w3.org/1999/xhtml" || this.localName !== "iframe") return undefined; if (!this._sandboxList) this._sandboxList = new DOMTokenList(this, "sandbox"); return this._sandboxList; } get sizes() { if (this.namespaceURI !== "http://www.w3.org/1999/xhtml" || this.localName !== "link") return undefined; if (!this._sizesList) this._sizesList = new DOMTokenList(this, "sizes"); return this._sizesList; } get htmlFor() { if (this.namespaceURI !== "http://www.w3.org/1999/xhtml") return undefined; const ln = this.localName; if (ln === "output") { if (!this._htmlForList) this._htmlForList = new DOMTokenList(this, "for"); return this._htmlForList; } if (ln === "label") return this.getAttribute("for") || ""; return undefined; } set htmlFor(v) { if (this.namespaceURI === "http://www.w3.org/1999/xhtml" && this.localName === "label") { this.setAttribute("for", String(v)); } } 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) { const popoverPrev = (n === "popover" || (typeof n === "string" && n.length === 7 && n.toLowerCase() === "popover")) ? this.popover : undefined; _dom("set_attribute", this._nid, n + "\0" + String(v)); if (popoverPrev !== undefined) this._popoverTypeMaybeChanged(popoverPrev); if (globalThis.__mutationObservers?.length) globalThis.__notifyMutation('attributes', this._nid, [], [], n); } setAttributeNS(ns, n, v) { this.setAttribute(n, v); } // Simplified NS handling removeAttribute(n) { const popoverPrev = (n === "popover" || (typeof n === "string" && n.length === 7 && n.toLowerCase() === "popover")) ? this.popover : undefined; _dom("remove_attribute", this._nid, n); if (popoverPrev !== undefined) this._popoverTypeMaybeChanged(popoverPrev); } 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) || []; return _nodeList(ids.map(_wrapEl).filter(Boolean)); } getElementsByTagName(t) { return HTMLCollection._from(this.querySelectorAll(t)); } getElementsByClassName(c) { return _getElementsByClassName(this, c); } matches(s) { // :popover-open is a JS-observable popover state, not understood by the // native selector engine. Handle it here (and strip it from compound // selectors so the rest can still be matched natively). if (typeof s === "string" && s.indexOf(":popover-open") !== -1) { if (this._popoverState !== "showing") return false; const rest = s.replace(/:popover-open/g, "").trim(); if (rest === "") return true; return this.matches(rest); } 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; } // --- Popover API (HTML "popover") --------------------------------------- // Read the popover content attribute case-insensitively. The HTML parser // lowercases attribute names, but runtime setAttribute("PoPoVeR", ...) // preserves case, and the IDL reflection matches the name ASCII-case- // insensitively. Returns the raw stored string, or null if absent. _popoverAttrValue() { const v = this.getAttribute("popover"); if (v !== null) return v; const names = _domParse("attribute_names", this._nid) || []; for (let i = 0; i < names.length; i++) { if (names[i].toLowerCase() === "popover") return this.getAttribute(names[i]); } return null; } // The reflected (effective) popover type: null (No Popover), "auto", // "hint", or "manual". Empty string maps to "auto"; any non-keyword value // (invalid) maps to "manual". get popover() { const raw = this._popoverAttrValue(); if (raw === null) return null; const v = String(raw).toLowerCase(); if (v === "auto" || v === "hint" || v === "manual") return v; if (v === "") return "auto"; return "manual"; } set popover(value) { if (value === null || value === undefined) { this._popoverRemoveAttr(); return; } this.setAttribute("popover", String(value)); } _popoverRemoveAttr() { if (this.getAttribute("popover") !== null) { this.removeAttribute("popover"); return; } const names = _domParse("attribute_names", this._nid) || []; for (let i = 0; i < names.length; i++) { if (names[i].toLowerCase() === "popover") { this.removeAttribute(names[i]); return; } } } // "check popover validity". expectedToBeShowing is true for hide, false for // show. Throws NotSupportedError when there is no valid popover type, and // InvalidStateError when the element is not connected; returns false (no // throw) when the current state does not match expectedToBeShowing. _checkPopoverValidity(expectedToBeShowing) { if (this.popover === null) throw new DOMException("Not supported on elements that don't have a valid value for the popover attribute", "NotSupportedError"); const showing = this._popoverState === "showing"; if ((expectedToBeShowing && !showing) || (!expectedToBeShowing && showing)) return false; if (!this.isConnected) throw new DOMException("Invalid on popover elements which aren't connected", "InvalidStateError"); return true; } showPopover() { if (!this._checkPopoverValidity(/*expectedToBeShowing*/false)) return; const beforeEvent = new ToggleEvent("beforetoggle", { cancelable: true, oldState: "closed", newState: "open" }); if (!this.dispatchEvent(beforeEvent)) return; // The beforetoggle handler may have changed our type or shown us; re-check. if (!this._checkPopoverValidity(/*expectedToBeShowing*/false)) return; this._popoverState = "showing"; const target = this; setTimeout(() => { try { target.dispatchEvent(new ToggleEvent("toggle", { oldState: "closed", newState: "open" })); } catch (e) {} }, 0); } hidePopover() { if (!this._checkPopoverValidity(/*expectedToBeShowing*/true)) return; this.dispatchEvent(new ToggleEvent("beforetoggle", { oldState: "open", newState: "closed" })); this._popoverState = "hidden"; const target = this; setTimeout(() => { try { target.dispatchEvent(new ToggleEvent("toggle", { oldState: "open", newState: "closed" })); } catch (e) {} }, 0); } togglePopover(force) { let options = force; if (options && typeof options === "object") force = options.force; const showing = this._popoverState === "showing"; if (showing && (force === undefined || force === null || force === false)) { this.hidePopover(); } else if (force === undefined || force === null || force === true) { this.showPopover(); } return this._popoverState === "showing"; } // Called from setAttribute/removeAttribute/IDL setter when the popover // attribute may have changed. If the effective type changed while showing, // hide the popover (firing the hide events) per the HTML spec. _popoverTypeMaybeChanged(prevType) { const newType = this.popover; if (this._popoverState === "showing" && prevType !== newType) { // Hide directly. Do not call hidePopover(): it re-validates against the // popover attribute, which may now be removed (No Popover), and would // throw NotSupportedError. This mirrors the spec hide with throw=false. this.dispatchEvent(new ToggleEvent("beforetoggle", { oldState: "open", newState: "closed" })); this._popoverState = "hidden"; const target = this; setTimeout(() => { try { target.dispatchEvent(new ToggleEvent("toggle", { oldState: "open", newState: "closed" })); } catch (e) {} }, 0); } } 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); } // For /, href returns the resolved absolute URL (the spec behavior, // and what scrapers want). It uses op_url_resolve, which returns just the // resolved string, rather than the full-component op the decomposition // members use. Other elements reflect the raw attribute. get href() { const ln = this.localName; if (ln === 'a' || ln === 'area') { const raw = this.getAttribute('href'); if (raw === null) return ''; const r = _urlResolveOp(raw, _anchorBase()); return r !== null ? r : raw; } return this.getAttribute("href") || ""; } set href(v) { this.setAttribute("href", v); } // HTMLHyperlinkElementUtils URL-decomposition members, live on /. get protocol() { const u = (this.localName === 'a' || this.localName === 'area') ? _elemHrefURL(this) : null; return u ? u.protocol : ''; } set protocol(v) { if (this.localName === 'a' || this.localName === 'area') _setElemHrefPart(this, 'protocol', v); } get username() { const u = (this.localName === 'a' || this.localName === 'area') ? _elemHrefURL(this) : null; return u ? u.username : ''; } set username(v) { if (this.localName === 'a' || this.localName === 'area') _setElemHrefPart(this, 'username', v); } get password() { const u = (this.localName === 'a' || this.localName === 'area') ? _elemHrefURL(this) : null; return u ? u.password : ''; } set password(v) { if (this.localName === 'a' || this.localName === 'area') _setElemHrefPart(this, 'password', v); } get host() { const u = (this.localName === 'a' || this.localName === 'area') ? _elemHrefURL(this) : null; return u ? u.host : ''; } set host(v) { if (this.localName === 'a' || this.localName === 'area') _setElemHrefPart(this, 'host', v); } get hostname() { const u = (this.localName === 'a' || this.localName === 'area') ? _elemHrefURL(this) : null; return u ? u.hostname : ''; } set hostname(v) { if (this.localName === 'a' || this.localName === 'area') _setElemHrefPart(this, 'hostname', v); } get port() { const u = (this.localName === 'a' || this.localName === 'area') ? _elemHrefURL(this) : null; return u ? u.port : ''; } set port(v) { if (this.localName === 'a' || this.localName === 'area') _setElemHrefPart(this, 'port', v); } get pathname() { const u = (this.localName === 'a' || this.localName === 'area') ? _elemHrefURL(this) : null; return u ? u.pathname : ''; } set pathname(v) { if (this.localName === 'a' || this.localName === 'area') _setElemHrefPart(this, 'pathname', v); } get search() { const u = (this.localName === 'a' || this.localName === 'area') ? _elemHrefURL(this) : null; return u ? u.search : ''; } set search(v) { if (this.localName === 'a' || this.localName === 'area') _setElemHrefPart(this, 'search', v); } get hash() { const u = (this.localName === 'a' || this.localName === 'area') ? _elemHrefURL(this) : null; return u ? u.hash : ''; } set hash(v) { if (this.localName === 'a' || this.localName === 'area') _setElemHrefPart(this, 'hash', v); } get origin() { const u = (this.localName === 'a' || this.localName === 'area') ? _elemHrefURL(this) : null; return u ? u.origin : ''; } 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('', 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('', 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('', '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 HTMLCollection._from(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 _convertNodes(nodes)) this.appendChild(n); } prepend(...nodes) { const ref = this.firstChild; for (const n of _convertNodes(nodes)) { if (ref) this.insertBefore(n, ref); else this.appendChild(n); } } replaceChildren(...nodes) { const converted = _convertNodes(nodes); let c; while ((c = this.firstChild)) this.removeChild(c); for (const n of converted) this.appendChild(n); } } // WHATWG "convert nodes into a node": a Node argument passes through, anything // else is stringified into a Text node, so e.g. append(null) inserts the text // "null" and append(undefined) inserts "undefined" per the (Node or DOMString) // union, rather than throwing. function _convertNodes(nodes) { const out = []; for (let i = 0; i < nodes.length; i++) { const n = nodes[i]; if (n && typeof n._nid === "number") out.push(n); else out.push(document.createTextNode(String(n))); } return out; } 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() { // An explicit type set by DOMParser/createDocument wins. if (this._contentType) return this._contentType; // `new Document()` (the WHATWG constructor, no backing node id) creates an // XML document, so createCDATASection/etc. must not throw. Live documents // wrapped from the tree carry a real nid and fall through to URL-derived. if (this._nid === undefined || this._nid === null) return "application/xml"; const url = this.URL || ""; // data: URLs carry their MIME type explicitly. const dm = /^data:([^,;]+)/i.exec(url); if (dm) { const mime = dm[1].toLowerCase(); if (mime === "application/xhtml+xml") return "application/xhtml+xml"; if (mime === "text/xml") return "text/xml"; if (mime === "application/xml" || mime.endsWith("+xml")) return "application/xml"; } if (/\.xhtml(?:[?#]|$)/i.test(url)) return "application/xhtml+xml"; if (/\.(?:xml|svg)(?:[?#]|$)/i.test(url)) return "application/xml"; 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 //