Now that #2694 centralized the default viewport, store an optional override
on the Page and have every viewport consumer read it through
Page.getViewport(): window.innerWidth/innerHeight, screen.width/height/
availWidth, visualViewport.width/height, Page.getLayoutMetrics, the
IntersectionObserver root rect, and matchMedia / the StyleManager media
cascade. This keeps the viewport consistent across all of them instead of
the split-brain a single-property override would create.
Emulation.setDeviceMetricsOverride writes the override (a 0 width or height
keeps that dimension, per the CDP default); clearDeviceMetricsOverride resets
to the default. Not-yet-emulated params (deviceScaleFactor, mobile, scale,
screenWidth/Height) are accepted but logged as not_implemented.
Aimed at improving correctness of the innerText getter and setter. Driven by a
number of WPT tests, largely in html/dom/elements/the-innertext-and-outertext-properties/
Meant as a replacement for (1). Though, unlike that PR, this commit does not
consider CSS at all (todo in a follow up PR). The change just to get correct
space handling is big enough on its own. The short version is that, as we
traverse the children, we need to keep various state in order to (for example)
avoid double whitespace. Table cells and rows need their own special handling
(tabs and newlines inserted between items), pre needs to preserve whitespace,
etc.
This also adds outerText which, for the getter, is identical to innerText and
for the setter, can reuse Element.replaceWith, except for special whitespace
handling at its edges.
(1) https://github.com/lightpanda-io/browser/pull/2741
Playwright sends Browser.setDownloadBehavior at the browser level during
connection setup, before any target/context exists. Returning an error there
aborted the whole connection, failing every playwright demo-runner test.
Treat the no-context case as a success no-op, matching the prior behavior.
Browser.setDownloadBehavior was a noop, so Lightpanda had no file-download
path. A response with Content-Disposition: attachment is now streamed to disk
under downloadPath, and Page.downloadWillBegin / Browser.downloadProgress are
emitted when eventsEnabled.
Fixes#2701
Follow up to https://github.com/lightpanda-io/browser/pull/2742
Where 2742 dealt with window-reuse across navigation, this commit addresses
window lifetime after a .close() (so, specifically for popups). I was hoping for
a fancier solution which would let us maintain eager release with safety, but
instead opted for the much simpler "window lifetime is tied to the page".
There's a couple reasons for this. It _is_ more consistent with how everything
else works (Workers, and iframes), but the real reason is that we lack the
infrastructure to do this safely/cleanly.
This is purely mechanical and was driven almost entirely by Claude. The goal is
simply to break Frame.zig into small / more cohesive components. So instead of
having ~10 observer-specific methods on Frame, they now live in Frame.observers.
In general, our code always takes *Frame as the last parameter (a consequence
that this is the easiest / less ambiguous way to auto-inject a Frame into a
webapi). But, for these, I kept it as the first parameter because it's still
the "receiver" even though the dot syntax isn't applicable.
Address review: permissions set via Browser.grantPermissions / setPermission
now live on the Browser instead of the active Page, so they persist across
page navigations (previously a navigation dropped them) and match how Chrome
scopes permissions to the browser context. navigator.permissions.query() reads
the browser-level state. Also log not_implemented for the origin and
browserContextId params, which are accepted but not yet honored.
These three Browser methods were noops, so a CDP client could not change
what navigator.permissions.query() reports - it always returned "prompt".
Store permission state on the active Page (keyed by permission name) and
have navigator.permissions.query() read it back. grantPermissions sets each
listed permission to "granted", setPermission sets a single permission to an
explicit state, and resetPermissions clears them (query falls back to
"prompt"). State is scoped to the Page and resets on navigation.
Adds a CDP round-trip test exercising all three methods.
The schema walker returned raw attribute values, so `attr: "href"` /
`attr: "src"` yielded whatever the markup contained — usually a relative
URL. That is inconsistent with the links and structuredData tools (which
resolve against the document base) and with the DOM .href/.src
properties, and it makes the common extract-then-goto pattern fail on
sites with relative links, since goto needs an absolute URL.
Resolve href/src via `new URL(raw, document.baseURI)`, falling back to
the raw value on parse failure. Other attributes pass through unchanged;
absolute URLs are unaffected.
`--log-filter-scopes` previously took a comma-separated list of scopes to
suppress. Extend it with explicit include/exclude directives applied
left-to-right:
-X filter out scope X
X alias for -X (backward compatible)
+X filter in scope X
all target every scope
This makes "filter everything except one scope" expressible, e.g.
`-all,+cdp` suppresses all logs except `cdp`. Bare scope names keep their
old meaning, so existing invocations like `http,unknown_prop` are
unaffected.
Internally, the suppressed-scope list is replaced by a resolved per-scope
`scope_enabled` boolean array built via `log.resolveFilterScopes()`, so the
hot-path `enabled()` check is a single array index. Filtering remains
Debug-only, as before. `testing.LogFilter` is adapted to the new
representation; all existing call sites are unchanged.
Adds a unit test for resolveFilterScopes (default, backward-compat,
-all,+cdp, ordering) and updates the help text.
The agent already supports these providers (via zenai), but the agent
command's help text still listed only anthropic/openai/gemini/huggingface/
ollama. Bring --help in sync:
- Add vercel, mistral, and llama_cpp to the --provider allowed values
- Add AI_GATEWAY_API_KEY and MISTRAL_API_KEY to the auto-detect and
env-key lists
- Document llama.cpp as a keyless local server (base http://localhost:8080/v1)
- Note that a provider can set its own effort default (Mistral -> none)
This realigns --help with the docs at lightpanda.io/docs/usage/agent.
Updates the zenai dependency and resolves the agent's effort based on
the configured provider's default. Also dynamically updates the active
effort when switching providers.
window.postMessage and MessagePort.postMessage stashed the message
js.Value.Temp verbatim and handed it straight into the MessageEvent, with
no structuredClone on the path. The receiver got the literal object the
sender posted, so a mutation on either side was visible to the other; worse,
posting to a different same-origin frame exposed the source realm's object
directly into the target realm instead of minting a fresh one there. The
spec requires StructuredSerialize in postMessage and deserialize in the
destination realm.
The legacy testing.async(async() => {...}); was simple to use, but it only
worked in simple cases. Any microtasks which wasn't immediately resolve would
still fail.
The newer testing.async code is more robust and can handle any async scenario.
It's a bit more verbose and it's limited to 1 per <script type=module>. This
commit migrates all remaining legacy usages to the new flow. While the simpler
one was nice, having both approaches is confusing (to humans and ai).