querySelector/querySelectorAll/matches/closest re-parsed the selector
string on every call. nodeDetails' SelectorPath fires dozens of these per
element while synthesizing a unique selector (often the same string twice
back-to-back), and page JS that queries in loops paid the same repeated parse.
Add a frame-lifetime cache (frame._selector_cache) keyed by the selector
string. The parsed AST borrows slices of its input, so on a miss the key is
duped into frame.arena and parsed against that owned copy; both share the
frame's lifetime and are bulk-freed on navigation. A 1024-entry cap bounds
growth from SelectorPath's one-off synthetic selectors, falling back to a
per-call arena parse beyond it. StyleManager rule parsing is left untouched
(parsed once per rebuild into its own arena).
The visibility predicate called getInlineStyleProperty -> getOrCreateStyle,
which always allocated a CSSStyleProperties + CSSStyleDeclaration and inserted
into frame._element_styles, even for elements with no style= attribute. Every
semantic-tree / interactiveElements walk checks visibility on every element, so
this was one wasted allocation per element per walk on the agent's hot path.
Only materialize the inline-style object when one already exists (JS-set styles)
or the element actually carries a style= attribute; otherwise return null
without allocating.
Updates the `script_skill` prompt to recommend and demonstrate parallel
page navigation using `Promise.all` and multiple `Page` instances,
rather than serial navigation on a single page.
Introduce `Registry.resetFrame` to selectively evict nodes owned by
the replaced page's frame. This prevents invalidating node IDs of
sibling pages during concurrent navigations.
Was reviewing https://github.com/lightpanda-io/browser/pull/2836 and realized
the StyleManager's getInlineStyleProperty could be optimized to avoid creating
the CSSStyleProperties in the case where there's no style attribute.
Frame.loadExternalStylesheet fetches an external <link rel=stylesheet>
synchronously, which registers a blocking request for the frame. While that
blocking request is active the DeferringLayer holds back the completion
callbacks of every other in-flight transfer for the frame, so they don't run
JS on the parser stack. The blocking-<script> path and the worker path flush
those deferred completions once their synchronous fetch returns, but the
external-stylesheet path did not. As a result a <script defer> that finished
loading during the stylesheet's blocking window stayed at complete == false:
the deferred-script queue never drained, so the deferred scripts never ran and
DOMContentLoaded / the load event / readyState -> "complete" never fired —
the document was stuck at readyState "loading" even though every request
completed with HTTP 200.
Flush the frame's deferring layer after the synchronous stylesheet fetch,
mirroring the other two synchronous-request call sites.
Closes#2842
getComputedStyle now reads inline values from the element's parsed
el.style through StyleManager.inlineStyleValue instead of re-parsing the
style= attribute, so computed and inline values share one source of
truth. Move the !important cascade precedence to the shared parse path
(applyParsedDeclaration) so el.style resolves duplicate declarations
correctly too.
Per spec, this should be accessible on the Document, not the HTMLDocument. I
ran into a site that was doing:
Object.getOwnPropertyDescriptor(Document.prototype, "cookie")
and that was failing
Changes `page.goto` to return a pending Promise instead of blocking
synchronously. Introduces a driver loop in `Runtime` to tick the
browser and settle pending navigations. This allows parallel gotos
and routes tool calls to their respective frames.
Nothing major, but the feature that caught my eye was the addition of a
threadpool for DNS resolution, rather than a thread-per-resolution (1). I've
enabled it.
(1) 39036c9021