Page teardown resets the identity-map Globals, which cancels their
pending weak callbacks — the only place fc_identity_pool nodes were
freed. Any object still referenced at page close leaked one Identity
per navigation until the connection closed. Destroy them in
FinalizerCallback.deinit instead; releaseRef unlinks itself when it
fires, so everything still linked there has a cancelled callback.
std.crypto.random's default backend mmaps a thread-local 528-byte state
page on first use and never unmaps it — there is no thread-exit hook.
With one detached thread per CDP connection (Server.handleConnection),
that leaks one resident page per connection (uuidv4 in
Page.getOrCreateOrigin touches it), ~4KB/conn of unbounded RSS growth.
Route every std.crypto.random call to the getrandom syscall instead.
.crypto_always_getrandom = true,
Idea here is to skip re-parsing that happen for each connection; we already use BoringSSL, so we can take more advantage of it by directly mutating cert store of `SSL_CTX`.
On for the main document parsing should a leading BOM be stripped. When setting
innerHTML, it should be preserved (and becomes a text node).
Fixes react hydration issue with theverge.com
Per review feedback: public querySelector doesn't guarantee reuse, so the cache
must be bounded regardless of the SelectorPath bypass. Move it off the Frame
(where it was wiped every navigation and unbounded) onto the Browser, since a
parsed selector references no Frame/Context — entries are now shared across the
browser's pages and survive navigation.
Selector.Cache is a StringArrayHashMap with per-entry arenas (so eviction can
free an individual entry, which a shared arena can't) and FIFO eviction of the
oldest entry past a capacity. The SelectorPath *Uncached bypass stays.
Replace the arbitrary 1024-entry cap with an explicit split: the public
querySelector/querySelectorAll/matches/closest entry points cache (page scripts,
waitForSelector — selectors that recur), while SelectorPath's synthesized one-off
candidates use new *Uncached variants that parse into a transient arena. The
cache now only ever holds genuinely-reused selectors, so it needs no size bound.
Lightpanda installs no SIGSEGV handler, so a segfault (or the abort() in
the panic path) falls through to the kernel and writes a core dump. When
many instances run under a shared core_pattern crash reporter -- e.g. a
containerized crawl fleet -- those cores become pure storage/alert noise,
and a browser core can capture the contents of arbitrary pages.
Crashes are already reported via telemetry, so this adds an opt-in
LIGHTPANDA_DISABLE_CORE_DUMP env var (mirroring LIGHTPANDA_DISABLE_TELEMETRY)
that zeroes the soft RLIMIT_CORE at startup. Default behavior is unchanged.
Remove sid. Include iid in every message. Booleans true/false => 1/0. Constant
string values => single letter. Example:
["8800df58-a5d5-4ca5-9a06-d7691a2a3780","H","fetch",0,"macos","aarch64","1.0.0-dev.7609+88b1bc671"]
["8800df58-a5d5-4ca5-9a06-d7691a2a3780","R"]
["8800df58-a5d5-4ca5-9a06-d7691a2a3780","N",1,"P"]
H => Header
R => Run
N => Navigate
B => Buffer Overflow / dropped
L => LLM
Navigate context are
P => Page
O => Open (popup)
I => Iframe
First, this adds 1 small piece of data to the navigate event: whether the
navigate was a page, frame or popup.
It also adds a session id, but as far as I'm concerned, this isn't "new"
information, or any new tracking/insight into users. Between the iid and the
"run" event, a "session" was always trackable. By giving it an explicit value,
we can shrink the size of all other messes.
This change reduces the telemetry payload by ~70% (despite the extra nav field).
I'm hoping this might remove a reason some people would consider turning it off.
It hits a /v2/ endpoint. The changes:
1 - A header is the first message in a session and contains all of the static
data, as well as a session id
2 - Every event is encoded as an array, [$SID, "event-type, params...]
```
{"sid":"92e98210a141f497","iid":"$UUID","mode":"fetch","os":"macos","arch":"aarch64","version":"$VERSION","proxy":false}
["92e98210a141f497","run"]
["92e98210a141f497","nav",false,"page"]
```
(the driver=cdp field was removed from nav, because it was always cdp).
Some notes for the server:
1 - The server can tell a header from an event based on the first character.
2 - The SID is 8 bytes, enough to be unique, but not globally unique. The
iid + sid + time window is how a events for the same SID can be grouped.
3 - A valid event is always an array of 2+ items, index 0 = SID, index 1 = type
Although positional data isn't expressive, it's still extendable.
The 4 events:
run, no parameters (mostly just used to flush the header now)
["sid", "run"]
// nav, tls, page/popup/iframe
["sid","nav",true,"page"]
// bof, # of lost telemetry events
["sid","bof",42]
// llm, provider, model (nullable)
["sid","llm","anthropic","claude"]
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.