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.
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.
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.
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.
Previously, every operation would run synchronously, and resolve the value
on the next drain (schedule task run on the next tick).
With 1 connection per DB, this doesn't work with SQLite: you cannot have nested
transactions. Imagine:
add
begin
insert into
JS callback (success)
add
begin <-- nested
insert
This approach captures the requested operation and does all necessary validation
(since most validation errors are returned synchronously), but only executes
operations on drain. Hence, rather than IDBTransaction._requests being a
queue of result values to emit on drain, it is now a queue of operations to
execute and then emit.
This will also potentially make it easier to address serious memory
issues by better scoping the lifetime of things (particularly requests).
Initial WIP on IndexedDB WebAPI. Uses sqlite and a new `--indexdb_dir` config.
Defaults to :memory:.
The main things missing are the IDBCursor, IDBIndex and IDBKeyRange, along with
a bunch of smaller apis.
Also, every object is currently tied to the Page arena / factory. It's possible
that's how it will have to be, but, once more of the API lands, I will check
if we can scope these better.
actions.setChecked set the input state and then dispatched a trusted
click, but the click's activation behavior (EventManager's
ActivationState) toggles a checkbox on every click dispatch — undoing
the state just set — and its commit path fires input/change, which
setChecked then fired a second time. Checkboxes always landed in the
opposite of the requested state with doubled events; radios only
appeared to work because radio activation always checks. It survived
because existing tests asserted the tool's report string, never the
resulting DOM state.
setChecked is now a no-op when the state already matches, otherwise it
performs the click (reusing actions.click) and verifies the state
landed — erroring before the click for radio+false (a click can never
uncheck a radio, and the click has observable page side effects) and
after it when a listener preventDefault()ed the toggle.
Caught by the checked.js golden in the demo agent regression suite.
The Runtime test now asserts DOM state and idempotency instead of
trusting the report string.
The main addition in this commit is that we hook into the Isolate's
AddNearHeapLimitCallback callback and try to force the isolate to shutdown
rather than letting v8 hit an OOM which would take down the entire process.
In support of this, we now support a `--v8-max-heap-mb` command line option to
set an explicit heap limit. As a simple way to test this feature, load a
relatively heavy JS page with `--v8-max-heap-mb 1`.
There's also a `--v8-flags-unsafe` which is a mechanism to pass arbitrary
flags to v8 via its `SetFlagsFromString`. The parameter is called `unsafe`
because some [of the many] configurable flags could conflict with how the
snapshot is built and result in crashes. The snapshot creator also gains a
`--v8-flags-unsafe` flag, so advance users COULD create their snapshot and
run lightpanda with the same set of flags.
When an iframe has "loading=lazy" it no longer delays the parent's "load" event.
It's still loaded (subject to `--disable-subframes` parameter).
Many sites use loading=lazy for non-critical content (e.g. ads). On Chrome/
Firefox, these are only loaded when they're in the viewport, so sites usually
can't depend/rely on them being loaded.
Per spec, nested timers eventually (5-level deeps) enforce a minimum timeout
(4ms).
This commit also fixes 2 UAFs in module loading where a map entry would become
invalid through nested mutations.
All issues were encountered on https://111skin.com/.