We had a basic DORect implementation (which probably covered more cases). This
commit extends the implementation to be more complete:
- Add DOMRectReadOnly (which DOMRect inherits from)
- Add StructuredClone support to DOMRect and DOMRectReadOnly
- Make DOMRect and DOMRectReadOnly usable from Worker
DOMRect can be on the hot path with IntersectionObserver, so we maintain a
value-based data object (DOMRect.Data) for internal use (things returned to js
always have to be heap allocated (and always were, we're just more explicit
about this part now)).
Rather than having a generic SVG type (which gets us past parsing, but even the
slightest JS usage is likely to fail), this adds concrete types for a number of
known SVG types. A lot of these are empty (but even that's enough to pass
something like `instanceof SVGAElement`), but a handful of the more important
accessors and methods are implemented (e.g. I ran into a site that made
extensive use of SVGSVGElement.getElementById).
Currently, we use the calling frame when mutating an element's attributes (e.g.
add/delete). This commit changes it to the element's owning frame. This should
fix both some errors and potential uaf.
Instruct the /save synthesis model to annotate the generated script with
short `//` intent comments — one per logical block (navigate, extract,
fan out, aggregate, return) describing what the block accomplishes, so a
later reader (human or agent) grasps the script at a glance.
The save synthesis prompt previously ended with "no commentary", which
discouraged code comments; reword it to forbid only prose/markdown fences
outside the code while explicitly asking for the `//` intent comments. The
same prompt backs the MCP `save` tool, so both surfaces benefit. Also add a
matching best-practice bullet + example to the agent's script-writing skill.
https://github.com/lightpanda-io/browser/pull/2901 introduced optimizations to
how attributes are stored and probed. One of those optimization breaks when
an attribute is probed across iframes.
This commit removes that specific optimization and reverts to a linear scan
comparing the strings (which is still faster after 2901 since it's an array and
not a linked list).
The docs promise triple-quoted values that span multiple lines, but the
REPL dispatched every physical line immediately, so the multi-line
/extract form died with 'unterminated quote' on its first line. A slash
command that leaves a '''...''' block open now keeps reading lines at a
continuation prompt until the block closes; Ctrl-D abandons it.
The one-shot --save flag bypassed save.parseCommand, so '--save test'
wrote a file literally named 'test' while the REPL's /save appended the
extension. Share one ensureJsExtension helper so the paths can't drift.
Adds a new command line argument `--watchdog-ms` which, when set, will terminate
any JS that appears to be hung.
When configured, a new thread is started. Workers heartbeat this thread to
signal activity (e.g. not stuck in a JS loop). However, workers also block for
their own reason (e.g. network polling), so they can signal the watchdog that
they are "entering a wait" and, when complete, that they are "existing a wait".
During such waits, the watchdog will not signal the isolate to terminate.
Obviously, it's important for workers to signal aliveness and whenever they plan
on doing a non-JS wait. So you could say we introduce safe points where the
watchdog (for that browser) is disabled. We could do the opposite: enable the
watchdog whenever we enter JS ("hey, I'm about to execute JS, monitor me). But
there are _a lot_ more place where this happens.
fetch() ignored RequestInit.redirect: both manual and error behaved
like follow. Thread a redirect mode through HttpClient.Request and the
webapi Request:
- manual: don't follow the 3xx; deliver it as the final response, which
Fetch turns into an opaque-redirect filtered response (status 0, type
opaqueredirect, redirected false, empty url/headers/body).
- error: reject the fetch promise with a TypeError.
Default stays .follow, so navigations, XHR and internal requests are
unaffected. Also exposes Request.prototype.redirect.
Fixes#2908
Largely about improving the named indexer storage["blah"] to support setting
and deletion. Also adds support for numeric indexes storage[4] by adding an
indexer (not named) and converting the integer -> string.
A number of other files had minor changes because namedIndexed now takes a
deleter, enumerator and query functions (or null) so all the other namedIndexed
now need to pass 3 other nulls. I assume these should be set on at least some
of the other consumers.
Currently, we give v8 8MB more memory. This new commit increases that to 64MB
up to a total of 256MB (at which point, no extra space is given).
The commit also instructs v8 to eventually go back to its original heap limit
(and not continuing using the higher limit it was given by nearHeapLimit). This
is important since the isolate can be relatively long lived compared to a script
that caused memory pressure. This feature is currently commented and depends on:
https://github.com/lightpanda-io/zig-v8-fork/pull/187
Adds deduping to attribute names. This has two benefits. First, it results in
fewer dupes/allocations. Second, it allows finding an attribute name by a single
pointer comparison.
Say we need to create an attribute attr1=val1 (from the parser or JS, doesn't
matter). We:
1 - Lookup "attr1" in String.intern, it doesn't exist
2 - We lookup or create "attr1" in Frame._attribute_names
Whether the name was found in String.intern, found in Frame._attribute_names or
created in Frame._attribute_names, any attribute name "attr1" always points to
the same value.
Now say we want to lookup the value "attr1". We _could_ iterate the attribute
list and do a string comparison on each attribute. OR, we could apply the same
canonicalization to that input as we did when building the list. We can do this
via a read-don't-create API. If it doesn't find it, than that attribute was
never canonicalized and thus, cannot exist (early exit!). If it IS found, then
we now have the pointer for "attr1" that all attributes with "attr1" use, so
we can just do a pointer comparison.
We also replace the name: []const u8, value: []const u8 with a packed
representation (where len: u32, instead of u64) meaning every attribute entry
saves an extra 8 bytes (on top of the 16 bytes saved by the previous commit)
An element's attributes are currently stored as a ?*Attribute.List which is an
intrusive doubly linkedlist of Attribute.Entry. This has two small benefits:
1 - Elements with no attributes only grow by 8 bytes
2 - Attribute list mutation (additions/deletion) are linkedlist cheap
This commit embeds an ArrayList-like structure directly in Element. The impact
being:
1 - Elements with no attributes now grow by 16 bytes (+8)
2 - Elements with 1+ attribute shrink from 32 -> 16 bytes (-16)
3 - Mutations are more expensive
4 - Fewer indirections (and better cache locality)
5 - Much fewer allocations (4 attributes go from 5 allocations to 1)
6 - Every Attribute shrinks by 16 bytes (no need for next/prev link)
While looking at a few popular sites (amazon product, redit post, ...), the
majority of elements have 1+ element and most attributes are never accessed in
JS and, when they are, reads are more frequent than writes (in fact, even
internally to support other WebAPIs, reads far outweigh writes).
There's virtually no real world site where this shouldn't reduce memory usage
by hundreds of KB and also improve performance (in a way that isn't significant
to the overall page loading though).
We typically persist a v8::Value by calling persist() or temp() on our v8
wrappers. Both persist() and temp() create a v8::Global, but persist() tracks
it in the page's globals ArrayList while temp() tracks it in the page's temps
HashMap.
Globals are only freed when the page is torn down, hence a simple ArrayList is
all we need. Temps can be freed at any point, hence HashMap is used. (Temps are
also freed on teardown, but they can be freed before that too).
This commit removes Temp (js.Value.Temp, js.Promise.Temp, js.Function.Temp)
and all the mechinery around them (e.g. pseudo Global generic). There are now
only Globals and any global can be freed at any time (while still being tracked
in the page in order to be able to free any un-freed on page teardown).
Many files were touched because .Temp -> .Global and .temp() -> .persist().
This commit is based off the indexeddb branch because indexeddb introduced
"bare" globals (globals which aren't tracked by the Page). Building this new
model to include "Bare" globals avoids a messy merge later.