fix(dom): add missing DOM/JS shims for modern frameworks
Closes#163.
Adds missing DOM and JS shims required by modern frameworks (Svelte 5,
Vue, Lit) and consent-management SDKs to execute without errors:
- Define the `Storage` global constructor to resolve `ReferenceError`s
and fix `Storage.prototype` access.
- Apply standard ChildNode methods (`before`, `after`, `replaceWith`,
`remove`) to `CharacterData.prototype`. This prevents `TypeError`s when
frameworks call these on Text or Comment nodes during hydration.
- Update `Element.prototype.isConnected` to perform a genuine parent-walk
rather than returning a hardcoded `true`.
- Fix `Element.prototype.remove` to properly clean up the internal ID
index so removed elements are no longer queryable.
- Remove outdated no-op stubs on the `Element` class that were shadowing
the proper polyfill implementations.
- about: URL scheme now handled in navigate_single instead of
hitting validate_url and failing with "Forbidden URL scheme"
- DOM.getBoxModel calls getBoundingClientRect via JS instead of
returning hardcoded pixel coords
- DOM.getContentQuads added (was missing, needed by Playwright click)
- globalThis._wrap exposed so CDP eval code can look up nodes by id
- globalThis.self = globalThis for webpack/bundler worker detection
document.readyState was hardcoded to "complete" so scripts checking
readyState === 'loading' always took the wrong branch. Fixed by using a
JS global (__documentReadyState__) that Rust sets to "loading" before
any scripts execute, "interactive" before DOMContentLoaded fires, and
"complete" after load.
document.addEventListener and document.dispatchEvent on Document were
no-ops, so DOMContentLoaded listeners registered by inline scripts were
never called. Added proper listener storage and dispatch on Document.
Adds document.getElementsByName() (was missing entirely).
Page.reload is now implemented (re-navigates to the current URL),
resolving "Unknown Page method: reload" errors from Playwright.
data: URL navigation now works, fixing Playwright's goto("data:text/html,...")
pattern that previously errored with "invalid format".
Page.navigate logic extracted into do_navigate() so reload can reuse it.
Fixes#137, #146 (SPA bootstrap lifecycle ordering).
document.elementFromPoint(x, y) was undefined, which caused Google
Publisher Tag, viewability libs and any code doing hit-testing to
throw on bootstrap and trigger retry loops in the surrounding
framework. This is one of the throwing APIs feeding into the wedge
described in #62.
Without a layout engine we can't return the actual topmost element,
but a non-throwing stub is strictly better than "undefined" for the
headless-scraping use case:
- coords inside the viewport return document.body (or
document.documentElement as a fallback) so callers that just want
a hit target stop throwing
- coords outside the viewport (negative, > innerWidth/innerHeight,
NaN/Infinity) return null per spec
- elementsFromPoint mirrors the same shape but returns a one- or
zero-length array
- ShadowRoot.prototype gets the same treatment for completeness
Five new unit tests in obscura-js cover function presence, in-viewport
hit, out-of-viewport coords, non-numeric input, and the array shape.
Generated by Ora Studio
Vibe coded by ousamabenyounes
Co-authored-by: Ora Studio <[email protected]>
document.createEvent('CustomEvent') was returning a generic Event,
so the legacy DOM Level 2 pattern
var e = document.createEvent('CustomEvent');
e.initCustomEvent('foo', true, false, payload);
threw "TypeError: e.initCustomEvent is not a function" because the
returned object was an Event, not a CustomEvent — and CustomEvent
didn't define initCustomEvent anyway. The Starbucks China bundle
(and a long tail of other CDN bundles using the same shim) depended
on this and aborted bootstrap.
Two changes in crates/obscura-js/js/bootstrap.js:
1. Add CustomEvent.prototype.initCustomEvent(type, bubbles, cancelable,
detail) matching the spec.
2. Rewrite Document.prototype.createEvent so it dispatches to the
right class based on the type string ('CustomEvent', 'MouseEvent',
'MouseEvents', 'KeyboardEvent', 'FocusEvent', 'InputEvent',
'UIEvent', 'WheelEvent', 'PointerEvent', 'ErrorEvent',
'PopStateEvent', 'AnimationEvent', 'TransitionEvent', and
case-insensitive 'Events' aliases). The HTMLDocument variant on
the second Document class delegates to the canonical impl.
Spec note: createEvent returns the event with an empty type until
init*Event() is called — matched exactly.
Four new unit tests cover initCustomEvent presence and field setters,
class dispatch for CustomEvent / MouseEvent / MouseEvents / KeyboardEvent,
and the unknown-type fallback to Event.
Generated by Ora Studio
Vibe coded by ousamabenyounes
Co-authored-by: Ora Studio <[email protected]>
jQuery DataTables references the standard CharacterData interface which
was undefined in obscura's DOM environment, causing ReferenceError and
empty page renders.
- Add CharacterData class extending Node with data/length/substringData/
appendData/insertData/deleteData/replaceData
- Make Text and Comment extend CharacterData instead of Node
- Update _wrap() to create proper Text/Comment instances by nodeType
- Add create_comment_node op for Document.createComment()
- Fix set_text_content to handle Comment node mutations
Fixes#72
Co-authored-by: KuaaMU <[email protected]>
Fixes#1: replace unwrap() panics in run_multi_worker_serve with match on the Result.
Fixes#2: route /json round-robin via worker_port instead of hardcoded port+1.
Fixes#3: return 502 Bad Gateway when a worker is unreachable instead of silently dropping the connection.
Fixes#5: thread --stealth through start_with_options -> CdpContext -> BrowserContext::with_options so the flag actually has an effect in serve mode (was let _ before). Log message now reflects the actual capability based on the stealth cargo feature being compiled in.
Fixes#15: try/catch the parentNode assignment in the attachShadow polyfill so it doesn't throw a TypeError against the read-only Node.parentNode getter (Cloudflare Turnstile triggers this path).