fix(browser,cdp): correct document lifecycle ordering and add missing DOM methods

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).
This commit is contained in:
SGavrl committed 2026-05-18 09:50:23 +00:00
1 parent e371e942fc
commit 913e6a4865
5 files changed
+249 -154

No files matched your search

+19 -2
View File
@@ -791,7 +791,7 @@ class Document extends Node {
get compatMode() { return "CSS1Compat"; }
get characterSet() { return "UTF-8"; }
get contentType() { return "text/html"; }
get readyState() { return "complete"; }
get readyState() { return globalThis.__documentReadyState__ || 'complete'; }
get hidden() { return false; }
get visibilityState() { return "visible"; }
getElementById(id) { return _wrapEl(+_dom("get_element_by_id", id)); }
@@ -805,6 +805,7 @@ class Document extends Node {
}
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') {
@@ -853,7 +854,23 @@ class Document extends Node {
return new Cls('');
}
createRange() { return { setStart(){}, setEnd(){}, collapse(){}, selectNodeContents(){}, cloneContents(){ return document.createDocumentFragment(); } }; }
addEventListener(type, fn, opts) {} removeEventListener() {} dispatchEvent() { return true; }
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 = {