dispatchStartupCommand hard-codes "TID-STARTUP" as the frame ID in
Page.getFrameTree. When a driver connects via connectOverCDP after a
real page already exists, subsequent lifecycle events (frameNavigated)
use the actual page frame ID. The driver's frame tracking was
initialized with "TID-STARTUP", causing a mismatch that hangs
navigation.
Check for an existing browser context with a target_id in
dispatchStartupCommand. If present, return the real frame ID and URL.
Fall back to "TID-STARTUP" only when no page exists yet.
Fixes#1800
This contribution was developed with AI assistance (Claude Code + Codex).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
detachFromTarget and setAutoAttach(false) both null bc.session_id
without notifying the client. Per the CDP spec, detaching a session
must fire a Target.detachedFromTarget event so the driver stops
sending messages on the stale session ID.
Capture the session_id before nulling it and fire the event in both
code paths. Add tests covering the event emission and the no-session
edge case.
Fixes#1819
This contribution was developed with AI assistance (Claude Code + Codex).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Small tweaks to https://github.com/lightpanda-io/browser/pull/1896
Improve the wait ergonomics with an Option with default parameter. Revert
page pointer logic to original (don't think that change was necessary).
History: We started with 1 context and thus only had 1 identity map. Frames
were added, and we tried to stick with 1 identity map per context. That didn't
work - it breaks cross-frame scripting. We introduced "Origin" so that all
frames on the same origin share the same objects. That almost worked, by
the v8::Inspector isn't bound by a Context's SecurityToken. So we tried 1 global
identity map. But that doesn't work. CDP IsolateWorlds do, in fact, need some
isolation. They need new v8::Objects created in their context, even if the
object already exists in the main context.
In the end, you end up with something like this: A page (and all its frames)
needs 1 view of the data. And each IsolateWorld needs it own view. This commit
introduces a js.Identity which is referenced by the context. The Session has a
js.Identity (used by all pages), and each IsolateWorld has its own js.Identity.
As a bonus, the arena pool memory-leak detection has been moved out of the
session and into the ArenaPool. This means _all_ arena pool access is audited
(in debug mode). This seems superfluous, but it's actually necessary since
IsolateWorlds (which now own their own identity) can outlive the Page so there's
no clear place to "check" for leaks - except on ArenaPool deinit.
js.Origin was added to allow frames on the same origin to share our zig<->js
maps / identity. It assumes that scripts on different origins will never be
allowed (by v8) to access the same zig instances.
If two different origins DID access the same zig instance, we'd have a few
different problems. First, while the mapping would exist in Origin1's
identity_map, when the zig instance was returned to a script in Origin2, it
would not be found in Origin2's identity_map, and thus create a new v8::Object.
Thus we'd end up with 2 v8::Objects for the same Zig instance. This is
potentially not the end of the world, but not great either as any zig-native
data _would_ be shared (it's the same instance after all), but js-native data
wouldn't.
The real problem this introduces though is with Finalizers. A weak reference
that falls out of scope in Origin1 will get cleaned up, even though it's still
referenced from Origin2.
Now, under normal circumstances, this isn't an issue; v8 _does_ ensure that
cross-origin access isn't allowed (because we set a SecurityToken on the
v8::Context). But it seems like the v8::Inspector isn't bound by these
restrictions and can happily access and share objects across origin.
The simplest solution I can come up with is to move the mapping from the Origin
to the Session. This does mean that objects might live longer than they have to.
When all references to an origin go out of scope, we can do some cleanup. Not
so when the Session owns this data. But really, how often are iframes on
different origins being created and deleted within the lifetime of a page?
When Origins were first introduces, the Session got burdened with having to
manage multiple lifecycles:
1 - The page-surviving data (e.g. history)
2 - The root page lifecycle (e.g. page_arena, queuedNavigation)
3 - The origin lookup
This commit doesn't change that, but it makes the session responsible for
_a lot_ more of the root page lifecycle (#2 above).
I lied. js.Origin still exists, but it's a shell of its former self. It only
exists to store the SecurityToken name that is re-used for every context with
the same origin.
The v8 namespace leaks into Session.
MutationObserver and IntersectionObserver are now back to using weak/strong refs
which was one of the failing cases before this change.
Removes manual git flags from CI and build scripts.
Versioning is now automatically derived from git and build.zig.zon.
With this PR, we follow https://semver.org/
Logic:
1. Read the version from build.zig.zon
2. If it doesn't have a `.pre` field (i.e. dev/alpha/beta) it will use that
3. Otherwise it will get the info from git: hash and number of commits since last `.0` version
4. Then build the version: `0.3.0-dev.1493+0896edc3`
Note that, since the latest stable version is `0.2.6`.
The convention is to use `0.3.0-dev`, as:
- `0.2.6` < `0.3.0.dev` < `0.3.0`
In the first iteration of this, we kept an ArrayList of all rules with
visibility properties. Why bother evaluating if a rule's selector matches an
element if that rule doesn't have any meanignful (i.e. visibility) properties?
This commit enhances that approach by bucketing the rules. Given the following
selectors:
.hidden {....}
.footer > .small {...}
We can store the rules based on their right-most selector. So, given an element
we can do:
if (getId(el)) |id| {
const rules = id_lookup.get(id) orelse continue;
// check rules
}
if (getClasses(el)) |classes| {
for (classes) |c| {
const rules = class_lookup(c) orelse continue;
// chck rules
}
}
...
On an amazon product page, the total list of visibility-related rules was ~230.
Now, scanning 230 rules for a match isn't _aweful_, but remember that this has
to be done up the ancestor tree AND, for Amazon, this is called over 20K times.
This change requires that the StyleManager becomes more matching/parsing-aware
but a typical visibility check on that same Amazon product page only has to
check 2 rules (down from 230) and often has to check 0 rules.
Also, we now filter out a few more interactive-related pseudo-elements, e.g.
:hover. These aren't supported by the browser as a whole (i.e. they can _never_
match), so they can be filtered out early, when building the rules lookup.
We now pay attention to the type of event that causes the unhandled exception.
This allows us to trigger the window.rejectionhandled event when that is the
correct type. It also lets us no-op for other event types which should not
trigger rejectionhandled or unhandledrejection.
Fixes stackoverflow in github integration.