Commit Graph

8472 Commits

Author SHA1 Message Date
Karl Seguin
24e8e731fa Merge pull request #2906 from lightpanda-io/v8-memory-limit-headroom
mem, v8: Increase the extra room we give v8 @ nearHeapLimit
2026-07-10 07:16:26 +08:00
Karl Seguin
a3ee14c69c default watchdog-ms to 30000 2026-07-09 21:03:56 +08:00
Karl Seguin
6f641b1250 feature: add watchdog for JS hangs
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.
2026-07-09 20:59:58 +08:00
Adrià Arrufat
62f7c79ad0 fetch: honor redirect option (manual and error modes)
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
2026-07-09 14:20:40 +02:00
Karl Seguin
ce2c880fa1 webapi: Improve storage WebAPI
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.
2026-07-09 18:54:14 +08:00
Karl Seguin
e7796a0d6a mem, v8: Increase the extra room we give v8 @ nearHeapLimit
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
2026-07-09 16:51:32 +08:00
Karl Seguin
272301a7b2 Merge pull request #2905 from lightpanda-io/cdp-deviceScaleFactor-1
cdp, minor: don't warn unimplemented when deviceScaleFactor == 1
2026-07-09 16:31:09 +08:00
Karl Seguin
fabbc958fa cdp, minor: don't warn unimplemented when deviceScaleFactor == 1 2026-07-09 16:15:55 +08:00
Pierre Tachoire
03134062b9 Merge pull request #2903 from lightpanda-io/cookie-public-suffix-list-check
cookie: Allow cookie on the bare domain matching public suffix list
2026-07-09 08:12:33 +02:00
Karl Seguin
1363240ba5 cookie: Allow cookie on the bare domain matching public suffix list
1 - Update the public suffix list
2 - Fix the restriction on cookies domains for the public suffix list, allowing
    the bare domain to be matched

Fixes https://github.com/lightpanda-io/browser/issues/2900
2026-07-09 11:54:14 +08:00
Karl Seguin
70493ce35b Merge pull request #2816 from lightpanda-io/nikneym/check-update
initial implementation for `update` command
2026-07-09 10:06:26 +08:00
Adrià Arrufat
3ee9cd5365 Merge pull request #2902 from lightpanda-io/agent-regression-slack
ci: publish agent-regression results to slack, run nightly
2026-07-08 16:32:19 +02:00
Adrià Arrufat
f5ddbb49fe ci: publish agent-regression results to slack, run nightly
Mirror the wpt/e2e-integration pattern: tee the suite output to a log,
strip the ANSI colors, and upload it to the #ci-agent channel
(AGENT_SLACK_CHANNEL_ID) via the CI slack bot, with the final
pass/fail summary as the comment. The post also happens when the
suite fails, without masking its exit code (explicit bash pipefail).

Add a nightly cron trigger (03:37, offset from wpt's 02:21) so the
suite actually runs unattended; workflow_dispatch stays for manual
runs with a model override.
2026-07-08 16:22:41 +02:00
Karl Seguin
4a57e3c402 perf, mem: canonicalize attribute names
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)
2026-07-08 19:14:41 +08:00
Karl Seguin
f32606a27b mem: Store Element's attributes as an array, not a linked list
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).
2026-07-08 18:42:55 +08:00
Halil Durak
73558b380e Updater.inform: fix broken function signature 2026-07-08 13:07:37 +03:00
Halil Durak
9488a74b50 Updater.inform: always receive information from remote 2026-07-08 13:06:58 +03:00
Halil Durak
847c28657d Updater.inform: flush no matter the result 2026-07-08 13:06:12 +03:00
Halil Durak
78aa2a12aa Updater: remove arena, stream received bytes directly to stdout 2026-07-08 13:00:31 +03:00
Halil Durak
7a4e21cc82 Updater: gracefully handle known status codes 2026-07-08 13:00:31 +03:00
Halil Durak
3b73a5e0e7 Updater: free X509_STORE in deinitializer 2026-07-08 13:00:31 +03:00
Halil Durak
affd1c279a Updater: refactor Updator for new version check endpoint 2026-07-08 13:00:30 +03:00
Halil Durak
cf00ef100c Network: mark createX509Store as pub 2026-07-08 13:00:30 +03:00
Halil Durak
8673fbb489 lp.update -> lp.checkVersion + honor --check in version 2026-07-08 13:00:30 +03:00
Halil Durak
d8184dc462 move update command to version --check 2026-07-08 13:00:30 +03:00
Halil Durak
be8628f235 update: add help command entry for update 2026-07-08 13:00:29 +03:00
Halil Durak
94239e123b Updater: finalize inform 2026-07-08 13:00:29 +03:00
Halil Durak
32fefeaa7a update: remove nightly channel check 2026-07-08 13:00:29 +03:00
Halil Durak
4c62337cfa Updater: prefer JSON structures specific to update channel 2026-07-08 13:00:29 +03:00
Halil Durak
6acb58eff4 Updater: continue on implementing Updater.inform 2026-07-08 13:00:28 +03:00
Halil Durak
bbd53e4d83 Updater: prefer arbitrary JSON union for parsing versions.json 2026-07-08 13:00:28 +03:00
Halil Durak
7bea3319e8 Updater: update versions.json URL 2026-07-08 13:00:28 +03:00
Halil Durak
6919400c42 add update command and make it runnable in main 2026-07-08 13:00:28 +03:00
Halil Durak
a716279e30 Updater: introduce Updater
Updater is a simple interface that does web requests for version comparison without needing `App` instance.
2026-07-08 13:00:27 +03:00
Halil Durak
03c51052b0 Config: add update command 2026-07-08 13:00:27 +03:00
Halil Durak
b97ca18dcb Network: mark globalInit and globalDeinit as pub 2026-07-08 13:00:24 +03:00
Karl Seguin
2a7b399d90 refactor: Rework how v8::Globals are managed
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.
2026-07-08 10:48:18 +08:00
Karl Seguin
958bea53de cleanup rebase 2026-07-08 10:38:04 +08:00
Karl Seguin
15d6b16268 Revert "refactor: Rework how v8::Globals are managed"
This reverts commit 61cc7bb949.
2026-07-08 10:24:57 +08:00
Karl Seguin
65ae4a8bc0 refactor: Rework how v8::Globals are managed
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.
2026-07-08 10:24:21 +08:00
Karl Seguin
6b3ee7f65d idb: on abort, rollback created stores/indexes
Also, better query key validation
2026-07-08 10:23:09 +08:00
Karl Seguin
2961264052 idb: add compound keys 2026-07-08 10:23:07 +08:00
Karl Seguin
b1453f12a3 idb: expose request.source, and improve cursor state check 2026-07-08 10:22:53 +08:00
Karl Seguin
28c36a8d0b idb: reject operations on deleted datastore 2026-07-08 10:22:32 +08:00
Karl Seguin
300d783b4d idb: abort transaction on callback exception 2026-07-08 10:22:11 +08:00
Karl Seguin
22f2b15842 idb: handle reentrant upgradeneeded callbacks
Add handling for upgradeneeded callbacks enqueuing more work.
2026-07-08 10:21:49 +08:00
Karl Seguin
0bb6291ad0 idb: improve indexeddb memory model
Rather than having everything tied to the page's memory (arena, factory), most
things are now tied to an IDBTransaction and its arena. The IDBTransaction is
reference counted and finalized with v8. The lifecycle is relatively complicated
compared to anything else we have, which is a concern, but using the page arena
seems like a dealbreaker to me.

Any "child" created for the transaction (e.g. IDBRequest) has its v8 acquireRef
and releaseRef forwarded to the Transaction. This should make v8's usage safe.
However, in addition to this, Zig itself must take a RC whenever a drain is
scheduled AND whenever the transaction is parked in the engine. And these
things, especially on cleanup, can be a little messy.

The _awful_ cursor allocations have been improved by a local re-used ArrayLists
for the key/primary key/value. So rather than accumulating _every_ allocation
we now only capture the peak.

One final memory-related area this commit addresses is the lifetime difference
between Transactions (Page) and Engine (Session). This is problematic because
the Engine can reference the Transaction. On js.Context deinit, the engine is
notified and all related Transactions are canceled/removed. This is...unusual
in our design. We have other similar cases that use a list on the frame/GWS to
track resources to cleanup. But, the Engine already _has_ to have this list so
rather than book-keeping in Engine AND Frame AND WGS, only Engine has a list at
the cost of js.Context having to notify it on teardown.
2026-07-08 10:21:49 +08:00
Karl Seguin
73b5386d26 idb: allow re-entrant and interleaved transactions
Because of the single-connection nature of our Engine (in-memory SQLite), the
IDB Engine now keeps a list of "gated" transactions, processing one at time.
When one transaction completes, waiting transactions are signaled so that they
can processes their queue.

This also adds a double-queue so that any requests that comes in while
processing requests goes into a new queue and is only drained on the next tick.

Note: this introduces a UAF, where Engine lives on Session and referneces
IDBTransactions which are tied to the page. The next bit work on IDB is the
memory re-work, so I'm punting that there.
2026-07-08 10:21:49 +08:00
Karl Seguin
ba74450d1b fix broken zig fmt 2026-07-08 10:21:47 +08:00
Karl Seguin
cb8d6d9270 idb: handle requeue index order and abort in settle 2026-07-08 10:21:29 +08:00